@kairyou/agent-tools 0.22.0 → 0.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/en/extras.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # Extras
2
2
 
3
- Fully usable, just situational. Install each on demand.
3
+ This page lists optional tools outside the day-to-day development workflow.
4
4
 
5
5
  ## at-self-eval
6
6
 
7
- Summarize git history, a work log you provide, or both into a self-evaluation
8
- for performance cycles (quarterly, semi-annual, promotion).
7
+ Turn Git commit history, a work log you provide, or both into a performance
8
+ self-evaluation for quarterly reviews, half-year reviews, or promotion materials.
9
9
 
10
10
  ```bash
11
11
  npx -y skills@latest add kairyou/agent-tools --skill at-self-eval -g -y
@@ -25,7 +25,7 @@ Review the generated result before use.
25
25
 
26
26
  ## at-daily-log
27
27
 
28
- Distill each day's Git commits across projects into a work report; runs when invoked.
28
+ Distill each day's Git commits across projects into a work report.
29
29
 
30
30
  ```bash
31
31
  npx -y skills@latest add kairyou/agent-tools --skill at-daily-log -g -y
@@ -35,12 +35,12 @@ Usage:
35
35
 
36
36
  - `/at-daily-log` — print today's log
37
37
  - `/at-daily-log 2026-07-31` — pick the date
38
- - `/at-daily-log last week` / `/at-daily-log the last two weeks` — one entry per active day, empty days skipped
38
+ - `/at-daily-log last week` / `/at-daily-log the last two weeks` — one report per date with Git commits; dates without commits are skipped
39
39
  - `/at-daily-log summarize today for C:\projects\project-a` — only that project
40
40
  - `/at-daily-log also include C:\projects\project-c` — add to the default scope
41
41
  - `/at-daily-log record the log` — record to the configured file; rerunning a day only refreshes the generated part
42
42
  - `/at-daily-log record it to C:\logs\daily-log.md` — record to that file
43
- - `/at-daily-log record daily at 18:00` — guides you through an OS scheduled task; days without commits are not written
43
+ - `/at-daily-log record daily at 18:00` — guides you through an OS scheduled task; no report is written when the day has no Git commits
44
44
 
45
45
  Recorded output example:
46
46
 
@@ -73,21 +73,6 @@ npx -y @kairyou/agent-tools@latest log -a claude codex opencode
73
73
  - `format: "daily"`: writes to a single Markdown file grouped by date; each completed answer with substantive results is recorded as one line, while pending prompts are omitted; content may be truncated by the length limit, so use it only as a lightweight activity index
74
74
  - Codex: run `/hooks` once after installing to approve it; opencode: restart after installing or updating
75
75
 
76
- `daily` output example:
77
-
78
- ```markdown
79
- + 2026-08-03
80
- <!-- log:2026-08-03:start -->
81
- 1. project-a: Found the cause of the login timeout: the renewal branch never updated the cache expiry, so the second request still read the old value. Patched session.ts and verified...
82
- 2. project-a: Fix is in, with 3 regression tests covering the renewal path; all passing.
83
- 3. project-b: Empty report exports came from an inverted permission filter, now corrected and confirmed working.
84
- <!-- log:2026-08-03:end -->
85
- ```
86
-
87
- One line per turn, taken verbatim from the closing text of that turn's AI reply
88
- (truncated when long); no distilling, no cross-turn consolidation. Updates likewise
89
- rewrite only what sits between the markers.
90
-
91
76
  `detailed` output example (excerpt):
92
77
 
93
78
  ```markdown
@@ -114,6 +99,21 @@ Changes
114
99
  - src/auth/session.ts | diff +42/-8 | 3 ops
115
100
  ```
116
101
 
102
+ `daily` output example:
103
+
104
+ ```markdown
105
+ + 2026-08-03
106
+ <!-- log:2026-08-03:start -->
107
+ 1. project-a: Found the cause of the login timeout: the renewal branch never updated the cache expiry, so the second request still read the old value. Patched session.ts and verified...
108
+ 2. project-a: Fix is in, with 3 regression tests covering the renewal path; all passing.
109
+ 3. project-b: Empty report exports came from an inverted permission filter, now corrected and confirmed working.
110
+ <!-- log:2026-08-03:end -->
111
+ ```
112
+
113
+ One line per turn, taken verbatim from the closing text of that turn's AI reply
114
+ (truncated when long); no distilling, no cross-turn consolidation. Updates likewise
115
+ rewrite only what sits between the markers.
116
+
117
117
  ## Configuration
118
118
 
