kimiflare 0.23.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);
@@ -1528,7 +1528,7 @@ var init_mode = __esm({
1528
1528
  "use strict";
1529
1529
  MODES = ["edit", "plan", "auto"];
1530
1530
  MUTATING_TOOLS = /* @__PURE__ */ new Set(["write", "edit", "bash"]);
1531
- DANGEROUS_PATTERNS = /[<>;&`$]|\$\(|\$\{|\|\||\b&\s*$/;
1531
+ DANGEROUS_PATTERNS = /[<>;`$]|\$\(|\$\{|\|\||\b&\s*$/;
1532
1532
  GIT_READONLY_SUBCOMMANDS = {
1533
1533
  log: true,
1534
1534
  diff: true,
@@ -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;
@@ -1888,8 +1891,10 @@ function injectCoauthor(command, coauthor) {
1888
1891
  if (command.includes(trailer)) return command;
1889
1892
  const createsCommit = /\bgit\s+(commit|merge|revert|cherry-pick)\b/.test(trimmed);
1890
1893
  const isRebaseContinue = /\bgit\s+rebase\b/.test(trimmed) && !/\b--abort\b|\b--skip\b/.test(trimmed);
1894
+ const movesHeadOnly = /\bgit\s+(reset|checkout|switch)\b/.test(trimmed);
1891
1895
  const mentionsGit = /\bgit\b/.test(trimmed);
1892
1896
  if (!createsCommit && !isRebaseContinue && !mentionsGit) return command;
1897
+ if (movesHeadOnly) return command;
1893
1898
  const tmpFile = join5(tmpdir(), `kf-coauthor-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
1894
1899
  const amendBlock = `
1895
1900
  if ! git log -1 --pretty=%B 2>/dev/null | grep -qF "${trailer}"; then
@@ -1902,7 +1907,7 @@ function injectCoauthor(command, coauthor) {
1902
1907
  const beforeHead = `git rev-parse HEAD 2>/dev/null || echo "NO_HEAD"`;
1903
1908
  const afterCheck = `
1904
1909
  _KF_AFTER_HEAD=$(git rev-parse HEAD 2>/dev/null || echo "NO_HEAD")
1905
- if [ "$_KF_BEFORE_HEAD" != "$_KF_AFTER_HEAD" ] && [ "$_KF_AFTER_HEAD" != "NO_HEAD" ]; then
1910
+ if [ "$_KF_BEFORE_HEAD" != "$_KF_AFTER_HEAD" ] && [ "$_KF_AFTER_HEAD" != "NO_HEAD" ] && git merge-base --is-ancestor "$_KF_BEFORE_HEAD" "$_KF_AFTER_HEAD" 2>/dev/null; then
1906
1911
  ${amendBlock}
1907
1912
  fi
1908
1913
  `.trim();
@@ -2837,6 +2842,16 @@ var init_expand_artifact = __esm({
2837
2842
  });
2838
2843
 
2839
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
+ }
2840
2855
  function normalizeToolOutput(result) {
2841
2856
  if (typeof result === "string") {
2842
2857
  const bytes = Buffer.byteLength(result, "utf8");
@@ -2939,6 +2954,20 @@ var init_executor = __esm({
2939
2954
  try {
2940
2955
  const result = await tool.run(args, ctx);
2941
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
+ }
2942
2971
  const reduced = reduceToolOutput(
2943
2972
  call.name,
2944
2973
  normalized.content,
@@ -3079,6 +3108,33 @@ var init_update_check = __esm({
3079
3108
  }
3080
3109
  });
3081
3110
 
3111
+ // src/util/version.ts
3112
+ import { readFileSync as readFileSync2 } from "fs";
3113
+ import { fileURLToPath as fileURLToPath2 } from "url";
3114
+ import { dirname as dirname3, join as join7 } from "path";
3115
+ function getAppVersion() {
3116
+ if (cachedVersion !== null) return cachedVersion;
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
+ }
3126
+ }
3127
+ cachedVersion = "0.0.0";
3128
+ return cachedVersion;
3129
+ }
3130
+ var cachedVersion;
3131
+ var init_version = __esm({
3132
+ "src/util/version.ts"() {
3133
+ "use strict";
3134
+ cachedVersion = null;
3135
+ }
3136
+ });
3137
+
3082
3138
  // src/agent/compact.ts
3083
3139
  function indexOfNthUserFromEnd(messages, n) {
3084
3140
  let seen = 0;
@@ -3552,11 +3608,12 @@ function recallArtifacts(messages, store, state) {
3552
3608
  }
3553
3609
  for (const failure of state.recent_failures) {
3554
3610
  const keyword = failure.split(":")[0];
3555
- if (keyword && text.toLowerCase().includes(keyword.toLowerCase())) {
3556
- for (const [id, meta] of Object.entries(state.artifact_index)) {
3557
- if (meta.source === "bash" && !ids.includes(id)) {
3558
- ids.push(id);
3559
- }
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);
3560
3617
  }
3561
3618
  }
3562
3619
  }
@@ -3968,13 +4025,12 @@ import React3 from "react";
3968
4025
  import { Box as Box4, Text as Text4, Static } from "ink";
3969
4026
  import Spinner2 from "ink-spinner";
3970
4027
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
3971
- var MAX_FINALIZED_EVENTS, ChatView, EventView;
4028
+ var ChatView, EventView;
3972
4029
  var init_chat = __esm({
3973
4030
  "src/ui/chat.tsx"() {
3974
4031
  "use strict";
3975
4032
  init_tool_view();
3976
4033
  init_markdown();
3977
- MAX_FINALIZED_EVENTS = 100;
3978
4034
  ChatView = React3.memo(function ChatView2({ events, showReasoning, theme, verbose }) {
3979
4035
  const finalized = [];
3980
4036
  const active = [];
@@ -3989,15 +4045,13 @@ var init_chat = __esm({
3989
4045
  finalized.push({ id: e.key, evt: e, showSeparator });
3990
4046
  }
3991
4047
  }
3992
- const droppedCount = Math.max(0, finalized.length - MAX_FINALIZED_EVENTS);
3993
- const visibleFinalized = droppedCount > 0 ? finalized.slice(droppedCount) : finalized;
3994
4048
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
3995
- /* @__PURE__ */ jsx4(Static, { items: visibleFinalized, children: (item) => /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
4049
+ /* @__PURE__ */ jsx4(Static, { items: finalized, children: (item) => /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
3996
4050
  item.showSeparator && /* @__PURE__ */ jsx4(Box4, { marginY: 1, children: /* @__PURE__ */ jsx4(Text4, { color: theme.info.color, dimColor: theme.info.dim, children: "\u2500".repeat(40) }) }),
3997
4051
  /* @__PURE__ */ jsx4(EventView, { evt: item.evt, showReasoning, theme, verbose })
3998
- ] }) }),
4052
+ ] }, item.id) }),
3999
4053
  active.map((e, i) => {
4000
- const prevEvt = i > 0 ? active[i - 1] : visibleFinalized[visibleFinalized.length - 1]?.evt;
4054
+ const prevEvt = i > 0 ? active[i - 1] : finalized[finalized.length - 1]?.evt;
4001
4055
  const showSeparator = e.kind === "user" && prevEvt && (prevEvt.kind === "assistant" || prevEvt.kind === "tool");
4002
4056
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
4003
4057
  showSeparator && /* @__PURE__ */ jsx4(Box4, { marginY: 1, children: /* @__PURE__ */ jsx4(Text4, { color: theme.info.color, dimColor: theme.info.dim, children: "\u2500".repeat(40) }) }),
@@ -4131,14 +4185,14 @@ function buildRightParts(usage, contextLimit, sessionUsage, gatewayMeta) {
4131
4185
  parts.push(`in ${sessionUsage.promptTokens}${cached ? ` (${cached} cached)` : ""}`);
4132
4186
  parts.push(`out ${sessionUsage.completionTokens}`);
4133
4187
  parts.push(`ctx ${pct}%`);
4134
- parts.push(`${sessionUsage.cost.toFixed(5)}`);
4188
+ parts.push(`$${sessionUsage.cost.toFixed(5)}`);
4135
4189
  } else {
4136
4190
  const cached = usage.prompt_tokens_details?.cached_tokens ?? 0;
4137
4191
  const cost = calculateCost(usage.prompt_tokens, usage.completion_tokens, cached);
4138
4192
  parts.push(`in ${usage.prompt_tokens}${cached ? ` (${cached} cached)` : ""}`);
4139
4193
  parts.push(`out ${usage.completion_tokens}`);
4140
4194
  parts.push(`ctx ${pct}%`);
4141
- parts.push(`${cost.total.toFixed(5)}`);
4195
+ parts.push(`$${cost.total.toFixed(5)}`);
4142
4196
  }
4143
4197
  const gatewayCache = formatGatewayCacheStatus(gatewayMeta);
4144
4198
  if (gatewayCache) parts.push(gatewayCache);
@@ -4888,18 +4942,18 @@ var init_source = __esm({
4888
4942
  }
4889
4943
  }
4890
4944
  });
4891
- createStyler = (open, close, parent) => {
4945
+ createStyler = (open3, close, parent) => {
4892
4946
  let openAll;
4893
4947
  let closeAll;
4894
4948
  if (parent === void 0) {
4895
- openAll = open;
4949
+ openAll = open3;
4896
4950
  closeAll = close;
4897
4951
  } else {
4898
- openAll = parent.openAll + open;
4952
+ openAll = parent.openAll + open3;
4899
4953
  closeAll = close + parent.closeAll;
4900
4954
  }
4901
4955
  return {
4902
- open,
4956
+ open: open3,
4903
4957
  close,
4904
4958
  openAll,
4905
4959
  closeAll,
@@ -5328,7 +5382,9 @@ function Welcome({ theme, accountId }) {
5328
5382
  ] }),
5329
5383
  /* @__PURE__ */ jsx12(Text12, { color: theme.user, children: s })
5330
5384
  ] }, i)) }),
5331
- /* @__PURE__ */ jsx12(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx12(Text12, { color: theme.info.color, dimColor: theme.info.dim, children: "Type a message or /help for commands \xB7 ctrl-c to exit \xB7 shift+tab to cycle modes" }) })
5385
+ /* @__PURE__ */ jsx12(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx12(Text12, { color: theme.info.color, dimColor: theme.info.dim, children: "Type a message or /help for commands \xB7 ctrl-c to exit \xB7 shift+tab to cycle modes" }) }),
5386
+ /* @__PURE__ */ jsx12(Box11, { children: /* @__PURE__ */ jsx12(Text12, { color: theme.info.color, dimColor: theme.info.dim, children: "Tip: type /hello to send feedback to the creator" }) }),
5387
+ /* @__PURE__ */ jsx12(Box11, { children: /* @__PURE__ */ jsx12(Text12, { color: theme.info.color, dimColor: theme.info.dim, children: "Join our community: https://discord.gg/aEuAUHNTK5 (type /community to open)" }) })
5332
5388
  ] });
