spacecode 2.5.4 → 2.5.6

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