119
119
  All in `~/.agent-tools/config.jsonc`:
@@ -4,10 +4,12 @@
4
4
  agent-tools/
5
5
  ├── .claude-plugin/ # Claude Code/plugin ecosystem manifest.
6
6
  ├── .codex-plugin/ # Codex plugin manifest.
7
+ ├── .zcode-plugin/ # ZCode plugin manifest.
7
8
  ├── capabilities/ # Runtime capabilities, one directory each.
8
9
  │ ├── statusline/ # Agent status line: branch, model, usage.
9
10
  │ ├── usage/ # Provider balance / quota display.
10
- └── vision/ # Cross-model image understanding.
11
+ ├── vision/ # Cross-model image understanding and host adapters.
12
+ │ └── log/ # Session work-log hooks and plugins.
11
13
  ├── skills/ # Reusable Agent Skills.
12
14
  │ ├── workflow/ # Workflow-oriented skills.
13
15
  │ │ ├── at-commit/ # Conventional Commit message skill.
@@ -1,10 +1,10 @@
1
1
  # Extras
2
2
 
3
- 完整可用, 只是场景较窄. 按需单独安装.
3
+ 这里收录日常开发主流程之外的可选工具.
4
4
 
5
5
  ## at-self-eval
6
6
 
7
- git 历史或你提供的工作日志归纳成自评产出清单, 用于季度/半年/晋升等绩效周期.
7
+ 基于 Git 提交历史或你提供的工作日志生成绩效自评, 可用于季度总结, 半年总结及晋升材料撰写.
8
8
 
9
9
  ```bash
10
10
  npx -y skills@latest add kairyou/agent-tools --skill at-self-eval -g -y
@@ -24,7 +24,7 @@ npx -y skills@latest add kairyou/agent-tools --skill at-self-eval -g -y
24
24
 
25
25
  ## at-daily-log
26
26
 
27
- 按天把各项目的 Git 提交提炼成工作日报, 调用时生成.
27
+ 按天把各项目的 Git 提交提炼成工作日报.
28
28
 
29
29
  ```bash
30
30
  npx -y skills@latest add kairyou/agent-tools --skill at-daily-log -g -y
@@ -34,12 +34,12 @@ npx -y skills@latest add kairyou/agent-tools --skill at-daily-log -g -y
34
34
 
35
35
  - `/at-daily-log` — 输出今天的日报
36
36
  - `/at-daily-log 2026-07-31` — 指定日期
37
- - `/at-daily-log 上周` / `/at-daily-log 最近两周` — 范围内每天一条, 跳过没有提交的日子
37
+ - `/at-daily-log 上周` / `/at-daily-log 最近两周` — 按日期生成日报, 跳过没有 Git 提交的日期
38
38
  - `/at-daily-log 统计 C:\projects\project-a 今天的工作` — 只统计指定项目
39
39
  - `/at-daily-log 另外包含 C:\projects\project-c` — 在默认范围上追加
40
40
  - `/at-daily-log 记录日报` — 记录到配置的文件; 同一天重复执行只更新生成的部分
41
41
  - `/at-daily-log 记录到 C:\logs\daily-log.md` — 记录到指定文件
42
- - `/at-daily-log 每天 18:00 自动记录` — 引导搭建系统定时任务; 没有提交的日子不会写入
42
+ - `/at-daily-log 每天 18:00 自动记录` — 引导搭建系统定时任务; 当天没有 Git 提交时不写入日报
43
43
 
44
44
  记录到文件的示例:
45
45
 
@@ -71,20 +71,6 @@ npx -y @kairyou/agent-tools@latest log -a claude codex opencode
71
71
  - `format: "daily"`: 写入单个 Markdown 文件, 并按日期归档; 每个已完成且有实质结果的回答记录为一行, 不记录未完成的提问; 内容可能因长度限制被截断, 因此仅适合作为轻量活动索引
72
72
  - Codex 安装后运行 `/hooks` 批准一次; opencode 安装或更新后需要重启
73
73
 
74
- `daily` 输出示例:
75
-
76
- ```markdown
77
- + 2026-08-03
78
- <!-- log:2026-08-03:start -->
79
- 1. project-a: 已定位登录超时的原因: 会话缓存在续期分支上没有更新过期时间, 第二次请求拿到的还是旧值。已经在 session.ts 补上续期并本地验证通过, 接下来...
80
- 2. project-a: 修复完成, 新增 3 个回归用例覆盖续期路径, 全部通过。
81
- 3. project-b: 报表导出为空定位到权限过滤条件写反, 已修正并确认导出恢复正常。
82
- <!-- log:2026-08-03:end -->
83
- ```
84
-
85
- 每轮一行, 直接摘录那一轮 AI 回复的收尾内容(过长会截断), 不做提炼也不跨轮归纳;
86
- 同样只重写标记之间的部分, 文件里的其他内容不会被碰.
87
-
88
74
  `detailed` 输出示例(节选):
