@tonyclaw/agent-inspector 2.0.16 → 2.0.18

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.
Files changed (37) hide show
  1. package/.output/cli.js +394 -55
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-DPs4UHwd.js → CompareDrawer-CepU-dLW.js} +1 -1
  4. package/.output/public/assets/{ProxyViewerContainer-B1KylMPs.js → ProxyViewerContainer-BG7rc33d.js} +16 -16
  5. package/.output/public/assets/{ReplayDialog-8ISC28XF.js → ReplayDialog-DK6SPv8X.js} +1 -1
  6. package/.output/public/assets/{RequestAnatomy-BhbsgfBO.js → RequestAnatomy-C_C6ZU3T.js} +1 -1
  7. package/.output/public/assets/{ResponseView-CyM7JXeA.js → ResponseView-Bqpra4Zw.js} +1 -1
  8. package/.output/public/assets/{StreamingChunkSequence-BaX0x38n.js → StreamingChunkSequence-CdPbpprq.js} +1 -1
  9. package/.output/public/assets/_sessionId-CsNARHj5.js +1 -0
  10. package/.output/public/assets/index-37NfO0zx.js +1 -0
  11. package/.output/public/assets/{main-B2t-3SYj.js → main-BRXsrxtv.js} +2 -2
  12. package/.output/server/{_sessionId-CEbZTs68.mjs → _sessionId-B8Bbfs-v.mjs} +2 -2
  13. package/.output/server/_ssr/{CompareDrawer-CEo48Wsu.mjs → CompareDrawer-TgGZLf1J.mjs} +2 -2
  14. package/.output/server/_ssr/{ProxyViewerContainer-5ekIK_jx.mjs → ProxyViewerContainer-B_ppaNmQ.mjs} +41 -12
  15. package/.output/server/_ssr/{ReplayDialog-C0nEvbYo.mjs → ReplayDialog-BXlajnMC.mjs} +3 -3
  16. package/.output/server/_ssr/{RequestAnatomy-CT55prmM.mjs → RequestAnatomy-CCrx09-i.mjs} +2 -2
  17. package/.output/server/_ssr/{ResponseView-DBZSBCOD.mjs → ResponseView-BxtMJrov.mjs} +2 -2
  18. package/.output/server/_ssr/{StreamingChunkSequence-BuVT7Oke.mjs → StreamingChunkSequence-BlpdSPb8.mjs} +2 -2
  19. package/.output/server/_ssr/{index-Cw_zJVQv.mjs → index-BeTMNSJy.mjs} +2 -2
  20. package/.output/server/_ssr/index.mjs +2 -2
  21. package/.output/server/_ssr/{router-DxjqHipT.mjs → router-Ddl1tuU7.mjs} +27 -11
  22. package/.output/server/{_tanstack-start-manifest_v-xeOo5gW5.mjs → _tanstack-start-manifest_v-Bvdz_U0f.mjs} +1 -1
  23. package/.output/server/index.mjs +56 -56
  24. package/README.md +37 -5
  25. package/package.json +3 -3
  26. package/scripts/setup-agent-skills.mjs +64 -0
  27. package/src/cli/doctor.ts +12 -5
  28. package/src/cli/onboard.ts +407 -13
  29. package/src/cli/templates/codex-skill-onboard.ts +5 -0
  30. package/src/cli/templates/command-onboard.ts +12 -4
  31. package/src/cli/templates/skill-onboard.ts +17 -21
  32. package/src/components/OnboardingBanner.tsx +5 -0
  33. package/src/components/providers/SettingsDialog.tsx +22 -4
  34. package/src/proxy/dataDir.ts +35 -13
  35. package/.output/public/assets/_sessionId-CShO0OrR.js +0 -1
  36. package/.output/public/assets/index-BkdYiTpV.js +0 -1
  37. package/scripts/setup-codex-skill.mjs +0 -38
@@ -5,12 +5,21 @@
5
5
  * Claude Code gets a skill + slash command. Codex gets a skill under
6
6
  * ~/.codex/skills that guides the user through connecting /api/mcp.
7
7
  *
8
- * Idempotent by default: re-runs without `--force` only write missing files.
9
- * `--force` overwrites, `--dry-run` writes nothing. Designed as an explicit
10
- * setup step that can fail softly without blocking normal CLI use.
8
+ * Idempotent by default: re-runs without `--force` write missing files and
9
+ * refresh older generated files. `--force` overwrites, `--dry-run` writes
10
+ * nothing. Designed as an explicit setup step that can fail softly without
11
+ * blocking normal CLI use.
11
12
  */
