kimiflare 0.24.0 → 0.25.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;
@@ -152,10 +151,11 @@ async function saveConfig(cfg) {
152
151
  await chmod(p, 384);
153
152
  return p;
154
153
  }
155
- var DEFAULT_MODEL, DEFAULT_REASONING_EFFORT;
154
+ var EFFORTS, DEFAULT_MODEL, DEFAULT_REASONING_EFFORT;
156
155
  var init_config = __esm({
157
156
  "src/config.ts"() {
158
157
  "use strict";
158
+ EFFORTS = ["low", "medium", "high"];
159
159
  DEFAULT_MODEL = "@cf/moonshotai/kimi-k2.6";
160
160
  DEFAULT_REASONING_EFFORT = "medium";
161
161
  }
@@ -173,10 +173,10 @@ async function* readSSE(stream, signal) {
173
173
  if (done) break;
174
174
  buffer += decoder.decode(value, { stream: true });
175
175
  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);
176
+ let sep3;
177
+ while ((sep3 = buffer.indexOf("\n\n")) !== -1) {
178
+ const event = buffer.slice(0, sep3);
179
+ buffer = buffer.slice(sep3 + 2);
180
180
  const data = extractData(event);
181
181
  if (data !== null) yield data;
182
182
  }
@@ -209,8 +209,8 @@ var init_errors = __esm({
209
209
  "src/util/errors.ts"() {
210
210
  "use strict";
211
211
  KimiApiError = class extends Error {
212
- constructor(message, code, httpStatus) {
213
- super(message);
212
+ constructor(message2, code, httpStatus) {
213
+ super(message2);
214
214
  this.code = code;
215
215
  this.httpStatus = httpStatus;
216
216
  this.name = "KimiApiError";
@@ -1027,8 +1027,8 @@ ${jsCode}
1027
1027
  await script.run(context, { timeout, release: true });
1028
1028
  await new Promise((r) => setTimeout(r, 10));
1029
1029
  } catch (err) {
1030
- const message = err instanceof Error ? err.message : String(err);
1031
- return { output: "", logs, error: message, toolCalls };
1030
+ const message2 = err instanceof Error ? err.message : String(err);
1031
+ return { output: "", logs, error: message2, toolCalls };
1032
1032
  } finally {
1033
1033
  isolate.dispose();
1034
1034
  }
@@ -1110,8 +1110,8 @@ ${jsCode}
1110
1110
  await runInNewContext(wrapped, sandbox, { timeout, displayErrors: true });
1111
1111
  await new Promise((r) => setTimeout(r, 10));
1112
1112
  } catch (err) {
1113
- const message = err instanceof Error ? err.message : String(err);
1114
- return { output: "", logs, error: message, toolCalls };
1113
+ const message2 = err instanceof Error ? err.message : String(err);
1114
+ return { output: "", logs, error: message2, toolCalls };
1115
1115
  }
1116
1116
  return { output: logs.join("\n"), logs, toolCalls };
1117
1117
  }
@@ -1119,8 +1119,8 @@ async function runInSandbox(opts2) {
1119
1119
  try {
1120
1120
  return await runWithIsolatedVm(opts2);
1121
1121
  } 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")) {
1122
+ const message2 = err instanceof Error ? err.message : String(err);
1123
+ if (message2.includes("isolated-vm") || message2.includes("Cannot find module") || message2.includes("bindings")) {
1124
1124
  return runWithNodeVm(opts2);
1125
1125
  }
1126
1126
  return runWithNodeVm(opts2);
@@ -1711,6 +1711,9 @@ function resolvePath(cwd, input) {
1711
1711
  }
1712
1712
  return isAbsolute(input) ? input : resolve(cwd, input);
1713
1713
  }
1714
+ function isPathOutside(relPath) {
1715
+ return relPath === ".." || relPath.startsWith(`..${sep}`) || isAbsolute(relPath);
1716
+ }
1714
1717
  function collapsePath(input, cwd, maxLen = 40) {
1715
1718
  if (!input) return input;
1716
1719
  let abs;
@@ -2839,6 +2842,16 @@ var init_expand_artifact = __esm({
2839
2842
  });
2840
2843
 
2841
2844
  // src/tools/executor.ts
2845
+ function isDiffCommand(cmd) {
2846
+ const trimmed = cmd.trim();
2847
+ if (/^git\s+show(?:\s|$)/.test(trimmed)) return true;
2848
+ if (/^git\s+diff(?:\s|$)/.test(trimmed)) return true;
2849
+ if (/^git\s+format-patch(?:\s|$)/.test(trimmed)) return true;
2850
+ const hasPatchFlag = /(?:^|\s)(?:-p|--patch)(?:\s|$)/.test(trimmed);
2851
+ if (/^git\s+log(?:\s|$)/.test(trimmed) && hasPatchFlag) return true;
2852
+ if (/^git\s+stash\s+show(?:\s|$)/.test(trimmed) && hasPatchFlag) return true;
2853
+ return false;
2854
+ }
2842
2855
  function normalizeToolOutput(result) {
2843
2856
  if (typeof result === "string") {
2844
2857
  const bytes = Buffer.byteLength(result, "utf8");
@@ -2941,6 +2954,20 @@ var init_executor = __esm({
2941
2954
  try {
2942
2955
  const result = await tool.run(args, ctx);
2943
2956
  const normalized = normalizeToolOutput(result);
2957
+ const cmd = call.name === "bash" && typeof args.command === "string" ? args.command : "";
2958
+ if (isDiffCommand(cmd)) {
2959
+ const artifactId = this.artifactStore.store(normalized.content);
2960
+ const bytes = Buffer.byteLength(normalized.content, "utf8");
2961
+ return {
2962
+ tool_call_id: call.id,
2963
+ name: call.name,
2964
+ content: normalized.content,
2965
+ ok: true,
2966
+ rawBytes: bytes,
2967
+ reducedBytes: bytes,
2968
+ artifactId
2969
+ };
2970
+ }
2944
2971
  const reduced = reduceToolOutput(
2945
2972
  call.name,
2946
2973
  normalized.content,
@@ -3087,13 +3114,17 @@ import { fileURLToPath as fileURLToPath2 } from "url";
3087
3114
  import { dirname as dirname3, join as join7 } from "path";
3088
3115
  function getAppVersion() {
3089
3116
  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";
3117
+ const here = dirname3(fileURLToPath2(import.meta.url));
3118
+ const candidates = [join7(here, "..", "..", "package.json"), join7(here, "..", "package.json")];
3119
+ for (const path of candidates) {
3120
+ try {
3121
+ const pkg = JSON.parse(readFileSync2(path, "utf8"));
3122
+ cachedVersion = pkg.version ?? "0.0.0";
3123
+ return cachedVersion;
3124
+ } catch {
3125
+ }
3096
3126
  }
3127
+ cachedVersion = "0.0.0";
3097
3128
  return cachedVersion;
3098
3129
  }
3099
3130
  var cachedVersion;
@@ -3577,11 +3608,12 @@ function recallArtifacts(messages, store, state) {
3577
3608
  }
3578
3609
  for (const failure of state.recent_failures) {
3579
3610
  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
- }
3611
+ if (!keyword) continue;
3612
+ const lowerKeyword = keyword.toLowerCase();
3613
+ if (!text.toLowerCase().includes(lowerKeyword)) continue;
3614
+ for (const [id, meta] of Object.entries(state.artifact_index)) {
3615
+ if (meta.source === "bash" && !ids.includes(id) && meta.summary.toLowerCase().includes(lowerKeyword)) {
3616
+ ids.push(id);
3585
3617
  }
3586
3618
  }
3587
3619
  }
@@ -4153,14 +4185,14 @@ function buildRightParts(usage, contextLimit, sessionUsage, gatewayMeta) {
4153
4185
  parts.push(`in ${sessionUsage.promptTokens}${cached ? ` (${cached} cached)` : ""}`);
4154
4186
  parts.push(`out ${sessionUsage.completionTokens}`);
4155
4187
  parts.push(`ctx ${pct}%`);
4156
- parts.push(`${sessionUsage.cost.toFixed(5)}`);
4188
+ parts.push(`$${sessionUsage.cost.toFixed(5)}`);
4157
4189
  } else {
4158
4190
  const cached = usage.prompt_tokens_details?.cached_tokens ?? 0;
4159
4191
  const cost = calculateCost(usage.prompt_tokens, usage.completion_tokens, cached);
4160
4192
  parts.push(`in ${usage.prompt_tokens}${cached ? ` (${cached} cached)` : ""}`);
4161
4193
  parts.push(`out ${usage.completion_tokens}`);
4162
4194
  parts.push(`ctx ${pct}%`);
4163
- parts.push(`${cost.total.toFixed(5)}`);
4195
+ parts.push(`$${cost.total.toFixed(5)}`);
4164
4196
  }
4165
4197
  const gatewayCache = formatGatewayCacheStatus(gatewayMeta);
4166
4198
  if (gatewayCache) parts.push(gatewayCache);
@@ -4910,18 +4942,18 @@ var init_source = __esm({
4910
4942
  }
4911
4943
  }
4912
4944
  });
4913
- createStyler = (open, close, parent) => {
4945
+ createStyler = (open3, close, parent) => {
4914
4946
  let openAll;
4915
4947
  let closeAll;
4916
4948
  if (parent === void 0) {
4917
- openAll = open;
4949
+ openAll = open3;
4918
4950
  closeAll = close;
4919
4951
  } else {
4920
- openAll = parent.openAll + open;
4952
+ openAll = parent.openAll + open3;
4921
4953
  closeAll = close + parent.closeAll;
4922
4954
  }
4923
4955
  return {
4924
- open,
4956
+ open: open3,
4925
4957
  close,
4926
4958
  openAll,
4927
4959
  closeAll,
@@ -5367,6 +5399,235 @@ var init_welcome = __esm({
5367
5399
  }
5368
5400
  });
5369
5401
 
5402
+ // src/ui/help-menu.tsx
5403
+ import { useState as useState6 } from "react";
5404
+ import { Box as Box12, Text as Text13, useInput as useInput2 } from "ink";
5405
+ import SelectInput4 from "ink-select-input";
5406
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
5407
+ function HelpMenu({ theme, themes, currentThemeName, customCommands, onDone, onCommand }) {
5408
+ const [page, setPage] = useState6("main");
5409
+ const customs = customCommands ?? [];
5410
+ useInput2((_input, key) => {
5411
+ if (key.escape) {
5412
+ if (page !== "main") {
5413
+ setPage("main");
5414
+ } else {
5415
+ onDone();
5416
+ }
5417
+ }
5418
+ });
5419
+ const handleSelect = (command) => {
5420
+ onCommand(command);
5421
+ onDone();
5422
+ };
5423
+ if (page === "main") {
5424
+ const items2 = CATEGORIES.map((cat) => ({
5425
+ label: cat.label,
5426
+ value: cat.key,
5427
+ key: cat.key
5428
+ }));
5429
+ if (customs.length > 0) {
5430
+ items2.push({ label: "Custom commands", value: "custom", key: "custom" });
5431
+ }
5432
+ items2.push({ label: "(close)", value: "__close__", key: "__close__" });
5433
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
5434
+ /* @__PURE__ */ jsx13(Text13, { color: theme.accent, bold: true, children: "Help" }),
5435
+ /* @__PURE__ */ jsx13(Text13, { color: theme.info.color, dimColor: false, children: "Arrow keys to navigate, Enter to select, Esc to close." }),
5436
+ /* @__PURE__ */ jsx13(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx13(
5437
+ SelectInput4,
5438
+ {
5439
+ items: items2,
5440
+ onSelect: (item) => {
5441
+ if (item.value === "__close__") {
5442
+ onDone();
5443
+ } else {
5444
+ setPage(item.value);
5445
+ }
5446
+ }
5447
+ }
5448
+ ) }),
5449
+ /* @__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)) }),
5450
+ /* @__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" }) })
5451
+ ] });
5452
+ }
5453
+ if (page === "theme") {
5454
+ const items2 = themes.map((t) => ({
5455
+ label: t.name === currentThemeName ? `${t.label} \xB7 current` : t.label,
5456
+ value: t.name,
5457
+ key: t.name
5458
+ }));
5459
+ items2.push({ label: "\u2190 Back", value: "__back__", key: "__back__" });
5460
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
5461
+ /* @__PURE__ */ jsx13(Text13, { color: theme.accent, bold: true, children: "Theme" }),
5462
+ /* @__PURE__ */ jsx13(Text13, { color: theme.info.color, dimColor: false, children: "Arrow keys to navigate, Enter to apply, Esc to go back." }),
5463
+ /* @__PURE__ */ jsx13(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx13(
5464
+ SelectInput4,
5465
+ {
5466
+ items: items2,
5467
+ onSelect: (item) => {
5468
+ if (item.value === "__back__") {
5469
+ setPage("main");
5470
+ } else {
5471
+ handleSelect(`/theme ${item.value}`);
5472
+ }
5473
+ }
5474
+ }
5475
+ ) })
5476
+ ] });
5477
+ }
5478
+ if (page === "custom") {
5479
+ const items2 = customs.map((c) => ({
5480
+ label: `${`/${c.name}`.padEnd(28)} ${c.description ?? ""}`.trimEnd(),
5481
+ value: `/${c.name}`,
5482
+ key: c.name
5483
+ }));
5484
+ items2.push({ label: "\u2190 Back", value: "__back__", key: "__back__" });
5485
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
5486
+ /* @__PURE__ */ jsx13(Text13, { color: theme.accent, bold: true, children: "Custom commands" }),
5487
+ /* @__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." }),
5488
+ /* @__PURE__ */ jsx13(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx13(
5489
+ SelectInput4,
5490
+ {
5491
+ items: items2,
5492
+ onSelect: (item) => {
5493
+ if (item.value === "__back__") {
5494
+ setPage("main");
5495
+ } else {
5496
+ handleSelect(item.value);
5497
+ }
5498
+ }
5499
+ }
5500
+ ) })
5501
+ ] });
5502
+ }
5503
+ const category = CATEGORIES.find((c) => c.key === page);
5504
+ const selectable = category.commands.filter((cmd) => cmd.selectable !== false);
5505
+ const staticCmds = category.commands.filter((cmd) => cmd.selectable === false);
5506
+ const items = selectable.map((cmd) => ({
5507
+ label: `${cmd.command.padEnd(28)} ${cmd.description}`,
5508
+ value: cmd.command,
5509
+ key: cmd.command
5510
+ }));
5511
+ items.push({ label: "\u2190 Back", value: "__back__", key: "__back__" });
5512
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
5513
+ /* @__PURE__ */ jsx13(Text13, { color: theme.accent, bold: true, children: category.label }),
5514
+ /* @__PURE__ */ jsx13(Text13, { color: theme.info.color, dimColor: false, children: "Arrow keys to navigate, Enter to execute, Esc to go back." }),
5515
+ /* @__PURE__ */ jsx13(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx13(
5516
+ SelectInput4,
5517
+ {
5518
+ items,
5519
+ onSelect: (item) => {
5520
+ if (item.value === "__back__") {
5521
+ setPage("main");
5522
+ } else {
5523
+ handleSelect(item.value);
5524
+ }
5525
+ }
5526
+ }
5527
+ ) }),
5528
+ 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)) })
5529
+ ] });
5530
+ }
5531
+ var CATEGORIES, SINGLE_COMMANDS;
5532
+ var init_help_menu = __esm({
5533
+ "src/ui/help-menu.tsx"() {
5534
+ "use strict";
5535
+ CATEGORIES = [
5536
+ {
5537
+ key: "mode",
5538
+ label: "Mode",
5539
+ commands: [
5540
+ { command: "/mode edit", description: "switch to edit mode" },
5541
+ { command: "/mode plan", description: "switch to plan mode" },
5542
+ { command: "/mode auto", description: "switch to auto mode" }
5543
+ ]
5544
+ },
5545
+ {
5546
+ key: "thinking",
5547
+ label: "Thinking",
5548
+ commands: [
5549
+ { command: "/thinking low", description: "fast, lower quality" },
5550
+ { command: "/thinking medium", description: "balanced" },
5551
+ { command: "/thinking high", description: "slow, higher quality" }
5552
+ ]
5553
+ },
5554
+ {
5555
+ key: "theme",
5556
+ label: "Theme",
5557
+ commands: []
5558
+ },
5559
+ {
5560
+ key: "session",
5561
+ label: "Session",
5562
+ commands: [
5563
+ { command: "/resume", description: "pick a past conversation" },
5564
+ { command: "/compact", description: "summarize old turns to free context" },
5565
+ { command: "/clear", description: "clear current conversation" }
5566
+ ]
5567
+ },
5568
+ {
5569
+ key: "memory",
5570
+ label: "Memory",
5571
+ commands: [
5572
+ { command: "/memory", description: "show memory stats" },
5573
+ { command: "/memory on", description: "enable memory" },
5574
+ { command: "/memory off", description: "disable memory" },
5575
+ { command: "/memory clear", description: "wipe memories for this repo" },
5576
+ { command: "/memory search <query>", description: "search stored memories", selectable: false }
5577
+ ]
5578
+ },
5579
+ {
5580
+ key: "mcp",
5581
+ label: "MCP",
5582
+ commands: [
5583
+ { command: "/mcp list", description: "list connected MCP servers and tools" },
5584
+ { command: "/mcp reload", description: "reconnect all configured MCP servers" }
5585
+ ]
5586
+ },
5587
+ {
5588
+ key: "gateway",
5589
+ label: "Gateway",
5590
+ commands: [
5591
+ { command: "/gateway", description: "show gateway status" },
5592
+ { command: "/gateway off", description: "disable AI Gateway (direct Workers AI)" },
5593
+ { command: "/gateway skip-cache true", description: "enable skip-cache" },
5594
+ { command: "/gateway skip-cache false", description: "disable skip-cache" },
5595
+ { command: "/gateway collect-logs true", description: "enable log collection" },
5596
+ { command: "/gateway collect-logs false", description: "disable log collection" },
5597
+ { command: "/gateway metadata clear", description: "remove all metadata" },
5598
+ { command: "/gateway <id>", description: "enable AI Gateway", selectable: false },
5599
+ { command: "/gateway cache-ttl <seconds>", description: "set cache TTL", selectable: false },
5600
+ { command: "/gateway metadata <key>=<value>", description: "add metadata", selectable: false }
5601
+ ]
5602
+ },
5603
+ {
5604
+ key: "info",
5605
+ label: "Info",
5606
+ commands: [
5607
+ { command: "/cost", description: "show cost report" },
5608
+ { command: "/model", description: "show current model" },
5609
+ { command: "/update", description: "check for updates" },
5610
+ { command: "/hello", description: "send a voice note to the creator" },
5611
+ { command: "/community", description: "join our Discord server" }
5612
+ ]
5613
+ },
5614
+ {
5615
+ key: "config",
5616
+ label: "Config",
5617
+ commands: [
5618
+ { command: "/init", description: "scan this repo and write a KIMI.md" },
5619
+ { command: "/logout", description: "clear credentials" }
5620
+ ]
5621
+ }
5622
+ ];
5623
+ SINGLE_COMMANDS = [
5624
+ { command: "/reasoning", description: "toggle show/hide model reasoning" },
5625
+ { command: "/help", description: "show this menu" },
5626
+ { command: "/exit", description: "exit kimiflare" }
5627
+ ];
5628
+ }
5629
+ });
5630
+
5370
5631
  // src/ui/theme.ts