89
75
 
90
76
  ```markdown
@@ -111,6 +97,20 @@ Changes
111
97
  - src/auth/session.ts | 变更 +42/-8 | 操作 3 次
112
98
  ```
113
99
 
100
+ `daily` 输出示例:
101
+
102
+ ```markdown
103
+ + 2026-08-03
104
+ <!-- log:2026-08-03:start -->
105
+ 1. project-a: 已定位登录超时的原因: 会话缓存在续期分支上没有更新过期时间, 第二次请求拿到的还是旧值. 已经在 session.ts 补上续期并本地验证通过, 接下来...
106
+ 2. project-a: 修复完成, 新增 3 个回归用例覆盖续期路径, 全部通过.
107
+ 3. project-b: 报表导出为空定位到权限过滤条件写反, 已修正并确认导出恢复正常.
108
+ <!-- log:2026-08-03:end -->
109
+ ```
110
+
111
+ 每轮一行, 直接摘录那一轮 AI 回复的收尾内容(过长会截断), 不做提炼也不跨轮归纳;
112
+ 同样只重写标记之间的部分, 文件里的其他内容不会被碰.
113
+
114
114
  ## 配置
115
115
 
116
116
  都在 `~/.agent-tools/config.jsonc`:
@@ -4,10 +4,12 @@
4
4
  agent-tools/
5
5
  ├── .claude-plugin/ # Claude Code/plugin 生态的 manifest.
6
6
  ├── .codex-plugin/ # Codex plugin manifest.
7
+ ├── .zcode-plugin/ # ZCode plugin manifest.
7
8
  ├── capabilities/ # Runtime capabilities, 一个一目录.
8
9
  │ ├── statusline/ # Agent 状态栏: 分支, 模型, 用量.
9
10
  │ ├── usage/ # Provider 余额/额度显示.
10
- └── vision/ # 跨模型识图.
11
+ ├── vision/ # 跨模型识图与各 Agent adapter.
12
+ │ └── log/ # Session work log hooks 与 plugins.
11
13
  ├── skills/ # 可复用的 Agent Skills.
12
14
  │ ├── workflow/ # 工作流类 skills.
13
15
  │ │ ├── at-commit/ # 生成 Conventional Commits message.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kairyou/agent-tools",
3
- "version": "0.22.0",
4
- "description": "Reusable Agent Skills, plus runtime capabilities (statusline, provider usage, vision) for Codex, Claude Code, and opencode.",
3
+ "version": "0.23.1",
4
+ "description": "Reusable Agent Skills plus per-agent runtime capabilities for compatible coding agents.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -15,6 +15,9 @@
15
15
  },
16
16
  "files": [
17
17
  "config.default.jsonc",
18
+ ".claude-plugin/",
19
+ ".codex-plugin/",
20
+ ".zcode-plugin/",
18
21
  "docs/",
19
22
  "dist/",
20
23
  "capabilities/",
@@ -25,14 +28,16 @@
25
28
  "jsonc-parser": "3.3.1"
26
29
  },
27
30
  "devDependencies": {
28
- "@modelcontextprotocol/sdk": "1.29.0",
29
- "esbuild": "0.28.1",
31
+ "@modelcontextprotocol/sdk": "1.30.0",
32
+ "esbuild": "0.28.2",
33
+ "typebox": "1.3.7",
30
34
  "zod": "4.4.3"
31
35
  },
