@kairyou/agent-tools 0.2.0 → 0.3.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 +75 -95
- package/README.zh-CN.md +71 -92
- 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} +3 -1
- 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} +4 -0
- 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 +5 -9
- package/scripts/build-vision.mjs +2 -2
- package/scripts/install.mjs +122 -88
- 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/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)
|
|
@@ -54,34 +57,38 @@ const INSTALL_ROOT =
|
|
|
54
57
|
const META_KEY = "_agentTools";
|
|
55
58
|
const META_VERSION = 1;
|
|
56
59
|
const SOURCE = {
|
|
57
|
-
codexUsageHook: path.join(REPO_ROOT, "
|
|
58
|
-
usageScript: path.join(REPO_ROOT, "
|
|
60
|
+
codexUsageHook: path.join(REPO_ROOT, "integrations", "usage", "codex-hook.mjs"),
|
|
61
|
+
usageScript: path.join(REPO_ROOT, "integrations", "usage", "core.mjs"),
|
|
62
|
+
usageCli: path.join(REPO_ROOT, "integrations", "usage", "cli.mjs"),
|
|
59
63
|
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, "
|
|
64
|
+
claudeStatusline: path.join(REPO_ROOT, "integrations", "statusline", "claude-statusline.mjs"),
|
|
65
|
+
opencodeUsagePlugin: path.join(REPO_ROOT, "integrations", "usage", "opencode-plugin.mjs"),
|
|
66
|
+
opencodeUsageTui: path.join(REPO_ROOT, "integrations", "usage", "opencode-tui.mjs"),
|
|
63
67
|
};
|
|
64
68
|
const RUNTIME = {
|
|
65
|
-
codexUsageHook: path.join(INSTALL_ROOT, "
|
|
66
|
-
usageScript: path.join(INSTALL_ROOT, "
|
|
69
|
+
codexUsageHook: path.join(INSTALL_ROOT, "integrations", "usage", "codex-hook.mjs"),
|
|
70
|
+
usageScript: path.join(INSTALL_ROOT, "integrations", "usage", "core.mjs"),
|
|
71
|
+
usageCli: path.join(INSTALL_ROOT, "integrations", "usage", "cli.mjs"),
|
|
67
72
|
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, "
|
|
73
|
+
claudeStatusline: path.join(INSTALL_ROOT, "integrations", "statusline", "claude-statusline.mjs"),
|
|
74
|
+
opencodeUsagePlugin: path.join(INSTALL_ROOT, "integrations", "usage", "opencode-plugin.mjs"),
|
|
75
|
+
opencodeUsageTui: path.join(INSTALL_ROOT, "integrations", "usage", "opencode-tui.mjs"),
|
|
71
76
|
};
|
|
72
77
|
const ALL_CAPS = ["statusline", "usage", "vision"];
|
|
73
78
|
const ALL_AGENTS = ["claude", "codex", "opencode"];
|
|
74
79
|
const AGENT_CAPS = {
|
|
75
|
-
claude: ["statusline", "vision"],
|
|
80
|
+
claude: ["statusline", "usage", "vision"],
|
|
76
81
|
codex: ["usage", "vision"],
|
|
77
82
|
opencode: ["usage", "vision"],
|
|
78
83
|
};
|
|
79
84
|
const VISION_MCP_NAME = "agent-tools-vision";
|
|
80
85
|
const VISION_SKILL_NAME = "at-vision";
|
|
81
|
-
// The at-vision skill ships inside the vision
|
|
86
|
+
// The at-vision skill ships inside the vision capability dir (integrations/vision),
|
|
82
87
|
// not skills/: it is unusable without the MCP server, so it must not surface
|
|
83
88
|
// as an independently installable skill.
|
|
84
|
-
const VISION_SKILL_SRC = path.join(REPO_ROOT, "
|
|
89
|
+
const VISION_SKILL_SRC = path.join(REPO_ROOT, "integrations", "vision", "skills", VISION_SKILL_NAME);
|
|
90
|
+
const USAGE_SKILL_NAME = "at-usage";
|
|
91
|
+
const USAGE_SKILL_SRC = path.join(REPO_ROOT, "integrations", "usage", "skills", USAGE_SKILL_NAME);
|
|
85
92
|
const VISION_BUNDLED_MCP_SERVER = path.join(REPO_ROOT, "dist", "vision", "mcp-server.mjs");
|
|
86
93
|
const VISION_BUNDLED_CLI = path.join(REPO_ROOT, "dist", "vision", "cli.mjs");
|
|
87
94
|
|
|
@@ -173,7 +180,7 @@ function isOpenCodeUsageTuiEntry(entry) {
|
|
|
173
180
|
const spec = Array.isArray(entry) ? entry[0] : entry;
|
|
174
181
|
return (
|
|
175
182
|
typeof spec === "string" &&
|
|
176
|
-
/\/
|
|
183
|
+
/\/integrations\/usage\/opencode-tui\.mjs$/i.test(spec.replace(/\\/g, "/"))
|
|
177
184
|
);
|
|
178
185
|
}
|
|
179
186
|
|
|
@@ -254,6 +261,7 @@ function installRuntimeAssets(opts) {
|
|
|
254
261
|
addFile(SOURCE.opencodeUsageTui, RUNTIME.opencodeUsageTui);
|
|
255
262
|
}
|
|
256
263
|
addFile(SOURCE.usageScript, RUNTIME.usageScript);
|
|
264
|
+
addFile(SOURCE.usageCli, RUNTIME.usageCli);
|
|
257
265
|
addFile(SOURCE.config, RUNTIME.config, { mergeJsonc: true });
|
|
258
266
|
}
|
|
259
267
|
if (files.length === 0) return;
|
|
@@ -264,7 +272,7 @@ function installRuntimeAssets(opts) {
|
|
|
264
272
|
function parseArgs(argv) {
|
|
265
273
|
const opts = {
|
|
266
274
|
agents: [],
|
|
267
|
-
|
|
275
|
+
integrations: [],
|
|
268
276
|
settings: null,
|
|
269
277
|
codexHooks: null,
|
|
270
278
|
opencodeConfigDir: null,
|
|
@@ -315,15 +323,15 @@ function parseArgs(argv) {
|
|
|
315
323
|
console.error(`Unknown option: ${a}`);
|
|
316
324
|
process.exit(2);
|
|
317
325
|
}
|
|
318
|
-
opts.
|
|
326
|
+
opts.integrations.push(a);
|
|
319
327
|
}
|
|
320
328
|
}
|
|
321
329
|
if (opts.agents.length === 0) opts.agents = ["claude"];
|
|
322
|
-
if (!opts.help && opts.
|
|
330
|
+
if (!opts.help && opts.integrations.length === 0) {
|
|
323
331
|
console.error(`Missing capability (available: ${ALL_CAPS.join(", ")})`);
|
|
324
332
|
process.exit(2);
|
|
325
333
|
}
|
|
326
|
-
for (const name of opts.
|
|
334
|
+
for (const name of opts.integrations) {
|
|
327
335
|
if (!ALL_CAPS.includes(name)) {
|
|
328
336
|
console.error(`Unknown capability: ${name} (available: ${ALL_CAPS.join(", ")})`);
|
|
329
337
|
process.exit(2);
|
|
@@ -333,14 +341,14 @@ function parseArgs(argv) {
|
|
|
333
341
|
}
|
|
334
342
|
|
|
335
343
|
function wants(opts, cap) {
|
|
336
|
-
return opts.
|
|
344
|
+
return opts.integrations.length === 0 || opts.integrations.includes(cap);
|
|
337
345
|
}
|
|
338
346
|
|
|
339
347
|
function validateAgentCapabilities(opts) {
|
|
340
348
|
const invalid = [];
|
|
341
349
|
for (const agent of opts.agents) {
|
|
342
350
|
const supported = AGENT_CAPS[agent] || [];
|
|
343
|
-
for (const cap of opts.
|
|
351
|
+
for (const cap of opts.integrations) {
|
|
344
352
|
if (!supported.includes(cap)) invalid.push(`${cap} -a ${agent}`);
|
|
345
353
|
}
|
|
346
354
|
}
|
|
@@ -351,7 +359,6 @@ function validateAgentCapabilities(opts) {
|
|
|
351
359
|
for (const agent of ALL_AGENTS) {
|
|
352
360
|
console.error(` ${agent}: ${AGENT_CAPS[agent].join(", ")}`);
|
|
353
361
|
}
|
|
354
|
-
console.error("Claude API usage is refreshed by the statusline capability; use `statusline -a claude`.");
|
|
355
362
|
process.exit(2);
|
|
356
363
|
}
|
|
357
364
|
|
|
@@ -409,7 +416,7 @@ function isOurProviderUsageEntry(entry) {
|
|
|
409
416
|
entry.hooks.some(
|
|
410
417
|
(h) =>
|
|
411
418
|
typeof h?.command === "string" &&
|
|
412
|
-
/(?:^|[/\\])
|
|
419
|
+
/(?:^|[/\\])codex-hook\.mjs(?:["\s]|$)/.test(h.command)
|
|
413
420
|
)
|
|
414
421
|
);
|
|
415
422
|
}
|
|
@@ -434,25 +441,26 @@ function applyProviderUsage(cfg, { remove }) {
|
|
|
434
441
|
|
|
435
442
|
// ---- Vision capability helpers. ----
|
|
436
443
|
|
|
437
|
-
|
|
444
|
+
// The installed tree mirrors the package tree, so the bundled runtime lands in
|
|
445
|
+
// dist/vision just like in the package.
|
|
446
|
+
const VISION_RUNTIME_DIR = path.join(INSTALL_ROOT, "dist", "vision");
|
|
438
447
|
const VISION_RATE_LIMIT_STATE = path.join(INSTALL_ROOT, "cache", "vision-rate-limit.json");
|
|
439
448
|
const VISION_DIST_DIR = path.join(REPO_ROOT, "dist", "vision");
|
|
440
449
|
const VISION_RUNTIME_SERVER = path.join(VISION_RUNTIME_DIR, "mcp-server.mjs");
|
|
441
450
|
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";
|
|
451
|
+
const SKILL_MARKER = ".agent-tools-managed.json";
|
|
449
452
|
const VISION_SKILL_MARKER_DATA = Object.freeze({
|
|
450
453
|
owner: "@kairyou/agent-tools",
|
|
451
454
|
capability: "vision",
|
|
452
455
|
artifact: "skill",
|
|
453
456
|
});
|
|
457
|
+
const USAGE_SKILL_MARKER_DATA = Object.freeze({
|
|
458
|
+
owner: "@kairyou/agent-tools",
|
|
459
|
+
capability: "usage",
|
|
460
|
+
artifact: "skill",
|
|
461
|
+
});
|
|
454
462
|
// Vision is bundled at release time. Install atomically swaps two self-contained
|
|
455
|
-
// entry files into ~/.agent-tools/vision
|
|
463
|
+
// entry files into ~/.agent-tools/dist/vision, so hosts never run npm and a
|
|
456
464
|
// failed update cannot destroy the previously working runtime.
|
|
457
465
|
function visionMcpCommand() {
|
|
458
466
|
return { command: "node", args: [fwd(VISION_RUNTIME_SERVER)] };
|
|
@@ -472,14 +480,17 @@ function sameFilePath(left, right) {
|
|
|
472
480
|
return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
473
481
|
}
|
|
474
482
|
|
|
483
|
+
function isVisionServerPath(value) {
|
|
484
|
+
return sameFilePath(value, VISION_RUNTIME_SERVER);
|
|
485
|
+
}
|
|
486
|
+
|
|
475
487
|
function isClaudeVisionMcp(value, mcp = visionMcpCommand()) {
|
|
476
488
|
return Boolean(
|
|
477
489
|
value &&
|
|
478
490
|
value.command === mcp.command &&
|
|
479
491
|
Array.isArray(value.args) &&
|
|
480
492
|
value.args.length === 1 &&
|
|
481
|
-
(
|
|
482
|
-
sameFilePath(value.args[0], VISION_LEGACY_RUNTIME_SERVER))
|
|
493
|
+
isVisionServerPath(value.args[0])
|
|
483
494
|
);
|
|
484
495
|
}
|
|
485
496
|
|
|
@@ -490,8 +501,7 @@ function isOpenCodeVisionMcp(value, mcp = visionMcpCommand()) {
|
|
|
490
501
|
Array.isArray(value.command) &&
|
|
491
502
|
value.command.length === 2 &&
|
|
492
503
|
value.command[0] === mcp.command &&
|
|
493
|
-
(
|
|
494
|
-
sameFilePath(value.command[1], VISION_LEGACY_RUNTIME_SERVER))
|
|
504
|
+
isVisionServerPath(value.command[1])
|
|
495
505
|
);
|
|
496
506
|
}
|
|
497
507
|
|
|
@@ -551,37 +561,26 @@ function installVisionRuntime(opts) {
|
|
|
551
561
|
}
|
|
552
562
|
}
|
|
553
563
|
|
|
554
|
-
function
|
|
555
|
-
const marker = path.join(dest,
|
|
564
|
+
function isManagedSkillDir(dest, markerData) {
|
|
565
|
+
const marker = path.join(dest, SKILL_MARKER);
|
|
556
566
|
if (!fs.existsSync(marker)) return false;
|
|
557
567
|
try {
|
|
558
568
|
const value = JSON.parse(fs.readFileSync(marker, "utf8"));
|
|
559
|
-
return Object.entries(
|
|
560
|
-
} catch {
|
|
561
|
-
return false;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
function isLegacyManagedVisionSkillDir(dest) {
|
|
566
|
-
try {
|
|
567
|
-
const entries = fs.readdirSync(dest);
|
|
568
|
-
if (entries.length !== 1 || entries[0] !== "SKILL.md") return false;
|
|
569
|
-
const skill = fs.readFileSync(path.join(dest, "SKILL.md"), "utf8");
|
|
570
|
-
return (
|
|
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
|
-
);
|
|
569
|
+
return Object.entries(markerData).every(([key, expected]) => value?.[key] === expected);
|
|
575
570
|
} catch {
|
|
576
571
|
return false;
|
|
577
572
|
}
|
|
578
573
|
}
|
|
579
574
|
|
|
580
|
-
function
|
|
581
|
-
|
|
575
|
+
function installManagedSkillDir(
|
|
576
|
+
skillsRoot,
|
|
577
|
+
{ name, source, markerData, replacements, remove, dryRun }
|
|
578
|
+
) {
|
|
579
|
+
const dest = path.join(skillsRoot, name);
|
|
580
|
+
const isManaged = () => isManagedSkillDir(dest, markerData);
|
|
582
581
|
if (remove) {
|
|
583
582
|
if (!fs.existsSync(dest)) return;
|
|
584
|
-
if (!
|
|
583
|
+
if (!isManaged()) {
|
|
585
584
|
console.log(` kept unmanaged ${dest}`);
|
|
586
585
|
return;
|
|
587
586
|
}
|
|
@@ -593,38 +592,61 @@ function installSkillDir(skillsRoot, { remove, dryRun }) {
|
|
|
593
592
|
console.log(` removed ${dest}`);
|
|
594
593
|
return;
|
|
595
594
|
}
|
|
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
|
-
}
|
|
595
|
+
if (fs.existsSync(dest) && !isManaged()) {
|
|
596
|
+
console.error(
|
|
597
|
+
` Refusing to overwrite existing unowned skill directory ${dest}. ` +
|
|
598
|
+
`Move or remove it, then re-run the install.`
|
|
599
|
+
);
|
|
600
|
+
process.exit(1);
|
|
606
601
|
}
|
|
607
602
|
if (dryRun) {
|
|
608
|
-
console.log(` [dry-run] would copy ${
|
|
603
|
+
console.log(` [dry-run] would copy ${source} -> ${dest}`);
|
|
609
604
|
return;
|
|
610
605
|
}
|
|
611
606
|
fs.rmSync(dest, { recursive: true, force: true });
|
|
612
|
-
fs.cpSync(
|
|
607
|
+
fs.cpSync(source, dest, { recursive: true });
|
|
613
608
|
const skillFile = path.join(dest, "SKILL.md");
|
|
614
|
-
|
|
615
|
-
const token
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
609
|
+
let skill = fs.readFileSync(skillFile, "utf8");
|
|
610
|
+
for (const [token, value] of Object.entries(replacements)) {
|
|
611
|
+
if (!skill.includes(token)) {
|
|
612
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
613
|
+
throw new Error(`${name} skill template is missing ${token}`);
|
|
614
|
+
}
|
|
615
|
+
skill = skill.replaceAll(token, value);
|
|
619
616
|
}
|
|
620
|
-
fs.writeFileSync(skillFile, skill
|
|
617
|
+
fs.writeFileSync(skillFile, skill);
|
|
621
618
|
fs.writeFileSync(
|
|
622
|
-
path.join(dest,
|
|
623
|
-
JSON.stringify(
|
|
619
|
+
path.join(dest, SKILL_MARKER),
|
|
620
|
+
JSON.stringify(markerData, null, 2) + "\n"
|
|
624
621
|
);
|
|
625
622
|
console.log(` wrote ${dest}`);
|
|
626
623
|
}
|
|
627
624
|
|
|
625
|
+
function installVisionSkillDir(skillsRoot, { remove, dryRun }) {
|
|
626
|
+
installManagedSkillDir(skillsRoot, {
|
|
627
|
+
name: VISION_SKILL_NAME,
|
|
628
|
+
source: VISION_SKILL_SRC,
|
|
629
|
+
markerData: VISION_SKILL_MARKER_DATA,
|
|
630
|
+
replacements: { "{{VISION_CLI_PATH}}": fwd(VISION_RUNTIME_CLI) },
|
|
631
|
+
remove,
|
|
632
|
+
dryRun,
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function installUsageSkillDir(skillsRoot, agent, { remove, dryRun }) {
|
|
637
|
+
installManagedSkillDir(skillsRoot, {
|
|
638
|
+
name: USAGE_SKILL_NAME,
|
|
639
|
+
source: USAGE_SKILL_SRC,
|
|
640
|
+
markerData: USAGE_SKILL_MARKER_DATA,
|
|
641
|
+
replacements: {
|
|
642
|
+
"{{USAGE_CLI_PATH}}": fwd(RUNTIME.usageCli),
|
|
643
|
+
"{{USAGE_AGENT}}": agent,
|
|
644
|
+
},
|
|
645
|
+
remove,
|
|
646
|
+
dryRun,
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
|
|
628
650
|
const CODEX_VISION_BEGIN = "# >>> agent-tools vision >>>";
|
|
629
651
|
const CODEX_VISION_END = "# <<< agent-tools vision <<<";
|
|
630
652
|
|
|
@@ -691,7 +713,7 @@ function runClaudeVision(opts) {
|
|
|
691
713
|
} else {
|
|
692
714
|
console.log(` no ${VISION_MCP_NAME} MCP entry found; nothing to remove.`);
|
|
693
715
|
}
|
|
694
|
-
|
|
716
|
+
installVisionSkillDir(skillsDir, { remove: true, dryRun: opts.dryRun });
|
|
695
717
|
console.log(" - vision");
|
|
696
718
|
return;
|
|
697
719
|
}
|
|
@@ -704,12 +726,21 @@ function runClaudeVision(opts) {
|
|
|
704
726
|
cfg.mcpServers = cfg.mcpServers || {};
|
|
705
727
|
cfg.mcpServers[VISION_MCP_NAME] = { type: "stdio", command: mcp.command, args: mcp.args };
|
|
706
728
|
writeJson(claudeJson, cfg, opts.dryRun);
|
|
707
|
-
|
|
729
|
+
installVisionSkillDir(skillsDir, { remove: false, dryRun: opts.dryRun });
|
|
708
730
|
console.log(" + vision (inspect_image MCP + at-vision skill)");
|
|
709
731
|
}
|
|
710
732
|
|
|
711
733
|
function runClaude(opts) {
|
|
712
734
|
if (wants(opts, "vision")) runClaudeVision(opts);
|
|
735
|
+
if (wants(opts, "usage")) {
|
|
736
|
+
const skillsDir = opts.claudeSkillsDir || path.join(os.homedir(), ".claude", "skills");
|
|
737
|
+
console.log(`claude usage: ${skillsDir}`);
|
|
738
|
+
installUsageSkillDir(skillsDir, "claude", {
|
|
739
|
+
remove: opts.uninstall,
|
|
740
|
+
dryRun: opts.dryRun,
|
|
741
|
+
});
|
|
742
|
+
console.log(opts.uninstall ? " - usage skill" : " + usage skill (at-usage)");
|
|
743
|
+
}
|
|
713
744
|
if (!wants(opts, "statusline")) return;
|
|
714
745
|
const settings = opts.settings || path.join(os.homedir(), ".claude", "settings.json");
|
|
715
746
|
console.log(`claude (global): ${settings}`);
|
|
@@ -758,7 +789,7 @@ function runCodexVision(opts) {
|
|
|
758
789
|
dryRun: opts.dryRun,
|
|
759
790
|
mcp: visionMcpCommand(),
|
|
760
791
|
});
|
|
761
|
-
|
|
792
|
+
installVisionSkillDir(skillsDir, { remove: opts.uninstall, dryRun: opts.dryRun });
|
|
762
793
|
console.log(opts.uninstall ? " - vision" : " + vision (inspect_image MCP + at-vision skill)");
|
|
763
794
|
}
|
|
764
795
|
|
|
@@ -767,6 +798,12 @@ function runCodex(opts) {
|
|
|
767
798
|
if (!wants(opts, "usage")) {
|
|
768
799
|
return;
|
|
769
800
|
}
|
|
801
|
+
const skillsDir = opts.codexSkillsDir || path.join(os.homedir(), ".agents", "skills");
|
|
802
|
+
console.log(`codex usage skill: ${skillsDir}`);
|
|
803
|
+
installUsageSkillDir(skillsDir, "codex", {
|
|
804
|
+
remove: opts.uninstall,
|
|
805
|
+
dryRun: opts.dryRun,
|
|
806
|
+
});
|
|
770
807
|
const file = opts.codexHooks || path.join(os.homedir(), ".codex", "hooks.json");
|
|
771
808
|
console.log(`codex (global): ${file}`);
|
|
772
809
|
const cfg = readJson(file);
|
|
@@ -854,7 +891,7 @@ function runOpencodeVision(opts) {
|
|
|
854
891
|
dryRun: opts.dryRun,
|
|
855
892
|
mcp: visionMcpCommand(),
|
|
856
893
|
});
|
|
857
|
-
|
|
894
|
+
installVisionSkillDir(skillsDir, { remove: opts.uninstall, dryRun: opts.dryRun });
|
|
858
895
|
console.log(opts.uninstall ? " - vision" : " + vision (inspect_image MCP + at-vision skill)");
|
|
859
896
|
}
|
|
860
897
|
|
|
@@ -905,10 +942,7 @@ function visionRuntimeHasRemainingReference(opts) {
|
|
|
905
942
|
const end = text.indexOf(CODEX_VISION_END, start + CODEX_VISION_BEGIN.length);
|
|
906
943
|
if (start !== -1 && end !== -1) {
|
|
907
944
|
const block = text.slice(start, end);
|
|
908
|
-
if (
|
|
909
|
-
block.includes(fwd(VISION_RUNTIME_SERVER)) ||
|
|
910
|
-
block.includes(fwd(VISION_LEGACY_RUNTIME_SERVER))
|
|
911
|
-
) {
|
|
945
|
+
if (block.includes(fwd(VISION_RUNTIME_SERVER))) {
|
|
912
946
|
return true;
|
|
913
947
|
}
|
|
914
948
|
}
|
|
@@ -948,7 +982,7 @@ function cleanupVisionRuntimeIfUnused(opts) {
|
|
|
948
982
|
try {
|
|
949
983
|
fs.rmdirSync(path.dirname(VISION_RATE_LIMIT_STATE));
|
|
950
984
|
} catch {
|
|
951
|
-
// The shared cache directory may contain state for other
|
|
985
|
+
// The shared cache directory may contain state for other integrations.
|
|
952
986
|
}
|
|
953
987
|
}
|
|
954
988
|
console.log(` removed unused vision runtime ${VISION_RUNTIME_DIR}`);
|
|
@@ -985,7 +1019,7 @@ function main() {
|
|
|
985
1019
|
}
|
|
986
1020
|
|
|
987
1021
|
// Standalone vision commands dispatch before capability parsing so image
|
|
988
|
-
// paths and questions are never mistaken for
|
|
1022
|
+
// paths and questions are never mistaken for integrations.
|
|
989
1023
|
const subcommand = process.argv[2];
|
|
990
1024
|
if (subcommand === "inspect-image") {
|
|
991
1025
|
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/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|