kimiflare 0.24.0 → 0.26.0

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
@@ -18,8 +18,7 @@ function configPath() {
18
18
  }
19
19
  function readReasoningEffortEnv() {
20
20
  const raw = process.env.KIMI_REASONING_EFFORT?.toLowerCase();
21
- if (raw === "low" || raw === "medium" || raw === "high") return raw;
22
- return void 0;
21
+ return EFFORTS.includes(raw ?? "") ? raw : void 0;
23
22
  }
24
23
  function readCoauthorEnv() {
25
24
  const enabled = process.env.KIMIFLARE_COAUTHOR;
@@ -84,6 +83,7 @@ async function loadConfig() {
84
83
  const envMemoryMaxAgeDays = readNumberEnv("KIMIFLARE_MEMORY_MAX_AGE_DAYS");
85
84
  const envMemoryMaxEntries = readNumberEnv("KIMIFLARE_MEMORY_MAX_ENTRIES");
86
85
  const envMemoryEmbeddingModel = process.env.KIMIFLARE_MEMORY_EMBEDDING_MODEL;
86
+ const envPlumbingModel = process.env.KIMIFLARE_PLUMBING_MODEL;
87
87
  const envCodeMode = readBooleanEnv("KIMIFLARE_CODE_MODE");
88
88
  if (envAccount && envToken) {
89
89
  return {
@@ -108,6 +108,7 @@ async function loadConfig() {
108
108
  memoryMaxAgeDays: envMemoryMaxAgeDays,
109
109
  memoryMaxEntries: envMemoryMaxEntries,
110
110
  memoryEmbeddingModel: envMemoryEmbeddingModel,
111
+ plumbingModel: envPlumbingModel,
111
112
  codeMode: envCodeMode
112
113
  };
113
114
  }
@@ -138,6 +139,7 @@ async function loadConfig() {
138
139
  memoryMaxAgeDays: envMemoryMaxAgeDays ?? parsed.memoryMaxAgeDays,
139
140
  memoryMaxEntries: envMemoryMaxEntries ?? parsed.memoryMaxEntries,
140
141
  memoryEmbeddingModel: envMemoryEmbeddingModel ?? parsed.memoryEmbeddingModel,
142
+ plumbingModel: envPlumbingModel ?? parsed.plumbingModel,
141
143
  codeMode: envCodeMode ?? parsed.codeMode
142
144
  };
143
145
  }
@@ -152,10 +154,11 @@ async function saveConfig(cfg) {
152
154
  await chmod(p, 384);
153
155
  return p;
154
156
  }
155
- var DEFAULT_MODEL, DEFAULT_REASONING_EFFORT;
157
+ var EFFORTS, DEFAULT_MODEL, DEFAULT_REASONING_EFFORT;
156
158
  var init_config = __esm({
157
159
  "src/config.ts"() {
158
160
  "use strict";
161
+ EFFORTS = ["low", "medium", "high"];
159
162
  DEFAULT_MODEL = "@cf/moonshotai/kimi-k2.6";
160
163
  DEFAULT_REASONING_EFFORT = "medium";
161
164
  }
@@ -173,10 +176,10 @@ async function* readSSE(stream, signal) {
173
176
  if (done) break;
174
177
  buffer += decoder.decode(value, { stream: true });
175
178
  buffer = buffer.replace(/\r\n/g, "\n");
176
- let sep2;
177
- while ((sep2 = buffer.indexOf("\n\n")) !== -1) {
178
- const event = buffer.slice(0, sep2);
179
- buffer = buffer.slice(sep2 + 2);
179
+ let sep3;
180
+ while ((sep3 = buffer.indexOf("\n\n")) !== -1) {
181
+ const event = buffer.slice(0, sep3);
182
+ buffer = buffer.slice(sep3 + 2);
180
183
  const data = extractData(event);
181
184
  if (data !== null) yield data;
182
185
  }
@@ -209,8 +212,8 @@ var init_errors = __esm({
209
212
  "src/util/errors.ts"() {
210
213
  "use strict";
211
214
  KimiApiError = class extends Error {
212
- constructor(message, code, httpStatus) {
213
- super(message);
215
+ constructor(message2, code, httpStatus) {
216
+ super(message2);
214
217
  this.code = code;
215
218
  this.httpStatus = httpStatus;
216
219
  this.name = "KimiApiError";
@@ -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 * ")}`);
@@ -1027,8 +1031,8 @@ ${jsCode}
1027
1031
  await script.run(context, { timeout, release: true });
1028
1032
  await new Promise((r) => setTimeout(r, 10));
1029
1033
  } catch (err) {
1030
- const message = err instanceof Error ? err.message : String(err);
1031
- return { output: "", logs, error: message, toolCalls };
1034
+ const message2 = err instanceof Error ? err.message : String(err);
1035
+ return { output: "", logs, error: message2, toolCalls };
1032
1036
  } finally {
1033
1037
  isolate.dispose();
1034
1038
  }
@@ -1110,8 +1114,8 @@ ${jsCode}
1110
1114
  await runInNewContext(wrapped, sandbox, { timeout, displayErrors: true });
1111
1115
  await new Promise((r) => setTimeout(r, 10));
1112
1116
  } catch (err) {
1113
- const message = err instanceof Error ? err.message : String(err);
1114
- return { output: "", logs, error: message, toolCalls };
1117
+ const message2 = err instanceof Error ? err.message : String(err);
1118
+ return { output: "", logs, error: message2, toolCalls };
1115
1119
  }
1116
1120
  return { output: logs.join("\n"), logs, toolCalls };
1117
1121
  }
@@ -1119,8 +1123,8 @@ async function runInSandbox(opts2) {
1119
1123
  try {
1120
1124
  return await runWithIsolatedVm(opts2);
1121
1125
  } catch (err) {
1122
- const message = err instanceof Error ? err.message : String(err);
1123
- if (message.includes("isolated-vm") || message.includes("Cannot find module") || message.includes("bindings")) {
1126
+ const message2 = err instanceof Error ? err.message : String(err);
1127
+ if (message2.includes("isolated-vm") || message2.includes("Cannot find module") || message2.includes("bindings")) {
1124
1128
  return runWithNodeVm(opts2);
1125
1129
  }
1126
1130
  return runWithNodeVm(opts2);
@@ -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
 
@@ -1711,6 +1753,9 @@ function resolvePath(cwd, input) {
1711
1753
  }
1712
1754
  return isAbsolute(input) ? input : resolve(cwd, input);
1713
1755
  }
1756
+ function isPathOutside(relPath) {
1757
+ return relPath === ".." || relPath.startsWith(`..${sep}`) || isAbsolute(relPath);
1758
+ }
1714
1759
  function collapsePath(input, cwd, maxLen = 40) {
1715
1760
  if (!input) return input;
1716
1761
  let abs;
@@ -2839,6 +2884,16 @@ var init_expand_artifact = __esm({
2839
2884
  });
2840
2885
 
2841
2886
  // src/tools/executor.ts
2887
+ function isDiffCommand(cmd) {
2888
+ const trimmed = cmd.trim();
2889
+ if (/^git\s+show(?:\s|$)/.test(trimmed)) return true;
2890
+ if (/^git\s+diff(?:\s|$)/.test(trimmed)) return true;
2891
+ if (/^git\s+format-patch(?:\s|$)/.test(trimmed)) return true;
2892
+ const hasPatchFlag = /(?:^|\s)(?:-p|--patch)(?:\s|$)/.test(trimmed);
2893
+ if (/^git\s+log(?:\s|$)/.test(trimmed) && hasPatchFlag) return true;
2894
+ if (/^git\s+stash\s+show(?:\s|$)/.test(trimmed) && hasPatchFlag) return true;
2895
+ return false;
2896
+ }
2842
2897
  function normalizeToolOutput(result) {
2843
2898
  if (typeof result === "string") {
2844
2899
  const bytes = Buffer.byteLength(result, "utf8");
@@ -2941,6 +2996,20 @@ var init_executor = __esm({
2941
2996
  try {
2942
2997
  const result = await tool.run(args, ctx);
2943
2998
  const normalized = normalizeToolOutput(result);
2999
+ const cmd = call.name === "bash" && typeof args.command === "string" ? args.command : "";
3000
+ if (isDiffCommand(cmd)) {
3001
+ const artifactId = this.artifactStore.store(normalized.content);
3002
+ const bytes = Buffer.byteLength(normalized.content, "utf8");
3003
+ return {
3004
+ tool_call_id: call.id,
3005
+ name: call.name,
3006
+ content: normalized.content,
3007
+ ok: true,
3008
+ rawBytes: bytes,
3009
+ reducedBytes: bytes,
3010
+ artifactId
3011
+ };
3012
+ }
2944
3013
  const reduced = reduceToolOutput(
2945
3014
  call.name,
2946
3015
  normalized.content,
@@ -3087,13 +3156,17 @@ import { fileURLToPath as fileURLToPath2 } from "url";
3087
3156
  import { dirname as dirname3, join as join7 } from "path";
3088
3157
  function getAppVersion() {
3089
3158
  if (cachedVersion !== null) return cachedVersion;
3090
- try {
3091
- const here = dirname3(fileURLToPath2(import.meta.url));
3092
- const pkg = JSON.parse(readFileSync2(join7(here, "..", "..", "package.json"), "utf8"));
3093
- cachedVersion = pkg.version ?? "0.0.0";
3094
- } catch {
3095
- cachedVersion = "0.0.0";
3159
+ const here = dirname3(fileURLToPath2(import.meta.url));
3160
+ const candidates = [join7(here, "..", "..", "package.json"), join7(here, "..", "package.json")];
3161
+ for (const path of candidates) {
3162
+ try {
3163
+ const pkg = JSON.parse(readFileSync2(path, "utf8"));
3164
+ cachedVersion = pkg.version ?? "0.0.0";
3165
+ return cachedVersion;
3166
+ } catch {
3167
+ }
3096
3168
  }
3169
+ cachedVersion = "0.0.0";
3097
3170
  return cachedVersion;
3098
3171
  }
3099
3172
  var cachedVersion;
@@ -3205,6 +3278,39 @@ function emptySessionState(task = "") {
3205
3278
  artifact_index: {}
3206
3279
  };
3207
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
+ }
3208
3314
  function formatRecalledArtifacts(recalled) {
3209
3315
  if (recalled.length === 0) return "";
3210
3316
  const lines = ["[recalled artifacts]"];
@@ -3577,11 +3683,12 @@ function recallArtifacts(messages, store, state) {
3577
3683
  }
3578
3684
  for (const failure of state.recent_failures) {
3579
3685
  const keyword = failure.split(":")[0];
3580
- if (keyword && text.toLowerCase().includes(keyword.toLowerCase())) {
3581
- for (const [id, meta] of Object.entries(state.artifact_index)) {
3582
- if (meta.source === "bash" && !ids.includes(id)) {
3583
- ids.push(id);
3584
- }
3686
+ if (!keyword) continue;
3687
+ const lowerKeyword = keyword.toLowerCase();
3688
+ if (!text.toLowerCase().includes(lowerKeyword)) continue;
3689
+ for (const [id, meta] of Object.entries(state.artifact_index)) {
3690
+ if (meta.source === "bash" && !ids.includes(id) && meta.summary.toLowerCase().includes(lowerKeyword)) {
3691
+ ids.push(id);
3585
3692
  }
3586
3693
  }
3587
3694
  }
@@ -4069,6 +4176,12 @@ var init_chat = __esm({
4069
4176
  evt.text
4070
4177
  ] });
4071
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
+ }
4072
4185
  return /* @__PURE__ */ jsxs4(Text4, { color: theme.error, children: [
4073
4186
  "! ",
4074
4187
  evt.text
@@ -4153,14 +4266,14 @@ function buildRightParts(usage, contextLimit, sessionUsage, gatewayMeta) {
4153
4266
  parts.push(`in ${sessionUsage.promptTokens}${cached ? ` (${cached} cached)` : ""}`);
4154
4267
  parts.push(`out ${sessionUsage.completionTokens}`);
4155
4268
  parts.push(`ctx ${pct}%`);
4156
- parts.push(`${sessionUsage.cost.toFixed(5)}`);
4269
+ parts.push(`$${sessionUsage.cost.toFixed(5)}`);
4157
4270
  } else {
4158
4271
  const cached = usage.prompt_tokens_details?.cached_tokens ?? 0;
4159
4272
  const cost = calculateCost(usage.prompt_tokens, usage.completion_tokens, cached);
4160
4273
  parts.push(`in ${usage.prompt_tokens}${cached ? ` (${cached} cached)` : ""}`);
4161
4274
  parts.push(`out ${usage.completion_tokens}`);
4162
4275
  parts.push(`ctx ${pct}%`);
4163
- parts.push(`${cost.total.toFixed(5)}`);
4276
+ parts.push(`$${cost.total.toFixed(5)}`);
4164
4277
  }
4165
4278
  const gatewayCache = formatGatewayCacheStatus(gatewayMeta);
4166
4279
  if (gatewayCache) parts.push(gatewayCache);
@@ -4910,18 +5023,18 @@ var init_source = __esm({
4910
5023
  }
4911
5024
  }
4912
5025
  });
4913
- createStyler = (open, close, parent) => {
5026
+ createStyler = (open3, close, parent) => {
4914
5027
  let openAll;
4915
5028
  let closeAll;
4916
5029
  if (parent === void 0) {
4917
- openAll = open;
5030
+ openAll = open3;
4918
5031
  closeAll = close;
4919
5032
  } else {
4920
- openAll = parent.openAll + open;
5033
+ openAll = parent.openAll + open3;
4921
5034
  closeAll = close + parent.closeAll;
4922
5035
  }
4923
5036
  return {
4924
- open,
5037
+ open: open3,
4925
5038
  close,
4926
5039
  openAll,
4927
5040
  closeAll,
@@ -5367,6 +5480,245 @@ var init_welcome = __esm({
5367
5480
  }
5368
5481
  });
5369
5482
 
5483
+ // src/ui/help-menu.tsx
5484
+ import { useState as useState6 } from "react";
5485
+ import { Box as Box12, Text as Text13, useInput as useInput2 } from "ink";
5486
+ import SelectInput4 from "ink-select-input";
5487
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
5488
+ function HelpMenu({ theme, themes, currentThemeName, customCommands, onDone, onCommand }) {
5489
+ const [page, setPage] = useState6("main");
5490
+ const customs = customCommands ?? [];
5491
+ useInput2((_input, key) => {
5492
+ if (key.escape) {
5493
+ if (page !== "main") {
5494
+ setPage("main");
5495
+ } else {
5496
+ onDone();
5497
+ }
5498
+ }
5499
+ });
5500
+ const handleSelect = (command) => {
5501
+ onCommand(command);
5502
+ onDone();
5503
+ };
5504
+ if (page === "main") {
5505
+ const items2 = CATEGORIES.map((cat) => ({
5506
+ label: cat.label,
5507
+ value: cat.key,
5508
+ key: cat.key
5509
+ }));
5510
+ if (customs.length > 0) {
5511
+ items2.push({ label: "Run custom commands", value: "custom", key: "custom" });
5512
+ }
5513
+ items2.push({ label: "(close)", value: "__close__", key: "__close__" });
5514
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
5515
+ /* @__PURE__ */ jsx13(Text13, { color: theme.accent, bold: true, children: "Help" }),
5516
+ /* @__PURE__ */ jsx13(Text13, { color: theme.info.color, dimColor: false, children: "Arrow keys to navigate, Enter to select, Esc to close." }),
5517
+ /* @__PURE__ */ jsx13(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx13(
5518
+ SelectInput4,
5519
+ {
5520
+ items: items2,
5521
+ onSelect: (item) => {
5522
+ if (item.value === "__close__") {
5523
+ onDone();
5524
+ } else {
5525
+ setPage(item.value);
5526
+ }
5527
+ }
5528
+ }
5529
+ ) }),
5530
+ /* @__PURE__ */ jsx13(Box12, { marginTop: 1, flexDirection: "column", children: SINGLE_COMMANDS.map((cmd) => /* @__PURE__ */ jsx13(Text13, { color: theme.info.color, dimColor: false, children: ` ${cmd.command.padEnd(20)} ${cmd.description}` }, cmd.command)) }),
5531
+ /* @__PURE__ */ jsx13(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx13(Text13, { color: theme.info.color, dimColor: false, children: "keys: ctrl-c interrupt/exit \xB7 ctrl-r toggle reasoning \xB7 ctrl-o verbose \xB7 ctrl+t theme \xB7 shift+tab cycle mode \xB7 \u2191/\u2193 history" }) })
5532
+ ] });
5533
+ }
5534
+ if (page === "theme") {
5535
+ const items2 = themes.map((t) => ({
5536
+ label: t.name === currentThemeName ? `${t.label} \xB7 current` : t.label,
5537
+ value: t.name,
5538
+ key: t.name
5539
+ }));
5540
+ items2.push({ label: "\u2190 Back", value: "__back__", key: "__back__" });
5541
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
5542
+ /* @__PURE__ */ jsx13(Text13, { color: theme.accent, bold: true, children: "Theme" }),
5543
+ /* @__PURE__ */ jsx13(Text13, { color: theme.info.color, dimColor: false, children: "Arrow keys to navigate, Enter to apply, Esc to go back." }),
5544
+ /* @__PURE__ */ jsx13(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx13(
5545
+ SelectInput4,
5546
+ {
5547
+ items: items2,
5548
+ onSelect: (item) => {
5549
+ if (item.value === "__back__") {
5550
+ setPage("main");
5551
+ } else {
5552
+ handleSelect(`/theme ${item.value}`);
5553
+ }
5554
+ }
5555
+ }
5556
+ ) })
5557
+ ] });
5558
+ }
5559
+ if (page === "custom") {
5560
+ const items2 = customs.map((c) => ({
5561
+ label: `${`/${c.name}`.padEnd(28)} ${c.description ?? ""}`.trimEnd(),
5562
+ value: `/${c.name}`,
5563
+ key: c.name
5564
+ }));
5565
+ items2.push({ label: "\u2190 Back", value: "__back__", key: "__back__" });
5566
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
5567
+ /* @__PURE__ */ jsx13(Text13, { color: theme.accent, bold: true, children: "Custom commands" }),
5568
+ /* @__PURE__ */ jsx13(Text13, { color: theme.info.color, dimColor: false, children: customs.length === 0 ? "no custom commands found in .kimiflare/commands/" : "Arrow keys to navigate, Enter to run, Esc to go back." }),
5569
+ /* @__PURE__ */ jsx13(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx13(
5570
+ SelectInput4,
5571
+ {
5572
+ items: items2,
5573
+ onSelect: (item) => {
5574
+ if (item.value === "__back__") {
5575
+ setPage("main");
5576
+ } else {
5577
+ handleSelect(item.value);
5578
+ }
5579
+ }
5580
+ }
5581
+ ) })
5582
+ ] });
5583
+ }
5584
+ const category = CATEGORIES.find((c) => c.key === page);
5585
+ const selectable = category.commands.filter((cmd) => cmd.selectable !== false);
5586
+ const staticCmds = category.commands.filter((cmd) => cmd.selectable === false);
5587
+ const items = selectable.map((cmd) => ({
5588
+ label: `${cmd.command.padEnd(28)} ${cmd.description}`,
5589
+ value: cmd.command,
5590
+ key: cmd.command
5591
+ }));
5592
+ items.push({ label: "\u2190 Back", value: "__back__", key: "__back__" });
5593
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
5594
+ /* @__PURE__ */ jsx13(Text13, { color: theme.accent, bold: true, children: category.label }),
5595
+ /* @__PURE__ */ jsx13(Text13, { color: theme.info.color, dimColor: false, children: "Arrow keys to navigate, Enter to execute, Esc to go back." }),
5596
+ /* @__PURE__ */ jsx13(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx13(
5597
+ SelectInput4,
5598
+ {
5599
+ items,
5600
+ onSelect: (item) => {
5601
+ if (item.value === "__back__") {
5602
+ setPage("main");
5603
+ } else {
5604
+ handleSelect(item.value);
5605
+ }
5606
+ }
5607
+ }
5608
+ ) }),
5609
+ staticCmds.length > 0 && /* @__PURE__ */ jsx13(Box12, { marginTop: 1, flexDirection: "column", children: staticCmds.map((cmd) => /* @__PURE__ */ jsx13(Text13, { color: theme.info.color, dimColor: true, children: ` ${cmd.command.padEnd(28)} ${cmd.description}` }, cmd.command)) })
5610
+ ] });
5611
+ }
5612
+ var CATEGORIES, SINGLE_COMMANDS;
5613
+ var init_help_menu = __esm({
5614
+ "src/ui/help-menu.tsx"() {
5615
+ "use strict";
5616
+ CATEGORIES = [
5617
+ {
5618
+ key: "mode",
5619
+ label: "Mode",
5620
+ commands: [
5621
+ { command: "/mode edit", description: "switch to edit mode" },
5622
+ { command: "/mode plan", description: "switch to plan mode" },
5623
+ { command: "/mode auto", description: "switch to auto mode" }
5624
+ ]
5625
+ },
5626
+ {
5627
+ key: "thinking",
5628
+ label: "Thinking",
5629
+ commands: [
5630
+ { command: "/thinking low", description: "fast, lower quality" },
5631
+ { command: "/thinking medium", description: "balanced" },
5632
+ { command: "/thinking high", description: "slow, higher quality" }
5633
+ ]
5634
+ },
5635
+ {
5636
+ key: "theme",
5637
+ label: "Theme",
5638
+ commands: []
5639
+ },
5640
+ {
5641
+ key: "session",
5642
+ label: "Session",
5643
+ commands: [
5644
+ { command: "/resume", description: "pick a past conversation" },
5645
+ { command: "/compact", description: "summarize old turns to free context" },
5646
+ { command: "/clear", description: "clear current conversation" }
5647
+ ]
5648
+ },
5649
+ {
5650
+ key: "memory",
5651
+ label: "Memory",
5652
+ commands: [
5653
+ { command: "/memory", description: "show memory stats" },
5654
+ { command: "/memory on", description: "enable memory" },
5655
+ { command: "/memory off", description: "disable memory" },
5656
+ { command: "/memory clear", description: "wipe memories for this repo" },
5657
+ { command: "/memory search <query>", description: "search stored memories", selectable: false }
5658
+ ]
5659
+ },
5660
+ {
5661
+ key: "mcp",
5662
+ label: "MCP",
5663
+ commands: [
5664
+ { command: "/mcp list", description: "list connected MCP servers and tools" },
5665
+ { command: "/mcp reload", description: "reconnect all configured MCP servers" }
5666
+ ]
5667
+ },
5668
+ {
5669
+ key: "gateway",
5670
+ label: "Gateway",
5671
+ commands: [
5672
+ { command: "/gateway", description: "show gateway status" },
5673
+ { command: "/gateway off", description: "disable AI Gateway (direct Workers AI)" },
5674
+ { command: "/gateway skip-cache true", description: "enable skip-cache" },
5675
+ { command: "/gateway skip-cache false", description: "disable skip-cache" },
5676
+ { command: "/gateway collect-logs true", description: "enable log collection" },
5677
+ { command: "/gateway collect-logs false", description: "disable log collection" },
5678
+ { command: "/gateway metadata clear", description: "remove all metadata" },
5679
+ { command: "/gateway <id>", description: "enable AI Gateway", selectable: false },
5680
+ { command: "/gateway cache-ttl <seconds>", description: "set cache TTL", selectable: false },
5681
+ { command: "/gateway metadata <key>=<value>", description: "add metadata", selectable: false }
5682
+ ]
5683
+ },
5684
+ {
5685
+ key: "info",
5686
+ label: "Info",
5687
+ commands: [
5688
+ { command: "/cost", description: "show cost report" },
5689
+ { command: "/model", description: "show current model" },
5690
+ { command: "/update", description: "check for updates" },
5691
+ { command: "/hello", description: "send a voice note to the creator" },
5692
+ { command: "/community", description: "join our Discord server" }
5693
+ ]
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
+ },
5705
+ {
5706
+ key: "config",
5707
+ label: "Config",
5708
+ commands: [
5709
+ { command: "/init", description: "scan this repo and write a KIMI.md" },
5710
+ { command: "/logout", description: "clear credentials" }
5711
+ ]
5712
+ }
5713
+ ];
5714
+ SINGLE_COMMANDS = [
5715
+ { command: "/reasoning", description: "toggle show/hide model reasoning" },
5716
+ { command: "/help", description: "show this menu" },
5717
+ { command: "/exit", description: "exit kimiflare" }
5718
+ ];
5719
+ }
5720
+ });
5721
+
5370
5722
  // src/ui/theme.ts
5371
5723
  function resolveTheme(name) {
5372
5724
  if (!name) return THEMES[DEFAULT_THEME_NAME];
@@ -6164,11 +6516,11 @@ function updateAccessedAt(db, ids) {
6164
6516
  function searchMemoriesFts(db, query, repoPath, limit = 50) {
6165
6517
  const sql = repoPath ? `SELECT m.*, rank FROM memories m
6166
6518
  JOIN memories_fts fts ON m.rowid = fts.rowid
6167
- 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'
6168
6520
  ORDER BY rank
6169
6521
  LIMIT ?` : `SELECT m.*, rank FROM memories m
6170
6522
  JOIN memories_fts fts ON m.rowid = fts.rowid
6171
- 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'
6172
6524
  ORDER BY rank
6173
6525
  LIMIT ?`;
6174
6526
  const params = repoPath ? [`${query}*`, repoPath, limit] : [`${query}*`, limit];
@@ -6181,7 +6533,7 @@ function searchMemoriesFts(db, query, repoPath, limit = 50) {
6181
6533
  function listMemoriesForVectorSearch(db, repoPath, since, limit = 2e3) {
6182
6534
  const rows = db.prepare(
6183
6535
  `SELECT * FROM memories
6184
- 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'
6185
6537
  ORDER BY accessed_at DESC
6186
6538
  LIMIT ?`
6187
6539
  ).all(repoPath, since, limit);
@@ -6628,7 +6980,20 @@ function redactSecrets(text) {
6628
6980
  }
6629
6981
  return result;
6630
6982
  }
6631
- 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;
6632
6997
  var init_manager2 = __esm({
6633
6998
  "src/memory/manager.ts"() {
6634
6999
  "use strict";
@@ -6656,19 +7021,12 @@ Return a JSON object:
6656
7021
  "confidence": "high" | "medium" | "low",
6657
7022
  "corrected_content": string | null // if minor correction needed, provide it; otherwise null
6658
7023
  }`;
6659
- 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.
6660
-
6661
- Rules:
6662
- - Topic keys are lowercase snake_case, max 3 words.
6663
- - If the new memory is about the same topic as an existing key, return the existing key.
6664
- - If it's a genuinely new topic, generate a new normalized key.
6665
- - Return ONLY the topic key string, nothing else.`;
6666
7024
  HYPOTHETICAL_QUERIES_SYSTEM = `Given a memory, generate 3-5 short search queries a user might type to find it.
6667
7025
  Cover different phrasings: declarative, interrogative, and keyword-based.
6668
7026
 
6669
7027
  Return a JSON array of strings. Example:
6670
7028
  ["what package manager does this project use?", "pnpm preference", "user likes pnpm over npm"]`;
6671
- MemoryManager = class {
7029
+ MemoryManager = class _MemoryManager {
6672
7030
  db = null;
6673
7031
  opts;
6674
7032
  constructor(opts2) {
@@ -6696,6 +7054,14 @@ Return a JSON array of strings. Example:
6696
7054
  gateway: this.opts.gateway
6697
7055
  };
6698
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
+ }
6699
7065
  shouldRedact() {
6700
7066
  return this.opts.redactSecrets !== false;
6701
7067
  }
@@ -6716,7 +7082,7 @@ Return a JSON array of strings. Example:
6716
7082
  if (verified.corrected_content) {
6717
7083
  safeContent = verified.corrected_content;
6718
7084
  }
6719
- const topicKey = await this.normalizeTopicKey(safeContent, repoPath, signal);
7085
+ const topicKey = this.normalizeTopicKey(safeContent, repoPath);
6720
7086
  const supersededIds = [];
6721
7087
  if (topicKey) {
6722
7088
  const existing = findMemoriesByTopicKey(this.db, repoPath, topicKey);
@@ -6768,6 +7134,38 @@ Return a JSON array of strings. Example:
6768
7134
  }
6769
7135
  return retrieveMemories({ db: this.db, query });
6770
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
+ }
6771
7169
  /**
6772
7170
  * Soft-delete a memory by ID.
6773
7171
  */
@@ -6827,7 +7225,7 @@ Return a JSON array of strings. Example:
6827
7225
  }
6828
7226
  async verifyMemory(content, signal) {
6829
7227
  const text = await runKimiText({
6830
- ...this.llmOpts,
7228
+ ...this.plumbingLlmOpts,
6831
7229
  signal,
6832
7230
  messages: [
6833
7231
  { role: "system", content: VERIFY_SYSTEM },
@@ -6851,31 +7249,13 @@ Context: This memory was explicitly provided by the user during a conversation.`
6851
7249
  const corrected = typeof rec.corrected_content === "string" ? rec.corrected_content : null;
6852
7250
  return { valid, corrected_content: corrected };
6853
7251
  }
6854
- async normalizeTopicKey(content, repoPath, signal) {
7252
+ normalizeTopicKey(content, repoPath) {
6855
7253
  const existingKeys = listTopicKeys(this.db, repoPath);
6856
- const keysBlock = existingKeys.length > 0 ? existingKeys.join("\n") : "(none yet)";
6857
- const text = await runKimiText({
6858
- ...this.llmOpts,
6859
- signal,
6860
- messages: [
6861
- { role: "system", content: TOPIC_KEY_SYSTEM },
6862
- {
6863
- role: "user",
6864
- content: `Existing topic keys:
6865
- ${keysBlock}
6866
-
6867
- New memory: "${content}"
6868
-
6869
- Return only the topic key string.`
6870
- }
6871
- ]
6872
- });
6873
- const key = text.trim().toLowerCase().replace(/\s+/g, "_").replace(/[^a-z0-9_]/g, "").slice(0, 60);
6874
- return key || null;
7254
+ return pickTopicKey(content, existingKeys);
6875
7255
  }
6876
7256
  async generateHypotheticalQueries(content, signal) {
6877
7257
  const text = await runKimiText({
6878
- ...this.llmOpts,
7258
+ ...this.plumbingLlmOpts,
6879
7259
  signal,
6880
7260
  messages: [
6881
7261
  { role: "system", content: HYPOTHETICAL_QUERIES_SYSTEM },
@@ -6932,133 +7312,1036 @@ var init_state = __esm({
6932
7312
  }
6933
7313
  });
6934
7314
 
6935
- // src/app.tsx
6936
- var app_exports = {};
6937
- __export(app_exports, {
6938
- renderApp: () => renderApp
6939
- });
6940
- import { useState as useState6, useRef as useRef3, useEffect as useEffect4, useCallback } from "react";
6941
- import { Box as Box12, Text as Text13, useApp, useInput as useInput2, render } from "ink";
6942
- import { existsSync } from "fs";
6943
- import { join as join12 } from "path";
6944
- import { unlink as unlink2 } from "fs/promises";
6945
- import { spawn as spawn2 } from "child_process";
6946
- import { platform as platform2 } from "os";
6947
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
6948
- function gatewayFromConfig(cfg) {
6949
- if (!cfg.aiGatewayId) return void 0;
6950
- return {
6951
- id: cfg.aiGatewayId,
6952
- cacheTtl: cfg.aiGatewayCacheTtl,
6953
- skipCache: cfg.aiGatewaySkipCache,
6954
- collectLogPayload: cfg.aiGatewayCollectLogPayload,
6955
- metadata: cfg.aiGatewayMetadata
6956
- };
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
+ }
7328
+ function parseFrontmatter(input) {
7329
+ const errors = [];
7330
+ if (!FENCE.test(input)) {
7331
+ return { data: {}, body: input, errors };
7332
+ }
7333
+ const afterOpen = input.replace(FENCE, "");
7334
+ const closeIdx = afterOpen.search(/\r?\n---\s*(\r?\n|$)/);
7335
+ if (closeIdx === -1) {
7336
+ errors.push("frontmatter not closed with ---");
7337
+ return { data: {}, body: input, errors };
7338
+ }
7339
+ const yaml = afterOpen.slice(0, closeIdx);
7340
+ const closeMatch = afterOpen.slice(closeIdx).match(/\r?\n---\s*(\r?\n|$)/);
7341
+ const body = closeMatch ? afterOpen.slice(closeIdx + closeMatch[0].length) : "";
7342
+ const data = {};
7343
+ const lines = yaml.split(/\r?\n/);
7344
+ for (const line of lines) {
7345
+ if (line.trim() === "" || line.trim().startsWith("#")) continue;
7346
+ const m = line.match(KV);
7347
+ if (!m) {
7348
+ errors.push(`unparseable line: ${line.trim()}`);
7349
+ continue;
7350
+ }
7351
+ const key = m[1];
7352
+ let value = m[2] ?? "";
7353
+ if (value.startsWith('"') && value.endsWith('"') && value.length >= 2 || value.startsWith("'") && value.endsWith("'") && value.length >= 2) {
7354
+ value = value.slice(1, -1);
7355
+ }
7356
+ data[key] = value;
7357
+ }
7358
+ return { data, body, errors };
6957
7359
  }
6958
- function gatewayUsageLookupFromConfig(cfg, meta) {
6959
- if (!cfg.aiGatewayId || !meta) return void 0;
7360
+ var FENCE, KV;
7361
+ var init_frontmatter = __esm({
7362
+ "src/commands/frontmatter.ts"() {
7363
+ "use strict";
7364
+ FENCE = /^---\s*\r?\n/;
7365
+ KV = /^([A-Za-z][\w-]*)\s*:\s*(.*?)\s*$/;
7366
+ }
7367
+ });
7368
+
7369
+ // src/commands/loader.ts
7370
+ import { open, realpath } from "fs/promises";
7371
+ import { homedir as homedir9 } from "os";
7372
+ import { join as join12, relative as relative2, sep as sep2 } from "path";
7373
+ import fg3 from "fast-glob";
7374
+ function projectCommandsDir(cwd = process.cwd()) {
7375
+ return join12(cwd, ".kimiflare", "commands");
7376
+ }
7377
+ function globalCommandsDir() {
7378
+ const xdg = process.env.XDG_CONFIG_HOME || join12(homedir9(), ".config");
7379
+ return join12(xdg, "kimiflare", "commands");
7380
+ }
7381
+ async function loadCustomCommands(cwd = process.cwd()) {
7382
+ const warnings = [];
7383
+ const byName = /* @__PURE__ */ new Map();
7384
+ const sources = [
7385
+ { dir: globalCommandsDir(), source: "global" },
7386
+ { dir: projectCommandsDir(cwd), source: "project" }
7387
+ ];
7388
+ const perSource = await Promise.all(
7389
+ sources.map(async ({ dir, source }) => {
7390
+ const safeDir = await resolveSafeDir(dir, source, cwd, warnings);
7391
+ if (safeDir === null) return [];
7392
+ const files = await fg3("**/*.md", {
7393
+ cwd: safeDir,
7394
+ absolute: true,
7395
+ onlyFiles: true,
7396
+ followSymbolicLinks: false,
7397
+ suppressErrors: true
7398
+ });
7399
+ return Promise.all(files.map((file) => loadOne(file, safeDir, source, warnings)));
7400
+ })
7401
+ );
7402
+ for (const loaded of perSource) {
7403
+ for (const cmd of loaded) {
7404
+ if (cmd) byName.set(cmd.name, cmd);
7405
+ }
7406
+ }
6960
7407
  return {
6961
- accountId: cfg.accountId,
6962
- apiToken: cfg.apiToken,
6963
- gatewayId: cfg.aiGatewayId,
6964
- meta
7408
+ commands: [...byName.values()].sort((a, b) => a.name.localeCompare(b.name)),
7409
+ warnings
6965
7410
  };
6966
7411
  }
6967
- function openBrowser(url) {
6968
- const cmd = platform2() === "darwin" ? "open" : platform2() === "win32" ? "start" : "xdg-open";
6969
- const child = spawn2(cmd, [url], { detached: true, stdio: "ignore" });
6970
- child.unref();
6971
- }
6972
- function capEvents(prev) {
6973
- if (prev.length <= MAX_EVENTS) return prev;
6974
- return prev.slice(prev.length - MAX_EVENTS);
7412
+ async function resolveSafeDir(dir, source, cwd, warnings) {
7413
+ let realDir;
7414
+ try {
7415
+ realDir = await realpath(dir);
7416
+ } catch (err) {
7417
+ const code = err.code;
7418
+ if (code !== "ENOENT" && code !== "ENOTDIR") {
7419
+ warnings.push(`commands dir ${dir} unreadable: ${err.message}`);
7420
+ }
7421
+ return null;
7422
+ }
7423
+ if (source === "project") {
7424
+ let realCwd;
7425
+ try {
7426
+ realCwd = await realpath(cwd);
7427
+ } catch {
7428
+ return null;
7429
+ }
7430
+ const rel = relative2(realCwd, realDir);
7431
+ if (rel !== "" && isPathOutside(rel)) {
7432
+ warnings.push(`commands dir ${dir} escapes workspace via symlink \u2014 skipped`);
7433
+ return null;
7434
+ }
7435
+ }
7436
+ return realDir;
6975
7437
  }
6976
- function compactEventsVisual(prev, keepLastTurns) {
6977
- let seen = 0;
6978
- let cutoff = -1;
6979
- for (let i = prev.length - 1; i >= 0; i--) {
6980
- if (prev[i].kind === "user") {
6981
- seen++;
6982
- if (seen === keepLastTurns + 1) {
6983
- cutoff = i;
6984
- break;
7438
+ async function loadOne(file, rootDir, source, warnings) {
7439
+ let content;
7440
+ try {
7441
+ const handle = await open(file, "r");
7442
+ try {
7443
+ const stats = await handle.stat();
7444
+ if (stats.size > MAX_COMMAND_FILE_BYTES) {
7445
+ warnings.push(`command file ${file} exceeds ${MAX_COMMAND_FILE_BYTES} bytes \u2014 skipped`);
7446
+ return null;
6985
7447
  }
7448
+ content = await handle.readFile("utf8");
7449
+ } finally {
7450
+ await handle.close();
6986
7451
  }
7452
+ } catch (e) {
7453
+ warnings.push(`failed to read command file ${file}: ${e.message}`);
7454
+ return null;
6987
7455
  }
6988
- if (cutoff <= 0) return prev;
6989
- const kept = prev.slice(cutoff);
6990
- return [
6991
- { kind: "info", key: mkKey(), text: `\xB7\xB7\xB7 ${cutoff} earlier messages compacted \xB7\xB7\xB7` },
6992
- ...kept
6993
- ];
6994
- }
6995
- function makePrefixMessages(cacheStable, model, mode, tools) {
6996
- if (cacheStable) {
6997
- return buildSystemMessages({ cwd: process.cwd(), tools, model, mode });
7456
+ const name = filenameToCommandName(file, rootDir);
7457
+ if (!name) {
7458
+ warnings.push(`invalid command name from ${file}`);
7459
+ return null;
6998
7460
  }
6999
- return [
7000
- {
7001
- role: "system",
7002
- content: buildSystemPrompt({ cwd: process.cwd(), tools, model, mode })
7461
+ const { data, body, errors } = parseFrontmatter(content);
7462
+ if (errors.length > 0) {
7463
+ warnings.push(`frontmatter errors in ${file}: ${errors.join("; ")} \u2014 skipped`);
7464
+ return null;
7465
+ }
7466
+ const cmd = {
7467
+ name,
7468
+ template: body,
7469
+ source,
7470
+ filepath: file
7471
+ };
7472
+ if (data.description) cmd.description = data.description;
7473
+ const modeRaw = data.mode ?? data.agent;
7474
+ if (modeRaw !== void 0) {
7475
+ const normalized = modeRaw === "build" ? "edit" : modeRaw;
7476
+ if (MODES.includes(normalized)) {
7477
+ cmd.mode = normalized;
7478
+ } else {
7479
+ warnings.push(`unknown mode "${modeRaw}" in ${file} \u2014 ignored`);
7003
7480
  }
7004
- ];
7005
- }
7006
- function findImagePaths(text) {
7007
- const paths = [];
7008
- for (const token of text.split(/\s+/)) {
7009
- const clean = token.replace(/^["']|["',;:!?]$/g, "").replace(/[.,;:!?]$/, "");
7010
- if (isImagePath(clean) && existsSync(clean)) {
7011
- paths.push(clean);
7481
+ }
7482
+ if (data.model !== void 0 && data.model !== "") {
7483
+ cmd.model = data.model;
7484
+ }
7485
+ if (data.effort !== void 0) {
7486
+ if (EFFORTS.includes(data.effort)) {
7487
+ cmd.effort = data.effort;
7488
+ } else {
7489
+ warnings.push(`unknown effort "${data.effort}" in ${file} \u2014 ignored`);
7012
7490
  }
7013
7491
  }
7014
- return [...new Set(paths)];
7492
+ return cmd;
7015
7493
  }
7016
- function App({ initialCfg, initialUpdateResult }) {
7017
- const { exit } = useApp();
7018
- const [cfg, setCfg] = useState6(initialCfg);
7019
- const [events, setRawEvents] = useState6([]);
7020
- const setEvents = useCallback(
7021
- (updater) => {
7022
- setRawEvents((prev) => {
7023
- const next = typeof updater === "function" ? updater(prev) : updater;
7024
- return capEvents(next);
7025
- });
7026
- },
7027
- []
7028
- );
7029
- const [input, setInput] = useState6("");
7030
- const [busy, setBusy] = useState6(false);
7031
- const [usage, setUsage] = useState6(null);
7032
- const [sessionUsage, setSessionUsage] = useState6(null);
7033
- const [gatewayMeta, setGatewayMeta] = useState6(null);
7034
- const [showReasoning, setShowReasoning] = useState6(false);
7035
- const [perm, setPerm] = useState6(null);
7036
- const [queue, setQueue] = useState6([]);
7037
- const [history, setHistory] = useState6([]);
7038
- const [historyIndex, setHistoryIndex] = useState6(-1);
7039
- const [draftInput, setDraftInput] = useState6("");
7040
- const [mode, setMode] = useState6("edit");
7041
- const [codeMode, setCodeMode] = useState6(initialCfg?.codeMode ?? false);
7042
- const [effort, setEffort] = useState6(
7043
- initialCfg?.reasoningEffort ?? DEFAULT_REASONING_EFFORT
7044
- );
7045
- const [theme, setTheme] = useState6(resolveTheme(initialCfg?.theme));
7046
- const [resumeSessions, setResumeSessions] = useState6(null);
7047
- const [showThemePicker, setShowThemePicker] = useState6(false);
7048
- const [originalTheme, setOriginalTheme] = useState6(null);
7049
- const [tasks, setTasks] = useState6([]);
7050
- const [tasksStartedAt, setTasksStartedAt] = useState6(null);
7051
- const [tasksStartTokens, setTasksStartTokens] = useState6(0);
7052
- const [turnStartedAt, setTurnStartedAt] = useState6(null);
7053
- const [verbose, setVerbose] = useState6(false);
7054
- const [hasUpdate, setHasUpdate] = useState6(initialUpdateResult?.hasUpdate ?? false);
7055
- const [latestVersion, setLatestVersion] = useState6(initialUpdateResult?.latestVersion ?? null);
7056
- const cacheStableRef = useRef3(initialCfg?.cacheStablePrompts !== false);
7057
- const messagesRef = useRef3(
7058
- makePrefixMessages(cacheStableRef.current, cfg?.model ?? DEFAULT_MODEL, "edit", ALL_TOOLS)
7059
- );
7060
- const executorRef = useRef3(new ToolExecutor(ALL_TOOLS));
7061
- const activeAsstIdRef = useRef3(null);
7494
+ function filenameToCommandName(file, rootDir) {
7495
+ const rel = relative2(rootDir, file);
7496
+ if (!rel || isPathOutside(rel)) return null;
7497
+ const noExt = rel.replace(/\.md$/i, "");
7498
+ const parts = noExt.split(sep2).filter((p) => p.length > 0);
7499
+ if (parts.length === 0) return null;
7500
+ if (parts.some((p) => !/^[\w.-]+$/.test(p))) return null;
7501
+ return parts.join("/");
7502
+ }
7503
+ var MAX_COMMAND_FILE_BYTES;
7504
+ var init_loader = __esm({
7505
+ "src/commands/loader.ts"() {
7506
+ "use strict";
7507
+ init_mode();
7508
+ init_config();
7509
+ init_paths();
7510
+ init_frontmatter();
7511
+ MAX_COMMAND_FILE_BYTES = 256 * 1024;
7512
+ }
7513
+ });
7514
+
7515
+ // src/commands/renderer.ts
7516
+ import { exec } from "child_process";
7517
+ import { open as open2, realpath as realpath2 } from "fs/promises";
7518
+ import { isAbsolute as isAbsolute2, relative as relative3, resolve as resolvePathJoin } from "path";
7519
+ import { promisify as promisify2 } from "util";
7520
+ function tokenizeArgs(s) {
7521
+ return [...s.matchAll(ARG_TOKEN_RE)].map((match) => {
7522
+ const token = match[0];
7523
+ if (token.length >= 2 && (token.startsWith('"') && token.endsWith('"') || token.startsWith("'") && token.endsWith("'"))) {
7524
+ return token.slice(1, -1);
7525
+ }
7526
+ return token;
7527
+ });
7528
+ }
7529
+ async function renderCommand(cmd, rawInput, opts2 = {}) {
7530
+ const warnings = [];
7531
+ const cwd = opts2.cwd ?? process.cwd();
7532
+ const maxFileBytes = opts2.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
7533
+ const argsString = stripCommandName(rawInput);
7534
+ const args = tokenizeArgs(argsString);
7535
+ const originalTemplate = cmd.template;
7536
+ const hadArguments = originalTemplate.includes("$ARGUMENTS");
7537
+ const hadPositionals = HAS_POSITIONAL.test(originalTemplate);
7538
+ let prompt = replacePositionals(originalTemplate, args);
7539
+ prompt = prompt.replaceAll("$ARGUMENTS", argsString);
7540
+ if (!hadArguments && !hadPositionals && argsString !== "") {
7541
+ prompt += `
7542
+
7543
+ ${argsString}`;
7544
+ }
7545
+ prompt = await replaceShell(prompt, warnings, opts2.shellTimeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS);
7546
+ prompt = await replaceFiles(prompt, warnings, cwd, maxFileBytes);
7547
+ if (prompt.trim() === "") {
7548
+ warnings.push("rendered prompt is empty");
7549
+ }
7550
+ return { prompt, warnings };
7551
+ }
7552
+ function stripCommandName(rawInput) {
7553
+ if (!rawInput.startsWith("/")) {
7554
+ return rawInput;
7555
+ }
7556
+ return rawInput.replace(/^\/\S+\s*/, "");
7557
+ }
7558
+ function replacePositionals(template, args) {
7559
+ const indexes = [...template.matchAll(POSITIONAL_RE)].map(
7560
+ (match) => Number(match[1])
7561
+ );
7562
+ const highest = indexes.length === 0 ? -1 : Math.max(...indexes);
7563
+ return template.replace(POSITIONAL_RE, (_match, n) => {
7564
+ const index = Number(n);
7565
+ if (index <= 0) {
7566
+ return "";
7567
+ }
7568
+ if (index === highest) {
7569
+ return args.slice(index - 1).join(" ");
7570
+ }
7571
+ return args[index - 1] ?? "";
7572
+ });
7573
+ }
7574
+ async function replaceShell(prompt, warnings, shellTimeoutMs) {
7575
+ const matches = [...prompt.matchAll(SHELL_RE)];
7576
+ const replacements = await Promise.all(
7577
+ matches.map(async (match) => {
7578
+ const command = match[1] ?? "";
7579
+ try {
7580
+ const { stdout } = await execAsync(command, {
7581
+ timeout: shellTimeoutMs,
7582
+ maxBuffer: 1024 * 1024
7583
+ });
7584
+ return String(stdout).trimEnd();
7585
+ } catch (error) {
7586
+ warnings.push(`shell command failed: \`${command}\` \u2014 ${message(error)}`);
7587
+ return "";
7588
+ }
7589
+ })
7590
+ );
7591
+ let index = 0;
7592
+ return prompt.replace(SHELL_RE, () => replacements[index++] ?? "");
7593
+ }
7594
+ async function replaceFiles(prompt, warnings, cwd, maxFileBytes) {
7595
+ const matches = [...prompt.matchAll(FILE_RE)];
7596
+ if (matches.length === 0) return prompt;
7597
+ const realCwd = await realpath2(cwd).catch(() => cwd);
7598
+ const replacements = await Promise.all(
7599
+ matches.map(async (match) => {
7600
+ const rawPath = match[1] ?? "";
7601
+ if (isAbsolute2(rawPath) || rawPath.startsWith("~")) {
7602
+ warnings.push(`file inclusion skipped: @${rawPath} \u2014 outside workspace`);
7603
+ return "";
7604
+ }
7605
+ const resolved = resolvePathJoin(cwd, rawPath);
7606
+ if (isPathOutside(relative3(cwd, resolved))) {
7607
+ warnings.push(`file inclusion skipped: @${rawPath} \u2014 outside workspace`);
7608
+ return "";
7609
+ }
7610
+ let real;
7611
+ try {
7612
+ real = await realpath2(resolved);
7613
+ } catch (error) {
7614
+ warnings.push(`file inclusion failed: @${rawPath} \u2014 ${message(error)}`);
7615
+ return "";
7616
+ }
7617
+ if (isPathOutside(relative3(realCwd, real))) {
7618
+ warnings.push(`file inclusion skipped: @${rawPath} \u2014 symlink escapes workspace`);
7619
+ return "";
7620
+ }
7621
+ try {
7622
+ const handle = await open2(real, "r");
7623
+ try {
7624
+ const stats = await handle.stat();
7625
+ if (stats.size > maxFileBytes) {
7626
+ warnings.push(
7627
+ `file inclusion skipped: @${rawPath} \u2014 exceeds ${maxFileBytes} bytes`
7628
+ );
7629
+ return "";
7630
+ }
7631
+ return await handle.readFile("utf8");
7632
+ } finally {
7633
+ await handle.close();
7634
+ }
7635
+ } catch (error) {
7636
+ warnings.push(`file inclusion failed: @${rawPath} \u2014 ${message(error)}`);
7637
+ return "";
7638
+ }
7639
+ })
7640
+ );
7641
+ let index = 0;
7642
+ return prompt.replace(FILE_RE, (_match, _path, trailing = "") => {
7643
+ const replacement = replacements[index++] ?? "";
7644
+ return replacement + trailing;
7645
+ });
7646
+ }
7647
+ function message(error) {
7648
+ return error instanceof Error ? error.message : String(error);
7649
+ }
7650
+ var execAsync, ARG_TOKEN_RE, POSITIONAL_RE, HAS_POSITIONAL, SHELL_RE, FILE_RE, DEFAULT_MAX_FILE_BYTES, DEFAULT_SHELL_TIMEOUT_MS;
7651
+ var init_renderer = __esm({
7652
+ "src/commands/renderer.ts"() {
7653
+ "use strict";
7654
+ init_paths();
7655
+ execAsync = promisify2(exec);
7656
+ ARG_TOKEN_RE = /(?:"[^"]*"|'[^']*'|[^\s"']+)/g;
7657
+ POSITIONAL_RE = /\$(\d+)/g;
7658
+ HAS_POSITIONAL = /\$\d+/;
7659
+ SHELL_RE = /!`([^`]+)`/g;
7660
+ FILE_RE = /(?<![\w`])@(\.?[^\s`,]+?)([.,;:!?)\]}]*)(?=[\s`,]|$)/g;
7661
+ DEFAULT_MAX_FILE_BYTES = 100 * 1024;
7662
+ DEFAULT_SHELL_TIMEOUT_MS = 5e3;
7663
+ }
7664
+ });
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
+
8212
+ // src/app.tsx
8213
+ var app_exports = {};
8214
+ __export(app_exports, {
8215
+ renderApp: () => renderApp
8216
+ });
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";
8220
+ import { existsSync } from "fs";
8221
+ import { join as join13 } from "path";
8222
+ import { unlink as unlink3 } from "fs/promises";
8223
+ import { spawn as spawn2 } from "child_process";
8224
+ import { platform as platform2 } from "os";
8225
+ import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
8226
+ function gatewayFromConfig(cfg) {
8227
+ if (!cfg.aiGatewayId) return void 0;
8228
+ return {
8229
+ id: cfg.aiGatewayId,
8230
+ cacheTtl: cfg.aiGatewayCacheTtl,
8231
+ skipCache: cfg.aiGatewaySkipCache,
8232
+ collectLogPayload: cfg.aiGatewayCollectLogPayload,
8233
+ metadata: cfg.aiGatewayMetadata
8234
+ };
8235
+ }
8236
+ function gatewayUsageLookupFromConfig(cfg, meta) {
8237
+ if (!cfg.aiGatewayId || !meta) return void 0;
8238
+ return {
8239
+ accountId: cfg.accountId,
8240
+ apiToken: cfg.apiToken,
8241
+ gatewayId: cfg.aiGatewayId,
8242
+ meta
8243
+ };
8244
+ }
8245
+ function openBrowser(url) {
8246
+ const cmd = platform2() === "darwin" ? "open" : platform2() === "win32" ? "start" : "xdg-open";
8247
+ const child = spawn2(cmd, [url], { detached: true, stdio: "ignore" });
8248
+ child.unref();
8249
+ }
8250
+ function capEvents(prev) {
8251
+ if (prev.length <= MAX_EVENTS) return prev;
8252
+ return prev.slice(prev.length - MAX_EVENTS);
8253
+ }
8254
+ function compactEventsVisual(prev, keepLastTurns) {
8255
+ let seen = 0;
8256
+ let cutoff = -1;
8257
+ for (let i = prev.length - 1; i >= 0; i--) {
8258
+ if (prev[i].kind === "user") {
8259
+ seen++;
8260
+ if (seen === keepLastTurns + 1) {
8261
+ cutoff = i;
8262
+ break;
8263
+ }
8264
+ }
8265
+ }
8266
+ if (cutoff <= 0) return prev;
8267
+ const kept = prev.slice(cutoff);
8268
+ return [
8269
+ { kind: "info", key: mkKey(), text: `\xB7\xB7\xB7 ${cutoff} earlier messages compacted \xB7\xB7\xB7` },
8270
+ ...kept
8271
+ ];
8272
+ }
8273
+ function makePrefixMessages(cacheStable, model, mode, tools) {
8274
+ if (cacheStable) {
8275
+ return buildSystemMessages({ cwd: process.cwd(), tools, model, mode });
8276
+ }
8277
+ return [
8278
+ {
8279
+ role: "system",
8280
+ content: buildSystemPrompt({ cwd: process.cwd(), tools, model, mode })
8281
+ }
8282
+ ];
8283
+ }
8284
+ function findImagePaths(text) {
8285
+ const paths = [];
8286
+ for (const token of text.split(/\s+/)) {
8287
+ const clean = token.replace(/^["']|["',;:!?]$/g, "").replace(/[.,;:!?]$/, "");
8288
+ if (isImagePath(clean) && existsSync(clean)) {
8289
+ paths.push(clean);
8290
+ }
8291
+ }
8292
+ return [...new Set(paths)];
8293
+ }
8294
+ function App({ initialCfg, initialUpdateResult }) {
8295
+ const { exit } = useApp();
8296
+ const [cfg, setCfg] = useState8(initialCfg);
8297
+ const [events, setRawEvents] = useState8([]);
8298
+ const setEvents = useCallback(
8299
+ (updater) => {
8300
+ setRawEvents((prev) => {
8301
+ const next = typeof updater === "function" ? updater(prev) : updater;
8302
+ return capEvents(next);
8303
+ });
8304
+ },
8305
+ []
8306
+ );
8307
+ const [input, setInput] = useState8("");
8308
+ const [busy, setBusy] = useState8(false);
8309
+ const [usage, setUsage] = useState8(null);
8310
+ const [sessionUsage, setSessionUsage] = useState8(null);
8311
+ const [gatewayMeta, setGatewayMeta] = useState8(null);
8312
+ const [showReasoning, setShowReasoning] = useState8(false);
8313
+ const [perm, setPerm] = useState8(null);
8314
+ const [queue, setQueue] = useState8([]);
8315
+ const [history, setHistory] = useState8([]);
8316
+ const [historyIndex, setHistoryIndex] = useState8(-1);
8317
+ const [draftInput, setDraftInput] = useState8("");
8318
+ const [mode, setMode] = useState8("edit");
8319
+ const [codeMode, setCodeMode] = useState8(initialCfg?.codeMode ?? false);
8320
+ const [effort, setEffort] = useState8(
8321
+ initialCfg?.reasoningEffort ?? DEFAULT_REASONING_EFFORT
8322
+ );
8323
+ const [theme, setTheme] = useState8(resolveTheme(initialCfg?.theme));
8324
+ const [resumeSessions, setResumeSessions] = useState8(null);
8325
+ const [showThemePicker, setShowThemePicker] = useState8(false);
8326
+ const [showHelpMenu, setShowHelpMenu] = useState8(false);
8327
+ const [originalTheme, setOriginalTheme] = useState8(null);
8328
+ const [commandWizard, setCommandWizard] = useState8(null);
8329
+ const [commandPicker, setCommandPicker] = useState8(null);
8330
+ const [commandToDelete, setCommandToDelete] = useState8(null);
8331
+ const [showCommandList, setShowCommandList] = useState8(false);
8332
+ const [tasks, setTasks] = useState8([]);
8333
+ const [tasksStartedAt, setTasksStartedAt] = useState8(null);
8334
+ const [tasksStartTokens, setTasksStartTokens] = useState8(0);
8335
+ const [turnStartedAt, setTurnStartedAt] = useState8(null);
8336
+ const [verbose, setVerbose] = useState8(false);
8337
+ const [hasUpdate, setHasUpdate] = useState8(initialUpdateResult?.hasUpdate ?? false);
8338
+ const [latestVersion, setLatestVersion] = useState8(initialUpdateResult?.latestVersion ?? null);
8339
+ const cacheStableRef = useRef3(initialCfg?.cacheStablePrompts !== false);
8340
+ const messagesRef = useRef3(
8341
+ makePrefixMessages(cacheStableRef.current, cfg?.model ?? DEFAULT_MODEL, "edit", ALL_TOOLS)
8342
+ );
8343
+ const executorRef = useRef3(new ToolExecutor(ALL_TOOLS));
8344
+ const activeAsstIdRef = useRef3(null);
7062
8345
  const activeControllerRef = useRef3(null);
7063
8346
  const sessionIdRef = useRef3(null);
7064
8347
  const modeRef = useRef3(mode);
@@ -7076,8 +8359,10 @@ function App({ initialCfg, initialUpdateResult }) {
7076
8359
  const mcpToolsRef = useRef3([]);
7077
8360
  const mcpInitRef = useRef3(false);
7078
8361
  const memoryManagerRef = useRef3(null);
8362
+ const sessionStartRecallRef = useRef3(null);
7079
8363
  const pendingTextRef = useRef3(/* @__PURE__ */ new Map());
7080
8364
  const flushTimeoutRef = useRef3(null);
8365
+ const customCommandsRef = useRef3([]);
7081
8366
  useEffect4(() => {
7082
8367
  if (!cfg) return;
7083
8368
  void Promise.resolve().then(() => (init_sessions(), sessions_exports)).then(
@@ -7104,12 +8389,13 @@ function App({ initialCfg, initialUpdateResult }) {
7104
8389
  }
7105
8390
  });
7106
8391
  if (cfg.memoryEnabled) {
7107
- const dbPath = cfg.memoryDbPath ?? join12(process.cwd(), ".kimiflare", "memory.db");
8392
+ const dbPath = cfg.memoryDbPath ?? join13(process.cwd(), ".kimiflare", "memory.db");
7108
8393
  const manager = new MemoryManager({
7109
8394
  dbPath,
7110
8395
  accountId: cfg.accountId,
7111
8396
  apiToken: cfg.apiToken,
7112
8397
  model: cfg.model,
8398
+ plumbingModel: cfg.plumbingModel,
7113
8399
  embeddingModel: cfg.memoryEmbeddingModel,
7114
8400
  gateway: gatewayFromConfig(cfg),
7115
8401
  maxAgeDays: cfg.memoryMaxAgeDays ?? RETENTION.memoryMaxAgeDays,
@@ -7122,7 +8408,7 @@ function App({ initialCfg, initialUpdateResult }) {
7122
8408
  if (total > 0) {
7123
8409
  setEvents((e) => [
7124
8410
  ...e,
7125
- { kind: "info", key: mkKey(), text: `memory cleanup: removed ${total} stale entries` }
8411
+ { kind: "memory", key: mkKey(), text: `memory cleanup: removed ${total} stale entries` }
7126
8412
  ]);
7127
8413
  }
7128
8414
  });
@@ -7130,15 +8416,59 @@ function App({ initialCfg, initialUpdateResult }) {
7130
8416
  if (fixed > 0) {
7131
8417
  setEvents((e) => [
7132
8418
  ...e,
7133
- { kind: "info", key: mkKey(), text: `memory backfill: embedded ${fixed} un-vectorized entries` }
8419
+ { kind: "memory", key: mkKey(), text: `memory backfill: embedded ${fixed} un-vectorized entries` }
7134
8420
  ]);
7135
8421
  }
7136
8422
  });
8423
+ const cwd = process.cwd();
8424
+ sessionStartRecallRef.current = (async () => {
8425
+ try {
8426
+ const results = await manager.recall({ text: cwd, repoPath: cwd, limit: 5 });
8427
+ if (results.length > 0) {
8428
+ const text = await manager.synthesizeRecalled(results);
8429
+ const lastSystemIdx = messagesRef.current.findLastIndex((m) => m.role === "system");
8430
+ const insertIdx = lastSystemIdx >= 0 ? lastSystemIdx + 1 : messagesRef.current.length;
8431
+ messagesRef.current.splice(insertIdx, 0, { role: "system", content: text });
8432
+ setEvents((e) => [
8433
+ ...e,
8434
+ { kind: "memory", key: mkKey(), text: `recalled ${results.length} memory${results.length === 1 ? "" : "ies"} about this repo` }
8435
+ ]);
8436
+ }
8437
+ } catch {
8438
+ }
8439
+ })();
7137
8440
  } else {
7138
8441
  memoryManagerRef.current?.close();
7139
8442
  memoryManagerRef.current = null;
7140
8443
  }
7141
- }, [cfg]);
8444
+ void loadCustomCommands(process.cwd()).then(({ commands, warnings }) => {
8445
+ customCommandsRef.current = commands;
8446
+ for (const w of warnings) {
8447
+ setEvents((e) => [...e, { kind: "info", key: mkKey(), text: `commands: ${w}` }]);
8448
+ }
8449
+ const shadowed = commands.filter((c) => BUILTIN_COMMAND_NAMES.has(c.name.toLowerCase()));
8450
+ for (const c of shadowed) {
8451
+ setEvents((e) => [
8452
+ ...e,
8453
+ { kind: "info", key: mkKey(), text: `commands: /${c.name} (${c.filepath}) shadowed by built-in \u2014 will not run` }
8454
+ ]);
8455
+ }
8456
+ });
8457
+ }, [cfg, setEvents]);
8458
+ const reloadCustomCommands = useCallback(async () => {
8459
+ const { commands, warnings } = await loadCustomCommands(process.cwd());
8460
+ customCommandsRef.current = commands;
8461
+ for (const w of warnings) {
8462
+ setEvents((e) => [...e, { kind: "info", key: mkKey(), text: `commands: ${w}` }]);
8463
+ }
8464
+ const shadowed = commands.filter((c) => BUILTIN_COMMAND_NAMES.has(c.name.toLowerCase()));
8465
+ for (const c of shadowed) {
8466
+ setEvents((e) => [
8467
+ ...e,
8468
+ { kind: "info", key: mkKey(), text: `commands: /${c.name} (${c.filepath}) shadowed by built-in \u2014 will not run` }
8469
+ ]);
8470
+ }
8471
+ }, [setEvents]);
7142
8472
  useEffect4(() => {
7143
8473
  if (!cfg || updateCheckedRef.current) return;
7144
8474
  updateCheckedRef.current = true;
@@ -7341,12 +8671,13 @@ function App({ initialCfg, initialUpdateResult }) {
7341
8671
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
7342
8672
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7343
8673
  messages: messagesRef.current,
7344
- sessionState: compiledContextRef.current ? sessionStateRef.current : void 0
8674
+ sessionState: compiledContextRef.current ? sessionStateRef.current : void 0,
8675
+ artifactStore: serializeArtifactStore(artifactStoreRef.current)
7345
8676
  });
7346
8677
  } catch {
7347
8678
  }
7348
8679
  }, [cfg, ensureSessionId]);
7349
- useInput2((inputChar, key) => {
8680
+ useInput5((inputChar, key) => {
7350
8681
  if (key.ctrl && inputChar === "c") {
7351
8682
  if (busy && activeControllerRef.current) {
7352
8683
  activeControllerRef.current.abort();
@@ -7537,13 +8868,13 @@ function App({ initialCfg, initialUpdateResult }) {
7537
8868
  }
7538
8869
  const cwd = process.cwd();
7539
8870
  for (const name of ["KIMI.md", "KIMIFLARE.md", "AGENT.md"]) {
7540
- if (existsSync(join12(cwd, name))) {
8871
+ if (existsSync(join13(cwd, name))) {
7541
8872
  setEvents((e) => [
7542
8873
  ...e,
7543
8874
  {
7544
8875
  kind: "info",
7545
8876
  key: mkKey(),
7546
- text: `${name} already exists at ${join12(cwd, name)} \u2014 delete it first if you want to regenerate`
8877
+ text: `${name} already exists at ${join13(cwd, name)} \u2014 delete it first if you want to regenerate`
7547
8878
  }
7548
8879
  ]);
7549
8880
  return;
@@ -7671,7 +9002,7 @@ function App({ initialCfg, initialUpdateResult }) {
7671
9002
  })
7672
9003
  }
7673
9004
  });
7674
- if (existsSync(join12(cwd, "KIMI.md"))) {
9005
+ if (existsSync(join13(cwd, "KIMI.md"))) {
7675
9006
  if (cacheStableRef.current) {
7676
9007
  messagesRef.current[1] = {
7677
9008
  role: "system",
@@ -7722,8 +9053,26 @@ function App({ initialCfg, initialUpdateResult }) {
7722
9053
  sessionIdRef.current = file.id;
7723
9054
  if (file.sessionState && compiledContextRef.current) {
7724
9055
  sessionStateRef.current = file.sessionState;
9056
+ }
9057
+ if (file.artifactStore) {
9058
+ artifactStoreRef.current = deserializeArtifactStore(file.artifactStore);
9059
+ } else {
7725
9060
  artifactStoreRef.current = new ArtifactStore();
7726
9061
  }
9062
+ const manager = memoryManagerRef.current;
9063
+ if (manager) {
9064
+ try {
9065
+ const cwd = process.cwd();
9066
+ const results = await manager.recall({ text: cwd, repoPath: cwd, limit: 5 });
9067
+ if (results.length > 0) {
9068
+ const text = await manager.synthesizeRecalled(results);
9069
+ const lastSystemIdx = messagesRef.current.findLastIndex((m) => m.role === "system");
9070
+ const insertIdx = lastSystemIdx >= 0 ? lastSystemIdx + 1 : messagesRef.current.length;
9071
+ messagesRef.current.splice(insertIdx, 0, { role: "system", content: text });
9072
+ }
9073
+ } catch {
9074
+ }
9075
+ }
7727
9076
  setEvents([
7728
9077
  {
7729
9078
  kind: "info",
@@ -8035,13 +9384,30 @@ use: /thinking low | medium | high`
8035
9384
  return true;
8036
9385
  }
8037
9386
  if (c === "/memory") {
8038
- if (!cfg?.memoryEnabled) {
8039
- setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "memory is disabled. Enable with KIMIFLARE_MEMORY_ENABLED=1" }]);
9387
+ if (!cfg) return true;
9388
+ if (arg === "on") {
9389
+ const next = { ...cfg, memoryEnabled: true };
9390
+ setCfg(next);
9391
+ void saveConfig(next).catch(() => {
9392
+ });
9393
+ setEvents((e) => [...e, { kind: "memory", key: mkKey(), text: "memory enabled" }]);
9394
+ return true;
9395
+ }
9396
+ if (arg === "off") {
9397
+ const next = { ...cfg, memoryEnabled: false };
9398
+ setCfg(next);
9399
+ void saveConfig(next).catch(() => {
9400
+ });
9401
+ setEvents((e) => [...e, { kind: "memory", key: mkKey(), text: "memory disabled" }]);
9402
+ return true;
9403
+ }
9404
+ if (!cfg.memoryEnabled) {
9405
+ setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "memory is disabled. Use /memory on to enable it, or set KIMIFLARE_MEMORY_ENABLED=1" }]);
8040
9406
  return true;
8041
9407
  }
8042
9408
  if (arg === "clear") {
8043
9409
  const cleared = memoryManagerRef.current?.clearRepo(process.cwd()) ?? 0;
8044
- setEvents((e) => [...e, { kind: "info", key: mkKey(), text: `cleared ${cleared} memories for this repo` }]);
9410
+ setEvents((e) => [...e, { kind: "memory", key: mkKey(), text: `cleared ${cleared} memories for this repo` }]);
8045
9411
  return true;
8046
9412
  }
8047
9413
  if (arg.startsWith("search ")) {
@@ -8176,7 +9542,7 @@ ${lines.join("\n")}` }]);
8176
9542
  return true;
8177
9543
  }
8178
9544
  if (c === "/logout") {
8179
- unlink2(configPath()).catch(() => {
9545
+ unlink3(configPath()).catch(() => {
8180
9546
  });
8181
9547
  setEvents((e) => [
8182
9548
  ...e,
@@ -8185,28 +9551,123 @@ ${lines.join("\n")}` }]);
8185
9551
  setCfg(null);
8186
9552
  return true;
8187
9553
  }
8188
- if (c === "/help") {
9554
+ if (c === "/command") {
9555
+ const sub = rest[0]?.toLowerCase() ?? "";
9556
+ if (sub === "create") {
9557
+ setCommandWizard({ mode: "create" });
9558
+ return true;
9559
+ }
9560
+ if (sub === "edit") {
9561
+ setCommandPicker({ mode: "edit" });
9562
+ return true;
9563
+ }
9564
+ if (sub === "delete") {
9565
+ setCommandPicker({ mode: "delete" });
9566
+ return true;
9567
+ }
9568
+ if (sub === "list") {
9569
+ setShowCommandList(true);
9570
+ return true;
9571
+ }
8189
9572
  setEvents((e) => [
8190
9573
  ...e,
8191
- {
8192
- kind: "info",
8193
- key: mkKey(),
8194
- text: "commands:\n /mode edit|plan|auto switch mode (or shift+tab to cycle)\n /plan /auto /edit shortcuts for /mode\n /thinking low|med|high set reasoning effort (quality vs speed)\n /theme interactive theme picker (or ctrl+t)\n /theme NAME set theme by name\n /resume pick a past conversation\n /compact summarize old turns to free context\n /init scan this repo and write a KIMI.md for future agents\n /memory show memory stats\n /memory search <query> search stored memories\n /memory clear wipe memories for this repo\n /mcp list list connected MCP servers and tools\n /mcp reload reconnect all configured MCP servers\n /reasoning toggle show/hide model reasoning\n /clear clear current conversation\n /hello send a voice note to the creator\n /community join our Discord server\n /gateway show gateway status\n /gateway ID enable AI Gateway\n /gateway off disable AI Gateway (direct Workers AI)\n /gateway cache-ttl N set gateway cache TTL in seconds\n /gateway skip-cache T|F set gateway skip-cache flag\n /gateway collect-logs T|F include payload in gateway logs\n /gateway metadata K=V add metadata key-value pair\n /gateway metadata clear remove all metadata\n /cost /model /update /logout /help /exit\nkeys: ctrl-c interrupt/exit \xB7 ctrl-r toggle reasoning \xB7 ctrl-o verbose \xB7 ctrl+t theme \xB7 shift+tab cycle mode \xB7 \u2191/\u2193 history"
8195
- }
9574
+ { kind: "info", key: mkKey(), text: "usage: /command create | edit | delete | list" }
8196
9575
  ]);
8197
9576
  return true;
8198
9577
  }
9578
+ if (c === "/help") {
9579
+ setShowHelpMenu(true);
9580
+ return true;
9581
+ }
8199
9582
  return false;
8200
9583
  },
8201
9584
  [cfg, exit, usage, effort, theme, mode, openResumePicker, runCompact, runInit, initMcp, setCfg]
8202
9585
  );
9586
+ const handleHelpCommand = useCallback(
9587
+ (command) => {
9588
+ setShowHelpMenu(false);
9589
+ const executed = handleSlash(command);
9590
+ if (!executed) {
9591
+ setEvents((e) => [...e, { kind: "error", key: mkKey(), text: `unknown command: ${command}` }]);
9592
+ }
9593
+ },
9594
+ [handleSlash]
9595
+ );
9596
+ const handleCommandSave = useCallback(
9597
+ async (opts2) => {
9598
+ setCommandWizard(null);
9599
+ try {
9600
+ if (commandWizard?.mode === "edit" && commandWizard.initial && commandWizard.initial.name !== opts2.name) {
9601
+ await deleteCustomCommand(commandWizard.initial);
9602
+ }
9603
+ const result = await saveCustomCommand(opts2);
9604
+ await reloadCustomCommands();
9605
+ setEvents((e) => [
9606
+ ...e,
9607
+ { kind: "info", key: mkKey(), text: `saved /${opts2.name} \u2192 ${result.filepath}` }
9608
+ ]);
9609
+ } catch (err) {
9610
+ setEvents((e) => [
9611
+ ...e,
9612
+ { kind: "error", key: mkKey(), text: `failed to save /${opts2.name}: ${err.message}` }
9613
+ ]);
9614
+ }
9615
+ },
9616
+ [commandWizard, reloadCustomCommands, setEvents]
9617
+ );
9618
+ const handleCommandDelete = useCallback(
9619
+ async (cmd) => {
9620
+ setCommandToDelete(null);
9621
+ try {
9622
+ await deleteCustomCommand(cmd);
9623
+ await reloadCustomCommands();
9624
+ setEvents((e) => [
9625
+ ...e,
9626
+ { kind: "info", key: mkKey(), text: `deleted /${cmd.name} (${cmd.filepath})` }
9627
+ ]);
9628
+ } catch (err) {
9629
+ setEvents((e) => [
9630
+ ...e,
9631
+ { kind: "error", key: mkKey(), text: `failed to delete /${cmd.name}: ${err.message}` }
9632
+ ]);
9633
+ }
9634
+ },
9635
+ [reloadCustomCommands, setEvents]
9636
+ );
8203
9637
  const processMessage = useCallback(
8204
9638
  async (text, displayText) => {
8205
9639
  if (!cfg) return;
8206
- const trimmed = text.trim();
9640
+ let trimmed = text.trim();
8207
9641
  if (!trimmed) return;
8208
- if (trimmed.startsWith("/") && handleSlash(trimmed)) return;
8209
- const display = displayText?.trim() || trimmed;
9642
+ let overrideModel;
9643
+ let overrideEffort;
9644
+ let display = displayText?.trim() || trimmed;
9645
+ if (trimmed.startsWith("/")) {
9646
+ if (handleSlash(trimmed)) return;
9647
+ const head = trimmed.split(/\s+/)[0].slice(1);
9648
+ const custom = customCommandsRef.current.find((c) => c.name === head);
9649
+ if (custom) {
9650
+ const info = (text2) => setEvents((e) => [...e, { kind: "info", key: mkKey(), text: text2 }]);
9651
+ const { prompt: rendered, warnings } = await renderCommand(custom, trimmed, {
9652
+ cwd: process.cwd()
9653
+ });
9654
+ for (const w of warnings) info(`${custom.name}: ${w}`);
9655
+ if (!rendered.trim()) return;
9656
+ const parts = [];
9657
+ if (custom.model) {
9658
+ overrideModel = custom.model;
9659
+ parts.push(`model=${custom.model}`);
9660
+ }
9661
+ if (custom.effort) {
9662
+ overrideEffort = custom.effort;
9663
+ parts.push(`effort=${custom.effort}`);
9664
+ }
9665
+ if (parts.length > 0) info(`command '${custom.name}' \u2192 ${parts.join(", ")} (this turn)`);
9666
+ if (custom.mode) info(`note: mode override (${custom.mode}) is not yet wired; current mode applies`);
9667
+ display = trimmed;
9668
+ trimmed = rendered;
9669
+ }
9670
+ }
8210
9671
  const imagePaths = findImagePaths(trimmed).slice(0, MAX_IMAGES_PER_MESSAGE);
8211
9672
  let images = [];
8212
9673
  let content = sanitizeString(trimmed);
@@ -8235,6 +9696,10 @@ ${lines.join("\n")}` }]);
8235
9696
  content = parts;
8236
9697
  }
8237
9698
  }
9699
+ if (sessionStartRecallRef.current) {
9700
+ await sessionStartRecallRef.current;
9701
+ sessionStartRecallRef.current = null;
9702
+ }
8238
9703
  setEvents((e) => [...e, { kind: "user", key: mkKey(), text: display, images: images.length > 0 ? images : void 0 }]);
8239
9704
  messagesRef.current.push({ role: "user", content });
8240
9705
  if (compiledContextRef.current) {
@@ -8258,14 +9723,14 @@ ${lines.join("\n")}` }]);
8258
9723
  await runAgentTurn({
8259
9724
  accountId: cfg.accountId,
8260
9725
  apiToken: cfg.apiToken,
8261
- model: cfg.model,
9726
+ model: overrideModel ?? cfg.model,
8262
9727
  gateway: gatewayFromConfig(cfg),
8263
9728
  messages: messagesRef.current,
8264
9729
  tools: [...ALL_TOOLS, ...mcpToolsRef.current],
8265
9730
  executor: executorRef.current,
8266
9731
  cwd: process.cwd(),
8267
9732
  signal: controller.signal,
8268
- reasoningEffort: effortRef.current,
9733
+ reasoningEffort: overrideEffort ?? effortRef.current,
8269
9734
  coauthor: cfg.coauthor !== false ? { name: cfg.coauthorName || "kimiflare", email: cfg.coauthorEmail || "kimiflare@proton.me" } : void 0,
8270
9735
  sessionId: ensureSessionId(),
8271
9736
  memoryManager: memoryManagerRef.current,
@@ -8370,24 +9835,84 @@ ${lines.join("\n")}` }]);
8370
9835
  }
8371
9836
  });
8372
9837
  await saveSessionSafe();
8373
- if (compiledContextRef.current && shouldCompact({ messages: messagesRef.current })) {
8374
- const result = compactMessages2({
8375
- messages: messagesRef.current,
8376
- state: sessionStateRef.current,
8377
- store: artifactStoreRef.current
8378
- });
8379
- if (result.metrics.rawTurnsRemoved > 0) {
8380
- messagesRef.current = result.newMessages;
8381
- sessionStateRef.current = result.newState;
8382
- setEvents((e) => [
8383
- ...e,
8384
- {
8385
- kind: "info",
8386
- key: mkKey(),
8387
- text: `auto-compacted: ${result.metrics.estimatedTokensBefore} \u2192 ${result.metrics.estimatedTokensAfter} tokens (${result.metrics.archivedArtifacts} artifacts)`
9838
+ if (shouldCompact({ messages: messagesRef.current })) {
9839
+ if (compiledContextRef.current) {
9840
+ const result = compactMessages2({
9841
+ messages: messagesRef.current,
9842
+ state: sessionStateRef.current,
9843
+ store: artifactStoreRef.current
9844
+ });
9845
+ if (result.metrics.rawTurnsRemoved > 0) {
9846
+ messagesRef.current = result.newMessages;
9847
+ sessionStateRef.current = result.newState;
9848
+ setEvents((e) => [
9849
+ ...e,
9850
+ {
9851
+ kind: "info",
9852
+ key: mkKey(),
9853
+ text: `auto-compacted: ${result.metrics.estimatedTokensBefore} \u2192 ${result.metrics.estimatedTokensAfter} tokens (${result.metrics.archivedArtifacts} artifacts)`
9854
+ }
9855
+ ]);
9856
+ await saveSessionSafe();
9857
+ }
9858
+ } else {
9859
+ try {
9860
+ const result = await compactMessages({
9861
+ accountId: cfg.accountId,
9862
+ apiToken: cfg.apiToken,
9863
+ model: cfg.model,
9864
+ messages: messagesRef.current,
9865
+ signal: controller.signal,
9866
+ gateway: gatewayFromConfig(cfg)
9867
+ });
9868
+ if (result.replacedCount > 0) {
9869
+ messagesRef.current = result.newMessages;
9870
+ setEvents((e) => [
9871
+ ...e,
9872
+ {
9873
+ kind: "info",
9874
+ key: mkKey(),
9875
+ text: `auto-compacted: ${result.replacedCount} messages summarized`
9876
+ }
9877
+ ]);
9878
+ await saveSessionSafe();
8388
9879
  }
8389
- ]);
8390
- await saveSessionSafe();
9880
+ } catch (compactErr) {
9881
+ if (compactErr.name !== "AbortError") {
9882
+ setEvents((es) => [
9883
+ ...es,
9884
+ {
9885
+ kind: "info",
9886
+ key: mkKey(),
9887
+ text: `auto-compact failed: ${compactErr.message ?? String(compactErr)}`
9888
+ }
9889
+ ]);
9890
+ }
9891
+ }
9892
+ }
9893
+ }
9894
+ const manager = memoryManagerRef.current;
9895
+ if (manager) {
9896
+ try {
9897
+ const cwd = process.cwd();
9898
+ const queryText = sessionStateRef.current.task || cwd;
9899
+ const results = await manager.recall({ text: queryText, repoPath: cwd, limit: 5 });
9900
+ if (results.length > 0) {
9901
+ const text2 = await manager.synthesizeRecalled(results);
9902
+ const lastSystemIdx = messagesRef.current.findLastIndex((m) => m.role === "system");
9903
+ const insertIdx = lastSystemIdx >= 0 ? lastSystemIdx + 1 : messagesRef.current.length;
9904
+ messagesRef.current.splice(insertIdx, 0, { role: "system", content: text2 });
9905
+ setEvents((e) => [
9906
+ ...e,
9907
+ {
9908
+ kind: "memory",
9909
+ key: mkKey(),
9910
+ text: `recalled ${results.length} memory${results.length === 1 ? "" : "ies"} after compaction`
9911
+ }
9912
+ ]);
9913
+ await saveSessionSafe();
9914
+ }
9915
+ } catch {
8391
9916
  }
8392
9917
  }
8393
9918
  } catch (e) {
@@ -8463,7 +9988,7 @@ ${lines.join("\n")}` }]);
8463
9988
  }
8464
9989
  }, [usage]);
8465
9990
  if (!cfg) {
8466
- return /* @__PURE__ */ jsx13(
9991
+ return /* @__PURE__ */ jsx17(
8467
9992
  Onboarding,
8468
9993
  {
8469
9994
  onDone: (newCfg) => {
@@ -8477,15 +10002,97 @@ ${lines.join("\n")}` }]);
8477
10002
  );
8478
10003
  }
8479
10004
  if (resumeSessions !== null) {
8480
- return /* @__PURE__ */ jsx13(Box12, { flexDirection: "column", children: /* @__PURE__ */ jsx13(ResumePicker, { sessions: resumeSessions, onPick: handleResumePick, theme }) });
10005
+ return /* @__PURE__ */ jsx17(Box16, { flexDirection: "column", children: /* @__PURE__ */ jsx17(ResumePicker, { sessions: resumeSessions, onPick: handleResumePick, theme }) });
8481
10006
  }
8482
10007
  if (showThemePicker) {
8483
- return /* @__PURE__ */ jsx13(Box12, { flexDirection: "column", children: /* @__PURE__ */ jsx13(ThemePicker, { themes: themeList(), current: theme, onPick: handleThemePick, onPreview: (t) => setTheme(t) }) });
10008
+ return /* @__PURE__ */ jsx17(Box16, { flexDirection: "column", children: /* @__PURE__ */ jsx17(ThemePicker, { themes: themeList(), current: theme, onPick: handleThemePick, onPreview: (t) => setTheme(t) }) });
10009
+ }
10010
+ if (showHelpMenu) {
10011
+ return /* @__PURE__ */ jsx17(Box16, { flexDirection: "column", children: /* @__PURE__ */ jsx17(
10012
+ HelpMenu,
10013
+ {
10014
+ theme,
10015
+ themes: themeList().map((t) => ({ name: t.name, label: t.label })),
10016
+ currentThemeName: theme.name,
10017
+ customCommands: customCommandsRef.current.filter((c) => !BUILTIN_COMMAND_NAMES.has(c.name.toLowerCase())).map((c) => ({ name: c.name, description: c.description })),
10018
+ onDone: () => setShowHelpMenu(false),
10019
+ onCommand: handleHelpCommand
10020
+ }
10021
+ ) });
10022
+ }
10023
+ if (commandWizard) {
10024
+ return /* @__PURE__ */ jsx17(Box16, { flexDirection: "column", children: /* @__PURE__ */ jsx17(
10025
+ CommandWizard,
10026
+ {
10027
+ theme,
10028
+ mode: commandWizard.mode,
10029
+ initial: commandWizard.initial,
10030
+ existingNames: customCommandsRef.current.map((c) => c.name),
10031
+ builtinNames: BUILTIN_COMMAND_NAMES,
10032
+ onDone: () => setCommandWizard(null),
10033
+ onSave: handleCommandSave
10034
+ }
10035
+ ) });
10036
+ }
10037
+ if (commandPicker) {
10038
+ return /* @__PURE__ */ jsx17(Box16, { flexDirection: "column", children: /* @__PURE__ */ jsx17(
10039
+ CommandPicker,
10040
+ {
10041
+ theme,
10042
+ commands: customCommandsRef.current,
10043
+ title: commandPicker.mode === "edit" ? "Edit custom command" : "Delete custom command",
10044
+ onPick: (cmd) => {
10045
+ setCommandPicker(null);
10046
+ if (!cmd) return;
10047
+ if (commandPicker.mode === "edit") {
10048
+ setCommandWizard({ mode: "edit", initial: cmd });
10049
+ } else {
10050
+ setCommandToDelete(cmd);
10051
+ }
10052
+ }
10053
+ }
10054
+ ) });
10055
+ }
10056
+ if (commandToDelete) {
10057
+ return /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
10058
+ /* @__PURE__ */ jsxs16(Text17, { color: theme.accent, bold: true, children: [
10059
+ "Delete /",
10060
+ commandToDelete.name,
10061
+ "?"
10062
+ ] }),
10063
+ /* @__PURE__ */ jsx17(Text17, { color: theme.info.color, dimColor: true, children: commandToDelete.filepath }),
10064
+ /* @__PURE__ */ jsx17(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx17(
10065
+ SelectInput7,
10066
+ {
10067
+ items: [
10068
+ { label: "Yes, delete", value: "yes", key: "yes" },
10069
+ { label: "Cancel", value: "cancel", key: "cancel" }
10070
+ ],
10071
+ onSelect: (item) => {
10072
+ if (item.value === "yes") {
10073
+ void handleCommandDelete(commandToDelete);
10074
+ } else {
10075
+ setCommandToDelete(null);
10076
+ }
10077
+ }
10078
+ }
10079
+ ) })
10080
+ ] });
10081
+ }
10082
+ if (showCommandList) {
10083
+ return /* @__PURE__ */ jsx17(Box16, { flexDirection: "column", children: /* @__PURE__ */ jsx17(
10084
+ CommandList,
10085
+ {
10086
+ theme,
10087
+ commands: customCommandsRef.current,
10088
+ onDone: () => setShowCommandList(false)
10089
+ }
10090
+ ) });
8484
10091
  }
8485
10092
  const hasConversation = events.some((e) => e.kind === "user" || e.kind === "assistant");
8486
- return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
8487
- !hasConversation && events.length === 0 ? /* @__PURE__ */ jsx13(Welcome, { theme, accountId: cfg.accountId }) : /* @__PURE__ */ jsx13(ChatView, { events, showReasoning, theme, verbose }),
8488
- perm ? /* @__PURE__ */ jsx13(
10093
+ return /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", children: [
10094
+ !hasConversation && events.length === 0 ? /* @__PURE__ */ jsx17(Welcome, { theme, accountId: cfg.accountId }) : /* @__PURE__ */ jsx17(ChatView, { events, showReasoning, theme, verbose }),
10095
+ perm ? /* @__PURE__ */ jsx17(
8489
10096
  PermissionModal,
8490
10097
  {
8491
10098
  tool: perm.tool,
@@ -8496,8 +10103,8 @@ ${lines.join("\n")}` }]);
8496
10103
  setPerm(null);
8497
10104
  }
8498
10105
  }
8499
- ) : /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", marginTop: 1, children: [
8500
- tasks.length > 0 && /* @__PURE__ */ jsx13(
10106
+ ) : /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", marginTop: 1, children: [
10107
+ tasks.length > 0 && /* @__PURE__ */ jsx17(
8501
10108
  TaskList,
8502
10109
  {
8503
10110
  tasks,
@@ -8506,11 +10113,11 @@ ${lines.join("\n")}` }]);
8506
10113
  tokensDelta: Math.max(0, (usage?.prompt_tokens ?? 0) - tasksStartTokens)
8507
10114
  }
