@mcpcloud/cli 0.4.0 → 0.5.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.
Files changed (2) hide show
  1. package/dist/index.js +1923 -797
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2548,8 +2548,31 @@ function isJsonMode() {
2548
2548
  return jsonMode;
2549
2549
  }
2550
2550
  var ciMode = false;
2551
+ var nonInteractive = false;
2552
+ var quiet = false;
2551
2553
  var warningsAsErrors = false;
2552
2554
  var warningCount = 0;
2555
+
2556
+ class CliExitError extends Error {
2557
+ code;
2558
+ constructor(code, message) {
2559
+ super(message ?? "");
2560
+ this.code = code;
2561
+ this.name = "CliExitError";
2562
+ }
2563
+ }
2564
+ function setNonInteractive(enabled) {
2565
+ nonInteractive = enabled;
2566
+ }
2567
+ function isNonInteractive() {
2568
+ return nonInteractive || ciMode;
2569
+ }
2570
+ function setQuiet(enabled) {
2571
+ quiet = enabled;
2572
+ }
2573
+ function isQuiet() {
2574
+ return quiet;
2575
+ }
2553
2576
  function setCiMode(enabled) {
2554
2577
  ciMode = enabled;
2555
2578
  }
@@ -2680,6 +2703,12 @@ var c = {
2680
2703
  function printJson(data) {
2681
2704
  console.log(JSON.stringify(data, null, 2));
2682
2705
  }
2706
+ function printJsonLine(data) {
2707
+ console.log(JSON.stringify(data));
2708
+ }
2709
+ function printLine(text) {
2710
+ console.log(text);
2711
+ }
2683
2712
  function printTable(rows, columns) {
2684
2713
  if (rows.length === 0) {
2685
2714
  console.log("No results.");
@@ -2756,11 +2785,11 @@ function printKeyValue(record) {
2756
2785
  }
2757
2786
  }
2758
2787
  function printSuccess(msg) {
2759
- if (!jsonMode)
2788
+ if (!jsonMode && !quiet)
2760
2789
  console.log(`${c.green(glyph("ok"))} ${msg}`);
2761
2790
  }
2762
2791
  function printInfo(msg) {
2763
- if (!jsonMode)
2792
+ if (!jsonMode && !quiet)
2764
2793
  console.log(msg);
2765
2794
  }
2766
2795
  function printError(msg) {
@@ -2772,7 +2801,7 @@ function printWarn(msg) {
2772
2801
  console.log(`${c.yellow(glyph("warn"))} ${msg}`);
2773
2802
  }
2774
2803
  function printStep(msg) {
2775
- if (!jsonMode)
2804
+ if (!jsonMode && !quiet)
2776
2805
  console.log(`${c.cyan(glyph("step"))} ${msg}`);
2777
2806
  }
2778
2807
  function formatDate(ms) {
@@ -2786,6 +2815,7 @@ var ERROR_REMEDIATION = {
2786
2815
  api_key_invalid: "The supplied key is invalid or revoked. Run `mcp login`.",
2787
2816
  revoked_api_key: "This key was revoked. Run `mcp login` to mint a new one.",
2788
2817
  expired_token: "Your session expired. Re-run the command (the CLI will refresh it).",
2818
+ expired_api_key: "This API key expired. Run `mcp api-keys rotate <id>` (or `mcp login`) to mint a fresh one.",
2789
2819
  api_key_name_required: "Pass --name <…> when creating an API key.",
2790
2820
  api_key_id_required: "Pass an apiKeyId. List existing keys with `mcp api-keys list`.",
2791
2821
  api_key_not_found: "No such API key for this user. Run `mcp api-keys list` to see live IDs.",
@@ -2868,6 +2898,11 @@ function describeError(err) {
2868
2898
  return { message: String(err) };
2869
2899
  }
2870
2900
  function handleError(err) {
2901
+ if (err instanceof CliExitError) {
2902
+ if (err.message)
2903
+ printError(err.message);
2904
+ process.exit(err.code);
2905
+ }
2871
2906
  const info = describeError(err);
2872
2907
  if (jsonMode) {
2873
2908
  printJson({ error: info });
@@ -3518,6 +3553,17 @@ function registerAuthCommands(program2) {
3518
3553
  }));
3519
3554
  }
3520
3555
 
3556
+ // src/lib/delete-confirm.ts
3557
+ function assertConfirmMatches(args) {
3558
+ const provided = args.confirm?.trim();
3559
+ if (!provided) {
3560
+ throw new Error(`Refusing to delete ${args.entityKind} "${args.entityName}". Re-run with --confirm "${args.entityName}" to proceed.`);
3561
+ }
3562
+ if (provided !== args.entityName) {
3563
+ throw new Error(`--confirm "${provided}" does not match the ${args.entityKind} name "${args.entityName}". Deletion aborted.`);
3564
+ }
3565
+ }
3566
+
3521
3567
  // src/lib/org.ts
3522
3568
  class NoOrganizationError extends Error {
3523
3569
  constructor() {
@@ -3734,6 +3780,29 @@ function registerProjectCommands(program2) {
3734
3780
  updated: formatDate(s.updatedAt)
3735
3781
  });
3736
3782
  }));
3783
+ projects.command("delete <projectId>").description('Permanently delete a project. Requires --confirm "<exact name>" and that the project is empty of servers/skills.').requiredOption("--confirm <name>", "The exact project name, typed to confirm the destructive action").option("--org <organizationId>", "Organization ID").addHelpText("after", [
3784
+ "",
3785
+ "Examples:",
3786
+ ' $ mcp projects delete proj_123 --confirm "Acme APIs"'
3787
+ ].join(`
3788
+ `)).action(runAction(async (projectId, opts) => {
3789
+ const orgId = await resolveOrgId(opts.org);
3790
+ const detail = await api.get("/api/v1/project", {
3791
+ organizationId: orgId,
3792
+ projectId
3793
+ });
3794
+ assertConfirmMatches({
3795
+ entityKind: "project",
3796
+ entityName: detail.project.name,
3797
+ confirm: opts.confirm
3798
+ });
3799
+ const data = await api.delete("/api/v1/project", { organizationId: orgId, projectId });
3800
+ if (isJsonMode()) {
3801
+ printJson(data);
3802
+ return;
3803
+ }
3804
+ printSuccess(`Deleted project ${data.deleted.name} (${data.deleted.id}).`);
3805
+ }));
3737
3806
  }
3738
3807
 
3739
3808
  // src/lib/duration.ts
@@ -3761,6 +3830,12 @@ function parseSince(raw, now = Date.now) {
3761
3830
  }
3762
3831
 
3763
3832
  // src/lib/deployment-events.ts
3833
+ function emitEvent(event) {
3834
+ if (isJsonMode())
3835
+ printJsonLine(event);
3836
+ else
3837
+ printLine(formatEventLine(event));
3838
+ }
3764
3839
  var TERMINAL_STAGES = new Set([
3765
3840
  "active",
3766
3841
  "failed",
@@ -3841,18 +3916,14 @@ async function tailDeploymentLogs(args) {
3841
3916
  return;
3842
3917
  }
3843
3918
  for (const event of initial.events)
3844
- console.log(formatEventLine(event));
3919
+ printLine(formatEventLine(event));
3845
3920
  return;
3846
3921
  }
3847
3922
  if (!isJsonMode() && initial.events.length > 0) {
3848
3923
  printStep(`Tailing ${c.bold(args.deploymentId)} (Ctrl-C to stop)…`);
3849
3924
  }
3850
- for (const event of initial.events) {
3851
- if (isJsonMode())
3852
- console.log(JSON.stringify(event));
3853
- else
3854
- console.log(formatEventLine(event));
3855
- }
3925
+ for (const event of initial.events)
3926
+ emitEvent(event);
3856
3927
  const lastTimestamp = initial.events[initial.events.length - 1]?.timestamp ?? initial.nextCursor ?? sinceMs ?? Date.now();
3857
3928
  const controller = new AbortController;
3858
3929
  const onSigint = () => controller.abort();
@@ -3865,12 +3936,7 @@ async function tailDeploymentLogs(args) {
3865
3936
  since: lastTimestamp,
3866
3937
  isDone: () => false,
3867
3938
  totalTimeoutMs,
3868
- onEvent: (event) => {
3869
- if (isJsonMode())
3870
- console.log(JSON.stringify(event));
3871
- else
3872
- console.log(formatEventLine(event));
3873
- },
3939
+ onEvent: emitEvent,
3874
3940
  signal: controller.signal
3875
3941
  });
3876
3942
  } finally {
@@ -4063,7 +4129,7 @@ async function resolveSetValue(args) {
4063
4129
  const decision = decideSetValueSource(args);
4064
4130
  if (decision.kind === "error") {
4065
4131
  printError(decision.message);
4066
- process.exit(1);
4132
+ throw new CliExitError(1);
4067
4133
  }
4068
4134
  if (decision.kind === "positional")
4069
4135
  return decision.value;
@@ -4071,7 +4137,7 @@ async function resolveSetValue(args) {
4071
4137
  const path = resolve(process.cwd(), decision.path);
4072
4138
  if (!existsSync3(path)) {
4073
4139
  printError(`--from-file path not found: ${path}`);
4074
- process.exit(1);
4140
+ throw new CliExitError(1);
4075
4141
  }
4076
4142
  return readFileSync3(path, "utf-8");
4077
4143
  }
@@ -4116,7 +4182,7 @@ async function runUnsetEnv(args) {
4116
4182
  const ok = await confirmUnset(args.bindingName, args.serverId);
4117
4183
  if (!ok) {
4118
4184
  printInfo(c.dim("Aborted."));
4119
- process.exit(1);
4185
+ throw new CliExitError(1);
4120
4186
  }
4121
4187
  }
4122
4188
  const orgId = await resolveOrgId(args.orgOverride);
@@ -4439,7 +4505,7 @@ function registerServerLifecycleCommands(servers) {
4439
4505
  if (isJsonMode()) {
4440
4506
  printJson({ deployment: dep, waitDone, terminalEvent });
4441
4507
  if (opts.wait && !waitDone)
4442
- process.exit(1);
4508
+ throw new CliExitError(1);
4443
4509
  return;
4444
4510
  }
4445
4511
  printSuccess(`Deployment ${c.bold(dep.deploymentId)} created.`);
@@ -4456,7 +4522,7 @@ function registerServerLifecycleCommands(servers) {
4456
4522
  } : {}
4457
4523
  });
4458
4524
  if (opts.wait && !waitDone)
4459
- process.exit(1);
4525
+ throw new CliExitError(1);
4460
4526
  }));
4461
4527
  servers.command("push-spec <serverId>").description("Push a local OpenAPI spec to the cloud (regenerates the bundle; optionally deploys)").option("--org <organizationId>", "Organization ID").option("--spec <path>", "Path to the OpenAPI/Swagger spec file", "./openapi.yaml").option("--enrich-on-change", "Preserve per-tool enrichment when the spec changes").option("--deploy", "Immediately deploy the regenerated bundle to Cloudflare Workers").option("--target <target>", `Deploy target (with --deploy): ${DEPLOY_TARGETS.join(" | ")}`, "workersDev").option("--access-mode <mode>", `Access mode (with --deploy): ${ACCESS_MODES.join(" | ")}`, "public").option("--wait", "With --deploy: tail deployment events until terminal state").option("--wait-timeout <seconds>", "Maximum seconds to wait for terminal state (default 300)", "300").addHelpText("after", [
4462
4528
  "",
@@ -4575,7 +4641,7 @@ function registerServerLifecycleCommands(servers) {
4575
4641
  terminalEvent
4576
4642
  });
4577
4643
  if (opts.wait && !waitDone)
4578
- process.exit(1);
4644
+ throw new CliExitError(1);
4579
4645
  return;
4580
4646
  }
4581
4647
  printSuccess(`Deployment ${c.bold(dep.deploymentId)} created.`);
@@ -4592,7 +4658,7 @@ function registerServerLifecycleCommands(servers) {
4592
4658
  } : {}
4593
4659
  });
4594
4660
  if (opts.wait && !waitDone)
4595
- process.exit(1);
4661
+ throw new CliExitError(1);
4596
4662
  }));
4597
4663
  }
4598
4664
 
@@ -4759,6 +4825,212 @@ function registerServerMutationCommands(servers) {
4759
4825
  "resumed at": formatDate(d.resumedAt)
4760
4826
  });
4761
4827
  }));