32
36
  "scripts": {
33
37
  "build": "node scripts/build.mjs",
34
38
  "prepare": "npm run build",
35
- "test": "node --test tests/*.test.mjs",
39
+ "test": "node --test --test-concurrency=1 tests/*.test.mjs",
40
+ "version": "node scripts/sync-plugin-version.mjs",
36
41
  "claude-skills:fetch": "node tools/claude-skill-sync/cli.mjs fetch",
37
42
  "claude-skills:inspect": "node tools/claude-skill-sync/cli.mjs inspect",
38
43
  "claude-skills:apply": "node tools/claude-skill-sync/cli.mjs apply",
package/scripts/build.mjs CHANGED
@@ -33,6 +33,7 @@ const TARGETS = {
33
33
  entryPoints: {
34
34
  "mcp-server": path.join(ROOT, "capabilities", "vision", "mcp-server.mjs"),
35
35
  cli: path.join(ROOT, "capabilities", "vision", "lib", "cli.mjs"),
36
+ "pi-extension": path.join(ROOT, "capabilities", "vision", "pi-extension.mjs"),
36
37
  },
37
38
  },
38
39
  log: {
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  // agent-tools installer: wires statusline / usage into each agent's config.
3
3
  // Runtime-dependent skills are installed with their capability. Standalone
4
- // Standalone skills are still handled by `npx skills add`.
4
+ // skills are still handled by `npx skills add`.
5
5
  //
6
6
  // Capabilities (all global for now — they target the user-level config):
7
7
  // statusline Claude Code statusLine script (claude only).
8
- // usage Active API provider quota/balance (all agents).
9
- // vision inspect_image MCP server + at-vision skill (all agents).
10
- // log AI session work log via agent hooks (all agents).
8
+ // usage Active API provider quota/balance (Claude/Codex/OpenCode).
9
+ // vision inspect_image host adapter + at-vision skill.
10
+ // log AI session work log via agent hooks (Claude/Codex/OpenCode).
11
11
  //
12
12
  // Standalone commands (dispatched before capability parsing):
13
13
  // inspect-image <path|url> --question "..." Human diagnostic for vision.
@@ -22,6 +22,12 @@
22
22
  // ~/.codex/config.toml (vision MCP) + ~/.agents/skills (at-vision)
23
23
  // opencode -> ~/.config/opencode/ (usage + log plugins,
24
24
  // vision MCP in opencode.json + skills/at-vision)
25
+ // pi -> ~/.pi/agent/extensions/ (native vision tool)
26
+ // + ~/.pi/agent/skills/at-vision
27
+ // zcode -> ~/.zcode/cli/config.json (vision MCP)
28
+ // + ~/.zcode/skills/at-vision
29
+ // dsh -> ~/.dsh/cordis.patch.yml (official MCP client row)
30
+ // + ~/.dsh/skills/at-vision
25
31
  // Runtime scripts are copied into ~/.agent-tools so this installer can be
26
32
  // run via npx from GitHub without requiring a persistent local clone.
27
33
  //
@@ -32,7 +38,7 @@
32
38
  // agent-tools <capabilities> [options]
33
39
  //
34
40
  // Options:
35
- // -a, --agent <names> Target agents: claude | codex | opencode.
41
+ // -a, --agent <names> Target agents: claude | codex | opencode | pi | zcode | dsh.
36
42
  // Default: claude.
37
43
  // --settings <path> Override the Claude settings.json (for testing).
38
44
  // --codex-hooks <path> Override the Codex hooks.json (for testing).
@@ -41,6 +47,10 @@
41
47
  // --claude-skills-dir <p> Override ~/.claude/skills (for testing).
42
48
  // --codex-config <path> Override ~/.codex/config.toml (for testing).
43
49
  // --codex-skills-dir <p> Override ~/.agents/skills (for testing).
50
+ // --pi-agent-dir <path> Override ~/.pi/agent (for testing).
51
+ // --zcode-config <path> Override ~/.zcode/cli/config.json (for testing).
52
+ // --zcode-skills-dir <path> Override ~/.zcode/skills (for testing).
53
+ // --dsh-home <path> Override ~/.dsh (for testing).
44
54
  // --uninstall Remove what this installer added, restoring backups.
45
55
  // --dry-run Print planned changes without writing anything.
46
56
  // -h, --help Show this help.
@@ -92,11 +102,14 @@ const RUNTIME = {
92
102
  opencodeUsageTui: path.join(INSTALL_ROOT, "dist", "usage", "opencode-tui.mjs"),
93
103
  };
94
104
  const ALL_CAPS = ["statusline", "usage", "vision", "log"];
95
- const ALL_AGENTS = ["claude", "codex", "opencode"];
105
+ const ALL_AGENTS = ["claude", "codex", "opencode", "pi", "zcode", "dsh"];
96
106
  const AGENT_CAPS = {
97
107
  claude: ["statusline", "usage", "vision", "log"],
98
108
  codex: ["usage", "vision", "log"],
99
109
  opencode: ["usage", "vision", "log"],
110
+ pi: ["vision"],
111
+ zcode: ["vision"],
112
+ dsh: ["vision"],
100
113
  };
101
114
  const VISION_MCP_NAME = "agent-tools-vision";
102
115
  const VISION_SKILL_NAME = "at-vision";
@@ -300,6 +313,10 @@ function parseArgs(argv) {
300
313
  claudeSkillsDir: null,
301
314
  codexConfig: null,
302
315
  codexSkillsDir: null,
316
+ piAgentDir: null,
317
+ zcodeConfig: null,
318
+ zcodeSkillsDir: null,
319
+ dshHome: null,
303
320
  uninstall: false,
304
321
  dryRun: false,
305
322
  help: false,
@@ -334,6 +351,10 @@ function parseArgs(argv) {
334
351
  case "--claude-skills-dir": opts.claudeSkillsDir = argv[++i]; break;
335
352
  case "--codex-config": opts.codexConfig = argv[++i]; break;
336
353
  case "--codex-skills-dir": opts.codexSkillsDir = argv[++i]; break;
354
+ case "--pi-agent-dir": opts.piAgentDir = argv[++i]; break;
355
+ case "--zcode-config": opts.zcodeConfig = argv[++i]; break;
356
+ case "--zcode-skills-dir": opts.zcodeSkillsDir = argv[++i]; break;
357
+ case "--dsh-home": opts.dshHome = argv[++i]; break;
337
358
  case "--uninstall": opts.uninstall = true; break;
338
359
  case "--dry-run": opts.dryRun = true; break;
339
360
  case "-h":
@@ -535,6 +556,7 @@ const VISION_RATE_LIMIT_STATE = path.join(INSTALL_ROOT, "cache", "vision-rate-li
535
556
  const VISION_DIST_DIR = path.join(REPO_ROOT, "dist", "vision");
536
557
  const VISION_RUNTIME_SERVER = path.join(VISION_RUNTIME_DIR, "mcp-server.mjs");
537
558
  const VISION_RUNTIME_CLI = path.join(VISION_RUNTIME_DIR, "cli.mjs");
559
+ const VISION_RUNTIME_PI_EXTENSION = path.join(VISION_RUNTIME_DIR, "pi-extension.mjs");
538
560
  const VISION_SKILL_IDENTITY = Object.freeze({
539
561
  owner: "@kairyou/agent-tools",
540
562
  capability: "vision",
@@ -545,7 +567,7 @@ const USAGE_SKILL_IDENTITY = Object.freeze({
545
567
  capability: "usage",
546
568
  artifact: "skill",
547
569
  });
548
- // Vision is bundled at release time. Install atomically swaps two self-contained
570
+ // Vision is bundled at release time. Install atomically swaps self-contained
549
571
  // entry files into ~/.agent-tools/dist/vision, so hosts never run npm and a
550
572
  // failed update cannot destroy the previously working runtime.
551
573
  function visionMcpCommand() {
@@ -591,6 +613,16 @@ function isOpenCodeVisionMcp(value, mcp = visionMcpCommand()) {
591
613
  );
592
614
  }
593
615
 
616
+ function isZcodeVisionMcp(value, mcp = visionMcpCommand()) {
617
+ return Boolean(
618
+ value &&
619
+ value.command === mcp.command &&
620
+ Array.isArray(value.args) &&
621
+ value.args.length === 1 &&
622
+ isVisionServerPath(value.args[0])
623
+ );
624
+ }
625
+
594
626
  function installVisionRuntime(opts) {
595
627
  if (opts.uninstall) return;
596
628
  console.log(`vision runtime: ${VISION_RUNTIME_DIR}`);
@@ -598,7 +630,7 @@ function installVisionRuntime(opts) {
598
630
  console.log(` [dry-run] would atomically install ${VISION_DIST_DIR} -> ${VISION_RUNTIME_DIR}`);
599
631
  return;
600
632
  }
601
- for (const name of ["mcp-server.mjs", "cli.mjs"]) {
633
+ for (const name of ["mcp-server.mjs", "cli.mjs", "pi-extension.mjs"]) {
602
634
  if (!fs.existsSync(path.join(VISION_DIST_DIR, name))) {
603
635
  throw new Error(`Missing bundled vision runtime ${path.join(VISION_DIST_DIR, name)}. Run npm run build.`);
604
636
  }
@@ -616,7 +648,7 @@ function installVisionRuntime(opts) {
616
648
  JSON.stringify({ owner: "@kairyou/agent-tools", capability: "vision", version: 1 }, null, 2) + "\n",
617
649
  false
618
650
  );
619
- for (const name of ["mcp-server.mjs", "cli.mjs"]) {
651
+ for (const name of ["mcp-server.mjs", "cli.mjs", "pi-extension.mjs"]) {
620
652
  const check = spawnSync(process.execPath, ["--check", path.join(stage, name)], {
621
653
  encoding: "utf8",
622
654
  });
@@ -1123,6 +1155,192 @@ function runOpencode(opts) {
1123
1155
  if (!opts.dryRun) console.log(" NOTE: restart opencode to load the agent-tools usage plugin.");
1124
1156
  }
1125
1157
 
1158
+ // ---- Pi: native extension tool plus the capability-owned skill. ----
1159
+
1160
+ const PI_VISION_STUB_NAME = "agent-tools-vision.js";
1161
+
1162
+ function piAgentDir(opts) {
1163
+ return opts.piAgentDir || path.join(os.homedir(), ".pi", "agent");
1164
+ }
1165
+
1166
+ function piVisionStubContents() {
1167
+ return (
1168
+ "// Generated by agent-tools installer; do not edit.\n" +
1169
+ `export { default } from ${JSON.stringify(pathToFileURL(VISION_RUNTIME_PI_EXTENSION).href)};\n`
1170
+ );
1171
+ }
1172
+
1173
+ function isManagedPiVisionStub(file) {
1174
+ return fs.existsSync(file) && fs.readFileSync(file, "utf8") === piVisionStubContents();
1175
+ }
1176
+
1177
+ function runPi(opts) {
1178
+ if (!wants(opts, "vision")) return;
1179
+ const agentDir = piAgentDir(opts);
1180
+ const stub = path.join(agentDir, "extensions", PI_VISION_STUB_NAME);
1181
+ const skillsDir = path.join(agentDir, "skills");
1182
+ console.log(`pi vision: ${stub}`);
1183
+
1184
+ if (opts.uninstall) {
1185
+ if (isManagedPiVisionStub(stub)) removeFile(stub, opts.dryRun);
1186
+ else if (fs.existsSync(stub)) console.log(` kept unmanaged ${stub}`);
1187
+ else console.log(" no agent-tools vision extension found; nothing to remove.");
1188
+ installVisionSkillDir(skillsDir, { remove: true, dryRun: opts.dryRun });
1189
+ console.log(" - vision");
1190
+ return;
1191
+ }
1192
+
1193
+ if (fs.existsSync(stub) && !isManagedPiVisionStub(stub)) {
1194
+ throw new Error(
1195
+ `Refusing to overwrite existing unmanaged Pi extension ${stub}. ` +
1196
+ "Move or remove it, then re-run the install."
1197
+ );
1198
+ }
1199
+ writeText(stub, piVisionStubContents(), opts.dryRun);
1200
+ installVisionSkillDir(skillsDir, { remove: false, dryRun: opts.dryRun });
1201
+ console.log(" + vision (native inspect_image extension + at-vision skill)");
1202
+ if (!opts.dryRun) console.log(" NOTE: restart Pi or run /reload to load the agent-tools vision extension.");
1203
+ }
1204
+
1205
+ // ---- ZCode: native user MCP config plus the user-level skill. ----
1206
+
1207
+ function updateZcodeVisionMcp(file, { remove, dryRun, mcp }) {
1208
+ const exists = fs.existsSync(file);
1209
+ const currentText = exists ? fs.readFileSync(file, "utf8") : "{}\n";
1210
+ const errors = [];
1211
+ const current = parseJsonc(currentText, errors, { allowTrailingComma: true }) || {};
1212
+ if (errors.length > 0 || typeof current !== "object" || Array.isArray(current)) {
1213
+ throw new Error(`Cannot parse ${file} as JSONC`);
1214
+ }
1215
+ const existing = current.mcp?.servers?.[VISION_MCP_NAME];
1216
+ if (existing && !isZcodeVisionMcp(existing, mcp)) {
1217
+ if (remove) {
1218
+ console.log(` kept unmanaged ${VISION_MCP_NAME} entry in ${file}`);
1219
+ return;
1220
+ }
1221
+ throw new Error(
1222
+ `Found an existing unmanaged ${VISION_MCP_NAME} entry in ${file}. ` +
1223
+ "Remove or rename it, then re-run the install."
1224
+ );
1225
+ }
1226
+ const desired = remove ? undefined : { command: mcp.command, args: mcp.args, env: {} };
1227
+ if (JSON.stringify(existing) === JSON.stringify(desired)) {
1228
+ console.log(` kept existing ${file}`);
1229
+ return;
1230
+ }
1231
+ const eol = currentText.includes("\r\n") ? "\r\n" : "\n";
1232
+ const edits = modify(currentText, ["mcp", "servers", VISION_MCP_NAME], desired, {
1233
+ formattingOptions: { insertSpaces: true, tabSize: 2, eol },
1234
+ });
1235
+ const updated = applyEdits(currentText, edits).replace(/\s*$/, "") + eol;
1236
+ writeText(file, updated, dryRun);
1237
+ }
1238
+
1239
+ function runZcode(opts) {
1240
+ if (!wants(opts, "vision")) return;
1241
+ const file = opts.zcodeConfig || path.join(os.homedir(), ".zcode", "cli", "config.json");
1242
+ const skillsDir = opts.zcodeSkillsDir || path.join(os.homedir(), ".zcode", "skills");
1243
+ console.log(`zcode vision: ${file}`);
1244
+ updateZcodeVisionMcp(file, {
1245
+ remove: opts.uninstall,
1246
+ dryRun: opts.dryRun,
1247
+ mcp: visionMcpCommand(),
1248
+ });
1249
+ installVisionSkillDir(skillsDir, { remove: opts.uninstall, dryRun: opts.dryRun });
1250
+ console.log(opts.uninstall ? " - vision" : " + vision (inspect_image MCP + at-vision skill)");
1251
+ if (!opts.uninstall && !opts.dryRun) {
1252
+ console.log(" NOTE: start a new ZCode session to load the agent-tools vision MCP server.");
1253
+ }
1254
+ }
1255
+
1256
+ // ---- DeepSeek Harness: official MCP client row in the global Cordis patch. ----
1257
+
1258
+ const DSH_VISION_BEGIN = "# >>> agent-tools vision >>>";
1259
+ const DSH_VISION_END = "# <<< agent-tools vision <<<";
1260
+ const DSH_VISION_ROW_ID = "agent-tools-vision";
1261
+
1262
+ function dshHome(opts) {
1263
+ return opts.dshHome || process.env.DSH_HOME || path.join(os.homedir(), ".dsh");
1264
+ }
1265
+
1266
+ function dshVisionBlock(mcp) {
1267
+ return [
1268
+ DSH_VISION_BEGIN,
1269
+ "- insert:",
1270
+ ` - id: ${DSH_VISION_ROW_ID}`,
1271
+ " name: '@deepseek-ai/dsh-mcp-client'",
1272
+ " config:",
1273
+ ` serverName: ${DSH_VISION_ROW_ID}`,
1274
+ " transport: stdio",
1275
+ ` command: ${JSON.stringify(mcp.command)}`,
1276
+ ` args: [${mcp.args.map((arg) => JSON.stringify(arg)).join(", ")}]`,
1277
+ " env: {}",
1278
+ DSH_VISION_END,
1279
+ "",
1280
+ ].join("\n");
1281
+ }
1282
+
1283
+ function updateDshVisionPatch(file, { remove, dryRun, mcp }) {
1284
+ const current = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "[]\n";
1285
+ const escape = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1286
+ const blockRe = new RegExp(
1287
+ `\\r?\\n?${escape(DSH_VISION_BEGIN)}[\\s\\S]*?${escape(DSH_VISION_END)}\\r?\\n?`
1288
+ );
1289
+ const hadManagedBlock = blockRe.test(current);
1290
+ let base = current.replace(blockRe, "\n").trim();
1291
+ if (base !== "" && base !== "[]" && !base.startsWith("-")) {
1292
+ throw new Error(`Cannot update ${file}: the Cordis patch must be a top-level YAML array`);
1293
+ }
1294
+
1295
+ if (!remove && !hadManagedBlock) {
1296
+ const conflict = new RegExp(
1297
+ `^\\s*(?:-\\s*)?(?:id|serverName):\\s*['\"]?${escape(DSH_VISION_ROW_ID)}['\"]?\\s*$`,
1298
+ "m"
1299
+ );
1300
+ if (conflict.test(base)) {
1301
+ throw new Error(
1302
+ `Found an existing unmanaged ${DSH_VISION_ROW_ID} Cordis row in ${file}. ` +
1303
+ "Remove or rename it, then re-run the install."
1304
+ );
1305
+ }
1306
+ }
1307
+
1308
+ let next;
1309
+ if (remove) {
1310
+ if (!hadManagedBlock) {
1311
+ console.log(` no managed ${DSH_VISION_ROW_ID} Cordis row found; nothing to remove.`);
1312
+ return;
1313
+ }
1314
+ next = base === "" ? "[]\n" : `${base}\n`;
1315
+ } else {
1316
+ const block = dshVisionBlock(mcp);
1317
+ next = base === "" || base === "[]" ? block : `${base}\n\n${block}`;
1318
+ }
1319
+ if (next === current) {
1320
+ console.log(` kept existing ${file}`);
1321
+ return;
1322
+ }
1323
+ writeText(file, next, dryRun);
1324
+ }
1325
+
1326
+ function runDsh(opts) {
1327
+ if (!wants(opts, "vision")) return;
1328
+ const home = dshHome(opts);
1329
+ const patchFile = path.join(home, "cordis.patch.yml");
1330
+ const skillsDir = path.join(home, "skills");
1331
+ console.log(`dsh vision: ${patchFile}`);
1332
+ updateDshVisionPatch(patchFile, {
1333
+ remove: opts.uninstall,
1334
+ dryRun: opts.dryRun,
1335
+ mcp: visionMcpCommand(),
1336
+ });
1337
+ installVisionSkillDir(skillsDir, { remove: opts.uninstall, dryRun: opts.dryRun });
1338
+ console.log(opts.uninstall ? " - vision" : " + vision (official MCP client row + at-vision skill)");
1339
+ if (!opts.uninstall && !opts.dryRun) {
1340
+ console.log(" NOTE: restart DSH and wait for mcp__agent-tools-vision__inspect_image to appear.");
1341
+ }
1342
+ }
1343
+
1126
1344
  function visionRuntimeHasRemainingReference(opts) {
1127
1345
  const removed = new Set(opts.agents);
1128
1346
  if (!removed.has("claude")) {
@@ -1162,6 +1380,36 @@ function visionRuntimeHasRemainingReference(opts) {
1162
1380
  return true;
1163
1381
  }
1164
1382
  }
1383
+ if (!removed.has("pi")) {
1384
+ const stub = path.join(piAgentDir(opts), "extensions", PI_VISION_STUB_NAME);
1385
+ try {
1386
+ if (isManagedPiVisionStub(stub)) return true;
1387
+ } catch {
1388
+ return true;
1389
+ }
1390
+ }
1391
+ if (!removed.has("zcode")) {
1392
+ const file = opts.zcodeConfig || path.join(os.homedir(), ".zcode", "cli", "config.json");
1393
+ try {
1394
+ if (isZcodeVisionMcp(readJsonc(file).mcp?.servers?.[VISION_MCP_NAME])) return true;
1395
+ } catch {
1396
+ return true;
1397
+ }
1398
+ }
1399
+ if (!removed.has("dsh")) {
1400
+ const file = path.join(dshHome(opts), "cordis.patch.yml");
1401
+ try {
1402
+ const text = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
1403
+ const start = text.indexOf(DSH_VISION_BEGIN);
1404
+ const end = text.indexOf(DSH_VISION_END, start + DSH_VISION_BEGIN.length);
1405
+ if (start !== -1 && end !== -1) {
1406
+ const block = text.slice(start, end);
1407
+ if (block.includes(fwd(VISION_RUNTIME_SERVER))) return true;
1408
+ }
1409
+ } catch {
1410
+ return true;
1411
+ }
1412
+ }
1165
1413
  return false;
1166
1414
  }
1167
1415
 
@@ -1187,7 +1435,14 @@ function cleanupVisionRuntimeIfUnused(opts) {
1187
1435
  console.log(` removed unused vision runtime ${VISION_RUNTIME_DIR}`);
1188
1436
  }
1189
1437
 
1190
- const AGENTS = { claude: runClaude, codex: runCodex, opencode: runOpencode };
1438
+ const AGENTS = {
1439
+ claude: runClaude,
1440
+ codex: runCodex,
1441
+ opencode: runOpencode,
1442
+ pi: runPi,
1443
+ zcode: runZcode,
1444
+ dsh: runDsh,
1445
+ };
1191
1446
 
1192
1447
  function main() {
1193
1448
  const opts = parseArgs(process.argv.slice(2));
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const ROOT = fileURLToPath(new URL("..", import.meta.url));
8
+ const version = JSON.parse(fs.readFileSync(path.join(ROOT, "package.json"), "utf8")).version;
9
+ const targets = [
10
+ [".claude-plugin/plugin.json", []],
11
+ [".codex-plugin/plugin.json", []],
12
+ [".zcode-plugin/plugin.json", []],
13
+ ];
14
+
15
+ for (const [relativePath, parentPath] of targets) {
16
+ const file = path.join(ROOT, relativePath);
17
+ const document = JSON.parse(fs.readFileSync(file, "utf8"));
18
+ let target = document;
19
+ for (const key of parentPath) target = target[key];
20
+ target.version = version;
21
+ fs.writeFileSync(file, `${JSON.stringify(document, null, 2)}\n`);
22
+ }