@mcpcloud/cli 0.3.0 → 0.4.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 +749 -288
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4151,9 +4151,83 @@ async function confirmUnset(bindingName, serverId) {
4151
4151
  return ch === "y";
4152
4152
  }
4153
4153
 
4154
+ // src/commands/servers-export.ts
4155
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "node:fs";
4156
+ import { dirname as dirname2, isAbsolute, join as join4, resolve as resolve2 } from "node:path";
4157
+ function formatBytes(bytes) {
4158
+ if (bytes < 1024)
4159
+ return `${bytes} B`;
4160
+ if (bytes < 1024 * 1024)
4161
+ return `${(bytes / 1024).toFixed(1)} KB`;
4162
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
4163
+ }
4164
+ function assertSafeRelativePath(path) {
4165
+ if (isAbsolute(path) || path.split(/[\\/]/).includes("..")) {
4166
+ throw new Error(`Refusing to write unsafe bundle path: ${path}`);
4167
+ }
4168
+ }
4169
+ async function downloadTar(downloadPath) {
4170
+ requireApiKey();
4171
+ const url = new URL(downloadPath, getBaseUrl());
4172
+ const res = await fetch(url.toString(), {
4173
+ headers: { Authorization: `Bearer ${getApiKey()}` }
4174
+ });
4175
+ if (!res.ok) {
4176
+ throw new Error(`Bundle download failed (${res.status}).`);
4177
+ }
4178
+ return new Uint8Array(await res.arrayBuffer());
4179
+ }
4180
+ function registerServerExportCommands(servers) {
4181
+ servers.command("export <serverId>").description("Export a self-host bundle (Dockerfile + node entry + config) for a server").option("--org <organizationId>", "Organization ID").requiredOption("--project <projectId>", "Project ID").option("--output <dir>", "Write the bundle files to a directory (default: ./<slug>)").option("--tar [file]", "Download the bundle as a single .tar instead").addHelpText("after", [
4182
+ "",
4183
+ "Examples:",
4184
+ " # Write the bundle into a directory and run it:",
4185
+ " $ mcp servers export srv_123 --project prj_123 --output ./my-server",
4186
+ " $ cd ./my-server && docker compose up --build",
4187
+ "",
4188
+ " # Download a single .tar:",
4189
+ " $ mcp servers export srv_123 --project prj_123 --tar my-server.tar"
4190
+ ].join(`
4191
+ `)).action(runAction(async (serverId, opts) => {
4192
+ const organizationId = await resolveOrgId(opts.org);
4193
+ const result = await api.post("/api/v1/server/self-host-export", { organizationId, projectId: opts.project, serverId });
4194
+ const summary = {
4195
+ artifactId: result.artifactId,
4196
+ downloadPath: result.downloadPath,
4197
+ fileCount: result.fileCount,
4198
+ byteSize: result.byteSize,
4199
+ server: result.server
4200
+ };
4201
+ if (opts.tar !== undefined) {
4202
+ const tarPath = resolve2(typeof opts.tar === "string" ? opts.tar : `${result.server.slug}.tar`);
4203
+ writeFileSync2(tarPath, await downloadTar(result.downloadPath));
4204
+ if (isJsonMode()) {
4205
+ printJson({ ...summary, output: tarPath });
4206
+ return;
4207
+ }
4208
+ printSuccess(`Downloaded self-host bundle (${result.fileCount} files, ${formatBytes(result.byteSize)}) to ${tarPath}.`);
4209
+ printKeyValue({ Unpack: `tar xf ${tarPath} && docker compose up --build` });
4210
+ return;
4211
+ }
4212
+ const dir = resolve2(opts.output ?? `./${result.server.slug}`);
4213
+ for (const file of result.files) {
4214
+ assertSafeRelativePath(file.path);
4215
+ const full = join4(dir, file.path);
4216
+ mkdirSync3(dirname2(full), { recursive: true });
4217
+ writeFileSync2(full, file.content);
4218
+ }
4219
+ if (isJsonMode()) {
4220
+ printJson({ ...summary, output: dir });
4221
+ return;
4222
+ }
4223
+ printSuccess(`Exported ${result.fileCount} files (${formatBytes(result.byteSize)}) to ${dir}.`);
4224
+ printKeyValue({ "Run it": `cd ${dir} && docker compose up --build` });
4225
+ }));
4226
+ }
4227
+
4154
4228
  // src/commands/servers-lifecycle.ts
4155
4229
  import { existsSync as existsSync5 } from "node:fs";
4156
- import { isAbsolute, resolve as resolve2 } from "node:path";
4230
+ import { isAbsolute as isAbsolute2, resolve as resolve3 } from "node:path";
4157
4231
 
4158
4232
  // src/lib/dev/spec-watcher.ts
4159
4233
  import { createHash as createHash2 } from "node:crypto";
@@ -4162,7 +4236,7 @@ import {
4162
4236
  readFileSync as readFileSync4,
4163
4237
  watch as fsWatch
4164
4238
  } from "node:fs";
