spacecode 2.5.3 → 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 +900 -168
- package/package.json +1 -1
package/dist/repl.mjs
CHANGED
|
@@ -4925,6 +4925,211 @@ var init_summarizer = __esm({
|
|
|
4925
4925
|
}
|
|
4926
4926
|
});
|
|
4927
4927
|
|
|
4928
|
+
// ../agent/src/import/command-file.ts
|
|
4929
|
+
function parseCommandFile(text) {
|
|
4930
|
+
const lines2 = text.replace(/\r\n/g, "\n").split("\n");
|
|
4931
|
+
const close = lines2[0] === "---" ? lines2.indexOf("---", 1) : -1;
|
|
4932
|
+
if (close > 0) {
|
|
4933
|
+
const line = lines2.slice(1, close).find((l) => /^description\s*:/.test(l));
|
|
4934
|
+
const description = line ? unquote(line.slice(line.indexOf(":") + 1)).slice(0, 200) : "";
|
|
4935
|
+
return { description, body: lines2.slice(close + 1).join("\n").replace(/^\n+/, "") };
|
|
4936
|
+
}
|
|
4937
|
+
const normalized = lines2.join("\n");
|
|
4938
|
+
const first = lines2.find((l) => l.trim() !== "");
|
|
4939
|
+
return { description: first ? first.trim().slice(0, 80) : "", body: normalized };
|
|
4940
|
+
}
|
|
4941
|
+
var unquote;
|
|
4942
|
+
var init_command_file = __esm({
|
|
4943
|
+
"../agent/src/import/command-file.ts"() {
|
|
4944
|
+
"use strict";
|
|
4945
|
+
unquote = (v2) => {
|
|
4946
|
+
const t = v2.trim();
|
|
4947
|
+
return t.length >= 2 && (t[0] === '"' || t[0] === "'") && t[t.length - 1] === t[0] ? t.slice(1, -1) : t;
|
|
4948
|
+
};
|
|
4949
|
+
}
|
|
4950
|
+
});
|
|
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
|
+
|
|
4928
5133
|
// ../agent/src/import/claude-sources.ts
|
|
4929
5134
|
function isEnoent(err) {
|
|
4930
5135
|
return typeof err === "object" && err !== null && err.code === "ENOENT";
|
|
@@ -4962,6 +5167,8 @@ async function readClaudeAssets({ fs: fs6, home, projectDirs }) {
|
|
|
4962
5167
|
report.assets.push({ kind: "instructions", name: "global", description: "Global CLAUDE.md", content: globalText, source: globalPath });
|
|
4963
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");
|
|
4964
5169
|
}
|
|
5170
|
+
await readCommands(fs6, home, report);
|
|
5171
|
+
await readSkills(fs6, home, report);
|
|
4965
5172
|
for (const dir of projectDirs) {
|
|
4966
5173
|
const memDir = `${home}/.claude/projects/${claudeProjectSlug(dir)}/memory`;
|
|
4967
5174
|
let entries;
|
|
@@ -4981,7 +5188,7 @@ async function readClaudeAssets({ fs: fs6, home, projectDirs }) {
|
|
|
4981
5188
|
if (text === null) continue;
|
|
4982
5189
|
const base = entry === "MEMORY.md" ? "index" : entry.slice(0, -3).replace(/[^A-Za-z0-9._-]/g, "_");
|
|
4983
5190
|
const name = `${key}.${base}`;
|
|
4984
|
-
if (!
|
|
5191
|
+
if (!NAME_RE3.test(name)) {
|
|
4985
5192
|
report.errors.push({ path: path12, message: `cannot derive a valid asset name from "${entry}"` });
|
|
4986
5193
|
continue;
|
|
4987
5194
|
}
|
|
@@ -5003,11 +5210,119 @@ async function readClaudeAssets({ fs: fs6, home, projectDirs }) {
|
|
|
5003
5210
|
report.notes.push("No Claude Code memory directory found for this project");
|
|
5004
5211
|
return report;
|
|
5005
5212
|
}
|
|
5006
|
-
|
|
5213
|
+
async function readCommands(fs6, home, report) {
|
|
5214
|
+
const dir = `${home}/.claude/commands`;
|
|
5215
|
+
let entries;
|
|
5216
|
+
try {
|
|
5217
|
+
entries = await fs6.readdir(dir);
|
|
5218
|
+
} catch (err) {
|
|
5219
|
+
if (!isEnoent(err)) report.errors.push({ path: dir, message: err instanceof Error ? err.message : String(err) });
|
|
5220
|
+
return;
|
|
5221
|
+
}
|
|
5222
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5223
|
+
for (const entry of [...entries].sort()) {
|
|
5224
|
+
const path12 = `${dir}/${entry}`;
|
|
5225
|
+
if (!entry.endsWith(".md")) {
|
|
5226
|
+
if (!entry.startsWith(".")) report.notes.push(`Skipped ${path12}: only top-level .md command files are imported (subdirectories are not supported)`);
|
|
5227
|
+
continue;
|
|
5228
|
+
}
|
|
5229
|
+
const name = entry.slice(0, -3);
|
|
5230
|
+
if (!COMMAND_NAME_RE.test(name)) {
|
|
5231
|
+
report.errors.push({ path: path12, message: `command name "${name}" must start with a letter and use only letters, digits, - and _` });
|
|
5232
|
+
continue;
|
|
5233
|
+
}
|
|
5234
|
+
if (seen.has(name.toLowerCase())) {
|
|
5235
|
+
report.errors.push({ path: path12, message: `command name collision on "${name}" (names are case-insensitive)` });
|
|
5236
|
+
continue;
|
|
5237
|
+
}
|
|
5238
|
+
const text = await readOptional(fs6, path12, report);
|
|
5239
|
+
if (text === null) continue;
|
|
5240
|
+
if (Buffer.byteLength(text, "utf8") > MAX_ASSET_BYTES) {
|
|
5241
|
+
report.errors.push({ path: path12, message: `skipped: ${Buffer.byteLength(text, "utf8")} bytes is over the ${MAX_ASSET_BYTES} byte asset limit` });
|
|
5242
|
+
continue;
|
|
5243
|
+
}
|
|
5244
|
+
seen.add(name.toLowerCase());
|
|
5245
|
+
report.assets.push({ kind: "command", name, description: parseCommandFile(text).description, content: text, source: path12 });
|
|
5246
|
+
}
|
|
5247
|
+
}
|
|
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;
|
|
5007
5315
|
var init_claude_sources = __esm({
|
|
5008
5316
|
"../agent/src/import/claude-sources.ts"() {
|
|
5009
5317
|
"use strict";
|
|
5010
|
-
|
|
5318
|
+
init_command_file();
|
|
5319
|
+
init_skill_file();
|
|
5320
|
+
init_skill_source();
|
|
5321
|
+
MAX_ASSET_BYTES = 256 * 1024;
|
|
5322
|
+
NAME_RE3 = /^[A-Za-z0-9._-]{1,100}$/;
|
|
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;
|
|
5011
5326
|
}
|
|
5012
5327
|
});
|
|
5013
5328
|
|
|
@@ -5057,6 +5372,16 @@ function createProfileClient({ baseUrl, apiKey, fetch: doFetch, timeoutMs = 8e3
|
|
|
5057
5372
|
const res = await call("POST", "/adapt", { body: { sections }, timeoutMs: ADAPT_TIMEOUT_MS });
|
|
5058
5373
|
return await res.json();
|
|
5059
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
|
+
},
|
|
5060
5385
|
async put(kind, name, input) {
|
|
5061
5386
|
const res = await call("PUT", `/assets/${enc(kind)}/${enc(name)}`, {
|
|
5062
5387
|
body: { content: input.content, description: input.description ?? "", base_version: input.baseVersion }
|
|
@@ -5239,46 +5564,6 @@ var init_instruction_sections = __esm({
|
|
|
5239
5564
|
}
|
|
5240
5565
|
});
|
|
5241
5566
|
|
|
5242
|
-
// ../agent/src/import/tool-names.ts
|
|
5243
|
-
function translateToolNames(text) {
|
|
5244
|
-
const counts = /* @__PURE__ */ new Map();
|
|
5245
|
-
const names = Object.keys(TOOL_NAME_MAP).join("|");
|
|
5246
|
-
const re3 = new RegExp("`(" + names + ")(\\([^`)]*\\))?`", "g");
|
|
5247
|
-
const out = text.replace(re3, (_m, name, args) => {
|
|
5248
|
-
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
5249
|
-
return "`" + TOOL_NAME_MAP[name] + (args ?? "") + "`";
|
|
5250
|
-
});
|
|
5251
|
-
return { text: out, replaced: [...counts].map(([from, count]) => ({ from, to: TOOL_NAME_MAP[from], count })) };
|
|
5252
|
-
}
|
|
5253
|
-
function findUnsupported(text) {
|
|
5254
|
-
return UNSUPPORTED.filter((u3) => u3.re.test(text)).map((u3) => u3.label);
|
|
5255
|
-
}
|
|
5256
|
-
var TOOL_NAME_MAP, UNSUPPORTED;
|
|
5257
|
-
var init_tool_names = __esm({
|
|
5258
|
-
"../agent/src/import/tool-names.ts"() {
|
|
5259
|
-
"use strict";
|
|
5260
|
-
TOOL_NAME_MAP = {
|
|
5261
|
-
Read: "read_file",
|
|
5262
|
-
Write: "write_file",
|
|
5263
|
-
Edit: "edit_file",
|
|
5264
|
-
MultiEdit: "edit_file",
|
|
5265
|
-
Glob: "list_directory",
|
|
5266
|
-
Grep: "search",
|
|
5267
|
-
Bash: "bash",
|
|
5268
|
-
TodoWrite: "todo_write"
|
|
5269
|
-
};
|
|
5270
|
-
UNSUPPORTED = [
|
|
5271
|
-
{ label: "hooks", re: /\bhooks?\b.*\.(sh|json)|\bPreToolUse\b|\bPostToolUse\b|\bSessionStart\b|\bUserPromptSubmit\b/i },
|
|
5272
|
-
{ label: "ToolSearch / deferred tools", re: /\bToolSearch\b|deferred tools?/i },
|
|
5273
|
-
{ label: "Claude Code's Task/subagent tools", re: /\bsubagents?\b|\bAgent tool\b|`Task`|subagent_type/i },
|
|
5274
|
-
{ label: "Claude Code skills / slash commands", re: /\bSkill tool\b|invoke (?:the )?\w+ skill|\/ship\b|\/handoff\b/i },
|
|
5275
|
-
{ label: "MCP tool names (mcp__*)", re: /\bmcp__[A-Za-z0-9_-]+/ },
|
|
5276
|
-
{ label: "WebFetch/WebSearch/NotebookEdit tools", re: /`(?:WebFetch|WebSearch|NotebookEdit)`/ },
|
|
5277
|
-
{ label: "plan mode / worktree tools", re: /\bEnterPlanMode\b|\bExitPlanMode\b|\bEnterWorktree\b/ }
|
|
5278
|
-
];
|
|
5279
|
-
}
|
|
5280
|
-
});
|
|
5281
|
-
|
|
5282
5567
|
// ../agent/src/import/coverage.ts
|
|
5283
5568
|
function extractRuleLines(text) {
|
|
5284
5569
|
const out = [];
|
|
@@ -5719,6 +6004,105 @@ var init_permission_rules = __esm({
|
|
|
5719
6004
|
}
|
|
5720
6005
|
});
|
|
5721
6006
|
|
|
6007
|
+
// ../agent/src/import/command-args.ts
|
|
6008
|
+
function parseWords(input) {
|
|
6009
|
+
const words = [];
|
|
6010
|
+
let current = "";
|
|
6011
|
+
let quote = null;
|
|
6012
|
+
let has = false;
|
|
6013
|
+
for (const ch of input) {
|
|
6014
|
+
if (quote) {
|
|
6015
|
+
if (ch === quote) quote = null;
|
|
6016
|
+
else current += ch;
|
|
6017
|
+
} else if (ch === '"' || ch === "'") {
|
|
6018
|
+
quote = ch;
|
|
6019
|
+
has = true;
|
|
6020
|
+
} else if (/\s/.test(ch)) {
|
|
6021
|
+
if (has) words.push(current);
|
|
6022
|
+
current = "";
|
|
6023
|
+
has = false;
|
|
6024
|
+
} else {
|
|
6025
|
+
current += ch;
|
|
6026
|
+
has = true;
|
|
6027
|
+
}
|
|
6028
|
+
}
|
|
6029
|
+
if (has) words.push(current);
|
|
6030
|
+
return words;
|
|
6031
|
+
}
|
|
6032
|
+
function expandCommand(body, args) {
|
|
6033
|
+
const trimmed = args.trim();
|
|
6034
|
+
const words = parseWords(trimmed);
|
|
6035
|
+
return body.replace(/\$ARGUMENTS|\$[1-9]/g, (m2) => m2 === "$ARGUMENTS" ? trimmed : words[Number(m2[1]) - 1] ?? "");
|
|
6036
|
+
}
|
|
6037
|
+
var init_command_args = __esm({
|
|
6038
|
+
"../agent/src/import/command-args.ts"() {
|
|
6039
|
+
"use strict";
|
|
6040
|
+
}
|
|
6041
|
+
});
|
|
6042
|
+
|
|
6043
|
+
// ../agent/src/import/command-registry.ts
|
|
6044
|
+
function buildCommandRegistry(input) {
|
|
6045
|
+
const builtins = new Set(input.builtins.map((b2) => b2.toLowerCase()));
|
|
6046
|
+
const commands = [];
|
|
6047
|
+
const skipped = [];
|
|
6048
|
+
const shadowed = [];
|
|
6049
|
+
const accepted = /* @__PURE__ */ new Map();
|
|
6050
|
+
const lists = [["repo", input.repo], ["user", input.user]];
|
|
6051
|
+
for (const [scope, defs] of lists) {
|
|
6052
|
+
for (const def of defs) {
|
|
6053
|
+
const name = def.name.toLowerCase();
|
|
6054
|
+
if (!VALID.test(def.name)) skipped.push({ name: def.name, scope, reason: "invalid name" });
|
|
6055
|
+
else if (builtins.has(name)) skipped.push({ name, scope, reason: `collides with built-in /${name}` });
|
|
6056
|
+
else if (accepted.get(name) === scope) skipped.push({ name, scope, reason: "duplicate name" });
|
|
6057
|
+
else if (accepted.get(name) === "repo") shadowed.push(name);
|
|
6058
|
+
else {
|
|
6059
|
+
accepted.set(name, scope);
|
|
6060
|
+
commands.push({ ...def, name, scope });
|
|
6061
|
+
}
|
|
6062
|
+
}
|
|
6063
|
+
}
|
|
6064
|
+
commands.sort((a, b2) => a.name < b2.name ? -1 : a.name > b2.name ? 1 : 0);
|
|
6065
|
+
return { commands, skipped, shadowed };
|
|
6066
|
+
}
|
|
6067
|
+
var VALID;
|
|
6068
|
+
var init_command_registry = __esm({
|
|
6069
|
+
"../agent/src/import/command-registry.ts"() {
|
|
6070
|
+
"use strict";
|
|
6071
|
+
VALID = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
6072
|
+
}
|
|
6073
|
+
});
|
|
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
|
+
|
|
5722
6106
|
// ../agent/src/index.ts
|
|
5723
6107
|
var init_src = __esm({
|
|
5724
6108
|
"../agent/src/index.ts"() {
|
|
@@ -5759,6 +6143,14 @@ var init_src = __esm({
|
|
|
5759
6143
|
init_trust();
|
|
5760
6144
|
init_imports_config();
|
|
5761
6145
|
init_permission_rules();
|
|
6146
|
+
init_command_args();
|
|
6147
|
+
init_command_file();
|
|
6148
|
+
init_command_registry();
|
|
6149
|
+
init_claude_sources();
|
|
6150
|
+
init_skill_file();
|
|
6151
|
+
init_skill_prompt();
|
|
6152
|
+
init_skill_tool();
|
|
6153
|
+
init_skill_source();
|
|
5762
6154
|
}
|
|
5763
6155
|
});
|
|
5764
6156
|
|
|
@@ -5799,14 +6191,225 @@ var init_api_key = __esm({
|
|
|
5799
6191
|
}
|
|
5800
6192
|
});
|
|
5801
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
|
+
|
|
5802
6405
|
// src/profile-runtime.ts
|
|
5803
|
-
import { join as
|
|
6406
|
+
import { join as join4 } from "node:path";
|
|
5804
6407
|
function backendUrl(env3) {
|
|
5805
6408
|
return env3.SPACECODE_BACKEND_URL?.trim() || env3.SPACECODE_API_URL?.trim() || DEFAULT_BACKEND_URL;
|
|
5806
6409
|
}
|
|
5807
6410
|
function fileCache(deps) {
|
|
5808
|
-
const dir =
|
|
5809
|
-
const file =
|
|
6411
|
+
const dir = join4(deps.env.SPACECODE_CONFIG_DIR?.trim() || join4(deps.homedir(), ".spacecode"));
|
|
6412
|
+
const file = join4(dir, "profile-cache.json");
|
|
5810
6413
|
return {
|
|
5811
6414
|
async read() {
|
|
5812
6415
|
let raw;
|
|
@@ -5852,8 +6455,15 @@ async function prepareProfileUnsafe(deps) {
|
|
|
5852
6455
|
const root = await deps.gitRoot?.() ?? null;
|
|
5853
6456
|
const keys = [.../* @__PURE__ */ new Set([projectKey(deps.cwd), ...root ? [projectKey(root)] : []])];
|
|
5854
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
|
+
});
|
|
5855
6465
|
const result = await loadProfile({ client, cache: fileCache(deps), projectKeys: keys });
|
|
5856
|
-
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 };
|
|
5857
6467
|
const key = result.profile.projectKey;
|
|
5858
6468
|
const hasMemory = result.profile.memoryIndex !== null && key !== null;
|
|
5859
6469
|
const hasSections = result.profile.instructions?.includes("## More instructions (on demand)") ?? false;
|
|
@@ -5862,10 +6472,13 @@ async function prepareProfileUnsafe(deps) {
|
|
|
5862
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" : ""}`
|
|
5863
6473
|
];
|
|
5864
6474
|
if (result.error) lines2.push(`profile warning: ${result.error}`);
|
|
6475
|
+
lines2.push(...skills.statusLines);
|
|
5865
6476
|
return {
|
|
5866
|
-
promptSection: section2.text,
|
|
5867
|
-
|
|
6477
|
+
promptSection: section2.text + skills.promptSection,
|
|
6478
|
+
skills: skills.source,
|
|
6479
|
+
tools: [...hasMemory ? [MEMORY_READ_TOOL] : [], ...hasSections ? [INSTRUCTIONS_READ_TOOL] : [], ...skills.tools],
|
|
5868
6480
|
handlers: {
|
|
6481
|
+
...skills.handlers,
|
|
5869
6482
|
...hasMemory ? { memory_read: createMemoryReadTool(client, key) } : {},
|
|
5870
6483
|
...hasSections ? { instructions_read: createInstructionsReadTool(client) } : {}
|
|
5871
6484
|
},
|
|
@@ -5878,6 +6491,8 @@ var init_profile_runtime = __esm({
|
|
|
5878
6491
|
"use strict";
|
|
5879
6492
|
init_src();
|
|
5880
6493
|
init_api_key();
|
|
6494
|
+
init_skills_loader();
|
|
6495
|
+
init_src();
|
|
5881
6496
|
DEFAULT_BACKEND_URL = "https://backend.spacecode.app";
|
|
5882
6497
|
EMPTY = (statusLines) => ({ promptSection: "", tools: [], handlers: {}, statusLines });
|
|
5883
6498
|
}
|
|
@@ -6027,6 +6642,13 @@ ${USAGE}`);
|
|
|
6027
6642
|
return 1;
|
|
6028
6643
|
}
|
|
6029
6644
|
const client = createProfileClient({ baseUrl: backendUrl(deps.env), apiKey, fetch: deps.fetch });
|
|
6645
|
+
const TENANT_ASSET_CAP = 200;
|
|
6646
|
+
let assetCount = 0;
|
|
6647
|
+
try {
|
|
6648
|
+
assetCount = (await client.index())?.assets.length ?? 0;
|
|
6649
|
+
} catch (err) {
|
|
6650
|
+
deps.log(` note: could not read the profile asset count (${err instanceof Error ? err.message : String(err)}); the server still enforces the cap`);
|
|
6651
|
+
}
|
|
6030
6652
|
let failed = report.errors.length > 0;
|
|
6031
6653
|
let imported = 0;
|
|
6032
6654
|
let skipped = 0;
|
|
@@ -6049,6 +6671,11 @@ ${USAGE}`);
|
|
|
6049
6671
|
deps.log(` unchanged ${a.kind}/${a.name}`);
|
|
6050
6672
|
continue;
|
|
6051
6673
|
}
|
|
6674
|
+
if (!existing && assetCount >= TENANT_ASSET_CAP) {
|
|
6675
|
+
failed = true;
|
|
6676
|
+
deps.log(` SKIPPED ${a.kind}/${a.name}: the profile already holds ${assetCount} of ${TENANT_ASSET_CAP} allowed assets`);
|
|
6677
|
+
continue;
|
|
6678
|
+
}
|
|
6052
6679
|
if (existing && !overwrite) {
|
|
6053
6680
|
failed = true;
|
|
6054
6681
|
deps.log(` DIFFERS ${a.kind}/${a.name}: the profile already has a different version; re-run with --overwrite to replace it`);
|
|
@@ -6056,6 +6683,7 @@ ${USAGE}`);
|
|
|
6056
6683
|
}
|
|
6057
6684
|
await client.put(a.kind, a.name, { content: a.content, description: a.description, baseVersion: existing?.version ?? 0 });
|
|
6058
6685
|
imported++;
|
|
6686
|
+
if (!existing) assetCount++;
|
|
6059
6687
|
deps.log(` imported ${a.kind}/${a.name}`);
|
|
6060
6688
|
} catch (err) {
|
|
6061
6689
|
failed = true;
|
|
@@ -39350,12 +39978,12 @@ var require_utils = __commonJS({
|
|
|
39350
39978
|
return str;
|
|
39351
39979
|
}
|
|
39352
39980
|
var codeCache = {};
|
|
39353
|
-
function addToCodeCache(name, on,
|
|
39981
|
+
function addToCodeCache(name, on, off2) {
|
|
39354
39982
|
on = "\x1B[" + on + "m";
|
|
39355
|
-
|
|
39983
|
+
off2 = "\x1B[" + off2 + "m";
|
|
39356
39984
|
codeCache[on] = { set: name, to: true };
|
|
39357
|
-
codeCache[
|
|
39358
|
-
codeCache[name] = { on, off };
|
|
39985
|
+
codeCache[off2] = { set: name, to: false };
|
|
39986
|
+
codeCache[name] = { on, off: off2 };
|
|
39359
39987
|
}
|
|
39360
39988
|
addToCodeCache("bold", 1, 22);
|
|
39361
39989
|
addToCodeCache("italics", 3, 23);
|
|
@@ -41237,7 +41865,7 @@ var require_core = __commonJS({
|
|
|
41237
41865
|
return match && match.index === 0;
|
|
41238
41866
|
}
|
|
41239
41867
|
var BACKREF_RE = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;
|
|
41240
|
-
function
|
|
41868
|
+
function join9(regexps, separator = "|") {
|
|
41241
41869
|
let numCaptures = 0;
|
|
41242
41870
|
return regexps.map((regex2) => {
|
|
41243
41871
|
numCaptures += 1;
|
|
@@ -41541,7 +42169,7 @@ var require_core = __commonJS({
|
|
|
41541
42169
|
this.exec = () => null;
|
|
41542
42170
|
}
|
|
41543
42171
|
const terminators = this.regexes.map((el) => el[1]);
|
|
41544
|
-
this.matcherRe = langRe(
|
|
42172
|
+
this.matcherRe = langRe(join9(terminators), true);
|
|
41545
42173
|
this.lastIndex = 0;
|
|
41546
42174
|
}
|
|
41547
42175
|
/** @param {string} s */
|
|
@@ -99047,7 +99675,7 @@ var init_credential_filter = __esm({
|
|
|
99047
99675
|
});
|
|
99048
99676
|
|
|
99049
99677
|
// src/session/session-store.ts
|
|
99050
|
-
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";
|
|
99051
99679
|
import { randomBytes } from "node:crypto";
|
|
99052
99680
|
import path3 from "node:path";
|
|
99053
99681
|
function redactCredentials(value) {
|
|
@@ -99136,11 +99764,11 @@ var init_session_store = __esm({
|
|
|
99136
99764
|
}
|
|
99137
99765
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
99138
99766
|
try {
|
|
99139
|
-
await
|
|
99140
|
-
await
|
|
99141
|
-
await
|
|
99142
|
-
await
|
|
99143
|
-
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);
|
|
99144
99772
|
} catch (err) {
|
|
99145
99773
|
throw new SessionError(file, `Could not save session: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
99146
99774
|
}
|
|
@@ -99153,7 +99781,7 @@ var init_session_store = __esm({
|
|
|
99153
99781
|
if (size > MAX_SESSION_BYTES) {
|
|
99154
99782
|
throw new SessionError(file, `Session is ${size} bytes, over the ${MAX_SESSION_BYTES}-byte cap; not loaded`);
|
|
99155
99783
|
}
|
|
99156
|
-
text = await
|
|
99784
|
+
text = await readFile2(file, "utf8");
|
|
99157
99785
|
} catch (err) {
|
|
99158
99786
|
if (err instanceof SessionError) throw err;
|
|
99159
99787
|
throw new SessionError(file, `Could not read session: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
@@ -99166,7 +99794,7 @@ var init_session_store = __esm({
|
|
|
99166
99794
|
async list() {
|
|
99167
99795
|
let names;
|
|
99168
99796
|
try {
|
|
99169
|
-
names = await
|
|
99797
|
+
names = await readdir2(this.dir);
|
|
99170
99798
|
} catch (err) {
|
|
99171
99799
|
if (err.code === "ENOENT") return { sessions: [], errors: [] };
|
|
99172
99800
|
throw new SessionError(this.dir, `Could not list sessions: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
@@ -100343,6 +100971,8 @@ var init_slash_commands = __esm({
|
|
|
100343
100971
|
{ name: "compact", description: "Summarize the conversation to free up context" },
|
|
100344
100972
|
{ name: "model", description: "List gateway models; /model <n|id> switches for this session" },
|
|
100345
100973
|
{ name: "turns", description: "Browse earlier turns and open their steps (shortcut: ctrl+o)" },
|
|
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>" },
|
|
100346
100976
|
{ name: "tools", description: "List the tools available to the agent" },
|
|
100347
100977
|
{ name: "exit", description: "Quit the REPL (alias: /quit)" },
|
|
100348
100978
|
{ name: "quit", description: "Quit the REPL" }
|
|
@@ -100351,15 +100981,15 @@ var init_slash_commands = __esm({
|
|
|
100351
100981
|
});
|
|
100352
100982
|
|
|
100353
100983
|
// src/components/PromptEditor.tsx
|
|
100354
|
-
function validateSubmission(text) {
|
|
100984
|
+
function validateSubmission(text, extra = []) {
|
|
100355
100985
|
const slash = parseSlashCommand(text);
|
|
100356
|
-
if (slash && !isKnownSlashCommand(slash.name)) return `Unknown command: /${slash.name}. Type /help for a list.`;
|
|
100986
|
+
if (slash && !isKnownSlashCommand(slash.name) && !extra.some((c) => c.name === slash.name)) return `Unknown command: /${slash.name}. Type /help for a list.`;
|
|
100357
100987
|
return null;
|
|
100358
100988
|
}
|
|
100359
100989
|
function withCursor(row, col) {
|
|
100360
100990
|
return `${row.slice(0, col)}\x1B[7m${row[col] ?? " "}\x1B[27m${row.slice(col + 1)}`;
|
|
100361
100991
|
}
|
|
100362
|
-
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 = [] }) {
|
|
100363
100993
|
const { exit } = use_app_default();
|
|
100364
100994
|
const { columns } = useTerminalSize();
|
|
100365
100995
|
const { isRawModeSupported } = use_stdin_default();
|
|
@@ -100395,15 +101025,15 @@ function PromptEditor({ onSubmit, disabled, history, listFiles: listFiles2, onTa
|
|
|
100395
101025
|
const mention = shell ? null : activeMention(buf.text, buf.cursor);
|
|
100396
101026
|
const mentionOpen = mention !== null;
|
|
100397
101027
|
(0, import_react29.useEffect)(() => {
|
|
100398
|
-
if (!mentionOpen || !
|
|
101028
|
+
if (!mentionOpen || !listFiles3 || filesRef.current !== null || loadingFiles.current) return;
|
|
100399
101029
|
loadingFiles.current = true;
|
|
100400
|
-
|
|
101030
|
+
listFiles3().then((f) => {
|
|
100401
101031
|
filesRef.current = f;
|
|
100402
101032
|
setFiles(f);
|
|
100403
101033
|
}).catch((err) => fail("Could not list files", err)).finally(() => {
|
|
100404
101034
|
loadingFiles.current = false;
|
|
100405
101035
|
});
|
|
100406
|
-
}, [mentionOpen,
|
|
101036
|
+
}, [mentionOpen, listFiles3, fail]);
|
|
100407
101037
|
const chooseSel = (n) => {
|
|
100408
101038
|
selRef.current = n;
|
|
100409
101039
|
setSel(n);
|
|
@@ -100462,7 +101092,7 @@ function PromptEditor({ onSubmit, disabled, history, listFiles: listFiles2, onTa
|
|
|
100462
101092
|
else update(applyEdit(cur, { type: "delete" }));
|
|
100463
101093
|
return;
|
|
100464
101094
|
case "tab": {
|
|
100465
|
-
const completed = completeSlash(cur.text, slashMatches(cur.text, SLASH_COMMANDS));
|
|
101095
|
+
const completed = completeSlash(cur.text, slashMatches(cur.text, [...SLASH_COMMANDS, ...extraCommands]));
|
|
100466
101096
|
if (completed) update(createBuffer(completed));
|
|
100467
101097
|
else onTab?.();
|
|
100468
101098
|
return;
|
|
@@ -100495,7 +101125,7 @@ function PromptEditor({ onSubmit, disabled, history, listFiles: listFiles2, onTa
|
|
|
100495
101125
|
return;
|
|
100496
101126
|
}
|
|
100497
101127
|
const text = inShell ? `!${typed}` : typed;
|
|
100498
|
-
const problem = inShell ? null : validateSubmission(text);
|
|
101128
|
+
const problem = inShell ? null : validateSubmission(text, extraCommands);
|
|
100499
101129
|
if (problem) {
|
|
100500
101130
|
setHint(problem);
|
|
100501
101131
|
return;
|
|
@@ -100515,7 +101145,7 @@ function PromptEditor({ onSubmit, disabled, history, listFiles: listFiles2, onTa
|
|
|
100515
101145
|
}
|
|
100516
101146
|
const rows = lines(buf);
|
|
100517
101147
|
const { line, col } = lineCol(buf);
|
|
100518
|
-
const matches = shell ? [] : slashMatches(buf.text, SLASH_COMMANDS);
|
|
101148
|
+
const matches = shell ? [] : slashMatches(buf.text, [...SLASH_COMMANDS, ...extraCommands]);
|
|
100519
101149
|
const picks = mention && files ? rankPaths(files, mention.query, PICKER_ROWS) : [];
|
|
100520
101150
|
const pickAt = Math.min(sel, Math.max(picks.length - 1, 0));
|
|
100521
101151
|
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { flexDirection: "column", children: [
|
|
@@ -100638,24 +101268,24 @@ var init_history_store = __esm({
|
|
|
100638
101268
|
});
|
|
100639
101269
|
|
|
100640
101270
|
// src/editor/history-file-io.ts
|
|
100641
|
-
import { chmod as
|
|
101271
|
+
import { chmod as chmod3, mkdir as mkdir3, readFile as readFile3, rename as rename3, writeFile as writeFile3 } from "node:fs/promises";
|
|
100642
101272
|
import path4 from "node:path";
|
|
100643
101273
|
function createFileHistoryIO(file) {
|
|
100644
101274
|
return {
|
|
100645
101275
|
async read() {
|
|
100646
101276
|
try {
|
|
100647
|
-
return await
|
|
101277
|
+
return await readFile3(file, "utf8");
|
|
100648
101278
|
} catch (err) {
|
|
100649
101279
|
if (err.code === "ENOENT") return null;
|
|
100650
101280
|
throw err;
|
|
100651
101281
|
}
|
|
100652
101282
|
},
|
|
100653
101283
|
async write(content) {
|
|
100654
|
-
await
|
|
101284
|
+
await mkdir3(path4.dirname(file), { recursive: true, mode: 448 });
|
|
100655
101285
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
100656
|
-
await
|
|
100657
|
-
await
|
|
100658
|
-
await
|
|
101286
|
+
await writeFile3(tmp, content, { mode: 384 });
|
|
101287
|
+
await chmod3(tmp, 384);
|
|
101288
|
+
await rename3(tmp, file);
|
|
100659
101289
|
}
|
|
100660
101290
|
};
|
|
100661
101291
|
}
|
|
@@ -100864,7 +101494,7 @@ var init_shell_turn = __esm({
|
|
|
100864
101494
|
});
|
|
100865
101495
|
|
|
100866
101496
|
// src/editor/file-list.ts
|
|
100867
|
-
async function
|
|
101497
|
+
async function listFiles2(readDir, opts = {}) {
|
|
100868
101498
|
const max = opts.maxFiles ?? DEFAULT_MAX_LISTED_FILES;
|
|
100869
101499
|
const files = [];
|
|
100870
101500
|
const queue = [""];
|
|
@@ -101091,18 +101721,18 @@ var init_mention_resolver = __esm({
|
|
|
101091
101721
|
});
|
|
101092
101722
|
|
|
101093
101723
|
// src/editor/node-mention-fs.ts
|
|
101094
|
-
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";
|
|
101095
101725
|
import path6 from "node:path";
|
|
101096
101726
|
function isInside(root, target) {
|
|
101097
101727
|
return target === root || target.startsWith(root.endsWith(path6.sep) ? root : root + path6.sep);
|
|
101098
101728
|
}
|
|
101099
101729
|
function createNodeMentionFS(cwd2) {
|
|
101100
101730
|
let rootPromise;
|
|
101101
|
-
const root = () => rootPromise ??=
|
|
101731
|
+
const root = () => rootPromise ??= realpath2(cwd2);
|
|
101102
101732
|
async function confined(abs) {
|
|
101103
101733
|
let real;
|
|
101104
101734
|
try {
|
|
101105
|
-
real = await
|
|
101735
|
+
real = await realpath2(abs);
|
|
101106
101736
|
} catch (err) {
|
|
101107
101737
|
if (err.code === "ENOENT") return null;
|
|
101108
101738
|
throw err;
|
|
@@ -101138,11 +101768,11 @@ var init_node_mention_fs = __esm({
|
|
|
101138
101768
|
});
|
|
101139
101769
|
|
|
101140
101770
|
// src/editor/node-read-dir.ts
|
|
101141
|
-
import { readdir as
|
|
101771
|
+
import { readdir as readdir3 } from "node:fs/promises";
|
|
101142
101772
|
import path7 from "node:path";
|
|
101143
101773
|
function createNodeReadDir(cwd2) {
|
|
101144
101774
|
return async (rel) => {
|
|
101145
|
-
const entries = await
|
|
101775
|
+
const entries = await readdir3(path7.join(cwd2, rel), { withFileTypes: true });
|
|
101146
101776
|
return entries.map((e) => ({ name: e.name, isFile: e.isFile(), isDirectory: e.isDirectory() }));
|
|
101147
101777
|
};
|
|
101148
101778
|
}
|
|
@@ -101153,11 +101783,11 @@ var init_node_read_dir = __esm({
|
|
|
101153
101783
|
});
|
|
101154
101784
|
|
|
101155
101785
|
// src/editor/node-mentions.ts
|
|
101156
|
-
import { readFile as
|
|
101786
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
101157
101787
|
import path8 from "node:path";
|
|
101158
101788
|
async function readGitignore(cwd2) {
|
|
101159
101789
|
try {
|
|
101160
|
-
return createGitignoreMatcher(await
|
|
101790
|
+
return createGitignoreMatcher(await readFile4(path8.join(cwd2, ".gitignore"), "utf8"));
|
|
101161
101791
|
} catch (err) {
|
|
101162
101792
|
if (err.code === "ENOENT") return () => false;
|
|
101163
101793
|
throw err;
|
|
@@ -101171,7 +101801,7 @@ function createNodeMentions(cwd2) {
|
|
|
101171
101801
|
return formatMentionPrompt(text, result);
|
|
101172
101802
|
},
|
|
101173
101803
|
async list() {
|
|
101174
|
-
return
|
|
101804
|
+
return listFiles2(createNodeReadDir(cwd2), { ignored: await readGitignore(cwd2) });
|
|
101175
101805
|
}
|
|
101176
101806
|
};
|
|
101177
101807
|
}
|
|
@@ -101187,75 +101817,48 @@ var init_node_mentions = __esm({
|
|
|
101187
101817
|
}
|
|
101188
101818
|
});
|
|
101189
101819
|
|
|
101190
|
-
// src/
|
|
101191
|
-
|
|
101192
|
-
|
|
101193
|
-
|
|
101194
|
-
|
|
101195
|
-
|
|
101196
|
-
|
|
101197
|
-
|
|
101198
|
-
|
|
101199
|
-
|
|
101200
|
-
|
|
101201
|
-
|
|
101202
|
-
|
|
101203
|
-
|
|
101204
|
-
|
|
101205
|
-
|
|
101206
|
-
|
|
101207
|
-
|
|
101208
|
-
|
|
101209
|
-
|
|
101210
|
-
|
|
101211
|
-
}
|
|
101212
|
-
|
|
101213
|
-
|
|
101214
|
-
|
|
101215
|
-
|
|
101216
|
-
|
|
101217
|
-
|
|
101218
|
-
|
|
101219
|
-
const loaded = parseImportsConfig(await readOrNull(configPath), configPath);
|
|
101220
|
-
if (loaded.error) lines2.push(`imports: ${loaded.error} (every category off)`);
|
|
101221
|
-
else {
|
|
101222
|
-
const on = IMPORT_CATEGORIES.filter((c) => loaded.config.categories[c]);
|
|
101223
|
-
lines2.push(on.length ? `imports: on for ${on.join(", ")}` : "imports: every category off (default)");
|
|
101224
|
-
}
|
|
101225
|
-
} catch (err) {
|
|
101226
|
-
lines2.push(`imports: not read (${err instanceof Error ? err.message : String(err)}); every category off`);
|
|
101227
|
-
}
|
|
101228
|
-
try {
|
|
101229
|
-
const files = {};
|
|
101230
|
-
for (const name of REPO_SOURCE_FILES) {
|
|
101231
|
-
const text = await readOrNull(join4(deps.cwd, name));
|
|
101232
|
-
if (text !== null) files[name] = text;
|
|
101233
|
-
}
|
|
101234
|
-
const found = Object.keys(files);
|
|
101235
|
-
if (found.length === 0) return lines2;
|
|
101236
|
-
const status = await createNodeTrustStore(deps.configDir).repoStatus(deps.cwd, files);
|
|
101237
|
-
const names = found.join(", ");
|
|
101238
|
-
if (status === "trusted" || status === "session") lines2.push(`repo sources: ${names} (trusted${status === "session" ? " for this session" : ""})`);
|
|
101239
|
-
else if (status === "never") lines2.push(`repo sources: ${names} NOT loaded (marked never trust)`);
|
|
101240
|
-
else lines2.push(`repo sources: ${names} NOT loaded (untrusted${deps.interactive ? "" : ", non-interactive run"})`);
|
|
101241
|
-
} catch (err) {
|
|
101242
|
-
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.`;
|
|
101243
101849
|
}
|
|
101244
|
-
return
|
|
101850
|
+
return "Usage: /skills | /skills show <name> [file] | /skills delete <name>";
|
|
101245
101851
|
}
|
|
101246
|
-
var
|
|
101247
|
-
|
|
101248
|
-
"src/import-gate.ts"() {
|
|
101852
|
+
var init_skills_command = __esm({
|
|
101853
|
+
"src/skills-command.ts"() {
|
|
101249
101854
|
"use strict";
|
|
101250
101855
|
init_src();
|
|
101251
|
-
REPO_SOURCE_FILES = [".mcp.json", ".claude/settings.json", ".claude/settings.local.json", "CLAUDE.local.md"];
|
|
101252
|
-
sha256 = (text) => createHash("sha256").update(text).digest("hex");
|
|
101253
101856
|
}
|
|
101254
101857
|
});
|
|
101255
101858
|
|
|
101256
101859
|
// src/permission-rules-loader.ts
|
|
101257
101860
|
import { readFile as readFile5 } from "node:fs/promises";
|
|
101258
|
-
import { join as
|
|
101861
|
+
import { join as join6 } from "node:path";
|
|
101259
101862
|
async function readOrNull2(path12) {
|
|
101260
101863
|
try {
|
|
101261
101864
|
return await readFile5(path12, "utf8");
|
|
@@ -101266,7 +101869,7 @@ async function readOrNull2(path12) {
|
|
|
101266
101869
|
}
|
|
101267
101870
|
async function loadPermissionRules(deps) {
|
|
101268
101871
|
const none = (line) => ({ rules: void 0, defaultMode: void 0, statusLines: [line] });
|
|
101269
|
-
const configPath =
|
|
101872
|
+
const configPath = join6(deps.configDir, "imports.json");
|
|
101270
101873
|
try {
|
|
101271
101874
|
const loaded = parseImportsConfig(await readOrNull2(configPath), configPath);
|
|
101272
101875
|
if (loaded.error) return none("permission rules: off (imports.json unreadable)");
|
|
@@ -101275,17 +101878,13 @@ async function loadPermissionRules(deps) {
|
|
|
101275
101878
|
return none(`permission rules: off (imports.json not read: ${err instanceof Error ? err.message : String(err)})`);
|
|
101276
101879
|
}
|
|
101277
101880
|
const lines2 = [];
|
|
101278
|
-
const files = [{ scope: "user", path:
|
|
101881
|
+
const files = [{ scope: "user", path: join6(deps.homeDir, ".claude", "settings.json") }];
|
|
101279
101882
|
try {
|
|
101280
|
-
const sources2 =
|
|
101281
|
-
for (const name of REPO_SOURCE_FILES) {
|
|
101282
|
-
const text = await readOrNull2(join5(deps.cwd, name));
|
|
101283
|
-
if (text !== null) sources2[name] = text;
|
|
101284
|
-
}
|
|
101883
|
+
const sources2 = await readRepoSources(deps.cwd);
|
|
101285
101884
|
if (sources2[".claude/settings.json"] !== void 0 || sources2[".claude/settings.local.json"] !== void 0) {
|
|
101286
101885
|
const status = await createNodeTrustStore(deps.configDir).repoStatus(deps.cwd, sources2);
|
|
101287
101886
|
if (status === "trusted" || status === "session") {
|
|
101288
|
-
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") });
|
|
101289
101888
|
} else lines2.push(`permission rules: repo settings NOT loaded (${status === "never" ? "marked never trust" : "untrusted repo"})`);
|
|
101290
101889
|
}
|
|
101291
101890
|
} catch (err) {
|
|
@@ -101326,10 +101925,98 @@ var init_permission_rules_loader = __esm({
|
|
|
101326
101925
|
}
|
|
101327
101926
|
});
|
|
101328
101927
|
|
|
101928
|
+
// src/command-loader.ts
|
|
101929
|
+
import { join as join7 } from "node:path";
|
|
101930
|
+
async function userCommands(deps, lines2) {
|
|
101931
|
+
try {
|
|
101932
|
+
const apiKey = await resolveApiKey({ env: deps.env, readFile: deps.readFile, homedir: deps.homedir });
|
|
101933
|
+
if (!apiKey) {
|
|
101934
|
+
lines2.push("commands: user commands NOT loaded (not signed in)");
|
|
101935
|
+
return [];
|
|
101936
|
+
}
|
|
101937
|
+
const client = createProfileClient({ baseUrl: backendUrl(deps.env), apiKey, fetch: deps.fetch, timeoutMs: 3e3 });
|
|
101938
|
+
const index = await client.index();
|
|
101939
|
+
const entries = (index?.assets ?? []).filter((a) => a.kind === "command");
|
|
101940
|
+
const defs = [];
|
|
101941
|
+
await Promise.all(
|
|
101942
|
+
entries.map(async (e) => {
|
|
101943
|
+
try {
|
|
101944
|
+
const asset = await client.get("command", e.name);
|
|
101945
|
+
if (asset) defs.push({ name: e.name, ...parseCommandFile(asset.content) });
|
|
101946
|
+
} catch (err) {
|
|
101947
|
+
lines2.push(`commands: ${e.name} NOT loaded (${err instanceof Error ? err.message : String(err)})`);
|
|
101948
|
+
}
|
|
101949
|
+
})
|
|
101950
|
+
);
|
|
101951
|
+
return defs;
|
|
101952
|
+
} catch (err) {
|
|
101953
|
+
lines2.push(`commands: user commands NOT loaded (${err instanceof Error ? err.message : String(err)})`);
|
|
101954
|
+
return [];
|
|
101955
|
+
}
|
|
101956
|
+
}
|
|
101957
|
+
async function repoCommands(deps, lines2) {
|
|
101958
|
+
try {
|
|
101959
|
+
const sources = await readRepoSources(deps.cwd);
|
|
101960
|
+
const names = Object.keys(sources).filter((k2) => k2.startsWith(".claude/commands/"));
|
|
101961
|
+
if (names.length === 0) return [];
|
|
101962
|
+
const status = await createNodeTrustStore(deps.configDir).repoStatus(deps.cwd, sources);
|
|
101963
|
+
if (status !== "trusted" && status !== "session") {
|
|
101964
|
+
lines2.push(`commands: ${names.length} repo command(s) NOT loaded (${status === "never" ? "marked never trust" : "untrusted repo"})`);
|
|
101965
|
+
return [];
|
|
101966
|
+
}
|
|
101967
|
+
const defs = [];
|
|
101968
|
+
for (const key of names) {
|
|
101969
|
+
const text = sources[key];
|
|
101970
|
+
const name = key.slice(".claude/commands/".length, -3);
|
|
101971
|
+
if (Buffer.byteLength(text, "utf8") > MAX_ASSET_BYTES) {
|
|
101972
|
+
lines2.push(`commands: repo command ${name} skipped (over ${MAX_ASSET_BYTES} bytes)`);
|
|
101973
|
+
continue;
|
|
101974
|
+
}
|
|
101975
|
+
defs.push({ name, ...parseCommandFile(text) });
|
|
101976
|
+
}
|
|
101977
|
+
return defs;
|
|
101978
|
+
} catch (err) {
|
|
101979
|
+
lines2.push(`commands: repo commands NOT loaded (${err instanceof Error ? err.message : String(err)})`);
|
|
101980
|
+
return [];
|
|
101981
|
+
}
|
|
101982
|
+
}
|
|
101983
|
+
async function loadCommands(deps) {
|
|
101984
|
+
const configPath = join7(deps.configDir, "imports.json");
|
|
101985
|
+
try {
|
|
101986
|
+
const loaded = parseImportsConfig(await deps.readFile(configPath).catch((err) => {
|
|
101987
|
+
if (err.code === "ENOENT") return null;
|
|
101988
|
+
throw err;
|
|
101989
|
+
}), configPath);
|
|
101990
|
+
if (loaded.error) return off("commands: off (imports.json unreadable)");
|
|
101991
|
+
if (!loaded.config.categories.commands) return off("commands: off (imports.json has commands off)");
|
|
101992
|
+
} catch (err) {
|
|
101993
|
+
return off(`commands: off (imports.json not read: ${err instanceof Error ? err.message : String(err)})`);
|
|
101994
|
+
}
|
|
101995
|
+
const lines2 = [];
|
|
101996
|
+
const [user, repo] = await Promise.all([userCommands(deps, lines2), repoCommands(deps, lines2)]);
|
|
101997
|
+
const reg = buildCommandRegistry({ builtins: deps.builtins, user, repo });
|
|
101998
|
+
const nUser = reg.commands.filter((c) => c.scope === "user").length;
|
|
101999
|
+
const nRepo = reg.commands.length - nUser;
|
|
102000
|
+
lines2.unshift(`commands: ${nUser} user, ${nRepo} repo${reg.shadowed.length ? `; repo shadows user: ${reg.shadowed.map((n) => "/" + n).join(", ")}` : ""}`);
|
|
102001
|
+
for (const s of reg.skipped) lines2.push(`commands: skipped [${s.scope}] /${s.name} (${s.reason})`);
|
|
102002
|
+
return { commands: reg.commands, statusLines: lines2 };
|
|
102003
|
+
}
|
|
102004
|
+
var off;
|
|
102005
|
+
var init_command_loader = __esm({
|
|
102006
|
+
"src/command-loader.ts"() {
|
|
102007
|
+
"use strict";
|
|
102008
|
+
init_src();
|
|
102009
|
+
init_api_key();
|
|
102010
|
+
init_import_gate();
|
|
102011
|
+
init_profile_runtime();
|
|
102012
|
+
off = (line) => ({ commands: [], statusLines: [line] });
|
|
102013
|
+
}
|
|
102014
|
+
});
|
|
102015
|
+
|
|
101329
102016
|
// src/useReplController.ts
|
|
101330
102017
|
import { readFile as readFile6, writeFile as writeFile4, mkdir as mkdir4 } from "node:fs/promises";
|
|
101331
102018
|
import { homedir as homedir2 } from "node:os";
|
|
101332
|
-
import { join as
|
|
102019
|
+
import { join as join8 } from "node:path";
|
|
101333
102020
|
import { execFile as execFile3 } from "node:child_process";
|
|
101334
102021
|
function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mentionsOpt, sessions, resumeLatest: resumeLatest2, onViewTurns, onViewResume, isEscOwned }) {
|
|
101335
102022
|
const { exit } = use_app_default();
|
|
@@ -101372,6 +102059,27 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
|
|
|
101372
102059
|
const pendingRef = (0, import_react30.useRef)(null);
|
|
101373
102060
|
const toolsetRef = (0, import_react30.useRef)(null);
|
|
101374
102061
|
const rulesRef = (0, import_react30.useRef)(void 0);
|
|
102062
|
+
const [commandsLoaded, setCommandsLoaded] = (0, import_react30.useState)({ commands: [], statusLines: [] });
|
|
102063
|
+
(0, import_react30.useEffect)(() => {
|
|
102064
|
+
let live = true;
|
|
102065
|
+
loadCommands({
|
|
102066
|
+
configDir: process.env.SPACECODE_CONFIG_DIR?.trim() || join8(homedir2(), ".spacecode"),
|
|
102067
|
+
cwd: process.cwd(),
|
|
102068
|
+
env: process.env,
|
|
102069
|
+
readFile: (p) => readFile6(p, "utf-8"),
|
|
102070
|
+
homedir: homedir2,
|
|
102071
|
+
fetch,
|
|
102072
|
+
builtins: SLASH_COMMANDS.map((c) => c.name)
|
|
102073
|
+
}).then((r) => {
|
|
102074
|
+
if (live) setCommandsLoaded(r);
|
|
102075
|
+
}, (err) => {
|
|
102076
|
+
if (live) setCommandsLoaded({ commands: [], statusLines: [`commands: NOT loaded (${err instanceof Error ? err.message : String(err)})`] });
|
|
102077
|
+
});
|
|
102078
|
+
return () => {
|
|
102079
|
+
live = false;
|
|
102080
|
+
};
|
|
102081
|
+
}, []);
|
|
102082
|
+
const commandEntries = (0, import_react30.useMemo)(() => commandsLoaded.commands.map((c) => ({ name: c.name, description: c.description })), [commandsLoaded]);
|
|
101375
102083
|
(0, import_react30.useEffect)(() => {
|
|
101376
102084
|
sessionTokensRef.current = sessionTokens;
|
|
101377
102085
|
}, [sessionTokens]);
|
|
@@ -101494,8 +102202,15 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
|
|
|
101494
102202
|
}
|
|
101495
102203
|
})();
|
|
101496
102204
|
}, [resumeLatest2, sessions, projectRoot, notice, applySession]);
|
|
101497
|
-
const submit = (0, import_react30.useCallback)(async (
|
|
101498
|
-
const
|
|
102205
|
+
const submit = (0, import_react30.useCallback)(async (rawPrompt) => {
|
|
102206
|
+
const typed = parseSlashCommand(rawPrompt);
|
|
102207
|
+
const custom = typed && !isKnownSlashCommand(typed.name) ? commandsLoaded.commands.find((c) => c.name === typed.name) : void 0;
|
|
102208
|
+
const prompt = custom ? expandCommand(custom.body, typed.args) : rawPrompt;
|
|
102209
|
+
if (custom && prompt.trim() === "") {
|
|
102210
|
+
notice(`/${custom.name} expands to an empty prompt.`);
|
|
102211
|
+
return;
|
|
102212
|
+
}
|
|
102213
|
+
const shellCommand = custom ? null : shellCommandOf(prompt);
|
|
101499
102214
|
if (shellCommand !== null) {
|
|
101500
102215
|
if (shellCommand === "") {
|
|
101501
102216
|
notice("Type a command after !");
|
|
@@ -101529,7 +102244,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
|
|
|
101529
102244
|
}
|
|
101530
102245
|
return;
|
|
101531
102246
|
}
|
|
101532
|
-
const slash = parseSlashCommand(prompt);
|
|
102247
|
+
const slash = custom ? null : parseSlashCommand(prompt);
|
|
101533
102248
|
if (slash && isKnownSlashCommand(slash.name)) {
|
|
101534
102249
|
switch (slash.name) {
|
|
101535
102250
|
case "exit":
|
|
@@ -101567,7 +102282,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
|
|
|
101567
102282
|
maxContextTokens: DEFAULT_MAX_CONTEXT_TOKENS,
|
|
101568
102283
|
tokens: sessionTokensRef.current,
|
|
101569
102284
|
costUsd: estimateCostUsd(sessionTokensRef.current.input, sessionTokensRef.current.output),
|
|
101570
|
-
profileLines: profileRef.current?.statusLines ?? ["profile: not loaded yet (loads on your first message)"]
|
|
102285
|
+
profileLines: [...profileRef.current?.statusLines ?? ["profile: not loaded yet (loads on your first message)"], ...commandsLoaded.statusLines]
|
|
101571
102286
|
}));
|
|
101572
102287
|
return;
|
|
101573
102288
|
case "resume": {
|
|
@@ -101672,6 +102387,20 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
|
|
|
101672
102387
|
if (onViewTurns) onViewTurns();
|
|
101673
102388
|
else notice("The turn viewer is not available here.");
|
|
101674
102389
|
return;
|
|
102390
|
+
case "commands": {
|
|
102391
|
+
const list2 = commandsLoaded.commands;
|
|
102392
|
+
notice(list2.length === 0 ? `No imported commands.
|
|
102393
|
+
${commandsLoaded.statusLines.map((l) => ` ${l}`).join("\n")}`.trim() : `Imported commands:
|
|
102394
|
+
${list2.map((c) => ` /${c.name} (${c.scope})${c.description ? ` ${c.description}` : ""}`).join("\n")}`);
|
|
102395
|
+
return;
|
|
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
|
+
}
|
|
101675
102404
|
case "tools": {
|
|
101676
102405
|
const tools = toolsetRef.current?.tools ?? createLocalToolset({ cwd: process.cwd(), permissions, onTodos: setTodos }).tools;
|
|
101677
102406
|
notice(`Tools:
|
|
@@ -101734,7 +102463,7 @@ ${tools.map((t) => ` ${t.name}`).join("\n")}`);
|
|
|
101734
102463
|
cwd: process.cwd(),
|
|
101735
102464
|
gitRoot: gitTopLevel,
|
|
101736
102465
|
importGate: async () => {
|
|
101737
|
-
const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() ||
|
|
102466
|
+
const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() || join8(homedir2(), ".spacecode");
|
|
101738
102467
|
const gate = await importGateStatus({ configDir, cwd: process.cwd(), interactive: Boolean(process.stdin.isTTY) });
|
|
101739
102468
|
const loaded = await loadPermissionRules({ configDir, homeDir: homedir2(), cwd: process.cwd() });
|
|
101740
102469
|
rulesRef.current = loaded.rules;
|
|
@@ -101806,7 +102535,7 @@ ${tools.map((t) => ` ${t.name}`).join("\n")}`);
|
|
|
101806
102535
|
abortRef.current = null;
|
|
101807
102536
|
setIsRunning(false);
|
|
101808
102537
|
}
|
|
101809
|
-
}, [repoName2, history, exit, permissions, notice, refuseAllConfirms, commitTurn, mentions, createToolset, sessions, projectRoot, persist, applySession, model, onViewTurns, onViewResume, setTodos]);
|
|
102538
|
+
}, [commandsLoaded, repoName2, history, exit, permissions, notice, refuseAllConfirms, commitTurn, mentions, createToolset, sessions, projectRoot, persist, applySession, model, onViewTurns, onViewResume, setTodos]);
|
|
101810
102539
|
const approxTokens = (0, import_react30.useMemo)(
|
|
101811
102540
|
() => dropDiscardedEvents(events).reduce((n, e) => n + (e.type === "text" ? estimateTokens(e.content) : e.type === "text_delta" ? estimateTokens(e.text) : 0), 0),
|
|
101812
102541
|
[events]
|
|
@@ -101845,7 +102574,7 @@ ${tools.map((t) => ` ${t.name}`).join("\n")}`);
|
|
|
101845
102574
|
}),
|
|
101846
102575
|
[sessions, applySession]
|
|
101847
102576
|
);
|
|
101848
|
-
return { turns, events, isRunning, livePrompt, turnTokens, notices, pending, mode, todos, stats, submit, retry, mentions, replayed, resume };
|
|
102577
|
+
return { turns, events, isRunning, livePrompt, turnTokens, notices, pending, mode, todos, stats, submit, retry, mentions, replayed, resume, commandEntries };
|
|
101849
102578
|
}
|
|
101850
102579
|
var import_react30, gitTopLevel, apiUrl;
|
|
101851
102580
|
var init_useReplController = __esm({
|
|
@@ -101867,8 +102596,11 @@ var init_useReplController = __esm({
|
|
|
101867
102596
|
init_node_mentions();
|
|
101868
102597
|
init_api_key();
|
|
101869
102598
|
init_profile_runtime();
|
|
102599
|
+
init_skills_command();
|
|
102600
|
+
init_src();
|
|
101870
102601
|
init_import_gate();
|
|
101871
102602
|
init_permission_rules_loader();
|
|
102603
|
+
init_command_loader();
|
|
101872
102604
|
init_slash_commands();
|
|
101873
102605
|
init_useEventAccumulator();
|
|
101874
102606
|
gitTopLevel = () => new Promise((resolve2) => {
|
|
@@ -102169,7 +102901,7 @@ function App2({ repoName: repoName2, onboardingMode: onboardingMode2, launchCoun
|
|
|
102169
102901
|
const { isRawModeSupported } = use_stdin_default();
|
|
102170
102902
|
const [resumeFound, setResumeFound] = (0, import_react34.useState)(null);
|
|
102171
102903
|
const openResume = (0, import_react34.useCallback)((found) => setResumeFound(found), []);
|
|
102172
|
-
const { turns, events, isRunning, livePrompt, notices, pending, mode, todos, stats, submit, retry, mentions, replayed, resume } = useReplController({ repoName: repoName2, yolo: yolo2, sessions: sessionStore, resumeLatest: resumeLatest2, onViewTurns: openViewer, onViewResume: isRawModeSupported && interactivePicker ? openResume : void 0, isEscOwned });
|
|
102904
|
+
const { turns, events, isRunning, livePrompt, notices, pending, mode, todos, stats, submit, retry, mentions, replayed, resume, commandEntries } = useReplController({ repoName: repoName2, yolo: yolo2, sessions: sessionStore, resumeLatest: resumeLatest2, onViewTurns: openViewer, onViewResume: isRawModeSupported && interactivePicker ? openResume : void 0, isEscOwned });
|
|
102173
102905
|
const { columns, rows } = useTerminalSize();
|
|
102174
102906
|
const viewing = viewingRaw && !pending;
|
|
102175
102907
|
const picking = resumeFound !== null && !pending;
|
|
@@ -102264,7 +102996,7 @@ function App2({ repoName: repoName2, onboardingMode: onboardingMode2, launchCoun
|
|
|
102264
102996
|
] }),
|
|
102265
102997
|
isRunning && !pending && stats.elapsedMs !== null && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { ...paint("secondary"), children: formatThinking(stats.elapsedMs, stats.approxTokens, unicode) }),
|
|
102266
102998
|
focusing && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { ...paint("secondary"), children: unicode ? "step focus: \u2191\u2193 or tab move \xB7 enter open/close \xB7 esc back to the prompt" : "step focus: up/down or tab move - enter open/close - esc back to the prompt" }),
|
|
102267
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PromptEditor, { onEmptyEnter: isRunning ? void 0 : retry, promptGlyph: promptGlyph(mode, unicode), unicode, fullWidth: true, onSubmit: submit, disabled: isRunning, history: promptHistory, listFiles: mentions.list, onTab: enterFocus, locked: focusing || viewing || picking || Boolean(pending) }),
|
|
102999
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PromptEditor, { extraCommands: commandEntries, onEmptyEnter: isRunning ? void 0 : retry, promptGlyph: promptGlyph(mode, unicode), unicode, fullWidth: true, onSubmit: submit, disabled: isRunning, history: promptHistory, listFiles: mentions.list, onTab: enterFocus, locked: focusing || viewing || picking || Boolean(pending) }),
|
|
102268
103000
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { ...footer.level === "critical" ? paint("delete") : footer.level === "warn" ? paint("needsYou") : paint("secondary"), children: footer.text }),
|
|
102269
103001
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { ...paint("secondary"), children: formatHints({ running: isRunning, asking: Boolean(pending), columns }, activeGlyphs().sep) })
|
|
102270
103002
|
] })
|
|
@@ -102310,7 +103042,7 @@ function App2({ repoName: repoName2, onboardingMode: onboardingMode2, launchCoun
|
|
|
102310
103042
|
pending && !waitingStep && promptNode,
|
|
102311
103043
|
isRunning && !pending && stats.elapsedMs !== null && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { ...paint("secondary"), children: formatThinking(stats.elapsedMs, stats.approxTokens, activeGlyphs().rail === "\u2502") }),
|
|
102312
103044
|
focusing && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { ...paint("secondary"), children: activeGlyphs().rail === "\u2502" ? "step focus: \u2191\u2193 or tab move \xB7 enter open/close \xB7 esc back to the prompt" : "step focus: up/down or tab move - enter open/close - esc back to the prompt" }),
|
|
102313
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PromptEditor, { onEmptyEnter: isRunning ? void 0 : retry, promptGlyph: promptGlyph(mode, activeGlyphs().rail === "\u2502"), unicode: activeGlyphs().rail === "\u2502", onSubmit: submit, disabled: isRunning, history: promptHistory, listFiles: mentions.list, onTab: enterFocus, locked: focusing || viewing || picking || Boolean(pending) }),
|
|
103045
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PromptEditor, { extraCommands: commandEntries, onEmptyEnter: isRunning ? void 0 : retry, promptGlyph: promptGlyph(mode, activeGlyphs().rail === "\u2502"), unicode: activeGlyphs().rail === "\u2502", onSubmit: submit, disabled: isRunning, history: promptHistory, listFiles: mentions.list, onTab: enterFocus, locked: focusing || viewing || picking || Boolean(pending) }),
|
|
102314
103046
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { ...footer.level === "critical" ? paint("delete") : footer.level === "warn" ? paint("needsYou") : paint("secondary"), children: footer.text }),
|
|
102315
103047
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { ...paint("secondary"), children: formatHints({ running: isRunning, asking: Boolean(pending), columns }, activeGlyphs().sep) })
|
|
102316
103048
|
] })
|
|
@@ -102453,7 +103185,7 @@ function clearOnResize(stdout) {
|
|
|
102453
103185
|
var import_jsx_runtime14 = __toESM(require_jsx_runtime(), 1);
|
|
102454
103186
|
if (process.argv[2] === "import-claude") {
|
|
102455
103187
|
const { runImportClaude: runImportClaude2 } = await Promise.resolve().then(() => (init_import_claude(), import_claude_exports));
|
|
102456
|
-
const { readFile: readFile7, readdir:
|
|
103188
|
+
const { readFile: readFile7, readdir: readdir4 } = await import("node:fs/promises");
|
|
102457
103189
|
const { execFile: execFile4 } = await import("node:child_process");
|
|
102458
103190
|
const gitRoot = () => new Promise((resolve2) => {
|
|
102459
103191
|
execFile4("git", ["rev-parse", "--show-toplevel"], { cwd: process.cwd() }, (err, stdout) => resolve2(err ? null : stdout.trim() || null));
|
|
@@ -102461,7 +103193,7 @@ if (process.argv[2] === "import-claude") {
|
|
|
102461
103193
|
const code = await runImportClaude2({
|
|
102462
103194
|
argv: process.argv.slice(2),
|
|
102463
103195
|
env: process.env,
|
|
102464
|
-
fs: { readFile: (p) => readFile7(p, "utf-8"), readdir: (p) =>
|
|
103196
|
+
fs: { readFile: (p) => readFile7(p, "utf-8"), readdir: (p) => readdir4(p) },
|
|
102465
103197
|
readFile: (p) => readFile7(p, "utf-8"),
|
|
102466
103198
|
homedir: os5.homedir,
|
|
102467
103199
|
cwd: process.cwd(),
|