5371
5632
  function resolveTheme(name) {
5372
5633
  if (!name) return THEMES[DEFAULT_THEME_NAME];
@@ -6932,19 +7193,358 @@ var init_state = __esm({
6932
7193
  }
6933
7194
  });
6934
7195
 
7196
+ // src/commands/frontmatter.ts
7197
+ function parseFrontmatter(input) {
7198
+ const errors = [];
7199
+ if (!FENCE.test(input)) {
7200
+ return { data: {}, body: input, errors };
7201
+ }
7202
+ const afterOpen = input.replace(FENCE, "");
7203
+ const closeIdx = afterOpen.search(/\r?\n---\s*(\r?\n|$)/);
7204
+ if (closeIdx === -1) {
7205
+ errors.push("frontmatter not closed with ---");
7206
+ return { data: {}, body: input, errors };
7207
+ }
7208
+ const yaml = afterOpen.slice(0, closeIdx);
7209
+ const closeMatch = afterOpen.slice(closeIdx).match(/\r?\n---\s*(\r?\n|$)/);
7210
+ const body = closeMatch ? afterOpen.slice(closeIdx + closeMatch[0].length) : "";
7211
+ const data = {};
7212
+ const lines = yaml.split(/\r?\n/);
7213
+ for (const line of lines) {
7214
+ if (line.trim() === "" || line.trim().startsWith("#")) continue;
7215
+ const m = line.match(KV);
7216
+ if (!m) {
7217
+ errors.push(`unparseable line: ${line.trim()}`);
7218
+ continue;
7219
+ }
7220
+ const key = m[1];
7221
+ let value = m[2] ?? "";
7222
+ if (value.startsWith('"') && value.endsWith('"') && value.length >= 2 || value.startsWith("'") && value.endsWith("'") && value.length >= 2) {
7223
+ value = value.slice(1, -1);
7224
+ }
7225
+ data[key] = value;
7226
+ }
7227
+ return { data, body, errors };
7228
+ }
7229
+ var FENCE, KV;
7230
+ var init_frontmatter = __esm({
7231
+ "src/commands/frontmatter.ts"() {
7232
+ "use strict";
7233
+ FENCE = /^---\s*\r?\n/;
7234
+ KV = /^([A-Za-z][\w-]*)\s*:\s*(.*?)\s*$/;
7235
+ }
7236
+ });
7237
+
7238
+ // src/commands/loader.ts
7239
+ import { open, realpath } from "fs/promises";
7240
+ import { homedir as homedir9 } from "os";
7241
+ import { join as join12, relative as relative2, sep as sep2 } from "path";
7242
+ import fg3 from "fast-glob";
7243
+ function projectCommandsDir(cwd = process.cwd()) {
7244
+ return join12(cwd, ".kimiflare", "commands");
7245
+ }
7246
+ function globalCommandsDir() {
7247
+ const xdg = process.env.XDG_CONFIG_HOME || join12(homedir9(), ".config");
7248
+ return join12(xdg, "kimiflare", "commands");
7249
+ }
7250
+ async function loadCustomCommands(cwd = process.cwd()) {
7251
+ const warnings = [];
7252
+ const byName = /* @__PURE__ */ new Map();
7253
+ const sources = [
7254
+ { dir: globalCommandsDir(), source: "global" },
7255
+ { dir: projectCommandsDir(cwd), source: "project" }
7256
+ ];
7257
+ const perSource = await Promise.all(
7258
+ sources.map(async ({ dir, source }) => {
7259
+ const safeDir = await resolveSafeDir(dir, source, cwd, warnings);
7260
+ if (safeDir === null) return [];
7261
+ const files = await fg3("**/*.md", {
7262
+ cwd: safeDir,
7263
+ absolute: true,
7264
+ onlyFiles: true,
7265
+ followSymbolicLinks: false,
7266
+ suppressErrors: true
7267
+ });
7268
+ return Promise.all(files.map((file) => loadOne(file, safeDir, source, warnings)));
7269
+ })
7270
+ );
7271
+ for (const loaded of perSource) {
7272
+ for (const cmd of loaded) {
7273
+ if (cmd) byName.set(cmd.name, cmd);
7274
+ }
7275
+ }
7276
+ return {
7277
+ commands: [...byName.values()].sort((a, b) => a.name.localeCompare(b.name)),
7278
+ warnings
7279
+ };
7280
+ }
7281
+ async function resolveSafeDir(dir, source, cwd, warnings) {
7282
+ let realDir;
7283
+ try {
7284
+ realDir = await realpath(dir);
7285
+ } catch (err) {
7286
+ const code = err.code;
7287
+ if (code !== "ENOENT" && code !== "ENOTDIR") {
7288
+ warnings.push(`commands dir ${dir} unreadable: ${err.message}`);
7289
+ }
7290
+ return null;
7291
+ }
7292
+ if (source === "project") {
7293
+ let realCwd;
7294
+ try {
7295
+ realCwd = await realpath(cwd);
7296
+ } catch {
7297
+ return null;
7298
+ }
7299
+ const rel = relative2(realCwd, realDir);
7300
+ if (rel !== "" && isPathOutside(rel)) {
7301
+ warnings.push(`commands dir ${dir} escapes workspace via symlink \u2014 skipped`);
7302
+ return null;
7303
+ }
7304
+ }
7305
+ return realDir;
7306
+ }
7307
+ async function loadOne(file, rootDir, source, warnings) {
7308
+ let content;
7309
+ try {
7310
+ const handle = await open(file, "r");
7311
+ try {
7312
+ const stats = await handle.stat();
7313
+ if (stats.size > MAX_COMMAND_FILE_BYTES) {
7314
+ warnings.push(`command file ${file} exceeds ${MAX_COMMAND_FILE_BYTES} bytes \u2014 skipped`);
7315
+ return null;
7316
+ }
7317
+ content = await handle.readFile("utf8");
7318
+ } finally {
7319
+ await handle.close();
7320
+ }
7321
+ } catch (e) {
7322
+ warnings.push(`failed to read command file ${file}: ${e.message}`);
7323
+ return null;
7324
+ }
7325
+ const name = filenameToCommandName(file, rootDir);
7326
+ if (!name) {
7327
+ warnings.push(`invalid command name from ${file}`);
7328
+ return null;
7329
+ }
7330
+ const { data, body, errors } = parseFrontmatter(content);
7331
+ if (errors.length > 0) {
7332
+ warnings.push(`frontmatter errors in ${file}: ${errors.join("; ")} \u2014 skipped`);
7333
+ return null;
7334
+ }
7335
+ const cmd = {
7336
+ name,
7337
+ template: body,
7338
+ source,
7339
+ filepath: file
7340
+ };
7341
+ if (data.description) cmd.description = data.description;
7342
+ const modeRaw = data.mode ?? data.agent;
7343
+ if (modeRaw !== void 0) {
7344
+ const normalized = modeRaw === "build" ? "edit" : modeRaw;
7345
+ if (MODES.includes(normalized)) {
7346
+ cmd.mode = normalized;
7347
+ } else {
7348
+ warnings.push(`unknown mode "${modeRaw}" in ${file} \u2014 ignored`);
7349
+ }
7350
+ }
7351
+ if (data.model !== void 0 && data.model !== "") {
7352
+ cmd.model = data.model;
7353
+ }
7354
+ if (data.effort !== void 0) {
7355
+ if (EFFORTS.includes(data.effort)) {
7356
+ cmd.effort = data.effort;
7357
+ } else {
7358
+ warnings.push(`unknown effort "${data.effort}" in ${file} \u2014 ignored`);
7359
+ }
7360
+ }
7361
+ return cmd;
7362
+ }
7363
+ function filenameToCommandName(file, rootDir) {
7364
+ const rel = relative2(rootDir, file);
7365
+ if (!rel || isPathOutside(rel)) return null;
7366
+ const noExt = rel.replace(/\.md$/i, "");
7367
+ const parts = noExt.split(sep2).filter((p) => p.length > 0);
7368
+ if (parts.length === 0) return null;
7369
+ if (parts.some((p) => !/^[\w.-]+$/.test(p))) return null;
7370
+ return parts.join("/");
7371
+ }
7372
+ var MAX_COMMAND_FILE_BYTES;
7373
+ var init_loader = __esm({
7374
+ "src/commands/loader.ts"() {
7375
+ "use strict";
7376
+ init_mode();
7377
+ init_config();
7378
+ init_paths();
7379
+ init_frontmatter();
7380
+ MAX_COMMAND_FILE_BYTES = 256 * 1024;
7381
+ }
7382
+ });
7383
+
7384
+ // src/commands/renderer.ts
7385
+ import { exec } from "child_process";
7386
+ import { open as open2, realpath as realpath2 } from "fs/promises";
7387
+ import { isAbsolute as isAbsolute2, relative as relative3, resolve as resolvePathJoin } from "path";
7388
+ import { promisify as promisify2 } from "util";
7389
+ function tokenizeArgs(s) {
7390
+ return [...s.matchAll(ARG_TOKEN_RE)].map((match) => {
7391
+ const token = match[0];
7392
+ if (token.length >= 2 && (token.startsWith('"') && token.endsWith('"') || token.startsWith("'") && token.endsWith("'"))) {
7393
+ return token.slice(1, -1);
7394
+ }
7395
+ return token;
7396
+ });
7397
+ }
7398
+ async function renderCommand(cmd, rawInput, opts2 = {}) {
7399
+ const warnings = [];
7400
+ const cwd = opts2.cwd ?? process.cwd();
7401
+ const maxFileBytes = opts2.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
7402
+ const argsString = stripCommandName(rawInput);
7403
+ const args = tokenizeArgs(argsString);
7404
+ const originalTemplate = cmd.template;
7405
+ const hadArguments = originalTemplate.includes("$ARGUMENTS");
7406
+ const hadPositionals = HAS_POSITIONAL.test(originalTemplate);
7407
+ let prompt = replacePositionals(originalTemplate, args);
7408
+ prompt = prompt.replaceAll("$ARGUMENTS", argsString);
7409
+ if (!hadArguments && !hadPositionals && argsString !== "") {
7410
+ prompt += `
7411
+
7412
+ ${argsString}`;
7413
+ }
7414
+ prompt = await replaceShell(prompt, warnings, opts2.shellTimeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS);
7415
+ prompt = await replaceFiles(prompt, warnings, cwd, maxFileBytes);
7416
+ if (prompt.trim() === "") {
7417
+ warnings.push("rendered prompt is empty");
7418
+ }
7419
+ return { prompt, warnings };
7420
+ }
7421
+ function stripCommandName(rawInput) {
7422
+ if (!rawInput.startsWith("/")) {
7423
+ return rawInput;
7424
+ }
7425
+ return rawInput.replace(/^\/\S+\s*/, "");
7426
+ }
7427
+ function replacePositionals(template, args) {
7428
+ const indexes = [...template.matchAll(POSITIONAL_RE)].map(
7429
+ (match) => Number(match[1])
7430
+ );
7431
+ const highest = indexes.length === 0 ? -1 : Math.max(...indexes);
7432
+ return template.replace(POSITIONAL_RE, (_match, n) => {
7433
+ const index = Number(n);
7434
+ if (index <= 0) {
7435
+ return "";
7436
+ }
7437
+ if (index === highest) {
7438
+ return args.slice(index - 1).join(" ");
7439
+ }
7440
+ return args[index - 1] ?? "";
7441
+ });
7442
+ }
7443
+ async function replaceShell(prompt, warnings, shellTimeoutMs) {
7444
+ const matches = [...prompt.matchAll(SHELL_RE)];
7445
+ const replacements = await Promise.all(
7446
+ matches.map(async (match) => {
7447
+ const command = match[1] ?? "";
7448
+ try {
7449
+ const { stdout } = await execAsync(command, {
7450
+ timeout: shellTimeoutMs,
7451
+ maxBuffer: 1024 * 1024
7452
+ });
7453
+ return String(stdout).trimEnd();
7454
+ } catch (error) {
7455
+ warnings.push(`shell command failed: \`${command}\` \u2014 ${message(error)}`);
7456
+ return "";
7457
+ }
7458
+ })
7459
+ );
7460
+ let index = 0;
7461
+ return prompt.replace(SHELL_RE, () => replacements[index++] ?? "");
7462
+ }
7463
+ async function replaceFiles(prompt, warnings, cwd, maxFileBytes) {
7464
+ const matches = [...prompt.matchAll(FILE_RE)];
7465
+ if (matches.length === 0) return prompt;
7466
+ const realCwd = await realpath2(cwd).catch(() => cwd);
7467
+ const replacements = await Promise.all(
7468
+ matches.map(async (match) => {
7469
+ const rawPath = match[1] ?? "";
7470
+ if (isAbsolute2(rawPath) || rawPath.startsWith("~")) {
7471
+ warnings.push(`file inclusion skipped: @${rawPath} \u2014 outside workspace`);
7472
+ return "";
7473
+ }
7474
+ const resolved = resolvePathJoin(cwd, rawPath);
7475
+ if (isPathOutside(relative3(cwd, resolved))) {
7476
+ warnings.push(`file inclusion skipped: @${rawPath} \u2014 outside workspace`);
7477
+ return "";
7478
+ }
7479
+ let real;
7480
+ try {
7481
+ real = await realpath2(resolved);
7482
+ } catch (error) {
7483
+ warnings.push(`file inclusion failed: @${rawPath} \u2014 ${message(error)}`);
7484
+ return "";
7485
+ }
7486
+ if (isPathOutside(relative3(realCwd, real))) {
7487
+ warnings.push(`file inclusion skipped: @${rawPath} \u2014 symlink escapes workspace`);
7488
+ return "";
7489
+ }
7490
+ try {
7491
+ const handle = await open2(real, "r");
7492
+ try {
7493
+ const stats = await handle.stat();
7494
+ if (stats.size > maxFileBytes) {
7495
+ warnings.push(
7496
+ `file inclusion skipped: @${rawPath} \u2014 exceeds ${maxFileBytes} bytes`
7497
+ );
7498
+ return "";
7499
+ }
7500
+ return await handle.readFile("utf8");
7501
+ } finally {
7502
+ await handle.close();
7503
+ }
7504
+ } catch (error) {
7505
+ warnings.push(`file inclusion failed: @${rawPath} \u2014 ${message(error)}`);
7506
+ return "";
7507
+ }
7508
+ })
7509
+ );
7510
+ let index = 0;
7511
+ return prompt.replace(FILE_RE, (_match, _path, trailing = "") => {
7512
+ const replacement = replacements[index++] ?? "";
7513
+ return replacement + trailing;
7514
+ });
7515
+ }
7516
+ function message(error) {
7517
+ return error instanceof Error ? error.message : String(error);
7518
+ }
7519
+ var execAsync, ARG_TOKEN_RE, POSITIONAL_RE, HAS_POSITIONAL, SHELL_RE, FILE_RE, DEFAULT_MAX_FILE_BYTES, DEFAULT_SHELL_TIMEOUT_MS;
7520
+ var init_renderer = __esm({
7521
+ "src/commands/renderer.ts"() {
7522
+ "use strict";
7523
+ init_paths();
7524
+ execAsync = promisify2(exec);
7525
+ ARG_TOKEN_RE = /(?:"[^"]*"|'[^']*'|[^\s"']+)/g;
7526
+ POSITIONAL_RE = /\$(\d+)/g;
7527
+ HAS_POSITIONAL = /\$\d+/;
7528
+ SHELL_RE = /!`([^`]+)`/g;
7529
+ FILE_RE = /(?<![\w`])@(\.?[^\s`,]+?)([.,;:!?)\]}]*)(?=[\s`,]|$)/g;
7530
+ DEFAULT_MAX_FILE_BYTES = 100 * 1024;
7531
+ DEFAULT_SHELL_TIMEOUT_MS = 5e3;
7532
+ }
7533
+ });
7534
+
6935
7535
  // src/app.tsx
