spacecode 2.5.4 → 2.5.6
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 +772 -160
- package/package.json +1 -1
package/dist/repl.mjs
CHANGED
|
@@ -4949,6 +4949,187 @@ var init_command_file = __esm({
|
|
|
4949
4949
|
}
|
|
4950
4950
|
});
|
|
4951
4951
|
|
|
4952
|
+
// ../agent/src/import/skill-file.ts
|
|
4953
|
+
function parseSkillFile(text) {
|
|
4954
|
+
const lines2 = text.replace(/\r\n/g, "\n").split("\n");
|
|
4955
|
+
if (lines2[0] !== "---") return { ok: false, error: "missing frontmatter" };
|
|
4956
|
+
const endIdx = lines2.indexOf("---", 1);
|
|
4957
|
+
if (endIdx === -1) return { ok: false, error: "missing frontmatter" };
|
|
4958
|
+
const front = lines2.slice(1, endIdx);
|
|
4959
|
+
let name;
|
|
4960
|
+
let description;
|
|
4961
|
+
const blockIndicators = /* @__PURE__ */ new Set([">", "|", ">-", "|-"]);
|
|
4962
|
+
for (let i = 0; i < front.length; i++) {
|
|
4963
|
+
const raw = front[i];
|
|
4964
|
+
if (!raw.trim() || raw.trim().startsWith("#") || /^\s/.test(raw)) continue;
|
|
4965
|
+
if (!raw.includes(":")) return { ok: false, error: `malformed frontmatter line: ${raw}` };
|
|
4966
|
+
const [rawKey, ...rest] = raw.split(":");
|
|
4967
|
+
const key = rawKey.trim();
|
|
4968
|
+
let value = rest.join(":").trim();
|
|
4969
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
4970
|
+
if (key === "description" && blockIndicators.has(value)) {
|
|
4971
|
+
const block = [];
|
|
4972
|
+
let j2 = i + 1;
|
|
4973
|
+
while (j2 < front.length && /^\s/.test(front[j2])) block.push(front[j2++].replace(/^\s+/, ""));
|
|
4974
|
+
description = block.join(" ").trim();
|
|
4975
|
+
i = j2 - 1;
|
|
4976
|
+
continue;
|
|
4977
|
+
}
|
|
4978
|
+
if (key === "name") name = value;
|
|
4979
|
+
else if (key === "description") description = value;
|
|
4980
|
+
}
|
|
4981
|
+
if (!name || name.trim() === "") return { ok: false, error: "missing name" };
|
|
4982
|
+
if (!description || description.trim() === "") return { ok: false, error: "missing description" };
|
|
4983
|
+
return { ok: true, name: name.trim(), description: description.trim(), body: lines2.slice(endIdx + 1).join("\n").replace(/^(?:[ \t]*\n)*/, "") };
|
|
4984
|
+
}
|
|
4985
|
+
var init_skill_file = __esm({
|
|
4986
|
+
"../agent/src/import/skill-file.ts"() {
|
|
4987
|
+
"use strict";
|
|
4988
|
+
}
|
|
4989
|
+
});
|
|
4990
|
+
|
|
4991
|
+
// ../agent/src/import/tool-names.ts
|
|
4992
|
+
function translateToolNames(text) {
|
|
4993
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4994
|
+
const names = Object.keys(TOOL_NAME_MAP).join("|");
|
|
4995
|
+
const re3 = new RegExp("`(" + names + ")(\\([^`)]*\\))?`", "g");
|
|
4996
|
+
const out = text.replace(re3, (_m, name, args) => {
|
|
4997
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
4998
|
+
return "`" + TOOL_NAME_MAP[name] + (args ?? "") + "`";
|
|
4999
|
+
});
|
|
5000
|
+
return { text: out, replaced: [...counts].map(([from, count]) => ({ from, to: TOOL_NAME_MAP[from], count })) };
|
|
5001
|
+
}
|
|
5002
|
+
function findUnsupported(text) {
|
|
5003
|
+
return UNSUPPORTED.filter((u3) => u3.re.test(text)).map((u3) => u3.label);
|
|
5004
|
+
}
|
|
5005
|
+
var TOOL_NAME_MAP, UNSUPPORTED;
|
|
5006
|
+
var init_tool_names = __esm({
|
|
5007
|
+
"../agent/src/import/tool-names.ts"() {
|
|
5008
|
+
"use strict";
|
|
5009
|
+
TOOL_NAME_MAP = {
|
|
5010
|
+
Read: "read_file",
|
|
5011
|
+
Write: "write_file",
|
|
5012
|
+
Edit: "edit_file",
|
|
5013
|
+
MultiEdit: "edit_file",
|
|
5014
|
+
Glob: "list_directory",
|
|
5015
|
+
Grep: "search",
|
|
5016
|
+
Bash: "bash",
|
|
5017
|
+
TodoWrite: "todo_write"
|
|
5018
|
+
};
|
|
5019
|
+
UNSUPPORTED = [
|
|
5020
|
+
{ label: "hooks", re: /\bhooks?\b.*\.(sh|json)|\bPreToolUse\b|\bPostToolUse\b|\bSessionStart\b|\bUserPromptSubmit\b/i },
|
|
5021
|
+
{ label: "ToolSearch / deferred tools", re: /\bToolSearch\b|deferred tools?/i },
|
|
5022
|
+
{ label: "Claude Code's Task/subagent tools", re: /\bsubagents?\b|\bAgent tool\b|`Task`|subagent_type/i },
|
|
5023
|
+
{ label: "Claude Code skills / slash commands", re: /\bSkill tool\b|invoke (?:the )?\w+ skill|\/ship\b|\/handoff\b/i },
|
|
5024
|
+
{ label: "MCP tool names (mcp__*)", re: /\bmcp__[A-Za-z0-9_-]+/ },
|
|
5025
|
+
{ label: "WebFetch/WebSearch/NotebookEdit tools", re: /`(?:WebFetch|WebSearch|NotebookEdit)`/ },
|
|
5026
|
+
{ label: "plan mode / worktree tools", re: /\bEnterPlanMode\b|\bExitPlanMode\b|\bEnterWorktree\b/ }
|
|
5027
|
+
];
|
|
5028
|
+
}
|
|
5029
|
+
});
|
|
5030
|
+
|
|
5031
|
+
// ../agent/src/import/skill-tool.ts
|
|
5032
|
+
function safeSkillPath(rel) {
|
|
5033
|
+
if (rel === "" || rel.length > 200 || rel.startsWith("/") || rel.includes("\\") || rel.includes("\0")) return null;
|
|
5034
|
+
const parts = rel.split("/");
|
|
5035
|
+
return parts.every((p) => p !== "" && p !== "." && p !== "..") ? parts.join("/") : null;
|
|
5036
|
+
}
|
|
5037
|
+
function formatSkillBody(name, body, scripts) {
|
|
5038
|
+
const parsed = parseSkillFile(body);
|
|
5039
|
+
const t = translateToolNames(parsed.ok ? parsed.body : body);
|
|
5040
|
+
const unsupported = findUnsupported(t.text);
|
|
5041
|
+
const head = [`# Skill: ${name}`];
|
|
5042
|
+
if (t.replaced.length) head.push(`Tool names translated: ${t.replaced.map((r) => `${r.from} -> ${r.to}`).join(", ")}.`);
|
|
5043
|
+
if (unsupported.length) head.push(`Not supported here (skip those parts): ${unsupported.join("; ")}.`);
|
|
5044
|
+
if (scripts) head.push("This skill ships scripts: they are not runnable until permitted (they run through `bash` and its permission rules).");
|
|
5045
|
+
return `${head.join("\n")}
|
|
5046
|
+
|
|
5047
|
+
${t.text}`;
|
|
5048
|
+
}
|
|
5049
|
+
function createSkillTool(source) {
|
|
5050
|
+
return async (input) => {
|
|
5051
|
+
const name = typeof input.name === "string" ? input.name.trim() : "";
|
|
5052
|
+
if (!NAME_RE.test(name)) return { output: 'skill needs a "name" from the skills list', is_error: true };
|
|
5053
|
+
try {
|
|
5054
|
+
if (typeof input.file === "string" && input.file !== "") {
|
|
5055
|
+
const rel = safeSkillPath(input.file);
|
|
5056
|
+
if (!rel) return { output: `skill file path "${input.file}" must be relative to the skill, with no ".." segments`, is_error: true };
|
|
5057
|
+
const text = await source.file(name, rel);
|
|
5058
|
+
return text === null ? { output: `Skill "${name}" has no file "${rel}".`, is_error: true } : { output: text };
|
|
5059
|
+
}
|
|
5060
|
+
const body = await source.body(name);
|
|
5061
|
+
if (body === null) return { output: `No skill named "${name}". Use a name from the skills list.`, is_error: true };
|
|
5062
|
+
const scripts = (await source.list()).find((s) => s.name === name)?.scripts ?? false;
|
|
5063
|
+
return { output: formatSkillBody(name, body, scripts) };
|
|
5064
|
+
} catch (err) {
|
|
5065
|
+
return { output: `skill failed: ${err instanceof Error ? err.message : String(err)}`, is_error: true };
|
|
5066
|
+
}
|
|
5067
|
+
};
|
|
5068
|
+
}
|
|
5069
|
+
var SKILL_TOOL, NAME_RE;
|
|
5070
|
+
var init_skill_tool = __esm({
|
|
5071
|
+
"../agent/src/import/skill-tool.ts"() {
|
|
5072
|
+
"use strict";
|
|
5073
|
+
init_tool_names();
|
|
5074
|
+
init_skill_file();
|
|
5075
|
+
SKILL_TOOL = {
|
|
5076
|
+
name: "skill",
|
|
5077
|
+
description: 'Load an imported skill by name (listed under "Skills (imported)" in your instructions) and follow it. Pass `file` to read a supporting file that ships with the skill (a path relative to the skill, e.g. "reference.md"). Skill text is written for Claude Code: tool names in it are translated, and lines under "Not supported here" name features that do not exist in this REPL.',
|
|
5078
|
+
input_schema: {
|
|
5079
|
+
type: "object",
|
|
5080
|
+
properties: {
|
|
5081
|
+
name: { type: "string", description: "The skill name exactly as listed." },
|
|
5082
|
+
file: { type: "string", description: 'Optional path of a sibling file inside the skill, e.g. "scripts/run.sh" or "reference.md".' }
|
|
5083
|
+
},
|
|
5084
|
+
required: ["name"]
|
|
5085
|
+
}
|
|
5086
|
+
};
|
|
5087
|
+
NAME_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
5088
|
+
}
|
|
5089
|
+
});
|
|
5090
|
+
|
|
5091
|
+
// ../agent/src/import/skill-source.ts
|
|
5092
|
+
function siblingAssetName(skill, rel) {
|
|
5093
|
+
return `${skill}.${rel.replace(/[^A-Za-z0-9_-]/g, "_")}`.slice(0, 100);
|
|
5094
|
+
}
|
|
5095
|
+
function isScriptPath(rel) {
|
|
5096
|
+
return SCRIPT_RE.test(rel);
|
|
5097
|
+
}
|
|
5098
|
+
function createProfileSkillSource(client) {
|
|
5099
|
+
const entries = async () => ((await client.index())?.assets ?? []).filter((a) => a.kind === "skill");
|
|
5100
|
+
return {
|
|
5101
|
+
async list() {
|
|
5102
|
+
const all = await entries();
|
|
5103
|
+
return all.filter((a) => NAME_RE2.test(a.name)).map((a) => ({
|
|
5104
|
+
name: a.name,
|
|
5105
|
+
description: a.description,
|
|
5106
|
+
scope: "user",
|
|
5107
|
+
scripts: all.some((s) => s.name.startsWith(`${a.name}.`) && s.description.startsWith(SIBLING_PREFIX) && isScriptPath(s.description.slice(SIBLING_PREFIX.length)))
|
|
5108
|
+
}));
|
|
5109
|
+
},
|
|
5110
|
+
async body(name) {
|
|
5111
|
+
return (await client.get("skill", name))?.content ?? null;
|
|
5112
|
+
},
|
|
5113
|
+
async file(name, rel) {
|
|
5114
|
+
const safe = safeSkillPath(rel);
|
|
5115
|
+
if (!safe) return null;
|
|
5116
|
+
const all = await entries();
|
|
5117
|
+
const hit = all.find((a) => a.name.startsWith(`${name}.`) && a.description === `${SIBLING_PREFIX}${safe}`);
|
|
5118
|
+
return hit ? (await client.get("skill", hit.name))?.content ?? null : null;
|
|
5119
|
+
}
|
|
5120
|
+
};
|
|
5121
|
+
}
|
|
5122
|
+
var SIBLING_PREFIX, NAME_RE2, SCRIPT_RE;
|
|
5123
|
+
var init_skill_source = __esm({
|
|
5124
|
+
"../agent/src/import/skill-source.ts"() {
|
|
5125
|
+
"use strict";
|
|
5126
|
+
init_skill_tool();
|
|
5127
|
+
SIBLING_PREFIX = "file:";
|
|
5128
|
+
NAME_RE2 = /^[A-Za-z0-9_-]{1,64}$/;
|
|
5129
|
+
SCRIPT_RE = /^(scripts\/|.*\.(sh|py|js|mjs|ts|rb|pl)$)/;
|
|
5130
|
+
}
|
|
5131
|
+
});
|
|
5132
|
+
|
|
4952
5133
|
// ../agent/src/import/claude-sources.ts
|
|
4953
5134
|
function isEnoent(err) {
|
|
4954
5135
|
return typeof err === "object" && err !== null && err.code === "ENOENT";
|
|
@@ -4987,6 +5168,7 @@ async function readClaudeAssets({ fs: fs6, home, projectDirs }) {
|
|
|
4987
5168
|
if (/^@\S+/m.test(globalText)) report.notes.push("CLAUDE.md contains @path includes; they are kept as plain text and not expanded in v1");
|
|
4988
5169
|
}
|
|
4989
5170
|
await readCommands(fs6, home, report);
|
|
5171
|
+
await readSkills(fs6, home, report);
|
|
4990
5172
|
for (const dir of projectDirs) {
|
|
4991
5173
|
const memDir = `${home}/.claude/projects/${claudeProjectSlug(dir)}/memory`;
|
|
4992
5174
|
let entries;
|
|
@@ -5006,7 +5188,7 @@ async function readClaudeAssets({ fs: fs6, home, projectDirs }) {
|
|
|
5006
5188
|
if (text === null) continue;
|
|
5007
5189
|
const base = entry === "MEMORY.md" ? "index" : entry.slice(0, -3).replace(/[^A-Za-z0-9._-]/g, "_");
|
|
5008
5190
|
const name = `${key}.${base}`;
|
|
5009
|
-
if (!
|
|
5191
|
+
if (!NAME_RE3.test(name)) {
|
|
5010
5192
|
report.errors.push({ path: path12, message: `cannot derive a valid asset name from "${entry}"` });
|
|
5011
5193
|
continue;
|
|
5012
5194
|
}
|
|
@@ -5063,14 +5245,84 @@ async function readCommands(fs6, home, report) {
|
|
|
5063
5245
|
report.assets.push({ kind: "command", name, description: parseCommandFile(text).description, content: text, source: path12 });
|
|
5064
5246
|
}
|
|
5065
5247
|
}
|
|
5066
|
-
|
|
5248
|
+
async function listFiles(fs6, dir, rel, out, report) {
|
|
5249
|
+
let entries;
|
|
5250
|
+
try {
|
|
5251
|
+
entries = await fs6.readdir(dir);
|
|
5252
|
+
} catch (err) {
|
|
5253
|
+
const code = err.code;
|
|
5254
|
+
if (code === "ENOTDIR") out.push(rel);
|
|
5255
|
+
else if (code !== "ENOENT") report.errors.push({ path: dir, message: err instanceof Error ? err.message : String(err) });
|
|
5256
|
+
return;
|
|
5257
|
+
}
|
|
5258
|
+
for (const e of [...entries].sort()) {
|
|
5259
|
+
if (e.startsWith(".")) continue;
|
|
5260
|
+
await listFiles(fs6, `${dir}/${e}`, rel === "" ? e : `${rel}/${e}`, out, report);
|
|
5261
|
+
}
|
|
5262
|
+
}
|
|
5263
|
+
async function readSkills(fs6, home, report) {
|
|
5264
|
+
const root = `${home}/.claude/skills`;
|
|
5265
|
+
let dirs;
|
|
5266
|
+
try {
|
|
5267
|
+
dirs = await fs6.readdir(root);
|
|
5268
|
+
} catch (err) {
|
|
5269
|
+
if (!isEnoent(err)) report.errors.push({ path: root, message: err instanceof Error ? err.message : String(err) });
|
|
5270
|
+
return;
|
|
5271
|
+
}
|
|
5272
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5273
|
+
for (const dir of [...dirs].sort()) {
|
|
5274
|
+
if (dir.startsWith(".")) continue;
|
|
5275
|
+
const skillPath = `${root}/${dir}/SKILL.md`;
|
|
5276
|
+
const text = await readOptional(fs6, skillPath, report);
|
|
5277
|
+
if (text === null) continue;
|
|
5278
|
+
const parsed = parseSkillFile(text);
|
|
5279
|
+
if (!parsed.ok) {
|
|
5280
|
+
report.errors.push({ path: skillPath, message: `malformed skill: ${parsed.error}` });
|
|
5281
|
+
continue;
|
|
5282
|
+
}
|
|
5283
|
+
if (!SKILL_NAME_RE.test(parsed.name) || seen.has(parsed.name.toLowerCase())) {
|
|
5284
|
+
report.errors.push({ path: skillPath, message: seen.has(parsed.name.toLowerCase()) ? `skill name collision on "${parsed.name}"` : `skill name "${parsed.name}" must use only letters, digits, - and _` });
|
|
5285
|
+
continue;
|
|
5286
|
+
}
|
|
5287
|
+
const bytes = Buffer.byteLength(text, "utf8");
|
|
5288
|
+
if (bytes > MAX_ASSET_BYTES) {
|
|
5289
|
+
report.errors.push({ path: skillPath, message: `skill skipped: SKILL.md is ${bytes} bytes, over the ${MAX_ASSET_BYTES} byte asset limit` });
|
|
5290
|
+
continue;
|
|
5291
|
+
}
|
|
5292
|
+
seen.add(parsed.name.toLowerCase());
|
|
5293
|
+
const files = [];
|
|
5294
|
+
await listFiles(fs6, `${root}/${dir}`, "", files, report);
|
|
5295
|
+
const siblings = [];
|
|
5296
|
+
const slugs = /* @__PURE__ */ new Set();
|
|
5297
|
+
for (const rel of files.filter((f) => f !== "SKILL.md")) {
|
|
5298
|
+
const path12 = `${root}/${dir}/${rel}`;
|
|
5299
|
+
const content = await readOptional(fs6, path12, report);
|
|
5300
|
+
if (content === null) continue;
|
|
5301
|
+
const size = Buffer.byteLength(content, "utf8");
|
|
5302
|
+
const name = siblingAssetName(parsed.name, rel);
|
|
5303
|
+
if (size > MAX_ASSET_BYTES || slugs.has(name) || siblings.length >= MAX_SKILL_FILES || content.includes("\0")) {
|
|
5304
|
+
report.notes.push(`Skill ${parsed.name}: ${rel} not imported (${content.includes("\0") ? "binary file" : size > MAX_ASSET_BYTES ? `${size} bytes, over the ${MAX_ASSET_BYTES} byte limit` : slugs.has(name) ? "name collision" : `more than ${MAX_SKILL_FILES} files`}); the skill still loads without it`);
|
|
5305
|
+
continue;
|
|
5306
|
+
}
|
|
5307
|
+
slugs.add(name);
|
|
5308
|
+
siblings.push({ kind: "skill", name, description: `${SIBLING_PREFIX}${rel}`, content, source: path12 });
|
|
5309
|
+
}
|
|
5310
|
+
if (siblings.some((s) => isScriptPath(s.description.slice(SIBLING_PREFIX.length)))) report.notes.push(`Skill ${parsed.name}: ships scripts, not runnable until permitted (they run through bash and its permission rules)`);
|
|
5311
|
+
report.assets.push({ kind: "skill", name: parsed.name, description: parsed.description, content: text, source: skillPath }, ...siblings);
|
|
5312
|
+
}
|
|
5313
|
+
}
|
|
5314
|
+
var MAX_ASSET_BYTES, NAME_RE3, COMMAND_NAME_RE, SKILL_NAME_RE, MAX_SKILL_FILES;
|
|
5067
5315
|
var init_claude_sources = __esm({
|
|
5068
5316
|
"../agent/src/import/claude-sources.ts"() {
|
|
5069
5317
|
"use strict";
|
|
5070
5318
|
init_command_file();
|
|
5319
|
+
init_skill_file();
|
|
5320
|
+
init_skill_source();
|
|
5071
5321
|
MAX_ASSET_BYTES = 256 * 1024;
|
|
5072
|
-
|
|
5322
|
+
NAME_RE3 = /^[A-Za-z0-9._-]{1,100}$/;
|
|
5073
5323
|
COMMAND_NAME_RE = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
5324
|
+
SKILL_NAME_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
5325
|
+
MAX_SKILL_FILES = 40;
|
|
5074
5326
|
}
|
|
5075
5327
|
});
|
|
5076
5328
|
|
|
@@ -5120,6 +5372,16 @@ function createProfileClient({ baseUrl, apiKey, fetch: doFetch, timeoutMs = 8e3
|
|
|
5120
5372
|
const res = await call("POST", "/adapt", { body: { sections }, timeoutMs: ADAPT_TIMEOUT_MS });
|
|
5121
5373
|
return await res.json();
|
|
5122
5374
|
},
|
|
5375
|
+
/** Deletes one asset; false when it did not exist. */
|
|
5376
|
+
async delete(kind, name) {
|
|
5377
|
+
try {
|
|
5378
|
+
await call("DELETE", `/assets/${enc(kind)}/${enc(name)}`);
|
|
5379
|
+
return true;
|
|
5380
|
+
} catch (err) {
|
|
5381
|
+
if (err instanceof ProfileApiError && err.status === 404) return false;
|
|
5382
|
+
throw err;
|
|
5383
|
+
}
|
|
5384
|
+
},
|
|
5123
5385
|
async put(kind, name, input) {
|
|
5124
5386
|
const res = await call("PUT", `/assets/${enc(kind)}/${enc(name)}`, {
|
|
5125
5387
|
body: { content: input.content, description: input.description ?? "", base_version: input.baseVersion }
|
|
@@ -5302,46 +5564,6 @@ var init_instruction_sections = __esm({
|
|
|
5302
5564
|
}
|
|
5303
5565
|
});
|
|
5304
5566
|
|
|
5305
|
-
// ../agent/src/import/tool-names.ts
|
|
5306
|
-
function translateToolNames(text) {
|
|
5307
|
-
const counts = /* @__PURE__ */ new Map();
|
|
5308
|
-
const names = Object.keys(TOOL_NAME_MAP).join("|");
|
|
5309
|
-
const re3 = new RegExp("`(" + names + ")(\\([^`)]*\\))?`", "g");
|
|
5310
|
-
const out = text.replace(re3, (_m, name, args) => {
|
|
5311
|
-
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
5312
|
-
return "`" + TOOL_NAME_MAP[name] + (args ?? "") + "`";
|
|
5313
|
-
});
|
|
5314
|
-
return { text: out, replaced: [...counts].map(([from, count]) => ({ from, to: TOOL_NAME_MAP[from], count })) };
|
|
5315
|
-
}
|
|
5316
|
-
function findUnsupported(text) {
|
|
5317
|
-
return UNSUPPORTED.filter((u3) => u3.re.test(text)).map((u3) => u3.label);
|
|
5318
|
-
}
|
|
5319
|
-
var TOOL_NAME_MAP, UNSUPPORTED;
|
|
5320
|
-
var init_tool_names = __esm({
|
|
5321
|
-
"../agent/src/import/tool-names.ts"() {
|
|
5322
|
-
"use strict";
|
|
5323
|
-
TOOL_NAME_MAP = {
|
|
5324
|
-
Read: "read_file",
|
|
5325
|
-
Write: "write_file",
|
|
5326
|
-
Edit: "edit_file",
|
|
5327
|
-
MultiEdit: "edit_file",
|
|
5328
|
-
Glob: "list_directory",
|
|
5329
|
-
Grep: "search",
|
|
5330
|
-
Bash: "bash",
|
|
5331
|
-
TodoWrite: "todo_write"
|
|
5332
|
-
};
|
|
5333
|
-
UNSUPPORTED = [
|
|
5334
|
-
{ label: "hooks", re: /\bhooks?\b.*\.(sh|json)|\bPreToolUse\b|\bPostToolUse\b|\bSessionStart\b|\bUserPromptSubmit\b/i },
|
|
5335
|
-
{ label: "ToolSearch / deferred tools", re: /\bToolSearch\b|deferred tools?/i },
|
|
5336
|
-
{ label: "Claude Code's Task/subagent tools", re: /\bsubagents?\b|\bAgent tool\b|`Task`|subagent_type/i },
|
|
5337
|
-
{ label: "Claude Code skills / slash commands", re: /\bSkill tool\b|invoke (?:the )?\w+ skill|\/ship\b|\/handoff\b/i },
|
|
5338
|
-
{ label: "MCP tool names (mcp__*)", re: /\bmcp__[A-Za-z0-9_-]+/ },
|
|
5339
|
-
{ label: "WebFetch/WebSearch/NotebookEdit tools", re: /`(?:WebFetch|WebSearch|NotebookEdit)`/ },
|
|
5340
|
-
{ label: "plan mode / worktree tools", re: /\bEnterPlanMode\b|\bExitPlanMode\b|\bEnterWorktree\b/ }
|
|
5341
|
-
];
|
|
5342
|
-
}
|
|
5343
|
-
});
|
|
5344
|
-
|
|
5345
5567
|
// ../agent/src/import/coverage.ts
|
|
5346
5568
|
function extractRuleLines(text) {
|
|
5347
5569
|
const out = [];
|
|
@@ -5850,6 +6072,69 @@ var init_command_registry = __esm({
|
|
|
5850
6072
|
}
|
|
5851
6073
|
});
|
|
5852
6074
|
|
|
6075
|
+
// ../agent/src/import/skill-prompt.ts
|
|
6076
|
+
function buildSkillsSection(skills, budget = SKILLS_BUDGET_CHARS) {
|
|
6077
|
+
if (skills.length === 0) return { text: "", trimmed: [], omitted: [] };
|
|
6078
|
+
const header = "\n## Skills (imported)\nCall the `skill` tool with a skill name to load its full instructions before doing work it covers.\n";
|
|
6079
|
+
const clean = skills.map((s) => ({ name: s.name, full: s.description.replace(/\s+/g, " ").trim() }));
|
|
6080
|
+
const descs = new Map(clean.map((s) => [s.name, s.full]));
|
|
6081
|
+
const line = (n) => `- ${n}: ${descs.get(n)}
|
|
6082
|
+
`;
|
|
6083
|
+
const size = (names2) => header.length + names2.reduce((a, n) => a + line(n).length, 0);
|
|
6084
|
+
let names = clean.map((s) => s.name);
|
|
6085
|
+
const trimmed = /* @__PURE__ */ new Set();
|
|
6086
|
+
while (size(names) > budget) {
|
|
6087
|
+
const longest = [...names].sort((a, b2) => descs.get(b2).length - descs.get(a).length)[0];
|
|
6088
|
+
const d = descs.get(longest);
|
|
6089
|
+
if (d.length <= MIN_DESC) break;
|
|
6090
|
+
descs.set(longest, `${d.slice(0, Math.max(MIN_DESC, Math.floor(d.length * 0.7)) - 1)}\u2026`);
|
|
6091
|
+
trimmed.add(longest);
|
|
6092
|
+
}
|
|
6093
|
+
const omitted = [];
|
|
6094
|
+
while (size(names) > budget && names.length > 0) omitted.unshift(names.pop());
|
|
6095
|
+
return { text: names.length ? header + names.map(line).join("") : "", trimmed: [...trimmed], omitted };
|
|
6096
|
+
}
|
|
6097
|
+
var SKILLS_BUDGET_CHARS, MIN_DESC;
|
|
6098
|
+
var init_skill_prompt = __esm({
|
|
6099
|
+
"../agent/src/import/skill-prompt.ts"() {
|
|
6100
|
+
"use strict";
|
|
6101
|
+
SKILLS_BUDGET_CHARS = 4e3;
|
|
6102
|
+
MIN_DESC = 20;
|
|
6103
|
+
}
|
|
6104
|
+
});
|
|
6105
|
+
|
|
6106
|
+
// ../agent/src/import/mcp-json-summary.ts
|
|
6107
|
+
function listMcpSpawns(text) {
|
|
6108
|
+
const result = [];
|
|
6109
|
+
let data;
|
|
6110
|
+
try {
|
|
6111
|
+
data = JSON.parse(text);
|
|
6112
|
+
} catch {
|
|
6113
|
+
return result;
|
|
6114
|
+
}
|
|
6115
|
+
if (typeof data !== "object" || data === null) return result;
|
|
6116
|
+
const mcpServers = data["mcpServers"];
|
|
6117
|
+
if (typeof mcpServers !== "object" || mcpServers === null) return result;
|
|
6118
|
+
for (const [name, value] of Object.entries(mcpServers)) {
|
|
6119
|
+
if (typeof value !== "object" || value === null) continue;
|
|
6120
|
+
const entry = value;
|
|
6121
|
+
const command = entry["command"];
|
|
6122
|
+
if (typeof command !== "string") continue;
|
|
6123
|
+
const argsRaw = entry["args"];
|
|
6124
|
+
const args = Array.isArray(argsRaw) ? argsRaw.filter((a) => typeof a === "string") : [];
|
|
6125
|
+
const envRaw = entry["env"];
|
|
6126
|
+
const envNames = typeof envRaw === "object" && envRaw !== null ? Object.keys(envRaw).sort() : [];
|
|
6127
|
+
result.push({ name, command, args, envNames });
|
|
6128
|
+
}
|
|
6129
|
+
result.sort((a, b2) => a.name.localeCompare(b2.name));
|
|
6130
|
+
return result;
|
|
6131
|
+
}
|
|
6132
|
+
var init_mcp_json_summary = __esm({
|
|
6133
|
+
"../agent/src/import/mcp-json-summary.ts"() {
|
|
6134
|
+
"use strict";
|
|
6135
|
+
}
|
|
6136
|
+
});
|
|
6137
|
+
|
|
5853
6138
|
// ../agent/src/index.ts
|
|
5854
6139
|
var init_src = __esm({
|
|
5855
6140
|
"../agent/src/index.ts"() {
|
|
@@ -5894,6 +6179,11 @@ var init_src = __esm({
|
|
|
5894
6179
|
init_command_file();
|
|
5895
6180
|
init_command_registry();
|
|
5896
6181
|
init_claude_sources();
|
|
6182
|
+
init_skill_file();
|
|
6183
|
+
init_skill_prompt();
|
|
6184
|
+
init_skill_tool();
|
|
6185
|
+
init_skill_source();
|
|
6186
|
+
init_mcp_json_summary();
|
|
5897
6187
|
}
|
|
5898
6188
|
});
|
|
5899
6189
|
|
|
@@ -5934,14 +6224,241 @@ var init_api_key = __esm({
|
|
|
5934
6224
|
}
|
|
5935
6225
|
});
|
|
5936
6226
|
|
|
6227
|
+
// src/import-gate.ts
|
|
6228
|
+
var import_gate_exports = {};
|
|
6229
|
+
__export(import_gate_exports, {
|
|
6230
|
+
REPO_SOURCE_FILES: () => REPO_SOURCE_FILES,
|
|
6231
|
+
createNodeTrustStore: () => createNodeTrustStore,
|
|
6232
|
+
importGateStatus: () => importGateStatus,
|
|
6233
|
+
nodeTrustFs: () => nodeTrustFs,
|
|
6234
|
+
readRepoSources: () => readRepoSources,
|
|
6235
|
+
sha256: () => sha256,
|
|
6236
|
+
writeTextAtomic: () => writeTextAtomic
|
|
6237
|
+
});
|
|
6238
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
6239
|
+
import { chmod, mkdir, readFile, readdir, realpath, rename, writeFile } from "node:fs/promises";
|
|
6240
|
+
import { dirname as dirname3, join as join2 } from "node:path";
|
|
6241
|
+
async function readOrNull(path12) {
|
|
6242
|
+
try {
|
|
6243
|
+
return await readFile(path12, "utf8");
|
|
6244
|
+
} catch (err) {
|
|
6245
|
+
if (err.code === "ENOENT") return null;
|
|
6246
|
+
throw err;
|
|
6247
|
+
}
|
|
6248
|
+
}
|
|
6249
|
+
async function writeTextAtomic(path12, text) {
|
|
6250
|
+
await mkdir(dirname3(path12), { recursive: true });
|
|
6251
|
+
const tmp = `${path12}.${process.pid}.${randomUUID()}.tmp`;
|
|
6252
|
+
await writeFile(tmp, text, { mode: 384 });
|
|
6253
|
+
await chmod(tmp, 384);
|
|
6254
|
+
await rename(tmp, path12);
|
|
6255
|
+
}
|
|
6256
|
+
async function readRepoSources(cwd2) {
|
|
6257
|
+
const files = {};
|
|
6258
|
+
for (const name of REPO_SOURCE_FILES) {
|
|
6259
|
+
const text = await readOrNull(join2(cwd2, name));
|
|
6260
|
+
if (text !== null) files[name] = text;
|
|
6261
|
+
}
|
|
6262
|
+
let entries = [];
|
|
6263
|
+
try {
|
|
6264
|
+
entries = await readdir(join2(cwd2, ".claude", "commands"));
|
|
6265
|
+
} catch (err) {
|
|
6266
|
+
if (err.code !== "ENOENT" && err.code !== "ENOTDIR") throw err;
|
|
6267
|
+
}
|
|
6268
|
+
for (const e of entries.filter((x2) => x2.endsWith(".md")).sort()) {
|
|
6269
|
+
const text = await readOrNull(join2(cwd2, ".claude", "commands", e));
|
|
6270
|
+
if (text !== null) files[`.claude/commands/${e}`] = text;
|
|
6271
|
+
}
|
|
6272
|
+
await readRepoSkillFiles(cwd2, files);
|
|
6273
|
+
return files;
|
|
6274
|
+
}
|
|
6275
|
+
async function readRepoSkillFiles(cwd2, files) {
|
|
6276
|
+
const root = join2(cwd2, ".claude", "skills");
|
|
6277
|
+
let count = 0;
|
|
6278
|
+
const walk = async (dir, rel) => {
|
|
6279
|
+
let entries;
|
|
6280
|
+
try {
|
|
6281
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
6282
|
+
} catch (err) {
|
|
6283
|
+
const code = err.code;
|
|
6284
|
+
if (code === "ENOENT" || code === "ENOTDIR") return;
|
|
6285
|
+
throw err;
|
|
6286
|
+
}
|
|
6287
|
+
for (const e of entries.sort((a, b2) => a.name < b2.name ? -1 : 1)) {
|
|
6288
|
+
if (e.name.startsWith(".") || e.isSymbolicLink()) continue;
|
|
6289
|
+
const key = `${rel}/${e.name}`;
|
|
6290
|
+
if (e.isDirectory()) await walk(join2(dir, e.name), key);
|
|
6291
|
+
else if (e.isFile() && count < MAX_REPO_SKILL_FILES) {
|
|
6292
|
+
const text = await readOrNull(join2(dir, e.name));
|
|
6293
|
+
if (text !== null && !text.includes("\0") && Buffer.byteLength(text, "utf8") <= MAX_REPO_SKILL_BYTES) {
|
|
6294
|
+
files[key] = text;
|
|
6295
|
+
count++;
|
|
6296
|
+
}
|
|
6297
|
+
}
|
|
6298
|
+
}
|
|
6299
|
+
};
|
|
6300
|
+
await walk(root, ".claude/skills");
|
|
6301
|
+
}
|
|
6302
|
+
function nodeTrustFs() {
|
|
6303
|
+
return { readText: readOrNull, writeTextAtomic, realPath: (p) => realpath(p) };
|
|
6304
|
+
}
|
|
6305
|
+
function createNodeTrustStore(configDir) {
|
|
6306
|
+
let store = trustStores.get(configDir);
|
|
6307
|
+
if (!store) {
|
|
6308
|
+
store = new TrustStore({ fs: nodeTrustFs(), path: join2(configDir, "trust.json"), hash: sha256 });
|
|
6309
|
+
trustStores.set(configDir, store);
|
|
6310
|
+
}
|
|
6311
|
+
return store;
|
|
6312
|
+
}
|
|
6313
|
+
async function importGateStatus(deps) {
|
|
6314
|
+
const lines2 = [];
|
|
6315
|
+
const configPath = join2(deps.configDir, "imports.json");
|
|
6316
|
+
try {
|
|
6317
|
+
const loaded = parseImportsConfig(await readOrNull(configPath), configPath);
|
|
6318
|
+
if (loaded.error) lines2.push(`imports: ${loaded.error} (every category off)`);
|
|
6319
|
+
else {
|
|
6320
|
+
const on = IMPORT_CATEGORIES.filter((c) => loaded.config.categories[c]);
|
|
6321
|
+
lines2.push(on.length ? `imports: on for ${on.join(", ")}` : "imports: every category off (default)");
|
|
6322
|
+
}
|
|
6323
|
+
} catch (err) {
|
|
6324
|
+
lines2.push(`imports: not read (${err instanceof Error ? err.message : String(err)}); every category off`);
|
|
6325
|
+
}
|
|
6326
|
+
try {
|
|
6327
|
+
const files = await readRepoSources(deps.cwd);
|
|
6328
|
+
const found = Object.keys(files);
|
|
6329
|
+
if (found.length === 0) return lines2;
|
|
6330
|
+
const status = await createNodeTrustStore(deps.configDir).repoStatus(deps.cwd, files);
|
|
6331
|
+
const names = found.join(", ");
|
|
6332
|
+
if (status === "trusted" || status === "session") lines2.push(`repo sources: ${names} (trusted${status === "session" ? " for this session" : ""})`);
|
|
6333
|
+
else if (status === "never") lines2.push(`repo sources: ${names} NOT loaded (marked never trust)`);
|
|
6334
|
+
else lines2.push(`repo sources: ${names} NOT loaded (untrusted${deps.interactive ? "" : ", non-interactive run"})`);
|
|
6335
|
+
} catch (err) {
|
|
6336
|
+
lines2.push(`repo sources: NOT loaded (trust check failed: ${err instanceof Error ? err.message : String(err)})`);
|
|
6337
|
+
}
|
|
6338
|
+
return lines2;
|
|
6339
|
+
}
|
|
6340
|
+
var REPO_SOURCE_FILES, MAX_REPO_SKILL_FILES, MAX_REPO_SKILL_BYTES, sha256, trustStores;
|
|
6341
|
+
var init_import_gate = __esm({
|
|
6342
|
+
"src/import-gate.ts"() {
|
|
6343
|
+
"use strict";
|
|
6344
|
+
init_src();
|
|
6345
|
+
REPO_SOURCE_FILES = [".mcp.json", ".claude/settings.json", ".claude/settings.local.json", "CLAUDE.local.md"];
|
|
6346
|
+
MAX_REPO_SKILL_FILES = 200;
|
|
6347
|
+
MAX_REPO_SKILL_BYTES = 256 * 1024;
|
|
6348
|
+
sha256 = (text) => createHash("sha256").update(text).digest("hex");
|
|
6349
|
+
trustStores = /* @__PURE__ */ new Map();
|
|
6350
|
+
}
|
|
6351
|
+
});
|
|
6352
|
+
|
|
6353
|
+
// src/skills-loader.ts
|
|
6354
|
+
import { join as join3 } from "node:path";
|
|
6355
|
+
function repoSkillSource(sources, parse, problems) {
|
|
6356
|
+
const dirs = /* @__PURE__ */ new Map();
|
|
6357
|
+
for (const key of Object.keys(sources).filter((k2) => k2.startsWith(REPO_PREFIX) && k2.endsWith("/SKILL.md") && k2.split("/").length === 4)) {
|
|
6358
|
+
const parsed = parse(sources[key]);
|
|
6359
|
+
if (!parsed.ok) problems.push(`${key}: malformed skill (${parsed.error})`);
|
|
6360
|
+
else if (dirs.has(parsed.name)) problems.push(`${key}: duplicate skill name "${parsed.name}"`);
|
|
6361
|
+
else dirs.set(parsed.name, { dir: key.split("/")[2], description: parsed.description });
|
|
6362
|
+
}
|
|
6363
|
+
const prefix = (name) => `${REPO_PREFIX}${dirs.get(name)?.dir}/`;
|
|
6364
|
+
return {
|
|
6365
|
+
async list() {
|
|
6366
|
+
return [...dirs].map(([name, d]) => ({
|
|
6367
|
+
name,
|
|
6368
|
+
description: d.description,
|
|
6369
|
+
scope: "repo",
|
|
6370
|
+
scripts: Object.keys(sources).some((k2) => k2.startsWith(prefix(name)) && SCRIPT_RE2.test(k2.slice(prefix(name).length)))
|
|
6371
|
+
}));
|
|
6372
|
+
},
|
|
6373
|
+
async body(name) {
|
|
6374
|
+
return dirs.has(name) ? sources[`${prefix(name)}SKILL.md`] ?? null : null;
|
|
6375
|
+
},
|
|
6376
|
+
async file(name, rel) {
|
|
6377
|
+
const safe = safeSkillPath(rel);
|
|
6378
|
+
return dirs.has(name) && safe ? sources[`${prefix(name)}${safe}`] ?? null : null;
|
|
6379
|
+
}
|
|
6380
|
+
};
|
|
6381
|
+
}
|
|
6382
|
+
function mergeSkillSources(repo, user) {
|
|
6383
|
+
return {
|
|
6384
|
+
async list() {
|
|
6385
|
+
const r = await repo?.list() ?? [];
|
|
6386
|
+
const names = new Set(r.map((s) => s.name));
|
|
6387
|
+
return [...r, ...(await user?.list() ?? []).filter((s) => !names.has(s.name))];
|
|
6388
|
+
},
|
|
6389
|
+
async body(name) {
|
|
6390
|
+
return await repo?.body(name) ?? null ?? await user?.body(name) ?? null;
|
|
6391
|
+
},
|
|
6392
|
+
async file(name, rel) {
|
|
6393
|
+
if (await repo?.body(name) != null) return repo.file(name, rel);
|
|
6394
|
+
return await user?.file(name, rel) ?? null;
|
|
6395
|
+
}
|
|
6396
|
+
};
|
|
6397
|
+
}
|
|
6398
|
+
async function loadSkills(deps) {
|
|
6399
|
+
const configPath = join3(deps.configDir, "imports.json");
|
|
6400
|
+
try {
|
|
6401
|
+
const loaded = parseImportsConfig(await deps.readFile(configPath).catch((err) => {
|
|
6402
|
+
if (err.code === "ENOENT") return null;
|
|
6403
|
+
throw err;
|
|
6404
|
+
}), configPath);
|
|
6405
|
+
if (loaded.error) return NONE("skills: off (imports.json unreadable)");
|
|
6406
|
+
if (!loaded.config.categories.skills) return NONE("skills: off (imports.json has skills off)");
|
|
6407
|
+
} catch (err) {
|
|
6408
|
+
return NONE(`skills: off (imports.json not read: ${err instanceof Error ? err.message : String(err)})`);
|
|
6409
|
+
}
|
|
6410
|
+
const lines2 = [];
|
|
6411
|
+
const problems = [];
|
|
6412
|
+
let repo = null;
|
|
6413
|
+
try {
|
|
6414
|
+
const sources = await readRepoSources(deps.cwd);
|
|
6415
|
+
if (Object.keys(sources).some((k2) => k2.startsWith(REPO_PREFIX))) {
|
|
6416
|
+
const status = await createNodeTrustStore(deps.configDir).repoStatus(deps.cwd, sources);
|
|
6417
|
+
if (status === "trusted" || status === "session") repo = repoSkillSource(sources, deps.parse, problems);
|
|
6418
|
+
else lines2.push(`skills: repo skills NOT loaded (${status === "never" ? "marked never trust" : "untrusted repo"})`);
|
|
6419
|
+
}
|
|
6420
|
+
} catch (err) {
|
|
6421
|
+
lines2.push(`skills: repo skills NOT loaded (${err instanceof Error ? err.message : String(err)})`);
|
|
6422
|
+
}
|
|
6423
|
+
let user = null;
|
|
6424
|
+
if (deps.client) {
|
|
6425
|
+
const source2 = createProfileSkillSource(deps.client);
|
|
6426
|
+
try {
|
|
6427
|
+
await source2.list();
|
|
6428
|
+
user = source2;
|
|
6429
|
+
} catch (err) {
|
|
6430
|
+
lines2.push(`skills: user skills NOT loaded (${err instanceof Error ? err.message : String(err)})`);
|
|
6431
|
+
}
|
|
6432
|
+
} else lines2.push("skills: user skills NOT loaded (not signed in)");
|
|
6433
|
+
const source = mergeSkillSources(repo, user);
|
|
6434
|
+
const all = await source.list();
|
|
6435
|
+
const shadowed = repo && user ? (await user.list()).filter((u3) => all.some((a) => a.name === u3.name && a.scope === "repo")).map((u3) => u3.name) : [];
|
|
6436
|
+
for (const p of problems) lines2.push(`skills: ${p}`);
|
|
6437
|
+
const section2 = buildSkillsSection(all);
|
|
6438
|
+
lines2.unshift(`skills: ${all.filter((s) => s.scope === "user").length} user, ${all.filter((s) => s.scope === "repo").length} repo${shadowed.length ? `; repo shadows user: ${shadowed.join(", ")}` : ""}${section2.trimmed.length ? `; descriptions trimmed: ${section2.trimmed.join(", ")}` : ""}${section2.omitted.length ? `; NOT listed (over the 4k prompt budget): ${section2.omitted.join(", ")}` : ""}`);
|
|
6439
|
+
if (all.length === 0) return { ...NONE(lines2[0]), statusLines: lines2, source };
|
|
6440
|
+
return { promptSection: section2.text, tools: [SKILL_TOOL], handlers: { skill: createSkillTool(source) }, statusLines: lines2, source };
|
|
6441
|
+
}
|
|
6442
|
+
var NONE, REPO_PREFIX, SCRIPT_RE2;
|
|
6443
|
+
var init_skills_loader = __esm({
|
|
6444
|
+
"src/skills-loader.ts"() {
|
|
6445
|
+
"use strict";
|
|
6446
|
+
init_src();
|
|
6447
|
+
init_import_gate();
|
|
6448
|
+
NONE = (line) => ({ promptSection: "", tools: [], handlers: {}, statusLines: [line], source: null });
|
|
6449
|
+
REPO_PREFIX = ".claude/skills/";
|
|
6450
|
+
SCRIPT_RE2 = /^(scripts\/|.*\.(sh|py|js|mjs|ts|rb|pl)$)/;
|
|
6451
|
+
}
|
|
6452
|
+
});
|
|
6453
|
+
|
|
5937
6454
|
// src/profile-runtime.ts
|
|
5938
|
-
import { join as
|
|
6455
|
+
import { join as join4 } from "node:path";
|
|
5939
6456
|
function backendUrl(env3) {
|
|
5940
6457
|
return env3.SPACECODE_BACKEND_URL?.trim() || env3.SPACECODE_API_URL?.trim() || DEFAULT_BACKEND_URL;
|
|
5941
6458
|
}
|
|
5942
6459
|
function fileCache(deps) {
|
|
5943
|
-
const dir =
|
|
5944
|
-
const file =
|
|
6460
|
+
const dir = join4(deps.env.SPACECODE_CONFIG_DIR?.trim() || join4(deps.homedir(), ".spacecode"));
|
|
6461
|
+
const file = join4(dir, "profile-cache.json");
|
|
5945
6462
|
return {
|
|
5946
6463
|
async read() {
|
|
5947
6464
|
let raw;
|
|
@@ -5987,8 +6504,15 @@ async function prepareProfileUnsafe(deps) {
|
|
|
5987
6504
|
const root = await deps.gitRoot?.() ?? null;
|
|
5988
6505
|
const keys = [.../* @__PURE__ */ new Set([projectKey(deps.cwd), ...root ? [projectKey(root)] : []])];
|
|
5989
6506
|
const client = createProfileClient({ baseUrl: backendUrl(deps.env), apiKey, fetch: deps.fetch, timeoutMs: 3e3 });
|
|
6507
|
+
const skills = await loadSkills({
|
|
6508
|
+
configDir: deps.env.SPACECODE_CONFIG_DIR?.trim() || join4(deps.homedir(), ".spacecode"),
|
|
6509
|
+
cwd: deps.cwd,
|
|
6510
|
+
readFile: deps.readFile,
|
|
6511
|
+
client,
|
|
6512
|
+
parse: parseSkillFile
|
|
6513
|
+
});
|
|
5990
6514
|
const result = await loadProfile({ client, cache: fileCache(deps), projectKeys: keys });
|
|
5991
|
-
if (!result.profile) return EMPTY([`profile: not loaded (${result.error ?? "unknown error"})
|
|
6515
|
+
if (!result.profile) return { ...EMPTY([`profile: not loaded (${result.error ?? "unknown error"})`, ...skills.statusLines]), promptSection: skills.promptSection, tools: skills.tools, handlers: skills.handlers, skills: skills.source };
|
|
5992
6516
|
const key = result.profile.projectKey;
|
|
5993
6517
|
const hasMemory = result.profile.memoryIndex !== null && key !== null;
|
|
5994
6518
|
const hasSections = result.profile.instructions?.includes("## More instructions (on demand)") ?? false;
|
|
@@ -5997,10 +6521,13 @@ async function prepareProfileUnsafe(deps) {
|
|
|
5997
6521
|
`profile: v${result.profile.version} ${result.source === "cache" ? "(STALE: served from cache)" : "(live)"}${result.profile.instructions ? ", instructions" : ""}${hasSections ? ", adapted (on-demand sections)" : ""}${hasMemory ? `, memory "${key}"` : ""}${section2.truncated ? ", TRUNCATED to fit the prompt budget" : ""}`
|
|
5998
6522
|
];
|
|
5999
6523
|
if (result.error) lines2.push(`profile warning: ${result.error}`);
|
|
6524
|
+
lines2.push(...skills.statusLines);
|
|
6000
6525
|
return {
|
|
6001
|
-
promptSection: section2.text,
|
|
6002
|
-
|
|
6526
|
+
promptSection: section2.text + skills.promptSection,
|
|
6527
|
+
skills: skills.source,
|
|
6528
|
+
tools: [...hasMemory ? [MEMORY_READ_TOOL] : [], ...hasSections ? [INSTRUCTIONS_READ_TOOL] : [], ...skills.tools],
|
|
6003
6529
|
handlers: {
|
|
6530
|
+
...skills.handlers,
|
|
6004
6531
|
...hasMemory ? { memory_read: createMemoryReadTool(client, key) } : {},
|
|
6005
6532
|
...hasSections ? { instructions_read: createInstructionsReadTool(client) } : {}
|
|
6006
6533
|
},
|
|
@@ -6013,6 +6540,8 @@ var init_profile_runtime = __esm({
|
|
|
6013
6540
|
"use strict";
|
|
6014
6541
|
init_src();
|
|
6015
6542
|
init_api_key();
|
|
6543
|
+
init_skills_loader();
|
|
6544
|
+
init_src();
|
|
6016
6545
|
DEFAULT_BACKEND_URL = "https://backend.spacecode.app";
|
|
6017
6546
|
EMPTY = (statusLines) => ({ promptSection: "", tools: [], handlers: {}, statusLines });
|
|
6018
6547
|
}
|
|
@@ -6226,6 +6755,86 @@ var init_import_claude = __esm({
|
|
|
6226
6755
|
}
|
|
6227
6756
|
});
|
|
6228
6757
|
|
|
6758
|
+
// src/repo-trust-prompt.ts
|
|
6759
|
+
var repo_trust_prompt_exports = {};
|
|
6760
|
+
__export(repo_trust_prompt_exports, {
|
|
6761
|
+
describeRepoSources: () => describeRepoSources,
|
|
6762
|
+
promptRepoTrust: () => promptRepoTrust
|
|
6763
|
+
});
|
|
6764
|
+
function ruleLists(path12, text, lines2, warnings) {
|
|
6765
|
+
let data;
|
|
6766
|
+
try {
|
|
6767
|
+
data = JSON.parse(text);
|
|
6768
|
+
} catch (err) {
|
|
6769
|
+
if (!(err instanceof SyntaxError)) throw err;
|
|
6770
|
+
warnings.push(`${path12}: not valid JSON, nothing from it loads`);
|
|
6771
|
+
return;
|
|
6772
|
+
}
|
|
6773
|
+
const perms = typeof data === "object" && data !== null ? data.permissions : void 0;
|
|
6774
|
+
if (typeof perms !== "object" || perms === null) return;
|
|
6775
|
+
for (const effect of ["allow", "deny", "ask"]) {
|
|
6776
|
+
const list2 = perms[effect];
|
|
6777
|
+
if (!Array.isArray(list2)) continue;
|
|
6778
|
+
for (const rule of list2) if (typeof rule === "string") lines2.push(` ${effect}: ${show(rule)} (${path12})`);
|
|
6779
|
+
}
|
|
6780
|
+
}
|
|
6781
|
+
function describeRepoSources(files) {
|
|
6782
|
+
const out = [];
|
|
6783
|
+
const warnings = [];
|
|
6784
|
+
if (files[".mcp.json"] !== void 0) {
|
|
6785
|
+
let valid = true;
|
|
6786
|
+
try {
|
|
6787
|
+
JSON.parse(files[".mcp.json"]);
|
|
6788
|
+
} catch (err) {
|
|
6789
|
+
if (!(err instanceof SyntaxError)) throw err;
|
|
6790
|
+
valid = false;
|
|
6791
|
+
warnings.push(".mcp.json: not valid JSON, nothing from it loads");
|
|
6792
|
+
}
|
|
6793
|
+
for (const s of valid ? listMcpSpawns(files[".mcp.json"]) : []) {
|
|
6794
|
+
const env3 = s.envNames.length ? ` env: ${s.envNames.join(", ")}` : "";
|
|
6795
|
+
out.push(` spawn: ${show(s.command)} ${s.args.map(show).join(" ")} [server ${show(s.name)}]${env3}`);
|
|
6796
|
+
}
|
|
6797
|
+
}
|
|
6798
|
+
for (const p of [".claude/settings.json", ".claude/settings.local.json"]) {
|
|
6799
|
+
if (files[p] !== void 0) ruleLists(p, files[p], out, warnings);
|
|
6800
|
+
}
|
|
6801
|
+
const names = Object.keys(files).sort();
|
|
6802
|
+
for (const k2 of names) {
|
|
6803
|
+
const m2 = /^\.claude\/commands\/([^/]+)\.md$/.exec(k2);
|
|
6804
|
+
if (m2) out.push(` command: /${show(m2[1]).slice(1, -1)}`);
|
|
6805
|
+
}
|
|
6806
|
+
const skills = /* @__PURE__ */ new Set();
|
|
6807
|
+
for (const k2 of names) {
|
|
6808
|
+
const m2 = /^\.claude\/skills\/([^/]+)\//.exec(k2);
|
|
6809
|
+
if (m2) skills.add(m2[1]);
|
|
6810
|
+
}
|
|
6811
|
+
for (const s of [...skills].sort()) out.push(` skill: ${show(s)}`);
|
|
6812
|
+
if (files["CLAUDE.local.md"] !== void 0) out.push(" instructions: CLAUDE.local.md");
|
|
6813
|
+
return [...warnings.map((w2) => ` warning: ${w2}`), ...out];
|
|
6814
|
+
}
|
|
6815
|
+
async function promptRepoTrust(cwd2, files, io) {
|
|
6816
|
+
io.write(`
|
|
6817
|
+
This repository has Claude Code sources that can run code or change permissions:
|
|
6818
|
+
${cwd2}
|
|
6819
|
+
${describeRepoSources(files).join("\n")}
|
|
6820
|
+
|
|
6821
|
+
`);
|
|
6822
|
+
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");
|
|
6823
|
+
const answer = (await io.ask("Choose 1-4 [4]: "))?.trim() ?? "";
|
|
6824
|
+
if (answer === "1") return "trusted";
|
|
6825
|
+
if (answer === "2") return "session";
|
|
6826
|
+
if (answer === "3") return "never";
|
|
6827
|
+
return "skip";
|
|
6828
|
+
}
|
|
6829
|
+
var show;
|
|
6830
|
+
var init_repo_trust_prompt = __esm({
|
|
6831
|
+
"src/repo-trust-prompt.ts"() {
|
|
6832
|
+
"use strict";
|
|
6833
|
+
init_src();
|
|
6834
|
+
show = (s) => JSON.stringify(s);
|
|
6835
|
+
}
|
|
6836
|
+
});
|
|
6837
|
+
|
|
6229
6838
|
// ../../node_modules/es-toolkit/dist/function/debounce.mjs
|
|
6230
6839
|
function debounce(func, debounceMs, { signal, edges } = {}) {
|
|
6231
6840
|
let pendingThis = void 0;
|
|
@@ -41385,7 +41994,7 @@ var require_core = __commonJS({
|
|
|
41385
41994
|
return match && match.index === 0;
|
|
41386
41995
|
}
|
|
41387
41996
|
var BACKREF_RE = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;
|
|
41388
|
-
function
|
|
41997
|
+
function join9(regexps, separator = "|") {
|
|
41389
41998
|
let numCaptures = 0;
|
|
41390
41999
|
return regexps.map((regex2) => {
|
|
41391
42000
|
numCaptures += 1;
|
|
@@ -41689,7 +42298,7 @@ var require_core = __commonJS({
|
|
|
41689
42298
|
this.exec = () => null;
|
|
41690
42299
|
}
|
|
41691
42300
|
const terminators = this.regexes.map((el) => el[1]);
|
|
41692
|
-
this.matcherRe = langRe(
|
|
42301
|
+
this.matcherRe = langRe(join9(terminators), true);
|
|
41693
42302
|
this.lastIndex = 0;
|
|
41694
42303
|
}
|
|
41695
42304
|
/** @param {string} s */
|
|
@@ -99195,7 +99804,7 @@ var init_credential_filter = __esm({
|
|
|
99195
99804
|
});
|
|
99196
99805
|
|
|
99197
99806
|
// src/session/session-store.ts
|
|
99198
|
-
import { chmod, mkdir, open, readdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
99807
|
+
import { chmod as chmod2, mkdir as mkdir2, open, readdir as readdir2, readFile as readFile2, rename as rename2, rm, stat, writeFile as writeFile2 } from "node:fs/promises";
|
|
99199
99808
|
import { randomBytes } from "node:crypto";
|
|
99200
99809
|
import path3 from "node:path";
|
|
99201
99810
|
function redactCredentials(value) {
|
|
@@ -99284,11 +99893,11 @@ var init_session_store = __esm({
|
|
|
99284
99893
|
}
|
|
99285
99894
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
99286
99895
|
try {
|
|
99287
|
-
await
|
|
99288
|
-
await
|
|
99289
|
-
await
|
|
99290
|
-
await
|
|
99291
|
-
await
|
|
99896
|
+
await mkdir2(this.dir, { recursive: true, mode: 448 });
|
|
99897
|
+
await chmod2(this.dir, 448);
|
|
99898
|
+
await writeFile2(tmp, body, { mode: 384 });
|
|
99899
|
+
await chmod2(tmp, 384);
|
|
99900
|
+
await rename2(tmp, file);
|
|
99292
99901
|
} catch (err) {
|
|
99293
99902
|
throw new SessionError(file, `Could not save session: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
99294
99903
|
}
|
|
@@ -99301,7 +99910,7 @@ var init_session_store = __esm({
|
|
|
99301
99910
|
if (size > MAX_SESSION_BYTES) {
|
|
99302
99911
|
throw new SessionError(file, `Session is ${size} bytes, over the ${MAX_SESSION_BYTES}-byte cap; not loaded`);
|
|
99303
99912
|
}
|
|
99304
|
-
text = await
|
|
99913
|
+
text = await readFile2(file, "utf8");
|
|
99305
99914
|
} catch (err) {
|
|
99306
99915
|
if (err instanceof SessionError) throw err;
|
|
99307
99916
|
throw new SessionError(file, `Could not read session: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
@@ -99314,7 +99923,7 @@ var init_session_store = __esm({
|
|
|
99314
99923
|
async list() {
|
|
99315
99924
|
let names;
|
|
99316
99925
|
try {
|
|
99317
|
-
names = await
|
|
99926
|
+
names = await readdir2(this.dir);
|
|
99318
99927
|
} catch (err) {
|
|
99319
99928
|
if (err.code === "ENOENT") return { sessions: [], errors: [] };
|
|
99320
99929
|
throw new SessionError(this.dir, `Could not list sessions: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
@@ -100492,6 +101101,7 @@ var init_slash_commands = __esm({
|
|
|
100492
101101
|
{ name: "model", description: "List gateway models; /model <n|id> switches for this session" },
|
|
100493
101102
|
{ name: "turns", description: "Browse earlier turns and open their steps (shortcut: ctrl+o)" },
|
|
100494
101103
|
{ name: "commands", description: "List the slash commands imported from Claude Code" },
|
|
101104
|
+
{ name: "skills", description: "List imported skills; /skills show <name> [file], /skills delete <name>" },
|
|
100495
101105
|
{ name: "tools", description: "List the tools available to the agent" },
|
|
100496
101106
|
{ name: "exit", description: "Quit the REPL (alias: /quit)" },
|
|
100497
101107
|
{ name: "quit", description: "Quit the REPL" }
|
|
@@ -100508,7 +101118,7 @@ function validateSubmission(text, extra = []) {
|
|
|
100508
101118
|
function withCursor(row, col) {
|
|
100509
101119
|
return `${row.slice(0, col)}\x1B[7m${row[col] ?? " "}\x1B[27m${row.slice(col + 1)}`;
|
|
100510
101120
|
}
|
|
100511
|
-
function PromptEditor({ onSubmit, disabled, history, listFiles:
|
|
101121
|
+
function PromptEditor({ onSubmit, disabled, history, listFiles: listFiles3, onTab, locked = false, promptGlyph: promptGlyph2 = ">", onEmptyEnter, unicode = true, fullWidth = false, extraCommands = [] }) {
|
|
100512
101122
|
const { exit } = use_app_default();
|
|
100513
101123
|
const { columns } = useTerminalSize();
|
|
100514
101124
|
const { isRawModeSupported } = use_stdin_default();
|
|
@@ -100544,15 +101154,15 @@ function PromptEditor({ onSubmit, disabled, history, listFiles: listFiles2, onTa
|
|
|
100544
101154
|
const mention = shell ? null : activeMention(buf.text, buf.cursor);
|
|
100545
101155
|
const mentionOpen = mention !== null;
|
|
100546
101156
|
(0, import_react29.useEffect)(() => {
|
|
100547
|
-
if (!mentionOpen || !
|
|
101157
|
+
if (!mentionOpen || !listFiles3 || filesRef.current !== null || loadingFiles.current) return;
|
|
100548
101158
|
loadingFiles.current = true;
|
|
100549
|
-
|
|
101159
|
+
listFiles3().then((f) => {
|
|
100550
101160
|
filesRef.current = f;
|
|
100551
101161
|
setFiles(f);
|
|
100552
101162
|
}).catch((err) => fail("Could not list files", err)).finally(() => {
|
|
100553
101163
|
loadingFiles.current = false;
|
|
100554
101164
|
});
|
|
100555
|
-
}, [mentionOpen,
|
|
101165
|
+
}, [mentionOpen, listFiles3, fail]);
|
|
100556
101166
|
const chooseSel = (n) => {
|
|
100557
101167
|
selRef.current = n;
|
|
100558
101168
|
setSel(n);
|
|
@@ -100787,24 +101397,24 @@ var init_history_store = __esm({
|
|
|
100787
101397
|
});
|
|
100788
101398
|
|
|
100789
101399
|
// src/editor/history-file-io.ts
|
|
100790
|
-
import { chmod as
|
|
101400
|
+
import { chmod as chmod3, mkdir as mkdir3, readFile as readFile3, rename as rename3, writeFile as writeFile3 } from "node:fs/promises";
|
|
100791
101401
|
import path4 from "node:path";
|
|
100792
101402
|
function createFileHistoryIO(file) {
|
|
100793
101403
|
return {
|
|
100794
101404
|
async read() {
|
|
100795
101405
|
try {
|
|
100796
|
-
return await
|
|
101406
|
+
return await readFile3(file, "utf8");
|
|
100797
101407
|
} catch (err) {
|
|
100798
101408
|
if (err.code === "ENOENT") return null;
|
|
100799
101409
|
throw err;
|
|
100800
101410
|
}
|
|
100801
101411
|
},
|
|
100802
101412
|
async write(content) {
|
|
100803
|
-
await
|
|
101413
|
+
await mkdir3(path4.dirname(file), { recursive: true, mode: 448 });
|
|
100804
101414
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
100805
|
-
await
|
|
100806
|
-
await
|
|
100807
|
-
await
|
|
101415
|
+
await writeFile3(tmp, content, { mode: 384 });
|
|
101416
|
+
await chmod3(tmp, 384);
|
|
101417
|
+
await rename3(tmp, file);
|
|
100808
101418
|
}
|
|
100809
101419
|
};
|
|
100810
101420
|
}
|
|
@@ -101013,7 +101623,7 @@ var init_shell_turn = __esm({
|
|
|
101013
101623
|
});
|
|
101014
101624
|
|
|
101015
101625
|
// src/editor/file-list.ts
|
|
101016
|
-
async function
|
|
101626
|
+
async function listFiles2(readDir, opts = {}) {
|
|
101017
101627
|
const max = opts.maxFiles ?? DEFAULT_MAX_LISTED_FILES;
|
|
101018
101628
|
const files = [];
|
|
101019
101629
|
const queue = [""];
|
|
@@ -101240,18 +101850,18 @@ var init_mention_resolver = __esm({
|
|
|
101240
101850
|
});
|
|
101241
101851
|
|
|
101242
101852
|
// src/editor/node-mention-fs.ts
|
|
101243
|
-
import { open as open2, realpath, stat as stat2 } from "node:fs/promises";
|
|
101853
|
+
import { open as open2, realpath as realpath2, stat as stat2 } from "node:fs/promises";
|
|
101244
101854
|
import path6 from "node:path";
|
|
101245
101855
|
function isInside(root, target) {
|
|
101246
101856
|
return target === root || target.startsWith(root.endsWith(path6.sep) ? root : root + path6.sep);
|
|
101247
101857
|
}
|
|
101248
101858
|
function createNodeMentionFS(cwd2) {
|
|
101249
101859
|
let rootPromise;
|
|
101250
|
-
const root = () => rootPromise ??=
|
|
101860
|
+
const root = () => rootPromise ??= realpath2(cwd2);
|
|
101251
101861
|
async function confined(abs) {
|
|
101252
101862
|
let real;
|
|
101253
101863
|
try {
|
|
101254
|
-
real = await
|
|
101864
|
+
real = await realpath2(abs);
|
|
101255
101865
|
} catch (err) {
|
|
101256
101866
|
if (err.code === "ENOENT") return null;
|
|
101257
101867
|
throw err;
|
|
@@ -101287,11 +101897,11 @@ var init_node_mention_fs = __esm({
|
|
|
101287
101897
|
});
|
|
101288
101898
|
|
|
101289
101899
|
// src/editor/node-read-dir.ts
|
|
101290
|
-
import { readdir as
|
|
101900
|
+
import { readdir as readdir3 } from "node:fs/promises";
|
|
101291
101901
|
import path7 from "node:path";
|
|
101292
101902
|
function createNodeReadDir(cwd2) {
|
|
101293
101903
|
return async (rel) => {
|
|
101294
|
-
const entries = await
|
|
101904
|
+
const entries = await readdir3(path7.join(cwd2, rel), { withFileTypes: true });
|
|
101295
101905
|
return entries.map((e) => ({ name: e.name, isFile: e.isFile(), isDirectory: e.isDirectory() }));
|
|
101296
101906
|
};
|
|
101297
101907
|
}
|
|
@@ -101302,11 +101912,11 @@ var init_node_read_dir = __esm({
|
|
|
101302
101912
|
});
|
|
101303
101913
|
|
|
101304
101914
|
// src/editor/node-mentions.ts
|
|
101305
|
-
import { readFile as
|
|
101915
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
101306
101916
|
import path8 from "node:path";
|
|
101307
101917
|
async function readGitignore(cwd2) {
|
|
101308
101918
|
try {
|
|
101309
|
-
return createGitignoreMatcher(await
|
|
101919
|
+
return createGitignoreMatcher(await readFile4(path8.join(cwd2, ".gitignore"), "utf8"));
|
|
101310
101920
|
} catch (err) {
|
|
101311
101921
|
if (err.code === "ENOENT") return () => false;
|
|
101312
101922
|
throw err;
|
|
@@ -101320,7 +101930,7 @@ function createNodeMentions(cwd2) {
|
|
|
101320
101930
|
return formatMentionPrompt(text, result);
|
|
101321
101931
|
},
|
|
101322
101932
|
async list() {
|
|
101323
|
-
return
|
|
101933
|
+
return listFiles2(createNodeReadDir(cwd2), { ignored: await readGitignore(cwd2) });
|
|
101324
101934
|
}
|
|
101325
101935
|
};
|
|
101326
101936
|
}
|
|
@@ -101336,89 +101946,48 @@ var init_node_mentions = __esm({
|
|
|
101336
101946
|
}
|
|
101337
101947
|
});
|
|
101338
101948
|
|
|
101339
|
-
// src/
|
|
101340
|
-
|
|
101341
|
-
|
|
101342
|
-
|
|
101343
|
-
|
|
101344
|
-
|
|
101345
|
-
|
|
101346
|
-
|
|
101347
|
-
|
|
101348
|
-
|
|
101349
|
-
|
|
101350
|
-
|
|
101351
|
-
|
|
101352
|
-
|
|
101353
|
-
|
|
101354
|
-
|
|
101355
|
-
|
|
101356
|
-
|
|
101357
|
-
|
|
101358
|
-
|
|
101359
|
-
|
|
101360
|
-
|
|
101361
|
-
|
|
101362
|
-
if (
|
|
101363
|
-
|
|
101364
|
-
|
|
101365
|
-
|
|
101366
|
-
|
|
101367
|
-
|
|
101368
|
-
if (err.code !== "ENOENT" && err.code !== "ENOTDIR") throw err;
|
|
101369
|
-
}
|
|
101370
|
-
for (const e of entries.filter((x2) => x2.endsWith(".md")).sort()) {
|
|
101371
|
-
const text = await readOrNull(join4(cwd2, ".claude", "commands", e));
|
|
101372
|
-
if (text !== null) files[`.claude/commands/${e}`] = text;
|
|
101373
|
-
}
|
|
101374
|
-
return files;
|
|
101375
|
-
}
|
|
101376
|
-
function nodeTrustFs() {
|
|
101377
|
-
return { readText: readOrNull, writeTextAtomic, realPath: (p) => realpath2(p) };
|
|
101378
|
-
}
|
|
101379
|
-
function createNodeTrustStore(configDir) {
|
|
101380
|
-
return new TrustStore({ fs: nodeTrustFs(), path: join4(configDir, "trust.json"), hash: sha256 });
|
|
101381
|
-
}
|
|
101382
|
-
async function importGateStatus(deps) {
|
|
101383
|
-
const lines2 = [];
|
|
101384
|
-
const configPath = join4(deps.configDir, "imports.json");
|
|
101385
|
-
try {
|
|
101386
|
-
const loaded = parseImportsConfig(await readOrNull(configPath), configPath);
|
|
101387
|
-
if (loaded.error) lines2.push(`imports: ${loaded.error} (every category off)`);
|
|
101388
|
-
else {
|
|
101389
|
-
const on = IMPORT_CATEGORIES.filter((c) => loaded.config.categories[c]);
|
|
101390
|
-
lines2.push(on.length ? `imports: on for ${on.join(", ")}` : "imports: every category off (default)");
|
|
101391
|
-
}
|
|
101392
|
-
} catch (err) {
|
|
101393
|
-
lines2.push(`imports: not read (${err instanceof Error ? err.message : String(err)}); every category off`);
|
|
101394
|
-
}
|
|
101395
|
-
try {
|
|
101396
|
-
const files = await readRepoSources(deps.cwd);
|
|
101397
|
-
const found = Object.keys(files);
|
|
101398
|
-
if (found.length === 0) return lines2;
|
|
101399
|
-
const status = await createNodeTrustStore(deps.configDir).repoStatus(deps.cwd, files);
|
|
101400
|
-
const names = found.join(", ");
|
|
101401
|
-
if (status === "trusted" || status === "session") lines2.push(`repo sources: ${names} (trusted${status === "session" ? " for this session" : ""})`);
|
|
101402
|
-
else if (status === "never") lines2.push(`repo sources: ${names} NOT loaded (marked never trust)`);
|
|
101403
|
-
else lines2.push(`repo sources: ${names} NOT loaded (untrusted${deps.interactive ? "" : ", non-interactive run"})`);
|
|
101404
|
-
} catch (err) {
|
|
101405
|
-
lines2.push(`repo sources: NOT loaded (trust check failed: ${err instanceof Error ? err.message : String(err)})`);
|
|
101949
|
+
// src/skills-command.ts
|
|
101950
|
+
async function runSkillsCommand(args, deps) {
|
|
101951
|
+
const [sub, name, file] = args.trim().split(/\s+/);
|
|
101952
|
+
if (!deps.source) return `Skills are not available.
|
|
101953
|
+
${deps.statusLines.map((l) => ` ${l}`).join("\n")}`;
|
|
101954
|
+
const all = await deps.source.list();
|
|
101955
|
+
if (!sub) {
|
|
101956
|
+
if (all.length === 0) return "No imported skills.";
|
|
101957
|
+
return `Imported skills:
|
|
101958
|
+
${all.map((s) => ` ${s.name} (${s.scope}${s.scripts ? ", ships scripts: not runnable until permitted" : ""}) ${s.description}`).join("\n")}
|
|
101959
|
+
|
|
101960
|
+
Skills are loaded on demand with the skill tool; bodies are not in the prompt. /skills show <name> [file], /skills delete <name>.`;
|
|
101961
|
+
}
|
|
101962
|
+
if (sub === "show") {
|
|
101963
|
+
if (!name) return "Usage: /skills show <name> [file]";
|
|
101964
|
+
const text = file ? await deps.source.file(name, file) : await deps.source.body(name);
|
|
101965
|
+
return text ?? `No ${file ? `file "${file}" in skill` : "skill named"} "${name}".`;
|
|
101966
|
+
}
|
|
101967
|
+
if (sub === "delete") {
|
|
101968
|
+
if (!name) return "Usage: /skills delete <name>";
|
|
101969
|
+
const skill = all.find((s) => s.name === name);
|
|
101970
|
+
if (!skill) return `No skill named "${name}".`;
|
|
101971
|
+
if (skill.scope === "repo") return `"${name}" is a repo skill (${".claude/skills"}); delete it from the repo, not the profile.`;
|
|
101972
|
+
if (!deps.client) return "Not signed in: cannot delete from the profile.";
|
|
101973
|
+
const assets = ((await deps.client.index())?.assets ?? []).filter(
|
|
101974
|
+
(a) => a.kind === "skill" && (a.name === name || a.name.startsWith(`${name}.`) && a.description.startsWith(SIBLING_PREFIX))
|
|
101975
|
+
);
|
|
101976
|
+
for (const a of assets) await deps.client.delete("skill", a.name);
|
|
101977
|
+
return `Deleted skill "${name}" and ${assets.length - 1} file(s) from the profile. Re-running import-claude would import it again.`;
|
|
101406
101978
|
}
|
|
101407
|
-
return
|
|
101979
|
+
return "Usage: /skills | /skills show <name> [file] | /skills delete <name>";
|
|
101408
101980
|
}
|
|
101409
|
-
var
|
|
101410
|
-
|
|
101411
|
-
"src/import-gate.ts"() {
|
|
101981
|
+
var init_skills_command = __esm({
|
|
101982
|
+
"src/skills-command.ts"() {
|
|
101412
101983
|
"use strict";
|
|
101413
101984
|
init_src();
|
|
101414
|
-
REPO_SOURCE_FILES = [".mcp.json", ".claude/settings.json", ".claude/settings.local.json", "CLAUDE.local.md"];
|
|
101415
|
-
sha256 = (text) => createHash("sha256").update(text).digest("hex");
|
|
101416
101985
|
}
|
|
101417
101986
|
});
|
|
101418
101987
|
|
|
101419
101988
|
// src/permission-rules-loader.ts
|
|
101420
101989
|
import { readFile as readFile5 } from "node:fs/promises";
|
|
101421
|
-
import { join as
|
|
101990
|
+
import { join as join6 } from "node:path";
|
|
101422
101991
|
async function readOrNull2(path12) {
|
|
101423
101992
|
try {
|
|
101424
101993
|
return await readFile5(path12, "utf8");
|
|
@@ -101429,7 +101998,7 @@ async function readOrNull2(path12) {
|
|
|
101429
101998
|
}
|
|
101430
101999
|
async function loadPermissionRules(deps) {
|
|
101431
102000
|
const none = (line) => ({ rules: void 0, defaultMode: void 0, statusLines: [line] });
|
|
101432
|
-
const configPath =
|
|
102001
|
+
const configPath = join6(deps.configDir, "imports.json");
|
|
101433
102002
|
try {
|
|
101434
102003
|
const loaded = parseImportsConfig(await readOrNull2(configPath), configPath);
|
|
101435
102004
|
if (loaded.error) return none("permission rules: off (imports.json unreadable)");
|
|
@@ -101438,13 +102007,13 @@ async function loadPermissionRules(deps) {
|
|
|
101438
102007
|
return none(`permission rules: off (imports.json not read: ${err instanceof Error ? err.message : String(err)})`);
|
|
101439
102008
|
}
|
|
101440
102009
|
const lines2 = [];
|
|
101441
|
-
const files = [{ scope: "user", path:
|
|
102010
|
+
const files = [{ scope: "user", path: join6(deps.homeDir, ".claude", "settings.json") }];
|
|
101442
102011
|
try {
|
|
101443
102012
|
const sources2 = await readRepoSources(deps.cwd);
|
|
101444
102013
|
if (sources2[".claude/settings.json"] !== void 0 || sources2[".claude/settings.local.json"] !== void 0) {
|
|
101445
102014
|
const status = await createNodeTrustStore(deps.configDir).repoStatus(deps.cwd, sources2);
|
|
101446
102015
|
if (status === "trusted" || status === "session") {
|
|
101447
|
-
files.push({ scope: "repo", path:
|
|
102016
|
+
files.push({ scope: "repo", path: join6(deps.cwd, ".claude", "settings.json") }, { scope: "local", path: join6(deps.cwd, ".claude", "settings.local.json") });
|
|
101448
102017
|
} else lines2.push(`permission rules: repo settings NOT loaded (${status === "never" ? "marked never trust" : "untrusted repo"})`);
|
|
101449
102018
|
}
|
|
101450
102019
|
} catch (err) {
|
|
@@ -101486,7 +102055,7 @@ var init_permission_rules_loader = __esm({
|
|
|
101486
102055
|
});
|
|
101487
102056
|
|
|
101488
102057
|
// src/command-loader.ts
|
|
101489
|
-
import { join as
|
|
102058
|
+
import { join as join7 } from "node:path";
|
|
101490
102059
|
async function userCommands(deps, lines2) {
|
|
101491
102060
|
try {
|
|
101492
102061
|
const apiKey = await resolveApiKey({ env: deps.env, readFile: deps.readFile, homedir: deps.homedir });
|
|
@@ -101541,7 +102110,7 @@ async function repoCommands(deps, lines2) {
|
|
|
101541
102110
|
}
|
|
101542
102111
|
}
|
|
101543
102112
|
async function loadCommands(deps) {
|
|
101544
|
-
const configPath =
|
|
102113
|
+
const configPath = join7(deps.configDir, "imports.json");
|
|
101545
102114
|
try {
|
|
101546
102115
|
const loaded = parseImportsConfig(await deps.readFile(configPath).catch((err) => {
|
|
101547
102116
|
if (err.code === "ENOENT") return null;
|
|
@@ -101576,7 +102145,7 @@ var init_command_loader = __esm({
|
|
|
101576
102145
|
// src/useReplController.ts
|
|
101577
102146
|
import { readFile as readFile6, writeFile as writeFile4, mkdir as mkdir4 } from "node:fs/promises";
|
|
101578
102147
|
import { homedir as homedir2 } from "node:os";
|
|
101579
|
-
import { join as
|
|
102148
|
+
import { join as join8 } from "node:path";
|
|
101580
102149
|
import { execFile as execFile3 } from "node:child_process";
|
|
101581
102150
|
function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mentionsOpt, sessions, resumeLatest: resumeLatest2, onViewTurns, onViewResume, isEscOwned }) {
|
|
101582
102151
|
const { exit } = use_app_default();
|
|
@@ -101623,7 +102192,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
|
|
|
101623
102192
|
(0, import_react30.useEffect)(() => {
|
|
101624
102193
|
let live = true;
|
|
101625
102194
|
loadCommands({
|
|
101626
|
-
configDir: process.env.SPACECODE_CONFIG_DIR?.trim() ||
|
|
102195
|
+
configDir: process.env.SPACECODE_CONFIG_DIR?.trim() || join8(homedir2(), ".spacecode"),
|
|
101627
102196
|
cwd: process.cwd(),
|
|
101628
102197
|
env: process.env,
|
|
101629
102198
|
readFile: (p) => readFile6(p, "utf-8"),
|
|
@@ -101954,6 +102523,13 @@ ${commandsLoaded.statusLines.map((l) => ` ${l}`).join("\n")}`.trim() : `Importe
|
|
|
101954
102523
|
${list2.map((c) => ` /${c.name} (${c.scope})${c.description ? ` ${c.description}` : ""}`).join("\n")}`);
|
|
101955
102524
|
return;
|
|
101956
102525
|
}
|
|
102526
|
+
case "skills": {
|
|
102527
|
+
const state = profileRef.current;
|
|
102528
|
+
const apiKey2 = await resolveApiKey({ env: process.env, readFile: (p) => readFile6(p, "utf-8"), homedir: homedir2 }).catch(() => null);
|
|
102529
|
+
const client = apiKey2 ? createProfileClient({ baseUrl: backendUrl(process.env), apiKey: apiKey2, fetch }) : null;
|
|
102530
|
+
notice(await runSkillsCommand(slash.args, { source: state?.skills ?? null, client, statusLines: state?.statusLines.filter((l) => l.startsWith("skills")) ?? ["skills: not loaded yet (loads on your first message)"] }));
|
|
102531
|
+
return;
|
|
102532
|
+
}
|
|
101957
102533
|
case "tools": {
|
|
101958
102534
|
const tools = toolsetRef.current?.tools ?? createLocalToolset({ cwd: process.cwd(), permissions, onTodos: setTodos }).tools;
|
|
101959
102535
|
notice(`Tools:
|
|
@@ -102016,7 +102592,7 @@ ${tools.map((t) => ` ${t.name}`).join("\n")}`);
|
|
|
102016
102592
|
cwd: process.cwd(),
|
|
102017
102593
|
gitRoot: gitTopLevel,
|
|
102018
102594
|
importGate: async () => {
|
|
102019
|
-
const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() ||
|
|
102595
|
+
const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() || join8(homedir2(), ".spacecode");
|
|
102020
102596
|
const gate = await importGateStatus({ configDir, cwd: process.cwd(), interactive: Boolean(process.stdin.isTTY) });
|
|
102021
102597
|
const loaded = await loadPermissionRules({ configDir, homeDir: homedir2(), cwd: process.cwd() });
|
|
102022
102598
|
rulesRef.current = loaded.rules;
|
|
@@ -102149,6 +102725,8 @@ var init_useReplController = __esm({
|
|
|
102149
102725
|
init_node_mentions();
|
|
102150
102726
|
init_api_key();
|
|
102151
102727
|
init_profile_runtime();
|
|
102728
|
+
init_skills_command();
|
|
102729
|
+
init_src();
|
|
102152
102730
|
init_import_gate();
|
|
102153
102731
|
init_permission_rules_loader();
|
|
102154
102732
|
init_command_loader();
|
|
@@ -102772,6 +103350,40 @@ var rawCount = process.env.LAUNCH_COUNT ? parseInt(process.env.LAUNCH_COUNT, 10)
|
|
|
102772
103350
|
var launchCount = rawCount !== void 0 && Number.isNaN(rawCount) ? void 0 : rawCount;
|
|
102773
103351
|
var yolo = process.argv.includes("--yolo");
|
|
102774
103352
|
var resumeLatest = process.argv.includes("--continue");
|
|
103353
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
103354
|
+
try {
|
|
103355
|
+
const { readRepoSources: readRepoSources2, createNodeTrustStore: createNodeTrustStore2 } = await Promise.resolve().then(() => (init_import_gate(), import_gate_exports));
|
|
103356
|
+
const { promptRepoTrust: promptRepoTrust2 } = await Promise.resolve().then(() => (init_repo_trust_prompt(), repo_trust_prompt_exports));
|
|
103357
|
+
const files = await readRepoSources2(process.cwd());
|
|
103358
|
+
if (Object.keys(files).length > 0) {
|
|
103359
|
+
const store = createNodeTrustStore2(process.env.SPACECODE_CONFIG_DIR?.trim() || path11.join(os5.homedir(), ".spacecode"));
|
|
103360
|
+
if (await store.repoStatus(process.cwd(), files) === "untrusted") {
|
|
103361
|
+
const { createInterface } = await import("node:readline/promises");
|
|
103362
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
103363
|
+
try {
|
|
103364
|
+
const decision = await promptRepoTrust2(process.cwd(), files, {
|
|
103365
|
+
write: (t) => {
|
|
103366
|
+
process.stdout.write(t);
|
|
103367
|
+
},
|
|
103368
|
+
ask: async (q2) => {
|
|
103369
|
+
try {
|
|
103370
|
+
return await rl.question(q2);
|
|
103371
|
+
} catch {
|
|
103372
|
+
return null;
|
|
103373
|
+
}
|
|
103374
|
+
}
|
|
103375
|
+
});
|
|
103376
|
+
if (decision !== "skip") await store.setRepo(process.cwd(), files, decision);
|
|
103377
|
+
} finally {
|
|
103378
|
+
rl.close();
|
|
103379
|
+
}
|
|
103380
|
+
}
|
|
103381
|
+
}
|
|
103382
|
+
} catch (err) {
|
|
103383
|
+
process.stderr.write(`Repo trust check failed (${err instanceof Error ? err.message : String(err)}); repo sources stay untrusted.
|
|
103384
|
+
`);
|
|
103385
|
+
}
|
|
103386
|
+
}
|
|
102775
103387
|
var restoreCiVariables = hideCiVariables(process.env, Boolean(process.stdout.isTTY));
|
|
102776
103388
|
var render2;
|
|
102777
103389
|
var App3;
|