5333
5389
  }
5334
5390
  var SUGGESTIONS;
@@ -5343,6 +5399,235 @@ var init_welcome = __esm({
5343
5399
  }
5344
5400
  });
5345
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
+
5346
5631
  // src/ui/theme.ts
5347
5632
  function resolveTheme(name) {
5348
5633
  if (!name) return THEMES[DEFAULT_THEME_NAME];
@@ -5579,10 +5864,10 @@ __export(sessions_exports, {
5579
5864
  });
5580
5865
  import { readFile as readFile7, writeFile as writeFile5, mkdir as mkdir5, readdir as readdir2, stat as stat3 } from "fs/promises";
5581
5866
  import { homedir as homedir6 } from "os";
5582
- import { join as join7 } from "path";
5867
+ import { join as join8 } from "path";
5583
5868
  function sessionsDir() {
5584
- const xdg = process.env.XDG_DATA_HOME || join7(homedir6(), ".local", "share");
5585
- return join7(xdg, "kimiflare", "sessions");
5869
+ const xdg = process.env.XDG_DATA_HOME || join8(homedir6(), ".local", "share");
5870
+ return join8(xdg, "kimiflare", "sessions");
5586
5871
  }
5587
5872
  function sanitize(text) {
5588
5873
  return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
@@ -5595,7 +5880,7 @@ function makeSessionId(firstPrompt) {
5595
5880
  async function saveSession(file) {
5596
5881
  const dir = sessionsDir();
5597
5882
  await mkdir5(dir, { recursive: true });
5598
- const path = join7(dir, `${file.id}.json`);
5883
+ const path = join8(dir, `${file.id}.json`);
5599
5884
  await writeFile5(path, JSON.stringify(file, null, 2), "utf8");
5600
5885
  return path;
5601
5886
  }
@@ -5615,7 +5900,7 @@ async function listSessions(limit = 30) {
5615
5900
  const summaries = [];
5616
5901
  for (const name of entries) {
5617
5902
  if (!name.endsWith(".json")) continue;
5618
- const path = join7(dir, name);
5903
+ const path = join8(dir, name);
5619
5904
  try {
5620
5905
  const [s, raw] = await Promise.all([stat3(path), readFile7(path, "utf8")]);
5621
5906
  const parsed = JSON.parse(raw);
@@ -5688,13 +5973,13 @@ var init_image = __esm({
5688
5973
  // src/usage-tracker.ts
5689
5974
  import { readFile as readFile9, writeFile as writeFile6, mkdir as mkdir6 } from "fs/promises";
5690
5975
  import { homedir as homedir7 } from "os";
5691
- import { join as join8 } from "path";
5976
+ import { join as join9 } from "path";
5692
5977
  function usageDir() {
5693
- const xdg = process.env.XDG_DATA_HOME || join8(homedir7(), ".local", "share");
5694
- return join8(xdg, "kimiflare");
5978
+ const xdg = process.env.XDG_DATA_HOME || join9(homedir7(), ".local", "share");
5979
+ return join9(xdg, "kimiflare");
5695
5980
  }
5696
5981
  function usagePath() {
5697
- return join8(usageDir(), "usage.json");
5982
+ return join9(usageDir(), "usage.json");
5698
5983
  }
5699
5984
  function today() {
5700
5985
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -5820,7 +6105,7 @@ async function getCostReport(sessionId) {
5820
6105
  const log = pruneUsageLog(await loadLog());
5821
6106
  const date = today();
5822
6107
  const currentMonth = date.slice(0, 7);
5823
- const session = log.sessions.find((s) => s.id === sessionId) ?? { date, promptTokens: 0, completionTokens: 0, cachedTokens: 0, cost: 0 };
6108
+ const session = sessionId ? log.sessions.find((s) => s.id === sessionId) ?? { date, promptTokens: 0, completionTokens: 0, cachedTokens: 0, cost: 0 } : { date, promptTokens: 0, completionTokens: 0, cachedTokens: 0, cost: 0 };
5824
6109
  const todayUsage = log.days.find((d) => d.date === date) ?? { date, promptTokens: 0, completionTokens: 0, cachedTokens: 0, cost: 0 };
5825
6110
  const monthUsage = {
5826
6111
  date: currentMonth,
@@ -5919,7 +6204,7 @@ __export(db_exports, {
5919
6204
  updateMemoryEmbedding: () => updateMemoryEmbedding
5920
6205
  });
5921
6206
  import Database from "better-sqlite3";
5922
- import { dirname as dirname3 } from "path";
6207
+ import { dirname as dirname4 } from "path";
5923
6208
  import { mkdirSync, statSync as statSync2 } from "fs";
5924
6209
  function initSchema(db) {
5925
6210
  db.exec(`
@@ -6000,7 +6285,7 @@ function openMemoryDb(dbPath) {
6000
6285
  if (dbInstance) {
6001
6286
  dbInstance.close();
6002
6287
  }
6003
- mkdirSync(dirname3(dbPath), { recursive: true });
6288
+ mkdirSync(dirname4(dbPath), { recursive: true });
6004
6289
  dbInstance = new Database(dbPath);
6005
6290
  dbInstance.pragma("journal_mode = WAL");
6006
6291
  dbInstance.pragma("foreign_keys = ON");
@@ -6872,17 +7157,394 @@ Return only the topic key string.`
6872
7157
  }
6873
7158
  });
6874
7159
 
7160
+ // src/util/state.ts
7161
+ import { readFile as readFile10, writeFile as writeFile7, mkdir as mkdir7 } from "fs/promises";
7162
+ import { homedir as homedir8 } from "os";
7163
+ import { join as join11 } from "path";
7164
+ function statePath() {
7165
+ const xdg = process.env.XDG_CONFIG_HOME || join11(homedir8(), ".config");
7166
+ return join11(xdg, "kimiflare", "state.json");
7167
+ }
7168
+ async function readState() {
7169
+ try {
7170
+ const raw = await readFile10(statePath(), "utf8");
7171
+ return JSON.parse(raw);
7172
+ } catch {
7173
+ return {};
7174
+ }
7175
+ }
7176
+ async function writeState(state) {
7177
+ const path = statePath();
7178
+ await mkdir7(join11(path, ".."), { recursive: true });
7179
+ await writeFile7(path, JSON.stringify(state, null, 2) + "\n", "utf8");
7180
+ }
7181
+ async function markCreatorMessageSeen(version) {
7182
+ const state = await readState();
7183
+ state.creatorMessageSeenVersion = version;
7184
+ await writeState(state);
7185
+ }
7186
+ async function shouldShowCreatorMessage(version) {
7187
+ const state = await readState();
7188
+ return state.creatorMessageSeenVersion !== version;
7189
+ }
7190
+ var init_state = __esm({
7191
+ "src/util/state.ts"() {
7192
+ "use strict";
7193
+ }
7194
+ });
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
+
6875
7535
  // src/app.tsx
6876
7536
  var app_exports = {};
6877
7537
  __export(app_exports, {
6878
7538
  renderApp: () => renderApp
6879
7539
  });
6880
- import { useState as useState6, useRef as useRef3, useEffect as useEffect4, useCallback } from "react";
6881
- 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";
6882
7542
  import { existsSync } from "fs";
6883
- import { join as join10 } from "path";
7543
+ import { join as join13 } from "path";
6884
7544
  import { unlink as unlink2 } from "fs/promises";
6885
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
7545
+ import { spawn as spawn2 } from "child_process";
7546
+ import { platform as platform2 } from "os";
7547
+ import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
6886
7548
  function gatewayFromConfig(cfg) {
6887
7549
  if (!cfg.aiGatewayId) return void 0;
6888
7550
  return {
@@ -6902,10 +7564,34 @@ function gatewayUsageLookupFromConfig(cfg, meta) {
6902
7564
  meta
6903
7565
  };
6904
7566
  }
7567
+ function openBrowser(url) {
7568
+ const cmd = platform2() === "darwin" ? "open" : platform2() === "win32" ? "start" : "xdg-open";
7569
+ const child = spawn2(cmd, [url], { detached: true, stdio: "ignore" });
7570
+ child.unref();
7571
+ }
6905
7572
  function capEvents(prev) {
6906
7573
  if (prev.length <= MAX_EVENTS) return prev;
6907
7574
  return prev.slice(prev.length - MAX_EVENTS);
6908
7575
  }
7576
+ function compactEventsVisual(prev, keepLastTurns) {
7577
+ let seen = 0;
7578
+ let cutoff = -1;
7579
+ for (let i = prev.length - 1; i >= 0; i--) {
7580
+ if (prev[i].kind === "user") {
7581
+ seen++;
7582
+ if (seen === keepLastTurns + 1) {
7583
+ cutoff = i;
7584
+ break;
7585
+ }
7586
+ }
7587
+ }
7588
+ if (cutoff <= 0) return prev;
7589
+ const kept = prev.slice(cutoff);
7590
+ return [
7591
+ { kind: "info", key: mkKey(), text: `\xB7\xB7\xB7 ${cutoff} earlier messages compacted \xB7\xB7\xB7` },
7592
+ ...kept
7593
+ ];
7594
+ }
6909
7595
  function makePrefixMessages(cacheStable, model, mode, tools) {
6910
7596
  if (cacheStable) {
6911
7597
  return buildSystemMessages({ cwd: process.cwd(), tools, model, mode });
@@ -6929,8 +7615,8 @@ function findImagePaths(text) {
6929
7615
  }
6930
7616
  function App({ initialCfg, initialUpdateResult }) {
6931
7617
  const { exit } = useApp();
6932
- const [cfg, setCfg] = useState6(initialCfg);
6933
- const [events, setRawEvents] = useState6([]);
7618
+ const [cfg, setCfg] = useState7(initialCfg);
7619
+ const [events, setRawEvents] = useState7([]);
6934
7620
  const setEvents = useCallback(
6935
7621
  (updater) => {
6936
7622
  setRawEvents((prev) => {
@@ -6940,33 +7626,34 @@ function App({ initialCfg, initialUpdateResult }) {
6940
7626
  },
6941
7627
  []
6942
7628
  );
6943
- const [input, setInput] = useState6("");
6944
- const [busy, setBusy] = useState6(false);
6945
- const [usage, setUsage] = useState6(null);
6946
- const [sessionUsage, setSessionUsage] = useState6(null);
6947
- const [gatewayMeta, setGatewayMeta] = useState6(null);
6948
- const [showReasoning, setShowReasoning] = useState6(false);
6949
- const [perm, setPerm] = useState6(null);
6950
- const [queue, setQueue] = useState6([]);
6951
- const [history, setHistory] = useState6([]);
6952
- const [historyIndex, setHistoryIndex] = useState6(-1);
6953
- const [draftInput, setDraftInput] = useState6("");
6954
- const [mode, setMode] = useState6("edit");
6955
- const [codeMode, setCodeMode] = useState6(initialCfg?.codeMode ?? false);
6956
- 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(
6957
7643
  initialCfg?.reasoningEffort ?? DEFAULT_REASONING_EFFORT
6958
7644
  );
6959
- const [theme, setTheme] = useState6(resolveTheme(initialCfg?.theme));
6960
- const [resumeSessions, setResumeSessions] = useState6(null);
6961
- const [showThemePicker, setShowThemePicker] = useState6(false);
6962
- const [originalTheme, setOriginalTheme] = useState6(null);
6963
- const [tasks, setTasks] = useState6([]);
6964
- const [tasksStartedAt, setTasksStartedAt] = useState6(null);
6965
- const [tasksStartTokens, setTasksStartTokens] = useState6(0);
6966
- const [turnStartedAt, setTurnStartedAt] = useState6(null);
6967
- const [verbose, setVerbose] = useState6(false);
6968
- const [hasUpdate, setHasUpdate] = useState6(initialUpdateResult?.hasUpdate ?? false);
6969
- 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);
6970
7657
  const cacheStableRef = useRef3(initialCfg?.cacheStablePrompts !== false);
6971
7658
  const messagesRef = useRef3(
6972
7659
  makePrefixMessages(cacheStableRef.current, cfg?.model ?? DEFAULT_MODEL, "edit", ALL_TOOLS)
@@ -6992,6 +7679,7 @@ function App({ initialCfg, initialUpdateResult }) {
6992
7679
  const memoryManagerRef = useRef3(null);
6993
7680
  const pendingTextRef = useRef3(/* @__PURE__ */ new Map());
6994
7681
  const flushTimeoutRef = useRef3(null);
7682
+ const customCommandsRef = useRef3([]);
6995
7683
  useEffect4(() => {
6996
7684
  if (!cfg) return;
6997
7685
  void Promise.resolve().then(() => (init_sessions(), sessions_exports)).then(
@@ -7004,8 +7692,21 @@ function App({ initialCfg, initialUpdateResult }) {
7004
7692
  }
7005
7693
  })
7006
7694
  );
7695
+ void shouldShowCreatorMessage(getAppVersion()).then((shouldShow) => {
7696
+ if (shouldShow) {
7697
+ setEvents((e) => [
7698
+ ...e,
7699
+ {
7700
+ kind: "info",
7701
+ key: mkKey(),
7702
+ text: "Hey, how do you like this version? I'd love to hear from you \u2014 type /hello to send me a voice note. Only I see it, and I may DM you back."
7703
+ }
7704
+ ]);
7705
+ void markCreatorMessageSeen(getAppVersion());
7706
+ }
7707
+ });
7007
7708
  if (cfg.memoryEnabled) {
7008
- const dbPath = cfg.memoryDbPath ?? join10(process.cwd(), ".kimiflare", "memory.db");
7709
+ const dbPath = cfg.memoryDbPath ?? join13(process.cwd(), ".kimiflare", "memory.db");
7009
7710
  const manager = new MemoryManager({
7010
7711
  dbPath,
7011
7712
  accountId: cfg.accountId,
@@ -7039,7 +7740,20 @@ function App({ initialCfg, initialUpdateResult }) {
7039
7740
  memoryManagerRef.current?.close();
7040
7741
  memoryManagerRef.current = null;
7041
7742
  }
7042
- }, [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]);
7043
7757
  useEffect4(() => {
7044
7758
  if (!cfg || updateCheckedRef.current) return;
7045
7759
  updateCheckedRef.current = true;
@@ -7247,7 +7961,7 @@ function App({ initialCfg, initialUpdateResult }) {
7247
7961
  } catch {
7248
7962
  }
7249
7963
  }, [cfg, ensureSessionId]);
7250
- useInput2((inputChar, key) => {
7964
+ useInput3((inputChar, key) => {
7251
7965
  if (key.ctrl && inputChar === "c") {
7252
7966
  if (busy && activeControllerRef.current) {
7253
7967
  activeControllerRef.current.abort();
@@ -7366,14 +8080,19 @@ function App({ initialCfg, initialUpdateResult }) {
7366
8080
  } else {
7367
8081
  messagesRef.current = result.newMessages;
7368
8082
  sessionStateRef.current = result.newState;
7369
- setEvents((e) => [
7370
- ...e,
7371
- {
7372
- kind: "info",
7373
- key: mkKey(),
7374
- text: `compacted ${result.metrics.rawTurnsRemoved} turns \u2192 ${result.metrics.estimatedTokensBefore} \u2192 ${result.metrics.estimatedTokensAfter} tokens, ${result.metrics.archivedArtifacts} artifacts`
7375
- }
7376
- ]);
8083
+ setEvents(
8084
+ (e) => compactEventsVisual(
8085
+ [
8086
+ ...e,
8087
+ {
8088
+ kind: "info",
8089
+ key: mkKey(),
8090
+ text: `compacted ${result.metrics.rawTurnsRemoved} turns \u2192 ${result.metrics.estimatedTokensBefore} \u2192 ${result.metrics.estimatedTokensAfter} tokens, ${result.metrics.archivedArtifacts} artifacts`
8091
+ }
8092
+ ],
8093
+ 4
8094
+ )
8095
+ );
7377
8096
  await saveSessionSafe();
7378
8097
  }
7379
8098
  } else {
@@ -7392,14 +8111,19 @@ function App({ initialCfg, initialUpdateResult }) {
7392
8111
  ]);
7393
8112
  } else {
7394
8113
  messagesRef.current = result.newMessages;
7395
- setEvents((e) => [
7396
- ...e,
7397
- {
7398
- kind: "info",
7399
- key: mkKey(),
7400
- text: `compacted ${result.replacedCount} messages into a summary`
7401
- }
7402
- ]);
8114
+ setEvents(
8115
+ (e) => compactEventsVisual(
8116
+ [
8117
+ ...e,
8118
+ {
8119
+ kind: "info",
8120
+ key: mkKey(),
8121
+ text: `compacted ${result.replacedCount} messages into a summary`
8122
+ }
8123
+ ],
8124
+ 4
8125
+ )
8126
+ );
7403
8127
  await saveSessionSafe();
7404
8128
  }
7405
8129
  }
@@ -7428,13 +8152,13 @@ function App({ initialCfg, initialUpdateResult }) {
7428
8152
  }
7429
8153
  const cwd = process.cwd();
7430
8154
  for (const name of ["KIMI.md", "KIMIFLARE.md", "AGENT.md"]) {
7431
- if (existsSync(join10(cwd, name))) {
8155
+ if (existsSync(join13(cwd, name))) {
7432
8156
  setEvents((e) => [
7433
8157
  ...e,
7434
8158
  {
7435
8159
  kind: "info",
7436
8160
  key: mkKey(),
7437
- text: `${name} already exists at ${join10(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`
7438
8162
  }
7439
8163
  ]);
7440
8164
  return;
@@ -7543,6 +8267,10 @@ function App({ initialCfg, initialUpdateResult }) {
7543
8267
  resolve2("allow");
7544
8268
  return;
7545
8269
  }
8270
+ if (req.tool.name === "bash") {
8271
+ setPerm({ tool: req.tool, args: req.args, resolve: resolve2 });
8272
+ return;
8273
+ }
7546
8274
  setEvents((e) => [
7547
8275
  ...e,
7548
8276
  {
@@ -7558,7 +8286,7 @@ function App({ initialCfg, initialUpdateResult }) {
7558
8286
  })
7559
8287
  }
7560
8288
  });
7561
- if (existsSync(join10(cwd, "KIMI.md"))) {
8289
+ if (existsSync(join13(cwd, "KIMI.md"))) {
7562
8290
  if (cacheStableRef.current) {
7563
8291
  messagesRef.current[1] = {
7564
8292
  role: "system",
@@ -7704,15 +8432,16 @@ function App({ initialCfg, initialUpdateResult }) {
7704
8432
  return true;
7705
8433
  }
7706
8434
  if (c === "/cost") {
7707
- if (!sessionIdRef.current) {
7708
- setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "no usage recorded yet" }]);
7709
- return true;
7710
- }
7711
- void getCostReport(sessionIdRef.current).then((report) => {
8435
+ void getCostReport(sessionIdRef.current ?? void 0).then((report) => {
7712
8436
  setEvents((e) => [
7713
8437
  ...e,
7714
8438
  { kind: "info", key: mkKey(), text: formatCostReport(report) }
7715
8439
  ]);
8440
+ }).catch((err) => {
8441
+ setEvents((e) => [
8442
+ ...e,
8443
+ { kind: "error", key: mkKey(), text: `cost report failed: ${err.message}` }
8444
+ ]);
7716
8445
  });
7717
8446
  return true;
7718
8447
  }
@@ -7921,8 +8650,25 @@ use: /thinking low | medium | high`
7921
8650
  return true;
7922
8651
  }
7923
8652
  if (c === "/memory") {
7924
- if (!cfg?.memoryEnabled) {
7925
- 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" }]);
7926
8672
  return true;
7927
8673
  }
7928
8674
  if (arg === "clear") {
@@ -7962,19 +8708,6 @@ ${lines.join("\n")}` }]);
7962
8708
  }
7963
8709
  return true;
7964
8710
  }
7965
- if (c === "/cost") {
7966
- if (!sessionIdRef.current) {
7967
- setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "no usage recorded yet" }]);
7968
- return true;
7969
- }
7970
- void getCostReport(sessionIdRef.current).then((report) => {
7971
- setEvents((e) => [
7972
- ...e,
7973
- { kind: "info", key: mkKey(), text: formatCostReport(report) }
7974
- ]);
7975
- });
7976
- return true;
7977
- }
7978
8711
  if (c === "/resume") {
7979
8712
  void openResumePicker();
7980
8713
  return true;
@@ -8056,6 +8789,24 @@ ${lines.join("\n")}` }]);
8056
8789
  ]);
8057
8790
  return true;
8058
8791
  }
8792
+ if (c === "/hello") {
8793
+ const session = crypto.randomUUID();
8794
+ const url = `${FEEDBACK_WORKER_URL}/?s=${session}&v=${getAppVersion()}`;
8795
+ openBrowser(url);
8796
+ setEvents((e) => [
8797
+ ...e,
8798
+ { kind: "info", key: mkKey(), text: "Opened voice note page in your browser. Record your message there and hit Send when you're done." }
8799
+ ]);
8800
+ return true;
8801
+ }
8802
+ if (c === "/community") {
8803
+ openBrowser("https://discord.gg/aEuAUHNTK5");
8804
+ setEvents((e) => [
8805
+ ...e,
8806
+ { kind: "info", key: mkKey(), text: "Opened Discord invite in your browser." }
8807
+ ]);
8808
+ return true;
8809
+ }
8059
8810
  if (c === "/logout") {
8060
8811
  unlink2(configPath()).catch(() => {
8061
8812
  });
@@ -8067,27 +8818,57 @@ ${lines.join("\n")}` }]);
8067
8818
  return true;
8068
8819
  }
8069
8820
  if (c === "/help") {
8070
- setEvents((e) => [
8071
- ...e,
8072
- {
8073
- kind: "info",
8074
- key: mkKey(),
8075
- 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 /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"
8076
- }
8077
- ]);
8821
+ setShowHelpMenu(true);
8078
8822
  return true;
8079
8823
  }
8080
8824
  return false;
8081
8825
  },
8082
8826
  [cfg, exit, usage, effort, theme, mode, openResumePicker, runCompact, runInit, initMcp, setCfg]
8083
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
+ );
8084
8838
  const processMessage = useCallback(
8085
8839
  async (text, displayText) => {
8086
8840
  if (!cfg) return;
8087
- const trimmed = text.trim();
8841
+ let trimmed = text.trim();
8088
8842
  if (!trimmed) return;
8089
- if (trimmed.startsWith("/") && handleSlash(trimmed)) return;
8090
- 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
+ }
8091
8872
  const imagePaths = findImagePaths(trimmed).slice(0, MAX_IMAGES_PER_MESSAGE);
8092
8873
  let images = [];
8093
8874
  let content = sanitizeString(trimmed);
@@ -8139,14 +8920,14 @@ ${lines.join("\n")}` }]);
8139
8920
  await runAgentTurn({
8140
8921
  accountId: cfg.accountId,
8141
8922
  apiToken: cfg.apiToken,
8142
- model: cfg.model,
8923
+ model: overrideModel ?? cfg.model,
8143
8924
  gateway: gatewayFromConfig(cfg),
8144
8925
  messages: messagesRef.current,
8145
8926
  tools: [...ALL_TOOLS, ...mcpToolsRef.current],
8146
8927
  executor: executorRef.current,
8147
8928
  cwd: process.cwd(),
8148
8929
  signal: controller.signal,
8149
- reasoningEffort: effortRef.current,
8930
+ reasoningEffort: overrideEffort ?? effortRef.current,
8150
8931
  coauthor: cfg.coauthor !== false ? { name: cfg.coauthorName || "kimiflare", email: cfg.coauthorEmail || "kimiflare@proton.me" } : void 0,
8151
8932
  sessionId: ensureSessionId(),
8152
8933
  memoryManager: memoryManagerRef.current,
@@ -8208,6 +8989,7 @@ ${lines.join("\n")}` }]);
8208
8989
  onUsageFinal: (u, meta) => {
8209
8990
  const sid = ensureSessionId();
8210
8991
  void recordUsage(sid, u, gatewayUsageLookupFromConfig(cfg, meta ?? gatewayMetaRef.current));
8992
+ void getCostReport(sid).then((report) => setSessionUsage(report.session));
8211
8993
  },
8212
8994
  onGatewayMeta: updateGatewayMeta,
8213
8995
  onTasks: (nextTasks) => {
@@ -8250,24 +9032,60 @@ ${lines.join("\n")}` }]);
8250
9032
  }
8251
9033
  });
8252
9034
  await saveSessionSafe();
8253
- if (compiledContextRef.current && shouldCompact({ messages: messagesRef.current })) {
8254
- const result = compactMessages2({
8255
- messages: messagesRef.current,
8256
- state: sessionStateRef.current,
8257
- store: artifactStoreRef.current
8258
- });
8259
- if (result.metrics.rawTurnsRemoved > 0) {
8260
- messagesRef.current = result.newMessages;
8261
- sessionStateRef.current = result.newState;
8262
- setEvents((e) => [
8263
- ...e,
8264
- {
8265
- kind: "info",
8266
- key: mkKey(),
8267
- 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();
8268
9076
  }
8269
- ]);
8270
- 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
+ }
8271
9089
  }
8272
9090
  }
8273
9091
  } catch (e) {
@@ -8343,7 +9161,7 @@ ${lines.join("\n")}` }]);
8343
9161
  }
8344
9162
  }, [usage]);
8345
9163
  if (!cfg) {
8346
- return /* @__PURE__ */ jsx13(
9164
+ return /* @__PURE__ */ jsx14(
8347
9165
  Onboarding,
8348
9166
  {
8349
9167
  onDone: (newCfg) => {
@@ -8357,15 +9175,28 @@ ${lines.join("\n")}` }]);
8357
9175
  );
8358
9176
  }
8359
9177
  if (resumeSessions !== null) {
8360
- 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 }) });
8361
9179
  }
8362
9180
  if (showThemePicker) {
8363
- 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
+ ) });
8364
9195
  }
8365
9196
  const hasConversation = events.some((e) => e.kind === "user" || e.kind === "assistant");
8366
- return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
8367
- !hasConversation && events.length === 0 ? /* @__PURE__ */ jsx13(Welcome, { theme, accountId: cfg.accountId }) : /* @__PURE__ */ jsx13(ChatView, { events, showReasoning, theme, verbose }),
8368
- 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(
8369
9200
  PermissionModal,
8370
9201
  {
8371
9202
  tool: perm.tool,
@@ -8376,8 +9207,8 @@ ${lines.join("\n")}` }]);
8376
9207
  setPerm(null);
8377
9208
  }
8378
9209
  }
8379
- ) : /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", marginTop: 1, children: [
8380
- tasks.length > 0 && /* @__PURE__ */ jsx13(
9210
+ ) : /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", marginTop: 1, children: [
9211
+ tasks.length > 0 && /* @__PURE__ */ jsx14(
8381
9212
  TaskList,
8382
9213
  {
8383
9214
  tasks,
@@ -8386,11 +9217,11 @@ ${lines.join("\n")}` }]);
8386
9217
  tokensDelta: Math.max(0, (usage?.prompt_tokens ?? 0) - tasksStartTokens)
8387
9218
  }
8388
9219
  ),
8389
- 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: [
8390
9221
  "\u23F3 ",
8391
9222
  q.display
8392
9223
  ] }, `queue_${i}`)) }),
8393
- /* @__PURE__ */ jsx13(
9224
+ /* @__PURE__ */ jsx14(
8394
9225
  StatusBar,
8395
9226
  {
8396
9227
  model: cfg.model,
@@ -8408,9 +9239,9 @@ ${lines.join("\n")}` }]);
8408
9239
  codeMode
8409
9240
  }
8410
9241
  ),
8411
- /* @__PURE__ */ jsxs12(Box12, { marginTop: 1, children: [
8412
- /* @__PURE__ */ jsx13(Text13, { color: theme.accent, children: "\u203A " }),
8413
- /* @__PURE__ */ jsx13(
9242
+ /* @__PURE__ */ jsxs13(Box13, { marginTop: 1, children: [
9243
+ /* @__PURE__ */ jsx14(Text14, { color: theme.accent, children: "\u203A " }),
9244
+ /* @__PURE__ */ jsx14(
8414
9245
  CustomTextInput,
8415
9246
  {
8416
9247
  value: input,
@@ -8459,12 +9290,12 @@ ${lines.join("\n")}` }]);
8459
9290
  ] });
8460
9291
  }
8461
9292
  async function renderApp(cfg, updateResult) {
8462
- const instance = render(/* @__PURE__ */ jsx13(App, { initialCfg: cfg, initialUpdateResult: updateResult }), {
9293
+ const instance = render(/* @__PURE__ */ jsx14(App, { initialCfg: cfg, initialUpdateResult: updateResult }), {
8463
9294
  incrementalRendering: true
8464
9295
  });
8465
9296
  await instance.waitUntilExit();
8466
9297
  }
8467
- var 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;
8468
9299
  var init_app = __esm({
8469
9300
  "src/app.tsx"() {
8470
9301
  "use strict";
@@ -8487,6 +9318,7 @@ var init_app = __esm({
8487
9318
  init_update_check();
8488
9319
  init_onboarding();
8489
9320
  init_welcome();
9321
+ init_help_menu();
8490
9322
  init_config();
8491
9323
  init_theme();
8492
9324
  init_mode();
@@ -8495,13 +9327,44 @@ var init_app = __esm({
8495
9327
  init_usage_tracker();
8496
9328
  init_manager2();
8497
9329
  init_storage_limits();
9330
+ init_state();
9331
+ init_version();
9332
+ init_loader();
9333
+ init_renderer();
9334
+ FEEDBACK_WORKER_URL = "https://kimiflare-feedback.sina-b35.workers.dev";
8498
9335
  CONTEXT_LIMIT = 262e3;
8499
9336
  AUTO_COMPACT_SUGGEST_PCT = 0.8;
8500
- MAX_EVENTS = 80;
9337
+ MAX_EVENTS = 500;
8501
9338
  nextAssistantId = 1;
8502
9339
  nextKey = 1;
8503
9340
  mkKey = () => `evt_${nextKey++}`;
8504
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
+ ]);
8505
9368
  EFFORT_DESCRIPTIONS = {
8506
9369
  low: "low \u2014 fastest; lightest reasoning. Best for simple Q&A, small edits, quick coordination.",
8507
9370
  medium: "medium \u2014 balanced (default). Solid quality on most edits, fast on trivial prompts.",
@@ -8516,21 +9379,10 @@ init_loop();
8516
9379
  init_system_prompt();
8517
9380
  init_executor();
8518
9381
  init_update_check();
9382
+ init_version();
8519
9383
  import { Command } from "commander";
8520
- import { readFileSync as readFileSync2 } from "fs";
8521
- import { fileURLToPath as fileURLToPath2 } from "url";
8522
- import { dirname as dirname4, join as join11 } from "path";
8523
- function readPackageVersion() {
8524
- try {
8525
- const here = dirname4(fileURLToPath2(import.meta.url));
8526
- const pkg = JSON.parse(readFileSync2(join11(here, "..", "package.json"), "utf8"));
8527
- return pkg.version ?? "0.0.0";
8528
- } catch {
8529
- return "0.0.0";
8530
- }
8531
- }
8532
9384
  var program = new Command();
8533
- program.name("kimiflare").description("Terminal coding agent powered by Kimi-K2.6 on Cloudflare Workers AI.").version(readPackageVersion()).option("-p, --print <prompt>", "one-shot mode: send prompt, stream reply to stdout, exit").option("-m, --model <id>", "model id (defaults to @cf/moonshotai/kimi-k2.6)").option("--dangerously-allow-all", "auto-approve every permission prompt (print mode only)").option("--reasoning", "include reasoning in stdout (print mode only)").parse();
9385
+ program.name("kimiflare").description("Terminal coding agent powered by Kimi-K2.6 on Cloudflare Workers AI.").version(getAppVersion()).option("-p, --print <prompt>", "one-shot mode: send prompt, stream reply to stdout, exit").option("-m, --model <id>", "model id (defaults to @cf/moonshotai/kimi-k2.6)").option("--dangerously-allow-all", "auto-approve every permission prompt (print mode only)").option("--reasoning", "include reasoning in stdout (print mode only)").parse();
8534
9386
  var opts = program.opts();
8535
9387
  async function main() {
8536
9388
  const cfg = await loadConfig();