@tonyclaw/agent-inspector 3.0.43 → 3.0.45

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 (80) hide show
  1. package/.output/cli.js +20744 -16945
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-BAXxwTwT.js → CompareDrawer-B3_TLTJh.js} +1 -1
  4. package/.output/public/assets/{InspectorPet-xYuGV4Qz.js → InspectorPet-Cpn6hv4P.js} +1 -1
  5. package/.output/public/assets/{ProxyViewerContainer-B6GZnUWi.js → ProxyViewerContainer-BEAhuGOJ.js} +27 -27
  6. package/.output/public/assets/{ReplayDialog-D73pyIg3.js → ReplayDialog-B3EKVdJk.js} +1 -1
  7. package/.output/public/assets/{RequestAnatomy-CSDta_nu.js → RequestAnatomy-COOQjyUC.js} +1 -1
  8. package/.output/public/assets/{ResponseView-C4aIUPby.js → ResponseView-DBCjkwye.js} +1 -1
  9. package/.output/public/assets/{StreamingChunkSequence-D4xxosLx.js → StreamingChunkSequence-CuXhDgMo.js} +1 -1
  10. package/.output/public/assets/{_sessionId-CZHbJyWO.js → _sessionId-BJlM3nmt.js} +1 -1
  11. package/.output/public/assets/{_sessionId-DvuNLzSE.js → _sessionId-zbgYl7ZJ.js} +1 -1
  12. package/.output/public/assets/{index-DZ_wpqQM.js → index-BIK4bPTv.js} +1 -1
  13. package/.output/public/assets/{index-B17u7JMC.js → index-BZo_8koU.js} +15 -15
  14. package/.output/public/assets/{index-C0g79BAs.js → index-D2Qw6Ri2.js} +1 -1
  15. package/.output/public/assets/{index-BWjeqxbk.css → index-ZWschkCh.css} +1 -1
  16. package/.output/public/assets/{index-CmEO8ykj.js → index-xF_NY8Yb.js} +5 -5
  17. package/.output/public/assets/{json-viewer-CuWU-H6K.js → json-viewer-B-Ao6pwW.js} +1 -1
  18. package/.output/public/assets/{jszip.min-DPJI_UKW.js → jszip.min-B_rfAUCe.js} +1 -1
  19. package/.output/server/_libs/modelcontextprotocol__core.mjs +25 -25
  20. package/.output/server/_libs/modelcontextprotocol__server.mjs +2 -2
  21. package/.output/server/_libs/zod.mjs +6 -6
  22. package/.output/server/{_sessionId-Cun21KZF.mjs → _sessionId-CJ99gZFV.mjs} +3 -1
  23. package/.output/server/{_sessionId-CoFyeOti.mjs → _sessionId-DEjScBpK.mjs} +5 -3
  24. package/.output/server/_ssr/{CompareDrawer-Bgc2TZh_.mjs → CompareDrawer-DtOIMeQB.mjs} +4 -2
  25. package/.output/server/_ssr/{InspectorPet-DyK2ojeh.mjs → InspectorPet-CXc4oCBQ.mjs} +3 -1
  26. package/.output/server/_ssr/{ProxyViewerContainer-hvn6oVWD.mjs → ProxyViewerContainer-DVPlwfkt.mjs} +52 -27
  27. package/.output/server/_ssr/{ReplayDialog-KpnrGtZ-.mjs → ReplayDialog-BQEA95qC.mjs} +6 -4
  28. package/.output/server/_ssr/{RequestAnatomy-uCsfucn8.mjs → RequestAnatomy-BMyd8B7Q.mjs} +4 -2
  29. package/.output/server/_ssr/{ResponseView-C8UPqoPO.mjs → ResponseView-qrTaEIM8.mjs} +4 -2
  30. package/.output/server/_ssr/{StreamingChunkSequence-BP28I9sh.mjs → StreamingChunkSequence-BFTL8mIy.mjs} +5 -3
  31. package/.output/server/_ssr/{index-Du3YNDKx.mjs → index-Bcj1Hcy4.mjs} +4 -2
  32. package/.output/server/_ssr/{index-BVwVoQlZ.mjs → index-DW-M7XUG.mjs} +3 -1
  33. package/.output/server/_ssr/index.mjs +4 -3
  34. package/.output/server/_ssr/{json-viewer-DrE0vtlB.mjs → json-viewer-DiI0Yjsn.mjs} +4 -2
  35. package/.output/server/_ssr/publicBasePathContext-5264L3rX.mjs +148 -0
  36. package/.output/server/_ssr/{router-C6nWUZHK.mjs → router-BJ_ltLim.mjs} +2432 -475
  37. package/.output/server/_ssr/start-C6MjPjxp.mjs +57 -0
  38. package/.output/server/_tanstack-start-manifest_v-C6pXXX9R.mjs +4 -0
  39. package/.output/server/index.mjs +99 -99
  40. package/.output/workers/logFinalizer.worker.js +4 -0
  41. package/.output/workers/sessionWorkerEntry.js +4 -0
  42. package/README.md +221 -25
  43. package/docs/instance-control.openapi.yaml +306 -0
  44. package/package.json +4 -1
  45. package/src/cli/alias.ts +440 -0
  46. package/src/cli/instance.ts +1037 -0
  47. package/src/cli/instanceArgs.ts +266 -0
  48. package/src/cli/instanceControl.ts +183 -0
  49. package/src/cli/instanceModel.ts +275 -0
  50. package/src/cli/instanceRegistry.ts +740 -0
  51. package/src/cli/onboard.ts +123 -35
  52. package/src/cli/onboardTarget.ts +179 -0
  53. package/src/cli/templates/codex-skill-onboard.ts +76 -28
  54. package/src/cli/templates/skill-onboard.ts +93 -59
  55. package/src/cli.ts +154 -16
  56. package/src/components/ProxyViewer.tsx +13 -5
  57. package/src/components/providers/SettingsDialog.tsx +13 -8
  58. package/src/lib/basePath.ts +8 -2
  59. package/src/lib/codingAgentEndpoints.ts +16 -0
  60. package/src/lib/instanceContract.ts +220 -0
  61. package/src/lib/managedInstance.ts +104 -0
  62. package/src/lib/publicBasePathContext.ts +107 -0
  63. package/src/mcp/instanceHandlers.ts +149 -0
  64. package/src/mcp/loopback.ts +16 -5
  65. package/src/mcp/mode.ts +25 -0
  66. package/src/mcp/server.ts +258 -18
  67. package/src/mcp/toolHandlers.ts +2 -2
  68. package/src/proxy/identityProxy.ts +656 -36
  69. package/src/proxy/runtimeShutdown.ts +73 -1
  70. package/src/proxy/sessionInfo.ts +5 -4
  71. package/src/routes/api/-instances.ts +109 -0
  72. package/src/routes/api/instances.$name.connection.ts +12 -0
  73. package/src/routes/api/instances.$name.restart.ts +23 -0
  74. package/src/routes/api/instances.$name.start.ts +18 -0
  75. package/src/routes/api/instances.$name.stop.ts +23 -0
  76. package/src/routes/api/instances.$name.ts +12 -0
  77. package/src/routes/api/instances.ts +16 -0
  78. package/src/start.ts +10 -0
  79. package/.output/server/_ssr/start-HYkvq4Ni.mjs +0 -4
  80. package/.output/server/_tanstack-start-manifest_v-rshlRaiN.mjs +0 -4