4165
- import { dirname as dirname2 } from "node:path";
4239
+ import { dirname as dirname3 } from "node:path";
4166
4240
  async function regenerateDevBundle(args) {
4167
4241
  try {
4168
4242
  const response = await api.post("/api/v1/server/dev-bundle", {
@@ -4221,7 +4295,7 @@ function watchSpecFile(options) {
4221
4295
  let lastHash = null;
4222
4296
  let pendingTimer = null;
4223
4297
  let closed = false;
4224
- const dir = dirname2(options.specPath);
4298
+ const dir = dirname3(options.specPath);
4225
4299
  const baseName = options.specPath.slice(dir.length + 1);
4226
4300
  const fire = () => {
4227
4301
  if (closed)
@@ -4398,7 +4472,7 @@ function registerServerLifecycleCommands(servers) {
4398
4472
  ].join(`
4399
4473
  `)).action(runAction(async (serverId, opts) => {
4400
4474
  const cwd = process.cwd();
4401
- const specPath = isAbsolute(opts.spec) ? opts.spec : resolve2(cwd, opts.spec);
4475
+ const specPath = isAbsolute2(opts.spec) ? opts.spec : resolve3(cwd, opts.spec);
4402
4476
  if (!existsSync5(specPath)) {
4403
4477
  throw new Error(`Spec file not found: ${specPath}`);
4404
4478
  }
@@ -4904,6 +4978,7 @@ function registerServerTestCommands(servers) {
4904
4978
  function registerServerCommands(program2) {
4905
4979
  const servers = program2.command("servers").description("Manage MCP servers");
4906
4980
  registerServerEnvCommands(servers);
4981
+ registerServerExportCommands(servers);
4907
4982
  registerServerLifecycleCommands(servers);
4908
4983
  registerServerMutationCommands(servers);
4909
4984
  registerServerTestCommands(servers);
@@ -5093,31 +5168,35 @@ import { relative } from "node:path";
5093
5168
  // src/lib/dev/handlers-sync.ts
5094
5169
  import {
5095
5170
  existsSync as existsSync8,
5096
- mkdirSync as mkdirSync5,
5171
+ mkdirSync as mkdirSync6,
5097
5172
  readFileSync as readFileSync7,
5098
5173
  statSync,
5099
- writeFileSync as writeFileSync4
5174
+ writeFileSync as writeFileSync5
5100
5175
  } from "node:fs";
5101
5176
  import { createHash as createHash3 } from "node:crypto";
5102
- import { dirname as dirname3, join as join6 } from "node:path";
5177
+ import { dirname as dirname4, join as join7 } from "node:path";
5103
5178
 
5104
5179
  // src/lib/dev/state.ts
5105
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "node:fs";
5106
- import { join as join4, resolve as resolve3 } from "node:path";
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";
5107
5182
  function devRoot(cwd) {
5108
- return join4(cwd, ".mcpcloud");
5183
+ return join5(cwd, ".mcpcloud");
5109
5184
  }
5110
5185
  function stateFile(cwd) {
5111
- return join4(devRoot(cwd), "state.json");
5186
+ return join5(devRoot(cwd), "state.json");
5112
5187
  }
5113
5188
  function serverDir(cwd) {
5114
- return join4(devRoot(cwd), "server");
5189
+ return join5(devRoot(cwd), "server");
5190
+ }
5191
+ function gitCloneDir(cwd, owner, name) {
5192
+ const safe = `${owner}__${name}`.replace(/[^a-zA-Z0-9._-]/g, "-");
5193
+ return join5(devRoot(cwd), "git", safe);
5115
5194
  }
5116
5195
  function envFile(cwd) {
5117
- return join4(devRoot(cwd), "env.json");
5196
+ return join5(devRoot(cwd), "env.json");
5118
5197
  }
5119
5198
  function backupsDir(cwd) {
5120
- return join4(devRoot(cwd), "agent-backups");
5199
+ return join5(devRoot(cwd), "agent-backups");
5121
5200
  }
5122
5201
  function readState(cwd) {
5123
5202
  const path = stateFile(cwd);
@@ -5138,20 +5217,21 @@ function readState(cwd) {
5138
5217
  }
5139
5218
  function writeState(cwd, state) {
5140
5219
  if (!existsSync6(devRoot(cwd))) {
5141
- mkdirSync3(devRoot(cwd), { recursive: true });
5220
+ mkdirSync4(devRoot(cwd), { recursive: true });
5142
5221
  }
5143
- writeFileSync2(stateFile(cwd), JSON.stringify(state, null, 2), "utf-8");
5222
+ writeFileSync3(stateFile(cwd), JSON.stringify(state, null, 2), "utf-8");
5144
5223
  }
5145
5224
  function ensureGitignore(cwd) {
5146
- const ignoreFile = join4(devRoot(cwd), ".gitignore");
5225
+ const ignoreFile = join5(devRoot(cwd), ".gitignore");
5147
5226
  if (existsSync6(ignoreFile))
5148
5227
  return;
5149
5228
  if (!existsSync6(devRoot(cwd))) {
5150
- mkdirSync3(devRoot(cwd), { recursive: true });
5229
+ mkdirSync4(devRoot(cwd), { recursive: true });
5151
5230
  }
5152
- writeFileSync2(ignoreFile, [
5231
+ writeFileSync3(ignoreFile, [
5153
5232
  "# generated by `mcp dev`",
5154
5233
  "server/",
5234
+ "git/",
5155
5235
  "env.json",
5156
5236
  "agent-backups/",
5157
5237
  "inspector/",
@@ -5163,13 +5243,13 @@ function ensureGitignore(cwd) {
5163
5243
  // src/lib/dev/tools-sync.ts
5164
5244
  import {
5165
5245
  existsSync as existsSync7,
5166
- mkdirSync as mkdirSync4,
5246
+ mkdirSync as mkdirSync5,
5167
5247
  readFileSync as readFileSync6,
5168
5248
  readdirSync,
5169
5249
  rmSync,
5170
- writeFileSync as writeFileSync3
5250
+ writeFileSync as writeFileSync4
5171
5251
  } from "node:fs";
5172
- import { join as join5 } from "node:path";
5252
+ import { join as join6 } from "node:path";
5173
5253
  var TOOLS_DIR_NAME = "tools";
5174
5254
  var RISK_CLASSES = [
5175
5255
  "read",
@@ -5178,10 +5258,10 @@ var RISK_CLASSES = [
5178
5258
  "external_effect"
5179
5259
  ];
5180
5260
  function toolsDir(devRootDir) {
5181
- return join5(devRootDir, TOOLS_DIR_NAME);
5261
+ return join6(devRootDir, TOOLS_DIR_NAME);
5182
5262
  }
5183
5263
  function serverToolsDir(devRootDir, serverId) {
5184
- return join5(toolsDir(devRootDir), serverId);
5264
+ return join6(toolsDir(devRootDir), serverId);
5185
5265
  }
5186
5266
  function escapeFrontmatterScalar(raw) {
5187
5267
  if (raw === "")
@@ -5371,7 +5451,7 @@ function arraysEqual(a, b) {
5371
5451
  }
5372
5452
  var STATE_FILE = ".tools-state.json";
5373
5453
  function toolsStateFile(devRootDir, serverId) {
5374
- return join5(serverToolsDir(devRootDir, serverId), STATE_FILE);
5454
+ return join6(serverToolsDir(devRootDir, serverId), STATE_FILE);
5375
5455
  }
5376
5456
  function readToolsState(devRootDir, serverId) {
5377
5457
  const file = toolsStateFile(devRootDir, serverId);
@@ -5393,8 +5473,8 @@ function readToolsState(devRootDir, serverId) {
5393
5473
  function writeToolsState(devRootDir, state) {
5394
5474
  const dir = serverToolsDir(devRootDir, state.serverId);
5395
5475
  if (!existsSync7(dir))
5396
- mkdirSync4(dir, { recursive: true });
5397
- writeFileSync3(toolsStateFile(devRootDir, state.serverId), JSON.stringify(state, null, 2), "utf-8");
5476
+ mkdirSync5(dir, { recursive: true });
5477
+ writeFileSync4(toolsStateFile(devRootDir, state.serverId), JSON.stringify(state, null, 2), "utf-8");
5398
5478
  }
5399
5479
  function hashLocalView(view) {
5400
5480
  return JSON.stringify({
@@ -5407,13 +5487,13 @@ function hashLocalView(view) {
5407
5487
  function materializeTools(args) {
5408
5488
  const dir = serverToolsDir(args.devRootDir, args.serverId);
5409
5489
  if (!existsSync7(dir))
5410
- mkdirSync4(dir, { recursive: true });
5490
+ mkdirSync5(dir, { recursive: true });
5411
5491
  const kept = new Set;
5412
5492
  let filesWritten = 0;
5413
5493
  for (const tool of args.tools) {
5414
5494
  const filename = `${tool.name}.md`;
5415
5495
  kept.add(filename);
5416
- writeFileSync3(join5(dir, filename), renderToolMarkdown(tool), "utf-8");
5496
+ writeFileSync4(join6(dir, filename), renderToolMarkdown(tool), "utf-8");
5417
5497
  filesWritten += 1;
5418
5498
  }
5419
5499
  let filesRemoved = 0;
@@ -5425,7 +5505,7 @@ function materializeTools(args) {
5425
5505
  if (kept.has(entry))
5426
5506
  continue;
5427
5507
  try {
5428
- rmSync(join5(dir, entry), { force: true });
5508
+ rmSync(join6(dir, entry), { force: true });
5429
5509
  filesRemoved += 1;
5430
5510
  } catch {}
5431
5511
  }
@@ -5451,7 +5531,7 @@ function materializeTools(args) {
5451
5531
  return { filesWritten, filesRemoved };
5452
5532
  }
5453
5533
  function readLocalTool(devRootDir, serverId, toolName) {
5454
- const filePath = join5(serverToolsDir(devRootDir, serverId), `${toolName}.md`);
5534
+ const filePath = join6(serverToolsDir(devRootDir, serverId), `${toolName}.md`);
5455
5535
  if (!existsSync7(filePath))
5456
5536
  return null;
5457
5537
  const state = readToolsState(devRootDir, serverId);
@@ -5488,7 +5568,7 @@ async function pushLocalToolEdit(args) {
5488
5568
  };
5489
5569
  }
5490
5570
  if (!local) {
5491
- const filePath = join5(serverToolsDir(args.devRootDir, args.serverId), `${args.toolName}.md`);
5571
+ const filePath = join6(serverToolsDir(args.devRootDir, args.serverId), `${args.toolName}.md`);
5492
5572
  if (!existsSync7(filePath))
5493
5573
  return { kind: "missing-file" };
5494
5574
  return { kind: "unknown-tool" };
@@ -5572,7 +5652,7 @@ function toKebabSlug(value) {
5572
5652
  return value.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() || "generated-mcp-server";
5573
5653
  }
5574
5654
  function serverBundleHandlersDir(devRootDir) {
5575
- return join6(devRootDir, "server", "src", "tools");
5655
+ return join7(devRootDir, "server", "src", "tools");
5576
5656
  }
5577
5657
  function handlerSlugFromWatcherPath(rel) {
5578
5658
  if (!rel)
@@ -5601,7 +5681,7 @@ function hashSource(source) {
5601
5681
  }
5602
5682
  var lastPushedHash = new Map;
5603
5683
  async function pushLocalHandlerEdit(args) {
5604
- const filePath = join6(serverBundleHandlersDir(args.devRootDir), `${args.slug}.ts`);
5684
+ const filePath = join7(serverBundleHandlersDir(args.devRootDir), `${args.slug}.ts`);
5605
5685
  if (!existsSync8(filePath)) {
5606
5686
  return { kind: "missing-file" };
5607
5687
  }
@@ -5753,9 +5833,9 @@ async function resetCloudHandler(args) {
5753
5833
  }
5754
5834
  }
5755
5835
  function writeLocalHandlerFromCloud(args) {
5756
- const filePath = join6(serverBundleHandlersDir(args.devRootDir), `${args.slug}.ts`);
5757
- mkdirSync5(dirname3(filePath), { recursive: true });
5758
- writeFileSync4(filePath, args.content, "utf-8");
5836
+ const filePath = join7(serverBundleHandlersDir(args.devRootDir), `${args.slug}.ts`);
5837
+ mkdirSync6(dirname4(filePath), { recursive: true });
5838
+ writeFileSync5(filePath, args.content, "utf-8");
5759
5839
  lastPushedHash.set(filePath, hashSource(args.content));
5760
5840
  return filePath;
5761
5841
  }
@@ -5764,7 +5844,7 @@ function handlerSlugFromToolName(toolName) {
5764
5844
  }
5765
5845
  var RECENT_EDIT_WINDOW_MS = 5 * 60 * 1000;
5766
5846
  function inspectLocalHandler(args) {
5767
- const filePath = join6(serverBundleHandlersDir(args.devRootDir), `${args.slug}.ts`);
5847
+ const filePath = join7(serverBundleHandlersDir(args.devRootDir), `${args.slug}.ts`);
5768
5848
  if (!existsSync8(filePath))
5769
5849
  return { kind: "no-local-file" };
5770
5850
  const cached2 = lastPushedHash.get(filePath);
@@ -6066,13 +6146,13 @@ function renderBool(value) {
6066
6146
 
6067
6147
  // src/commands/tools-edit.ts
6068
6148
  import { existsSync as existsSync11 } from "node:fs";
6069
- import { join as join8, relative as relative2 } from "node:path";
6149
+ import { join as join9, relative as relative2 } from "node:path";
6070
6150
 
6071
6151
  // src/lib/editor-shell.ts
6072
6152
  import { spawn as spawn2 } from "node:child_process";
6073
6153
  import { existsSync as existsSync10, readFileSync as readFileSync9 } from "node:fs";
6074
6154
  import { platform as platform2 } from "node:os";
6075
- import { delimiter, join as join7 } from "node:path";
6155
+ import { delimiter, join as join8 } from "node:path";
6076
6156
  var DEFAULT_FALLBACKS = ["vi"];
6077
6157
  function isExecutableFile(path) {
6078
6158
  try {
@@ -6093,7 +6173,7 @@ function findOnPath(command) {
6093
6173
  if (!dir)
6094
6174
  continue;
6095
6175
  for (const ext of exts) {
6096
- const candidate = join7(dir, command + ext);
6176
+ const candidate = join8(dir, command + ext);
6097
6177
  if (isExecutableFile(candidate))
6098
6178
  return candidate;
6099
6179
  }
@@ -6114,10 +6194,10 @@ function resolveEditorCommand(args) {
6114
6194
  return { command: DEFAULT_FALLBACKS[0], source: "fallback" };
6115
6195
  }
6116
6196
  function defaultRun(cmd, args) {
6117
- return new Promise((resolve4, reject) => {
6197
+ return new Promise((resolve5, reject) => {
6118
6198
  const child = spawn2(cmd, args, { stdio: "inherit" });
6119
6199
  child.on("error", (err) => reject(err));
6120
- child.on("exit", (code) => resolve4(code ?? 0));
6200
+ child.on("exit", (code) => resolve5(code ?? 0));
6121
6201
  });
6122
6202
  }
6123
6203
  async function runEditor(args) {
@@ -6129,8 +6209,8 @@ async function runEditor(args) {
6129
6209
  };
6130
6210
  }
6131
6211
  const { command } = resolveEditorCommand({ command: args.command });
6132
- const resolve4 = args.resolveCommand ?? findOnPath;
6133
- const resolved = resolve4(command);
6212
+ const resolve5 = args.resolveCommand ?? findOnPath;
6213
+ const resolved = resolve5(command);
6134
6214
  if (!resolved) {
6135
6215
  return {
6136
6216
  ok: false,
@@ -6189,7 +6269,7 @@ async function runToolsEdit(args) {
6189
6269
  if (!ctx)
6190
6270
  return;
6191
6271
  const cwd = process.cwd();
6192
- const filePath = join8(serverToolsDir(devRoot(cwd), ctx.serverId), `${args.toolName}.md`);
6272
+ const filePath = join9(serverToolsDir(devRoot(cwd), ctx.serverId), `${args.toolName}.md`);
6193
6273
  if (!existsSync11(filePath)) {
6194
6274
  printError(`Tool file not found: ${relative2(cwd, filePath)}`);
6195
6275
  printInfo(` ${c.dim("Run")} ${c.bold(`mcp tools pull ${args.toolName}`)} ${c.dim("first to materialize it, or check the name is spelled correctly.")}`);
@@ -7531,13 +7611,13 @@ function registerSkillTestCommands(skills) {
7531
7611
 
7532
7612
  // src/commands/skills.ts
7533
7613
  async function runClaudeMcpAdd(connectionName, mcpUrl) {
7534
- return new Promise((resolve4) => {
7614
+ return new Promise((resolve5) => {
7535
7615
  const child = spawn3("claude", ["mcp", "add", "--transport", "http", connectionName, mcpUrl], {
7536
7616
  stdio: "inherit",
7537
7617
  shell: false
7538
7618
  });
7539
- child.on("error", () => resolve4(127));
7540
- child.on("exit", (code) => resolve4(code ?? 1));
7619
+ child.on("error", () => resolve5(127));
7620
+ child.on("exit", (code) => resolve5(code ?? 1));
7541
7621
  });
7542
7622
  }
7543
7623
  function registerSkillCommands(program2) {
@@ -7854,9 +7934,9 @@ function registerApiKeyCommands(program2) {
7854
7934
 
7855
7935
  // src/commands/config.ts
7856
7936
  import { homedir as homedir2 } from "node:os";
7857
- import { join as join9 } from "node:path";
7937
+ import { join as join10 } from "node:path";
7858
7938
  function configFilePath() {
7859
- return join9(homedir2(), ".mcpcloud", "config.json");
7939
+ return join10(homedir2(), ".mcpcloud", "config.json");
7860
7940
  }
7861
7941
  function previewKey2(key) {
7862
7942
  if (!key)
@@ -8097,12 +8177,12 @@ function registerConfigCommands(program2) {
8097
8177
  }
8098
8178
 
8099
8179
  // src/commands/dev.ts
8100
- import { existsSync as existsSync31 } from "node:fs";
8101
- import { isAbsolute as isAbsolute2, relative as relative7, resolve as resolve6 } from "node:path";
8180
+ import { existsSync as existsSync32 } from "node:fs";
8181
+ import { isAbsolute as isAbsolute3, relative as relative7, resolve as resolve7 } from "node:path";
8102
8182
 
8103
8183
  // src/lib/mcp-invoke.ts
8104
8184
  import { existsSync as existsSync12, readFileSync as readFileSync10 } from "node:fs";
8105
- import { resolve as resolve4 } from "node:path";
8185
+ import { resolve as resolve5 } from "node:path";
8106
8186
  var DEFAULT_TIMEOUT_MS3 = 60000;
8107
8187
  var invokeRequestId = 1;
8108
8188
  async function invokeMcpTool(args) {
@@ -8239,7 +8319,7 @@ async function parseToolArguments(opts) {
8239
8319
  const reader = opts.readStdin ?? defaultStdinReader;
8240
8320
  source = await reader();
8241
8321
  } else if (raw.startsWith("@")) {
8242
- const filePath = resolve4(opts.cwd ?? process.cwd(), raw.slice(1));
8322
+ const filePath = resolve5(opts.cwd ?? process.cwd(), raw.slice(1));
8243
8323
  if (!existsSync12(filePath)) {
8244
8324
  return { ok: false, reason: "file-not-found", path: filePath };
8245
8325
  }
@@ -8320,29 +8400,29 @@ function truncateJsonPreview(value) {
8320
8400
  // src/lib/dev/sessions.ts
8321
8401
  import {
8322
8402
  existsSync as existsSync13,
8323
- mkdirSync as mkdirSync6,
8403
+ mkdirSync as mkdirSync7,
8324
8404
  readFileSync as readFileSync11,
8325
8405
  readdirSync as readdirSync2,
8326
8406
  unlinkSync,
8327
- writeFileSync as writeFileSync5
8407
+ writeFileSync as writeFileSync6
8328
8408
  } from "node:fs";
8329
8409
  import { homedir as homedir3 } from "node:os";
8330
- import { join as join10 } from "node:path";
8410
+ import { join as join11 } from "node:path";
8331
8411
  var SESSIONS_DIR_NAME = "dev-sessions";
8332
8412
  function sessionsDir() {
8333
- return join10(homedir3(), ".mcpcloud", SESSIONS_DIR_NAME);
8413
+ return join11(homedir3(), ".mcpcloud", SESSIONS_DIR_NAME);
8334
8414
  }
8335
8415
  function sessionFile(pid) {
8336
- return join10(sessionsDir(), `${pid}.json`);
8416
+ return join11(sessionsDir(), `${pid}.json`);
8337
8417
  }
8338
8418
  function ensureDir() {
8339
8419
  const dir = sessionsDir();
8340
8420
  if (!existsSync13(dir))
8341
- mkdirSync6(dir, { recursive: true, mode: 448 });
8421
+ mkdirSync7(dir, { recursive: true, mode: 448 });
8342
8422
  }
8343
8423
  function recordSession(record) {
8344
8424
  ensureDir();
8345
- writeFileSync5(sessionFile(record.pid), JSON.stringify(record, null, 2), {
8425
+ writeFileSync6(sessionFile(record.pid), JSON.stringify(record, null, 2), {
8346
8426
  encoding: "utf-8",
8347
8427
  mode: 384
8348
8428
  });
@@ -8363,7 +8443,7 @@ function listSessions() {
8363
8443
  for (const entry of readdirSync2(dir)) {
8364
8444
  if (!entry.endsWith(".json"))
8365
8445
  continue;
8366
- const path = join10(dir, entry);
8446
+ const path = join11(dir, entry);
8367
8447
  try {
8368
8448
  const parsed = JSON.parse(readFileSync11(path, "utf-8"));
8369
8449
  if (typeof parsed.pid !== "number") {
@@ -8558,27 +8638,27 @@ function invokeResultToJson(result) {
8558
8638
  import {
8559
8639
  appendFileSync as appendFileSync2,
8560
8640
  existsSync as existsSync14,
8561
- mkdirSync as mkdirSync7,
8641
+ mkdirSync as mkdirSync8,
8562
8642
  readFileSync as readFileSync12,
8563
8643
  readdirSync as readdirSync3,
8564
8644
  renameSync,
8565
8645
  statSync as statSync2
8566
8646
  } from "node:fs";
8567
- import { join as join11 } from "node:path";
8647
+ import { join as join12 } from "node:path";
8568
8648
  var INSPECTOR_DIRNAME = "inspector";
8569
8649
  var ACTIVE_FILE = "calls.ndjson";
8570
8650
  var ROTATED_PREFIX = "calls.";
8571
8651
  var ROTATED_SUFFIX = ".ndjson";
8572
8652
  var ROTATION_LIMIT_BYTES = 50 * 1024 * 1024;
8573
8653
  function inspectorDir(cwd) {
8574
- return join11(devRoot(cwd), INSPECTOR_DIRNAME);
8654
+ return join12(devRoot(cwd), INSPECTOR_DIRNAME);
8575
8655
  }
8576
8656
  function ensureInspectorDir(dir) {
8577
8657
  if (!existsSync14(dir))
8578
- mkdirSync7(dir, { recursive: true });
8658
+ mkdirSync8(dir, { recursive: true });
8579
8659
  }
8580
8660
  function activeFilePath(dir) {
8581
- return join11(dir, ACTIVE_FILE);
8661
+ return join12(dir, ACTIVE_FILE);
8582
8662
  }
8583
8663
  function isRotatedName(name) {
8584
8664
  return name !== ACTIVE_FILE && name.startsWith(ROTATED_PREFIX) && name.endsWith(ROTATED_SUFFIX);
@@ -8588,7 +8668,7 @@ function listRotatedFiles(dir) {
8588
8668
  return [];
8589
8669
  const rotated = readdirSync3(dir).filter(isRotatedName);
8590
8670
  rotated.sort();
8591
- return rotated.map((n) => join11(dir, n));
8671
+ return rotated.map((n) => join12(dir, n));
8592
8672
  }
8593
8673
  function parseLines(text) {
8594
8674
  if (!text)
@@ -8813,7 +8893,7 @@ import {
8813
8893
  closeSync,
8814
8894
  statSync as statSync3
8815
8895
  } from "node:fs";
8816
- import { join as join12 } from "node:path";
8896
+ import { join as join13 } from "node:path";
8817
8897
  var DEFAULT_INTERVAL_MS = 250;
8818
8898
  var MAX_READ_CHUNK = 64 * 1024;
8819
8899
  function registerDevTailCommand(dev) {
@@ -8822,7 +8902,7 @@ function registerDevTailCommand(dev) {
8822
8902
  async function runDevTail(opts) {
8823
8903
  const cwd = process.cwd();
8824
8904
  const dir = inspectorDir(cwd);
8825
- const activePath = join12(dir, ACTIVE_FILE);
8905
+ const activePath = join13(dir, ACTIVE_FILE);
8826
8906
  const intervalMs = parseInterval(opts.intervalMs);
8827
8907
  if (!existsSync15(dir)) {
8828
8908
  printInfo(`No inspector history yet at ${dir}. Run \`mcp dev\` to start capturing.`);
@@ -8930,7 +9010,7 @@ function emitLines(text, filter) {
8930
9010
  }
8931
9011
  }
8932
9012
  function sleep(ms) {
8933
- return new Promise((resolve5) => setTimeout(resolve5, ms));
9013
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
8934
9014
  }
8935
9015
 
8936
9016
  // ../../node_modules/.bun/@clack+core@1.3.1/node_modules/@clack/core/dist/index.mjs
@@ -9556,6 +9636,24 @@ var Q = class extends m {
9556
9636
  }
9557
9637
  }
9558
9638
  };
9639
+
9640
+ class X extends m {
9641
+ get cursor() {
9642
+ return this.value ? 0 : 1;
9643
+ }
9644
+ get _value() {
9645
+ return this.cursor === 0;
9646
+ }
9647
+ constructor(t) {
9648
+ super(t, false), this.value = !!t.initialValue, this.on("userInput", () => {
9649
+ this.value = this._value;
9650
+ }), this.on("confirm", (s) => {
9651
+ this.output.write(import_sisteransi.cursor.move(0, -1)), this.value = s, this.state = "submit", this.close();
9652
+ }), this.on("cursor", () => {
9653
+ this.value = !this.value;
9654
+ });
9655
+ }
9656
+ }
9559
9657
  var Z = { Y: { type: "year", len: 4 }, M: { type: "month", len: 2 }, D: { type: "day", len: 2 } };
9560
9658
  function P(r) {
9561
9659
  return [...r].map((t) => Z[t]);
@@ -9992,6 +10090,33 @@ var F2 = ({ cursor: t, options: i, style: s, output: r = process.stdout, maxItem
9992
10090
  S2.push(G2);
9993
10091
  return h2 && S2.push(l), S2;
9994
10092
  };
10093
+ var ue = (t) => {
10094
+ const i = t.active ?? "Yes", s = t.inactive ?? "No";
10095
+ return new X({ active: i, inactive: s, signal: t.signal, input: t.input, output: t.output, initialValue: t.initialValue ?? true, render() {
10096
+ const r = t.withGuide ?? h.withGuide, u = `${P2(this.state)} `, n = r ? `${e("gray", $2)} ` : "", a = W(t.output, t.message, n, u), c2 = `${r ? `${e("gray", $2)}
10097
+ ` : ""}${a}
10098
+ `, o = this.value ? i : s;
10099
+ switch (this.state) {
10100
+ case "submit": {
10101
+ const l = r ? `${e("gray", $2)} ` : "";
10102
+ return `${c2}${l}${e("dim", o)}`;
10103
+ }
10104
+ case "cancel": {
10105
+ const l = r ? `${e("gray", $2)} ` : "";
10106
+ return `${c2}${l}${e(["strikethrough", "dim"], o)}${r ? `
10107
+ ${e("gray", $2)}` : ""}`;
10108
+ }
10109
+ default: {
10110
+ const l = r ? `${e("cyan", $2)} ` : "", d = r ? e("cyan", x2) : "";
10111
+ return `${c2}${l}${this.value ? `${e("green", z2)} ${i}` : `${e("dim", U2)} ${e("dim", i)}`}${t.vertical ? r ? `
10112
+ ${e("cyan", $2)} ` : `
10113
+ ` : ` ${e("dim", "/")} `}${this.value ? `${e("dim", U2)} ${e("dim", s)}` : `${e("green", z2)} ${s}`}
10114
+ ${d}
10115
+ `;
10116
+ }
10117
+ }
10118
+ } }).prompt();
10119
+ };
9995
10120
  var me = (t = "", i) => {
9996
10121
  const s = i?.output ?? process.stdout, r = i?.withGuide ?? h.withGuide ? `${e("gray", x2)} ` : "";
9997
10122
  s.write(`${r}${e("red", t)}
@@ -10092,6 +10217,18 @@ async function resolveDevTarget(deps) {
10092
10217
  }
10093
10218
  };
10094
10219
  }
10220
+ async function promptResumeOrSwitch(args) {
10221
+ const label = args.serverName ? `${args.serverName} ${c.dim(`(${args.serverId})`)}` : args.serverId;
10222
+ const resume = await ue({
10223
+ message: `Resume ${label}?`,
10224
+ active: "Resume",
10225
+ inactive: "Pick a different server",
10226
+ initialValue: true
10227
+ });
10228
+ if (q(resume))
10229
+ return "cancel";
10230
+ return resume ? "resume" : "switch";
10231
+ }
10095
10232
  function canRunInteractive() {
10096
10233
  if (isCiMode())
10097
10234
  return false;
@@ -10160,12 +10297,12 @@ async function runInteractiveBootstrap() {
10160
10297
  // src/lib/dev/bundle-sync.ts
10161
10298
  import {
10162
10299
  existsSync as existsSync16,
10163
- mkdirSync as mkdirSync8,
10300
+ mkdirSync as mkdirSync9,
10164
10301
  readFileSync as readFileSync13,
10165
10302
  rmSync as rmSync2,
10166
- writeFileSync as writeFileSync6
10303
+ writeFileSync as writeFileSync7
10167
10304
  } from "node:fs";
10168
- import { dirname as dirname4, join as join13 } from "node:path";
10305
+ import { dirname as dirname5, join as join14 } from "node:path";
10169
10306
  async function resolveServerProject(args) {
10170
10307
  const data = await api.get("/api/v1/server", {
10171
10308
  organizationId: args.organizationId,
@@ -10177,6 +10314,20 @@ async function resolveServerProject(args) {
10177
10314
  version: data.server.version
10178
10315
  };
10179
10316
  }
10317
+ async function resolveServerGitLink(args) {
10318
+ const data = await api.get("/api/v1/server/git-link", {
10319
+ organizationId: args.organizationId,
10320
+ serverId: args.serverId
10321
+ });
10322
+ return data.gitLink ?? null;
10323
+ }
10324
+ async function resolveServerGitPullStatus(args) {
10325
+ const data = await api.get("/api/v1/server/git-sync-status", {
10326
+ organizationId: args.organizationId,
10327
+ serverId: args.serverId
10328
+ });
10329
+ return data.pull ?? null;
10330
+ }
10180
10331
  async function fetchBundle(args) {
10181
10332
  requireApiKey();
10182
10333
  const url = new URL("/api/v1/artifacts/download", getBaseUrl());
@@ -10208,7 +10359,7 @@ async function fetchBundle(args) {
10208
10359
  }
10209
10360
  function materializeBundle(destDir, bundle, options = {}) {
10210
10361
  if (!existsSync16(destDir)) {
10211
- mkdirSync8(destDir, { recursive: true });
10362
+ mkdirSync9(destDir, { recursive: true });
10212
10363
  }
10213
10364
  const preserve = options.preservePaths;
10214
10365
  const kept = new Set;
@@ -10223,9 +10374,9 @@ function materializeBundle(destDir, bundle, options = {}) {
10223
10374
  preservedCount += 1;
10224
10375
  continue;
10225
10376
  }
10226
- const target = join13(destDir, safePath);
10227
- mkdirSync8(dirname4(target), { recursive: true });
10228
- writeFileSync6(target, file.content, "utf-8");
10377
+ const target = join14(destDir, safePath);
10378
+ mkdirSync9(dirname5(target), { recursive: true });
10379
+ writeFileSync7(target, file.content, "utf-8");
10229
10380
  writtenCount += 1;
10230
10381
  }
10231
10382
  let removed = 0;
@@ -10253,7 +10404,7 @@ function pruneStaleFiles(rootDir, currentDir, kept) {
10253
10404
  const { relative: relative6 } = __require("node:path");
10254
10405
  let removed = 0;
10255
10406
  for (const entry of readdirSync4(currentDir)) {
10256
- const abs = join13(currentDir, entry);
10407
+ const abs = join14(currentDir, entry);
10257
10408
  const stat = statSync4(abs);
10258
10409
  if (stat.isDirectory()) {
10259
10410
  removed += pruneStaleFiles(rootDir, abs, kept);
@@ -10274,10 +10425,53 @@ function pruneStaleFiles(rootDir, currentDir, kept) {
10274
10425
  return removed;
10275
10426
  }
10276
10427
 
10428
+ // src/lib/dev/git-pull-poll.ts
10429
+ var DEFAULT_INTERVAL_MS2 = 6000;
10430
+ function reducePollState(baseline, status) {
10431
+ if (!status)
10432
+ return { baseline, nudge: false };
10433
+ const sha = status.lastPulledGitSha;
10434
+ if (baseline === undefined)
10435
+ return { baseline: sha, nudge: false };
10436
+ if (sha && sha !== baseline)
10437
+ return { baseline: sha, nudge: true };
10438
+ return { baseline, nudge: false };
10439
+ }
10440
+ function startGitPullPoll(args) {
10441
+ const intervalMs = args.intervalMs ?? DEFAULT_INTERVAL_MS2;
10442
+ let baseline;
10443
+ let stopped = false;
10444
+ let timer = null;
10445
+ const tick = async () => {
10446
+ if (stopped)
10447
+ return;
10448
+ try {
10449
+ const status = await args.fetchStatus();
10450
+ const next = reducePollState(baseline, status);
10451
+ baseline = next.baseline;
10452
+ if (next.nudge && status)
10453
+ args.onChange(status);
10454
+ } catch (err) {
10455
+ args.onError?.(err);
10456
+ } finally {
10457
+ if (!stopped)
10458
+ timer = setTimeout(() => void tick(), intervalMs);
10459
+ }
10460
+ };
10461
+ tick();
10462
+ return {
10463
+ close() {
10464
+ stopped = true;
10465
+ if (timer)
10466
+ clearTimeout(timer);
10467
+ }
10468
+ };
10469
+ }
10470
+
10277
10471
  // src/lib/dev/editor.ts
10278
10472
  import { spawn as spawn4 } from "node:child_process";
10279
10473
  import { existsSync as existsSync17, statSync as statSync4 } from "node:fs";
10280
- import { delimiter as delimiter2, join as join14 } from "node:path";
10474
+ import { delimiter as delimiter2, join as join15 } from "node:path";
10281
10475
  import { platform as platform3 } from "node:os";
10282
10476
  var TARGET_LABEL = {
10283
10477
  tools: "tool metadata",
@@ -10313,7 +10507,7 @@ function findOnPath2(command) {
10313
10507
  if (!dir)
10314
10508
  continue;
10315
10509
  for (const ext of exts) {
10316
- const candidate = join14(dir, command + ext);
10510
+ const candidate = join15(dir, command + ext);
10317
10511
  if (existsSync17(candidate) && isExecutable(candidate))
10318
10512
  return candidate;
10319
10513
  }
@@ -10595,7 +10789,7 @@ function parseOpenChoice(raw) {
10595
10789
 
10596
10790
  // src/lib/dev/file-watcher.ts
10597
10791
  import { existsSync as existsSync18, statSync as statSync5, watch as fsWatch2 } from "node:fs";
10598
- import { join as join15 } from "node:path";
10792
+ import { join as join16 } from "node:path";
10599
10793
  var DEFAULT_IGNORE = ["node_modules", ".git", "_mcpsh_host.mjs"];
10600
10794
  function watchDir(options) {
10601
10795
  if (!existsSync18(options.rootDir)) {
@@ -10634,7 +10828,7 @@ function watchDir(options) {
10634
10828
  for (const entry of readdirSync4(options.rootDir)) {
10635
10829
  if (ignore.has(entry))
10636
10830
  continue;
10637
- const sub = join15(options.rootDir, entry);
10831
+ const sub = join16(options.rootDir, entry);
10638
10832
  try {
10639
10833
  const stat = statSync5(sub);
10640
10834
  if (!stat.isDirectory())
@@ -10901,7 +11095,7 @@ async function handleOne2(args) {
10901
11095
  // src/lib/dev/local-runtime.ts
10902
11096
  import { spawn as spawn5 } from "node:child_process";
10903
11097
  import { existsSync as existsSync21 } from "node:fs";
10904
- import { join as join16 } from "node:path";
11098
+ import { join as join17 } from "node:path";
10905
11099
  function detectRuntime(preferred = "auto") {
10906
11100
  if (preferred === "bun" || preferred === "node")
10907
11101
  return preferred;
@@ -10938,50 +11132,160 @@ function startRuntime(options) {
10938
11132
  if (child.exitCode !== null)
10939
11133
  return;
10940
11134
  child.kill("SIGTERM");
10941
- await new Promise((resolve5) => {
11135
+ await new Promise((resolve6) => {
10942
11136
  const timer = setTimeout(() => {
10943
11137
  if (child.exitCode === null)
10944
11138
  child.kill("SIGKILL");
10945
- resolve5();
11139
+ resolve6();
10946
11140
  }, 3000);
10947
11141
  child.once("exit", () => {
10948
11142
  clearTimeout(timer);
10949
- resolve5();
11143
+ resolve6();
10950
11144
  });
10951
11145
  });
10952
11146
  }
10953
11147
  };
10954
11148
  }
10955
11149
  async function installDependencies(options) {
10956
- const pkgPath = join16(options.serverDir, "package.json");
11150
+ const pkgPath = join17(options.serverDir, "package.json");
10957
11151
  if (!existsSync21(pkgPath))
10958
11152
  return { ran: false, exitCode: null };
10959
- const nodeModules = join16(options.serverDir, "node_modules");
11153
+ const nodeModules = join17(options.serverDir, "node_modules");
10960
11154
  if (options.skipIfPresent !== false && existsSync21(nodeModules)) {
10961
11155
  return { ran: false, exitCode: 0 };
10962
11156
  }
10963
11157
  options.onStep?.("Installing dependencies (bun install)…");
10964
- return await new Promise((resolve5) => {
11158
+ return await new Promise((resolve6) => {
10965
11159
  const child = spawn5("bun", ["install", "--silent"], {
10966
11160
  cwd: options.serverDir,
10967
11161
  stdio: "inherit"
10968
11162
  });
10969
- child.on("error", () => resolve5({ ran: true, exitCode: 127 }));
10970
- child.on("exit", (code) => resolve5({ ran: true, exitCode: code }));
11163
+ child.on("error", () => resolve6({ ran: true, exitCode: 127 }));
11164
+ child.on("exit", (code) => resolve6({ ran: true, exitCode: code }));
10971
11165
  });
10972
11166
  }
10973
11167
 
10974
11168
  // src/lib/dev/prepare.ts
10975
- import { existsSync as existsSync24, writeFileSync as writeFileSync9 } from "node:fs";
10976
- import { join as join19, relative as relative6 } from "node:path";
11169
+ import { existsSync as existsSync25, writeFileSync as writeFileSync11 } from "node:fs";
11170
+ import { join as join21, relative as relative6 } from "node:path";
11171
+
11172
+ // src/lib/dev/git-clone.ts
11173
+ import { spawnSync } from "node:child_process";
11174
+ import {
11175
+ existsSync as existsSync22,
11176
+ mkdirSync as mkdirSync10,
11177
+ readFileSync as readFileSync14,
11178
+ writeFileSync as writeFileSync8
11179
+ } from "node:fs";
11180
+ import { dirname as dirname6, join as join18 } from "node:path";
11181
+
11182
+ class GitNotAvailableError extends Error {
11183
+ constructor() {
11184
+ super("git is not installed or not on your PATH. Install Git to use Git-native dev mode.");
11185
+ this.name = "GitNotAvailableError";
11186
+ }
11187
+ }
11188
+
11189
+ class GitCloneError extends Error {
11190
+ constructor(message) {
11191
+ super(message);
11192
+ this.name = "GitCloneError";
11193
+ }
11194
+ }
11195
+ var GIT_LOCAL_EXCLUDES = [
11196
+ "_mcpsh_*",
11197
+ "node_modules/",
11198
+ "bun.lock"
11199
+ ];
11200
+ var EXCLUDE_HEADER = "# added by `mcp dev` (Git-native mode)";
11201
+ function gitCloneArgs(httpsCloneUrl, branch, repoDir) {
11202
+ return [
11203
+ "clone",
11204
+ "--branch",
11205
+ branch,
11206
+ "--single-branch",
11207
+ httpsCloneUrl,
11208
+ repoDir
11209
+ ];
11210
+ }
11211
+ function gitFetchArgs(branch) {
11212
+ return ["fetch", "--quiet", "origin", branch];
11213
+ }
11214
+ function mergeLocalExcludes(existing) {
11215
+ const lines = existing.split(`
11216
+ `);
11217
+ const present = new Set(lines.map((l) => l.trim()));
11218
+ const missing = GIT_LOCAL_EXCLUDES.filter((pattern) => !present.has(pattern));
11219
+ if (missing.length === 0)
11220
+ return null;
11221
+ const prefix = existing.length > 0 && !existing.endsWith(`
11222
+ `) ? `
11223
+ ` : "";
11224
+ const block = present.has(EXCLUDE_HEADER) ? missing.join(`
11225
+ `) : [EXCLUDE_HEADER, ...missing].join(`
11226
+ `);
11227
+ return `${existing}${prefix}${block}
11228
+ `;
11229
+ }
11230
+ function runGit(args, cwd) {
11231
+ const res = spawnSync("git", args, {
11232
+ cwd,
11233
+ encoding: "utf-8",
11234
+ stdio: ["inherit", "pipe", "pipe"]
11235
+ });
11236
+ if (res.error && res.error.code === "ENOENT") {
11237
+ throw new GitNotAvailableError;
11238
+ }
11239
+ return {
11240
+ status: res.status ?? 1,
11241
+ stdout: res.stdout ?? "",
11242
+ stderr: res.stderr ?? ""
11243
+ };
11244
+ }
11245
+ function isGitAvailable() {
11246
+ try {
11247
+ return runGit(["--version"]).status === 0;
11248
+ } catch {
11249
+ return false;
11250
+ }
11251
+ }
11252
+ function applyLocalExcludes(repoDir) {
11253
+ const excludePath = join18(repoDir, ".git", "info", "exclude");
11254
+ const existing = existsSync22(excludePath) ? readFileSync14(excludePath, "utf-8") : "";
11255
+ const next = mergeLocalExcludes(existing);
11256
+ if (next === null)
11257
+ return;
11258
+ if (!existsSync22(dirname6(excludePath))) {
11259
+ mkdirSync10(dirname6(excludePath), { recursive: true });
11260
+ }
11261
+ writeFileSync8(excludePath, next, "utf-8");
11262
+ }
11263
+ function ensureClone(opts) {
11264
+ if (!isGitAvailable()) {
11265
+ throw new GitNotAvailableError;
11266
+ }
11267
+ if (existsSync22(join18(opts.repoDir, ".git"))) {
11268
+ opts.onStep?.("Refreshing existing clone (git fetch)…");
11269
+ runGit(gitFetchArgs(opts.branch), opts.repoDir);
11270
+ applyLocalExcludes(opts.repoDir);
11271
+ return { cloned: false };
11272
+ }
11273
+ opts.onStep?.(`Cloning ${opts.httpsCloneUrl} (branch ${opts.branch})…`);
11274
+ const res = runGit(gitCloneArgs(opts.httpsCloneUrl, opts.branch, opts.repoDir));
11275
+ if (res.status !== 0) {
11276
+ throw new GitCloneError(`git clone failed (exit ${res.status}). ${res.stderr.trim() || "Check that you have access to the repository with your local git credentials."}`);
11277
+ }
11278
+ applyLocalExcludes(opts.repoDir);
11279
+ return { cloned: true };
11280
+ }
10977
11281
 
10978
11282
  // src/lib/dev/host-runtime.ts
10979
- import { existsSync as existsSync23, mkdirSync as mkdirSync10, writeFileSync as writeFileSync8 } from "node:fs";
10980
- import { dirname as dirname5, join as join18 } from "node:path";
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";
10981
11285
 
10982
11286
  // src/lib/dev/inspector-module.ts
10983
- import { existsSync as existsSync22, mkdirSync as mkdirSync9, writeFileSync as writeFileSync7 } from "node:fs";
10984
- import { join as join17 } from "node:path";
11287
+ import { existsSync as existsSync23, mkdirSync as mkdirSync11, writeFileSync as writeFileSync9 } from "node:fs";
11288
+ import { join as join19 } from "node:path";
10985
11289
 
10986
11290
  // src/lib/dev/inspector-assets.ts
10987
11291
  var INSPECTOR_HTML = `<!doctype html>
@@ -11482,7 +11786,7 @@ function requireReact_production() {
11482
11786
  react_production.useTransition = function() {
11483
11787
  return ReactSharedInternals.H.useTransition();
11484
11788
  };
11485
- react_production.version = "19.2.6";
11789
+ react_production.version = "19.2.7";
11486
11790
  return react_production;
11487
11791
  }
11488
11792
  var hasRequiredReact;
@@ -11909,7 +12213,7 @@ function requireReactDom_production() {
11909
12213
  reactDom_production.useFormStatus = function() {
11910
12214
  return ReactSharedInternals.H.useHostTransitionStatus();
11911
12215
  };
11912
- reactDom_production.version = "19.2.6";
12216
+ reactDom_production.version = "19.2.7";
11913
12217
  return reactDom_production;
11914
12218
  }
11915
12219
  var hasRequiredReactDom;
@@ -23353,12 +23657,12 @@ function requireReactDomClient_production() {
23353
23657
  }
23354
23658
  };
23355
23659
  var isomorphicReactPackageVersion$jscomp$inline_1840 = React.version;
23356
- if ("19.2.6" !== isomorphicReactPackageVersion$jscomp$inline_1840)
23660
+ if ("19.2.7" !== isomorphicReactPackageVersion$jscomp$inline_1840)
23357
23661
  throw Error(
23358
23662
  formatProdErrorMessage(
23359
23663
  527,
23360
23664
  isomorphicReactPackageVersion$jscomp$inline_1840,
23361
- "19.2.6"
23665
+ "19.2.7"
23362
23666
  )
23363
23667
  );
23364
23668
  ReactDOMSharedInternals.findDOMNode = function(componentOrElement) {
@@ -23376,10 +23680,10 @@ function requireReactDomClient_production() {
23376
23680
  };
23377
23681
  var internals$jscomp$inline_2347 = {
23378
23682
  bundleType: 0,
23379
- version: "19.2.6",
23683
+ version: "19.2.7",
23380
23684
  rendererPackageName: "react-dom",
23381
23685
  currentDispatcherRef: ReactSharedInternals,
23382
- reconcilerVersion: "19.2.6"
23686
+ reconcilerVersion: "19.2.7"
23383
23687
  };
23384
23688
  if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
23385
23689
  var hook$jscomp$inline_2348 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
@@ -23446,7 +23750,7 @@ function requireReactDomClient_production() {
23446
23750
  listenToAllSupportedEvents(container);
23447
23751
  return new ReactDOMHydrationRoot(initialChildren);
23448
23752
  };
23449
- reactDomClient_production.version = "19.2.6";
23753
+ reactDomClient_production.version = "19.2.7";
23450
23754
  return reactDomClient_production;
23451
23755
  }
23452
23756
  var hasRequiredClient;
@@ -26116,10 +26420,10 @@ function buildInspectorModule() {
26116
26420
  `);
26117
26421
  }
26118
26422
  function writeInspectorModule(serverDir2) {
26119
- if (!existsSync22(serverDir2))
26120
- mkdirSync9(serverDir2, { recursive: true });
26121
- const target = join17(serverDir2, INSPECTOR_FILE_NAME);
26122
- writeFileSync7(target, buildInspectorModule(), "utf-8");
26423
+ if (!existsSync23(serverDir2))
26424
+ mkdirSync11(serverDir2, { recursive: true });
26425
+ const target = join19(serverDir2, INSPECTOR_FILE_NAME);
26426
+ writeFileSync9(target, buildInspectorModule(), "utf-8");
26123
26427
  return target;
26124
26428
  }
26125
26429
 
@@ -26298,11 +26602,11 @@ function buildHostScript(options) {
26298
26602
  `);
26299
26603
  }
26300
26604
  function writeHostScript(serverDir2, options) {
26301
- if (!existsSync23(serverDir2))
26302
- mkdirSync10(serverDir2, { recursive: true });
26303
- const target = join18(serverDir2, HOST_FILE_NAME);
26304
- mkdirSync10(dirname5(target), { recursive: true });
26305
- writeFileSync8(target, buildHostScript(options), "utf-8");
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");
26306
26610
  if (options.inspector) {
26307
26611
  writeInspectorModule(serverDir2);
26308
26612
  }
@@ -26314,12 +26618,50 @@ var ENTRY_CANDIDATES = ["src/worker.ts", "src/server.ts", "src/index.ts"];
26314
26618
  async function prepareDev(opts) {
26315
26619
  const cwd = opts.cwd;
26316
26620
  ensureGitignore(cwd);
26317
- const dest = serverDir(cwd);
26621
+ let dest = serverDir(cwd);
26318
26622
  const env = envFile(cwd);
26319
26623
  const backups = backupsDir(cwd);
26320
26624
  const cached2 = readState(cwd);
26321
26625
  let projectId = cached2?.projectId;
26322
- if (!opts.offline) {
26626
+ let serverName = cached2?.serverName;
26627
+ let gitNative = false;
26628
+ let repoDir;
26629
+ const gitLink = opts.offline ? null : await resolveServerGitLink({
26630
+ serverId: opts.serverId,
26631
+ organizationId: opts.organizationId
26632
+ }).catch(() => null);
26633
+ if (gitLink) {
26634
+ gitNative = true;
26635
+ projectId = projectId ?? gitLink.projectId;
26636
+ serverName = `${gitLink.owner}/${gitLink.name}`;
26637
+ repoDir = gitCloneDir(cwd, gitLink.owner, gitLink.name);
26638
+ dest = gitLink.pathPrefix ? join21(repoDir, gitLink.pathPrefix) : repoDir;
26639
+ printStep(`Git-linked server — cloning ${gitLink.owner}/${gitLink.name}@${gitLink.branch}…`);
26640
+ try {
26641
+ const result = ensureClone({
26642
+ repoDir,
26643
+ httpsCloneUrl: gitLink.httpsCloneUrl,
26644
+ branch: gitLink.branch,
26645
+ onStep: (m2) => printStep(m2)
26646
+ });
26647
+ printSuccess(result.cloned ? `Cloned to ${relative6(cwd, repoDir)}` : `Using existing clone at ${relative6(cwd, repoDir)}`);
26648
+ } catch (err) {
26649
+ if (err instanceof GitNotAvailableError) {
26650
+ printError(err.message);
26651
+ } else if (err instanceof GitCloneError) {
26652
+ printError(err.message);
26653
+ } else {
26654
+ printError(`Git clone failed: ${err instanceof Error ? err.message : String(err)}`);
26655
+ }
26656
+ process.exit(1);
26657
+ }
26658
+ if (!existsSync25(env)) {
26659
+ writeFileSync11(env, `{}
26660
+ `, "utf-8");
26661
+ printInfo(` → seeded ${relative6(cwd, env)} (empty; fill secrets here)`);
26662
+ }
26663
+ printInfo(` ${"Open the clone in your editor; commit + push to sync (config under mcpcloud/ is read-only)."}`);
26664
+ } else if (!opts.offline) {
26323
26665
  if (!projectId) {
26324
26666
  printStep(`Looking up server ${opts.serverId}…`);
26325
26667
  const lookup = await resolveServerProject({
@@ -26327,6 +26669,7 @@ async function prepareDev(opts) {
26327
26669
  organizationId: opts.organizationId
26328
26670
  });
26329
26671
  projectId = lookup.projectId;
26672
+ serverName = lookup.serverName;
26330
26673
  printInfo(` → ${lookup.serverName} (project ${projectId})`);
26331
26674
  }
26332
26675
  printStep("Fetching bundle…");
@@ -26337,8 +26680,8 @@ async function prepareDev(opts) {
26337
26680
  });
26338
26681
  const result = materializeBundle(dest, bundle, { prune: true });
26339
26682
  printInfo(` → wrote ${result.filesWritten} files (pruned ${result.filesRemoved})`);
26340
- if (!existsSync24(env)) {
26341
- writeFileSync9(env, `{}
26683
+ if (!existsSync25(env)) {
26684
+ writeFileSync11(env, `{}
26342
26685
  `, "utf-8");
26343
26686
  printInfo(` → seeded ${relative6(cwd, env)} (empty; fill secrets here)`);
26344
26687
  }
@@ -26355,7 +26698,7 @@ async function prepareDev(opts) {
26355
26698
  });
26356
26699
  printInfo(` → wrote ${toolsResult.filesWritten} editable tool file${toolsResult.filesWritten === 1 ? "" : "s"}${toolsResult.filesRemoved > 0 ? ` (pruned ${toolsResult.filesRemoved})` : ""}`);
26357
26700
  } else {
26358
- if (!existsSync24(dest)) {
26701
+ if (!existsSync25(dest)) {
26359
26702
  printError("No cached bundle found. Run once without --offline first.");
26360
26703
  process.exit(1);
26361
26704
  }
@@ -26364,7 +26707,7 @@ async function prepareDev(opts) {
26364
26707
  process.exit(1);
26365
26708
  }
26366
26709
  }
26367
- const entryRel = ENTRY_CANDIDATES.find((c2) => existsSync24(join19(dest, c2)));
26710
+ const entryRel = ENTRY_CANDIDATES.find((c2) => existsSync25(join21(dest, c2)));
26368
26711
  if (!entryRel) {
26369
26712
  printError("Could not locate an entry file (expected src/worker.ts).");
26370
26713
  process.exit(1);
@@ -26385,7 +26728,8 @@ async function prepareDev(opts) {
26385
26728
  bundleVersion: 0,
26386
26729
  lastSyncedAt: Date.now(),
26387
26730
  transports: opts.transports,
26388
- port: opts.port
26731
+ port: opts.port,
26732
+ ...serverName !== undefined ? { serverName } : {}
26389
26733
  };
26390
26734
  writeState(cwd, state);
26391
26735
  const storePath = inspectorDir(cwd);
@@ -26403,7 +26747,9 @@ async function prepareDev(opts) {
26403
26747
  destDir: dest,
26404
26748
  envPath: env,
26405
26749
  backupsPath: backups,
26406
- state
26750
+ state,
26751
+ gitNative,
26752
+ ...repoDir !== undefined ? { repoDir } : {}
26407
26753
  };
26408
26754
  }
26409
26755
 
@@ -26519,73 +26865,73 @@ function createBurstGuard() {
26519
26865
  }
26520
26866
 
26521
26867
  // src/lib/dev/agent-connectors/claude-code.ts
26522
- import { existsSync as existsSync26, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "node:fs";
26868
+ import { existsSync as existsSync27, unlinkSync as unlinkSync2, writeFileSync as writeFileSync13 } from "node:fs";
26523
26869
  import { homedir as homedir4 } from "node:os";
26524
- import { join as join21 } from "node:path";
26870
+ import { join as join23 } from "node:path";
26525
26871
 
26526
26872
  // src/lib/dev/agent-connectors/json-config-utils.ts
26527
- import { existsSync as existsSync25, mkdirSync as mkdirSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "node:fs";
26528
- import { dirname as dirname6, join as join20, basename } from "node:path";
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";
26529
26875
  function readJsonFile(path) {
26530
- if (!existsSync25(path))
26876
+ if (!existsSync26(path))
26531
26877
  return { ok: true, value: {} };
26532
26878
  try {
26533
- const raw = readFileSync14(path, "utf-8");
26879
+ const raw = readFileSync15(path, "utf-8");
26534
26880
  if (!raw.trim())
26535
26881
  return { ok: true, value: {} };
26536
26882
  return { ok: true, value: JSON.parse(raw) };
26537
26883
  } catch {
26538
26884
  try {
26539
- return { ok: false, raw: readFileSync14(path, "utf-8") };
26885
+ return { ok: false, raw: readFileSync15(path, "utf-8") };
26540
26886
  } catch {
26541
26887
  return { ok: false };
26542
26888
  }
26543
26889
  }
26544
26890
  }
26545
26891
  function atomicWriteJson(path, value) {
26546
- mkdirSync11(dirname6(path), { recursive: true });
26892
+ mkdirSync13(dirname8(path), { recursive: true });
26547
26893
  const tmp = `${path}.mcpsh-${process.pid}-${Date.now()}.tmp`;
26548
- writeFileSync10(tmp, JSON.stringify(value, null, 2) + `
26894
+ writeFileSync12(tmp, JSON.stringify(value, null, 2) + `
26549
26895
  `, "utf-8");
26550
26896
  const { renameSync: renameSync2 } = __require("node:fs");
26551
26897
  renameSync2(tmp, path);
26552
26898
  }
26553
26899
  function backupConfig(args) {
26554
- if (!existsSync25(args.configPath)) {
26900
+ if (!existsSync26(args.configPath)) {
26555
26901
  return { backupPath: null, existed: false };
26556
26902
  }
26557
- mkdirSync11(args.backupsDir, { recursive: true });
26903
+ mkdirSync13(args.backupsDir, { recursive: true });
26558
26904
  const filename = `${args.agentId}__${basename(args.configPath)}.bak`;
26559
- const backupPath = join20(args.backupsDir, filename);
26560
- if (!existsSync25(backupPath)) {
26561
- const raw = readFileSync14(args.configPath, "utf-8");
26562
- writeFileSync10(backupPath, raw, "utf-8");
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");
26563
26909
  }
26564
26910
  return { backupPath, existed: true };
26565
26911
  }
26566
26912
  function restoreFromBackup(args) {
26567
26913
  const filename = `${args.agentId}__${basename(args.configPath)}.bak`;
26568
- const backupPath = join20(args.backupsDir, filename);
26569
- if (!existsSync25(backupPath)) {
26914
+ const backupPath = join22(args.backupsDir, filename);
26915
+ if (!existsSync26(backupPath)) {
26570
26916
  return { restored: false };
26571
26917
  }
26572
- const raw = readFileSync14(backupPath, "utf-8");
26573
- mkdirSync11(dirname6(args.configPath), { recursive: true });
26574
- writeFileSync10(args.configPath, raw, "utf-8");
26918
+ const raw = readFileSync15(backupPath, "utf-8");
26919
+ mkdirSync13(dirname8(args.configPath), { recursive: true });
26920
+ writeFileSync12(args.configPath, raw, "utf-8");
26575
26921
  return { restored: true };
26576
26922
  }
26577
26923
 
26578
26924
  // src/lib/dev/agent-connectors/claude-code.ts
26579
26925
  function configPath() {
26580
- return join21(homedir4(), ".claude.json");
26926
+ return join23(homedir4(), ".claude.json");
26581
26927
  }
26582
26928
  function legacyConfigPath() {
26583
- return join21(homedir4(), ".claude", "mcp.json");
26929
+ return join23(homedir4(), ".claude", "mcp.json");
26584
26930
  }
26585
26931
  function resolveConfigPath() {
26586
- if (existsSync26(configPath()))
26932
+ if (existsSync27(configPath()))
26587
26933
  return configPath();
26588
- if (existsSync26(legacyConfigPath()))
26934
+ if (existsSync27(legacyConfigPath()))
26589
26935
  return legacyConfigPath();
26590
26936
  return configPath();
26591
26937
  }
@@ -26595,8 +26941,8 @@ var claudeCodeConnector = {
26595
26941
  hotkey: "c",
26596
26942
  describeLocation: () => resolveConfigPath(),
26597
26943
  async detect() {
26598
- const claudeDir = join21(homedir4(), ".claude");
26599
- if (existsSync26(configPath()) || existsSync26(legacyConfigPath()) || existsSync26(claudeDir)) {
26944
+ const claudeDir = join23(homedir4(), ".claude");
26945
+ if (existsSync27(configPath()) || existsSync27(legacyConfigPath()) || existsSync27(claudeDir)) {
26600
26946
  return { installed: true, note: "Found Claude Code config" };
26601
26947
  }
26602
26948
  return { installed: false };
@@ -26634,7 +26980,7 @@ var claudeCodeConnector = {
26634
26980
  backupsDir: args.backupsDir,
26635
26981
  agentId: this.id
26636
26982
  });
26637
- if (!restored.restored && existsSync26(path)) {
26983
+ if (!restored.restored && existsSync27(path)) {
26638
26984
  const after = readJsonFile(path);
26639
26985
  if (after.ok && after.value && typeof after.value === "object") {
26640
26986
  const root = after.value;
@@ -26644,7 +26990,7 @@ var claudeCodeConnector = {
26644
26990
  try {
26645
26991
  unlinkSync2(path);
26646
26992
  } catch {
26647
- writeFileSync11(path, `{}
26993
+ writeFileSync13(path, `{}
26648
26994
  `, "utf-8");
26649
26995
  }
26650
26996
  }
@@ -26656,16 +27002,16 @@ var claudeCodeConnector = {
26656
27002
 
26657
27003
  // src/lib/dev/agent-connectors/codex.ts
26658
27004
  import {
26659
- existsSync as existsSync27,
26660
- mkdirSync as mkdirSync12,
26661
- readFileSync as readFileSync15,
27005
+ existsSync as existsSync28,
27006
+ mkdirSync as mkdirSync14,
27007
+ readFileSync as readFileSync16,
26662
27008
  unlinkSync as unlinkSync3,
26663
- writeFileSync as writeFileSync12
27009
+ writeFileSync as writeFileSync14
26664
27010
  } from "node:fs";
26665
27011
  import { homedir as homedir5 } from "node:os";
26666
- import { dirname as dirname7, join as join22 } from "node:path";
27012
+ import { dirname as dirname9, join as join24 } from "node:path";
26667
27013
  function configPath2() {
26668
- return join22(homedir5(), ".codex", "config.toml");
27014
+ return join24(homedir5(), ".codex", "config.toml");
26669
27015
  }
26670
27016
  var SECTION_PREFIX = "mcp_servers.";
26671
27017
  function buildSection(name, url) {
@@ -26703,7 +27049,7 @@ var codexConnector = {
26703
27049
  hotkey: "x",
26704
27050
  describeLocation: () => configPath2(),
26705
27051
  async detect() {
26706
- if (existsSync27(join22(homedir5(), ".codex")) || existsSync27(configPath2())) {
27052
+ if (existsSync28(join24(homedir5(), ".codex")) || existsSync28(configPath2())) {
26707
27053
  return { installed: true, note: "Found ~/.codex/" };
26708
27054
  }
26709
27055
  return { installed: false };
@@ -26716,8 +27062,8 @@ var codexConnector = {
26716
27062
  agentId: this.id
26717
27063
  });
26718
27064
  let existing = "";
26719
- if (existsSync27(path)) {
26720
- existing = readFileSync15(path, "utf-8");
27065
+ if (existsSync28(path)) {
27066
+ existing = readFileSync16(path, "utf-8");
26721
27067
  }
26722
27068
  const conflict = findSectionRange(existing, args.name) !== null;
26723
27069
  let next = existing;
@@ -26732,18 +27078,18 @@ var codexConnector = {
26732
27078
  next += `
26733
27079
  `;
26734
27080
  next += buildSection(args.name, args.url);
26735
- mkdirSync12(dirname7(path), { recursive: true });
26736
- writeFileSync12(path, next, "utf-8");
27081
+ mkdirSync14(dirname9(path), { recursive: true });
27082
+ writeFileSync14(path, next, "utf-8");
26737
27083
  return { added: !conflict, conflict };
26738
27084
  },
26739
27085
  async remove(args) {
26740
27086
  const path = configPath2();
26741
- if (existsSync27(path)) {
26742
- const existing = readFileSync15(path, "utf-8");
27087
+ if (existsSync28(path)) {
27088
+ const existing = readFileSync16(path, "utf-8");
26743
27089
  const range = findSectionRange(existing, args.name);
26744
27090
  if (range) {
26745
27091
  const next = existing.slice(0, range.start) + existing.slice(range.end);
26746
- writeFileSync12(path, next, "utf-8");
27092
+ writeFileSync14(path, next, "utf-8");
26747
27093
  }
26748
27094
  }
26749
27095
  const restored = restoreFromBackup({
@@ -26751,8 +27097,8 @@ var codexConnector = {
26751
27097
  backupsDir: args.backupsDir,
26752
27098
  agentId: this.id
26753
27099
  });
26754
- if (!restored.restored && existsSync27(path)) {
26755
- const after = readFileSync15(path, "utf-8");
27100
+ if (!restored.restored && existsSync28(path)) {
27101
+ const after = readFileSync16(path, "utf-8");
26756
27102
  if (!after.trim()) {
26757
27103
  try {
26758
27104
  unlinkSync3(path);
@@ -26764,11 +27110,11 @@ var codexConnector = {
26764
27110
  };
26765
27111
 
26766
27112
  // src/lib/dev/agent-connectors/continue.ts
26767
- import { existsSync as existsSync28, unlinkSync as unlinkSync4, writeFileSync as writeFileSync13 } from "node:fs";
27113
+ import { existsSync as existsSync29, unlinkSync as unlinkSync4, writeFileSync as writeFileSync15 } from "node:fs";
26768
27114
  import { homedir as homedir6 } from "node:os";
26769
- import { join as join23 } from "node:path";
27115
+ import { join as join25 } from "node:path";
26770
27116
  function configPath3() {
26771
- return join23(homedir6(), ".continue", "config.json");
27117
+ return join25(homedir6(), ".continue", "config.json");
26772
27118
  }
26773
27119
  function isContinueServerEntry(value) {
26774
27120
  return Boolean(value && typeof value === "object" && typeof value.name === "string");
@@ -26779,8 +27125,8 @@ var continueConnector = {
26779
27125
  hotkey: "n",
26780
27126
  describeLocation: () => configPath3(),
26781
27127
  async detect() {
26782
- const dir = join23(homedir6(), ".continue");
26783
- if (existsSync28(dir) || existsSync28(configPath3())) {
27128
+ const dir = join25(homedir6(), ".continue");
27129
+ if (existsSync29(dir) || existsSync29(configPath3())) {
26784
27130
  return { installed: true, note: "Found ~/.continue/" };
26785
27131
  }
26786
27132
  return { installed: false };
@@ -26818,7 +27164,7 @@ var continueConnector = {
26818
27164
  backupsDir: args.backupsDir,
26819
27165
  agentId: this.id
26820
27166
  });
26821
- if (!restored.restored && existsSync28(path)) {
27167
+ if (!restored.restored && existsSync29(path)) {
26822
27168
  const after = readJsonFile(path);
26823
27169
  if (after.ok && after.value && typeof after.value === "object") {
26824
27170
  const root = after.value;
@@ -26828,7 +27174,7 @@ var continueConnector = {
26828
27174
  try {
26829
27175
  unlinkSync4(path);
26830
27176
  } catch {
26831
- writeFileSync13(path, `{}
27177
+ writeFileSync15(path, `{}
26832
27178
  `, "utf-8");
26833
27179
  }
26834
27180
  }
@@ -26839,11 +27185,11 @@ var continueConnector = {
26839
27185
  };
26840
27186
 
26841
27187
  // src/lib/dev/agent-connectors/cursor.ts
26842
- import { existsSync as existsSync29, unlinkSync as unlinkSync5, writeFileSync as writeFileSync14 } from "node:fs";
27188
+ import { existsSync as existsSync30, unlinkSync as unlinkSync5, writeFileSync as writeFileSync16 } from "node:fs";
26843
27189
  import { homedir as homedir7 } from "node:os";
26844
- import { join as join24 } from "node:path";
27190
+ import { join as join26 } from "node:path";
26845
27191
  function globalConfigPath() {
26846
- return join24(homedir7(), ".cursor", "mcp.json");
27192
+ return join26(homedir7(), ".cursor", "mcp.json");
26847
27193
  }
26848
27194
  var cursorConnector = {
26849
27195
  id: "cursor",
@@ -26851,9 +27197,9 @@ var cursorConnector = {
26851
27197
  hotkey: "u",
26852
27198
  describeLocation: () => globalConfigPath(),
26853
27199
  async detect() {
26854
- const cursorDir = join24(homedir7(), ".cursor");
26855
- const macAppSupport = join24(homedir7(), "Library", "Application Support", "Cursor");
26856
- if (existsSync29(cursorDir) || existsSync29(macAppSupport)) {
27200
+ const cursorDir = join26(homedir7(), ".cursor");
27201
+ const macAppSupport = join26(homedir7(), "Library", "Application Support", "Cursor");
27202
+ if (existsSync30(cursorDir) || existsSync30(macAppSupport)) {
26857
27203
  return { installed: true, note: "Found Cursor config dir" };
26858
27204
  }
26859
27205
  return { installed: false };
@@ -26891,7 +27237,7 @@ var cursorConnector = {
26891
27237
  backupsDir: args.backupsDir,
26892
27238
  agentId: this.id
26893
27239
  });
26894
- if (!restored.restored && existsSync29(path)) {
27240
+ if (!restored.restored && existsSync30(path)) {
26895
27241
  const after = readJsonFile(path);
26896
27242
  if (after.ok && after.value && typeof after.value === "object") {
26897
27243
  const root = after.value;
@@ -26901,7 +27247,7 @@ var cursorConnector = {
26901
27247
  try {
26902
27248
  unlinkSync5(path);
26903
27249
  } catch {
26904
- writeFileSync14(path, `{}
27250
+ writeFileSync16(path, `{}
26905
27251
  `, "utf-8");
26906
27252
  }
26907
27253
  }
@@ -26912,30 +27258,30 @@ var cursorConnector = {
26912
27258
  };
26913
27259
 
26914
27260
  // src/lib/dev/agent-connectors/vscode-copilot.ts
26915
- import { existsSync as existsSync30, unlinkSync as unlinkSync6, writeFileSync as writeFileSync15 } from "node:fs";
27261
+ import { existsSync as existsSync31, unlinkSync as unlinkSync6, writeFileSync as writeFileSync17 } from "node:fs";
26916
27262
  import { homedir as homedir8, platform as platform4 } from "node:os";
26917
- import { join as join25, resolve as resolve5 } from "node:path";
27263
+ import { join as join27, resolve as resolve6 } from "node:path";
26918
27264
  function userLevelConfigPath() {
26919
27265
  const home = homedir8();
26920
27266
  const p2 = platform4();
26921
27267
  if (p2 === "darwin")
26922
- return join25(home, "Library", "Application Support", "Code", "User", "mcp.json");
27268
+ return join27(home, "Library", "Application Support", "Code", "User", "mcp.json");
26923
27269
  if (p2 === "win32") {
26924
- const appData = process.env["APPDATA"] ?? join25(home, "AppData", "Roaming");
26925
- return join25(appData, "Code", "User", "mcp.json");
27270
+ const appData = process.env["APPDATA"] ?? join27(home, "AppData", "Roaming");
27271
+ return join27(appData, "Code", "User", "mcp.json");
26926
27272
  }
26927
- const xdg = process.env["XDG_CONFIG_HOME"] ?? join25(home, ".config");
26928
- return join25(xdg, "Code", "User", "mcp.json");
27273
+ const xdg = process.env["XDG_CONFIG_HOME"] ?? join27(home, ".config");
27274
+ return join27(xdg, "Code", "User", "mcp.json");
26929
27275
  }
26930
27276
  function isHomeDir(cwd) {
26931
- return resolve5(cwd) === resolve5(homedir8());
27277
+ return resolve6(cwd) === resolve6(homedir8());
26932
27278
  }
26933
27279
  function hasWorkspaceVscode(cwd) {
26934
- return existsSync30(join25(cwd, ".vscode"));
27280
+ return existsSync31(join27(cwd, ".vscode"));
26935
27281
  }
26936
27282
  function configPath4(cwd) {
26937
27283
  if (!isHomeDir(cwd) && hasWorkspaceVscode(cwd)) {
26938
- return join25(cwd, ".vscode", "mcp.json");
27284
+ return join27(cwd, ".vscode", "mcp.json");
26939
27285
  }
26940
27286
  return userLevelConfigPath();
26941
27287
  }
@@ -26949,7 +27295,7 @@ var vscodeCopilotConnector = {
26949
27295
  if (!isHomeDir(here) && hasWorkspaceVscode(here)) {
26950
27296
  return { installed: true, note: "Found .vscode/ in current dir" };
26951
27297
  }
26952
- if (existsSync30(userLevelConfigPath().replace(/mcp\.json$/, ""))) {
27298
+ if (existsSync31(userLevelConfigPath().replace(/mcp\.json$/, ""))) {
26953
27299
  return { installed: true, note: "Found VS Code user profile" };
26954
27300
  }
26955
27301
  return { installed: false };
@@ -26987,7 +27333,7 @@ var vscodeCopilotConnector = {
26987
27333
  backupsDir: args.backupsDir,
26988
27334
  agentId: this.id
26989
27335
  });
26990
- if (!restored.restored && existsSync30(path)) {
27336
+ if (!restored.restored && existsSync31(path)) {
26991
27337
  const after = readJsonFile(path);
26992
27338
  if (after.ok && after.value && typeof after.value === "object") {
26993
27339
  const root = after.value;
@@ -26997,7 +27343,7 @@ var vscodeCopilotConnector = {
26997
27343
  try {
26998
27344
  unlinkSync6(path);
26999
27345
  } catch {
27000
- writeFileSync15(path, `{}
27346
+ writeFileSync17(path, `{}
27001
27347
  `, "utf-8");
27002
27348
  }
27003
27349
  }
@@ -27141,7 +27487,7 @@ function parseGraceMs(value, fallbackMs) {
27141
27487
  return Math.round(n * 1000);
27142
27488
  }
27143
27489
  function resolveSpecPath(cwd, raw) {
27144
- return isAbsolute2(raw) ? raw : resolve6(cwd, raw);
27490
+ return isAbsolute3(raw) ? raw : resolve7(cwd, raw);
27145
27491
  }
27146
27492
  function buildConnectionName(serverId) {
27147
27493
  const slug = serverId.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 24) || "mcp-server";
@@ -27149,6 +27495,16 @@ function buildConnectionName(serverId) {
27149
27495
  }
27150
27496
  function registerDevCommand(program2) {
27151
27497
  const dev = program2.command("dev").description("Run a deployed MCP server locally with hot reload + agent auto-wire").option("--server <serverId>", "Server ID (default: from .mcpcloud/state.json)").option("--org <organizationId>", "Organization ID (overrides default)").option("--port <number>", "Local port (default 8787)").option("--transport <list>", "Comma-separated: http,sse,stdio (default http,sse)").option("--stdio", "Shorthand for --transport stdio").option("--offline", "Use cached bundle, skip Convex sync").option("--no-watch", "Disable file watcher").option("--no-connect", "Disable agent auto-wire prompts").option("--auto-connect <agent>", "Auto-wire the named agent (claude-code, cursor, codex, vscode-copilot, continue)").option("--no-inspector", "Disable the inspector UI (default: enabled at /__inspect)").option("--spec <path>", "Watch this OpenAPI spec file and regenerate the bundle on change").option("--no-auto-regen", "Show spec drift but do not auto-apply (manual file edit still works)").option("--regen-grace <seconds>", "Grace period before auto-applying spec changes (default 5)").option("--enrich-on-change", "Preserve per-tool enrichment when the spec changes").option("--open <choice>", "Open an editor on startup: yes | always | no | never | ask").option("--open-target <choice>", "What to open: tools (.mcpcloud/tools/<server>/) | bundle (read-only .mcpcloud/server/) | all (both). When omitted on first run, mcp dev prompts.").option("--editor <command>", "Editor binary for this invocation (e.g. cursor, code, windsurf). Overrides saved editorCommand and the multi-editor picker.").option("--verbose", "Verbose logs").action(runAction(runDev));
27498
+ dev.addHelpText("after", `
27499
+ Git-native mode (automatic):
27500
+ If the chosen server is linked to a GitHub repo, mcp dev clones it under
27501
+ .mcpcloud/git/ and runs against the working copy — no flag needed. Edit in
27502
+ your own editor, then commit + push: the webhook reconciles src/** back into
27503
+ MCPCloud. The CLI's tool/handler PATCH watchers are off in this mode (native
27504
+ git is the version control). Config under mcpcloud/ is a read-only mirror —
27505
+ change it in the dashboard. Deploy when ready with \`mcp deploy\` or the UI.
27506
+ An unlinked server uses the default flow (download bundle, edit via dashboard).
27507
+ `);
27152
27508
  dev.command("list").description("List running mcp dev sessions on this machine").action(runAction(runDevList));
27153
27509
  dev.command("kill <pid>").description('Stop a running mcp dev session by PID (or "all" to stop every session)').action(runAction(runDevKill));
27154
27510
  dev.command("init").description("Interactively pick an organization + server and write .mcpcloud/state.json (forces the picker even if state already exists)").action(runAction(runDevInit));
@@ -27178,7 +27534,8 @@ async function runDevInit() {
27178
27534
  bundleVersion: 0,
27179
27535
  lastSyncedAt: 0,
27180
27536
  transports: ["http", "sse"],
27181
- port: 8787
27537
+ port: 8787,
27538
+ serverName: result.target.serverName
27182
27539
  });
27183
27540
  printSuccess(`Saved .mcpcloud/state.json — run ${c.bold("mcp dev")} to start.`);
27184
27541
  }
@@ -27234,6 +27591,21 @@ async function runDev(opts) {
27234
27591
  const cached2 = readState(cwd);
27235
27592
  let serverId = opts.server ?? cached2?.serverId;
27236
27593
  let organizationId = opts.org ?? cached2?.organizationId ?? undefined;
27594
+ if (!opts.server && cached2?.serverId && canRunInteractive()) {
27595
+ const choice = await promptResumeOrSwitch({
27596
+ serverId: cached2.serverId,
27597
+ ...cached2.serverName !== undefined ? { serverName: cached2.serverName } : {}
27598
+ });
27599
+ if (choice === "cancel")
27600
+ process.exit(1);
27601
+ if (choice === "switch") {
27602
+ const result = await runInteractiveBootstrap();
27603
+ if (result.kind !== "resolved")
27604
+ process.exit(1);
27605
+ serverId = result.target.serverId;
27606
+ organizationId = result.target.organizationId;
27607
+ }
27608
+ }
27237
27609
  if (!serverId) {
27238
27610
  if (!canRunInteractive()) {
27239
27611
  printError("No server selected. Pass --server <serverId>, run `mcp dev init` to pick one interactively, or run from a directory with .mcpcloud/state.json.");
@@ -27274,7 +27646,7 @@ async function runDev(opts) {
27274
27646
  let specPath = null;
27275
27647
  if (opts.spec) {
27276
27648
  const resolved = resolveSpecPath(cwd, opts.spec);
27277
- if (!existsSync31(resolved)) {
27649
+ if (!existsSync32(resolved)) {
27278
27650
  printError(`Spec file not found: ${resolved}`);
27279
27651
  process.exit(1);
27280
27652
  }
@@ -27303,7 +27675,8 @@ async function runDev(opts) {
27303
27675
  enrichOnChange,
27304
27676
  openChoice,
27305
27677
  openTargetOverride,
27306
- editorOverride
27678
+ editorOverride,
27679
+ gitNative: prepared.gitNative
27307
27680
  });
27308
27681
  }
27309
27682
  async function emitStaleHandlerOverrideWarning(args) {
@@ -27338,6 +27711,21 @@ async function runWithRuntime(args) {
27338
27711
  }
27339
27712
  });
27340
27713
  runtime = launch();
27714
+ let fullShutdownReady = false;
27715
+ const killRuntimeNow = () => {
27716
+ try {
27717
+ runtime?.child.kill("SIGKILL");
27718
+ } catch {}
27719
+ };
27720
+ process.once("exit", killRuntimeNow);
27721
+ const earlySignal = (signal) => {
27722
+ if (fullShutdownReady)
27723
+ return;
27724
+ killRuntimeNow();
27725
+ process.exit(signal === "SIGINT" ? 130 : 143);
27726
+ };
27727
+ process.on("SIGINT", () => earlySignal("SIGINT"));
27728
+ process.on("SIGTERM", () => earlySignal("SIGTERM"));
27341
27729
  const baseUrl = `http://127.0.0.1:${args.port}`;
27342
27730
  const mcpUrl = `${baseUrl}/mcp`;
27343
27731
  const connectionName = buildConnectionName(args.serverId);
@@ -27354,7 +27742,14 @@ async function runWithRuntime(args) {
27354
27742
  });
27355
27743
  wired.push(...selected);
27356
27744
  }
27357
- if (args.transports[0] !== "stdio") {
27745
+ if (args.transports[0] !== "stdio" && args.gitNative) {
27746
+ await maybeOpenEditor({
27747
+ dir: args.prepared.repoDir ?? args.prepared.destDir,
27748
+ target: "bundle",
27749
+ override: args.openChoice,
27750
+ editorOverride: args.editorOverride
27751
+ });
27752
+ } else if (args.transports[0] !== "stdio") {
27358
27753
  const savedTarget = readConfig().editorOpenTarget;
27359
27754
  let target = resolveOpenTarget({
27360
27755
  override: args.openTargetOverride,
@@ -27405,11 +27800,16 @@ async function runWithRuntime(args) {
27405
27800
  const watcher = args.watch ? watchDir({
27406
27801
  rootDir: args.prepared.destDir,
27407
27802
  debounceMs: 250,
27408
- ignore: ["node_modules", "_mcpsh_host.mjs", ".git"],
27803
+ ignore: [
27804
+ "node_modules",
27805
+ "_mcpsh_host.mjs",
27806
+ "_mcpsh_inspector.mjs",
27807
+ ".git"
27808
+ ],
27409
27809
  onChange: async (path) => {
27410
27810
  if (shuttingDown)
27411
27811
  return;
27412
- if (shouldWarnAboutBundleEdit({
27812
+ if (!args.gitNative && shouldWarnAboutBundleEdit({
27413
27813
  changedPath: path,
27414
27814
  alreadyWarned: bundleEditWarned,
27415
27815
  suppressed: suppressBundleWarn,
@@ -27427,14 +27827,14 @@ async function runWithRuntime(args) {
27427
27827
  runtime = launch();
27428
27828
  }
27429
27829
  }) : null;
27430
- const toolsWatcher = args.watch ? startToolsWatcher({
27830
+ const toolsWatcher = args.watch && !args.gitNative ? startToolsWatcher({
27431
27831
  cwd: args.cwd,
27432
27832
  serverId: args.serverId,
27433
27833
  organizationId: args.organizationId,
27434
27834
  projectId: args.prepared.state.projectId,
27435
27835
  devBaseUrl: args.inspector ? baseUrl : undefined
27436
27836
  }) : null;
27437
- const handlersWatcher = args.watch ? startHandlersWatcher({
27837
+ const handlersWatcher = args.watch && !args.gitNative ? startHandlersWatcher({
27438
27838
  cwd: args.cwd,
27439
27839
  serverId: args.serverId,
27440
27840
  organizationId: args.organizationId,
@@ -27443,7 +27843,7 @@ async function runWithRuntime(args) {
27443
27843
  let specWatcher = null;
27444
27844
  let pendingApply = null;
27445
27845
  let regenInFlight = false;
27446
- if (args.specPath) {
27846
+ if (args.specPath && !args.gitNative) {
27447
27847
  const initial = readSpecFile(args.specPath);
27448
27848
  let lastAppliedHash = initial.hash;
27449
27849
  specWatcher = watchSpecFile({
@@ -27524,20 +27924,60 @@ async function runWithRuntime(args) {
27524
27924
  printInfo(` ${c.dim("Inspector: ")} ${c.cyan(`${baseUrl}/__inspect`)}`);
27525
27925
  }
27526
27926
  printInfo(` ${c.dim(`Edit files in ${relative7(args.cwd, args.prepared.destDir)}/ to hot-reload.`)}`);
27927
+ if (args.gitNative && args.prepared.repoDir) {
27928
+ printInfo(` ${c.dim(`Git-native: edit + commit + push in ${relative7(args.cwd, args.prepared.repoDir)}/ — the webhook reconciles src/**.`)}`);
27929
+ printInfo(` ${c.dim("Config under mcpcloud/ is read-only; change it in the dashboard. Deploy when ready (mcp deploy).")}`);
27930
+ }
27527
27931
  if (toolsWatcher) {
27528
27932
  printInfo(` ${c.dim(`Edit tool metadata in .mcpcloud/tools/${args.serverId}/ (changes push to cloud on save).`)}`);
27529
27933
  }
27530
27934
  if (handlersWatcher) {
27531
27935
  printInfo(` ${c.dim(`Edit tool handlers in .mcpcloud/server/src/tools/<slug>.ts (changes push to cloud on save).`)}`);
27532
27936
  }
27533
- if (args.specPath) {
27937
+ if (specWatcher) {
27534
27938
  printInfo(` ${c.dim(`Spec watch: ${relative7(args.cwd, args.specPath)}`)}`);
27535
27939
  }
27536
- await emitStaleHandlerOverrideWarning({
27537
- organizationId: args.organizationId,
27538
- projectId: args.prepared.state.projectId,
27539
- serverId: args.serverId
27540
- });
27940
+ const gitPullPoller = args.gitNative ? startGitPullPoll({
27941
+ fetchStatus: () => resolveServerGitPullStatus({
27942
+ serverId: args.serverId,
27943
+ organizationId: args.organizationId
27944
+ }),
27945
+ onChange: (status) => {
27946
+ if (shuttingDown)
27947
+ return;
27948
+ const sha = status.lastPulledGitSha ? status.lastPulledGitSha.slice(0, 7) : "HEAD";
27949
+ if (status.rejectedFileCount > 0) {
27950
+ printWarn(`Push ${sha}: MCPCloud rejected ${status.rejectedFileCount} file${status.rejectedFileCount === 1 ? "" : "s"} (unsafe handler code).`);
27951
+ if (status.error)
27952
+ printInfo(` ${c.dim(status.error)}`);
27953
+ printInfo(` ${c.dim("Fix the flagged file(s) and push again.")}`);
27954
+ return;
27955
+ }
27956
+ if (status.status === "error") {
27957
+ printWarn(`Push ${sha}: MCPCloud could not reconcile — ${status.error ?? "unknown error"}.`);
27958
+ return;
27959
+ }
27960
+ if (status.status !== "pulled") {
27961
+ printInfo(`Push ${sha}: ${status.hadConfigDrift ? "touched the read-only mcpcloud/ config — ignored." : "touched only generated files outside src/** — nothing synced."}`);
27962
+ printInfo(` ${c.dim("Only src/ round-trips; edit there to sync. README / mcpcloud/ are generated.")}`);
27963
+ return;
27964
+ }
27965
+ const parts = [`${status.pulledFileCount} updated`];
27966
+ if (status.deletedFileCount > 0) {
27967
+ parts.push(`${status.deletedFileCount} deleted`);
27968
+ }
27969
+ printSuccess(`Push ${sha} reconciled (${parts.join(", ")}) — changes ready for deployment.`);
27970
+ printInfo(` ${c.dim("Deploy with")} ${c.bold("mcp deploy")} ${c.dim("or the dashboard — no preview needed, this session is your local server.")}`);
27971
+ },
27972
+ onError: () => {}
27973
+ }) : null;
27974
+ if (!args.gitNative) {
27975
+ await emitStaleHandlerOverrideWarning({
27976
+ organizationId: args.organizationId,
27977
+ projectId: args.prepared.state.projectId,
27978
+ serverId: args.serverId
27979
+ });
27980
+ }
27541
27981
  printInfo(` ${c.dim("Ctrl+C to stop.")}`);
27542
27982
  if (isJsonMode()) {
27543
27983
  printJson({
@@ -27576,6 +28016,9 @@ async function runWithRuntime(args) {
27576
28016
  try {
27577
28017
  specWatcher?.close();
27578
28018
  } catch {}
28019
+ try {
28020
+ gitPullPoller?.close();
28021
+ } catch {}
27579
28022
  if (pendingApply) {
27580
28023
  clearTimeout(pendingApply);
27581
28024
  pendingApply = null;
@@ -27595,6 +28038,7 @@ async function runWithRuntime(args) {
27595
28038
  printSuccess("Bye.");
27596
28039
  process.exit(0);
27597
28040
  };
28041
+ fullShutdownReady = true;
27598
28042
  process.on("SIGINT", () => void shutdown("SIGINT"));
27599
28043
  process.on("SIGTERM", () => void shutdown("SIGTERM"));
27600
28044
  shortcuts = startShortcuts({
@@ -28018,13 +28462,13 @@ async function confirmRollback(deploymentId) {
28018
28462
  }
28019
28463
 
28020
28464
  // src/commands/doctor.ts
28021
- import { existsSync as existsSync33, statSync as statSync6, readFileSync as readFileSync17 } from "node:fs";
28465
+ import { existsSync as existsSync34, statSync as statSync6, readFileSync as readFileSync18 } from "node:fs";
28022
28466
  import { homedir as homedir9, platform as platform6 } from "node:os";
28023
- import { join as join27, delimiter as delimiter3 } from "node:path";
28467
+ import { join as join29, delimiter as delimiter3 } from "node:path";
28024
28468
 
28025
28469
  // src/lib/version-check.ts
28026
- import { existsSync as existsSync32, mkdirSync as mkdirSync13, readFileSync as readFileSync16, writeFileSync as writeFileSync16 } from "node:fs";
28027
- import { join as join26 } from "node:path";
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";
28028
28472
  var CACHE_TTL_MS = 24 * 60 * 60 * 1000;
28029
28473
  var FETCH_TIMEOUT_MS2 = 2000;
28030
28474
  var REGISTRY_URL = "https://registry.npmjs.org/@mcpcloud/cli/latest";
@@ -28032,13 +28476,13 @@ function cacheDir() {
28032
28476
  return configDir();
28033
28477
  }
28034
28478
  function cacheFile() {
28035
- return join26(cacheDir(), "version-check.json");
28479
+ return join28(cacheDir(), "version-check.json");
28036
28480
  }
28037
28481
  function readCache() {
28038
- if (!existsSync32(cacheFile()))
28482
+ if (!existsSync33(cacheFile()))
28039
28483
  return null;
28040
28484
  try {
28041
- const raw = JSON.parse(readFileSync16(cacheFile(), "utf-8"));
28485
+ const raw = JSON.parse(readFileSync17(cacheFile(), "utf-8"));
28042
28486
  if (typeof raw.latest !== "string" || typeof raw.fetchedAt !== "number")
28043
28487
  return null;
28044
28488
  return { latest: raw.latest, fetchedAt: raw.fetchedAt };
@@ -28048,9 +28492,9 @@ function readCache() {
28048
28492
  }
28049
28493
  function writeCache(entry) {
28050
28494
  try {
28051
- if (!existsSync32(cacheDir()))
28052
- mkdirSync13(cacheDir(), { recursive: true, mode: 448 });
28053
- writeFileSync16(cacheFile(), JSON.stringify(entry, null, 2), { mode: 384 });
28495
+ if (!existsSync33(cacheDir()))
28496
+ mkdirSync15(cacheDir(), { recursive: true, mode: 448 });
28497
+ writeFileSync18(cacheFile(), JSON.stringify(entry, null, 2), { mode: 384 });
28054
28498
  } catch {}
28055
28499
  }
28056
28500
  function compareVersions(a, b) {
@@ -28161,8 +28605,8 @@ function checkNode() {
28161
28605
  }
28162
28606
  function checkConfigFile() {
28163
28607
  const t0 = Date.now();
28164
- const path = join27(homedir9(), ".mcpcloud", "config.json");
28165
- if (!existsSync33(path)) {
28608
+ const path = join29(homedir9(), ".mcpcloud", "config.json");
28609
+ if (!existsSync34(path)) {
28166
28610
  return {
28167
28611
  name: "Config file",
28168
28612
  status: "warn",
@@ -28184,7 +28628,7 @@ function checkConfigFile() {
28184
28628
  } catch {}
28185
28629
  }
28186
28630
  try {
28187
- JSON.parse(readFileSync17(path, "utf-8"));
28631
+ JSON.parse(readFileSync18(path, "utf-8"));
28188
28632
  } catch (err) {
28189
28633
  return {
28190
28634
  name: "Config file",
@@ -28319,8 +28763,8 @@ function checkClaudeCli() {
28319
28763
  for (const dir of PATH.split(delimiter3)) {
28320
28764
  if (!dir)
28321
28765
  continue;
28322
- const candidate = join27(dir, exe);
28323
- if (existsSync33(candidate)) {
28766
+ const candidate = join29(dir, exe);
28767
+ if (existsSync34(candidate)) {
28324
28768
  return {
28325
28769
  name: "claude CLI",
28326
28770
  status: "pass",
@@ -28659,10 +29103,10 @@ function prompt2(question) {
28659
29103
  throw new Error("Interactive prompts are disabled in CI mode. Pass --org / --project / --name explicitly or run `mcp init` outside CI.");
28660
29104
  }
28661
29105
  const rl = createInterface2({ input: process.stdin, output: process.stderr });
28662
- return new Promise((resolve7) => {
29106
+ return new Promise((resolve8) => {
28663
29107
  rl.question(question, (answer) => {
28664
29108
  rl.close();
28665
- resolve7(answer.trim());
29109
+ resolve8(answer.trim());
28666
29110
  });
28667
29111
  });
28668
29112
  }
@@ -29261,38 +29705,38 @@ function registerOAuthCommands(program2) {
29261
29705
  // src/lib/plugins.ts
29262
29706
  import { spawn as spawn7 } from "node:child_process";
29263
29707
  import {
29264
- existsSync as existsSync34,
29265
- mkdirSync as mkdirSync14,
29708
+ existsSync as existsSync35,
29709
+ mkdirSync as mkdirSync16,
29266
29710
  readdirSync as readdirSync4,
29267
- readFileSync as readFileSync18,
29711
+ readFileSync as readFileSync19,
29268
29712
  rmSync as rmSync3,
29269
29713
  statSync as statSync7
29270
29714
  } from "node:fs";
29271
- import { join as join28 } from "node:path";
29715
+ import { join as join30 } from "node:path";
29272
29716
  import { pathToFileURL } from "node:url";
29273
29717
  function pluginsDir() {
29274
- return join28(configDir(), "plugins");
29718
+ return join30(configDir(), "plugins");
29275
29719
  }
29276
29720
  function ensureDir2() {
29277
29721
  const dir = pluginsDir();
29278
- if (!existsSync34(dir))
29279
- mkdirSync14(dir, { recursive: true, mode: 448 });
29722
+ if (!existsSync35(dir))
29723
+ mkdirSync16(dir, { recursive: true, mode: 448 });
29280
29724
  }
29281
29725
  function readManifestFromPackageDir(packageDir) {
29282
- const pkgPath = join28(packageDir, "package.json");
29283
- if (!existsSync34(pkgPath))
29726
+ const pkgPath = join30(packageDir, "package.json");
29727
+ if (!existsSync35(pkgPath))
29284
29728
  return null;
29285
29729
  let pkg;
29286
29730
  try {
29287
- pkg = JSON.parse(readFileSync18(pkgPath, "utf-8"));
29731
+ pkg = JSON.parse(readFileSync19(pkgPath, "utf-8"));
29288
29732
  } catch {
29289
29733
  return null;
29290
29734
  }
29291
29735
  if (!pkg.name)
29292
29736
  return null;
29293
29737
  const relEntry = pkg.mcpsh?.register ?? pkg.main ?? "index.js";
29294
- const entry = join28(packageDir, relEntry);
29295
- if (!existsSync34(entry))
29738
+ const entry = join30(packageDir, relEntry);
29739
+ if (!existsSync35(entry))
29296
29740
  return null;
29297
29741
  return {
29298
29742
  name: pkg.name,
@@ -29303,11 +29747,11 @@ function readManifestFromPackageDir(packageDir) {
29303
29747
  }
29304
29748
  function listInstalledPlugins() {
29305
29749
  const dir = pluginsDir();
29306
- if (!existsSync34(dir))
29750
+ if (!existsSync35(dir))
29307
29751
  return [];
29308
29752
  const out = [];
29309
29753
  for (const entry of readdirSync4(dir)) {
29310
- const full = join28(dir, entry);
29754
+ const full = join30(dir, entry);
29311
29755
  let s;
29312
29756
  try {
29313
29757
  s = statSync7(full);
@@ -29318,7 +29762,7 @@ function listInstalledPlugins() {
29318
29762
  continue;
29319
29763
  if (entry.startsWith("@")) {
29320
29764
  for (const child of readdirSync4(full)) {
29321
- const m2 = readManifestFromPackageDir(join28(full, child));
29765
+ const m2 = readManifestFromPackageDir(join30(full, child));
29322
29766
  if (m2)
29323
29767
  out.push(m2);
29324
29768
  }
@@ -29351,26 +29795,43 @@ async function loadPlugins(program2) {
29351
29795
  }
29352
29796
  }
29353
29797
  }
29354
- function runShell(command, cwd) {
29355
- return new Promise((resolve7) => {
29356
- const child = spawn7(command, { shell: true, cwd, stdio: "inherit" });
29357
- child.on("close", (code) => resolve7(code ?? 1));
29358
- child.on("error", () => resolve7(1));
29798
+ var PACKAGE_SPEC_PATTERN = /^(@[a-z0-9~-][\w.~-]*\/)?[a-z0-9~-][\w.~-]*(@[-\w.^~><=*+|]+)?$/i;
29799
+ function runNpm(args, cwd) {
29800
+ return new Promise((resolve8) => {
29801
+ const isWindows = process.platform === "win32";
29802
+ const child = spawn7(isWindows ? "npm.cmd" : "npm", args, {
29803
+ shell: isWindows,
29804
+ cwd,
29805
+ stdio: "inherit"
29806
+ });
29807
+ child.on("close", (code) => resolve8(code ?? 1));
29808
+ child.on("error", () => resolve8(1));
29359
29809
  });
29360
29810
  }
29361
29811
  async function installPlugin(packageSpec) {
29812
+ if (!PACKAGE_SPEC_PATTERN.test(packageSpec)) {
29813
+ throw new Error(`"${packageSpec}" is not a plain name@version npm package spec. URL, git, and path specs are not supported for plugins.`);
29814
+ }
29362
29815
  ensureDir2();
29363
29816
  const dir = pluginsDir();
29364
- const code = await runShell(`npm install --prefix "${dir}" --no-save --silent ${JSON.stringify(packageSpec)}`, dir);
29817
+ const code = await runNpm([
29818
+ "install",
29819
+ "--prefix",
29820
+ dir,
29821
+ "--no-save",
29822
+ "--silent",
29823
+ "--ignore-scripts",
29824
+ packageSpec
29825
+ ], dir);
29365
29826
  if (code !== 0) {
29366
29827
  throw new Error(`npm install ${packageSpec} exited with code ${code}.`);
29367
29828
  }
29368
- const nm = join28(dir, "node_modules");
29369
- if (!existsSync34(nm)) {
29829
+ const nm = join30(dir, "node_modules");
29830
+ if (!existsSync35(nm)) {
29370
29831
  throw new Error(`npm install ran but produced no node_modules under ${dir}.`);
29371
29832
  }
29372
29833
  const baseName = packageSpec.replace(/@[^@/]+$/, "");
29373
- const candidatePath = baseName.startsWith("@") ? join28(nm, baseName.split("/")[0], baseName.split("/")[1] ?? "") : join28(nm, baseName);
29834
+ const candidatePath = baseName.startsWith("@") ? join30(nm, baseName.split("/")[0], baseName.split("/")[1] ?? "") : join30(nm, baseName);
29374
29835
  const manifest = readManifestFromPackageDir(candidatePath);
29375
29836
  if (!manifest) {
29376
29837
  throw new Error(`Installed but could not read plugin manifest at ${candidatePath}.`);
@@ -29378,26 +29839,26 @@ async function installPlugin(packageSpec) {
29378
29839
  return { name: manifest.name, entry: manifest.entry };
29379
29840
  }
29380
29841
  function removePlugin(name) {
29381
- const nm = join28(pluginsDir(), "node_modules");
29382
- if (!existsSync34(nm))
29842
+ const nm = join30(pluginsDir(), "node_modules");
29843
+ if (!existsSync35(nm))
29383
29844
  return false;
29384
- const target = name.startsWith("@") ? join28(nm, name.split("/")[0], name.split("/")[1] ?? "") : join28(nm, name);
29385
- if (!existsSync34(target))
29845
+ const target = name.startsWith("@") ? join30(nm, name.split("/")[0], name.split("/")[1] ?? "") : join30(nm, name);
29846
+ if (!existsSync35(target))
29386
29847
  return false;
29387
29848
  rmSync3(target, { recursive: true, force: true });
29388
29849
  return true;
29389
29850
  }
29390
29851
  function listInstalledPluginsCombined() {
29391
29852
  const direct = listInstalledPlugins();
29392
- const nm = join28(pluginsDir(), "node_modules");
29393
- if (!existsSync34(nm))
29853
+ const nm = join30(pluginsDir(), "node_modules");
29854
+ if (!existsSync35(nm))
29394
29855
  return direct;
29395
29856
  const seen = new Set(direct.map((p2) => p2.name));
29396
29857
  const out = [...direct];
29397
29858
  for (const entry of readdirSync4(nm)) {
29398
29859
  if (entry === ".bin" || entry === ".package-lock.json")
29399
29860
  continue;
29400
- const full = join28(nm, entry);
29861
+ const full = join30(nm, entry);
29401
29862
  let s;
29402
29863
  try {
29403
29864
  s = statSync7(full);
@@ -29408,7 +29869,7 @@ function listInstalledPluginsCombined() {
29408
29869
  continue;
29409
29870
  if (entry.startsWith("@")) {
29410
29871
  for (const child of readdirSync4(full)) {
29411
- const m2 = readManifestFromPackageDir(join28(full, child));
29872
+ const m2 = readManifestFromPackageDir(join30(full, child));
29412
29873
  if (m2 && !seen.has(m2.name)) {
29413
29874
  seen.add(m2.name);
29414
29875
  out.push(m2);
@@ -29864,7 +30325,7 @@ function clipboardCandidatesFor(platform7, waylandDisplay) {
29864
30325
  return linux;
29865
30326
  }
29866
30327
  async function defaultIsExecutable(binary) {
29867
- return await new Promise((resolve7) => {
30328
+ return await new Promise((resolve8) => {
29868
30329
  const isWin = process.platform === "win32";
29869
30330
  const cmd = isWin ? "where" : "command";
29870
30331
  const args = isWin ? [binary] : ["-v", binary];
@@ -29872,8 +30333,8 @@ async function defaultIsExecutable(binary) {
29872
30333
  shell: !isWin,
29873
30334
  stdio: "ignore"
29874
30335
  });
29875
- child.on("error", () => resolve7(false));
29876
- child.on("exit", (code) => resolve7(code === 0));
30336
+ child.on("error", () => resolve8(false));
30337
+ child.on("exit", (code) => resolve8(code === 0));
29877
30338
  });
29878
30339
  }
29879
30340
  async function detectClipboard(probe) {
@@ -29895,7 +30356,7 @@ async function copyToClipboard(text, probe) {
29895
30356
  };
29896
30357
  }
29897
30358
  const spawnImpl = probe?.spawnImpl ?? spawn8;
29898
- return await new Promise((resolve7) => {
30359
+ return await new Promise((resolve8) => {
29899
30360
  const child = spawnImpl(candidate.binary, candidate.args, {
29900
30361
  stdio: ["pipe", "ignore", "pipe"]
29901
30362
  });
@@ -29904,7 +30365,7 @@ async function copyToClipboard(text, probe) {
29904
30365
  stderr += chunk.toString("utf-8");
29905
30366
  });
29906
30367
  child.on("error", (err) => {
29907
- resolve7({
30368
+ resolve8({
29908
30369
  kind: "failed",
29909
30370
  binary: candidate.binary,
29910
30371
  message: err instanceof Error ? err.message : String(err)
@@ -29912,9 +30373,9 @@ async function copyToClipboard(text, probe) {
29912
30373
  });
29913
30374
  child.on("exit", (code) => {
29914
30375
  if (code === 0) {
29915
- resolve7({ kind: "ok", binary: candidate.binary });
30376
+ resolve8({ kind: "ok", binary: candidate.binary });
29916
30377
  } else {
29917
- resolve7({
30378
+ resolve8({
29918
30379
  kind: "failed",
29919
30380
  binary: candidate.binary,
29920
30381
  message: stderr.trim() || `${candidate.binary} exited with status ${code ?? "?"}`
@@ -29924,7 +30385,7 @@ async function copyToClipboard(text, probe) {
29924
30385
  try {
29925
30386
  child.stdin?.end(text);
29926
30387
  } catch (err) {
29927
- resolve7({
30388
+ resolve8({
29928
30389
  kind: "failed",
29929
30390
  binary: candidate.binary,
29930
30391
  message: err instanceof Error ? err.message : String(err)
@@ -30994,12 +31455,12 @@ function displayValueFor(field, value) {
30994
31455
 
30995
31456
  // src/lib/tui/state-store.ts
30996
31457
  import {
30997
- existsSync as existsSync35,
30998
- mkdirSync as mkdirSync15,
30999
- readFileSync as readFileSync19,
31000
- writeFileSync as writeFileSync17
31458
+ existsSync as existsSync36,
31459
+ mkdirSync as mkdirSync17,
31460
+ readFileSync as readFileSync20,
31461
+ writeFileSync as writeFileSync19
31001
31462
  } from "node:fs";
31002
- import { join as join29 } from "node:path";
31463
+ import { join as join31 } from "node:path";
31003
31464
  var ALL_TABS = [
31004
31465
  "servers",
31005
31466
  "projects",
@@ -31009,7 +31470,7 @@ var ALL_TABS = [
31009
31470
  "devSessions"
31010
31471
  ];
31011
31472
  function tuiStateFile() {
31012
- return join29(configDir(), "tui-state.json");
31473
+ return join31(configDir(), "tui-state.json");
31013
31474
  }
31014
31475
  function isTab(v2) {
31015
31476
  return typeof v2 === "string" && ALL_TABS.includes(v2);
@@ -31074,11 +31535,11 @@ function sanitizeTuiState(input) {
31074
31535
  }
31075
31536
  function readTuiState() {
31076
31537
  const path = tuiStateFile();
31077
- if (!existsSync35(path))
31538
+ if (!existsSync36(path))
31078
31539
  return {};
31079
31540
  let parsed;
31080
31541
  try {
31081
- parsed = JSON.parse(readFileSync19(path, "utf-8"));
31542
+ parsed = JSON.parse(readFileSync20(path, "utf-8"));
31082
31543
  } catch {
31083
31544
  return {};
31084
31545
  }
@@ -31086,10 +31547,10 @@ function readTuiState() {
31086
31547
  }
31087
31548
  function writeTuiState(state) {
31088
31549
  try {
31089
- if (!existsSync35(configDir())) {
31090
- mkdirSync15(configDir(), { recursive: true, mode: 448 });
31550
+ if (!existsSync36(configDir())) {
31551
+ mkdirSync17(configDir(), { recursive: true, mode: 448 });
31091
31552
  }
31092
- writeFileSync17(tuiStateFile(), JSON.stringify(state, null, 2), {
31553
+ writeFileSync19(tuiStateFile(), JSON.stringify(state, null, 2), {
31093
31554
  encoding: "utf-8",
31094
31555
  mode: 384
31095
31556
  });
@@ -33094,7 +33555,7 @@ function registerUiCommand(program2) {
33094
33555
  }
33095
33556
  return false;
33096
33557
  };
33097
- await new Promise((resolve7) => {
33558
+ await new Promise((resolve8) => {
33098
33559
  const flushAndExit = () => {
33099
33560
  try {
33100
33561
  if (persistTimer) {
@@ -33104,7 +33565,7 @@ function registerUiCommand(program2) {
33104
33565
  writeTuiState(snapshotForPersist());
33105
33566
  } catch {}
33106
33567
  shutdown(handles);
33107
- resolve7();
33568
+ resolve8();
33108
33569
  };
33109
33570
  const executePaletteCommand = (id) => {
33110
33571
  if (id.startsWith("tab:")) {
@@ -33713,7 +34174,7 @@ function registerUiCommand(program2) {
33713
34174
 
33714
34175
  // src/commands/update.ts
33715
34176
  import { spawn as spawn9 } from "node:child_process";
33716
- import { dirname as dirname8 } from "node:path";
34177
+ import { dirname as dirname10 } from "node:path";
33717
34178
  import { fileURLToPath as fileURLToPath2 } from "node:url";
33718
34179
  function detectInstallContext() {
33719
34180
  const override = process.env["MCPSH_PACKAGE_MANAGER"]?.trim().toLowerCase();
@@ -33726,7 +34187,7 @@ function detectInstallContext() {
33726
34187
  }
33727
34188
  const here = (() => {
33728
34189
  try {
33729
- return dirname8(fileURLToPath2(import.meta.url));
34190
+ return dirname10(fileURLToPath2(import.meta.url));
33730
34191
  } catch {
33731
34192
  return process.argv[1] ?? "";
33732
34193
  }
@@ -33773,11 +34234,11 @@ function buildCommand(manager) {
33773
34234
  return "npm install -g @mcpcloud/cli@latest";
33774
34235
  }
33775
34236
  }
33776
- function runShell2(command) {
33777
- return new Promise((resolve7) => {
34237
+ function runShell(command) {
34238
+ return new Promise((resolve8) => {
33778
34239
  const child = spawn9(command, { shell: true, stdio: "inherit" });
33779
- child.on("close", (code) => resolve7(code ?? 1));
33780
- child.on("error", () => resolve7(1));
34240
+ child.on("close", (code) => resolve8(code ?? 1));
34241
+ child.on("error", () => resolve8(1));
33781
34242
  });
33782
34243
  }
33783
34244
  function registerUpdateCommand(program2) {
@@ -33821,7 +34282,7 @@ function registerUpdateCommand(program2) {
33821
34282
  const ctx = detectInstallContext();
33822
34283
  printStep(`Running: ${c.cyan(ctx.command)}`);
33823
34284
  printInfo(c.dim(` (${ctx.rationale}; override with MCPSH_PACKAGE_MANAGER)`));
33824
- const code = await runShell2(ctx.command);
34285
+ const code = await runShell(ctx.command);
33825
34286
  if (code !== 0) {
33826
34287
  if (isJsonMode())
33827
34288
  printJson({