@kairyou/agent-tools 0.2.0 → 0.4.0
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/README.md +112 -115
- package/README.zh-CN.md +108 -115
- package/dist/statusline/claude-statusline.mjs +355 -0
- package/dist/usage/cli.mjs +25 -0
- package/dist/usage/codex-hook.mjs +144 -0
- package/dist/usage/core.mjs +1054 -0
- package/dist/usage/opencode-plugin.mjs +80 -0
- package/dist/usage/opencode-tui.mjs +46 -0
- package/dist/vision/cli.mjs +13 -13
- package/dist/vision/mcp-server.mjs +12 -12
- package/{statusline/claude/statusline.mjs → integrations/statusline/claude-statusline.mjs} +33 -2
- package/integrations/usage/cli.mjs +27 -0
- package/{hooks/codex/usage-hook.mjs → integrations/usage/codex-hook.mjs} +1 -1
- package/{lib/usage.mjs → integrations/usage/core.mjs} +34 -1
- package/{plugins/opencode/usage-plugin.mjs → integrations/usage/opencode-plugin.mjs} +1 -1
- package/integrations/usage/skills/at-usage/SKILL.md +16 -0
- package/{plugins → integrations}/vision/mcp-server.mjs +4 -4
- package/package.json +8 -12
- package/scripts/build.mjs +65 -0
- package/scripts/install.mjs +135 -90
- package/scripts/release.mjs +75 -5
- package/hooks/claude/.gitkeep +0 -1
- package/hooks/codex/.gitkeep +0 -1
- package/hooks/common/.gitkeep +0 -1
- package/hooks/opencode/.gitkeep +0 -1
- package/scripts/build-vision.mjs +0 -35
- package/statusline/.gitkeep +0 -1
- package/statusline/codex/.gitkeep +0 -1
- /package/{plugins/opencode/usage-tui.mjs → integrations/usage/opencode-tui.mjs} +0 -0
- /package/{lib/vision → integrations/vision/lib}/cli.mjs +0 -0
- /package/{lib/vision → integrations/vision/lib}/config.mjs +0 -0
- /package/{lib/vision → integrations/vision/lib}/errors.mjs +0 -0
- /package/{lib/vision → integrations/vision/lib}/image-source.mjs +0 -0
- /package/{lib/vision → integrations/vision/lib}/inspect.mjs +0 -0
- /package/{lib/vision → integrations/vision/lib}/providers/anthropic-compatible.mjs +0 -0
- /package/{lib/vision → integrations/vision/lib}/providers/openai-compatible.mjs +0 -0
- /package/{lib/vision → integrations/vision/lib}/providers/shared.mjs +0 -0
- /package/{lib/vision → integrations/vision/lib}/rate-limit.mjs +0 -0
- /package/{lib/vision → integrations/vision/lib}/redact.mjs +0 -0
- /package/{plugins → integrations}/vision/skills/at-vision/SKILL.md +0 -0
package/scripts/install.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
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
|
+
// workflow/integration skills are still handled by `npx skills add`.
|
|
4
5
|
//
|
|
5
6
|
// Capabilities (all global for now — they target the user-level config):
|
|
6
7
|
// statusline Claude Code statusLine script (claude only).
|
|
7
|
-
// usage Active API provider quota/balance (
|
|
8
|
+
// usage Active API provider quota/balance (all agents).
|
|
8
9
|
// vision inspect_image MCP server + at-vision skill (all agents).
|
|
9
10
|
//
|
|
10
11
|
// Standalone commands (dispatched before capability parsing):
|
|
@@ -13,8 +14,10 @@
|
|
|
13
14
|
//
|
|
14
15
|
// Targets:
|
|
15
16
|
// claude -> ~/.claude/settings.json (statusLine key)
|
|
17
|
+
// + ~/.claude/skills/at-usage
|
|
16
18
|
// ~/.claude.json (vision MCP) + ~/.claude/skills (at-vision)
|
|
17
19
|
// codex -> ~/.codex/hooks.json (standalone hooks file)
|
|
20
|
+
// + ~/.agents/skills/at-usage
|
|
18
21
|
// ~/.codex/config.toml (vision MCP) + ~/.agents/skills (at-vision)
|
|
19
22
|
// opencode -> ~/.config/opencode/ (server + TUI plugins,
|
|
20
23
|
// vision MCP in opencode.json + skills/at-vision)
|
|
@@ -53,35 +56,41 @@ const INSTALL_ROOT =
|
|
|
53
56
|
process.env.AGENT_TOOLS_HOME || path.join(os.homedir(), ".agent-tools");
|
|
54
57
|
const META_KEY = "_agentTools";
|
|
55
58
|
const META_VERSION = 1;
|
|
59
|
+
// Everything copied into ~/.agent-tools is built output from dist/ (see
|
|
60
|
+
// scripts/build.mjs); integrations/ holds the sources.
|
|
56
61
|
const SOURCE = {
|
|
57
|
-
codexUsageHook: path.join(REPO_ROOT, "
|
|
58
|
-
usageScript: path.join(REPO_ROOT, "
|
|
62
|
+
codexUsageHook: path.join(REPO_ROOT, "dist", "usage", "codex-hook.mjs"),
|
|
63
|
+
usageScript: path.join(REPO_ROOT, "dist", "usage", "core.mjs"),
|
|
64
|
+
usageCli: path.join(REPO_ROOT, "dist", "usage", "cli.mjs"),
|
|
59
65
|
config: path.join(REPO_ROOT, "config.default.jsonc"),
|
|
60
|
-
claudeStatusline: path.join(REPO_ROOT, "
|
|
61
|
-
opencodeUsagePlugin: path.join(REPO_ROOT, "
|
|
62
|
-
opencodeUsageTui: path.join(REPO_ROOT, "
|
|
66
|
+
claudeStatusline: path.join(REPO_ROOT, "dist", "statusline", "claude-statusline.mjs"),
|
|
67
|
+
opencodeUsagePlugin: path.join(REPO_ROOT, "dist", "usage", "opencode-plugin.mjs"),
|
|
68
|
+
opencodeUsageTui: path.join(REPO_ROOT, "dist", "usage", "opencode-tui.mjs"),
|
|
63
69
|
};
|
|
64
70
|
const RUNTIME = {
|
|
65
|
-
codexUsageHook: path.join(INSTALL_ROOT, "
|
|
66
|
-
usageScript: path.join(INSTALL_ROOT, "
|
|
71
|
+
codexUsageHook: path.join(INSTALL_ROOT, "dist", "usage", "codex-hook.mjs"),
|
|
72
|
+
usageScript: path.join(INSTALL_ROOT, "dist", "usage", "core.mjs"),
|
|
73
|
+
usageCli: path.join(INSTALL_ROOT, "dist", "usage", "cli.mjs"),
|
|
67
74
|
config: path.join(INSTALL_ROOT, "config.jsonc"),
|
|
68
|
-
claudeStatusline: path.join(INSTALL_ROOT, "
|
|
69
|
-
opencodeUsagePlugin: path.join(INSTALL_ROOT, "
|
|
70
|
-
opencodeUsageTui: path.join(INSTALL_ROOT, "
|
|
75
|
+
claudeStatusline: path.join(INSTALL_ROOT, "dist", "statusline", "claude-statusline.mjs"),
|
|
76
|
+
opencodeUsagePlugin: path.join(INSTALL_ROOT, "dist", "usage", "opencode-plugin.mjs"),
|
|
77
|
+
opencodeUsageTui: path.join(INSTALL_ROOT, "dist", "usage", "opencode-tui.mjs"),
|
|
71
78
|
};
|
|
72
79
|
const ALL_CAPS = ["statusline", "usage", "vision"];
|
|
73
80
|
const ALL_AGENTS = ["claude", "codex", "opencode"];
|
|
74
81
|
const AGENT_CAPS = {
|
|
75
|
-
claude: ["statusline", "vision"],
|
|
82
|
+
claude: ["statusline", "usage", "vision"],
|
|
76
83
|
codex: ["usage", "vision"],
|
|
77
84
|
opencode: ["usage", "vision"],
|
|
78
85
|
};
|
|
79
86
|
const VISION_MCP_NAME = "agent-tools-vision";
|
|
80
87
|
const VISION_SKILL_NAME = "at-vision";
|
|
81
|
-
// The at-vision skill ships inside the vision
|
|
88
|
+
// The at-vision skill ships inside the vision capability dir (integrations/vision),
|
|
82
89
|
// not skills/: it is unusable without the MCP server, so it must not surface
|
|
83
90
|
// as an independently installable skill.
|
|
84
|
-
const VISION_SKILL_SRC = path.join(REPO_ROOT, "
|
|
91
|
+
const VISION_SKILL_SRC = path.join(REPO_ROOT, "integrations", "vision", "skills", VISION_SKILL_NAME);
|
|
92
|
+
const USAGE_SKILL_NAME = "at-usage";
|
|
93
|
+
const USAGE_SKILL_SRC = path.join(REPO_ROOT, "integrations", "usage", "skills", USAGE_SKILL_NAME);
|
|
85
94
|
const VISION_BUNDLED_MCP_SERVER = path.join(REPO_ROOT, "dist", "vision", "mcp-server.mjs");
|
|
86
95
|
const VISION_BUNDLED_CLI = path.join(REPO_ROOT, "dist", "vision", "cli.mjs");
|
|
87
96
|
|
|
@@ -130,7 +139,11 @@ function stripJsonComments(input) {
|
|
|
130
139
|
function readJsonc(file) {
|
|
131
140
|
if (!fs.existsSync(file)) return {};
|
|
132
141
|
const raw = fs.readFileSync(file, "utf8").replace(/^\uFEFF/, "");
|
|
133
|
-
|
|
142
|
+
if (!raw.trim()) return {};
|
|
143
|
+
const errors = [];
|
|
144
|
+
const parsed = parseJsonc(raw, errors, { allowTrailingComma: true });
|
|
145
|
+
if (errors.length > 0) throw new Error(`Cannot parse ${file} as JSONC`);
|
|
146
|
+
return parsed ?? {};
|
|
134
147
|
}
|
|
135
148
|
|
|
136
149
|
function headerComments(text) {
|
|
@@ -173,7 +186,7 @@ function isOpenCodeUsageTuiEntry(entry) {
|
|
|
173
186
|
const spec = Array.isArray(entry) ? entry[0] : entry;
|
|
174
187
|
return (
|
|
175
188
|
typeof spec === "string" &&
|
|
176
|
-
/\/
|
|
189
|
+
/\/dist\/usage\/opencode-tui\.mjs$/i.test(spec.replace(/\\/g, "/"))
|
|
177
190
|
);
|
|
178
191
|
}
|
|
179
192
|
|
|
@@ -254,9 +267,15 @@ function installRuntimeAssets(opts) {
|
|
|
254
267
|
addFile(SOURCE.opencodeUsageTui, RUNTIME.opencodeUsageTui);
|
|
255
268
|
}
|
|
256
269
|
addFile(SOURCE.usageScript, RUNTIME.usageScript);
|
|
270
|
+
addFile(SOURCE.usageCli, RUNTIME.usageCli);
|
|
257
271
|
addFile(SOURCE.config, RUNTIME.config, { mergeJsonc: true });
|
|
258
272
|
}
|
|
259
273
|
if (files.length === 0) return;
|
|
274
|
+
for (const [src] of files) {
|
|
275
|
+
if (!fs.existsSync(src)) {
|
|
276
|
+
throw new Error(`Missing built artifact ${src}. Run npm run build.`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
260
279
|
console.log(`runtime: ${INSTALL_ROOT}`);
|
|
261
280
|
for (const [src, dest, options] of files) copyRuntimeFile(src, dest, opts.dryRun, options);
|
|
262
281
|
}
|
|
@@ -264,7 +283,7 @@ function installRuntimeAssets(opts) {
|
|
|
264
283
|
function parseArgs(argv) {
|
|
265
284
|
const opts = {
|
|
266
285
|
agents: [],
|
|
267
|
-
|
|
286
|
+
integrations: [],
|
|
268
287
|
settings: null,
|
|
269
288
|
codexHooks: null,
|
|
270
289
|
opencodeConfigDir: null,
|
|
@@ -315,15 +334,15 @@ function parseArgs(argv) {
|
|
|
315
334
|
console.error(`Unknown option: ${a}`);
|
|
316
335
|
process.exit(2);
|
|
317
336
|
}
|
|
318
|
-
opts.
|
|
337
|
+
opts.integrations.push(a);
|
|
319
338
|
}
|
|
320
339
|
}
|
|
321
340
|
if (opts.agents.length === 0) opts.agents = ["claude"];
|
|
322
|
-
if (!opts.help && opts.
|
|
341
|
+
if (!opts.help && opts.integrations.length === 0) {
|
|
323
342
|
console.error(`Missing capability (available: ${ALL_CAPS.join(", ")})`);
|
|
324
343
|
process.exit(2);
|
|
325
344
|
}
|
|
326
|
-
for (const name of opts.
|
|
345
|
+
for (const name of opts.integrations) {
|
|
327
346
|
if (!ALL_CAPS.includes(name)) {
|
|
328
347
|
console.error(`Unknown capability: ${name} (available: ${ALL_CAPS.join(", ")})`);
|
|
329
348
|
process.exit(2);
|
|
@@ -333,14 +352,14 @@ function parseArgs(argv) {
|
|
|
333
352
|
}
|
|
334
353
|
|
|
335
354
|
function wants(opts, cap) {
|
|
336
|
-
return opts.
|
|
355
|
+
return opts.integrations.length === 0 || opts.integrations.includes(cap);
|
|
337
356
|
}
|
|
338
357
|
|
|
339
358
|
function validateAgentCapabilities(opts) {
|
|
340
359
|
const invalid = [];
|
|
341
360
|
for (const agent of opts.agents) {
|
|
342
361
|
const supported = AGENT_CAPS[agent] || [];
|
|
343
|
-
for (const cap of opts.
|
|
362
|
+
for (const cap of opts.integrations) {
|
|
344
363
|
if (!supported.includes(cap)) invalid.push(`${cap} -a ${agent}`);
|
|
345
364
|
}
|
|
346
365
|
}
|
|
@@ -351,7 +370,6 @@ function validateAgentCapabilities(opts) {
|
|
|
351
370
|
for (const agent of ALL_AGENTS) {
|
|
352
371
|
console.error(` ${agent}: ${AGENT_CAPS[agent].join(", ")}`);
|
|
353
372
|
}
|
|
354
|
-
console.error("Claude API usage is refreshed by the statusline capability; use `statusline -a claude`.");
|
|
355
373
|
process.exit(2);
|
|
356
374
|
}
|
|
357
375
|
|
|
@@ -409,7 +427,7 @@ function isOurProviderUsageEntry(entry) {
|
|
|
409
427
|
entry.hooks.some(
|
|
410
428
|
(h) =>
|
|
411
429
|
typeof h?.command === "string" &&
|
|
412
|
-
/(?:^|[/\\])
|
|
430
|
+
/(?:^|[/\\])codex-hook\.mjs(?:["\s]|$)/.test(h.command)
|
|
413
431
|
)
|
|
414
432
|
);
|
|
415
433
|
}
|
|
@@ -434,25 +452,26 @@ function applyProviderUsage(cfg, { remove }) {
|
|
|
434
452
|
|
|
435
453
|
// ---- Vision capability helpers. ----
|
|
436
454
|
|
|
437
|
-
|
|
455
|
+
// The installed tree mirrors the package tree, so the bundled runtime lands in
|
|
456
|
+
// dist/vision just like in the package.
|
|
457
|
+
const VISION_RUNTIME_DIR = path.join(INSTALL_ROOT, "dist", "vision");
|
|
438
458
|
const VISION_RATE_LIMIT_STATE = path.join(INSTALL_ROOT, "cache", "vision-rate-limit.json");
|
|
439
459
|
const VISION_DIST_DIR = path.join(REPO_ROOT, "dist", "vision");
|
|
440
460
|
const VISION_RUNTIME_SERVER = path.join(VISION_RUNTIME_DIR, "mcp-server.mjs");
|
|
441
461
|
const VISION_RUNTIME_CLI = path.join(VISION_RUNTIME_DIR, "cli.mjs");
|
|
442
|
-
const
|
|
443
|
-
VISION_RUNTIME_DIR,
|
|
444
|
-
"plugins",
|
|
445
|
-
"vision",
|
|
446
|
-
"mcp-server.mjs"
|
|
447
|
-
);
|
|
448
|
-
const VISION_SKILL_MARKER = ".agent-tools-managed.json";
|
|
462
|
+
const SKILL_MARKER = ".agent-tools-managed.json";
|
|
449
463
|
const VISION_SKILL_MARKER_DATA = Object.freeze({
|
|
450
464
|
owner: "@kairyou/agent-tools",
|
|
451
465
|
capability: "vision",
|
|
452
466
|
artifact: "skill",
|
|
453
467
|
});
|
|
468
|
+
const USAGE_SKILL_MARKER_DATA = Object.freeze({
|
|
469
|
+
owner: "@kairyou/agent-tools",
|
|
470
|
+
capability: "usage",
|
|
471
|
+
artifact: "skill",
|
|
472
|
+
});
|
|
454
473
|
// Vision is bundled at release time. Install atomically swaps two self-contained
|
|
455
|
-
// entry files into ~/.agent-tools/vision
|
|
474
|
+
// entry files into ~/.agent-tools/dist/vision, so hosts never run npm and a
|
|
456
475
|
// failed update cannot destroy the previously working runtime.
|
|
457
476
|
function visionMcpCommand() {
|
|
458
477
|
return { command: "node", args: [fwd(VISION_RUNTIME_SERVER)] };
|
|
@@ -472,14 +491,17 @@ function sameFilePath(left, right) {
|
|
|
472
491
|
return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
473
492
|
}
|
|
474
493
|
|
|
494
|
+
function isVisionServerPath(value) {
|
|
495
|
+
return sameFilePath(value, VISION_RUNTIME_SERVER);
|
|
496
|
+
}
|
|
497
|
+
|
|
475
498
|
function isClaudeVisionMcp(value, mcp = visionMcpCommand()) {
|
|
476
499
|
return Boolean(
|
|
477
500
|
value &&
|
|
478
501
|
value.command === mcp.command &&
|
|
479
502
|
Array.isArray(value.args) &&
|
|
480
503
|
value.args.length === 1 &&
|
|
481
|
-
(
|
|
482
|
-
sameFilePath(value.args[0], VISION_LEGACY_RUNTIME_SERVER))
|
|
504
|
+
isVisionServerPath(value.args[0])
|
|
483
505
|
);
|
|
484
506
|
}
|
|
485
507
|
|
|
@@ -490,8 +512,7 @@ function isOpenCodeVisionMcp(value, mcp = visionMcpCommand()) {
|
|
|
490
512
|
Array.isArray(value.command) &&
|
|
491
513
|
value.command.length === 2 &&
|
|
492
514
|
value.command[0] === mcp.command &&
|
|
493
|
-
(
|
|
494
|
-
sameFilePath(value.command[1], VISION_LEGACY_RUNTIME_SERVER))
|
|
515
|
+
isVisionServerPath(value.command[1])
|
|
495
516
|
);
|
|
496
517
|
}
|
|
497
518
|
|
|
@@ -504,7 +525,7 @@ function installVisionRuntime(opts) {
|
|
|
504
525
|
}
|
|
505
526
|
for (const name of ["mcp-server.mjs", "cli.mjs"]) {
|
|
506
527
|
if (!fs.existsSync(path.join(VISION_DIST_DIR, name))) {
|
|
507
|
-
throw new Error(`Missing bundled vision runtime ${path.join(VISION_DIST_DIR, name)}. Run npm run build
|
|
528
|
+
throw new Error(`Missing bundled vision runtime ${path.join(VISION_DIST_DIR, name)}. Run npm run build.`);
|
|
508
529
|
}
|
|
509
530
|
}
|
|
510
531
|
|
|
@@ -551,37 +572,26 @@ function installVisionRuntime(opts) {
|
|
|
551
572
|
}
|
|
552
573
|
}
|
|
553
574
|
|
|
554
|
-
function
|
|
555
|
-
const marker = path.join(dest,
|
|
575
|
+
function isManagedSkillDir(dest, markerData) {
|
|
576
|
+
const marker = path.join(dest, SKILL_MARKER);
|
|
556
577
|
if (!fs.existsSync(marker)) return false;
|
|
557
578
|
try {
|
|
558
579
|
const value = JSON.parse(fs.readFileSync(marker, "utf8"));
|
|
559
|
-
return Object.entries(
|
|
580
|
+
return Object.entries(markerData).every(([key, expected]) => value?.[key] === expected);
|
|
560
581
|
} catch {
|
|
561
582
|
return false;
|
|
562
583
|
}
|
|
563
584
|
}
|
|
564
585
|
|
|
565
|
-
function
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
/^---\s*[\s\S]*?^name:\s*at-vision\s*$/m.test(skill) &&
|
|
572
|
-
skill.includes("# Visual Reasoning Policy") &&
|
|
573
|
-
skill.includes("`inspect_image` MCP tool (server `agent-tools-vision`)")
|
|
574
|
-
);
|
|
575
|
-
} catch {
|
|
576
|
-
return false;
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
function installSkillDir(skillsRoot, { remove, dryRun }) {
|
|
581
|
-
const dest = path.join(skillsRoot, VISION_SKILL_NAME);
|
|
586
|
+
function installManagedSkillDir(
|
|
587
|
+
skillsRoot,
|
|
588
|
+
{ name, source, markerData, replacements, remove, dryRun }
|
|
589
|
+
) {
|
|
590
|
+
const dest = path.join(skillsRoot, name);
|
|
591
|
+
const isManaged = () => isManagedSkillDir(dest, markerData);
|
|
582
592
|
if (remove) {
|
|
583
593
|
if (!fs.existsSync(dest)) return;
|
|
584
|
-
if (!
|
|
594
|
+
if (!isManaged()) {
|
|
585
595
|
console.log(` kept unmanaged ${dest}`);
|
|
586
596
|
return;
|
|
587
597
|
}
|
|
@@ -593,38 +603,61 @@ function installSkillDir(skillsRoot, { remove, dryRun }) {
|
|
|
593
603
|
console.log(` removed ${dest}`);
|
|
594
604
|
return;
|
|
595
605
|
}
|
|
596
|
-
if (fs.existsSync(dest) && !
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
`Move or remove it, then re-run the install.`
|
|
603
|
-
);
|
|
604
|
-
process.exit(1);
|
|
605
|
-
}
|
|
606
|
+
if (fs.existsSync(dest) && !isManaged()) {
|
|
607
|
+
console.error(
|
|
608
|
+
` Refusing to overwrite existing unowned skill directory ${dest}. ` +
|
|
609
|
+
`Move or remove it, then re-run the install.`
|
|
610
|
+
);
|
|
611
|
+
process.exit(1);
|
|
606
612
|
}
|
|
607
613
|
if (dryRun) {
|
|
608
|
-
console.log(` [dry-run] would copy ${
|
|
614
|
+
console.log(` [dry-run] would copy ${source} -> ${dest}`);
|
|
609
615
|
return;
|
|
610
616
|
}
|
|
611
617
|
fs.rmSync(dest, { recursive: true, force: true });
|
|
612
|
-
fs.cpSync(
|
|
618
|
+
fs.cpSync(source, dest, { recursive: true });
|
|
613
619
|
const skillFile = path.join(dest, "SKILL.md");
|
|
614
|
-
|
|
615
|
-
const token
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
620
|
+
let skill = fs.readFileSync(skillFile, "utf8");
|
|
621
|
+
for (const [token, value] of Object.entries(replacements)) {
|
|
622
|
+
if (!skill.includes(token)) {
|
|
623
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
624
|
+
throw new Error(`${name} skill template is missing ${token}`);
|
|
625
|
+
}
|
|
626
|
+
skill = skill.replaceAll(token, value);
|
|
619
627
|
}
|
|
620
|
-
fs.writeFileSync(skillFile, skill
|
|
628
|
+
fs.writeFileSync(skillFile, skill);
|
|
621
629
|
fs.writeFileSync(
|
|
622
|
-
path.join(dest,
|
|
623
|
-
JSON.stringify(
|
|
630
|
+
path.join(dest, SKILL_MARKER),
|
|
631
|
+
JSON.stringify(markerData, null, 2) + "\n"
|
|
624
632
|
);
|
|
625
633
|
console.log(` wrote ${dest}`);
|
|
626
634
|
}
|
|
627
635
|
|
|
636
|
+
function installVisionSkillDir(skillsRoot, { remove, dryRun }) {
|
|
637
|
+
installManagedSkillDir(skillsRoot, {
|
|
638
|
+
name: VISION_SKILL_NAME,
|
|
639
|
+
source: VISION_SKILL_SRC,
|
|
640
|
+
markerData: VISION_SKILL_MARKER_DATA,
|
|
641
|
+
replacements: { "{{VISION_CLI_PATH}}": fwd(VISION_RUNTIME_CLI) },
|
|
642
|
+
remove,
|
|
643
|
+
dryRun,
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function installUsageSkillDir(skillsRoot, agent, { remove, dryRun }) {
|
|
648
|
+
installManagedSkillDir(skillsRoot, {
|
|
649
|
+
name: USAGE_SKILL_NAME,
|
|
650
|
+
source: USAGE_SKILL_SRC,
|
|
651
|
+
markerData: USAGE_SKILL_MARKER_DATA,
|
|
652
|
+
replacements: {
|
|
653
|
+
"{{USAGE_CLI_PATH}}": fwd(RUNTIME.usageCli),
|
|
654
|
+
"{{USAGE_AGENT}}": agent,
|
|
655
|
+
},
|
|
656
|
+
remove,
|
|
657
|
+
dryRun,
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
|
|
628
661
|
const CODEX_VISION_BEGIN = "# >>> agent-tools vision >>>";
|
|
629
662
|
const CODEX_VISION_END = "# <<< agent-tools vision <<<";
|
|
630
663
|
|
|
@@ -691,7 +724,7 @@ function runClaudeVision(opts) {
|
|
|
691
724
|
} else {
|
|
692
725
|
console.log(` no ${VISION_MCP_NAME} MCP entry found; nothing to remove.`);
|
|
693
726
|
}
|
|
694
|
-
|
|
727
|
+
installVisionSkillDir(skillsDir, { remove: true, dryRun: opts.dryRun });
|
|
695
728
|
console.log(" - vision");
|
|
696
729
|
return;
|
|
697
730
|
}
|
|
@@ -704,12 +737,21 @@ function runClaudeVision(opts) {
|
|
|
704
737
|
cfg.mcpServers = cfg.mcpServers || {};
|
|
705
738
|
cfg.mcpServers[VISION_MCP_NAME] = { type: "stdio", command: mcp.command, args: mcp.args };
|
|
706
739
|
writeJson(claudeJson, cfg, opts.dryRun);
|
|
707
|
-
|
|
740
|
+
installVisionSkillDir(skillsDir, { remove: false, dryRun: opts.dryRun });
|
|
708
741
|
console.log(" + vision (inspect_image MCP + at-vision skill)");
|
|
709
742
|
}
|
|
710
743
|
|
|
711
744
|
function runClaude(opts) {
|
|
712
745
|
if (wants(opts, "vision")) runClaudeVision(opts);
|
|
746
|
+
if (wants(opts, "usage")) {
|
|
747
|
+
const skillsDir = opts.claudeSkillsDir || path.join(os.homedir(), ".claude", "skills");
|
|
748
|
+
console.log(`claude usage: ${skillsDir}`);
|
|
749
|
+
installUsageSkillDir(skillsDir, "claude", {
|
|
750
|
+
remove: opts.uninstall,
|
|
751
|
+
dryRun: opts.dryRun,
|
|
752
|
+
});
|
|
753
|
+
console.log(opts.uninstall ? " - usage skill" : " + usage skill (at-usage)");
|
|
754
|
+
}
|
|
713
755
|
if (!wants(opts, "statusline")) return;
|
|
714
756
|
const settings = opts.settings || path.join(os.homedir(), ".claude", "settings.json");
|
|
715
757
|
console.log(`claude (global): ${settings}`);
|
|
@@ -758,7 +800,7 @@ function runCodexVision(opts) {
|
|
|
758
800
|
dryRun: opts.dryRun,
|
|
759
801
|
mcp: visionMcpCommand(),
|
|
760
802
|
});
|
|
761
|
-
|
|
803
|
+
installVisionSkillDir(skillsDir, { remove: opts.uninstall, dryRun: opts.dryRun });
|
|
762
804
|
console.log(opts.uninstall ? " - vision" : " + vision (inspect_image MCP + at-vision skill)");
|
|
763
805
|
}
|
|
764
806
|
|
|
@@ -767,6 +809,12 @@ function runCodex(opts) {
|
|
|
767
809
|
if (!wants(opts, "usage")) {
|
|
768
810
|
return;
|
|
769
811
|
}
|
|
812
|
+
const skillsDir = opts.codexSkillsDir || path.join(os.homedir(), ".agents", "skills");
|
|
813
|
+
console.log(`codex usage skill: ${skillsDir}`);
|
|
814
|
+
installUsageSkillDir(skillsDir, "codex", {
|
|
815
|
+
remove: opts.uninstall,
|
|
816
|
+
dryRun: opts.dryRun,
|
|
817
|
+
});
|
|
770
818
|
const file = opts.codexHooks || path.join(os.homedir(), ".codex", "hooks.json");
|
|
771
819
|
console.log(`codex (global): ${file}`);
|
|
772
820
|
const cfg = readJson(file);
|
|
@@ -854,7 +902,7 @@ function runOpencodeVision(opts) {
|
|
|
854
902
|
dryRun: opts.dryRun,
|
|
855
903
|
mcp: visionMcpCommand(),
|
|
856
904
|
});
|
|
857
|
-
|
|
905
|
+
installVisionSkillDir(skillsDir, { remove: opts.uninstall, dryRun: opts.dryRun });
|
|
858
906
|
console.log(opts.uninstall ? " - vision" : " + vision (inspect_image MCP + at-vision skill)");
|
|
859
907
|
}
|
|
860
908
|
|
|
@@ -905,10 +953,7 @@ function visionRuntimeHasRemainingReference(opts) {
|
|
|
905
953
|
const end = text.indexOf(CODEX_VISION_END, start + CODEX_VISION_BEGIN.length);
|
|
906
954
|
if (start !== -1 && end !== -1) {
|
|
907
955
|
const block = text.slice(start, end);
|
|
908
|
-
if (
|
|
909
|
-
block.includes(fwd(VISION_RUNTIME_SERVER)) ||
|
|
910
|
-
block.includes(fwd(VISION_LEGACY_RUNTIME_SERVER))
|
|
911
|
-
) {
|
|
956
|
+
if (block.includes(fwd(VISION_RUNTIME_SERVER))) {
|
|
912
957
|
return true;
|
|
913
958
|
}
|
|
914
959
|
}
|
|
@@ -948,7 +993,7 @@ function cleanupVisionRuntimeIfUnused(opts) {
|
|
|
948
993
|
try {
|
|
949
994
|
fs.rmdirSync(path.dirname(VISION_RATE_LIMIT_STATE));
|
|
950
995
|
} catch {
|
|
951
|
-
// The shared cache directory may contain state for other
|
|
996
|
+
// The shared cache directory may contain state for other integrations.
|
|
952
997
|
}
|
|
953
998
|
}
|
|
954
999
|
console.log(` removed unused vision runtime ${VISION_RUNTIME_DIR}`);
|
|
@@ -985,7 +1030,7 @@ function main() {
|
|
|
985
1030
|
}
|
|
986
1031
|
|
|
987
1032
|
// Standalone vision commands dispatch before capability parsing so image
|
|
988
|
-
// paths and questions are never mistaken for
|
|
1033
|
+
// paths and questions are never mistaken for integrations.
|
|
989
1034
|
const subcommand = process.argv[2];
|
|
990
1035
|
if (subcommand === "inspect-image") {
|
|
991
1036
|
const { runInspectImageCli } = await import(
|
package/scripts/release.mjs
CHANGED
|
@@ -1,19 +1,55 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Release helper for
|
|
2
|
+
// Release helper for this package.
|
|
3
3
|
//
|
|
4
4
|
// Usage:
|
|
5
5
|
// npm run release [-- minor|major] # default: patch
|
|
6
6
|
// npm run release -- --dry-run # npm test + preview tarball, no bump/publish
|
|
7
7
|
//
|
|
8
|
-
// Steps: clean-tree check -> npm test -> npm version -> npm publish -> git push
|
|
8
|
+
// Steps: clean-tree check -> npm auth check -> npm test -> npm version -> npm publish -> git push
|
|
9
9
|
//
|
|
10
|
-
// Release when scripts/
|
|
10
|
+
// Release when scripts/ integrations/ config.default.jsonc or
|
|
11
11
|
// package.json deps change — npx users only get these from the published package.
|
|
12
12
|
// (skills/ ships from GitHub via `npx skills add`; no release needed.)
|
|
13
13
|
|
|
14
14
|
import { spawnSync } from "node:child_process";
|
|
15
|
+
import { readFileSync } from "node:fs";
|
|
15
16
|
|
|
16
17
|
const BUMPS = ["patch", "minor", "major"];
|
|
18
|
+
const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
19
|
+
const PACKAGE_NAME = packageJson.name;
|
|
20
|
+
|
|
21
|
+
if (typeof PACKAGE_NAME !== "string" || PACKAGE_NAME.trim() === "") {
|
|
22
|
+
console.error("package.json must define a non-empty name before releasing.");
|
|
23
|
+
process.exit(2);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function readNpmConfig(key) {
|
|
27
|
+
const npmExecPath = process.env.npm_execpath;
|
|
28
|
+
const result = npmExecPath
|
|
29
|
+
? spawnSync(process.execPath, [npmExecPath, "config", "get", key], { encoding: "utf8" })
|
|
30
|
+
: spawnSync("npm", ["config", "get", key], { encoding: "utf8", shell: true });
|
|
31
|
+
|
|
32
|
+
if (result.status !== 0) {
|
|
33
|
+
const detail = result.stderr?.trim() || result.error?.message || "npm config exited unexpectedly";
|
|
34
|
+
console.error(`Unable to read npm config ${key}:\n${detail}`);
|
|
35
|
+
process.exit(result.status ?? 2);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const value = result.stdout?.trim();
|
|
39
|
+
return value && value !== "undefined" && value !== "null" ? value : undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const packageScope = PACKAGE_NAME.match(/^(@[^/]+)\//)?.[1];
|
|
43
|
+
const REGISTRY = (packageScope && readNpmConfig(`${packageScope}:registry`)) || readNpmConfig("registry");
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const registryUrl = new URL(REGISTRY);
|
|
47
|
+
if (!["http:", "https:"].includes(registryUrl.protocol)) throw new Error("unsupported protocol");
|
|
48
|
+
} catch {
|
|
49
|
+
console.error("npm registry config must be a valid HTTP(S) URL before releasing.");
|
|
50
|
+
process.exit(2);
|
|
51
|
+
}
|
|
52
|
+
|
|
17
53
|
let bump = "patch";
|
|
18
54
|
let dryRun = false;
|
|
19
55
|
for (const arg of process.argv.slice(2)) {
|
|
@@ -26,20 +62,40 @@ for (const arg of process.argv.slice(2)) {
|
|
|
26
62
|
}
|
|
27
63
|
|
|
28
64
|
function run(command, opts = {}) {
|
|
65
|
+
const { onFailure, ...spawnOptions } = opts;
|
|
29
66
|
console.log(`\n> ${command}`);
|
|
30
|
-
const result = spawnSync(command, { stdio: "inherit", shell: true, ...
|
|
67
|
+
const result = spawnSync(command, { stdio: "inherit", shell: true, ...spawnOptions });
|
|
31
68
|
if (result.status !== 0) {
|
|
32
69
|
console.error(`\nAborted: \`${command}\` exited with ${result.status}.`);
|
|
70
|
+
onFailure?.();
|
|
33
71
|
process.exit(result.status ?? 1);
|
|
34
72
|
}
|
|
35
73
|
}
|
|
36
74
|
|
|
75
|
+
function printCommands(message, commands) {
|
|
76
|
+
console.error(`\n${message}\n`);
|
|
77
|
+
for (const command of commands) console.error(` ${command}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
37
80
|
const status = spawnSync("git status --porcelain", { shell: true, encoding: "utf8" });
|
|
38
81
|
if (!dryRun && status.stdout.trim() !== "") {
|
|
39
82
|
console.error("Working tree is not clean — commit or stash first:\n" + status.stdout);
|
|
40
83
|
process.exit(1);
|
|
41
84
|
}
|
|
42
85
|
|
|
86
|
+
if (!dryRun) {
|
|
87
|
+
// Fail before npm version creates a commit and tag when the saved token is stale.
|
|
88
|
+
run(`npm whoami --registry=${REGISTRY}`, {
|
|
89
|
+
onFailure() {
|
|
90
|
+
printCommands("npm authentication is missing or expired. Log in, verify the account, then retry:", [
|
|
91
|
+
`npm login --registry=${REGISTRY}`,
|
|
92
|
+
`npm whoami --registry=${REGISTRY}`,
|
|
93
|
+
`npm run release -- ${bump}`,
|
|
94
|
+
]);
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
43
99
|
run("npm test");
|
|
44
100
|
|
|
45
101
|
if (dryRun) {
|
|
@@ -50,7 +106,21 @@ if (dryRun) {
|
|
|
50
106
|
}
|
|
51
107
|
|
|
52
108
|
run(`npm version ${bump}`);
|
|
53
|
-
run("npm publish"
|
|
109
|
+
run("npm publish", {
|
|
110
|
+
onFailure() {
|
|
111
|
+
printCommands(
|
|
112
|
+
"npm publish failed after the version commit and tag were created. Do not run the release command again. Fix authentication/permissions, then resume:",
|
|
113
|
+
[
|
|
114
|
+
`npm login --registry=${REGISTRY}`,
|
|
115
|
+
`npm whoami --registry=${REGISTRY}`,
|
|
116
|
+
`npm owner ls ${PACKAGE_NAME}`,
|
|
117
|
+
"npm publish",
|
|
118
|
+
`npm view ${PACKAGE_NAME} version`,
|
|
119
|
+
"git push --follow-tags",
|
|
120
|
+
],
|
|
121
|
+
);
|
|
122
|
+
},
|
|
123
|
+
});
|
|
54
124
|
run("git push --follow-tags");
|
|
55
125
|
|
|
56
126
|
console.log("\nRelease complete.");
|
package/hooks/claude/.gitkeep
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
package/hooks/codex/.gitkeep
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
package/hooks/common/.gitkeep
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
package/hooks/opencode/.gitkeep
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
package/scripts/build-vision.mjs
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
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
|
-
import { build } from "esbuild";
|
|
7
|
-
|
|
8
|
-
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
9
|
-
const OUT_DIR = path.join(ROOT, "dist", "vision");
|
|
10
|
-
const STAGE_DIR = path.join(ROOT, "dist", `.vision-build-${process.pid}-${Date.now()}`);
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
await build({
|
|
14
|
-
entryPoints: {
|
|
15
|
-
"mcp-server": path.join(ROOT, "plugins", "vision", "mcp-server.mjs"),
|
|
16
|
-
cli: path.join(ROOT, "lib", "vision", "cli.mjs"),
|
|
17
|
-
},
|
|
18
|
-
outdir: STAGE_DIR,
|
|
19
|
-
bundle: true,
|
|
20
|
-
platform: "node",
|
|
21
|
-
format: "esm",
|
|
22
|
-
target: "node22",
|
|
23
|
-
mainFields: ["module", "main"],
|
|
24
|
-
outExtension: { ".js": ".mjs" },
|
|
25
|
-
packages: "bundle",
|
|
26
|
-
sourcemap: false,
|
|
27
|
-
legalComments: "none",
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
fs.rmSync(OUT_DIR, { recursive: true, force: true });
|
|
31
|
-
fs.renameSync(STAGE_DIR, OUT_DIR);
|
|
32
|
-
console.log(`built ${path.relative(ROOT, OUT_DIR)}`);
|
|
33
|
-
} finally {
|
|
34
|
-
fs.rmSync(STAGE_DIR, { recursive: true, force: true });
|
|
35
|
-
}
|
package/statusline/.gitkeep
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|