8508
10115
  ),
8509
- queue.length > 0 && /* @__PURE__ */ jsx13(Box12, { flexDirection: "column", marginBottom: 1, children: queue.map((q, i) => /* @__PURE__ */ jsxs12(Text13, { color: theme.queue.color, dimColor: theme.queue.dim, children: [
10116
+ 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: [
8510
10117
  "\u23F3 ",
8511
10118
  q.display
8512
10119
  ] }, `queue_${i}`)) }),
8513
- /* @__PURE__ */ jsx13(
10120
+ /* @__PURE__ */ jsx17(
8514
10121
  StatusBar,
8515
10122
  {
8516
10123
  model: cfg.model,
@@ -8528,9 +10135,9 @@ ${lines.join("\n")}` }]);
8528
10135
  codeMode
8529
10136
  }
8530
10137
  ),
8531
- /* @__PURE__ */ jsxs12(Box12, { marginTop: 1, children: [
8532
- /* @__PURE__ */ jsx13(Text13, { color: theme.accent, children: "\u203A " }),
8533
- /* @__PURE__ */ jsx13(
10138
+ /* @__PURE__ */ jsxs16(Box16, { marginTop: 1, children: [
10139
+ /* @__PURE__ */ jsx17(Text17, { color: theme.accent, children: "\u203A " }),
10140
+ /* @__PURE__ */ jsx17(
8534
10141
  CustomTextInput,
8535
10142
  {
8536
10143
  value: input,
@@ -8579,12 +10186,12 @@ ${lines.join("\n")}` }]);
8579
10186
  ] });
