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/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/repl.mjs +284 -2
- package/package.json +1 -1
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
|
-
|
|
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;
|