spacecode 2.5.5 → 2.5.7

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.
package/dist/repl.mjs CHANGED
@@ -4988,6 +4988,76 @@ var init_skill_file = __esm({
4988
4988
  }
4989
4989
  });
4990
4990
 
4991
+ // ../agent/src/import/claude-json-mcp.ts
4992
+ function normalizeClaudeJsonMcp(text, projectPaths) {
4993
+ const errors = [];
4994
+ let parsed;
4995
+ try {
4996
+ parsed = JSON.parse(text);
4997
+ } catch (err) {
4998
+ if (!(err instanceof SyntaxError)) throw err;
4999
+ return { servers: [], errors: ["not valid JSON"] };
5000
+ }
5001
+ const root = asObject(parsed) ?? {};
5002
+ const serverMap = /* @__PURE__ */ new Map();
5003
+ const processEntry = (name, raw, scope) => {
5004
+ const entry = asObject(raw);
5005
+ if (!entry) {
5006
+ errors.push(`${name}: not an object`);
5007
+ return;
5008
+ }
5009
+ const hasUrl = typeof entry.url === "string";
5010
+ const hasCommand = typeof entry.command === "string";
5011
+ let transport;
5012
+ let url = "";
5013
+ let command = "";
5014
+ let args = [];
5015
+ if (hasUrl && !hasCommand) {
5016
+ transport = "http";
5017
+ url = entry.url;
5018
+ } else if (hasCommand) {
5019
+ transport = "stdio";
5020
+ command = entry.command;
5021
+ if (Array.isArray(entry.args)) args = entry.args.filter((a) => typeof a === "string");
5022
+ } else {
5023
+ errors.push(`${name}: needs a command or a url`);
5024
+ return;
5025
+ }
5026
+ const rawEnv = transport === "http" ? asObject(entry.headers) : asObject(entry.env);
5027
+ const env3 = {};
5028
+ const literalKeys = /* @__PURE__ */ new Set();
5029
+ if (rawEnv) {
5030
+ for (const [k2, v2] of Object.entries(rawEnv)) {
5031
+ if (typeof v2 !== "string") continue;
5032
+ if (v2.includes("${")) {
5033
+ env3[k2] = v2;
5034
+ } else {
5035
+ env3[k2] = "${" + literalVarName(name, k2) + "}";
5036
+ literalKeys.add(k2);
5037
+ }
5038
+ }
5039
+ }
5040
+ serverMap.set(name, { name, scope, transport, command, args, url, env: env3, literalKeys: [...literalKeys].sort() });
5041
+ };
5042
+ for (const [name, raw] of Object.entries(asObject(root.mcpServers) ?? {})) processEntry(name, raw, "user");
5043
+ const projects = asObject(root.projects) ?? {};
5044
+ for (const p of projectPaths) {
5045
+ const servers = asObject(asObject(projects[p])?.mcpServers);
5046
+ if (!servers) continue;
5047
+ for (const [name, raw] of Object.entries(servers)) processEntry(name, raw, "project");
5048
+ }
5049
+ return { servers: [...serverMap.values()].sort((a, b2) => a.name < b2.name ? -1 : a.name > b2.name ? 1 : 0), errors };
5050
+ }
5051
+ var sanitize, literalVarName, asObject;
5052
+ var init_claude_json_mcp = __esm({
5053
+ "../agent/src/import/claude-json-mcp.ts"() {
5054
+ "use strict";
5055
+ sanitize = (s) => s.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
5056
+ literalVarName = (server, key) => `MCP_${sanitize(server)}_${sanitize(key)}`;
5057
+ asObject = (v2) => v2 && typeof v2 === "object" && !Array.isArray(v2) ? v2 : void 0;
5058
+ }
5059
+ });
5060
+
4991
5061
  // ../agent/src/import/tool-names.ts
