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

视觉桥 toRolex/periscope

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

把图片转成文字描述,让纯文本 agent 看懂截图

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

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

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

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:仅索引
是什么[目录]—
dsh 原生插件 · vision
暂无时间戳·判据 →
装得上吗[实装验证]—
本站尚未做安装检查
数据截至 2026/09/27·判据 →
安全吗[自动扫描]—
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
数据截至 2026/11/03·判据 →
还在维护吗[仓库]—
更新放缓:最近一次提交在 80 天前
暂无时间戳·判据 →
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成

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

验证判据与等级说明 →

可信度证据

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

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

可执行外部命令读写/删除本地文件读取剪贴板或密钥环境变量发起外部网络请求
查看扫描证据(共 109 处,展示前 99 条)
  • 读取密钥环境变量
    dist/config/config.js:111
    const apiKey = process.env.PERISCOPE_API_KEY ?? merged.apiKey;
  • 发起网络请求
    dist/transport.js:6
    const res = await fetch(url, {
  • 读取密钥环境变量
    dist/core/describe.test.js:344
    const keyBefore = process.env.PERISCOPE_API_KEY;
  • 读取密钥环境变量
    dist/core/describe.test.js:346
    process.env.PERISCOPE_API_KEY = 'sk-env';
  • 读取密钥环境变量
    dist/core/describe.test.js:358
    delete process.env.PERISCOPE_API_KEY;
  • 读取密钥环境变量
    dist/core/describe.test.js:360
    process.env.PERISCOPE_API_KEY = keyBefore;
  • 发起网络请求
    dsh-plugin/dist/transport.js:6
    const res = await fetch(url, {
  • 发起网络请求
    dsh-plugin/src/transport.ts:24
    const res = await fetch(url, {
  • 写入本地文件
    src/cache.test.ts:37
    fs.utimesSync(imagePath, past, past);
  • 读取本地文件
    src/cache.test.ts:45
    const originalMtimeMs = fs.statSync(imagePath).mtimeMs;
  • 写入本地文件
    src/cache.test.ts:47
    fs.writeFileSync(imagePath, 'changed-size-content');
  • 写入本地文件
    src/cache.test.ts:48
    fs.utimesSync(imagePath, new Date(originalMtimeMs), new Date(originalMtimeMs));
  • 读取本地文件
    src/cache.test.ts:79
    assert.ok(fs.existsSync(path.join(cacheDir, `${key}.txt`)), '缓存条目应落盘');
  • 读取本地文件
    src/cache.ts:23
    stat = fs.statSync(resolved);
  • 读取本地文件
    src/cache.ts:39
    if (!fs.existsSync(filePath)) return undefined;
  • 读取本地文件
    src/cache.ts:40
    return fs.readFileSync(filePath).toString('utf8');
  • 写入本地文件
    src/cache.ts:45
    fs.mkdirSync(cacheDir, { recursive: true });
  • 写入本地文件
    src/cache.ts:46
    fs.writeFileSync(entryPath(cacheDir, key), value);
  • 写入本地文件
    src/cli/doctor.test.ts:24
    fs.writeFileSync(filePath, JSON.stringify(config, null, 2));
  • 写入本地文件
    src/cli/doctor.test.ts:35
    fs.writeFileSync(filePath, JSON.stringify(config, null, 2));
  • 写入本地文件
    src/cli/doctor.test.ts:42
    fs.mkdirSync(path.dirname(full), { recursive: true });
  • 写入本地文件
    src/cli/doctor.test.ts:43
    fs.writeFileSync(full, '// seed\n');
  • 写入本地文件
    src/cli/doctor.test.ts:63
    fs.mkdirSync(cacheDir, { recursive: true });
  • 写入本地文件
    src/cli/doctor.test.ts:64
    fs.writeFileSync(
  • 读取本地文件
    src/cli/doctor.test.ts:135
    assert.ok(!fs.existsSync(configPath), 'doctor 不应懒创建 config 文件');
  • 读取本地文件
    src/cli/doctor.ts:63
    const raw = fs.readFileSync(packageJsonPath).toString('utf8');
  • 读取本地文件
    src/cli/doctor.ts:92
    if (!fs.existsSync(configPath)) {
  • 读取本地文件
    src/cli/doctor.ts:106
    if (!fs.existsSync(configPath)) {
  • 读取本地文件
    src/cli/doctor.ts:110
    const cfg = JSON.parse(fs.readFileSync(configPath).toString('utf8')) as Record<
  • 读取本地文件
    src/cli/doctor.ts:168
    if (!fs.existsSync(path.join(distDir, rel))) {
  • 读取本地文件
    src/cli/doctor.ts:224
    schema = JSON.parse(fs.readFileSync(cachePath).toString('utf8')) as Record<string, unknown>;
  • 读取本地文件
    src/cli/init.test.ts:61
    return JSON.parse(fs.readFileSync(configPath).toString('utf8')) as Record<string, unknown>;
  • 读取本地文件
    src/cli/init.test.ts:76
    assert.ok(!fs.existsSync(configPath), '非 TTY 不应写出配置文件');
  • 读取本地文件
    src/cli/init.test.ts:200
    assert.ok(!fs.existsSync(configPath), 'baseUrl 留空不应写出配置');
  • 读取本地文件
    src/cli/init.test.ts:214
    assert.ok(!fs.existsSync(configPath), 'model 留空不应写出配置');
  • 写入本地文件
    src/cli/init.test.ts:256
    fs.writeFileSync(
  • 写入本地文件
    src/cli/init.test.ts:279
    fs.writeFileSync(configPath, originalContent);
  • 读取本地文件
    src/cli/init.test.ts:293
    const after = fs.readFileSync(configPath).toString('utf8');
  • 读取本地文件
    src/cli/init.test.ts:308
    assert.ok(fs.existsSync(configPath), `应写入 PERISCOPE_CONFIG 路径: ${configPath}`);
  • 读取本地文件
    src/cli/init.ts:227
    const exists = fs.existsSync(configPath);
  • 写入本地文件
    src/cli/init.ts:258
    fs.mkdirSync(path.dirname(configPath), { recursive: true });
  • 写入本地文件
    src/cli/init.ts:259
    fs.writeFileSync(configPath, JSON.stringify(next, null, 2) + '\n');
  • 写入本地文件
    src/cli/plugin-schema.test.ts:29
    fs.mkdirSync(dir, { recursive: true });
  • 写入本地文件
    src/cli/plugin-schema.test.ts:31
    fs.writeFileSync(cachePath, JSON.stringify(schema, null, 2));
  • 写入本地文件
    src/cli/plugin-schema.test.ts:131
    fs.utimesSync(cachePath, past, past);
  • 读取本地文件
    src/cli/plugin-schema.test.ts:167
    fs.readFileSync(pluginSchemaCachePath(dir)).toString('utf8'),
  • 写入本地文件
    src/cli/plugin-schema.test.ts:176
    fs.utimesSync(cachePath, past, past);
  • 读取本地文件
    src/cli/plugin-schema.test.ts:189
    const written = JSON.parse(fs.readFileSync(cachePath).toString('utf8')) as Record<
  • 读取本地文件
    src/cli/plugin-schema.ts:32
    return now - fs.statSync(cachePath).mtimeMs < ttlMs;
  • 读取本地文件
    src/cli/plugin-schema.ts:178
    const raw = fs.readFileSync(cachePath).toString('utf8');
  • 写入本地文件
    src/cli/plugin-schema.ts:186
    fs.mkdirSync(cacheDir, { recursive: true });
  • 写入本地文件
    src/cli/plugin-schema.ts:187
    fs.writeFileSync(cachePath, JSON.stringify(schema, null, 2));
  • 写入本地文件
    src/cli/scripts.test.ts:21
    fs.mkdirSync(cacheDir, { recursive: true });
  • 写入本地文件
    src/cli/scripts.test.ts:22
    fs.writeFileSync(
  • 读取本地文件
    src/cli/scripts.test.ts:91
    assert.ok(fs.existsSync(configPath), '配置文件应被懒创建');
  • 读取本地文件
    src/cli/scripts.test.ts:92
    const written = JSON.parse(fs.readFileSync(configPath).toString('utf8'));
  • 读取本地文件
    src/cli/scripts.test.ts:168
    assert.equal(fs.existsSync(srcDispatcher), false, 'src/cli/index.ts 应已删除');
  • 读取本地文件
    src/cli/scripts.test.ts:169
    assert.equal(fs.existsSync(distDispatcher), false, 'dist/cli/index.js 应已删除');
  • 读取本地文件
    src/cli/scripts.test.ts:230
    const originalContent = fs.readFileSync(configPath).toString('utf8');
  • 读取本地文件
    src/cli/scripts.test.ts:239
    const afterContent = fs.readFileSync(configPath).toString('utf8');
  • 写入本地文件
    src/cli/scripts.test.ts:248
    fs.writeFileSync(img2, Buffer.from('second-image-bytes', 'utf8'));
  • 读取本地文件
    src/config/config.test.ts:20
    assert.ok(fs.existsSync(configPath), '配置文件应被自动创建');
  • 读取本地文件
    src/config/config.test.ts:21
    const written = JSON.parse(fs.readFileSync(configPath).toString('utf8'));
  • 写入本地文件
    src/config/config.test.ts:84
    fs.writeFileSync(
  • 写入本地文件
    src/config/config.test.ts:96
    fs.writeFileSync(
  • 写入本地文件
    src/config/config.test.ts:108
    fs.writeFileSync(configPath, JSON.stringify(DEFAULT_CONFIG));
  • 写入本地文件
    src/config/config.test.ts:117
    fs.writeFileSync(
  • 读取本地文件
    src/config/config.ts:93
    if (fs.existsSync(configPath)) {
  • 读取本地文件
    src/config/config.ts:94
    const raw = fs.readFileSync(configPath).toString('utf8');
  • 写入本地文件
    src/config/config.ts:97
    fs.mkdirSync(path.dirname(configPath), { recursive: true });
  • 写入本地文件
    src/config/config.ts:98
    fs.writeFileSync(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2) + '\n');
  • 读取密钥环境变量
    src/config/config.ts:108
    const apiKey = process.env.PERISCOPE_API_KEY ?? merged.apiKey;
  • 写入本地文件
    src/core/describe.cache.test.ts:64
    fs.utimesSync(imagePath, past, past);
  • 写入本地文件
    src/core/describe.cache.test.ts:77
    fs.writeFileSync(imagePath, 'changed-content');
  • 读取密钥环境变量
    src/core/describe.test.ts:402
    const keyBefore = process.env.PERISCOPE_API_KEY;
  • 读取密钥环境变量
    src/core/describe.test.ts:404
    process.env.PERISCOPE_API_KEY = 'sk-env';
  • 读取密钥环境变量
    src/core/describe.test.ts:412
    if (keyBefore === undefined) delete process.env.PERISCOPE_API_KEY;
  • 读取密钥环境变量
    src/core/describe.test.ts:413
    else process.env.PERISCOPE_API_KEY = keyBefore;
  • 读取本地文件
    src/core/describe.ts:40
    data = fs.readFileSync(resolved);
  • 读取本地文件
    src/hook/index.ts:117
    return fs.readFileSync(0, 'utf8');
  • 读取本地文件
    src/plugin/index.test.ts:35
    return fs.readFileSync(filePath).toString('utf8');
  • 执行外部命令来自配置/变量
    src/plugin/index.test.ts:56
    const child = spawn(process.execPath, args, { env });
  • 读取本地文件
    src/plugin/index.test.ts:87
    assert.ok(fs.existsSync(PLUGIN_MANIFEST), 'plugin.json 必须存在');
  • 读取本地文件
    src/plugin/index.test.ts:93
    assert.ok(fs.existsSync(HOOKS_FILE), 'hooks/hooks.json 必须存在');
  • 读取本地文件
    src/plugin/index.test.ts:120
    assert.ok(fs.existsSync(SKILL_FILE), 'skills/describe-image/SKILL.md 必须存在');
  • 读取本地文件
    src/plugin/index.test.ts:148
    fs.existsSync(path.join(REPO_ROOT, '.codex-plugin')),
  • 读取本地文件
    src/plugin/index.test.ts:206
    assert.ok(fs.existsSync(cliEntry), '编译产物 dist/cli/describe.js 应存在');
  • 读取本地文件
    src/plugin/manifest.test.ts:24
    return fs.readFileSync(filePath).toString('utf8');
  • 读取本地文件
    src/plugin/manifest.test.ts:33
    fs.existsSync(AGENT_PLUGINS_MANIFEST),
  • 读取本地文件
    src/plugin/manifest.test.ts:81
    fs.existsSync(CLAUDE_PLUGIN_MANIFEST),
  • 读取本地文件
    src/plugin/manifest.test.ts:89
    assert.ok(fs.existsSync(HOOKS_FILE), 'hooks/hooks.json 必须保留(Claude Code 体验不变)');
  • 读取本地文件
    src/readme.test.ts:11
    assert.ok(fs.existsSync(README_PATH), `README 应存在于仓库根: ${README_PATH}`);
  • 读取本地文件
    src/readme.test.ts:12
    return fs.readFileSync(README_PATH).toString('utf8');
  • 读取本地文件
    src/set-up.test.ts:11
    assert.ok(fs.existsSync(SETUP_PATH), `set-up skill 应存在: ${SETUP_PATH}`);
  • 读取本地文件
    src/set-up.test.ts:12
    return fs.readFileSync(SETUP_PATH).toString('utf8');
  • 写入本地文件
    src/testing/fixtures.ts:27
    fs.writeFileSync(filePath, Buffer.from(PNG_1PX_BASE64, 'base64'));
  • 写入本地文件
    src/testing/fixtures.ts:38
    fs.writeFileSync(filePath, JSON.stringify(config, null, 2));
  • 执行外部命令来自配置/变量
    src/testing/hook.ts:13
    const child = spawn(process.execPath, [entry], { env });
  • 发起网络请求
    src/transport.ts:20
    const res = await fetch(url, {
扫描于 2026/11/3(1 天前) · 范围 入口闭包 + 全包扫描 · 来源 GitHub 仓库

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

社区实装还没有人打卡 · 装过的第一个就是你
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 图片转文字描述
  • 供纯文本智能体读取截图

把视觉能力接给纯文本编码智能体——面向 DeepSeek 等模型的 Claude Code 和 Codex 插件

README

由 DeepSeek 最新模型翻译生成
⭐ 如果这个项目对你有帮助,欢迎点个 Star。 🥇 DeepSeek Harness 最好的"眼睛" 🥇 特性 • 工作机制 • 快速上手 • 用法 • 配置 • FAQ • 开发 periscope 是给纯文本 coding agent 的视觉桥:把图片译成文字描述,让只读文字的 agent 也能看懂截图、报错、表格和架构图。它以 Agent Plugins 1.0.0 标准打包,兼容 harness 可直接加载;Claude Code 与 dsh 不读该格式,因此各带一层专属适配。 [!NOTE] 为什么有两个专属适配? Agent Plugins 只定义兼容 harness 的加载方式。Claude Code 读自己的 .claude-plugin/ 格式,dsh 读 cordis patch——两者都不读标准的 plugin.json,所以 periscope 为它们各提供一层适配。除此之外的 agent 无需任何额外适配。
展开完整文档(README 剩余 1 节)
⭐ 如果这个项目对你有帮助,欢迎点个 Star。

🥇 DeepSeek Harness 最好的"眼睛" 🥇

特性 • 工作机制 • 快速上手 • 用法 • 配置 • FAQ • 开发

periscope 是给纯文本 coding agent 的视觉桥:把图片译成文字描述,让只读文字的 agent 也能看懂截图、报错、表格和架构图。它以 Agent Plugins 1.0.0 标准打包,兼容 harness 可直接加载;Claude Code 与 dsh 不读该格式,因此各带一层专属适配。

[!NOTE]
为什么有两个专属适配?
Agent Plugins 只定义兼容 harness 的加载方式。Claude Code 读自己的 .claude-plugin/ 格式,dsh 读 cordis patch——两者都不读标准的 plugin.json,所以 periscope 为它们各提供一层适配。除此之外的 agent 无需任何额外适配。

特性

- 接入任意 agent — Agent Plugins 标准插件,兼容 harness 直接加载,Claude Code / dsh 单独适配
- BYOM 三协议 — openai / anthropic / responses 按需切换,视觉模型自带,不绑定任何服务商
- 零构建零依赖 — dist/ 随仓库提交,Node ≥ 22 直接跑,不装 TypeScript、不跑 build
- 绝不中断会话 — 端点故障降级 [Image N] 描述不可用 占位符,hook 始终放行、桥绝不抛错
- 本地缓存 — 同图命中缓存,不重复请求视觉端点

工作机制

图片 / 路径 / URL 经 periscope 的三协议桥送到你的视觉端点(BYOM),得到文字描述后以 [Image N] 名称: 描述 的形式交给接入层。三种接入层共享同一条 describe 能力:

| 接入层 | 加载方式 | 能力 |
| Agent Plugins 兼容 harness | 直接加载(根 plugin.json + skills/describe-image) | agent 按 skill 指令调 describe.js |
| Claude Code | .claude-plugin/ + hooks/hooks.json + skills/ | 贴图自动注入描述,或 /describe-image 手动触发 |
| dsh(deepseek-harness) | npm 包 periscope-dsh + dsh.bundle patch | 注册 periscope-deepseek route,Web UI 选中即看图 |

快速上手

前置要求

- Node.js ≥ 22 — 仅独立脚本方式需要;作为插件使用无需安装任何依赖

Claude Code

① 安装插件 claude plugin marketplace add toRolex/periscope claude plugin install periscope

② 配置视觉端点(独立终端运行交互式 wizard)
node dist/cli/init.js

装好后直接在会话里贴一张截图,agent 自动读出描述;或手动运行:

node dist/cli/describe.js ./截图.png --intent ocr
error TS2322: Type 'string' is not assignable to type 'number'
at src/example.ts:42:5

[!TIP]
在会话里敲 /set-up 可以引导完成配置并自动跑 doctor 自检。

不装插件也能用独立脚本:

git clone https://github.com/toRolex/periscope.git
cd periscope
node dist/cli/describe.js ./demo.png # dist/ 已提交,无需 build

dsh(deepseek-harness)

① 安装(本包提交 dist/,支持 git/file 免构建安装)bash
dsh plugin --profile web add file:
dsh --profile web --dump-config # 复查 cordis 树里出现 periscope-deepseek 行

② 用环境变量配置视觉端点(apiKey 只从 env 读,协议缺省 openai)
export PERISCOPE_API_KEY=sk-xxx
export PERISCOPE_VISION_BASE_URL=http://localhost:11434/v1
export PERISCOPE_VISION_MODEL=qwen2.5-vl

③ 启动 Web UI,模型选择器选「periscope(看图桥 → deepseek)」即看图
dsh web

env 是最短配置路径;想写进 cordis.yml 的完整写法见配置。

其他 harness

Agent Plugins 兼容 harness(VS Code / ChatGPT-Codex / Kiro / GitHub Copilot / Cursor)直接加载标准插件即可,agent 会在需要读图时按 skills/describe-image 的指令调用 describe.js,无需任何额外适配。

用法

describe —— 描述图片 bash
node dist/cli/describe.js [...] [--intent ocr|table|chart|"描述内容"]

- 多图以空格分隔、并行请求(总耗时约等于最慢单图);远程 URL 直接透传,无需下载。
- --intent 命中内置任务模板,其他文本原样透传给模型。
- 成功退出码 0;失败信息走 stderr、退出码非零。

内置任务模板:

| 模板 | 作用 |
| ocr | 提取图片中的全部文字 |
| table | 把图片中的表格转换为 Markdown 表格 |
| chart | 把图片中的图表转换为结构化文字描述 |

init —— 交互式配置 bash
node dist/cli/init.js

在独立终端(TTY)运行的交互式 wizard:选择协议 → 填写 baseUrl / model(apiKey 可留空,本地端点无需鉴权)→ 确认写入配置文件。

doctor —— 本地自检 bash
node dist/cli/doctor.js [--offline]

六项纯本地自检(config 存在性、三协议段、Node 版本、dist/ 产物、插件 manifest),不发起外部请求;--offline 禁用一切网络拉取。

Claude Code 贴图 hook

装成插件后,贴图(或让 agent 引用本地图片 / URL)时自动触发:并行描述各图,把 [Image N] basename: 描述 逐行注入上下文。始终放行——端点故障注入「描述不可用」,绝不阻塞会话;同一图片命中本地缓存不重复请求。

dsh 桥

periscope-deepseek route 把 ImageBlock 经视觉端点译成文字,再委托给主文本模型(默认 deepseek);端点故障降级为可操作引导占位符并落 log,不中断会话。

配置

配置文件默认 ~/.config/periscope/config.json(PERISCOPE_CONFIG 可覆盖)。首次运行懒创建空模板,用 init wizard 或手改填入端点。

protocol 决定当前使用的协议适配器,每个协议有独立的 baseUrl / model:

| protocol | 请求端点 | 鉴权 | 示例 model |
| openai(默认) | {baseUrl}/chat/completions | Bearer | qwen-vl-max |
| anthropic | {baseUrl}/v1/messages | x-api-key | claude-3-5-sonnet-latest |
| responses | {baseUrl}/responses | Bearer | gpt-4o-mini |

openai 只指请求形状:兼容 chat/completions 格式的端点都可用,不默认指向任何服务商。

环境变量:

| 变量 | 作用 |
| PERISCOPE_API_KEY | 视觉端点 API key(优先于配置文件) |
| PERISCOPE_CONFIG | 配置文件路径 |
| PERISCOPE_CACHE_DIR | 缓存目录(默认 ~/.cache/periscope/) |
| PERISCOPE_VISION_ | dsh 侧 env fallback(protocol / baseUrl / model) |

dsh 侧:cordis.yml

dsh 侧配置走 dsh Config(cordis.yml + env fallback),apiKey 仅从环境变量读取,不写进配置:
yaml
- insert:
- id: periscope-deepseek
name: 'periscope-dsh'
config: # 全部可选;缺省走 env fallback protocol: openai # openai | anthropic | responses baseUrl: https://your-vision-endpoint.example.com/v1 model: your-vision-model

[!IMPORTANT]
dsh 侧的 apiKey 只从环境变量读取(PERISCOPE_API_KEY 或 config.apiKeyEnv 指定的变量),不会写进任何配置文件。

FAQ

- 缓存在哪 / 怎么清? ~/.cache/periscope/,每图一个 .txt;rm -rf 即可,或用 PERISCOPE_CACHE_DIR 指到别处。
- 远程 URL 为什么不走缓存? 缓存 key 依赖本地文件的路径 + 修改时间 + 大小,URL 无本地 stat。
- 没配置端点会怎样? describe 报错并引导运行 init;dsh 侧降级为可操作引导占位符。本地端点(Ollama 等)可不填 apiKey,请求不带鉴权头。
- 端点挂了会阻塞会话吗? 不会,始终降级为「描述不可用」,hook 恒 approve。
- 需要装 TypeScript / 跑 build 吗? 纯使用不需要,dist/ 已提交;pnpm install 只在改源码或跑测试时需要。
- Node 版本要求? Node.js ≥ 22。

开发

- devDependencies 仅 typescript;pnpm build 构建、pnpm test 测试(tsc && node --test 'dist//.test.js')。
- 离线 mock 视觉端点 src/testing/mock-server.ts 供自动化冒烟;dsh-plugin/ 与主仓同构。
- 两侧 describe 引擎各保留一份副本、函数签名刻意一致,接口稳定后抽独立 npm 包(届时是纯移动非重构)。
- 更多项目背景与术语见 CONTEXT.md,架构决策见 docs/adr。

报 bug / 提需求 / 交流走 GitHub Issues。
预览分享卡片长什么样 ↓toRolex/periscope
分享 · DeepSeek Harness Hub
toRolex/periscope

把图片转成文字描述,让纯文本 agent 看懂截图

这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。
认领条目 →
回到顶部