spacecode 2.12.5 → 2.12.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 +55 -11
- package/package.json +1 -1
package/dist/repl.mjs
CHANGED
|
@@ -8465,15 +8465,26 @@ var init_mcp_manager = __esm({
|
|
|
8465
8465
|
});
|
|
8466
8466
|
|
|
8467
8467
|
// src/mcp/approve-mcp.ts
|
|
8468
|
+
function unsetVars(d, env3) {
|
|
8469
|
+
const texts = d.transport === "http" ? [d.url, ...Object.values(d.env)] : [d.command, ...d.args, ...Object.values(d.env)];
|
|
8470
|
+
return [...new Set(texts.flatMap((t) => expandRefs(t, env3).missing))].sort();
|
|
8471
|
+
}
|
|
8468
8472
|
async function runApproveMcp(deps) {
|
|
8469
8473
|
const { defs, statusLines } = await loadMcpDefs(deps);
|
|
8470
8474
|
if (!deps.quiet) for (const l of statusLines) deps.log(l);
|
|
8471
8475
|
const stdio = defs;
|
|
8472
|
-
|
|
8476
|
+
let todo = [];
|
|
8473
8477
|
for (const d of stdio) {
|
|
8474
8478
|
const key = approvalKey(d);
|
|
8475
8479
|
if (await deps.trust.stdioStatus(key) === "unapproved") todo.push(d);
|
|
8476
8480
|
}
|
|
8481
|
+
if (deps.quiet && deps.procEnv && todo.length > 0) {
|
|
8482
|
+
const env3 = deps.procEnv;
|
|
8483
|
+
const ready = todo.filter((d) => unsetVars(d, env3).length === 0);
|
|
8484
|
+
const waiting = todo.length - ready.length;
|
|
8485
|
+
if (waiting > 0) deps.log(`${waiting} imported MCP server(s) wait for environment variables (see \`spacecode approve-mcp\`); they are not asked about until those are set.`);
|
|
8486
|
+
todo = ready;
|
|
8487
|
+
}
|
|
8477
8488
|
if (todo.length === 0) {
|
|
8478
8489
|
if (!deps.quiet) deps.log(stdio.length === 0 ? "No stdio MCP servers to approve." : "Every stdio MCP server is already approved on this machine.");
|
|
8479
8490
|
return 0;
|
|
@@ -8482,6 +8493,13 @@ async function runApproveMcp(deps) {
|
|
|
8482
8493
|
deps.log(`${todo.length} MCP server(s) need approval, but this is not an interactive terminal: nothing was approved.`);
|
|
8483
8494
|
return 1;
|
|
8484
8495
|
}
|
|
8496
|
+
if (deps.quiet && todo.length > STARTUP_PROMPT_LIMIT) {
|
|
8497
|
+
const answer = (await deps.ask(`${todo.length} imported MCP servers can be approved now. Review them one by one? [y/N] `) ?? "").trim().toLowerCase();
|
|
8498
|
+
if (answer !== "y" && answer !== "yes") {
|
|
8499
|
+
deps.log("Skipped. Run `spacecode approve-mcp` when you want to review them; nothing spawns until then.");
|
|
8500
|
+
return 0;
|
|
8501
|
+
}
|
|
8502
|
+
}
|
|
8485
8503
|
if (deps.quiet) deps.log(`
|
|
8486
8504
|
${todo.length} imported MCP server(s) want to run a command on this machine. Nothing spawns until you approve it (you can also run \`spacecode approve-mcp\` later).`);
|
|
8487
8505
|
let approved = 0;
|
|
@@ -8506,12 +8524,14 @@ MCP server ${show(d.name)} (${d.origin} definition) wants to run on this machine
|
|
|
8506
8524
|
${approved} of ${todo.length} approved.`);
|
|
8507
8525
|
return 0;
|
|
8508
8526
|
}
|
|
8509
|
-
var show;
|
|
8527
|
+
var STARTUP_PROMPT_LIMIT, show;
|
|
8510
8528
|
var init_approve_mcp = __esm({
|
|
8511
8529
|
"src/mcp/approve-mcp.ts"() {
|
|
8512
8530
|
"use strict";
|
|
8531
|
+
init_src();
|
|
8513
8532
|
init_mcp_loader();
|
|
8514
8533
|
init_mcp_manager();
|
|
8534
|
+
STARTUP_PROMPT_LIMIT = 3;
|
|
8515
8535
|
show = (s) => JSON.stringify(s);
|
|
8516
8536
|
}
|
|
8517
8537
|
});
|
|
@@ -8545,7 +8565,8 @@ async function runApproveMcpOnNode(opts) {
|
|
|
8545
8565
|
}
|
|
8546
8566
|
} } : {},
|
|
8547
8567
|
log: (line) => console.log(line),
|
|
8548
|
-
quiet: opts.quiet
|
|
8568
|
+
quiet: opts.quiet,
|
|
8569
|
+
procEnv: process.env
|
|
8549
8570
|
});
|
|
8550
8571
|
} finally {
|
|
8551
8572
|
rl?.close();
|
|
@@ -104580,12 +104601,18 @@ var init_import_reload = __esm({
|
|
|
104580
104601
|
|
|
104581
104602
|
// src/command-loader.ts
|
|
104582
104603
|
import { join as join14 } from "node:path";
|
|
104583
|
-
|
|
104604
|
+
function skillCommandBody(name) {
|
|
104605
|
+
return `Run the imported skill "${name}": call the skill tool with name "${name}" and follow its instructions.
|
|
104606
|
+
|
|
104607
|
+
Arguments (may be empty): $ARGUMENTS`;
|
|
104608
|
+
}
|
|
104609
|
+
async function userCommands(deps, lines2, includeSkills) {
|
|
104610
|
+
const skillNames = /* @__PURE__ */ new Set();
|
|
104584
104611
|
try {
|
|
104585
104612
|
const apiKey = await resolveApiKey({ env: deps.env, readFile: deps.readFile, homedir: deps.homedir });
|
|
104586
104613
|
if (!apiKey) {
|
|
104587
104614
|
lines2.push("commands: user commands NOT loaded (not signed in)");
|
|
104588
|
-
return [];
|
|
104615
|
+
return { defs: [], skillNames };
|
|
104589
104616
|
}
|
|
104590
104617
|
const client = createProfileClient({ baseUrl: backendUrl(deps.env), apiKey, fetch: deps.fetch, timeoutMs: 3e3 });
|
|
104591
104618
|
const index = await client.index();
|
|
@@ -104601,10 +104628,24 @@ async function userCommands(deps, lines2) {
|
|
|
104601
104628
|
}
|
|
104602
104629
|
})
|
|
104603
104630
|
);
|
|
104604
|
-
|
|
104631
|
+
if (includeSkills) {
|
|
104632
|
+
const taken = /* @__PURE__ */ new Set([...defs.map((d) => d.name.toLowerCase()), ...deps.builtins.map((b2) => b2.toLowerCase())]);
|
|
104633
|
+
let hidden = 0;
|
|
104634
|
+
for (const a of index?.assets ?? []) {
|
|
104635
|
+
if (a.kind !== "skill" || (a.description ?? "").startsWith("file:")) continue;
|
|
104636
|
+
if (taken.has(a.name.toLowerCase())) {
|
|
104637
|
+
hidden++;
|
|
104638
|
+
continue;
|
|
104639
|
+
}
|
|
104640
|
+
skillNames.add(a.name.toLowerCase());
|
|
104641
|
+
defs.push({ name: a.name, description: a.description ?? "", body: skillCommandBody(a.name) });
|
|
104642
|
+
}
|
|
104643
|
+
if (hidden > 0) lines2.push(`commands: ${hidden} skill(s) not offered as /name (a command or built-in has that name)`);
|
|
104644
|
+
}
|
|
104645
|
+
return { defs, skillNames };
|
|
104605
104646
|
} catch (err) {
|
|
104606
104647
|
lines2.push(`commands: user commands NOT loaded (${err instanceof Error ? err.message : String(err)})`);
|
|
104607
|
-
return [];
|
|
104648
|
+
return { defs: [], skillNames };
|
|
104608
104649
|
}
|
|
104609
104650
|
}
|
|
104610
104651
|
async function repoCommands(deps, lines2) {
|
|
@@ -104635,6 +104676,7 @@ async function repoCommands(deps, lines2) {
|
|
|
104635
104676
|
}
|
|
104636
104677
|
async function loadCommands(deps) {
|
|
104637
104678
|
const configPath = join14(deps.configDir, "imports.json");
|
|
104679
|
+
let skillsOn = false;
|
|
104638
104680
|
try {
|
|
104639
104681
|
const loaded = parseImportsConfig(await deps.readFile(configPath).catch((err) => {
|
|
104640
104682
|
if (err.code === "ENOENT") return null;
|
|
@@ -104642,15 +104684,17 @@ async function loadCommands(deps) {
|
|
|
104642
104684
|
}), configPath);
|
|
104643
104685
|
if (loaded.error) return off("commands: off (imports.json unreadable)");
|
|
104644
104686
|
if (!loaded.config.categories.commands) return off("commands: off (imports.json has commands off)");
|
|
104687
|
+
skillsOn = loaded.config.categories.skills;
|
|
104645
104688
|
} catch (err) {
|
|
104646
104689
|
return off(`commands: off (imports.json not read: ${err instanceof Error ? err.message : String(err)})`);
|
|
104647
104690
|
}
|
|
104648
104691
|
const lines2 = [];
|
|
104649
|
-
const [user, repo] = await Promise.all([userCommands(deps, lines2), repoCommands(deps, lines2)]);
|
|
104692
|
+
const [{ defs: user, skillNames }, repo] = await Promise.all([userCommands(deps, lines2, skillsOn), repoCommands(deps, lines2)]);
|
|
104650
104693
|
const reg = buildCommandRegistry({ builtins: deps.builtins, user, repo });
|
|
104651
|
-
const
|
|
104652
|
-
const
|
|
104653
|
-
|
|
104694
|
+
const nSkill = reg.commands.filter((c) => c.scope === "user" && skillNames.has(c.name)).length;
|
|
104695
|
+
const nUser = reg.commands.filter((c) => c.scope === "user").length - nSkill;
|
|
104696
|
+
const nRepo = reg.commands.length - nUser - nSkill;
|
|
104697
|
+
lines2.unshift(`commands: ${nUser} user, ${nRepo} repo${nSkill ? `, ${nSkill} from skills` : ""}${reg.shadowed.length ? `; repo shadows user: ${reg.shadowed.map((n) => "/" + n).join(", ")}` : ""}`);
|
|
104654
104698
|
for (const s of reg.skipped) lines2.push(`commands: skipped [${s.scope}] /${s.name} (${s.reason})`);
|
|
104655
104699
|
return { commands: reg.commands, statusLines: lines2 };
|
|
104656
104700
|
}
|