@caddis/cli 0.2.2 → 0.2.4

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.
@@ -2,7 +2,10 @@
2
2
  """caddis warm-start hook for agy (Antigravity) — PreInvocation event.
3
3
 
4
4
  Ports the relay half of claude-harness/hooks/inject_relay.py. agy has NO SessionStart event, so the
5
- nearest equivalent is PreInvocation with `invocationNum == 1` — the first model turn of a session.
5
+ nearest equivalent is PreInvocation with `invocationNum == 0` — the first model turn of a session.
6
+ **agy's invocations are 0-indexed** (confirmed via a live-fire session against the real `agy` binary
7
+ 2026-07-30 -- the original code checked `== 1`, which is agy's SECOND turn, so this hook silently
8
+ never fired at all until the live check caught it; see .caddis/plans/agy-hooks-port.md step 5).
6
9
  On that turn only, the workspace's relay doc is injected as an ephemeral step so a fresh agy session
7
10
  resumes with zero re-discovery. Every later invocation emits nothing (re-injecting the relay on every
8
11
  turn would burn context and drown the conversation).
@@ -118,10 +121,10 @@ def main() -> None:
118
121
  return
119
122
  if not isinstance(data, dict):
120
123
  return
121
- # SessionStart equivalent: the FIRST invocation only. Anything else (or an unparseable
122
- # invocationNum) injects nothing.
124
+ # SessionStart equivalent: the FIRST invocation only (agy 0-indexes invocationNum, so the
125
+ # first turn is 0, not 1). Anything else (or an unparseable invocationNum) injects nothing.
123
126
  try:
124
- if int(data.get("invocationNum")) != 1:
127
+ if int(data.get("invocationNum")) != 0:
125
128
  return
126
129
  except Exception:
127
130
  return
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "caddis-extras",
3
- "version": "1.3.14",
3
+ "version": "1.3.20",
4
4
  "description": "caddis-extras — the long-tail skills for the Antigravity (agy) caddis plugin; install alongside caddis for the full set."
5
5
  }
@@ -28,10 +28,13 @@ churn — the preset table in `oss_review.py` is the one place to edit a rename,
28
28
  `REVIEW_BASE_URL` always win, so you can point at any new id without touching code.
29
29
 
30
30
  ## How to run
31
- From the repo root (the tool ships at `.github/tools/oss_review.py`):
31
+ From the repo root. Locate the tool, in order:
32
+ - `.github/tools/oss_review.py` — this project vendors its own copy.
33
+ - `${CLAUDE_PLUGIN_ROOT}/scripts/oss_review.py` — the plugin-bundled copy (every Claude Code
34
+ install has this; use it whenever the project has no local copy).
32
35
  ```
33
- python .github/tools/oss_review.py # review the working tree (staged+unstaged)
34
- python .github/tools/oss_review.py --range origin/main..HEAD # review a branch's commits
36
+ python <resolved-path>/oss_review.py # review the working tree (staged+unstaged)
37
+ python <resolved-path>/oss_review.py --range origin/main..HEAD # review a branch's commits
35
38
  ```
36
39
  Optional flags: `--cwd <repo>`, `--base-url <url>`, `--model <id>` (override the env).
37
40
 
@@ -1,7 +1,7 @@
1
1
  {
2
- "poolVersion": "1.3.41",
2
+ "poolVersion": "1.3.54",
3
3
  "bundles": {
4
- "antigravity-plugin": "1.3.41",
5
- "antigravity-plugin-extras": "1.3.14"
4
+ "antigravity-plugin": "1.3.54",
5
+ "antigravity-plugin-extras": "1.3.20"
6
6
  }
7
7
  }
package/dist/cli.js CHANGED
@@ -3473,6 +3473,51 @@ var require_commander = __commonJS({
3473
3473
  }
3474
3474
  });
3475
3475
 
3476
+ // src/util/exec.ts
3477
+ async function run(cmd, args, opts = {}) {
3478
+ const { execa } = await import("execa");
3479
+ try {
3480
+ const result = await execa(cmd, args, {
3481
+ cwd: opts.cwd,
3482
+ timeout: opts.timeout ?? DEFAULT_TIMEOUT_MS,
3483
+ env: opts.env,
3484
+ reject: false,
3485
+ all: false,
3486
+ stripFinalNewline: true,
3487
+ // Windows: resolve .cmd/.ps1 shims through the shell-less path lookup
3488
+ // execa already does, but be explicit that we are not using a shell.
3489
+ shell: false,
3490
+ windowsHide: true
3491
+ });
3492
+ return {
3493
+ ok: result.exitCode === 0 && !result.failed,
3494
+ code: result.exitCode ?? null,
3495
+ stdout: String(result.stdout ?? ""),
3496
+ stderr: String(result.stderr ?? ""),
3497
+ failure: result.failed && result.exitCode === void 0 ? result.shortMessage : void 0
3498
+ };
3499
+ } catch (error) {
3500
+ return {
3501
+ ok: false,
3502
+ code: null,
3503
+ stdout: "",
3504
+ stderr: "",
3505
+ failure: error instanceof Error ? error.message : String(error)
3506
+ };
3507
+ }
3508
+ }
3509
+ function formatCommand(cmd, args) {
3510
+ const quoted = args.map((a) => /\s/.test(a) ? `"${a}"` : a);
3511
+ return [cmd, ...quoted].join(" ");
3512
+ }
3513
+ var DEFAULT_TIMEOUT_MS;
3514
+ var init_exec = __esm({
3515
+ "src/util/exec.ts"() {
3516
+ "use strict";
3517
+ DEFAULT_TIMEOUT_MS = 12e4;
3518
+ }
3519
+ });
3520
+
3476
3521
  // node_modules/isexe/dist/commonjs/index.min.js
3477
3522
  var require_index_min = __commonJS({
3478
3523
  "node_modules/isexe/dist/commonjs/index.min.js"(exports) {
@@ -4483,6 +4528,13 @@ var init_drive = __esm({
4483
4528
  });
4484
4529
 
4485
4530
  // src/commands/report.ts
4531
+ async function checkCliUpdate(currentVersion) {
4532
+ const result = await run("npm", ["view", "@caddis/cli", "version"], { timeout: 5e3 });
4533
+ if (!result.ok) return null;
4534
+ const latest = result.stdout.trim();
4535
+ if (!latest || latest === currentVersion) return null;
4536
+ return { current: currentVersion, latest };
4537
+ }
4486
4538
  function classify(adapter, detection, status4, poolVersion) {
4487
4539
  if (!detection.present) return "absent";
4488
4540
  if (!adapter.supported) return "unsupported";
@@ -4491,24 +4543,28 @@ function classify(adapter, detection, status4, poolVersion) {
4491
4543
  if (poolVersion === "unknown") return "unknown";
4492
4544
  return status4.version === poolVersion ? "current" : "stale";
4493
4545
  }
4494
- async function gather(adapters) {
4546
+ async function gather(adapters, options = {}) {
4495
4547
  const manifest = bundleManifest();
4496
4548
  const poolVersion = manifest.poolVersion;
4497
4549
  const extrasVersion = manifest.bundles["antigravity-plugin-extras"];
4498
- const agents = await Promise.all(
4499
- adapters.map(async (adapter) => {
4500
- const detection = await adapter.detect();
4501
- const status4 = detection.present ? await adapter.status() : { installed: false, note: "agent not installed" };
4502
- return {
4503
- adapter,
4504
- detection,
4505
- status: status4,
4506
- drift: classify(adapter, detection, status4, poolVersion),
4507
- extrasDrift: classifyExtras(adapter, detection, status4, extrasVersion)
4508
- };
4509
- })
4510
- );
4511
- return { cliVersion: packageInfo().version, poolVersion, extrasVersion, agents };
4550
+ const cliVersion = packageInfo().version;
4551
+ const [agents, cliUpdate] = await Promise.all([
4552
+ Promise.all(
4553
+ adapters.map(async (adapter) => {
4554
+ const detection = await adapter.detect();
4555
+ const status4 = detection.present ? await adapter.status() : { installed: false, note: "agent not installed" };
4556
+ return {
4557
+ adapter,
4558
+ detection,
4559
+ status: status4,
4560
+ drift: classify(adapter, detection, status4, poolVersion),
4561
+ extrasDrift: classifyExtras(adapter, detection, status4, extrasVersion)
4562
+ };
4563
+ })
4564
+ ),
4565
+ options.checkCliUpdate ? checkCliUpdate(cliVersion) : Promise.resolve(null)
4566
+ ]);
4567
+ return { cliVersion, poolVersion, extrasVersion, agents, cliUpdate: cliUpdate ?? void 0 };
4512
4568
  }
4513
4569
  function classifyExtras(adapter, detection, status4, extrasVersion) {
4514
4570
  if (!status4.extras) return void 0;
@@ -4528,6 +4584,7 @@ function actionable(report, includeCurrent = false) {
4528
4584
  var init_report = __esm({
4529
4585
  "src/commands/report.ts"() {
4530
4586
  "use strict";
4587
+ init_exec();
4531
4588
  init_pkg();
4532
4589
  }
4533
4590
  });
@@ -4646,7 +4703,7 @@ __export(doctor_exports, {
4646
4703
  import os3 from "os";
4647
4704
  import process2 from "process";
4648
4705
  async function doctor(options) {
4649
- const report = await gather(options.adapters);
4706
+ const report = await gather(options.adapters, { checkCliUpdate: true });
4650
4707
  const findings = [];
4651
4708
  if (options.json) {
4652
4709
  line(JSON.stringify(toJson(report), null, 2));
@@ -4667,7 +4724,17 @@ function renderEnvironment(report, problems) {
4667
4724
  problems.push({ kind: "problem", text: `node ${process2.versions.node} is below the required ${REQUIRED_NODE_MAJOR}.${REQUIRED_NODE_MINOR}`, fix: "upgrade node" });
4668
4725
  }
4669
4726
  item("info", `platform ${process2.platform} ${process2.arch} (${os3.release()})`);
4670
- item("info", `@caddis/cli ${report.cliVersion}`);
4727
+ if (report.cliUpdate) {
4728
+ item("warn", `@caddis/cli ${report.cliVersion} ${color.dim("\u2192")} ${color.bold(report.cliUpdate.latest)} available`);
4729
+ detail("everything below is compared against the pool THIS install carries, which is now behind the published one");
4730
+ problems.push({
4731
+ kind: "problem",
4732
+ text: `@caddis/cli itself is behind: ${report.cliUpdate.current} installed, ${report.cliUpdate.latest} published`,
4733
+ fix: "npm i -g @caddis/cli@latest"
4734
+ });
4735
+ } else {
4736
+ item("ok", `@caddis/cli ${report.cliVersion}`);
4737
+ }
4671
4738
  const manifest = bundleManifest();
4672
4739
  if (report.poolVersion === "unknown") {
4673
4740
  item("fail", "shipped pool version unknown");
@@ -4837,6 +4904,7 @@ function problemFor(entry, poolVersion) {
4837
4904
  function toJson(report) {
4838
4905
  return {
4839
4906
  cliVersion: report.cliVersion,
4907
+ cliUpdate: report.cliUpdate ?? null,
4840
4908
  poolVersion: report.poolVersion,
4841
4909
  extrasVersion: report.extrasVersion ?? null,
4842
4910
  node: process2.versions.node,
@@ -4990,44 +5058,8 @@ var {
4990
5058
  Help
4991
5059
  } = import_index.default;
4992
5060
 
4993
- // src/util/exec.ts
4994
- var DEFAULT_TIMEOUT_MS = 12e4;
4995
- async function run(cmd, args, opts = {}) {
4996
- const { execa } = await import("execa");
4997
- try {
4998
- const result = await execa(cmd, args, {
4999
- cwd: opts.cwd,
5000
- timeout: opts.timeout ?? DEFAULT_TIMEOUT_MS,
5001
- env: opts.env,
5002
- reject: false,
5003
- all: false,
5004
- stripFinalNewline: true,
5005
- // Windows: resolve .cmd/.ps1 shims through the shell-less path lookup
5006
- // execa already does, but be explicit that we are not using a shell.
5007
- shell: false,
5008
- windowsHide: true
5009
- });
5010
- return {
5011
- ok: result.exitCode === 0 && !result.failed,
5012
- code: result.exitCode ?? null,
5013
- stdout: String(result.stdout ?? ""),
5014
- stderr: String(result.stderr ?? ""),
5015
- failure: result.failed && result.exitCode === void 0 ? result.shortMessage : void 0
5016
- };
5017
- } catch (error) {
5018
- return {
5019
- ok: false,
5020
- code: null,
5021
- stdout: "",
5022
- stderr: "",
5023
- failure: error instanceof Error ? error.message : String(error)
5024
- };
5025
- }
5026
- }
5027
- function formatCommand(cmd, args) {
5028
- const quoted = args.map((a) => /\s/.test(a) ? `"${a}"` : a);
5029
- return [cmd, ...quoted].join(" ");
5030
- }
5061
+ // src/agents/claude.ts
5062
+ init_exec();
5031
5063
 
5032
5064
  // src/util/which.ts
5033
5065
  var import_which = __toESM(require_lib(), 1);
@@ -5167,7 +5199,8 @@ var claudeAdapter = {
5167
5199
  };
5168
5200
 
5169
5201
  // src/agents/agy.ts
5170
- import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
5202
+ init_exec();
5203
+ import { readFileSync as readFileSync2, existsSync as existsSync2, mkdtempSync, cpSync, rmSync } from "fs";
5171
5204
  import os from "os";
5172
5205
  import path2 from "path";
5173
5206
  init_pkg();
@@ -5239,6 +5272,25 @@ function shortenHome(target) {
5239
5272
  const home = os.homedir();
5240
5273
  return target.startsWith(home) ? path2.join("~", target.slice(home.length)) : target;
5241
5274
  }
5275
+ function stageForAgy(bundleDir) {
5276
+ if (!bundleDir.includes("@")) {
5277
+ return { installPath: bundleDir, cleanup: () => {
5278
+ } };
5279
+ }
5280
+ const staged = mkdtempSync(path2.join(os.tmpdir(), "caddis-agy-"));
5281
+ const dest = path2.join(staged, path2.basename(bundleDir));
5282
+ cpSync(bundleDir, dest, { recursive: true });
5283
+ return {
5284
+ installPath: dest,
5285
+ // Best-effort: a stray temp dir left behind is harmless clutter, not a failure.
5286
+ cleanup: () => {
5287
+ try {
5288
+ rmSync(staged, { recursive: true, force: true });
5289
+ } catch {
5290
+ }
5291
+ }
5292
+ };
5293
+ }
5242
5294
  async function drive2(_action, options) {
5243
5295
  const detected = await detect2();
5244
5296
  if (!detected.present) {
@@ -5253,12 +5305,12 @@ async function drive2(_action, options) {
5253
5305
  message: `the ${BUNDLE} bundle is missing from this package \u2014 reinstall @caddis/cli`
5254
5306
  };
5255
5307
  }
5256
- const planned = [{ cmd: BIN2, args: ["plugin", "install", bundle] }];
5308
+ const planned = [{ cmd: BIN2, args: ["plugin", "install", bundle], bundleDir: bundle }];
5257
5309
  const extrasInstalled = extrasStatusFromHome(os.homedir()).installed;
5258
5310
  if (options.extras === true || extrasInstalled) {
5259
5311
  const extrasBundle = bundlePath(EXTRAS_BUNDLE);
5260
5312
  if (extrasBundle) {
5261
- planned.push({ cmd: BIN2, args: ["plugin", "install", extrasBundle] });
5313
+ planned.push({ cmd: BIN2, args: ["plugin", "install", extrasBundle], bundleDir: extrasBundle });
5262
5314
  } else if (options.extras === true) {
5263
5315
  return {
5264
5316
  ok: false,
@@ -5272,25 +5324,34 @@ async function drive2(_action, options) {
5272
5324
  return {
5273
5325
  ok: true,
5274
5326
  skipped: true,
5327
+ // Show the logical bundle path, not a staged temp copy that was never created.
5275
5328
  steps: planned.map((s) => ({ command: formatCommand(s.cmd, s.args), ok: true, code: null })),
5276
5329
  message: "dry run \u2014 nothing executed"
5277
5330
  };
5278
5331
  }
5279
- const steps2 = [];
5280
- for (const step of planned) {
5281
- const command = formatCommand(step.cmd, step.args);
5282
- const result = await run(step.cmd, step.args);
5283
- steps2.push({
5284
- command,
5285
- ok: result.ok,
5286
- code: result.code,
5287
- output: result.ok ? void 0 : (result.stderr || result.stdout || result.failure || "").trim().split(/\r?\n/).slice(-3).join("\n")
5288
- });
5289
- if (!result.ok) {
5290
- return { ok: false, skipped: false, steps: steps2, message: `\`${command}\` failed` };
5332
+ const cleanups = [];
5333
+ try {
5334
+ const steps2 = [];
5335
+ for (const step of planned) {
5336
+ const command = formatCommand(step.cmd, step.args);
5337
+ const staged = stageForAgy(step.bundleDir);
5338
+ cleanups.push(staged.cleanup);
5339
+ const execArgs = [...step.args.slice(0, -1), staged.installPath];
5340
+ const result = await run(step.cmd, execArgs);
5341
+ steps2.push({
5342
+ command,
5343
+ ok: result.ok,
5344
+ code: result.code,
5345
+ output: result.ok ? void 0 : (result.stderr || result.stdout || result.failure || "").trim().split(/\r?\n/).slice(-3).join("\n")
5346
+ });
5347
+ if (!result.ok) {
5348
+ return { ok: false, skipped: false, steps: steps2, message: `\`${command}\` failed` };
5349
+ }
5291
5350
  }
5351
+ return { ok: true, skipped: false, steps: steps2 };
5352
+ } finally {
5353
+ cleanups.forEach((fn) => fn());
5292
5354
  }
5293
- return { ok: true, skipped: false, steps: steps2 };
5294
5355
  }
5295
5356
  var agyAdapter = {
5296
5357
  id: "agy",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caddis/cli",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Install, update and diagnose the caddis dev harness across every coding agent you use (Claude Code, agy/Antigravity, and more).",
5
5
  "keywords": [
6
6
  "caddis",