🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

数据画布 zshunxinz/data-canvas-dsh

DeepSeek Harness
zshunxinz15 天前有提交已提供中文文档扫描:含敏感能力

把查询结果沉淀为可复用卡片并追溯每个数字来源

31.2/100
综合分
2
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/22 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add zshunxinz/data-canvas-dsh
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · tool
装得上吗
本站已在 dsh 0.2.1-rc.2 下真实安装成功
非静态推断
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 15 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件写入剪贴板(不计风险)读取剪贴板或密钥环境变量发起外部网络请求
查看扫描证据(共 78 处,展示前 72 条)
  • 发起网络请求
    client.js:511
    const response = await fetch(`${API}${path}`, {
  • 写入剪贴板
    client.js:3442
    await globalThis.navigator.clipboard.writeText(method)
  • 写入剪贴板
    client.js:3677
    try { await navigator.clipboard.writeText(request.ref.prompt); setChosen('已复制') }
  • 写入剪贴板
    client.js:3781
    try { await navigator.clipboard.writeText(usage.storage_path); setCopied(true) } catch { setCopied(false) }
  • 写入剪贴板
    client.js:4061
    await navigator.clipboard.writeText(prompt)
  • 写入剪贴板
    client.js:4756
    await navigator.clipboard.writeText(text)
  • 读取本地文件
    lib/store.js:89
    if (!existsSync(path)) return fallback
  • 读取本地文件
    lib/store.js:91
    return JSON.parse(readFileSync(path, 'utf8'))
  • 写入本地文件
    lib/store.js:99
    mkdirSync(dirname(path), { recursive: true })
  • 写入本地文件
    lib/store.js:101
    writeFileSync(tmp, JSON.stringify(data, null, 2), 'utf8')
  • 写入本地文件
    lib/store.js:103
    renameSync(tmp, path)
  • 删除本地文件
    lib/store.js:105
    try { unlinkSync(tmp) } catch { }
  • 读取本地文件
    lib/store.js:360
    const history = existsSync(path) ? readJson(path, { undo: [], redo: [] }) : { undo: [], redo: [] }
  • 读取本地文件
    lib/store.js:376
    if (!existsSync(path)) return { undo: [], redo: [] }
  • 读取本地文件
    lib/store.js:487
    if (existsSync(markerPath)) return null
  • 读取本地文件
    lib/store.js:537
    if (!existsSync(path)) {
  • 删除本地文件
    lib/store.js:574
    try { unlinkSync(path) } catch { }
  • 删除本地文件
    lib/store.js:810
    unlinkSync(join(this.templateDir, `${id}.json`))
  • 删除本地文件
    lib/store.js:978
    unlinkSync(path)
  • 读取本地文件
    lib/routes.js:38
    const PLUGIN_VERSION = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version
  • 读取本地文件
    lib/routes.js:44
    try { text = readFileSync(new URL('../CHANGELOG.md', import.meta.url), 'utf8') } catch { return [] }
  • 读取本地文件
    lib/trace-service.js:27
    function readRef (path) { try { return JSON.parse(readFileSync(path, 'utf8')) } catch (error) { if (error.code === 'ENOENT') return null; throw new Ca
  • 写入本地文件
    lib/trace-service.js:30
    try { writeFileSync(tmp, JSON.stringify(value), { flag: 'wx' }); renameSync(tmp, path) }
  • 写入本地文件
    lib/trace-service.js:30
    try { writeFileSync(tmp, JSON.stringify(value), { flag: 'wx' }); renameSync(tmp, path) }
  • 删除本地文件
    lib/trace-service.js:31
    finally { try { unlinkSync(tmp) } catch (error) { if (error.code !== 'ENOENT') throw error } }
  • 写入本地文件
    lib/trace-service.js:180
    mkdirSync(this.followups, { recursive: true }); writeRef(path, record)
  • 读取本地文件
    lib/reference-service.js:86
    if (!existsSync(this.usages)) return []
  • 读取本地文件
    lib/reference-service.js:88
    for (const name of readdirSync(this.usages)) {
  • 读取本地文件
    lib/reference-service.js:90
    for (const line of readFileSync(join(this.usages, name), 'utf8').split('\n')) {
  • 读取本地文件
    lib/execution-evidence.js:16
    if (existsSync(path)) return
  • 写入本地文件
    lib/execution-evidence.js:21
    mkdirSync(dir, { recursive: true })
  • 写入本地文件
    lib/execution-evidence.js:23
    writeFileSync(tmp, JSON.stringify(record), { flag: 'wx' }); renameSync(tmp, path)
  • 写入本地文件
    lib/execution-evidence.js:23
    writeFileSync(tmp, JSON.stringify(record), { flag: 'wx' }); renameSync(tmp, path)
  • 读取本地文件
    lib/execution-evidence.js:28
    if (!existsSync(path)) return null
  • 读取本地文件
    lib/execution-evidence.js:29
    return JSON.parse(readFileSync(path, 'utf8'))
  • 写入本地文件
    lib/example-data.js:19
    mkdirSync(root, { recursive: true })
  • 读取本地文件
    lib/example-data.js:21
    if (existsSync(marker)) return false
  • 读取本地文件
    lib/example-data.js:22
    if (!existsSync(EXAMPLE_FILE)) {
  • 写入本地文件
    lib/example-data.js:24
    writeFileSync(marker, new Date().toISOString(), 'utf8')
  • 读取本地文件
    lib/example-data.js:30
    const bytes = readFileSync(EXAMPLE_FILE)
  • 写入本地文件
    lib/example-data.js:39
    writeFileSync(marker, new Date().toISOString(), 'utf8')
  • 写入本地文件
    lib/data-store.js:37
    try { writeFileSync(tmp, text, { encoding: 'utf8', flag: 'wx' }); renameSync(tmp, path) }
  • 写入本地文件
    lib/data-store.js:37
    try { writeFileSync(tmp, text, { encoding: 'utf8', flag: 'wx' }); renameSync(tmp, path) }
  • 删除本地文件
    lib/data-store.js:38
    finally { try { unlinkSync(tmp) } catch (e) { if (e.code !== 'ENOENT') throw e } }
  • 读取本地文件
    lib/data-store.js:41
    try { return JSON.parse(readFileSync(path, 'utf8')) }
  • 写入本地文件
    lib/data-store.js:49
    mkdirSync(this.inputs, { recursive: true })
  • 读取本地文件
    lib/data-store.js:51
    if (!existsSync(path)) write(path, input)
  • 读取本地文件
    lib/data-store.js:62
    try { files = readdirSync(this.dir) } catch (error) { if (error.code === 'ENOENT') return []; throw error }
  • 写入本地文件
    lib/data-store.js:81
    mkdirSync(this.dir, { recursive: true }); write(join(this.dir, `${data.id}.json`), data)
  • 删除本地文件
    lib/data-store.js:98
    remove (id) { const data = this.get(id); unlinkSync(join(this.dir, `${validId(id)}.json`)); if (data.secret_ref) deleteSecret(this.root, data.secret_r
  • 写入本地文件
    lib/data-store.js:153
    mkdirSync(this.results, { recursive: true }); write(join(this.results, `${result.id}.json`), result)
  • 写入本地文件
    lib/data-store.js:183
    mkdirSync(this.dir, { recursive: true })
  • 读取本地文件
    lib/data-store.js:254
    try { files = readdirSync(this.results) } catch (error) { if (error.code === 'ENOENT') return []; throw error }
  • 读取本地文件
    lib/data-store.js:287
    try { files = readdirSync(this.results) } catch (error) { if (error.code === 'ENOENT') return []; throw error }
  • 读取密钥环境变量
    lib/secrets.js:20
    const raw = process.env.XCHARTS_SECRET_KEY
  • 读取本地文件
    lib/secrets.js:30
    if (existsSync(path)) {
  • 读取本地文件
    lib/secrets.js:31
    const key = readFileSync(path)
  • 写入本地文件
    lib/secrets.js:35
    mkdirSync(root, { recursive: true })
  • 写入本地文件
    lib/secrets.js:37
    writeFileSync(path, key, { mode: 0o600 })
  • 写入本地文件
    lib/secrets.js:39
    try { chmodSync(path, 0o600) } catch { }
  • 写入本地文件
    lib/secrets.js:59
    mkdirSync(secretsDir(root), { recursive: true })
  • 写入本地文件
    lib/secrets.js:60
    writeFileSync(recordPath(root, ref), JSON.stringify(record), { mode: 0o600 })
  • 读取本地文件
    lib/secrets.js:67
    try { record = JSON.parse(readFileSync(recordPath(root, ref), 'utf8')) } catch { throw new CanvasError('SECRET_NOT_FOUND', '凭据不存在或已被删除') }
  • 删除本地文件
    lib/secrets.js:83
    try { unlinkSync(recordPath(root, ref)); return true } catch { return false }
  • 读取本地文件
    lib/storage-maintenance.js:19
    const entries = readdirSync(path)
  • 读取本地文件
    lib/storage-maintenance.js:22
    try { const info = statSync(join(path, entry)); if (info.isFile()) { bytes += info.size; files++ } } catch { }
  • 读取本地文件
    lib/storage-maintenance.js:29
    try { return readdirSync(root).reduce((sum, entry) => { try { return sum + (statSync(join(root, entry)).isFile() ? 1 : 0) } catch { return sum } }, 0)
  • 读取本地文件
    lib/storage-maintenance.js:29
    try { return readdirSync(root).reduce((sum, entry) => { try { return sum + (statSync(join(root, entry)).isFile() ? 1 : 0) } catch { return sum } }, 0)
  • 读取本地文件
    lib/storage-maintenance.js:64
    if (!existsSync(path)) return { target, label: meta.label, deleted_files: 0, freed_bytes: 0 }
  • 删除本地文件递归
    lib/storage-maintenance.js:66
    rmSync(path, { recursive: true, force: true })
  • 写入本地文件
    lib/usage-log.js:11
    mkdirSync(dir, { recursive: true })
  • 写入本地文件
    lib/usage-log.js:12
    appendFileSync(join(dir, `${record.at.slice(0, 7)}.jsonl`), `${JSON.stringify(record)}\n`, 'utf8')
扫描于 2026/11/3(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-tools@deepseek-ai/schemastery
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

数据画布,把dsh分析数据的结果变成可复用的分析卡片:支持导入文件或连接数据库(只读),查询、看结果、落卡、在对话里引用、追溯每个数字的来源。

README

由 DeepSeek 最新模型翻译生成
data-canvas-dsh 把数据变成可复用的分析卡片:导入文件或连接数据库(只读),查询、看结果、落卡、在对话里引用、追溯每个数字的来源。 这是 DeepSeek Harness 的一个插件。它不要求你写代码,也不接管你的数据:文件与查询结果都保存在本机,数据库只读访问,密码不会离开这台电脑。 它适合谁 - 想在一个地方反复查看同一批分析结果,而不是每次重新问 AI; - 需要把「某个数字怎么来的」讲清楚(查询、数据源、卡片版本都能追溯); - 用聊天推进分析,但希望结果能沉淀成卡片、随时回来改。 装上之后,先看示例 安装并重启 DSH 后,打开侧边栏的「数据源」,列表里已经有一份: 零售订单(示例) — 1,200 行、19 列 不需要你准备任何数据。它包含 2025 年全年的订单:门店、渠道、商品类别、数量、单价、折扣、销售额、成本、利润、评分等,还带少量缺失值,用来展示解析提示。你可以直接查询它,也可以随时删除。 想用自己的数据:点「新增数据源」拖入 CSV/TSV/XLSX/Parquet,或连接 MySQL/PostgreSQL。 数据源与分析:默认示例数据 五分钟上手 1. 看一份数据 左侧选中「零售订单(示例)」,右侧就是数据预览;表结构栏会列出字段,点表名可展开字段,右键可看完整表结构或 CREATE TABLE 语法。
展开完整文档(README 剩余 1 节)
data-canvas-dsh

把数据变成可复用的分析卡片:导入文件或连接数据库(只读),查询、看结果、落卡、在对话里引用、追溯每个数字的来源。

这是 DeepSeek Harness 的一个插件。它不要求你写代码,也不接管你的数据:文件与查询结果都保存在本机,数据库只读访问,密码不会离开这台电脑。

它适合谁

- 想在一个地方反复查看同一批分析结果,而不是每次重新问 AI;
- 需要把「某个数字怎么来的」讲清楚(查询、数据源、卡片版本都能追溯);
- 用聊天推进分析,但希望结果能沉淀成卡片、随时回来改。

装上之后,先看示例

安装并重启 DSH 后,打开侧边栏的「数据源」,列表里已经有一份:

零售订单(示例) — 1,200 行、19 列

不需要你准备任何数据。它包含 2025 年全年的订单:门店、渠道、商品类别、数量、单价、折扣、销售额、成本、利润、评分等,还带少量缺失值,用来展示解析提示。你可以直接查询它,也可以随时删除。

想用自己的数据:点「新增数据源」拖入 CSV/TSV/XLSX/Parquet,或连接 MySQL/PostgreSQL。

数据源与分析:默认示例数据

五分钟上手

1. 看一份数据

左侧选中「零售订单(示例)」,右侧就是数据预览;表结构栏会列出字段,点表名可展开字段,右键可看完整表结构或 CREATE TABLE 语法。

2. 查一个问题

- 文件数据源(包括示例):点「配置查询」,选择「门店」作为分组、指标「销售额 · 求和」,执行;
- 数据库:在 SQL 框里写查询,Ctrl/Cmd + Enter 执行:

SELECT store, SUM(amount) AS amount
FROM orders
GROUP BY store
ORDER BY amount DESC

3. 把结果留下来

- 点「添加到画布」→ 结果变成一张卡片,在「分析画布」里拖拽布局;
- 点「在对话中使用」→ 把结果的引用发给 AI,它回答时会先读取这份结果;
- 卡片菜单里可以查看溯源:这张卡片来自哪次查询、哪个数据源、哪个版本。

4. 回到对话继续

在聊天里引用数据源或结果,AI 会基于真实数据回答;追问卡片时,它会沿着卡片版本继续,而不是重新猜。

分析画布

对话中的卡片与引用

功能一览

数据源

- 文件:CSV / TSV / XLSX / Parquet,导入时给出编码、分隔符、缺失值等解析提示,失败可调整设置重试;
- 数据库:MySQL / PostgreSQL,只读连接,支持测试连接、表结构浏览、按字段名找表;
- 查询:只读 SQL(单条、自动 LIMIT、15 秒超时、可取消)、常用 SQL、最近执行、格式化。

结果

- 表格:排序、列显隐、列宽拖拽、冻结首列、全屏、表内搜索、复制、导出 CSV、字段画像;
- NULL 与空字符串区分显示(NULL 是淡灰「—」);
- 结果可添加为画布卡片,或在对话里引用。

分析画布

- 网格布局、拖拽/缩放、对齐、撤销重做;
- 卡片数据版本与溯源、复算(结果口径变化会提示)。

数据管理

- 引用协议 xcharts:ds/...、xcharts:res/...、xcharts:card/...,AI 先解析引用再回答;
- 使用情况(哪些卡片/会话用过这份数据)、备份/恢复、存储占用与清理、插件内更新记录。

安装

前置:DeepSeek Harness(桌面或 dsh web),Node.js 22+。

方式一:从 GitHub 安装(当前推荐)

在 profile 的 package.json 里声明 git 依赖(固定到发布 tag,避免分支变动):

{
"dependencies": {
"data-canvas-dsh": "github:zshunxinz/data-canvas-dsh#v0.28.1"
}
}

并在 dsh.profile.bundles 中加入 data-canvas-dsh,然后重启 DSH。

仓库已提交构建产物(client.js、client.echarts.js),git 安装无需再跑构建;依赖(pg/mysql2/hyparquet)会随安装自动装上。

方式二:本地源码(开发/调试)

{
"dependencies": { "data-canvas-dsh": "link:<path-to-data-canvas-dsh>" }
}

并在 dsh.profile.bundles 中加入 data-canvas-dsh,然后重启 DSH。

npm 包暂未发布;发布后会补 npm install data-canvas-dsh 的方式。

宿主侧的路由与工具在启动时加载,因此安装或升级后需要重启 DSH;浏览器页面刷新即可。

安全与隐私

- 只读:数据库连接使用只读事务;SQL 守卫只放行单条查询,拒绝写操作、DDL、多语句;插件不提供数据编辑。
- 密码不出本机:凭据用 AES-256-GCM 加密保存在本地 secrets/;数据源列表、Agent 工具输出、日志、备份、导出文件中都没有密码。
- 主机打码:工具与溯源输出中显示 10.0.. 这样的形式。
- 数据不离开本机:插件不向外部服务上传数据;AI 只能通过你主动引用的内容看到数据。

建议连接数据库时使用只读账号——代码守卫是纵深防御,不能替代数据库权限。

兼容性

- DSH:开发与验证于 0.2.0-rc.2 系列;
- Node.js:22+;
- 数据库:MySQL 5.7+ / PostgreSQL 12+;
- 文件:CSV / TSV / XLSX / Parquet,单文件最多 50,000 行;
- 数据源结果最多保存 5,000 行。

常见问题

示例数据从哪来?可以删吗? 来自仓库的 examples/retail-orders.csv,首次运行时自动导入。可以随时删除,删掉后不会被重新加回来。想找回:删除 ~/.dsh/storages/xcharts-canvas/.example-source-imported 后重启,或手动导入该文件。

数据存在哪里? 默认路径 ~/.dsh/storages/xcharts-canvas/(画布、数据源、结果、凭据)。备份/导出在「设置」里操作,备份不包含密码。

为什么连不上数据库? 先用「测试连接」确认主机、端口、库名与账号;只读账号需要相应表的查询权限。错误提示会给出可操作建议。

能修改数据库里的数据吗? 不能,这是刻意的设计。插件只用于分析,写操作请用专门的数据库工具。

SQL 有没有限制? 单条语句、只读、自动加 LIMIT、15 秒超时;SELECT / WITH / EXPLAIN / SHOW / DESCRIBE 等可执行。

更多文档

- 使用说明 — 界面、功能与常见操作
- 架构 — 宿主/客户端、数据存储、API、引用协议
- 安全 — 只读、凭据、脱敏、威胁模型与限制
- 开发 — 构建、测试、代码组织与发布

开发

pnpm install
pnpm build:client # 拼接 client-src 为 client.js
pnpm test # 构建 + 全量测试
pnpm pack # 生成可安装的 tgz

示例数据可用 python scripts/make-example-data.py 重新生成(固定随机种子)。

许可与贡献

MIT。欢迎 issue 与 PR;提交前请跑 pnpm test。

依赖许可:pg、mysql2、hyparquet、csv-parse、exceljs 为 MIT,echarts 为 Apache-2.0。
预览分享卡片长什么样 ↓zshunxinz/data-canvas-dsh
分享 · DeepSeek Harness Hub
zshunxinz/data-canvas-dsh

把查询结果沉淀为可复用卡片并追溯每个数字来源

这是你的仓库?认领条目

回到顶部