kimiflare 0.25.0 → 0.26.1

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 CHANGED
@@ -83,6 +83,7 @@ async function loadConfig() {
83
83
  const envMemoryMaxAgeDays = readNumberEnv("KIMIFLARE_MEMORY_MAX_AGE_DAYS");
84
84
  const envMemoryMaxEntries = readNumberEnv("KIMIFLARE_MEMORY_MAX_ENTRIES");
85
85
  const envMemoryEmbeddingModel = process.env.KIMIFLARE_MEMORY_EMBEDDING_MODEL;
86
+ const envPlumbingModel = process.env.KIMIFLARE_PLUMBING_MODEL;
86
87
  const envCodeMode = readBooleanEnv("KIMIFLARE_CODE_MODE");
87
88
  if (envAccount && envToken) {
88
89
  return {
@@ -107,6 +108,7 @@ async function loadConfig() {
107
108
  memoryMaxAgeDays: envMemoryMaxAgeDays,
108
109
  memoryMaxEntries: envMemoryMaxEntries,
109
110
  memoryEmbeddingModel: envMemoryEmbeddingModel,
111
+ plumbingModel: envPlumbingModel,
110
112
  codeMode: envCodeMode
111
113
  };
112
114
  }
@@ -137,6 +139,7 @@ async function loadConfig() {
137
139
  memoryMaxAgeDays: envMemoryMaxAgeDays ?? parsed.memoryMaxAgeDays,
138
140
  memoryMaxEntries: envMemoryMaxEntries ?? parsed.memoryMaxEntries,
139
141
  memoryEmbeddingModel: envMemoryEmbeddingModel ?? parsed.memoryEmbeddingModel,
142
+ plumbingModel: envPlumbingModel ?? parsed.plumbingModel,
140
143
  codeMode: envCodeMode ?? parsed.codeMode
141
144
  };
142
145
  }
@@ -248,7 +251,7 @@ function stableStringify(value, replacer, space) {
248
251
  function stripOldImages(messages, keepLastTurns) {
249
252
  if (keepLastTurns < 0) return messages;
250
253
  let userCount = 0;
251
- let cutoffIndex = messages.length;
254
+ let cutoffIndex = 0;
252
255
  for (let i = messages.length - 1; i >= 0; i--) {
253
256
  if (messages[i].role === "user") {
254
257
  userCount++;
@@ -880,7 +883,7 @@ function schemaToTsType(prop, required = true) {
880
883
  types.push(`${itemType}[]`);
881
884
  } else if (prop.type === "object") {
882
885
  if (prop.properties && Object.keys(prop.properties).length > 0) {
883
- const entries = Object.entries(prop.properties).map(([key, val]) => {
886
+ const entries = Object.entries(prop.properties).sort(([a], [b]) => a.localeCompare(b)).map(([key, val]) => {
884
887
  const isReq = prop.required?.includes(key) ?? false;
885
888
  return ` ${key}${isReq ? "" : "?"}: ${schemaToTsType(val, isReq)};`;
886
889
  }).join("\n");
@@ -901,7 +904,7 @@ ${entries}
901
904
  return required ? result : `${result} | undefined`;
902
905
  }
903
906
  function generateInterface(name, properties, required = []) {
904
- const entries = Object.entries(properties).map(([key, val]) => {
907
+ const entries = Object.entries(properties).sort(([a], [b]) => a.localeCompare(b)).map(([key, val]) => {
905
908
  const isReq = required.includes(key);
906
909
  return ` ${key}${isReq ? "" : "?"}: ${schemaToTsType(val, isReq)};`;
907
910
  }).join("\n");
@@ -921,13 +924,14 @@ function generateTypeScriptApi(tools) {
921
924
  const inputInterfaces = [];
922
925
  const outputInterfaces = [];
923
926
  const methodEntries = [];
924
- for (const tool of tools) {
927
+ for (const tool of [...tools].sort((a, b) => a.name.localeCompare(b.name))) {
925
928
  const baseName = sanitizeTypeName(tool.name);
926
929
  const inputName = `${baseName}_Input`;
927
930
  const outputName = `${baseName}_Output`;
928
931
  const params = tool.parameters;
929
- if (params.properties && Object.keys(params.properties).length > 0) {
930
- inputInterfaces.push(generateInterface(inputName, params.properties, params.required));
932
+ const sortedPropKeys = params.properties ? Object.keys(params.properties).sort((a, b) => a.localeCompare(b)) : [];
933
+ if (sortedPropKeys.length > 0 && params.properties) {
934
+ inputInterfaces.push(generateInterface(inputName, params.properties, [...params.required ?? []].sort((a, b) => a.localeCompare(b))));
931
935
  inputInterfaces.push("");
932
936
  methodEntries.push(` /**`);
933
937
  methodEntries.push(` * ${tool.description.replace(/\n/g, "\n * ")}`);
@@ -1148,7 +1152,14 @@ async function runAgentTurn(opts2) {
1148
1152
  let toolDefs;
1149
1153
  let codeModeApiString = "";
1150
1154
  if (codeMode) {
1151
- codeModeApiString = generateTypeScriptApi(opts2.tools);
1155
+ const toolsKey = stableStringify(opts2.tools);
1156
+ const cached = codeModeApiCache.get(toolsKey);
1157
+ if (cached) {
1158
+ codeModeApiString = cached;
1159
+ } else {
1160
+ codeModeApiString = generateTypeScriptApi(opts2.tools);
1161
+ codeModeApiCache.set(toolsKey, codeModeApiString);
1162
+ }
1152
1163
  toolDefs = [
1153
1164
  {
1154
1165
  type: "function",
@@ -1183,6 +1194,9 @@ Use console.log() to return results. Only console.log output will be sent back t
1183
1194
  }
1184
1195
  let turn = 0;
1185
1196
  let lastUsage = null;
1197
+ const recentToolCalls = [];
1198
+ const LOOP_WINDOW = 8;
1199
+ const LOOP_THRESHOLD = 2;
1186
1200
  for (let iter = 0; iter < max; iter++) {
1187
1201
  turn++;
1188
1202
  const previousMessages = opts2.messages.slice();
@@ -1313,6 +1327,28 @@ Use console.log() to return results. Only console.log output will be sent back t
1313
1327
  }
1314
1328
  for (const tc of toolCalls) {
1315
1329
  if (opts2.signal.aborted) throw new DOMException("aborted", "AbortError");
1330
+ const loopSignature = `${tc.function.name}:${stableStringify(tc.function.arguments)}`;
1331
+ const loopCount = recentToolCalls.filter((s) => s === loopSignature).length;
1332
+ if (loopCount >= LOOP_THRESHOLD) {
1333
+ const warning = `Loop detected: you have called ${tc.function.name} with the same arguments multiple times in a row. Consider a different approach.`;
1334
+ const loopResult = {
1335
+ tool_call_id: tc.id,
1336
+ name: tc.function.name,
1337
+ content: warning,
1338
+ ok: false
1339
+ };
1340
+ toolResults.push(loopResult);
1341
+ opts2.messages.push({
1342
+ role: "tool",
1343
+ tool_call_id: tc.id,
1344
+ content: sanitizeString(warning),
1345
+ name: tc.function.name
1346
+ });
1347
+ opts2.callbacks.onToolResult?.(loopResult);
1348
+ recentToolCalls.push(loopSignature);
1349
+ if (recentToolCalls.length > LOOP_WINDOW) recentToolCalls.shift();
1350
+ continue;
1351
+ }
1316
1352
  if (codeMode && tc.function.name === "execute_code") {
1317
1353
  const args = JSON.parse(tc.function.arguments || "{}");
1318
1354
  const code = args.code || "";
@@ -1353,6 +1389,8 @@ ${sandboxResult.output}` : sandboxResult.output;
1353
1389
  name: "execute_code"
1354
1390
  });
1355
1391
  opts2.callbacks.onToolResult?.(result);
1392
+ recentToolCalls.push(loopSignature);
1393
+ if (recentToolCalls.length > LOOP_WINDOW) recentToolCalls.shift();
1356
1394
  } else {
1357
1395
  const result = await opts2.executor.run(
1358
1396
  { id: tc.id, name: tc.function.name, arguments: tc.function.arguments },
@@ -1367,6 +1405,8 @@ ${sandboxResult.output}` : sandboxResult.output;
1367
1405
  name: result.name
1368
1406
  });
1369
1407
  opts2.callbacks.onToolResult?.(result);
1408
+ recentToolCalls.push(loopSignature);
1409
+ if (recentToolCalls.length > LOOP_WINDOW) recentToolCalls.shift();
1370
1410
  }
1371
1411
  }
1372
1412
  if (opts2.sessionId && lastUsage) {
@@ -1392,6 +1432,7 @@ function validateToolArguments(raw) {
1392
1432
  return "{}";
1393
1433
  }
1394
1434
  }
1435
+ var codeModeApiCache;
1395
1436
  var init_loop = __esm({
1396
1437
  "src/agent/loop.ts"() {
1397
1438
  "use strict";
@@ -1401,6 +1442,7 @@ var init_loop = __esm({
1401
1442
  init_cost_debug();
1402
1443
  init_strip_reasoning();
1403
1444
  init_code_mode();
1445
+ codeModeApiCache = /* @__PURE__ */ new Map();
1404
1446
  }
1405
1447
  });
1406
1448
 
@@ -3236,6 +3278,39 @@ function emptySessionState(task = "") {
3236
3278
  artifact_index: {}
3237
3279
  };
3238
3280
  }
3281
+ function serializeArtifactStore(store) {
3282
+ const MAX_ARTIFACT_CHARS = 5e4;
3283
+ const out = [];
3284
+ for (const a of store.list()) {
3285
+ out.push({
3286
+ id: a.id,
3287
+ type: a.type,
3288
+ summary: a.summary,
3289
+ raw: a.raw.slice(0, MAX_ARTIFACT_CHARS),
3290
+ source: a.source,
3291
+ path: a.path,
3292
+ lineRange: a.lineRange,
3293
+ ts: a.ts
3294
+ });
3295
+ }
3296
+ return out;
3297
+ }
3298
+ function deserializeArtifactStore(data) {
3299
+ const store = new ArtifactStore();
3300
+ for (const a of data) {
3301
+ store.add({
3302
+ id: a.id,
3303
+ type: a.type,
3304
+ summary: a.summary,
3305
+ raw: a.raw,
3306
+ source: a.source,
3307
+ path: a.path,
3308
+ lineRange: a.lineRange,
3309
+ ts: a.ts
3310
+ });
3311
+ }
3312
+ return store;
3313
+ }
3239
3314
  function formatRecalledArtifacts(recalled) {
3240
3315
  if (recalled.length === 0) return "";
3241
3316
  const lines = ["[recalled artifacts]"];
@@ -4101,6 +4176,12 @@ var init_chat = __esm({
4101
4176
  evt.text
4102
4177
  ] });
4103
4178
  }
4179
+ if (evt.kind === "memory") {
4180
+ return /* @__PURE__ */ jsxs4(Text4, { color: theme.info.color, dimColor: theme.info.dim, children: [
4181
+ "\u25C8 ",
4182
+ evt.text
4183
+ ] });
4184
+ }
4104
4185
  return /* @__PURE__ */ jsxs4(Text4, { color: theme.error, children: [
4105
4186
  "! ",
4106
4187
  evt.text
@@ -5427,7 +5508,7 @@ function HelpMenu({ theme, themes, currentThemeName, customCommands, onDone, onC
5427
5508
  key: cat.key
5428
5509
  }));
5429
5510
  if (customs.length > 0) {
5430
- items2.push({ label: "Custom commands", value: "custom", key: "custom" });
5511
+ items2.push({ label: "Run custom commands", value: "custom", key: "custom" });
5431
5512
  }
5432
5513
  items2.push({ label: "(close)", value: "__close__", key: "__close__" });
5433
5514
  return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
@@ -5611,6 +5692,16 @@ var init_help_menu = __esm({
5611
5692
  { command: "/community", description: "join our Discord server" }
5612
5693
  ]
5613
5694
  },
5695
+ {
5696
+ key: "commands",
5697
+ label: "Commands",
5698
+ commands: [
5699
+ { command: "/command create", description: "create a new custom slash command" },
5700
+ { command: "/command edit", description: "edit an existing custom command" },
5701
+ { command: "/command delete", description: "delete a custom command" },
5702
+ { command: "/command list", description: "list all custom commands" }
5703
+ ]
5704
+ },
5614
5705
  {
5615
5706
  key: "config",
5616
5707
  label: "Config",
@@ -6425,11 +6516,11 @@ function updateAccessedAt(db, ids) {
6425
6516
  function searchMemoriesFts(db, query, repoPath, limit = 50) {
6426
6517
  const sql = repoPath ? `SELECT m.*, rank FROM memories m
6427
6518
  JOIN memories_fts fts ON m.rowid = fts.rowid
6428
- WHERE memories_fts MATCH ? AND m.repo_path = ? AND m.forgotten = 0 AND m.superseded_by IS NULL
6519
+ WHERE memories_fts MATCH ? AND m.repo_path = ? AND m.forgotten = 0 AND m.superseded_by IS NULL AND m.category != 'task'
6429
6520
  ORDER BY rank
6430
6521
  LIMIT ?` : `SELECT m.*, rank FROM memories m
6431
6522
  JOIN memories_fts fts ON m.rowid = fts.rowid
6432
- WHERE memories_fts MATCH ? AND m.forgotten = 0 AND m.superseded_by IS NULL
6523
+ WHERE memories_fts MATCH ? AND m.forgotten = 0 AND m.superseded_by IS NULL AND m.category != 'task'
6433
6524
  ORDER BY rank
6434
6525
  LIMIT ?`;
6435
6526
  const params = repoPath ? [`${query}*`, repoPath, limit] : [`${query}*`, limit];
@@ -6442,7 +6533,7 @@ function searchMemoriesFts(db, query, repoPath, limit = 50) {
6442
6533
  function listMemoriesForVectorSearch(db, repoPath, since, limit = 2e3) {
6443
6534
  const rows = db.prepare(
6444
6535
  `SELECT * FROM memories
6445
- WHERE repo_path = ? AND created_at >= ? AND forgotten = 0 AND superseded_by IS NULL
6536
+ WHERE repo_path = ? AND created_at >= ? AND forgotten = 0 AND superseded_by IS NULL AND category != 'task'
6446
6537
  ORDER BY accessed_at DESC
6447
6538
  LIMIT ?`
6448
6539
  ).all(repoPath, since, limit);
@@ -6889,7 +6980,20 @@ function redactSecrets(text) {
6889
6980
  }
6890
6981
  return result;
6891
6982
  }
6892
- var SECRET_PATTERNS, VERIFY_SYSTEM, TOPIC_KEY_SYSTEM, HYPOTHETICAL_QUERIES_SYSTEM, MemoryManager;
6983
+ function deterministicTopicKey(content) {
6984
+ return content.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().replace(/\s+/g, "_").slice(0, 60);
6985
+ }
6986
+ function pickTopicKey(content, existingKeys) {
6987
+ const normalized = deterministicTopicKey(content);
6988
+ if (!normalized) return null;
6989
+ for (const existing of existingKeys) {
6990
+ if (normalized.includes(existing) || existing.includes(normalized)) {
6991
+ return existing;
6992
+ }
6993
+ }
6994
+ return normalized;
6995
+ }
6996
+ var SECRET_PATTERNS, VERIFY_SYSTEM, HYPOTHETICAL_QUERIES_SYSTEM, MemoryManager;
6893
6997
  var init_manager2 = __esm({
6894
6998
  "src/memory/manager.ts"() {
6895
6999
  "use strict";
@@ -6917,19 +7021,12 @@ Return a JSON object:
6917
7021
  "confidence": "high" | "medium" | "low",
6918
7022
  "corrected_content": string | null // if minor correction needed, provide it; otherwise null
6919
7023
  }`;
6920
- TOPIC_KEY_SYSTEM = `You are a topic normalization engine. Given a new memory and a list of existing topic keys for this project, decide whether the new memory belongs to an existing topic or needs a new one.
6921
-
6922
- Rules:
6923
- - Topic keys are lowercase snake_case, max 3 words.
6924
- - If the new memory is about the same topic as an existing key, return the existing key.
6925
- - If it's a genuinely new topic, generate a new normalized key.
6926
- - Return ONLY the topic key string, nothing else.`;
6927
7024
  HYPOTHETICAL_QUERIES_SYSTEM = `Given a memory, generate 3-5 short search queries a user might type to find it.
6928
7025
  Cover different phrasings: declarative, interrogative, and keyword-based.
6929
7026
 
6930
7027
  Return a JSON array of strings. Example:
6931
7028
  ["what package manager does this project use?", "pnpm preference", "user likes pnpm over npm"]`;
6932
- MemoryManager = class {
7029
+ MemoryManager = class _MemoryManager {
6933
7030
  db = null;
6934
7031
  opts;
6935
7032
  constructor(opts2) {
@@ -6957,6 +7054,14 @@ Return a JSON array of strings. Example:
6957
7054
  gateway: this.opts.gateway
6958
7055
  };
6959
7056
  }
7057
+ get plumbingLlmOpts() {
7058
+ return {
7059
+ accountId: this.opts.accountId,
7060
+ apiToken: this.opts.apiToken,
7061
+ model: this.opts.plumbingModel ?? "@cf/meta/llama-4-scout-17b-16e-instruct",
7062
+ gateway: this.opts.gateway
7063
+ };
7064
+ }
6960
7065
  shouldRedact() {
6961
7066
  return this.opts.redactSecrets !== false;
6962
7067
  }
@@ -6977,7 +7082,7 @@ Return a JSON array of strings. Example:
6977
7082
  if (verified.corrected_content) {
6978
7083
  safeContent = verified.corrected_content;
6979
7084
  }
6980
- const topicKey = await this.normalizeTopicKey(safeContent, repoPath, signal);
7085
+ const topicKey = this.normalizeTopicKey(safeContent, repoPath);
6981
7086
  const supersededIds = [];
6982
7087
  if (topicKey) {
6983
7088
  const existing = findMemoriesByTopicKey(this.db, repoPath, topicKey);
@@ -7029,6 +7134,38 @@ Return a JSON array of strings. Example:
7029
7134
  }
7030
7135
  return retrieveMemories({ db: this.db, query });
7031
7136
  }
7137
+ /**
7138
+ * Format recalled memories as a compact context block for injection into messages.
7139
+ */
7140
+ static formatRecalled(results) {
7141
+ if (results.length === 0) return "";
7142
+ const lines = ["[recalled memories]"];
7143
+ for (const r of results) {
7144
+ const files = r.memory.relatedFiles.length > 0 ? ` [${r.memory.relatedFiles.join(", ")}]` : "";
7145
+ lines.push(`- [${r.memory.category}] ${r.memory.content}${files}`);
7146
+ }
7147
+ return lines.join("\n");
7148
+ }
7149
+ /**
7150
+ * Synthesize recalled memories into a dense prose paragraph.
7151
+ * Uses the lightweight plumbing model (Scout) to keep costs low.
7152
+ */
7153
+ async synthesizeRecalled(results, signal) {
7154
+ if (results.length === 0) return "";
7155
+ const raw = _MemoryManager.formatRecalled(results);
7156
+ const text = await runKimiText({
7157
+ ...this.plumbingLlmOpts,
7158
+ signal,
7159
+ messages: [
7160
+ {
7161
+ role: "system",
7162
+ content: "You are a context-synthesis engine. Given a list of recalled memories about a codebase, produce a single dense paragraph of context for a coding assistant. Preserve all facts, file paths, and decisions. Do not add information not present in the memories. Be terse."
7163
+ },
7164
+ { role: "user", content: raw }
7165
+ ]
7166
+ });
7167
+ return text || raw;
7168
+ }
7032
7169
  /**
7033
7170
  * Soft-delete a memory by ID.
7034
7171
  */
@@ -7088,7 +7225,7 @@ Return a JSON array of strings. Example:
7088
7225
  }
7089
7226
  async verifyMemory(content, signal) {
7090
7227
  const text = await runKimiText({
7091
- ...this.llmOpts,
7228
+ ...this.plumbingLlmOpts,
7092
7229
  signal,
7093
7230
  messages: [
7094
7231
  { role: "system", content: VERIFY_SYSTEM },
@@ -7112,31 +7249,13 @@ Context: This memory was explicitly provided by the user during a conversation.`
7112
7249
  const corrected = typeof rec.corrected_content === "string" ? rec.corrected_content : null;
7113
7250
  return { valid, corrected_content: corrected };
7114
7251
  }
7115
- async normalizeTopicKey(content, repoPath, signal) {
7252
+ normalizeTopicKey(content, repoPath) {
7116
7253
  const existingKeys = listTopicKeys(this.db, repoPath);
7117
- const keysBlock = existingKeys.length > 0 ? existingKeys.join("\n") : "(none yet)";
7118
- const text = await runKimiText({
7119
- ...this.llmOpts,
7120
- signal,
7121
- messages: [
7122
- { role: "system", content: TOPIC_KEY_SYSTEM },
7123
- {
7124
- role: "user",
7125
- content: `Existing topic keys:
7126
- ${keysBlock}
7127
-
7128
- New memory: "${content}"
7129
-
7130
- Return only the topic key string.`
7131
- }
7132
- ]
7133
- });
7134
- const key = text.trim().toLowerCase().replace(/\s+/g, "_").replace(/[^a-z0-9_]/g, "").slice(0, 60);
7135
- return key || null;
7254
+ return pickTopicKey(content, existingKeys);
7136
7255
  }
7137
7256
  async generateHypotheticalQueries(content, signal) {
7138
7257
  const text = await runKimiText({
7139
- ...this.llmOpts,
7258
+ ...this.plumbingLlmOpts,
7140
7259
  signal,
7141
7260
  messages: [
7142
7261
  { role: "system", content: HYPOTHETICAL_QUERIES_SYSTEM },
@@ -7194,6 +7313,18 @@ var init_state = __esm({
7194
7313
  });
7195
7314
 
7196
7315
  // src/commands/frontmatter.ts
7316
+ function serializeFrontmatter(data) {
7317
+ const lines = [];
7318
+ for (const [key, value] of Object.entries(data)) {
7319
+ if (value === void 0 || value === "") continue;
7320
+ lines.push(`${key}: ${value}`);
7321
+ }
7322
+ if (lines.length === 0) return "";
7323
+ return `---
7324
+ ${lines.join("\n")}
7325
+ ---
7326
+ `;
7327
+ }
7197
7328
  function parseFrontmatter(input) {
7198
7329
  const errors = [];
7199
7330
  if (!FENCE.test(input)) {
@@ -7532,19 +7663,566 @@ var init_renderer = __esm({
7532
7663
  }
7533
7664
  });
7534
7665
 
7666
+ // src/commands/save.ts
7667
+ import { mkdir as mkdir8, writeFile as writeFile8, unlink as unlink2 } from "fs/promises";
7668
+ import { dirname as dirname5 } from "path";
7669
+ async function saveCustomCommand(opts2) {
7670
+ const dir = opts2.source === "project" ? projectCommandsDir(opts2.cwd) : globalCommandsDir();
7671
+ const filepath = `${dir}/${opts2.name}.md`;
7672
+ const data = {};
7673
+ if (opts2.description) data.description = opts2.description;
7674
+ if (opts2.mode) data.mode = opts2.mode;
7675
+ if (opts2.model) data.model = opts2.model;
7676
+ if (opts2.effort) data.effort = opts2.effort;
7677
+ const frontmatter = serializeFrontmatter(data);
7678
+ const content = frontmatter + opts2.template;
7679
+ await mkdir8(dirname5(filepath), { recursive: true });
7680
+ await writeFile8(filepath, content, "utf8");
7681
+ return { filepath };
7682
+ }
7683
+ async function deleteCustomCommand(cmd) {
7684
+ await unlink2(cmd.filepath);
7685
+ }
7686
+ var init_save = __esm({
7687
+ "src/commands/save.ts"() {
7688
+ "use strict";
7689
+ init_frontmatter();
7690
+ init_loader();
7691
+ }
7692
+ });
7693
+
7694
+ // src/ui/command-wizard.tsx
7695
+ import { useState as useState7 } from "react";
7696
+ import { Box as Box13, Text as Text14, useInput as useInput3, useWindowSize as useWindowSize2 } from "ink";
7697
+ import SelectInput5 from "ink-select-input";
7698
+ import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
7699
+ function CommandWizard({ theme, mode, initial, existingNames, builtinNames, onDone, onSave }) {
7700
+ const [step, setStep] = useState7("name");
7701
+ const [name, setName] = useState7(initial?.name ?? "");
7702
+ const [description, setDescription] = useState7(initial?.description ?? "");
7703
+ const [template, setTemplate] = useState7(initial?.template ?? "");
7704
+ const [cmdMode, setCmdMode] = useState7(initial?.mode);
7705
+ const [cmdEffort, setCmdEffort] = useState7(initial?.effort);
7706
+ const [cmdModel, setCmdModel] = useState7(initial?.model);
7707
+ const [source, setSource] = useState7(initial?.source ?? "project");
7708
+ const [error, setError] = useState7(null);
7709
+ const { columns } = useWindowSize2();
7710
+ const totalSteps = 5;
7711
+ const stepIndex = step === "name" ? 1 : step === "description" ? 2 : step === "template" ? 3 : step === "advanced" || step === "mode" || step === "effort" || step === "model" ? 4 : step === "location" ? 4 : 5;
7712
+ const isEditingSelf = (n) => initial !== void 0 && initial.name === n;
7713
+ const validateName = (n) => {
7714
+ const trimmed = n.trim();
7715
+ if (!trimmed) return "name is required";
7716
+ if (!NAME_RE.test(trimmed)) return "invalid name: use letters, numbers, _ - / only; must start with a letter";
7717
+ if (builtinNames.has(trimmed.toLowerCase())) return `/${trimmed} is a built-in command`;
7718
+ if (existingNames.includes(trimmed) && !isEditingSelf(trimmed)) return `/${trimmed} already exists`;
7719
+ return null;
7720
+ };
7721
+ useInput3((_input, key) => {
7722
+ if (key.escape) {
7723
+ onDone();
7724
+ }
7725
+ });
7726
+ const handleNameSubmit = (value) => {
7727
+ const trimmed = value.trim();
7728
+ const err = validateName(trimmed);
7729
+ if (err) {
7730
+ setError(err);
7731
+ return;
7732
+ }
7733
+ setError(null);
7734
+ setName(trimmed);
7735
+ setStep("description");
7736
+ };
7737
+ const handleDescriptionSubmit = (value) => {
7738
+ setDescription(value.trim());
7739
+ setStep("template");
7740
+ };
7741
+ const handleTemplateSubmit = (value) => {
7742
+ const trimmed = value.trim();
7743
+ if (!trimmed) {
7744
+ setError("template cannot be empty");
7745
+ return;
7746
+ }
7747
+ setError(null);
7748
+ setTemplate(trimmed);
7749
+ setStep("advanced");
7750
+ };
7751
+ const handleAdvancedChoice = (choice) => {
7752
+ if (choice === "skip") {
7753
+ setCmdMode(void 0);
7754
+ setCmdEffort(void 0);
7755
+ setCmdModel("");
7756
+ if (mode === "edit" && initial) {
7757
+ setSource(initial.source);
7758
+ setStep("confirm");
7759
+ } else {
7760
+ setStep("location");
7761
+ }
7762
+ } else {
7763
+ setStep("mode");
7764
+ }
7765
+ };
7766
+ const handleModeChoice = (m) => {
7767
+ setCmdMode(m === "none" ? void 0 : m);
7768
+ setStep("effort");
7769
+ };
7770
+ const handleEffortChoice = (e) => {
7771
+ setCmdEffort(e === "none" ? void 0 : e);
7772
+ setStep("model");
7773
+ };
7774
+ const handleModelSubmit = (value) => {
7775
+ const trimmed = value.trim();
7776
+ setCmdModel(trimmed || void 0);
7777
+ if (mode === "edit" && initial) {
7778
+ setSource(initial.source);
7779
+ setStep("confirm");
7780
+ } else {
7781
+ setStep("location");
7782
+ }
7783
+ };
7784
+ const handleLocationChoice = (s) => {
7785
+ setSource(s);
7786
+ setStep("confirm");
7787
+ };
7788
+ const handleConfirm = (choice) => {
7789
+ if (choice === "cancel") {
7790
+ onDone();
7791
+ return;
7792
+ }
7793
+ onSave({
7794
+ name,
7795
+ description: description || void 0,
7796
+ template,
7797
+ source,
7798
+ mode: cmdMode,
7799
+ model: cmdModel || void 0,
7800
+ effort: cmdEffort
7801
+ });
7802
+ };
7803
+ const previewContent = () => {
7804
+ const data = {};
7805
+ if (description) data.description = description;
7806
+ if (cmdMode) data.mode = cmdMode;
7807
+ if (cmdModel) data.model = cmdModel;
7808
+ if (cmdEffort) data.effort = cmdEffort;
7809
+ const fm = Object.entries(data).map(([k, v]) => `${k}: ${v}`).join("\n");
7810
+ if (fm) {
7811
+ return `---
7812
+ ${fm}
7813
+ ---
7814
+ ${template}`;
7815
+ }
7816
+ return template;
7817
+ };
7818
+ const renderStep = () => {
7819
+ switch (step) {
7820
+ case "name":
7821
+ return /* @__PURE__ */ jsxs13(Fragment2, { children: [
7822
+ /* @__PURE__ */ jsxs13(Text14, { color: theme.accent, bold: true, children: [
7823
+ mode === "create" ? "Create" : "Edit",
7824
+ " custom command \u2014 Name (",
7825
+ stepIndex,
7826
+ "/",
7827
+ totalSteps,
7828
+ ")"
7829
+ ] }),
7830
+ error && /* @__PURE__ */ jsx14(Text14, { color: theme.error, children: error }),
7831
+ /* @__PURE__ */ jsx14(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx14(
7832
+ CustomTextInput,
7833
+ {
7834
+ value: name,
7835
+ onChange: setName,
7836
+ onSubmit: handleNameSubmit,
7837
+ focus: true
7838
+ }
7839
+ ) }),
7840
+ /* @__PURE__ */ jsx14(Text14, { color: theme.info.color, dimColor: true, children: "letters, numbers, _ - / only; must start with a letter" })
7841
+ ] });
7842
+ case "description":
7843
+ return /* @__PURE__ */ jsxs13(Fragment2, { children: [
7844
+ /* @__PURE__ */ jsxs13(Text14, { color: theme.accent, bold: true, children: [
7845
+ mode === "create" ? "Create" : "Edit",
7846
+ " custom command \u2014 Description (",
7847
+ stepIndex,
7848
+ "/",
7849
+ totalSteps,
7850
+ ")"
7851
+ ] }),
7852
+ /* @__PURE__ */ jsx14(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx14(
7853
+ CustomTextInput,
7854
+ {
7855
+ value: description,
7856
+ onChange: setDescription,
7857
+ onSubmit: handleDescriptionSubmit,
7858
+ focus: true
7859
+ }
7860
+ ) }),
7861
+ /* @__PURE__ */ jsx14(Text14, { color: theme.info.color, dimColor: true, children: "Press Enter to skip" })
7862
+ ] });
7863
+ case "template": {
7864
+ const guide = /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", paddingLeft: 1, children: [
7865
+ /* @__PURE__ */ jsx14(Text14, { color: theme.accent, bold: true, children: "What is this?" }),
7866
+ /* @__PURE__ */ jsx14(Text14, { color: theme.info.color, dimColor: true, children: "A prompt template \u2014 instructions to the AI." }),
7867
+ /* @__PURE__ */ jsxs13(Text14, { color: theme.info.color, dimColor: true, children: [
7868
+ "When you type /",
7869
+ name || "yourcommand",
7870
+ " later, this gets sent to the model."
7871
+ ] }),
7872
+ /* @__PURE__ */ jsxs13(Box13, { marginTop: 1, flexDirection: "column", children: [
7873
+ /* @__PURE__ */ jsx14(Text14, { color: theme.accent, bold: true, children: "Variables" }),
7874
+ /* @__PURE__ */ jsxs13(Text14, { color: theme.info.color, dimColor: true, children: [
7875
+ " ",
7876
+ "$1, $2 ... \u2192 arguments you type"
7877
+ ] }),
7878
+ /* @__PURE__ */ jsxs13(Text14, { color: theme.info.color, dimColor: true, children: [
7879
+ " ",
7880
+ "$ARGUMENTS \u2192 everything after the command"
7881
+ ] })
7882
+ ] }),
7883
+ /* @__PURE__ */ jsxs13(Box13, { marginTop: 1, flexDirection: "column", children: [
7884
+ /* @__PURE__ */ jsx14(Text14, { color: theme.accent, bold: true, children: "Dynamic inlines" }),
7885
+ /* @__PURE__ */ jsxs13(Text14, { color: theme.info.color, dimColor: true, children: [
7886
+ " ",
7887
+ "!`git diff` \u2192 shell output inlined"
7888
+ ] }),
7889
+ /* @__PURE__ */ jsxs13(Text14, { color: theme.info.color, dimColor: true, children: [
7890
+ " ",
7891
+ "@README.md \u2192 file contents inlined"
7892
+ ] })
7893
+ ] }),
7894
+ /* @__PURE__ */ jsxs13(Box13, { marginTop: 1, flexDirection: "column", children: [
7895
+ /* @__PURE__ */ jsx14(Text14, { color: theme.accent, bold: true, children: "Example" }),
7896
+ /* @__PURE__ */ jsx14(Text14, { color: theme.info.color, dimColor: true, children: "Review this PR diff:" }),
7897
+ /* @__PURE__ */ jsx14(Text14, { color: theme.info.color, dimColor: true, children: "!`git diff main...HEAD`" }),
7898
+ /* @__PURE__ */ jsx14(Text14, { color: theme.info.color, dimColor: true, children: "Focus on: $1" })
7899
+ ] })
7900
+ ] });
7901
+ const inputArea = /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", flexGrow: 1, children: [
7902
+ error && /* @__PURE__ */ jsx14(Text14, { color: theme.error, children: error }),
7903
+ /* @__PURE__ */ jsx14(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx14(
7904
+ CustomTextInput,
7905
+ {
7906
+ value: template,
7907
+ onChange: setTemplate,
7908
+ onSubmit: handleTemplateSubmit,
7909
+ focus: true,
7910
+ enablePaste: true
7911
+ }
7912
+ ) }),
7913
+ columns < 100 && /* @__PURE__ */ jsxs13(Fragment2, { children: [
7914
+ /* @__PURE__ */ jsx14(Text14, { color: theme.info.color, dimColor: true, children: "Paste multi-line templates with Ctrl+V." }),
7915
+ /* @__PURE__ */ jsx14(Text14, { color: theme.info.color, dimColor: true, children: "Variables: $1 $2 ... $ARGUMENTS Shell: !`cmd` File: @path" })
7916
+ ] })
7917
+ ] });
7918
+ return /* @__PURE__ */ jsxs13(Fragment2, { children: [
7919
+ /* @__PURE__ */ jsxs13(Text14, { color: theme.accent, bold: true, children: [
7920
+ mode === "create" ? "Create" : "Edit",
7921
+ " custom command \u2014 Template (",
7922
+ stepIndex,
7923
+ "/",
7924
+ totalSteps,
7925
+ ")"
7926
+ ] }),
7927
+ columns >= 100 ? /* @__PURE__ */ jsxs13(Box13, { flexDirection: "row", marginTop: 1, children: [
7928
+ /* @__PURE__ */ jsx14(Box13, { flexDirection: "column", width: "50%", children: inputArea }),
7929
+ /* @__PURE__ */ jsx14(Box13, { flexDirection: "column", width: "50%", children: guide })
7930
+ ] }) : /* @__PURE__ */ jsx14(Box13, { flexDirection: "column", marginTop: 1, children: inputArea })
7931
+ ] });
7932
+ }
7933
+ case "advanced": {
7934
+ const items = [
7935
+ { label: "Set advanced options", value: "set", key: "set" },
7936
+ { label: "Skip", value: "skip", key: "skip" },
7937
+ { label: "\u2190 Cancel", value: "cancel", key: "cancel" }
7938
+ ];
7939
+ return /* @__PURE__ */ jsxs13(Fragment2, { children: [
7940
+ /* @__PURE__ */ jsxs13(Text14, { color: theme.accent, bold: true, children: [
7941
+ mode === "create" ? "Create" : "Edit",
7942
+ " custom command \u2014 Options (",
7943
+ stepIndex,
7944
+ "/",
7945
+ totalSteps,
7946
+ ")"
7947
+ ] }),
7948
+ /* @__PURE__ */ jsx14(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx14(
7949
+ SelectInput5,
7950
+ {
7951
+ items,
7952
+ onSelect: (item) => {
7953
+ if (item.value === "cancel") onDone();
7954
+ else handleAdvancedChoice(item.value);
7955
+ }
7956
+ }
7957
+ ) })
7958
+ ] });
7959
+ }
7960
+ case "mode": {
7961
+ const items = [
7962
+ { label: cmdMode === void 0 ? "none \xB7 current" : "none", value: "none", key: "none" },
7963
+ { label: cmdMode === "edit" ? "edit \xB7 current" : "edit", value: "edit", key: "edit" },
7964
+ { label: cmdMode === "plan" ? "plan \xB7 current" : "plan", value: "plan", key: "plan" },
7965
+ { label: cmdMode === "auto" ? "auto \xB7 current" : "auto", value: "auto", key: "auto" },
7966
+ { label: "\u2190 Back", value: "__back__", key: "__back__" }
7967
+ ];
7968
+ return /* @__PURE__ */ jsxs13(Fragment2, { children: [
7969
+ /* @__PURE__ */ jsxs13(Text14, { color: theme.accent, bold: true, children: [
7970
+ "Mode override (",
7971
+ stepIndex,
7972
+ "/",
7973
+ totalSteps,
7974
+ ")"
7975
+ ] }),
7976
+ /* @__PURE__ */ jsx14(Text14, { color: theme.info.color, dimColor: true, children: "Saved to file but not yet enforced at runtime" }),
7977
+ /* @__PURE__ */ jsx14(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx14(
7978
+ SelectInput5,
7979
+ {
7980
+ items,
7981
+ onSelect: (item) => {
7982
+ if (item.value === "__back__") setStep("advanced");
7983
+ else handleModeChoice(item.value);
7984
+ }
7985
+ }
7986
+ ) })
7987
+ ] });
7988
+ }
7989
+ case "effort": {
7990
+ const items = [
7991
+ { label: cmdEffort === void 0 ? "none \xB7 current" : "none", value: "none", key: "none" },
7992
+ { label: cmdEffort === "low" ? "low \xB7 current" : "low", value: "low", key: "low" },
7993
+ { label: cmdEffort === "medium" ? "medium \xB7 current" : "medium", value: "medium", key: "medium" },
7994
+ { label: cmdEffort === "high" ? "high \xB7 current" : "high", value: "high", key: "high" },
7995
+ { label: "\u2190 Back", value: "__back__", key: "__back__" }
7996
+ ];
7997
+ return /* @__PURE__ */ jsxs13(Fragment2, { children: [
7998
+ /* @__PURE__ */ jsxs13(Text14, { color: theme.accent, bold: true, children: [
7999
+ "Reasoning effort (",
8000
+ stepIndex,
8001
+ "/",
8002
+ totalSteps,
8003
+ ")"
8004
+ ] }),
8005
+ /* @__PURE__ */ jsx14(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx14(
8006
+ SelectInput5,
8007
+ {
8008
+ items,
8009
+ onSelect: (item) => {
8010
+ if (item.value === "__back__") setStep("mode");
8011
+ else handleEffortChoice(item.value);
8012
+ }
8013
+ }
8014
+ ) })
8015
+ ] });
8016
+ }
8017
+ case "model":
8018
+ return /* @__PURE__ */ jsxs13(Fragment2, { children: [
8019
+ /* @__PURE__ */ jsxs13(Text14, { color: theme.accent, bold: true, children: [
8020
+ "Model override (",
8021
+ stepIndex,
8022
+ "/",
8023
+ totalSteps,
8024
+ ")"
8025
+ ] }),
8026
+ /* @__PURE__ */ jsx14(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx14(
8027
+ CustomTextInput,
8028
+ {
8029
+ value: cmdModel ?? "",
8030
+ onChange: setCmdModel,
8031
+ onSubmit: handleModelSubmit,
8032
+ focus: true
8033
+ }
8034
+ ) }),
8035
+ /* @__PURE__ */ jsx14(Text14, { color: theme.info.color, dimColor: true, children: "Press Enter to skip" })
8036
+ ] });
8037
+ case "location": {
8038
+ const items = [
8039
+ { label: source === "project" ? "Project \xB7 current" : "Project", value: "project", key: "project" },
8040
+ { label: source === "global" ? "Global \xB7 current" : "Global", value: "global", key: "global" },
8041
+ { label: "\u2190 Back", value: "__back__", key: "__back__" }
8042
+ ];
8043
+ return /* @__PURE__ */ jsxs13(Fragment2, { children: [
8044
+ /* @__PURE__ */ jsxs13(Text14, { color: theme.accent, bold: true, children: [
8045
+ "Save location (",
8046
+ stepIndex,
8047
+ "/",
8048
+ totalSteps,
8049
+ ")"
8050
+ ] }),
8051
+ /* @__PURE__ */ jsx14(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx14(
8052
+ SelectInput5,
8053
+ {
8054
+ items,
8055
+ onSelect: (item) => {
8056
+ if (item.value === "__back__") setStep("advanced");
8057
+ else handleLocationChoice(item.value);
8058
+ }
8059
+ }
8060
+ ) }),
8061
+ /* @__PURE__ */ jsx14(Text14, { color: theme.info.color, dimColor: true, children: "Project: .kimiflare/commands/ Global: ~/.config/kimiflare/commands/" })
8062
+ ] });
8063
+ }
8064
+ case "confirm": {
8065
+ const items = [
8066
+ { label: "Save", value: "save", key: "save" },
8067
+ { label: "Cancel", value: "cancel", key: "cancel" }
8068
+ ];
8069
+ return /* @__PURE__ */ jsxs13(Fragment2, { children: [
8070
+ /* @__PURE__ */ jsxs13(Text14, { color: theme.accent, bold: true, children: [
8071
+ mode === "create" ? "Create" : "Edit",
8072
+ " custom command \u2014 Confirm (",
8073
+ stepIndex,
8074
+ "/",
8075
+ totalSteps,
8076
+ ")"
8077
+ ] }),
8078
+ /* @__PURE__ */ jsxs13(Text14, { color: theme.info.color, dimColor: true, children: [
8079
+ source === "project" ? ".kimiflare/commands/" : "~/.config/kimiflare/commands/",
8080
+ name,
8081
+ ".md"
8082
+ ] }),
8083
+ /* @__PURE__ */ jsx14(Box13, { marginTop: 1, flexDirection: "column", children: previewContent().split("\n").map((line, i) => /* @__PURE__ */ jsx14(Text14, { color: theme.info.color, dimColor: true, children: line || " " }, i)) }),
8084
+ /* @__PURE__ */ jsx14(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx14(
8085
+ SelectInput5,
8086
+ {
8087
+ items,
8088
+ onSelect: (item) => handleConfirm(item.value)
8089
+ }
8090
+ ) })
8091
+ ] });
8092
+ }
8093
+ }
8094
+ };
8095
+ return /* @__PURE__ */ jsx14(Box13, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: renderStep() });
8096
+ }
8097
+ var NAME_RE;
8098
+ var init_command_wizard = __esm({
8099
+ "src/ui/command-wizard.tsx"() {
8100
+ "use strict";
8101
+ init_text_input();
8102
+ NAME_RE = /^[a-zA-Z][a-zA-Z0-9_\-/]*$/;
8103
+ }
8104
+ });
8105
+
8106
+ // src/ui/command-picker.tsx
8107
+ import { Box as Box14, Text as Text15 } from "ink";
8108
+ import SelectInput6 from "ink-select-input";
8109
+ import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
8110
+ function CommandPicker({ theme, commands, title, onPick }) {
8111
+ const items = commands.map((cmd) => ({
8112
+ label: `/${cmd.name.padEnd(20)} ${cmd.description ?? ""}`,
8113
+ value: cmd,
8114
+ key: cmd.name
8115
+ }));
8116
+ items.push({ label: "\u2190 Cancel", value: null, key: "__cancel__" });
8117
+ return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
8118
+ /* @__PURE__ */ jsx15(Text15, { color: theme.accent, bold: true, children: title }),
8119
+ /* @__PURE__ */ jsx15(Text15, { color: theme.info.color, dimColor: false, children: "Arrow keys to navigate, Enter to select." }),
8120
+ /* @__PURE__ */ jsx15(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx15(
8121
+ SelectInput6,
8122
+ {
8123
+ items,
8124
+ onSelect: (item) => {
8125
+ if (item.key === "__cancel__") {
8126
+ onPick(null);
8127
+ } else {
8128
+ onPick(item.value);
8129
+ }
8130
+ }
8131
+ }
8132
+ ) })
8133
+ ] });
8134
+ }
8135
+ var init_command_picker = __esm({
8136
+ "src/ui/command-picker.tsx"() {
8137
+ "use strict";
8138
+ }
8139
+ });
8140
+
8141
+ // src/ui/command-list.tsx
8142
+ import { Box as Box15, Text as Text16, useInput as useInput4 } from "ink";
8143
+ import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
8144
+ function CommandList({ theme, commands, onDone }) {
8145
+ useInput4((_input, key) => {
8146
+ if (key.escape) {
8147
+ onDone();
8148
+ }
8149
+ });
8150
+ return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
8151
+ /* @__PURE__ */ jsx16(Text16, { color: theme.accent, bold: true, children: "Custom commands" }),
8152
+ /* @__PURE__ */ jsx16(Text16, { color: theme.info.color, dimColor: false, children: "Esc to close." }),
8153
+ /* @__PURE__ */ jsxs15(Box15, { marginTop: 1, flexDirection: "column", children: [
8154
+ commands.length === 0 && /* @__PURE__ */ jsx16(Text16, { color: theme.info.color, dimColor: true, children: "No custom commands found." }),
8155
+ commands.map((cmd) => /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", marginBottom: 1, children: [
8156
+ /* @__PURE__ */ jsxs15(Text16, { color: theme.accent, bold: true, children: [
8157
+ "/",
8158
+ cmd.name
8159
+ ] }),
8160
+ /* @__PURE__ */ jsxs15(Text16, { color: theme.info.color, dimColor: true, children: [
8161
+ " ",
8162
+ "source: ",
8163
+ cmd.source
8164
+ ] }),
8165
+ /* @__PURE__ */ jsxs15(Text16, { color: theme.info.color, dimColor: true, children: [
8166
+ " ",
8167
+ "path: ",
8168
+ cmd.filepath
8169
+ ] }),
8170
+ cmd.description && /* @__PURE__ */ jsxs15(Text16, { color: theme.info.color, dimColor: true, children: [
8171
+ " ",
8172
+ "desc: ",
8173
+ cmd.description
8174
+ ] }),
8175
+ cmd.mode && /* @__PURE__ */ jsxs15(Text16, { color: theme.info.color, dimColor: true, children: [
8176
+ " ",
8177
+ "mode: ",
8178
+ cmd.mode
8179
+ ] }),
8180
+ cmd.effort && /* @__PURE__ */ jsxs15(Text16, { color: theme.info.color, dimColor: true, children: [
8181
+ " ",
8182
+ "effort: ",
8183
+ cmd.effort
8184
+ ] }),
8185
+ cmd.model && /* @__PURE__ */ jsxs15(Text16, { color: theme.info.color, dimColor: true, children: [
8186
+ " ",
8187
+ "model: ",
8188
+ cmd.model
8189
+ ] }),
8190
+ /* @__PURE__ */ jsxs15(Text16, { color: theme.info.color, dimColor: true, children: [
8191
+ " ",
8192
+ "template:"
8193
+ ] }),
8194
+ cmd.template.split("\n").slice(0, 5).map((line, i) => /* @__PURE__ */ jsxs15(Text16, { color: theme.info.color, dimColor: true, children: [
8195
+ " ",
8196
+ line || " "
8197
+ ] }, i)),
8198
+ cmd.template.split("\n").length > 5 && /* @__PURE__ */ jsxs15(Text16, { color: theme.info.color, dimColor: true, children: [
8199
+ " ",
8200
+ "..."
8201
+ ] })
8202
+ ] }, cmd.name))
8203
+ ] })
8204
+ ] });
8205
+ }
8206
+ var init_command_list = __esm({
8207
+ "src/ui/command-list.tsx"() {
8208
+ "use strict";
8209
+ }
8210
+ });
8211
+
7535
8212
  // src/app.tsx
7536
8213
  var app_exports = {};
7537
8214
  __export(app_exports, {
7538
8215
  renderApp: () => renderApp
7539
8216
  });
7540
- import { useState as useState7, useRef as useRef3, useEffect as useEffect4, useCallback } from "react";
7541
- import { Box as Box13, Text as Text14, useApp, useInput as useInput3, render } from "ink";
8217
+ import { useState as useState8, useRef as useRef3, useEffect as useEffect4, useCallback } from "react";
8218
+ import { Box as Box16, Text as Text17, useApp, useInput as useInput5, render } from "ink";
8219
+ import SelectInput7 from "ink-select-input";
7542
8220
  import { existsSync } from "fs";
7543
8221
  import { join as join13 } from "path";
7544
- import { unlink as unlink2 } from "fs/promises";
8222
+ import { unlink as unlink3 } from "fs/promises";
7545
8223
  import { spawn as spawn2 } from "child_process";
7546
8224
  import { platform as platform2 } from "os";
7547
- import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
8225
+ import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
7548
8226
  function gatewayFromConfig(cfg) {
7549
8227
  if (!cfg.aiGatewayId) return void 0;
7550
8228
  return {
@@ -7605,18 +8283,29 @@ function makePrefixMessages(cacheStable, model, mode, tools) {
7605
8283
  }
7606
8284
  function findImagePaths(text) {
7607
8285
  const paths = [];
7608
- for (const token of text.split(/\s+/)) {
7609
- const clean = token.replace(/^["']|["',;:!?]$/g, "").replace(/[.,;:!?]$/, "");
7610
- if (isImagePath(clean) && existsSync(clean)) {
8286
+ const quotedRegex = /"([^"]+)"|'([^']+)'/g;
8287
+ let match;
8288
+ while ((match = quotedRegex.exec(text)) !== null) {
8289
+ const path = match[1] ?? match[2];
8290
+ if (path && isImagePath(path) && existsSync(path)) {
8291
+ paths.push(path);
8292
+ }
8293
+ }
8294
+ const remaining = text.replace(/"[^"]+"|'[^']+'/g, "");
8295
+ const ESCAPED_SPACE = "\0";
8296
+ const processed = remaining.replace(/\\ /g, ESCAPED_SPACE);
8297
+ for (const token of processed.split(/\s+/)) {
8298
+ const clean = token.replace(new RegExp(ESCAPED_SPACE, "g"), " ").replace(/^["']|["',;:!?]$/g, "").replace(/[.,;:!?]$/, "");
8299
+ if (clean && isImagePath(clean) && existsSync(clean) && !paths.includes(clean)) {
7611
8300
  paths.push(clean);
7612
8301
  }
7613
8302
  }
7614
- return [...new Set(paths)];
8303
+ return paths;
7615
8304
  }
7616
8305
  function App({ initialCfg, initialUpdateResult }) {
7617
8306
  const { exit } = useApp();
7618
- const [cfg, setCfg] = useState7(initialCfg);
7619
- const [events, setRawEvents] = useState7([]);
8307
+ const [cfg, setCfg] = useState8(initialCfg);
8308
+ const [events, setRawEvents] = useState8([]);
7620
8309
  const setEvents = useCallback(
7621
8310
  (updater) => {
7622
8311
  setRawEvents((prev) => {
@@ -7626,34 +8315,38 @@ function App({ initialCfg, initialUpdateResult }) {
7626
8315
  },
7627
8316
  []
7628
8317
  );
7629
- const [input, setInput] = useState7("");
7630
- const [busy, setBusy] = useState7(false);
7631
- const [usage, setUsage] = useState7(null);
7632
- const [sessionUsage, setSessionUsage] = useState7(null);
7633
- const [gatewayMeta, setGatewayMeta] = useState7(null);
7634
- const [showReasoning, setShowReasoning] = useState7(false);
7635
- const [perm, setPerm] = useState7(null);
7636
- const [queue, setQueue] = useState7([]);
7637
- const [history, setHistory] = useState7([]);
7638
- const [historyIndex, setHistoryIndex] = useState7(-1);
7639
- const [draftInput, setDraftInput] = useState7("");
7640
- const [mode, setMode] = useState7("edit");
7641
- const [codeMode, setCodeMode] = useState7(initialCfg?.codeMode ?? false);
7642
- const [effort, setEffort] = useState7(
8318
+ const [input, setInput] = useState8("");
8319
+ const [busy, setBusy] = useState8(false);
8320
+ const [usage, setUsage] = useState8(null);
8321
+ const [sessionUsage, setSessionUsage] = useState8(null);
8322
+ const [gatewayMeta, setGatewayMeta] = useState8(null);
8323
+ const [showReasoning, setShowReasoning] = useState8(false);
8324
+ const [perm, setPerm] = useState8(null);
8325
+ const [queue, setQueue] = useState8([]);
8326
+ const [history, setHistory] = useState8([]);
8327
+ const [historyIndex, setHistoryIndex] = useState8(-1);
8328
+ const [draftInput, setDraftInput] = useState8("");
8329
+ const [mode, setMode] = useState8("edit");
8330
+ const [codeMode, setCodeMode] = useState8(initialCfg?.codeMode ?? false);
8331
+ const [effort, setEffort] = useState8(
7643
8332
  initialCfg?.reasoningEffort ?? DEFAULT_REASONING_EFFORT
7644
8333
  );
7645
- const [theme, setTheme] = useState7(resolveTheme(initialCfg?.theme));
7646
- const [resumeSessions, setResumeSessions] = useState7(null);
7647
- const [showThemePicker, setShowThemePicker] = useState7(false);
7648
- const [showHelpMenu, setShowHelpMenu] = useState7(false);
7649
- const [originalTheme, setOriginalTheme] = useState7(null);
7650
- const [tasks, setTasks] = useState7([]);
7651
- const [tasksStartedAt, setTasksStartedAt] = useState7(null);
7652
- const [tasksStartTokens, setTasksStartTokens] = useState7(0);
7653
- const [turnStartedAt, setTurnStartedAt] = useState7(null);
7654
- const [verbose, setVerbose] = useState7(false);
7655
- const [hasUpdate, setHasUpdate] = useState7(initialUpdateResult?.hasUpdate ?? false);
7656
- const [latestVersion, setLatestVersion] = useState7(initialUpdateResult?.latestVersion ?? null);
8334
+ const [theme, setTheme] = useState8(resolveTheme(initialCfg?.theme));
8335
+ const [resumeSessions, setResumeSessions] = useState8(null);
8336
+ const [showThemePicker, setShowThemePicker] = useState8(false);
8337
+ const [showHelpMenu, setShowHelpMenu] = useState8(false);
8338
+ const [originalTheme, setOriginalTheme] = useState8(null);
8339
+ const [commandWizard, setCommandWizard] = useState8(null);
8340
+ const [commandPicker, setCommandPicker] = useState8(null);
8341
+ const [commandToDelete, setCommandToDelete] = useState8(null);
8342
+ const [showCommandList, setShowCommandList] = useState8(false);
8343
+ const [tasks, setTasks] = useState8([]);
8344
+ const [tasksStartedAt, setTasksStartedAt] = useState8(null);
8345
+ const [tasksStartTokens, setTasksStartTokens] = useState8(0);
8346
+ const [turnStartedAt, setTurnStartedAt] = useState8(null);
8347
+ const [verbose, setVerbose] = useState8(false);
8348
+ const [hasUpdate, setHasUpdate] = useState8(initialUpdateResult?.hasUpdate ?? false);
8349
+ const [latestVersion, setLatestVersion] = useState8(initialUpdateResult?.latestVersion ?? null);
7657
8350
  const cacheStableRef = useRef3(initialCfg?.cacheStablePrompts !== false);
7658
8351
  const messagesRef = useRef3(
7659
8352
  makePrefixMessages(cacheStableRef.current, cfg?.model ?? DEFAULT_MODEL, "edit", ALL_TOOLS)
@@ -7677,6 +8370,7 @@ function App({ initialCfg, initialUpdateResult }) {
7677
8370
  const mcpToolsRef = useRef3([]);
7678
8371
  const mcpInitRef = useRef3(false);
7679
8372
  const memoryManagerRef = useRef3(null);
8373
+ const sessionStartRecallRef = useRef3(null);
7680
8374
  const pendingTextRef = useRef3(/* @__PURE__ */ new Map());
7681
8375
  const flushTimeoutRef = useRef3(null);
7682
8376
  const customCommandsRef = useRef3([]);
@@ -7712,6 +8406,7 @@ function App({ initialCfg, initialUpdateResult }) {
7712
8406
  accountId: cfg.accountId,
7713
8407
  apiToken: cfg.apiToken,
7714
8408
  model: cfg.model,
8409
+ plumbingModel: cfg.plumbingModel,
7715
8410
  embeddingModel: cfg.memoryEmbeddingModel,
7716
8411
  gateway: gatewayFromConfig(cfg),
7717
8412
  maxAgeDays: cfg.memoryMaxAgeDays ?? RETENTION.memoryMaxAgeDays,
@@ -7724,7 +8419,7 @@ function App({ initialCfg, initialUpdateResult }) {
7724
8419
  if (total > 0) {
7725
8420
  setEvents((e) => [
7726
8421
  ...e,
7727
- { kind: "info", key: mkKey(), text: `memory cleanup: removed ${total} stale entries` }
8422
+ { kind: "memory", key: mkKey(), text: `memory cleanup: removed ${total} stale entries` }
7728
8423
  ]);
7729
8424
  }
7730
8425
  });
@@ -7732,10 +8427,27 @@ function App({ initialCfg, initialUpdateResult }) {
7732
8427
  if (fixed > 0) {
7733
8428
  setEvents((e) => [
7734
8429
  ...e,
7735
- { kind: "info", key: mkKey(), text: `memory backfill: embedded ${fixed} un-vectorized entries` }
8430
+ { kind: "memory", key: mkKey(), text: `memory backfill: embedded ${fixed} un-vectorized entries` }
7736
8431
  ]);
7737
8432
  }
7738
8433
  });
8434
+ const cwd = process.cwd();
8435
+ sessionStartRecallRef.current = (async () => {
8436
+ try {
8437
+ const results = await manager.recall({ text: cwd, repoPath: cwd, limit: 5 });
8438
+ if (results.length > 0) {
8439
+ const text = await manager.synthesizeRecalled(results);
8440
+ const lastSystemIdx = messagesRef.current.findLastIndex((m) => m.role === "system");
8441
+ const insertIdx = lastSystemIdx >= 0 ? lastSystemIdx + 1 : messagesRef.current.length;
8442
+ messagesRef.current.splice(insertIdx, 0, { role: "system", content: text });
8443
+ setEvents((e) => [
8444
+ ...e,
8445
+ { kind: "memory", key: mkKey(), text: `recalled ${results.length} memory${results.length === 1 ? "" : "ies"} about this repo` }
8446
+ ]);
8447
+ }
8448
+ } catch {
8449
+ }
8450
+ })();
7739
8451
  } else {
7740
8452
  memoryManagerRef.current?.close();
7741
8453
  memoryManagerRef.current = null;
@@ -7754,6 +8466,20 @@ function App({ initialCfg, initialUpdateResult }) {
7754
8466
  }
7755
8467
  });
7756
8468
  }, [cfg, setEvents]);
8469
+ const reloadCustomCommands = useCallback(async () => {
8470
+ const { commands, warnings } = await loadCustomCommands(process.cwd());
8471
+ customCommandsRef.current = commands;
8472
+ for (const w of warnings) {
8473
+ setEvents((e) => [...e, { kind: "info", key: mkKey(), text: `commands: ${w}` }]);
8474
+ }
8475
+ const shadowed = commands.filter((c) => BUILTIN_COMMAND_NAMES.has(c.name.toLowerCase()));
8476
+ for (const c of shadowed) {
8477
+ setEvents((e) => [
8478
+ ...e,
8479
+ { kind: "info", key: mkKey(), text: `commands: /${c.name} (${c.filepath}) shadowed by built-in \u2014 will not run` }
8480
+ ]);
8481
+ }
8482
+ }, [setEvents]);
7757
8483
  useEffect4(() => {
7758
8484
  if (!cfg || updateCheckedRef.current) return;
7759
8485
  updateCheckedRef.current = true;
@@ -7956,12 +8682,13 @@ function App({ initialCfg, initialUpdateResult }) {
7956
8682
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
7957
8683
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7958
8684
  messages: messagesRef.current,
7959
- sessionState: compiledContextRef.current ? sessionStateRef.current : void 0
8685
+ sessionState: compiledContextRef.current ? sessionStateRef.current : void 0,
8686
+ artifactStore: serializeArtifactStore(artifactStoreRef.current)
7960
8687
  });
7961
8688
  } catch {
7962
8689
  }
7963
8690
  }, [cfg, ensureSessionId]);
7964
- useInput3((inputChar, key) => {
8691
+ useInput5((inputChar, key) => {
7965
8692
  if (key.ctrl && inputChar === "c") {
7966
8693
  if (busy && activeControllerRef.current) {
7967
8694
  activeControllerRef.current.abort();
@@ -8337,8 +9064,26 @@ function App({ initialCfg, initialUpdateResult }) {
8337
9064
  sessionIdRef.current = file.id;
8338
9065
  if (file.sessionState && compiledContextRef.current) {
8339
9066
  sessionStateRef.current = file.sessionState;
9067
+ }
9068
+ if (file.artifactStore) {
9069
+ artifactStoreRef.current = deserializeArtifactStore(file.artifactStore);
9070
+ } else {
8340
9071
  artifactStoreRef.current = new ArtifactStore();
8341
9072
  }
9073
+ const manager = memoryManagerRef.current;
9074
+ if (manager) {
9075
+ try {
9076
+ const cwd = process.cwd();
9077
+ const results = await manager.recall({ text: cwd, repoPath: cwd, limit: 5 });
9078
+ if (results.length > 0) {
9079
+ const text = await manager.synthesizeRecalled(results);
9080
+ const lastSystemIdx = messagesRef.current.findLastIndex((m) => m.role === "system");
9081
+ const insertIdx = lastSystemIdx >= 0 ? lastSystemIdx + 1 : messagesRef.current.length;
9082
+ messagesRef.current.splice(insertIdx, 0, { role: "system", content: text });
9083
+ }
9084
+ } catch {
9085
+ }
9086
+ }
8342
9087
  setEvents([
8343
9088
  {
8344
9089
  kind: "info",
@@ -8656,7 +9401,7 @@ use: /thinking low | medium | high`
8656
9401
  setCfg(next);
8657
9402
  void saveConfig(next).catch(() => {
8658
9403
  });
8659
- setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "memory enabled" }]);
9404
+ setEvents((e) => [...e, { kind: "memory", key: mkKey(), text: "memory enabled" }]);
8660
9405
  return true;
8661
9406
  }
8662
9407
  if (arg === "off") {
@@ -8664,7 +9409,7 @@ use: /thinking low | medium | high`
8664
9409
  setCfg(next);
8665
9410
  void saveConfig(next).catch(() => {
8666
9411
  });
8667
- setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "memory disabled" }]);
9412
+ setEvents((e) => [...e, { kind: "memory", key: mkKey(), text: "memory disabled" }]);
8668
9413
  return true;
8669
9414
  }
8670
9415
  if (!cfg.memoryEnabled) {
@@ -8673,7 +9418,7 @@ use: /thinking low | medium | high`
8673
9418
  }
8674
9419
  if (arg === "clear") {
8675
9420
  const cleared = memoryManagerRef.current?.clearRepo(process.cwd()) ?? 0;
8676
- setEvents((e) => [...e, { kind: "info", key: mkKey(), text: `cleared ${cleared} memories for this repo` }]);
9421
+ setEvents((e) => [...e, { kind: "memory", key: mkKey(), text: `cleared ${cleared} memories for this repo` }]);
8677
9422
  return true;
8678
9423
  }
8679
9424
  if (arg.startsWith("search ")) {
@@ -8808,7 +9553,7 @@ ${lines.join("\n")}` }]);
8808
9553
  return true;
8809
9554
  }
8810
9555
  if (c === "/logout") {
8811
- unlink2(configPath()).catch(() => {
9556
+ unlink3(configPath()).catch(() => {
8812
9557
  });
8813
9558
  setEvents((e) => [
8814
9559
  ...e,
@@ -8817,6 +9562,30 @@ ${lines.join("\n")}` }]);
8817
9562
  setCfg(null);
8818
9563
  return true;
8819
9564
  }
9565
+ if (c === "/command") {
9566
+ const sub = rest[0]?.toLowerCase() ?? "";
9567
+ if (sub === "create") {
9568
+ setCommandWizard({ mode: "create" });
9569
+ return true;
9570
+ }
9571
+ if (sub === "edit") {
9572
+ setCommandPicker({ mode: "edit" });
9573
+ return true;
9574
+ }
9575
+ if (sub === "delete") {
9576
+ setCommandPicker({ mode: "delete" });
9577
+ return true;
9578
+ }
9579
+ if (sub === "list") {
9580
+ setShowCommandList(true);
9581
+ return true;
9582
+ }
9583
+ setEvents((e) => [
9584
+ ...e,
9585
+ { kind: "info", key: mkKey(), text: "usage: /command create | edit | delete | list" }
9586
+ ]);
9587
+ return true;
9588
+ }
8820
9589
  if (c === "/help") {
8821
9590
  setShowHelpMenu(true);
8822
9591
  return true;
@@ -8835,6 +9604,47 @@ ${lines.join("\n")}` }]);
8835
9604
  },
8836
9605
  [handleSlash]
8837
9606
  );
9607
+ const handleCommandSave = useCallback(
9608
+ async (opts2) => {
9609
+ setCommandWizard(null);
9610
+ try {
9611
+ if (commandWizard?.mode === "edit" && commandWizard.initial && commandWizard.initial.name !== opts2.name) {
9612
+ await deleteCustomCommand(commandWizard.initial);
9613
+ }
9614
+ const result = await saveCustomCommand(opts2);
9615
+ await reloadCustomCommands();
9616
+ setEvents((e) => [
9617
+ ...e,
9618
+ { kind: "info", key: mkKey(), text: `saved /${opts2.name} \u2192 ${result.filepath}` }
9619
+ ]);
9620
+ } catch (err) {
9621
+ setEvents((e) => [
9622
+ ...e,
9623
+ { kind: "error", key: mkKey(), text: `failed to save /${opts2.name}: ${err.message}` }
9624
+ ]);
9625
+ }
9626
+ },
9627
+ [commandWizard, reloadCustomCommands, setEvents]
9628
+ );
9629
+ const handleCommandDelete = useCallback(
9630
+ async (cmd) => {
9631
+ setCommandToDelete(null);
9632
+ try {
9633
+ await deleteCustomCommand(cmd);
9634
+ await reloadCustomCommands();
9635
+ setEvents((e) => [
9636
+ ...e,
9637
+ { kind: "info", key: mkKey(), text: `deleted /${cmd.name} (${cmd.filepath})` }
9638
+ ]);
9639
+ } catch (err) {
9640
+ setEvents((e) => [
9641
+ ...e,
9642
+ { kind: "error", key: mkKey(), text: `failed to delete /${cmd.name}: ${err.message}` }
9643
+ ]);
9644
+ }
9645
+ },
9646
+ [reloadCustomCommands, setEvents]
9647
+ );
8838
9648
  const processMessage = useCallback(
8839
9649
  async (text, displayText) => {
8840
9650
  if (!cfg) return;
@@ -8897,6 +9707,10 @@ ${lines.join("\n")}` }]);
8897
9707
  content = parts;
8898
9708
  }
8899
9709
  }
9710
+ if (sessionStartRecallRef.current) {
9711
+ await sessionStartRecallRef.current;
9712
+ sessionStartRecallRef.current = null;
9713
+ }
8900
9714
  setEvents((e) => [...e, { kind: "user", key: mkKey(), text: display, images: images.length > 0 ? images : void 0 }]);
8901
9715
  messagesRef.current.push({ role: "user", content });
8902
9716
  if (compiledContextRef.current) {
@@ -9088,6 +9902,30 @@ ${lines.join("\n")}` }]);
9088
9902
  }
9089
9903
  }
9090
9904
  }
9905
+ const manager = memoryManagerRef.current;
9906
+ if (manager) {
9907
+ try {
9908
+ const cwd = process.cwd();
9909
+ const queryText = sessionStateRef.current.task || cwd;
9910
+ const results = await manager.recall({ text: queryText, repoPath: cwd, limit: 5 });
9911
+ if (results.length > 0) {
9912
+ const text2 = await manager.synthesizeRecalled(results);
9913
+ const lastSystemIdx = messagesRef.current.findLastIndex((m) => m.role === "system");
9914
+ const insertIdx = lastSystemIdx >= 0 ? lastSystemIdx + 1 : messagesRef.current.length;
9915
+ messagesRef.current.splice(insertIdx, 0, { role: "system", content: text2 });
9916
+ setEvents((e) => [
9917
+ ...e,
9918
+ {
9919
+ kind: "memory",
9920
+ key: mkKey(),
9921
+ text: `recalled ${results.length} memory${results.length === 1 ? "" : "ies"} after compaction`
9922
+ }
9923
+ ]);
9924
+ await saveSessionSafe();
9925
+ }
9926
+ } catch {
9927
+ }
9928
+ }
9091
9929
  } catch (e) {
9092
9930
  if (e.name === "AbortError") {
9093
9931
  setEvents((es) => [...es, { kind: "info", key: mkKey(), text: "(aborted)" }]);
@@ -9161,7 +9999,7 @@ ${lines.join("\n")}` }]);
9161
9999
  }
9162
10000
  }, [usage]);
9163
10001
  if (!cfg) {
9164
- return /* @__PURE__ */ jsx14(
10002
+ return /* @__PURE__ */ jsx17(
9165
10003
  Onboarding,
9166
10004
  {
9167
10005
  onDone: (newCfg) => {
@@ -9175,13 +10013,13 @@ ${lines.join("\n")}` }]);
9175
10013
  );
9176
10014
  }
9177
10015
  if (resumeSessions !== null) {
9178
- return /* @__PURE__ */ jsx14(Box13, { flexDirection: "column", children: /* @__PURE__ */ jsx14(ResumePicker, { sessions: resumeSessions, onPick: handleResumePick, theme }) });
10016
+ return /* @__PURE__ */ jsx17(Box16, { flexDirection: "column", children: /* @__PURE__ */ jsx17(ResumePicker, { sessions: resumeSessions, onPick: handleResumePick, theme }) });
9179
10017
  }
9180
10018
  if (showThemePicker) {
9181
- return /* @__PURE__ */ jsx14(Box13, { flexDirection: "column", children: /* @__PURE__ */ jsx14(ThemePicker, { themes: themeList(), current: theme, onPick: handleThemePick, onPreview: (t) => setTheme(t) }) });
10019
+ return /* @__PURE__ */ jsx17(Box16, { flexDirection: "column", children: /* @__PURE__ */ jsx17(ThemePicker, { themes: themeList(), current: theme, onPick: handleThemePick, onPreview: (t) => setTheme(t) }) });
9182
10020
  }
9183
10021
  if (showHelpMenu) {
9184
- return /* @__PURE__ */ jsx14(Box13, { flexDirection: "column", children: /* @__PURE__ */ jsx14(
10022
+ return /* @__PURE__ */ jsx17(Box16, { flexDirection: "column", children: /* @__PURE__ */ jsx17(
9185
10023
  HelpMenu,
9186
10024
  {
9187
10025
  theme,
@@ -9193,10 +10031,79 @@ ${lines.join("\n")}` }]);
9193
10031
  }
9194
10032
  ) });
9195
10033
  }
10034
+ if (commandWizard) {
10035
+ return /* @__PURE__ */ jsx17(Box16, { flexDirection: "column", children: /* @__PURE__ */ jsx17(
10036
+ CommandWizard,
10037
+ {
10038
+ theme,
10039
+ mode: commandWizard.mode,
10040
+ initial: commandWizard.initial,
10041
+ existingNames: customCommandsRef.current.map((c) => c.name),
10042
+ builtinNames: BUILTIN_COMMAND_NAMES,
10043
+ onDone: () => setCommandWizard(null),
10044
+ onSave: handleCommandSave
10045
+ }
10046
+ ) });
10047
+ }
10048
+ if (commandPicker) {
10049
+ return /* @__PURE__ */ jsx17(Box16, { flexDirection: "column", children: /* @__PURE__ */ jsx17(
10050
+ CommandPicker,
10051
+ {
10052
+ theme,
10053
+ commands: customCommandsRef.current,
10054
+ title: commandPicker.mode === "edit" ? "Edit custom command" : "Delete custom command",
10055
+ onPick: (cmd) => {
10056
+ setCommandPicker(null);
10057
+ if (!cmd) return;
10058
+ if (commandPicker.mode === "edit") {
10059
+ setCommandWizard({ mode: "edit", initial: cmd });
10060
+ } else {
10061
+ setCommandToDelete(cmd);
10062
+ }
10063
+ }
10064
+ }
10065
+ ) });
10066
+ }
10067
+ if (commandToDelete) {
10068
+ return /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
10069
+ /* @__PURE__ */ jsxs16(Text17, { color: theme.accent, bold: true, children: [
10070
+ "Delete /",
10071
+ commandToDelete.name,
10072
+ "?"
10073
+ ] }),
10074
+ /* @__PURE__ */ jsx17(Text17, { color: theme.info.color, dimColor: true, children: commandToDelete.filepath }),
10075
+ /* @__PURE__ */ jsx17(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx17(
10076
+ SelectInput7,
10077
+ {
10078
+ items: [
10079
+ { label: "Yes, delete", value: "yes", key: "yes" },
10080
+ { label: "Cancel", value: "cancel", key: "cancel" }
10081
+ ],
10082
+ onSelect: (item) => {
10083
+ if (item.value === "yes") {
10084
+ void handleCommandDelete(commandToDelete);
10085
+ } else {
10086
+ setCommandToDelete(null);
10087
+ }
10088
+ }
10089
+ }
10090
+ ) })
10091
+ ] });
10092
+ }
10093
+ if (showCommandList) {
10094
+ return /* @__PURE__ */ jsx17(Box16, { flexDirection: "column", children: /* @__PURE__ */ jsx17(
10095
+ CommandList,
10096
+ {
10097
+ theme,
10098
+ commands: customCommandsRef.current,
10099
+ onDone: () => setShowCommandList(false)
10100
+ }
10101
+ ) });
10102
+ }
9196
10103
  const hasConversation = events.some((e) => e.kind === "user" || e.kind === "assistant");
9197
- return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
9198
- !hasConversation && events.length === 0 ? /* @__PURE__ */ jsx14(Welcome, { theme, accountId: cfg.accountId }) : /* @__PURE__ */ jsx14(ChatView, { events, showReasoning, theme, verbose }),
9199
- perm ? /* @__PURE__ */ jsx14(
10104
+ return /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", children: [
10105
+ !hasConversation && events.length === 0 ? /* @__PURE__ */ jsx17(Welcome, { theme, accountId: cfg.accountId }) : /* @__PURE__ */ jsx17(ChatView, { events, showReasoning, theme, verbose }),
10106
+ perm ? /* @__PURE__ */ jsx17(
9200
10107
  PermissionModal,
9201
10108
  {
9202
10109
  tool: perm.tool,
@@ -9207,8 +10114,8 @@ ${lines.join("\n")}` }]);
9207
10114
  setPerm(null);
9208
10115
  }
9209
10116
  }
9210
- ) : /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", marginTop: 1, children: [
9211
- tasks.length > 0 && /* @__PURE__ */ jsx14(
10117
+ ) : /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", marginTop: 1, children: [
10118
+ tasks.length > 0 && /* @__PURE__ */ jsx17(
9212
10119
  TaskList,
9213
10120
  {
9214
10121
  tasks,
@@ -9217,11 +10124,11 @@ ${lines.join("\n")}` }]);
9217
10124
  tokensDelta: Math.max(0, (usage?.prompt_tokens ?? 0) - tasksStartTokens)
9218
10125
  }
9219
10126
  ),
9220
- queue.length > 0 && /* @__PURE__ */ jsx14(Box13, { flexDirection: "column", marginBottom: 1, children: queue.map((q, i) => /* @__PURE__ */ jsxs13(Text14, { color: theme.queue.color, dimColor: theme.queue.dim, children: [
10127
+ queue.length > 0 && /* @__PURE__ */ jsx17(Box16, { flexDirection: "column", marginBottom: 1, children: queue.map((q, i) => /* @__PURE__ */ jsxs16(Text17, { color: theme.queue.color, dimColor: theme.queue.dim, children: [
9221
10128
  "\u23F3 ",
9222
10129
  q.display
9223
10130
  ] }, `queue_${i}`)) }),
9224
- /* @__PURE__ */ jsx14(
10131
+ /* @__PURE__ */ jsx17(
9225
10132
  StatusBar,
9226
10133
  {
9227
10134
  model: cfg.model,
@@ -9239,9 +10146,9 @@ ${lines.join("\n")}` }]);
9239
10146
  codeMode
9240
10147
  }
9241
10148
  ),
9242
- /* @__PURE__ */ jsxs13(Box13, { marginTop: 1, children: [
9243
- /* @__PURE__ */ jsx14(Text14, { color: theme.accent, children: "\u203A " }),
9244
- /* @__PURE__ */ jsx14(
10149
+ /* @__PURE__ */ jsxs16(Box16, { marginTop: 1, children: [
10150
+ /* @__PURE__ */ jsx17(Text17, { color: theme.accent, children: "\u203A " }),
10151
+ /* @__PURE__ */ jsx17(
9245
10152
  CustomTextInput,
9246
10153
  {
9247
10154
  value: input,
@@ -9290,7 +10197,7 @@ ${lines.join("\n")}` }]);
9290
10197
  ] });
9291
10198
  }
9292
10199
  async function renderApp(cfg, updateResult) {
9293
- const instance = render(/* @__PURE__ */ jsx14(App, { initialCfg: cfg, initialUpdateResult: updateResult }), {
10200
+ const instance = render(/* @__PURE__ */ jsx17(App, { initialCfg: cfg, initialUpdateResult: updateResult }), {
9294
10201
  incrementalRendering: true
9295
10202
  });
9296
10203
  await instance.waitUntilExit();
@@ -9331,6 +10238,10 @@ var init_app = __esm({
9331
10238
  init_version();
9332
10239
  init_loader();
9333
10240
  init_renderer();
10241
+ init_save();
10242
+ init_command_wizard();
10243
+ init_command_picker();
10244
+ init_command_list();
9334
10245
  FEEDBACK_WORKER_URL = "https://kimiflare-feedback.sina-b35.workers.dev";
9335
10246
  CONTEXT_LIMIT = 262e3;
9336
10247
  AUTO_COMPACT_SUGGEST_PCT = 0.8;