6936
7536
  var app_exports = {};
6937
7537
  __export(app_exports, {
6938
7538
  renderApp: () => renderApp
6939
7539
  });
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";
7540
+ import { useState as useState7, useRef as useRef3, useEffect as useEffect4, useCallback } from "react";
7541
+ import { Box as Box13, Text as Text14, useApp, useInput as useInput3, render } from "ink";
6942
7542
  import { existsSync } from "fs";
6943
- import { join as join12 } from "path";
7543
+ import { join as join13 } from "path";
6944
7544
  import { unlink as unlink2 } from "fs/promises";
6945
7545
  import { spawn as spawn2 } from "child_process";
6946
7546
  import { platform as platform2 } from "os";
6947
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
7547
+ import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
6948
7548
  function gatewayFromConfig(cfg) {
6949
7549
  if (!cfg.aiGatewayId) return void 0;
6950
7550
  return {
@@ -7015,8 +7615,8 @@ function findImagePaths(text) {
7015
7615
  }
7016
7616
  function App({ initialCfg, initialUpdateResult }) {
7017
7617
  const { exit } = useApp();
7018
- const [cfg, setCfg] = useState6(initialCfg);
7019
- const [events, setRawEvents] = useState6([]);
7618
+ const [cfg, setCfg] = useState7(initialCfg);
7619
+ const [events, setRawEvents] = useState7([]);
7020
7620
  const setEvents = useCallback(
7021
7621
  (updater) => {
7022
7622
  setRawEvents((prev) => {
@@ -7026,33 +7626,34 @@ function App({ initialCfg, initialUpdateResult }) {
7026
7626
  },
7027
7627
  []
7028
7628
  );
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(
7629
+ const [input, setInput] = useState7("");
7630
+ const [busy, setBusy] = useState7(false);
7631
+ const [usage, setUsage] = useState7(null);
7632
+ const [sessionUsage, setSessionUsage] = useState7(null);
7633
+ const [gatewayMeta, setGatewayMeta] = useState7(null);
7634
+ const [showReasoning, setShowReasoning] = useState7(false);
7635
+ const [perm, setPerm] = useState7(null);
7636
+ const [queue, setQueue] = useState7([]);
7637
+ const [history, setHistory] = useState7([]);
7638
+ const [historyIndex, setHistoryIndex] = useState7(-1);
7639
+ const [draftInput, setDraftInput] = useState7("");
7640
+ const [mode, setMode] = useState7("edit");
7641
+ const [codeMode, setCodeMode] = useState7(initialCfg?.codeMode ?? false);
7642
+ const [effort, setEffort] = useState7(
7043
7643
  initialCfg?.reasoningEffort ?? DEFAULT_REASONING_EFFORT
7044
7644
  );
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);
7645
+ const [theme, setTheme] = useState7(resolveTheme(initialCfg?.theme));
7646
+ const [resumeSessions, setResumeSessions] = useState7(null);
7647
+ const [showThemePicker, setShowThemePicker] = useState7(false);
7648
+ const [showHelpMenu, setShowHelpMenu] = useState7(false);
7649
+ const [originalTheme, setOriginalTheme] = useState7(null);
7650
+ const [tasks, setTasks] = useState7([]);
7651
+ const [tasksStartedAt, setTasksStartedAt] = useState7(null);
7652
+ const [tasksStartTokens, setTasksStartTokens] = useState7(0);
7653
+ const [turnStartedAt, setTurnStartedAt] = useState7(null);
7654
+ const [verbose, setVerbose] = useState7(false);
7655
+ const [hasUpdate, setHasUpdate] = useState7(initialUpdateResult?.hasUpdate ?? false);
7656
+ const [latestVersion, setLatestVersion] = useState7(initialUpdateResult?.latestVersion ?? null);
7056
7657
  const cacheStableRef = useRef3(initialCfg?.cacheStablePrompts !== false);
7057
7658
  const messagesRef = useRef3(
7058
7659
  makePrefixMessages(cacheStableRef.current, cfg?.model ?? DEFAULT_MODEL, "edit", ALL_TOOLS)
@@ -7078,6 +7679,7 @@ function App({ initialCfg, initialUpdateResult }) {
7078
7679
  const memoryManagerRef = useRef3(null);
7079
7680
  const pendingTextRef = useRef3(/* @__PURE__ */ new Map());
7080
7681
  const flushTimeoutRef = useRef3(null);
7682
+ const customCommandsRef = useRef3([]);
7081
7683
  useEffect4(() => {
7082
7684
  if (!cfg) return;
7083
7685
  void Promise.resolve().then(() => (init_sessions(), sessions_exports)).then(
@@ -7104,7 +7706,7 @@ function App({ initialCfg, initialUpdateResult }) {
7104
7706
  }
7105
7707
  });
7106
7708
  if (cfg.memoryEnabled) {
7107
- const dbPath = cfg.memoryDbPath ?? join12(process.cwd(), ".kimiflare", "memory.db");
7709
+ const dbPath = cfg.memoryDbPath ?? join13(process.cwd(), ".kimiflare", "memory.db");
7108
7710
  const manager = new MemoryManager({
7109
7711
  dbPath,
7110
7712
  accountId: cfg.accountId,
@@ -7138,7 +7740,20 @@ function App({ initialCfg, initialUpdateResult }) {
7138
7740
  memoryManagerRef.current?.close();
7139
7741
  memoryManagerRef.current = null;
7140
7742
  }
7141
- }, [cfg]);
7743
+ void loadCustomCommands(process.cwd()).then(({ commands, warnings }) => {
7744
+ customCommandsRef.current = commands;
7745
+ for (const w of warnings) {
7746
+ setEvents((e) => [...e, { kind: "info", key: mkKey(), text: `commands: ${w}` }]);
7747
+ }
7748
+ const shadowed = commands.filter((c) => BUILTIN_COMMAND_NAMES.has(c.name.toLowerCase()));
7749
+ for (const c of shadowed) {
7750
+ setEvents((e) => [
7751
+ ...e,
7752
+ { kind: "info", key: mkKey(), text: `commands: /${c.name} (${c.filepath}) shadowed by built-in \u2014 will not run` }
7753
+ ]);
7754
+ }
7755
+ });
7756
+ }, [cfg, setEvents]);
7142
7757
  useEffect4(() => {
7143
7758
  if (!cfg || updateCheckedRef.current) return;
7144
7759
  updateCheckedRef.current = true;
@@ -7346,7 +7961,7 @@ function App({ initialCfg, initialUpdateResult }) {
7346
7961
  } catch {
7347
7962
  }
7348
7963
  }, [cfg, ensureSessionId]);
7349
- useInput2((inputChar, key) => {
7964
+ useInput3((inputChar, key) => {
7350
7965
  if (key.ctrl && inputChar === "c") {
7351
7966
  if (busy && activeControllerRef.current) {
7352
7967
  activeControllerRef.current.abort();
@@ -7537,13 +8152,13 @@ function App({ initialCfg, initialUpdateResult }) {
7537
8152
  }
7538
8153
  const cwd = process.cwd();
7539
8154
  for (const name of ["KIMI.md", "KIMIFLARE.md", "AGENT.md"]) {
7540
- if (existsSync(join12(cwd, name))) {
8155
+ if (existsSync(join13(cwd, name))) {
7541
8156
  setEvents((e) => [
7542
8157
  ...e,
7543
8158
  {
7544
8159
  kind: "info",
7545
8160
  key: mkKey(),
7546
- text: `${name} already exists at ${join12(cwd, name)} \u2014 delete it first if you want to regenerate`
8161
+ text: `${name} already exists at ${join13(cwd, name)} \u2014 delete it first if you want to regenerate`
7547
8162
  }
7548
8163
  ]);
7549
8164
  return;
@@ -7671,7 +8286,7 @@ function App({ initialCfg, initialUpdateResult }) {
7671
8286
  })
7672
8287
  }
7673
8288
  });
7674
- if (existsSync(join12(cwd, "KIMI.md"))) {
8289
+ if (existsSync(join13(cwd, "KIMI.md"))) {
7675
8290
  if (cacheStableRef.current) {
7676
8291
  messagesRef.current[1] = {
7677
8292
  role: "system",
@@ -8035,8 +8650,25 @@ use: /thinking low | medium | high`
8035
8650
  return true;
8036
8651
  }
8037
8652
  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" }]);
8653
+ if (!cfg) return true;
8654
+ if (arg === "on") {
8655
+ const next = { ...cfg, memoryEnabled: true };
8656
+ setCfg(next);
8657
+ void saveConfig(next).catch(() => {
8658
+ });
8659
+ setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "memory enabled" }]);
8660
+ return true;
8661
+ }
8662
+ if (arg === "off") {
8663
+ const next = { ...cfg, memoryEnabled: false };
8664
+ setCfg(next);
8665
+ void saveConfig(next).catch(() => {
8666
+ });
8667
+ setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "memory disabled" }]);
8668
+ return true;
8669
+ }
8670
+ if (!cfg.memoryEnabled) {
8671
+ setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "memory is disabled. Use /memory on to enable it, or set KIMIFLARE_MEMORY_ENABLED=1" }]);
8040
8672
  return true;
8041
8673
  }
8042
8674
  if (arg === "clear") {
@@ -8186,27 +8818,57 @@ ${lines.join("\n")}` }]);
8186
8818
  return true;
8187
8819
  }
8188
8820
  if (c === "/help") {
8189
- setEvents((e) => [
8190
- ...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
- }
8196
- ]);
8821
+ setShowHelpMenu(true);
8197
8822
  return true;
8198
8823
  }
8199
8824
  return false;
8200
8825
  },
8201
8826
  [cfg, exit, usage, effort, theme, mode, openResumePicker, runCompact, runInit, initMcp, setCfg]
8202
8827
  );
8828
+ const handleHelpCommand = useCallback(
8829
+ (command) => {
8830
+ setShowHelpMenu(false);
8831
+ const executed = handleSlash(command);
8832
+ if (!executed) {
8833
+ setEvents((e) => [...e, { kind: "error", key: mkKey(), text: `unknown command: ${command}` }]);
8834
+ }
8835
+ },
8836
+ [handleSlash]
8837
+ );
8203
8838
  const processMessage = useCallback(
8204
8839
  async (text, displayText) => {
8205
8840
  if (!cfg) return;
8206
- const trimmed = text.trim();
8841
+ let trimmed = text.trim();
8207
8842
  if (!trimmed) return;
8208
- if (trimmed.startsWith("/") && handleSlash(trimmed)) return;
8209
- const display = displayText?.trim() || trimmed;
8843
+ let overrideModel;
8844
+ let overrideEffort;
8845
+ let display = displayText?.trim() || trimmed;
8846
+ if (trimmed.startsWith("/")) {
8847
+ if (handleSlash(trimmed)) return;
8848
+ const head = trimmed.split(/\s+/)[0].slice(1);
8849
+ const custom = customCommandsRef.current.find((c) => c.name === head);
8850
+ if (custom) {
8851
+ const info = (text2) => setEvents((e) => [...e, { kind: "info", key: mkKey(), text: text2 }]);
8852
+ const { prompt: rendered, warnings } = await renderCommand(custom, trimmed, {
8853
+ cwd: process.cwd()
8854
+ });
8855
+ for (const w of warnings) info(`${custom.name}: ${w}`);
8856
+ if (!rendered.trim()) return;
8857
+ const parts = [];
8858
+ if (custom.model) {
8859
+ overrideModel = custom.model;
8860
+ parts.push(`model=${custom.model}`);
8861
+ }
8862
+ if (custom.effort) {
8863
+ overrideEffort = custom.effort;
8864
+ parts.push(`effort=${custom.effort}`);
8865
+ }
8866
+ if (parts.length > 0) info(`command '${custom.name}' \u2192 ${parts.join(", ")} (this turn)`);
8867
+ if (custom.mode) info(`note: mode override (${custom.mode}) is not yet wired; current mode applies`);
8868
+ display = trimmed;
8869
+ trimmed = rendered;
8870
+ }
8871
+ }
8210
8872
  const imagePaths = findImagePaths(trimmed).slice(0, MAX_IMAGES_PER_MESSAGE);
8211
8873
  let images = [];
8212
8874
  let content = sanitizeString(trimmed);
@@ -8258,14 +8920,14 @@ ${lines.join("\n")}` }]);
8258
8920
  await runAgentTurn({
8259
8921
  accountId: cfg.accountId,
8260
8922
  apiToken: cfg.apiToken,
8261
- model: cfg.model,
8923
+ model: overrideModel ?? cfg.model,
8262
8924
  gateway: gatewayFromConfig(cfg),
8263
8925
  messages: messagesRef.current,
8264
8926
  tools: [...ALL_TOOLS, ...mcpToolsRef.current],
8265
8927
  executor: executorRef.current,
8266
8928
  cwd: process.cwd(),
8267
8929
  signal: controller.signal,
8268
- reasoningEffort: effortRef.current,
8930
+ reasoningEffort: overrideEffort ?? effortRef.current,
8269
8931
  coauthor: cfg.coauthor !== false ? { name: cfg.coauthorName || "kimiflare", email: cfg.coauthorEmail || "kimiflare@proton.me" } : void 0,
8270
8932
  sessionId: ensureSessionId(),
8271
8933
  memoryManager: memoryManagerRef.current,
@@ -8370,24 +9032,60 @@ ${lines.join("\n")}` }]);
8370
9032
  }
8371
9033
  });
8372
9034
  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)`
9035
+ if (shouldCompact({ messages: messagesRef.current })) {
9036
+ if (compiledContextRef.current) {
9037
+ const result = compactMessages2({
9038
+ messages: messagesRef.current,
9039
+ state: sessionStateRef.current,
9040
+ store: artifactStoreRef.current
9041
+ });
9042
+ if (result.metrics.rawTurnsRemoved > 0) {
9043
+ messagesRef.current = result.newMessages;
9044
+ sessionStateRef.current = result.newState;
9045
+ setEvents((e) => [
9046
+ ...e,
9047
+ {
9048
+ kind: "info",
9049
+ key: mkKey(),
9050
+ text: `auto-compacted: ${result.metrics.estimatedTokensBefore} \u2192 ${result.metrics.estimatedTokensAfter} tokens (${result.metrics.archivedArtifacts} artifacts)`
9051
+ }
9052
+ ]);
9053
+ await saveSessionSafe();
9054
+ }
9055
+ } else {
9056
+ try {
9057
+ const result = await compactMessages({
9058
+ accountId: cfg.accountId,
9059
+ apiToken: cfg.apiToken,
9060
+ model: cfg.model,
9061
+ messages: messagesRef.current,
9062
+ signal: controller.signal,
9063
+ gateway: gatewayFromConfig(cfg)
9064
+ });
9065
+ if (result.replacedCount > 0) {
9066
+ messagesRef.current = result.newMessages;
9067
+ setEvents((e) => [
9068
+ ...e,
9069
+ {
9070
+ kind: "info",
9071
+ key: mkKey(),
9072
+ text: `auto-compacted: ${result.replacedCount} messages summarized`
9073
+ }
9074
+ ]);
9075
+ await saveSessionSafe();
8388
9076
  }
8389
- ]);
8390
- await saveSessionSafe();
9077
+ } catch (compactErr) {
9078
+ if (compactErr.name !== "AbortError") {
9079
+ setEvents((es) => [
9080
+ ...es,
9081
+ {
9082
+ kind: "info",
9083
+ key: mkKey(),
9084
+ text: `auto-compact failed: ${compactErr.message ?? String(compactErr)}`
9085
+ }
9086
+ ]);
9087
+ }
9088
+ }
8391
9089
  }
8392
9090
  }
8393
9091
  } catch (e) {
@@ -8463,7 +9161,7 @@ ${lines.join("\n")}` }]);
8463
9161
  }
8464
9162
  }, [usage]);