4992
5062
  function translateToolNames(text) {
4993
5063
  const counts = /* @__PURE__ */ new Map();
@@ -5169,6 +5239,7 @@ async function readClaudeAssets({ fs: fs6, home, projectDirs }) {
5169
5239
  }
5170
5240
  await readCommands(fs6, home, report);
5171
5241
  await readSkills(fs6, home, report);
5242
+ await readMcpServers(fs6, home, projectDirs, report);
5172
5243
  for (const dir of projectDirs) {
5173
5244
  const memDir = `${home}/.claude/projects/${claudeProjectSlug(dir)}/memory`;
5174
5245
  let entries;
@@ -5311,12 +5382,51 @@ async function readSkills(fs6, home, report) {
5311
5382
  report.assets.push({ kind: "skill", name: parsed.name, description: parsed.description, content: text, source: skillPath }, ...siblings);
5312
5383
  }
5313
5384
  }
5385
+ async function readMcpServers(fs6, home, projectDirs, report) {
5386
+ const path12 = `${home}/.claude.json`;
5387
+ const text = await readOptional(fs6, path12, report);
5388
+ if (text === null) return;
5389
+ const { servers, errors } = normalizeClaudeJsonMcp(text, projectDirs);
5390
+ for (const e of errors) report.errors.push({ path: path12, message: e });
5391
+ const seen = /* @__PURE__ */ new Set();
5392
+ for (const s of servers) {
5393
+ if (!NAME_RE3.test(s.name)) {
5394
+ report.errors.push({ path: path12, message: `MCP server name "${s.name}" must use only letters, digits, . _ - (max 100)` });
5395
+ continue;
5396
+ }
5397
+ if (seen.has(s.name.toLowerCase())) {
5398
+ report.errors.push({ path: path12, message: `MCP server name collision on "${s.name}" (names are case-insensitive)` });
5399
+ continue;
5400
+ }
5401
+ seen.add(s.name.toLowerCase());
5402
+ const { literalKeys, ...def } = s;
5403
+ const content = JSON.stringify(def, null, 2);
5404
+ if (Buffer.byteLength(content, "utf8") > MAX_ASSET_BYTES) {
5405
+ report.errors.push({ path: path12, message: `MCP server "${s.name}" skipped: over the ${MAX_ASSET_BYTES} byte asset limit` });
5406
+ continue;
5407
+ }
5408
+ if (literalKeys.length > 0) {
5409
+ report.notes.push(`MCP server "${s.name}": literal values for ${literalKeys.join(", ")} were NOT imported; set ${literalKeys.map((k2) => literalVarName(s.name, k2)).join(", ")} in your environment`);
5410
+ }
5411
+ if (s.transport === "http" && /[?@]/.test(s.url.replace(/^https?:\/\//, ""))) {
5412
+ report.notes.push(`MCP server "${s.name}": its url contains a query string or credentials, which are stored as written; move any token into a header or environment variable`);
5413
+ }
5414
+ report.assets.push({
5415
+ kind: "mcp",
5416
+ name: s.name,
5417
+ description: s.transport === "stdio" ? `stdio: ${s.command}` : `http: ${s.url}`,
5418
+ content,
5419
+ source: `${path12} (${s.scope})`
5420
+ });
5421
+ }
5422
+ }
5314
5423
  var MAX_ASSET_BYTES, NAME_RE3, COMMAND_NAME_RE, SKILL_NAME_RE, MAX_SKILL_FILES;
5315
5424
  var init_claude_sources = __esm({
5316
5425
  "../agent/src/import/claude-sources.ts"() {
5317
5426
  "use strict";
5318
5427
  init_command_file();
5319
5428
  init_skill_file();
5429
+ init_claude_json_mcp();
5320
5430
  init_skill_source();
5321
5431
  MAX_ASSET_BYTES = 256 * 1024;
5322
5432
  NAME_RE3 = /^[A-Za-z0-9._-]{1,100}$/;
@@ -6103,6 +6213,45 @@ var init_skill_prompt = __esm({
6103
6213
  }
6104
6214
  });
6105
6215
 
6216
+ // ../agent/src/import/mcp-json-summary.ts
6217
+ function listMcpSpawns(text) {
6218
+ const result = [];
6219
+ let data;
6220
+ try {
6221
+ data = JSON.parse(text);
6222
+ } catch {
6223
+ return result;
6224
+ }
6225
+ if (typeof data !== "object" || data === null) return result;
6226
+ const mcpServers = data["mcpServers"];
6227
+ if (typeof mcpServers !== "object" || mcpServers === null) return result;
6228
+ for (const [name, value] of Object.entries(mcpServers)) {
6229
+ if (typeof value !== "object" || value === null) continue;
6230
+ const entry = value;
6231
+ const command = entry["command"];
6232
+ if (typeof command !== "string") continue;
6233
+ const argsRaw = entry["args"];
6234
+ const args = Array.isArray(argsRaw) ? argsRaw.filter((a) => typeof a === "string") : [];
6235
+ const envRaw = entry["env"];
6236
+ const envNames = typeof envRaw === "object" && envRaw !== null ? Object.keys(envRaw).sort() : [];
6237
+ result.push({ name, command, args, envNames });
6238
+ }
6239
+ result.sort((a, b2) => a.name.localeCompare(b2.name));
6240
+ return result;
6241
+ }
6242
+ var init_mcp_json_summary = __esm({
6243
+ "../agent/src/import/mcp-json-summary.ts"() {
6244
+ "use strict";
6245
+ }
6246
+ });
6247
+
6248
+ // ../agent/src/import/mcp-env-expand.ts
6249
+ var init_mcp_env_expand = __esm({
6250
+ "../agent/src/import/mcp-env-expand.ts"() {
6251
+ "use strict";
6252
+ }
6253
+ });
6254
+
6106
6255
  // ../agent/src/index.ts
6107
6256
  var init_src = __esm({
6108
6257
  "../agent/src/index.ts"() {
@@ -6151,6 +6300,9 @@ var init_src = __esm({
6151
6300
  init_skill_prompt();
6152
6301
  init_skill_tool();
6153
6302
  init_skill_source();
6303
+ init_mcp_json_summary();
6304
+ init_mcp_env_expand();
6305
+ init_claude_json_mcp();
6154
6306
  }
6155
6307
  });
6156
6308
 
@@ -6192,6 +6344,16 @@ var init_api_key = __esm({
6192
6344
  });
6193
6345
 
6194
6346
  // src/import-gate.ts
6347
+ var import_gate_exports = {};
6348
+ __export(import_gate_exports, {
6349
+ REPO_SOURCE_FILES: () => REPO_SOURCE_FILES,
6350
+ createNodeTrustStore: () => createNodeTrustStore,
6351
+ importGateStatus: () => importGateStatus,
6352
+ nodeTrustFs: () => nodeTrustFs,
6353
+ readRepoSources: () => readRepoSources,
6354
+ sha256: () => sha256,
6355
+ writeTextAtomic: () => writeTextAtomic
6356
+ });
6195
6357
  import { createHash, randomUUID } from "node:crypto";
6196
6358
  import { chmod, mkdir, readFile, readdir, realpath, rename, writeFile } from "node:fs/promises";
6197
6359
  import { dirname as dirname3, join as join2 } from "node:path";
@@ -6260,7 +6422,12 @@ function nodeTrustFs() {
6260
6422
  return { readText: readOrNull, writeTextAtomic, realPath: (p) => realpath(p) };
6261
6423
  }
6262
6424
  function createNodeTrustStore(configDir) {
6263
- return new TrustStore({ fs: nodeTrustFs(), path: join2(configDir, "trust.json"), hash: sha256 });
6425
+ let store = trustStores.get(configDir);
6426
+ if (!store) {
6427
+ store = new TrustStore({ fs: nodeTrustFs(), path: join2(configDir, "trust.json"), hash: sha256 });
6428
+ trustStores.set(configDir, store);
6429
+ }
6430
+ return store;
6264
6431
  }
6265
6432
  async function importGateStatus(deps) {
6266
6433
  const lines2 = [];
@@ -6289,7 +6456,7 @@ async function importGateStatus(deps) {
6289
6456
  }
6290
6457
  return lines2;
6291
6458
  }
6292
- var REPO_SOURCE_FILES, MAX_REPO_SKILL_FILES, MAX_REPO_SKILL_BYTES, sha256;
6459
+ var REPO_SOURCE_FILES, MAX_REPO_SKILL_FILES, MAX_REPO_SKILL_BYTES, sha256, trustStores;
6293
6460
  var init_import_gate = __esm({
6294
6461
  "src/import-gate.ts"() {
6295
6462
  "use strict";
@@ -6298,6 +6465,7 @@ var init_import_gate = __esm({
6298
6465
  MAX_REPO_SKILL_FILES = 200;
6299
6466
  MAX_REPO_SKILL_BYTES = 256 * 1024;
6300
6467
  sha256 = (text) => createHash("sha256").update(text).digest("hex");
6468
+ trustStores = /* @__PURE__ */ new Map();
6301
6469
  }
6302
6470
  });
6303
6471
 
@@ -6706,6 +6874,86 @@ var init_import_claude = __esm({
6706
6874
  }
6707
6875
  });
6708
6876
 
6877
+ // src/repo-trust-prompt.ts
6878
+ var repo_trust_prompt_exports = {};
6879
+ __export(repo_trust_prompt_exports, {
6880
+ describeRepoSources: () => describeRepoSources,
6881
+ promptRepoTrust: () => promptRepoTrust
6882
+ });
6883
+ function ruleLists(path12, text, lines2, warnings) {
6884
+ let data;
6885
+ try {
6886
+ data = JSON.parse(text);
6887
+ } catch (err) {
6888
+ if (!(err instanceof SyntaxError)) throw err;
6889
+ warnings.push(`${path12}: not valid JSON, nothing from it loads`);
6890
+ return;
6891
+ }
6892
+ const perms = typeof data === "object" && data !== null ? data.permissions : void 0;
6893
+ if (typeof perms !== "object" || perms === null) return;
6894
+ for (const effect of ["allow", "deny", "ask"]) {
6895
+ const list2 = perms[effect];
6896
+ if (!Array.isArray(list2)) continue;
6897
+ for (const rule of list2) if (typeof rule === "string") lines2.push(` ${effect}: ${show(rule)} (${path12})`);
6898
+ }
6899
+ }
6900
+ function describeRepoSources(files) {
6901
+ const out = [];
6902
+ const warnings = [];
6903
+ if (files[".mcp.json"] !== void 0) {
6904
+ let valid = true;
6905
+ try {
6906
+ JSON.parse(files[".mcp.json"]);
6907
+ } catch (err) {
6908
+ if (!(err instanceof SyntaxError)) throw err;
6909
+ valid = false;
6910
+ warnings.push(".mcp.json: not valid JSON, nothing from it loads");
6911
+ }
6912
+ for (const s of valid ? listMcpSpawns(files[".mcp.json"]) : []) {
6913
+ const env3 = s.envNames.length ? ` env: ${s.envNames.join(", ")}` : "";
6914
+ out.push(` spawn: ${show(s.command)} ${s.args.map(show).join(" ")} [server ${show(s.name)}]${env3}`);
6915
+ }
6916
+ }
6917
+ for (const p of [".claude/settings.json", ".claude/settings.local.json"]) {
6918
+ if (files[p] !== void 0) ruleLists(p, files[p], out, warnings);
6919
+ }
6920
+ const names = Object.keys(files).sort();
6921
+ for (const k2 of names) {
6922
+ const m2 = /^\.claude\/commands\/([^/]+)\.md$/.exec(k2);
6923
+ if (m2) out.push(` command: /${show(m2[1]).slice(1, -1)}`);
6924
+ }
6925
+ const skills = /* @__PURE__ */ new Set();
6926
+ for (const k2 of names) {
6927
+ const m2 = /^\.claude\/skills\/([^/]+)\//.exec(k2);
6928
+ if (m2) skills.add(m2[1]);
6929
+ }
6930
+ for (const s of [...skills].sort()) out.push(` skill: ${show(s)}`);
6931
+ if (files["CLAUDE.local.md"] !== void 0) out.push(" instructions: CLAUDE.local.md");
6932
+ return [...warnings.map((w2) => ` warning: ${w2}`), ...out];
6933
+ }
6934
+ async function promptRepoTrust(cwd2, files, io) {
6935
+ io.write(`
6936
+ This repository has Claude Code sources that can run code or change permissions:
6937
+ ${cwd2}
6938
+ ${describeRepoSources(files).join("\n")}
6939
+
6940
+ `);
6941
+ io.write(" 1) Trust (remembered until these files change)\n 2) Trust for this session only\n 3) Never trust this repo\n 4) Skip (load nothing, ask again next time)\n");
6942
+ const answer = (await io.ask("Choose 1-4 [4]: "))?.trim() ?? "";
6943
+ if (answer === "1") return "trusted";
6944
+ if (answer === "2") return "session";
6945
+ if (answer === "3") return "never";
6946
+ return "skip";
6947
+ }
6948
+ var show;
6949
+ var init_repo_trust_prompt = __esm({
6950
+ "src/repo-trust-prompt.ts"() {
6951
+ "use strict";
6952
+ init_src();
6953
+ show = (s) => JSON.stringify(s);
6954
+ }
6955
+ });
6956
+
6709
6957
  // ../../node_modules/es-toolkit/dist/function/debounce.mjs
6710
6958
  function debounce(func, debounceMs, { signal, edges } = {}) {
6711
6959
  let pendingThis = void 0;
@@ -103221,6 +103469,40 @@ var rawCount = process.env.LAUNCH_COUNT ? parseInt(process.env.LAUNCH_COUNT, 10)
103221
103469
  var launchCount = rawCount !== void 0 && Number.isNaN(rawCount) ? void 0 : rawCount;
103222
103470
  var yolo = process.argv.includes("--yolo");
103223
103471
  var resumeLatest = process.argv.includes("--continue");
103472
+ if (process.stdin.isTTY && process.stdout.isTTY) {
103473
+ try {
103474
+ const { readRepoSources: readRepoSources2, createNodeTrustStore: createNodeTrustStore2 } = await Promise.resolve().then(() => (init_import_gate(), import_gate_exports));
103475
+ const { promptRepoTrust: promptRepoTrust2 } = await Promise.resolve().then(() => (init_repo_trust_prompt(), repo_trust_prompt_exports));
103476
+ const files = await readRepoSources2(process.cwd());
103477
+ if (Object.keys(files).length > 0) {
103478
+ const store = createNodeTrustStore2(process.env.SPACECODE_CONFIG_DIR?.trim() || path11.join(os5.homedir(), ".spacecode"));
103479
+ if (await store.repoStatus(process.cwd(), files) === "untrusted") {
103480
+ const { createInterface } = await import("node:readline/promises");
103481
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
103482
+ try {
103483
+ const decision = await promptRepoTrust2(process.cwd(), files, {
103484
+ write: (t) => {
103485
+ process.stdout.write(t);
103486
+ },
103487
+ ask: async (q2) => {
103488
+ try {
103489
+ return await rl.question(q2);
103490
+ } catch {
103491
+ return null;
103492
+ }
103493
+ }
103494
+ });
103495
+ if (decision !== "skip") await store.setRepo(process.cwd(), files, decision);
103496
+ } finally {
103497
+ rl.close();
103498
+ }
103499
+ }
103500
+ }
103501
+ } catch (err) {
103502
+ process.stderr.write(`Repo trust check failed (${err instanceof Error ? err.message : String(err)}); repo sources stay untrusted.
103503
+ `);
103504
+ }
103505
+ }
103224
103506
  var restoreCiVariables = hideCiVariables(process.env, Boolean(process.stdout.isTTY));
103225
103507
  var render2;
103226
103508
  var App3;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spacecode",
3
- "version": "2.5.5",
3
+ "version": "2.5.7",
4
4
  "description": "SpaceCode - launch cloud dev environments with AI coding agents",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",