@@ -26,6 +26,7 @@ import { fileURLToPath } from "node:url";
26
26
 
27
27
  import { appendBasePathToOrigin, getConfiguredBasePath } from "../lib/basePath.js";
28
28
  import { detectAll, detectFirst } from "./detect-tools.js";
29
+ import { type OnboardTarget, resolveOnboardTarget } from "./onboardTarget.js";
29
30
  import { renderCommandOnboard } from "./templates/command-onboard.js";
30
31
  import {
31
32
  renderCodexSkillOnboard,
@@ -38,7 +39,11 @@ const __dirname = dirname(__filename);
38
39
 
39
40
  const DEFAULT_PORT = 9527;
40
41
  const DEFAULT_ORIGIN = `http://localhost:${DEFAULT_PORT}`;
41
- const DEFAULT_MCP_URL = `${DEFAULT_ORIGIN}/api/mcp`;
42
+ const DEFAULT_MCP_URL = appendBasePathToOrigin(
43
+ DEFAULT_ORIGIN,
44
+ "/api/mcp",
45
+ getConfiguredBasePath({}),
46
+ );
42
47
  const LEGACY_MCP_URL = "http://localhost:9527/api/mcp";
43
48
  const SKILL_DIR_NAME = "agent-inspector-onboard";
44
49
  const SKILL_FILE_NAME = "SKILL.md";
@@ -65,12 +70,15 @@ export type OnboardFlags = {
65
70
  opencodeOnly: boolean;
66
71
  opencodeConfig: string | null;
67
72
  opencodeTransport: AgentMcpTransport;
68
- opencodeMcpUrl: string;
73
+ opencodeMcpUrl: string | null;
69
74
  mimo: boolean;
70
75
  mimoOnly: boolean;
71
76
  mimoConfig: string | null;
72
77
  mimoTransport: AgentMcpTransport;
73
- mimoMcpUrl: string;
78
+ mimoMcpUrl: string | null;
79
+ instanceName: string | null;
80
+ baseUrl: string | null;
81
+ mcpName: string | null;
74
82
  uninstall: boolean;
75
83
  status: boolean;
76
84
  json: boolean;
@@ -88,6 +96,7 @@ type AgentMcpConfigTarget = {
88
96
  configPath: string;
89
97
  transport: AgentMcpTransport;
90
98
  endpoint: string;
99
+ mcpName: string;
91
100
  setupAction: string;
92
101
  forceAction: string;
93
102
  verifyCommand: string;
@@ -102,6 +111,7 @@ type AgentMcpConfigStatus = {
102
111
  state: AgentMcpConfigState;
103
112
  transport: AgentMcpStatusTransport;
104
113
  endpoint: string;
114
+ mcpName: string;
105
115
  action: string;
106
116
  forceAction: string;
107
117
  verifyCommand: string;
@@ -183,11 +193,6 @@ function actionForStatus(label: string, state: GeneratedFileState): string {
183
193
  }
184
194
 
185
195
  function parseFlags(argv: readonly string[]): OnboardFlags {
186
- const defaultMcpUrl = appendBasePathToOrigin(
187
- DEFAULT_ORIGIN,
188
- "/api/mcp",
189
- getConfiguredBasePath(process.env),
190
- );
191
196
  const flags: OnboardFlags = {
192
197
  force: false,
193
198
  dryRun: false,
@@ -199,12 +204,15 @@ function parseFlags(argv: readonly string[]): OnboardFlags {
199
204
  opencodeOnly: false,
200
205
  opencodeConfig: null,
201
206
  opencodeTransport: "local",
202
- opencodeMcpUrl: defaultMcpUrl,
207
+ opencodeMcpUrl: null,
203
208
  mimo: false,
204
209
  mimoOnly: false,
205
210
  mimoConfig: null,
206
211
  mimoTransport: "local",
207
- mimoMcpUrl: defaultMcpUrl,
212
+ mimoMcpUrl: null,
213
+ instanceName: null,
214
+ baseUrl: null,
215
+ mcpName: null,
208
216
  uninstall: false,
209
217
  status: false,
210
218
  json: false,
@@ -332,6 +340,36 @@ function parseFlags(argv: readonly string[]): OnboardFlags {
332
340
  i++;
333
341
  break;
334
342
  }
343
+ case "--instance": {
344
+ const next = argv[i + 1];
345
+ if (next === undefined || next.trim().length === 0) {
346
+ process.stderr.write("agent-inspector onboard: --instance requires a name\n");
347
+ process.exit(2);
348
+ }
349
+ flags.instanceName = next;
350
+ i++;
351
+ break;
352
+ }
353
+ case "--base-url": {
354
+ const next = argv[i + 1];
355
+ if (next === undefined || next.trim().length === 0) {
356
+ process.stderr.write("agent-inspector onboard: --base-url requires a URL\n");
357
+ process.exit(2);
358
+ }
359
+ flags.baseUrl = next;
360
+ i++;
361
+ break;
362
+ }
363
+ case "--mcp-name": {
364
+ const next = argv[i + 1];
365
+ if (next === undefined || next.trim().length === 0) {
366
+ process.stderr.write("agent-inspector onboard: --mcp-name requires a name\n");
367
+ process.exit(2);
368
+ }
369
+ flags.mcpName = next;
370
+ i++;
371
+ break;
372
+ }
335
373
  case "--uninstall":
336
374
  flags.uninstall = true;
337
375
  break;
@@ -401,6 +439,9 @@ Options:
401
439
  --mimo-transport <local|remote>
402
440
  MiMo Code MCP transport to write (default: local)
403
441
  --mimo-mcp-url <url> Agent Inspector MCP endpoint for MiMo Code
442
+ --instance <name> Target a registered named Inspector instance
443
+ --base-url <url> Target an explicit credential-free Inspector Base URL
444
+ --mcp-name <name> MCP config key (named default: agent-inspector-<instance>)
404
445
  --uninstall Remove generated files whose version matches this package
405
446
  --status Show installed onboarding file versions and suggested action
406
447
  --json Emit machine-readable JSON with --status
@@ -487,34 +528,49 @@ function resolveMiMoConfigPath(flags: OnboardFlags): string {
487
528
  return jsoncPath;
488
529
  }
489
530
 
490
- function openCodeTarget(flags: OnboardFlags): AgentMcpConfigTarget {
531
+ function onboardSelectionArguments(flags: OnboardFlags, target: OnboardTarget): string {
532
+ const args: string[] = [];
533
+ if (target.kind === "instance" && target.instanceName !== null) {
534
+ args.push("--instance", target.instanceName);
535
+ } else if (target.kind === "base-url") {
536
+ args.push("--base-url", target.baseUrl);
537
+ }
538
+ if (flags.mcpName !== null) args.push("--mcp-name", target.mcpName);
539
+ return args.length === 0 ? "" : ` ${args.join(" ")}`;
540
+ }
541
+
542
+ function openCodeTarget(flags: OnboardFlags, onboardTarget: OnboardTarget): AgentMcpConfigTarget {
543
+ const setupAction = `agent-inspector onboard --opencode-only${onboardSelectionArguments(flags, onboardTarget)}`;
491
544
  return {
492
545
  label: "OpenCode",
493
546
  configLabel: "OpenCode MCP config",
494
547
  configPath: resolveOpenCodeConfigPath(flags),
495
548
  transport: flags.opencodeTransport,
496
- endpoint: flags.opencodeMcpUrl,
497
- setupAction: "agent-inspector onboard --opencode-only",
498
- forceAction: "agent-inspector onboard --opencode-only --force",
549
+ endpoint: flags.opencodeMcpUrl ?? onboardTarget.mcpUrl,
550
+ mcpName: onboardTarget.mcpName,
551
+ setupAction,
552
+ forceAction: `${setupAction} --force`,
499
553
  verifyCommand: "opencode mcp list",
500
- invalidAction: "fix OpenCode JSON/JSONC, then rerun agent-inspector onboard --opencode-only",
501
- configObjectName: "mcp.agent-inspector",
554
+ invalidAction: `fix OpenCode JSON/JSONC, then rerun ${setupAction}`,
555
+ configObjectName: `mcp.${onboardTarget.mcpName}`,
502
556
  defaultSchema: "https://opencode.ai/config.json",
503
557
  };
504
558
  }
505
559
 
506
- function miMoTarget(flags: OnboardFlags): AgentMcpConfigTarget {
560
+ function miMoTarget(flags: OnboardFlags, onboardTarget: OnboardTarget): AgentMcpConfigTarget {
561
+ const setupAction = `agent-inspector onboard --mimo-only${onboardSelectionArguments(flags, onboardTarget)}`;
507
562
  return {
508
563
  label: "MiMo Code",
509
564
  configLabel: "MiMo Code MCP config",
510
565
  configPath: resolveMiMoConfigPath(flags),
511
566
  transport: flags.mimoTransport,
512
- endpoint: flags.mimoMcpUrl,
513
- setupAction: "agent-inspector onboard --mimo-only",
514
- forceAction: "agent-inspector onboard --mimo-only --force",
567
+ endpoint: flags.mimoMcpUrl ?? onboardTarget.mcpUrl,
568
+ mcpName: onboardTarget.mcpName,
569
+ setupAction,
570
+ forceAction: `${setupAction} --force`,
515
571
  verifyCommand: "mimo mcp list",
516
- invalidAction: "fix MiMo Code JSON/JSONC, then rerun agent-inspector onboard --mimo-only",
517
- configObjectName: "mcp.agent-inspector",
572
+ invalidAction: `fix MiMo Code JSON/JSONC, then rerun ${setupAction}`,
573
+ configObjectName: `mcp.${onboardTarget.mcpName}`,
518
574
  defaultSchema: "https://mimo.xiaomi.com/mimocode/config.json",
519
575
  };
520
576
  }
@@ -731,6 +787,7 @@ function makeAgentStatus(
731
787
  state,
732
788
  transport,
733
789
  endpoint: target.endpoint,
790
+ mcpName: target.mcpName,
734
791
  action: target.setupAction,
735
792
  forceAction: target.forceAction,
736
793
  verifyCommand: target.verifyCommand,
@@ -743,12 +800,12 @@ function makeAgentStatus(
743
800
  };
744
801
  }
745
802
 
746
- function getAgentEntry(config: Record<string, unknown>): unknown {
803
+ function getAgentEntry(config: Record<string, unknown>, mcpName: string): unknown {
747
804
  const mcp = config["mcp"];
748
805
  if (!isObject(mcp)) {
749
806
  return undefined;
750
807
  }
751
- return mcp["agent-inspector"];
808
+ return mcp[mcpName];
752
809
  }
753
810
 
754
811
  function buildAgentConfig(
@@ -768,7 +825,7 @@ function buildAgentConfig(
768
825
 
769
826
  const currentMcp = config["mcp"];
770
827
  const nextMcp: Record<string, unknown> = isObject(currentMcp) ? { ...currentMcp } : {};
771
- nextMcp["agent-inspector"] = buildAgentMcpEntry(target.transport, target.endpoint);
828
+ nextMcp[target.mcpName] = buildAgentMcpEntry(target.transport, target.endpoint);
772
829
  nextConfig["mcp"] = nextMcp;
773
830
  return nextConfig;
774
831
  }
@@ -777,7 +834,10 @@ function formatAgentConfig(config: Record<string, unknown>): string {
777
834
  return `${JSON.stringify(config, null, 2)}\n`;
778
835
  }
779
836
 
780
- function removeAgentEntry(config: Record<string, unknown>): Record<string, unknown> {
837
+ function removeAgentEntry(
838
+ config: Record<string, unknown>,
839
+ mcpName: string,
840
+ ): Record<string, unknown> {
781
841
  const nextConfig: Record<string, unknown> = {};
782
842
  for (const [key, value] of Object.entries(config)) {
783
843
  if (key !== "mcp") {
@@ -792,7 +852,7 @@ function removeAgentEntry(config: Record<string, unknown>): Record<string, unkno
792
852
 
793
853
  const nextMcp: Record<string, unknown> = {};
794
854
  for (const [key, value] of Object.entries(currentMcp)) {
795
- if (key !== "agent-inspector") {
855
+ if (key !== mcpName) {
796
856
  nextMcp[key] = value;
797
857
  }
798
858
  }
@@ -809,7 +869,7 @@ function readAgentStatus(target: AgentMcpConfigTarget): AgentMcpConfigStatus {
809
869
  return makeAgentStatus(target, "invalid", "unknown", read.message);
810
870
  }
811
871
 
812
- const entry = getAgentEntry(read.config);
872
+ const entry = getAgentEntry(read.config, target.mcpName);
813
873
  if (entry === undefined) {
814
874
  const detail = read.exists
815
875
  ? `${target.label} config has no ${target.configObjectName}`
@@ -861,12 +921,14 @@ function buildAgentPlan(
861
921
  }
862
922
 
863
923
  const status = readAgentStatus(target);
864
- const entry = getAgentEntry(read.config);
924
+ const entry = getAgentEntry(read.config, target.mcpName);
865
925
  const nextBody = formatAgentConfig(buildAgentConfig(read.config, target));
866
926
 
867
927
  if (uninstall) {
868
928
  const shouldRemove = entry !== undefined && isKnownAgentEntry(entry, target.endpoint, true);
869
- const removeBody = shouldRemove ? formatAgentConfig(removeAgentEntry(read.config)) : null;
929
+ const removeBody = shouldRemove
930
+ ? formatAgentConfig(removeAgentEntry(read.config, target.mcpName))
931
+ : null;
870
932
  return {
871
933
  status,
872
934
  body: removeBody,
@@ -1129,14 +1191,21 @@ function buildPlannedFiles(
1129
1191
  flags: OnboardFlags,
1130
1192
  targets: OnboardTargets,
1131
1193
  version: string,
1194
+ onboardTarget: OnboardTarget,
1132
1195
  ): PlannedFile[] {
1133
1196
  const installClaude = !flags.codexOnly && !hasOnlyAgentMcpTarget(flags);
1134
1197
  const installCodex = !flags.skipCodexSkill && !hasOnlyAgentMcpTarget(flags);
1135
1198
  const detectedSummary = installClaude ? buildDetectedSummary() : "";
1199
+ const targetArguments = onboardSelectionArguments(flags, onboardTarget);
1136
1200
  const claudeSkillBody = installClaude
1137
1201
  ? renderSkillOnboard({
1138
1202
  version,
1139
- port: DEFAULT_PORT,
1203
+ port: onboardTarget.port,
1204
+ baseUrl: onboardTarget.baseUrl,
1205
+ mcpName: onboardTarget.mcpName,
1206
+ targetKind: onboardTarget.kind,
1207
+ instanceName: onboardTarget.instanceName,
1208
+ onboardTargetArguments: targetArguments,
1140
1209
  detectedSummary,
1141
1210
  })
1142
1211
  : "";
@@ -1144,7 +1213,12 @@ function buildPlannedFiles(
1144
1213
  const codexSkillBody = installCodex
1145
1214
  ? renderCodexSkillOnboard({
1146
1215
  version,
1147
- port: DEFAULT_PORT,
1216
+ port: onboardTarget.port,
1217
+ baseUrl: onboardTarget.baseUrl,
1218
+ mcpName: onboardTarget.mcpName,
1219
+ targetKind: onboardTarget.kind,
1220
+ instanceName: onboardTarget.instanceName,
1221
+ onboardTargetArguments: targetArguments,
1148
1222
  })
1149
1223
  : "";
1150
1224
 
@@ -1348,6 +1422,7 @@ function runStatus(
1348
1422
  process.stdout.write(`${agentStatus.label}: ${agentStatus.state}`);
1349
1423
  process.stdout.write(`, transport ${agentStatus.transport}\n`);
1350
1424
  process.stdout.write(` ${agentStatus.path}\n`);
1425
+ process.stdout.write(` MCP key: ${agentStatus.mcpName}\n`);
1351
1426
  process.stdout.write(` Endpoint: ${agentStatus.endpoint}\n`);
1352
1427
  process.stdout.write(` Detail: ${agentStatus.detail}\n`);
1353
1428
  process.stdout.write(` Action: ${agentStatus.action}\n`);
@@ -1371,16 +1446,28 @@ export function runOnboard(argv: readonly string[]): Promise<number> {
1371
1446
 
1372
1447
  function runOnboardSync(argv: readonly string[]): number {
1373
1448
  const flags = parseFlags(argv);
1449
+ const resolvedTarget = resolveOnboardTarget({
1450
+ instanceName: flags.instanceName,
1451
+ baseUrl: flags.baseUrl,
1452
+ mcpName: flags.mcpName,
1453
+ });
1454
+ if (!resolvedTarget.ok) {
1455
+ process.stderr.write(`agent-inspector onboard: ${resolvedTarget.message}\n`);
1456
+ return 2;
1457
+ }
1458
+ const onboardTarget = resolvedTarget.target;
1374
1459
  const targets = resolveTargets(flags);
1375
1460
  const version = readPackageVersion();
1376
- const plannedFiles = buildPlannedFiles(flags, targets, version);
1461
+ const plannedFiles = buildPlannedFiles(flags, targets, version, onboardTarget);
1377
1462
  const enabledFiles = plannedFiles.filter((file) => file.enabled);
1378
1463
  const agentPlans: AgentMcpConfigPlan[] = [];
1379
1464
  if (isOpenCodeEnabled(flags)) {
1380
- agentPlans.push(buildAgentPlan(openCodeTarget(flags), flags.uninstall, flags.force));
1465
+ agentPlans.push(
1466
+ buildAgentPlan(openCodeTarget(flags, onboardTarget), flags.uninstall, flags.force),
1467
+ );
1381
1468
  }
1382
1469
  if (isMiMoEnabled(flags)) {
1383
- agentPlans.push(buildAgentPlan(miMoTarget(flags), flags.uninstall, flags.force));
1470
+ agentPlans.push(buildAgentPlan(miMoTarget(flags, onboardTarget), flags.uninstall, flags.force));
1384
1471
  }
1385
1472
  const agentStatuses = agentPlans.map((plan) => plan.status);
1386
1473
 
@@ -1411,6 +1498,7 @@ function runOnboardSync(argv: readonly string[]): number {
1411
1498
  for (const plan of agentPlans) {
1412
1499
  process.stdout.write(`${plan.status.label}: ${plan.status.path} (${plan.status.state})\n`);
1413
1500
  process.stdout.write(` Transport: ${plan.status.transport}\n`);
1501
+ process.stdout.write(` MCP key: ${plan.status.mcpName}\n`);
1414
1502
  process.stdout.write(` Endpoint: ${plan.status.endpoint}\n`);
1415
1503
  process.stdout.write(` Action: ${plan.status.action}\n`);
1416
1504
  if (plan.blockedMessage !== null) {
@@ -0,0 +1,179 @@
1
+ import { appendBasePathToOrigin, getConfiguredBasePath } from "../lib/basePath.js";
2
+ import { getInstancesRoot, readInstanceRecord, validateInstanceName } from "./instanceRegistry.js";
3
+ import { urlForHost } from "./networkHints.js";
4
+
5
+ const DEFAULT_PORT = 9527;
6
+ const DEFAULT_ORIGIN = `http://localhost:${String(DEFAULT_PORT)}`;
7
+ const MCP_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
8
+
9
+ type OnboardTargetEnv = Readonly<Record<string, string | undefined>>;
10
+
11
+ export type OnboardTargetKind = "default" | "instance" | "base-url";
12
+
13
+ export type OnboardTarget = {
14
+ kind: OnboardTargetKind;
15
+ instanceName: string | null;
16
+ mcpName: string;
17
+ port: number;
18
+ baseUrl: string;
19
+ apiUrl: string;
20
+ healthUrl: string;
21
+ anthropicProxyUrl: string;
22
+ openAiProxyUrl: string;
23
+ mcpUrl: string;
24
+ };
25
+
26
+ export type OnboardTargetOptions = {
27
+ instanceName: string | null;
28
+ baseUrl: string | null;
29
+ mcpName: string | null;
30
+ };
31
+
32
+ export type OnboardTargetResult =
33
+ | { ok: true; target: OnboardTarget }
34
+ | { ok: false; message: string };
35
+
36
+ function targetFromBase(
37
+ kind: OnboardTargetKind,
38
+ instanceName: string | null,
39
+ mcpName: string,
40
+ port: number,
41
+ baseUrl: string,
42
+ ): OnboardTarget {
43
+ return {
44
+ kind,
45
+ instanceName,
46
+ mcpName,
47
+ port,
48
+ baseUrl,
49
+ apiUrl: `${baseUrl}/api`,
50
+ healthUrl: `${baseUrl}/api/health`,
51
+ anthropicProxyUrl: `${baseUrl}/proxy`,
52
+ openAiProxyUrl: `${baseUrl}/proxy/v1`,
53
+ mcpUrl: `${baseUrl}/api/mcp`,
54
+ };
55
+ }
56
+
57
+ function validateMcpName(rawName: string): string | null {
58
+ const name = rawName.trim();
59
+ return name === rawName && MCP_NAME_PATTERN.test(name) ? name : null;
60
+ }
61
+
62
+ function normalizeExplicitBaseUrl(rawBaseUrl: string): { baseUrl: string; port: number } | null {
63
+ const candidate = rawBaseUrl.trim();
64
+ if (
65
+ candidate.length === 0 ||
66
+ candidate !== rawBaseUrl ||
67
+ candidate.includes("?") ||
68
+ candidate.includes("#")
69
+ ) {
70
+ return null;
71
+ }
72
+
73
+ try {
74
+ const parsed = new URL(candidate);
75
+ if (
76
+ (parsed.protocol !== "http:" && parsed.protocol !== "https:") ||
77
+ parsed.hostname.length === 0 ||
78
+ parsed.username.length > 0 ||
79
+ parsed.password.length > 0
80
+ ) {
81
+ return null;
82
+ }
83
+ const pathname = parsed.pathname.replace(/\/+$/u, "");
84
+ const baseUrl = `${parsed.origin}${pathname === "" ? "" : pathname}`;
85
+ const defaultPort = parsed.protocol === "https:" ? 443 : 80;
86
+ const port = parsed.port === "" ? defaultPort : Number(parsed.port);
87
+ return Number.isInteger(port) && port >= 1 && port <= 65_535 ? { baseUrl, port } : null;
88
+ } catch {
89
+ return null;
90
+ }
91
+ }
92
+
93
+ export function resolveOnboardTarget(
94
+ options: OnboardTargetOptions,
95
+ env: OnboardTargetEnv = process.env,
96
+ ): OnboardTargetResult {
97
+ if (options.instanceName !== null && options.baseUrl !== null) {
98
+ return {
99
+ ok: false,
100
+ message: "--instance and --base-url are mutually exclusive",
101
+ };
102
+ }
103
+
104
+ const explicitMcpName = options.mcpName === null ? null : validateMcpName(options.mcpName);
105
+ if (options.mcpName !== null && explicitMcpName === null) {
106
+ return {
107
+ ok: false,
108
+ message:
109
+ "--mcp-name must be 1-128 letters, digits, dots, hyphens, or underscores and start with a letter or digit",
110
+ };
111
+ }
112
+
113
+ if (options.baseUrl !== null) {
114
+ const explicit = normalizeExplicitBaseUrl(options.baseUrl);
115
+ if (explicit === null) {
116
+ return {
117
+ ok: false,
118
+ message:
119
+ "--base-url must be a credential-free HTTP or HTTPS URL without a query or fragment",
120
+ };
121
+ }
122
+ return {
123
+ ok: true,
124
+ target: targetFromBase(
125
+ "base-url",
126
+ null,
127
+ explicitMcpName ?? "agent-inspector",
128
+ explicit.port,
129
+ explicit.baseUrl,
130
+ ),
131
+ };
132
+ }
133
+
134
+ if (options.instanceName !== null) {
135
+ const validatedName = validateInstanceName(options.instanceName);
136
+ if (validatedName.kind === "error") {
137
+ return { ok: false, message: validatedName.error.message };
138
+ }
139
+ const record = readInstanceRecord(getInstancesRoot(env), validatedName.value);
140
+ if (record.kind === "error") {
141
+ return { ok: false, message: record.error.message };
142
+ }
143
+ if (record.value === null) {
144
+ return {
145
+ ok: false,
146
+ message: `Named instance ${validatedName.value} does not exist`,
147
+ };
148
+ }
149
+ const origin = urlForHost(record.value.publicPort, record.value.host);
150
+ const baseUrl = appendBasePathToOrigin(origin, "/", record.value.basePath);
151
+ return {
152
+ ok: true,
153
+ target: targetFromBase(
154
+ "instance",
155
+ record.value.name,
156
+ explicitMcpName ?? `agent-inspector-${record.value.name}`,
157
+ record.value.publicPort,
158
+ baseUrl,
159
+ ),
160
+ };
161
+ }
162
+
163
+ const baseUrl = appendBasePathToOrigin(DEFAULT_ORIGIN, "/", getConfiguredBasePath(env));
164
+ return {
165
+ ok: true,
166
+ target: targetFromBase(
167
+ "default",
168
+ null,
169
+ explicitMcpName ?? "agent-inspector",
170
+ DEFAULT_PORT,
171
+ baseUrl,
172
+ ),
173
+ };
174
+ }
175
+
176
+ export const onboardTargetForTests = {
177
+ normalizeExplicitBaseUrl,
178
+ validateMcpName,
179
+ };