8465
9163
  if (!cfg) {
8466
- return /* @__PURE__ */ jsx13(
9164
+ return /* @__PURE__ */ jsx14(
8467
9165
  Onboarding,
8468
9166
  {
8469
9167
  onDone: (newCfg) => {
@@ -8477,15 +9175,28 @@ ${lines.join("\n")}` }]);
8477
9175
  );
8478
9176
  }
8479
9177
  if (resumeSessions !== null) {
8480
- return /* @__PURE__ */ jsx13(Box12, { flexDirection: "column", children: /* @__PURE__ */ jsx13(ResumePicker, { sessions: resumeSessions, onPick: handleResumePick, theme }) });
9178
+ return /* @__PURE__ */ jsx14(Box13, { flexDirection: "column", children: /* @__PURE__ */ jsx14(ResumePicker, { sessions: resumeSessions, onPick: handleResumePick, theme }) });
8481
9179
  }
8482
9180
  if (showThemePicker) {
8483
- return /* @__PURE__ */ jsx13(Box12, { flexDirection: "column", children: /* @__PURE__ */ jsx13(ThemePicker, { themes: themeList(), current: theme, onPick: handleThemePick, onPreview: (t) => setTheme(t) }) });
9181
+ return /* @__PURE__ */ jsx14(Box13, { flexDirection: "column", children: /* @__PURE__ */ jsx14(ThemePicker, { themes: themeList(), current: theme, onPick: handleThemePick, onPreview: (t) => setTheme(t) }) });
9182
+ }
9183
+ if (showHelpMenu) {
9184
+ return /* @__PURE__ */ jsx14(Box13, { flexDirection: "column", children: /* @__PURE__ */ jsx14(
9185
+ HelpMenu,
9186
+ {
9187
+ theme,
9188
+ themes: themeList().map((t) => ({ name: t.name, label: t.label })),
9189
+ currentThemeName: theme.name,
9190
+ customCommands: customCommandsRef.current.filter((c) => !BUILTIN_COMMAND_NAMES.has(c.name.toLowerCase())).map((c) => ({ name: c.name, description: c.description })),
9191
+ onDone: () => setShowHelpMenu(false),
9192
+ onCommand: handleHelpCommand
9193
+ }
9194
+ ) });
8484
9195
  }
8485
9196
  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(
9197
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
9198
+ !hasConversation && events.length === 0 ? /* @__PURE__ */ jsx14(Welcome, { theme, accountId: cfg.accountId }) : /* @__PURE__ */ jsx14(ChatView, { events, showReasoning, theme, verbose }),
9199
+ perm ? /* @__PURE__ */ jsx14(
8489
9200
  PermissionModal,
8490
9201
  {
8491
9202
  tool: perm.tool,
@@ -8496,8 +9207,8 @@ ${lines.join("\n")}` }]);
8496
9207
  setPerm(null);
8497
9208
  }
8498
9209
  }
