kimiflare 0.86.0 → 0.87.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
@@ -385,6 +385,396 @@ var init_lsp_config = __esm({
385
385
  }
386
386
  });
387
387
 
388
+ // src/util/version.ts
389
+ import { readFileSync } from "fs";
390
+ import { fileURLToPath } from "url";
391
+ import { dirname as dirname2, join as join3 } from "path";
392
+ function getAppVersion() {
393
+ if (cachedVersion !== null) return cachedVersion;
394
+ const here = dirname2(fileURLToPath(import.meta.url));
395
+ const candidates = [join3(here, "..", "..", "package.json"), join3(here, "..", "package.json")];
396
+ for (const path of candidates) {
397
+ try {
398
+ const pkg = JSON.parse(readFileSync(path, "utf8"));
399
+ cachedVersion = pkg.version ?? "0.0.0";
400
+ return cachedVersion;
401
+ } catch {
402
+ }
403
+ }
404
+ cachedVersion = "0.0.0";
405
+ return cachedVersion;
406
+ }
407
+ function getUserAgent() {
408
+ return `kimiflare/${getAppVersion()} (+https://github.com/sinameraji/kimiflare)`;
409
+ }
410
+ var cachedVersion;
411
+ var init_version = __esm({
412
+ "src/util/version.ts"() {
413
+ "use strict";
414
+ cachedVersion = null;
415
+ }
416
+ });
417
+
418
+ // src/util/update-check.ts
419
+ import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
420
+ import { homedir as homedir2 } from "os";
421
+ import { join as join4, dirname as dirname3 } from "path";
422
+ import { fileURLToPath as fileURLToPath2 } from "url";
423
+ function cachePath() {
424
+ const xdg = process.env.XDG_CONFIG_HOME || join4(homedir2(), ".config");
425
+ return join4(xdg, "kimiflare", "update-check.json");
426
+ }
427
+ async function findPackageJson(startDir) {
428
+ let dir = startDir;
429
+ while (true) {
430
+ const candidate = join4(dir, "package.json");
431
+ try {
432
+ const raw = await readFile3(candidate, "utf8");
433
+ const parsed = JSON.parse(raw);
434
+ if (parsed.name === "kimiflare" && parsed.version) {
435
+ return { path: candidate, version: parsed.version };
436
+ }
437
+ } catch {
438
+ }
439
+ const parent = dirname3(dir);
440
+ if (parent === dir) break;
441
+ dir = parent;
442
+ }
443
+ return null;
444
+ }
445
+ async function readLocalVersion() {
446
+ const here = dirname3(fileURLToPath2(import.meta.url));
447
+ const found = await findPackageJson(here);
448
+ return found?.version ?? null;
449
+ }
450
+ async function readCache() {
451
+ try {
452
+ const raw = await readFile3(cachePath(), "utf8");
453
+ const parsed = JSON.parse(raw);
454
+ if (Date.now() - parsed.checkedAt < CACHE_TTL_MS) {
455
+ return parsed;
456
+ }
457
+ } catch {
458
+ }
459
+ return null;
460
+ }
461
+ async function writeCache(entry) {
462
+ const p = cachePath();
463
+ await mkdir3(dirname3(p), { recursive: true });
464
+ await writeFile3(p, JSON.stringify(entry), "utf8");
465
+ }
466
+ async function fetchLatestVersion() {
467
+ try {
468
+ const res = await fetch(NPM_REGISTRY, {
469
+ headers: { "User-Agent": getUserAgent(), Accept: "application/json" }
470
+ });
471
+ if (!res.ok) return null;
472
+ const data = await res.json();
473
+ return data.version ?? null;
474
+ } catch {
475
+ return null;
476
+ }
477
+ }
478
+ function stripV(v) {
479
+ return v.startsWith("v") ? v.slice(1) : v;
480
+ }
481
+ function isNewer(local, remote) {
482
+ const a = stripV(local).split(".").map(Number);
483
+ const b = stripV(remote).split(".").map(Number);
484
+ for (let i = 0; i < Math.max(a.length, b.length); i++) {
485
+ const av = a[i] ?? 0;
486
+ const bv = b[i] ?? 0;
487
+ if (av < bv) return true;
488
+ if (av > bv) return false;
489
+ }
490
+ return false;
491
+ }
492
+ async function readOptionalDepVersion(name) {
493
+ try {
494
+ const here = dirname3(fileURLToPath2(import.meta.url));
495
+ const candidate = join4(here, "..", "..", "node_modules", name, "package.json");
496
+ const raw = await readFile3(candidate, "utf8");
497
+ const parsed = JSON.parse(raw);
498
+ return parsed.version ?? null;
499
+ } catch {
500
+ return null;
501
+ }
502
+ }
503
+ async function fetchDistTagVersion(name, tag2) {
504
+ try {
505
+ const res = await fetch(`https://registry.npmjs.org/${name}/${tag2}`, {
506
+ headers: { "User-Agent": getUserAgent(), Accept: "application/json" }
507
+ });
508
+ if (!res.ok) return null;
509
+ const data = await res.json();
510
+ return data.version ?? null;
511
+ } catch {
512
+ return null;
513
+ }
514
+ }
515
+ async function checkOptionalDependency(name, tag2) {
516
+ const localVersion = await readOptionalDepVersion(name);
517
+ if (!localVersion) {
518
+ return { name, hasUpdate: false, localVersion: null, latestVersion: null };
519
+ }
520
+ const latestVersion = await fetchDistTagVersion(name, tag2);
521
+ if (!latestVersion) {
522
+ return { name, hasUpdate: false, localVersion, latestVersion: null };
523
+ }
524
+ const hasUpdate = isNewer(localVersion, latestVersion);
525
+ return { name, hasUpdate, localVersion, latestVersion };
526
+ }
527
+ async function checkForUpdate(force = false) {
528
+ const localVersion = await readLocalVersion();
529
+ if (!localVersion) return { hasUpdate: false, localVersion: null, latestVersion: null };
530
+ if (!force) {
531
+ const cached = await readCache();
532
+ if (cached) {
533
+ const hasUpdate2 = isNewer(localVersion, cached.latestVersion);
534
+ return { hasUpdate: hasUpdate2, localVersion, latestVersion: cached.latestVersion };
535
+ }
536
+ }
537
+ const latestVersion = await fetchLatestVersion();
538
+ if (!latestVersion) {
539
+ return { hasUpdate: false, localVersion, latestVersion: null };
540
+ }
541
+ const hasUpdate = isNewer(localVersion, latestVersion);
542
+ await writeCache({ checkedAt: Date.now(), latestVersion });
543
+ return { hasUpdate, localVersion, latestVersion };
544
+ }
545
+ var CACHE_TTL_MS, NPM_REGISTRY;
546
+ var init_update_check = __esm({
547
+ "src/util/update-check.ts"() {
548
+ "use strict";
549
+ init_version();
550
+ CACHE_TTL_MS = 60 * 60 * 1e3;
551
+ NPM_REGISTRY = "https://registry.npmjs.org/kimiflare/latest";
552
+ }
553
+ });
554
+
555
+ // src/remote/session-store.ts
556
+ import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir4, readdir } from "fs/promises";
557
+ import { homedir as homedir3 } from "os";
558
+ import { join as join5 } from "path";
559
+ function remoteDir() {
560
+ const xdg = process.env.XDG_DATA_HOME || join5(homedir3(), ".config");
561
+ return join5(xdg, "kimiflare", "remote");
562
+ }
563
+ async function saveRemoteSession(session) {
564
+ const dir = remoteDir();
565
+ await mkdir4(dir, { recursive: true });
566
+ const path = join5(dir, `${session.sessionId}.json`);
567
+ await writeFile4(path, JSON.stringify(session, null, 2) + "\n", "utf8");
568
+ }
569
+ async function loadRemoteSession(sessionId) {
570
+ try {
571
+ const path = join5(remoteDir(), `${sessionId}.json`);
572
+ const raw = await readFile4(path, "utf8");
573
+ return JSON.parse(raw);
574
+ } catch {
575
+ return null;
576
+ }
577
+ }
578
+ async function listRemoteSessions() {
579
+ const dir = remoteDir();
580
+ try {
581
+ const files = await readdir(dir);
582
+ const sessions = [];
583
+ for (const file of files) {
584
+ if (!file.endsWith(".json")) continue;
585
+ try {
586
+ const raw = await readFile4(join5(dir, file), "utf8");
587
+ sessions.push(JSON.parse(raw));
588
+ } catch {
589
+ }
590
+ }
591
+ return sessions.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
592
+ } catch {
593
+ return [];
594
+ }
595
+ }
596
+ async function getMostRecentRemoteSession() {
597
+ const sessions = await listRemoteSessions();
598
+ return sessions[0] ?? null;
599
+ }
600
+ var init_session_store = __esm({
601
+ "src/remote/session-store.ts"() {
602
+ "use strict";
603
+ }
604
+ });
605
+
606
+ // src/remote/deploy.ts
607
+ import { execSync } from "child_process";
608
+ import { join as join6, dirname as dirname4 } from "path";
609
+ import { fileURLToPath as fileURLToPath3 } from "url";
610
+ import { randomBytes } from "crypto";
611
+ function generateSecret() {
612
+ return randomBytes(32).toString("hex");
613
+ }
614
+ function runCapture(cmd, cwd) {
615
+ return execSync(cmd, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
616
+ }
617
+ async function* deployForTui() {
618
+ yield { message: "Checking prerequisites..." };
619
+ try {
620
+ runCapture("wrangler --version");
621
+ } catch {
622
+ yield { message: "wrangler not found. Install: npm install -g wrangler", error: true };
623
+ yield { message: "Then run: wrangler login", error: true };
624
+ throw new Error("wrangler not installed");
625
+ }
626
+ yield { message: "wrangler OK" };
627
+ try {
628
+ runCapture("wrangler whoami");
629
+ } catch {
630
+ yield { message: "wrangler not authenticated. Run: wrangler login", error: true };
631
+ throw new Error("wrangler not authenticated");
632
+ }
633
+ yield { message: "wrangler authenticated" };
634
+ try {
635
+ runCapture("docker --version");
636
+ } catch {
637
+ yield { message: "Docker not found. Install: https://docs.docker.com/get-docker/", error: true };
638
+ throw new Error("docker not installed");
639
+ }
640
+ yield { message: "Docker OK" };
641
+ yield { message: "Building remote agent bundle..." };
642
+ try {
643
+ runCapture("npm run build:remote-agent", join6(REMOTE_DIR, ".."));
644
+ yield { message: "Agent bundle built" };
645
+ } catch (err) {
646
+ yield { message: `Build failed: ${err instanceof Error ? err.message : String(err)}`, error: true };
647
+ throw err;
648
+ }
649
+ yield { message: "Deploying Worker to Cloudflare..." };
650
+ try {
651
+ runCapture("wrangler deploy", WORKER_DIR);
652
+ yield { message: "Worker deployed" };
653
+ } catch (err) {
654
+ yield { message: `Deploy failed: ${err instanceof Error ? err.message : String(err)}`, error: true };
655
+ throw err;
656
+ }
657
+ let workerUrl;
658
+ try {
659
+ const info = runCapture("wrangler info", WORKER_DIR);
660
+ const match = info.match(/https:\/\/[^\s]+\.workers\.dev/);
661
+ if (match) workerUrl = match[0];
662
+ } catch {
663
+ }
664
+ if (!workerUrl) {
665
+ yield { message: "Could not auto-detect Worker URL", error: true };
666
+ throw new Error("Worker URL not found");
667
+ }
668
+ yield { message: `Worker URL: ${workerUrl}` };
669
+ const authSecret = generateSecret();
670
+ const cfg = await loadConfig();
671
+ const cfToken = process.env.CF_API_TOKEN ?? cfg?.apiToken;
672
+ if (!cfToken) {
673
+ yield { message: "CF_API_TOKEN not found. Set CF_API_TOKEN env var or apiToken in config", error: true };
674
+ throw new Error("CF_API_TOKEN missing");
675
+ }
676
+ yield { message: "Setting Worker secrets..." };
677
+ try {
678
+ execSync(`wrangler secret put REMOTE_AUTH_SECRET`, {
679
+ cwd: WORKER_DIR,
680
+ input: authSecret,
681
+ stdio: ["pipe", "pipe", "pipe"]
682
+ });
683
+ execSync(`wrangler secret put CF_API_TOKEN`, {
684
+ cwd: WORKER_DIR,
685
+ input: cfToken,
686
+ stdio: ["pipe", "pipe", "pipe"]
687
+ });
688
+ yield { message: "Secrets set" };
689
+ } catch (err) {
690
+ yield { message: `Secret setup failed: ${err instanceof Error ? err.message : String(err)}`, error: true };
691
+ throw err;
692
+ }
693
+ const imageTag = "ghcr.io/sinameraji/kimiflare-remote-agent:latest";
694
+ yield { message: "Building container image..." };
695
+ try {
696
+ runCapture(`docker build -t ${imageTag} .`, REMOTE_DIR);
697
+ yield { message: "Image built" };
698
+ } catch (err) {
699
+ yield { message: `Image build failed: ${err instanceof Error ? err.message : String(err)}`, error: true };
700
+ throw err;
701
+ }
702
+ yield { message: `Pushing ${imageTag}...` };
703
+ try {
704
+ runCapture(`docker push ${imageTag}`, REMOTE_DIR);
705
+ yield { message: "Image pushed" };
706
+ } catch (err) {
707
+ yield { message: `Push failed: ${err instanceof Error ? err.message : String(err)}`, error: true };
708
+ yield { message: "Make sure you're logged into ghcr.io: docker login ghcr.io -u USERNAME -p GITHUB_TOKEN", error: true };
709
+ throw err;
710
+ }
711
+ const nextCfg = {
712
+ ...cfg ?? { accountId: "", apiToken: "", model: "@cf/moonshotai/kimi-k2.6" },
713
+ remoteWorkerUrl: workerUrl,
714
+ remoteAuthSecret: authSecret
715
+ };
716
+ await saveConfig(nextCfg);
717
+ yield { message: "Config saved" };
718
+ yield { message: "Remote infrastructure ready!", done: true };
719
+ return { workerUrl, authSecret };
720
+ }
721
+ async function runDeploy() {
722
+ console.log("kimiflare remote deploy\n");
723
+ try {
724
+ for await (const step of deployForTui()) {
725
+ console.log(step.message);
726
+ if (step.done) break;
727
+ if (step.error) process.exit(1);
728
+ }
729
+ console.log("\nDeploy complete!");
730
+ } catch (err) {
731
+ console.error(`Deploy failed: ${err instanceof Error ? err.message : String(err)}`);
732
+ process.exit(1);
733
+ }
734
+ }
735
+ async function checkDeployStatus() {
736
+ let wrangler = false;
737
+ let wranglerAuth = false;
738
+ let docker = false;
739
+ let workerUrl;
740
+ try {
741
+ execSync("wrangler --version", { stdio: "pipe" });
742
+ wrangler = true;
743
+ } catch {
744
+ }
745
+ if (wrangler) {
746
+ try {
747
+ execSync("wrangler whoami", { stdio: "pipe" });
748
+ wranglerAuth = true;
749
+ } catch {
750
+ }
751
+ }
752
+ try {
753
+ execSync("docker --version", { stdio: "pipe" });
754
+ docker = true;
755
+ } catch {
756
+ }
757
+ const cfg = await loadConfig();
758
+ if (cfg?.remoteWorkerUrl) {
759
+ try {
760
+ const res = await fetch(`${cfg.remoteWorkerUrl}/health`, { signal: AbortSignal.timeout(5e3) });
761
+ if (res.ok) workerUrl = cfg.remoteWorkerUrl;
762
+ } catch {
763
+ }
764
+ }
765
+ return { wrangler, wranglerAuth, docker, workerUrl };
766
+ }
767
+ var __dirname2, REMOTE_DIR, WORKER_DIR;
768
+ var init_deploy = __esm({
769
+ "src/remote/deploy.ts"() {
770
+ "use strict";
771
+ init_config();
772
+ __dirname2 = dirname4(fileURLToPath3(import.meta.url));
773
+ REMOTE_DIR = join6(__dirname2, "..", "..", "..", "remote");
774
+ WORKER_DIR = join6(REMOTE_DIR, "worker");
775
+ }
776
+ });
777
+
388
778
  // src/util/log-sink.ts
389
779
  var log_sink_exports = {};
390
780
  __export(log_sink_exports, {
@@ -401,12 +791,12 @@ __export(log_sink_exports, {
401
791
  setLogTurnId: () => setLogTurnId,
402
792
  writeLogLine: () => writeLogLine
403
793
  });
404
- import { homedir as homedir2 } from "os";
405
- import { join as join3 } from "path";
794
+ import { homedir as homedir4 } from "os";
795
+ import { join as join7 } from "path";
406
796
  import { mkdirSync, createWriteStream, readdirSync, statSync, unlinkSync } from "fs";
407
797
  function defaultLogDir() {
408
- const xdg = process.env.XDG_CONFIG_HOME || join3(homedir2(), ".config");
409
- return join3(xdg, "kimiflare", "logs");
798
+ const xdg = process.env.XDG_CONFIG_HOME || join7(homedir4(), ".config");
799
+ return join7(xdg, "kimiflare", "logs");
410
800
  }
411
801
  function isInNodeTestContext() {
412
802
  if (process.env.NODE_TEST_CONTEXT) return true;
@@ -418,7 +808,7 @@ function logDir() {
418
808
  return overrideDir ?? defaultLogDir();
419
809
  }
420
810
  function logPathFor(date = /* @__PURE__ */ new Date()) {
421
- return join3(logDir(), `${date.toISOString().slice(0, 10)}.jsonl`);
811
+ return join7(logDir(), `${date.toISOString().slice(0, 10)}.jsonl`);
422
812
  }
423
813
  function setLogDirForTesting(dir) {
424
814
  if (currentStream) {
@@ -441,8 +831,8 @@ async function flushAndCloseForTesting() {
441
831
  const s = currentStream;
442
832
  currentStream = null;
443
833
  currentDate = null;
444
- await new Promise((resolve5) => {
445
- s.end(() => resolve5());
834
+ await new Promise((resolve8) => {
835
+ s.end(() => resolve8());
446
836
  });
447
837
  }
448
838
  function isLogSinkEnabled() {
@@ -488,7 +878,7 @@ function pruneOldLogs(retentionDays = RETENTION_DAYS) {
488
878
  const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
489
879
  for (const name of entries) {
490
880
  if (!name.endsWith(".jsonl")) continue;
491
- const full = join3(logDir(), name);
881
+ const full = join7(logDir(), name);
492
882
  try {
493
883
  const st = statSync(full);
494
884
  if (st.mtimeMs < cutoff) {
@@ -919,36 +1309,6 @@ var init_errors = __esm({
919
1309
  }
920
1310
  });
921
1311
 
922
- // src/util/version.ts
923
- import { readFileSync } from "fs";
924
- import { fileURLToPath } from "url";
925
- import { dirname as dirname2, join as join4 } from "path";
926
- function getAppVersion() {
927
- if (cachedVersion !== null) return cachedVersion;
928
- const here = dirname2(fileURLToPath(import.meta.url));
929
- const candidates = [join4(here, "..", "..", "package.json"), join4(here, "..", "package.json")];
930
- for (const path of candidates) {
931
- try {
932
- const pkg = JSON.parse(readFileSync(path, "utf8"));
933
- cachedVersion = pkg.version ?? "0.0.0";
934
- return cachedVersion;
935
- } catch {
936
- }
937
- }
938
- cachedVersion = "0.0.0";
939
- return cachedVersion;
940
- }
941
- function getUserAgent() {
942
- return `kimiflare/${getAppVersion()} (+https://github.com/sinameraji/kimiflare)`;
943
- }
944
- var cachedVersion;
945
- var init_version = __esm({
946
- "src/util/version.ts"() {
947
- "use strict";
948
- cachedVersion = null;
949
- }
950
- });
951
-
952
1312
  // src/agent/messages.ts
953
1313
  function sanitizeString(str) {
954
1314
  return str.replace(/[\uD800-\uDFFF]/g, "\uFFFD");
@@ -1432,11 +1792,11 @@ function extractCloudflareError(parsed, rawText) {
1432
1792
  return null;
1433
1793
  }
1434
1794
  function sleep(ms, signal) {
1435
- return new Promise((resolve5, reject) => {
1795
+ return new Promise((resolve8, reject) => {
1436
1796
  if (signal?.aborted) return reject(new DOMException("aborted", "AbortError"));
1437
1797
  const t = setTimeout(() => {
1438
1798
  signal?.removeEventListener("abort", onAbort);
1439
- resolve5();
1799
+ resolve8();
1440
1800
  }, ms);
1441
1801
  const onAbort = () => {
1442
1802
  clearTimeout(t);
@@ -1501,21 +1861,21 @@ var init_registry2 = __esm({
1501
1861
  });
1502
1862
 
1503
1863
  // src/storage-limits.ts
1504
- import { readdir, stat, unlink } from "fs/promises";
1505
- import { join as join5 } from "path";
1864
+ import { readdir as readdir2, stat as stat2, unlink } from "fs/promises";
1865
+ import { join as join8 } from "path";
1506
1866
  async function listFilesByMtime(dir, pattern = /.*/) {
1507
1867
  let entries;
1508
1868
  try {
1509
- entries = await readdir(dir);
1869
+ entries = await readdir2(dir);
1510
1870
  } catch {
1511
1871
  return [];
1512
1872
  }
1513
1873
  const files = [];
1514
1874
  for (const name of entries) {
1515
1875
  if (!pattern.test(name)) continue;
1516
- const p = join5(dir, name);
1876
+ const p = join8(dir, name);
1517
1877
  try {
1518
- const s = await stat(p);
1878
+ const s = await stat2(p);
1519
1879
  if (s.isFile()) files.push({ path: p, mtime: s.mtime });
1520
1880
  } catch {
1521
1881
  }
@@ -1554,7 +1914,7 @@ async function rotateJsonl(path, maxBytes, rotations) {
1554
1914
  const { rename } = await import("fs/promises");
1555
1915
  let s;
1556
1916
  try {
1557
- s = await stat(path);
1917
+ s = await stat2(path);
1558
1918
  } catch {
1559
1919
  return;
1560
1920
  }
@@ -1596,15 +1956,15 @@ var init_storage_limits = __esm({
1596
1956
  });
1597
1957
 
1598
1958
  // src/cost-debug.ts
1599
- import { appendFile, mkdir as mkdir3 } from "fs/promises";
1600
- import { homedir as homedir3 } from "os";
1601
- import { join as join6 } from "path";
1959
+ import { appendFile, mkdir as mkdir5 } from "fs/promises";
1960
+ import { homedir as homedir5 } from "os";
1961
+ import { join as join9 } from "path";
1602
1962
  function debugDir() {
1603
- const xdg = process.env.XDG_DATA_HOME || join6(homedir3(), ".local", "share");
1604
- return join6(xdg, "kimiflare");
1963
+ const xdg = process.env.XDG_DATA_HOME || join9(homedir5(), ".local", "share");
1964
+ return join9(xdg, "kimiflare");
1605
1965
  }
1606
1966
  function debugPath() {
1607
- return join6(debugDir(), "cost-debug.jsonl");
1967
+ return join9(debugDir(), "cost-debug.jsonl");
1608
1968
  }
1609
1969
  function now() {
1610
1970
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -1659,7 +2019,7 @@ function buildToolStats(results) {
1659
2019
  });
1660
2020
  }
1661
2021
  async function logCostDebug(entry) {
1662
- await mkdir3(debugDir(), { recursive: true });
2022
+ await mkdir5(debugDir(), { recursive: true });
1663
2023
  await rotateJsonl(debugPath(), RETENTION.costDebugMaxBytes, RETENTION.costDebugRotations);
1664
2024
  await appendFile(debugPath(), JSON.stringify(entry) + "\n", "utf8");
1665
2025
  }
@@ -2159,9 +2519,9 @@ var require_node_gyp_build = __commonJS({
2159
2519
  var debug = getFirst(path.join(dir, "build/Debug"), matchBuild);
2160
2520
  if (debug) return debug;
2161
2521
  }
2162
- var prebuild = resolve5(dir);
2522
+ var prebuild = resolve8(dir);
2163
2523
  if (prebuild) return prebuild;
2164
- var nearby = resolve5(path.dirname(process.execPath));
2524
+ var nearby = resolve8(path.dirname(process.execPath));
2165
2525
  if (nearby) return nearby;
2166
2526
  var target = [
2167
2527
  "platform=" + platform8,
@@ -2177,7 +2537,7 @@ var require_node_gyp_build = __commonJS({
2177
2537
  // eslint-disable-line
2178
2538
  ].filter(Boolean).join(" ");
2179
2539
  throw new Error("No native build was found for " + target + "\n loaded from: " + dir + "\n");
2180
- function resolve5(dir2) {
2540
+ function resolve8(dir2) {
2181
2541
  var tuples = readdirSync2(path.join(dir2, "prebuilds")).map(parseTuple);
2182
2542
  var tuple = tuples.filter(matchTuple(platform8, arch)).sort(compareTuples)[0];
2183
2543
  if (!tuple) return;
@@ -2317,7 +2677,7 @@ var require_isolated_vm = __commonJS({
2317
2677
  });
2318
2678
 
2319
2679
  // src/code-mode/sandbox.ts
2320
- import { join as join7, dirname as dirname3 } from "path";
2680
+ import { join as join10, dirname as dirname5 } from "path";
2321
2681
  import { pathToFileURL } from "url";
2322
2682
  function stripTypescript(code) {
2323
2683
  let js = code;
@@ -2342,13 +2702,13 @@ async function loadTypescript(cwd) {
2342
2702
  } catch {
2343
2703
  }
2344
2704
  let dir = cwd;
2345
- while (dir !== dirname3(dir)) {
2705
+ while (dir !== dirname5(dir)) {
2346
2706
  try {
2347
- const tsPath = join7(dir, "node_modules", "typescript", "lib", "typescript.js");
2707
+ const tsPath = join10(dir, "node_modules", "typescript", "lib", "typescript.js");
2348
2708
  return await import(pathToFileURL(tsPath).href);
2349
2709
  } catch {
2350
2710
  }
2351
- dir = dirname3(dir);
2711
+ dir = dirname5(dir);
2352
2712
  }
2353
2713
  return null;
2354
2714
  }
@@ -3025,7 +3385,7 @@ function truncateForEmbedding(text) {
3025
3385
  return text.slice(0, MAX_EMBED_CHARS);
3026
3386
  }
3027
3387
  async function sleep2(ms) {
3028
- return new Promise((resolve5) => setTimeout(resolve5, ms));
3388
+ return new Promise((resolve8) => setTimeout(resolve8, ms));
3029
3389
  }
3030
3390
  async function fetchWithRetry(url, init, retries = 3) {
3031
3391
  let lastError;
@@ -3080,10 +3440,10 @@ async function fetchEmbeddings(opts2) {
3080
3440
  const truncated = truncateForEmbedding(text);
3081
3441
  const body = JSON.stringify({ text: [truncated] });
3082
3442
  const res = await fetchWithRetry(url, { method: "POST", headers, body });
3083
- const json = await res.json();
3443
+ const json2 = await res.json();
3084
3444
  let vectors = [];
3085
- if (json && typeof json === "object") {
3086
- const result = json.result;
3445
+ if (json2 && typeof json2 === "object") {
3446
+ const result = json2.result;
3087
3447
  if (result && typeof result === "object") {
3088
3448
  const data = result.data;
3089
3449
  if (Array.isArray(data)) {
@@ -3572,12 +3932,12 @@ var init_mode = __esm({
3572
3932
  });
3573
3933
 
3574
3934
  // src/agent/system-prompt.ts
3575
- import { platform, release, homedir as homedir4 } from "os";
3576
- import { basename, join as join8 } from "path";
3935
+ import { platform, release, homedir as homedir6 } from "os";
3936
+ import { basename, join as join11 } from "path";
3577
3937
  import { readFileSync as readFileSync2, statSync as statSync2 } from "fs";
3578
3938
  function loadContextFile(cwd) {
3579
3939
  for (const name of CONTEXT_FILENAMES) {
3580
- const path = join8(cwd, name);
3940
+ const path = join11(cwd, name);
3581
3941
  try {
3582
3942
  const s = statSync2(path);
3583
3943
  if (!s.isFile() || s.size > MAX_CONTEXT_BYTES) continue;
@@ -3627,7 +3987,7 @@ If the user asks what model you are, answer with exactly: \`${opts2.model}\`. Th
3627
3987
  - Working directory: ${opts2.cwd}
3628
3988
  - Platform: ${platform()} ${release()}
3629
3989
  - Shell: ${shell}
3630
- - Home: ${homedir4()}
3990
+ - Home: ${homedir6()}
3631
3991
  - Today: ${date}`;
3632
3992
  const hasLsp = opts2.tools.some((t) => t.name.startsWith("lsp_"));
3633
3993
  const lspBlock = hasLsp ? "\n\nLSP tools are available for semantic code intelligence. Prefer `lsp_definition` over `grep` when looking for the source of a symbol. Prefer `lsp_references` over `grep` when finding usages. Use `lsp_hover` to confirm types before refactoring." : "";
@@ -4828,10 +5188,10 @@ var init_tool_error = __esm({
4828
5188
 
4829
5189
  // src/util/paths.ts
4830
5190
  import { resolve, isAbsolute, relative, sep } from "path";
4831
- import { homedir as homedir5 } from "os";
5191
+ import { homedir as homedir7 } from "os";
4832
5192
  function resolvePath(cwd, input) {
4833
5193
  if (input.startsWith("~/") || input === "~") {
4834
- return resolve(homedir5(), input === "~" ? "." : input.slice(2));
5194
+ return resolve(homedir7(), input === "~" ? "." : input.slice(2));
4835
5195
  }
4836
5196
  return isAbsolute(input) ? input : resolve(cwd, input);
4837
5197
  }
@@ -4865,7 +5225,7 @@ var init_paths = __esm({
4865
5225
  });
4866
5226
 
4867
5227
  // src/tools/read.ts
4868
- import { readFile as readFile3, stat as stat2 } from "fs/promises";
5228
+ import { readFile as readFile5, stat as stat3 } from "fs/promises";
4869
5229
  import { createReadStream } from "fs";
4870
5230
  import { createInterface } from "readline";
4871
5231
  function aborted(signal) {
@@ -4936,7 +5296,7 @@ var init_read = __esm({
4936
5296
  async run(args, ctx) {
4937
5297
  if (aborted(ctx.signal)) throw abortError();
4938
5298
  const abs = resolvePath(ctx.cwd, args.path);
4939
- const st = await stat2(abs);
5299
+ const st = await stat3(abs);
4940
5300
  if (aborted(ctx.signal)) throw abortError();
4941
5301
  if (st.size > MAX_BYTES) {
4942
5302
  if (args.offset === void 0 || args.limit === void 0) {
@@ -4947,7 +5307,7 @@ var init_read = __esm({
4947
5307
  const lines2 = await readSliceStreaming(abs, args.offset, args.limit, ctx.signal);
4948
5308
  return formatLines(lines2, args.offset);
4949
5309
  }
4950
- const text = await readFile3(abs, { encoding: "utf8", signal: ctx.signal });
5310
+ const text = await readFile5(abs, { encoding: "utf8", signal: ctx.signal });
4951
5311
  if (aborted(ctx.signal)) throw abortError();
4952
5312
  const lines = text.split("\n");
4953
5313
  const start = Math.max(0, (args.offset ?? 1) - 1);
@@ -4960,8 +5320,8 @@ var init_read = __esm({
4960
5320
  });
4961
5321
 
4962
5322
  // src/tools/write.ts
4963
- import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
4964
- import { dirname as dirname4 } from "path";
5323
+ import { mkdir as mkdir6, readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
5324
+ import { dirname as dirname6 } from "path";
4965
5325
  var writeTool;
4966
5326
  var init_write = __esm({
4967
5327
  "src/tools/write.ts"() {
@@ -4988,11 +5348,11 @@ var init_write = __esm({
4988
5348
  const abs = resolvePath(ctx.cwd, args.path);
4989
5349
  let before = "";
4990
5350
  try {
4991
- before = await readFile4(abs, "utf8");
5351
+ before = await readFile6(abs, "utf8");
4992
5352
  } catch {
4993
5353
  }
4994
- await mkdir4(dirname4(abs), { recursive: true });
4995
- await writeFile3(abs, args.content, "utf8");
5354
+ await mkdir6(dirname6(abs), { recursive: true });
5355
+ await writeFile5(abs, args.content, "utf8");
4996
5356
  const verb = before ? "Overwrote" : "Created";
4997
5357
  return `${verb} ${args.path} (${args.content.length} chars).`;
4998
5358
  }
@@ -5001,7 +5361,7 @@ var init_write = __esm({
5001
5361
  });
5002
5362
 
5003
5363
  // src/tools/edit.ts
5004
- import { readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
5364
+ import { readFile as readFile7, writeFile as writeFile6 } from "fs/promises";
5005
5365
  function countOccurrences(haystack, needle) {
5006
5366
  if (!needle) return 0;
5007
5367
  let count = 0;
@@ -5039,7 +5399,7 @@ var init_edit = __esm({
5039
5399
  }),
5040
5400
  async run(args, ctx) {
5041
5401
  const abs = resolvePath(ctx.cwd, args.path);
5042
- const orig = await readFile5(abs, "utf8");
5402
+ const orig = await readFile7(abs, "utf8");
5043
5403
  const occurrences = countOccurrences(orig, args.old_string);
5044
5404
  if (occurrences === 0) throw new Error(`old_string not found in ${args.path}`);
5045
5405
  if (occurrences > 1 && !args.replace_all) {
@@ -5048,7 +5408,7 @@ var init_edit = __esm({
5048
5408
  );
5049
5409
  }
5050
5410
  const next = args.replace_all ? orig.split(args.old_string).join(args.new_string) : orig.replace(args.old_string, args.new_string);
5051
- await writeFile4(abs, next, "utf8");
5411
+ await writeFile6(abs, next, "utf8");
5052
5412
  return `Replaced ${occurrences} occurrence(s) in ${args.path}.`;
5053
5413
  }
5054
5414
  };
@@ -5058,7 +5418,7 @@ var init_edit = __esm({
5058
5418
  // src/tools/bash.ts
5059
5419
  import { spawn } from "child_process";
5060
5420
  import { tmpdir, platform as platform2 } from "os";
5061
- import { join as join9 } from "path";
5421
+ import { join as join12 } from "path";
5062
5422
  function getShellCommand(override) {
5063
5423
  const raw = override?.trim();
5064
5424
  if (raw && raw !== "auto") {
@@ -5104,7 +5464,7 @@ function injectCoauthor(command, coauthor) {
5104
5464
  const mentionsGit = /\bgit\b/.test(trimmed);
5105
5465
  if (!createsCommit && !isRebaseContinue && !mentionsGit) return command;
5106
5466
  if (movesHeadOnly) return command;
5107
- const tmpFile = join9(tmpdir(), `kf-coauthor-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
5467
+ const tmpFile = join12(tmpdir(), `kf-coauthor-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
5108
5468
  const amendBlock = `
5109
5469
  if ! git log -1 --pretty=%B 2>/dev/null | grep -qF "${trailer}"; then
5110
5470
  git log -1 --pretty=%B | git interpret-trailers --trailer "${trailer}" > "${tmpFile}" && git commit --amend -F "${tmpFile}" --no-edit && rm -f "${tmpFile}"
@@ -5126,7 +5486,7 @@ function runBash(args, ctx) {
5126
5486
  const timeout = Math.min(Math.max(1e3, args.timeout_ms ?? DEFAULT_TIMEOUT), MAX_TIMEOUT);
5127
5487
  const { shell, args: shellArgs, isPosix } = getShellCommand(ctx.shell);
5128
5488
  const command = isPosix ? injectCoauthor(args.command, ctx.coauthor) : args.command;
5129
- return new Promise((resolve5, reject) => {
5489
+ return new Promise((resolve8, reject) => {
5130
5490
  logger.debug("bash:spawn", { command: args.command.slice(0, 200), cwd: ctx.cwd, shell });
5131
5491
  const child = spawn(shell, [...shellArgs, command], {
5132
5492
  cwd: ctx.cwd,
@@ -5179,7 +5539,7 @@ ${stdout.trimEnd()}`);
5179
5539
  ${stderr.trimEnd()}`);
5180
5540
  if (!stdout && !stderr) parts.push("(no output)");
5181
5541
  const raw = parts.join("\n");
5182
- resolve5({
5542
+ resolve8({
5183
5543
  content: raw,
5184
5544
  rawBytes: Buffer.byteLength(raw, "utf8"),
5185
5545
  reducedBytes: Buffer.byteLength(raw, "utf8")
@@ -5219,8 +5579,8 @@ var init_bash = __esm({
5219
5579
  });
5220
5580
 
5221
5581
  // src/util/glob.ts
5222
- import { readdir as readdir2, lstat, realpath } from "fs/promises";
5223
- import { join as join10, relative as relative2, resolve as resolve2 } from "path";
5582
+ import { readdir as readdir3, lstat, realpath } from "fs/promises";
5583
+ import { join as join13, relative as relative2, resolve as resolve2 } from "path";
5224
5584
  function parsePattern(pattern) {
5225
5585
  const parts = pattern.split(/\//g);
5226
5586
  return parts.map((p) => {
@@ -5318,7 +5678,7 @@ async function* walk(root, pattern, options) {
5318
5678
  if (segIdx + 1 >= segs.length) {
5319
5679
  let entries2;
5320
5680
  try {
5321
- entries2 = await readdir2(dirPath, { withFileTypes: true, encoding: "utf8" });
5681
+ entries2 = await readdir3(dirPath, { withFileTypes: true, encoding: "utf8" });
5322
5682
  } catch (err) {
5323
5683
  if (!suppressErrors) throw err;
5324
5684
  return;
@@ -5329,7 +5689,7 @@ async function* walk(root, pattern, options) {
5329
5689
  if (!dot && name.startsWith(".")) continue;
5330
5690
  const matched = seg.type === "literal" ? seg.value === name : matchSegment(name, seg.value);
5331
5691
  if (!matched) continue;
5332
- const childPath = join10(dirPath, name);
5692
+ const childPath = join13(dirPath, name);
5333
5693
  const childRel = [...relativeParts, name];
5334
5694
  const childRelStr = childRel.join("/");
5335
5695
  if (shouldIgnore(childRelStr, ignorePatterns)) continue;
@@ -5343,7 +5703,7 @@ async function* walk(root, pattern, options) {
5343
5703
  }
5344
5704
  let entries;
5345
5705
  try {
5346
- entries = await readdir2(dirPath, { withFileTypes: true, encoding: "utf8" });
5706
+ entries = await readdir3(dirPath, { withFileTypes: true, encoding: "utf8" });
5347
5707
  } catch (err) {
5348
5708
  if (!suppressErrors) throw err;
5349
5709
  return;
@@ -5354,7 +5714,7 @@ async function* walk(root, pattern, options) {
5354
5714
  if (!dot && name.startsWith(".")) continue;
5355
5715
  const matched = seg.type === "literal" ? seg.value === name : matchSegment(name, seg.value);
5356
5716
  if (!matched) continue;
5357
- const childPath = join10(dirPath, name);
5717
+ const childPath = join13(dirPath, name);
5358
5718
  const childRel = [...relativeParts, name];
5359
5719
  const childRelStr = childRel.join("/");
5360
5720
  if (shouldIgnore(childRelStr, ignorePatterns)) continue;
@@ -5387,7 +5747,7 @@ async function* walk(root, pattern, options) {
5387
5747
  yield* processEntries(dirPath, segIdx + 1, relativeParts);
5388
5748
  let entries;
5389
5749
  try {
5390
- entries = await readdir2(dirPath, { withFileTypes: true, encoding: "utf8" });
5750
+ entries = await readdir3(dirPath, { withFileTypes: true, encoding: "utf8" });
5391
5751
  } catch (err) {
5392
5752
  if (!suppressErrors) throw err;
5393
5753
  return;
@@ -5396,7 +5756,7 @@ async function* walk(root, pattern, options) {
5396
5756
  const name = String(ent.name);
5397
5757
  if (name === "." || name === "..") continue;
5398
5758
  if (!dot && name.startsWith(".")) continue;
5399
- const childPath = join10(dirPath, name);
5759
+ const childPath = join13(dirPath, name);
5400
5760
  const childRel = [...relativeParts, name];
5401
5761
  const childRelStr = childRel.join("/");
5402
5762
  if (shouldIgnore(childRelStr, ignorePatterns)) continue;
@@ -5543,7 +5903,7 @@ var init_glob2 = __esm({
5543
5903
  // src/tools/grep.ts
5544
5904
  import { execFile } from "child_process";
5545
5905
  import { promisify } from "util";
5546
- import { readFile as readFile6 } from "fs/promises";
5906
+ import { readFile as readFile8 } from "fs/promises";
5547
5907
  async function hasRipgrep() {
5548
5908
  if (cachedHasRg !== null) return cachedHasRg;
5549
5909
  try {
@@ -5590,7 +5950,7 @@ async function runJsFallback(args, root, mode, signal) {
5590
5950
  if (signal?.aborted) throw new DOMException("aborted", "AbortError");
5591
5951
  const file = files[fi];
5592
5952
  try {
5593
- const content = await readFile6(file, "utf8");
5953
+ const content = await readFile8(file, "utf8");
5594
5954
  if (mode === "files") {
5595
5955
  if (re.test(content)) out.push(file);
5596
5956
  } else {
@@ -6080,9 +6440,9 @@ var changelog_image_exports = {};
6080
6440
  __export(changelog_image_exports, {
6081
6441
  changelogImageTool: () => changelogImageTool
6082
6442
  });
6083
- import { readFile as readFile7 } from "fs/promises";
6084
- import { writeFile as writeFile5 } from "fs/promises";
6085
- import { join as join11 } from "path";
6443
+ import { readFile as readFile9 } from "fs/promises";
6444
+ import { writeFile as writeFile7 } from "fs/promises";
6445
+ import { join as join14 } from "path";
6086
6446
  import { Resvg } from "@resvg/resvg-js";
6087
6447
  async function githubFetch2(path, token) {
6088
6448
  const controller = new AbortController();
@@ -6114,7 +6474,7 @@ function escapeXml(str) {
6114
6474
  }
6115
6475
  async function loadLogoBase64() {
6116
6476
  try {
6117
- const buf = await readFile7(join11(process.cwd(), "docs", "logo.png"));
6477
+ const buf = await readFile9(join14(process.cwd(), "docs", "logo.png"));
6118
6478
  return `data:image/png;base64,${buf.toString("base64")}`;
6119
6479
  } catch {
6120
6480
  return null;
@@ -6407,7 +6767,7 @@ Format your response as plain text bullet points, one per line, starting with "\
6407
6767
  setTask("4", "completed");
6408
6768
  setTask("5", "in_progress");
6409
6769
  const outputPath = args.output ?? `./changelog-${args.repo}-${version.replace(/[^a-zA-Z0-9._-]/g, "_")}.png`;
6410
- await writeFile5(outputPath, pngBuffer);
6770
+ await writeFile7(outputPath, pngBuffer);
6411
6771
  setTask("5", "completed");
6412
6772
  const periodLabel = days === 1 ? "past day" : `past ${days} days`;
6413
6773
  const content = `\u2713 Changelog image saved to ${outputPath}
@@ -6420,12 +6780,12 @@ Format your response as plain text bullet points, one per line, starting with "\
6420
6780
  });
6421
6781
 
6422
6782
  // src/tools/browser.ts
6423
- import { mkdir as mkdir5 } from "fs/promises";
6783
+ import { mkdir as mkdir7 } from "fs/promises";
6424
6784
  import { tmpdir as tmpdir2 } from "os";
6425
- import { join as join12, dirname as dirname5 } from "path";
6785
+ import { join as join15, dirname as dirname7 } from "path";
6426
6786
  async function autoScroll(page) {
6427
6787
  await page.evaluate(async () => {
6428
- await new Promise((resolve5) => {
6788
+ await new Promise((resolve8) => {
6429
6789
  let totalHeight = 0;
6430
6790
  const distance = 300;
6431
6791
  const timer2 = setInterval(() => {
@@ -6434,12 +6794,12 @@ async function autoScroll(page) {
6434
6794
  totalHeight += distance;
6435
6795
  if (totalHeight >= scrollHeight) {
6436
6796
  clearInterval(timer2);
6437
- resolve5();
6797
+ resolve8();
6438
6798
  }
6439
6799
  }, 100);
6440
6800
  setTimeout(() => {
6441
6801
  clearInterval(timer2);
6442
- resolve5();
6802
+ resolve8();
6443
6803
  }, 1e4);
6444
6804
  });
6445
6805
  });
@@ -6498,8 +6858,8 @@ var init_browser = __esm({
6498
6858
  }
6499
6859
  let screenshotPath;
6500
6860
  if (args.screenshot) {
6501
- screenshotPath = join12(tmpdir2(), `kimiflare-browser-${Date.now()}.png`);
6502
- await mkdir5(dirname5(screenshotPath), { recursive: true });
6861
+ screenshotPath = join15(tmpdir2(), `kimiflare-browser-${Date.now()}.png`);
6862
+ await mkdir7(dirname7(screenshotPath), { recursive: true });
6503
6863
  await page.screenshot({ path: screenshotPath, fullPage: true });
6504
6864
  }
6505
6865
  const text = await page.evaluate(() => {
@@ -7866,369 +8226,598 @@ var init_executor = __esm({
7866
8226
  }
7867
8227
  });
7868
8228
 
7869
- // src/util/update-check.ts
7870
- import { readFile as readFile8, writeFile as writeFile7, mkdir as mkdir6 } from "fs/promises";
7871
- import { homedir as homedir6 } from "os";
7872
- import { join as join13, dirname as dirname6 } from "path";
7873
- import { fileURLToPath as fileURLToPath2 } from "url";
7874
- function cachePath() {
7875
- const xdg = process.env.XDG_CONFIG_HOME || join13(homedir6(), ".config");
7876
- return join13(xdg, "kimiflare", "update-check.json");
8229
+ // src/sessions.ts
8230
+ var sessions_exports = {};
8231
+ __export(sessions_exports, {
8232
+ addCheckpoint: () => addCheckpoint,
8233
+ generateSessionTitle: () => generateSessionTitle,
8234
+ listSessions: () => listSessions,
8235
+ loadSession: () => loadSession,
8236
+ loadSessionFromCheckpoint: () => loadSessionFromCheckpoint,
8237
+ makeSessionId: () => makeSessionId,
8238
+ pruneSessions: () => pruneSessions,
8239
+ saveSession: () => saveSession,
8240
+ sessionsDir: () => sessionsDir
8241
+ });
8242
+ import { readFile as readFile10, writeFile as writeFile9, mkdir as mkdir8, readdir as readdir4, stat as stat4 } from "fs/promises";
8243
+ import { homedir as homedir8 } from "os";
8244
+ import { join as join16 } from "path";
8245
+ function sessionsDir() {
8246
+ const xdg = process.env.XDG_DATA_HOME || join16(homedir8(), ".local", "share");
8247
+ return join16(xdg, "kimiflare", "sessions");
7877
8248
  }
7878
- async function findPackageJson(startDir) {
7879
- let dir = startDir;
7880
- while (true) {
7881
- const candidate = join13(dir, "package.json");
8249
+ function sanitize(text) {
8250
+ return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
8251
+ }
8252
+ function generateSessionTitle(firstPrompt, intent) {
8253
+ const prefix = INTENT_PREFIX_MAP[intent] ?? INTENT_PREFIX_MAP.default;
8254
+ const cleaned = firstPrompt.replace(/\s+/g, " ").replace(/[\n\r]/g, " ").trim();
8255
+ const words = cleaned.split(" ").slice(0, 6).join(" ");
8256
+ const title = `${prefix} ${words}`;
8257
+ return title.length > 40 ? title.slice(0, 37) + "..." : title;
8258
+ }
8259
+ function makeSessionId(firstPrompt) {
8260
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
8261
+ const slug = sanitize(firstPrompt) || "session";
8262
+ return `${ts}_${slug}`;
8263
+ }
8264
+ async function saveSession(file) {
8265
+ const dir = sessionsDir();
8266
+ await mkdir8(dir, { recursive: true });
8267
+ const path = join16(dir, `${file.id}.json`);
8268
+ await writeFile9(path, JSON.stringify(file, null, 2), "utf8");
8269
+ return path;
8270
+ }
8271
+ async function pruneSessions() {
8272
+ const dir = sessionsDir();
8273
+ const files = await listFilesByMtime(dir, /\.json$/);
8274
+ return pruneFiles(files, RETENTION.sessionMaxAgeDays, RETENTION.sessionMaxCount);
8275
+ }
8276
+ async function listSessions(limit = 30, cwd) {
8277
+ const dir = sessionsDir();
8278
+ let entries;
8279
+ try {
8280
+ entries = await readdir4(dir);
8281
+ } catch {
8282
+ return [];
8283
+ }
8284
+ const summaries = [];
8285
+ for (const name of entries) {
8286
+ if (!name.endsWith(".json")) continue;
8287
+ const path = join16(dir, name);
7882
8288
  try {
7883
- const raw = await readFile8(candidate, "utf8");
8289
+ const [s, raw] = await Promise.all([stat4(path), readFile10(path, "utf8")]);
7884
8290
  const parsed = JSON.parse(raw);
7885
- if (parsed.name === "kimiflare" && parsed.version) {
7886
- return { path: candidate, version: parsed.version };
7887
- }
8291
+ if (cwd && parsed.cwd !== cwd) continue;
8292
+ const firstUser = parsed.messages.find((m) => m.role === "user");
8293
+ const firstPrompt = typeof firstUser?.content === "string" ? firstUser.content : firstUser?.content ? firstUser.content.find((p) => p.type === "text")?.text ?? "(no prompt)" : "(no prompt)";
8294
+ summaries.push({
8295
+ id: parsed.id,
8296
+ filePath: path,
8297
+ cwd: parsed.cwd,
8298
+ firstPrompt: firstPrompt.slice(0, 80),
8299
+ title: parsed.title,
8300
+ messageCount: parsed.messages.filter((m) => m.role !== "system").length,
8301
+ updatedAt: parsed.updatedAt ?? s.mtime.toISOString(),
8302
+ checkpointCount: parsed.checkpoints?.length ?? 0
8303
+ });
7888
8304
  } catch {
7889
8305
  }
7890
- const parent = dirname6(dir);
7891
- if (parent === dir) break;
7892
- dir = parent;
7893
8306
  }
7894
- return null;
8307
+ summaries.sort((a, b) => b.updatedAt < a.updatedAt ? -1 : 1);
8308
+ return summaries.slice(0, limit);
7895
8309
  }
7896
- async function readLocalVersion() {
7897
- const here = dirname6(fileURLToPath2(import.meta.url));
7898
- const found = await findPackageJson(here);
7899
- return found?.version ?? null;
8310
+ async function loadSession(filePath) {
8311
+ const raw = await readFile10(filePath, "utf8");
8312
+ return JSON.parse(raw);
7900
8313
  }
7901
- async function readCache() {
7902
- try {
7903
- const raw = await readFile8(cachePath(), "utf8");
7904
- const parsed = JSON.parse(raw);
7905
- if (Date.now() - parsed.checkedAt < CACHE_TTL_MS) {
7906
- return parsed;
7907
- }
7908
- } catch {
7909
- }
7910
- return null;
8314
+ async function addCheckpoint(filePath, checkpoint) {
8315
+ const file = await loadSession(filePath);
8316
+ if (!file.checkpoints) file.checkpoints = [];
8317
+ file.checkpoints.push(checkpoint);
8318
+ await saveSession(file);
7911
8319
  }
7912
- async function writeCache(entry) {
7913
- const p = cachePath();
7914
- await mkdir6(dirname6(p), { recursive: true });
7915
- await writeFile7(p, JSON.stringify(entry), "utf8");
8320
+ async function loadSessionFromCheckpoint(filePath, checkpointId) {
8321
+ const file = await loadSession(filePath);
8322
+ const checkpoint = file.checkpoints?.find((c) => c.id === checkpointId);
8323
+ if (!checkpoint) {
8324
+ throw new Error(`checkpoint ${checkpointId} not found`);
8325
+ }
8326
+ const truncated = file.messages.slice(0, checkpoint.turnIndex);
8327
+ return {
8328
+ file: {
8329
+ ...file,
8330
+ messages: truncated,
8331
+ sessionState: checkpoint.sessionState ?? file.sessionState,
8332
+ artifactStore: checkpoint.artifactStore ?? file.artifactStore
8333
+ },
8334
+ checkpoint
8335
+ };
7916
8336
  }
7917
- async function fetchLatestVersion() {
7918
- try {
7919
- const res = await fetch(NPM_REGISTRY, {
7920
- headers: { "User-Agent": getUserAgent(), Accept: "application/json" }
7921
- });
7922
- if (!res.ok) return null;
7923
- const data = await res.json();
7924
- return data.version ?? null;
7925
- } catch {
7926
- return null;
8337
+ var INTENT_PREFIX_MAP;
8338
+ var init_sessions = __esm({
8339
+ "src/sessions.ts"() {
8340
+ "use strict";
8341
+ init_storage_limits();
8342
+ INTENT_PREFIX_MAP = {
8343
+ diagnose: "Bug:",
8344
+ feature_bounded: "Feature:",
8345
+ feature_exploratory: "Feature:",
8346
+ polish: "Refactor:",
8347
+ meta: "Plan:",
8348
+ explore: "Explore:",
8349
+ qa: "Q&A:",
8350
+ verify: "Verify:",
8351
+ small_edit: "Edit:",
8352
+ default: "Task:"
8353
+ };
7927
8354
  }
8355
+ });
8356
+
8357
+ // src/util/image.ts
8358
+ import { readFile as readFile11 } from "fs/promises";
8359
+ import { basename as basename2 } from "path";
8360
+ async function encodeImageFile(filePath) {
8361
+ const buf = await readFile11(filePath);
8362
+ if (buf.byteLength > MAX_IMAGE_BYTES) {
8363
+ throw new Error(
8364
+ `image too large (${(buf.byteLength / 1024 / 1024).toFixed(1)} MB); max is ${MAX_IMAGE_BYTES / 1024 / 1024} MB`
8365
+ );
8366
+ }
8367
+ const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
8368
+ const mime = EXT_TO_MIME[ext] ?? "image/jpeg";
8369
+ const b64 = buf.toString("base64");
8370
+ return {
8371
+ filename: basename2(filePath),
8372
+ mime,
8373
+ dataUrl: `data:${mime};base64,${b64}`
8374
+ };
7928
8375
  }
7929
- function stripV(v) {
7930
- return v.startsWith("v") ? v.slice(1) : v;
8376
+ function isImagePath(path) {
8377
+ const ext = path.slice(path.lastIndexOf(".")).toLowerCase();
8378
+ return ext in EXT_TO_MIME;
7931
8379
  }
7932
- function isNewer(local, remote) {
7933
- const a = stripV(local).split(".").map(Number);
7934
- const b = stripV(remote).split(".").map(Number);
7935
- for (let i = 0; i < Math.max(a.length, b.length); i++) {
7936
- const av = a[i] ?? 0;
7937
- const bv = b[i] ?? 0;
7938
- if (av < bv) return true;
7939
- if (av > bv) return false;
8380
+ var MAX_IMAGE_BYTES, EXT_TO_MIME;
8381
+ var init_image = __esm({
8382
+ "src/util/image.ts"() {
8383
+ "use strict";
8384
+ MAX_IMAGE_BYTES = 5 * 1024 * 1024;
8385
+ EXT_TO_MIME = {
8386
+ ".png": "image/png",
8387
+ ".jpg": "image/jpeg",
8388
+ ".jpeg": "image/jpeg",
8389
+ ".gif": "image/gif",
8390
+ ".webp": "image/webp",
8391
+ ".bmp": "image/bmp"
8392
+ };
7940
8393
  }
7941
- return false;
8394
+ });
8395
+
8396
+ // src/permissions-evaluator.ts
8397
+ import { resolve as resolve3, relative as relative3 } from "path";
8398
+ import { homedir as homedir9 } from "os";
8399
+ function evaluatePermissionRules(req, rules) {
8400
+ const toolRules = rules[req.tool];
8401
+ if (!toolRules) return "ask";
8402
+ const target = extractTarget(req.tool, req.args, req.cwd);
8403
+ if (!target) return "ask";
8404
+ const entries = Object.entries(toolRules).sort((a, b) => b[0].length - a[0].length);
8405
+ for (const [pattern, rule] of entries) {
8406
+ if (matchPattern(target, pattern, req.cwd)) {
8407
+ return rule;
8408
+ }
8409
+ }
8410
+ return "ask";
7942
8411
  }
7943
- async function readOptionalDepVersion(name) {
7944
- try {
7945
- const here = dirname6(fileURLToPath2(import.meta.url));
7946
- const candidate = join13(here, "..", "..", "node_modules", name, "package.json");
7947
- const raw = await readFile8(candidate, "utf8");
7948
- const parsed = JSON.parse(raw);
7949
- return parsed.version ?? null;
7950
- } catch {
7951
- return null;
8412
+ function expandHome(path) {
8413
+ if (path.startsWith("~/")) {
8414
+ return resolve3(homedir9(), path.slice(2));
7952
8415
  }
8416
+ return path;
7953
8417
  }
7954
- async function fetchDistTagVersion(name, tag2) {
7955
- try {
7956
- const res = await fetch(`https://registry.npmjs.org/${name}/${tag2}`, {
7957
- headers: { "User-Agent": getUserAgent(), Accept: "application/json" }
7958
- });
7959
- if (!res.ok) return null;
7960
- const data = await res.json();
7961
- return data.version ?? null;
7962
- } catch {
7963
- return null;
8418
+ function extractTarget(tool, args, cwd) {
8419
+ switch (tool) {
8420
+ case "write":
8421
+ case "edit":
8422
+ case "read":
8423
+ case "glob":
8424
+ case "grep":
8425
+ return typeof args.path === "string" ? resolve3(cwd, expandHome(args.path)) : null;
8426
+ case "bash":
8427
+ return typeof args.command === "string" ? args.command : null;
8428
+ case "browser_fetch":
8429
+ case "web_fetch":
8430
+ return typeof args.url === "string" ? args.url : null;
8431
+ case "search_web":
8432
+ return typeof args.query === "string" ? args.query : null;
8433
+ default:
8434
+ return (typeof args.path === "string" ? resolve3(cwd, expandHome(args.path)) : null) ?? (typeof args.url === "string" ? args.url : null) ?? (typeof args.command === "string" ? args.command : null);
7964
8435
  }
7965
8436
  }
7966
- async function checkOptionalDependency(name, tag2) {
7967
- const localVersion = await readOptionalDepVersion(name);
7968
- if (!localVersion) {
7969
- return { name, hasUpdate: false, localVersion: null, latestVersion: null };
8437
+ function matchPattern(target, pattern, cwd) {
8438
+ let expandedPattern = pattern;
8439
+ if (pattern.startsWith("~/")) {
8440
+ expandedPattern = resolve3(homedir9(), pattern.slice(2));
7970
8441
  }
7971
- const latestVersion = await fetchDistTagVersion(name, tag2);
7972
- if (!latestVersion) {
7973
- return { name, hasUpdate: false, localVersion, latestVersion: null };
8442
+ if (target.startsWith(expandedPattern)) {
8443
+ return true;
7974
8444
  }
7975
- const hasUpdate = isNewer(localVersion, latestVersion);
7976
- return { name, hasUpdate, localVersion, latestVersion };
7977
- }
7978
- async function checkForUpdate(force = false) {
7979
- const localVersion = await readLocalVersion();
7980
- if (!localVersion) return { hasUpdate: false, localVersion: null, latestVersion: null };
7981
- if (!force) {
7982
- const cached = await readCache();
7983
- if (cached) {
7984
- const hasUpdate2 = isNewer(localVersion, cached.latestVersion);
7985
- return { hasUpdate: hasUpdate2, localVersion, latestVersion: cached.latestVersion };
8445
+ const regex = globToRegex2(expandedPattern);
8446
+ if (regex.test(target)) {
8447
+ return true;
8448
+ }
8449
+ try {
8450
+ const relTarget = relative3(cwd, target);
8451
+ if (regex.test(relTarget)) {
8452
+ return true;
7986
8453
  }
8454
+ } catch {
7987
8455
  }
7988
- const latestVersion = await fetchLatestVersion();
7989
- if (!latestVersion) {
7990
- return { hasUpdate: false, localVersion, latestVersion: null };
8456
+ return false;
8457
+ }
8458
+ function globToRegex2(pattern) {
8459
+ let regex = pattern.replace(/\*\*/g, "<<<DOUBLESTAR>>>").replace(/\*/g, "[^/]*").replace(/<<<DOUBLESTAR>>>/g, ".*").replace(/\?/g, ".");
8460
+ if (!regex.startsWith("/") && !regex.startsWith(".*")) {
8461
+ regex = ".*/" + regex;
7991
8462
  }
7992
- const hasUpdate = isNewer(localVersion, latestVersion);
7993
- await writeCache({ checkedAt: Date.now(), latestVersion });
7994
- return { hasUpdate, localVersion, latestVersion };
8463
+ return new RegExp(`^${regex}$`);
7995
8464
  }
7996
- var CACHE_TTL_MS, NPM_REGISTRY;
7997
- var init_update_check = __esm({
7998
- "src/util/update-check.ts"() {
8465
+ var init_permissions_evaluator = __esm({
8466
+ "src/permissions-evaluator.ts"() {
7999
8467
  "use strict";
8000
- init_version();
8001
- CACHE_TTL_MS = 60 * 60 * 1e3;
8002
- NPM_REGISTRY = "https://registry.npmjs.org/kimiflare/latest";
8003
8468
  }
8004
8469
  });
8005
8470
 
8006
- // src/remote/session-store.ts
8007
- import { readFile as readFile9, writeFile as writeFile8, mkdir as mkdir7, readdir as readdir3 } from "fs/promises";
8008
- import { homedir as homedir7 } from "os";
8009
- import { join as join14 } from "path";
8010
- function remoteDir() {
8011
- const xdg = process.env.XDG_DATA_HOME || join14(homedir7(), ".config");
8012
- return join14(xdg, "kimiflare", "remote");
8471
+ // src/hooks/types.ts
8472
+ var HOOK_EVENTS;
8473
+ var init_types = __esm({
8474
+ "src/hooks/types.ts"() {
8475
+ "use strict";
8476
+ HOOK_EVENTS = [
8477
+ "PreToolUse",
8478
+ "PostToolUse",
8479
+ "UserPromptSubmit",
8480
+ "Stop",
8481
+ "PreCompact"
8482
+ ];
8483
+ }
8484
+ });
8485
+
8486
+ // src/hooks/settings.ts
8487
+ import { existsSync as existsSync2, readFileSync as readFileSync3, mkdirSync as mkdirSync2, writeFileSync } from "fs";
8488
+ import { homedir as homedir10 } from "os";
8489
+ import { join as join17, dirname as dirname8 } from "path";
8490
+ import { createHash } from "crypto";
8491
+ function globalSettingsPath() {
8492
+ const xdg = process.env.XDG_CONFIG_HOME || join17(homedir10(), ".config");
8493
+ return join17(xdg, "kimiflare", "settings.json");
8013
8494
  }
8014
- async function saveRemoteSession(session) {
8015
- const dir = remoteDir();
8016
- await mkdir7(dir, { recursive: true });
8017
- const path = join14(dir, `${session.sessionId}.json`);
8018
- await writeFile8(path, JSON.stringify(session, null, 2) + "\n", "utf8");
8495
+ function projectSettingsPath(cwd) {
8496
+ return join17(cwd, ".kimiflare", "settings.json");
8019
8497
  }
8020
- async function loadRemoteSession(sessionId) {
8498
+ function readSettingsFile(path) {
8499
+ if (!existsSync2(path)) return null;
8021
8500
  try {
8022
- const path = join14(remoteDir(), `${sessionId}.json`);
8023
- const raw = await readFile9(path, "utf8");
8024
- return JSON.parse(raw);
8501
+ const raw = readFileSync3(path, "utf8");
8502
+ const parsed = JSON.parse(raw);
8503
+ return parsed && typeof parsed === "object" ? parsed : null;
8025
8504
  } catch {
8026
8505
  return null;
8027
8506
  }
8028
8507
  }
8029
- async function listRemoteSessions() {
8030
- const dir = remoteDir();
8031
- try {
8032
- const files = await readdir3(dir);
8033
- const sessions = [];
8034
- for (const file of files) {
8035
- if (!file.endsWith(".json")) continue;
8036
- try {
8037
- const raw = await readFile9(join14(dir, file), "utf8");
8038
- sessions.push(JSON.parse(raw));
8039
- } catch {
8040
- }
8508
+ function deriveHookId(event, command) {
8509
+ const h = createHash("sha256").update(`${event}\0${command}`).digest("hex");
8510
+ return h.slice(0, 8);
8511
+ }
8512
+ function normalizeHook(event, raw, source) {
8513
+ if (!raw || typeof raw !== "object") return null;
8514
+ const r = raw;
8515
+ if (typeof r.command !== "string" || !r.command.trim()) return null;
8516
+ const enabled = typeof r.enabled === "boolean" ? r.enabled : true;
8517
+ const matcher = typeof r.matcher === "string" ? r.matcher : void 0;
8518
+ const timeoutMs = typeof r.timeoutMs === "number" && r.timeoutMs > 0 ? r.timeoutMs : void 0;
8519
+ const description = typeof r.description === "string" ? r.description : void 0;
8520
+ const id = typeof r.id === "string" && r.id.length > 0 ? r.id : deriveHookId(event, r.command);
8521
+ return { id, matcher, command: r.command, timeoutMs, enabled, description, source };
8522
+ }
8523
+ function mergeHookMaps(a, b) {
8524
+ const out = {};
8525
+ for (const ev of HOOK_EVENTS) {
8526
+ const left = a?.[ev] ?? [];
8527
+ const right = b?.[ev] ?? [];
8528
+ if (left.length + right.length === 0) continue;
8529
+ out[ev] = [...left, ...right];
8530
+ }
8531
+ return out;
8532
+ }
8533
+ function loadHooksSettings(cwd) {
8534
+ const global = readSettingsFile(globalSettingsPath());
8535
+ const project = readSettingsFile(projectSettingsPath(cwd));
8536
+ const normalized = (raw, source) => {
8537
+ const out = {};
8538
+ for (const ev of HOOK_EVENTS) {
8539
+ const list = raw?.hooks?.[ev];
8540
+ if (!Array.isArray(list)) continue;
8541
+ const cleaned = list.map((h) => normalizeHook(ev, h, source)).filter((h) => h !== null);
8542
+ if (cleaned.length > 0) out[ev] = cleaned;
8041
8543
  }
8042
- return sessions.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
8043
- } catch {
8044
- return [];
8544
+ return out;
8545
+ };
8546
+ const merged = mergeHookMaps(normalized(global, "global"), normalized(project, "project"));
8547
+ return { hooks: merged };
8548
+ }
8549
+ function appendHook(scope, cwd, event, hook) {
8550
+ const path = scope === "global" ? globalSettingsPath() : projectSettingsPath(cwd);
8551
+ const existing = readSettingsFile(path) ?? {};
8552
+ const hooks = existing.hooks ?? {};
8553
+ const list = hooks[event] ?? [];
8554
+ const { source: _src, ...toWrite } = hook;
8555
+ const newId = toWrite.id ?? deriveHookId(event, toWrite.command);
8556
+ const byId = /* @__PURE__ */ new Map();
8557
+ for (const raw of list) {
8558
+ if (!raw || typeof raw !== "object") continue;
8559
+ const h = raw;
8560
+ if (typeof h.command !== "string") continue;
8561
+ const id = h.id ?? deriveHookId(event, h.command);
8562
+ if (byId.has(id)) continue;
8563
+ byId.set(id, h);
8045
8564
  }
8565
+ byId.set(newId, { ...toWrite, id: newId });
8566
+ hooks[event] = Array.from(byId.values()).map((h) => {
8567
+ if (hook.id) return h;
8568
+ const { id: _id, ...rest } = h;
8569
+ return rest;
8570
+ });
8571
+ existing.hooks = hooks;
8572
+ mkdirSync2(dirname8(path), { recursive: true });
8573
+ writeFileSync(path, JSON.stringify(existing, null, 2) + "\n", "utf8");
8574
+ return path;
8046
8575
  }
8047
- async function getMostRecentRemoteSession() {
8048
- const sessions = await listRemoteSessions();
8049
- return sessions[0] ?? null;
8576
+ function setHookEnabled(cwd, id, enabled) {
8577
+ for (const [scope, path] of [
8578
+ ["global", globalSettingsPath()],
8579
+ ["project", projectSettingsPath(cwd)]
8580
+ ]) {
8581
+ const existing = readSettingsFile(path);
8582
+ if (!existing?.hooks) continue;
8583
+ let changed = false;
8584
+ for (const ev of HOOK_EVENTS) {
8585
+ const list = existing.hooks[ev];
8586
+ if (!Array.isArray(list)) continue;
8587
+ for (const hook of list) {
8588
+ if (!hook || typeof hook !== "object") continue;
8589
+ const h = hook;
8590
+ const hookId = h.id ?? deriveHookId(ev, h.command);
8591
+ if (hookId === id) {
8592
+ h.enabled = enabled;
8593
+ changed = true;
8594
+ }
8595
+ }
8596
+ }
8597
+ if (changed) {
8598
+ mkdirSync2(dirname8(path), { recursive: true });
8599
+ writeFileSync(path, JSON.stringify(existing, null, 2) + "\n", "utf8");
8600
+ return path;
8601
+ }
8602
+ void scope;
8603
+ }
8604
+ return null;
8050
8605
  }
8051
- var init_session_store = __esm({
8052
- "src/remote/session-store.ts"() {
8606
+ var init_settings = __esm({
8607
+ "src/hooks/settings.ts"() {
8053
8608
  "use strict";
8609
+ init_types();
8054
8610
  }
8055
8611
  });
8056
8612
 
8057
- // src/remote/deploy.ts
8058
- import { execSync } from "child_process";
8059
- import { join as join15, dirname as dirname7 } from "path";
8060
- import { fileURLToPath as fileURLToPath3 } from "url";
8061
- import { randomBytes } from "crypto";
8062
- function generateSecret() {
8063
- return randomBytes(32).toString("hex");
8064
- }
8065
- function runCapture(cmd, cwd) {
8066
- return execSync(cmd, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
8613
+ // src/hooks/runner.ts
8614
+ import { spawn as spawn2 } from "child_process";
8615
+ function isVetoEvent(event) {
8616
+ return event === "PreToolUse" || event === "UserPromptSubmit";
8067
8617
  }
8068
- async function* deployForTui() {
8069
- yield { message: "Checking prerequisites..." };
8070
- try {
8071
- runCapture("wrangler --version");
8072
- } catch {
8073
- yield { message: "wrangler not found. Install: npm install -g wrangler", error: true };
8074
- yield { message: "Then run: wrangler login", error: true };
8075
- throw new Error("wrangler not installed");
8076
- }
8077
- yield { message: "wrangler OK" };
8078
- try {
8079
- runCapture("wrangler whoami");
8080
- } catch {
8081
- yield { message: "wrangler not authenticated. Run: wrangler login", error: true };
8082
- throw new Error("wrangler not authenticated");
8083
- }
8084
- yield { message: "wrangler authenticated" };
8085
- try {
8086
- runCapture("docker --version");
8087
- } catch {
8088
- yield { message: "Docker not found. Install: https://docs.docker.com/get-docker/", error: true };
8089
- throw new Error("docker not installed");
8090
- }
8091
- yield { message: "Docker OK" };
8092
- yield { message: "Building remote agent bundle..." };
8093
- try {
8094
- runCapture("npm run build:remote-agent", join15(REMOTE_DIR, ".."));
8095
- yield { message: "Agent bundle built" };
8096
- } catch (err) {
8097
- yield { message: `Build failed: ${err instanceof Error ? err.message : String(err)}`, error: true };
8098
- throw err;
8099
- }
8100
- yield { message: "Deploying Worker to Cloudflare..." };
8101
- try {
8102
- runCapture("wrangler deploy", WORKER_DIR);
8103
- yield { message: "Worker deployed" };
8104
- } catch (err) {
8105
- yield { message: `Deploy failed: ${err instanceof Error ? err.message : String(err)}`, error: true };
8106
- throw err;
8107
- }
8108
- let workerUrl;
8109
- try {
8110
- const info = runCapture("wrangler info", WORKER_DIR);
8111
- const match = info.match(/https:\/\/[^\s]+\.workers\.dev/);
8112
- if (match) workerUrl = match[0];
8113
- } catch {
8114
- }
8115
- if (!workerUrl) {
8116
- yield { message: "Could not auto-detect Worker URL", error: true };
8117
- throw new Error("Worker URL not found");
8118
- }
8119
- yield { message: `Worker URL: ${workerUrl}` };
8120
- const authSecret = generateSecret();
8121
- const cfg = await loadConfig();
8122
- const cfToken = process.env.CF_API_TOKEN ?? cfg?.apiToken;
8123
- if (!cfToken) {
8124
- yield { message: "CF_API_TOKEN not found. Set CF_API_TOKEN env var or apiToken in config", error: true };
8125
- throw new Error("CF_API_TOKEN missing");
8126
- }
8127
- yield { message: "Setting Worker secrets..." };
8128
- try {
8129
- execSync(`wrangler secret put REMOTE_AUTH_SECRET`, {
8130
- cwd: WORKER_DIR,
8131
- input: authSecret,
8132
- stdio: ["pipe", "pipe", "pipe"]
8133
- });
8134
- execSync(`wrangler secret put CF_API_TOKEN`, {
8135
- cwd: WORKER_DIR,
8136
- input: cfToken,
8137
- stdio: ["pipe", "pipe", "pipe"]
8138
- });
8139
- yield { message: "Secrets set" };
8140
- } catch (err) {
8141
- yield { message: `Secret setup failed: ${err instanceof Error ? err.message : String(err)}`, error: true };
8142
- throw err;
8143
- }
8144
- const imageTag = "ghcr.io/sinameraji/kimiflare-remote-agent:latest";
8145
- yield { message: "Building container image..." };
8146
- try {
8147
- runCapture(`docker build -t ${imageTag} .`, REMOTE_DIR);
8148
- yield { message: "Image built" };
8149
- } catch (err) {
8150
- yield { message: `Image build failed: ${err instanceof Error ? err.message : String(err)}`, error: true };
8151
- throw err;
8618
+ function buildHookEnv(payload) {
8619
+ const env2 = {
8620
+ KIMIFLARE_HOOK_EVENT: payload.event,
8621
+ KIMIFLARE_HOOK_CWD: payload.cwd,
8622
+ KIMIFLARE_HOOK_PAYLOAD: JSON.stringify(payload)
8623
+ };
8624
+ if (payload.session_id) env2.KIMIFLARE_HOOK_SESSION_ID = payload.session_id;
8625
+ if (payload.event === "PreToolUse" || payload.event === "PostToolUse") {
8626
+ const p = payload;
8627
+ env2.KIMIFLARE_HOOK_TOOL = p.tool;
8628
+ const path = p.args.path;
8629
+ if (typeof path === "string") env2.KIMIFLARE_HOOK_PATH = path;
8630
+ if (p.tier) env2.KIMIFLARE_HOOK_TIER = p.tier;
8631
+ if (p.event === "PostToolUse") {
8632
+ env2.KIMIFLARE_HOOK_RESULT_OK = String(p.result.ok);
8633
+ const ec = p.result.errorCode;
8634
+ if (ec) env2.KIMIFLARE_HOOK_RESULT_ERROR_CODE = ec;
8635
+ }
8152
8636
  }
8153
- yield { message: `Pushing ${imageTag}...` };
8154
- try {
8155
- runCapture(`docker push ${imageTag}`, REMOTE_DIR);
8156
- yield { message: "Image pushed" };
8157
- } catch (err) {
8158
- yield { message: `Push failed: ${err instanceof Error ? err.message : String(err)}`, error: true };
8159
- yield { message: "Make sure you're logged into ghcr.io: docker login ghcr.io -u USERNAME -p GITHUB_TOKEN", error: true };
8160
- throw err;
8637
+ if (payload.event === "UserPromptSubmit") {
8638
+ const p = payload;
8639
+ if (p.tier) env2.KIMIFLARE_HOOK_TIER = p.tier;
8161
8640
  }
8162
- const nextCfg = {
8163
- ...cfg ?? { accountId: "", apiToken: "", model: "@cf/moonshotai/kimi-k2.6" },
8164
- remoteWorkerUrl: workerUrl,
8165
- remoteAuthSecret: authSecret
8166
- };
8167
- await saveConfig(nextCfg);
8168
- yield { message: "Config saved" };
8169
- yield { message: "Remote infrastructure ready!", done: true };
8170
- return { workerUrl, authSecret };
8641
+ return env2;
8171
8642
  }
8172
- async function runDeploy() {
8173
- console.log("kimiflare remote deploy\n");
8174
- try {
8175
- for await (const step of deployForTui()) {
8176
- console.log(step.message);
8177
- if (step.done) break;
8178
- if (step.error) process.exit(1);
8179
- }
8180
- console.log("\nDeploy complete!");
8181
- } catch (err) {
8182
- console.error(`Deploy failed: ${err instanceof Error ? err.message : String(err)}`);
8183
- process.exit(1);
8643
+ function capStream(s) {
8644
+ if (Buffer.byteLength(s, "utf8") <= STREAM_CAP_BYTES) return s;
8645
+ let cut = s;
8646
+ while (Buffer.byteLength(cut, "utf8") > STREAM_CAP_BYTES) {
8647
+ cut = cut.slice(0, Math.floor(cut.length * 0.9));
8184
8648
  }
8649
+ return `${cut}
8650
+ [\u2026truncated]`;
8185
8651
  }
8186
- async function checkDeployStatus() {
8187
- let wrangler = false;
8188
- let wranglerAuth = false;
8189
- let docker = false;
8190
- let workerUrl;
8652
+ async function runHook(hook, payload, signal) {
8653
+ const start = Date.now();
8654
+ const timeoutMs = hook.timeoutMs ?? DEFAULT_TIMEOUT_MS;
8655
+ const env2 = { ...process.env, ...buildHookEnv(payload) };
8656
+ const json2 = JSON.stringify(payload);
8657
+ const id = hook.id ?? "anonymous";
8658
+ let result;
8191
8659
  try {
8192
- execSync("wrangler --version", { stdio: "pipe" });
8193
- wrangler = true;
8194
- } catch {
8660
+ result = await spawnImpl(hook.command, json2, env2, payload.cwd, timeoutMs, signal);
8661
+ } catch (e) {
8662
+ return {
8663
+ id,
8664
+ exitCode: null,
8665
+ stdout: "",
8666
+ stderr: e instanceof Error ? e.message : String(e),
8667
+ timedOut: false,
8668
+ durationMs: Date.now() - start
8669
+ };
8195
8670
  }
8196
- if (wrangler) {
8671
+ return {
8672
+ id,
8673
+ exitCode: result.exitCode,
8674
+ stdout: capStream(result.stdout.trim()),
8675
+ stderr: capStream(result.stderr.trim()),
8676
+ timedOut: result.timedOut,
8677
+ durationMs: Date.now() - start
8678
+ };
8679
+ }
8680
+ function filterHooks(hooks, toolName) {
8681
+ if (!hooks || hooks.length === 0) return [];
8682
+ return hooks.filter((h) => {
8683
+ if (h.enabled === false) return false;
8684
+ if (!h.matcher) return true;
8685
+ if (!toolName) return true;
8197
8686
  try {
8198
- execSync("wrangler whoami", { stdio: "pipe" });
8199
- wranglerAuth = true;
8687
+ return new RegExp(h.matcher).test(toolName);
8200
8688
  } catch {
8689
+ return false;
8201
8690
  }
8202
- }
8203
- try {
8204
- execSync("docker --version", { stdio: "pipe" });
8205
- docker = true;
8206
- } catch {
8207
- }
8208
- const cfg = await loadConfig();
8209
- if (cfg?.remoteWorkerUrl) {
8210
- try {
8211
- const res = await fetch(`${cfg.remoteWorkerUrl}/health`, { signal: AbortSignal.timeout(5e3) });
8212
- if (res.ok) workerUrl = cfg.remoteWorkerUrl;
8213
- } catch {
8691
+ });
8692
+ }
8693
+ async function runHooks(event, hooks, payload, toolName = null, signal) {
8694
+ const matched = filterHooks(hooks, toolName);
8695
+ const outcomes = [];
8696
+ const veto = isVetoEvent(event);
8697
+ const vetoReasons = [];
8698
+ let vetoed = false;
8699
+ for (const hook of matched) {
8700
+ const outcome = await runHook(hook, payload, signal);
8701
+ outcomes.push(outcome);
8702
+ if (outcome.timedOut) {
8703
+ logger.warn("hook:timeout", {
8704
+ event,
8705
+ id: outcome.id,
8706
+ timeoutMs: hook.timeoutMs ?? DEFAULT_TIMEOUT_MS
8707
+ });
8708
+ } else if (outcome.exitCode !== 0 && outcome.exitCode !== null) {
8709
+ logger.info("hook:nonzero_exit", {
8710
+ event,
8711
+ id: outcome.id,
8712
+ exitCode: outcome.exitCode
8713
+ });
8714
+ }
8715
+ if (veto && (outcome.exitCode !== 0 || outcome.timedOut)) {
8716
+ vetoed = true;
8717
+ const reason = outcome.stdout || outcome.stderr || `hook ${outcome.id} exited ${outcome.exitCode}`;
8718
+ vetoReasons.push(reason);
8719
+ break;
8214
8720
  }
8215
8721
  }
8216
- return { wrangler, wranglerAuth, docker, workerUrl };
8722
+ return { outcomes, vetoed, vetoReason: vetoReasons.join("\n") };
8217
8723
  }
8218
- var __dirname2, REMOTE_DIR, WORKER_DIR;
8219
- var init_deploy = __esm({
8220
- "src/remote/deploy.ts"() {
8724
+ var DEFAULT_TIMEOUT_MS, STREAM_CAP_BYTES, defaultSpawn, spawnImpl;
8725
+ var init_runner = __esm({
8726
+ "src/hooks/runner.ts"() {
8221
8727
  "use strict";
8222
- init_config();
8223
- __dirname2 = dirname7(fileURLToPath3(import.meta.url));
8224
- REMOTE_DIR = join15(__dirname2, "..", "..", "..", "remote");
8225
- WORKER_DIR = join15(REMOTE_DIR, "worker");
8728
+ init_logger();
8729
+ DEFAULT_TIMEOUT_MS = 3e4;
8730
+ STREAM_CAP_BYTES = 4 * 1024;
8731
+ defaultSpawn = (command, payloadJson, env2, cwd, timeoutMs, signal) => new Promise((resolve8) => {
8732
+ const child = spawn2(command, {
8733
+ shell: true,
8734
+ cwd,
8735
+ env: env2,
8736
+ stdio: ["pipe", "pipe", "pipe"]
8737
+ });
8738
+ let stdout = "";
8739
+ let stderr = "";
8740
+ let settled = false;
8741
+ const finish = (exitCode, timedOut = false) => {
8742
+ if (settled) return;
8743
+ settled = true;
8744
+ clearTimeout(timer2);
8745
+ signal?.removeEventListener("abort", onAbort);
8746
+ resolve8({ exitCode, stdout, stderr, timedOut });
8747
+ };
8748
+ const onAbort = () => {
8749
+ child.kill("SIGTERM");
8750
+ finish(null, true);
8751
+ };
8752
+ const timer2 = setTimeout(() => {
8753
+ child.kill("SIGTERM");
8754
+ finish(null, true);
8755
+ }, timeoutMs);
8756
+ signal?.addEventListener("abort", onAbort);
8757
+ child.stdout.setEncoding("utf8");
8758
+ child.stderr.setEncoding("utf8");
8759
+ child.stdout.on("data", (d) => {
8760
+ stdout += d;
8761
+ });
8762
+ child.stderr.on("data", (d) => {
8763
+ stderr += d;
8764
+ });
8765
+ child.on("error", () => finish(null));
8766
+ child.on("exit", (code) => finish(code));
8767
+ try {
8768
+ child.stdin.end(payloadJson);
8769
+ } catch {
8770
+ }
8771
+ });
8772
+ spawnImpl = defaultSpawn;
8773
+ }
8774
+ });
8775
+
8776
+ // src/hooks/manager.ts
8777
+ var manager_exports = {};
8778
+ __export(manager_exports, {
8779
+ HooksManager: () => HooksManager
8780
+ });
8781
+ var HooksManager;
8782
+ var init_manager = __esm({
8783
+ "src/hooks/manager.ts"() {
8784
+ "use strict";
8785
+ init_settings();
8786
+ init_runner();
8787
+ HooksManager = class {
8788
+ cwd;
8789
+ settings;
8790
+ constructor(cwd) {
8791
+ this.cwd = cwd;
8792
+ this.settings = loadHooksSettings(cwd);
8793
+ }
8794
+ /** Re-read settings from disk. */
8795
+ reload() {
8796
+ this.settings = loadHooksSettings(this.cwd);
8797
+ }
8798
+ /** All hooks registered for an event, before matcher filtering. */
8799
+ hooksFor(event) {
8800
+ return this.settings.hooks?.[event] ?? [];
8801
+ }
8802
+ /** True if at least one enabled hook would match this event. Cheap
8803
+ * pre-check the loop can use to avoid building payloads for events
8804
+ * that have no listeners. */
8805
+ hasEnabledHooks(event) {
8806
+ const list = this.hooksFor(event);
8807
+ return list.some((h) => h.enabled !== false);
8808
+ }
8809
+ /** Fire all matching hooks for `event`. Toolname is used only by
8810
+ * PreToolUse / PostToolUse matchers. */
8811
+ fire(event, payload, toolName = null, signal) {
8812
+ return runHooks(event, this.hooksFor(event), payload, toolName, signal);
8813
+ }
8814
+ };
8226
8815
  }
8227
8816
  });
8228
8817
 
8229
8818
  // src/cost-attribution/types.ts
8230
8819
  var ALL_CATEGORIES;
8231
- var init_types = __esm({
8820
+ var init_types2 = __esm({
8232
8821
  "src/cost-attribution/types.ts"() {
8233
8822
  "use strict";
8234
8823
  ALL_CATEGORIES = [
@@ -8307,7 +8896,7 @@ function buildReport(opts2) {
8307
8896
  var init_report = __esm({
8308
8897
  "src/cost-attribution/report.ts"() {
8309
8898
  "use strict";
8310
- init_types();
8899
+ init_types2();
8311
8900
  }
8312
8901
  });
8313
8902
 
@@ -8431,10 +9020,10 @@ async function fetchGatewayLogs(accountId, apiToken, gatewayId, startDate, endDa
8431
9020
  if (!res.ok) {
8432
9021
  throw new Error(`gateway logs HTTP ${res.status}`);
8433
9022
  }
8434
- const json = await res.json();
8435
- const page = Array.isArray(json.result) ? json.result : [];
9023
+ const json2 = await res.json();
9024
+ const page = Array.isArray(json2.result) ? json2.result : [];
8436
9025
  out.push(...page);
8437
- cursor = json.result_info?.cursor;
9026
+ cursor = json2.result_info?.cursor;
8438
9027
  if (!cursor || page.length === 0) break;
8439
9028
  }
8440
9029
  return out;
@@ -8524,12 +9113,12 @@ function extOf(path) {
8524
9113
  const idx = path.lastIndexOf(".");
8525
9114
  return idx >= 0 ? path.slice(idx).toLowerCase() : "";
8526
9115
  }
8527
- function basename2(path) {
9116
+ function basename4(path) {
8528
9117
  const idx = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
8529
9118
  return idx >= 0 ? path.slice(idx + 1) : path;
8530
9119
  }
8531
9120
  function classifyFile(path) {
8532
- const base = basename2(path);
9121
+ const base = basename4(path);
8533
9122
  const ext = extOf(path);
8534
9123
  if (TEST_PATTERNS.test(base)) return "writing-tests";
8535
9124
  if (base.toLowerCase().startsWith("readme")) return "reading-documentation";
@@ -8540,7 +9129,7 @@ function classifyFile(path) {
8540
9129
  return null;
8541
9130
  }
8542
9131
  function classifyWriteFile(path) {
8543
- const base = basename2(path);
9132
+ const base = basename4(path);
8544
9133
  const ext = extOf(path);
8545
9134
  if (TEST_PATTERNS.test(base)) return "writing-tests";
8546
9135
  if (base.toLowerCase().startsWith("readme") || DOC_EXTS.has(ext)) return "writing-documentation";
@@ -8549,7 +9138,7 @@ function classifyWriteFile(path) {
8549
9138
  return null;
8550
9139
  }
8551
9140
  function classifyEditFile(path) {
8552
- const base = basename2(path);
9141
+ const base = basename4(path);
8553
9142
  const ext = extOf(path);
8554
9143
  if (base.toLowerCase().startsWith("readme") || DOC_EXTS.has(ext)) return "editing-documentation";
8555
9144
  if (CONFIG_EXTS.has(ext)) return "editing-configuration";
@@ -8736,12 +9325,12 @@ var init_heuristic = __esm({
8736
9325
  });
8737
9326
 
8738
9327
  // src/cost-attribution/classify-from-session.ts
8739
- import { readFile as readFile10 } from "fs/promises";
8740
- import { join as join16 } from "path";
8741
- import { homedir as homedir8 } from "os";
8742
- function sessionsDir() {
8743
- const xdg = process.env.XDG_DATA_HOME || join16(homedir8(), ".local", "share");
8744
- return join16(xdg, "kimiflare", "sessions");
9328
+ import { readFile as readFile13 } from "fs/promises";
9329
+ import { join as join18 } from "path";
9330
+ import { homedir as homedir11 } from "os";
9331
+ function sessionsDir2() {
9332
+ const xdg = process.env.XDG_DATA_HOME || join18(homedir11(), ".local", "share");
9333
+ return join18(xdg, "kimiflare", "sessions");
8745
9334
  }
8746
9335
  function parseToolCalls(calls) {
8747
9336
  return calls.map((c) => {
@@ -8755,7 +9344,7 @@ function parseToolCalls(calls) {
8755
9344
  }
8756
9345
  async function classifyFromSessionFile(sessionId) {
8757
9346
  try {
8758
- const raw = await readFile10(join16(sessionsDir(), `${sessionId}.json`), "utf8");
9347
+ const raw = await readFile13(join18(sessionsDir2(), `${sessionId}.json`), "utf8");
8759
9348
  const session = JSON.parse(raw);
8760
9349
  const messages = session.messages ?? [];
8761
9350
  const turns = [];
@@ -8788,15 +9377,15 @@ var cli_exports = {};
8788
9377
  __export(cli_exports, {
8789
9378
  runCostCommand: () => runCostCommand
8790
9379
  });
8791
- import { readFile as readFile11 } from "fs/promises";
8792
- import { join as join17 } from "path";
8793
- import { homedir as homedir9 } from "os";
9380
+ import { readFile as readFile14 } from "fs/promises";
9381
+ import { join as join19 } from "path";
9382
+ import { homedir as homedir12 } from "os";
8794
9383
  function usageDir() {
8795
- const xdg = process.env.XDG_DATA_HOME || join17(homedir9(), ".local", "share");
8796
- return join17(xdg, "kimiflare");
9384
+ const xdg = process.env.XDG_DATA_HOME || join19(homedir12(), ".local", "share");
9385
+ return join19(xdg, "kimiflare");
8797
9386
  }
8798
9387
  function usagePath() {
8799
- return join17(usageDir(), "usage.json");
9388
+ return join19(usageDir(), "usage.json");
8800
9389
  }
8801
9390
  function today() {
8802
9391
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -8808,7 +9397,7 @@ function daysAgo(n) {
8808
9397
  }
8809
9398
  async function loadLog() {
8810
9399
  try {
8811
- const raw = await readFile11(usagePath(), "utf8");
9400
+ const raw = await readFile14(usagePath(), "utf8");
8812
9401
  return JSON.parse(raw);
8813
9402
  } catch {
8814
9403
  return { version: 1, days: [], sessions: [] };
@@ -8894,134 +9483,6 @@ var init_cli = __esm({
8894
9483
  }
8895
9484
  });
8896
9485
 
8897
- // src/sessions.ts
8898
- var sessions_exports = {};
8899
- __export(sessions_exports, {
8900
- addCheckpoint: () => addCheckpoint,
8901
- generateSessionTitle: () => generateSessionTitle,
8902
- listSessions: () => listSessions,
8903
- loadSession: () => loadSession,
8904
- loadSessionFromCheckpoint: () => loadSessionFromCheckpoint,
8905
- makeSessionId: () => makeSessionId,
8906
- pruneSessions: () => pruneSessions,
8907
- saveSession: () => saveSession,
8908
- sessionsDir: () => sessionsDir2
8909
- });
8910
- import { readFile as readFile12, writeFile as writeFile9, mkdir as mkdir8, readdir as readdir4, stat as stat4 } from "fs/promises";
8911
- import { homedir as homedir10 } from "os";
8912
- import { join as join18 } from "path";
8913
- function sessionsDir2() {
8914
- const xdg = process.env.XDG_DATA_HOME || join18(homedir10(), ".local", "share");
8915
- return join18(xdg, "kimiflare", "sessions");
8916
- }
8917
- function sanitize(text) {
8918
- return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
8919
- }
8920
- function generateSessionTitle(firstPrompt, intent) {
8921
- const prefix = INTENT_PREFIX_MAP[intent] ?? INTENT_PREFIX_MAP.default;
8922
- const cleaned = firstPrompt.replace(/\s+/g, " ").replace(/[\n\r]/g, " ").trim();
8923
- const words = cleaned.split(" ").slice(0, 6).join(" ");
8924
- const title = `${prefix} ${words}`;
8925
- return title.length > 40 ? title.slice(0, 37) + "..." : title;
8926
- }
8927
- function makeSessionId(firstPrompt) {
8928
- const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
8929
- const slug = sanitize(firstPrompt) || "session";
8930
- return `${ts}_${slug}`;
8931
- }
8932
- async function saveSession(file) {
8933
- const dir = sessionsDir2();
8934
- await mkdir8(dir, { recursive: true });
8935
- const path = join18(dir, `${file.id}.json`);
8936
- await writeFile9(path, JSON.stringify(file, null, 2), "utf8");
8937
- return path;
8938
- }
8939
- async function pruneSessions() {
8940
- const dir = sessionsDir2();
8941
- const files = await listFilesByMtime(dir, /\.json$/);
8942
- return pruneFiles(files, RETENTION.sessionMaxAgeDays, RETENTION.sessionMaxCount);
8943
- }
8944
- async function listSessions(limit = 30, cwd) {
8945
- const dir = sessionsDir2();
8946
- let entries;
8947
- try {
8948
- entries = await readdir4(dir);
8949
- } catch {
8950
- return [];
8951
- }
8952
- const summaries = [];
8953
- for (const name of entries) {
8954
- if (!name.endsWith(".json")) continue;
8955
- const path = join18(dir, name);
8956
- try {
8957
- const [s, raw] = await Promise.all([stat4(path), readFile12(path, "utf8")]);
8958
- const parsed = JSON.parse(raw);
8959
- if (cwd && parsed.cwd !== cwd) continue;
8960
- const firstUser = parsed.messages.find((m) => m.role === "user");
8961
- const firstPrompt = typeof firstUser?.content === "string" ? firstUser.content : firstUser?.content ? firstUser.content.find((p) => p.type === "text")?.text ?? "(no prompt)" : "(no prompt)";
8962
- summaries.push({
8963
- id: parsed.id,
8964
- filePath: path,
8965
- cwd: parsed.cwd,
8966
- firstPrompt: firstPrompt.slice(0, 80),
8967
- title: parsed.title,
8968
- messageCount: parsed.messages.filter((m) => m.role !== "system").length,
8969
- updatedAt: parsed.updatedAt ?? s.mtime.toISOString(),
8970
- checkpointCount: parsed.checkpoints?.length ?? 0
8971
- });
8972
- } catch {
8973
- }
8974
- }
8975
- summaries.sort((a, b) => b.updatedAt < a.updatedAt ? -1 : 1);
8976
- return summaries.slice(0, limit);
8977
- }
8978
- async function loadSession(filePath) {
8979
- const raw = await readFile12(filePath, "utf8");
8980
- return JSON.parse(raw);
8981
- }
8982
- async function addCheckpoint(filePath, checkpoint) {
8983
- const file = await loadSession(filePath);
8984
- if (!file.checkpoints) file.checkpoints = [];
8985
- file.checkpoints.push(checkpoint);
8986
- await saveSession(file);
8987
- }
8988
- async function loadSessionFromCheckpoint(filePath, checkpointId) {
8989
- const file = await loadSession(filePath);
8990
- const checkpoint = file.checkpoints?.find((c) => c.id === checkpointId);
8991
- if (!checkpoint) {
8992
- throw new Error(`checkpoint ${checkpointId} not found`);
8993
- }
8994
- const truncated = file.messages.slice(0, checkpoint.turnIndex);
8995
- return {
8996
- file: {
8997
- ...file,
8998
- messages: truncated,
8999
- sessionState: checkpoint.sessionState ?? file.sessionState,
9000
- artifactStore: checkpoint.artifactStore ?? file.artifactStore
9001
- },
9002
- checkpoint
9003
- };
9004
- }
9005
- var INTENT_PREFIX_MAP;
9006
- var init_sessions = __esm({
9007
- "src/sessions.ts"() {
9008
- "use strict";
9009
- init_storage_limits();
9010
- INTENT_PREFIX_MAP = {
9011
- diagnose: "Bug:",
9012
- feature_bounded: "Feature:",
9013
- feature_exploratory: "Feature:",
9014
- polish: "Refactor:",
9015
- meta: "Plan:",
9016
- explore: "Explore:",
9017
- qa: "Q&A:",
9018
- verify: "Verify:",
9019
- small_edit: "Edit:",
9020
- default: "Task:"
9021
- };
9022
- }
9023
- });
9024
-
9025
9486
  // src/camouflage-resume.ts
9026
9487
  var camouflage_resume_exports = {};
9027
9488
  __export(camouflage_resume_exports, {
@@ -9111,7 +9572,7 @@ __export(tui_auth_exports, {
9111
9572
  authGitHubForTui: () => authGitHubForTui
9112
9573
  });
9113
9574
  function sleep3(ms) {
9114
- return new Promise((resolve5) => setTimeout(resolve5, ms));
9575
+ return new Promise((resolve8) => setTimeout(resolve8, ms));
9115
9576
  }
9116
9577
  async function* authGitHubForTui() {
9117
9578
  yield { message: "Starting GitHub OAuth device flow..." };
@@ -9193,6 +9654,701 @@ var init_tui_auth = __esm({
9193
9654
  }
9194
9655
  });
9195
9656
 
9657
+ // src/server/sse.ts
9658
+ function createSseStream(res) {
9659
+ res.writeHead(200, {
9660
+ "Content-Type": "text/event-stream",
9661
+ "Cache-Control": "no-cache",
9662
+ Connection: "keep-alive"
9663
+ });
9664
+ const heartbeat = setInterval(() => {
9665
+ res.write(":heartbeat\n\n");
9666
+ }, 3e4);
9667
+ res.on("close", () => {
9668
+ clearInterval(heartbeat);
9669
+ });
9670
+ return {
9671
+ send(event, data) {
9672
+ const payload = JSON.stringify(data);
9673
+ res.write(`event: ${event}
9674
+ `);
9675
+ res.write(`data: ${payload}
9676
+
9677
+ `);
9678
+ },
9679
+ close() {
9680
+ clearInterval(heartbeat);
9681
+ res.end();
9682
+ }
9683
+ };
9684
+ }
9685
+ var init_sse2 = __esm({
9686
+ "src/server/sse.ts"() {
9687
+ "use strict";
9688
+ }
9689
+ });
9690
+
9691
+ // src/server/openapi.ts
9692
+ function getOpenApiSpec() {
9693
+ const spec = {
9694
+ openapi: "3.1.0",
9695
+ info: {
9696
+ title: "KimiFlare Headless Server API",
9697
+ version: "1.0.0",
9698
+ description: "HTTP API for running KimiFlare agent sessions headlessly."
9699
+ },
9700
+ servers: [{ url: "/", description: "Local server" }],
9701
+ paths: {
9702
+ "/": {
9703
+ get: {
9704
+ summary: "Health check",
9705
+ responses: {
9706
+ "200": {
9707
+ description: "Server is running",
9708
+ content: {
9709
+ "application/json": {
9710
+ schema: { type: "object", properties: { status: { type: "string" }, version: { type: "string" } } }
9711
+ }
9712
+ }
9713
+ }
9714
+ }
9715
+ }
9716
+ },
9717
+ "/prompt": {
9718
+ post: {
9719
+ summary: "Start a new agent session with a prompt",
9720
+ requestBody: {
9721
+ required: true,
9722
+ content: {
9723
+ "application/json": {
9724
+ schema: {
9725
+ type: "object",
9726
+ required: ["prompt"],
9727
+ properties: {
9728
+ prompt: { type: "string", description: "The user prompt" },
9729
+ model: { type: "string", description: "Model ID to use" },
9730
+ cwd: { type: "string", description: "Working directory" },
9731
+ title: { type: "string", description: "Session title" },
9732
+ files: { type: "array", items: { type: "string" }, description: "File paths or globs to attach" },
9733
+ allowAll: { type: "boolean", description: "Auto-approve all tool calls" }
9734
+ }
9735
+ }
9736
+ }
9737
+ }
9738
+ },
9739
+ responses: {
9740
+ "202": {
9741
+ description: "Session started",
9742
+ content: {
9743
+ "application/json": {
9744
+ schema: {
9745
+ type: "object",
9746
+ properties: {
9747
+ sessionId: { type: "string" },
9748
+ status: { type: "string" }
9749
+ }
9750
+ }
9751
+ }
9752
+ }
9753
+ }
9754
+ }
9755
+ }
9756
+ },
9757
+ "/session": {
9758
+ get: {
9759
+ summary: "List sessions",
9760
+ parameters: [
9761
+ {
9762
+ name: "cwd",
9763
+ in: "query",
9764
+ schema: { type: "string" },
9765
+ description: "Filter by working directory"
9766
+ }
9767
+ ],
9768
+ responses: {
9769
+ "200": {
9770
+ description: "List of sessions",
9771
+ content: {
9772
+ "application/json": {
9773
+ schema: {
9774
+ type: "object",
9775
+ properties: {
9776
+ sessions: {
9777
+ type: "array",
9778
+ items: {
9779
+ type: "object",
9780
+ properties: {
9781
+ id: { type: "string" },
9782
+ cwd: { type: "string" },
9783
+ firstPrompt: { type: "string" },
9784
+ title: { type: "string" },
9785
+ messageCount: { type: "number" },
9786
+ updatedAt: { type: "string" }
9787
+ }
9788
+ }
9789
+ }
9790
+ }
9791
+ }
9792
+ }
9793
+ }
9794
+ }
9795
+ }
9796
+ }
9797
+ },
9798
+ "/session/{id}": {
9799
+ get: {
9800
+ summary: "Get session state",
9801
+ parameters: [
9802
+ {
9803
+ name: "id",
9804
+ in: "path",
9805
+ required: true,
9806
+ schema: { type: "string" }
9807
+ }
9808
+ ],
9809
+ responses: {
9810
+ "200": {
9811
+ description: "Session state",
9812
+ content: {
9813
+ "application/json": {
9814
+ schema: {
9815
+ type: "object",
9816
+ properties: {
9817
+ id: { type: "string" },
9818
+ cwd: { type: "string" },
9819
+ model: { type: "string" },
9820
+ messages: { type: "array" },
9821
+ title: { type: "string" },
9822
+ updatedAt: { type: "string" }
9823
+ }
9824
+ }
9825
+ }
9826
+ }
9827
+ },
9828
+ "404": { description: "Session not found" }
9829
+ }
9830
+ },
9831
+ delete: {
9832
+ summary: "Delete a session",
9833
+ parameters: [
9834
+ {
9835
+ name: "id",
9836
+ in: "path",
9837
+ required: true,
9838
+ schema: { type: "string" }
9839
+ }
9840
+ ],
9841
+ responses: {
9842
+ "200": {
9843
+ description: "Session deleted",
9844
+ content: {
9845
+ "application/json": {
9846
+ schema: {
9847
+ type: "object",
9848
+ properties: { deleted: { type: "string" } }
9849
+ }
9850
+ }
9851
+ }
9852
+ }
9853
+ }
9854
+ }
9855
+ },
9856
+ "/session/{id}/prompt": {
9857
+ post: {
9858
+ summary: "Send a follow-up prompt to a session",
9859
+ parameters: [
9860
+ {
9861
+ name: "id",
9862
+ in: "path",
9863
+ required: true,
9864
+ schema: { type: "string" }
9865
+ }
9866
+ ],
9867
+ requestBody: {
9868
+ required: true,
9869
+ content: {
9870
+ "application/json": {
9871
+ schema: {
9872
+ type: "object",
9873
+ required: ["prompt"],
9874
+ properties: {
9875
+ prompt: { type: "string" },
9876
+ files: { type: "array", items: { type: "string" } },
9877
+ allowAll: { type: "boolean" }
9878
+ }
9879
+ }
9880
+ }
9881
+ }
9882
+ },
9883
+ responses: {
9884
+ "202": {
9885
+ description: "Follow-up started",
9886
+ content: {
9887
+ "application/json": {
9888
+ schema: {
9889
+ type: "object",
9890
+ properties: {
9891
+ sessionId: { type: "string" },
9892
+ status: { type: "string" }
9893
+ }
9894
+ }
9895
+ }
9896
+ }
9897
+ },
9898
+ "404": { description: "Session not found" }
9899
+ }
9900
+ }
9901
+ },
9902
+ "/event": {
9903
+ get: {
9904
+ summary: "Server-Sent Events stream",
9905
+ responses: {
9906
+ "200": {
9907
+ description: "SSE stream of session events",
9908
+ content: {
9909
+ "text/event-stream": {
9910
+ schema: {
9911
+ type: "object",
9912
+ description: "Stream of events: server.connected, assistant.delta, tool.call, tool.result, usage.update, session.completed, error"
9913
+ }
9914
+ }
9915
+ }
9916
+ }
9917
+ }
9918
+ }
9919
+ }
9920
+ }
9921
+ };
9922
+ return `<!DOCTYPE html>
9923
+ <html>
9924
+ <head>
9925
+ <meta charset="utf-8">
9926
+ <title>KimiFlare Headless Server API</title>
9927
+ <style>
9928
+ body { font-family: system-ui, sans-serif; max-width: 900px; margin: 40px auto; padding: 0 20px; }
9929
+ pre { background: #f5f5f5; padding: 16px; border-radius: 8px; overflow-x: auto; }
9930
+ h1 { border-bottom: 2px solid #333; padding-bottom: 8px; }
9931
+ h2 { margin-top: 32px; }
9932
+ code { background: #f0f0f0; padding: 2px 6px; border-radius: 4px; }
9933
+ </style>
9934
+ </head>
9935
+ <body>
9936
+ <h1>KimiFlare Headless Server API</h1>
9937
+ <p>OpenAPI 3.1 specification for the local HTTP server.</p>
9938
+ <h2>Spec</h2>
9939
+ <pre>${JSON.stringify(spec, null, 2)}</pre>
9940
+ </body>
9941
+ </html>`;
9942
+ }
9943
+ var init_openapi = __esm({
9944
+ "src/server/openapi.ts"() {
9945
+ "use strict";
9946
+ }
9947
+ });
9948
+
9949
+ // src/server/routes.ts
9950
+ import { URL as URL2 } from "url";
9951
+ import { readFile as readFile15 } from "fs/promises";
9952
+ import { resolve as resolve5, basename as basename5 } from "path";
9953
+ function json(res, status, data) {
9954
+ res.writeHead(status, { "Content-Type": "application/json" });
9955
+ res.end(JSON.stringify(data));
9956
+ }
9957
+ function badRequest(res, message2) {
9958
+ json(res, 400, { error: message2 });
9959
+ }
9960
+ function notFound(res, message2) {
9961
+ json(res, 404, { error: message2 });
9962
+ }
9963
+ async function readBody(req) {
9964
+ const chunks = [];
9965
+ for await (const chunk of req) {
9966
+ chunks.push(chunk);
9967
+ }
9968
+ const text = Buffer.concat(chunks).toString("utf8");
9969
+ try {
9970
+ return JSON.parse(text);
9971
+ } catch {
9972
+ return text;
9973
+ }
9974
+ }
9975
+ async function resolveFiles2(filePatterns, cwd) {
9976
+ const resolved = /* @__PURE__ */ new Set();
9977
+ for (const pattern of filePatterns) {
9978
+ try {
9979
+ const stat8 = await import("fs/promises").then((m) => m.stat(resolve5(cwd, pattern)));
9980
+ if (stat8.isFile()) {
9981
+ resolved.add(resolve5(cwd, pattern));
9982
+ continue;
9983
+ }
9984
+ } catch {
9985
+ }
9986
+ const matches = await glob(pattern, { cwd, absolute: true });
9987
+ for (const m of matches) {
9988
+ resolved.add(m);
9989
+ }
9990
+ }
9991
+ return [...resolved];
9992
+ }
9993
+ async function buildUserMessage2(prompt, files, cwd) {
9994
+ let text = prompt;
9995
+ const imageParts = [];
9996
+ const fileContents = [];
9997
+ for (const filePath of files) {
9998
+ if (isImagePath(filePath)) {
9999
+ try {
10000
+ const img = await encodeImageFile(filePath);
10001
+ imageParts.push({ type: "image_url", image_url: { url: img.dataUrl } });
10002
+ } catch (e) {
10003
+ fileContents.push(`
10004
+ <!-- failed to attach image ${basename5(filePath)}: ${e.message} -->
10005
+ `);
10006
+ }
10007
+ } else {
10008
+ try {
10009
+ const content = await readFile15(filePath, "utf8");
10010
+ const relPath = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
10011
+ fileContents.push(`
10012
+ --- ${relPath} ---
10013
+ ${content}
10014
+ --- end ${relPath} ---
10015
+ `);
10016
+ } catch (e) {
10017
+ fileContents.push(`
10018
+ <!-- failed to read ${basename5(filePath)}: ${e.message} -->
10019
+ `);
10020
+ }
10021
+ }
10022
+ }
10023
+ if (fileContents.length > 0) {
10024
+ text += "\n\n" + fileContents.join("\n");
10025
+ }
10026
+ if (imageParts.length > 0) {
10027
+ const parts = [{ type: "text", text }];
10028
+ parts.push(...imageParts);
10029
+ return parts;
10030
+ }
10031
+ return text;
10032
+ }
10033
+ function setupRoutes(config2) {
10034
+ async function handleRequest(req, res) {
10035
+ const url = new URL2(req.url ?? "/", `http://${req.headers.host}`);
10036
+ const method = req.method ?? "GET";
10037
+ const pathname = url.pathname;
10038
+ try {
10039
+ if (pathname === "/" && method === "GET") {
10040
+ json(res, 200, { status: "ok", version: process.env.npm_package_version ?? "dev" });
10041
+ return;
10042
+ }
10043
+ if (pathname === "/doc" && method === "GET") {
10044
+ res.writeHead(200, { "Content-Type": "text/html" });
10045
+ res.end(getOpenApiSpec());
10046
+ return;
10047
+ }
10048
+ if (pathname === "/event" && method === "GET") {
10049
+ const client = createSseStream(res);
10050
+ client.send("server.connected", { timestamp: Date.now() });
10051
+ return;
10052
+ }
10053
+ if (pathname === "/session" && method === "GET") {
10054
+ const cwd = url.searchParams.get("cwd") ?? void 0;
10055
+ const sessions = await listSessions(30, cwd);
10056
+ json(res, 200, { sessions });
10057
+ return;
10058
+ }
10059
+ const sessionMatch = pathname.match(/^\/session\/([^/]+)$/);
10060
+ if (sessionMatch && method === "GET") {
10061
+ const sessionId = sessionMatch[1];
10062
+ const active = activeSessions.get(sessionId);
10063
+ if (active) {
10064
+ json(res, 200, {
10065
+ id: active.sessionFile.id,
10066
+ cwd: active.sessionFile.cwd,
10067
+ model: active.sessionFile.model,
10068
+ messages: active.messages,
10069
+ title: active.sessionFile.title,
10070
+ updatedAt: active.sessionFile.updatedAt
10071
+ });
10072
+ return;
10073
+ }
10074
+ try {
10075
+ const file = await loadSession(resolve5(sessionsDir(), `${sessionId}.json`));
10076
+ json(res, 200, {
10077
+ id: file.id,
10078
+ cwd: file.cwd,
10079
+ model: file.model,
10080
+ messages: file.messages,
10081
+ title: file.title,
10082
+ updatedAt: file.updatedAt
10083
+ });
10084
+ return;
10085
+ } catch {
10086
+ notFound(res, `session ${sessionId} not found`);
10087
+ return;
10088
+ }
10089
+ }
10090
+ if (sessionMatch && method === "DELETE") {
10091
+ const sessionId = sessionMatch[1];
10092
+ activeSessions.delete(sessionId);
10093
+ try {
10094
+ const { unlink: unlink6 } = await import("fs/promises");
10095
+ await unlink6(resolve5(sessionsDir(), `${sessionId}.json`));
10096
+ } catch {
10097
+ }
10098
+ json(res, 200, { deleted: sessionId });
10099
+ return;
10100
+ }
10101
+ if (pathname === "/prompt" && method === "POST") {
10102
+ const body = await readBody(req);
10103
+ const prompt = typeof body.prompt === "string" ? body.prompt : "";
10104
+ const model = typeof body.model === "string" ? body.model : config2.model ?? "@cf/moonshotai/kimi-k2.6";
10105
+ const cwd = typeof body.cwd === "string" ? body.cwd : process.cwd();
10106
+ const title = typeof body.title === "string" ? body.title : void 0;
10107
+ const files = Array.isArray(body.files) ? body.files.filter((f) => typeof f === "string") : [];
10108
+ const allowAll = body.allowAll === true;
10109
+ if (!prompt) {
10110
+ badRequest(res, "prompt is required");
10111
+ return;
10112
+ }
10113
+ const { makeSessionId: makeSessionId2 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
10114
+ const sessionId = makeSessionId2(prompt);
10115
+ const sessionFile = {
10116
+ id: sessionId,
10117
+ cwd,
10118
+ model,
10119
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
10120
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
10121
+ messages: [],
10122
+ title
10123
+ };
10124
+ const executor = new ToolExecutor(ALL_TOOLS);
10125
+ const messages = [
10126
+ { role: "system", content: buildSystemPrompt({ cwd, tools: ALL_TOOLS, model }) }
10127
+ ];
10128
+ const resolvedFiles = await resolveFiles2(files, cwd);
10129
+ const userContent = await buildUserMessage2(prompt, resolvedFiles, cwd);
10130
+ messages.push({ role: "user", content: userContent });
10131
+ const active = {
10132
+ sessionFile,
10133
+ messages,
10134
+ executor,
10135
+ sseClients: /* @__PURE__ */ new Set()
10136
+ };
10137
+ activeSessions.set(sessionId, active);
10138
+ runAgentTurnForSession(active, config2, allowAll);
10139
+ json(res, 202, { sessionId, status: "started" });
10140
+ return;
10141
+ }
10142
+ if (pathname === "/session/:id/prompt" && method === "POST") {
10143
+ }
10144
+ const sessionPromptMatch = pathname.match(/^\/session\/([^/]+)\/prompt$/);
10145
+ if (sessionPromptMatch && method === "POST") {
10146
+ const sessionId = sessionPromptMatch[1];
10147
+ const active = activeSessions.get(sessionId);
10148
+ if (!active) {
10149
+ notFound(res, `session ${sessionId} not found or expired`);
10150
+ return;
10151
+ }
10152
+ const body = await readBody(req);
10153
+ const prompt = typeof body.prompt === "string" ? body.prompt : "";
10154
+ const files = Array.isArray(body.files) ? body.files.filter((f) => typeof f === "string") : [];
10155
+ const allowAll = body.allowAll === true;
10156
+ if (!prompt) {
10157
+ badRequest(res, "prompt is required");
10158
+ return;
10159
+ }
10160
+ const resolvedFiles = await resolveFiles2(files, active.sessionFile.cwd);
10161
+ const userContent = await buildUserMessage2(prompt, resolvedFiles, active.sessionFile.cwd);
10162
+ active.messages.push({ role: "user", content: userContent });
10163
+ runAgentTurnForSession(active, config2, allowAll);
10164
+ json(res, 202, { sessionId, status: "started" });
10165
+ return;
10166
+ }
10167
+ notFound(res, `unknown endpoint: ${method} ${pathname}`);
10168
+ } catch (err) {
10169
+ logger.error("server: request error", { error: err.message, path: pathname });
10170
+ json(res, 500, { error: err.message });
10171
+ }
10172
+ }
10173
+ function cleanup() {
10174
+ for (const [, active] of activeSessions) {
10175
+ for (const client of active.sseClients) {
10176
+ client.close();
10177
+ }
10178
+ }
10179
+ activeSessions.clear();
10180
+ }
10181
+ return { handleRequest, cleanup };
10182
+ }
10183
+ async function runAgentTurnForSession(active, config2, allowAll) {
10184
+ const controller = new AbortController();
10185
+ const { sessionFile, messages, executor } = active;
10186
+ const callbacks = {
10187
+ onTextDelta: (delta) => {
10188
+ for (const client of active.sseClients) {
10189
+ client.send("assistant.delta", { delta });
10190
+ }
10191
+ },
10192
+ onToolCallFinalized: (call) => {
10193
+ for (const client of active.sseClients) {
10194
+ client.send("tool.call", {
10195
+ id: call.id,
10196
+ name: call.function.name,
10197
+ arguments: call.function.arguments
10198
+ });
10199
+ }
10200
+ },
10201
+ onToolResult: (result) => {
10202
+ for (const client of active.sseClients) {
10203
+ client.send("tool.result", {
10204
+ toolCallId: result.tool_call_id,
10205
+ name: result.name,
10206
+ content: result.content,
10207
+ ok: result.ok
10208
+ });
10209
+ }
10210
+ },
10211
+ onUsage: (usage) => {
10212
+ for (const client of active.sseClients) {
10213
+ client.send("usage.update", {
10214
+ promptTokens: usage.prompt_tokens,
10215
+ completionTokens: usage.completion_tokens,
10216
+ totalTokens: usage.total_tokens
10217
+ });
10218
+ }
10219
+ },
10220
+ onWarning: (msg) => {
10221
+ for (const client of active.sseClients) {
10222
+ client.send("warning", { message: msg });
10223
+ }
10224
+ },
10225
+ askPermission: async ({ tool, args }) => {
10226
+ if (allowAll) return "allow";
10227
+ if (config2.permissions) {
10228
+ const rule = evaluatePermissionRules({ tool: tool.name, args, cwd: sessionFile.cwd }, config2.permissions);
10229
+ if (rule === "allow") return "allow";
10230
+ if (rule === "deny") {
10231
+ for (const client of active.sseClients) {
10232
+ client.send("permission.denied", { tool: tool.name, args, reason: "config_rule" });
10233
+ }
10234
+ return "deny";
10235
+ }
10236
+ }
10237
+ for (const client of active.sseClients) {
10238
+ client.send("permission.request", { tool: tool.name, args });
10239
+ }
10240
+ return "deny";
10241
+ }
10242
+ };
10243
+ try {
10244
+ await runAgentTurn({
10245
+ accountId: config2.accountId,
10246
+ apiToken: config2.apiToken,
10247
+ model: sessionFile.model,
10248
+ messages,
10249
+ tools: ALL_TOOLS,
10250
+ executor,
10251
+ cwd: sessionFile.cwd,
10252
+ signal: controller.signal,
10253
+ codeMode: config2.codeMode,
10254
+ callbacks
10255
+ });
10256
+ sessionFile.messages = messages;
10257
+ sessionFile.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
10258
+ await saveSession(sessionFile);
10259
+ for (const client of active.sseClients) {
10260
+ client.send("session.completed", { sessionId: sessionFile.id });
10261
+ }
10262
+ } catch (err) {
10263
+ const message2 = err.message;
10264
+ logger.error("server: agent turn failed", { sessionId: sessionFile.id, error: message2 });
10265
+ for (const client of active.sseClients) {
10266
+ client.send("error", { message: message2, sessionId: sessionFile.id });
10267
+ }
10268
+ }
10269
+ }
10270
+ var activeSessions;
10271
+ var init_routes = __esm({
10272
+ "src/server/routes.ts"() {
10273
+ "use strict";
10274
+ init_loop();
10275
+ init_system_prompt();
10276
+ init_executor();
10277
+ init_sessions();
10278
+ init_logger();
10279
+ init_sse2();
10280
+ init_openapi();
10281
+ init_permissions_evaluator();
10282
+ init_image();
10283
+ init_glob();
10284
+ activeSessions = /* @__PURE__ */ new Map();
10285
+ }
10286
+ });
10287
+
10288
+ // src/server/index.ts
10289
+ var server_exports = {};
10290
+ __export(server_exports, {
10291
+ startServer: () => startServer
10292
+ });
10293
+ import { createServer } from "http";
10294
+ async function startServer(opts2) {
10295
+ const { handleRequest, cleanup } = setupRoutes(opts2.config);
10296
+ const server = createServer((req, res) => {
10297
+ res.setHeader("Access-Control-Allow-Origin", "*");
10298
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
10299
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Last-Event-ID");
10300
+ if (req.method === "OPTIONS") {
10301
+ res.writeHead(204);
10302
+ res.end();
10303
+ return;
10304
+ }
10305
+ const password = process.env.KIMIFLARE_SERVER_PASSWORD;
10306
+ if (password) {
10307
+ const auth = req.headers.authorization ?? "";
10308
+ const expected = "Basic " + Buffer.from(`kimiflare:${password}`).toString("base64");
10309
+ if (auth !== expected) {
10310
+ res.writeHead(401, { "WWW-Authenticate": 'Basic realm="kimiflare"' });
10311
+ res.end(JSON.stringify({ error: "Unauthorized" }));
10312
+ return;
10313
+ }
10314
+ }
10315
+ void handleRequest(req, res);
10316
+ });
10317
+ server.on("close", () => {
10318
+ cleanup();
10319
+ });
10320
+ const shutdown = () => {
10321
+ logger.info("server: shutting down gracefully");
10322
+ server.close(() => {
10323
+ cleanup();
10324
+ process.exit(0);
10325
+ });
10326
+ setTimeout(() => {
10327
+ logger.warn("server: forced shutdown");
10328
+ process.exit(1);
10329
+ }, 1e4);
10330
+ };
10331
+ process.on("SIGINT", shutdown);
10332
+ process.on("SIGTERM", shutdown);
10333
+ return new Promise((resolve8, reject) => {
10334
+ server.listen(opts2.port, opts2.hostname, () => {
10335
+ logger.info("server: listening", { hostname: opts2.hostname, port: opts2.port });
10336
+ console.log(`kimiflare serve: http://${opts2.hostname}:${opts2.port}`);
10337
+ console.log(` API docs: http://${opts2.hostname}:${opts2.port}/doc`);
10338
+ console.log(` SSE stream: http://${opts2.hostname}:${opts2.port}/event`);
10339
+ resolve8(server);
10340
+ });
10341
+ server.on("error", reject);
10342
+ });
10343
+ }
10344
+ var init_server = __esm({
10345
+ "src/server/index.ts"() {
10346
+ "use strict";
10347
+ init_logger();
10348
+ init_routes();
10349
+ }
10350
+ });
10351
+
9196
10352
  // src/memory/schema.ts
9197
10353
  var DEFAULT_EMBEDDING_DIM;
9198
10354
  var init_schema = __esm({
@@ -9230,8 +10386,8 @@ __export(db_exports, {
9230
10386
  updateMemoryEmbedding: () => updateMemoryEmbedding
9231
10387
  });
9232
10388
  import Database from "better-sqlite3";
9233
- import { dirname as dirname8 } from "path";
9234
- import { mkdirSync as mkdirSync2, statSync as statSync3 } from "fs";
10389
+ import { dirname as dirname9 } from "path";
10390
+ import { mkdirSync as mkdirSync3, statSync as statSync3 } from "fs";
9235
10391
  function initSchema(db) {
9236
10392
  db.exec(`
9237
10393
  CREATE TABLE IF NOT EXISTS memories (
@@ -9315,7 +10471,7 @@ function openMemoryDb(dbPath) {
9315
10471
  if (dbInstance) {
9316
10472
  dbInstance.close();
9317
10473
  }
9318
- mkdirSync2(dirname8(dbPath), { recursive: true });
10474
+ mkdirSync3(dirname9(dbPath), { recursive: true });
9319
10475
  dbInstance = new Database(dbPath);
9320
10476
  dbInstance.pragma("journal_mode = WAL");
9321
10477
  dbInstance.pragma("foreign_keys = ON");
@@ -9656,8 +10812,8 @@ function computeExactScore(memory, queryText, cwd) {
9656
10812
  let score = 0;
9657
10813
  const lowerQuery = queryText.toLowerCase();
9658
10814
  for (const file of memory.relatedFiles) {
9659
- const basename6 = file.split("/").pop() ?? file;
9660
- if (lowerQuery.includes(basename6.toLowerCase()) || basename6.toLowerCase().includes(lowerQuery)) {
10815
+ const basename8 = file.split("/").pop() ?? file;
10816
+ if (lowerQuery.includes(basename8.toLowerCase()) || basename8.toLowerCase().includes(lowerQuery)) {
9661
10817
  score += 0.3;
9662
10818
  }
9663
10819
  if (cwd && file.startsWith(cwd)) {
@@ -9892,7 +11048,7 @@ function pickTopicKey(content, existingKeys) {
9892
11048
  return normalized;
9893
11049
  }
9894
11050
  var SECRET_PATTERNS, VERIFY_SYSTEM, HYPOTHETICAL_QUERIES_SYSTEM, MemoryManager;
9895
- var init_manager = __esm({
11051
+ var init_manager2 = __esm({
9896
11052
  "src/memory/manager.ts"() {
9897
11053
  "use strict";
9898
11054
  init_client();
@@ -10237,7 +11393,7 @@ Context: This memory was explicitly provided by the user during a conversation.`
10237
11393
  });
10238
11394
 
10239
11395
  // src/lsp/connection.ts
10240
- import { spawn as spawn2 } from "child_process";
11396
+ import { spawn as spawn3 } from "child_process";
10241
11397
  import { EventEmitter } from "events";
10242
11398
  var LspConnection;
10243
11399
  var init_connection = __esm({
@@ -10259,7 +11415,7 @@ var init_connection = __esm({
10259
11415
  if (this.child) {
10260
11416
  throw new Error("LSP connection already started");
10261
11417
  }
10262
- return new Promise((resolve5, reject) => {
11418
+ return new Promise((resolve8, reject) => {
10263
11419
  const abortController = new AbortController();
10264
11420
  const spawnTimer = setTimeout(() => {
10265
11421
  abortController.abort();
@@ -10267,7 +11423,7 @@ var init_connection = __esm({
10267
11423
  reject(new Error(`LSP server spawn timed out after ${spawnTimeoutMs}ms`));
10268
11424
  }, spawnTimeoutMs);
10269
11425
  try {
10270
- const child = spawn2(command[0], command.slice(1), {
11426
+ const child = spawn3(command[0], command.slice(1), {
10271
11427
  env: { ...process.env, ...env2 },
10272
11428
  stdio: ["pipe", "pipe", "pipe"]
10273
11429
  });
@@ -10298,7 +11454,7 @@ var init_connection = __esm({
10298
11454
  if (child.pid) {
10299
11455
  clearTimeout(spawnTimer);
10300
11456
  this.child = child;
10301
- resolve5();
11457
+ resolve8();
10302
11458
  }
10303
11459
  });
10304
11460
  } catch (err) {
@@ -10313,12 +11469,12 @@ var init_connection = __esm({
10313
11469
  }
10314
11470
  const id = this.nextId++;
10315
11471
  const msg = { jsonrpc: "2.0", id, method, params };
10316
- return new Promise((resolve5, reject) => {
11472
+ return new Promise((resolve8, reject) => {
10317
11473
  const timer2 = setTimeout(() => {
10318
11474
  this.pending.delete(id);
10319
11475
  reject(toolTimeoutError(`LSP request '${method}'`, this.requestTimeoutMs));
10320
11476
  }, this.requestTimeoutMs);
10321
- const pending = { resolve: resolve5, reject, signal, timer: timer2 };
11477
+ const pending = { resolve: resolve8, reject, signal, timer: timer2 };
10322
11478
  this.pending.set(id, pending);
10323
11479
  if (signal) {
10324
11480
  const onAbort = () => {
@@ -10654,7 +11810,7 @@ var init_client2 = __esm({
10654
11810
 
10655
11811
  // src/lsp/manager.ts
10656
11812
  var DEFAULT_LSP_TIMEOUT_MS, DEFAULT_LSP_MAX_RESTART_ATTEMPTS, RESTART_BASE_DELAY_MS, RESTART_MAX_DELAY_MS, LspManager;
10657
- var init_manager2 = __esm({
11813
+ var init_manager3 = __esm({
10658
11814
  "src/lsp/manager.ts"() {
10659
11815
  "use strict";
10660
11816
  init_connection();
@@ -10882,10 +12038,10 @@ var init_manager2 = __esm({
10882
12038
  });
10883
12039
 
10884
12040
  // src/lsp/adapter.ts
10885
- import { relative as relative3 } from "path";
12041
+ import { relative as relative4 } from "path";
10886
12042
  function formatLocation(loc, cwd) {
10887
12043
  const path = fromUri(loc.uri);
10888
- const rel = relative3(cwd, path) || path;
12044
+ const rel = relative4(cwd, path) || path;
10889
12045
  return `${rel}:${loc.range.start.line + 1}:${loc.range.start.character + 1}`;
10890
12046
  }
10891
12047
  function formatLocations(locs, cwd) {
@@ -10922,7 +12078,7 @@ function formatWorkspaceSymbols(symbols, cwd) {
10922
12078
  if (!symbols || symbols.length === 0) return "No symbols found.";
10923
12079
  return symbols.map((s) => {
10924
12080
  const path = fromUri(s.location.uri);
10925
- const rel = relative3(cwd, path) || path;
12081
+ const rel = relative4(cwd, path) || path;
10926
12082
  const container = s.containerName ? ` in ${s.containerName}` : "";
10927
12083
  return `${s.name} (${symbolKindName(s.kind)})${container} \u2014 ${rel}:${s.location.range.start.line + 1}:${s.location.range.start.character + 1}`;
10928
12084
  }).join("\n");
@@ -10942,7 +12098,7 @@ function formatWorkspaceEdit(edit, cwd) {
10942
12098
  if (edit.changes) {
10943
12099
  for (const [uri, edits] of Object.entries(edit.changes)) {
10944
12100
  const path = fromUri(uri);
10945
- const rel = relative3(cwd, path) || path;
12101
+ const rel = relative4(cwd, path) || path;
10946
12102
  lines.push(`File: ${rel}`);
10947
12103
  for (const e of edits) {
10948
12104
  lines.push(` ${e.range.start.line + 1}:${e.range.start.character + 1}-${e.range.end.line + 1}:${e.range.end.character + 1}: ${e.newText}`);
@@ -10967,7 +12123,7 @@ var init_adapter = __esm({
10967
12123
  });
10968
12124
 
10969
12125
  // src/tools/lsp.ts
10970
- import { relative as relative4 } from "path";
12126
+ import { relative as relative5 } from "path";
10971
12127
  function makeOutput2(content) {
10972
12128
  const bytes = Buffer.byteLength(content, "utf8");
10973
12129
  return { content, rawBytes: bytes, reducedBytes: bytes };
@@ -10975,7 +12131,7 @@ function makeOutput2(content) {
10975
12131
  function resolveLspPath(args, ctx) {
10976
12132
  const raw = typeof args.path === "string" ? args.path : "";
10977
12133
  const resolved = resolvePath(ctx.cwd, raw);
10978
- const rel = relative4(ctx.cwd, resolved);
12134
+ const rel = relative5(ctx.cwd, resolved);
10979
12135
  if (isPathOutside(rel)) {
10980
12136
  throw new Error(`Path outside workspace: ${raw}`);
10981
12137
  }
@@ -11259,20 +12415,20 @@ var init_pricing = __esm({
11259
12415
  });
11260
12416
 
11261
12417
  // src/usage-tracker.ts
11262
- import { readFile as readFile13, writeFile as writeFile10, mkdir as mkdir9 } from "fs/promises";
11263
- import { homedir as homedir11 } from "os";
11264
- import { join as join20 } from "path";
12418
+ import { readFile as readFile16, writeFile as writeFile10, mkdir as mkdir9 } from "fs/promises";
12419
+ import { homedir as homedir13 } from "os";
12420
+ import { join as join21 } from "path";
11265
12421
  import { EventEmitter as EventEmitter2 } from "events";
11266
12422
  import { randomUUID } from "crypto";
11267
12423
  function usageDir2() {
11268
- const xdg = process.env.XDG_DATA_HOME || join20(homedir11(), ".local", "share");
11269
- return join20(xdg, "kimiflare");
12424
+ const xdg = process.env.XDG_DATA_HOME || join21(homedir13(), ".local", "share");
12425
+ return join21(xdg, "kimiflare");
11270
12426
  }
11271
12427
  function usagePath2() {
11272
- return join20(usageDir2(), "usage.json");
12428
+ return join21(usageDir2(), "usage.json");
11273
12429
  }
11274
12430
  function historyPath() {
11275
- return join20(usageDir2(), "history.jsonl");
12431
+ return join21(usageDir2(), "history.jsonl");
11276
12432
  }
11277
12433
  function today2() {
11278
12434
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -11283,7 +12439,7 @@ function cutoffDate(daysBack) {
11283
12439
  }
11284
12440
  async function loadLog2() {
11285
12441
  try {
11286
- const raw = await readFile13(usagePath2(), "utf8");
12442
+ const raw = await readFile16(usagePath2(), "utf8");
11287
12443
  const parsed = JSON.parse(raw);
11288
12444
  if (parsed.version === LOG_VERSION2) return parsed;
11289
12445
  } catch {
@@ -11301,7 +12457,7 @@ function withLock(fn) {
11301
12457
  }
11302
12458
  async function loadHistory() {
11303
12459
  try {
11304
- const raw = await readFile13(historyPath(), "utf8");
12460
+ const raw = await readFile16(historyPath(), "utf8");
11305
12461
  const lines = raw.split("\n").filter((l) => l.trim());
11306
12462
  const entries = [];
11307
12463
  for (const line of lines) {
@@ -11717,372 +12873,25 @@ var init_permissions = __esm({
11717
12873
  }
11718
12874
  });
11719
12875
 
11720
- // src/hooks/types.ts
11721
- var HOOK_EVENTS;
11722
- var init_types2 = __esm({
11723
- "src/hooks/types.ts"() {
11724
- "use strict";
11725
- HOOK_EVENTS = [
11726
- "PreToolUse",
11727
- "PostToolUse",
11728
- "UserPromptSubmit",
11729
- "Stop",
11730
- "PreCompact"
11731
- ];
11732
- }
11733
- });
11734
-
11735
- // src/hooks/settings.ts
11736
- import { existsSync as existsSync2, readFileSync as readFileSync3, mkdirSync as mkdirSync3, writeFileSync } from "fs";
11737
- import { homedir as homedir12 } from "os";
11738
- import { join as join21, dirname as dirname9 } from "path";
11739
- import { createHash } from "crypto";
11740
- function globalSettingsPath() {
11741
- const xdg = process.env.XDG_CONFIG_HOME || join21(homedir12(), ".config");
11742
- return join21(xdg, "kimiflare", "settings.json");
11743
- }
11744
- function projectSettingsPath(cwd) {
11745
- return join21(cwd, ".kimiflare", "settings.json");
11746
- }
11747
- function readSettingsFile(path) {
11748
- if (!existsSync2(path)) return null;
11749
- try {
11750
- const raw = readFileSync3(path, "utf8");
11751
- const parsed = JSON.parse(raw);
11752
- return parsed && typeof parsed === "object" ? parsed : null;
11753
- } catch {
11754
- return null;
11755
- }
11756
- }
11757
- function deriveHookId(event, command) {
11758
- const h = createHash("sha256").update(`${event}\0${command}`).digest("hex");
11759
- return h.slice(0, 8);
11760
- }
11761
- function normalizeHook(event, raw, source) {
11762
- if (!raw || typeof raw !== "object") return null;
11763
- const r = raw;
11764
- if (typeof r.command !== "string" || !r.command.trim()) return null;
11765
- const enabled = typeof r.enabled === "boolean" ? r.enabled : true;
11766
- const matcher = typeof r.matcher === "string" ? r.matcher : void 0;
11767
- const timeoutMs = typeof r.timeoutMs === "number" && r.timeoutMs > 0 ? r.timeoutMs : void 0;
11768
- const description = typeof r.description === "string" ? r.description : void 0;
11769
- const id = typeof r.id === "string" && r.id.length > 0 ? r.id : deriveHookId(event, r.command);
11770
- return { id, matcher, command: r.command, timeoutMs, enabled, description, source };
11771
- }
11772
- function mergeHookMaps(a, b) {
11773
- const out = {};
11774
- for (const ev of HOOK_EVENTS) {
11775
- const left = a?.[ev] ?? [];
11776
- const right = b?.[ev] ?? [];
11777
- if (left.length + right.length === 0) continue;
11778
- out[ev] = [...left, ...right];
11779
- }
11780
- return out;
11781
- }
11782
- function loadHooksSettings(cwd) {
11783
- const global = readSettingsFile(globalSettingsPath());
11784
- const project = readSettingsFile(projectSettingsPath(cwd));
11785
- const normalized = (raw, source) => {
11786
- const out = {};
11787
- for (const ev of HOOK_EVENTS) {
11788
- const list = raw?.hooks?.[ev];
11789
- if (!Array.isArray(list)) continue;
11790
- const cleaned = list.map((h) => normalizeHook(ev, h, source)).filter((h) => h !== null);
11791
- if (cleaned.length > 0) out[ev] = cleaned;
11792
- }
11793
- return out;
11794
- };
11795
- const merged = mergeHookMaps(normalized(global, "global"), normalized(project, "project"));
11796
- return { hooks: merged };
11797
- }
11798
- function appendHook(scope, cwd, event, hook) {
11799
- const path = scope === "global" ? globalSettingsPath() : projectSettingsPath(cwd);
11800
- const existing = readSettingsFile(path) ?? {};
11801
- const hooks = existing.hooks ?? {};
11802
- const list = hooks[event] ?? [];
11803
- const { source: _src, ...toWrite } = hook;
11804
- const newId = toWrite.id ?? deriveHookId(event, toWrite.command);
11805
- const byId = /* @__PURE__ */ new Map();
11806
- for (const raw of list) {
11807
- if (!raw || typeof raw !== "object") continue;
11808
- const h = raw;
11809
- if (typeof h.command !== "string") continue;
11810
- const id = h.id ?? deriveHookId(event, h.command);
11811
- if (byId.has(id)) continue;
11812
- byId.set(id, h);
11813
- }
11814
- byId.set(newId, { ...toWrite, id: newId });
11815
- hooks[event] = Array.from(byId.values()).map((h) => {
11816
- if (hook.id) return h;
11817
- const { id: _id, ...rest } = h;
11818
- return rest;
11819
- });
11820
- existing.hooks = hooks;
11821
- mkdirSync3(dirname9(path), { recursive: true });
11822
- writeFileSync(path, JSON.stringify(existing, null, 2) + "\n", "utf8");
11823
- return path;
11824
- }
11825
- function setHookEnabled(cwd, id, enabled) {
11826
- for (const [scope, path] of [
11827
- ["global", globalSettingsPath()],
11828
- ["project", projectSettingsPath(cwd)]
11829
- ]) {
11830
- const existing = readSettingsFile(path);
11831
- if (!existing?.hooks) continue;
11832
- let changed = false;
11833
- for (const ev of HOOK_EVENTS) {
11834
- const list = existing.hooks[ev];
11835
- if (!Array.isArray(list)) continue;
11836
- for (const hook of list) {
11837
- if (!hook || typeof hook !== "object") continue;
11838
- const h = hook;
11839
- const hookId = h.id ?? deriveHookId(ev, h.command);
11840
- if (hookId === id) {
11841
- h.enabled = enabled;
11842
- changed = true;
11843
- }
11844
- }
11845
- }
11846
- if (changed) {
11847
- mkdirSync3(dirname9(path), { recursive: true });
11848
- writeFileSync(path, JSON.stringify(existing, null, 2) + "\n", "utf8");
11849
- return path;
11850
- }
11851
- void scope;
11852
- }
11853
- return null;
11854
- }
11855
- var init_settings = __esm({
11856
- "src/hooks/settings.ts"() {
11857
- "use strict";
11858
- init_types2();
11859
- }
11860
- });
11861
-
11862
- // src/hooks/runner.ts
11863
- import { spawn as spawn3 } from "child_process";
11864
- function isVetoEvent(event) {
11865
- return event === "PreToolUse" || event === "UserPromptSubmit";
11866
- }
11867
- function buildHookEnv(payload) {
11868
- const env2 = {
11869
- KIMIFLARE_HOOK_EVENT: payload.event,
11870
- KIMIFLARE_HOOK_CWD: payload.cwd,
11871
- KIMIFLARE_HOOK_PAYLOAD: JSON.stringify(payload)
11872
- };
11873
- if (payload.session_id) env2.KIMIFLARE_HOOK_SESSION_ID = payload.session_id;
11874
- if (payload.event === "PreToolUse" || payload.event === "PostToolUse") {
11875
- const p = payload;
11876
- env2.KIMIFLARE_HOOK_TOOL = p.tool;
11877
- const path = p.args.path;
11878
- if (typeof path === "string") env2.KIMIFLARE_HOOK_PATH = path;
11879
- if (p.tier) env2.KIMIFLARE_HOOK_TIER = p.tier;
11880
- if (p.event === "PostToolUse") {
11881
- env2.KIMIFLARE_HOOK_RESULT_OK = String(p.result.ok);
11882
- const ec = p.result.errorCode;
11883
- if (ec) env2.KIMIFLARE_HOOK_RESULT_ERROR_CODE = ec;
11884
- }
11885
- }
11886
- if (payload.event === "UserPromptSubmit") {
11887
- const p = payload;
11888
- if (p.tier) env2.KIMIFLARE_HOOK_TIER = p.tier;
11889
- }
11890
- return env2;
11891
- }
11892
- function capStream(s) {
11893
- if (Buffer.byteLength(s, "utf8") <= STREAM_CAP_BYTES) return s;
11894
- let cut = s;
11895
- while (Buffer.byteLength(cut, "utf8") > STREAM_CAP_BYTES) {
11896
- cut = cut.slice(0, Math.floor(cut.length * 0.9));
11897
- }
11898
- return `${cut}
11899
- [\u2026truncated]`;
11900
- }
11901
- async function runHook(hook, payload, signal) {
11902
- const start = Date.now();
11903
- const timeoutMs = hook.timeoutMs ?? DEFAULT_TIMEOUT_MS;
11904
- const env2 = { ...process.env, ...buildHookEnv(payload) };
11905
- const json = JSON.stringify(payload);
11906
- const id = hook.id ?? "anonymous";
11907
- let result;
11908
- try {
11909
- result = await spawnImpl(hook.command, json, env2, payload.cwd, timeoutMs, signal);
11910
- } catch (e) {
11911
- return {
11912
- id,
11913
- exitCode: null,
11914
- stdout: "",
11915
- stderr: e instanceof Error ? e.message : String(e),
11916
- timedOut: false,
11917
- durationMs: Date.now() - start
11918
- };
11919
- }
11920
- return {
11921
- id,
11922
- exitCode: result.exitCode,
11923
- stdout: capStream(result.stdout.trim()),
11924
- stderr: capStream(result.stderr.trim()),
11925
- timedOut: result.timedOut,
11926
- durationMs: Date.now() - start
11927
- };
11928
- }
11929
- function filterHooks(hooks, toolName) {
11930
- if (!hooks || hooks.length === 0) return [];
11931
- return hooks.filter((h) => {
11932
- if (h.enabled === false) return false;
11933
- if (!h.matcher) return true;
11934
- if (!toolName) return true;
11935
- try {
11936
- return new RegExp(h.matcher).test(toolName);
11937
- } catch {
11938
- return false;
11939
- }
11940
- });
11941
- }
11942
- async function runHooks(event, hooks, payload, toolName = null, signal) {
11943
- const matched = filterHooks(hooks, toolName);
11944
- const outcomes = [];
11945
- const veto = isVetoEvent(event);
11946
- const vetoReasons = [];
11947
- let vetoed = false;
11948
- for (const hook of matched) {
11949
- const outcome = await runHook(hook, payload, signal);
11950
- outcomes.push(outcome);
11951
- if (outcome.timedOut) {
11952
- logger.warn("hook:timeout", {
11953
- event,
11954
- id: outcome.id,
11955
- timeoutMs: hook.timeoutMs ?? DEFAULT_TIMEOUT_MS
11956
- });
11957
- } else if (outcome.exitCode !== 0 && outcome.exitCode !== null) {
11958
- logger.info("hook:nonzero_exit", {
11959
- event,
11960
- id: outcome.id,
11961
- exitCode: outcome.exitCode
11962
- });
11963
- }
11964
- if (veto && (outcome.exitCode !== 0 || outcome.timedOut)) {
11965
- vetoed = true;
11966
- const reason = outcome.stdout || outcome.stderr || `hook ${outcome.id} exited ${outcome.exitCode}`;
11967
- vetoReasons.push(reason);
11968
- break;
11969
- }
11970
- }
11971
- return { outcomes, vetoed, vetoReason: vetoReasons.join("\n") };
11972
- }
11973
- var DEFAULT_TIMEOUT_MS, STREAM_CAP_BYTES, defaultSpawn, spawnImpl;
11974
- var init_runner = __esm({
11975
- "src/hooks/runner.ts"() {
11976
- "use strict";
11977
- init_logger();
11978
- DEFAULT_TIMEOUT_MS = 3e4;
11979
- STREAM_CAP_BYTES = 4 * 1024;
11980
- defaultSpawn = (command, payloadJson, env2, cwd, timeoutMs, signal) => new Promise((resolve5) => {
11981
- const child = spawn3(command, {
11982
- shell: true,
11983
- cwd,
11984
- env: env2,
11985
- stdio: ["pipe", "pipe", "pipe"]
11986
- });
11987
- let stdout = "";
11988
- let stderr = "";
11989
- let settled = false;
11990
- const finish = (exitCode, timedOut = false) => {
11991
- if (settled) return;
11992
- settled = true;
11993
- clearTimeout(timer2);
11994
- signal?.removeEventListener("abort", onAbort);
11995
- resolve5({ exitCode, stdout, stderr, timedOut });
11996
- };
11997
- const onAbort = () => {
11998
- child.kill("SIGTERM");
11999
- finish(null, true);
12000
- };
12001
- const timer2 = setTimeout(() => {
12002
- child.kill("SIGTERM");
12003
- finish(null, true);
12004
- }, timeoutMs);
12005
- signal?.addEventListener("abort", onAbort);
12006
- child.stdout.setEncoding("utf8");
12007
- child.stderr.setEncoding("utf8");
12008
- child.stdout.on("data", (d) => {
12009
- stdout += d;
12010
- });
12011
- child.stderr.on("data", (d) => {
12012
- stderr += d;
12013
- });
12014
- child.on("error", () => finish(null));
12015
- child.on("exit", (code) => finish(code));
12016
- try {
12017
- child.stdin.end(payloadJson);
12018
- } catch {
12019
- }
12020
- });
12021
- spawnImpl = defaultSpawn;
12022
- }
12023
- });
12024
-
12025
- // src/hooks/manager.ts
12026
- var manager_exports = {};
12027
- __export(manager_exports, {
12028
- HooksManager: () => HooksManager
12029
- });
12030
- var HooksManager;
12031
- var init_manager3 = __esm({
12032
- "src/hooks/manager.ts"() {
12033
- "use strict";
12034
- init_settings();
12035
- init_runner();
12036
- HooksManager = class {
12037
- cwd;
12038
- settings;
12039
- constructor(cwd) {
12040
- this.cwd = cwd;
12041
- this.settings = loadHooksSettings(cwd);
12042
- }
12043
- /** Re-read settings from disk. */
12044
- reload() {
12045
- this.settings = loadHooksSettings(this.cwd);
12046
- }
12047
- /** All hooks registered for an event, before matcher filtering. */
12048
- hooksFor(event) {
12049
- return this.settings.hooks?.[event] ?? [];
12050
- }
12051
- /** True if at least one enabled hook would match this event. Cheap
12052
- * pre-check the loop can use to avoid building payloads for events
12053
- * that have no listeners. */
12054
- hasEnabledHooks(event) {
12055
- const list = this.hooksFor(event);
12056
- return list.some((h) => h.enabled !== false);
12057
- }
12058
- /** Fire all matching hooks for `event`. Toolname is used only by
12059
- * PreToolUse / PostToolUse matchers. */
12060
- fire(event, payload, toolName = null, signal) {
12061
- return runHooks(event, this.hooksFor(event), payload, toolName, signal);
12062
- }
12063
- };
12064
- }
12065
- });
12066
-
12067
12876
  // src/sdk/session.ts
12068
- import { resolve as resolve3 } from "path";
12069
- import { homedir as homedir13 } from "os";
12877
+ import { resolve as resolve6 } from "path";
12878
+ import { homedir as homedir14 } from "os";
12070
12879
  import { join as join22 } from "path";
12071
12880
  import { existsSync as existsSync3 } from "fs";
12072
12881
  async function createAgentSession(opts2) {
12073
12882
  const config2 = await resolveSdkConfig(opts2);
12074
- const cwd = resolve3(opts2.cwd ?? process.cwd());
12883
+ const cwd = resolve6(opts2.cwd ?? process.cwd());
12075
12884
  const tools = opts2.tools ?? ALL_TOOLS;
12076
12885
  let hooks;
12077
12886
  if (opts2.enableHooks) {
12078
- const { HooksManager: HooksManager2 } = await Promise.resolve().then(() => (init_manager3(), manager_exports));
12887
+ const { HooksManager: HooksManager2 } = await Promise.resolve().then(() => (init_manager(), manager_exports));
12079
12888
  hooks = new HooksManager2(cwd);
12080
12889
  }
12081
12890
  const executor = new ToolExecutor(tools, { hooks });
12082
12891
  let memoryManager = null;
12083
12892
  const memoryEnabled = opts2.memoryEnabled ?? config2.memoryEnabled ?? false;
12084
12893
  if (memoryEnabled) {
12085
- const dbPath = config2.memoryDbPath ?? join22(homedir13(), ".local", "share", "kimiflare", "memory.db");
12894
+ const dbPath = config2.memoryDbPath ?? join22(homedir14(), ".local", "share", "kimiflare", "memory.db");
12086
12895
  memoryManager = new MemoryManager({
12087
12896
  dbPath,
12088
12897
  accountId: config2.accountId,
@@ -12113,7 +12922,7 @@ async function createAgentSession(opts2) {
12113
12922
  }
12114
12923
  let sessionFile;
12115
12924
  if (opts2.sessionId) {
12116
- const filePath = join22(sessionsDir2(), `${opts2.sessionId}.json`);
12925
+ const filePath = join22(sessionsDir(), `${opts2.sessionId}.json`);
12117
12926
  try {
12118
12927
  sessionFile = await loadSession(filePath);
12119
12928
  } catch {
@@ -12175,8 +12984,8 @@ var init_session = __esm({
12175
12984
  init_loop();
12176
12985
  init_system_prompt();
12177
12986
  init_executor();
12178
- init_manager();
12179
12987
  init_manager2();
12988
+ init_manager3();
12180
12989
  init_lsp();
12181
12990
  init_sessions();
12182
12991
  init_usage_tracker();
@@ -12272,8 +13081,8 @@ var init_session = __esm({
12272
13081
  const parts = [{ type: "text", text }];
12273
13082
  for (const img of options.images) {
12274
13083
  if ("path" in img) {
12275
- const { readFile: readFile24 } = await import("fs/promises");
12276
- const data = await readFile24(img.path, "base64");
13084
+ const { readFile: readFile25 } = await import("fs/promises");
13085
+ const data = await readFile25(img.path, "base64");
12277
13086
  const mimeType = img.path.endsWith(".png") ? "image/png" : img.path.endsWith(".jpg") || img.path.endsWith(".jpeg") ? "image/jpeg" : "image/webp";
12278
13087
  parts.push({ type: "image_url", image_url: { url: `data:${mimeType};base64,${data}` } });
12279
13088
  } else {
@@ -12465,12 +13274,12 @@ var init_session = __esm({
12465
13274
  toolName: req.tool.name,
12466
13275
  args: req.args
12467
13276
  });
12468
- const decision = await new Promise((resolve5) => {
12469
- this.permissionResolvers.set(requestId, resolve5);
13277
+ const decision = await new Promise((resolve8) => {
13278
+ this.permissionResolvers.set(requestId, resolve8);
12470
13279
  setTimeout(() => {
12471
13280
  if (this.permissionResolvers.has(requestId)) {
12472
13281
  this.permissionResolvers.delete(requestId);
12473
- resolve5("deny");
13282
+ resolve8("deny");
12474
13283
  }
12475
13284
  }, 3e5);
12476
13285
  });
@@ -12519,7 +13328,7 @@ var init_session = __esm({
12519
13328
  this.lspManager?.notifyChange(path, content);
12520
13329
  } else {
12521
13330
  void import("fs/promises").then(
12522
- ({ readFile: readFile24 }) => readFile24(path, "utf8").then((c) => this.lspManager?.notifyChange(path, c)).catch(() => {
13331
+ ({ readFile: readFile25 }) => readFile25(path, "utf8").then((c) => this.lspManager?.notifyChange(path, c)).catch(() => {
12523
13332
  })
12524
13333
  ).catch(() => {
12525
13334
  });
@@ -12801,20 +13610,20 @@ var init_repo_info = __esm({
12801
13610
  });
12802
13611
 
12803
13612
  // src/agent/supervisor.ts
12804
- import { readdir as readdir5, readFile as readFile14, stat as stat5 } from "fs/promises";
13613
+ import { readdir as readdir5, readFile as readFile17, stat as stat5 } from "fs/promises";
12805
13614
  import { createHash as createHash2 } from "crypto";
12806
- import { resolve as resolve4, join as join23 } from "path";
13615
+ import { resolve as resolve7, join as join23 } from "path";
12807
13616
  async function preReadFilesForWorkers(files, repoRoot, maxChars = DEFAULT_PRE_READ_MAX_CHARS) {
12808
13617
  const results = [];
12809
13618
  const filesRead = [];
12810
13619
  let chars = 0;
12811
13620
  for (const file of files) {
12812
13621
  if (chars >= maxChars) break;
12813
- const path = resolve4(repoRoot, file);
13622
+ const path = resolve7(repoRoot, file);
12814
13623
  try {
12815
13624
  const s = await stat5(path);
12816
13625
  if (!s.isFile()) continue;
12817
- const raw = await readFile14(path, "utf8");
13626
+ const raw = await readFile17(path, "utf8");
12818
13627
  const remaining = maxChars - chars;
12819
13628
  const content = raw.length > remaining ? raw.slice(0, remaining) + "\n\u2026 (truncated)" : raw;
12820
13629
  results.push(`--- ${file} ---
@@ -13813,8 +14622,8 @@ async function runEmitMode(opts2) {
13813
14622
  async function nextFollowUp() {
13814
14623
  if (followUpQueue.length > 0) return followUpQueue.shift();
13815
14624
  if (stdinClosed) return null;
13816
- return new Promise((resolve5) => {
13817
- followUpResolver = resolve5;
14625
+ return new Promise((resolve8) => {
14626
+ followUpResolver = resolve8;
13818
14627
  });
13819
14628
  }
13820
14629
  const cwd = process.cwd();
@@ -13996,8 +14805,8 @@ ${conflicts.join("\n")}`,
13996
14805
  return "allow";
13997
14806
  }
13998
14807
  if (opts2.multiTurn) {
13999
- const choice = await new Promise((resolve5) => {
14000
- pendingPermissions.set(reqId, resolve5);
14808
+ const choice = await new Promise((resolve8) => {
14809
+ pendingPermissions.set(reqId, resolve8);
14001
14810
  });
14002
14811
  if (choice === "allow" || choice === "allow_session") {
14003
14812
  emit("PermissionGranted", { request_id: reqId });
@@ -14098,9 +14907,246 @@ var init_emit_mode = __esm({
14098
14907
  }
14099
14908
  });
14100
14909
 
14910
+ // src/attach-mode.ts
14911
+ var attach_mode_exports = {};
14912
+ __export(attach_mode_exports, {
14913
+ runAttachMode: () => runAttachMode
14914
+ });
14915
+ async function runAttachMode(opts2) {
14916
+ const url = opts2.attachUrl.replace(/\/$/, "");
14917
+ const format = opts2.format ?? "text";
14918
+ const endpoint = opts2.sessionId ? `${url}/session/${opts2.sessionId}/prompt` : `${url}/prompt`;
14919
+ const body = {
14920
+ prompt: opts2.prompt,
14921
+ allowAll: opts2.allowAll ?? false
14922
+ };
14923
+ if (opts2.model) body.model = opts2.model;
14924
+ if (opts2.files) body.files = opts2.files;
14925
+ const response = await fetch(endpoint, {
14926
+ method: "POST",
14927
+ headers: { "Content-Type": "application/json" },
14928
+ body: JSON.stringify(body)
14929
+ });
14930
+ if (!response.ok) {
14931
+ const text = await response.text().catch(() => "unknown error");
14932
+ console.error(`kimiflare attach: ${response.status} ${text}`);
14933
+ process.exit(1);
14934
+ }
14935
+ const result = await response.json();
14936
+ const sessionId = result.sessionId;
14937
+ if (format === "json") {
14938
+ await streamSseJson(url, sessionId);
14939
+ } else {
14940
+ await streamSse(url, sessionId, format);
14941
+ }
14942
+ }
14943
+ async function streamSse(url, sessionId, format) {
14944
+ const eventSource = new EventSource(`${url}/event`);
14945
+ let done = false;
14946
+ return new Promise((resolve8, reject) => {
14947
+ eventSource.onmessage = (event) => {
14948
+ try {
14949
+ const data = JSON.parse(event.data);
14950
+ if (data.event === "server.connected") return;
14951
+ switch (data.event) {
14952
+ case "assistant.delta":
14953
+ if (format === "text") {
14954
+ process.stdout.write(data.delta);
14955
+ } else if (format === "stream-json") {
14956
+ process.stdout.write(JSON.stringify({ event: "text_delta", delta: data.delta }) + "\n");
14957
+ }
14958
+ break;
14959
+ case "tool.call":
14960
+ if (format === "text") {
14961
+ process.stderr.write(`\x1B[2m[tool ${data.name}(${JSON.stringify(data.arguments)})]\x1B[0m
14962
+ `);
14963
+ } else if (format === "stream-json") {
14964
+ process.stdout.write(JSON.stringify({ event: "tool_call", id: data.id, name: data.name, arguments: data.arguments }) + "\n");
14965
+ }
14966
+ break;
14967
+ case "tool.result":
14968
+ if (format === "text") {
14969
+ const snippet = data.content.length > 400 ? data.content.slice(0, 400) + "..." : data.content;
14970
+ process.stderr.write(`\x1B[2m[result: ${snippet.replace(/\n/g, " \u23CE ")}]\x1B[0m
14971
+ `);
14972
+ } else if (format === "stream-json") {
14973
+ process.stdout.write(JSON.stringify({ event: "tool_result", toolCallId: data.toolCallId, name: data.name, content: data.content, ok: data.ok }) + "\n");
14974
+ }
14975
+ break;
14976
+ case "usage.update":
14977
+ if (format === "stream-json") {
14978
+ process.stdout.write(JSON.stringify({ event: "usage", promptTokens: data.promptTokens, completionTokens: data.completionTokens, totalTokens: data.totalTokens }) + "\n");
14979
+ }
14980
+ break;
14981
+ case "warning":
14982
+ if (format === "text") {
14983
+ process.stderr.write(`\x1B[33mkimiflare: ${data.message}\x1B[0m
14984
+ `);
14985
+ } else if (format === "stream-json") {
14986
+ process.stdout.write(JSON.stringify({ event: "warning", message: data.message }) + "\n");
14987
+ }
14988
+ break;
14989
+ case "session.completed":
14990
+ done = true;
14991
+ eventSource.close();
14992
+ if (format === "text") process.stdout.write("\n");
14993
+ resolve8(void 0);
14994
+ break;
14995
+ case "error":
14996
+ done = true;
14997
+ eventSource.close();
14998
+ if (format === "text") {
14999
+ process.stderr.write(`
15000
+ \x1B[31mError: ${data.message}\x1B[0m
15001
+ `);
15002
+ } else if (format === "stream-json") {
15003
+ process.stdout.write(JSON.stringify({ event: "error", message: data.message }) + "\n");
15004
+ }
15005
+ process.exitCode = 1;
15006
+ resolve8(void 0);
15007
+ break;
15008
+ }
15009
+ } catch {
15010
+ }
15011
+ };
15012
+ eventSource.onerror = () => {
15013
+ if (!done) {
15014
+ eventSource.close();
15015
+ reject(new Error("SSE connection failed"));
15016
+ }
15017
+ };
15018
+ setTimeout(() => {
15019
+ if (!done) {
15020
+ eventSource.close();
15021
+ reject(new Error("SSE stream timed out after 10 minutes"));
15022
+ }
15023
+ }, 6e5);
15024
+ });
15025
+ }
15026
+ async function streamSseJson(url, sessionId) {
15027
+ const text = [];
15028
+ const toolCalls = [];
15029
+ const toolResults = [];
15030
+ let usage;
15031
+ const eventSource = new EventSource(`${url}/event`);
15032
+ return new Promise((resolve8, reject) => {
15033
+ eventSource.onmessage = (event) => {
15034
+ try {
15035
+ const data = JSON.parse(event.data);
15036
+ if (data.event === "server.connected") return;
15037
+ switch (data.event) {
15038
+ case "assistant.delta":
15039
+ text.push(data.delta);
15040
+ break;
15041
+ case "tool.call":
15042
+ toolCalls.push({ id: data.id, name: data.name, arguments: data.arguments });
15043
+ break;
15044
+ case "tool.result":
15045
+ toolResults.push({ toolCallId: data.toolCallId, name: data.name, content: data.content, ok: data.ok });
15046
+ break;
15047
+ case "usage.update":
15048
+ usage = {
15049
+ promptTokens: data.promptTokens,
15050
+ completionTokens: data.completionTokens,
15051
+ totalTokens: data.totalTokens
15052
+ };
15053
+ break;
15054
+ case "session.completed":
15055
+ eventSource.close();
15056
+ process.stdout.write(
15057
+ JSON.stringify(
15058
+ {
15059
+ text: text.join(""),
15060
+ toolCalls,
15061
+ toolResults,
15062
+ usage,
15063
+ sessionId
15064
+ },
15065
+ null,
15066
+ 2
15067
+ ) + "\n"
15068
+ );
15069
+ resolve8(void 0);
15070
+ break;
15071
+ case "error":
15072
+ eventSource.close();
15073
+ process.stdout.write(
15074
+ JSON.stringify({ error: data.message, sessionId }, null, 2) + "\n"
15075
+ );
15076
+ process.exitCode = 1;
15077
+ resolve8(void 0);
15078
+ break;
15079
+ }
15080
+ } catch {
15081
+ }
15082
+ };
15083
+ eventSource.onerror = () => {
15084
+ eventSource.close();
15085
+ reject(new Error("SSE connection failed"));
15086
+ };
15087
+ setTimeout(() => {
15088
+ eventSource.close();
15089
+ reject(new Error("SSE stream timed out after 10 minutes"));
15090
+ }, 6e5);
15091
+ });
15092
+ }
15093
+ var EventSource;
15094
+ var init_attach_mode = __esm({
15095
+ "src/attach-mode.ts"() {
15096
+ "use strict";
15097
+ EventSource = class {
15098
+ url;
15099
+ controller;
15100
+ onmessage = null;
15101
+ onerror = null;
15102
+ constructor(url) {
15103
+ this.url = url;
15104
+ this.controller = new AbortController();
15105
+ this.start();
15106
+ }
15107
+ async start() {
15108
+ try {
15109
+ const response = await fetch(this.url, {
15110
+ signal: this.controller.signal,
15111
+ headers: { Accept: "text/event-stream" }
15112
+ });
15113
+ if (!response.ok || !response.body) {
15114
+ this.onerror?.();
15115
+ return;
15116
+ }
15117
+ const reader = response.body.getReader();
15118
+ const decoder = new TextDecoder();
15119
+ let buffer = "";
15120
+ while (true) {
15121
+ const { done, value } = await reader.read();
15122
+ if (done) break;
15123
+ buffer += decoder.decode(value, { stream: true });
15124
+ const lines = buffer.split("\n");
15125
+ buffer = lines.pop() ?? "";
15126
+ let currentData = "";
15127
+ for (const line of lines) {
15128
+ if (line.startsWith("data: ")) {
15129
+ currentData = line.slice(6);
15130
+ } else if (line === "" && currentData) {
15131
+ this.onmessage?.({ data: currentData });
15132
+ currentData = "";
15133
+ }
15134
+ }
15135
+ }
15136
+ } catch {
15137
+ this.onerror?.();
15138
+ }
15139
+ }
15140
+ close() {
15141
+ this.controller.abort();
15142
+ }
15143
+ };
15144
+ }
15145
+ });
15146
+
14101
15147
  // src/remote/deploy-commute.ts
14102
15148
  import { spawn as spawn4 } from "child_process";
14103
- import { mkdtemp, readFile as readFile15, writeFile as writeFile11, rm } from "fs/promises";
15149
+ import { mkdtemp, readFile as readFile18, writeFile as writeFile11, rm } from "fs/promises";
14104
15150
  import { tmpdir as tmpdir3 } from "os";
14105
15151
  import { join as join24 } from "path";
14106
15152
  import { randomBytes as randomBytes2 } from "crypto";
@@ -14115,56 +15161,56 @@ async function cfApiFetch(accountId, apiToken, path, init) {
14115
15161
  ...init?.headers
14116
15162
  }
14117
15163
  });
14118
- const json = await res.json();
14119
- return json;
15164
+ const json2 = await res.json();
15165
+ return json2;
14120
15166
  }
14121
15167
  async function listDurableObjectNamespaces(accountId, apiToken) {
14122
- const json = await cfApiFetch(
15168
+ const json2 = await cfApiFetch(
14123
15169
  accountId,
14124
15170
  apiToken,
14125
15171
  "/workers/durable_objects/namespaces"
14126
15172
  );
14127
- if (!json.success || !json.result) {
15173
+ if (!json2.success || !json2.result) {
14128
15174
  throw new Error(
14129
- json.errors?.map((e) => e.message).join(", ") ?? "Failed to list DO namespaces"
15175
+ json2.errors?.map((e) => e.message).join(", ") ?? "Failed to list DO namespaces"
14130
15176
  );
14131
15177
  }
14132
- return json.result;
15178
+ return json2.result;
14133
15179
  }
14134
15180
  async function deleteDurableObjectNamespace(accountId, apiToken, namespaceId) {
14135
- const json = await cfApiFetch(
15181
+ const json2 = await cfApiFetch(
14136
15182
  accountId,
14137
15183
  apiToken,
14138
15184
  `/workers/durable_objects/namespaces/${encodeURIComponent(namespaceId)}`,
14139
15185
  { method: "DELETE" }
14140
15186
  );
14141
- if (!json.success) {
15187
+ if (!json2.success) {
14142
15188
  throw new Error(
14143
- json.errors?.map((e) => e.message).join(", ") ?? "Failed to delete DO namespace"
15189
+ json2.errors?.map((e) => e.message).join(", ") ?? "Failed to delete DO namespace"
14144
15190
  );
14145
15191
  }
14146
15192
  }
14147
15193
  async function listContainerApplications(accountId, apiToken) {
14148
- const json = await cfApiFetch(
15194
+ const json2 = await cfApiFetch(
14149
15195
  accountId,
14150
15196
  apiToken,
14151
15197
  "/containers/applications"
14152
15198
  );
14153
- if (!json.success || !json.result) {
15199
+ if (!json2.success || !json2.result) {
14154
15200
  return [];
14155
15201
  }
14156
- return json.result;
15202
+ return json2.result;
14157
15203
  }
14158
15204
  async function deleteContainerApplication(accountId, apiToken, appId) {
14159
- const json = await cfApiFetch(
15205
+ const json2 = await cfApiFetch(
14160
15206
  accountId,
14161
15207
  apiToken,
14162
15208
  `/containers/applications/${encodeURIComponent(appId)}`,
14163
15209
  { method: "DELETE" }
14164
15210
  );
14165
- if (!json.success) {
15211
+ if (!json2.success) {
14166
15212
  throw new Error(
14167
- json.errors?.map((e) => e.message).join(", ") ?? "Failed to delete container application"
15213
+ json2.errors?.map((e) => e.message).join(", ") ?? "Failed to delete container application"
14168
15214
  );
14169
15215
  }
14170
15216
  }
@@ -14172,7 +15218,7 @@ function generateSecret2() {
14172
15218
  return randomBytes2(32).toString("hex");
14173
15219
  }
14174
15220
  function runCmd(cmd, args, opts2 = {}) {
14175
- return new Promise((resolve5) => {
15221
+ return new Promise((resolve8) => {
14176
15222
  const child = spawn4(cmd, args, {
14177
15223
  cwd: opts2.cwd,
14178
15224
  env: { ...process.env, ...opts2.env ?? {} },
@@ -14193,11 +15239,11 @@ function runCmd(cmd, args, opts2 = {}) {
14193
15239
  const timer2 = opts2.timeoutMs ? setTimeout(() => child.kill("SIGKILL"), opts2.timeoutMs) : null;
14194
15240
  child.on("close", (code) => {
14195
15241
  if (timer2) clearTimeout(timer2);
14196
- resolve5({ stdout, stderr, code: code ?? -1 });
15242
+ resolve8({ stdout, stderr, code: code ?? -1 });
14197
15243
  });
14198
15244
  child.on("error", (err) => {
14199
15245
  if (timer2) clearTimeout(timer2);
14200
- resolve5({ stdout, stderr: stderr + String(err), code: -1 });
15246
+ resolve8({ stdout, stderr: stderr + String(err), code: -1 });
14201
15247
  });
14202
15248
  });
14203
15249
  }
@@ -14433,7 +15479,7 @@ ${(install.stderr || install.stdout).slice(-1200).trim()}`,
14433
15479
  ok: true
14434
15480
  };
14435
15481
  yield { message: "Patching wrangler.toml\u2026" };
14436
- let toml = await readFile15(wranglerToml, "utf8");
15482
+ let toml = await readFile18(wranglerToml, "utf8");
14437
15483
  toml = toml.replace(/^name\s*=\s*"[^"]+"/m, `name = "${workerName}"`);
14438
15484
  toml = toml.replace(
14439
15485
  /(\[\[kv_namespaces\]\][\s\S]*?binding\s*=\s*"OAUTH_KV"[\s\S]*?id\s*=\s*")[^"]+(")/,
@@ -14659,45 +15705,6 @@ var init_deploy_commute = __esm({
14659
15705
  }
14660
15706
  });
14661
15707
 
14662
- // src/util/image.ts
14663
- import { readFile as readFile16 } from "fs/promises";
14664
- import { basename as basename3 } from "path";
14665
- async function encodeImageFile(filePath) {
14666
- const buf = await readFile16(filePath);
14667
- if (buf.byteLength > MAX_IMAGE_BYTES) {
14668
- throw new Error(
14669
- `image too large (${(buf.byteLength / 1024 / 1024).toFixed(1)} MB); max is ${MAX_IMAGE_BYTES / 1024 / 1024} MB`
14670
- );
14671
- }
14672
- const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
14673
- const mime = EXT_TO_MIME[ext] ?? "image/jpeg";
14674
- const b64 = buf.toString("base64");
14675
- return {
14676
- filename: basename3(filePath),
14677
- mime,
14678
- dataUrl: `data:${mime};base64,${b64}`
14679
- };
14680
- }
14681
- function isImagePath(path) {
14682
- const ext = path.slice(path.lastIndexOf(".")).toLowerCase();
14683
- return ext in EXT_TO_MIME;
14684
- }
14685
- var MAX_IMAGE_BYTES, EXT_TO_MIME;
14686
- var init_image = __esm({
14687
- "src/util/image.ts"() {
14688
- "use strict";
14689
- MAX_IMAGE_BYTES = 5 * 1024 * 1024;
14690
- EXT_TO_MIME = {
14691
- ".png": "image/png",
14692
- ".jpg": "image/jpeg",
14693
- ".jpeg": "image/jpeg",
14694
- ".gif": "image/gif",
14695
- ".webp": "image/webp",
14696
- ".bmp": "image/bmp"
14697
- };
14698
- }
14699
- });
14700
-
14701
15708
  // src/ui/app-helpers.ts
14702
15709
  var app_helpers_exports = {};
14703
15710
  __export(app_helpers_exports, {
@@ -15342,7 +16349,7 @@ var init_frontmatter = __esm({
15342
16349
  });
15343
16350
 
15344
16351
  // src/skills/loader.ts
15345
- import { readFile as readFile17, readdir as readdir6, stat as stat6 } from "fs/promises";
16352
+ import { readFile as readFile19, readdir as readdir6, stat as stat6 } from "fs/promises";
15346
16353
  import { join as join26, extname } from "path";
15347
16354
  function normalizeManifest(raw, filePath) {
15348
16355
  const name = typeof raw.name === "string" ? raw.name : "";
@@ -15357,7 +16364,7 @@ function normalizeManifest(raw, filePath) {
15357
16364
  return { name, description, match, scope, priority, enabled };
15358
16365
  }
15359
16366
  async function loadSkillFile(filePath) {
15360
- const raw = await readFile17(filePath, "utf-8");
16367
+ const raw = await readFile19(filePath, "utf-8");
15361
16368
  const parsed = parseFrontmatter(raw);
15362
16369
  const manifest = normalizeManifest(parsed.data, filePath);
15363
16370
  const body = parsed.content.trim();
@@ -15407,7 +16414,7 @@ var init_loader = __esm({
15407
16414
  });
15408
16415
 
15409
16416
  // src/skills/discovery.ts
15410
- import { readdir as readdir7, stat as stat7, readFile as readFile18 } from "fs/promises";
16417
+ import { readdir as readdir7, stat as stat7, readFile as readFile20 } from "fs/promises";
15411
16418
  import { join as join27, extname as extname2 } from "path";
15412
16419
  async function dirExists(path) {
15413
16420
  try {
@@ -15445,7 +16452,7 @@ async function discoverSkills(cwd) {
15445
16452
  return ordered;
15446
16453
  }
15447
16454
  async function readSkillFile(filePath) {
15448
- const bytes = await readFile18(filePath);
16455
+ const bytes = await readFile20(filePath);
15449
16456
  return { bytes, text: bytes.toString("utf-8") };
15450
16457
  }
15451
16458
  var SKILL_EXTENSIONS;
@@ -16677,8 +17684,8 @@ var init_one_dark = __esm({
16677
17684
  });
16678
17685
 
16679
17686
  // src/ui/theme.ts
16680
- function normalizeTheme(json) {
16681
- const obj = json;
17687
+ function normalizeTheme(json2) {
17688
+ const obj = json2;
16682
17689
  const palette = obj.palette;
16683
17690
  const normalizeDim = (v) => {
16684
17691
  if (v === void 0) return void 0;
@@ -16860,8 +17867,8 @@ async function listGateways(accountId, apiToken) {
16860
17867
  if (!res.ok) {
16861
17868
  throw new AiGatewayError(await parseCloudflareError(res));
16862
17869
  }
16863
- const json = await res.json();
16864
- return Array.isArray(json.result) ? json.result : [];
17870
+ const json2 = await res.json();
17871
+ return Array.isArray(json2.result) ? json2.result : [];
16865
17872
  }
16866
17873
  async function createGateway(accountId, apiToken, id) {
16867
17874
  const res = await doFetch(baseUrl(accountId), {
@@ -16887,15 +17894,15 @@ async function createGateway(accountId, apiToken, id) {
16887
17894
  if (!res.ok) {
16888
17895
  throw new AiGatewayError(await parseCloudflareError(res));
16889
17896
  }
16890
- const json = await res.json();
16891
- if (!json.result) {
17897
+ const json2 = await res.json();
17898
+ if (!json2.result) {
16892
17899
  throw new AiGatewayError({
16893
17900
  kind: "other",
16894
17901
  status: res.status,
16895
17902
  message: "Cloudflare returned no gateway result"
16896
17903
  });
16897
17904
  }
16898
- return json.result;
17905
+ return json2.result;
16899
17906
  }
16900
17907
  async function enableGatewayAuth(accountId, apiToken, gatewayId) {
16901
17908
  const url = `${baseUrl(accountId)}/${encodeURIComponent(gatewayId)}`;
@@ -16952,7 +17959,7 @@ var init_ai_gateway_api = __esm({
16952
17959
  });
16953
17960
 
16954
17961
  // src/skills/manager.ts
16955
- import { mkdir as mkdir10, writeFile as writeFile12, unlink as unlink2, readFile as readFile19 } from "fs/promises";
17962
+ import { mkdir as mkdir10, writeFile as writeFile12, unlink as unlink2, readFile as readFile21 } from "fs/promises";
16956
17963
  import { join as join28 } from "path";
16957
17964
  function getSkillDirs(cwd) {
16958
17965
  return {
@@ -17007,7 +18014,7 @@ async function setSkillEnabled(name, enabled, cwd) {
17007
18014
  const all = await listAllSkills(cwd);
17008
18015
  const skill = all.project.find((s) => s.name === name) ?? all.global.find((s) => s.name === name);
17009
18016
  if (!skill) throw new Error(`skill "${name}" not found`);
17010
- const raw = await readFile19(skill.filePath, "utf-8");
18017
+ const raw = await readFile21(skill.filePath, "utf-8");
17011
18018
  const parsed = parseFrontmatter(raw);
17012
18019
  parsed.data.enabled = enabled;
17013
18020
  const yaml = Object.entries(parsed.data).map(([k, v]) => {
@@ -17091,13 +18098,13 @@ var init_frontmatter2 = __esm({
17091
18098
 
17092
18099
  // src/commands/loader.ts
17093
18100
  import { open, realpath as realpath2 } from "fs/promises";
17094
- import { homedir as homedir14 } from "os";
17095
- import { join as join29, relative as relative5, sep as sep2 } from "path";
18101
+ import { homedir as homedir15 } from "os";
18102
+ import { join as join29, relative as relative6, sep as sep2 } from "path";
17096
18103
  function projectCommandsDir(cwd = process.cwd()) {
17097
18104
  return join29(cwd, ".kimiflare", "commands");
17098
18105
  }
17099
18106
  function globalCommandsDir() {
17100
- const xdg = process.env.XDG_CONFIG_HOME || join29(homedir14(), ".config");
18107
+ const xdg = process.env.XDG_CONFIG_HOME || join29(homedir15(), ".config");
17101
18108
  return join29(xdg, "kimiflare", "commands");
17102
18109
  }
17103
18110
  async function loadCustomCommands(cwd = process.cwd()) {
@@ -17152,7 +18159,7 @@ async function resolveSafeDir(dir, source, cwd, warnings) {
17152
18159
  } catch {
17153
18160
  return null;
17154
18161
  }
17155
- const rel = relative5(realCwd, realDir);
18162
+ const rel = relative6(realCwd, realDir);
17156
18163
  if (rel !== "" && isPathOutside(rel)) {
17157
18164
  warnings.push(`commands dir ${dir} escapes workspace via symlink \u2014 skipped`);
17158
18165
  return null;
@@ -17227,7 +18234,7 @@ async function loadOne(file, rootDir, source, warnings) {
17227
18234
  return cmd;
17228
18235
  }
17229
18236
  function filenameToCommandName(file, rootDir) {
17230
- const rel = relative5(rootDir, file);
18237
+ const rel = relative6(rootDir, file);
17231
18238
  if (!rel || isPathOutside(rel)) return null;
17232
18239
  const noExt = rel.replace(/\.md$/i, "");
17233
18240
  const parts = noExt.split(sep2).filter((p) => p.length > 0);
@@ -17909,9 +18916,9 @@ import { execSync as execSync6, spawn as spawn6 } from "child_process";
17909
18916
  import { appendFileSync, mkdirSync as mkdirSync4, openSync } from "fs";
17910
18917
  import { unlink as unlink4 } from "fs/promises";
17911
18918
  import { join as join31 } from "path";
17912
- import { homedir as homedir15, platform as platform6 } from "os";
18919
+ import { homedir as homedir16, platform as platform6 } from "os";
17913
18920
  import { randomUUID as randomUUID2 } from "crypto";
17914
- import { readFile as readFile20 } from "fs/promises";
18921
+ import { readFile as readFile22 } from "fs/promises";
17915
18922
  import QRCode from "qrcode";
17916
18923
  function kimiLog(payload) {
17917
18924
  if (!KIMI_LOG_PATH) return;
@@ -17949,7 +18956,7 @@ function gatewayFromOpts2(opts2) {
17949
18956
  }
17950
18957
  async function runUiMode(opts2) {
17951
18958
  await loadCamouflage();
17952
- const xdgConfig = process.env.XDG_CONFIG_HOME || join31(homedir15(), ".config");
18959
+ const xdgConfig = process.env.XDG_CONFIG_HOME || join31(homedir16(), ".config");
17953
18960
  const camoDbDir = join31(xdgConfig, "kimiflare");
17954
18961
  try {
17955
18962
  mkdirSync4(camoDbDir, { recursive: true });
@@ -18812,8 +19819,8 @@ Executor opened PR: ${prUrl}` : plan });
18812
19819
  cam.send("PermissionGranted", { request_id: reqId });
18813
19820
  return "allow";
18814
19821
  }
18815
- const choice = await new Promise((resolve5) => {
18816
- pendingPermissions.set(reqId, resolve5);
19822
+ const choice = await new Promise((resolve8) => {
19823
+ pendingPermissions.set(reqId, resolve8);
18817
19824
  });
18818
19825
  cam.send(
18819
19826
  choice === "deny" ? "PermissionDenied" : "PermissionGranted",
@@ -18947,12 +19954,12 @@ Executor opened PR: ${prUrl}` : plan });
18947
19954
  async function nextFollowUp() {
18948
19955
  if (followUpQueue.length > 0) return followUpQueue.shift();
18949
19956
  if (aborted2) return null;
18950
- return new Promise((resolve5) => {
18951
- followUpResolver = resolve5;
19957
+ return new Promise((resolve8) => {
19958
+ followUpResolver = resolve8;
18952
19959
  });
18953
19960
  }
18954
19961
  async function openInboxModal() {
18955
- const URL2 = "https://hello.kimiflare.com";
19962
+ const URL3 = "https://hello.kimiflare.com";
18956
19963
  const f = await form(cam, {
18957
19964
  id: `inbox-${Date.now()}`,
18958
19965
  title: "/inbox \xB7 check for a voice reply",
@@ -18970,7 +19977,7 @@ Executor opened PR: ${prUrl}` : plan });
18970
19977
  let data;
18971
19978
  try {
18972
19979
  const res = await fetch(
18973
- `${URL2}/inbox/check?u=${encodeURIComponent(handle)}&s=${encodeURIComponent(secret)}`
19980
+ `${URL3}/inbox/check?u=${encodeURIComponent(handle)}&s=${encodeURIComponent(secret)}`
18974
19981
  );
18975
19982
  if (!res.ok) throw new Error(`server returned ${res.status}`);
18976
19983
  data = await res.json();
@@ -18995,7 +20002,7 @@ Executor opened PR: ${prUrl}` : plan });
18995
20002
  });
18996
20003
  if (pick3.cancelled || !pick3.value) return;
18997
20004
  openBrowser2(
18998
- `${URL2}/inbox?u=${encodeURIComponent(handle)}&s=${encodeURIComponent(secret)}&m=${encodeURIComponent(pick3.value)}`
20005
+ `${URL3}/inbox?u=${encodeURIComponent(handle)}&s=${encodeURIComponent(secret)}&m=${encodeURIComponent(pick3.value)}`
18999
20006
  );
19000
20007
  cam.send("ShowToast", { text: "opened in browser", kind: "success", ttl_ms: 1500 });
19001
20008
  }
@@ -20311,7 +21318,7 @@ Executor opened PR: ${prUrl}` : plan });
20311
21318
  try {
20312
21319
  const result2 = await createSkill({ name: name2, description: description || void 0, scope, cwd: process.cwd() });
20313
21320
  if (content) {
20314
- const { writeFile: writeFile15 } = await import("fs/promises");
21321
+ const { writeFile: writeFile14 } = await import("fs/promises");
20315
21322
  const yamlLines = [
20316
21323
  `name: ${name2}`,
20317
21324
  "enabled: true",
@@ -20326,7 +21333,7 @@ ${yamlLines.join("\n")}
20326
21333
 
20327
21334
  ${content}
20328
21335
  `;
20329
- await writeFile15(result2.filepath, fileContent, "utf8");
21336
+ await writeFile14(result2.filepath, fileContent, "utf8");
20330
21337
  }
20331
21338
  cam.send("ShowToast", { text: `created skill '${name2}' \u2192 ${result2.filepath}`, kind: "success", ttl_ms: 3e3 });
20332
21339
  } catch (err) {
@@ -20369,7 +21376,7 @@ ${content}
20369
21376
  }
20370
21377
  let currentContent;
20371
21378
  try {
20372
- currentContent = await readFile20(filepath, "utf-8");
21379
+ currentContent = await readFile22(filepath, "utf-8");
20373
21380
  } catch (err) {
20374
21381
  cam.send("ShowToast", { text: `failed to read skill: ${err instanceof Error ? err.message : String(err)}`, kind: "error", ttl_ms: 3e3 });
20375
21382
  return true;
@@ -20389,8 +21396,8 @@ ${content}
20389
21396
  return true;
20390
21397
  }
20391
21398
  try {
20392
- const { writeFile: writeFile15 } = await import("fs/promises");
20393
- await writeFile15(filepath, newContent, "utf8");
21399
+ const { writeFile: writeFile14 } = await import("fs/promises");
21400
+ await writeFile14(filepath, newContent, "utf8");
20394
21401
  cam.send("ShowToast", { text: `updated skill '${name2}' \u2192 ${filepath}`, kind: "success", ttl_ms: 3e3 });
20395
21402
  } catch (err) {
20396
21403
  cam.send("ShowToast", { text: `failed to save skill: ${err instanceof Error ? err.message : String(err)}`, kind: "error", ttl_ms: 3e3 });
@@ -20873,11 +21880,11 @@ var init_ui_mode = __esm({
20873
21880
  init_glob();
20874
21881
  init_errors();
20875
21882
  init_builtins();
20876
- init_manager();
21883
+ init_manager2();
20877
21884
  init_db2();
20878
21885
  init_manager4();
20879
- init_manager2();
20880
21886
  init_manager3();
21887
+ init_manager();
20881
21888
  init_lsp();
20882
21889
  init_skills();
20883
21890
  init_storage_limits();
@@ -20900,7 +21907,7 @@ var init_ui_mode = __esm({
20900
21907
  init_worker_client();
20901
21908
  init_builtins();
20902
21909
  init_settings();
20903
- init_types2();
21910
+ init_types();
20904
21911
  init_recommended();
20905
21912
  init_context_generator();
20906
21913
  init_mode();
@@ -23414,19 +24421,19 @@ function usePermissionController(getMode, onPlanModeBlocked) {
23414
24421
  const onPlanModeBlockedRef = useRef2(onPlanModeBlocked);
23415
24422
  onPlanModeBlockedRef.current = onPlanModeBlocked;
23416
24423
  const askPermission = useCallback2(
23417
- (req, askOpts) => new Promise((resolve5) => {
24424
+ (req, askOpts) => new Promise((resolve8) => {
23418
24425
  const outcome = decidePermission(req, getModeRef.current(), askOpts);
23419
24426
  if (outcome.kind === "resolve") {
23420
- resolve5(outcome.decision);
24427
+ resolve8(outcome.decision);
23421
24428
  return;
23422
24429
  }
23423
24430
  if (outcome.kind === "plan_blocked") {
23424
24431
  onPlanModeBlockedRef.current(outcome.toolName);
23425
- resolve5(AUTO_DENY);
24432
+ resolve8(AUTO_DENY);
23426
24433
  return;
23427
24434
  }
23428
- resolveRef.current = resolve5;
23429
- setPending({ tool: req.tool, args: req.args, resolve: resolve5 });
24435
+ resolveRef.current = resolve8;
24436
+ setPending({ tool: req.tool, args: req.args, resolve: resolve8 });
23430
24437
  }),
23431
24438
  []
23432
24439
  );
@@ -25431,7 +26438,7 @@ var init_welcome = __esm({
25431
26438
  // src/commands/renderer.ts
25432
26439
  import { exec } from "child_process";
25433
26440
  import { open as open2, realpath as realpath3 } from "fs/promises";
25434
- import { isAbsolute as isAbsolute2, relative as relative7, resolve as resolvePathJoin, basename as pathBasename } from "path";
26441
+ import { isAbsolute as isAbsolute2, relative as relative8, resolve as resolvePathJoin, basename as pathBasename } from "path";
25435
26442
  import { promisify as promisify2 } from "util";
25436
26443
  function tokenizeArgs(s) {
25437
26444
  return [...s.matchAll(ARG_TOKEN_RE)].map((match) => {
@@ -25536,7 +26543,7 @@ async function replaceFiles(prompt, warnings, cmd, cwd, maxFileBytes) {
25536
26543
  return "";
25537
26544
  }
25538
26545
  const resolved = resolvePathJoin(cwd, rawPath);
25539
- if (isPathOutside(relative7(cwd, resolved))) {
26546
+ if (isPathOutside(relative8(cwd, resolved))) {
25540
26547
  warnings.push(`file inclusion skipped: @${rawPath} \u2014 outside workspace`);
25541
26548
  return "";
25542
26549
  }
@@ -25547,7 +26554,7 @@ async function replaceFiles(prompt, warnings, cmd, cwd, maxFileBytes) {
25547
26554
  warnings.push(`file inclusion failed: @${rawPath} \u2014 ${message(error)}`);
25548
26555
  return "";
25549
26556
  }
25550
- if (isPathOutside(relative7(realCwd, real))) {
26557
+ if (isPathOutside(relative8(realCwd, real))) {
25551
26558
  warnings.push(`file inclusion skipped: @${rawPath} \u2014 symlink escapes workspace`);
25552
26559
  return "";
25553
26560
  }
@@ -25660,9 +26667,9 @@ var init_wcag = __esm({
25660
26667
  });
25661
26668
 
25662
26669
  // src/ui/theme-loader.ts
25663
- import { readFile as readFile21, readdir as readdir9 } from "fs/promises";
26670
+ import { readFile as readFile23, readdir as readdir9 } from "fs/promises";
25664
26671
  import { join as join32 } from "path";
25665
- import { homedir as homedir16 } from "os";
26672
+ import { homedir as homedir17 } from "os";
25666
26673
  function projectThemesDir(cwd = process.cwd()) {
25667
26674
  return join32(cwd, ".kimiflare", "themes");
25668
26675
  }
@@ -25756,23 +26763,23 @@ async function loadThemesFromDir(dir, source) {
25756
26763
  const path = join32(dir, file);
25757
26764
  let raw;
25758
26765
  try {
25759
- raw = await readFile21(path, "utf-8");
26766
+ raw = await readFile23(path, "utf-8");
25760
26767
  } catch (e) {
25761
26768
  errors.push(`${path}: ${e instanceof Error ? e.message : String(e)}`);
25762
26769
  continue;
25763
26770
  }
25764
- let json;
26771
+ let json2;
25765
26772
  try {
25766
- json = JSON.parse(raw);
26773
+ json2 = JSON.parse(raw);
25767
26774
  } catch (e) {
25768
26775
  errors.push(`${path}: invalid JSON \u2014 ${e instanceof Error ? e.message : String(e)}`);
25769
26776
  continue;
25770
26777
  }
25771
- if (!json || typeof json !== "object") {
26778
+ if (!json2 || typeof json2 !== "object") {
25772
26779
  errors.push(`${path}: root must be an object`);
25773
26780
  continue;
25774
26781
  }
25775
- const obj = json;
26782
+ const obj = json2;
25776
26783
  const fileErrors = [];
25777
26784
  if (typeof obj.name !== "string" || obj.name.length === 0) {
25778
26785
  fileErrors.push("name is required");
@@ -25903,7 +26910,7 @@ var init_theme_loader = __esm({
25903
26910
  init_wcag();
25904
26911
  init_theme();
25905
26912
  USER_THEMES_DIR = join32(
25906
- process.env.XDG_CONFIG_HOME || join32(homedir16(), ".config"),
26913
+ process.env.XDG_CONFIG_HOME || join32(homedir17(), ".config"),
25907
26914
  "kimiflare",
25908
26915
  "themes"
25909
26916
  );
@@ -30279,11 +31286,11 @@ var tui_report_exports = {};
30279
31286
  __export(tui_report_exports, {
30280
31287
  getCategoryReportText: () => getCategoryReportText
30281
31288
  });
30282
- import { readFile as readFile22 } from "fs/promises";
31289
+ import { readFile as readFile24 } from "fs/promises";
30283
31290
  import { join as join33 } from "path";
30284
- import { homedir as homedir17 } from "os";
31291
+ import { homedir as homedir18 } from "os";
30285
31292
  function usageDir3() {
30286
- const xdg = process.env.XDG_DATA_HOME || join33(homedir17(), ".local", "share");
31293
+ const xdg = process.env.XDG_DATA_HOME || join33(homedir18(), ".local", "share");
30287
31294
  return join33(xdg, "kimiflare");
30288
31295
  }
30289
31296
  function usagePath3() {
@@ -30299,7 +31306,7 @@ function daysAgo2(n) {
30299
31306
  }
30300
31307
  async function loadLog3() {
30301
31308
  try {
30302
- const raw = await readFile22(usagePath3(), "utf8");
31309
+ const raw = await readFile24(usagePath3(), "utf8");
30303
31310
  return JSON.parse(raw);
30304
31311
  } catch {
30305
31312
  return { version: 1, days: [], sessions: [] };
@@ -30429,7 +31436,7 @@ var init_slash_commands = __esm({
30429
31436
  init_executor();
30430
31437
  init_recommended();
30431
31438
  init_settings();
30432
- init_types2();
31439
+ init_types();
30433
31440
  init_update_check();
30434
31441
  init_version();
30435
31442
  init_app_helpers();
@@ -31972,7 +32979,7 @@ async function runInit(deps) {
31972
32979
  lspManagerRef.current.notifyChange(path, content);
31973
32980
  } else {
31974
32981
  void import("fs/promises").then(
31975
- ({ readFile: readFile24 }) => readFile24(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
32982
+ ({ readFile: readFile25 }) => readFile25(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
31976
32983
  })
31977
32984
  );
31978
32985
  }
@@ -32058,9 +33065,9 @@ async function runInit(deps) {
32058
33065
  },
32059
33066
  onGatewayMeta: updateGatewayMeta,
32060
33067
  askPermission: (req) => askForPermission(req, { promptOnBlockedBash: true }),
32061
- onLoopDetected: () => new Promise((resolve5) => {
32062
- loopResolveRef.current = resolve5;
32063
- setLoopModal({ resolve: resolve5 });
33068
+ onLoopDetected: () => new Promise((resolve8) => {
33069
+ loopResolveRef.current = resolve8;
33070
+ setLoopModal({ resolve: resolve8 });
32064
33071
  }),
32065
33072
  onKimiMdStale: () => {
32066
33073
  if (!kimiMdStaleNudgedRef.current) {
@@ -32171,45 +33178,9 @@ var init_run_init = __esm({
32171
33178
  }
32172
33179
  });
32173
33180
 
32174
- // src/util/state.ts
32175
- import { readFile as readFile23, writeFile as writeFile14, mkdir as mkdir12 } from "fs/promises";
32176
- import { homedir as homedir18 } from "os";
32177
- import { join as join36 } from "path";
32178
- function statePath() {
32179
- const xdg = process.env.XDG_CONFIG_HOME || join36(homedir18(), ".config");
32180
- return join36(xdg, "kimiflare", "state.json");
32181
- }
32182
- async function readState() {
32183
- try {
32184
- const raw = await readFile23(statePath(), "utf8");
32185
- return JSON.parse(raw);
32186
- } catch {
32187
- return {};
32188
- }
32189
- }
32190
- async function writeState(state) {
32191
- const path = statePath();
32192
- await mkdir12(join36(path, ".."), { recursive: true });
32193
- await writeFile14(path, JSON.stringify(state, null, 2) + "\n", "utf8");
32194
- }
32195
- async function markCreatorMessageSeen(version) {
32196
- const state = await readState();
32197
- state.creatorMessageSeenVersion = version;
32198
- await writeState(state);
32199
- }
32200
- async function shouldShowCreatorMessage(version) {
32201
- const state = await readState();
32202
- return state.creatorMessageSeenVersion !== version;
32203
- }
32204
- var init_state = __esm({
32205
- "src/util/state.ts"() {
32206
- "use strict";
32207
- }
32208
- });
32209
-
32210
33181
  // src/ui/run-startup-tasks.ts
32211
33182
  import { existsSync as existsSync7 } from "fs";
32212
- import { join as join37 } from "path";
33183
+ import { join as join36 } from "path";
32213
33184
  function runStartupTasks(deps) {
32214
33185
  const {
32215
33186
  cfg,
@@ -32221,46 +33192,16 @@ function runStartupTasks(deps) {
32221
33192
  customCommandsRef,
32222
33193
  setCustomCommandsVersion
32223
33194
  } = deps;
32224
- void Promise.resolve().then(() => (init_sessions(), sessions_exports)).then(
32225
- ({ pruneSessions: pruneSessions2 }) => pruneSessions2().then((removed) => {
32226
- if (removed > 0) {
32227
- setEvents((e) => [
32228
- ...e,
32229
- { kind: "info", key: mkKey2(), text: `pruned ${removed} old session files` }
32230
- ]);
32231
- }
32232
- })
32233
- );
32234
- void Promise.resolve().then(() => (init_log_sink(), log_sink_exports)).then(({ pruneOldLogs: pruneOldLogs2, logPathFor: logPathFor2, isLogSinkEnabled: isLogSinkEnabled2 }) => {
33195
+ void Promise.resolve().then(() => (init_sessions(), sessions_exports)).then(({ pruneSessions: pruneSessions2 }) => pruneSessions2());
33196
+ void Promise.resolve().then(() => (init_log_sink(), log_sink_exports)).then(({ pruneOldLogs: pruneOldLogs2, isLogSinkEnabled: isLogSinkEnabled2 }) => {
32235
33197
  if (!isLogSinkEnabled2()) return;
32236
33198
  try {
32237
33199
  pruneOldLogs2();
32238
33200
  } catch {
32239
33201
  }
32240
- setEvents((e) => [
32241
- ...e,
32242
- {
32243
- kind: "info",
32244
- key: mkKey2(),
32245
- text: `structured logs: ${logPathFor2()} (tail with: tail -f $(kimiflare logs path) | jq)`
32246
- }
32247
- ]);
32248
- });
32249
- void shouldShowCreatorMessage(getAppVersion()).then((shouldShow) => {
32250
- if (shouldShow) {
32251
- setEvents((e) => [
32252
- ...e,
32253
- {
32254
- kind: "info",
32255
- key: mkKey2(),
32256
- 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."
32257
- }
32258
- ]);
32259
- void markCreatorMessageSeen(getAppVersion());
32260
- }
32261
33202
  });
32262
33203
  if (cfg.memoryEnabled) {
32263
- const dbPath = cfg.memoryDbPath ?? join37(process.cwd(), ".kimiflare", "memory.db");
33204
+ const dbPath = cfg.memoryDbPath ?? join36(process.cwd(), ".kimiflare", "memory.db");
32264
33205
  const manager = new MemoryManager({
32265
33206
  dbPath,
32266
33207
  accountId: cfg.accountId,
@@ -32294,7 +33235,7 @@ function runStartupTasks(deps) {
32294
33235
  });
32295
33236
  const cwd = process.cwd();
32296
33237
  sessionStartRecallRef.current = manager.recall({ text: cwd, repoPath: cwd, limit: 5 });
32297
- if (existsSync7(join37(cwd, "KIMI.md"))) {
33238
+ if (existsSync7(join36(cwd, "KIMI.md"))) {
32298
33239
  const lastRefresh = manager.getLastKimiMdRefreshTime(cwd);
32299
33240
  const driftCount = manager.countHighSignalMemoriesSince(cwd, lastRefresh);
32300
33241
  if (driftCount >= 5) {
@@ -32305,7 +33246,7 @@ function runStartupTasks(deps) {
32305
33246
  memoryManagerRef.current?.close();
32306
33247
  memoryManagerRef.current = null;
32307
33248
  }
32308
- const skillDbPath = cfg.memoryDbPath ?? join37(process.cwd(), ".kimiflare", "memory.db");
33249
+ const skillDbPath = cfg.memoryDbPath ?? join36(process.cwd(), ".kimiflare", "memory.db");
32309
33250
  const skillDb = getMemoryDb() ?? openMemoryDb(skillDbPath);
32310
33251
  initSkillsSchema(skillDb);
32311
33252
  void indexSkills({
@@ -32348,11 +33289,9 @@ var init_run_startup_tasks = __esm({
32348
33289
  "use strict";
32349
33290
  init_builtins();
32350
33291
  init_loader2();
32351
- init_manager();
33292
+ init_manager2();
32352
33293
  init_db2();
32353
33294
  init_skills();
32354
- init_state();
32355
- init_version();
32356
33295
  init_storage_limits();
32357
33296
  init_app_helpers();
32358
33297
  }
@@ -32458,10 +33397,7 @@ async function initLsp(deps) {
32458
33397
  if (!cfg.lspEnabled || !cfg.lspServers || lspInitRef.current) {
32459
33398
  if (lspInitRef.current) return;
32460
33399
  if (!cfg.lspEnabled) {
32461
- setEvents((es) => [
32462
- ...es,
32463
- { kind: "info", key: mkKey2(), text: "LSP is disabled. Enable it in config to use language servers." }
32464
- ]);
33400
+ return;
32465
33401
  } else if (!cfg.lspServers || Object.keys(cfg.lspServers).length === 0) {
32466
33402
  setEvents((es) => [
32467
33403
  ...es,
@@ -32774,7 +33710,7 @@ __export(app_exports, {
32774
33710
  import React25, { useState as useState29, useRef as useRef7, useEffect as useEffect11, useCallback as useCallback10, useMemo as useMemo6 } from "react";
32775
33711
  import { Box as Box42, Text as Text43, useApp, useInput as useInput21, render } from "ink";
32776
33712
  import { existsSync as existsSync8 } from "fs";
32777
- import { join as join38 } from "path";
33713
+ import { join as join37 } from "path";
32778
33714
  import { jsx as jsx44, jsxs as jsxs42 } from "react/jsx-runtime";
32779
33715
  function App({
32780
33716
  initialCfg,
@@ -34178,7 +35114,7 @@ ${wcagWarnings.join("\n")}` }
34178
35114
  }
34179
35115
  }
34180
35116
  turnCounterRef.current += 1;
34181
- if (turnCounterRef.current % 15 === 0 && existsSync8(join38(process.cwd(), "KIMI.md")) && !kimiMdStale) {
35117
+ if (turnCounterRef.current % 15 === 0 && existsSync8(join37(process.cwd(), "KIMI.md")) && !kimiMdStale) {
34182
35118
  setEvents((e) => [
34183
35119
  ...e,
34184
35120
  { kind: "info", key: mkKey(), text: "Tip: Rerunning /init occasionally helps KimiFlare stay accurate as your project evolves." }
@@ -34202,7 +35138,7 @@ ${wcagWarnings.join("\n")}` }
34202
35138
  };
34203
35139
  ensureSessionId();
34204
35140
  const { sessionsDir: sessionsDir3 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
34205
- const filePath = join38(sessionsDir3(), `${sessionIdRef.current}.json`);
35141
+ const filePath = join37(sessionsDir3(), `${sessionIdRef.current}.json`);
34206
35142
  await addCheckpoint(filePath, cp);
34207
35143
  }
34208
35144
  const summary = await generateContinuationSummary({
@@ -34457,13 +35393,13 @@ ${conflicts.join("\n")}` }
34457
35393
  planOptionsRef.current = options;
34458
35394
  },
34459
35395
  askPermission: askForPermission,
34460
- onToolLimitReached: () => new Promise((resolve5) => {
34461
- limitResolveRef.current = resolve5;
34462
- setLimitModal({ limit: 200, resolve: resolve5 });
35396
+ onToolLimitReached: () => new Promise((resolve8) => {
35397
+ limitResolveRef.current = resolve8;
35398
+ setLimitModal({ limit: 200, resolve: resolve8 });
34463
35399
  }),
34464
- onLoopDetected: () => new Promise((resolve5) => {
34465
- loopResolveRef.current = resolve5;
34466
- setLoopModal({ resolve: resolve5 });
35400
+ onLoopDetected: () => new Promise((resolve8) => {
35401
+ loopResolveRef.current = resolve8;
35402
+ setLoopModal({ resolve: resolve8 });
34467
35403
  }),
34468
35404
  onKimiMdStale: () => {
34469
35405
  if (!kimiMdStaleNudgedRef.current) {
@@ -34564,7 +35500,7 @@ ${conflicts.join("\n")}` }
34564
35500
  lspManagerRef.current.notifyChange(path, content2);
34565
35501
  } else {
34566
35502
  void import("fs/promises").then(
34567
- ({ readFile: readFile24 }) => readFile24(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
35503
+ ({ readFile: readFile25 }) => readFile25(path, "utf8").then((c) => lspManagerRef.current.notifyChange(path, c)).catch(() => {
34568
35504
  })
34569
35505
  );
34570
35506
  }
@@ -34851,7 +35787,7 @@ ${conflicts.join("\n")}` }
34851
35787
  onCommandDelete: handleCommandDelete2,
34852
35788
  lspServers: cfg?.lspServers ?? {},
34853
35789
  lspScope,
34854
- hasProjectDir: existsSync8(join38(process.cwd(), ".kimiflare")),
35790
+ hasProjectDir: existsSync8(join37(process.cwd(), ".kimiflare")),
34855
35791
  onLspSave: handleLspSave2,
34856
35792
  themes: themeList(),
34857
35793
  onPickTheme: handleThemePick,
@@ -35218,8 +36154,8 @@ var init_app = __esm({
35218
36154
  init_session_state();
35219
36155
  init_executor();
35220
36156
  init_manager4();
35221
- init_manager2();
35222
36157
  init_manager3();
36158
+ init_manager();
35223
36159
  init_messages();
35224
36160
  init_errors();
35225
36161
  init_abort_scope();
@@ -35278,10 +36214,6 @@ var init_app = __esm({
35278
36214
  // src/index.tsx
35279
36215
  init_config();
35280
36216
  init_lsp_config();
35281
- init_loop();
35282
- init_errors();
35283
- init_system_prompt();
35284
- init_executor();
35285
36217
  init_update_check();
35286
36218
  init_version();
35287
36219
  import { Command as Command2 } from "commander";
@@ -35461,9 +36393,334 @@ function renderLogo(version) {
35461
36393
  return out.join("\n");
35462
36394
  }
35463
36395
 
36396
+ // src/print-mode.ts
36397
+ init_loop();
36398
+ init_system_prompt();
36399
+ init_executor();
36400
+ init_errors();
36401
+ init_sessions();
36402
+ init_image();
36403
+ init_glob();
36404
+ init_permissions_evaluator();
36405
+ import { readFile as readFile12 } from "fs/promises";
36406
+ import { resolve as resolve4, basename as basename3 } from "path";
36407
+ function gatewayFromPrintOpts(opts2) {
36408
+ if (!opts2.aiGatewayId) return void 0;
36409
+ return {
36410
+ id: opts2.aiGatewayId,
36411
+ cacheTtl: opts2.aiGatewayCacheTtl,
36412
+ skipCache: opts2.aiGatewaySkipCache,
36413
+ collectLogPayload: opts2.aiGatewayCollectLogPayload,
36414
+ metadata: opts2.aiGatewayMetadata
36415
+ };
36416
+ }
36417
+ async function resolveSession(opts2) {
36418
+ if (opts2.sessionId) {
36419
+ const filePath = resolve4(sessionsDir(), `${opts2.sessionId}.json`);
36420
+ try {
36421
+ const file = await loadSession(filePath);
36422
+ return { sessionFile: file, isNew: false };
36423
+ } catch {
36424
+ }
36425
+ }
36426
+ if (opts2.continueSession) {
36427
+ const cwd = opts2.dir ? resolve4(opts2.dir) : process.cwd();
36428
+ const sessions = await listSessions(1, cwd);
36429
+ if (sessions.length > 0) {
36430
+ const filePath = sessions[0].filePath;
36431
+ const file = await loadSession(filePath);
36432
+ return { sessionFile: file, isNew: false };
36433
+ }
36434
+ }
36435
+ const { makeSessionId: makeSessionId2 } = await Promise.resolve().then(() => (init_sessions(), sessions_exports));
36436
+ const id = makeSessionId2(opts2.prompt);
36437
+ return {
36438
+ sessionFile: {
36439
+ id,
36440
+ cwd: opts2.dir ? resolve4(opts2.dir) : process.cwd(),
36441
+ model: opts2.model,
36442
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
36443
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
36444
+ messages: [],
36445
+ title: opts2.title
36446
+ },
36447
+ isNew: true
36448
+ };
36449
+ }
36450
+ async function resolveFiles(filePatterns, cwd) {
36451
+ const resolved = /* @__PURE__ */ new Set();
36452
+ for (const pattern of filePatterns) {
36453
+ try {
36454
+ const stat8 = await import("fs/promises").then((m) => m.stat(resolve4(cwd, pattern)));
36455
+ if (stat8.isFile()) {
36456
+ resolved.add(resolve4(cwd, pattern));
36457
+ continue;
36458
+ }
36459
+ } catch {
36460
+ }
36461
+ const matches = await glob(pattern, { cwd, absolute: true });
36462
+ for (const m of matches) {
36463
+ resolved.add(m);
36464
+ }
36465
+ }
36466
+ return [...resolved];
36467
+ }
36468
+ async function buildUserMessage(prompt, files, cwd) {
36469
+ let text = prompt;
36470
+ const imageParts = [];
36471
+ const fileContents = [];
36472
+ for (const filePath of files) {
36473
+ if (isImagePath(filePath)) {
36474
+ try {
36475
+ const img = await encodeImageFile(filePath);
36476
+ imageParts.push({ type: "image_url", image_url: { url: img.dataUrl } });
36477
+ } catch (e) {
36478
+ fileContents.push(`
36479
+ <!-- failed to attach image ${basename3(filePath)}: ${e.message} -->
36480
+ `);
36481
+ }
36482
+ } else {
36483
+ try {
36484
+ const content = await readFile12(filePath, "utf8");
36485
+ const relPath = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
36486
+ fileContents.push(`
36487
+ --- ${relPath} ---
36488
+ ${content}
36489
+ --- end ${relPath} ---
36490
+ `);
36491
+ } catch (e) {
36492
+ fileContents.push(`
36493
+ <!-- failed to read ${basename3(filePath)}: ${e.message} -->
36494
+ `);
36495
+ }
36496
+ }
36497
+ }
36498
+ if (fileContents.length > 0) {
36499
+ text += "\n\n" + fileContents.join("\n");
36500
+ }
36501
+ const display = prompt;
36502
+ if (imageParts.length > 0) {
36503
+ const parts = [{ type: "text", text }];
36504
+ parts.push(...imageParts);
36505
+ return { content: parts, display };
36506
+ }
36507
+ return { content: text, display };
36508
+ }
36509
+ async function runPrintMode(opts2) {
36510
+ const startMs = Date.now();
36511
+ if (opts2.updateResult.hasUpdate) {
36512
+ process.stderr.write(
36513
+ `\x1B[33mkimiflare update available: ${opts2.updateResult.localVersion} \u2192 ${opts2.updateResult.latestVersion}\x1B[0m
36514
+ \x1B[33m npm update -g kimiflare then restart\x1B[0m
36515
+
36516
+ `
36517
+ );
36518
+ }
36519
+ const cwd = opts2.dir ? resolve4(opts2.dir) : process.cwd();
36520
+ const { HooksManager: HooksManager2 } = await Promise.resolve().then(() => (init_manager(), manager_exports));
36521
+ const hooks = new HooksManager2(cwd);
36522
+ const executor = new ToolExecutor(ALL_TOOLS, { hooks });
36523
+ const { sessionFile, isNew } = await resolveSession(opts2);
36524
+ if (opts2.title && isNew) {
36525
+ sessionFile.title = opts2.title;
36526
+ }
36527
+ const messages = [];
36528
+ if (isNew || sessionFile.messages.length === 0) {
36529
+ messages.push({ role: "system", content: buildSystemPrompt({ cwd, tools: ALL_TOOLS, model: opts2.model }) });
36530
+ } else {
36531
+ const nonSystem = sessionFile.messages.filter((m) => m.role !== "system");
36532
+ messages.push({ role: "system", content: buildSystemPrompt({ cwd, tools: ALL_TOOLS, model: opts2.model }) });
36533
+ messages.push(...nonSystem);
36534
+ }
36535
+ const files = opts2.files ? await resolveFiles(opts2.files, cwd) : [];
36536
+ const { content: userContent } = await buildUserMessage(opts2.prompt, files, cwd);
36537
+ messages.push({ role: "user", content: userContent });
36538
+ const controller = new AbortController();
36539
+ process.on("SIGINT", () => controller.abort());
36540
+ const format = opts2.format ?? "text";
36541
+ let printedReasoningHeader = false;
36542
+ let printedAnswerHeader = false;
36543
+ const jsonOutput = {
36544
+ text: "",
36545
+ toolCalls: [],
36546
+ toolResults: [],
36547
+ durationMs: 0,
36548
+ sessionId: sessionFile.id
36549
+ };
36550
+ function emitStreamJson(eventType, payload) {
36551
+ if (format === "stream-json") {
36552
+ process.stdout.write(JSON.stringify({ event: eventType, ...payload }) + "\n");
36553
+ }
36554
+ }
36555
+ const callbacks = {
36556
+ onReasoningDelta: opts2.showReasoning ? (delta) => {
36557
+ if (format === "text") {
36558
+ if (!printedReasoningHeader) {
36559
+ process.stderr.write("\x1B[2m--- reasoning ---\n");
36560
+ printedReasoningHeader = true;
36561
+ }
36562
+ process.stderr.write(delta);
36563
+ }
36564
+ } : void 0,
36565
+ onTextDelta: (delta) => {
36566
+ if (format === "text") {
36567
+ if (opts2.showReasoning && printedReasoningHeader && !printedAnswerHeader) {
36568
+ process.stderr.write("\n--- answer ---\x1B[0m\n");
36569
+ printedAnswerHeader = true;
36570
+ }
36571
+ process.stdout.write(delta);
36572
+ } else if (format === "json") {
36573
+ jsonOutput.text += delta;
36574
+ } else if (format === "stream-json") {
36575
+ emitStreamJson("text_delta", { delta });
36576
+ }
36577
+ },
36578
+ onToolCallFinalized: (call) => {
36579
+ if (format === "text") {
36580
+ process.stderr.write(`\x1B[2m[tool ${call.function.name}(${call.function.arguments})]\x1B[0m
36581
+ `);
36582
+ } else if (format === "json") {
36583
+ let args = {};
36584
+ try {
36585
+ args = JSON.parse(call.function.arguments);
36586
+ } catch {
36587
+ }
36588
+ jsonOutput.toolCalls.push({ id: call.id, name: call.function.name, arguments: args });
36589
+ } else if (format === "stream-json") {
36590
+ emitStreamJson("tool_call", { id: call.id, name: call.function.name, arguments: call.function.arguments });
36591
+ }
36592
+ },
36593
+ onToolResult: (result) => {
36594
+ if (format === "text") {
36595
+ const snippet = result.content.length > 400 ? result.content.slice(0, 400) + "..." : result.content;
36596
+ process.stderr.write(`\x1B[2m[result: ${snippet.replace(/\n/g, " \u23CE ")}]\x1B[0m
36597
+ `);
36598
+ } else if (format === "json") {
36599
+ jsonOutput.toolResults.push({
36600
+ toolCallId: result.tool_call_id,
36601
+ name: result.name,
36602
+ content: result.content,
36603
+ ok: result.ok
36604
+ });
36605
+ } else if (format === "stream-json") {
36606
+ emitStreamJson("tool_result", {
36607
+ toolCallId: result.tool_call_id,
36608
+ name: result.name,
36609
+ content: result.content,
36610
+ ok: result.ok
36611
+ });
36612
+ }
36613
+ },
36614
+ onUsage: (usage) => {
36615
+ if (format === "json") {
36616
+ jsonOutput.usage = {
36617
+ promptTokens: usage.prompt_tokens ?? 0,
36618
+ completionTokens: usage.completion_tokens ?? 0,
36619
+ totalTokens: usage.total_tokens ?? 0
36620
+ };
36621
+ } else if (format === "stream-json") {
36622
+ emitStreamJson("usage", {
36623
+ promptTokens: usage.prompt_tokens ?? 0,
36624
+ completionTokens: usage.completion_tokens ?? 0,
36625
+ totalTokens: usage.total_tokens ?? 0
36626
+ });
36627
+ }
36628
+ },
36629
+ onWarning: (msg) => {
36630
+ if (format === "text") {
36631
+ process.stderr.write(`\x1B[33mkimiflare: ${msg}\x1B[0m
36632
+ `);
36633
+ } else if (format === "stream-json") {
36634
+ emitStreamJson("warning", { message: msg });
36635
+ }
36636
+ },
36637
+ askPermission: async ({ tool, args }) => {
36638
+ if (opts2.allowAll) return "allow";
36639
+ if (opts2.permissions) {
36640
+ const rule = evaluatePermissionRules({ tool: tool.name, args, cwd }, opts2.permissions);
36641
+ if (rule === "allow") return "allow";
36642
+ if (rule === "deny") {
36643
+ const msg2 = `[permission denied by config rule: ${tool.name}(${JSON.stringify(args)})]`;
36644
+ if (format === "text") process.stderr.write(`\x1B[31m${msg2}\x1B[0m
36645
+ `);
36646
+ else if (format === "stream-json") emitStreamJson("permission_denied", { tool: tool.name, args, reason: "config_rule" });
36647
+ return "deny";
36648
+ }
36649
+ }
36650
+ const msg = `[permission denied: ${tool.name}(${JSON.stringify(args)}) \u2014 pass --dangerously-allow-all to approve in print mode, or configure permissions in config.json]`;
36651
+ if (format === "text") {
36652
+ process.stderr.write(`\x1B[31m${msg}\x1B[0m
36653
+ `);
36654
+ } else if (format === "stream-json") {
36655
+ emitStreamJson("permission_denied", { tool: tool.name, args });
36656
+ }
36657
+ return "deny";
36658
+ }
36659
+ };
36660
+ try {
36661
+ await runAgentTurn({
36662
+ accountId: opts2.accountId,
36663
+ apiToken: opts2.apiToken,
36664
+ model: opts2.model,
36665
+ gateway: gatewayFromPrintOpts(opts2),
36666
+ messages,
36667
+ tools: ALL_TOOLS,
36668
+ executor,
36669
+ hooks,
36670
+ cwd,
36671
+ signal: controller.signal,
36672
+ codeMode: opts2.codeMode,
36673
+ continueOnLimit: opts2.continueOnLimit,
36674
+ maxInputTokens: opts2.maxInputTokens,
36675
+ coauthor: opts2.coauthor !== false ? { name: opts2.coauthorName || "kimiflare", email: opts2.coauthorEmail || "kimiflare@proton.me" } : void 0,
36676
+ callbacks
36677
+ });
36678
+ } catch (err) {
36679
+ if (err instanceof BudgetExhaustedError) {
36680
+ const msg = "[Budget exhausted \u2014 exiting with code 42]";
36681
+ if (format === "text") process.stderr.write(`
36682
+ \x1B[33m${msg}\x1B[0m
36683
+ `);
36684
+ else if (format === "stream-json") emitStreamJson("error", { message: msg, code: 42 });
36685
+ process.exitCode = 42;
36686
+ return;
36687
+ }
36688
+ if (err instanceof AgentLoopError) {
36689
+ const msg = "[Agent loop detected \u2014 exiting with code 43]";
36690
+ if (format === "text") process.stderr.write(`
36691
+ \x1B[33m${msg}\x1B[0m
36692
+ `);
36693
+ else if (format === "stream-json") emitStreamJson("error", { message: msg, code: 43 });
36694
+ process.exitCode = 43;
36695
+ return;
36696
+ }
36697
+ if (err instanceof KimiApiError) {
36698
+ const msg = `Error: ${humanizeCloudflareError(err)}`;
36699
+ if (format === "text") process.stderr.write(`
36700
+ \x1B[31m${msg}\x1B[0m
36701
+ `);
36702
+ else if (format === "stream-json") emitStreamJson("error", { message: msg, code: 1 });
36703
+ process.exitCode = 1;
36704
+ return;
36705
+ }
36706
+ throw err;
36707
+ }
36708
+ sessionFile.messages = messages;
36709
+ sessionFile.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
36710
+ await saveSession(sessionFile);
36711
+ jsonOutput.durationMs = Date.now() - startMs;
36712
+ if (format === "text") {
36713
+ process.stdout.write("\n");
36714
+ } else if (format === "json") {
36715
+ process.stdout.write(JSON.stringify(jsonOutput, null, 2) + "\n");
36716
+ } else if (format === "stream-json") {
36717
+ emitStreamJson("done", { sessionId: sessionFile.id, durationMs: jsonOutput.durationMs });
36718
+ }
36719
+ }
36720
+
35464
36721
  // src/index.tsx
35465
36722
  var program = new Command2();
35466
- 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)").option("--continue-on-limit", "reset tool-call counter and continue when the 200-call limit is hit (print mode only)").option("--max-input-tokens <n>", "cumulative prompt token budget; exits 42 when exhausted (print mode only)", (v) => parseInt(v, 10)).option("--emit-events", "emit Camouflage NDJSON events to stdout; requires -p (for initial prompt)").option("--multi-turn", "with --emit-events: keep reading stdin for UserInputSubmitted follow-ups after the initial turn").option("--ui <name>", "render UI with the given engine: `ink` (default, stable) or `camouflage` (experimental Rust TUI). Can also be set via the KIMIFLARE_UI environment variable.").option("--camouflage-bin <path>", "with --ui camouflage: path to the camouflage-tui binary (defaults to PATH lookup)").option("--mode <mode>", "run mode: interactive (default), print, rpc");
36723
+ 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)").option("--thinking", "alias for --reasoning").option("--continue-on-limit", "reset tool-call counter and continue when the 200-call limit is hit (print mode only)").option("--max-input-tokens <n>", "cumulative prompt token budget; exits 42 when exhausted (print mode only)", (v) => parseInt(v, 10)).option("--emit-events", "emit Camouflage NDJSON events to stdout; requires -p (for initial prompt)").option("--multi-turn", "with --emit-events: keep reading stdin for UserInputSubmitted follow-ups after the initial turn").option("--ui <name>", "render UI with the given engine: `ink` (default, stable) or `camouflage` (experimental Rust TUI). Can also be set via the KIMIFLARE_UI environment variable.").option("--camouflage-bin <path>", "with --ui camouflage: path to the camouflage-tui binary (defaults to PATH lookup)").option("--mode <mode>", "run mode: interactive (default), print, rpc").option("-c, --continue", "continue the most recent session in the current working directory (print mode only)").option("-S, --session <id>", "resume a specific session by id (print mode only)").option("-f, --file <path>", "attach file(s) to the prompt; repeatable, supports globs (print mode only)", (v, prev) => (prev ?? []).concat(v)).option("--format <mode>", "output format for print mode: text (default), json, stream-json").option("--dir <path>", "run in the specified directory instead of the current one (print mode only)").option("--title <title>", "override the auto-generated session title (print mode only)").option("--attach <url>", "attach to a running kimiflare serve instance (print mode only)");
35467
36724
  program.command("cost").description("Show cost attribution by task type (requires costAttribution enabled)").option("-w, --week", "last 7 days (default)").option("-m, --month", "last 30 days").option("-d, --day", "today only").option("-s, --session <id>", "single session detail").option("-c, --category <name>", "filter by category").option("--json", "machine-readable output").option("--reclassify", "re-run classification on all sessions").option("--local-only", "skip Cloudflare reconciliation").action(async (cmdOpts) => {
35468
36725
  const cfg = await loadConfig();
35469
36726
  const enabled = cfg?.costAttribution ?? false;
@@ -35516,6 +36773,19 @@ Open: ${step.url}`);
35516
36773
  }
35517
36774
  })
35518
36775
  );
36776
+ program.command("serve").description("Start a headless HTTP server for API access and CI integration").option("--port <n>", "port to listen on", (v) => parseInt(v, 10), 4096).option("--hostname <host>", "hostname to listen on", "127.0.0.1").action(async (cmdOpts) => {
36777
+ const cfg = await loadConfig();
36778
+ if (!cfg) {
36779
+ console.error("kimiflare serve: missing credentials.");
36780
+ process.exit(2);
36781
+ }
36782
+ const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
36783
+ await startServer2({
36784
+ port: cmdOpts.port,
36785
+ hostname: cmdOpts.hostname,
36786
+ config: cfg
36787
+ });
36788
+ });
35519
36789
  program.action(async () => {
35520
36790
  await main();
35521
36791
  });
@@ -35580,16 +36850,41 @@ async function main() {
35580
36850
  process.exit(2);
35581
36851
  }
35582
36852
  const model = opts.model ?? cfg.model ?? DEFAULT_MODEL;
36853
+ const format = opts.format ?? "text";
36854
+ if (format !== "text" && format !== "json" && format !== "stream-json") {
36855
+ console.error(`kimiflare: invalid --format "${format}". Use: text, json, stream-json`);
36856
+ process.exit(2);
36857
+ }
36858
+ if (opts.attach) {
36859
+ const { runAttachMode: runAttachMode2 } = await Promise.resolve().then(() => (init_attach_mode(), attach_mode_exports));
36860
+ await runAttachMode2({
36861
+ attachUrl: opts.attach,
36862
+ prompt: opts.print,
36863
+ model,
36864
+ files: opts.file,
36865
+ format,
36866
+ allowAll: !!opts.dangerouslyAllowAll,
36867
+ sessionId: opts.session
36868
+ });
36869
+ return;
36870
+ }
35583
36871
  await runPrintMode({
35584
36872
  ...cfg,
35585
36873
  model,
35586
36874
  prompt: opts.print,
35587
36875
  allowAll: !!opts.dangerouslyAllowAll,
35588
- showReasoning: !!opts.reasoning,
36876
+ showReasoning: !!(opts.reasoning || opts.thinking),
35589
36877
  codeMode: cfg.codeMode,
35590
36878
  continueOnLimit: !!opts.continueOnLimit,
35591
36879
  maxInputTokens: opts.maxInputTokens,
35592
- updateResult
36880
+ updateResult,
36881
+ continueSession: !!opts.continue,
36882
+ sessionId: opts.session,
36883
+ files: opts.file,
36884
+ format,
36885
+ dir: opts.dir,
36886
+ title: opts.title,
36887
+ permissions: cfg.permissions
35593
36888
  });
35594
36889
  return;
35595
36890
  }
@@ -35646,112 +36941,4 @@ async function main() {
35646
36941
  await renderApp2(null, updateResult, lspScope, lspProjectPath);
35647
36942
  }
35648
36943
  }
35649
- function gatewayFromPrintOpts(opts2) {
35650
- if (!opts2.aiGatewayId) return void 0;
35651
- return {
35652
- id: opts2.aiGatewayId,
35653
- cacheTtl: opts2.aiGatewayCacheTtl,
35654
- skipCache: opts2.aiGatewaySkipCache,
35655
- collectLogPayload: opts2.aiGatewayCollectLogPayload,
35656
- metadata: opts2.aiGatewayMetadata
35657
- };
35658
- }
35659
- async function runPrintMode(opts2) {
35660
- if (opts2.updateResult.hasUpdate) {
35661
- process.stderr.write(
35662
- `\x1B[33mkimiflare update available: ${opts2.updateResult.localVersion} \u2192 ${opts2.updateResult.latestVersion}\x1B[0m
35663
- \x1B[33m npm update -g kimiflare then restart\x1B[0m
35664
-
35665
- `
35666
- );
35667
- }
35668
- const cwd = process.cwd();
35669
- const { HooksManager: HooksManager2 } = await Promise.resolve().then(() => (init_manager3(), manager_exports));
35670
- const hooks = new HooksManager2(cwd);
35671
- const executor = new ToolExecutor(ALL_TOOLS, { hooks });
35672
- const messages = [
35673
- { role: "system", content: buildSystemPrompt({ cwd, tools: ALL_TOOLS, model: opts2.model }) },
35674
- { role: "user", content: opts2.prompt }
35675
- ];
35676
- const controller = new AbortController();
35677
- process.on("SIGINT", () => controller.abort());
35678
- let printedReasoningHeader = false;
35679
- let printedAnswerHeader = false;
35680
- try {
35681
- await runAgentTurn({
35682
- accountId: opts2.accountId,
35683
- apiToken: opts2.apiToken,
35684
- model: opts2.model,
35685
- gateway: gatewayFromPrintOpts(opts2),
35686
- messages,
35687
- tools: ALL_TOOLS,
35688
- executor,
35689
- hooks,
35690
- // M6.1: Stop fires at end of print-mode turn too.
35691
- cwd,
35692
- signal: controller.signal,
35693
- codeMode: opts2.codeMode,
35694
- continueOnLimit: opts2.continueOnLimit,
35695
- maxInputTokens: opts2.maxInputTokens,
35696
- coauthor: opts2.coauthor !== false ? { name: opts2.coauthorName || "kimiflare", email: opts2.coauthorEmail || "kimiflare@proton.me" } : void 0,
35697
- callbacks: {
35698
- onReasoningDelta: opts2.showReasoning ? (delta) => {
35699
- if (!printedReasoningHeader) {
35700
- process.stderr.write("\x1B[2m--- reasoning ---\n");
35701
- printedReasoningHeader = true;
35702
- }
35703
- process.stderr.write(delta);
35704
- } : void 0,
35705
- onTextDelta: (delta) => {
35706
- if (opts2.showReasoning && printedReasoningHeader && !printedAnswerHeader) {
35707
- process.stderr.write("\n--- answer ---\x1B[0m\n");
35708
- printedAnswerHeader = true;
35709
- }
35710
- process.stdout.write(delta);
35711
- },
35712
- onToolCallFinalized: (call) => {
35713
- process.stderr.write(`\x1B[2m[tool ${call.function.name}(${call.function.arguments})]\x1B[0m
35714
- `);
35715
- },
35716
- onToolResult: (result) => {
35717
- const snippet = result.content.length > 400 ? result.content.slice(0, 400) + "..." : result.content;
35718
- process.stderr.write(`\x1B[2m[result: ${snippet.replace(/\n/g, " \u23CE ")}]\x1B[0m
35719
- `);
35720
- },
35721
- onWarning: (msg) => {
35722
- process.stderr.write(`\x1B[33mkimiflare: ${msg}\x1B[0m
35723
- `);
35724
- },
35725
- askPermission: async ({ tool, args }) => {
35726
- if (opts2.allowAll) return "allow";
35727
- process.stderr.write(
35728
- `\x1B[31m[permission denied: ${tool.name}(${JSON.stringify(args)}) \u2014 pass --dangerously-allow-all to approve in print mode]\x1B[0m
35729
- `
35730
- );
35731
- return "deny";
35732
- }
35733
- }
35734
- });
35735
- } catch (err) {
35736
- if (err instanceof BudgetExhaustedError) {
35737
- process.stderr.write("\n\x1B[33m[Budget exhausted \u2014 exiting with code 42]\x1B[0m\n");
35738
- process.exitCode = 42;
35739
- return;
35740
- }
35741
- if (err instanceof AgentLoopError) {
35742
- process.stderr.write("\n\x1B[33m[Agent loop detected \u2014 exiting with code 43]\x1B[0m\n");
35743
- process.exitCode = 43;
35744
- return;
35745
- }
35746
- if (err instanceof KimiApiError) {
35747
- process.stderr.write(`
35748
- \x1B[31mError: ${humanizeCloudflareError(err)}\x1B[0m
35749
- `);
35750
- process.exitCode = 1;
35751
- return;
35752
- }
35753
- throw err;
35754
- }
35755
- process.stdout.write("\n");
35756
- }
35757
36944
  //# sourceMappingURL=index.js.map