12
13
 
13
- import { mkdirSync, writeFileSync, existsSync, readFileSync } from "node:fs";
14
+ import {
15
+ mkdirSync,
16
+ writeFileSync,
17
+ existsSync,
18
+ readFileSync,
19
+ unlinkSync,
20
+ readdirSync,
21
+ rmdirSync,
22
+ } from "node:fs";
14
23
  import { homedir } from "node:os";
15
24
  import { dirname, join } from "node:path";
16
25
  import { fileURLToPath } from "node:url";
@@ -37,6 +46,10 @@ const SKILL_FILE_NAME = "SKILL.md";
37
46
  const COMMAND_FILE_NAME =
38
47
  process.platform === "win32" ? "agent-inspector-onboard.md" : "agent-inspector:onboard.md";
39
48
 
49
+ const GENERATED_VERSION_PATTERN = /^\s*version:\s*([0-9]+\.[0-9]+\.[0-9]+(?:[-+][^\s]+)?)\s*$/m;
50
+ const FRONTMATTER_PATTERN = /^---\r?\n([\s\S]*?)\r?\n---/;
51
+ const SEMVER_PATTERN = /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/;
52
+
40
53
  export type OnboardFlags = {
41
54
  force: boolean;
42
55
  dryRun: boolean;
@@ -44,6 +57,9 @@ export type OnboardFlags = {
44
57
  skipToolWire: boolean;
45
58
  codexOnly: boolean;
46
59
  skipCodexSkill: boolean;
60
+ uninstall: boolean;
61
+ status: boolean;
62
+ json: boolean;
47
63
  skillDir: string | null;
48
64
  codexSkillDir: string | null;
49
65
  };
@@ -61,6 +77,47 @@ type PlannedFile = {
61
77
  enabled: boolean;
62
78
  };
63
79
 
80
+ type ParsedVersion = {
81
+ major: number;
82
+ minor: number;
83
+ patch: number;
84
+ };
85
+
86
+ type GeneratedFileState = "missing" | "current" | "outdated" | "newer" | "custom";
87
+
88
+ type GeneratedFileStatus = {
89
+ label: string;
90
+ path: string;
91
+ state: GeneratedFileState;
92
+ version: string | null;
93
+ action: string;
94
+ };
95
+
96
+ function actionForStatus(label: string, state: GeneratedFileState): string {
97
+ switch (state) {
98
+ case "missing":
99
+ switch (label) {
100
+ case "Codex skill":
101
+ return "agent-inspector onboard --codex-only";
102
+ case "Claude skill":
103
+ case "Claude command":
104
+ return "agent-inspector onboard --skip-codex-skill";
105
+ default:
106
+ return "agent-inspector onboard";
107
+ }
108
+ case "outdated":
109
+ return "agent-inspector onboard --force";
110
+ case "custom":
111
+ return "preserved; use agent-inspector onboard --force to replace";
112
+ case "newer":
113
+ return "no action; installed file is newer than this package";
114
+ case "current":
115
+ return "no action";
116
+ default:
117
+ return "agent-inspector onboard";
118
+ }
119
+ }
120
+
64
121
  function parseFlags(argv: readonly string[]): OnboardFlags {
65
122
  const flags: OnboardFlags = {
66
123
  force: false,
@@ -69,6 +126,9 @@ function parseFlags(argv: readonly string[]): OnboardFlags {
69
126
  skipToolWire: false,
70
127
  codexOnly: false,
71
128
  skipCodexSkill: false,
129
+ uninstall: false,
130
+ status: false,
131
+ json: false,
72
132
  skillDir: null,
73
133
  codexSkillDir: null,
74
134
  };
@@ -95,6 +155,15 @@ function parseFlags(argv: readonly string[]): OnboardFlags {
95
155
  case "--skip-codex-skill":
96
156
  flags.skipCodexSkill = true;
97
157
  break;
158
+ case "--uninstall":
159
+ flags.uninstall = true;
160
+ break;
161
+ case "--status":
162
+ flags.status = true;
163
+ break;
164
+ case "--json":
165
+ flags.json = true;
166
+ break;
98
167
  case "--skill-dir": {
99
168
  const next = argv[i + 1];
100
169
  if (next === undefined) {
@@ -143,6 +212,9 @@ Options:
143
212
  --skip-tool-wire Skip the wire-tool phase in the Claude skill body
144
213
  --skip-codex-skill Install only the Claude Code skill and slash command
145
214
  --codex-only Install only the Codex skill
215
+ --uninstall Remove generated files whose version matches this package
216
+ --status Show installed onboarding file versions and suggested action
217
+ --json Emit machine-readable JSON with --status
146
218
  --skill-dir <path> Override the target Claude root directory (default: ~/.claude)
147
219
  --codex-skill-dir <path> Override the target Codex skills directory (default: ~/.codex/skills)
148
220
  -h, --help Show this help
@@ -181,10 +253,19 @@ function buildDetectedSummary(): string {
181
253
  }
182
254
 
183
255
  function readPackageVersion(): string {
256
+ const packageJsonPaths = [
257
+ join(__dirname, "..", "package.json"),
258
+ join(__dirname, "..", "..", "package.json"),
259
+ ];
260
+
184
261
  try {
185
- const raw: unknown = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf8"));
186
- if (isObject(raw) && typeof raw["version"] === "string") {
187
- return raw["version"];
262
+ for (const packageJsonPath of packageJsonPaths) {
263
+ if (existsSync(packageJsonPath)) {
264
+ const raw: unknown = JSON.parse(readFileSync(packageJsonPath, "utf8"));
265
+ if (isObject(raw) && typeof raw["version"] === "string") {
266
+ return raw["version"];
267
+ }
268
+ }
188
269
  }
189
270
  } catch {
190
271
  // best-effort: leave version as the fallback
@@ -192,6 +273,176 @@ function readPackageVersion(): string {
192
273
  return "0.0.0";
193
274
  }
194
275
 
276
+ function parseVersion(version: string): ParsedVersion | null {
277
+ const match = SEMVER_PATTERN.exec(version);
278
+ if (match === null) {
279
+ return null;
280
+ }
281
+
282
+ const majorText = match[1];
283
+ const minorText = match[2];
284
+ const patchText = match[3];
285
+ if (majorText === undefined || minorText === undefined || patchText === undefined) {
286
+ return null;
287
+ }
288
+
289
+ const major = Number(majorText);
290
+ const minor = Number(minorText);
291
+ const patch = Number(patchText);
292
+ if (!Number.isInteger(major) || !Number.isInteger(minor) || !Number.isInteger(patch)) {
293
+ return null;
294
+ }
295
+
296
+ return { major, minor, patch };
297
+ }
298
+
299
+ function compareVersions(left: string, right: string): number | null {
300
+ const leftVersion = parseVersion(left);
301
+ const rightVersion = parseVersion(right);
302
+ if (leftVersion === null || rightVersion === null) {
303
+ return null;
304
+ }
305
+
306
+ const leftParts = [leftVersion.major, leftVersion.minor, leftVersion.patch];
307
+ const rightParts = [rightVersion.major, rightVersion.minor, rightVersion.patch];
308
+ for (let index = 0; index < leftParts.length; index++) {
309
+ const leftPart = leftParts[index];
310
+ const rightPart = rightParts[index];
311
+ if (leftPart === undefined || rightPart === undefined) {
312
+ return null;
313
+ }
314
+ if (leftPart > rightPart) {
315
+ return 1;
316
+ }
317
+ if (leftPart < rightPart) {
318
+ return -1;
319
+ }
320
+ }
321
+
322
+ return 0;
323
+ }
324
+
325
+ function extractGeneratedVersion(body: string): string | null {
326
+ const frontmatterMatch = FRONTMATTER_PATTERN.exec(body);
327
+ if (frontmatterMatch === null) {
328
+ return null;
329
+ }
330
+
331
+ const frontmatter = frontmatterMatch[1];
332
+ if (frontmatter === undefined) {
333
+ return null;
334
+ }
335
+
336
+ const versionMatch = GENERATED_VERSION_PATTERN.exec(frontmatter);
337
+ if (versionMatch === null) {
338
+ return null;
339
+ }
340
+
341
+ return versionMatch[1] ?? null;
342
+ }
343
+
344
+ function hasAgentInspectorAuthor(body: string): boolean {
345
+ const frontmatterMatch = FRONTMATTER_PATTERN.exec(body);
346
+ if (frontmatterMatch === null) {
347
+ return false;
348
+ }
349
+
350
+ const frontmatter = frontmatterMatch[1];
351
+ if (frontmatter === undefined) {
352
+ return false;
353
+ }
354
+
355
+ return /^\s*author:\s*agent-inspector\s*$/m.test(frontmatter);
356
+ }
357
+
358
+ function readGeneratedVersion(path: string): string | null {
359
+ try {
360
+ return extractGeneratedVersion(readFileSync(path, "utf8"));
361
+ } catch {
362
+ return null;
363
+ }
364
+ }
365
+
366
+ function isMatchingGeneratedFile(path: string, currentVersion: string): boolean {
367
+ try {
368
+ const body = readFileSync(path, "utf8");
369
+ return hasAgentInspectorAuthor(body) && extractGeneratedVersion(body) === currentVersion;
370
+ } catch {
371
+ return false;
372
+ }
373
+ }
374
+
375
+ function readGeneratedFileStatus(file: PlannedFile, currentVersion: string): GeneratedFileStatus {
376
+ if (!existsSync(file.path)) {
377
+ return {
378
+ label: file.label,
379
+ path: file.path,
380
+ state: "missing",
381
+ version: null,
382
+ action: actionForStatus(file.label, "missing"),
383
+ };
384
+ }
385
+
386
+ try {
387
+ const body = readFileSync(file.path, "utf8");
388
+ const version = extractGeneratedVersion(body);
389
+ if (!hasAgentInspectorAuthor(body) || version === null) {
390
+ return {
391
+ label: file.label,
392
+ path: file.path,
393
+ state: "custom",
394
+ version,
395
+ action: actionForStatus(file.label, "custom"),
396
+ };
397
+ }
398
+
399
+ const comparison = compareVersions(version, currentVersion);
400
+ if (comparison === null) {
401
+ return {
402
+ label: file.label,
403
+ path: file.path,
404
+ state: "custom",
405
+ version,
406
+ action: actionForStatus(file.label, "custom"),
407
+ };
408
+ }
409
+ if (comparison < 0) {
410
+ return {
411
+ label: file.label,
412
+ path: file.path,
413
+ state: "outdated",
414
+ version,
415
+ action: actionForStatus(file.label, "outdated"),
416
+ };
417
+ }
418
+ if (comparison > 0) {
419
+ return {
420
+ label: file.label,
421
+ path: file.path,
422
+ state: "newer",
423
+ version,
424
+ action: actionForStatus(file.label, "newer"),
425
+ };
426
+ }
427
+
428
+ return {
429
+ label: file.label,
430
+ path: file.path,
431
+ state: "current",
432
+ version,
433
+ action: actionForStatus(file.label, "current"),
434
+ };
435
+ } catch {
436
+ return {
437
+ label: file.label,
438
+ path: file.path,
439
+ state: "custom",
440
+ version: null,
441
+ action: actionForStatus(file.label, "custom"),
442
+ };
443
+ }
444
+ }
445
+
195
446
  function buildPlannedFiles(
196
447
  flags: OnboardFlags,
197
448
  targets: OnboardTargets,
@@ -207,7 +458,7 @@ function buildPlannedFiles(
207
458
  detectedSummary,
208
459
  })
209
460
  : "";
210
- const commandBody = installClaude ? renderCommandOnboard() : "";
461
+ const commandBody = installClaude ? renderCommandOnboard({ version }) : "";
211
462
  const codexSkillBody = installCodex
212
463
  ? renderCodexSkillOnboard({
213
464
  version,
@@ -237,8 +488,136 @@ function buildPlannedFiles(
237
488
  ];
238
489
  }
239
490
 
240
- function shouldWrite(file: PlannedFile, force: boolean): boolean {
241
- return file.enabled && (force || !existsSync(file.path));
491
+ function shouldWrite(file: PlannedFile, force: boolean, currentVersion: string): boolean {
492
+ if (!file.enabled) {
493
+ return false;
494
+ }
495
+ if (force || !existsSync(file.path)) {
496
+ return true;
497
+ }
498
+
499
+ const existingVersion = readGeneratedVersion(file.path);
500
+ if (existingVersion === null) {
501
+ return false;
502
+ }
503
+
504
+ const comparison = compareVersions(existingVersion, currentVersion);
505
+ return comparison !== null && comparison < 0;
506
+ }
507
+
508
+ function shouldUninstall(file: PlannedFile, currentVersion: string): boolean {
509
+ return (
510
+ file.enabled && existsSync(file.path) && isMatchingGeneratedFile(file.path, currentVersion)
511
+ );
512
+ }
513
+
514
+ function removeEmptyDirectory(path: string): void {
515
+ try {
516
+ if (readdirSync(path).length === 0) {
517
+ rmdirSync(path);
518
+ }
519
+ } catch {
520
+ // best-effort cleanup only
521
+ }
522
+ }
523
+
524
+ function runUninstall(
525
+ plannedFiles: readonly PlannedFile[],
526
+ currentVersion: string,
527
+ dryRun: boolean,
528
+ ): number {
529
+ const enabledFiles = plannedFiles.filter((file) => file.enabled);
530
+ const filesToRemove = enabledFiles.filter((file) => shouldUninstall(file, currentVersion));
531
+
532
+ if (dryRun) {
533
+ process.stdout.write(`agent-inspector onboard --uninstall --dry-run\n\n`);
534
+ for (const file of enabledFiles) {
535
+ const status = shouldUninstall(file, currentVersion) ? "matching" : "skipped";
536
+ process.stdout.write(`${file.label}: ${file.path} (${status})\n`);
537
+ }
538
+ process.stdout.write(`\nNo files were removed.\n`);
539
+ return 0;
540
+ }
541
+
542
+ if (filesToRemove.length === 0) {
543
+ process.stdout.write(
544
+ "agent-inspector onboard: no generated onboarding files match this package version\n",
545
+ );
546
+ return 0;
547
+ }
548
+
549
+ try {
550
+ for (const file of filesToRemove) {
551
+ unlinkSync(file.path);
552
+ removeEmptyDirectory(dirname(file.path));
553
+ process.stdout.write(`Removed ${file.label}: ${file.path}\n`);
554
+ }
555
+ } catch (err) {
556
+ const msg = err instanceof Error ? err.message : String(err);
557
+ process.stderr.write(`agent-inspector onboard: failed to remove files: ${msg}\n`);
558
+ return 1;
559
+ }
560
+
561
+ return 0;
562
+ }
563
+
564
+ function formatStatusVersion(status: GeneratedFileStatus): string {
565
+ return status.version ?? "-";
566
+ }
567
+
568
+ function summarizeStatusAction(statuses: readonly GeneratedFileStatus[]): string {
569
+ const actionable = statuses.filter(
570
+ (status) => status.state === "missing" || status.state === "outdated",
571
+ );
572
+ if (actionable.length > 0) {
573
+ const actions = Array.from(new Set(actionable.map((status) => status.action)));
574
+ return `Run: ${actions.join(" && ")}`;
575
+ }
576
+ if (statuses.some((status) => status.state === "custom")) {
577
+ return "Custom files are preserved. Use --force only if you want to replace them.";
578
+ }
579
+ if (statuses.some((status) => status.state === "newer")) {
580
+ return "Installed files are newer than this package. No action needed.";
581
+ }
582
+ return "All selected onboarding files are current.";
583
+ }
584
+
585
+ function runStatus(
586
+ plannedFiles: readonly PlannedFile[],
587
+ currentVersion: string,
588
+ json: boolean,
589
+ ): number {
590
+ const enabledFiles = plannedFiles.filter((file) => file.enabled);
591
+ const statuses = enabledFiles.map((file) => readGeneratedFileStatus(file, currentVersion));
592
+ const summary = summarizeStatusAction(statuses);
593
+
594
+ if (json) {
595
+ process.stdout.write(
596
+ `${JSON.stringify(
597
+ {
598
+ packageVersion: currentVersion,
599
+ files: statuses,
600
+ suggestedAction: summary,
601
+ },
602
+ null,
603
+ 2,
604
+ )}\n`,
605
+ );
606
+ return 0;
607
+ }
608
+
609
+ process.stdout.write(`agent-inspector onboard status\n`);
610
+ process.stdout.write(`Package version: ${currentVersion}\n\n`);
611
+
612
+ for (const status of statuses) {
613
+ process.stdout.write(`${status.label}: ${status.state}`);
614
+ process.stdout.write(`, version ${formatStatusVersion(status)}\n`);
615
+ process.stdout.write(` ${status.path}\n`);
616
+ process.stdout.write(` Action: ${status.action}\n`);
617
+ }
618
+
619
+ process.stdout.write(`\nSuggested action: ${summary}\n`);
620
+ return 0;
242
621
  }
243
622
 
244
623
  export function runOnboard(argv: readonly string[]): Promise<number> {
@@ -260,11 +639,24 @@ function runOnboardSync(argv: readonly string[]): number {
260
639
  const plannedFiles = buildPlannedFiles(flags, targets, version);
261
640
  const enabledFiles = plannedFiles.filter((file) => file.enabled);
262
641
 
642
+ if (flags.json && !flags.status) {
643
+ process.stderr.write("agent-inspector onboard: --json is only supported with --status\n");
644
+ return 2;
645
+ }
646
+
263
647
  if (enabledFiles.length === 0) {
264
648
  process.stderr.write("agent-inspector onboard: no onboarding target selected\n");
265
649
  return 2;
266
650
  }
267
651
 
652
+ if (flags.status) {
653
+ return runStatus(plannedFiles, version, flags.json);
654
+ }
655
+
656
+ if (flags.uninstall) {
657
+ return runUninstall(plannedFiles, version, flags.dryRun);
658
+ }
659
+
268
660
  if (flags.dryRun) {
269
661
  process.stdout.write(`agent-inspector onboard --dry-run\n\n`);
270
662
  for (const file of enabledFiles) {
@@ -294,9 +686,11 @@ function runOnboardSync(argv: readonly string[]): number {
294
686
  return 0;
295
687
  }
296
688
 
297
- const filesToWrite = plannedFiles.filter((file) => shouldWrite(file, flags.force));
689
+ const filesToWrite = plannedFiles.filter((file) => shouldWrite(file, flags.force, version));
298
690
  if (filesToWrite.length === 0) {
299
- process.stdout.write("agent-inspector onboard: all selected onboarding files already exist\n");
691
+ process.stdout.write(
692
+ "agent-inspector onboard: all selected onboarding files are already up to date\n",
693
+ );
300
694
  process.stdout.write("Re-run with --force to refresh.\n");
301
695
  return 0;
302
696
  }
@@ -316,7 +710,7 @@ function runOnboardSync(argv: readonly string[]): number {
316
710
  const toolHint = firstTool !== null ? firstTool.displayName : "no known tool detected";
317
711
 
318
712
  for (const file of filesToWrite) {
319
- process.stdout.write(`Installed ${file.label} to: ${file.path}\n`);
713
+ process.stdout.write(`Installed/updated ${file.label} to: ${file.path}\n`);
320
714
  }
321
715
  process.stdout.write(`\nNext steps:\n`);
322
716
  if (!flags.codexOnly) {
@@ -57,12 +57,14 @@ Check that the package and Codex home exist without printing secrets.
57
57
 
58
58
  \`\`\`powershell
59
59
  Get-Command agent-inspector -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source
60
+ agent-inspector onboard --status
60
61
  Test-Path (Join-Path $env:USERPROFILE ".codex")
61
62
  Test-Path (Join-Path $env:USERPROFILE ".codex\\config.toml")
62
63
  \`\`\`
63
64
 
64
65
  \`\`\`bash
65
66
  command -v agent-inspector || true
67
+ agent-inspector onboard --status
66
68
  test -d "$HOME/.codex" && echo "codex home: present" || echo "codex home: missing"
67
69
  test -f "$HOME/.codex/config.toml" && echo "config: present" || echo "config: missing"
68
70
  \`\`\`
@@ -73,6 +75,9 @@ If \`agent-inspector\` is missing, ask the user to install it first:
73
75
  npm install -g @tonyclaw/agent-inspector
74
76
  \`\`\`
75
77
 
78
+ If \`agent-inspector onboard --status\` reports \`outdated\` or \`missing\`, ask the user to run
79
+ \`agent-inspector onboard --force\` and restart this skill before continuing.
80
+
76
81
  Do not read \`~/.codex/auth.json\`, token files, or unrelated Codex state files.
77
82
 
78
83
  ---
@@ -1,17 +1,25 @@
1
1
  /**
2
- * Renders `~/.claude/commands/agent-inspector:onboard.md` a thin slash
2
+ * Renders `~/.claude/commands/agent-inspector:onboard.md` - a thin slash
3
3
  * command that loads the `agent-inspector-onboard` skill. Claude Code
4
4
  * auto-discovers both the skill and the command once they're written,
5
5
  * so the command body just references the skill by name.
6
6
  */
7
7
 
8
- export function renderCommandOnboard(): string {
8
+ export type CommandOnboardContext = {
9
+ /** Package version (from `package.json`), stamped into the frontmatter. */
10
+ readonly version: string;
11
+ };
12
+
13
+ export function renderCommandOnboard(ctx: CommandOnboardContext): string {
9
14
  return `---
10
- description: Walk through agent-inspector setup start the proxy, wire your AI tool, capture your first request.
15
+ description: Walk through agent-inspector setup - start the proxy, wire your AI tool, capture your first request.
16
+ metadata:
17
+ author: agent-inspector
18
+ version: ${ctx.version}
11
19
  ---
12
20
 
13
21
  Invoke the \`agent-inspector-onboard\` skill and follow its phases.
14
22
 
15
- The user wants to set up agent-inspector. If they have specific context (e.g. "I'm on Windows", "I already added my API key"), honor it but otherwise just run the skill end-to-end and let the \`EXPLAIN / DO / PAUSE\` markers drive the conversation.
23
+ The user wants to set up agent-inspector. If they have specific context (e.g. "I'm on Windows", "I already added my API key"), honor it - but otherwise just run the skill end-to-end and let the \`EXPLAIN / DO / PAUSE\` markers drive the conversation.
16
24
  `;
17
25
  }
@@ -76,7 +76,19 @@ Default proxy port: \`${port}\` (override with \`PORT=<n> agent-inspector\` or \
76
76
 
77
77
  **EXPLAIN:** "Before we do anything, let me see what's already set up. If some of the steps are already done, we can skip them."
78
78
 
79
- **DO:** Probe the three pieces of state this skill touches. Use targeted checks do **not** read large JSON files into the conversation.
79
+ **DO:** First check whether the generated onboarding files match the installed npm package. If any
80
+ entry is \`outdated\` or \`missing\`, tell the user to run \`agent-inspector onboard --force\` before
81
+ continuing so they are not guided by stale instructions.
82
+
83
+ \`\`\`bash
84
+ agent-inspector onboard --status
85
+ \`\`\`
86
+
87
+ \`\`\`powershell
88
+ agent-inspector onboard --status
89
+ \`\`\`
90
+
91
+ **DO:** Then probe the three pieces of state this skill touches. Use targeted checks — do **not** read large JSON files into the conversation.
80
92
 
81
93
  \`\`\`bash
82
94
  # 1. Is the proxy already up?
@@ -89,8 +101,6 @@ agent_inspector_data_dir() {
89
101
  case "$AGENT_INSPECTOR_DATA_DIR" in /*) printf '%s\n' "$AGENT_INSPECTOR_DATA_DIR" ;; *) printf '%s\n' "$base/$AGENT_INSPECTOR_DATA_DIR" ;; esac
90
102
  elif [ -n "\${AGENT_INSPECTOR_CONFIG_DIR:-}" ]; then
91
103
  case "$AGENT_INSPECTOR_CONFIG_DIR" in /*) printf '%s\n' "$AGENT_INSPECTOR_CONFIG_DIR" ;; *) printf '%s\n' "$base/$AGENT_INSPECTOR_CONFIG_DIR" ;; esac
92
- elif { [ ! -e "$base/.agent-inspector/providers.json" ] && [ ! -e "$base/.agent-inspector/config.json" ] && [ ! -e "$base/.agent-inspector/logs" ] && [ ! -e "$base/.agent-inspector/chunks" ]; } && { [ -e "$base/.llm-inspector/providers.json" ] || [ -e "$base/.llm-inspector/config.json" ] || [ -e "$base/.llm-inspector/logs" ] || [ -e "$base/.llm-inspector/chunks" ]; }; then
93
- printf '%s\n' "$base/.llm-inspector"
94
104
  else
95
105
  printf '%s\n' "$base/.agent-inspector"
96
106
  fi
@@ -137,14 +147,7 @@ function Resolve-AgentInspectorDataDir {
137
147
  if ([System.IO.Path]::IsPathRooted($env:AGENT_INSPECTOR_CONFIG_DIR)) { return $env:AGENT_INSPECTOR_CONFIG_DIR }
138
148
  return (Join-Path $base $env:AGENT_INSPECTOR_CONFIG_DIR)
139
149
  }
140
- $current = Join-Path $base '.agent-inspector'
141
- $legacy = Join-Path $base '.llm-inspector'
142
- $currentHasState = (Test-Path (Join-Path $current 'providers.json')) -or (Test-Path (Join-Path $current 'config.json')) -or (Test-Path (Join-Path $current 'logs')) -or (Test-Path (Join-Path $current 'chunks'))
143
- $legacyHasState = (Test-Path (Join-Path $legacy 'providers.json')) -or (Test-Path (Join-Path $legacy 'config.json')) -or (Test-Path (Join-Path $legacy 'logs')) -or (Test-Path (Join-Path $legacy 'chunks'))
144
- if (-not $currentHasState -and $legacyHasState) {
145
- return $legacy
146
- }
147
- return $current
150
+ return (Join-Path $base '.agent-inspector')
148
151
  }
149
152
  $dataDir = Resolve-AgentInspectorDataDir
150
153
  $providers = Join-Path $dataDir 'providers.json'
@@ -260,8 +263,6 @@ agent_inspector_data_dir() {
260
263
  case "$AGENT_INSPECTOR_DATA_DIR" in /*) printf '%s\n' "$AGENT_INSPECTOR_DATA_DIR" ;; *) printf '%s\n' "$base/$AGENT_INSPECTOR_DATA_DIR" ;; esac
261
264
  elif [ -n "\${AGENT_INSPECTOR_CONFIG_DIR:-}" ]; then
262
265
  case "$AGENT_INSPECTOR_CONFIG_DIR" in /*) printf '%s\n' "$AGENT_INSPECTOR_CONFIG_DIR" ;; *) printf '%s\n' "$base/$AGENT_INSPECTOR_CONFIG_DIR" ;; esac
263
- elif { [ ! -e "$base/.agent-inspector/providers.json" ] && [ ! -e "$base/.agent-inspector/config.json" ] && [ ! -e "$base/.agent-inspector/logs" ] && [ ! -e "$base/.agent-inspector/chunks" ]; } && { [ -e "$base/.llm-inspector/providers.json" ] || [ -e "$base/.llm-inspector/config.json" ] || [ -e "$base/.llm-inspector/logs" ] || [ -e "$base/.llm-inspector/chunks" ]; }; then
264
- printf '%s\n' "$base/.llm-inspector"
265
266
  else
266
267
  printf '%s\n' "$base/.agent-inspector"
267
268
  fi
@@ -305,14 +306,7 @@ function Resolve-AgentInspectorDataDir {
305
306
  if ([System.IO.Path]::IsPathRooted($env:AGENT_INSPECTOR_CONFIG_DIR)) { return $env:AGENT_INSPECTOR_CONFIG_DIR }
306
307
  return (Join-Path $base $env:AGENT_INSPECTOR_CONFIG_DIR)
307
308
  }
308
- $current = Join-Path $base '.agent-inspector'
309
- $legacy = Join-Path $base '.llm-inspector'
310
- $currentHasState = (Test-Path (Join-Path $current 'providers.json')) -or (Test-Path (Join-Path $current 'config.json')) -or (Test-Path (Join-Path $current 'logs')) -or (Test-Path (Join-Path $current 'chunks'))
311
- $legacyHasState = (Test-Path (Join-Path $legacy 'providers.json')) -or (Test-Path (Join-Path $legacy 'config.json')) -or (Test-Path (Join-Path $legacy 'logs')) -or (Test-Path (Join-Path $legacy 'chunks'))
312
- if (-not $currentHasState -and $legacyHasState) {
313
- return $legacy
314
- }
315
- return $current
309
+ return (Join-Path $base '.agent-inspector')
316
310
  }
317
311
  $dir = Resolve-AgentInspectorDataDir
318
312
  $file = Join-Path $dir 'providers.json'
@@ -546,6 +540,8 @@ done
546
540
  \`\`\`
547
541
 
548
542
  - **Re-run onboard**: \`agent-inspector onboard --force\` refreshes this skill.
543
+ - **Check onboard files**: \`agent-inspector onboard --status\` shows whether generated skills are
544
+ current, outdated, missing, newer, or custom.
549
545
  - **Full docs**: see the project README (linked from the Web UI footer).
550
546
 
551
547
  > **PAUSE** — use \`AskUserQuestion\` with header \`All set?\` and options \`["All set, I'm done", "Wait, I want to revisit a phase"]\`. Wait for the answer.
@@ -54,6 +54,11 @@ export function OnboardingBanner(): JSX.Element | null {
54
54
  <strong>Replay and Memory</strong>: available in both modes for provider checks and
55
55
  reviewable knowledge candidates.
56
56
  </li>
57
+ <li>
58
+ <strong>Agent setup</strong>: run <code>agent-inspector onboard --status</code>, then
59
+ use <code>/agent-inspector:onboard</code> in Claude Code or ask Codex to use{" "}
60
+ <code>agent-inspector-onboard</code>.
61
+ </li>
57
62
  </ul>
58
63
  </div>
59
64
  <button