8580
10187
  }
8581
10188
  async function renderApp(cfg, updateResult) {
8582
- const instance = render(/* @__PURE__ */ jsx13(App, { initialCfg: cfg, initialUpdateResult: updateResult }), {
10189
+ const instance = render(/* @__PURE__ */ jsx17(App, { initialCfg: cfg, initialUpdateResult: updateResult }), {
8583
10190
  incrementalRendering: true
8584
10191
  });
8585
10192
  await instance.waitUntilExit();
8586
10193
  }
8587
- var FEEDBACK_WORKER_URL, CONTEXT_LIMIT, AUTO_COMPACT_SUGGEST_PCT, MAX_EVENTS, nextAssistantId, nextKey, mkKey, MAX_IMAGES_PER_MESSAGE, EFFORT_DESCRIPTIONS;
10194
+ var FEEDBACK_WORKER_URL, CONTEXT_LIMIT, AUTO_COMPACT_SUGGEST_PCT, MAX_EVENTS, nextAssistantId, nextKey, mkKey, MAX_IMAGES_PER_MESSAGE, BUILTIN_COMMAND_NAMES, EFFORT_DESCRIPTIONS;
8588
10195
  var init_app = __esm({
8589
10196
  "src/app.tsx"() {
8590
10197
  "use strict";
@@ -8607,6 +10214,7 @@ var init_app = __esm({
8607
10214
  init_update_check();
8608
10215
  init_onboarding();
8609
10216
  init_welcome();
10217
+ init_help_menu();
8610
10218
  init_config();
8611
10219
  init_theme();
8612
10220
  init_mode();
@@ -8617,6 +10225,12 @@ var init_app = __esm({
8617
10225
  init_storage_limits();
8618
10226
  init_state();
8619
10227
  init_version();
10228
+ init_loader();
10229
+ init_renderer();
10230
+ init_save();
10231
+ init_command_wizard();
10232
+ init_command_picker();
10233
+ init_command_list();
8620
10234
  FEEDBACK_WORKER_URL = "https://kimiflare-feedback.sina-b35.workers.dev";
8621
10235
  CONTEXT_LIMIT = 262e3;
8622
10236
  AUTO_COMPACT_SUGGEST_PCT = 0.8;
@@ -8625,6 +10239,32 @@ var init_app = __esm({
8625
10239
  nextKey = 1;
8626
10240
  mkKey = () => `evt_${nextKey++}`;
8627
10241
  MAX_IMAGES_PER_MESSAGE = 10;
10242
+ BUILTIN_COMMAND_NAMES = /* @__PURE__ */ new Set([
10243
+ "exit",
10244
+ "quit",
10245
+ "clear",
10246
+ "reasoning",
10247
+ "cost",
10248
+ "model",
10249
+ "thinking",
10250
+ "effort",
10251
+ "theme",
10252
+ "mode",
10253
+ "plan",
10254
+ "auto",
10255
+ "edit",
10256
+ "resume",
10257
+ "compact",
10258
+ "init",
10259
+ "update",
10260
+ "mcp",
10261
+ "logout",
10262
+ "help",
10263
+ "memory",
10264
+ "gateway",
10265
+ "hello",
10266
+ "community"
10267
+ ]);
8628
10268
  EFFORT_DESCRIPTIONS = {
8629
10269
  low: "low \u2014 fastest; lightest reasoning. Best for simple Q&A, small edits, quick coordination.",
8630
10270
  medium: "medium \u2014 balanced (default). Solid quality on most edits, fast on trivial prompts.",