8499
- ) : /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", marginTop: 1, children: [
8500
- tasks.length > 0 && /* @__PURE__ */ jsx13(
9210
+ ) : /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", marginTop: 1, children: [
9211
+ tasks.length > 0 && /* @__PURE__ */ jsx14(
8501
9212
  TaskList,
8502
9213
  {
8503
9214
  tasks,
@@ -8506,11 +9217,11 @@ ${lines.join("\n")}` }]);
8506
9217
  tokensDelta: Math.max(0, (usage?.prompt_tokens ?? 0) - tasksStartTokens)
8507
9218
  }
8508
9219
  ),
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: [
9220
+ queue.length > 0 && /* @__PURE__ */ jsx14(Box13, { flexDirection: "column", marginBottom: 1, children: queue.map((q, i) => /* @__PURE__ */ jsxs13(Text14, { color: theme.queue.color, dimColor: theme.queue.dim, children: [
8510
9221
  "\u23F3 ",
8511
9222
  q.display
8512
9223
  ] }, `queue_${i}`)) }),
8513
- /* @__PURE__ */ jsx13(
9224
+ /* @__PURE__ */ jsx14(
8514
9225
  StatusBar,
8515
9226
  {
8516
9227
  model: cfg.model,
@@ -8528,9 +9239,9 @@ ${lines.join("\n")}` }]);
8528
9239
  codeMode
