spacecode 2.5.4 → 2.5.5
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 +609 -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,37 @@ 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
|
+
|
|
5853
6106
|
// ../agent/src/index.ts
|
|
5854
6107
|
var init_src = __esm({
|
|
5855
6108
|
"../agent/src/index.ts"() {
|
|
@@ -5894,6 +6147,10 @@ var init_src = __esm({
|
|
|
5894
6147
|
init_command_file();
|
|
5895
6148
|
init_command_registry();
|
|
5896
6149
|
init_claude_sources();
|
|
6150
|
+
init_skill_file();
|
|
6151
|
+
init_skill_prompt();
|
|
6152
|
+
init_skill_tool();
|
|
6153
|
+
init_skill_source();
|
|
5897
6154
|
}
|
|
5898
6155
|
});
|
|
5899
6156
|
|
|
@@ -5934,14 +6191,225 @@ var init_api_key = __esm({
|
|
|
5934
6191
|
}
|
|
5935
6192
|
});
|
|
5936
6193
|
|
|
6194
|
+
// src/import-gate.ts
|
|
6195
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
6196
|
+
import { chmod, mkdir, readFile, readdir, realpath, rename, writeFile } from "node:fs/promises";
|
|
6197
|
+
import { dirname as dirname3, join as join2 } from "node:path";
|
|
6198
|
+
async function readOrNull(path12) {
|
|
6199
|
+
try {
|
|
6200
|
+
return await readFile(path12, "utf8");
|
|
6201
|
+
} catch (err) {
|
|
6202
|
+
if (err.code === "ENOENT") return null;
|
|
6203
|
+
throw err;
|
|
6204
|
+
}
|
|
6205
|
+
}
|
|
6206
|
+
async function writeTextAtomic(path12, text) {
|
|
6207
|
+
await mkdir(dirname3(path12), { recursive: true });
|
|
6208
|
+
const tmp = `${path12}.${process.pid}.${randomUUID()}.tmp`;
|
|
6209
|
+
await writeFile(tmp, text, { mode: 384 });
|
|
6210
|
+
await chmod(tmp, 384);
|
|
6211
|
+
await rename(tmp, path12);
|
|
6212
|
+
}
|
|
6213
|
+
async function readRepoSources(cwd2) {
|
|
6214
|
+
const files = {};
|
|
6215
|
+
for (const name of REPO_SOURCE_FILES) {
|
|
6216
|
+
const text = await readOrNull(join2(cwd2, name));
|
|
6217
|
+
if (text !== null) files[name] = text;
|
|
6218
|
+
}
|
|
6219
|
+
let entries = [];
|
|
6220
|
+
try {
|
|
6221
|
+
entries = await readdir(join2(cwd2, ".claude", "commands"));
|
|
6222
|
+
} catch (err) {
|
|
6223
|
+
if (err.code !== "ENOENT" && err.code !== "ENOTDIR") throw err;
|
|
6224
|
+
}
|
|
6225
|
+
for (const e of entries.filter((x2) => x2.endsWith(".md")).sort()) {
|
|
6226
|
+
const text = await readOrNull(join2(cwd2, ".claude", "commands", e));
|
|
6227
|
+
if (text !== null) files[`.claude/commands/${e}`] = text;
|
|
6228
|
+
}
|
|
6229
|
+
await readRepoSkillFiles(cwd2, files);
|
|
6230
|
+
return files;
|
|
6231
|
+
}
|
|
6232
|
+
async function readRepoSkillFiles(cwd2, files) {
|
|
6233
|
+
const root = join2(cwd2, ".claude", "skills");
|
|
6234
|
+
let count = 0;
|
|
6235
|
+
const walk = async (dir, rel) => {
|
|
6236
|
+
let entries;
|
|
6237
|
+
try {
|
|
6238
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
6239
|
+
} catch (err) {
|
|
6240
|
+
const code = err.code;
|
|
6241
|
+
if (code === "ENOENT" || code === "ENOTDIR") return;
|
|
6242
|
+
throw err;
|
|
6243
|
+
}
|
|
6244
|
+
for (const e of entries.sort((a, b2) => a.name < b2.name ? -1 : 1)) {
|
|
6245
|
+
if (e.name.startsWith(".") || e.isSymbolicLink()) continue;
|
|
6246
|
+
const key = `${rel}/${e.name}`;
|
|
6247
|
+
if (e.isDirectory()) await walk(join2(dir, e.name), key);
|
|
6248
|
+
else if (e.isFile() && count < MAX_REPO_SKILL_FILES) {
|
|
6249
|
+
const text = await readOrNull(join2(dir, e.name));
|
|
6250
|
+
if (text !== null && !text.includes("\0") && Buffer.byteLength(text, "utf8") <= MAX_REPO_SKILL_BYTES) {
|
|
6251
|
+
files[key] = text;
|
|
6252
|
+
count++;
|
|
6253
|
+
}
|
|
6254
|
+
}
|
|
6255
|
+
}
|
|
6256
|
+
};
|
|
6257
|
+
await walk(root, ".claude/skills");
|
|
6258
|
+
}
|
|
6259
|
+
function nodeTrustFs() {
|
|
6260
|
+
return { readText: readOrNull, writeTextAtomic, realPath: (p) => realpath(p) };
|
|
6261
|
+
}
|
|
6262
|
+
function createNodeTrustStore(configDir) {
|
|
6263
|
+
return new TrustStore({ fs: nodeTrustFs(), path: join2(configDir, "trust.json"), hash: sha256 });
|
|
6264
|
+
}
|
|
6265
|
+
async function importGateStatus(deps) {
|
|
6266
|
+
const lines2 = [];
|
|
6267
|
+
const configPath = join2(deps.configDir, "imports.json");
|
|
6268
|
+
try {
|
|
6269
|
+
const loaded = parseImportsConfig(await readOrNull(configPath), configPath);
|
|
6270
|
+
if (loaded.error) lines2.push(`imports: ${loaded.error} (every category off)`);
|
|
6271
|
+
else {
|
|
6272
|
+
const on = IMPORT_CATEGORIES.filter((c) => loaded.config.categories[c]);
|
|
6273
|
+
lines2.push(on.length ? `imports: on for ${on.join(", ")}` : "imports: every category off (default)");
|
|
6274
|
+
}
|
|
6275
|
+
} catch (err) {
|
|
6276
|
+
lines2.push(`imports: not read (${err instanceof Error ? err.message : String(err)}); every category off`);
|
|
6277
|
+
}
|
|
6278
|
+
try {
|
|
6279
|
+
const files = await readRepoSources(deps.cwd);
|
|
6280
|
+
const found = Object.keys(files);
|
|
6281
|
+
if (found.length === 0) return lines2;
|
|
6282
|
+
const status = await createNodeTrustStore(deps.configDir).repoStatus(deps.cwd, files);
|
|
6283
|
+
const names = found.join(", ");
|
|
6284
|
+
if (status === "trusted" || status === "session") lines2.push(`repo sources: ${names} (trusted${status === "session" ? " for this session" : ""})`);
|
|
6285
|
+
else if (status === "never") lines2.push(`repo sources: ${names} NOT loaded (marked never trust)`);
|
|
6286
|
+
else lines2.push(`repo sources: ${names} NOT loaded (untrusted${deps.interactive ? "" : ", non-interactive run"})`);
|
|
6287
|
+
} catch (err) {
|
|
6288
|
+
lines2.push(`repo sources: NOT loaded (trust check failed: ${err instanceof Error ? err.message : String(err)})`);
|
|
6289
|
+
}
|
|
6290
|
+
return lines2;
|
|
6291
|
+
}
|
|
6292
|
+
var REPO_SOURCE_FILES, MAX_REPO_SKILL_FILES, MAX_REPO_SKILL_BYTES, sha256;
|
|
6293
|
+
var init_import_gate = __esm({
|
|
6294
|
+
"src/import-gate.ts"() {
|
|
6295
|
+
"use strict";
|
|
6296
|
+
init_src();
|
|
6297
|
+
REPO_SOURCE_FILES = [".mcp.json", ".claude/settings.json", ".claude/settings.local.json", "CLAUDE.local.md"];
|
|
6298
|
+
MAX_REPO_SKILL_FILES = 200;
|
|
6299
|
+
MAX_REPO_SKILL_BYTES = 256 * 1024;
|
|
6300
|
+
sha256 = (text) => createHash("sha256").update(text).digest("hex");
|
|
6301
|
+
}
|
|
6302
|
+
});
|
|
6303
|
+
|
|
6304
|
+
// src/skills-loader.ts
|
|
6305
|
+
import { join as join3 } from "node:path";
|
|
6306
|
+
function repoSkillSource(sources, parse, problems) {
|
|
6307
|
+
const dirs = /* @__PURE__ */ new Map();
|
|
6308
|
+
for (const key of Object.keys(sources).filter((k2) => k2.startsWith(REPO_PREFIX) && k2.endsWith("/SKILL.md") && k2.split("/").length === 4)) {
|
|
6309
|
+
const parsed = parse(sources[key]);
|
|
6310
|
+
if (!parsed.ok) problems.push(`${key}: malformed skill (${parsed.error})`);
|
|
6311
|
+
else if (dirs.has(parsed.name)) problems.push(`${key}: duplicate skill name "${parsed.name}"`);
|
|
6312
|
+
else dirs.set(parsed.name, { dir: key.split("/")[2], description: parsed.description });
|
|
6313
|
+
}
|
|
6314
|
+
const prefix = (name) => `${REPO_PREFIX}${dirs.get(name)?.dir}/`;
|
|
6315
|
+
return {
|
|
6316
|
+
async list() {
|
|
6317
|
+
return [...dirs].map(([name, d]) => ({
|
|
6318
|
+
name,
|
|
6319
|
+
description: d.description,
|
|
6320
|
+
scope: "repo",
|
|
6321
|
+
scripts: Object.keys(sources).some((k2) => k2.startsWith(prefix(name)) && SCRIPT_RE2.test(k2.slice(prefix(name).length)))
|
|
6322
|
+
}));
|
|
6323
|
+
},
|
|
6324
|
+
async body(name) {
|
|
6325
|
+
return dirs.has(name) ? sources[`${prefix(name)}SKILL.md`] ?? null : null;
|
|
6326
|
+
},
|
|
6327
|
+
async file(name, rel) {
|
|
6328
|
+
const safe = safeSkillPath(rel);
|
|
6329
|
+
return dirs.has(name) && safe ? sources[`${prefix(name)}${safe}`] ?? null : null;
|
|
6330
|
+
}
|
|
6331
|
+
};
|
|
6332
|
+
}
|
|
6333
|
+
function mergeSkillSources(repo, user) {
|
|
6334
|
+
return {
|
|
6335
|
+
async list() {
|
|
6336
|
+
const r = await repo?.list() ?? [];
|
|
6337
|
+
const names = new Set(r.map((s) => s.name));
|
|
6338
|
+
return [...r, ...(await user?.list() ?? []).filter((s) => !names.has(s.name))];
|
|
6339
|
+
},
|
|
6340
|
+
async body(name) {
|
|
6341
|
+
return await repo?.body(name) ?? null ?? await user?.body(name) ?? null;
|
|
6342
|
+
},
|
|
6343
|
+
async file(name, rel) {
|
|
6344
|
+
if (await repo?.body(name) != null) return repo.file(name, rel);
|
|
6345
|
+
return await user?.file(name, rel) ?? null;
|
|
6346
|
+
}
|
|
6347
|
+
};
|
|
6348
|
+
}
|
|
6349
|
+
async function loadSkills(deps) {
|
|
6350
|
+
const configPath = join3(deps.configDir, "imports.json");
|
|
6351
|
+
try {
|
|
6352
|
+
const loaded = parseImportsConfig(await deps.readFile(configPath).catch((err) => {
|
|
6353
|
+
if (err.code === "ENOENT") return null;
|
|
6354
|
+
throw err;
|
|
6355
|
+
}), configPath);
|
|
6356
|
+
if (loaded.error) return NONE("skills: off (imports.json unreadable)");
|
|
6357
|
+
if (!loaded.config.categories.skills) return NONE("skills: off (imports.json has skills off)");
|
|
6358
|
+
} catch (err) {
|
|
6359
|
+
return NONE(`skills: off (imports.json not read: ${err instanceof Error ? err.message : String(err)})`);
|
|
6360
|
+
}
|
|
6361
|
+
const lines2 = [];
|
|
6362
|
+
const problems = [];
|
|
6363
|
+
let repo = null;
|
|
6364
|
+
try {
|
|
6365
|
+
const sources = await readRepoSources(deps.cwd);
|
|
6366
|
+
if (Object.keys(sources).some((k2) => k2.startsWith(REPO_PREFIX))) {
|
|
6367
|
+
const status = await createNodeTrustStore(deps.configDir).repoStatus(deps.cwd, sources);
|
|
6368
|
+
if (status === "trusted" || status === "session") repo = repoSkillSource(sources, deps.parse, problems);
|
|
6369
|
+
else lines2.push(`skills: repo skills NOT loaded (${status === "never" ? "marked never trust" : "untrusted repo"})`);
|
|
6370
|
+
}
|
|
6371
|
+
} catch (err) {
|
|
6372
|
+
lines2.push(`skills: repo skills NOT loaded (${err instanceof Error ? err.message : String(err)})`);
|
|
6373
|
+
}
|
|
6374
|
+
let user = null;
|
|
6375
|
+
if (deps.client) {
|
|
6376
|
+
const source2 = createProfileSkillSource(deps.client);
|
|
6377
|
+
try {
|
|
6378
|
+
await source2.list();
|
|
6379
|
+
user = source2;
|
|
6380
|
+
} catch (err) {
|
|
6381
|
+
lines2.push(`skills: user skills NOT loaded (${err instanceof Error ? err.message : String(err)})`);
|
|
6382
|
+
}
|
|
6383
|
+
} else lines2.push("skills: user skills NOT loaded (not signed in)");
|
|
6384
|
+
const source = mergeSkillSources(repo, user);
|
|
6385
|
+
const all = await source.list();
|
|
6386
|
+
const shadowed = repo && user ? (await user.list()).filter((u3) => all.some((a) => a.name === u3.name && a.scope === "repo")).map((u3) => u3.name) : [];
|
|
6387
|
+
for (const p of problems) lines2.push(`skills: ${p}`);
|
|
6388
|
+
const section2 = buildSkillsSection(all);
|
|
6389
|
+
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(", ")}` : ""}`);
|
|
6390
|
+
if (all.length === 0) return { ...NONE(lines2[0]), statusLines: lines2, source };
|
|
6391
|
+
return { promptSection: section2.text, tools: [SKILL_TOOL], handlers: { skill: createSkillTool(source) }, statusLines: lines2, source };
|
|
6392
|
+
}
|
|
6393
|
+
var NONE, REPO_PREFIX, SCRIPT_RE2;
|
|
6394
|
+
var init_skills_loader = __esm({
|
|
6395
|
+
"src/skills-loader.ts"() {
|
|
6396
|
+
"use strict";
|
|
6397
|
+
init_src();
|
|
6398
|
+
init_import_gate();
|
|
6399
|
+
NONE = (line) => ({ promptSection: "", tools: [], handlers: {}, statusLines: [line], source: null });
|
|
6400
|
+
REPO_PREFIX = ".claude/skills/";
|
|
6401
|
+
SCRIPT_RE2 = /^(scripts\/|.*\.(sh|py|js|mjs|ts|rb|pl)$)/;
|
|
6402
|
+
}
|
|
6403
|
+
});
|
|
6404
|
+
|
|
5937
6405
|
// src/profile-runtime.ts
|
|
5938
|
-
import { join as
|
|
6406
|
+
import { join as join4 } from "node:path";
|
|
5939
6407
|
function backendUrl(env3) {
|
|
5940
6408
|
return env3.SPACECODE_BACKEND_URL?.trim() || env3.SPACECODE_API_URL?.trim() || DEFAULT_BACKEND_URL;
|
|
5941
6409
|
}
|
|
5942
6410
|
function fileCache(deps) {
|
|
5943
|
-
const dir =
|
|
5944
|
-
const file =
|
|
6411
|
+
const dir = join4(deps.env.SPACECODE_CONFIG_DIR?.trim() || join4(deps.homedir(), ".spacecode"));
|
|
6412
|
+
const file = join4(dir, "profile-cache.json");
|
|
5945
6413
|
return {
|
|
5946
6414
|
async read() {
|
|
5947
6415
|
let raw;
|
|
@@ -5987,8 +6455,15 @@ async function prepareProfileUnsafe(deps) {
|
|
|
5987
6455
|
const root = await deps.gitRoot?.() ?? null;
|
|
5988
6456
|
const keys = [.../* @__PURE__ */ new Set([projectKey(deps.cwd), ...root ? [projectKey(root)] : []])];
|
|
5989
6457
|
const client = createProfileClient({ baseUrl: backendUrl(deps.env), apiKey, fetch: deps.fetch, timeoutMs: 3e3 });
|
|
6458
|
+
const skills = await loadSkills({
|
|
6459
|
+
configDir: deps.env.SPACECODE_CONFIG_DIR?.trim() || join4(deps.homedir(), ".spacecode"),
|
|
6460
|
+
cwd: deps.cwd,
|
|
6461
|
+
readFile: deps.readFile,
|
|
6462
|
+
client,
|
|
6463
|
+
parse: parseSkillFile
|
|
6464
|
+
});
|
|
5990
6465
|
const result = await loadProfile({ client, cache: fileCache(deps), projectKeys: keys });
|
|
5991
|
-
if (!result.profile) return EMPTY([`profile: not loaded (${result.error ?? "unknown error"})
|
|
6466
|
+
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
6467
|
const key = result.profile.projectKey;
|
|
5993
6468
|
const hasMemory = result.profile.memoryIndex !== null && key !== null;
|
|
5994
6469
|
const hasSections = result.profile.instructions?.includes("## More instructions (on demand)") ?? false;
|
|
@@ -5997,10 +6472,13 @@ async function prepareProfileUnsafe(deps) {
|
|
|
5997
6472
|
`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
6473
|
];
|
|
5999
6474
|
if (result.error) lines2.push(`profile warning: ${result.error}`);
|
|
6475
|
+
lines2.push(...skills.statusLines);
|
|
6000
6476
|
return {
|
|
6001
|
-
promptSection: section2.text,
|
|
6002
|
-
|
|
6477
|
+
promptSection: section2.text + skills.promptSection,
|
|
6478
|
+
skills: skills.source,
|
|
6479
|
+
tools: [...hasMemory ? [MEMORY_READ_TOOL] : [], ...hasSections ? [INSTRUCTIONS_READ_TOOL] : [], ...skills.tools],
|
|
6003
6480
|
handlers: {
|
|
6481
|
+
...skills.handlers,
|
|
6004
6482
|
...hasMemory ? { memory_read: createMemoryReadTool(client, key) } : {},
|
|
6005
6483
|
...hasSections ? { instructions_read: createInstructionsReadTool(client) } : {}
|
|
6006
6484
|
},
|
|
@@ -6013,6 +6491,8 @@ var init_profile_runtime = __esm({
|
|
|
6013
6491
|
"use strict";
|
|
6014
6492
|
init_src();
|
|
6015
6493
|
init_api_key();
|
|
6494
|
+
init_skills_loader();
|
|
6495
|
+
init_src();
|
|
6016
6496
|
DEFAULT_BACKEND_URL = "https://backend.spacecode.app";
|
|
6017
6497
|
EMPTY = (statusLines) => ({ promptSection: "", tools: [], handlers: {}, statusLines });
|
|
6018
6498
|
}
|
|
@@ -41385,7 +41865,7 @@ var require_core = __commonJS({
|
|
|
41385
41865
|
return match && match.index === 0;
|
|
41386
41866
|
}
|
|
41387
41867
|
var BACKREF_RE = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;
|
|
41388
|
-
function
|
|
41868
|
+
function join9(regexps, separator = "|") {
|
|
41389
41869
|
let numCaptures = 0;
|
|
41390
41870
|
return regexps.map((regex2) => {
|
|
41391
41871
|
numCaptures += 1;
|
|
@@ -41689,7 +42169,7 @@ var require_core = __commonJS({
|
|
|
41689
42169
|
this.exec = () => null;
|
|
41690
42170
|
}
|
|
41691
42171
|
const terminators = this.regexes.map((el) => el[1]);
|
|
41692
|
-
this.matcherRe = langRe(
|
|
42172
|
+
this.matcherRe = langRe(join9(terminators), true);
|
|
41693
42173
|
this.lastIndex = 0;
|
|
41694
42174
|
}
|
|
41695
42175
|
/** @param {string} s */
|
|
@@ -99195,7 +99675,7 @@ var init_credential_filter = __esm({
|
|
|
99195
99675
|
});
|
|
99196
99676
|
|
|
99197
99677
|
// src/session/session-store.ts
|
|
99198
|
-
import { chmod, mkdir, open, readdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
99678
|
+
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
99679
|
import { randomBytes } from "node:crypto";
|
|
99200
99680
|
import path3 from "node:path";
|
|
99201
99681
|
function redactCredentials(value) {
|
|
@@ -99284,11 +99764,11 @@ var init_session_store = __esm({
|
|
|
99284
99764
|
}
|
|
99285
99765
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
99286
99766
|
try {
|
|
99287
|
-
await
|
|
99288
|
-
await
|
|
99289
|
-
await
|
|
99290
|
-
await
|
|
99291
|
-
await
|
|
99767
|
+
await mkdir2(this.dir, { recursive: true, mode: 448 });
|
|
99768
|
+
await chmod2(this.dir, 448);
|
|
99769
|
+
await writeFile2(tmp, body, { mode: 384 });
|
|
99770
|
+
await chmod2(tmp, 384);
|
|
99771
|
+
await rename2(tmp, file);
|
|
99292
99772
|
} catch (err) {
|
|
99293
99773
|
throw new SessionError(file, `Could not save session: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
99294
99774
|
}
|
|
@@ -99301,7 +99781,7 @@ var init_session_store = __esm({
|
|
|
99301
99781
|
if (size > MAX_SESSION_BYTES) {
|
|
99302
99782
|
throw new SessionError(file, `Session is ${size} bytes, over the ${MAX_SESSION_BYTES}-byte cap; not loaded`);
|
|
99303
99783
|
}
|
|
99304
|
-
text = await
|
|
99784
|
+
text = await readFile2(file, "utf8");
|
|
99305
99785
|
} catch (err) {
|
|
99306
99786
|
if (err instanceof SessionError) throw err;
|
|
99307
99787
|
throw new SessionError(file, `Could not read session: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
@@ -99314,7 +99794,7 @@ var init_session_store = __esm({
|
|
|
99314
99794
|
async list() {
|
|
99315
99795
|
let names;
|
|
99316
99796
|
try {
|
|
99317
|
-
names = await
|
|
99797
|
+
names = await readdir2(this.dir);
|
|
99318
99798
|
} catch (err) {
|
|
99319
99799
|
if (err.code === "ENOENT") return { sessions: [], errors: [] };
|
|
99320
99800
|
throw new SessionError(this.dir, `Could not list sessions: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
@@ -100492,6 +100972,7 @@ var init_slash_commands = __esm({
|
|
|
100492
100972
|
{ name: "model", description: "List gateway models; /model <n|id> switches for this session" },
|
|
100493
100973
|
{ name: "turns", description: "Browse earlier turns and open their steps (shortcut: ctrl+o)" },
|
|
100494
100974
|
{ name: "commands", description: "List the slash commands imported from Claude Code" },
|
|
100975
|
+
{ name: "skills", description: "List imported skills; /skills show <name> [file], /skills delete <name>" },
|
|
100495
100976
|
{ name: "tools", description: "List the tools available to the agent" },
|
|
100496
100977
|
{ name: "exit", description: "Quit the REPL (alias: /quit)" },
|
|
100497
100978
|
{ name: "quit", description: "Quit the REPL" }
|
|
@@ -100508,7 +100989,7 @@ function validateSubmission(text, extra = []) {
|
|
|
100508
100989
|
function withCursor(row, col) {
|
|
100509
100990
|
return `${row.slice(0, col)}\x1B[7m${row[col] ?? " "}\x1B[27m${row.slice(col + 1)}`;
|
|
100510
100991
|
}
|
|
100511
|
-
function PromptEditor({ onSubmit, disabled, history, listFiles:
|
|
100992
|
+
function PromptEditor({ onSubmit, disabled, history, listFiles: listFiles3, onTab, locked = false, promptGlyph: promptGlyph2 = ">", onEmptyEnter, unicode = true, fullWidth = false, extraCommands = [] }) {
|
|
100512
100993
|
const { exit } = use_app_default();
|
|
100513
100994
|
const { columns } = useTerminalSize();
|
|
100514
100995
|
const { isRawModeSupported } = use_stdin_default();
|
|
@@ -100544,15 +101025,15 @@ function PromptEditor({ onSubmit, disabled, history, listFiles: listFiles2, onTa
|
|
|
100544
101025
|
const mention = shell ? null : activeMention(buf.text, buf.cursor);
|
|
100545
101026
|
const mentionOpen = mention !== null;
|
|
100546
101027
|
(0, import_react29.useEffect)(() => {
|
|
100547
|
-
if (!mentionOpen || !
|
|
101028
|
+
if (!mentionOpen || !listFiles3 || filesRef.current !== null || loadingFiles.current) return;
|
|
100548
101029
|
loadingFiles.current = true;
|
|
100549
|
-
|
|
101030
|
+
listFiles3().then((f) => {
|
|
100550
101031
|
filesRef.current = f;
|
|
100551
101032
|
setFiles(f);
|
|
100552
101033
|
}).catch((err) => fail("Could not list files", err)).finally(() => {
|
|
100553
101034
|
loadingFiles.current = false;
|
|
100554
101035
|
});
|
|
100555
|
-
}, [mentionOpen,
|
|
101036
|
+
}, [mentionOpen, listFiles3, fail]);
|
|
100556
101037
|
const chooseSel = (n) => {
|
|
100557
101038
|
selRef.current = n;
|
|
100558
101039
|
setSel(n);
|
|
@@ -100787,24 +101268,24 @@ var init_history_store = __esm({
|
|
|
100787
101268
|
});
|
|
100788
101269
|
|
|
100789
101270
|
// src/editor/history-file-io.ts
|
|
100790
|
-
import { chmod as
|
|
101271
|
+
import { chmod as chmod3, mkdir as mkdir3, readFile as readFile3, rename as rename3, writeFile as writeFile3 } from "node:fs/promises";
|
|
100791
101272
|
import path4 from "node:path";
|
|
100792
101273
|
function createFileHistoryIO(file) {
|
|
100793
101274
|
return {
|
|
100794
101275
|
async read() {
|
|
100795
101276
|
try {
|
|
100796
|
-
return await
|
|
101277
|
+
return await readFile3(file, "utf8");
|
|
100797
101278
|
} catch (err) {
|
|
100798
101279
|
if (err.code === "ENOENT") return null;
|
|
100799
101280
|
throw err;
|
|
100800
101281
|
}
|
|
100801
101282
|
},
|
|
100802
101283
|
async write(content) {
|
|
100803
|
-
await
|
|
101284
|
+
await mkdir3(path4.dirname(file), { recursive: true, mode: 448 });
|
|
100804
101285
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
100805
|
-
await
|
|
100806
|
-
await
|
|
100807
|
-
await
|
|
101286
|
+
await writeFile3(tmp, content, { mode: 384 });
|
|
101287
|
+
await chmod3(tmp, 384);
|
|
101288
|
+
await rename3(tmp, file);
|
|
100808
101289
|
}
|
|
100809
101290
|
};
|
|
100810
101291
|
}
|
|
@@ -101013,7 +101494,7 @@ var init_shell_turn = __esm({
|
|
|
101013
101494
|
});
|
|
101014
101495
|
|
|
101015
101496
|
// src/editor/file-list.ts
|
|
101016
|
-
async function
|
|
101497
|
+
async function listFiles2(readDir, opts = {}) {
|
|
101017
101498
|
const max = opts.maxFiles ?? DEFAULT_MAX_LISTED_FILES;
|
|
101018
101499
|
const files = [];
|
|
101019
101500
|
const queue = [""];
|
|
@@ -101240,18 +101721,18 @@ var init_mention_resolver = __esm({
|
|
|
101240
101721
|
});
|
|
101241
101722
|
|
|
101242
101723
|
// src/editor/node-mention-fs.ts
|
|
101243
|
-
import { open as open2, realpath, stat as stat2 } from "node:fs/promises";
|
|
101724
|
+
import { open as open2, realpath as realpath2, stat as stat2 } from "node:fs/promises";
|
|
101244
101725
|
import path6 from "node:path";
|
|
101245
101726
|
function isInside(root, target) {
|
|
101246
101727
|
return target === root || target.startsWith(root.endsWith(path6.sep) ? root : root + path6.sep);
|
|
101247
101728
|
}
|
|
101248
101729
|
function createNodeMentionFS(cwd2) {
|
|
101249
101730
|
let rootPromise;
|
|
101250
|
-
const root = () => rootPromise ??=
|
|
101731
|
+
const root = () => rootPromise ??= realpath2(cwd2);
|
|
101251
101732
|
async function confined(abs) {
|
|
101252
101733
|
let real;
|
|
101253
101734
|
try {
|
|
101254
|
-
real = await
|
|
101735
|
+
real = await realpath2(abs);
|
|
101255
101736
|
} catch (err) {
|
|
101256
101737
|
if (err.code === "ENOENT") return null;
|
|
101257
101738
|
throw err;
|
|
@@ -101287,11 +101768,11 @@ var init_node_mention_fs = __esm({
|
|
|
101287
101768
|
});
|
|
101288
101769
|
|
|
101289
101770
|
// src/editor/node-read-dir.ts
|
|
101290
|
-
import { readdir as
|
|
101771
|
+
import { readdir as readdir3 } from "node:fs/promises";
|
|
101291
101772
|
import path7 from "node:path";
|
|
101292
101773
|
function createNodeReadDir(cwd2) {
|
|
101293
101774
|
return async (rel) => {
|
|
101294
|
-
const entries = await
|
|
101775
|
+
const entries = await readdir3(path7.join(cwd2, rel), { withFileTypes: true });
|
|
101295
101776
|
return entries.map((e) => ({ name: e.name, isFile: e.isFile(), isDirectory: e.isDirectory() }));
|
|
101296
101777
|
};
|
|
101297
101778
|
}
|
|
@@ -101302,11 +101783,11 @@ var init_node_read_dir = __esm({
|
|
|
101302
101783
|
});
|
|
101303
101784
|
|
|
101304
101785
|
// src/editor/node-mentions.ts
|
|
101305
|
-
import { readFile as
|
|
101786
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
101306
101787
|
import path8 from "node:path";
|
|
101307
101788
|
async function readGitignore(cwd2) {
|
|
101308
101789
|
try {
|
|
101309
|
-
return createGitignoreMatcher(await
|
|
101790
|
+
return createGitignoreMatcher(await readFile4(path8.join(cwd2, ".gitignore"), "utf8"));
|
|
101310
101791
|
} catch (err) {
|
|
101311
101792
|
if (err.code === "ENOENT") return () => false;
|
|
101312
101793
|
throw err;
|
|
@@ -101320,7 +101801,7 @@ function createNodeMentions(cwd2) {
|
|
|
101320
101801
|
return formatMentionPrompt(text, result);
|
|
101321
101802
|
},
|
|
101322
101803
|
async list() {
|
|
101323
|
-
return
|
|
101804
|
+
return listFiles2(createNodeReadDir(cwd2), { ignored: await readGitignore(cwd2) });
|
|
101324
101805
|
}
|
|
101325
101806
|
};
|
|
101326
101807
|
}
|
|
@@ -101336,89 +101817,48 @@ var init_node_mentions = __esm({
|
|
|
101336
101817
|
}
|
|
101337
101818
|
});
|
|
101338
101819
|
|
|
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)})`);
|
|
101820
|
+
// src/skills-command.ts
|
|
101821
|
+
async function runSkillsCommand(args, deps) {
|
|
101822
|
+
const [sub, name, file] = args.trim().split(/\s+/);
|
|
101823
|
+
if (!deps.source) return `Skills are not available.
|
|
101824
|
+
${deps.statusLines.map((l) => ` ${l}`).join("\n")}`;
|
|
101825
|
+
const all = await deps.source.list();
|
|
101826
|
+
if (!sub) {
|
|
101827
|
+
if (all.length === 0) return "No imported skills.";
|
|
101828
|
+
return `Imported skills:
|
|
101829
|
+
${all.map((s) => ` ${s.name} (${s.scope}${s.scripts ? ", ships scripts: not runnable until permitted" : ""}) ${s.description}`).join("\n")}
|
|
101830
|
+
|
|
101831
|
+
Skills are loaded on demand with the skill tool; bodies are not in the prompt. /skills show <name> [file], /skills delete <name>.`;
|
|
101832
|
+
}
|
|
101833
|
+
if (sub === "show") {
|
|
101834
|
+
if (!name) return "Usage: /skills show <name> [file]";
|
|
101835
|
+
const text = file ? await deps.source.file(name, file) : await deps.source.body(name);
|
|
101836
|
+
return text ?? `No ${file ? `file "${file}" in skill` : "skill named"} "${name}".`;
|
|
101837
|
+
}
|
|
101838
|
+
if (sub === "delete") {
|
|
101839
|
+
if (!name) return "Usage: /skills delete <name>";
|
|
101840
|
+
const skill = all.find((s) => s.name === name);
|
|
101841
|
+
if (!skill) return `No skill named "${name}".`;
|
|
101842
|
+
if (skill.scope === "repo") return `"${name}" is a repo skill (${".claude/skills"}); delete it from the repo, not the profile.`;
|
|
101843
|
+
if (!deps.client) return "Not signed in: cannot delete from the profile.";
|
|
101844
|
+
const assets = ((await deps.client.index())?.assets ?? []).filter(
|
|
101845
|
+
(a) => a.kind === "skill" && (a.name === name || a.name.startsWith(`${name}.`) && a.description.startsWith(SIBLING_PREFIX))
|
|
101846
|
+
);
|
|
101847
|
+
for (const a of assets) await deps.client.delete("skill", a.name);
|
|
101848
|
+
return `Deleted skill "${name}" and ${assets.length - 1} file(s) from the profile. Re-running import-claude would import it again.`;
|
|
101406
101849
|
}
|
|
101407
|
-
return
|
|
101850
|
+
return "Usage: /skills | /skills show <name> [file] | /skills delete <name>";
|
|
101408
101851
|
}
|
|
101409
|
-
var
|
|
101410
|
-
|
|
101411
|
-
"src/import-gate.ts"() {
|
|
101852
|
+
var init_skills_command = __esm({
|
|
101853
|
+
"src/skills-command.ts"() {
|
|
101412
101854
|
"use strict";
|
|
101413
101855
|
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
101856
|
}
|
|
101417
101857
|
});
|
|
101418
101858
|
|
|
101419
101859
|
// src/permission-rules-loader.ts
|
|
101420
101860
|
import { readFile as readFile5 } from "node:fs/promises";
|
|
101421
|
-
import { join as
|
|
101861
|
+
import { join as join6 } from "node:path";
|
|
101422
101862
|
async function readOrNull2(path12) {
|
|
101423
101863
|
try {
|
|
101424
101864
|
return await readFile5(path12, "utf8");
|
|
@@ -101429,7 +101869,7 @@ async function readOrNull2(path12) {
|
|
|
101429
101869
|
}
|
|
101430
101870
|
async function loadPermissionRules(deps) {
|
|
101431
101871
|
const none = (line) => ({ rules: void 0, defaultMode: void 0, statusLines: [line] });
|
|
101432
|
-
const configPath =
|
|
101872
|
+
const configPath = join6(deps.configDir, "imports.json");
|
|
101433
101873
|
try {
|
|
101434
101874
|
const loaded = parseImportsConfig(await readOrNull2(configPath), configPath);
|
|
101435
101875
|
if (loaded.error) return none("permission rules: off (imports.json unreadable)");
|
|
@@ -101438,13 +101878,13 @@ async function loadPermissionRules(deps) {
|
|
|
101438
101878
|
return none(`permission rules: off (imports.json not read: ${err instanceof Error ? err.message : String(err)})`);
|
|
101439
101879
|
}
|
|
101440
101880
|
const lines2 = [];
|
|
101441
|
-
const files = [{ scope: "user", path:
|
|
101881
|
+
const files = [{ scope: "user", path: join6(deps.homeDir, ".claude", "settings.json") }];
|
|
101442
101882
|
try {
|
|
101443
101883
|
const sources2 = await readRepoSources(deps.cwd);
|
|
101444
101884
|
if (sources2[".claude/settings.json"] !== void 0 || sources2[".claude/settings.local.json"] !== void 0) {
|
|
101445
101885
|
const status = await createNodeTrustStore(deps.configDir).repoStatus(deps.cwd, sources2);
|
|
101446
101886
|
if (status === "trusted" || status === "session") {
|
|
101447
|
-
files.push({ scope: "repo", path:
|
|
101887
|
+
files.push({ scope: "repo", path: join6(deps.cwd, ".claude", "settings.json") }, { scope: "local", path: join6(deps.cwd, ".claude", "settings.local.json") });
|
|
101448
101888
|
} else lines2.push(`permission rules: repo settings NOT loaded (${status === "never" ? "marked never trust" : "untrusted repo"})`);
|
|
101449
101889
|
}
|
|
101450
101890
|
} catch (err) {
|
|
@@ -101486,7 +101926,7 @@ var init_permission_rules_loader = __esm({
|
|
|
101486
101926
|
});
|
|
101487
101927
|
|
|
101488
101928
|
// src/command-loader.ts
|
|
101489
|
-
import { join as
|
|
101929
|
+
import { join as join7 } from "node:path";
|
|
101490
101930
|
async function userCommands(deps, lines2) {
|
|
101491
101931
|
try {
|
|
101492
101932
|
const apiKey = await resolveApiKey({ env: deps.env, readFile: deps.readFile, homedir: deps.homedir });
|
|
@@ -101541,7 +101981,7 @@ async function repoCommands(deps, lines2) {
|
|
|
101541
101981
|
}
|
|
101542
101982
|
}
|
|
101543
101983
|
async function loadCommands(deps) {
|
|
101544
|
-
const configPath =
|
|
101984
|
+
const configPath = join7(deps.configDir, "imports.json");
|
|
101545
101985
|
try {
|
|
101546
101986
|
const loaded = parseImportsConfig(await deps.readFile(configPath).catch((err) => {
|
|
101547
101987
|
if (err.code === "ENOENT") return null;
|
|
@@ -101576,7 +102016,7 @@ var init_command_loader = __esm({
|
|
|
101576
102016
|
// src/useReplController.ts
|
|
101577
102017
|
import { readFile as readFile6, writeFile as writeFile4, mkdir as mkdir4 } from "node:fs/promises";
|
|
101578
102018
|
import { homedir as homedir2 } from "node:os";
|
|
101579
|
-
import { join as
|
|
102019
|
+
import { join as join8 } from "node:path";
|
|
101580
102020
|
import { execFile as execFile3 } from "node:child_process";
|
|
101581
102021
|
function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mentionsOpt, sessions, resumeLatest: resumeLatest2, onViewTurns, onViewResume, isEscOwned }) {
|
|
101582
102022
|
const { exit } = use_app_default();
|
|
@@ -101623,7 +102063,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
|
|
|
101623
102063
|
(0, import_react30.useEffect)(() => {
|
|
101624
102064
|
let live = true;
|
|
101625
102065
|
loadCommands({
|
|
101626
|
-
configDir: process.env.SPACECODE_CONFIG_DIR?.trim() ||
|
|
102066
|
+
configDir: process.env.SPACECODE_CONFIG_DIR?.trim() || join8(homedir2(), ".spacecode"),
|
|
101627
102067
|
cwd: process.cwd(),
|
|
101628
102068
|
env: process.env,
|
|
101629
102069
|
readFile: (p) => readFile6(p, "utf-8"),
|
|
@@ -101954,6 +102394,13 @@ ${commandsLoaded.statusLines.map((l) => ` ${l}`).join("\n")}`.trim() : `Importe
|
|
|
101954
102394
|
${list2.map((c) => ` /${c.name} (${c.scope})${c.description ? ` ${c.description}` : ""}`).join("\n")}`);
|
|
101955
102395
|
return;
|
|
101956
102396
|
}
|
|
102397
|
+
case "skills": {
|
|
102398
|
+
const state = profileRef.current;
|
|
102399
|
+
const apiKey2 = await resolveApiKey({ env: process.env, readFile: (p) => readFile6(p, "utf-8"), homedir: homedir2 }).catch(() => null);
|
|
102400
|
+
const client = apiKey2 ? createProfileClient({ baseUrl: backendUrl(process.env), apiKey: apiKey2, fetch }) : null;
|
|
102401
|
+
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)"] }));
|
|
102402
|
+
return;
|
|
102403
|
+
}
|
|
101957
102404
|
case "tools": {
|
|
101958
102405
|
const tools = toolsetRef.current?.tools ?? createLocalToolset({ cwd: process.cwd(), permissions, onTodos: setTodos }).tools;
|
|
101959
102406
|
notice(`Tools:
|
|
@@ -102016,7 +102463,7 @@ ${tools.map((t) => ` ${t.name}`).join("\n")}`);
|
|
|
102016
102463
|
cwd: process.cwd(),
|
|
102017
102464
|
gitRoot: gitTopLevel,
|
|
102018
102465
|
importGate: async () => {
|
|
102019
|
-
const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() ||
|
|
102466
|
+
const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() || join8(homedir2(), ".spacecode");
|
|
102020
102467
|
const gate = await importGateStatus({ configDir, cwd: process.cwd(), interactive: Boolean(process.stdin.isTTY) });
|
|
102021
102468
|
const loaded = await loadPermissionRules({ configDir, homeDir: homedir2(), cwd: process.cwd() });
|
|
102022
102469
|
rulesRef.current = loaded.rules;
|
|
@@ -102149,6 +102596,8 @@ var init_useReplController = __esm({
|
|
|
102149
102596
|
init_node_mentions();
|
|
102150
102597
|
init_api_key();
|
|
102151
102598
|
init_profile_runtime();
|
|
102599
|
+
init_skills_command();
|
|
102600
|
+
init_src();
|
|
102152
102601
|
init_import_gate();
|
|
102153
102602
|
init_permission_rules_loader();
|
|
102154
102603
|
init_command_loader();
|