spacecode 2.5.3 → 2.5.4
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 +320 -37
- package/package.json +1 -1
package/dist/repl.mjs
CHANGED
|
@@ -4925,6 +4925,30 @@ 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
|
+
|
|
4928
4952
|
// ../agent/src/import/claude-sources.ts
|
|
4929
4953
|
function isEnoent(err) {
|
|
4930
4954
|
return typeof err === "object" && err !== null && err.code === "ENOENT";
|
|
@@ -4962,6 +4986,7 @@ async function readClaudeAssets({ fs: fs6, home, projectDirs }) {
|
|
|
4962
4986
|
report.assets.push({ kind: "instructions", name: "global", description: "Global CLAUDE.md", content: globalText, source: globalPath });
|
|
4963
4987
|
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
4988
|
}
|
|
4989
|
+
await readCommands(fs6, home, report);
|
|
4965
4990
|
for (const dir of projectDirs) {
|
|
4966
4991
|
const memDir = `${home}/.claude/projects/${claudeProjectSlug(dir)}/memory`;
|
|
4967
4992
|
let entries;
|
|
@@ -5003,11 +5028,49 @@ async function readClaudeAssets({ fs: fs6, home, projectDirs }) {
|
|
|
5003
5028
|
report.notes.push("No Claude Code memory directory found for this project");
|
|
5004
5029
|
return report;
|
|
5005
5030
|
}
|
|
5006
|
-
|
|
5031
|
+
async function readCommands(fs6, home, report) {
|
|
5032
|
+
const dir = `${home}/.claude/commands`;
|
|
5033
|
+
let entries;
|
|
5034
|
+
try {
|
|
5035
|
+
entries = await fs6.readdir(dir);
|
|
5036
|
+
} catch (err) {
|
|
5037
|
+
if (!isEnoent(err)) report.errors.push({ path: dir, message: err instanceof Error ? err.message : String(err) });
|
|
5038
|
+
return;
|
|
5039
|
+
}
|
|
5040
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5041
|
+
for (const entry of [...entries].sort()) {
|
|
5042
|
+
const path12 = `${dir}/${entry}`;
|
|
5043
|
+
if (!entry.endsWith(".md")) {
|
|
5044
|
+
if (!entry.startsWith(".")) report.notes.push(`Skipped ${path12}: only top-level .md command files are imported (subdirectories are not supported)`);
|
|
5045
|
+
continue;
|
|
5046
|
+
}
|
|
5047
|
+
const name = entry.slice(0, -3);
|
|
5048
|
+
if (!COMMAND_NAME_RE.test(name)) {
|
|
5049
|
+
report.errors.push({ path: path12, message: `command name "${name}" must start with a letter and use only letters, digits, - and _` });
|
|
5050
|
+
continue;
|
|
5051
|
+
}
|
|
5052
|
+
if (seen.has(name.toLowerCase())) {
|
|
5053
|
+
report.errors.push({ path: path12, message: `command name collision on "${name}" (names are case-insensitive)` });
|
|
5054
|
+
continue;
|
|
5055
|
+
}
|
|
5056
|
+
const text = await readOptional(fs6, path12, report);
|
|
5057
|
+
if (text === null) continue;
|
|
5058
|
+
if (Buffer.byteLength(text, "utf8") > MAX_ASSET_BYTES) {
|
|
5059
|
+
report.errors.push({ path: path12, message: `skipped: ${Buffer.byteLength(text, "utf8")} bytes is over the ${MAX_ASSET_BYTES} byte asset limit` });
|
|
5060
|
+
continue;
|
|
5061
|
+
}
|
|
5062
|
+
seen.add(name.toLowerCase());
|
|
5063
|
+
report.assets.push({ kind: "command", name, description: parseCommandFile(text).description, content: text, source: path12 });
|
|
5064
|
+
}
|
|
5065
|
+
}
|
|
5066
|
+
var MAX_ASSET_BYTES, NAME_RE, COMMAND_NAME_RE;
|
|
5007
5067
|
var init_claude_sources = __esm({
|
|
5008
5068
|
"../agent/src/import/claude-sources.ts"() {
|
|
5009
5069
|
"use strict";
|
|
5070
|
+
init_command_file();
|
|
5071
|
+
MAX_ASSET_BYTES = 256 * 1024;
|
|
5010
5072
|
NAME_RE = /^[A-Za-z0-9._-]{1,100}$/;
|
|
5073
|
+
COMMAND_NAME_RE = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
5011
5074
|
}
|
|
5012
5075
|
});
|
|
5013
5076
|
|
|
@@ -5719,6 +5782,74 @@ var init_permission_rules = __esm({
|
|
|
5719
5782
|
}
|
|
5720
5783
|
});
|
|
5721
5784
|
|
|
5785
|
+
// ../agent/src/import/command-args.ts
|
|
5786
|
+
function parseWords(input) {
|
|
5787
|
+
const words = [];
|
|
5788
|
+
let current = "";
|
|
5789
|
+
let quote = null;
|
|
5790
|
+
let has = false;
|
|
5791
|
+
for (const ch of input) {
|
|
5792
|
+
if (quote) {
|
|
5793
|
+
if (ch === quote) quote = null;
|
|
5794
|
+
else current += ch;
|
|
5795
|
+
} else if (ch === '"' || ch === "'") {
|
|
5796
|
+
quote = ch;
|
|
5797
|
+
has = true;
|
|
5798
|
+
} else if (/\s/.test(ch)) {
|
|
5799
|
+
if (has) words.push(current);
|
|
5800
|
+
current = "";
|
|
5801
|
+
has = false;
|
|
5802
|
+
} else {
|
|
5803
|
+
current += ch;
|
|
5804
|
+
has = true;
|
|
5805
|
+
}
|
|
5806
|
+
}
|
|
5807
|
+
if (has) words.push(current);
|
|
5808
|
+
return words;
|
|
5809
|
+
}
|
|
5810
|
+
function expandCommand(body, args) {
|
|
5811
|
+
const trimmed = args.trim();
|
|
5812
|
+
const words = parseWords(trimmed);
|
|
5813
|
+
return body.replace(/\$ARGUMENTS|\$[1-9]/g, (m2) => m2 === "$ARGUMENTS" ? trimmed : words[Number(m2[1]) - 1] ?? "");
|
|
5814
|
+
}
|
|
5815
|
+
var init_command_args = __esm({
|
|
5816
|
+
"../agent/src/import/command-args.ts"() {
|
|
5817
|
+
"use strict";
|
|
5818
|
+
}
|
|
5819
|
+
});
|
|
5820
|
+
|
|
5821
|
+
// ../agent/src/import/command-registry.ts
|
|
5822
|
+
function buildCommandRegistry(input) {
|
|
5823
|
+
const builtins = new Set(input.builtins.map((b2) => b2.toLowerCase()));
|
|
5824
|
+
const commands = [];
|
|
5825
|
+
const skipped = [];
|
|
5826
|
+
const shadowed = [];
|
|
5827
|
+
const accepted = /* @__PURE__ */ new Map();
|
|
5828
|
+
const lists = [["repo", input.repo], ["user", input.user]];
|
|
5829
|
+
for (const [scope, defs] of lists) {
|
|
5830
|
+
for (const def of defs) {
|
|
5831
|
+
const name = def.name.toLowerCase();
|
|
5832
|
+
if (!VALID.test(def.name)) skipped.push({ name: def.name, scope, reason: "invalid name" });
|
|
5833
|
+
else if (builtins.has(name)) skipped.push({ name, scope, reason: `collides with built-in /${name}` });
|
|
5834
|
+
else if (accepted.get(name) === scope) skipped.push({ name, scope, reason: "duplicate name" });
|
|
5835
|
+
else if (accepted.get(name) === "repo") shadowed.push(name);
|
|
5836
|
+
else {
|
|
5837
|
+
accepted.set(name, scope);
|
|
5838
|
+
commands.push({ ...def, name, scope });
|
|
5839
|
+
}
|
|
5840
|
+
}
|
|
5841
|
+
}
|
|
5842
|
+
commands.sort((a, b2) => a.name < b2.name ? -1 : a.name > b2.name ? 1 : 0);
|
|
5843
|
+
return { commands, skipped, shadowed };
|
|
5844
|
+
}
|
|
5845
|
+
var VALID;
|
|
5846
|
+
var init_command_registry = __esm({
|
|
5847
|
+
"../agent/src/import/command-registry.ts"() {
|
|
5848
|
+
"use strict";
|
|
5849
|
+
VALID = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
5850
|
+
}
|
|
5851
|
+
});
|
|
5852
|
+
|
|
5722
5853
|
// ../agent/src/index.ts
|
|
5723
5854
|
var init_src = __esm({
|
|
5724
5855
|
"../agent/src/index.ts"() {
|
|
@@ -5759,6 +5890,10 @@ var init_src = __esm({
|
|
|
5759
5890
|
init_trust();
|
|
5760
5891
|
init_imports_config();
|
|
5761
5892
|
init_permission_rules();
|
|
5893
|
+
init_command_args();
|
|
5894
|
+
init_command_file();
|
|
5895
|
+
init_command_registry();
|
|
5896
|
+
init_claude_sources();
|
|
5762
5897
|
}
|
|
5763
5898
|
});
|
|
5764
5899
|
|
|
@@ -6027,6 +6162,13 @@ ${USAGE}`);
|
|
|
6027
6162
|
return 1;
|
|
6028
6163
|
}
|
|
6029
6164
|
const client = createProfileClient({ baseUrl: backendUrl(deps.env), apiKey, fetch: deps.fetch });
|
|
6165
|
+
const TENANT_ASSET_CAP = 200;
|
|
6166
|
+
let assetCount = 0;
|
|
6167
|
+
try {
|
|
6168
|
+
assetCount = (await client.index())?.assets.length ?? 0;
|
|
6169
|
+
} catch (err) {
|
|
6170
|
+
deps.log(` note: could not read the profile asset count (${err instanceof Error ? err.message : String(err)}); the server still enforces the cap`);
|
|
6171
|
+
}
|
|
6030
6172
|
let failed = report.errors.length > 0;
|
|
6031
6173
|
let imported = 0;
|
|
6032
6174
|
let skipped = 0;
|
|
@@ -6049,6 +6191,11 @@ ${USAGE}`);
|
|
|
6049
6191
|
deps.log(` unchanged ${a.kind}/${a.name}`);
|
|
6050
6192
|
continue;
|
|
6051
6193
|
}
|
|
6194
|
+
if (!existing && assetCount >= TENANT_ASSET_CAP) {
|
|
6195
|
+
failed = true;
|
|
6196
|
+
deps.log(` SKIPPED ${a.kind}/${a.name}: the profile already holds ${assetCount} of ${TENANT_ASSET_CAP} allowed assets`);
|
|
6197
|
+
continue;
|
|
6198
|
+
}
|
|
6052
6199
|
if (existing && !overwrite) {
|
|
6053
6200
|
failed = true;
|
|
6054
6201
|
deps.log(` DIFFERS ${a.kind}/${a.name}: the profile already has a different version; re-run with --overwrite to replace it`);
|
|
@@ -6056,6 +6203,7 @@ ${USAGE}`);
|
|
|
6056
6203
|
}
|
|
6057
6204
|
await client.put(a.kind, a.name, { content: a.content, description: a.description, baseVersion: existing?.version ?? 0 });
|
|
6058
6205
|
imported++;
|
|
6206
|
+
if (!existing) assetCount++;
|
|
6059
6207
|
deps.log(` imported ${a.kind}/${a.name}`);
|
|
6060
6208
|
} catch (err) {
|
|
6061
6209
|
failed = true;
|
|
@@ -39350,12 +39498,12 @@ var require_utils = __commonJS({
|
|
|
39350
39498
|
return str;
|
|
39351
39499
|
}
|
|
39352
39500
|
var codeCache = {};
|
|
39353
|
-
function addToCodeCache(name, on,
|
|
39501
|
+
function addToCodeCache(name, on, off2) {
|
|
39354
39502
|
on = "\x1B[" + on + "m";
|
|
39355
|
-
|
|
39503
|
+
off2 = "\x1B[" + off2 + "m";
|
|
39356
39504
|
codeCache[on] = { set: name, to: true };
|
|
39357
|
-
codeCache[
|
|
39358
|
-
codeCache[name] = { on, off };
|
|
39505
|
+
codeCache[off2] = { set: name, to: false };
|
|
39506
|
+
codeCache[name] = { on, off: off2 };
|
|
39359
39507
|
}
|
|
39360
39508
|
addToCodeCache("bold", 1, 22);
|
|
39361
39509
|
addToCodeCache("italics", 3, 23);
|
|
@@ -41237,7 +41385,7 @@ var require_core = __commonJS({
|
|
|
41237
41385
|
return match && match.index === 0;
|
|
41238
41386
|
}
|
|
41239
41387
|
var BACKREF_RE = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;
|
|
41240
|
-
function
|
|
41388
|
+
function join8(regexps, separator = "|") {
|
|
41241
41389
|
let numCaptures = 0;
|
|
41242
41390
|
return regexps.map((regex2) => {
|
|
41243
41391
|
numCaptures += 1;
|
|
@@ -41541,7 +41689,7 @@ var require_core = __commonJS({
|
|
|
41541
41689
|
this.exec = () => null;
|
|
41542
41690
|
}
|
|
41543
41691
|
const terminators = this.regexes.map((el) => el[1]);
|
|
41544
|
-
this.matcherRe = langRe(
|
|
41692
|
+
this.matcherRe = langRe(join8(terminators), true);
|
|
41545
41693
|
this.lastIndex = 0;
|
|
41546
41694
|
}
|
|
41547
41695
|
/** @param {string} s */
|
|
@@ -100343,6 +100491,7 @@ var init_slash_commands = __esm({
|
|
|
100343
100491
|
{ name: "compact", description: "Summarize the conversation to free up context" },
|
|
100344
100492
|
{ name: "model", description: "List gateway models; /model <n|id> switches for this session" },
|
|
100345
100493
|
{ name: "turns", description: "Browse earlier turns and open their steps (shortcut: ctrl+o)" },
|
|
100494
|
+
{ name: "commands", description: "List the slash commands imported from Claude Code" },
|
|
100346
100495
|
{ name: "tools", description: "List the tools available to the agent" },
|
|
100347
100496
|
{ name: "exit", description: "Quit the REPL (alias: /quit)" },
|
|
100348
100497
|
{ name: "quit", description: "Quit the REPL" }
|
|
@@ -100351,15 +100500,15 @@ var init_slash_commands = __esm({
|
|
|
100351
100500
|
});
|
|
100352
100501
|
|
|
100353
100502
|
// src/components/PromptEditor.tsx
|
|
100354
|
-
function validateSubmission(text) {
|
|
100503
|
+
function validateSubmission(text, extra = []) {
|
|
100355
100504
|
const slash = parseSlashCommand(text);
|
|
100356
|
-
if (slash && !isKnownSlashCommand(slash.name)) return `Unknown command: /${slash.name}. Type /help for a list.`;
|
|
100505
|
+
if (slash && !isKnownSlashCommand(slash.name) && !extra.some((c) => c.name === slash.name)) return `Unknown command: /${slash.name}. Type /help for a list.`;
|
|
100357
100506
|
return null;
|
|
100358
100507
|
}
|
|
100359
100508
|
function withCursor(row, col) {
|
|
100360
100509
|
return `${row.slice(0, col)}\x1B[7m${row[col] ?? " "}\x1B[27m${row.slice(col + 1)}`;
|
|
100361
100510
|
}
|
|
100362
|
-
function PromptEditor({ onSubmit, disabled, history, listFiles: listFiles2, onTab, locked = false, promptGlyph: promptGlyph2 = ">", onEmptyEnter, unicode = true, fullWidth = false }) {
|
|
100511
|
+
function PromptEditor({ onSubmit, disabled, history, listFiles: listFiles2, onTab, locked = false, promptGlyph: promptGlyph2 = ">", onEmptyEnter, unicode = true, fullWidth = false, extraCommands = [] }) {
|
|
100363
100512
|
const { exit } = use_app_default();
|
|
100364
100513
|
const { columns } = useTerminalSize();
|
|
100365
100514
|
const { isRawModeSupported } = use_stdin_default();
|
|
@@ -100462,7 +100611,7 @@ function PromptEditor({ onSubmit, disabled, history, listFiles: listFiles2, onTa
|
|
|
100462
100611
|
else update(applyEdit(cur, { type: "delete" }));
|
|
100463
100612
|
return;
|
|
100464
100613
|
case "tab": {
|
|
100465
|
-
const completed = completeSlash(cur.text, slashMatches(cur.text, SLASH_COMMANDS));
|
|
100614
|
+
const completed = completeSlash(cur.text, slashMatches(cur.text, [...SLASH_COMMANDS, ...extraCommands]));
|
|
100466
100615
|
if (completed) update(createBuffer(completed));
|
|
100467
100616
|
else onTab?.();
|
|
100468
100617
|
return;
|
|
@@ -100495,7 +100644,7 @@ function PromptEditor({ onSubmit, disabled, history, listFiles: listFiles2, onTa
|
|
|
100495
100644
|
return;
|
|
100496
100645
|
}
|
|
100497
100646
|
const text = inShell ? `!${typed}` : typed;
|
|
100498
|
-
const problem = inShell ? null : validateSubmission(text);
|
|
100647
|
+
const problem = inShell ? null : validateSubmission(text, extraCommands);
|
|
100499
100648
|
if (problem) {
|
|
100500
100649
|
setHint(problem);
|
|
100501
100650
|
return;
|
|
@@ -100515,7 +100664,7 @@ function PromptEditor({ onSubmit, disabled, history, listFiles: listFiles2, onTa
|
|
|
100515
100664
|
}
|
|
100516
100665
|
const rows = lines(buf);
|
|
100517
100666
|
const { line, col } = lineCol(buf);
|
|
100518
|
-
const matches = shell ? [] : slashMatches(buf.text, SLASH_COMMANDS);
|
|
100667
|
+
const matches = shell ? [] : slashMatches(buf.text, [...SLASH_COMMANDS, ...extraCommands]);
|
|
100519
100668
|
const picks = mention && files ? rankPaths(files, mention.query, PICKER_ROWS) : [];
|
|
100520
100669
|
const pickAt = Math.min(sel, Math.max(picks.length - 1, 0));
|
|
100521
100670
|
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { flexDirection: "column", children: [
|
|
@@ -101189,7 +101338,7 @@ var init_node_mentions = __esm({
|
|
|
101189
101338
|
|
|
101190
101339
|
// src/import-gate.ts
|
|
101191
101340
|
import { createHash, randomUUID } from "node:crypto";
|
|
101192
|
-
import { chmod as chmod3, mkdir as mkdir3, readFile as readFile4, realpath as realpath2, rename as rename3, writeFile as writeFile3 } from "node:fs/promises";
|
|
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";
|
|
101193
101342
|
import { dirname as dirname3, join as join4 } from "node:path";
|
|
101194
101343
|
async function readOrNull(path12) {
|
|
101195
101344
|
try {
|
|
@@ -101206,6 +101355,24 @@ async function writeTextAtomic(path12, text) {
|
|
|
101206
101355
|
await chmod3(tmp, 384);
|
|
101207
101356
|
await rename3(tmp, path12);
|
|
101208
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
|
+
}
|
|
101209
101376
|
function nodeTrustFs() {
|
|
101210
101377
|
return { readText: readOrNull, writeTextAtomic, realPath: (p) => realpath2(p) };
|
|
101211
101378
|
}
|
|
@@ -101226,11 +101393,7 @@ async function importGateStatus(deps) {
|
|
|
101226
101393
|
lines2.push(`imports: not read (${err instanceof Error ? err.message : String(err)}); every category off`);
|
|
101227
101394
|
}
|
|
101228
101395
|
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
|
-
}
|
|
101396
|
+
const files = await readRepoSources(deps.cwd);
|
|
101234
101397
|
const found = Object.keys(files);
|
|
101235
101398
|
if (found.length === 0) return lines2;
|
|
101236
101399
|
const status = await createNodeTrustStore(deps.configDir).repoStatus(deps.cwd, files);
|
|
@@ -101277,11 +101440,7 @@ async function loadPermissionRules(deps) {
|
|
|
101277
101440
|
const lines2 = [];
|
|
101278
101441
|
const files = [{ scope: "user", path: join5(deps.homeDir, ".claude", "settings.json") }];
|
|
101279
101442
|
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
|
-
}
|
|
101443
|
+
const sources2 = await readRepoSources(deps.cwd);
|
|
101285
101444
|
if (sources2[".claude/settings.json"] !== void 0 || sources2[".claude/settings.local.json"] !== void 0) {
|
|
101286
101445
|
const status = await createNodeTrustStore(deps.configDir).repoStatus(deps.cwd, sources2);
|
|
101287
101446
|
if (status === "trusted" || status === "session") {
|
|
@@ -101326,10 +101485,98 @@ var init_permission_rules_loader = __esm({
|
|
|
101326
101485
|
}
|
|
101327
101486
|
});
|
|
101328
101487
|
|
|
101488
|
+
// src/command-loader.ts
|
|
101489
|
+
import { join as join6 } from "node:path";
|
|
101490
|
+
async function userCommands(deps, lines2) {
|
|
101491
|
+
try {
|
|
101492
|
+
const apiKey = await resolveApiKey({ env: deps.env, readFile: deps.readFile, homedir: deps.homedir });
|
|
101493
|
+
if (!apiKey) {
|
|
101494
|
+
lines2.push("commands: user commands NOT loaded (not signed in)");
|
|
101495
|
+
return [];
|
|
101496
|
+
}
|
|
101497
|
+
const client = createProfileClient({ baseUrl: backendUrl(deps.env), apiKey, fetch: deps.fetch, timeoutMs: 3e3 });
|
|
101498
|
+
const index = await client.index();
|
|
101499
|
+
const entries = (index?.assets ?? []).filter((a) => a.kind === "command");
|
|
101500
|
+
const defs = [];
|
|
101501
|
+
await Promise.all(
|
|
101502
|
+
entries.map(async (e) => {
|
|
101503
|
+
try {
|
|
101504
|
+
const asset = await client.get("command", e.name);
|
|
101505
|
+
if (asset) defs.push({ name: e.name, ...parseCommandFile(asset.content) });
|
|
101506
|
+
} catch (err) {
|
|
101507
|
+
lines2.push(`commands: ${e.name} NOT loaded (${err instanceof Error ? err.message : String(err)})`);
|
|
101508
|
+
}
|
|
101509
|
+
})
|
|
101510
|
+
);
|
|
101511
|
+
return defs;
|
|
101512
|
+
} catch (err) {
|
|
101513
|
+
lines2.push(`commands: user commands NOT loaded (${err instanceof Error ? err.message : String(err)})`);
|
|
101514
|
+
return [];
|
|
101515
|
+
}
|
|
101516
|
+
}
|
|
101517
|
+
async function repoCommands(deps, lines2) {
|
|
101518
|
+
try {
|
|
101519
|
+
const sources = await readRepoSources(deps.cwd);
|
|
101520
|
+
const names = Object.keys(sources).filter((k2) => k2.startsWith(".claude/commands/"));
|
|
101521
|
+
if (names.length === 0) return [];
|
|
101522
|
+
const status = await createNodeTrustStore(deps.configDir).repoStatus(deps.cwd, sources);
|
|
101523
|
+
if (status !== "trusted" && status !== "session") {
|
|
101524
|
+
lines2.push(`commands: ${names.length} repo command(s) NOT loaded (${status === "never" ? "marked never trust" : "untrusted repo"})`);
|
|
101525
|
+
return [];
|
|
101526
|
+
}
|
|
101527
|
+
const defs = [];
|
|
101528
|
+
for (const key of names) {
|
|
101529
|
+
const text = sources[key];
|
|
101530
|
+
const name = key.slice(".claude/commands/".length, -3);
|
|
101531
|
+
if (Buffer.byteLength(text, "utf8") > MAX_ASSET_BYTES) {
|
|
101532
|
+
lines2.push(`commands: repo command ${name} skipped (over ${MAX_ASSET_BYTES} bytes)`);
|
|
101533
|
+
continue;
|
|
101534
|
+
}
|
|
101535
|
+
defs.push({ name, ...parseCommandFile(text) });
|
|
101536
|
+
}
|
|
101537
|
+
return defs;
|
|
101538
|
+
} catch (err) {
|
|
101539
|
+
lines2.push(`commands: repo commands NOT loaded (${err instanceof Error ? err.message : String(err)})`);
|
|
101540
|
+
return [];
|
|
101541
|
+
}
|
|
101542
|
+
}
|
|
101543
|
+
async function loadCommands(deps) {
|
|
101544
|
+
const configPath = join6(deps.configDir, "imports.json");
|
|
101545
|
+
try {
|
|
101546
|
+
const loaded = parseImportsConfig(await deps.readFile(configPath).catch((err) => {
|
|
101547
|
+
if (err.code === "ENOENT") return null;
|
|
101548
|
+
throw err;
|
|
101549
|
+
}), configPath);
|
|
101550
|
+
if (loaded.error) return off("commands: off (imports.json unreadable)");
|
|
101551
|
+
if (!loaded.config.categories.commands) return off("commands: off (imports.json has commands off)");
|
|
101552
|
+
} catch (err) {
|
|
101553
|
+
return off(`commands: off (imports.json not read: ${err instanceof Error ? err.message : String(err)})`);
|
|
101554
|
+
}
|
|
101555
|
+
const lines2 = [];
|
|
101556
|
+
const [user, repo] = await Promise.all([userCommands(deps, lines2), repoCommands(deps, lines2)]);
|
|
101557
|
+
const reg = buildCommandRegistry({ builtins: deps.builtins, user, repo });
|
|
101558
|
+
const nUser = reg.commands.filter((c) => c.scope === "user").length;
|
|
101559
|
+
const nRepo = reg.commands.length - nUser;
|
|
101560
|
+
lines2.unshift(`commands: ${nUser} user, ${nRepo} repo${reg.shadowed.length ? `; repo shadows user: ${reg.shadowed.map((n) => "/" + n).join(", ")}` : ""}`);
|
|
101561
|
+
for (const s of reg.skipped) lines2.push(`commands: skipped [${s.scope}] /${s.name} (${s.reason})`);
|
|
101562
|
+
return { commands: reg.commands, statusLines: lines2 };
|
|
101563
|
+
}
|
|
101564
|
+
var off;
|
|
101565
|
+
var init_command_loader = __esm({
|
|
101566
|
+
"src/command-loader.ts"() {
|
|
101567
|
+
"use strict";
|
|
101568
|
+
init_src();
|
|
101569
|
+
init_api_key();
|
|
101570
|
+
init_import_gate();
|
|
101571
|
+
init_profile_runtime();
|
|
101572
|
+
off = (line) => ({ commands: [], statusLines: [line] });
|
|
101573
|
+
}
|
|
101574
|
+
});
|
|
101575
|
+
|
|
101329
101576
|
// src/useReplController.ts
|
|
101330
101577
|
import { readFile as readFile6, writeFile as writeFile4, mkdir as mkdir4 } from "node:fs/promises";
|
|
101331
101578
|
import { homedir as homedir2 } from "node:os";
|
|
101332
|
-
import { join as
|
|
101579
|
+
import { join as join7 } from "node:path";
|
|
101333
101580
|
import { execFile as execFile3 } from "node:child_process";
|
|
101334
101581
|
function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mentionsOpt, sessions, resumeLatest: resumeLatest2, onViewTurns, onViewResume, isEscOwned }) {
|
|
101335
101582
|
const { exit } = use_app_default();
|
|
@@ -101372,6 +101619,27 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
|
|
|
101372
101619
|
const pendingRef = (0, import_react30.useRef)(null);
|
|
101373
101620
|
const toolsetRef = (0, import_react30.useRef)(null);
|
|
101374
101621
|
const rulesRef = (0, import_react30.useRef)(void 0);
|
|
101622
|
+
const [commandsLoaded, setCommandsLoaded] = (0, import_react30.useState)({ commands: [], statusLines: [] });
|
|
101623
|
+
(0, import_react30.useEffect)(() => {
|
|
101624
|
+
let live = true;
|
|
101625
|
+
loadCommands({
|
|
101626
|
+
configDir: process.env.SPACECODE_CONFIG_DIR?.trim() || join7(homedir2(), ".spacecode"),
|
|
101627
|
+
cwd: process.cwd(),
|
|
101628
|
+
env: process.env,
|
|
101629
|
+
readFile: (p) => readFile6(p, "utf-8"),
|
|
101630
|
+
homedir: homedir2,
|
|
101631
|
+
fetch,
|
|
101632
|
+
builtins: SLASH_COMMANDS.map((c) => c.name)
|
|
101633
|
+
}).then((r) => {
|
|
101634
|
+
if (live) setCommandsLoaded(r);
|
|
101635
|
+
}, (err) => {
|
|
101636
|
+
if (live) setCommandsLoaded({ commands: [], statusLines: [`commands: NOT loaded (${err instanceof Error ? err.message : String(err)})`] });
|
|
101637
|
+
});
|
|
101638
|
+
return () => {
|
|
101639
|
+
live = false;
|
|
101640
|
+
};
|
|
101641
|
+
}, []);
|
|
101642
|
+
const commandEntries = (0, import_react30.useMemo)(() => commandsLoaded.commands.map((c) => ({ name: c.name, description: c.description })), [commandsLoaded]);
|
|
101375
101643
|
(0, import_react30.useEffect)(() => {
|
|
101376
101644
|
sessionTokensRef.current = sessionTokens;
|
|
101377
101645
|
}, [sessionTokens]);
|
|
@@ -101494,8 +101762,15 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
|
|
|
101494
101762
|
}
|
|
101495
101763
|
})();
|
|
101496
101764
|
}, [resumeLatest2, sessions, projectRoot, notice, applySession]);
|
|
101497
|
-
const submit = (0, import_react30.useCallback)(async (
|
|
101498
|
-
const
|
|
101765
|
+
const submit = (0, import_react30.useCallback)(async (rawPrompt) => {
|
|
101766
|
+
const typed = parseSlashCommand(rawPrompt);
|
|
101767
|
+
const custom = typed && !isKnownSlashCommand(typed.name) ? commandsLoaded.commands.find((c) => c.name === typed.name) : void 0;
|
|
101768
|
+
const prompt = custom ? expandCommand(custom.body, typed.args) : rawPrompt;
|
|
101769
|
+
if (custom && prompt.trim() === "") {
|
|
101770
|
+
notice(`/${custom.name} expands to an empty prompt.`);
|
|
101771
|
+
return;
|
|
101772
|
+
}
|
|
101773
|
+
const shellCommand = custom ? null : shellCommandOf(prompt);
|
|
101499
101774
|
if (shellCommand !== null) {
|
|
101500
101775
|
if (shellCommand === "") {
|
|
101501
101776
|
notice("Type a command after !");
|
|
@@ -101529,7 +101804,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
|
|
|
101529
101804
|
}
|
|
101530
101805
|
return;
|
|
101531
101806
|
}
|
|
101532
|
-
const slash = parseSlashCommand(prompt);
|
|
101807
|
+
const slash = custom ? null : parseSlashCommand(prompt);
|
|
101533
101808
|
if (slash && isKnownSlashCommand(slash.name)) {
|
|
101534
101809
|
switch (slash.name) {
|
|
101535
101810
|
case "exit":
|
|
@@ -101567,7 +101842,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
|
|
|
101567
101842
|
maxContextTokens: DEFAULT_MAX_CONTEXT_TOKENS,
|
|
101568
101843
|
tokens: sessionTokensRef.current,
|
|
101569
101844
|
costUsd: estimateCostUsd(sessionTokensRef.current.input, sessionTokensRef.current.output),
|
|
101570
|
-
profileLines: profileRef.current?.statusLines ?? ["profile: not loaded yet (loads on your first message)"]
|
|
101845
|
+
profileLines: [...profileRef.current?.statusLines ?? ["profile: not loaded yet (loads on your first message)"], ...commandsLoaded.statusLines]
|
|
101571
101846
|
}));
|
|
101572
101847
|
return;
|
|
101573
101848
|
case "resume": {
|
|
@@ -101672,6 +101947,13 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
|
|
|
101672
101947
|
if (onViewTurns) onViewTurns();
|
|
101673
101948
|
else notice("The turn viewer is not available here.");
|
|
101674
101949
|
return;
|
|
101950
|
+
case "commands": {
|
|
101951
|
+
const list2 = commandsLoaded.commands;
|
|
101952
|
+
notice(list2.length === 0 ? `No imported commands.
|
|
101953
|
+
${commandsLoaded.statusLines.map((l) => ` ${l}`).join("\n")}`.trim() : `Imported commands:
|
|
101954
|
+
${list2.map((c) => ` /${c.name} (${c.scope})${c.description ? ` ${c.description}` : ""}`).join("\n")}`);
|
|
101955
|
+
return;
|
|
101956
|
+
}
|
|
101675
101957
|
case "tools": {
|
|
101676
101958
|
const tools = toolsetRef.current?.tools ?? createLocalToolset({ cwd: process.cwd(), permissions, onTodos: setTodos }).tools;
|
|
101677
101959
|
notice(`Tools:
|
|
@@ -101734,7 +102016,7 @@ ${tools.map((t) => ` ${t.name}`).join("\n")}`);
|
|
|
101734
102016
|
cwd: process.cwd(),
|
|
101735
102017
|
gitRoot: gitTopLevel,
|
|
101736
102018
|
importGate: async () => {
|
|
101737
|
-
const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() ||
|
|
102019
|
+
const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() || join7(homedir2(), ".spacecode");
|
|
101738
102020
|
const gate = await importGateStatus({ configDir, cwd: process.cwd(), interactive: Boolean(process.stdin.isTTY) });
|
|
101739
102021
|
const loaded = await loadPermissionRules({ configDir, homeDir: homedir2(), cwd: process.cwd() });
|
|
101740
102022
|
rulesRef.current = loaded.rules;
|
|
@@ -101806,7 +102088,7 @@ ${tools.map((t) => ` ${t.name}`).join("\n")}`);
|
|
|
101806
102088
|
abortRef.current = null;
|
|
101807
102089
|
setIsRunning(false);
|
|
101808
102090
|
}
|
|
101809
|
-
}, [repoName2, history, exit, permissions, notice, refuseAllConfirms, commitTurn, mentions, createToolset, sessions, projectRoot, persist, applySession, model, onViewTurns, onViewResume, setTodos]);
|
|
102091
|
+
}, [commandsLoaded, repoName2, history, exit, permissions, notice, refuseAllConfirms, commitTurn, mentions, createToolset, sessions, projectRoot, persist, applySession, model, onViewTurns, onViewResume, setTodos]);
|
|
101810
102092
|
const approxTokens = (0, import_react30.useMemo)(
|
|
101811
102093
|
() => dropDiscardedEvents(events).reduce((n, e) => n + (e.type === "text" ? estimateTokens(e.content) : e.type === "text_delta" ? estimateTokens(e.text) : 0), 0),
|
|
101812
102094
|
[events]
|
|
@@ -101845,7 +102127,7 @@ ${tools.map((t) => ` ${t.name}`).join("\n")}`);
|
|
|
101845
102127
|
}),
|
|
101846
102128
|
[sessions, applySession]
|
|
101847
102129
|
);
|
|
101848
|
-
return { turns, events, isRunning, livePrompt, turnTokens, notices, pending, mode, todos, stats, submit, retry, mentions, replayed, resume };
|
|
102130
|
+
return { turns, events, isRunning, livePrompt, turnTokens, notices, pending, mode, todos, stats, submit, retry, mentions, replayed, resume, commandEntries };
|
|
101849
102131
|
}
|
|
101850
102132
|
var import_react30, gitTopLevel, apiUrl;
|
|
101851
102133
|
var init_useReplController = __esm({
|
|
@@ -101869,6 +102151,7 @@ var init_useReplController = __esm({
|
|
|
101869
102151
|
init_profile_runtime();
|
|
101870
102152
|
init_import_gate();
|
|
101871
102153
|
init_permission_rules_loader();
|
|
102154
|
+
init_command_loader();
|
|
101872
102155
|
init_slash_commands();
|
|
101873
102156
|
init_useEventAccumulator();
|
|
101874
102157
|
gitTopLevel = () => new Promise((resolve2) => {
|
|
@@ -102169,7 +102452,7 @@ function App2({ repoName: repoName2, onboardingMode: onboardingMode2, launchCoun
|
|
|
102169
102452
|
const { isRawModeSupported } = use_stdin_default();
|
|
102170
102453
|
const [resumeFound, setResumeFound] = (0, import_react34.useState)(null);
|
|
102171
102454
|
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 });
|
|
102455
|
+
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
102456
|
const { columns, rows } = useTerminalSize();
|
|
102174
102457
|
const viewing = viewingRaw && !pending;
|
|
102175
102458
|
const picking = resumeFound !== null && !pending;
|
|
@@ -102264,7 +102547,7 @@ function App2({ repoName: repoName2, onboardingMode: onboardingMode2, launchCoun
|
|
|
102264
102547
|
] }),
|
|
102265
102548
|
isRunning && !pending && stats.elapsedMs !== null && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { ...paint("secondary"), children: formatThinking(stats.elapsedMs, stats.approxTokens, unicode) }),
|
|
102266
102549
|
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) }),
|
|
102550
|
+
/* @__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
102551
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { ...footer.level === "critical" ? paint("delete") : footer.level === "warn" ? paint("needsYou") : paint("secondary"), children: footer.text }),
|
|
102269
102552
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { ...paint("secondary"), children: formatHints({ running: isRunning, asking: Boolean(pending), columns }, activeGlyphs().sep) })
|
|
102270
102553
|
] })
|
|
@@ -102310,7 +102593,7 @@ function App2({ repoName: repoName2, onboardingMode: onboardingMode2, launchCoun
|
|
|
102310
102593
|
pending && !waitingStep && promptNode,
|
|
102311
102594
|
isRunning && !pending && stats.elapsedMs !== null && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { ...paint("secondary"), children: formatThinking(stats.elapsedMs, stats.approxTokens, activeGlyphs().rail === "\u2502") }),
|
|
102312
102595
|
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) }),
|
|
102596
|
+
/* @__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
102597
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { ...footer.level === "critical" ? paint("delete") : footer.level === "warn" ? paint("needsYou") : paint("secondary"), children: footer.text }),
|
|
102315
102598
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { ...paint("secondary"), children: formatHints({ running: isRunning, asking: Boolean(pending), columns }, activeGlyphs().sep) })
|
|
102316
102599
|
] })
|
|
@@ -102453,7 +102736,7 @@ function clearOnResize(stdout) {
|
|
|
102453
102736
|
var import_jsx_runtime14 = __toESM(require_jsx_runtime(), 1);
|
|
102454
102737
|
if (process.argv[2] === "import-claude") {
|
|
102455
102738
|
const { runImportClaude: runImportClaude2 } = await Promise.resolve().then(() => (init_import_claude(), import_claude_exports));
|
|
102456
|
-
const { readFile: readFile7, readdir:
|
|
102739
|
+
const { readFile: readFile7, readdir: readdir4 } = await import("node:fs/promises");
|
|
102457
102740
|
const { execFile: execFile4 } = await import("node:child_process");
|
|
102458
102741
|
const gitRoot = () => new Promise((resolve2) => {
|
|
102459
102742
|
execFile4("git", ["rev-parse", "--show-toplevel"], { cwd: process.cwd() }, (err, stdout) => resolve2(err ? null : stdout.trim() || null));
|
|
@@ -102461,7 +102744,7 @@ if (process.argv[2] === "import-claude") {
|
|
|
102461
102744
|
const code = await runImportClaude2({
|
|
102462
102745
|
argv: process.argv.slice(2),
|
|
102463
102746
|
env: process.env,
|
|
102464
|
-
fs: { readFile: (p) => readFile7(p, "utf-8"), readdir: (p) =>
|
|
102747
|
+
fs: { readFile: (p) => readFile7(p, "utf-8"), readdir: (p) => readdir4(p) },
|
|
102465
102748
|
readFile: (p) => readFile7(p, "utf-8"),
|
|
102466
102749
|
homedir: os5.homedir,
|
|
102467
102750
|
cwd: process.cwd(),
|