8529
9240
  }
8530
9241
  ),
8531
- /* @__PURE__ */ jsxs12(Box12, { marginTop: 1, children: [
8532
- /* @__PURE__ */ jsx13(Text13, { color: theme.accent, children: "\u203A " }),
8533
- /* @__PURE__ */ jsx13(
9242
+ /* @__PURE__ */ jsxs13(Box13, { marginTop: 1, children: [
9243
+ /* @__PURE__ */ jsx14(Text14, { color: theme.accent, children: "\u203A " }),
9244
+ /* @__PURE__ */ jsx14(
8534
9245
  CustomTextInput,
8535
9246
  {
8536
9247
  value: input,
@@ -8579,12 +9290,12 @@ ${lines.join("\n")}` }]);
8579
9290
  ] });
8580
9291
  }
8581
9292
  async function renderApp(cfg, updateResult) {
8582
- const instance = render(/* @__PURE__ */ jsx13(App, { initialCfg: cfg, initialUpdateResult: updateResult }), {
9293
+ const instance = render(/* @__PURE__ */ jsx14(App, { initialCfg: cfg, initialUpdateResult: updateResult }), {
8583
9294
  incrementalRendering: true
8584
9295
  });
8585
9296
  await instance.waitUntilExit();
8586
9297
  }
8587
- var FEEDBACK_WORKER_URL, CONTEXT_LIMIT, AUTO_COMPACT_SUGGEST_PCT, MAX_EVENTS, nextAssistantId, nextKey, mkKey, MAX_IMAGES_PER_MESSAGE, EFFORT_DESCRIPTIONS;
9298
+ 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
9299
  var init_app = __esm({
8589
9300
  "src/app.tsx"() {
8590
9301
  "use strict";
@@ -8607,6 +9318,7 @@ var init_app = __esm({
8607
9318
  init_update_check();
8608
9319
  init_onboarding();
8609
9320
  init_welcome();
9321
+ init_help_menu();
8610
9322
  init_config();
8611
9323
  init_theme();
8612
9324
  init_mode();
@@ -8617,6 +9329,8 @@ var init_app = __esm({
8617
9329
  init_storage_limits();
8618
9330
  init_state();
8619
9331
  init_version();
9332
+ init_loader();
9333
+ init_renderer();
8620
9334
  FEEDBACK_WORKER_URL = "https://kimiflare-feedback.sina-b35.workers.dev";
8621
9335
  CONTEXT_LIMIT = 262e3;
8622
9336
  AUTO_COMPACT_SUGGEST_PCT = 0.8;
@@ -8625,6 +9339,32 @@ var init_app = __esm({
8625
9339
  nextKey = 1;
8626
9340
  mkKey = () => `evt_${nextKey++}`;
8627
9341
  MAX_IMAGES_PER_MESSAGE = 10;
9342
+ BUILTIN_COMMAND_NAMES = /* @__PURE__ */ new Set([
9343
+ "exit",
9344
+ "quit",
9345
+ "clear",
9346
+ "reasoning",
9347
+ "cost",
9348
+ "model",
9349
+ "thinking",
9350
+ "effort",
9351
+ "theme",
9352
+ "mode",
9353
+ "plan",
9354
+ "auto",
9355
+ "edit",
9356
+ "resume",
9357
+ "compact",
9358
+ "init",
9359
+ "update",
9360
+ "mcp",
9361
+ "logout",
9362
+ "help",
9363
+ "memory",
9364
+ "gateway",
9365
+ "hello",
9366
+ "community"
9367
+ ]);
8628
9368
  EFFORT_DESCRIPTIONS = {
8629
9369
  low: "low \u2014 fastest; lightest reasoning. Best for simple Q&A, small edits, quick coordination.",
8630
9370
  medium: "medium \u2014 balanced (default). Solid quality on most edits, fast on trivial prompts.",