4828
+ servers.command("delete <serverId>").description('Permanently delete a server and all its data. Requires --confirm "<exact name>".').requiredOption("--confirm <name>", "The exact server name, typed to confirm the destructive action").option("--org <organizationId>", "Organization ID").option("--force", "Delete even if a Worker is still live (skips the active-deployment guard)").addHelpText("after", [
4829
+ "",
4830
+ "This cascades: tools, versions, deployments, events, OAuth connections,",
4831
+ "test runs, rollups, and the dispatch KV / dedicated-container instance.",
4832
+ "",
4833
+ "Examples:",
4834
+ ' $ mcp servers delete srv_123 --confirm "Stripe MCP"',
4835
+ ' $ mcp servers delete srv_123 --confirm "Stripe MCP" --force'
4836
+ ].join(`
4837
+ `)).action(runAction(async (serverId, opts) => {
4838
+ const orgId = await resolveOrgId(opts.org);
4839
+ const detail = await api.get("/api/v1/server", {
4840
+ organizationId: orgId,
4841
+ serverId
4842
+ });
4843
+ assertConfirmMatches({
4844
+ entityKind: "server",
4845
+ entityName: detail.server.name,
4846
+ confirm: opts.confirm
4847
+ });
4848
+ const data = await api.delete("/api/v1/server", {
4849
+ organizationId: orgId,
4850
+ serverId,
4851
+ ...opts.force ? { force: true } : {}
4852
+ });
4853
+ if (isJsonMode()) {
4854
+ printJson(data);
4855
+ return;
4856
+ }
4857
+ printSuccess(`Deleted server ${c.bold(data.deleted.name)} (${data.deleted.id}).`);
4858
+ }));
4859
+ }
4860
+
4861
+ // src/lib/spec-pipeline.ts
4862
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
4863
+ import { dirname as dirname4, join as join5, resolve as resolve4 } from "node:path";
4864
+ var SOURCE_TYPES = ["openapi", "graphql", "url"];
4865
+ function isHttpUrl(value) {
4866
+ return /^https?:\/\//i.test(value.trim());
4867
+ }
4868
+ function looksLikeGraphql(spec) {
4869
+ return /\b(type\s+Query|type\s+Mutation|schema\s*\{|extend\s+type)\b/.test(spec);
4870
+ }
4871
+ async function readSpecSource(spec, override, options = {}) {
4872
+ const trimmedSpec = spec.trim();
4873
+ if (override === "url" || !override && isHttpUrl(trimmedSpec)) {
4874
+ if (!isHttpUrl(trimmedSpec)) {
4875
+ throw new Error(`--source-type url requires an http(s) --spec URL, got: ${spec}`);
4876
+ }
4877
+ return { sourceType: "url", sourceUrl: trimmedSpec };
4878
+ }
4879
+ let content;
4880
+ let pathHint = "";
4881
+ if (trimmedSpec === "@-" || trimmedSpec === "-") {
4882
+ const reader = options.readStdin ?? defaultStdinReader;
4883
+ content = await reader();
4884
+ } else {
4885
+ const filePath = resolve4(options.cwd ?? process.cwd(), trimmedSpec);
4886
+ if (!existsSync6(filePath)) {
4887
+ throw new Error(`Spec file not found: ${filePath}`);
4888
+ }
4889
+ content = readFileSync5(filePath, "utf-8");
4890
+ pathHint = filePath;
4891
+ }
4892
+ if (!content.trim()) {
4893
+ throw new Error("The spec is empty — nothing to ingest.");
4894
+ }
4895
+ let sourceType;
4896
+ if (override === "openapi" || override === "graphql") {
4897
+ sourceType = override;
4898
+ } else if (/\.(graphql|gql)$/i.test(pathHint) || looksLikeGraphql(content)) {
4899
+ sourceType = "graphql";
4900
+ } else {
4901
+ sourceType = "openapi";
4902
+ }
4903
+ return { sourceType, rawSpec: content };
4904
+ }
4905
+ function buildIngestBody(args) {
4906
+ const body = {
4907
+ organizationId: args.organizationId,
4908
+ projectId: args.projectId,
4909
+ sourceType: args.payload.sourceType
4910
+ };
4911
+ if (args.payload.sourceType === "url") {
4912
+ body["sourceUrl"] = args.payload.sourceUrl;
4913
+ } else {
4914
+ body["rawSpec"] = args.payload.rawSpec;
4915
+ }
4916
+ return body;
4917
+ }
4918
+ function writeBundleToDisk(bundle, outDir, cwd = process.cwd()) {
4919
+ const root = resolve4(cwd, outDir);
4920
+ const written = [];
4921
+ for (const file of bundle.files) {
4922
+ const dest = join5(root, file.path);
4923
+ mkdirSync4(dirname4(dest), { recursive: true });
4924
+ writeFileSync3(dest, file.content, "utf-8");
4925
+ written.push(dest);
4926
+ }
4927
+ return written;
4928
+ }
4929
+ async function defaultStdinReader() {
4930
+ const chunks = [];
4931
+ return new Promise((res, rej) => {
4932
+ process.stdin.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
4933
+ process.stdin.on("end", () => res(Buffer.concat(chunks).toString("utf-8")));
4934
+ process.stdin.on("error", rej);
4935
+ });
4936
+ }
4937
+
4938
+ // src/commands/servers-spec.ts
4939
+ function validateSourceType(value) {
4940
+ if (value === undefined)
4941
+ return;
4942
+ const v = value.trim().toLowerCase();
4943
+ if (!SOURCE_TYPES.includes(v)) {
4944
+ throw new Error(`Invalid --source-type: ${value}. Expected one of: ${SOURCE_TYPES.join(", ")}.`);
4945
+ }
4946
+ return v;
4947
+ }
4948
+ function registerServerSpecCommands(servers) {
4949
+ servers.command("ingest").description("Create a server from an OpenAPI/GraphQL spec (file, URL, or @- stdin). Wraps POST /api/v1/server/ingest.").requiredOption("--project <projectId>", "Project to create the server in").requiredOption("--spec <file|url>", "Spec source: a local file, an http(s) URL (fetched server-side), or @- for stdin").option("--org <organizationId>", "Organization ID").option("--source-type <type>", `Override spec classification: ${SOURCE_TYPES.join(" | ")} (auto-detected by default)`).addHelpText("after", [
4950
+ "",
4951
+ "Examples:",
4952
+ " $ mcp servers ingest --project proj_123 --spec ./openapi.yaml",
4953
+ " $ mcp servers ingest --project proj_123 --spec https://api.example.com/openapi.json",
4954
+ " $ cat schema.graphql | mcp servers ingest --project proj_123 --spec @-"
4955
+ ].join(`
4956
+ `)).action(runAction(async (opts) => {
4957
+ const orgId = await resolveOrgId(opts.org);
4958
+ const sourceType = validateSourceType(opts.sourceType);
4959
+ const payload = await readSpecSource(opts.spec, sourceType);
4960
+ if (!isJsonMode()) {
4961
+ printStep(`Importing ${c.bold(payload.sourceType)} spec into project ${c.dim(opts.project)}…`);
4962
+ }
4963
+ const data = await api.post("/api/v1/server/ingest", buildIngestBody({
4964
+ organizationId: orgId,
4965
+ projectId: opts.project,
4966
+ payload
4967
+ }));
4968
+ if (isJsonMode()) {
4969
+ printJson(data);
4970
+ return;
4971
+ }
4972
+ printSuccess(`Server ${c.bold(data.server.id)} created from spec.`);
4973
+ printKeyValue({
4974
+ "server id": data.server.id,
4975
+ "server name": data.server.name,
4976
+ "api source": data.apiSource.id ?? "—",
4977
+ project: opts.project
4978
+ });
4979
+ printStep(`Next: \`mcp servers generate ${data.server.id} --out ./out\` or \`mcp servers deploy ${data.server.id} --wait\`.`);
4980
+ }));
4981
+ servers.command("generate <serverId>").description("Generate the server's TypeScript bundle (the exact code `deploy` ships) and optionally write it to disk — a dry-run for the codegen path. Wraps POST /api/v1/server/generate.").option("--org <organizationId>", "Organization ID").option("--project <projectId>", "Project ID (looked up from the server when omitted)").option("--out <dir>", "Write the generated files to this directory instead of just summarizing").addHelpText("after", [
4982
+ "",
4983
+ "Notes:",
4984
+ " Codegen runs on the builder Worker (arch-evolution P7f). If generate",
4985
+ " fails with a builder error, the environment’s builder Worker is down.",
4986
+ "",
4987
+ "Examples:",
4988
+ " $ mcp servers generate srv_123 # summarize the bundle",
4989
+ " $ mcp servers generate srv_123 --out ./build # write files to disk",
4990
+ " $ mcp --json servers generate srv_123 | jq '.bundle.files[].path'"
4991
+ ].join(`
4992
+ `)).action(runAction(async (serverId, opts) => {
4993
+ const orgId = await resolveOrgId(opts.org);
4994
+ let projectId = opts.project;
4995
+ if (!projectId) {
4996
+ const detail = await api.get("/api/v1/server", { organizationId: orgId, serverId });
4997
+ projectId = detail.server.projectId;
4998
+ }
4999
+ if (!isJsonMode()) {
5000
+ printStep(`Generating bundle for ${c.bold(serverId)}…`);
5001
+ }
5002
+ const data = await api.post("/api/v1/server/generate", { organizationId: orgId, projectId, serverId });
5003
+ if (opts.out) {
5004
+ const written = writeBundleToDisk(data.bundle, opts.out);
5005
+ if (isJsonMode()) {
5006
+ printJson({
5007
+ server: data.server,
5008
+ outDir: opts.out,
5009
+ files: written
5010
+ });
5011
+ return;
5012
+ }
5013
+ printSuccess(`Wrote ${written.length} file(s) to ${c.bold(opts.out)}.`);
5014
+ for (const file of data.bundle.files) {
5015
+ printStep(file.path);
5016
+ }
5017
+ return;
5018
+ }
5019
+ if (isJsonMode()) {
5020
+ printJson(data);
5021
+ return;
5022
+ }
5023
+ printSuccess(`Generated ${data.bundle.files.length} file(s) for ${c.bold(data.server.name)} (not written — pass --out <dir>).`);
5024
+ printKeyValue({
5025
+ "entry file": data.bundle.entryFile,
5026
+ "schema version": data.bundle.schemaVersion,
5027
+ version: data.server.version,
5028
+ files: data.bundle.files.length
5029
+ });
5030
+ for (const file of data.bundle.files) {
5031
+ printStep(`${file.path} ${c.dim(`(${file.content.length} bytes)`)}`);
5032
+ }
5033
+ }));
4762
5034
  }
4763
5035
 
4764
5036
  // src/commands/servers-tests.ts
@@ -4981,6 +5253,7 @@ function registerServerCommands(program2) {
4981
5253
  registerServerExportCommands(servers);
4982
5254
  registerServerLifecycleCommands(servers);
4983
5255
  registerServerMutationCommands(servers);
5256
+ registerServerSpecCommands(servers);
4984
5257
  registerServerTestCommands(servers);
4985
5258
  servers.command("list").description("List servers").option("--org <organizationId>", "Organization ID").option("--project <projectId>", "Filter by project ID").option("--limit <n>", "Maximum results (default 25)", parsePositiveIntOption("limit"), "25").addHelpText("after", [
4986
5259
  "",
@@ -5162,48 +5435,48 @@ function registerServerCommands(program2) {
5162
5435
  }
5163
5436
 
5164
5437
  // src/commands/tools-diff.ts
5165
- import { existsSync as existsSync9, readFileSync as readFileSync8 } from "node:fs";
5438
+ import { existsSync as existsSync10, readFileSync as readFileSync9 } from "node:fs";
5166
5439
  import { relative } from "node:path";
5167
5440
 
5168
5441
  // src/lib/dev/handlers-sync.ts
5169
5442
  import {
5170
- existsSync as existsSync8,
5171
- mkdirSync as mkdirSync6,
5172
- readFileSync as readFileSync7,
5443
+ existsSync as existsSync9,
5444
+ mkdirSync as mkdirSync7,
5445
+ readFileSync as readFileSync8,
5173
5446
  statSync,
5174
- writeFileSync as writeFileSync5
5447
+ writeFileSync as writeFileSync6
5175
5448
  } from "node:fs";
5176
5449
  import { createHash as createHash3 } from "node:crypto";
5177
- import { dirname as dirname4, join as join7 } from "node:path";
5450
+ import { dirname as dirname5, join as join8 } from "node:path";
5178
5451
 
5179
5452
  // src/lib/dev/state.ts
5180
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
5181
- import { join as join5, resolve as resolve4 } from "node:path";
5453
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
5454
+ import { join as join6, resolve as resolve5 } from "node:path";
5182
5455
  function devRoot(cwd) {
5183
- return join5(cwd, ".mcpcloud");
5456
+ return join6(cwd, ".mcpcloud");
5184
5457
  }
5185
5458
  function stateFile(cwd) {
5186
- return join5(devRoot(cwd), "state.json");
5459
+ return join6(devRoot(cwd), "state.json");
5187
5460
  }
5188
5461
  function serverDir(cwd) {
5189
- return join5(devRoot(cwd), "server");
5462
+ return join6(devRoot(cwd), "server");
5190
5463
  }
5191
5464
  function gitCloneDir(cwd, owner, name) {
5192
5465
  const safe = `${owner}__${name}`.replace(/[^a-zA-Z0-9._-]/g, "-");
5193
- return join5(devRoot(cwd), "git", safe);
5466
+ return join6(devRoot(cwd), "git", safe);
5194
5467
  }
5195
5468
  function envFile(cwd) {
5196
- return join5(devRoot(cwd), "env.json");
5469
+ return join6(devRoot(cwd), "env.json");
5197
5470
  }
5198
5471
  function backupsDir(cwd) {
5199
- return join5(devRoot(cwd), "agent-backups");
5472
+ return join6(devRoot(cwd), "agent-backups");
5200
5473
  }
5201
5474
  function readState(cwd) {
5202
5475
  const path = stateFile(cwd);
5203
- if (!existsSync6(path))
5476
+ if (!existsSync7(path))
5204
5477
  return null;
5205
5478
  try {
5206
- const parsed = JSON.parse(readFileSync5(path, "utf-8"));
5479
+ const parsed = JSON.parse(readFileSync6(path, "utf-8"));
5207
5480
  if (!parsed || typeof parsed !== "object")
5208
5481
  return null;
5209
5482
  const s = parsed;
@@ -5216,19 +5489,19 @@ function readState(cwd) {
5216
5489
  }
5217
5490
  }
5218
5491
  function writeState(cwd, state) {
5219
- if (!existsSync6(devRoot(cwd))) {
5220
- mkdirSync4(devRoot(cwd), { recursive: true });
5492
+ if (!existsSync7(devRoot(cwd))) {
5493
+ mkdirSync5(devRoot(cwd), { recursive: true });
5221
5494
  }
5222
- writeFileSync3(stateFile(cwd), JSON.stringify(state, null, 2), "utf-8");
5495
+ writeFileSync4(stateFile(cwd), JSON.stringify(state, null, 2), "utf-8");
5223
5496
  }
5224
5497
  function ensureGitignore(cwd) {
5225
- const ignoreFile = join5(devRoot(cwd), ".gitignore");
5226
- if (existsSync6(ignoreFile))
5498
+ const ignoreFile = join6(devRoot(cwd), ".gitignore");
5499
+ if (existsSync7(ignoreFile))
5227
5500
  return;
5228
- if (!existsSync6(devRoot(cwd))) {
5229
- mkdirSync4(devRoot(cwd), { recursive: true });
5501
+ if (!existsSync7(devRoot(cwd))) {
5502
+ mkdirSync5(devRoot(cwd), { recursive: true });
5230
5503
  }
5231
- writeFileSync3(ignoreFile, [
5504
+ writeFileSync4(ignoreFile, [
5232
5505
  "# generated by `mcp dev`",
5233
5506
  "server/",
5234
5507
  "git/",
@@ -5242,14 +5515,14 @@ function ensureGitignore(cwd) {
5242
5515
 
5243
5516
  // src/lib/dev/tools-sync.ts
5244
5517
  import {
5245
- existsSync as existsSync7,
5246
- mkdirSync as mkdirSync5,
5247
- readFileSync as readFileSync6,
5518
+ existsSync as existsSync8,
5519
+ mkdirSync as mkdirSync6,
5520
+ readFileSync as readFileSync7,
5248
5521
  readdirSync,
5249
5522
  rmSync,
5250
- writeFileSync as writeFileSync4
5523
+ writeFileSync as writeFileSync5
5251
5524
  } from "node:fs";
5252
- import { join as join6 } from "node:path";
5525
+ import { join as join7 } from "node:path";
5253
5526
  var TOOLS_DIR_NAME = "tools";
5254
5527
  var RISK_CLASSES = [
5255
5528
  "read",
@@ -5258,10 +5531,10 @@ var RISK_CLASSES = [
5258
5531
  "external_effect"
5259
5532
  ];
5260
5533
  function toolsDir(devRootDir) {
5261
- return join6(devRootDir, TOOLS_DIR_NAME);
5534
+ return join7(devRootDir, TOOLS_DIR_NAME);
5262
5535
  }
5263
5536
  function serverToolsDir(devRootDir, serverId) {
5264
- return join6(toolsDir(devRootDir), serverId);
5537
+ return join7(toolsDir(devRootDir), serverId);
5265
5538
  }
5266
5539
  function escapeFrontmatterScalar(raw) {
5267
5540
  if (raw === "")
@@ -5451,14 +5724,14 @@ function arraysEqual(a, b) {
5451
5724
  }
5452
5725
  var STATE_FILE = ".tools-state.json";
5453
5726
  function toolsStateFile(devRootDir, serverId) {
5454
- return join6(serverToolsDir(devRootDir, serverId), STATE_FILE);
5727
+ return join7(serverToolsDir(devRootDir, serverId), STATE_FILE);
5455
5728
  }
5456
5729
  function readToolsState(devRootDir, serverId) {
5457
5730
  const file = toolsStateFile(devRootDir, serverId);
5458
- if (!existsSync7(file))
5731
+ if (!existsSync8(file))
5459
5732
  return null;
5460
5733
  try {
5461
- const parsed = JSON.parse(readFileSync6(file, "utf-8"));
5734
+ const parsed = JSON.parse(readFileSync7(file, "utf-8"));
5462
5735
  if (!parsed || typeof parsed !== "object")
5463
5736
  return null;
5464
5737
  const s = parsed;
@@ -5472,9 +5745,9 @@ function readToolsState(devRootDir, serverId) {
5472
5745
  }
5473
5746
  function writeToolsState(devRootDir, state) {
5474
5747
  const dir = serverToolsDir(devRootDir, state.serverId);
5475
- if (!existsSync7(dir))
5476
- mkdirSync5(dir, { recursive: true });
5477
- writeFileSync4(toolsStateFile(devRootDir, state.serverId), JSON.stringify(state, null, 2), "utf-8");
5748
+ if (!existsSync8(dir))
5749
+ mkdirSync6(dir, { recursive: true });
5750
+ writeFileSync5(toolsStateFile(devRootDir, state.serverId), JSON.stringify(state, null, 2), "utf-8");
5478
5751
  }
5479
5752
  function hashLocalView(view) {
5480
5753
  return JSON.stringify({
@@ -5486,14 +5759,14 @@ function hashLocalView(view) {
5486
5759
  }
5487
5760
  function materializeTools(args) {
5488
5761
  const dir = serverToolsDir(args.devRootDir, args.serverId);
5489
- if (!existsSync7(dir))
5490
- mkdirSync5(dir, { recursive: true });
5762
+ if (!existsSync8(dir))
5763
+ mkdirSync6(dir, { recursive: true });
5491
5764
  const kept = new Set;
5492
5765
  let filesWritten = 0;
5493
5766
  for (const tool of args.tools) {
5494
5767
  const filename = `${tool.name}.md`;
5495
5768
  kept.add(filename);
5496
- writeFileSync4(join6(dir, filename), renderToolMarkdown(tool), "utf-8");
5769
+ writeFileSync5(join7(dir, filename), renderToolMarkdown(tool), "utf-8");
5497
5770
  filesWritten += 1;
5498
5771
  }
5499
5772
  let filesRemoved = 0;
@@ -5505,7 +5778,7 @@ function materializeTools(args) {
5505
5778
  if (kept.has(entry))
5506
5779
  continue;
5507
5780
  try {
5508
- rmSync(join6(dir, entry), { force: true });
5781
+ rmSync(join7(dir, entry), { force: true });
5509
5782
  filesRemoved += 1;
5510
5783
  } catch {}
5511
5784
  }
@@ -5531,14 +5804,14 @@ function materializeTools(args) {
5531
5804
  return { filesWritten, filesRemoved };
5532
5805
  }
5533
5806
  function readLocalTool(devRootDir, serverId, toolName) {
5534
- const filePath = join6(serverToolsDir(devRootDir, serverId), `${toolName}.md`);
5535
- if (!existsSync7(filePath))
5807
+ const filePath = join7(serverToolsDir(devRootDir, serverId), `${toolName}.md`);
5808
+ if (!existsSync8(filePath))
5536
5809
  return null;
5537
5810
  const state = readToolsState(devRootDir, serverId);
5538
5811
  const stateEntry = state?.tools[toolName];
5539
5812
  if (!stateEntry)
5540
5813
  return null;
5541
- const view = parseToolMarkdown(readFileSync6(filePath, "utf-8"));
5814
+ const view = parseToolMarkdown(readFileSync7(filePath, "utf-8"));
5542
5815
  return {
5543
5816
  view,
5544
5817
  cloudId: stateEntry.id,
@@ -5568,8 +5841,8 @@ async function pushLocalToolEdit(args) {
5568
5841
  };
5569
5842
  }
5570
5843
  if (!local) {
5571
- const filePath = join6(serverToolsDir(args.devRootDir, args.serverId), `${args.toolName}.md`);
5572
- if (!existsSync7(filePath))
5844
+ const filePath = join7(serverToolsDir(args.devRootDir, args.serverId), `${args.toolName}.md`);
5845
+ if (!existsSync8(filePath))
5573
5846
  return { kind: "missing-file" };
5574
5847
  return { kind: "unknown-tool" };
5575
5848
  }
@@ -5652,7 +5925,7 @@ function toKebabSlug(value) {
5652
5925
  return value.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() || "generated-mcp-server";
5653
5926
  }
5654
5927
  function serverBundleHandlersDir(devRootDir) {
5655
- return join7(devRootDir, "server", "src", "tools");
5928
+ return join8(devRootDir, "server", "src", "tools");
5656
5929
  }
5657
5930
  function handlerSlugFromWatcherPath(rel) {
5658
5931
  if (!rel)
@@ -5681,15 +5954,15 @@ function hashSource(source) {
5681
5954
  }
5682
5955
  var lastPushedHash = new Map;
5683
5956
  async function pushLocalHandlerEdit(args) {
5684
- const filePath = join7(serverBundleHandlersDir(args.devRootDir), `${args.slug}.ts`);
5685
- if (!existsSync8(filePath)) {
5957
+ const filePath = join8(serverBundleHandlersDir(args.devRootDir), `${args.slug}.ts`);
5958
+ if (!existsSync9(filePath)) {
5686
5959
  return { kind: "missing-file" };
5687
5960
  }
5688
5961
  const resolved = resolveToolFromSlug(args.devRootDir, args.serverId, args.slug);
5689
5962
  if (!resolved) {
5690
5963
  return { kind: "unknown-tool", slug: args.slug };
5691
5964
  }
5692
- const source = readFileSync7(filePath, "utf-8");
5965
+ const source = readFileSync8(filePath, "utf-8");
5693
5966
  const hash = hashSource(source);
5694
5967
  if (lastPushedHash.get(filePath) === hash) {
5695
5968
  return { kind: "noop" };
@@ -5833,9 +6106,9 @@ async function resetCloudHandler(args) {
5833
6106
  }
5834
6107
  }
5835
6108
  function writeLocalHandlerFromCloud(args) {
5836
- const filePath = join7(serverBundleHandlersDir(args.devRootDir), `${args.slug}.ts`);
5837
- mkdirSync6(dirname4(filePath), { recursive: true });
5838
- writeFileSync5(filePath, args.content, "utf-8");
6109
+ const filePath = join8(serverBundleHandlersDir(args.devRootDir), `${args.slug}.ts`);
6110
+ mkdirSync7(dirname5(filePath), { recursive: true });
6111
+ writeFileSync6(filePath, args.content, "utf-8");
5839
6112
  lastPushedHash.set(filePath, hashSource(args.content));
5840
6113
  return filePath;
5841
6114
  }
@@ -5844,11 +6117,11 @@ function handlerSlugFromToolName(toolName) {
5844
6117
  }
5845
6118
  var RECENT_EDIT_WINDOW_MS = 5 * 60 * 1000;
5846
6119
  function inspectLocalHandler(args) {
5847
- const filePath = join7(serverBundleHandlersDir(args.devRootDir), `${args.slug}.ts`);
5848
- if (!existsSync8(filePath))
6120
+ const filePath = join8(serverBundleHandlersDir(args.devRootDir), `${args.slug}.ts`);
6121
+ if (!existsSync9(filePath))
5849
6122
  return { kind: "no-local-file" };
5850
6123
  const cached2 = lastPushedHash.get(filePath);
5851
- const onDisk = hashSource(readFileSync7(filePath, "utf-8"));
6124
+ const onDisk = hashSource(readFileSync8(filePath, "utf-8"));
5852
6125
  if (cached2 !== undefined) {
5853
6126
  return cached2 === onDisk ? { kind: "matches-cache" } : { kind: "differs-from-cache" };
5854
6127
  }
@@ -5886,7 +6159,7 @@ async function resolveToolsContext(opts = {}) {
5886
6159
  if (opts.requireServer === false)
5887
6160
  return null;
5888
6161
  printError("No server selected. Pass --server <serverId> or run from a directory with .mcpcloud/state.json.");
5889
- process.exit(1);
6162
+ throw new CliExitError(1);
5890
6163
  }
5891
6164
  const organizationId = opts.orgOverride ?? cached2?.organizationId ?? await resolveOrgId(undefined);
5892
6165
  let projectId = cached2?.projectId;
@@ -5916,7 +6189,7 @@ function registerToolsDiffCommand(tools) {
5916
6189
  `)).action(runAction(async (name, opts) => {
5917
6190
  if (opts.metadataOnly && opts.handlerOnly) {
5918
6191
  printError("Pass at most one of --metadata-only or --handler-only.");
5919
- process.exit(1);
6192
+ throw new CliExitError(1);
5920
6193
  }
5921
6194
  await runToolsDiff({
5922
6195
  toolName: name,
@@ -5944,7 +6217,7 @@ async function runToolsDiff(args) {
5944
6217
  const sample = tools.slice(0, 5).map((t) => t.name).join(", ");
5945
6218
  printInfo(` ${c.dim("Available:")} ${sample}${tools.length > 5 ? c.dim(`, +${tools.length - 5} more`) : ""}`);
5946
6219
  }
5947
- process.exit(1);
6220
+ throw new CliExitError(1);
5948
6221
  }
5949
6222
  const metadata = args.includeMetadata ? buildMetadataDiff({ devRootDir: dev, cwd, serverId: ctx.serverId, cloud }) : null;
5950
6223
  const handler = args.includeHandler ? await buildHandlerDiff({
@@ -5978,7 +6251,7 @@ async function runToolsDiff(args) {
5978
6251
  }
5979
6252
  function buildMetadataDiff(args) {
5980
6253
  const filePath = `${serverToolsDir(args.devRootDir, args.serverId)}/${args.cloud.name}.md`;
5981
- if (!existsSync9(filePath)) {
6254
+ if (!existsSync10(filePath)) {
5982
6255
  return {
5983
6256
  hasLocal: false,
5984
6257
  localPath: null,
@@ -5992,7 +6265,7 @@ function buildMetadataDiff(args) {
5992
6265
  let local = null;
5993
6266
  let parseError = null;
5994
6267
  try {
5995
- local = parseToolMarkdown(readFileSync8(filePath, "utf-8"));
6268
+ local = parseToolMarkdown(readFileSync9(filePath, "utf-8"));
5996
6269
  } catch (err) {
5997
6270
  parseError = err instanceof Error ? err.message : String(err);
5998
6271
  }
@@ -6034,8 +6307,8 @@ async function buildHandlerDiff(args) {
6034
6307
  }
6035
6308
  const slug = handlerSlugFromToolName(args.toolName);
6036
6309
  const filePath = `${serverBundleHandlersDir(args.devRootDir)}/${slug}.ts`;
6037
- const hasLocal = existsSync9(filePath);
6038
- const localContent = hasLocal ? readFileSync8(filePath, "utf-8") : null;
6310
+ const hasLocal = existsSync10(filePath);
6311
+ const localContent = hasLocal ? readFileSync9(filePath, "utf-8") : null;
6039
6312
  const localPath = hasLocal ? relative(args.cwd, filePath) || filePath : null;
6040
6313
  const changed = hasLocal ? localContent !== cloudResult.snapshot.content : true;
6041
6314
  return {
@@ -6145,18 +6418,18 @@ function renderBool(value) {
6145
6418
  }
6146
6419
 
6147
6420
  // src/commands/tools-edit.ts
6148
- import { existsSync as existsSync11 } from "node:fs";
6149
- import { join as join9, relative as relative2 } from "node:path";
6421
+ import { existsSync as existsSync12 } from "node:fs";
6422
+ import { join as join10, relative as relative2 } from "node:path";
6150
6423
 
6151
6424
  // src/lib/editor-shell.ts
6152
6425
  import { spawn as spawn2 } from "node:child_process";
6153
- import { existsSync as existsSync10, readFileSync as readFileSync9 } from "node:fs";
6426
+ import { existsSync as existsSync11, readFileSync as readFileSync10 } from "node:fs";
6154
6427
  import { platform as platform2 } from "node:os";
6155
- import { delimiter, join as join8 } from "node:path";
6428
+ import { delimiter, join as join9 } from "node:path";
6156
6429
  var DEFAULT_FALLBACKS = ["vi"];
6157
6430
  function isExecutableFile(path) {
6158
6431
  try {
6159
- return existsSync10(path);
6432
+ return existsSync11(path);
6160
6433
  } catch {
6161
6434
  return false;
6162
6435
  }
@@ -6173,7 +6446,7 @@ function findOnPath(command) {
6173
6446
  if (!dir)
6174
6447
  continue;
6175
6448
  for (const ext of exts) {
6176
- const candidate = join8(dir, command + ext);
6449
+ const candidate = join9(dir, command + ext);
6177
6450
  if (isExecutableFile(candidate))
6178
6451
  return candidate;
6179
6452
  }
@@ -6194,14 +6467,14 @@ function resolveEditorCommand(args) {
6194
6467
  return { command: DEFAULT_FALLBACKS[0], source: "fallback" };
6195
6468
  }
6196
6469
  function defaultRun(cmd, args) {
6197
- return new Promise((resolve5, reject) => {
6470
+ return new Promise((resolve6, reject) => {
6198
6471
  const child = spawn2(cmd, args, { stdio: "inherit" });
6199
6472
  child.on("error", (err) => reject(err));
6200
- child.on("exit", (code) => resolve5(code ?? 0));
6473
+ child.on("exit", (code) => resolve6(code ?? 0));
6201
6474
  });
6202
6475
  }
6203
6476
  async function runEditor(args) {
6204
- if (!existsSync10(args.filePath)) {
6477
+ if (!existsSync11(args.filePath)) {
6205
6478
  return {
6206
6479
  ok: false,
6207
6480
  reason: "file-missing",
@@ -6209,8 +6482,8 @@ async function runEditor(args) {
6209
6482
  };
6210
6483
  }
6211
6484
  const { command } = resolveEditorCommand({ command: args.command });
6212
- const resolve5 = args.resolveCommand ?? findOnPath;
6213
- const resolved = resolve5(command);
6485
+ const resolve6 = args.resolveCommand ?? findOnPath;
6486
+ const resolved = resolve6(command);
6214
6487
  if (!resolved) {
6215
6488
  return {
6216
6489
  ok: false,
@@ -6236,7 +6509,7 @@ async function runEditor(args) {
6236
6509
  message: `${command} exited with code ${exitCode}. The file may be unsaved; re-run after fixing.`
6237
6510
  };
6238
6511
  }
6239
- const contents = readFileSync9(args.filePath, "utf-8");
6512
+ const contents = readFileSync10(args.filePath, "utf-8");
6240
6513
  return { ok: true, contents, command };
6241
6514
  }
6242
6515
 
@@ -6260,7 +6533,7 @@ function registerToolsEditCommand(tools) {
6260
6533
  async function runToolsEdit(args) {
6261
6534
  if (isJsonMode()) {
6262
6535
  printError("`mcp tools edit` is interactive and cannot be used with --json.");
6263
- process.exit(1);
6536
+ throw new CliExitError(1);
6264
6537
  }
6265
6538
  const ctx = await resolveToolsContext({
6266
6539
  orgOverride: args.orgOverride,
@@ -6269,11 +6542,11 @@ async function runToolsEdit(args) {
6269
6542
  if (!ctx)
6270
6543
  return;
6271
6544
  const cwd = process.cwd();
6272
- const filePath = join9(serverToolsDir(devRoot(cwd), ctx.serverId), `${args.toolName}.md`);
6273
- if (!existsSync11(filePath)) {
6545
+ const filePath = join10(serverToolsDir(devRoot(cwd), ctx.serverId), `${args.toolName}.md`);
6546
+ if (!existsSync12(filePath)) {
6274
6547
  printError(`Tool file not found: ${relative2(cwd, filePath)}`);
6275
6548
  printInfo(` ${c.dim("Run")} ${c.bold(`mcp tools pull ${args.toolName}`)} ${c.dim("first to materialize it, or check the name is spelled correctly.")}`);
6276
- process.exit(1);
6549
+ throw new CliExitError(1);
6277
6550
  }
6278
6551
  printStep(`Opening ${c.bold(relative2(cwd, filePath))} in editor…`);
6279
6552
  const editorResult = await runEditor({
@@ -6282,7 +6555,7 @@ async function runToolsEdit(args) {
6282
6555
  });
6283
6556
  if (!editorResult.ok) {
6284
6557
  printError(editorResult.message);
6285
- process.exit(1);
6558
+ throw new CliExitError(1);
6286
6559
  }
6287
6560
  printStep(`Pushing edit: ${c.bold(args.toolName)}…`);
6288
6561
  const push = await pushLocalToolEdit({
@@ -6298,7 +6571,7 @@ async function runToolsEdit(args) {
6298
6571
  return;
6299
6572
  case "missing-file":
6300
6573
  printError(`${args.toolName}.md disappeared after the editor exited.`);
6301
- process.exit(1);
6574
+ throw new CliExitError(1);
6302
6575
  return;
6303
6576
  case "unknown-tool":
6304
6577
  printWarn(`${args.toolName}.md is not tracked in .tools-state.json — restart \`mcp dev\` or \`mcp tools pull\` to re-materialize.`);
@@ -6306,7 +6579,7 @@ async function runToolsEdit(args) {
6306
6579
  case "parse-error":
6307
6580
  printError(`Couldn't parse ${args.toolName}.md: ${push.message}`);
6308
6581
  printInfo(` ${c.dim("Re-open and fix the file, or run")} ${c.bold(`mcp tools pull ${args.toolName}`)} ${c.dim("to reset.")}`);
6309
- process.exit(1);
6582
+ throw new CliExitError(1);
6310
6583
  return;
6311
6584
  case "patched":
6312
6585
  if (isJsonMode()) {
@@ -6319,11 +6592,11 @@ async function runToolsEdit(args) {
6319
6592
  case "conflict":
6320
6593
  printError(`Conflict on ${c.bold(args.toolName)}: the cloud was updated by someone else since you read it.`);
6321
6594
  printInfo(` ${c.dim("Run:")} ${c.bold(`mcp tools pull ${args.toolName}`)} ${c.dim("to refresh, then re-apply your changes via")} ${c.bold(`mcp tools edit ${args.toolName}`)}${c.dim(".")}`);
6322
- process.exit(1);
6595
+ throw new CliExitError(1);
6323
6596
  return;
6324
6597
  case "api-error":
6325
6598
  printError(`Failed to save ${args.toolName} (HTTP ${push.status}): ${push.message}`);
6326
- process.exit(1);
6599
+ throw new CliExitError(1);
6327
6600
  return;
6328
6601
  default: {
6329
6602
  const _exhaustive = push;
@@ -6426,7 +6699,7 @@ async function runToolsEnrich(args) {
6426
6699
  const sample = list.slice(0, 5).map((t) => t.name).join(", ");
6427
6700
  printInfo(` ${c.dim("Available:")} ${sample}${list.length > 5 ? c.dim(`, +${list.length - 5} more`) : ""}`);
6428
6701
  }
6429
- process.exit(1);
6702
+ throw new CliExitError(1);
6430
6703
  }
6431
6704
  if (!isJsonMode()) {
6432
6705
  printStep(`Running AI enrichment for ${c.bold(args.toolName)}${args.modelId ? c.dim(` (model ${args.modelId})`) : ""}…`);
@@ -6446,7 +6719,7 @@ async function runToolsEnrich(args) {
6446
6719
  } else {
6447
6720
  printError(`Enrichment failed: ${err instanceof Error ? err.message : String(err)}`);
6448
6721
  }
6449
- process.exit(1);
6722
+ throw new CliExitError(1);
6450
6723
  }
6451
6724
  const detailed = await fetchEnrichedToolByName({
6452
6725
  organizationId: ctx.organizationId,
@@ -6456,11 +6729,11 @@ async function runToolsEnrich(args) {
6456
6729
  });
6457
6730
  if (!detailed) {
6458
6731
  printError("Enrichment ran, but the tool record could not be re-fetched. Try `mcp tools show` manually.");
6459
- process.exit(1);
6732
+ throw new CliExitError(1);
6460
6733
  }
6461
6734
  if (!detailed.enrichment) {
6462
6735
  printError("Enrichment ran without producing a suggestion. The AI may have failed silently — check `mcp tools show` or the dashboard.");
6463
- process.exit(1);
6736
+ throw new CliExitError(1);
6464
6737
  }
6465
6738
  if (isJsonMode()) {
6466
6739
  const json = {
@@ -6499,7 +6772,7 @@ async function runToolsEnrich(args) {
6499
6772
  const applyResult = await applySuggestion(detailed, ctx);
6500
6773
  if (!applyResult.ok) {
6501
6774
  printError(applyResult.message);
6502
- process.exit(1);
6775
+ throw new CliExitError(1);
6503
6776
  }
6504
6777
  printSuccess(`Applied ${c.bold(args.toolName)} ${c.dim(`(${applyResult.changedFields.length} field${applyResult.changedFields.length === 1 ? "" : "s"} patched)`)}`);
6505
6778
  printInfo(` ${c.dim("Run")} ${c.bold(`mcp tools pull ${args.toolName}`)} ${c.dim("to refresh your local copy.")}`);
@@ -6702,7 +6975,7 @@ async function runToolsPull(args) {
6702
6975
  const sample = forServer.slice(0, 5).map((t) => t.name).join(", ");
6703
6976
  printInfo(` ${c.dim("Available:")} ${sample}${forServer.length > 5 ? c.dim(`, +${forServer.length - 5} more`) : ""}`);
6704
6977
  }
6705
- process.exit(1);
6978
+ throw new CliExitError(1);
6706
6979
  }
6707
6980
  if (forServer.length === 0) {
6708
6981
  if (isJsonMode()) {
@@ -6777,13 +7050,13 @@ async function runPullHandler(args) {
6777
7050
  if (state.kind === "differs-from-cache") {
6778
7051
  printError(`${c.bold(slug2)}.ts has unsaved local edits (content differs from the last push). Pull would overwrite them.`);
6779
7052
  printInfo(` ${c.dim("Save the file (the watcher pushes to cloud), or re-run with")} ${c.bold("--force")} ${c.dim("to discard local edits.")}`);
6780
- process.exit(1);
7053
+ throw new CliExitError(1);
6781
7054
  }
6782
7055
  if (state.kind === "no-cache-entry" && state.recentlyModified) {
6783
7056
  const ageSec = Math.floor(state.ageMs / 1000);
6784
7057
  printError(`${c.bold(slug2)}.ts was modified ${ageSec}s ago and this CLI session has no record of pushing it (likely a fresh dev start). Pull would overwrite recent edits.`);
6785
7058
  printInfo(` ${c.dim("Save in the watcher session you edited from, or re-run with")} ${c.bold("--force")} ${c.dim("to discard local edits.")}`);
6786
- process.exit(1);
7059
+ throw new CliExitError(1);
6787
7060
  }
6788
7061
  }
6789
7062
  if (!isJsonMode())
@@ -6797,15 +7070,15 @@ async function runPullHandler(args) {
6797
7070
  });
6798
7071
  if (result.kind === "unknown-tool") {
6799
7072
  printError(`Tool ${c.bold(args.toolName)} not found in local .tools-state.json. ` + `Run \`mcp dev\` first to hydrate the bundle, or check the spelling (use the tool name, not the file slug).`);
6800
- process.exit(1);
7073
+ throw new CliExitError(1);
6801
7074
  }
6802
7075
  if (result.kind === "handler-row-not-hydrated") {
6803
7076
  printError(`No generated bundle exists for this server yet — codegen has never been run. ` + `Run \`mcp servers generate\` (or use the dashboard) to produce a bundle, then retry.`);
6804
- process.exit(1);
7077
+ throw new CliExitError(1);
6805
7078
  }
6806
7079
  if (result.kind === "api-error") {
6807
7080
  printError(`Failed to fetch handler (${result.status}${result.code ? ` ${result.code}` : ""}): ${result.message}`);
6808
- process.exit(1);
7081
+ throw new CliExitError(1);
6809
7082
  }
6810
7083
  const slug = handlerSlugFromToolName(result.snapshot.toolName);
6811
7084
  const written = writeLocalHandlerFromCloud({
@@ -6889,13 +7162,13 @@ async function runResetHandler(args) {
6889
7162
  if (state.kind === "differs-from-cache") {
6890
7163
  printError(`${c.bold(slug2)}.ts has unsaved local edits. Reset would overwrite them.`);
6891
7164
  printInfo(` ${c.dim("Save first (the watcher pushes to cloud), or re-run with")} ${c.bold("--force")} ${c.dim("to discard local edits.")}`);
6892
- process.exit(1);
7165
+ throw new CliExitError(1);
6893
7166
  }
6894
7167
  if (state.kind === "no-cache-entry" && state.recentlyModified) {
6895
7168
  const ageSec = Math.floor(state.ageMs / 1000);
6896
7169
  printError(`${c.bold(slug2)}.ts was modified ${ageSec}s ago and this CLI session has no record of pushing it. Reset would overwrite recent edits.`);
6897
7170
  printInfo(` ${c.dim("Save in the watcher session you edited from, or re-run with")} ${c.bold("--force")} ${c.dim("to discard local edits.")}`);
6898
- process.exit(1);
7171
+ throw new CliExitError(1);
6899
7172
  }
6900
7173
  }
6901
7174
  if (!args.assumeYes && !isJsonMode()) {
@@ -6916,20 +7189,20 @@ async function runResetHandler(args) {
6916
7189
  });
6917
7190
  if (result.kind === "unknown-tool") {
6918
7191
  printError(`Tool ${c.bold(args.toolName)} not found in local .tools-state.json. ` + `Run \`mcp dev\` first to hydrate, or check the spelling (use the tool name, not the file slug).`);
6919
- process.exit(1);
7192
+ throw new CliExitError(1);
6920
7193
  }
6921
7194
  if (result.kind === "handler-row-not-hydrated") {
6922
7195
  printError(`No generated bundle exists for this server yet — codegen has never been run. ` + `Run \`mcp servers generate\` (or use the dashboard) to produce a bundle, then retry.`);
6923
- process.exit(1);
7196
+ throw new CliExitError(1);
6924
7197
  }
6925
7198
  if (result.kind === "conflict") {
6926
7199
  printError(`Conflict resetting ${c.bold(result.toolName)} (cloud now at version ${result.currentVersion ?? "?"}).`);
6927
7200
  printInfo(` ${c.dim("Re-run `mcp tools pull-handler " + result.toolName + "` then retry.")}`);
6928
- process.exit(1);
7201
+ throw new CliExitError(1);
6929
7202
  }
6930
7203
  if (result.kind === "api-error") {
6931
7204
  printError(`Failed to reset handler (${result.status}${result.code ? ` ${result.code}` : ""}): ${result.message}`);
6932
- process.exit(1);
7205
+ throw new CliExitError(1);
6933
7206
  }
6934
7207
  const slug = handlerSlugFromToolName(result.snapshot.toolName);
6935
7208
  const written = writeLocalHandlerFromCloud({
@@ -6986,7 +7259,7 @@ async function runToolsShow(args) {
6986
7259
  const sample = tools.slice(0, 5).map((t) => t.name).join(", ");
6987
7260
  printInfo(` ${c.dim("Available:")} ${sample}${tools.length > 5 ? c.dim(`, +${tools.length - 5} more`) : ""}`);
6988
7261
  }
6989
- process.exit(1);
7262
+ throw new CliExitError(1);
6990
7263
  }
6991
7264
  const [detailed, handlerResult] = await Promise.all([
6992
7265
  fetchEnrichedToolDetail({
@@ -7399,6 +7672,29 @@ function registerSkillMutationCommands(skills) {
7399
7672
  "archived at": s.archivedAt ? formatDate(s.archivedAt) : "—"
7400
7673
  });
7401
7674
  }));
7675
+ skills.command("delete <skillId>").description('Permanently delete a draft skill. Requires --confirm "<exact name>". Published skills with versions/installs are rejected (archive instead).').requiredOption("--confirm <name>", "The exact skill name, typed to confirm the destructive action").option("--org <organizationId>", "Organization ID").addHelpText("after", [
7676
+ "",
7677
+ "Examples:",
7678
+ ' $ mcp skills delete skill_123 --confirm "Weather Reporter"'
7679
+ ].join(`
7680
+ `)).action(runAction(async (skillId, opts) => {
7681
+ const orgId = await resolveOrgId(opts.org);
7682
+ const detail = await api.get("/api/v1/skill", {
7683
+ organizationId: orgId,
7684
+ skillId
7685
+ });
7686
+ assertConfirmMatches({
7687
+ entityKind: "skill",
7688
+ entityName: detail.skill.name,
7689
+ confirm: opts.confirm
7690
+ });
7691
+ const data = await api.delete("/api/v1/skill", { organizationId: orgId, skillId });
7692
+ if (isJsonMode()) {
7693
+ printJson(data);
7694
+ return;
7695
+ }
7696
+ printSuccess(`Deleted skill ${data.deleted.name} (${data.deleted.id}).`);
7697
+ }));
7402
7698
  skills.command("install <skillId>").description("Install a skill into the caller's project").option("--org <organizationId>", "Organization ID").option("--project <projectId>", "Project ID (defaults to the skill's project)").option("--pin <semver>", "Pin a specific skill version (sent as `version` on the wire)").option("--config <json>", "Per-installation runtime config (passed verbatim to the skill)").action(runAction(async (skillId, opts) => {
7403
7699
  const orgId = await resolveOrgId(opts.org);
7404
7700
  const { projectId } = await resolveSkillProject(orgId, skillId, opts.project);
@@ -7609,42 +7905,471 @@ function registerSkillTestCommands(skills) {
7609
7905
  }));
7610
7906
  }
7611
7907
 
7612
- // src/commands/skills.ts
7613
- async function runClaudeMcpAdd(connectionName, mcpUrl) {
7614
- return new Promise((resolve5) => {
7615
- const child = spawn3("claude", ["mcp", "add", "--transport", "http", connectionName, mcpUrl], {
7616
- stdio: "inherit",
7617
- shell: false
7618
- });
7619
- child.on("error", () => resolve5(127));
7620
- child.on("exit", (code) => resolve5(code ?? 1));
7908
+ // src/lib/mcp-invoke.ts
7909
+ import { existsSync as existsSync13, readFileSync as readFileSync11 } from "node:fs";
7910
+ import { resolve as resolve6 } from "node:path";
7911
+ var DEFAULT_TIMEOUT_MS3 = 60000;
7912
+ var invokeRequestId = 1;
7913
+ async function invokeMcpTool(args) {
7914
+ const startedAt = Date.now();
7915
+ const controller = new AbortController;
7916
+ const timer = setTimeout(() => controller.abort(), args.timeoutMs ?? DEFAULT_TIMEOUT_MS3);
7917
+ const headers = {
7918
+ "content-type": "application/json",
7919
+ accept: "application/json, text/event-stream",
7920
+ ...args.headers
7921
+ };
7922
+ const body = JSON.stringify({
7923
+ jsonrpc: "2.0",
7924
+ id: `mcp-cli-invoke-${invokeRequestId++}`,
7925
+ method: "tools/call",
7926
+ params: { name: args.toolName, arguments: args.toolArguments }
7621
7927
  });
7622
- }
7623
- function registerSkillCommands(program2) {
7624
- const skills = program2.command("skills").description("Manage skills");
7625
- registerSkillMutationCommands(skills);
7626
- registerSkillTestCommands(skills);
7627
- skills.command("list").description("List skills in an organization").option("--org <organizationId>", "Organization ID").option("--project <projectId>", "Filter by project ID").option("--limit <n>", "Maximum results (default 25)", parsePositiveIntOption("limit"), "25").addHelpText("after", [
7628
- "",
7629
- "Examples:",
7630
- " $ mcp skills list",
7631
- " $ mcp skills list --filter status=published",
7632
- " $ mcp skills list --field id,name --format jsonl"
7633
- ].join(`
7634
- `)).action(runAction(async (opts) => {
7635
- const orgId = await resolveOrgId(opts.org);
7636
- const params = {
7637
- organizationId: orgId,
7638
- limit: opts.limit
7639
- };
7640
- if (opts.project)
7641
- params["projectId"] = opts.project;
7642
- const data = await api.get("/api/v1/skills", params);
7643
- printList(data.skills.map((s) => ({
7644
- id: s.id,
7645
- name: s.name,
7646
- project: s.projectName,
7647
- status: s.status,
7928
+ try {
7929
+ const res = await fetch(args.url, {
7930
+ method: "POST",
7931
+ headers,
7932
+ body,
7933
+ signal: controller.signal
7934
+ });
7935
+ const rawText = await res.text();
7936
+ const contentType = (res.headers.get("content-type") || "").toLowerCase();
7937
+ const jsonText = contentType.includes("text/event-stream") ? unwrapEventStream(rawText) : rawText;
7938
+ const parsed = parseJsonRpc(jsonText);
7939
+ const durationMs = Date.now() - startedAt;
7940
+ if (!parsed.ok) {
7941
+ if (!res.ok) {
7942
+ return {
7943
+ ok: false,
7944
+ reason: "http-error",
7945
+ status: res.status,
7946
+ message: parsed.error?.message ?? (rawText.slice(0, 200) || res.statusText),
7947
+ durationMs
7948
+ };
7949
+ }
7950
+ return {
7951
+ ok: false,
7952
+ reason: "rpc-error",
7953
+ error: parsed.error ?? {
7954
+ code: -32603,
7955
+ message: "Malformed JSON-RPC response.",
7956
+ data: rawText
7957
+ },
7958
+ durationMs
7959
+ };
7960
+ }
7961
+ return {
7962
+ ok: true,
7963
+ result: parsed.result,
7964
+ durationMs
7965
+ };
7966
+ } catch (err) {
7967
+ const durationMs = Date.now() - startedAt;
7968
+ const aborted = err instanceof Error && (err.name === "AbortError" || /abort/i.test(err.message));
7969
+ return {
7970
+ ok: false,
7971
+ reason: "http-error",
7972
+ status: 0,
7973
+ message: aborted ? `Request timed out after ${args.timeoutMs ?? DEFAULT_TIMEOUT_MS3}ms.` : err instanceof Error ? err.message : String(err),
7974
+ durationMs
7975
+ };
7976
+ } finally {
7977
+ clearTimeout(timer);
7978
+ }
7979
+ }
7980
+ async function listMcpTools(args) {
7981
+ const startedAt = Date.now();
7982
+ const controller = new AbortController;
7983
+ const timer = setTimeout(() => controller.abort(), args.timeoutMs ?? DEFAULT_TIMEOUT_MS3);
7984
+ const headers = {
7985
+ "content-type": "application/json",
7986
+ accept: "application/json, text/event-stream",
7987
+ ...args.headers
7988
+ };
7989
+ const body = JSON.stringify({
7990
+ jsonrpc: "2.0",
7991
+ id: `mcp-cli-list-${invokeRequestId++}`,
7992
+ method: "tools/list",
7993
+ params: {}
7994
+ });
7995
+ try {
7996
+ const res = await fetch(args.url, {
7997
+ method: "POST",
7998
+ headers,
7999
+ body,
8000
+ signal: controller.signal
8001
+ });
8002
+ const rawText = await res.text();
8003
+ const contentType = (res.headers.get("content-type") || "").toLowerCase();
8004
+ const jsonText = contentType.includes("text/event-stream") ? unwrapEventStream(rawText) : rawText;
8005
+ const parsed = parseJsonRpc(jsonText);
8006
+ const durationMs = Date.now() - startedAt;
8007
+ if (!parsed.ok) {
8008
+ if (!res.ok) {
8009
+ return {
8010
+ ok: false,
8011
+ reason: "http-error",
8012
+ status: res.status,
8013
+ message: parsed.error?.message ?? (rawText.slice(0, 200) || res.statusText),
8014
+ durationMs
8015
+ };
8016
+ }
8017
+ return {
8018
+ ok: false,
8019
+ reason: "rpc-error",
8020
+ error: parsed.error ?? {
8021
+ code: -32603,
8022
+ message: "Malformed JSON-RPC response.",
8023
+ data: rawText
8024
+ },
8025
+ durationMs
8026
+ };
8027
+ }
8028
+ const rawTools = parsed.result.tools;
8029
+ const tools = Array.isArray(rawTools) ? rawTools.flatMap((entry) => {
8030
+ if (!entry || typeof entry !== "object")
8031
+ return [];
8032
+ const name = entry.name;
8033
+ if (typeof name !== "string" || !name)
8034
+ return [];
8035
+ const description = entry.description;
8036
+ return [
8037
+ {
8038
+ name,
8039
+ ...typeof description === "string" ? { description } : {}
8040
+ }
8041
+ ];
8042
+ }) : [];
8043
+ return { ok: true, tools, durationMs };
8044
+ } catch (err) {
8045
+ const durationMs = Date.now() - startedAt;
8046
+ const aborted = err instanceof Error && (err.name === "AbortError" || /abort/i.test(err.message));
8047
+ return {
8048
+ ok: false,
8049
+ reason: "http-error",
8050
+ status: 0,
8051
+ message: aborted ? `Request timed out after ${args.timeoutMs ?? DEFAULT_TIMEOUT_MS3}ms.` : err instanceof Error ? err.message : String(err),
8052
+ durationMs
8053
+ };
8054
+ } finally {
8055
+ clearTimeout(timer);
8056
+ }
8057
+ }
8058
+ function parseJsonRpc(text) {
8059
+ if (!text.trim()) {
8060
+ return {
8061
+ ok: false,
8062
+ result: {},
8063
+ error: { code: -32700, message: "Response body was empty." }
8064
+ };
8065
+ }
8066
+ let parsed;
8067
+ try {
8068
+ parsed = JSON.parse(text);
8069
+ } catch (err) {
8070
+ return {
8071
+ ok: false,
8072
+ result: {},
8073
+ error: {
8074
+ code: -32700,
8075
+ message: `Response was not JSON: ${err instanceof Error ? err.message : String(err)}`
8076
+ }
8077
+ };
8078
+ }
8079
+ if (!parsed || typeof parsed !== "object") {
8080
+ return {
8081
+ ok: false,
8082
+ result: {},
8083
+ error: { code: -32603, message: "Malformed JSON-RPC response.", data: parsed }
8084
+ };
8085
+ }
8086
+ const obj = parsed;
8087
+ if ("result" in obj && obj.result && typeof obj.result === "object") {
8088
+ return { ok: true, result: obj.result };
8089
+ }
8090
+ if ("error" in obj && obj.error && typeof obj.error === "object") {
8091
+ const errObj = obj.error;
8092
+ return {
8093
+ ok: false,
8094
+ result: {},
8095
+ error: {
8096
+ code: typeof errObj.code === "number" ? errObj.code : -32603,
8097
+ message: typeof errObj.message === "string" ? errObj.message : "RPC error.",
8098
+ data: errObj.data
8099
+ }
8100
+ };
8101
+ }
8102
+ return {
8103
+ ok: false,
8104
+ result: {},
8105
+ error: { code: -32603, message: "Response had no result or error field." }
8106
+ };
8107
+ }
8108
+ function unwrapEventStream(text) {
8109
+ const parts = [];
8110
+ for (const line of text.split(/\r?\n/)) {
8111
+ if (line.startsWith("data:"))
8112
+ parts.push(line.slice(5).replace(/^ /, ""));
8113
+ }
8114
+ return parts.join("");
8115
+ }
8116
+ async function parseToolArguments(opts) {
8117
+ const raw = opts.raw?.trim();
8118
+ if (!raw)
8119
+ return { ok: true, value: {} };
8120
+ let source;
8121
+ if (raw === "@-" || raw === "-") {
8122
+ const reader = opts.readStdin ?? defaultStdinReader2;
8123
+ source = await reader();
8124
+ } else if (raw.startsWith("@")) {
8125
+ const filePath = resolve6(opts.cwd ?? process.cwd(), raw.slice(1));
8126
+ if (!existsSync13(filePath)) {
8127
+ return { ok: false, reason: "file-not-found", path: filePath };
8128
+ }
8129
+ source = readFileSync11(filePath, "utf-8");
8130
+ } else {
8131
+ source = raw;
8132
+ }
8133
+ let parsed;
8134
+ try {
8135
+ parsed = JSON.parse(source);
8136
+ } catch (err) {
8137
+ return {
8138
+ ok: false,
8139
+ reason: "parse-error",
8140
+ message: err instanceof Error ? err.message : String(err)
8141
+ };
8142
+ }
8143
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
8144
+ return { ok: false, reason: "not-an-object" };
8145
+ }
8146
+ return { ok: true, value: parsed };
8147
+ }
8148
+ async function defaultStdinReader2() {
8149
+ const chunks = [];
8150
+ return new Promise((res, rej) => {
8151
+ process.stdin.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
8152
+ process.stdin.on("end", () => res(Buffer.concat(chunks).toString("utf-8")));
8153
+ process.stdin.on("error", rej);
8154
+ });
8155
+ }
8156
+ function formatToolCallResult(result) {
8157
+ const lines = [];
8158
+ const blocks = result.content ?? [];
8159
+ for (const block of blocks) {
8160
+ if (block.type === "text" && typeof block.text === "string") {
8161
+ lines.push(block.text);
8162
+ continue;
8163
+ }
8164
+ if (block.type === "image") {
8165
+ const sized = typeof block.data === "string" ? `${block.data.length} bytes` : "?";
8166
+ lines.push(`[image ${block.mimeType ?? "application/octet-stream"} · ${sized} base64]`);
8167
+ continue;
8168
+ }
8169
+ if (block.type === "audio") {
8170
+ const sized = typeof block.data === "string" ? `${block.data.length} bytes` : "?";
8171
+ lines.push(`[audio ${block.mimeType ?? "application/octet-stream"} · ${sized} base64]`);
8172
+ continue;
8173
+ }
8174
+ if (block.type === "resource") {
8175
+ const r = block.resource ?? {};
8176
+ const inlineText = typeof r.text === "string" ? `
8177
+ ${r.text}` : "";
8178
+ lines.push(`[resource ${r.uri ?? "<no uri>"}${r.mimeType ? " · " + r.mimeType : ""}]${inlineText}`);
8179
+ continue;
8180
+ }
8181
+ lines.push(`[${block.type} block: ${truncateJsonPreview(block)}]`);
8182
+ }
8183
+ if (lines.length === 0 && result.structuredContent !== undefined) {
8184
+ lines.push(`[structured]`);
8185
+ lines.push(JSON.stringify(result.structuredContent, null, 2));
8186
+ }
8187
+ if (lines.length === 0) {
8188
+ lines.push("(no content)");
8189
+ }
8190
+ return { lines, isToolError: result.isError === true };
8191
+ }
8192
+ function truncateJsonPreview(value) {
8193
+ const json = (() => {
8194
+ try {
8195
+ return JSON.stringify(value);
8196
+ } catch {
8197
+ return "<unserializable>";
8198
+ }
8199
+ })();
8200
+ return json.length > 100 ? json.slice(0, 97) + "…" : json;
8201
+ }
8202
+
8203
+ // src/commands/skills-invoke.ts
8204
+ function parseTimeoutMs(value, fallbackMs) {
8205
+ if (!value)
8206
+ return fallbackMs;
8207
+ const n = Number(value);
8208
+ if (!Number.isFinite(n) || n <= 0 || n > 600) {
8209
+ throw new Error(`Invalid --timeout: ${value} (expected seconds, 1–600)`);
8210
+ }
8211
+ return Math.round(n * 1000);
8212
+ }
8213
+ function registerSkillInvokeCommand(skills) {
8214
+ skills.command("invoke <skillId>").description("Run a deployed skill from the terminal. Resolves the skill’s MCP endpoint, auto-discovers its single tool, and calls it.").option("--org <organizationId>", "Organization ID (overrides default)").option("--input <json>", "Skill input as inline JSON, `@path/to/input.json`, or `@-` for stdin. Defaults to `{}`.").option("--tool <name>", "Override the auto-discovered tool name (a deployed skill normally exposes exactly one)").option("--timeout <seconds>", "Request timeout in seconds (default 60, max 600)").option("--raw", "Print the raw JSON-RPC `result` envelope").addHelpText("after", [
8215
+ "",
8216
+ "Examples:",
8217
+ ` $ mcp skills invoke skill_123 --input '{"topic":"weather"}'`,
8218
+ " $ mcp skills invoke skill_123 --input @./input.json",
8219
+ ` $ echo '{"q":"hi"}' | mcp skills invoke skill_123 --input @-`,
8220
+ " $ mcp --json skills invoke skill_123",
8221
+ "",
8222
+ "Exit codes:",
8223
+ " 0 the call succeeded (the skill may still report isError=true)",
8224
+ " 1 the skill has no active deployment, the input was malformed,",
8225
+ " or the HTTP/JSON-RPC layer errored"
8226
+ ].join(`
8227
+ `)).action(runAction(async (skillId, opts) => {
8228
+ await runSkillInvoke(skillId, opts);
8229
+ }));
8230
+ }
8231
+ async function runSkillInvoke(skillId, opts) {
8232
+ const orgId = await resolveOrgId(opts.org);
8233
+ const parsedInput = await parseToolArguments({ raw: opts.input });
8234
+ if (!parsedInput.ok) {
8235
+ failOnInputError(parsedInput);
8236
+ }
8237
+ const data = await api.get("/api/v1/skill", {
8238
+ organizationId: orgId,
8239
+ skillId
8240
+ });
8241
+ const active = data.skill.activeDeployment;
8242
+ if (!active || !active.mcpUrl) {
8243
+ printError(`Skill ${c.bold(data.skill.name)} has no active deployment. Deploy it first, then retry.`);
8244
+ throw new CliExitError(1);
8245
+ }
8246
+ if (active.status !== "active") {
8247
+ printWarn(`Skill deployment is ${active.status}, not active — the call may be refused.`);
8248
+ }
8249
+ const timeoutMs = parseTimeoutMs(opts.timeout, 60000);
8250
+ const toolName = opts.tool ?? await discoverSkillTool(active.mcpUrl, timeoutMs);
8251
+ const result = await invokeMcpTool({
8252
+ url: active.mcpUrl,
8253
+ toolName,
8254
+ toolArguments: parsedInput.ok ? parsedInput.value : {},
8255
+ timeoutMs
8256
+ });
8257
+ if (isJsonMode()) {
8258
+ printJson({
8259
+ skillId: data.skill.id,
8260
+ slug: data.skill.slug,
8261
+ deploymentId: active.id,
8262
+ toolName,
8263
+ ...invokeResultToJson(result)
8264
+ });
8265
+ if (!result.ok)
8266
+ throw new CliExitError(1);
8267
+ return;
8268
+ }
8269
+ if (!result.ok) {
8270
+ if (result.reason === "rpc-error") {
8271
+ printError(`RPC error from ${c.bold(toolName)}: ${result.error.message}` + (result.error.code ? c.dim(` (code ${result.error.code})`) : ""));
8272
+ } else {
8273
+ const statusLine = result.status > 0 ? `HTTP ${result.status}` : "network error";
8274
+ printError(`${statusLine} from skill runtime: ${result.message}`);
8275
+ }
8276
+ throw new CliExitError(1);
8277
+ }
8278
+ if (opts.raw) {
8279
+ printInfo(JSON.stringify(result.result, null, 2));
8280
+ return;
8281
+ }
8282
+ const formatted = formatToolCallResult(result.result);
8283
+ for (const line of formatted.lines)
8284
+ printInfo(line);
8285
+ if (formatted.isToolError) {
8286
+ printWarn(`Skill reported isError=true. Inspect ${c.bold("--json")} or ${c.bold("--raw")} for the full envelope.`);
8287
+ }
8288
+ }
8289
+ async function discoverSkillTool(mcpUrl, timeoutMs) {
8290
+ const listed = await listMcpTools({ url: mcpUrl, timeoutMs });
8291
+ if (!listed.ok) {
8292
+ const detail = listed.reason === "rpc-error" ? listed.error.message : `${listed.status > 0 ? `HTTP ${listed.status}` : "network error"}: ${listed.message}`;
8293
+ printError(`Could not list the skill's tools to auto-discover the entry point (${detail}). Pass --tool <name> to call it directly.`);
8294
+ throw new CliExitError(1);
8295
+ }
8296
+ if (listed.tools.length === 0) {
8297
+ printError("The skill deployment advertises no tools. It may still be warming up — retry shortly.");
8298
+ throw new CliExitError(1);
8299
+ }
8300
+ if (listed.tools.length > 1) {
8301
+ printError(`The skill exposes ${listed.tools.length} tools (${listed.tools.map((tool) => tool.name).join(", ")}). Pass --tool <name> to choose one.`);
8302
+ throw new CliExitError(1);
8303
+ }
8304
+ return listed.tools[0].name;
8305
+ }
8306
+ function failOnInputError(parsed) {
8307
+ if (parsed.ok)
8308
+ throw new Error("unreachable");
8309
+ if (parsed.reason === "parse-error") {
8310
+ printError(`Failed to parse --input JSON: ${parsed.message}`);
8311
+ } else if (parsed.reason === "file-not-found") {
8312
+ printError(`--input file not found: ${parsed.path}`);
8313
+ } else if (parsed.reason === "not-an-object") {
8314
+ printError("--input must decode to a JSON object (not array / scalar).");
8315
+ }
8316
+ throw new CliExitError(1);
8317
+ }
8318
+ function invokeResultToJson(result) {
8319
+ if (result.ok) {
8320
+ return { ok: true, durationMs: result.durationMs, result: result.result };
8321
+ }
8322
+ if (result.reason === "rpc-error") {
8323
+ return {
8324
+ ok: false,
8325
+ durationMs: result.durationMs,
8326
+ error: { kind: "rpc-error", ...result.error }
8327
+ };
8328
+ }
8329
+ return {
8330
+ ok: false,
8331
+ durationMs: result.durationMs,
8332
+ error: { kind: "http-error", status: result.status, message: result.message }
8333
+ };
8334
+ }
8335
+
8336
+ // src/commands/skills.ts
8337
+ async function runClaudeMcpAdd(connectionName, mcpUrl) {
8338
+ return new Promise((resolve7) => {
8339
+ const child = spawn3("claude", ["mcp", "add", "--transport", "http", connectionName, mcpUrl], {
8340
+ stdio: "inherit",
8341
+ shell: false
8342
+ });
8343
+ child.on("error", () => resolve7(127));
8344
+ child.on("exit", (code) => resolve7(code ?? 1));
8345
+ });
8346
+ }
8347
+ function registerSkillCommands(program2) {
8348
+ const skills = program2.command("skills").description("Manage skills");
8349
+ registerSkillMutationCommands(skills);
8350
+ registerSkillTestCommands(skills);
8351
+ registerSkillInvokeCommand(skills);
8352
+ skills.command("list").description("List skills in an organization").option("--org <organizationId>", "Organization ID").option("--project <projectId>", "Filter by project ID").option("--limit <n>", "Maximum results (default 25)", parsePositiveIntOption("limit"), "25").addHelpText("after", [
8353
+ "",
8354
+ "Examples:",
8355
+ " $ mcp skills list",
8356
+ " $ mcp skills list --filter status=published",
8357
+ " $ mcp skills list --field id,name --format jsonl"
8358
+ ].join(`
8359
+ `)).action(runAction(async (opts) => {
8360
+ const orgId = await resolveOrgId(opts.org);
8361
+ const params = {
8362
+ organizationId: orgId,
8363
+ limit: opts.limit
8364
+ };
8365
+ if (opts.project)
8366
+ params["projectId"] = opts.project;
8367
+ const data = await api.get("/api/v1/skills", params);
8368
+ printList(data.skills.map((s) => ({
8369
+ id: s.id,
8370
+ name: s.name,
8371
+ project: s.projectName,
8372
+ status: s.status,
7648
8373
  version: s.version,
7649
8374
  updated: formatDate(s.updatedAt)
7650
8375
  })), [
@@ -7686,7 +8411,7 @@ function registerSkillCommands(program2) {
7686
8411
  skills.command("connect <skillId>").description("Print (or apply) the MCP configuration that connects this skill to your coding agent").option("--org <organizationId>", "Organization ID").option("--agent <agent>", `Agent preset: ${AGENT_KEYS.join(", ")}`, "claude-code").option("--name <name>", "Override the connection name shown in the agent (defaults to the skill slug)").option("--apply", 'For claude-code: invoke "claude mcp add" automatically. Other agents print only.').action(runAction(async (skillId, opts) => {
7687
8412
  if (!isAgentKey(opts.agent)) {
7688
8413
  printError(`Unknown --agent '${opts.agent}'. Valid values: ${AGENT_KEYS.join(", ")}.`);
7689
- process.exit(1);
8414
+ throw new CliExitError(1);
7690
8415
  }
7691
8416
  const agent = opts.agent;
7692
8417
  const orgId = await resolveOrgId(opts.org);
@@ -7707,7 +8432,7 @@ function registerSkillCommands(program2) {
7707
8432
  } else {
7708
8433
  printError(`Skill '${s.name}' has no active deployment. Deploy it first, then re-run "mcp skills connect".`);
7709
8434
  }
7710
- process.exit(1);
8435
+ throw new CliExitError(1);
7711
8436
  }
7712
8437
  const connectionName = opts.name?.trim() || s.slug || s.name;
7713
8438
  const preset = getAgentPreset({
@@ -7726,7 +8451,7 @@ function registerSkillCommands(program2) {
7726
8451
  } else {
7727
8452
  printError(`--apply is only supported for --agent claude-code right now. For ${preset.label}, copy the snippet into ${preset.location}.`);
7728
8453
  }
7729
- process.exit(1);
8454
+ throw new CliExitError(1);
7730
8455
  }
7731
8456
  if (!isJsonMode()) {
7732
8457
  printInfo(`Applying via "claude mcp add --transport http ${connectionName} ${deployment.mcpUrl}"…`);
@@ -7742,7 +8467,7 @@ function registerSkillCommands(program2) {
7742
8467
  } else {
7743
8468
  printError('Could not find the "claude" CLI on PATH. Install Claude Code or rerun without --apply.');
7744
8469
  }
7745
- process.exit(1);
8470
+ throw new CliExitError(1);
7746
8471
  }
7747
8472
  if (code !== 0) {
7748
8473
  if (isJsonMode()) {
@@ -7754,7 +8479,7 @@ function registerSkillCommands(program2) {
7754
8479
  } else {
7755
8480
  printError(`"claude mcp add" exited with code ${code}.`);
7756
8481
  }
7757
- process.exit(code || 1);
8482
+ throw new CliExitError(code || 1);
7758
8483
  }
7759
8484
  if (isJsonMode()) {
7760
8485
  printJson({
@@ -7878,6 +8603,29 @@ function registerSkillCommands(program2) {
7878
8603
  }
7879
8604
 
7880
8605
  // src/commands/api-keys.ts
8606
+ var MAX_EXPIRY_DAYS = 365;
8607
+ function parseExpiresInDays(raw) {
8608
+ if (raw === undefined)
8609
+ return;
8610
+ const trimmed = raw.trim().toLowerCase();
8611
+ const match = /^(\d+)\s*(d|w|m)?$/.exec(trimmed);
8612
+ if (!match) {
8613
+ throw new Error(`Invalid --expires-in: ${raw}. Expected a duration like 90d, 12w, 6m, or a day count.`);
8614
+ }
8615
+ const value = Number.parseInt(match[1], 10);
8616
+ const unit = match[2] ?? "d";
8617
+ let days;
8618
+ if (unit === "w")
8619
+ days = value * 7;
8620
+ else if (unit === "m")
8621
+ days = value * 30;
8622
+ else
8623
+ days = value;
8624
+ if (days < 1 || days > MAX_EXPIRY_DAYS) {
8625
+ throw new Error(`--expires-in must resolve to between 1 and ${MAX_EXPIRY_DAYS} days (got ${days}).`);
8626
+ }
8627
+ return days;
8628
+ }
7881
8629
  function formatPreview(key) {
7882
8630
  return `${key.keyPrefix}…${key.lastFour}`;
7883
8631
  }
@@ -7890,37 +8638,92 @@ function registerApiKeyCommands(program2) {
7890
8638
  name: k.name,
7891
8639
  preview: formatPreview(k),
7892
8640
  created: formatDate(k.createdAt),
7893
- "last used": k.lastUsedAt ? formatDate(k.lastUsedAt) : "—"
7894
- })), [
7895
- { key: "id", label: "ID", width: 20 },
7896
- { key: "name", label: "Name", width: 24 },
7897
- { key: "preview", label: "Preview", width: 18 },
7898
- { key: "created", label: "Created", width: 22 },
7899
- { key: "last used", label: "Last Used", width: 22 }
7900
- ], data);
8641
+ "last used": k.lastUsedAt ? formatDate(k.lastUsedAt) : "—",
8642
+ expires: k.expiresAt ? formatDate(k.expiresAt) : "never"
8643
+ })), [
8644
+ { key: "id", label: "ID", width: 20 },
8645
+ { key: "name", label: "Name", width: 24 },
8646
+ { key: "preview", label: "Preview", width: 18 },
8647
+ { key: "created", label: "Created", width: 22 },
8648
+ { key: "last used", label: "Last Used", width: 22 },
8649
+ { key: "expires", label: "Expires", width: 22 }
8650
+ ], data);
8651
+ }));
8652
+ apiKeys.command("create").description("Create a new API key (the secret is shown once)").requiredOption("--name <name>", "Display name for the key").option("--expires-in <duration>", "Auto-expire the key after this long: 90d, 12w, 6m, or a day count (1–365). Default: never.").action(runAction(async (opts) => {
8653
+ const name = opts.name.trim();
8654
+ if (!name)
8655
+ throw new Error("--name must not be empty.");
8656
+ const expiresInDays = parseExpiresInDays(opts.expiresIn);
8657
+ const data = await api.post("/api/v1/api-keys", {
8658
+ name,
8659
+ ...expiresInDays !== undefined ? { expiresInDays } : {}
8660
+ });
8661
+ if (isJsonMode()) {
8662
+ printJson(data);
8663
+ return;
8664
+ }
8665
+ printSuccess("API key created — copy the secret below now, it will not be shown again.");
8666
+ console.log("");
8667
+ printKeyValue({
8668
+ id: data.apiKeyId,
8669
+ name: data.name,
8670
+ key: data.apiKey,
8671
+ "key prefix": data.keyPrefix,
8672
+ "last four": data.lastFour,
8673
+ expires: data.expiresAt ? formatDate(data.expiresAt) : "never"
8674
+ });
8675
+ console.log("");
8676
+ printInfo("Save it via `mcp login --key <secret>` or export MCPCLOUD_API_KEY=<secret>.");
8677
+ }));
8678
+ apiKeys.command("describe <apiKeyId>").description("Show one API key (status, expiry, last used) — never the secret").action(runAction(async (apiKeyId) => {
8679
+ const data = await api.get("/api/v1/api-keys", { apiKeyId });
8680
+ if (isJsonMode()) {
8681
+ printJson(data);
8682
+ return;
8683
+ }
8684
+ const k = data.apiKey;
8685
+ printKeyValue({
8686
+ id: k.id,
8687
+ name: k.name,
8688
+ preview: formatPreview(k),
8689
+ status: k.status,
8690
+ created: formatDate(k.createdAt),
8691
+ "last used": k.lastUsedAt ? formatDate(k.lastUsedAt) : "—",
8692
+ expires: k.expiresAt ? formatDate(k.expiresAt) : "never",
8693
+ revoked: k.revokedAt ? formatDate(k.revokedAt) : "—"
8694
+ });
7901
8695
  }));
7902
- apiKeys.command("create").description("Create a new API key (the secret is shown once)").requiredOption("--name <name>", "Display name for the key").action(runAction(async (opts) => {
7903
- const name = opts.name.trim();
7904
- if (!name)
7905
- throw new Error("--name must not be empty.");
7906
- const data = await api.post("/api/v1/api-keys", {
7907
- name
8696
+ apiKeys.command("rotate <apiKeyId>").description("Mint a replacement key and grace-expire the old one (24h window). The new secret is shown once.").option("--expires-in <duration>", "Expiry for the NEW key: 90d, 12w, 6m, or a day count (1–365). Default: never.").addHelpText("after", [
8697
+ "",
8698
+ "The old key keeps working for 24h so running automation can swap to the",
8699
+ "new secret before the old one stops. Update your secrets within that window.",
8700
+ "",
8701
+ "Examples:",
8702
+ " $ mcp api-keys rotate key_123",
8703
+ " $ mcp api-keys rotate key_123 --expires-in 90d"
8704
+ ].join(`
8705
+ `)).action(runAction(async (apiKeyId, opts) => {
8706
+ const expiresInDays = parseExpiresInDays(opts.expiresIn);
8707
+ const data = await api.patch("/api/v1/api-keys", {
8708
+ apiKeyId,
8709
+ ...expiresInDays !== undefined ? { expiresInDays } : {}
7908
8710
  });
7909
8711
  if (isJsonMode()) {
7910
8712
  printJson(data);
7911
8713
  return;
7912
8714
  }
7913
- printSuccess("API key created — copy the secret below now, it will not be shown again.");
8715
+ printSuccess("API key rotated — copy the new secret below now, it will not be shown again.");
7914
8716
  console.log("");
7915
8717
  printKeyValue({
7916
- id: data.apiKeyId,
8718
+ "new id": data.apiKeyId,
7917
8719
  name: data.name,
7918
8720
  key: data.apiKey,
7919
8721
  "key prefix": data.keyPrefix,
7920
- "last four": data.lastFour
8722
+ "last four": data.lastFour,
8723
+ expires: data.expiresAt ? formatDate(data.expiresAt) : "never",
8724
+ "old key": data.rotatedFrom?.apiKeyId ?? "—",
8725
+ "old key valid until": data.rotatedFrom ? formatDate(data.rotatedFrom.gracePeriodEndsAt) : "—"
7921
8726
  });
7922
- console.log("");
7923
- printInfo("Save it via `mcp login --key <secret>` or export MCPCLOUD_API_KEY=<secret>.");
7924
8727
  }));
7925
8728
  apiKeys.command("revoke <apiKeyId>").description("Permanently revoke an API key").action(runAction(async (apiKeyId) => {
7926
8729
  const data = await api.delete("/api/v1/api-keys", { apiKeyId });
@@ -7934,9 +8737,9 @@ function registerApiKeyCommands(program2) {
7934
8737
 
7935
8738
  // src/commands/config.ts
7936
8739
  import { homedir as homedir2 } from "node:os";
7937
- import { join as join10 } from "node:path";
8740
+ import { join as join11 } from "node:path";
7938
8741
  function configFilePath() {
7939
- return join10(homedir2(), ".mcpcloud", "config.json");
8742
+ return join11(homedir2(), ".mcpcloud", "config.json");
7940
8743
  }
7941
8744
  function previewKey2(key) {
7942
8745
  if (!key)
@@ -8083,353 +8886,136 @@ function registerConfigCommands(program2) {
8083
8886
  cleared.push("open preference");
8084
8887
  if (hadCmd)
8085
8888
  cleared.push("editor command");
8086
- if (hadTarget)
8087
- cleared.push("open target");
8088
- const what = cleared.length === 0 ? "nothing (already empty)" : cleared.join(" + ");
8089
- printSuccess(`Cleared ${what}. \`mcp dev\` will re-detect editors and prompt next session.`);
8090
- }));
8091
- config.command("set-editor-command <command>").description("Override the editor launch command (default: code; e.g. cursor, code-insiders)").action(runAction((command) => {
8092
- const cfg = readConfig();
8093
- cfg.editorCommand = command.trim();
8094
- writeConfig(cfg);
8095
- if (isJsonMode()) {
8096
- printJson({ editorCommand: cfg.editorCommand });
8097
- return;
8098
- }
8099
- printSuccess(`Saved editor command: ${cfg.editorCommand}`);
8100
- }));
8101
- config.command("use <name>").description("Switch the active profile (e.g. staging, prod)").action(runAction((name) => {
8102
- setCurrentProfile(name);
8103
- if (isJsonMode()) {
8104
- printJson({ currentProfile: name });
8105
- return;
8106
- }
8107
- printSuccess(`Switched to profile: ${name}`);
8108
- }));
8109
- const profile = config.command("profile").description("Manage named config profiles (apiKey + baseUrl + defaultOrganizationId)");
8110
- profile.command("list").description("List all configured profiles").action(runAction(() => {
8111
- const profiles = listProfiles();
8112
- if (isJsonMode()) {
8113
- printJson({ profiles, currentProfile: getActiveProfileName() });
8114
- return;
8115
- }
8116
- if (profiles.length === 0) {
8117
- console.log("No profiles configured. Run `mcp config profile add <name>` to create one.");
8118
- return;
8119
- }
8120
- printTable(profiles.map((p) => ({
8121
- current: p.isCurrent ? c.green("✓") : "",
8122
- name: p.name,
8123
- baseUrl: p.baseUrl ?? "—",
8124
- "default org": p.defaultOrganizationId ?? "—",
8125
- "api key": p.hasApiKey ? c.dim("saved") : c.dim("—")
8126
- })), [
8127
- { key: "current", label: "", width: 3 },
8128
- { key: "name", label: "Name", width: 18 },
8129
- { key: "baseUrl", label: "Base URL", width: 36 },
8130
- { key: "default org", label: "Default org", width: 22 },
8131
- { key: "api key", label: "API key", width: 10 }
8132
- ]);
8133
- }));
8134
- profile.command("add <name>").description("Create a new profile (use --url / --key / --app / --org to seed values)").option("--url <url>", "API base URL for the new profile").option("--key <apiKey>", "API key for the new profile (writes to config.json mode 0600)").option("--app <url>", "Dashboard origin override for the new profile").option("--org <organizationId>", "Default organization ID for the new profile").action(runAction((name, opts) => {
8135
- if (opts.url) {
8136
- try {
8137
- new URL(opts.url);
8138
- } catch {
8139
- throw new Error(`Invalid --url: ${opts.url}`);
8140
- }
8141
- }
8142
- if (opts.app) {
8143
- try {
8144
- new URL(opts.app);
8145
- } catch {
8146
- throw new Error(`Invalid --app: ${opts.app}`);
8147
- }
8148
- }
8149
- const seed = {};
8150
- if (opts.url)
8151
- seed.baseUrl = opts.url;
8152
- if (opts.key)
8153
- seed.apiKey = opts.key;
8154
- if (opts.app)
8155
- seed.appUrl = opts.app.replace(/\/+$/, "");
8156
- if (opts.org)
8157
- seed.defaultOrganizationId = opts.org;
8158
- addProfile(name, seed);
8159
- if (isJsonMode()) {
8160
- printJson({ added: name.trim(), seeded: Object.keys(seed) });
8161
- return;
8162
- }
8163
- printSuccess(`Created profile: ${name.trim()}`);
8164
- if (opts.key) {
8165
- console.log(c.dim(" Tip: passing --key writes the secret to your shell history. Prefer `mcp --profile <name> login` for interactive setup."));
8166
- }
8167
- console.log(c.dim(` Switch to it with \`mcp config use ${name.trim()}\`.`));
8168
- }));
8169
- profile.command("remove <name>").description("Delete a profile (cannot remove the active or only-remaining profile)").action(runAction((name) => {
8170
- removeProfile(name);
8171
- if (isJsonMode()) {
8172
- printJson({ removed: name });
8173
- return;
8174
- }
8175
- printSuccess(`Removed profile: ${name}`);
8176
- }));
8177
- }
8178
-
8179
- // src/commands/dev.ts
8180
- import { existsSync as existsSync32 } from "node:fs";
8181
- import { isAbsolute as isAbsolute3, relative as relative7, resolve as resolve7 } from "node:path";
8182
-
8183
- // src/lib/mcp-invoke.ts
8184
- import { existsSync as existsSync12, readFileSync as readFileSync10 } from "node:fs";
8185
- import { resolve as resolve5 } from "node:path";
8186
- var DEFAULT_TIMEOUT_MS3 = 60000;
8187
- var invokeRequestId = 1;
8188
- async function invokeMcpTool(args) {
8189
- const startedAt = Date.now();
8190
- const controller = new AbortController;
8191
- const timer = setTimeout(() => controller.abort(), args.timeoutMs ?? DEFAULT_TIMEOUT_MS3);
8192
- const headers = {
8193
- "content-type": "application/json",
8194
- accept: "application/json, text/event-stream",
8195
- ...args.headers
8196
- };
8197
- const body = JSON.stringify({
8198
- jsonrpc: "2.0",
8199
- id: `mcp-cli-invoke-${invokeRequestId++}`,
8200
- method: "tools/call",
8201
- params: { name: args.toolName, arguments: args.toolArguments }
8202
- });
8203
- try {
8204
- const res = await fetch(args.url, {
8205
- method: "POST",
8206
- headers,
8207
- body,
8208
- signal: controller.signal
8209
- });
8210
- const rawText = await res.text();
8211
- const contentType = (res.headers.get("content-type") || "").toLowerCase();
8212
- const jsonText = contentType.includes("text/event-stream") ? unwrapEventStream(rawText) : rawText;
8213
- const parsed = parseJsonRpc(jsonText);
8214
- const durationMs = Date.now() - startedAt;
8215
- if (!parsed.ok) {
8216
- if (!res.ok) {
8217
- return {
8218
- ok: false,
8219
- reason: "http-error",
8220
- status: res.status,
8221
- message: parsed.error?.message ?? (rawText.slice(0, 200) || res.statusText),
8222
- durationMs
8223
- };
8224
- }
8225
- return {
8226
- ok: false,
8227
- reason: "rpc-error",
8228
- error: parsed.error ?? {
8229
- code: -32603,
8230
- message: "Malformed JSON-RPC response.",
8231
- data: rawText
8232
- },
8233
- durationMs
8234
- };
8235
- }
8236
- return {
8237
- ok: true,
8238
- result: parsed.result,
8239
- durationMs
8240
- };
8241
- } catch (err) {
8242
- const durationMs = Date.now() - startedAt;
8243
- const aborted = err instanceof Error && (err.name === "AbortError" || /abort/i.test(err.message));
8244
- return {
8245
- ok: false,
8246
- reason: "http-error",
8247
- status: 0,
8248
- message: aborted ? `Request timed out after ${args.timeoutMs ?? DEFAULT_TIMEOUT_MS3}ms.` : err instanceof Error ? err.message : String(err),
8249
- durationMs
8250
- };
8251
- } finally {
8252
- clearTimeout(timer);
8253
- }
8254
- }
8255
- function parseJsonRpc(text) {
8256
- if (!text.trim()) {
8257
- return {
8258
- ok: false,
8259
- result: {},
8260
- error: { code: -32700, message: "Response body was empty." }
8261
- };
8262
- }
8263
- let parsed;
8264
- try {
8265
- parsed = JSON.parse(text);
8266
- } catch (err) {
8267
- return {
8268
- ok: false,
8269
- result: {},
8270
- error: {
8271
- code: -32700,
8272
- message: `Response was not JSON: ${err instanceof Error ? err.message : String(err)}`
8273
- }
8274
- };
8275
- }
8276
- if (!parsed || typeof parsed !== "object") {
8277
- return {
8278
- ok: false,
8279
- result: {},
8280
- error: { code: -32603, message: "Malformed JSON-RPC response.", data: parsed }
8281
- };
8282
- }
8283
- const obj = parsed;
8284
- if ("result" in obj && obj.result && typeof obj.result === "object") {
8285
- return { ok: true, result: obj.result };
8286
- }
8287
- if ("error" in obj && obj.error && typeof obj.error === "object") {
8288
- const errObj = obj.error;
8289
- return {
8290
- ok: false,
8291
- result: {},
8292
- error: {
8293
- code: typeof errObj.code === "number" ? errObj.code : -32603,
8294
- message: typeof errObj.message === "string" ? errObj.message : "RPC error.",
8295
- data: errObj.data
8296
- }
8297
- };
8298
- }
8299
- return {
8300
- ok: false,
8301
- result: {},
8302
- error: { code: -32603, message: "Response had no result or error field." }
8303
- };
8304
- }
8305
- function unwrapEventStream(text) {
8306
- const parts = [];
8307
- for (const line of text.split(/\r?\n/)) {
8308
- if (line.startsWith("data:"))
8309
- parts.push(line.slice(5).replace(/^ /, ""));
8310
- }
8311
- return parts.join("");
8312
- }
8313
- async function parseToolArguments(opts) {
8314
- const raw = opts.raw?.trim();
8315
- if (!raw)
8316
- return { ok: true, value: {} };
8317
- let source;
8318
- if (raw === "@-" || raw === "-") {
8319
- const reader = opts.readStdin ?? defaultStdinReader;
8320
- source = await reader();
8321
- } else if (raw.startsWith("@")) {
8322
- const filePath = resolve5(opts.cwd ?? process.cwd(), raw.slice(1));
8323
- if (!existsSync12(filePath)) {
8324
- return { ok: false, reason: "file-not-found", path: filePath };
8889
+ if (hadTarget)
8890
+ cleared.push("open target");
8891
+ const what = cleared.length === 0 ? "nothing (already empty)" : cleared.join(" + ");
8892
+ printSuccess(`Cleared ${what}. \`mcp dev\` will re-detect editors and prompt next session.`);
8893
+ }));
8894
+ config.command("set-editor-command <command>").description("Override the editor launch command (default: code; e.g. cursor, code-insiders)").action(runAction((command) => {
8895
+ const cfg = readConfig();
8896
+ cfg.editorCommand = command.trim();
8897
+ writeConfig(cfg);
8898
+ if (isJsonMode()) {
8899
+ printJson({ editorCommand: cfg.editorCommand });
8900
+ return;
8325
8901
  }
8326
- source = readFileSync10(filePath, "utf-8");
8327
- } else {
8328
- source = raw;
8329
- }
8330
- let parsed;
8331
- try {
8332
- parsed = JSON.parse(source);
8333
- } catch (err) {
8334
- return {
8335
- ok: false,
8336
- reason: "parse-error",
8337
- message: err instanceof Error ? err.message : String(err)
8338
- };
8339
- }
8340
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
8341
- return { ok: false, reason: "not-an-object" };
8342
- }
8343
- return { ok: true, value: parsed };
8344
- }
8345
- async function defaultStdinReader() {
8346
- const chunks = [];
8347
- return new Promise((res, rej) => {
8348
- process.stdin.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
8349
- process.stdin.on("end", () => res(Buffer.concat(chunks).toString("utf-8")));
8350
- process.stdin.on("error", rej);
8351
- });
8352
- }
8353
- function formatToolCallResult(result) {
8354
- const lines = [];
8355
- const blocks = result.content ?? [];
8356
- for (const block of blocks) {
8357
- if (block.type === "text" && typeof block.text === "string") {
8358
- lines.push(block.text);
8359
- continue;
8902
+ printSuccess(`Saved editor command: ${cfg.editorCommand}`);
8903
+ }));
8904
+ config.command("use <name>").description("Switch the active profile (e.g. staging, prod)").action(runAction((name) => {
8905
+ setCurrentProfile(name);
8906
+ if (isJsonMode()) {
8907
+ printJson({ currentProfile: name });
8908
+ return;
8360
8909
  }
8361
- if (block.type === "image") {
8362
- const sized = typeof block.data === "string" ? `${block.data.length} bytes` : "?";
8363
- lines.push(`[image ${block.mimeType ?? "application/octet-stream"} · ${sized} base64]`);
8364
- continue;
8910
+ printSuccess(`Switched to profile: ${name}`);
8911
+ }));
8912
+ const profile = config.command("profile").description("Manage named config profiles (apiKey + baseUrl + defaultOrganizationId)");
8913
+ profile.command("list").description("List all configured profiles").action(runAction(() => {
8914
+ const profiles = listProfiles();
8915
+ if (isJsonMode()) {
8916
+ printJson({ profiles, currentProfile: getActiveProfileName() });
8917
+ return;
8365
8918
  }
8366
- if (block.type === "audio") {
8367
- const sized = typeof block.data === "string" ? `${block.data.length} bytes` : "?";
8368
- lines.push(`[audio ${block.mimeType ?? "application/octet-stream"} · ${sized} base64]`);
8369
- continue;
8919
+ if (profiles.length === 0) {
8920
+ console.log("No profiles configured. Run `mcp config profile add <name>` to create one.");
8921
+ return;
8370
8922
  }
8371
- if (block.type === "resource") {
8372
- const r = block.resource ?? {};
8373
- const inlineText = typeof r.text === "string" ? `
8374
- ${r.text}` : "";
8375
- lines.push(`[resource ${r.uri ?? "<no uri>"}${r.mimeType ? " · " + r.mimeType : ""}]${inlineText}`);
8376
- continue;
8923
+ printTable(profiles.map((p) => ({
8924
+ current: p.isCurrent ? c.green("✓") : "",
8925
+ name: p.name,
8926
+ baseUrl: p.baseUrl ?? "",
8927
+ "default org": p.defaultOrganizationId ?? "",
8928
+ "api key": p.hasApiKey ? c.dim("saved") : c.dim("—")
8929
+ })), [
8930
+ { key: "current", label: "", width: 3 },
8931
+ { key: "name", label: "Name", width: 18 },
8932
+ { key: "baseUrl", label: "Base URL", width: 36 },
8933
+ { key: "default org", label: "Default org", width: 22 },
8934
+ { key: "api key", label: "API key", width: 10 }
8935
+ ]);
8936
+ }));
8937
+ profile.command("add <name>").description("Create a new profile (use --url / --key / --app / --org to seed values)").option("--url <url>", "API base URL for the new profile").option("--key <apiKey>", "API key for the new profile (writes to config.json mode 0600)").option("--app <url>", "Dashboard origin override for the new profile").option("--org <organizationId>", "Default organization ID for the new profile").action(runAction((name, opts) => {
8938
+ if (opts.url) {
8939
+ try {
8940
+ new URL(opts.url);
8941
+ } catch {
8942
+ throw new Error(`Invalid --url: ${opts.url}`);
8943
+ }
8377
8944
  }
8378
- lines.push(`[${block.type} block: ${truncateJsonPreview(block)}]`);
8379
- }
8380
- if (lines.length === 0 && result.structuredContent !== undefined) {
8381
- lines.push(`[structured]`);
8382
- lines.push(JSON.stringify(result.structuredContent, null, 2));
8383
- }
8384
- if (lines.length === 0) {
8385
- lines.push("(no content)");
8386
- }
8387
- return { lines, isToolError: result.isError === true };
8388
- }
8389
- function truncateJsonPreview(value) {
8390
- const json = (() => {
8391
- try {
8392
- return JSON.stringify(value);
8393
- } catch {
8394
- return "<unserializable>";
8945
+ if (opts.app) {
8946
+ try {
8947
+ new URL(opts.app);
8948
+ } catch {
8949
+ throw new Error(`Invalid --app: ${opts.app}`);
8950
+ }
8395
8951
  }
8396
- })();
8397
- return json.length > 100 ? json.slice(0, 97) + "…" : json;
8952
+ const seed = {};
8953
+ if (opts.url)
8954
+ seed.baseUrl = opts.url;
8955
+ if (opts.key)
8956
+ seed.apiKey = opts.key;
8957
+ if (opts.app)
8958
+ seed.appUrl = opts.app.replace(/\/+$/, "");
8959
+ if (opts.org)
8960
+ seed.defaultOrganizationId = opts.org;
8961
+ addProfile(name, seed);
8962
+ if (isJsonMode()) {
8963
+ printJson({ added: name.trim(), seeded: Object.keys(seed) });
8964
+ return;
8965
+ }
8966
+ printSuccess(`Created profile: ${name.trim()}`);
8967
+ if (opts.key) {
8968
+ console.log(c.dim(" Tip: passing --key writes the secret to your shell history. Prefer `mcp --profile <name> login` for interactive setup."));
8969
+ }
8970
+ console.log(c.dim(` Switch to it with \`mcp config use ${name.trim()}\`.`));
8971
+ }));
8972
+ profile.command("remove <name>").description("Delete a profile (cannot remove the active or only-remaining profile)").action(runAction((name) => {
8973
+ removeProfile(name);
8974
+ if (isJsonMode()) {
8975
+ printJson({ removed: name });
8976
+ return;
8977
+ }
8978
+ printSuccess(`Removed profile: ${name}`);
8979
+ }));
8398
8980
  }
8399
8981
 
8982
+ // src/commands/dev.ts
8983
+ import { existsSync as existsSync33 } from "node:fs";
8984
+ import { isAbsolute as isAbsolute3, relative as relative7, resolve as resolve8 } from "node:path";
8985
+
8400
8986
  // src/lib/dev/sessions.ts
8401
8987
  import {
8402
- existsSync as existsSync13,
8403
- mkdirSync as mkdirSync7,
8404
- readFileSync as readFileSync11,
8988
+ existsSync as existsSync14,
8989
+ mkdirSync as mkdirSync8,
8990
+ readFileSync as readFileSync12,
8405
8991
  readdirSync as readdirSync2,
8406
8992
  unlinkSync,
8407
- writeFileSync as writeFileSync6
8993
+ writeFileSync as writeFileSync7
8408
8994
  } from "node:fs";
8409
8995
  import { homedir as homedir3 } from "node:os";
8410
- import { join as join11 } from "node:path";
8996
+ import { join as join12 } from "node:path";
8411
8997
  var SESSIONS_DIR_NAME = "dev-sessions";
8412
8998
  function sessionsDir() {
8413
- return join11(homedir3(), ".mcpcloud", SESSIONS_DIR_NAME);
8999
+ return join12(homedir3(), ".mcpcloud", SESSIONS_DIR_NAME);
8414
9000
  }
8415
9001
  function sessionFile(pid) {
8416
- return join11(sessionsDir(), `${pid}.json`);
9002
+ return join12(sessionsDir(), `${pid}.json`);
8417
9003
  }
8418
9004
  function ensureDir() {
8419
9005
  const dir = sessionsDir();
8420
- if (!existsSync13(dir))
8421
- mkdirSync7(dir, { recursive: true, mode: 448 });
9006
+ if (!existsSync14(dir))
9007
+ mkdirSync8(dir, { recursive: true, mode: 448 });
8422
9008
  }
8423
9009
  function recordSession(record) {
8424
9010
  ensureDir();
8425
- writeFileSync6(sessionFile(record.pid), JSON.stringify(record, null, 2), {
9011
+ writeFileSync7(sessionFile(record.pid), JSON.stringify(record, null, 2), {
8426
9012
  encoding: "utf-8",
8427
9013
  mode: 384
8428
9014
  });
8429
9015
  }
8430
9016
  function removeSession(pid) {
8431
9017
  const path = sessionFile(pid);
8432
- if (existsSync13(path)) {
9018
+ if (existsSync14(path)) {
8433
9019
  try {
8434
9020
  unlinkSync(path);
8435
9021
  } catch {}
@@ -8437,15 +9023,15 @@ function removeSession(pid) {
8437
9023
  }
8438
9024
  function listSessions() {
8439
9025
  const dir = sessionsDir();
8440
- if (!existsSync13(dir))
9026
+ if (!existsSync14(dir))
8441
9027
  return [];
8442
9028
  const live = [];
8443
9029
  for (const entry of readdirSync2(dir)) {
8444
9030
  if (!entry.endsWith(".json"))
8445
9031
  continue;
8446
- const path = join11(dir, entry);
9032
+ const path = join12(dir, entry);
8447
9033
  try {
8448
- const parsed = JSON.parse(readFileSync11(path, "utf-8"));
9034
+ const parsed = JSON.parse(readFileSync12(path, "utf-8"));
8449
9035
  if (typeof parsed.pid !== "number") {
8450
9036
  unlinkSync(path);
8451
9037
  continue;
@@ -8505,7 +9091,7 @@ async function killSession(pid, timeoutMs = 3000) {
8505
9091
  }
8506
9092
 
8507
9093
  // src/commands/dev-invoke.ts
8508
- function parseTimeoutMs(value, fallbackMs) {
9094
+ function parseTimeoutMs2(value, fallbackMs) {
8509
9095
  if (!value)
8510
9096
  return fallbackMs;
8511
9097
  const n = Number(value);
@@ -8532,7 +9118,7 @@ function registerDevInvokeCommand(dev) {
8532
9118
  toolName: tool,
8533
9119
  argsRaw: opts.args,
8534
9120
  urlOverride: opts.url,
8535
- timeoutMs: parseTimeoutMs(opts.timeout, 60000),
9121
+ timeoutMs: parseTimeoutMs2(opts.timeout, 60000),
8536
9122
  raw: Boolean(opts.raw)
8537
9123
  });
8538
9124
  }));
@@ -8548,12 +9134,12 @@ async function runDevInvoke(args) {
8548
9134
  } else if (parsedArgs.reason === "not-an-object") {
8549
9135
  printError("--args must decode to a JSON object (not array / scalar).");
8550
9136
  }
8551
- process.exit(1);
9137
+ throw new CliExitError(1);
8552
9138
  }
8553
9139
  const baseUrl = args.urlOverride ?? resolveDevBaseUrl(cwd);
8554
9140
  if (!baseUrl) {
8555
9141
  printError("No running `mcp dev` session in this directory. Start one, or pass --url <baseUrl>.");
8556
- process.exit(1);
9142
+ throw new CliExitError(1);
8557
9143
  }
8558
9144
  const mcpUrl = baseUrl.endsWith("/mcp") ? baseUrl : `${baseUrl.replace(/\/$/, "")}/mcp`;
8559
9145
  const result = await invokeMcpTool({
@@ -8581,10 +9167,10 @@ function renderDevInvokeResult(args) {
8581
9167
  printJson({
8582
9168
  toolName: args.toolName,
8583
9169
  url: args.url,
8584
- ...invokeResultToJson(args.result)
9170
+ ...invokeResultToJson2(args.result)
8585
9171
  });
8586
9172
  if (!args.result.ok)
8587
- process.exit(1);
9173
+ throw new CliExitError(1);
8588
9174
  return;
8589
9175
  }
8590
9176
  if (!args.result.ok) {
@@ -8598,7 +9184,7 @@ function renderDevInvokeResult(args) {
8598
9184
  const statusLine = args.result.status > 0 ? `HTTP ${args.result.status}` : "network error";
8599
9185
  printError(`${statusLine} from local runtime: ${args.result.message}`);
8600
9186
  }
8601
- process.exit(1);
9187
+ throw new CliExitError(1);
8602
9188
  }
8603
9189
  if (args.raw) {
8604
9190
  printInfo(JSON.stringify(args.result.result, null, 2));
@@ -8612,7 +9198,7 @@ function renderDevInvokeResult(args) {
8612
9198
  printWarn(`Tool reported isError=true. Inspect ${c.bold("--json")} or ${c.bold("--raw")} for the full envelope.`);
8613
9199
  }
8614
9200
  }
8615
- function invokeResultToJson(result) {
9201
+ function invokeResultToJson2(result) {
8616
9202
  if (result.ok) {
8617
9203
  return { ok: true, durationMs: result.durationMs, result: result.result };
8618
9204
  }
@@ -8637,38 +9223,38 @@ function invokeResultToJson(result) {
8637
9223
  // src/lib/dev/inspector-store.ts
8638
9224
  import {
8639
9225
  appendFileSync as appendFileSync2,
8640
- existsSync as existsSync14,
8641
- mkdirSync as mkdirSync8,
8642
- readFileSync as readFileSync12,
9226
+ existsSync as existsSync15,
9227
+ mkdirSync as mkdirSync9,
9228
+ readFileSync as readFileSync13,
8643
9229
  readdirSync as readdirSync3,
8644
9230
  renameSync,
8645
9231
  statSync as statSync2
8646
9232
  } from "node:fs";
8647
- import { join as join12 } from "node:path";
9233
+ import { join as join13 } from "node:path";
8648
9234
  var INSPECTOR_DIRNAME = "inspector";
8649
9235
  var ACTIVE_FILE = "calls.ndjson";
8650
9236
  var ROTATED_PREFIX = "calls.";
8651
9237
  var ROTATED_SUFFIX = ".ndjson";
8652
9238
  var ROTATION_LIMIT_BYTES = 50 * 1024 * 1024;
8653
9239
  function inspectorDir(cwd) {
8654
- return join12(devRoot(cwd), INSPECTOR_DIRNAME);
9240
+ return join13(devRoot(cwd), INSPECTOR_DIRNAME);
8655
9241
  }
8656
9242
  function ensureInspectorDir(dir) {
8657
- if (!existsSync14(dir))
8658
- mkdirSync8(dir, { recursive: true });
9243
+ if (!existsSync15(dir))
9244
+ mkdirSync9(dir, { recursive: true });
8659
9245
  }
8660
9246
  function activeFilePath(dir) {
8661
- return join12(dir, ACTIVE_FILE);
9247
+ return join13(dir, ACTIVE_FILE);
8662
9248
  }
8663
9249
  function isRotatedName(name) {
8664
9250
  return name !== ACTIVE_FILE && name.startsWith(ROTATED_PREFIX) && name.endsWith(ROTATED_SUFFIX);
8665
9251
  }
8666
9252
  function listRotatedFiles(dir) {
8667
- if (!existsSync14(dir))
9253
+ if (!existsSync15(dir))
8668
9254
  return [];
8669
9255
  const rotated = readdirSync3(dir).filter(isRotatedName);
8670
9256
  rotated.sort();
8671
- return rotated.map((n) => join12(dir, n));
9257
+ return rotated.map((n) => join13(dir, n));
8672
9258
  }
8673
9259
  function parseLines(text) {
8674
9260
  if (!text)
@@ -8685,13 +9271,13 @@ function parseLines(text) {
8685
9271
  return out;
8686
9272
  }
8687
9273
  function readAll(dir) {
8688
- if (!existsSync14(dir))
9274
+ if (!existsSync15(dir))
8689
9275
  return [];
8690
9276
  const out = [];
8691
9277
  for (const path of [...listRotatedFiles(dir), activeFilePath(dir)]) {
8692
- if (!existsSync14(path))
9278
+ if (!existsSync15(path))
8693
9279
  continue;
8694
- out.push(...parseLines(readFileSync12(path, "utf-8")));
9280
+ out.push(...parseLines(readFileSync13(path, "utf-8")));
8695
9281
  }
8696
9282
  return out;
8697
9283
  }
@@ -8759,7 +9345,7 @@ async function runDevReplay(opts) {
8759
9345
  const baseUrl = opts.url ?? resolveBaseUrl(cwd);
8760
9346
  if (!opts.dryRun && !baseUrl) {
8761
9347
  printError("No running mcp dev session in this directory. Start `mcp dev`, or pass --url <baseUrl>.");
8762
- process.exit(1);
9348
+ throw new CliExitError(1);
8763
9349
  }
8764
9350
  const results = [];
8765
9351
  for (const record of records) {
@@ -8887,13 +9473,13 @@ function formatSummary(record) {
8887
9473
 
8888
9474
  // src/commands/dev-tail.ts
8889
9475
  import {
8890
- existsSync as existsSync15,
9476
+ existsSync as existsSync16,
8891
9477
  openSync,
8892
9478
  readSync,
8893
9479
  closeSync,
8894
9480
  statSync as statSync3
8895
9481
  } from "node:fs";
8896
- import { join as join13 } from "node:path";
9482
+ import { join as join14 } from "node:path";
8897
9483
  var DEFAULT_INTERVAL_MS = 250;
8898
9484
  var MAX_READ_CHUNK = 64 * 1024;
8899
9485
  function registerDevTailCommand(dev) {
@@ -8902,14 +9488,14 @@ function registerDevTailCommand(dev) {
8902
9488
  async function runDevTail(opts) {
8903
9489
  const cwd = process.cwd();
8904
9490
  const dir = inspectorDir(cwd);
8905
- const activePath = join13(dir, ACTIVE_FILE);
9491
+ const activePath = join14(dir, ACTIVE_FILE);
8906
9492
  const intervalMs = parseInterval(opts.intervalMs);
8907
- if (!existsSync15(dir)) {
9493
+ if (!existsSync16(dir)) {
8908
9494
  printInfo(`No inspector history yet at ${dir}. Run \`mcp dev\` to start capturing.`);
8909
- process.exit(0);
9495
+ throw new CliExitError(0);
8910
9496
  }
8911
9497
  let cursor = 0;
8912
- if (existsSync15(activePath)) {
9498
+ if (existsSync16(activePath)) {
8913
9499
  try {
8914
9500
  cursor = opts.fromStart ? 0 : statSync3(activePath).size;
8915
9501
  } catch {
@@ -8925,7 +9511,7 @@ async function runDevTail(opts) {
8925
9511
  process.on("SIGTERM", stop);
8926
9512
  while (!stopped) {
8927
9513
  let size = 0;
8928
- if (existsSync15(activePath)) {
9514
+ if (existsSync16(activePath)) {
8929
9515
  try {
8930
9516
  size = statSync3(activePath).size;
8931
9517
  } catch {
@@ -9010,7 +9596,7 @@ function emitLines(text, filter) {
9010
9596
  }
9011
9597
  }
9012
9598
  function sleep(ms) {
9013
- return new Promise((resolve6) => setTimeout(resolve6, ms));
9599
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
9014
9600
  }
9015
9601
 
9016
9602
  // ../../node_modules/.bun/@clack+core@1.3.1/node_modules/@clack/core/dist/index.mjs
@@ -10296,13 +10882,13 @@ async function runInteractiveBootstrap() {
10296
10882
 
10297
10883
  // src/lib/dev/bundle-sync.ts
10298
10884
  import {
10299
- existsSync as existsSync16,
10300
- mkdirSync as mkdirSync9,
10301
- readFileSync as readFileSync13,
10885
+ existsSync as existsSync17,
10886
+ mkdirSync as mkdirSync10,
10887
+ readFileSync as readFileSync14,
10302
10888
  rmSync as rmSync2,
10303
- writeFileSync as writeFileSync7
10889
+ writeFileSync as writeFileSync8
10304
10890
  } from "node:fs";
10305
- import { dirname as dirname5, join as join14 } from "node:path";
10891
+ import { dirname as dirname6, join as join15 } from "node:path";
10306
10892
  async function resolveServerProject(args) {
10307
10893
  const data = await api.get("/api/v1/server", {
10308
10894
  organizationId: args.organizationId,
@@ -10358,8 +10944,8 @@ async function fetchBundle(args) {
10358
10944
  return parsed;
10359
10945
  }
10360
10946
  function materializeBundle(destDir, bundle, options = {}) {
10361
- if (!existsSync16(destDir)) {
10362
- mkdirSync9(destDir, { recursive: true });
10947
+ if (!existsSync17(destDir)) {
10948
+ mkdirSync10(destDir, { recursive: true });
10363
10949
  }
10364
10950
  const preserve = options.preservePaths;
10365
10951
  const kept = new Set;
@@ -10374,9 +10960,9 @@ function materializeBundle(destDir, bundle, options = {}) {
10374
10960
  preservedCount += 1;
10375
10961
  continue;
10376
10962
  }
10377
- const target = join14(destDir, safePath);
10378
- mkdirSync9(dirname5(target), { recursive: true });
10379
- writeFileSync7(target, file.content, "utf-8");
10963
+ const target = join15(destDir, safePath);
10964
+ mkdirSync10(dirname6(target), { recursive: true });
10965
+ writeFileSync8(target, file.content, "utf-8");
10380
10966
  writtenCount += 1;
10381
10967
  }
10382
10968
  let removed = 0;
@@ -10404,7 +10990,7 @@ function pruneStaleFiles(rootDir, currentDir, kept) {
10404
10990
  const { relative: relative6 } = __require("node:path");
10405
10991
  let removed = 0;
10406
10992
  for (const entry of readdirSync4(currentDir)) {
10407
- const abs = join14(currentDir, entry);
10993
+ const abs = join15(currentDir, entry);
10408
10994
  const stat = statSync4(abs);
10409
10995
  if (stat.isDirectory()) {
10410
10996
  removed += pruneStaleFiles(rootDir, abs, kept);
@@ -10470,8 +11056,8 @@ function startGitPullPoll(args) {
10470
11056
 
10471
11057
  // src/lib/dev/editor.ts
10472
11058
  import { spawn as spawn4 } from "node:child_process";
10473
- import { existsSync as existsSync17, statSync as statSync4 } from "node:fs";
10474
- import { delimiter as delimiter2, join as join15 } from "node:path";
11059
+ import { existsSync as existsSync18, statSync as statSync4 } from "node:fs";
11060
+ import { delimiter as delimiter2, join as join16 } from "node:path";
10475
11061
  import { platform as platform3 } from "node:os";
10476
11062
  var TARGET_LABEL = {
10477
11063
  tools: "tool metadata",
@@ -10507,8 +11093,8 @@ function findOnPath2(command) {
10507
11093
  if (!dir)
10508
11094
  continue;
10509
11095
  for (const ext of exts) {
10510
- const candidate = join15(dir, command + ext);
10511
- if (existsSync17(candidate) && isExecutable(candidate))
11096
+ const candidate = join16(dir, command + ext);
11097
+ if (existsSync18(candidate) && isExecutable(candidate))
10512
11098
  return candidate;
10513
11099
  }
10514
11100
  }
@@ -10788,11 +11374,11 @@ function parseOpenChoice(raw) {
10788
11374
  }
10789
11375
 
10790
11376
  // src/lib/dev/file-watcher.ts
10791
- import { existsSync as existsSync18, statSync as statSync5, watch as fsWatch2 } from "node:fs";
10792
- import { join as join16 } from "node:path";
11377
+ import { existsSync as existsSync19, statSync as statSync5, watch as fsWatch2 } from "node:fs";
11378
+ import { join as join17 } from "node:path";
10793
11379
  var DEFAULT_IGNORE = ["node_modules", ".git", "_mcpsh_host.mjs"];
10794
11380
  function watchDir(options) {
10795
- if (!existsSync18(options.rootDir)) {
11381
+ if (!existsSync19(options.rootDir)) {
10796
11382
  throw new Error(`watchDir: directory does not exist: ${options.rootDir}`);
10797
11383
  }
10798
11384
  const ignore = new Set([...DEFAULT_IGNORE, ...options.ignore ?? []]);
@@ -10828,7 +11414,7 @@ function watchDir(options) {
10828
11414
  for (const entry of readdirSync4(options.rootDir)) {
10829
11415
  if (ignore.has(entry))
10830
11416
  continue;
10831
- const sub = join16(options.rootDir, entry);
11417
+ const sub = join17(options.rootDir, entry);
10832
11418
  try {
10833
11419
  const stat = statSync5(sub);
10834
11420
  if (!stat.isDirectory())
@@ -10876,10 +11462,10 @@ function isToolHandlerPath(rel) {
10876
11462
  }
10877
11463
 
10878
11464
  // src/lib/dev/tools-watcher.ts
10879
- import { existsSync as existsSync19 } from "node:fs";
11465
+ import { existsSync as existsSync20 } from "node:fs";
10880
11466
  function startToolsWatcher(args) {
10881
11467
  const rootDir = serverToolsDir(devRoot(args.cwd), args.serverId);
10882
- if (!existsSync19(rootDir))
11468
+ if (!existsSync20(rootDir))
10883
11469
  return null;
10884
11470
  let inflight = false;
10885
11471
  let pendingNames = new Set;
@@ -10994,10 +11580,10 @@ async function maybeHotReload(args) {
10994
11580
  }
10995
11581
 
10996
11582
  // src/lib/dev/handlers-watcher.ts
10997
- import { existsSync as existsSync20 } from "node:fs";
11583
+ import { existsSync as existsSync21 } from "node:fs";
10998
11584
  function startHandlersWatcher(args) {
10999
11585
  const rootDir = serverBundleHandlersDir(devRoot(args.cwd));
11000
- if (!existsSync20(rootDir))
11586
+ if (!existsSync21(rootDir))
11001
11587
  return null;
11002
11588
  let inflight = false;
11003
11589
  let pendingSlugs = new Set;
@@ -11094,8 +11680,8 @@ async function handleOne2(args) {
11094
11680
 
11095
11681
  // src/lib/dev/local-runtime.ts
11096
11682
  import { spawn as spawn5 } from "node:child_process";
11097
- import { existsSync as existsSync21 } from "node:fs";
11098
- import { join as join17 } from "node:path";
11683
+ import { existsSync as existsSync22 } from "node:fs";
11684
+ import { join as join18 } from "node:path";
11099
11685
  function detectRuntime(preferred = "auto") {
11100
11686
  if (preferred === "bun" || preferred === "node")
11101
11687
  return preferred;
@@ -11106,7 +11692,7 @@ function detectRuntime(preferred = "auto") {
11106
11692
  return "node";
11107
11693
  }
11108
11694
  function startRuntime(options) {
11109
- if (!existsSync21(options.hostScriptPath)) {
11695
+ if (!existsSync22(options.hostScriptPath)) {
11110
11696
  throw new Error(`Host script not found: ${options.hostScriptPath}`);
11111
11697
  }
11112
11698
  const runtime = detectRuntime(options.runtime);
@@ -11132,52 +11718,52 @@ function startRuntime(options) {
11132
11718
  if (child.exitCode !== null)
11133
11719
  return;
11134
11720
  child.kill("SIGTERM");
11135
- await new Promise((resolve6) => {
11721
+ await new Promise((resolve7) => {
11136
11722
  const timer = setTimeout(() => {
11137
11723
  if (child.exitCode === null)
11138
11724
  child.kill("SIGKILL");
11139
- resolve6();
11725
+ resolve7();
11140
11726
  }, 3000);
11141
11727
  child.once("exit", () => {
11142
11728
  clearTimeout(timer);
11143
- resolve6();
11729
+ resolve7();
11144
11730
  });
11145
11731
  });
11146
11732
  }
11147
11733
  };
11148
11734
  }
11149
11735
  async function installDependencies(options) {
11150
- const pkgPath = join17(options.serverDir, "package.json");
11151
- if (!existsSync21(pkgPath))
11736
+ const pkgPath = join18(options.serverDir, "package.json");
11737
+ if (!existsSync22(pkgPath))
11152
11738
  return { ran: false, exitCode: null };
11153
- const nodeModules = join17(options.serverDir, "node_modules");
11154
- if (options.skipIfPresent !== false && existsSync21(nodeModules)) {
11739
+ const nodeModules = join18(options.serverDir, "node_modules");
11740
+ if (options.skipIfPresent !== false && existsSync22(nodeModules)) {
11155
11741
  return { ran: false, exitCode: 0 };
11156
11742
  }
11157
11743
  options.onStep?.("Installing dependencies (bun install)…");
11158
- return await new Promise((resolve6) => {
11744
+ return await new Promise((resolve7) => {
11159
11745
  const child = spawn5("bun", ["install", "--silent"], {
11160
11746
  cwd: options.serverDir,
11161
11747
  stdio: "inherit"
11162
11748
  });
11163
- child.on("error", () => resolve6({ ran: true, exitCode: 127 }));
11164
- child.on("exit", (code) => resolve6({ ran: true, exitCode: code }));
11749
+ child.on("error", () => resolve7({ ran: true, exitCode: 127 }));
11750
+ child.on("exit", (code) => resolve7({ ran: true, exitCode: code }));
11165
11751
  });
11166
11752
  }
11167
11753
 
11168
11754
  // src/lib/dev/prepare.ts
11169
- import { existsSync as existsSync25, writeFileSync as writeFileSync11 } from "node:fs";
11170
- import { join as join21, relative as relative6 } from "node:path";
11755
+ import { existsSync as existsSync26, writeFileSync as writeFileSync12 } from "node:fs";
11756
+ import { join as join22, relative as relative6 } from "node:path";
11171
11757
 
11172
11758
  // src/lib/dev/git-clone.ts
11173
11759
  import { spawnSync } from "node:child_process";
11174
11760
  import {
11175
- existsSync as existsSync22,
11176
- mkdirSync as mkdirSync10,
11177
- readFileSync as readFileSync14,
11178
- writeFileSync as writeFileSync8
11761
+ existsSync as existsSync23,
11762
+ mkdirSync as mkdirSync11,
11763
+ readFileSync as readFileSync15,
11764
+ writeFileSync as writeFileSync9
11179
11765
  } from "node:fs";
11180
- import { dirname as dirname6, join as join18 } from "node:path";
11766
+ import { dirname as dirname7, join as join19 } from "node:path";
11181
11767
 
11182
11768
  class GitNotAvailableError extends Error {
11183
11769
  constructor() {
@@ -11250,21 +11836,21 @@ function isGitAvailable() {
11250
11836
  }
11251
11837
  }
11252
11838
  function applyLocalExcludes(repoDir) {
11253
- const excludePath = join18(repoDir, ".git", "info", "exclude");
11254
- const existing = existsSync22(excludePath) ? readFileSync14(excludePath, "utf-8") : "";
11839
+ const excludePath = join19(repoDir, ".git", "info", "exclude");
11840
+ const existing = existsSync23(excludePath) ? readFileSync15(excludePath, "utf-8") : "";
11255
11841
  const next = mergeLocalExcludes(existing);
11256
11842
  if (next === null)
11257
11843
  return;
11258
- if (!existsSync22(dirname6(excludePath))) {
11259
- mkdirSync10(dirname6(excludePath), { recursive: true });
11844
+ if (!existsSync23(dirname7(excludePath))) {
11845
+ mkdirSync11(dirname7(excludePath), { recursive: true });
11260
11846
  }
11261
- writeFileSync8(excludePath, next, "utf-8");
11847
+ writeFileSync9(excludePath, next, "utf-8");
11262
11848
  }
11263
11849
  function ensureClone(opts) {
11264
11850
  if (!isGitAvailable()) {
11265
11851
  throw new GitNotAvailableError;
11266
11852
  }
11267
- if (existsSync22(join18(opts.repoDir, ".git"))) {
11853
+ if (existsSync23(join19(opts.repoDir, ".git"))) {
11268
11854
  opts.onStep?.("Refreshing existing clone (git fetch)…");
11269
11855
  runGit(gitFetchArgs(opts.branch), opts.repoDir);
11270
11856
  applyLocalExcludes(opts.repoDir);
@@ -11280,12 +11866,12 @@ function ensureClone(opts) {
11280
11866
  }
11281
11867
 
11282
11868
  // src/lib/dev/host-runtime.ts
11283
- import { existsSync as existsSync24, mkdirSync as mkdirSync12, writeFileSync as writeFileSync10 } from "node:fs";
11284
- import { dirname as dirname7, join as join20 } from "node:path";
11869
+ import { existsSync as existsSync25, mkdirSync as mkdirSync13, writeFileSync as writeFileSync11 } from "node:fs";
11870
+ import { dirname as dirname8, join as join21 } from "node:path";
11285
11871
 
11286
11872
  // src/lib/dev/inspector-module.ts
11287
- import { existsSync as existsSync23, mkdirSync as mkdirSync11, writeFileSync as writeFileSync9 } from "node:fs";
11288
- import { join as join19 } from "node:path";
11873
+ import { existsSync as existsSync24, mkdirSync as mkdirSync12, writeFileSync as writeFileSync10 } from "node:fs";
11874
+ import { join as join20 } from "node:path";
11289
11875
 
11290
11876
  // src/lib/dev/inspector-assets.ts
11291
11877
  var INSPECTOR_HTML = `<!doctype html>
@@ -26420,10 +27006,10 @@ function buildInspectorModule() {
26420
27006
  `);
26421
27007
  }
26422
27008
  function writeInspectorModule(serverDir2) {
26423
- if (!existsSync23(serverDir2))
26424
- mkdirSync11(serverDir2, { recursive: true });
26425
- const target = join19(serverDir2, INSPECTOR_FILE_NAME);
26426
- writeFileSync9(target, buildInspectorModule(), "utf-8");
27009
+ if (!existsSync24(serverDir2))
27010
+ mkdirSync12(serverDir2, { recursive: true });
27011
+ const target = join20(serverDir2, INSPECTOR_FILE_NAME);
27012
+ writeFileSync10(target, buildInspectorModule(), "utf-8");
26427
27013
  return target;
26428
27014
  }
26429
27015
 
@@ -26602,11 +27188,11 @@ function buildHostScript(options) {
26602
27188
  `);
26603
27189
  }
26604
27190
  function writeHostScript(serverDir2, options) {
26605
- if (!existsSync24(serverDir2))
26606
- mkdirSync12(serverDir2, { recursive: true });
26607
- const target = join20(serverDir2, HOST_FILE_NAME);
26608
- mkdirSync12(dirname7(target), { recursive: true });
26609
- writeFileSync10(target, buildHostScript(options), "utf-8");
27191
+ if (!existsSync25(serverDir2))
27192
+ mkdirSync13(serverDir2, { recursive: true });
27193
+ const target = join21(serverDir2, HOST_FILE_NAME);
27194
+ mkdirSync13(dirname8(target), { recursive: true });
27195
+ writeFileSync11(target, buildHostScript(options), "utf-8");
26610
27196
  if (options.inspector) {
26611
27197
  writeInspectorModule(serverDir2);
26612
27198
  }
@@ -26635,7 +27221,7 @@ async function prepareDev(opts) {
26635
27221
  projectId = projectId ?? gitLink.projectId;
26636
27222
  serverName = `${gitLink.owner}/${gitLink.name}`;
26637
27223
  repoDir = gitCloneDir(cwd, gitLink.owner, gitLink.name);
26638
- dest = gitLink.pathPrefix ? join21(repoDir, gitLink.pathPrefix) : repoDir;
27224
+ dest = gitLink.pathPrefix ? join22(repoDir, gitLink.pathPrefix) : repoDir;
26639
27225
  printStep(`Git-linked server — cloning ${gitLink.owner}/${gitLink.name}@${gitLink.branch}…`);
26640
27226
  try {
26641
27227
  const result = ensureClone({
@@ -26655,8 +27241,8 @@ async function prepareDev(opts) {
26655
27241
  }
26656
27242
  process.exit(1);
26657
27243
  }
26658
- if (!existsSync25(env)) {
26659
- writeFileSync11(env, `{}
27244
+ if (!existsSync26(env)) {
27245
+ writeFileSync12(env, `{}
26660
27246
  `, "utf-8");
26661
27247
  printInfo(` → seeded ${relative6(cwd, env)} (empty; fill secrets here)`);
26662
27248
  }
@@ -26680,8 +27266,8 @@ async function prepareDev(opts) {
26680
27266
  });
26681
27267
  const result = materializeBundle(dest, bundle, { prune: true });
26682
27268
  printInfo(` → wrote ${result.filesWritten} files (pruned ${result.filesRemoved})`);
26683
- if (!existsSync25(env)) {
26684
- writeFileSync11(env, `{}
27269
+ if (!existsSync26(env)) {
27270
+ writeFileSync12(env, `{}
26685
27271
  `, "utf-8");
26686
27272
  printInfo(` → seeded ${relative6(cwd, env)} (empty; fill secrets here)`);
26687
27273
  }
@@ -26698,7 +27284,7 @@ async function prepareDev(opts) {
26698
27284
  });
26699
27285
  printInfo(` → wrote ${toolsResult.filesWritten} editable tool file${toolsResult.filesWritten === 1 ? "" : "s"}${toolsResult.filesRemoved > 0 ? ` (pruned ${toolsResult.filesRemoved})` : ""}`);
26700
27286
  } else {
26701
- if (!existsSync25(dest)) {
27287
+ if (!existsSync26(dest)) {
26702
27288
  printError("No cached bundle found. Run once without --offline first.");
26703
27289
  process.exit(1);
26704
27290
  }
@@ -26707,7 +27293,7 @@ async function prepareDev(opts) {
26707
27293
  process.exit(1);
26708
27294
  }
26709
27295
  }
26710
- const entryRel = ENTRY_CANDIDATES.find((c2) => existsSync25(join21(dest, c2)));
27296
+ const entryRel = ENTRY_CANDIDATES.find((c2) => existsSync26(join22(dest, c2)));
26711
27297
  if (!entryRel) {
26712
27298
  printError("Could not locate an entry file (expected src/worker.ts).");
26713
27299
  process.exit(1);
@@ -26865,73 +27451,73 @@ function createBurstGuard() {
26865
27451
  }
26866
27452
 
26867
27453
  // src/lib/dev/agent-connectors/claude-code.ts
26868
- import { existsSync as existsSync27, unlinkSync as unlinkSync2, writeFileSync as writeFileSync13 } from "node:fs";
27454
+ import { existsSync as existsSync28, unlinkSync as unlinkSync2, writeFileSync as writeFileSync14 } from "node:fs";
26869
27455
  import { homedir as homedir4 } from "node:os";
26870
- import { join as join23 } from "node:path";
27456
+ import { join as join24 } from "node:path";
26871
27457
 
26872
27458
  // src/lib/dev/agent-connectors/json-config-utils.ts
26873
- import { existsSync as existsSync26, mkdirSync as mkdirSync13, readFileSync as readFileSync15, writeFileSync as writeFileSync12 } from "node:fs";
26874
- import { dirname as dirname8, join as join22, basename } from "node:path";
27459
+ import { existsSync as existsSync27, mkdirSync as mkdirSync14, readFileSync as readFileSync16, writeFileSync as writeFileSync13 } from "node:fs";
27460
+ import { dirname as dirname9, join as join23, basename } from "node:path";
26875
27461
  function readJsonFile(path) {
26876
- if (!existsSync26(path))
27462
+ if (!existsSync27(path))
26877
27463
  return { ok: true, value: {} };
26878
27464
  try {
26879
- const raw = readFileSync15(path, "utf-8");
27465
+ const raw = readFileSync16(path, "utf-8");
26880
27466
  if (!raw.trim())
26881
27467
  return { ok: true, value: {} };
26882
27468
  return { ok: true, value: JSON.parse(raw) };
26883
27469
  } catch {
26884
27470
  try {
26885
- return { ok: false, raw: readFileSync15(path, "utf-8") };
27471
+ return { ok: false, raw: readFileSync16(path, "utf-8") };
26886
27472
  } catch {
26887
27473
  return { ok: false };
26888
27474
  }
26889
27475
  }
26890
27476
  }
26891
27477
  function atomicWriteJson(path, value) {
26892
- mkdirSync13(dirname8(path), { recursive: true });
27478
+ mkdirSync14(dirname9(path), { recursive: true });
26893
27479
  const tmp = `${path}.mcpsh-${process.pid}-${Date.now()}.tmp`;
26894
- writeFileSync12(tmp, JSON.stringify(value, null, 2) + `
27480
+ writeFileSync13(tmp, JSON.stringify(value, null, 2) + `
26895
27481
  `, "utf-8");
26896
27482
  const { renameSync: renameSync2 } = __require("node:fs");
26897
27483
  renameSync2(tmp, path);
26898
27484
  }
26899
27485
  function backupConfig(args) {
26900
- if (!existsSync26(args.configPath)) {
27486
+ if (!existsSync27(args.configPath)) {
26901
27487
  return { backupPath: null, existed: false };
26902
27488
  }
26903
- mkdirSync13(args.backupsDir, { recursive: true });
27489
+ mkdirSync14(args.backupsDir, { recursive: true });
26904
27490
  const filename = `${args.agentId}__${basename(args.configPath)}.bak`;
26905
- const backupPath = join22(args.backupsDir, filename);
26906
- if (!existsSync26(backupPath)) {
26907
- const raw = readFileSync15(args.configPath, "utf-8");
26908
- writeFileSync12(backupPath, raw, "utf-8");
27491
+ const backupPath = join23(args.backupsDir, filename);
27492
+ if (!existsSync27(backupPath)) {
27493
+ const raw = readFileSync16(args.configPath, "utf-8");
27494
+ writeFileSync13(backupPath, raw, "utf-8");
26909
27495
  }
26910
27496
  return { backupPath, existed: true };
26911
27497
  }
26912
27498
  function restoreFromBackup(args) {
26913
27499
  const filename = `${args.agentId}__${basename(args.configPath)}.bak`;
26914
- const backupPath = join22(args.backupsDir, filename);
26915
- if (!existsSync26(backupPath)) {
27500
+ const backupPath = join23(args.backupsDir, filename);
27501
+ if (!existsSync27(backupPath)) {
26916
27502
  return { restored: false };
26917
27503
  }
26918
- const raw = readFileSync15(backupPath, "utf-8");
26919
- mkdirSync13(dirname8(args.configPath), { recursive: true });
26920
- writeFileSync12(args.configPath, raw, "utf-8");
27504
+ const raw = readFileSync16(backupPath, "utf-8");
27505
+ mkdirSync14(dirname9(args.configPath), { recursive: true });
27506
+ writeFileSync13(args.configPath, raw, "utf-8");
26921
27507
  return { restored: true };
26922
27508
  }
26923
27509
 
26924
27510
  // src/lib/dev/agent-connectors/claude-code.ts
26925
27511
  function configPath() {
26926
- return join23(homedir4(), ".claude.json");
27512
+ return join24(homedir4(), ".claude.json");
26927
27513
  }
26928
27514
  function legacyConfigPath() {
26929
- return join23(homedir4(), ".claude", "mcp.json");
27515
+ return join24(homedir4(), ".claude", "mcp.json");
26930
27516
  }
26931
27517
  function resolveConfigPath() {
26932
- if (existsSync27(configPath()))
27518
+ if (existsSync28(configPath()))
26933
27519
  return configPath();
26934
- if (existsSync27(legacyConfigPath()))
27520
+ if (existsSync28(legacyConfigPath()))
26935
27521
  return legacyConfigPath();
26936
27522
  return configPath();
26937
27523
  }
@@ -26941,8 +27527,8 @@ var claudeCodeConnector = {
26941
27527
  hotkey: "c",
26942
27528
  describeLocation: () => resolveConfigPath(),
26943
27529
  async detect() {
26944
- const claudeDir = join23(homedir4(), ".claude");
26945
- if (existsSync27(configPath()) || existsSync27(legacyConfigPath()) || existsSync27(claudeDir)) {
27530
+ const claudeDir = join24(homedir4(), ".claude");
27531
+ if (existsSync28(configPath()) || existsSync28(legacyConfigPath()) || existsSync28(claudeDir)) {
26946
27532
  return { installed: true, note: "Found Claude Code config" };
26947
27533
  }
26948
27534
  return { installed: false };
@@ -26980,7 +27566,7 @@ var claudeCodeConnector = {
26980
27566
  backupsDir: args.backupsDir,
26981
27567
  agentId: this.id
26982
27568
  });
26983
- if (!restored.restored && existsSync27(path)) {
27569
+ if (!restored.restored && existsSync28(path)) {
26984
27570
  const after = readJsonFile(path);
26985
27571
  if (after.ok && after.value && typeof after.value === "object") {
26986
27572
  const root = after.value;
@@ -26990,7 +27576,7 @@ var claudeCodeConnector = {
26990
27576
  try {
26991
27577
  unlinkSync2(path);
26992
27578
  } catch {
26993
- writeFileSync13(path, `{}
27579
+ writeFileSync14(path, `{}
26994
27580
  `, "utf-8");
26995
27581
  }
26996
27582
  }
@@ -27002,16 +27588,16 @@ var claudeCodeConnector = {
27002
27588
 
27003
27589
  // src/lib/dev/agent-connectors/codex.ts
27004
27590
  import {
27005
- existsSync as existsSync28,
27006
- mkdirSync as mkdirSync14,
27007
- readFileSync as readFileSync16,
27591
+ existsSync as existsSync29,
27592
+ mkdirSync as mkdirSync15,
27593
+ readFileSync as readFileSync17,
27008
27594
  unlinkSync as unlinkSync3,
27009
- writeFileSync as writeFileSync14
27595
+ writeFileSync as writeFileSync15
27010
27596
  } from "node:fs";
27011
27597
  import { homedir as homedir5 } from "node:os";
27012
- import { dirname as dirname9, join as join24 } from "node:path";
27598
+ import { dirname as dirname10, join as join25 } from "node:path";
27013
27599
  function configPath2() {
27014
- return join24(homedir5(), ".codex", "config.toml");
27600
+ return join25(homedir5(), ".codex", "config.toml");
27015
27601
  }
27016
27602
  var SECTION_PREFIX = "mcp_servers.";
27017
27603
  function buildSection(name, url) {
@@ -27049,7 +27635,7 @@ var codexConnector = {
27049
27635
  hotkey: "x",
27050
27636
  describeLocation: () => configPath2(),
27051
27637
  async detect() {
27052
- if (existsSync28(join24(homedir5(), ".codex")) || existsSync28(configPath2())) {
27638
+ if (existsSync29(join25(homedir5(), ".codex")) || existsSync29(configPath2())) {
27053
27639
  return { installed: true, note: "Found ~/.codex/" };
27054
27640
  }
27055
27641
  return { installed: false };
@@ -27062,8 +27648,8 @@ var codexConnector = {
27062
27648
  agentId: this.id
27063
27649
  });
27064
27650
  let existing = "";
27065
- if (existsSync28(path)) {
27066
- existing = readFileSync16(path, "utf-8");
27651
+ if (existsSync29(path)) {
27652
+ existing = readFileSync17(path, "utf-8");
27067
27653
  }
27068
27654
  const conflict = findSectionRange(existing, args.name) !== null;
27069
27655
  let next = existing;
@@ -27078,18 +27664,18 @@ var codexConnector = {
27078
27664
  next += `
27079
27665
  `;
27080
27666
  next += buildSection(args.name, args.url);
27081
- mkdirSync14(dirname9(path), { recursive: true });
27082
- writeFileSync14(path, next, "utf-8");
27667
+ mkdirSync15(dirname10(path), { recursive: true });
27668
+ writeFileSync15(path, next, "utf-8");
27083
27669
  return { added: !conflict, conflict };
27084
27670
  },
27085
27671
  async remove(args) {
27086
27672
  const path = configPath2();
27087
- if (existsSync28(path)) {
27088
- const existing = readFileSync16(path, "utf-8");
27673
+ if (existsSync29(path)) {
27674
+ const existing = readFileSync17(path, "utf-8");
27089
27675
  const range = findSectionRange(existing, args.name);
27090
27676
  if (range) {
27091
27677
  const next = existing.slice(0, range.start) + existing.slice(range.end);
27092
- writeFileSync14(path, next, "utf-8");
27678
+ writeFileSync15(path, next, "utf-8");
27093
27679
  }
27094
27680
  }
27095
27681
  const restored = restoreFromBackup({
@@ -27097,8 +27683,8 @@ var codexConnector = {
27097
27683
  backupsDir: args.backupsDir,
27098
27684
  agentId: this.id
27099
27685
  });
27100
- if (!restored.restored && existsSync28(path)) {
27101
- const after = readFileSync16(path, "utf-8");
27686
+ if (!restored.restored && existsSync29(path)) {
27687
+ const after = readFileSync17(path, "utf-8");
27102
27688
  if (!after.trim()) {
27103
27689
  try {
27104
27690
  unlinkSync3(path);
@@ -27110,11 +27696,11 @@ var codexConnector = {
27110
27696
  };
27111
27697
 
27112
27698
  // src/lib/dev/agent-connectors/continue.ts
27113
- import { existsSync as existsSync29, unlinkSync as unlinkSync4, writeFileSync as writeFileSync15 } from "node:fs";
27699
+ import { existsSync as existsSync30, unlinkSync as unlinkSync4, writeFileSync as writeFileSync16 } from "node:fs";
27114
27700
  import { homedir as homedir6 } from "node:os";
27115
- import { join as join25 } from "node:path";
27701
+ import { join as join26 } from "node:path";
27116
27702
  function configPath3() {
27117
- return join25(homedir6(), ".continue", "config.json");
27703
+ return join26(homedir6(), ".continue", "config.json");
27118
27704
  }
27119
27705
  function isContinueServerEntry(value) {
27120
27706
  return Boolean(value && typeof value === "object" && typeof value.name === "string");
@@ -27125,8 +27711,8 @@ var continueConnector = {
27125
27711
  hotkey: "n",
27126
27712
  describeLocation: () => configPath3(),
27127
27713
  async detect() {
27128
- const dir = join25(homedir6(), ".continue");
27129
- if (existsSync29(dir) || existsSync29(configPath3())) {
27714
+ const dir = join26(homedir6(), ".continue");
27715
+ if (existsSync30(dir) || existsSync30(configPath3())) {
27130
27716
  return { installed: true, note: "Found ~/.continue/" };
27131
27717
  }
27132
27718
  return { installed: false };
@@ -27164,7 +27750,7 @@ var continueConnector = {
27164
27750
  backupsDir: args.backupsDir,
27165
27751
  agentId: this.id
27166
27752
  });
27167
- if (!restored.restored && existsSync29(path)) {
27753
+ if (!restored.restored && existsSync30(path)) {
27168
27754
  const after = readJsonFile(path);
27169
27755
  if (after.ok && after.value && typeof after.value === "object") {
27170
27756
  const root = after.value;
@@ -27174,7 +27760,7 @@ var continueConnector = {
27174
27760
  try {
27175
27761
  unlinkSync4(path);
27176
27762
  } catch {
27177
- writeFileSync15(path, `{}
27763
+ writeFileSync16(path, `{}
27178
27764
  `, "utf-8");
27179
27765
  }
27180
27766
  }
@@ -27185,11 +27771,11 @@ var continueConnector = {
27185
27771
  };
27186
27772
 
27187
27773
  // src/lib/dev/agent-connectors/cursor.ts
27188
- import { existsSync as existsSync30, unlinkSync as unlinkSync5, writeFileSync as writeFileSync16 } from "node:fs";
27774
+ import { existsSync as existsSync31, unlinkSync as unlinkSync5, writeFileSync as writeFileSync17 } from "node:fs";
27189
27775
  import { homedir as homedir7 } from "node:os";
27190
- import { join as join26 } from "node:path";
27776
+ import { join as join27 } from "node:path";
27191
27777
  function globalConfigPath() {
27192
- return join26(homedir7(), ".cursor", "mcp.json");
27778
+ return join27(homedir7(), ".cursor", "mcp.json");
27193
27779
  }
27194
27780
  var cursorConnector = {
27195
27781
  id: "cursor",
@@ -27197,9 +27783,9 @@ var cursorConnector = {
27197
27783
  hotkey: "u",
27198
27784
  describeLocation: () => globalConfigPath(),
27199
27785
  async detect() {
27200
- const cursorDir = join26(homedir7(), ".cursor");
27201
- const macAppSupport = join26(homedir7(), "Library", "Application Support", "Cursor");
27202
- if (existsSync30(cursorDir) || existsSync30(macAppSupport)) {
27786
+ const cursorDir = join27(homedir7(), ".cursor");
27787
+ const macAppSupport = join27(homedir7(), "Library", "Application Support", "Cursor");
27788
+ if (existsSync31(cursorDir) || existsSync31(macAppSupport)) {
27203
27789
  return { installed: true, note: "Found Cursor config dir" };
27204
27790
  }
27205
27791
  return { installed: false };
@@ -27237,7 +27823,7 @@ var cursorConnector = {
27237
27823
  backupsDir: args.backupsDir,
27238
27824
  agentId: this.id
27239
27825
  });
27240
- if (!restored.restored && existsSync30(path)) {
27826
+ if (!restored.restored && existsSync31(path)) {
27241
27827
  const after = readJsonFile(path);
27242
27828
  if (after.ok && after.value && typeof after.value === "object") {
27243
27829
  const root = after.value;
@@ -27247,7 +27833,7 @@ var cursorConnector = {
27247
27833
  try {
27248
27834
  unlinkSync5(path);
27249
27835
  } catch {
27250
- writeFileSync16(path, `{}
27836
+ writeFileSync17(path, `{}
27251
27837
  `, "utf-8");
27252
27838
  }
27253
27839
  }
@@ -27258,30 +27844,30 @@ var cursorConnector = {
27258
27844
  };
27259
27845
 
27260
27846
  // src/lib/dev/agent-connectors/vscode-copilot.ts
27261
- import { existsSync as existsSync31, unlinkSync as unlinkSync6, writeFileSync as writeFileSync17 } from "node:fs";
27847
+ import { existsSync as existsSync32, unlinkSync as unlinkSync6, writeFileSync as writeFileSync18 } from "node:fs";
27262
27848
  import { homedir as homedir8, platform as platform4 } from "node:os";
27263
- import { join as join27, resolve as resolve6 } from "node:path";
27849
+ import { join as join28, resolve as resolve7 } from "node:path";
27264
27850
  function userLevelConfigPath() {
27265
27851
  const home = homedir8();
27266
27852
  const p2 = platform4();
27267
27853
  if (p2 === "darwin")
27268
- return join27(home, "Library", "Application Support", "Code", "User", "mcp.json");
27854
+ return join28(home, "Library", "Application Support", "Code", "User", "mcp.json");
27269
27855
  if (p2 === "win32") {
27270
- const appData = process.env["APPDATA"] ?? join27(home, "AppData", "Roaming");
27271
- return join27(appData, "Code", "User", "mcp.json");
27856
+ const appData = process.env["APPDATA"] ?? join28(home, "AppData", "Roaming");
27857
+ return join28(appData, "Code", "User", "mcp.json");
27272
27858
  }
27273
- const xdg = process.env["XDG_CONFIG_HOME"] ?? join27(home, ".config");
27274
- return join27(xdg, "Code", "User", "mcp.json");
27859
+ const xdg = process.env["XDG_CONFIG_HOME"] ?? join28(home, ".config");
27860
+ return join28(xdg, "Code", "User", "mcp.json");
27275
27861
  }
27276
27862
  function isHomeDir(cwd) {
27277
- return resolve6(cwd) === resolve6(homedir8());
27863
+ return resolve7(cwd) === resolve7(homedir8());
27278
27864
  }
27279
27865
  function hasWorkspaceVscode(cwd) {
27280
- return existsSync31(join27(cwd, ".vscode"));
27866
+ return existsSync32(join28(cwd, ".vscode"));
27281
27867
  }
27282
27868
  function configPath4(cwd) {
27283
27869
  if (!isHomeDir(cwd) && hasWorkspaceVscode(cwd)) {
27284
- return join27(cwd, ".vscode", "mcp.json");
27870
+ return join28(cwd, ".vscode", "mcp.json");
27285
27871
  }
27286
27872
  return userLevelConfigPath();
27287
27873
  }
@@ -27295,7 +27881,7 @@ var vscodeCopilotConnector = {
27295
27881
  if (!isHomeDir(here) && hasWorkspaceVscode(here)) {
27296
27882
  return { installed: true, note: "Found .vscode/ in current dir" };
27297
27883
  }
27298
- if (existsSync31(userLevelConfigPath().replace(/mcp\.json$/, ""))) {
27884
+ if (existsSync32(userLevelConfigPath().replace(/mcp\.json$/, ""))) {
27299
27885
  return { installed: true, note: "Found VS Code user profile" };
27300
27886
  }
27301
27887
  return { installed: false };
@@ -27333,7 +27919,7 @@ var vscodeCopilotConnector = {
27333
27919
  backupsDir: args.backupsDir,
27334
27920
  agentId: this.id
27335
27921
  });
27336
- if (!restored.restored && existsSync31(path)) {
27922
+ if (!restored.restored && existsSync32(path)) {
27337
27923
  const after = readJsonFile(path);
27338
27924
  if (after.ok && after.value && typeof after.value === "object") {
27339
27925
  const root = after.value;
@@ -27343,7 +27929,7 @@ var vscodeCopilotConnector = {
27343
27929
  try {
27344
27930
  unlinkSync6(path);
27345
27931
  } catch {
27346
- writeFileSync17(path, `{}
27932
+ writeFileSync18(path, `{}
27347
27933
  `, "utf-8");
27348
27934
  }
27349
27935
  }
@@ -27487,7 +28073,7 @@ function parseGraceMs(value, fallbackMs) {
27487
28073
  return Math.round(n * 1000);
27488
28074
  }
27489
28075
  function resolveSpecPath(cwd, raw) {
27490
- return isAbsolute3(raw) ? raw : resolve7(cwd, raw);
28076
+ return isAbsolute3(raw) ? raw : resolve8(cwd, raw);
27491
28077
  }
27492
28078
  function buildConnectionName(serverId) {
27493
28079
  const slug = serverId.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 24) || "mcp-server";
@@ -27646,7 +28232,7 @@ async function runDev(opts) {
27646
28232
  let specPath = null;
27647
28233
  if (opts.spec) {
27648
28234
  const resolved = resolveSpecPath(cwd, opts.spec);
27649
- if (!existsSync32(resolved)) {
28235
+ if (!existsSync33(resolved)) {
27650
28236
  printError(`Spec file not found: ${resolved}`);
27651
28237
  process.exit(1);
27652
28238
  }
@@ -28407,7 +28993,7 @@ function registerDeploymentCommands(program2) {
28407
28993
  const ok = await confirmRollback(deploymentId);
28408
28994
  if (!ok) {
28409
28995
  printInfo(c.dim("Aborted."));
28410
- process.exit(1);
28996
+ throw new CliExitError(1);
28411
28997
  }
28412
28998
  }
28413
28999
  const orgId = await resolveOrgId(opts.org);
@@ -28416,20 +29002,57 @@ function registerDeploymentCommands(program2) {
28416
29002
  printJson(data);
28417
29003
  return;
28418
29004
  }
28419
- const d = data.deployment;
28420
- printSuccess(`Deployment ${d.deploymentId} rolled back to active.`);
29005
+ const d = data.deployment;
29006
+ printSuccess(`Deployment ${d.deploymentId} rolled back to active.`);
29007
+ printKeyValue({
29008
+ "deployment id": d.deploymentId,
29009
+ server: d.serverId,
29010
+ "previous status": d.previousStatus,
29011
+ status: d.status,
29012
+ "access mode": d.accessMode,
29013
+ url: d.deploymentUrl ?? "—",
29014
+ target: d.target,
29015
+ version: d.version,
29016
+ demoted: d.demotedDeploymentId ?? "—",
29017
+ "rolled back at": formatDate(d.rolledBackAt)
29018
+ });
29019
+ }));
29020
+ deployments.command("health <deploymentId>").description("Show a deployment's last health-check result (status, HTTP code, when it was checked)").option("--org <organizationId>", "Organization ID").addHelpText("after", [
29021
+ "",
29022
+ "Exit codes:",
29023
+ " 0 the deployment reported a healthy status",
29024
+ " 1 the deployment is unhealthy, unknown, or not found",
29025
+ "",
29026
+ "Examples:",
29027
+ " $ mcp deployments health dep_123",
29028
+ ' $ mcp --quiet deployments health dep_123 && echo "deploy is green"',
29029
+ " $ mcp --json deployments health dep_123 | jq '.health.status'"
29030
+ ].join(`
29031
+ `)).action(runAction(async (deploymentId, opts) => {
29032
+ const orgId = await resolveOrgId(opts.org);
29033
+ const data = await api.get("/api/v1/deployment/health", { organizationId: orgId, deploymentId });
29034
+ const isHealthy = data.health.status === "healthy";
29035
+ if (isQuiet()) {
29036
+ if (!isHealthy)
29037
+ throw new CliExitError(1);
29038
+ return;
29039
+ }
29040
+ if (isJsonMode()) {
29041
+ printJson(data);
29042
+ if (!isHealthy)
29043
+ throw new CliExitError(1);
29044
+ return;
29045
+ }
28421
29046
  printKeyValue({
28422
- "deployment id": d.deploymentId,
28423
- server: d.serverId,
28424
- "previous status": d.previousStatus,
28425
- status: d.status,
28426
- "access mode": d.accessMode,
28427
- url: d.deploymentUrl ?? "—",
28428
- target: d.target,
28429
- version: d.version,
28430
- demoted: d.demotedDeploymentId ?? "—",
28431
- "rolled back at": formatDate(d.rolledBackAt)
29047
+ deployment: `${data.deployment.serverName} (${data.deployment.id})`,
29048
+ status: data.health.status,
29049
+ "http status": data.health.statusCode ?? "—",
29050
+ "checked at": data.health.checkedAt ? formatDate(data.health.checkedAt) : "—",
29051
+ "health error": data.health.error ?? "—",
29052
+ "deploy error": data.health.deploymentError ?? "—"
28432
29053
  });
29054
+ if (!isHealthy)
29055
+ throw new CliExitError(1);
28433
29056
  }));
28434
29057
  deployments.command("logs <deploymentId>").description("Show recent deployment events; pass --follow to tail new ones as they arrive").option("--org <organizationId>", "Organization ID").option("--limit <n>", "Number of recent events on the initial read (default 50)", parsePositiveIntOption("limit"), "50").option("--follow", "Tail new events as they arrive (long-poll). Ctrl-C to stop.").option("--since <when>", "Only show events after this point. Accepts relative (5m, 1h, 30s), Unix-ms, or ISO-8601.").option("--follow-timeout <seconds>", "Maximum seconds to keep --follow open before exiting (default: 1 hour).", "3600").addHelpText("after", [
28435
29058
  "",
@@ -28462,13 +29085,13 @@ async function confirmRollback(deploymentId) {
28462
29085
  }
28463
29086
 
28464
29087
  // src/commands/doctor.ts
28465
- import { existsSync as existsSync34, statSync as statSync6, readFileSync as readFileSync18 } from "node:fs";
29088
+ import { existsSync as existsSync35, statSync as statSync6, readFileSync as readFileSync19 } from "node:fs";
28466
29089
  import { homedir as homedir9, platform as platform6 } from "node:os";
28467
- import { join as join29, delimiter as delimiter3 } from "node:path";
29090
+ import { join as join30, delimiter as delimiter3 } from "node:path";
28468
29091
 
28469
29092
  // src/lib/version-check.ts
28470
- import { existsSync as existsSync33, mkdirSync as mkdirSync15, readFileSync as readFileSync17, writeFileSync as writeFileSync18 } from "node:fs";
28471
- import { join as join28 } from "node:path";
29093
+ import { existsSync as existsSync34, mkdirSync as mkdirSync16, readFileSync as readFileSync18, writeFileSync as writeFileSync19 } from "node:fs";
29094
+ import { join as join29 } from "node:path";
28472
29095
  var CACHE_TTL_MS = 24 * 60 * 60 * 1000;
28473
29096
  var FETCH_TIMEOUT_MS2 = 2000;
28474
29097
  var REGISTRY_URL = "https://registry.npmjs.org/@mcpcloud/cli/latest";
@@ -28476,13 +29099,13 @@ function cacheDir() {
28476
29099
  return configDir();
28477
29100
  }
28478
29101
  function cacheFile() {
28479
- return join28(cacheDir(), "version-check.json");
29102
+ return join29(cacheDir(), "version-check.json");
28480
29103
  }
28481
29104
  function readCache() {
28482
- if (!existsSync33(cacheFile()))
29105
+ if (!existsSync34(cacheFile()))
28483
29106
  return null;
28484
29107
  try {
28485
- const raw = JSON.parse(readFileSync17(cacheFile(), "utf-8"));
29108
+ const raw = JSON.parse(readFileSync18(cacheFile(), "utf-8"));
28486
29109
  if (typeof raw.latest !== "string" || typeof raw.fetchedAt !== "number")
28487
29110
  return null;
28488
29111
  return { latest: raw.latest, fetchedAt: raw.fetchedAt };
@@ -28492,9 +29115,9 @@ function readCache() {
28492
29115
  }
28493
29116
  function writeCache(entry) {
28494
29117
  try {
28495
- if (!existsSync33(cacheDir()))
28496
- mkdirSync15(cacheDir(), { recursive: true, mode: 448 });
28497
- writeFileSync18(cacheFile(), JSON.stringify(entry, null, 2), { mode: 384 });
29118
+ if (!existsSync34(cacheDir()))
29119
+ mkdirSync16(cacheDir(), { recursive: true, mode: 448 });
29120
+ writeFileSync19(cacheFile(), JSON.stringify(entry, null, 2), { mode: 384 });
28498
29121
  } catch {}
28499
29122
  }
28500
29123
  function compareVersions(a, b) {
@@ -28605,8 +29228,8 @@ function checkNode() {
28605
29228
  }
28606
29229
  function checkConfigFile() {
28607
29230
  const t0 = Date.now();
28608
- const path = join29(homedir9(), ".mcpcloud", "config.json");
28609
- if (!existsSync34(path)) {
29231
+ const path = join30(homedir9(), ".mcpcloud", "config.json");
29232
+ if (!existsSync35(path)) {
28610
29233
  return {
28611
29234
  name: "Config file",
28612
29235
  status: "warn",
@@ -28628,7 +29251,7 @@ function checkConfigFile() {
28628
29251
  } catch {}
28629
29252
  }
28630
29253
  try {
28631
- JSON.parse(readFileSync18(path, "utf-8"));
29254
+ JSON.parse(readFileSync19(path, "utf-8"));
28632
29255
  } catch (err) {
28633
29256
  return {
28634
29257
  name: "Config file",
@@ -28763,8 +29386,8 @@ function checkClaudeCli() {
28763
29386
  for (const dir of PATH.split(delimiter3)) {
28764
29387
  if (!dir)
28765
29388
  continue;
28766
- const candidate = join29(dir, exe);
28767
- if (existsSync34(candidate)) {
29389
+ const candidate = join30(dir, exe);
29390
+ if (existsSync35(candidate)) {
28768
29391
  return {
28769
29392
  name: "claude CLI",
28770
29393
  status: "pass",
@@ -29099,14 +29722,14 @@ function registerHelpCommand(program2) {
29099
29722
  // src/commands/init.ts
29100
29723
  import { createInterface as createInterface2 } from "node:readline";
29101
29724
  function prompt2(question) {
29102
- if (isCiMode()) {
29725
+ if (isNonInteractive()) {
29103
29726
  throw new Error("Interactive prompts are disabled in CI mode. Pass --org / --project / --name explicitly or run `mcp init` outside CI.");
29104
29727
  }
29105
29728
  const rl = createInterface2({ input: process.stdin, output: process.stderr });
29106
- return new Promise((resolve8) => {
29729
+ return new Promise((resolve9) => {
29107
29730
  rl.question(question, (answer) => {
29108
29731
  rl.close();
29109
- resolve8(answer.trim());
29732
+ resolve9(answer.trim());
29110
29733
  });
29111
29734
  });
29112
29735
  }
@@ -29117,7 +29740,7 @@ async function pickProject(orgId, override) {
29117
29740
  }
29118
29741
  const list = await api.get("/api/v1/projects", { organizationId: orgId, limit: "25" });
29119
29742
  if (list.projects.length === 0) {
29120
- if (isCiMode()) {
29743
+ if (isNonInteractive()) {
29121
29744
  throw new Error("Organization has no projects yet. Pass --project <id> to reuse an existing one or run outside CI to create one.");
29122
29745
  }
29123
29746
  printInfo(c.dim("No projects yet — creating one."));
@@ -29127,7 +29750,7 @@ async function pickProject(orgId, override) {
29127
29750
  const created = await api.post("/api/v1/projects", { organizationId: orgId, name });
29128
29751
  return { project: created.project, created: true };
29129
29752
  }
29130
- if (isCiMode()) {
29753
+ if (isNonInteractive()) {
29131
29754
  throw new Error("Multiple projects found. Pass --project <id> to pick one in CI mode.");
29132
29755
  }
29133
29756
  printInfo("");
@@ -29157,7 +29780,7 @@ async function pickServerName(override) {
29157
29780
  throw new Error("--name must not be empty.");
29158
29781
  return trimmed;
29159
29782
  }
29160
- if (isCiMode()) {
29783
+ if (isNonInteractive()) {
29161
29784
  throw new Error("Interactive prompts are disabled in CI mode. Pass --name <serverName>.");
29162
29785
  }
29163
29786
  const name = await prompt2("Server name: ");
@@ -29166,27 +29789,25 @@ async function pickServerName(override) {
29166
29789
  return name;
29167
29790
  }
29168
29791
  function registerInitCommand(program2) {
29169
- program2.command("init").description("Guided onboarding: pick org → pick/create project → create empty server skeleton").option("--org <organizationId>", "Organization ID").option("--project <projectId>", "Reuse an existing project instead of creating one").option("--name <serverName>", "Name for the new server").option("--non-interactive", "Fail instead of prompting for missing values (also implied by CI mode)").addHelpText("after", [
29170
- "",
29171
- "What v0 does:",
29172
- " 1. Resolves the target organization (--org or default).",
29173
- " 2. Lets you pick or create a project.",
29174
- " 3. Creates an empty server skeleton in that project.",
29792
+ program2.command("init").description("Guided onboarding: pick org → pick/create project → create a server (empty skeleton, or generated from an OpenAPI/GraphQL spec via --from-spec)").option("--org <organizationId>", "Organization ID").option("--project <projectId>", "Reuse an existing project instead of creating one").option("--name <serverName>", "Name for the new server (skeleton path only)").option("--from-spec <file|url>", "Build the server from an OpenAPI/GraphQL spec: a local file, http(s) URL (fetched server-side), or @- for stdin").option("--source-type <type>", `Override spec classification: ${SOURCE_TYPES.join(" | ")} (auto-detected by default)`).option("--deploy", "After importing the spec, deploy the server").option("--wait", "With --deploy, follow events until the deploy is terminal").option("--wait-timeout <seconds>", "Max seconds to wait for a terminal deploy state (default 300)", "300").addHelpText("after", [
29175
29793
  "",
29176
- "Not yet wired (B1 backend):",
29177
- " Source-material import (OpenAPI/GraphQL spec) and AI tool enrichment.",
29178
- " Once `POST /api/v1/server/{ingest,enrich,generate}` ship, this command",
29179
- " will gain --from-spec <url|path> for the full guided flow.",
29794
+ "Two modes:",
29795
+ " Skeleton (default): resolve org → pick/create project create an empty server.",
29796
+ " Spec (--from-spec): resolve org → pick/create project → ingest the spec into a",
29797
+ " generated server, then optionally --deploy [--wait].",
29180
29798
  "",
29181
29799
  "Examples:",
29182
29800
  " $ mcp init",
29183
- ' $ mcp init --org org_acme --project proj_123 --name "Stripe MCP"'
29801
+ ' $ mcp init --org org_acme --project proj_123 --name "Stripe MCP"',
29802
+ " $ mcp init --from-spec ./openapi.yaml --project proj_123",
29803
+ " $ mcp init --from-spec https://api.example.com/openapi.json --project proj_123 --deploy --wait"
29184
29804
  ].join(`
29185
29805
  `)).action(runAction(async (opts) => {
29186
- if (opts.nonInteractive) {
29187
- setCiMode(true);
29188
- }
29189
29806
  const orgId = await resolveOrgId(opts.org);
29807
+ if (opts.fromSpec) {
29808
+ await runSpecInit({ orgId, opts });
29809
+ return;
29810
+ }
29190
29811
  if (!isJsonMode()) {
29191
29812
  const orgs = await api.get("/api/v1/organizations");
29192
29813
  const org = orgs.organizations.find((o) => o.id === orgId);
@@ -29222,12 +29843,103 @@ function registerInitCommand(program2) {
29222
29843
  printSuccess(`Server ${c.bold(server.id)} ready.`);
29223
29844
  printInfo("");
29224
29845
  printInfo("Next steps:");
29225
- printInfo(` 1. Import an API spec currently via the dashboard's "Import spec" UI.`);
29226
- printInfo(` ${c.dim("(CLI ingest is backend-blocked; coming with B1)")}`);
29846
+ printInfo(` 1. Import an API spec: \`mcp servers ingest --project ${project.id} --spec ./openapi.yaml\``);
29847
+ printInfo(` ${c.dim("(or re-run `mcp init --from-spec ./openapi.yaml` for the guided flow)")}`);
29227
29848
  printInfo(` 2. Run \`mcp dev --spec ./openapi.yaml\` to iterate locally.`);
29228
29849
  printInfo(` 3. \`mcp servers deploy ${server.id} --wait\` to ship it.`);
29229
29850
  }));
29230
29851
  }
29852
+ function validateSourceType2(value) {
29853
+ if (value === undefined)
29854
+ return;
29855
+ const v2 = value.trim().toLowerCase();
29856
+ if (!SOURCE_TYPES.includes(v2)) {
29857
+ throw new Error(`Invalid --source-type: ${value}. Expected one of: ${SOURCE_TYPES.join(", ")}.`);
29858
+ }
29859
+ return v2;
29860
+ }
29861
+ async function runSpecInit(args) {
29862
+ const { orgId, opts } = args;
29863
+ const sourceType = validateSourceType2(opts.sourceType);
29864
+ const payload = await readSpecSource(opts.fromSpec, sourceType);
29865
+ const { project, created: projectCreated } = await pickProject(orgId, opts.project);
29866
+ if (!isJsonMode() && projectCreated) {
29867
+ printSuccess(`Project ${c.bold(project.name)} created (${project.id}).`);
29868
+ }
29869
+ if (!isJsonMode()) {
29870
+ printStep(`Importing ${c.bold(payload.sourceType)} spec into ${c.bold(project.name)}…`);
29871
+ }
29872
+ const ingest = await api.post("/api/v1/server/ingest", buildIngestBody({
29873
+ organizationId: orgId,
29874
+ projectId: project.id,
29875
+ payload
29876
+ }));
29877
+ const server = ingest.server;
29878
+ if (!isJsonMode()) {
29879
+ printSuccess(`Server ${c.bold(server.id)} created from spec.`);
29880
+ }
29881
+ let deployResult = null;
29882
+ if (opts.deploy) {
29883
+ if (!isJsonMode())
29884
+ printStep(`Deploying ${c.bold(server.id)}…`);
29885
+ const deployData = await api.post("/api/v1/server/deploy", {
29886
+ organizationId: orgId,
29887
+ serverId: server.id
29888
+ });
29889
+ let waitDone = !opts.wait;
29890
+ let terminalEvent = null;
29891
+ if (opts.wait) {
29892
+ const totalTimeoutMs = Math.max(10, Number.parseInt(opts.waitTimeout, 10) || 300) * 1000;
29893
+ const followed = await followDeploymentEvents({
29894
+ organizationId: orgId,
29895
+ deploymentId: deployData.deployment.deploymentId,
29896
+ totalTimeoutMs,
29897
+ onEvent: (event) => {
29898
+ if (!isJsonMode())
29899
+ printInfo(formatEventLine(event));
29900
+ }
29901
+ });
29902
+ waitDone = followed.done;
29903
+ terminalEvent = followed.terminalEvent;
29904
+ }
29905
+ deployResult = { deployment: deployData.deployment, waitDone, terminalEvent };
29906
+ }
29907
+ if (isJsonMode()) {
29908
+ printJson({
29909
+ organizationId: orgId,
29910
+ project,
29911
+ projectCreated,
29912
+ apiSource: ingest.apiSource,
29913
+ server,
29914
+ deploy: deployResult
29915
+ });
29916
+ if (deployResult && opts.wait && !deployResult.waitDone)
29917
+ throw new CliExitError(1);
29918
+ return;
29919
+ }
29920
+ if (deployResult) {
29921
+ const dep = deployResult.deployment;
29922
+ printSuccess(`Deployment ${c.bold(dep.deploymentId)} created.`);
29923
+ printKeyValue({
29924
+ "server id": server.id,
29925
+ "deployment id": dep.deploymentId,
29926
+ url: dep.deploymentUrl ?? "—",
29927
+ target: dep.target,
29928
+ "access mode": dep.accessMode,
29929
+ status: dep.status
29930
+ });
29931
+ if (opts.wait && !deployResult.waitDone) {
29932
+ printError(`Deploy did not reach a terminal state in time. Watch with \`mcp deployments logs ${dep.deploymentId} --follow\`.`);
29933
+ throw new CliExitError(1);
29934
+ }
29935
+ printStep(`Try it: \`mcp invoke ${server.id} <tool>\`.`);
29936
+ return;
29937
+ }
29938
+ printInfo("");
29939
+ printInfo("Next steps:");
29940
+ printStep(`Generate the bundle: \`mcp servers generate ${server.id} --out ./build\``);
29941
+ printStep(`Deploy it: \`mcp servers deploy ${server.id} --wait\``);
29942
+ }
29231
29943
 
29232
29944
  // src/commands/installation.ts
29233
29945
  function registerInstallationCommands(program2) {
@@ -29369,7 +30081,7 @@ function mapApiError2(err, context) {
29369
30081
  }
29370
30082
 
29371
30083
  // src/commands/invoke.ts
29372
- function parseTimeoutMs2(value, fallbackMs) {
30084
+ function parseTimeoutMs3(value, fallbackMs) {
29373
30085
  if (!value)
29374
30086
  return fallbackMs;
29375
30087
  const n = Number(value);
@@ -29399,7 +30111,7 @@ function registerInvokeCommand(program2) {
29399
30111
  orgOverride: opts.org,
29400
30112
  deploymentOverride: opts.deployment,
29401
30113
  argsRaw: opts.args,
29402
- timeoutMs: parseTimeoutMs2(opts.timeout, 60000),
30114
+ timeoutMs: parseTimeoutMs3(opts.timeout, 60000),
29403
30115
  raw: Boolean(opts.raw)
29404
30116
  });
29405
30117
  }));
@@ -29417,7 +30129,7 @@ async function runInvoke(args) {
29417
30129
  });
29418
30130
  if (!resolved.ok) {
29419
30131
  printError(resolved.message);
29420
- process.exit(1);
30132
+ throw new CliExitError(1);
29421
30133
  }
29422
30134
  const target = resolved.target;
29423
30135
  const result = await invokeMcpTool({
@@ -29447,7 +30159,7 @@ function failOnArgsError(parsed) {
29447
30159
  } else if (parsed.reason === "not-an-object") {
29448
30160
  printError("--args must decode to a JSON object (not array / scalar).");
29449
30161
  }
29450
- process.exit(1);
30162
+ throw new CliExitError(1);
29451
30163
  }
29452
30164
  function renderInvokeResult(args) {
29453
30165
  if (isJsonMode()) {
@@ -29457,10 +30169,10 @@ function renderInvokeResult(args) {
29457
30169
  deploymentId: args.deploymentId,
29458
30170
  accessMode: args.accessMode,
29459
30171
  exchangedRuntimeToken: args.exchangedRuntimeToken,
29460
- ...invokeResultToJson2(args.result)
30172
+ ...invokeResultToJson3(args.result)
29461
30173
  });
29462
30174
  if (!args.result.ok)
29463
- process.exit(1);
30175
+ throw new CliExitError(1);
29464
30176
  return;
29465
30177
  }
29466
30178
  if (!args.result.ok) {
@@ -29474,7 +30186,7 @@ function renderInvokeResult(args) {
29474
30186
  const statusLine = args.result.status > 0 ? `HTTP ${args.result.status}` : "network error";
29475
30187
  printError(`${statusLine} from deployment: ${args.result.message}`);
29476
30188
  }
29477
- process.exit(1);
30189
+ throw new CliExitError(1);
29478
30190
  }
29479
30191
  if (args.raw) {
29480
30192
  printInfo(JSON.stringify(args.result.result, null, 2));
@@ -29488,7 +30200,7 @@ function renderInvokeResult(args) {
29488
30200
  printWarn(`Tool reported isError=true. Inspect ${c.bold("--json")} or ${c.bold("--raw")} for the full envelope.`);
29489
30201
  }
29490
30202
  }
29491
- function invokeResultToJson2(result) {
30203
+ function invokeResultToJson3(result) {
29492
30204
  if (result.ok) {
29493
30205
  return {
29494
30206
  ok: true,
@@ -29667,7 +30379,106 @@ function registerMarketplaceCommands(program2) {
29667
30379
  }
29668
30380
  }
29669
30381
  printInfo("");
29670
- printInfo(c.dim(" Install via the dashboard for now; CLI install lands when the registry install mutation ships."));
30382
+ printInfo(c.dim(` Install it: \`mcp marketplace install ${a.id}\` · Fork it: \`mcp marketplace fork ${a.id} --project <id>\``));
30383
+ }));
30384
+ marketplace.command("install <artifactId>").description("Install a published registry artifact (server or skill) into your organization").option("--org <organizationId>", "Organization ID").option("--semver <semver>", "Install a specific version (default: latest)").addHelpText("after", [
30385
+ "",
30386
+ "Notes:",
30387
+ " Paid listings must be purchased from the dashboard first. Private-org",
30388
+ " artifacts from another org require the Pro plan.",
30389
+ "",
30390
+ "Examples:",
30391
+ " $ mcp marketplace install rga_123",
30392
+ " $ mcp marketplace install rga_123 --semver 1.2.0"
30393
+ ].join(`
30394
+ `)).action(runAction(async (artifactId, opts) => {
30395
+ const orgId = await resolveOrgId(opts.org);
30396
+ const data = await api.post("/api/v1/registry/install", {
30397
+ organizationId: orgId,
30398
+ artifactId,
30399
+ ...opts.semver ? { version: opts.semver } : {}
30400
+ });
30401
+ if (isJsonMode()) {
30402
+ printJson(data);
30403
+ return;
30404
+ }
30405
+ const i = data.installation;
30406
+ printSuccess(`Installed ${c.bold(i.artifactType)} (v${i.version}) into your organization.`);
30407
+ printKeyValue({
30408
+ "installation id": i.id,
30409
+ type: i.artifactType,
30410
+ artifact: i.primaryArtifactId,
30411
+ version: i.version,
30412
+ status: i.status
30413
+ });
30414
+ }));
30415
+ marketplace.command("fork <artifactId>").description("Fork a registry artifact into one of your projects as an editable draft").requiredOption("--project <projectId>", "Project to fork the artifact into").option("--org <organizationId>", "Organization ID").addHelpText("after", [
30416
+ "",
30417
+ "Examples:",
30418
+ " $ mcp marketplace fork rga_123 --project proj_456"
30419
+ ].join(`
30420
+ `)).action(runAction(async (artifactId, opts) => {
30421
+ const orgId = await resolveOrgId(opts.org);
30422
+ const data = await api.post("/api/v1/registry/fork", {
30423
+ organizationId: orgId,
30424
+ sourceArtifactId: artifactId,
30425
+ targetProjectId: opts.project
30426
+ });
30427
+ if (isJsonMode()) {
30428
+ printJson(data);
30429
+ return;
30430
+ }
30431
+ printSuccess(`Forked ${c.bold(data.fork.artifactType)} → ${data.fork.forkedId} (draft).`);
30432
+ printStep(data.fork.artifactType === "server" ? `Iterate: \`mcp dev\` then \`mcp servers deploy ${data.fork.forkedId} --wait\`.` : `Iterate on skill ${data.fork.forkedId} then \`mcp skills deploy\`.`);
30433
+ }));
30434
+ marketplace.command("publish <serverId|skillId>").description("Publish a deployed server or a versioned skill to the registry (requires org admin)").requiredOption("--type <type>", "Artifact type: server | skill").requiredOption("--project <projectId>", "The artifact's project ID").option("--semver <semver>", "Version to publish (required for servers; skills use their current version)").option("--visibility <visibility>", "public (marketplace) | private (your org only). Default public.", "public").option("--changelog <text>", "Optional changelog for this version").option("--org <organizationId>", "Organization ID").addHelpText("after", [
30435
+ "",
30436
+ "Notes:",
30437
+ " Servers must be successfully deployed before publishing. Publishing to",
30438
+ " the public marketplace or a private-org registry requires the Pro plan.",
30439
+ "",
30440
+ "Examples:",
30441
+ " $ mcp marketplace publish srv_123 --type server --project proj_1 --semver 1.0.0",
30442
+ " $ mcp marketplace publish skill_9 --type skill --project proj_1 --visibility private"
30443
+ ].join(`
30444
+ `)).action(runAction(async (id, opts) => {
30445
+ const orgId = await resolveOrgId(opts.org);
30446
+ const type = opts.type.trim().toLowerCase();
30447
+ if (type !== "server" && type !== "skill") {
30448
+ throw new Error(`Invalid --type: ${opts.type}. Expected one of: server, skill.`);
30449
+ }
30450
+ const visibility = opts.visibility.trim().toLowerCase();
30451
+ if (visibility !== "public" && visibility !== "private") {
30452
+ throw new Error(`Invalid --visibility: ${opts.visibility}. Expected one of: public, private.`);
30453
+ }
30454
+ const access = visibility === "private" ? "privateOrg" : "public";
30455
+ const body = {
30456
+ organizationId: orgId,
30457
+ artifactType: type,
30458
+ projectId: opts.project,
30459
+ access,
30460
+ ...opts.changelog ? { changelog: opts.changelog } : {}
30461
+ };
30462
+ if (type === "server") {
30463
+ if (!opts.semver) {
30464
+ throw new Error("--semver <semver> is required when --type server.");
30465
+ }
30466
+ body["serverId"] = id;
30467
+ body["semver"] = opts.semver;
30468
+ } else {
30469
+ body["skillId"] = id;
30470
+ }
30471
+ const data = await api.post("/api/v1/registry/publish", body);
30472
+ if (isJsonMode()) {
30473
+ printJson(data);
30474
+ return;
30475
+ }
30476
+ printSuccess(`Published ${c.bold(data.artifact.name)} to the ${data.artifact.access === "public" ? "marketplace" : "private registry"}.`);
30477
+ printKeyValue({
30478
+ "artifact id": data.artifact.id,
30479
+ name: data.artifact.name,
30480
+ access: data.artifact.access
30481
+ });
29671
30482
  }));
29672
30483
  }
29673
30484
 
@@ -29700,43 +30511,349 @@ function registerOAuthCommands(program2) {
29700
30511
  updated: formatDate(c2.updatedAt)
29701
30512
  });
29702
30513
  }));
30514
+ oauth.command("connections").description("List OAuth provider connections across the organization").option("--org <organizationId>", "Organization ID").option("--status <status>", "Filter: all | connected | expired | error | revoked (default connected)", "connected").option("--server <serverId>", "Restrict to one server").addHelpText("after", [
30515
+ "",
30516
+ "Examples:",
30517
+ " $ mcp oauth connections",
30518
+ " $ mcp oauth connections --status all",
30519
+ " $ mcp --json oauth connections | jq '.connections[].id'"
30520
+ ].join(`
30521
+ `)).action(runAction(async (opts) => {
30522
+ const orgId = await resolveOrgId(opts.org);
30523
+ const params = {
30524
+ organizationId: orgId,
30525
+ status: opts.status
30526
+ };
30527
+ if (opts.server)
30528
+ params["serverId"] = opts.server;
30529
+ const data = await api.get("/api/v1/oauth/connections", params);
30530
+ printList(data.connections.map((conn) => ({
30531
+ id: conn.id,
30532
+ provider: conn.providerKey,
30533
+ server: conn.serverName ?? "—",
30534
+ status: conn.status,
30535
+ scopes: conn.scopes.length,
30536
+ updated: formatDate(conn.updatedAt)
30537
+ })), [
30538
+ { key: "id", label: "ID", width: 22 },
30539
+ { key: "provider", label: "Provider", width: 16 },
30540
+ { key: "server", label: "Server", width: 22 },
30541
+ { key: "status", label: "Status", width: 12 },
30542
+ { key: "scopes", label: "Scopes", width: 8 },
30543
+ { key: "updated", label: "Updated", width: 22 }
30544
+ ], data);
30545
+ }));
30546
+ }
30547
+
30548
+ // src/commands/usage.ts
30549
+ var MAX_WINDOW_DAYS = 90;
30550
+ function parseWindowDays(raw) {
30551
+ if (!raw)
30552
+ return 30;
30553
+ const trimmed = raw.trim().toLowerCase();
30554
+ const match = /^(\d+)\s*(d|w|h)?$/.exec(trimmed);
30555
+ if (!match) {
30556
+ throw new Error(`Invalid --since: ${raw}. Expected a window like 30d, 4w, or a day count.`);
30557
+ }
30558
+ const value = Number.parseInt(match[1], 10);
30559
+ const unit = match[2] ?? "d";
30560
+ let days;
30561
+ if (unit === "w")
30562
+ days = value * 7;
30563
+ else if (unit === "h")
30564
+ days = Math.ceil(value / 24);
30565
+ else
30566
+ days = value;
30567
+ if (days < 1)
30568
+ days = 1;
30569
+ if (days > MAX_WINDOW_DAYS)
30570
+ days = MAX_WINDOW_DAYS;
30571
+ return days;
30572
+ }
30573
+ function formatMicros(micros) {
30574
+ if (micros == null)
30575
+ return "—";
30576
+ return `$${(micros / 1e6).toFixed(2)}`;
30577
+ }
30578
+ function registerUsageCommands(program2) {
30579
+ const usage = program2.command("usage").description("Show organization usage (tool-call volume) and credit balance from the terminal").option("--org <organizationId>", "Organization ID").option("--since <window>", "Aggregation window: 30d, 4w, or a day count (1–90, default 30d)").addHelpText("after", [
30580
+ "",
30581
+ "Examples:",
30582
+ " $ mcp usage # last 30 days of request volume + credit balance",
30583
+ " $ mcp usage --since 7d",
30584
+ " $ mcp --json usage | jq '.usage.totals.requestCount'",
30585
+ " $ mcp usage ledger --limit 20 # recent credit ledger entries"
30586
+ ].join(`
30587
+ `)).action(runAction(async (opts) => {
30588
+ const orgId = await resolveOrgId(opts.org);
30589
+ const windowDays = parseWindowDays(opts.since);
30590
+ const [usageData, creditsData] = await Promise.all([
30591
+ api.get("/api/v1/billing/usage", {
30592
+ organizationId: orgId,
30593
+ windowDays: String(windowDays)
30594
+ }),
30595
+ api.get("/api/v1/credits/ledger", {
30596
+ organizationId: orgId,
30597
+ limit: "1"
30598
+ })
30599
+ ]);
30600
+ if (isJsonMode()) {
30601
+ printJson({
30602
+ organization: usageData.organization,
30603
+ usage: usageData.usage,
30604
+ account: creditsData.account
30605
+ });
30606
+ return;
30607
+ }
30608
+ const t = usageData.usage.totals;
30609
+ printKeyValue({
30610
+ organization: `${usageData.organization.name ?? "—"} (${usageData.organization.id})`,
30611
+ plan: usageData.organization.plan ?? "—",
30612
+ window: `${usageData.usage.windowDays}d (since ${formatDate(usageData.usage.windowStartedAt)})`,
30613
+ requests: t.requestCount,
30614
+ allowed: t.allowedCount,
30615
+ "rate-limited": t.rateLimitedCount,
30616
+ unauthorized: t.unauthorizedCount,
30617
+ failed: t.failedCount,
30618
+ "avg cpu ms": usageData.usage.averageCpuTimeMs == null ? "—" : usageData.usage.averageCpuTimeMs.toFixed(1),
30619
+ "avg duration ms": usageData.usage.averageDurationMs == null ? "—" : usageData.usage.averageDurationMs.toFixed(1),
30620
+ "credit balance": formatMicros(creditsData.account?.balanceMicros),
30621
+ "credits granted": formatMicros(creditsData.account?.totalGrantedMicros),
30622
+ "credits debited": formatMicros(creditsData.account?.totalDebitedMicros)
30623
+ });
30624
+ }));
30625
+ usage.command("ledger").description("List organization credit ledger entries (most recent first)").option("--org <organizationId>", "Organization ID").option("--limit <n>", "Maximum entries (1–100, default 50)", "50").option("--cursor <createdAtMs>", "Pagination cursor: the createdAt ms from a previous nextCursor").action(runAction(async (opts) => {
30626
+ const orgId = await resolveOrgId(opts.org);
30627
+ const params = {
30628
+ organizationId: orgId,
30629
+ limit: opts.limit
30630
+ };
30631
+ if (opts.cursor)
30632
+ params["cursor"] = opts.cursor;
30633
+ const data = await api.get("/api/v1/credits/ledger", params);
30634
+ printList(data.entries.map((entry) => ({
30635
+ id: entry.id,
30636
+ kind: entry.entryKind,
30637
+ amount: formatMicros(entry.amountMicros),
30638
+ balance: formatMicros(entry.balanceAfterMicros),
30639
+ source: entry.source ?? "—",
30640
+ note: entry.note ?? "—",
30641
+ created: formatDate(entry.createdAt)
30642
+ })), [
30643
+ { key: "id", label: "ID", width: 22 },
30644
+ { key: "kind", label: "Kind", width: 14 },
30645
+ { key: "amount", label: "Amount", width: 12 },
30646
+ { key: "balance", label: "Balance", width: 12 },
30647
+ { key: "source", label: "Source", width: 16 },
30648
+ { key: "note", label: "Note", width: 24 },
30649
+ { key: "created", label: "Created", width: 22 }
30650
+ ], data);
30651
+ }));
30652
+ }
30653
+
30654
+ // src/commands/metrics.ts
30655
+ function formatPercent(rate) {
30656
+ if (rate == null)
30657
+ return "—";
30658
+ return `${(rate * 100).toFixed(2)}%`;
30659
+ }
30660
+ function formatMs(value) {
30661
+ if (value == null)
30662
+ return "—";
30663
+ return `${value.toFixed(1)}ms`;
30664
+ }
30665
+ function registerMetricsCommands(program2) {
30666
+ const metrics = program2.command("metrics").description("Aggregate deployment metrics (request volume, error rate)");
30667
+ metrics.command("deployment <deploymentId>").description("Show aggregate request metrics for a deployment over a window (CI deploy gate)").option("--org <organizationId>", "Organization ID").option("--since <window>", "Aggregation window: 24h, 7d, 4w, or a day count (1–90, default 7d)").option("--max-error-rate <ratio>", "Exit non-zero if the error rate exceeds this fraction (e.g. 0.05). For CI gates.").addHelpText("after", [
30668
+ "",
30669
+ "Exit codes:",
30670
+ " 0 metrics fetched (and error rate within --max-error-rate if set)",
30671
+ " 1 error rate exceeded --max-error-rate",
30672
+ "",
30673
+ "Examples:",
30674
+ " $ mcp metrics deployment dep_123 --since 24h",
30675
+ " $ mcp metrics deployment dep_123 --since 7d --max-error-rate 0.02",
30676
+ " $ mcp --json metrics deployment dep_123 | jq '.metrics.errorRate'"
30677
+ ].join(`
30678
+ `)).action(runAction(async (deploymentId, opts) => {
30679
+ const orgId = await resolveOrgId(opts.org);
30680
+ const windowDays = parseWindowDays(opts.since);
30681
+ const data = await api.get("/api/v1/deployment/metrics", {
30682
+ organizationId: orgId,
30683
+ deploymentId,
30684
+ windowDays: String(windowDays)
30685
+ });
30686
+ let maxErrorRate = null;
30687
+ if (opts.maxErrorRate !== undefined) {
30688
+ const parsed = Number(opts.maxErrorRate);
30689
+ if (!Number.isFinite(parsed) || parsed < 0 || parsed > 1) {
30690
+ throw new Error(`Invalid --max-error-rate: ${opts.maxErrorRate}. Expected a fraction between 0 and 1.`);
30691
+ }
30692
+ maxErrorRate = parsed;
30693
+ }
30694
+ const errorRate = data.metrics.errorRate;
30695
+ const breached = maxErrorRate !== null && errorRate !== null && errorRate > maxErrorRate;
30696
+ if (isJsonMode()) {
30697
+ printJson(data);
30698
+ if (breached)
30699
+ throw new CliExitError(1);
30700
+ return;
30701
+ }
30702
+ const m2 = data.metrics;
30703
+ printKeyValue({
30704
+ deployment: `${data.deployment.serverName} (${data.deployment.id})`,
30705
+ window: `${m2.windowDays}d (since ${formatDate(m2.windowStartedAt)})`,
30706
+ requests: m2.totals.requestCount,
30707
+ allowed: m2.totals.allowedCount,
30708
+ "rate-limited": m2.totals.rateLimitedCount,
30709
+ unauthorized: m2.totals.unauthorizedCount,
30710
+ failed: m2.totals.failedCount,
30711
+ "error rate": formatPercent(errorRate),
30712
+ "avg duration": formatMs(m2.averageDurationMs),
30713
+ "avg cpu": formatMs(m2.averageCpuTimeMs),
30714
+ "last event": m2.lastEventAt ? formatDate(m2.lastEventAt) : "—"
30715
+ });
30716
+ if (breached)
30717
+ throw new CliExitError(1);
30718
+ }));
30719
+ }
30720
+
30721
+ // src/commands/orgs.ts
30722
+ var ACCESS_STATES = ["active", "disabled", "all"];
30723
+ function registerOrgCommands(program2) {
30724
+ const orgs = program2.command("orgs").description("List and inspect the organizations you belong to");
30725
+ orgs.command("list").description("List organizations you can access").addHelpText("after", [
30726
+ "",
30727
+ "Examples:",
30728
+ " $ mcp orgs list",
30729
+ " $ mcp --json orgs list | jq '.organizations[].id'"
30730
+ ].join(`
30731
+ `)).action(runAction(async () => {
30732
+ const data = await api.get("/api/v1/organizations");
30733
+ printList(data.organizations.map((org) => ({
30734
+ id: org.id,
30735
+ name: org.name ?? "—",
30736
+ slug: org.slug ?? "—",
30737
+ plan: org.plan ?? "—",
30738
+ role: org.role,
30739
+ default: org.isDefault || org.id === data.defaultOrganizationId ? "*" : ""
30740
+ })), [
30741
+ { key: "id", label: "ID", width: 22 },
30742
+ { key: "name", label: "Name", width: 24 },
30743
+ { key: "slug", label: "Slug", width: 18 },
30744
+ { key: "plan", label: "Plan", width: 12 },
30745
+ { key: "role", label: "Role", width: 10 },
30746
+ { key: "default", label: "Default", width: 7 }
30747
+ ], data);
30748
+ }));
30749
+ orgs.command("get [organizationId]").description("Show one organization (defaults to your active org)").action(runAction(async (organizationId) => {
30750
+ const orgId = await resolveOrgId(organizationId);
30751
+ const data = await api.get("/api/v1/organization", {
30752
+ organizationId: orgId
30753
+ });
30754
+ if (isJsonMode()) {
30755
+ printJson(data);
30756
+ return;
30757
+ }
30758
+ const org = data.organization;
30759
+ printKeyValue({
30760
+ id: org.id,
30761
+ name: org.name ?? "—",
30762
+ slug: org.slug ?? "—",
30763
+ plan: org.plan ?? "—",
30764
+ "your role": org.role
30765
+ });
30766
+ }));
30767
+ orgs.command("members [organizationId]").description("List members of an organization").option("--access-state <state>", `Filter: ${ACCESS_STATES.join(" | ")} (default active)`, "active").option("--limit <n>", "Maximum members (1–100, default 50)", "50").option("--cursor <cursor>", "Pagination cursor from a previous nextCursor").action(runAction(async (organizationId, opts) => {
30768
+ const orgId = await resolveOrgId(organizationId);
30769
+ const params = {
30770
+ organizationId: orgId,
30771
+ accessState: opts.accessState,
30772
+ limit: opts.limit
30773
+ };
30774
+ if (opts.cursor)
30775
+ params["cursor"] = opts.cursor;
30776
+ const data = await api.get("/api/v1/organization/members", params);
30777
+ printList(data.members.map((member) => ({
30778
+ userId: member.userId,
30779
+ name: member.name ?? "—",
30780
+ email: member.email ?? "—",
30781
+ role: member.role,
30782
+ access: member.accessState
30783
+ })), [
30784
+ { key: "userId", label: "User ID", width: 22 },
30785
+ { key: "name", label: "Name", width: 20 },
30786
+ { key: "email", label: "Email", width: 28 },
30787
+ { key: "role", label: "Role", width: 10 },
30788
+ { key: "access", label: "Access", width: 10 }
30789
+ ], data);
30790
+ }));
30791
+ orgs.command("usage [organizationId]").description("Show request-volume usage for an organization").option("--since <window>", "Aggregation window: 30d, 4w, or a day count (1–90, default 30d)").action(runAction(async (organizationId, opts) => {
30792
+ const orgId = await resolveOrgId(organizationId);
30793
+ const windowDays = parseWindowDays(opts.since);
30794
+ const data = await api.get("/api/v1/organization/usage", { organizationId: orgId, windowDays: String(windowDays) });
30795
+ if (isJsonMode()) {
30796
+ printJson(data);
30797
+ return;
30798
+ }
30799
+ const t = data.usage.totals;
30800
+ printKeyValue({
30801
+ organization: `${data.organization.name ?? "—"} (${data.organization.id})`,
30802
+ window: `${data.usage.windowDays}d (since ${formatDate(data.usage.windowStartedAt)})`,
30803
+ requests: t.requestCount,
30804
+ allowed: t.allowedCount,
30805
+ "rate-limited": t.rateLimitedCount,
30806
+ unauthorized: t.unauthorizedCount,
30807
+ failed: t.failedCount
30808
+ });
30809
+ }));
30810
+ orgs.command("use <organizationId>").description("Set the default organization for this profile (discoverable alias of `mcp config set-org`)").action(runAction((organizationId) => {
30811
+ const config = readConfig();
30812
+ config.defaultOrganizationId = organizationId;
30813
+ writeConfig(config);
30814
+ if (isJsonMode()) {
30815
+ printJson({ defaultOrganizationId: organizationId });
30816
+ return;
30817
+ }
30818
+ printSuccess(`Default organization set to ${organizationId}`);
30819
+ }));
29703
30820
  }
29704
30821
 
29705
30822
  // src/lib/plugins.ts
29706
30823
  import { spawn as spawn7 } from "node:child_process";
29707
30824
  import {
29708
- existsSync as existsSync35,
29709
- mkdirSync as mkdirSync16,
30825
+ existsSync as existsSync36,
30826
+ mkdirSync as mkdirSync17,
29710
30827
  readdirSync as readdirSync4,
29711
- readFileSync as readFileSync19,
30828
+ readFileSync as readFileSync20,
29712
30829
  rmSync as rmSync3,
29713
30830
  statSync as statSync7
29714
30831
  } from "node:fs";
29715
- import { join as join30 } from "node:path";
30832
+ import { join as join31 } from "node:path";
29716
30833
  import { pathToFileURL } from "node:url";
29717
30834
  function pluginsDir() {
29718
- return join30(configDir(), "plugins");
30835
+ return join31(configDir(), "plugins");
29719
30836
  }
29720
30837
  function ensureDir2() {
29721
30838
  const dir = pluginsDir();
29722
- if (!existsSync35(dir))
29723
- mkdirSync16(dir, { recursive: true, mode: 448 });
30839
+ if (!existsSync36(dir))
30840
+ mkdirSync17(dir, { recursive: true, mode: 448 });
29724
30841
  }
29725
30842
  function readManifestFromPackageDir(packageDir) {
29726
- const pkgPath = join30(packageDir, "package.json");
29727
- if (!existsSync35(pkgPath))
30843
+ const pkgPath = join31(packageDir, "package.json");
30844
+ if (!existsSync36(pkgPath))
29728
30845
  return null;
29729
30846
  let pkg;
29730
30847
  try {
29731
- pkg = JSON.parse(readFileSync19(pkgPath, "utf-8"));
30848
+ pkg = JSON.parse(readFileSync20(pkgPath, "utf-8"));
29732
30849
  } catch {
29733
30850
  return null;
29734
30851
  }
29735
30852
  if (!pkg.name)
29736
30853
  return null;
29737
- const relEntry = pkg.mcpsh?.register ?? pkg.main ?? "index.js";
29738
- const entry = join30(packageDir, relEntry);
29739
- if (!existsSync35(entry))
30854
+ const relEntry = pkg.mcpsh?.register ?? pkg.mcp?.register ?? pkg.main ?? "index.js";
30855
+ const entry = join31(packageDir, relEntry);
30856
+ if (!existsSync36(entry))
29740
30857
  return null;
29741
30858
  return {
29742
30859
  name: pkg.name,
@@ -29747,11 +30864,11 @@ function readManifestFromPackageDir(packageDir) {
29747
30864
  }
29748
30865
  function listInstalledPlugins() {
29749
30866
  const dir = pluginsDir();
29750
- if (!existsSync35(dir))
30867
+ if (!existsSync36(dir))
29751
30868
  return [];
29752
30869
  const out = [];
29753
30870
  for (const entry of readdirSync4(dir)) {
29754
- const full = join30(dir, entry);
30871
+ const full = join31(dir, entry);
29755
30872
  let s;
29756
30873
  try {
29757
30874
  s = statSync7(full);
@@ -29762,7 +30879,7 @@ function listInstalledPlugins() {
29762
30879
  continue;
29763
30880
  if (entry.startsWith("@")) {
29764
30881
  for (const child of readdirSync4(full)) {
29765
- const m2 = readManifestFromPackageDir(join30(full, child));
30882
+ const m2 = readManifestFromPackageDir(join31(full, child));
29766
30883
  if (m2)
29767
30884
  out.push(m2);
29768
30885
  }
@@ -29797,15 +30914,15 @@ async function loadPlugins(program2) {
29797
30914
  }
29798
30915
  var PACKAGE_SPEC_PATTERN = /^(@[a-z0-9~-][\w.~-]*\/)?[a-z0-9~-][\w.~-]*(@[-\w.^~><=*+|]+)?$/i;
29799
30916
  function runNpm(args, cwd) {
29800
- return new Promise((resolve8) => {
30917
+ return new Promise((resolve9) => {
29801
30918
  const isWindows = process.platform === "win32";
29802
30919
  const child = spawn7(isWindows ? "npm.cmd" : "npm", args, {
29803
30920
  shell: isWindows,
29804
30921
  cwd,
29805
30922
  stdio: "inherit"
29806
30923
  });
29807
- child.on("close", (code) => resolve8(code ?? 1));
29808
- child.on("error", () => resolve8(1));
30924
+ child.on("close", (code) => resolve9(code ?? 1));
30925
+ child.on("error", () => resolve9(1));
29809
30926
  });
29810
30927
  }
29811
30928
  async function installPlugin(packageSpec) {
@@ -29826,12 +30943,12 @@ async function installPlugin(packageSpec) {
29826
30943
  if (code !== 0) {
29827
30944
  throw new Error(`npm install ${packageSpec} exited with code ${code}.`);
29828
30945
  }
29829
- const nm = join30(dir, "node_modules");
29830
- if (!existsSync35(nm)) {
30946
+ const nm = join31(dir, "node_modules");
30947
+ if (!existsSync36(nm)) {
29831
30948
  throw new Error(`npm install ran but produced no node_modules under ${dir}.`);
29832
30949
  }
29833
30950
  const baseName = packageSpec.replace(/@[^@/]+$/, "");
29834
- const candidatePath = baseName.startsWith("@") ? join30(nm, baseName.split("/")[0], baseName.split("/")[1] ?? "") : join30(nm, baseName);
30951
+ const candidatePath = baseName.startsWith("@") ? join31(nm, baseName.split("/")[0], baseName.split("/")[1] ?? "") : join31(nm, baseName);
29835
30952
  const manifest = readManifestFromPackageDir(candidatePath);
29836
30953
  if (!manifest) {
29837
30954
  throw new Error(`Installed but could not read plugin manifest at ${candidatePath}.`);
@@ -29839,26 +30956,26 @@ async function installPlugin(packageSpec) {
29839
30956
  return { name: manifest.name, entry: manifest.entry };
29840
30957
  }
29841
30958
  function removePlugin(name) {
29842
- const nm = join30(pluginsDir(), "node_modules");
29843
- if (!existsSync35(nm))
30959
+ const nm = join31(pluginsDir(), "node_modules");
30960
+ if (!existsSync36(nm))
29844
30961
  return false;
29845
- const target = name.startsWith("@") ? join30(nm, name.split("/")[0], name.split("/")[1] ?? "") : join30(nm, name);
29846
- if (!existsSync35(target))
30962
+ const target = name.startsWith("@") ? join31(nm, name.split("/")[0], name.split("/")[1] ?? "") : join31(nm, name);
30963
+ if (!existsSync36(target))
29847
30964
  return false;
29848
30965
  rmSync3(target, { recursive: true, force: true });
29849
30966
  return true;
29850
30967
  }
29851
30968
  function listInstalledPluginsCombined() {
29852
30969
  const direct = listInstalledPlugins();
29853
- const nm = join30(pluginsDir(), "node_modules");
29854
- if (!existsSync35(nm))
30970
+ const nm = join31(pluginsDir(), "node_modules");
30971
+ if (!existsSync36(nm))
29855
30972
  return direct;
29856
30973
  const seen = new Set(direct.map((p2) => p2.name));
29857
30974
  const out = [...direct];
29858
30975
  for (const entry of readdirSync4(nm)) {
29859
30976
  if (entry === ".bin" || entry === ".package-lock.json")
29860
30977
  continue;
29861
- const full = join30(nm, entry);
30978
+ const full = join31(nm, entry);
29862
30979
  let s;
29863
30980
  try {
29864
30981
  s = statSync7(full);
@@ -29869,7 +30986,7 @@ function listInstalledPluginsCombined() {
29869
30986
  continue;
29870
30987
  if (entry.startsWith("@")) {
29871
30988
  for (const child of readdirSync4(full)) {
29872
- const m2 = readManifestFromPackageDir(join30(full, child));
30989
+ const m2 = readManifestFromPackageDir(join31(full, child));
29873
30990
  if (m2 && !seen.has(m2.name)) {
29874
30991
  seen.add(m2.name);
29875
30992
  out.push(m2);
@@ -29899,7 +31016,7 @@ function registerPluginCommands(program2) {
29899
31016
  " {",
29900
31017
  ' "name": "mcpsh-plugin-foo",',
29901
31018
  ' "main": "index.js",',
29902
- ' "mcp": { "register": "index.js" }',
31019
+ ' "mcpsh": { "register": "index.js" }',
29903
31020
  " }",
29904
31021
  "",
29905
31022
  " // index.js",
@@ -29908,7 +31025,11 @@ function registerPluginCommands(program2) {
29908
31025
  " const orgs = await api.get('/api/v1/organizations')",
29909
31026
  " console.log(orgs)",
29910
31027
  " })",
29911
- " }"
31028
+ " }",
31029
+ "",
31030
+ "Trust model: plugins run IN-PROCESS with the CLI — same API client, same",
31031
+ "credentials, same filesystem access. Install only plugins you trust.",
31032
+ ' (The legacy "mcp.register" key is still accepted for older plugins.)'
29912
31033
  ].join(`
29913
31034
  `));
29914
31035
  plugin.command("install <package>").description("Install a plugin from npm (uses npm under the hood)").addHelpText("after", [
@@ -30325,7 +31446,7 @@ function clipboardCandidatesFor(platform7, waylandDisplay) {
30325
31446
  return linux;
30326
31447
  }
30327
31448
  async function defaultIsExecutable(binary) {
30328
- return await new Promise((resolve8) => {
31449
+ return await new Promise((resolve9) => {
30329
31450
  const isWin = process.platform === "win32";
30330
31451
  const cmd = isWin ? "where" : "command";
30331
31452
  const args = isWin ? [binary] : ["-v", binary];
@@ -30333,8 +31454,8 @@ async function defaultIsExecutable(binary) {
30333
31454
  shell: !isWin,
30334
31455
  stdio: "ignore"
30335
31456
  });
30336
- child.on("error", () => resolve8(false));
30337
- child.on("exit", (code) => resolve8(code === 0));
31457
+ child.on("error", () => resolve9(false));
31458
+ child.on("exit", (code) => resolve9(code === 0));
30338
31459
  });
30339
31460
  }
30340
31461
  async function detectClipboard(probe) {
@@ -30356,7 +31477,7 @@ async function copyToClipboard(text, probe) {
30356
31477
  };
30357
31478
  }
30358
31479
  const spawnImpl = probe?.spawnImpl ?? spawn8;
30359
- return await new Promise((resolve8) => {
31480
+ return await new Promise((resolve9) => {
30360
31481
  const child = spawnImpl(candidate.binary, candidate.args, {
30361
31482
  stdio: ["pipe", "ignore", "pipe"]
30362
31483
  });
@@ -30365,7 +31486,7 @@ async function copyToClipboard(text, probe) {
30365
31486
  stderr += chunk.toString("utf-8");
30366
31487
  });
30367
31488
  child.on("error", (err) => {
30368
- resolve8({
31489
+ resolve9({
30369
31490
  kind: "failed",
30370
31491
  binary: candidate.binary,
30371
31492
  message: err instanceof Error ? err.message : String(err)
@@ -30373,9 +31494,9 @@ async function copyToClipboard(text, probe) {
30373
31494
  });
30374
31495
  child.on("exit", (code) => {
30375
31496
  if (code === 0) {
30376
- resolve8({ kind: "ok", binary: candidate.binary });
31497
+ resolve9({ kind: "ok", binary: candidate.binary });
30377
31498
  } else {
30378
- resolve8({
31499
+ resolve9({
30379
31500
  kind: "failed",
30380
31501
  binary: candidate.binary,
30381
31502
  message: stderr.trim() || `${candidate.binary} exited with status ${code ?? "?"}`
@@ -30385,7 +31506,7 @@ async function copyToClipboard(text, probe) {
30385
31506
  try {
30386
31507
  child.stdin?.end(text);
30387
31508
  } catch (err) {
30388
- resolve8({
31509
+ resolve9({
30389
31510
  kind: "failed",
30390
31511
  binary: candidate.binary,
30391
31512
  message: err instanceof Error ? err.message : String(err)
@@ -31455,12 +32576,12 @@ function displayValueFor(field, value) {
31455
32576
 
31456
32577
  // src/lib/tui/state-store.ts
31457
32578
  import {
31458
- existsSync as existsSync36,
31459
- mkdirSync as mkdirSync17,
31460
- readFileSync as readFileSync20,
31461
- writeFileSync as writeFileSync19
32579
+ existsSync as existsSync37,
32580
+ mkdirSync as mkdirSync18,
32581
+ readFileSync as readFileSync21,
32582
+ writeFileSync as writeFileSync20
31462
32583
  } from "node:fs";
31463
- import { join as join31 } from "node:path";
32584
+ import { join as join32 } from "node:path";
31464
32585
  var ALL_TABS = [
31465
32586
  "servers",
31466
32587
  "projects",
@@ -31470,7 +32591,7 @@ var ALL_TABS = [
31470
32591
  "devSessions"
31471
32592
  ];
31472
32593
  function tuiStateFile() {
31473
- return join31(configDir(), "tui-state.json");
32594
+ return join32(configDir(), "tui-state.json");
31474
32595
  }
31475
32596
  function isTab(v2) {
31476
32597
  return typeof v2 === "string" && ALL_TABS.includes(v2);
@@ -31535,11 +32656,11 @@ function sanitizeTuiState(input) {
31535
32656
  }
31536
32657
  function readTuiState() {
31537
32658
  const path = tuiStateFile();
31538
- if (!existsSync36(path))
32659
+ if (!existsSync37(path))
31539
32660
  return {};
31540
32661
  let parsed;
31541
32662
  try {
31542
- parsed = JSON.parse(readFileSync20(path, "utf-8"));
32663
+ parsed = JSON.parse(readFileSync21(path, "utf-8"));
31543
32664
  } catch {
31544
32665
  return {};
31545
32666
  }
@@ -31547,10 +32668,10 @@ function readTuiState() {
31547
32668
  }
31548
32669
  function writeTuiState(state) {
31549
32670
  try {
31550
- if (!existsSync36(configDir())) {
31551
- mkdirSync17(configDir(), { recursive: true, mode: 448 });
32671
+ if (!existsSync37(configDir())) {
32672
+ mkdirSync18(configDir(), { recursive: true, mode: 448 });
31552
32673
  }
31553
- writeFileSync19(tuiStateFile(), JSON.stringify(state, null, 2), {
32674
+ writeFileSync20(tuiStateFile(), JSON.stringify(state, null, 2), {
31554
32675
  encoding: "utf-8",
31555
32676
  mode: 384
31556
32677
  });
@@ -33555,7 +34676,7 @@ function registerUiCommand(program2) {
33555
34676
  }
33556
34677
  return false;
33557
34678
  };
33558
- await new Promise((resolve8) => {
34679
+ await new Promise((resolve9) => {
33559
34680
  const flushAndExit = () => {
33560
34681
  try {
33561
34682
  if (persistTimer) {
@@ -33565,7 +34686,7 @@ function registerUiCommand(program2) {
33565
34686
  writeTuiState(snapshotForPersist());
33566
34687
  } catch {}
33567
34688
  shutdown(handles);
33568
- resolve8();
34689
+ resolve9();
33569
34690
  };
33570
34691
  const executePaletteCommand = (id) => {
33571
34692
  if (id.startsWith("tab:")) {
@@ -34174,7 +35295,7 @@ function registerUiCommand(program2) {
34174
35295
 
34175
35296
  // src/commands/update.ts
34176
35297
  import { spawn as spawn9 } from "node:child_process";
34177
- import { dirname as dirname10 } from "node:path";
35298
+ import { dirname as dirname11 } from "node:path";
34178
35299
  import { fileURLToPath as fileURLToPath2 } from "node:url";
34179
35300
  function detectInstallContext() {
34180
35301
  const override = process.env["MCPSH_PACKAGE_MANAGER"]?.trim().toLowerCase();
@@ -34187,7 +35308,7 @@ function detectInstallContext() {
34187
35308
  }
34188
35309
  const here = (() => {
34189
35310
  try {
34190
- return dirname10(fileURLToPath2(import.meta.url));
35311
+ return dirname11(fileURLToPath2(import.meta.url));
34191
35312
  } catch {
34192
35313
  return process.argv[1] ?? "";
34193
35314
  }
@@ -34235,10 +35356,10 @@ function buildCommand(manager) {
34235
35356
  }
34236
35357
  }
34237
35358
  function runShell(command) {
34238
- return new Promise((resolve8) => {
35359
+ return new Promise((resolve9) => {
34239
35360
  const child = spawn9(command, { shell: true, stdio: "inherit" });
34240
- child.on("close", (code) => resolve8(code ?? 1));
34241
- child.on("error", () => resolve8(1));
35361
+ child.on("close", (code) => resolve9(code ?? 1));
35362
+ child.on("error", () => resolve9(1));
34242
35363
  });
34243
35364
  }
34244
35365
  function registerUpdateCommand(program2) {
@@ -34304,12 +35425,14 @@ function registerUpdateCommand(program2) {
34304
35425
  // src/create-program.ts
34305
35426
  function createProgram() {
34306
35427
  const program2 = new Command;
34307
- program2.name("mcp").description("The official CLI for MCPCloud — manage projects, MCP servers, skills, and API keys from the terminal").version(getCliVersion(), "-v, --version", "Print the CLI version").option("--json", "Output raw JSON (useful for scripting)").option("--base-url <url>", "API base URL for this invocation. Falls back to MCPCLOUD_BASE_URL or `mcp config set-url`.").option("--profile <name>", "Run against a named config profile for this invocation (overrides MCPCLOUD_PROFILE and the saved current profile).").option("--ci", "Force CI mode for this invocation. Auto-detected when CI=true. Disables color, swaps Unicode glyphs for ASCII, refuses interactive prompts, and always surfaces request IDs.").option("--warnings-as-errors", "Exit non-zero if any warning is emitted during the command (CI ergonomics).").option("--format <mode>", "Output format for list commands: table (default), tsv, jsonl, json.").option("--no-table", "Shorthand for --format tsv. Tab-separated rows for awk/cut/grep pipelines.").option("--field <names>", "Comma-separated list of fields to include in list output (repeatable).", (value, prev) => prev ? [...prev, value] : [value]).option("--filter <key=substring>", "Substring filter for list rows (repeatable). Case-insensitive.", (value, prev) => prev ? [...prev, value] : [value]).option("--idempotency-key <key>", "Stable Idempotency-Key for mutations (1–255 ASCII chars, no whitespace). When omitted, the CLI auto-generates a UUID per invocation so internal retries on transient failures stay safe.").option("--debug", "Print every HTTP request/response with secrets redacted. Equivalent to MCPCLOUD_LOG=debug.").hook("preAction", (thisCommand) => {
35428
+ program2.name("mcp").description("The official CLI for MCPCloud — manage projects, MCP servers, skills, and API keys from the terminal").version(getCliVersion(), "-v, --version", "Print the CLI version").option("--json", "Output raw JSON (useful for scripting)").option("--base-url <url>", "API base URL for this invocation. Falls back to MCPCLOUD_BASE_URL or `mcp config set-url`.").option("--profile <name>", "Run against a named config profile for this invocation (overrides MCPCLOUD_PROFILE and the saved current profile).").option("--ci", "Force CI mode for this invocation. Auto-detected when CI=true. Disables color, swaps Unicode glyphs for ASCII, refuses interactive prompts, and always surfaces request IDs.").option("--warnings-as-errors", "Exit non-zero if any warning is emitted during the command (CI ergonomics).").option("--non-interactive", "Refuse interactive prompts (fail fast on missing input) without the color/glyph changes of --ci.").option("--quiet", "Suppress success/step/info chrome; data and errors still print. Pairs with exit codes for health/existence gates.").option("--format <mode>", "Output format for list commands: table (default), tsv, jsonl, json.").option("--no-table", "Shorthand for --format tsv. Tab-separated rows for awk/cut/grep pipelines.").option("--field <names>", "Comma-separated list of fields to include in list output (repeatable).", (value, prev) => prev ? [...prev, value] : [value]).option("--filter <key=substring>", "Substring filter for list rows (repeatable). Case-insensitive.", (value, prev) => prev ? [...prev, value] : [value]).option("--idempotency-key <key>", "Stable Idempotency-Key for mutations (1–255 ASCII chars, no whitespace). When omitted, the CLI auto-generates a UUID per invocation so internal retries on transient failures stay safe.").option("--debug", "Print every HTTP request/response with secrets redacted. Equivalent to MCPCLOUD_LOG=debug.").hook("preAction", (thisCommand) => {
34308
35429
  const opts = thisCommand.optsWithGlobals();
34309
35430
  setJsonMode(Boolean(opts.json));
34310
35431
  setBaseUrlOverride(opts.baseUrl);
34311
35432
  setProfileOverride(opts.profile);
34312
35433
  setCiMode(Boolean(opts.ci) || isCiEnv());
35434
+ setNonInteractive(Boolean(opts.nonInteractive));
35435
+ setQuiet(Boolean(opts.quiet));
34313
35436
  setWarningsAsErrors(Boolean(opts.warningsAsErrors));
34314
35437
  setListFormat(opts.format);
34315
35438
  setNoTable(opts.table === false);
@@ -34347,6 +35470,9 @@ function createProgram() {
34347
35470
  registerSkillCommands(program2);
34348
35471
  registerApiKeyCommands(program2);
34349
35472
  registerDeploymentCommands(program2);
35473
+ registerOrgCommands(program2);
35474
+ registerUsageCommands(program2);
35475
+ registerMetricsCommands(program2);
34350
35476
  registerDevCommand(program2);
34351
35477
  registerCompletionCommand(program2);
34352
35478
  registerDoctorCommand(program2);