kimiflare 0.93.1 → 0.95.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
@@ -17616,7 +17616,7 @@ function StatusBar({ usage, sessionUsage, thinking, turnStartedAt, mode, context
17616
17616
  const elapsed = turnStartedAt !== null ? formatElapsed3(Math.max(0, now2 - turnStartedAt)) : null;
17617
17617
  const idleParts = [];
17618
17618
  if (gitBranch) idleParts.push(gitBranch);
17619
- if (model) idleParts.push(shortenModelId(model));
17619
+ if (model && !cloudMode) idleParts.push(shortenModelId(model));
17620
17620
  if (cloudMode) idleParts.push("CLOUD");
17621
17621
  if (codeMode) idleParts.push("CODE");
17622
17622
  const metaParts = [];
@@ -20405,6 +20405,287 @@ var init_key_entry_modal = __esm({
20405
20405
  }
20406
20406
  });
20407
20407
 
20408
+ // src/ui/app-helpers.ts
20409
+ var app_helpers_exports = {};
20410
+ __export(app_helpers_exports, {
20411
+ AUTO_COMPACT_THRESHOLD: () => AUTO_COMPACT_THRESHOLD,
20412
+ CONTEXT_LIMIT: () => CONTEXT_LIMIT,
20413
+ DEFAULT_AUTO_FRESH_SUGGESTION_TURNS: () => DEFAULT_AUTO_FRESH_SUGGESTION_TURNS,
20414
+ FEEDBACK_WORKER_URL: () => FEEDBACK_WORKER_URL,
20415
+ MAX_EVENTS: () => MAX_EVENTS,
20416
+ MAX_IMAGES_PER_MESSAGE: () => MAX_IMAGES_PER_MESSAGE,
20417
+ buildFilePickerIgnoreList: () => buildFilePickerIgnoreList,
20418
+ capEvents: () => capEvents,
20419
+ compactEventsVisual: () => compactEventsVisual,
20420
+ detectGitBranch: () => detectGitBranch,
20421
+ detectGitHubRepo: () => detectGitHubRepo,
20422
+ findImagePaths: () => findImagePaths,
20423
+ formatTokens: () => formatTokens4,
20424
+ gatewayFromConfig: () => gatewayFromConfig,
20425
+ gatewayUsageLookupFromConfig: () => gatewayUsageLookupFromConfig,
20426
+ makePrefixMessages: () => makePrefixMessages,
20427
+ mkAssistantId: () => mkAssistantId,
20428
+ mkKey: () => mkKey,
20429
+ openBrowser: () => openBrowser,
20430
+ rebuildSystemPromptForMode: () => rebuildSystemPromptForMode,
20431
+ trackRecentFile: () => trackRecentFile
20432
+ });
20433
+ import { execSync as execSync3, spawn as spawn4 } from "child_process";
20434
+ import { existsSync as existsSync4, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
20435
+ import { join as join26 } from "path";
20436
+ import { platform as platform4 } from "os";
20437
+ function buildFilePickerIgnoreList(cwd) {
20438
+ const hardcoded = [
20439
+ // Dependencies
20440
+ "**/node_modules/**",
20441
+ "**/vendor/**",
20442
+ "**/.bundle/**",
20443
+ "**/bower_components/**",
20444
+ // Version control
20445
+ "**/.git/**",
20446
+ "**/.svn/**",
20447
+ "**/.hg/**",
20448
+ // Build / output directories
20449
+ "**/dist/**",
20450
+ "**/build/**",
20451
+ "**/out/**",
20452
+ "**/public/**",
20453
+ "**/.next/**",
20454
+ "**/.nuxt/**",
20455
+ "**/.svelte-kit/**",
20456
+ "**/.vercel/**",
20457
+ "**/.netlify/**",
20458
+ "**/target/**",
20459
+ "**/bin/**",
20460
+ "**/obj/**",
20461
+ "**/Debug/**",
20462
+ "**/Release/**",
20463
+ "**/.gradle/**",
20464
+ // Caches
20465
+ "**/.cache/**",
20466
+ "**/.parcel-cache/**",
20467
+ "**/.turbo/**",
20468
+ "**/.eslintcache",
20469
+ "**/.stylelintcache",
20470
+ "**/.rpt2_cache/**",
20471
+ "**/.rts2_cache/**",
20472
+ // Temporary
20473
+ "**/tmp/**",
20474
+ "**/temp/**",
20475
+ "**/*.tmp",
20476
+ // Coverage
20477
+ "**/coverage/**",
20478
+ "**/.nyc_output/**",
20479
+ // OS files
20480
+ "**/.DS_Store",
20481
+ "**/Thumbs.db",
20482
+ // Logs
20483
+ "**/*.log",
20484
+ "**/logs/**",
20485
+ // Lock files (auto-generated, usually huge)
20486
+ "**/package-lock.json",
20487
+ "**/yarn.lock",
20488
+ "**/pnpm-lock.yaml",
20489
+ "**/bun.lockb",
20490
+ "**/Cargo.lock",
20491
+ "**/Gemfile.lock",
20492
+ "**/composer.lock",
20493
+ "**/Pipfile.lock",
20494
+ "**/poetry.lock",
20495
+ "**/go.sum",
20496
+ // Minified / source maps
20497
+ "**/*.min.js",
20498
+ "**/*.min.css",
20499
+ "**/*.map",
20500
+ // kimiflare internal
20501
+ "**/.kimiflare/**",
20502
+ // IDE (usually not relevant to mention)
20503
+ "**/.idea/**"
20504
+ ];
20505
+ const gitignorePatterns = [];
20506
+ try {
20507
+ const gitignorePath = join26(cwd, ".gitignore");
20508
+ const stats = statSync4(gitignorePath);
20509
+ if (stats.size > MAX_GITIGNORE_SIZE) {
20510
+ return hardcoded;
20511
+ }
20512
+ const content = readFileSync4(gitignorePath, "utf-8");
20513
+ for (const line of content.split(/\r?\n/)) {
20514
+ const trimmed = line.trim();
20515
+ if (!trimmed || trimmed.startsWith("#")) continue;
20516
+ if (trimmed.startsWith("!")) continue;
20517
+ let pattern = trimmed;
20518
+ const isAnchored = pattern.startsWith("/");
20519
+ const isDir = pattern.endsWith("/");
20520
+ if (isAnchored) pattern = pattern.slice(1);
20521
+ if (isDir) pattern = pattern.slice(0, -1);
20522
+ if (!pattern) continue;
20523
+ if (isAnchored) {
20524
+ gitignorePatterns.push(isDir ? pattern + "/**" : pattern);
20525
+ } else {
20526
+ gitignorePatterns.push(isDir ? "**/" + pattern + "/**" : "**/" + pattern);
20527
+ }
20528
+ }
20529
+ } catch {
20530
+ }
20531
+ return [...hardcoded, ...gitignorePatterns];
20532
+ }
20533
+ function gatewayFromConfig(cfg) {
20534
+ if (process.env.KIMIFLARE_DISABLE_AI_GATEWAY === "1") return void 0;
20535
+ if (!cfg.aiGatewayId) return void 0;
20536
+ return {
20537
+ id: cfg.aiGatewayId,
20538
+ cacheTtl: cfg.aiGatewayCacheTtl,
20539
+ skipCache: cfg.aiGatewaySkipCache,
20540
+ collectLogPayload: cfg.aiGatewayCollectLogPayload,
20541
+ metadata: cfg.aiGatewayMetadata
20542
+ };
20543
+ }
20544
+ function gatewayUsageLookupFromConfig(cfg, meta) {
20545
+ if (process.env.KIMIFLARE_DISABLE_AI_GATEWAY === "1") return void 0;
20546
+ if (!cfg.aiGatewayId || !meta) return void 0;
20547
+ return {
20548
+ accountId: cfg.accountId,
20549
+ apiToken: cfg.apiToken,
20550
+ gatewayId: cfg.aiGatewayId,
20551
+ meta
20552
+ };
20553
+ }
20554
+ function openBrowser(url) {
20555
+ const cmd = platform4() === "darwin" ? "open" : platform4() === "win32" ? "start" : "xdg-open";
20556
+ const child = spawn4(cmd, [url], { detached: true, stdio: "ignore" });
20557
+ child.unref();
20558
+ }
20559
+ function detectGitHubRepo(cachedRepo) {
20560
+ if (cachedRepo) {
20561
+ const parts = cachedRepo.split("/");
20562
+ if (parts.length === 2) return { owner: parts[0], name: parts[1] };
20563
+ }
20564
+ try {
20565
+ const remoteUrl = execSync3("git remote get-url origin", { cwd: process.cwd(), encoding: "utf8" }).trim().replace(/\/+$/, "");
20566
+ const httpsMatch = remoteUrl.match(/github\.com\/([^\/]+)\/([^\/]+?)(?:\.git)?$/);
20567
+ if (httpsMatch) return { owner: httpsMatch[1], name: httpsMatch[2] };
20568
+ const sshMatch = remoteUrl.match(/github\.com:([^\/]+)\/([^\/]+?)(?:\.git)?$/);
20569
+ if (sshMatch) return { owner: sshMatch[1], name: sshMatch[2] };
20570
+ } catch {
20571
+ }
20572
+ return null;
20573
+ }
20574
+ function detectGitBranch() {
20575
+ try {
20576
+ return execSync3("git branch --show-current", { cwd: process.cwd(), encoding: "utf8" }).trim() || null;
20577
+ } catch {
20578
+ return null;
20579
+ }
20580
+ }
20581
+ function formatTokens4(n) {
20582
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
20583
+ if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
20584
+ return String(n);
20585
+ }
20586
+ function trackRecentFile(ref, path, max = 10) {
20587
+ ref.current.set(path, Date.now());
20588
+ if (ref.current.size > max) {
20589
+ let oldest = null;
20590
+ let oldestTime = Infinity;
20591
+ for (const [p, t] of ref.current) {
20592
+ if (t < oldestTime) {
20593
+ oldestTime = t;
20594
+ oldest = p;
20595
+ }
20596
+ }
20597
+ if (oldest) ref.current.delete(oldest);
20598
+ }
20599
+ }
20600
+ function capEvents(prev) {
20601
+ if (prev.length <= MAX_EVENTS) return prev;
20602
+ return prev.slice(prev.length - MAX_EVENTS);
20603
+ }
20604
+ function compactEventsVisual(prev, keepLastTurns) {
20605
+ let seen = 0;
20606
+ let cutoff = -1;
20607
+ for (let i = prev.length - 1; i >= 0; i--) {
20608
+ if (prev[i].kind === "user") {
20609
+ seen++;
20610
+ if (seen === keepLastTurns + 1) {
20611
+ cutoff = i;
20612
+ break;
20613
+ }
20614
+ }
20615
+ }
20616
+ if (cutoff <= 0) return prev;
20617
+ const kept = prev.slice(cutoff);
20618
+ return [
20619
+ { kind: "info", key: mkKey(), text: `\xB7\xB7\xB7 ${cutoff} earlier messages compacted \xB7\xB7\xB7` },
20620
+ ...kept
20621
+ ];
20622
+ }
20623
+ function makePrefixMessages(cacheStable, model, mode, tools, preferPullRequests) {
20624
+ if (cacheStable) {
20625
+ return buildSystemMessages({ cwd: process.cwd(), tools, model, mode, preferPullRequests });
20626
+ }
20627
+ return [
20628
+ {
20629
+ role: "system",
20630
+ content: buildSystemPrompt({ cwd: process.cwd(), tools, model, mode, preferPullRequests })
20631
+ }
20632
+ ];
20633
+ }
20634
+ function rebuildSystemPromptForMode(messages, cacheStable, model, mode, tools, preferPullRequests) {
20635
+ if (cacheStable) {
20636
+ const rebuilt = buildSystemMessages({ cwd: process.cwd(), tools, model, mode, preferPullRequests });
20637
+ messages[0] = rebuilt[0];
20638
+ if (rebuilt[1]) {
20639
+ messages[1] = rebuilt[1];
20640
+ }
20641
+ } else {
20642
+ messages[0] = {
20643
+ role: "system",
20644
+ content: buildSystemPrompt({ cwd: process.cwd(), tools, model, mode, preferPullRequests })
20645
+ };
20646
+ }
20647
+ }
20648
+ function findImagePaths(text) {
20649
+ const paths = [];
20650
+ const quotedRegex = /"([^"]+)"|'([^']+)'/g;
20651
+ let match;
20652
+ while ((match = quotedRegex.exec(text)) !== null) {
20653
+ const path = match[1] ?? match[2];
20654
+ if (path && isImagePath(path) && existsSync4(path)) {
20655
+ paths.push(path);
20656
+ }
20657
+ }
20658
+ const remaining = text.replace(/"[^"]+"|'[^']+'/g, "");
20659
+ const ESCAPED_SPACE = "\0";
20660
+ const processed = remaining.replace(/\\ /g, ESCAPED_SPACE);
20661
+ for (const token of processed.split(/\s+/)) {
20662
+ const clean = token.replace(new RegExp(ESCAPED_SPACE, "g"), " ").replace(/^["']|["',;:!?]$/g, "").replace(/[.,;:!?]$/, "");
20663
+ if (clean && isImagePath(clean) && existsSync4(clean) && !paths.includes(clean)) {
20664
+ paths.push(clean);
20665
+ }
20666
+ }
20667
+ return paths;
20668
+ }
20669
+ var MAX_GITIGNORE_SIZE, CONTEXT_LIMIT, AUTO_COMPACT_THRESHOLD, MAX_EVENTS, DEFAULT_AUTO_FRESH_SUGGESTION_TURNS, MAX_IMAGES_PER_MESSAGE, FEEDBACK_WORKER_URL, nextKey, mkKey, nextAssistantId, mkAssistantId;
20670
+ var init_app_helpers = __esm({
20671
+ "src/ui/app-helpers.ts"() {
20672
+ "use strict";
20673
+ init_system_prompt();
20674
+ init_image();
20675
+ MAX_GITIGNORE_SIZE = 1 * 1024 * 1024;
20676
+ CONTEXT_LIMIT = 262e3;
20677
+ AUTO_COMPACT_THRESHOLD = 0.8;
20678
+ MAX_EVENTS = 500;
20679
+ DEFAULT_AUTO_FRESH_SUGGESTION_TURNS = 30;
20680
+ MAX_IMAGES_PER_MESSAGE = 10;
20681
+ FEEDBACK_WORKER_URL = "https://hello.kimiflare.com";
20682
+ nextKey = 1;
20683
+ mkKey = () => `evt_${nextKey++}`;
20684
+ nextAssistantId = 1;
20685
+ mkAssistantId = () => nextAssistantId++;
20686
+ }
20687
+ });
20688
+
20408
20689
  // src/ui/onboarding.tsx
20409
20690
  import { useState as useState14, useCallback as useCallback3, useEffect as useEffect8 } from "react";
20410
20691
  import { Box as Box20, Text as Text20, useInput as useInput10 } from "ink";
@@ -20444,6 +20725,14 @@ function Onboarding({ onDone, onCancel }) {
20444
20725
  [onCancel]
20445
20726
  )
20446
20727
  );
20728
+ useInput10(
20729
+ (_input, key) => {
20730
+ if (step !== "cloudAuth") return;
20731
+ if (key.return && cloudAuthStatus?.url) {
20732
+ openBrowser(cloudAuthStatus.url);
20733
+ }
20734
+ }
20735
+ );
20447
20736
  useInput10(
20448
20737
  (_input, key) => {
20449
20738
  if (step !== "mode") return;
@@ -20846,7 +21135,11 @@ function Onboarding({ onDone, onCancel }) {
20846
21135
  step === "cloudAuth" && /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", marginTop: 1, children: [
20847
21136
  /* @__PURE__ */ jsx21(Text20, { bold: true, color: theme.accent, children: "Kimiflare Cloud Authentication" }),
20848
21137
  cloudAuthStatus ? /* @__PURE__ */ jsxs19(Fragment, { children: [
20849
- /* @__PURE__ */ jsx21(Text20, { color: theme.info.color, children: "1. Open this URL in your browser:" }),
21138
+ /* @__PURE__ */ jsxs19(Text20, { color: theme.info.color, children: [
21139
+ "1. Press ",
21140
+ /* @__PURE__ */ jsx21(Text20, { bold: true, color: theme.accent, children: "Enter" }),
21141
+ " to open this URL in your browser:"
21142
+ ] }),
20850
21143
  /* @__PURE__ */ jsx21(Text20, { color: theme.info.color, children: cloudAuthStatus.url }),
20851
21144
  /* @__PURE__ */ jsx21(Box20, { marginTop: 1, children: /* @__PURE__ */ jsx21(Text20, { color: theme.info.color, children: "2. Sign in with GitHub or Email" }) }),
20852
21145
  /* @__PURE__ */ jsx21(Box20, { marginTop: 1, children: /* @__PURE__ */ jsxs19(Text20, { color: theme.info.color, children: [
@@ -20900,7 +21193,7 @@ function Onboarding({ onDone, onCancel }) {
20900
21193
  "\u2022".repeat(apiToken.length)
20901
21194
  ] })
20902
21195
  ] }),
20903
- /* @__PURE__ */ jsxs19(Text20, { color: theme.info.color, children: [
21196
+ !cloudMode && /* @__PURE__ */ jsxs19(Text20, { color: theme.info.color, children: [
20904
21197
  "Model: ",
20905
21198
  model
20906
21199
  ] }),
@@ -20959,6 +21252,7 @@ var init_onboarding = __esm({
20959
21252
  init_key_entry_modal();
20960
21253
  init_registry();
20961
21254
  init_config();
21255
+ init_app_helpers();
20962
21256
  init_theme_context();
20963
21257
  init_ai_gateway_api();
20964
21258
  }
@@ -21082,13 +21376,13 @@ var init_frontmatter = __esm({
21082
21376
  // src/commands/loader.ts
21083
21377
  import { open, realpath as realpath2 } from "fs/promises";
21084
21378
  import { homedir as homedir17 } from "os";
21085
- import { join as join26, relative as relative6, sep as sep2 } from "path";
21379
+ import { join as join27, relative as relative6, sep as sep2 } from "path";
21086
21380
  function projectCommandsDir(cwd = process.cwd()) {
21087
- return join26(cwd, ".kimiflare", "commands");
21381
+ return join27(cwd, ".kimiflare", "commands");
21088
21382
  }
21089
21383
  function globalCommandsDir() {
21090
- const xdg = process.env.XDG_CONFIG_HOME || join26(homedir17(), ".config");
21091
- return join26(xdg, "kimiflare", "commands");
21384
+ const xdg = process.env.XDG_CONFIG_HOME || join27(homedir17(), ".config");
21385
+ return join27(xdg, "kimiflare", "commands");
21092
21386
  }
21093
21387
  async function loadCustomCommands(cwd = process.cwd()) {
21094
21388
  const warnings = [];
@@ -22340,10 +22634,10 @@ var init_wcag = __esm({
22340
22634
 
22341
22635
  // src/ui/theme-loader.ts
22342
22636
  import { readFile as readFile19, readdir as readdir6 } from "fs/promises";
22343
- import { join as join27 } from "path";
22637
+ import { join as join28 } from "path";
22344
22638
  import { homedir as homedir18 } from "os";
22345
22639
  function projectThemesDir(cwd = process.cwd()) {
22346
- return join27(cwd, ".kimiflare", "themes");
22640
+ return join28(cwd, ".kimiflare", "themes");
22347
22641
  }
22348
22642
  function isHexColor(c) {
22349
22643
  return /^#[0-9a-fA-F]{6}$/.test(c);
@@ -22432,7 +22726,7 @@ async function loadThemesFromDir(dir, source) {
22432
22726
  return { themes, errors };
22433
22727
  }
22434
22728
  for (const file of files.filter((f) => f.endsWith(".json"))) {
22435
- const path = join27(dir, file);
22729
+ const path = join28(dir, file);
22436
22730
  let raw;
22437
22731
  try {
22438
22732
  raw = await readFile19(path, "utf-8");
@@ -22581,8 +22875,8 @@ var init_theme_loader = __esm({
22581
22875
  "use strict";
22582
22876
  init_wcag();
22583
22877
  init_theme();
22584
- USER_THEMES_DIR = join27(
22585
- process.env.XDG_CONFIG_HOME || join27(homedir18(), ".config"),
22878
+ USER_THEMES_DIR = join28(
22879
+ process.env.XDG_CONFIG_HOME || join28(homedir18(), ".config"),
22586
22880
  "kimiflare",
22587
22881
  "themes"
22588
22882
  );
@@ -23673,7 +23967,7 @@ var init_command_list = __esm({
23673
23967
  import { useState as useState18 } from "react";
23674
23968
  import { Box as Box28, Text as Text28 } from "ink";
23675
23969
  import SelectInput10 from "ink-select-input";
23676
- import { spawn as spawn4 } from "child_process";
23970
+ import { spawn as spawn5 } from "child_process";
23677
23971
  import { jsx as jsx29, jsxs as jsxs27 } from "react/jsx-runtime";
23678
23972
  function LspWizard({ servers, currentScope, hasProjectDir, onDone, onSave }) {
23679
23973
  const theme = useTheme();
@@ -23687,7 +23981,7 @@ function LspWizard({ servers, currentScope, hasProjectDir, onDone, onSave }) {
23687
23981
  const runInstall = (command) => {
23688
23982
  setInstallState({ status: "running", output: "Installing..." });
23689
23983
  const { shell, args } = getShellCommand();
23690
- const child = spawn4(shell, [...args, command], {
23984
+ const child = spawn5(shell, [...args, command], {
23691
23985
  env: process.env
23692
23986
  });
23693
23987
  let stdout = "";
@@ -23890,2032 +24184,1751 @@ function LspWizard({ servers, currentScope, hasProjectDir, onDone, onSave }) {
23890
24184
  CustomTextInput,
23891
24185
  {
23892
24186
  value: customName,
23893
- onChange: setCustomName,
23894
- onSubmit: (value) => {
23895
- if (value.trim()) {
23896
- setCustomName(value.trim());
23897
- setPage("custom-command");
23898
- }
23899
- }
23900
- }
23901
- )
23902
- ] }),
23903
- /* @__PURE__ */ jsx29(Box28, { marginTop: 1, children: /* @__PURE__ */ jsx29(
23904
- SelectInput10,
23905
- {
23906
- items: [{ label: "\u2190 Back", value: "__back__", key: "__back__" }],
23907
- onSelect: () => setPage("add")
23908
- }
23909
- ) })
23910
- ] });
23911
- }
23912
- if (page === "custom-command") {
23913
- return /* @__PURE__ */ jsxs27(Box28, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
23914
- /* @__PURE__ */ jsx29(Text28, { color: theme.accent, bold: true, children: "Custom LSP Server \u2014 Command" }),
23915
- /* @__PURE__ */ jsx29(Text28, { color: theme.info.color, dimColor: false, children: "Enter the command to start the server (space-separated)." }),
23916
- /* @__PURE__ */ jsxs27(Box28, { marginTop: 1, children: [
23917
- /* @__PURE__ */ jsx29(Text28, { color: theme.accent, children: "\u203A " }),
23918
- /* @__PURE__ */ jsx29(
23919
- CustomTextInput,
23920
- {
23921
- value: customCommand,
23922
- onChange: setCustomCommand,
23923
- onSubmit: (value) => {
23924
- if (value.trim()) {
23925
- setCustomCommand(value.trim());
23926
- handleSaveCustom();
23927
- }
23928
- }
23929
- }
23930
- )
23931
- ] }),
23932
- /* @__PURE__ */ jsx29(Box28, { marginTop: 1, children: /* @__PURE__ */ jsx29(
23933
- SelectInput10,
23934
- {
23935
- items: [{ label: "\u2190 Back", value: "__back__", key: "__back__" }],
23936
- onSelect: () => setPage("custom-name")
23937
- }
23938
- ) })
23939
- ] });
23940
- }
23941
- if (page === "scope") {
23942
- const defaultToProject = hasProjectDir || currentScope === "project";
23943
- const items = [
23944
- {
23945
- label: defaultToProject ? "This project only (\xB7 current)" : "This project only",
23946
- value: "project",
23947
- key: "project"
23948
- },
23949
- {
23950
- label: defaultToProject ? "Global config" : "Global config (\xB7 current)",
23951
- value: "global",
23952
- key: "global"
23953
- },
23954
- { label: "\u2190 Back", value: "__back__", key: "__back__" }
23955
- ];
23956
- return /* @__PURE__ */ jsxs27(Box28, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
23957
- /* @__PURE__ */ jsx29(Text28, { color: theme.accent, bold: true, children: "Save LSP Config" }),
23958
- /* @__PURE__ */ jsx29(Text28, { color: theme.info.color, dimColor: false, children: "Where should this server configuration be saved?" }),
23959
- /* @__PURE__ */ jsx29(Box28, { marginTop: 1, children: /* @__PURE__ */ jsx29(
23960
- SelectInput10,
23961
- {
23962
- items,
23963
- onSelect: (item) => {
23964
- if (item.value === "__back__") {
23965
- setPage("main");
23966
- setPendingServers(null);
23967
- } else {
23968
- handleScopeSelect(item.value);
23969
- }
23970
- }
23971
- }
23972
- ) })
23973
- ] });
23974
- }
23975
- if (page === "edit") {
23976
- const keys = Object.keys(servers);
23977
- if (keys.length === 0) {
23978
- return /* @__PURE__ */ jsxs27(Box28, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
23979
- /* @__PURE__ */ jsx29(Text28, { color: theme.accent, bold: true, children: "Edit LSP Server" }),
23980
- /* @__PURE__ */ jsx29(Text28, { color: theme.info.color, children: "No servers configured." }),
23981
- /* @__PURE__ */ jsx29(Box28, { marginTop: 1, children: /* @__PURE__ */ jsx29(
23982
- SelectInput10,
23983
- {
23984
- items: [{ label: "\u2190 Back", value: "__back__", key: "__back__" }],
23985
- onSelect: () => setPage("main")
23986
- }
23987
- ) })
23988
- ] });
23989
- }
23990
- const items = [
23991
- ...keys.map((k) => {
23992
- const s = servers[k];
23993
- const status = s.enabled !== false ? "enabled" : "disabled";
23994
- return {
23995
- label: `${k.padEnd(16)} ${status} ${s.command.join(" ")}`,
23996
- value: k,
23997
- key: k
23998
- };
23999
- }),
24000
- { label: "\u2190 Back", value: "__back__", key: "__back__" }
24001
- ];
24002
- return /* @__PURE__ */ jsxs27(Box28, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24003
- /* @__PURE__ */ jsx29(Text28, { color: theme.accent, bold: true, children: "Edit LSP Server" }),
24004
- /* @__PURE__ */ jsx29(Text28, { color: theme.info.color, dimColor: false, children: "Select a server to toggle enabled/disabled." }),
24005
- /* @__PURE__ */ jsx29(Box28, { marginTop: 1, children: /* @__PURE__ */ jsx29(
24006
- SelectInput10,
24007
- {
24008
- items,
24009
- onSelect: (item) => {
24010
- if (item.value === "__back__") {
24011
- setPage("main");
24012
- } else {
24013
- handleToggle(item.value);
24014
- }
24015
- }
24016
- }
24017
- ) })
24018
- ] });
24019
- }
24020
- if (page === "delete") {
24021
- const keys = Object.keys(servers);
24022
- if (keys.length === 0) {
24023
- return /* @__PURE__ */ jsxs27(Box28, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24024
- /* @__PURE__ */ jsx29(Text28, { color: theme.accent, bold: true, children: "Delete LSP Server" }),
24025
- /* @__PURE__ */ jsx29(Text28, { color: theme.info.color, children: "No servers configured." }),
24026
- /* @__PURE__ */ jsx29(Box28, { marginTop: 1, children: /* @__PURE__ */ jsx29(
24027
- SelectInput10,
24028
- {
24029
- items: [{ label: "\u2190 Back", value: "__back__", key: "__back__" }],
24030
- onSelect: () => setPage("main")
24031
- }
24032
- ) })
24033
- ] });
24034
- }
24035
- const items = [
24036
- ...keys.map((k) => ({
24037
- label: `${k.padEnd(16)} ${servers[k].command.join(" ")}`,
24038
- value: k,
24039
- key: k
24040
- })),
24041
- { label: "\u2190 Back", value: "__back__", key: "__back__" }
24042
- ];
24043
- return /* @__PURE__ */ jsxs27(Box28, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24044
- /* @__PURE__ */ jsx29(Text28, { color: theme.accent, bold: true, children: "Delete LSP Server" }),
24045
- /* @__PURE__ */ jsx29(Text28, { color: theme.info.color, dimColor: false, children: "Select a server to remove from config." }),
24046
- /* @__PURE__ */ jsx29(Box28, { marginTop: 1, children: /* @__PURE__ */ jsx29(
24047
- SelectInput10,
24048
- {
24049
- items,
24050
- onSelect: (item) => {
24051
- if (item.value === "__back__") {
24052
- setPage("main");
24053
- } else {
24054
- handleDelete(item.value);
24187
+ onChange: setCustomName,
24188
+ onSubmit: (value) => {
24189
+ if (value.trim()) {
24190
+ setCustomName(value.trim());
24191
+ setPage("custom-command");
24192
+ }
24055
24193
  }
24056
24194
  }
24057
- }
24058
- ) })
24059
- ] });
24060
- }
24061
- if (page === "list") {
24062
- const keys = Object.keys(servers);
24063
- return /* @__PURE__ */ jsxs27(Box28, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24064
- /* @__PURE__ */ jsx29(Text28, { color: theme.accent, bold: true, children: "Configured LSP Servers" }),
24065
- keys.length === 0 ? /* @__PURE__ */ jsx29(Text28, { color: theme.info.color, children: "No servers configured." }) : /* @__PURE__ */ jsx29(Box28, { marginTop: 1, flexDirection: "column", children: keys.map((k) => {
24066
- const s = servers[k];
24067
- const status = s.enabled !== false ? "enabled" : "disabled";
24068
- return /* @__PURE__ */ jsx29(Text28, { color: theme.info.color, children: ` ${k.padEnd(16)} ${status} ${s.command.join(" ")}` }, k);
24069
- }) }),
24195
+ )
24196
+ ] }),
24070
24197
  /* @__PURE__ */ jsx29(Box28, { marginTop: 1, children: /* @__PURE__ */ jsx29(
24071
24198
  SelectInput10,
24072
24199
  {
24073
24200
  items: [{ label: "\u2190 Back", value: "__back__", key: "__back__" }],
24074
- onSelect: () => setPage("main")
24201
+ onSelect: () => setPage("add")
24075
24202
  }
24076
24203
  ) })
24077
24204
  ] });
24078
24205
  }
24079
- return null;
24080
- }
24081
- var PRESETS;
24082
- var init_lsp_wizard = __esm({
24083
- "src/ui/lsp-wizard.tsx"() {
24084
- "use strict";
24085
- init_theme_context();
24086
- init_bash();
24087
- init_text_input();
24088
- PRESETS = [
24089
- {
24090
- id: "typescript",
24091
- name: "TypeScript",
24092
- description: "TypeScript and JavaScript support",
24093
- command: ["typescript-language-server", "--stdio"],
24094
- installCommand: "npm install -g typescript-language-server typescript",
24095
- installHint: "Requires Node.js and npm"
24096
- },
24097
- {
24098
- id: "python",
24099
- name: "Python (Pyright)",
24100
- description: "Python type checking and IntelliSense",
24101
- command: ["pyright-langserver", "--stdio"],
24102
- installCommand: "npm install -g pyright",
24103
- installHint: "Requires Node.js and npm (alternative: pip install pyright)"
24104
- },
24105
- {
24106
- id: "rust",
24107
- name: "Rust",
24108
- description: "Rust analyzer for Rust code",
24109
- command: ["rust-analyzer"],
24110
- installCommand: "rustup component add rust-analyzer",
24111
- installHint: "Requires Rust toolchain (rustup)"
24112
- },
24113
- {
24114
- id: "go",
24115
- name: "Go",
24116
- description: "Go language server (gopls)",
24117
- command: ["gopls"],
24118
- installCommand: "go install golang.org/x/tools/gopls@latest",
24119
- installHint: "Requires Go toolchain"
24120
- },
24121
- {
24122
- id: "json",
24123
- name: "JSON",
24124
- description: "JSON language support",
24125
- command: ["vscode-json-language-server", "--stdio"],
24126
- installCommand: "npm install -g vscode-langservers-extracted",
24127
- installHint: "Requires Node.js and npm"
24128
- },
24129
- {
24130
- id: "css",
24131
- name: "CSS",
24132
- description: "CSS/SCSS/Less language support",
24133
- command: ["vscode-css-language-server", "--stdio"],
24134
- installCommand: "npm install -g vscode-langservers-extracted",
24135
- installHint: "Requires Node.js and npm"
24136
- },
24137
- {
24138
- id: "html",
24139
- name: "HTML",
24140
- description: "HTML language support",
24141
- command: ["vscode-html-language-server", "--stdio"],
24142
- installCommand: "npm install -g vscode-langservers-extracted",
24143
- installHint: "Requires Node.js and npm"
24144
- },
24145
- {
24146
- id: "eslint",
24147
- name: "ESLint",
24148
- description: "JavaScript/TypeScript linting",
24149
- command: ["vscode-eslint-language-server", "--stdio"],
24150
- installCommand: "npm install -g vscode-langservers-extracted",
24151
- installHint: "Requires Node.js and npm"
24152
- },
24153
- {
24154
- id: "docker",
24155
- name: "Dockerfile",
24156
- description: "Dockerfile language support",
24157
- command: ["docker-langserver", "--stdio"],
24158
- installCommand: "npm install -g dockerfile-language-server-nodejs",
24159
- installHint: "Requires Node.js and npm"
24160
- },
24161
- {
24162
- id: "yaml",
24163
- name: "YAML",
24164
- description: "YAML language support",
24165
- command: ["yaml-language-server", "--stdio"],
24166
- installCommand: "npm install -g yaml-language-server",
24167
- installHint: "Requires Node.js and npm"
24168
- },
24169
- {
24170
- id: "bash",
24171
- name: "Bash",
24172
- description: "Bash shell script support",
24173
- command: ["bash-language-server", "start"],
24174
- installCommand: "npm install -g bash-language-server",
24175
- installHint: "Requires Node.js and npm"
24176
- },
24177
- {
24178
- id: "lua",
24179
- name: "Lua",
24180
- description: "Lua language support",
24181
- command: ["lua-language-server"],
24182
- installCommand: "brew install lua-language-server (macOS) or see https://luals.github.io",
24183
- installHint: "Install varies by platform \u2014 see https://luals.github.io"
24184
- },
24185
- {
24186
- id: "custom",
24187
- name: "Custom",
24188
- description: "Enter your own language server command",
24189
- command: [],
24190
- installCommand: "",
24191
- installHint: "You will enter the command manually"
24192
- }
24193
- ];
24194
- }
24195
- });
24196
-
24197
- // src/ui/theme-picker.tsx
24198
- import { Box as Box29, Text as Text29 } from "ink";
24199
- import SelectInput11 from "ink-select-input";
24200
- import { jsx as jsx30, jsxs as jsxs28 } from "react/jsx-runtime";
24201
- function PaletteSwatches({ palette }) {
24202
- const colors = [
24203
- palette.primary,
24204
- palette.secondary,
24205
- palette.success,
24206
- palette.error
24207
- ];
24208
- return /* @__PURE__ */ jsx30(Box29, { children: colors.map((c, i) => /* @__PURE__ */ jsx30(Text29, { color: c, children: "\u2588" }, i)) });
24209
- }
24210
- function ThemePicker({ themes, onPick }) {
24211
- const current = useTheme();
24212
- const items = [
24213
- ...themes.map((t) => ({ label: t.label, value: t.name })),
24214
- { label: "< Back", value: "__back__" }
24215
- ];
24216
- return /* @__PURE__ */ jsxs28(Box29, { flexDirection: "column", borderStyle: "round", borderColor: current.accent, paddingX: 1, children: [
24217
- /* @__PURE__ */ jsx30(Text29, { color: current.accent, bold: true, children: "Pick a theme (restart to apply)" }),
24218
- /* @__PURE__ */ jsx30(Box29, { marginTop: 1, children: /* @__PURE__ */ jsx30(
24219
- SelectInput11,
24220
- {
24221
- items,
24222
- onSelect: (item) => {
24223
- if (item.value === "__back__") {
24224
- onPick(null);
24225
- return;
24226
- }
24227
- const t = themes.find((x) => x.name === item.value);
24228
- onPick(t ?? null);
24229
- },
24230
- itemComponent: ({ label, isSelected }) => {
24231
- const t = themes.find((x) => x.label === label);
24232
- const color = t?.accent ?? current.accent;
24233
- return /* @__PURE__ */ jsxs28(Box29, { children: [
24234
- /* @__PURE__ */ jsx30(Text29, { color, bold: isSelected, dimColor: !isSelected, children: label }),
24235
- t && /* @__PURE__ */ jsx30(Box29, { marginLeft: 1, children: /* @__PURE__ */ jsx30(PaletteSwatches, { palette: t.palette }) })
24236
- ] });
24237
- }
24238
- }
24239
- ) })
24240
- ] });
24241
- }
24242
- var init_theme_picker = __esm({
24243
- "src/ui/theme-picker.tsx"() {
24244
- "use strict";
24245
- init_theme_context();
24246
- }
24247
- });
24248
-
24249
- // src/ui/ui-picker.tsx
24250
- import { Box as Box30, Text as Text30 } from "ink";
24251
- import SelectInput12 from "ink-select-input";
24252
- import { jsx as jsx31, jsxs as jsxs29 } from "react/jsx-runtime";
24253
- function UiPicker({ current, onPick }) {
24254
- const theme = useTheme();
24255
- const items = [
24256
- {
24257
- label: "React Ink",
24258
- value: "ink",
24259
- description: "stable \u2014 current default"
24260
- },
24261
- { label: "< Back", value: "__back__", description: "" }
24262
- ];
24263
- return /* @__PURE__ */ jsxs29(Box30, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24264
- /* @__PURE__ */ jsx31(Text30, { color: theme.accent, bold: true, children: "Pick UI engine (takes effect on next launch)" }),
24265
- /* @__PURE__ */ jsx31(Box30, { marginTop: 1, children: /* @__PURE__ */ jsx31(
24266
- SelectInput12,
24267
- {
24268
- items,
24269
- initialIndex: items.findIndex((i) => i.value === current),
24270
- onSelect: (item) => {
24271
- if (item.value === "__back__") {
24272
- onPick(null);
24273
- return;
24206
+ if (page === "custom-command") {
24207
+ return /* @__PURE__ */ jsxs27(Box28, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24208
+ /* @__PURE__ */ jsx29(Text28, { color: theme.accent, bold: true, children: "Custom LSP Server \u2014 Command" }),
24209
+ /* @__PURE__ */ jsx29(Text28, { color: theme.info.color, dimColor: false, children: "Enter the command to start the server (space-separated)." }),
24210
+ /* @__PURE__ */ jsxs27(Box28, { marginTop: 1, children: [
24211
+ /* @__PURE__ */ jsx29(Text28, { color: theme.accent, children: "\u203A " }),
24212
+ /* @__PURE__ */ jsx29(
24213
+ CustomTextInput,
24214
+ {
24215
+ value: customCommand,
24216
+ onChange: setCustomCommand,
24217
+ onSubmit: (value) => {
24218
+ if (value.trim()) {
24219
+ setCustomCommand(value.trim());
24220
+ handleSaveCustom();
24221
+ }
24222
+ }
24274
24223
  }
24275
- onPick(item.value);
24276
- },
24277
- itemComponent: ({ label, isSelected }) => {
24278
- const item = items.find((i) => i.label === label);
24279
- const desc = item?.description ?? "";
24280
- return /* @__PURE__ */ jsxs29(Box30, { children: [
24281
- /* @__PURE__ */ jsx31(Text30, { bold: isSelected, dimColor: !isSelected, children: label }),
24282
- desc ? /* @__PURE__ */ jsx31(Text30, { dimColor: true, children: ` \u2014 ${desc}` }) : null
24283
- ] });
24284
- }
24285
- }
24286
- ) })
24287
- ] });
24288
- }
24289
- var init_ui_picker = __esm({
24290
- "src/ui/ui-picker.tsx"() {
24291
- "use strict";
24292
- init_theme_context();
24224
+ )
24225
+ ] }),
24226
+ /* @__PURE__ */ jsx29(Box28, { marginTop: 1, children: /* @__PURE__ */ jsx29(
24227
+ SelectInput10,
24228
+ {
24229
+ items: [{ label: "\u2190 Back", value: "__back__", key: "__back__" }],
24230
+ onSelect: () => setPage("custom-name")
24231
+ }
24232
+ ) })
24233
+ ] });
24293
24234
  }
24294
- });
24295
-
24296
- // src/ui/mode-picker.tsx
24297
- import { Box as Box31, Text as Text31 } from "ink";
24298
- import SelectInput13 from "ink-select-input";
24299
- import { jsx as jsx32, jsxs as jsxs30 } from "react/jsx-runtime";
24300
- function ModePicker({ current, onPick }) {
24301
- const theme = useTheme();
24302
- const availableModes = MODES;
24303
- const items = availableModes.map((m) => ({
24304
- label: `${m === current ? "\u25CF " : " "}${modeDescription(m)}`,
24305
- value: m,
24306
- key: m
24307
- }));
24308
- return /* @__PURE__ */ jsxs30(Box31, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24309
- /* @__PURE__ */ jsx32(Text31, { color: theme.accent, bold: true, children: "Select mode" }),
24310
- /* @__PURE__ */ jsx32(Text31, { color: theme.info.color, dimColor: false, children: "Arrow keys to navigate, Enter to select, Esc to cancel." }),
24311
- /* @__PURE__ */ jsx32(Box31, { marginTop: 1, children: /* @__PURE__ */ jsx32(
24312
- SelectInput13,
24235
+ if (page === "scope") {
24236
+ const defaultToProject = hasProjectDir || currentScope === "project";
24237
+ const items = [
24313
24238
  {
24314
- items,
24315
- onSelect: (item) => onPick(item.value)
24316
- }
24317
- ) })
24318
- ] });
24319
- }
24320
- var init_mode_picker = __esm({
24321
- "src/ui/mode-picker.tsx"() {
24322
- "use strict";
24323
- init_mode();
24324
- init_theme_context();
24325
- }
24326
- });
24327
-
24328
- // src/ui/shell-picker.tsx
24329
- import { Box as Box32, Text as Text32 } from "ink";
24330
- import SelectInput14 from "ink-select-input";
24331
- import { jsx as jsx33, jsxs as jsxs31 } from "react/jsx-runtime";
24332
- function ShellPicker({ current, onPick }) {
24333
- const theme = useTheme();
24334
- const items = SHELLS.map((s) => ({
24335
- label: `${s.value === (current ?? "auto") ? "\u25CF " : " "}${s.label}`,
24336
- value: s.value,
24337
- key: s.value
24338
- }));
24339
- return /* @__PURE__ */ jsxs31(Box32, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24340
- /* @__PURE__ */ jsx33(Text32, { color: theme.accent, bold: true, children: "Select shell" }),
24341
- /* @__PURE__ */ jsx33(Text32, { color: theme.info.color, dimColor: false, children: "Arrow keys to navigate, Enter to select, Esc to cancel." }),
24342
- /* @__PURE__ */ jsx33(Box32, { marginTop: 1, children: /* @__PURE__ */ jsx33(SelectInput14, { items, onSelect: (item) => onPick(item.value) }) })
24343
- ] });
24344
- }
24345
- var SHELLS;
24346
- var init_shell_picker = __esm({
24347
- "src/ui/shell-picker.tsx"() {
24348
- "use strict";
24349
- init_theme_context();
24350
- SHELLS = [
24351
- { label: "auto \u2014 detect from environment", value: "auto" },
24352
- { label: "bash", value: "bash" },
24353
- { label: "cmd.exe (Windows)", value: "cmd" },
24354
- { label: "PowerShell", value: "powershell" }
24239
+ label: defaultToProject ? "This project only (\xB7 current)" : "This project only",
24240
+ value: "project",
24241
+ key: "project"
24242
+ },
24243
+ {
24244
+ label: defaultToProject ? "Global config" : "Global config (\xB7 current)",
24245
+ value: "global",
24246
+ key: "global"
24247
+ },
24248
+ { label: "\u2190 Back", value: "__back__", key: "__back__" }
24355
24249
  ];
24250
+ return /* @__PURE__ */ jsxs27(Box28, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24251
+ /* @__PURE__ */ jsx29(Text28, { color: theme.accent, bold: true, children: "Save LSP Config" }),
24252
+ /* @__PURE__ */ jsx29(Text28, { color: theme.info.color, dimColor: false, children: "Where should this server configuration be saved?" }),
24253
+ /* @__PURE__ */ jsx29(Box28, { marginTop: 1, children: /* @__PURE__ */ jsx29(
24254
+ SelectInput10,
24255
+ {
24256
+ items,
24257
+ onSelect: (item) => {
24258
+ if (item.value === "__back__") {
24259
+ setPage("main");
24260
+ setPendingServers(null);
24261
+ } else {
24262
+ handleScopeSelect(item.value);
24263
+ }
24264
+ }
24265
+ }
24266
+ ) })
24267
+ ] });
24356
24268
  }
24357
- });
24358
-
24359
- // src/ui/memory-picker.tsx
24360
- import { useState as useState19, useCallback as useCallback5 } from "react";
24361
- import { Box as Box33, Text as Text33, useInput as useInput13 } from "ink";
24362
- import SelectInput15 from "ink-select-input";
24363
- import { jsx as jsx34, jsxs as jsxs32 } from "react/jsx-runtime";
24364
- function MemoryPicker({ enabled, memoryManager, onAction, onDone }) {
24365
- const theme = useTheme();
24366
- const [screen, setScreen] = useState19("menu");
24367
- const [query, setQuery] = useState19("");
24368
- const [results, setResults] = useState19([]);
24369
- const [searching, setSearching] = useState19(false);
24370
- const [searched, setSearched] = useState19(false);
24371
- useInput13((_, key) => {
24372
- if (key.escape) {
24373
- if (screen === "search" || screen === "confirm-clear") {
24374
- setScreen("menu");
24375
- setQuery("");
24376
- setResults([]);
24377
- setSearched(false);
24378
- } else {
24379
- onDone();
24380
- }
24269
+ if (page === "edit") {
24270
+ const keys = Object.keys(servers);
24271
+ if (keys.length === 0) {
24272
+ return /* @__PURE__ */ jsxs27(Box28, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24273
+ /* @__PURE__ */ jsx29(Text28, { color: theme.accent, bold: true, children: "Edit LSP Server" }),
24274
+ /* @__PURE__ */ jsx29(Text28, { color: theme.info.color, children: "No servers configured." }),
24275
+ /* @__PURE__ */ jsx29(Box28, { marginTop: 1, children: /* @__PURE__ */ jsx29(
24276
+ SelectInput10,
24277
+ {
24278
+ items: [{ label: "\u2190 Back", value: "__back__", key: "__back__" }],
24279
+ onSelect: () => setPage("main")
24280
+ }
24281
+ ) })
24282
+ ] });
24381
24283
  }
24382
- });
24383
- const runSearch = useCallback5(
24384
- async (q) => {
24385
- if (!memoryManager || !q.trim()) return;
24386
- setSearching(true);
24387
- try {
24388
- const res = await memoryManager.recall({ text: q.trim(), limit: 10 });
24389
- setResults(res);
24390
- } catch {
24391
- setResults([]);
24392
- } finally {
24393
- setSearching(false);
24394
- setSearched(true);
24395
- }
24396
- },
24397
- [memoryManager]
24398
- );
24399
- if (screen === "confirm-clear") {
24400
- return /* @__PURE__ */ jsxs32(Box33, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24401
- /* @__PURE__ */ jsx34(Text33, { color: theme.accent, bold: true, children: "\u26A0\uFE0F Clear All Memories" }),
24402
- /* @__PURE__ */ jsx34(Text33, { color: theme.info.color, children: "This will permanently delete every stored memory. This cannot be undone." }),
24403
- /* @__PURE__ */ jsx34(Box33, { marginTop: 1, children: /* @__PURE__ */ jsx34(
24404
- SelectInput15,
24284
+ const items = [
24285
+ ...keys.map((k) => {
24286
+ const s = servers[k];
24287
+ const status = s.enabled !== false ? "enabled" : "disabled";
24288
+ return {
24289
+ label: `${k.padEnd(16)} ${status} ${s.command.join(" ")}`,
24290
+ value: k,
24291
+ key: k
24292
+ };
24293
+ }),
24294
+ { label: "\u2190 Back", value: "__back__", key: "__back__" }
24295
+ ];
24296
+ return /* @__PURE__ */ jsxs27(Box28, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24297
+ /* @__PURE__ */ jsx29(Text28, { color: theme.accent, bold: true, children: "Edit LSP Server" }),
24298
+ /* @__PURE__ */ jsx29(Text28, { color: theme.info.color, dimColor: false, children: "Select a server to toggle enabled/disabled." }),
24299
+ /* @__PURE__ */ jsx29(Box28, { marginTop: 1, children: /* @__PURE__ */ jsx29(
24300
+ SelectInput10,
24405
24301
  {
24406
- items: [
24407
- { label: "Yes, clear everything", value: "yes", key: "yes" },
24408
- { label: "No, keep my memories", value: "no", key: "no" }
24409
- ],
24302
+ items,
24410
24303
  onSelect: (item) => {
24411
- if (item.value === "yes") {
24412
- onAction("clear");
24304
+ if (item.value === "__back__") {
24305
+ setPage("main");
24306
+ } else {
24307
+ handleToggle(item.value);
24413
24308
  }
24414
- onDone();
24415
24309
  }
24416
24310
  }
24417
24311
  ) })
24418
24312
  ] });
24419
24313
  }
24420
- if (screen === "search") {
24421
- return /* @__PURE__ */ jsxs32(Box33, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24422
- /* @__PURE__ */ jsx34(Text33, { color: theme.accent, bold: true, children: "Search Memories" }),
24423
- /* @__PURE__ */ jsx34(Text33, { color: theme.info.color, dimColor: false, children: "Type a query and press Enter. Esc to go back." }),
24424
- /* @__PURE__ */ jsx34(Box33, { marginTop: 1, children: /* @__PURE__ */ jsx34(
24425
- CustomTextInput,
24314
+ if (page === "delete") {
24315
+ const keys = Object.keys(servers);
24316
+ if (keys.length === 0) {
24317
+ return /* @__PURE__ */ jsxs27(Box28, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24318
+ /* @__PURE__ */ jsx29(Text28, { color: theme.accent, bold: true, children: "Delete LSP Server" }),
24319
+ /* @__PURE__ */ jsx29(Text28, { color: theme.info.color, children: "No servers configured." }),
24320
+ /* @__PURE__ */ jsx29(Box28, { marginTop: 1, children: /* @__PURE__ */ jsx29(
24321
+ SelectInput10,
24322
+ {
24323
+ items: [{ label: "\u2190 Back", value: "__back__", key: "__back__" }],
24324
+ onSelect: () => setPage("main")
24325
+ }
24326
+ ) })
24327
+ ] });
24328
+ }
24329
+ const items = [
24330
+ ...keys.map((k) => ({
24331
+ label: `${k.padEnd(16)} ${servers[k].command.join(" ")}`,
24332
+ value: k,
24333
+ key: k
24334
+ })),
24335
+ { label: "\u2190 Back", value: "__back__", key: "__back__" }
24336
+ ];
24337
+ return /* @__PURE__ */ jsxs27(Box28, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24338
+ /* @__PURE__ */ jsx29(Text28, { color: theme.accent, bold: true, children: "Delete LSP Server" }),
24339
+ /* @__PURE__ */ jsx29(Text28, { color: theme.info.color, dimColor: false, children: "Select a server to remove from config." }),
24340
+ /* @__PURE__ */ jsx29(Box28, { marginTop: 1, children: /* @__PURE__ */ jsx29(
24341
+ SelectInput10,
24426
24342
  {
24427
- value: query,
24428
- onChange: setQuery,
24429
- onSubmit: (q) => runSearch(q),
24430
- focus: true
24431
- }
24432
- ) }),
24433
- searching && /* @__PURE__ */ jsx34(Box33, { marginTop: 1, children: /* @__PURE__ */ jsx34(Text33, { color: theme.info.color, children: "Searching\u2026" }) }),
24434
- searched && !searching && results.length === 0 && /* @__PURE__ */ jsx34(Box33, { marginTop: 1, children: /* @__PURE__ */ jsx34(Text33, { color: theme.info.color, children: "No memories found." }) }),
24435
- results.length > 0 && /* @__PURE__ */ jsxs32(Box33, { flexDirection: "column", marginTop: 1, children: [
24436
- /* @__PURE__ */ jsxs32(Text33, { color: theme.accent, bold: true, children: [
24437
- "Results (",
24438
- results.length,
24439
- ")"
24440
- ] }),
24441
- results.map((r, i) => /* @__PURE__ */ jsxs32(Box33, { flexDirection: "column", marginTop: 1, children: [
24442
- /* @__PURE__ */ jsxs32(Text33, { color: theme.info.color, dimColor: false, children: [
24443
- "#",
24444
- i + 1,
24445
- " \xB7 ",
24446
- r.memory.category,
24447
- " \xB7 importance ",
24448
- r.memory.importance
24449
- ] }),
24450
- /* @__PURE__ */ jsx34(Text33, { children: r.memory.content.length > 120 ? r.memory.content.slice(0, 120) + "\u2026" : r.memory.content }),
24451
- /* @__PURE__ */ jsxs32(Text33, { color: theme.info.color, dimColor: false, children: [
24452
- "score: ",
24453
- r.combinedScore.toFixed(3),
24454
- " \xB7 ",
24455
- new Date(r.memory.createdAt).toLocaleDateString()
24456
- ] })
24457
- ] }, i))
24458
- ] })
24343
+ items,
24344
+ onSelect: (item) => {
24345
+ if (item.value === "__back__") {
24346
+ setPage("main");
24347
+ } else {
24348
+ handleDelete(item.value);
24349
+ }
24350
+ }
24351
+ }
24352
+ ) })
24459
24353
  ] });
24460
24354
  }
24461
- const items = [
24462
- { label: enabled ? "\u25CF Disable memory" : "\u25CF Enable memory", value: enabled ? "off" : "on", key: "toggle" },
24463
- { label: " Show memory stats", value: "stats", key: "stats" },
24464
- { label: " Clear all memories", value: "clear", key: "clear" },
24465
- { label: " Search memories\u2026", value: "search", key: "search" },
24466
- { label: " (close)", value: "__close__", key: "close" }
24467
- ];
24468
- return /* @__PURE__ */ jsxs32(Box33, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24469
- /* @__PURE__ */ jsx34(Text33, { color: theme.accent, bold: true, children: "Memory" }),
24470
- /* @__PURE__ */ jsx34(Text33, { color: theme.info.color, dimColor: false, children: "Arrow keys to navigate, Enter to select, Esc to close." }),
24471
- /* @__PURE__ */ jsx34(Box33, { marginTop: 1, children: /* @__PURE__ */ jsx34(
24472
- SelectInput15,
24473
- {
24474
- items,
24475
- onSelect: (item) => {
24476
- if (item.value === "__close__") {
24477
- onDone();
24478
- } else if (item.value === "clear") {
24479
- setScreen("confirm-clear");
24480
- } else if (item.value === "search") {
24481
- setScreen("search");
24482
- setQuery("");
24483
- setResults([]);
24484
- setSearched(false);
24485
- } else {
24486
- onAction(item.value);
24487
- onDone();
24488
- }
24355
+ if (page === "list") {
24356
+ const keys = Object.keys(servers);
24357
+ return /* @__PURE__ */ jsxs27(Box28, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24358
+ /* @__PURE__ */ jsx29(Text28, { color: theme.accent, bold: true, children: "Configured LSP Servers" }),
24359
+ keys.length === 0 ? /* @__PURE__ */ jsx29(Text28, { color: theme.info.color, children: "No servers configured." }) : /* @__PURE__ */ jsx29(Box28, { marginTop: 1, flexDirection: "column", children: keys.map((k) => {
24360
+ const s = servers[k];
24361
+ const status = s.enabled !== false ? "enabled" : "disabled";
24362
+ return /* @__PURE__ */ jsx29(Text28, { color: theme.info.color, children: ` ${k.padEnd(16)} ${status} ${s.command.join(" ")}` }, k);
24363
+ }) }),
24364
+ /* @__PURE__ */ jsx29(Box28, { marginTop: 1, children: /* @__PURE__ */ jsx29(
24365
+ SelectInput10,
24366
+ {
24367
+ items: [{ label: "\u2190 Back", value: "__back__", key: "__back__" }],
24368
+ onSelect: () => setPage("main")
24489
24369
  }
24490
- }
24491
- ) })
24492
- ] });
24370
+ ) })
24371
+ ] });
24372
+ }
24373
+ return null;
24493
24374
  }
24494
- var init_memory_picker = __esm({
24495
- "src/ui/memory-picker.tsx"() {
24375
+ var PRESETS;
24376
+ var init_lsp_wizard = __esm({
24377
+ "src/ui/lsp-wizard.tsx"() {
24496
24378
  "use strict";
24497
- init_text_input();
24498
24379
  init_theme_context();
24380
+ init_bash();
24381
+ init_text_input();
24382
+ PRESETS = [
24383
+ {
24384
+ id: "typescript",
24385
+ name: "TypeScript",
24386
+ description: "TypeScript and JavaScript support",
24387
+ command: ["typescript-language-server", "--stdio"],
24388
+ installCommand: "npm install -g typescript-language-server typescript",
24389
+ installHint: "Requires Node.js and npm"
24390
+ },
24391
+ {
24392
+ id: "python",
24393
+ name: "Python (Pyright)",
24394
+ description: "Python type checking and IntelliSense",
24395
+ command: ["pyright-langserver", "--stdio"],
24396
+ installCommand: "npm install -g pyright",
24397
+ installHint: "Requires Node.js and npm (alternative: pip install pyright)"
24398
+ },
24399
+ {
24400
+ id: "rust",
24401
+ name: "Rust",
24402
+ description: "Rust analyzer for Rust code",
24403
+ command: ["rust-analyzer"],
24404
+ installCommand: "rustup component add rust-analyzer",
24405
+ installHint: "Requires Rust toolchain (rustup)"
24406
+ },
24407
+ {
24408
+ id: "go",
24409
+ name: "Go",
24410
+ description: "Go language server (gopls)",
24411
+ command: ["gopls"],
24412
+ installCommand: "go install golang.org/x/tools/gopls@latest",
24413
+ installHint: "Requires Go toolchain"
24414
+ },
24415
+ {
24416
+ id: "json",
24417
+ name: "JSON",
24418
+ description: "JSON language support",
24419
+ command: ["vscode-json-language-server", "--stdio"],
24420
+ installCommand: "npm install -g vscode-langservers-extracted",
24421
+ installHint: "Requires Node.js and npm"
24422
+ },
24423
+ {
24424
+ id: "css",
24425
+ name: "CSS",
24426
+ description: "CSS/SCSS/Less language support",
24427
+ command: ["vscode-css-language-server", "--stdio"],
24428
+ installCommand: "npm install -g vscode-langservers-extracted",
24429
+ installHint: "Requires Node.js and npm"
24430
+ },
24431
+ {
24432
+ id: "html",
24433
+ name: "HTML",
24434
+ description: "HTML language support",
24435
+ command: ["vscode-html-language-server", "--stdio"],
24436
+ installCommand: "npm install -g vscode-langservers-extracted",
24437
+ installHint: "Requires Node.js and npm"
24438
+ },
24439
+ {
24440
+ id: "eslint",
24441
+ name: "ESLint",
24442
+ description: "JavaScript/TypeScript linting",
24443
+ command: ["vscode-eslint-language-server", "--stdio"],
24444
+ installCommand: "npm install -g vscode-langservers-extracted",
24445
+ installHint: "Requires Node.js and npm"
24446
+ },
24447
+ {
24448
+ id: "docker",
24449
+ name: "Dockerfile",
24450
+ description: "Dockerfile language support",
24451
+ command: ["docker-langserver", "--stdio"],
24452
+ installCommand: "npm install -g dockerfile-language-server-nodejs",
24453
+ installHint: "Requires Node.js and npm"
24454
+ },
24455
+ {
24456
+ id: "yaml",
24457
+ name: "YAML",
24458
+ description: "YAML language support",
24459
+ command: ["yaml-language-server", "--stdio"],
24460
+ installCommand: "npm install -g yaml-language-server",
24461
+ installHint: "Requires Node.js and npm"
24462
+ },
24463
+ {
24464
+ id: "bash",
24465
+ name: "Bash",
24466
+ description: "Bash shell script support",
24467
+ command: ["bash-language-server", "start"],
24468
+ installCommand: "npm install -g bash-language-server",
24469
+ installHint: "Requires Node.js and npm"
24470
+ },
24471
+ {
24472
+ id: "lua",
24473
+ name: "Lua",
24474
+ description: "Lua language support",
24475
+ command: ["lua-language-server"],
24476
+ installCommand: "brew install lua-language-server (macOS) or see https://luals.github.io",
24477
+ installHint: "Install varies by platform \u2014 see https://luals.github.io"
24478
+ },
24479
+ {
24480
+ id: "custom",
24481
+ name: "Custom",
24482
+ description: "Enter your own language server command",
24483
+ command: [],
24484
+ installCommand: "",
24485
+ installHint: "You will enter the command manually"
24486
+ }
24487
+ ];
24499
24488
  }
24500
24489
  });
24501
24490
 
24502
- // src/ui/gateway-picker.tsx
24503
- import { Box as Box34, Text as Text34 } from "ink";
24504
- import SelectInput16 from "ink-select-input";
24505
- import { jsx as jsx35, jsxs as jsxs33 } from "react/jsx-runtime";
24506
- function GatewayPicker({ gatewayId, skipCache, collectLogs, metadataCount, onAction, onDone }) {
24507
- const theme = useTheme();
24491
+ // src/ui/theme-picker.tsx
24492
+ import { Box as Box29, Text as Text29 } from "ink";
24493
+ import SelectInput11 from "ink-select-input";
24494
+ import { jsx as jsx30, jsxs as jsxs28 } from "react/jsx-runtime";
24495
+ function PaletteSwatches({ palette }) {
24496
+ const colors = [
24497
+ palette.primary,
24498
+ palette.secondary,
24499
+ palette.success,
24500
+ palette.error
24501
+ ];
24502
+ return /* @__PURE__ */ jsx30(Box29, { children: colors.map((c, i) => /* @__PURE__ */ jsx30(Text29, { color: c, children: "\u2588" }, i)) });
24503
+ }
24504
+ function ThemePicker({ themes, onPick }) {
24505
+ const current = useTheme();
24508
24506
  const items = [
24509
- { label: ` gatewayId: ${gatewayId ?? "(not set)"}`, value: "__label_id", key: "label_id" },
24510
- { label: " \u2192 Set gateway ID\u2026", value: "set_id", key: "set_id" }
24507
+ ...themes.map((t) => ({ label: t.label, value: t.name })),
24508
+ { label: "< Back", value: "__back__" }
24511
24509
  ];
24512
- if (gatewayId) {
24513
- items.push({ label: " \u2192 Disable gateway", value: "off", key: "off" });
24514
- items.push({
24515
- label: ` skipCache: ${skipCache ? "true" : "false"}`,
24516
- value: "__label_skip",
24517
- key: "label_skip"
24518
- });
24519
- items.push({
24520
- label: ` \u2192 Toggle skip-cache`,
24521
- value: "toggle_skip",
24522
- key: "toggle_skip"
24523
- });
24524
- items.push({
24525
- label: ` collectLogs: ${collectLogs ? "true" : "false"}`,
24526
- value: "__label_logs",
24527
- key: "label_logs"
24528
- });
24529
- items.push({
24530
- label: ` \u2192 Toggle collect-logs`,
24531
- value: "toggle_logs",
24532
- key: "toggle_logs"
24533
- });
24534
- items.push({
24535
- label: ` metadata: ${metadataCount} key${metadataCount === 1 ? "" : "s"}`,
24536
- value: "__label_meta",
24537
- key: "label_meta"
24538
- });
24539
- items.push({ label: " \u2192 Clear metadata", value: "clear_meta", key: "clear_meta" });
24540
- items.push({ label: " \u2192 Set cache TTL\u2026", value: "set_ttl", key: "set_ttl" });
24541
- items.push({ label: " \u2192 Add metadata\u2026", value: "add_meta", key: "add_meta" });
24542
- }
24543
- items.push({ label: " (close)", value: "__close__", key: "close" });
24544
- const selectable = items.filter((i) => !i.value.startsWith("__label_"));
24545
- return /* @__PURE__ */ jsxs33(Box34, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24546
- /* @__PURE__ */ jsx35(Text34, { color: theme.accent, bold: true, children: "AI Gateway" }),
24547
- /* @__PURE__ */ jsx35(Text34, { color: theme.info.color, dimColor: false, children: "Arrow keys to navigate, Enter to select, Esc to close." }),
24548
- /* @__PURE__ */ jsx35(Box34, { marginTop: 1, children: /* @__PURE__ */ jsx35(
24549
- SelectInput16,
24510
+ return /* @__PURE__ */ jsxs28(Box29, { flexDirection: "column", borderStyle: "round", borderColor: current.accent, paddingX: 1, children: [
24511
+ /* @__PURE__ */ jsx30(Text29, { color: current.accent, bold: true, children: "Pick a theme (restart to apply)" }),
24512
+ /* @__PURE__ */ jsx30(Box29, { marginTop: 1, children: /* @__PURE__ */ jsx30(
24513
+ SelectInput11,
24550
24514
  {
24551
- items: selectable,
24515
+ items,
24552
24516
  onSelect: (item) => {
24553
- if (item.value === "__close__") {
24554
- onDone();
24555
- } else {
24556
- onAction(item.value);
24517
+ if (item.value === "__back__") {
24518
+ onPick(null);
24519
+ return;
24557
24520
  }
24521
+ const t = themes.find((x) => x.name === item.value);
24522
+ onPick(t ?? null);
24523
+ },
24524
+ itemComponent: ({ label, isSelected }) => {
24525
+ const t = themes.find((x) => x.label === label);
24526
+ const color = t?.accent ?? current.accent;
24527
+ return /* @__PURE__ */ jsxs28(Box29, { children: [
24528
+ /* @__PURE__ */ jsx30(Text29, { color, bold: isSelected, dimColor: !isSelected, children: label }),
24529
+ t && /* @__PURE__ */ jsx30(Box29, { marginLeft: 1, children: /* @__PURE__ */ jsx30(PaletteSwatches, { palette: t.palette }) })
24530
+ ] });
24558
24531
  }
24559
24532
  }
24560
24533
  ) })
24561
24534
  ] });
24562
24535
  }
24563
- var init_gateway_picker = __esm({
24564
- "src/ui/gateway-picker.tsx"() {
24536
+ var init_theme_picker = __esm({
24537
+ "src/ui/theme-picker.tsx"() {
24565
24538
  "use strict";
24566
24539
  init_theme_context();
24567
24540
  }
24568
24541
  });
24569
24542
 
24570
- // src/ui/skills-picker.tsx
24571
- import { Box as Box35, Text as Text35 } from "ink";
24572
- import SelectInput17 from "ink-select-input";
24573
- import { jsx as jsx36, jsxs as jsxs34 } from "react/jsx-runtime";
24574
- function SkillsPicker({ onAction, onDone }) {
24543
+ // src/ui/ui-picker.tsx
24544
+ import { Box as Box30, Text as Text30 } from "ink";
24545
+ import SelectInput12 from "ink-select-input";
24546
+ import { jsx as jsx31, jsxs as jsxs29 } from "react/jsx-runtime";
24547
+ function UiPicker({ current, onPick }) {
24575
24548
  const theme = useTheme();
24576
24549
  const items = [
24577
- { label: " List skills", value: "list", key: "list" },
24578
- { label: " Add skill\u2026", value: "add", key: "add" },
24579
- { label: " Edit skill\u2026", value: "edit", key: "edit" },
24580
- { label: " Delete skill\u2026", value: "delete", key: "delete" },
24581
- { label: " Enable skill\u2026", value: "enable", key: "enable" },
24582
- { label: " Disable skill\u2026", value: "disable", key: "disable" },
24583
- { label: " (close)", value: "__close__", key: "close" }
24550
+ {
24551
+ label: "React Ink",
24552
+ value: "ink",
24553
+ description: "stable \u2014 current default"
24554
+ },
24555
+ { label: "< Back", value: "__back__", description: "" }
24584
24556
  ];
24585
- return /* @__PURE__ */ jsxs34(Box35, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24586
- /* @__PURE__ */ jsx36(Text35, { color: theme.accent, bold: true, children: "Skills" }),
24587
- /* @__PURE__ */ jsx36(Text35, { color: theme.info.color, dimColor: false, children: "Arrow keys to navigate, Enter to select, Esc to close." }),
24588
- /* @__PURE__ */ jsx36(Box35, { marginTop: 1, children: /* @__PURE__ */ jsx36(
24589
- SelectInput17,
24557
+ return /* @__PURE__ */ jsxs29(Box30, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24558
+ /* @__PURE__ */ jsx31(Text30, { color: theme.accent, bold: true, children: "Pick UI engine (takes effect on next launch)" }),
24559
+ /* @__PURE__ */ jsx31(Box30, { marginTop: 1, children: /* @__PURE__ */ jsx31(
24560
+ SelectInput12,
24590
24561
  {
24591
24562
  items,
24563
+ initialIndex: items.findIndex((i) => i.value === current),
24592
24564
  onSelect: (item) => {
24593
- if (item.value === "__close__") {
24594
- onDone();
24595
- } else {
24596
- onAction(item.value);
24565
+ if (item.value === "__back__") {
24566
+ onPick(null);
24567
+ return;
24597
24568
  }
24569
+ onPick(item.value);
24570
+ },
24571
+ itemComponent: ({ label, isSelected }) => {
24572
+ const item = items.find((i) => i.label === label);
24573
+ const desc = item?.description ?? "";
24574
+ return /* @__PURE__ */ jsxs29(Box30, { children: [
24575
+ /* @__PURE__ */ jsx31(Text30, { bold: isSelected, dimColor: !isSelected, children: label }),
24576
+ desc ? /* @__PURE__ */ jsx31(Text30, { dimColor: true, children: ` \u2014 ${desc}` }) : null
24577
+ ] });
24598
24578
  }
24599
24579
  }
24600
24580
  ) })
24601
24581
  ] });
24602
24582
  }
24603
- var init_skills_picker = __esm({
24604
- "src/ui/skills-picker.tsx"() {
24583
+ var init_ui_picker = __esm({
24584
+ "src/ui/ui-picker.tsx"() {
24605
24585
  "use strict";
24606
24586
  init_theme_context();
24607
24587
  }
24608
24588
  });
24609
24589
 
24610
- // src/remote/worker-client.ts
24611
- var worker_client_exports = {};
24612
- __export(worker_client_exports, {
24613
- cancelRemoteSession: () => cancelRemoteSession,
24614
- getRemoteStatus: () => getRemoteStatus,
24615
- startRemoteSession: () => startRemoteSession,
24616
- streamRemoteProgress: () => streamRemoteProgress
24617
- });
24618
- async function startRemoteSession(opts2) {
24619
- const workerUrl = opts2.cfg.remoteWorkerUrl;
24620
- if (!workerUrl) {
24621
- throw new Error("Remote worker URL not configured. Set remoteWorkerUrl in config.");
24622
- }
24623
- const githubToken = opts2.cfg.githubOAuthToken;
24624
- if (!githubToken) {
24625
- throw new Error("GitHub token not found. Run `kimiflare auth github` first.");
24626
- }
24627
- const res = await fetch(`${workerUrl}/remote/start`, {
24628
- method: "POST",
24629
- headers: {
24630
- "Content-Type": "application/json",
24631
- Authorization: `Bearer ${opts2.cfg.remoteAuthSecret ?? ""}`
24632
- },
24633
- body: JSON.stringify({
24634
- prompt: opts2.prompt,
24635
- repo: opts2.repo,
24636
- githubToken,
24637
- accountId: opts2.cfg.accountId,
24638
- apiToken: opts2.cfg.apiToken,
24639
- model: opts2.cfg.model,
24640
- reasoningEffort: opts2.cfg.reasoningEffort,
24641
- ttlMinutes: opts2.ttlMinutes ?? opts2.cfg.remoteTtlMinutes,
24642
- tokensBudget: opts2.tokensBudget ?? opts2.cfg.remoteMaxInputTokens
24643
- })
24644
- });
24645
- if (!res.ok) {
24646
- const text = await res.text();
24647
- throw new Error(`Failed to start remote session: ${res.status} ${text}`);
24648
- }
24649
- const data = await res.json();
24650
- await saveRemoteSession({
24651
- sessionId: data.sessionId,
24652
- prompt: opts2.prompt,
24653
- repo: `${opts2.repo.owner}/${opts2.repo.name}`,
24654
- workerUrl,
24655
- status: "running",
24656
- branch: `kimiflare/remote/${data.sessionId}`,
24657
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
24658
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
24659
- });
24660
- return data;
24661
- }
24662
- async function* streamRemoteProgress(workerUrl, sessionId, signal) {
24663
- const res = await fetch(`${workerUrl}/remote/stream/${sessionId}`, { signal });
24664
- if (!res.ok) {
24665
- throw new Error(`Failed to connect to stream: ${res.status}`);
24666
- }
24667
- if (!res.body) {
24668
- throw new Error("No response body");
24669
- }
24670
- for await (const line of readSSE(res.body, signal)) {
24671
- try {
24672
- yield JSON.parse(line);
24673
- } catch {
24674
- }
24675
- }
24676
- }
24677
- async function getRemoteStatus(workerUrl, sessionId, authSecret) {
24678
- const res = await fetch(`${workerUrl}/remote/status/${sessionId}`, {
24679
- headers: authSecret ? { Authorization: `Bearer ${authSecret}` } : {}
24680
- });
24681
- if (!res.ok) {
24682
- const text = await res.text();
24683
- throw new Error(`Failed to get status: ${res.status} ${text}`);
24684
- }
24685
- return res.json();
24686
- }
24687
- async function cancelRemoteSession(workerUrl, sessionId, authSecret) {
24688
- const res = await fetch(`${workerUrl}/remote/cancel/${sessionId}`, {
24689
- method: "POST",
24690
- headers: authSecret ? { Authorization: `Bearer ${authSecret}` } : {}
24691
- });
24692
- if (!res.ok) {
24693
- const text = await res.text();
24694
- throw new Error(`Failed to cancel session: ${res.status} ${text}`);
24695
- }
24696
- }
24697
- var init_worker_client = __esm({
24698
- "src/remote/worker-client.ts"() {
24699
- "use strict";
24700
- init_session_store();
24701
- init_sse();
24702
- }
24703
- });
24704
-
24705
- // src/ui/remote-dashboard.tsx
24706
- import { useEffect as useEffect10, useState as useState20 } from "react";
24707
- import { Box as Box36, Text as Text36, useInput as useInput14 } from "ink";
24708
- import SelectInput18 from "ink-select-input";
24709
- import { jsx as jsx37, jsxs as jsxs35 } from "react/jsx-runtime";
24710
- function RemoteDashboard({ onSelect, onCancel }) {
24590
+ // src/ui/mode-picker.tsx
24591
+ import { Box as Box31, Text as Text31 } from "ink";
24592
+ import SelectInput13 from "ink-select-input";
24593
+ import { jsx as jsx32, jsxs as jsxs30 } from "react/jsx-runtime";
24594
+ function ModePicker({ current, onPick }) {
24711
24595
  const theme = useTheme();
24712
- const [sessions, setSessions] = useState20([]);
24713
- const [loading, setLoading] = useState20(true);
24714
- const [error, setError] = useState20(null);
24715
- const [refreshing, setRefreshing] = useState20(false);
24716
- useEffect10(() => {
24717
- loadSessions();
24718
- }, []);
24719
- async function loadSessions() {
24720
- try {
24721
- setRefreshing(true);
24722
- const list = await listRemoteSessions();
24723
- const updated = await Promise.all(
24724
- list.map(async (s) => {
24725
- if (s.status === "running" || s.status === "pending") {
24726
- try {
24727
- const status = await getRemoteStatus(s.workerUrl, s.sessionId);
24728
- return {
24729
- ...s,
24730
- status: status.status,
24731
- prUrl: status.prUrl ?? s.prUrl,
24732
- tokensUsed: status.tokensUsed ?? s.tokensUsed,
24733
- tokensBudget: status.tokensBudget ?? s.tokensBudget,
24734
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
24735
- };
24736
- } catch {
24737
- return s;
24738
- }
24739
- }
24740
- return s;
24741
- })
24742
- );
24743
- setSessions(updated.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()));
24744
- setError(null);
24745
- } catch (err) {
24746
- setError(err instanceof Error ? err.message : String(err));
24747
- } finally {
24748
- setLoading(false);
24749
- setRefreshing(false);
24750
- }
24751
- }
24752
- useInput14((input, key) => {
24753
- if (input === "r" || input === "R") {
24754
- void loadSessions();
24755
- }
24756
- if (key.escape && onCancel) {
24757
- onCancel();
24758
- }
24759
- });
24760
- const items = sessions.map((s) => ({
24761
- label: formatSessionLine(s),
24762
- value: s.sessionId
24596
+ const availableModes = MODES;
24597
+ const items = availableModes.map((m) => ({
24598
+ label: `${m === current ? "\u25CF " : " "}${modeDescription(m)}`,
24599
+ value: m,
24600
+ key: m
24763
24601
  }));
24764
- if (loading) {
24765
- return /* @__PURE__ */ jsx37(Box36, { flexDirection: "column", padding: 1, children: /* @__PURE__ */ jsx37(Text36, { color: theme.accent, children: "Loading remote sessions..." }) });
24766
- }
24767
- if (error) {
24768
- return /* @__PURE__ */ jsxs35(Box36, { flexDirection: "column", padding: 1, children: [
24769
- /* @__PURE__ */ jsxs35(Text36, { color: theme.error, children: [
24770
- "Error: ",
24771
- error
24772
- ] }),
24773
- /* @__PURE__ */ jsx37(Text36, { dimColor: true, children: "Press R to retry, Esc to close" })
24774
- ] });
24775
- }
24776
- if (sessions.length === 0) {
24777
- return /* @__PURE__ */ jsxs35(Box36, { flexDirection: "column", padding: 1, children: [
24778
- /* @__PURE__ */ jsx37(Text36, { color: theme.accent, children: "No remote sessions yet." }),
24779
- /* @__PURE__ */ jsx37(Text36, { dimColor: true, children: "Type /remote <prompt> to start one." }),
24780
- /* @__PURE__ */ jsx37(Text36, { dimColor: true, children: "Press Esc to close" })
24781
- ] });
24782
- }
24783
- return /* @__PURE__ */ jsxs35(Box36, { flexDirection: "column", padding: 1, children: [
24784
- /* @__PURE__ */ jsxs35(Text36, { bold: true, color: theme.accent, children: [
24785
- "Recent remote tasks ",
24786
- refreshing ? "(refreshing...)" : ""
24787
- ] }),
24788
- /* @__PURE__ */ jsx37(Box36, { marginTop: 1, children: /* @__PURE__ */ jsx37(
24789
- SelectInput18,
24790
- {
24791
- items,
24792
- onSelect: (item) => {
24793
- const session = sessions.find((s) => s.sessionId === item.value);
24794
- if (session) onSelect?.(session);
24795
- }
24602
+ return /* @__PURE__ */ jsxs30(Box31, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24603
+ /* @__PURE__ */ jsx32(Text31, { color: theme.accent, bold: true, children: "Select mode" }),
24604
+ /* @__PURE__ */ jsx32(Text31, { color: theme.info.color, dimColor: false, children: "Arrow keys to navigate, Enter to select, Esc to cancel." }),
24605
+ /* @__PURE__ */ jsx32(Box31, { marginTop: 1, children: /* @__PURE__ */ jsx32(
24606
+ SelectInput13,
24607
+ {
24608
+ items,
24609
+ onSelect: (item) => onPick(item.value)
24796
24610
  }
24797
- ) }),
24798
- /* @__PURE__ */ jsx37(Box36, { marginTop: 1, children: /* @__PURE__ */ jsx37(Text36, { dimColor: true, children: "\u2191\u2193 navigate \u2022 Enter select \u2022 R refresh \u2022 Esc close" }) })
24611
+ ) })
24799
24612
  ] });
24800
24613
  }
24801
- function formatSessionLine(s) {
24802
- const icon = s.status === "done" ? "\u2705" : s.status === "error" ? "\u274C" : s.status === "cancelled" ? "\u23F9\uFE0F" : s.status === "running" ? "\u23F3" : "\u23F8";
24803
- const ago = formatAgo(new Date(s.updatedAt));
24804
- const prompt = s.prompt.slice(0, 30) + (s.prompt.length > 30 ? "\u2026" : "");
24805
- const outcome = s.prUrl ? `PR ${s.prUrl.split("/").pop()}` : s.status;
24806
- const cost = s.tokensUsed && s.tokensBudget ? ` (${formatTokens4(s.tokensUsed)}/${formatTokens4(s.tokensBudget)})` : s.tokensUsed ? ` (${formatTokens4(s.tokensUsed)})` : "";
24807
- return `${icon} ${prompt} \u2192 ${outcome} ${ago}${cost}`;
24808
- }
24809
- function formatAgo(date) {
24810
- const ms = Date.now() - date.getTime();
24811
- const minutes = Math.floor(ms / 6e4);
24812
- const hours = Math.floor(minutes / 60);
24813
- const days = Math.floor(hours / 24);
24814
- if (days > 0) return `${days}d ago`;
24815
- if (hours > 0) return `${hours}h ago`;
24816
- if (minutes > 0) return `${minutes}m ago`;
24817
- return "just now";
24818
- }
24819
- function formatTokens4(n) {
24820
- if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
24821
- if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
24822
- return String(n);
24823
- }
24824
- function RemoteSessionDetail({
24825
- session,
24826
- onBack,
24827
- onCancel
24828
- }) {
24829
- const theme = useTheme();
24830
- const [cancelling, setCancelling] = useState20(false);
24831
- useInput14((input, key) => {
24832
- if (key.escape) {
24833
- onBack();
24834
- }
24835
- if ((input === "c" || input === "C") && onCancel && (session.status === "running" || session.status === "pending")) {
24836
- void handleCancel();
24837
- }
24838
- });
24839
- async function handleCancel() {
24840
- if (!onCancel) return;
24841
- setCancelling(true);
24842
- try {
24843
- await cancelRemoteSession(session.workerUrl, session.sessionId);
24844
- onCancel(session);
24845
- } catch {
24846
- } finally {
24847
- setCancelling(false);
24848
- }
24614
+ var init_mode_picker = __esm({
24615
+ "src/ui/mode-picker.tsx"() {
24616
+ "use strict";
24617
+ init_mode();
24618
+ init_theme_context();
24849
24619
  }
24850
- const isRunning = session.status === "running" || session.status === "pending";
24851
- return /* @__PURE__ */ jsxs35(Box36, { flexDirection: "column", padding: 1, children: [
24852
- /* @__PURE__ */ jsx37(Text36, { bold: true, color: theme.accent, children: "Remote Session" }),
24853
- /* @__PURE__ */ jsxs35(Box36, { marginTop: 1, flexDirection: "column", children: [
24854
- /* @__PURE__ */ jsxs35(Text36, { children: [
24855
- "ID: ",
24856
- session.sessionId
24857
- ] }),
24858
- /* @__PURE__ */ jsxs35(Text36, { children: [
24859
- "Repo: ",
24860
- session.repo
24861
- ] }),
24862
- /* @__PURE__ */ jsxs35(Text36, { children: [
24863
- "Status: ",
24864
- session.status
24865
- ] }),
24866
- /* @__PURE__ */ jsxs35(Text36, { children: [
24867
- "Prompt: ",
24868
- session.prompt
24869
- ] }),
24870
- session.prUrl && /* @__PURE__ */ jsxs35(Text36, { children: [
24871
- "PR: ",
24872
- session.prUrl
24873
- ] }),
24874
- session.errorMessage && /* @__PURE__ */ jsxs35(Text36, { color: theme.error, children: [
24875
- "Error: ",
24876
- session.errorMessage
24877
- ] }),
24878
- session.tokensUsed !== void 0 && /* @__PURE__ */ jsxs35(Text36, { children: [
24879
- "Tokens: ",
24880
- formatTokens4(session.tokensUsed),
24881
- session.tokensBudget ? ` / ${formatTokens4(session.tokensBudget)}` : ""
24882
- ] }),
24883
- /* @__PURE__ */ jsxs35(Text36, { children: [
24884
- "Created: ",
24885
- new Date(session.createdAt).toLocaleString()
24886
- ] }),
24887
- session.finishedAt && /* @__PURE__ */ jsxs35(Text36, { children: [
24888
- "Finished: ",
24889
- new Date(session.finishedAt).toLocaleString()
24890
- ] })
24891
- ] }),
24892
- /* @__PURE__ */ jsxs35(Box36, { marginTop: 1, flexDirection: "row", gap: 2, children: [
24893
- isRunning && onCancel && /* @__PURE__ */ jsx37(Text36, { color: theme.error, children: cancelling ? "Cancelling..." : "[C] Cancel session" }),
24894
- /* @__PURE__ */ jsx37(Text36, { dimColor: true, children: "Esc back" })
24895
- ] })
24620
+ });
24621
+
24622
+ // src/ui/shell-picker.tsx
24623
+ import { Box as Box32, Text as Text32 } from "ink";
24624
+ import SelectInput14 from "ink-select-input";
24625
+ import { jsx as jsx33, jsxs as jsxs31 } from "react/jsx-runtime";
24626
+ function ShellPicker({ current, onPick }) {
24627
+ const theme = useTheme();
24628
+ const items = SHELLS.map((s) => ({
24629
+ label: `${s.value === (current ?? "auto") ? "\u25CF " : " "}${s.label}`,
24630
+ value: s.value,
24631
+ key: s.value
24632
+ }));
24633
+ return /* @__PURE__ */ jsxs31(Box32, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24634
+ /* @__PURE__ */ jsx33(Text32, { color: theme.accent, bold: true, children: "Select shell" }),
24635
+ /* @__PURE__ */ jsx33(Text32, { color: theme.info.color, dimColor: false, children: "Arrow keys to navigate, Enter to select, Esc to cancel." }),
24636
+ /* @__PURE__ */ jsx33(Box32, { marginTop: 1, children: /* @__PURE__ */ jsx33(SelectInput14, { items, onSelect: (item) => onPick(item.value) }) })
24896
24637
  ] });
24897
24638
  }
24898
- var init_remote_dashboard = __esm({
24899
- "src/ui/remote-dashboard.tsx"() {
24639
+ var SHELLS;
24640
+ var init_shell_picker = __esm({
24641
+ "src/ui/shell-picker.tsx"() {
24900
24642
  "use strict";
24901
24643
  init_theme_context();
24902
- init_session_store();
24903
- init_worker_client();
24644
+ SHELLS = [
24645
+ { label: "auto \u2014 detect from environment", value: "auto" },
24646
+ { label: "bash", value: "bash" },
24647
+ { label: "cmd.exe (Windows)", value: "cmd" },
24648
+ { label: "PowerShell", value: "powershell" }
24649
+ ];
24904
24650
  }
24905
24651
  });
24906
24652
 
24907
- // src/ui/inbox-modal.tsx
24908
- import { useState as useState21, useCallback as useCallback6 } from "react";
24909
- import { Box as Box37, Text as Text37, useInput as useInput15 } from "ink";
24910
- import { Fragment as Fragment3, jsx as jsx38, jsxs as jsxs36 } from "react/jsx-runtime";
24911
- function InboxModal({ onDone, onOpen }) {
24653
+ // src/ui/memory-picker.tsx
24654
+ import { useState as useState19, useCallback as useCallback5 } from "react";
24655
+ import { Box as Box33, Text as Text33, useInput as useInput13 } from "ink";
24656
+ import SelectInput15 from "ink-select-input";
24657
+ import { jsx as jsx34, jsxs as jsxs32 } from "react/jsx-runtime";
24658
+ function MemoryPicker({ enabled, memoryManager, onAction, onDone }) {
24912
24659
  const theme = useTheme();
24913
- const [step, setStep] = useState21("twitter");
24914
- const [twitter, setTwitter] = useState21("");
24915
- const [secret, setSecret] = useState21("");
24916
- const [messages, setMessages] = useState21([]);
24917
- const [selectedIndex, setSelectedIndex] = useState21(0);
24918
- const [error, setError] = useState21(null);
24919
- const checkInbox = useCallback6(
24920
- async (u, s) => {
24921
- setStep("checking");
24922
- setError(null);
24923
- try {
24924
- const res = await fetch(
24925
- `${FEEDBACK_WORKER_URL}/inbox/check?u=${encodeURIComponent(u)}&s=${encodeURIComponent(s)}`
24926
- );
24927
- if (!res.ok) {
24928
- throw new Error(`Server returned ${res.status}`);
24929
- }
24930
- const data = await res.json();
24931
- const sorted = (data.messages ?? []).sort((a, b) => b.createdAt - a.createdAt);
24932
- setMessages(sorted);
24933
- setSelectedIndex(0);
24934
- setStep("result");
24935
- } catch (e) {
24936
- setError(e instanceof Error ? e.message : String(e));
24937
- setMessages([]);
24938
- setStep("result");
24939
- }
24940
- },
24941
- []
24942
- );
24943
- const handleTwitterSubmit = useCallback6(
24944
- (value) => {
24945
- const trimmed = value.trim();
24946
- if (!trimmed) {
24947
- onDone();
24948
- return;
24949
- }
24950
- setTwitter(trimmed);
24951
- setStep("secret");
24952
- },
24953
- [onDone]
24954
- );
24955
- const handleSecretSubmit = useCallback6(
24956
- (value) => {
24957
- const trimmed = value.trim();
24958
- if (!trimmed) {
24959
- onDone();
24960
- return;
24961
- }
24962
- setSecret(trimmed);
24963
- void checkInbox(twitter, trimmed);
24964
- },
24965
- [twitter, checkInbox, onDone]
24966
- );
24967
- const openSelected = useCallback6(() => {
24968
- if (messages.length === 0) return;
24969
- const msg = messages[selectedIndex];
24970
- if (!msg) return;
24971
- const url = `${FEEDBACK_WORKER_URL}/inbox?u=${encodeURIComponent(twitter)}&s=${encodeURIComponent(secret)}&m=${encodeURIComponent(msg.id)}`;
24972
- onOpen(url);
24973
- onDone();
24974
- }, [messages, selectedIndex, twitter, secret, onOpen, onDone]);
24975
- useInput15(
24976
- (_input, key) => {
24977
- if (key.escape) {
24660
+ const [screen, setScreen] = useState19("menu");
24661
+ const [query, setQuery] = useState19("");
24662
+ const [results, setResults] = useState19([]);
24663
+ const [searching, setSearching] = useState19(false);
24664
+ const [searched, setSearched] = useState19(false);
24665
+ useInput13((_, key) => {
24666
+ if (key.escape) {
24667
+ if (screen === "search" || screen === "confirm-clear") {
24668
+ setScreen("menu");
24669
+ setQuery("");
24670
+ setResults([]);
24671
+ setSearched(false);
24672
+ } else {
24978
24673
  onDone();
24979
- return;
24980
24674
  }
24981
- if (step === "result") {
24982
- if (key.upArrow) {
24983
- setSelectedIndex((i) => Math.max(0, i - 1));
24984
- return;
24985
- }
24986
- if (key.downArrow) {
24987
- setSelectedIndex((i) => Math.min(messages.length - 1, i + 1));
24988
- return;
24989
- }
24990
- if (key.return && messages.length > 0) {
24991
- openSelected();
24992
- }
24675
+ }
24676
+ });
24677
+ const runSearch = useCallback5(
24678
+ async (q) => {
24679
+ if (!memoryManager || !q.trim()) return;
24680
+ setSearching(true);
24681
+ try {
24682
+ const res = await memoryManager.recall({ text: q.trim(), limit: 10 });
24683
+ setResults(res);
24684
+ } catch {
24685
+ setResults([]);
24686
+ } finally {
24687
+ setSearching(false);
24688
+ setSearched(true);
24993
24689
  }
24994
- },
24995
- { isActive: step === "result" }
24996
- );
24997
- return /* @__PURE__ */ jsxs36(Box37, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24998
- /* @__PURE__ */ jsx38(Text37, { color: theme.accent, bold: true, children: "/inbox" }),
24999
- step === "twitter" && /* @__PURE__ */ jsxs36(Fragment3, { children: [
25000
- /* @__PURE__ */ jsx38(Text37, { color: theme.palette.foreground, children: "Enter your Twitter username (or press Enter to cancel):" }),
25001
- /* @__PURE__ */ jsx38(Box37, { marginTop: 1, children: /* @__PURE__ */ jsx38(
25002
- CustomTextInput,
24690
+ },
24691
+ [memoryManager]
24692
+ );
24693
+ if (screen === "confirm-clear") {
24694
+ return /* @__PURE__ */ jsxs32(Box33, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24695
+ /* @__PURE__ */ jsx34(Text33, { color: theme.accent, bold: true, children: "\u26A0\uFE0F Clear All Memories" }),
24696
+ /* @__PURE__ */ jsx34(Text33, { color: theme.info.color, children: "This will permanently delete every stored memory. This cannot be undone." }),
24697
+ /* @__PURE__ */ jsx34(Box33, { marginTop: 1, children: /* @__PURE__ */ jsx34(
24698
+ SelectInput15,
25003
24699
  {
25004
- value: twitter,
25005
- onChange: setTwitter,
25006
- onSubmit: handleTwitterSubmit,
25007
- focus: true
24700
+ items: [
24701
+ { label: "Yes, clear everything", value: "yes", key: "yes" },
24702
+ { label: "No, keep my memories", value: "no", key: "no" }
24703
+ ],
24704
+ onSelect: (item) => {
24705
+ if (item.value === "yes") {
24706
+ onAction("clear");
24707
+ }
24708
+ onDone();
24709
+ }
25008
24710
  }
25009
24711
  ) })
25010
- ] }),
25011
- step === "secret" && /* @__PURE__ */ jsxs36(Fragment3, { children: [
25012
- /* @__PURE__ */ jsx38(Text37, { color: theme.palette.foreground, children: "Enter your secret (or press Enter to cancel):" }),
25013
- /* @__PURE__ */ jsx38(Box37, { marginTop: 1, children: /* @__PURE__ */ jsx38(
24712
+ ] });
24713
+ }
24714
+ if (screen === "search") {
24715
+ return /* @__PURE__ */ jsxs32(Box33, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24716
+ /* @__PURE__ */ jsx34(Text33, { color: theme.accent, bold: true, children: "Search Memories" }),
24717
+ /* @__PURE__ */ jsx34(Text33, { color: theme.info.color, dimColor: false, children: "Type a query and press Enter. Esc to go back." }),
24718
+ /* @__PURE__ */ jsx34(Box33, { marginTop: 1, children: /* @__PURE__ */ jsx34(
25014
24719
  CustomTextInput,
25015
24720
  {
25016
- value: secret,
25017
- onChange: setSecret,
25018
- onSubmit: handleSecretSubmit,
25019
- mask: "*",
24721
+ value: query,
24722
+ onChange: setQuery,
24723
+ onSubmit: (q) => runSearch(q),
25020
24724
  focus: true
25021
24725
  }
25022
- ) })
25023
- ] }),
25024
- step === "checking" && /* @__PURE__ */ jsx38(Text37, { color: theme.info.color, children: "Checking your inbox\u2026" }),
25025
- step === "result" && /* @__PURE__ */ jsxs36(Fragment3, { children: [
25026
- error ? /* @__PURE__ */ jsxs36(Text37, { color: theme.error, children: [
25027
- "Error: ",
25028
- error
25029
- ] }) : messages.length > 0 ? /* @__PURE__ */ jsxs36(Fragment3, { children: [
25030
- /* @__PURE__ */ jsxs36(Text37, { color: theme.palette.foreground, children: [
25031
- "You have ",
25032
- messages.length,
25033
- " message",
25034
- messages.length === 1 ? "" : "s",
25035
- messages.some((m) => !m.seen) ? " (\u{1F534} new)" : "",
25036
- ":"
24726
+ ) }),
24727
+ searching && /* @__PURE__ */ jsx34(Box33, { marginTop: 1, children: /* @__PURE__ */ jsx34(Text33, { color: theme.info.color, children: "Searching\u2026" }) }),
24728
+ searched && !searching && results.length === 0 && /* @__PURE__ */ jsx34(Box33, { marginTop: 1, children: /* @__PURE__ */ jsx34(Text33, { color: theme.info.color, children: "No memories found." }) }),
24729
+ results.length > 0 && /* @__PURE__ */ jsxs32(Box33, { flexDirection: "column", marginTop: 1, children: [
24730
+ /* @__PURE__ */ jsxs32(Text33, { color: theme.accent, bold: true, children: [
24731
+ "Results (",
24732
+ results.length,
24733
+ ")"
25037
24734
  ] }),
25038
- /* @__PURE__ */ jsx38(Box37, { flexDirection: "column", marginTop: 1, children: messages.map((msg, idx) => {
25039
- const isSelected = idx === selectedIndex;
25040
- const dateStr = new Date(msg.createdAt).toLocaleString();
25041
- const marker = msg.seen ? " " : "\u{1F534} ";
25042
- return /* @__PURE__ */ jsxs36(
25043
- Text37,
25044
- {
25045
- color: isSelected ? theme.accent : theme.palette.foreground,
25046
- bold: isSelected,
25047
- dimColor: !isSelected && msg.seen,
25048
- children: [
25049
- isSelected ? "> " : " ",
25050
- marker,
25051
- dateStr,
25052
- msg.seen ? " (played)" : " (new)"
25053
- ]
25054
- },
25055
- msg.id
25056
- );
25057
- }) }),
25058
- /* @__PURE__ */ jsx38(Box37, { marginTop: 1, children: /* @__PURE__ */ jsx38(Text37, { color: theme.info.color, children: "\u2191\u2193 to select \xB7 Enter to open in browser" }) })
25059
- ] }) : /* @__PURE__ */ jsxs36(Text37, { color: theme.muted?.color ?? theme.palette.secondary, children: [
25060
- "No messages yet for @",
25061
- twitter,
25062
- " / ",
25063
- secret.replace(/./g, "*"),
25064
- "."
25065
- ] }),
25066
- /* @__PURE__ */ jsx38(Text37, { dimColor: true, children: "Press Esc to close." })
25067
- ] })
24735
+ results.map((r, i) => /* @__PURE__ */ jsxs32(Box33, { flexDirection: "column", marginTop: 1, children: [
24736
+ /* @__PURE__ */ jsxs32(Text33, { color: theme.info.color, dimColor: false, children: [
24737
+ "#",
24738
+ i + 1,
24739
+ " \xB7 ",
24740
+ r.memory.category,
24741
+ " \xB7 importance ",
24742
+ r.memory.importance
24743
+ ] }),
24744
+ /* @__PURE__ */ jsx34(Text33, { children: r.memory.content.length > 120 ? r.memory.content.slice(0, 120) + "\u2026" : r.memory.content }),
24745
+ /* @__PURE__ */ jsxs32(Text33, { color: theme.info.color, dimColor: false, children: [
24746
+ "score: ",
24747
+ r.combinedScore.toFixed(3),
24748
+ " \xB7 ",
24749
+ new Date(r.memory.createdAt).toLocaleDateString()
24750
+ ] })
24751
+ ] }, i))
24752
+ ] })
24753
+ ] });
24754
+ }
24755
+ const items = [
24756
+ { label: enabled ? "\u25CF Disable memory" : "\u25CF Enable memory", value: enabled ? "off" : "on", key: "toggle" },
24757
+ { label: " Show memory stats", value: "stats", key: "stats" },
24758
+ { label: " Clear all memories", value: "clear", key: "clear" },
24759
+ { label: " Search memories\u2026", value: "search", key: "search" },
24760
+ { label: " (close)", value: "__close__", key: "close" }
24761
+ ];
24762
+ return /* @__PURE__ */ jsxs32(Box33, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24763
+ /* @__PURE__ */ jsx34(Text33, { color: theme.accent, bold: true, children: "Memory" }),
24764
+ /* @__PURE__ */ jsx34(Text33, { color: theme.info.color, dimColor: false, children: "Arrow keys to navigate, Enter to select, Esc to close." }),
24765
+ /* @__PURE__ */ jsx34(Box33, { marginTop: 1, children: /* @__PURE__ */ jsx34(
24766
+ SelectInput15,
24767
+ {
24768
+ items,
24769
+ onSelect: (item) => {
24770
+ if (item.value === "__close__") {
24771
+ onDone();
24772
+ } else if (item.value === "clear") {
24773
+ setScreen("confirm-clear");
24774
+ } else if (item.value === "search") {
24775
+ setScreen("search");
24776
+ setQuery("");
24777
+ setResults([]);
24778
+ setSearched(false);
24779
+ } else {
24780
+ onAction(item.value);
24781
+ onDone();
24782
+ }
24783
+ }
24784
+ }
24785
+ ) })
25068
24786
  ] });
25069
24787
  }
25070
- var FEEDBACK_WORKER_URL;
25071
- var init_inbox_modal = __esm({
25072
- "src/ui/inbox-modal.tsx"() {
24788
+ var init_memory_picker = __esm({
24789
+ "src/ui/memory-picker.tsx"() {
25073
24790
  "use strict";
25074
24791
  init_text_input();
25075
24792
  init_theme_context();
25076
- FEEDBACK_WORKER_URL = "https://hello.kimiflare.com";
25077
24793
  }
25078
24794
  });
25079
24795
 
25080
- // src/remote/deploy-commute.ts
25081
- import { spawn as spawn5 } from "child_process";
25082
- import { mkdtemp, readFile as readFile20, writeFile as writeFile12, rm } from "fs/promises";
25083
- import { tmpdir as tmpdir3 } from "os";
25084
- import { join as join28 } from "path";
25085
- import { randomBytes as randomBytes2 } from "crypto";
25086
- async function cfApiFetch(accountId, apiToken, path, init) {
25087
- const url = `${CF_API2}/accounts/${encodeURIComponent(accountId)}${path}`;
25088
- const res = await fetch(url, {
25089
- ...init,
25090
- headers: {
25091
- Authorization: `Bearer ${apiToken}`,
25092
- "Content-Type": "application/json",
25093
- "User-Agent": getUserAgent(),
25094
- ...init?.headers
25095
- }
25096
- });
25097
- const json2 = await res.json();
25098
- return json2;
25099
- }
25100
- async function listDurableObjectNamespaces(accountId, apiToken) {
25101
- const json2 = await cfApiFetch(
25102
- accountId,
25103
- apiToken,
25104
- "/workers/durable_objects/namespaces"
25105
- );
25106
- if (!json2.success || !json2.result) {
25107
- throw new Error(
25108
- json2.errors?.map((e) => e.message).join(", ") ?? "Failed to list DO namespaces"
25109
- );
25110
- }
25111
- return json2.result;
25112
- }
25113
- async function deleteDurableObjectNamespace(accountId, apiToken, namespaceId) {
25114
- const json2 = await cfApiFetch(
25115
- accountId,
25116
- apiToken,
25117
- `/workers/durable_objects/namespaces/${encodeURIComponent(namespaceId)}`,
25118
- { method: "DELETE" }
25119
- );
25120
- if (!json2.success) {
25121
- throw new Error(
25122
- json2.errors?.map((e) => e.message).join(", ") ?? "Failed to delete DO namespace"
25123
- );
25124
- }
25125
- }
25126
- async function listContainerApplications(accountId, apiToken) {
25127
- const json2 = await cfApiFetch(
25128
- accountId,
25129
- apiToken,
25130
- "/containers/applications"
25131
- );
25132
- if (!json2.success || !json2.result) {
25133
- return [];
25134
- }
25135
- return json2.result;
25136
- }
25137
- async function deleteContainerApplication(accountId, apiToken, appId) {
25138
- const json2 = await cfApiFetch(
25139
- accountId,
25140
- apiToken,
25141
- `/containers/applications/${encodeURIComponent(appId)}`,
25142
- { method: "DELETE" }
25143
- );
25144
- if (!json2.success) {
25145
- throw new Error(
25146
- json2.errors?.map((e) => e.message).join(", ") ?? "Failed to delete container application"
25147
- );
25148
- }
25149
- }
25150
- function generateSecret2() {
25151
- return randomBytes2(32).toString("hex");
25152
- }
25153
- function runCmd(cmd, args, opts2 = {}) {
25154
- return new Promise((resolve8) => {
25155
- const child = spawn5(cmd, args, {
25156
- cwd: opts2.cwd,
25157
- env: { ...process.env, ...opts2.env ?? {} },
25158
- stdio: [opts2.input ? "pipe" : "ignore", "pipe", "pipe"]
24796
+ // src/ui/gateway-picker.tsx
24797
+ import { Box as Box34, Text as Text34 } from "ink";
24798
+ import SelectInput16 from "ink-select-input";
24799
+ import { jsx as jsx35, jsxs as jsxs33 } from "react/jsx-runtime";
24800
+ function GatewayPicker({ gatewayId, skipCache, collectLogs, metadataCount, onAction, onDone }) {
24801
+ const theme = useTheme();
24802
+ const items = [
24803
+ { label: ` gatewayId: ${gatewayId ?? "(not set)"}`, value: "__label_id", key: "label_id" },
24804
+ { label: " \u2192 Set gateway ID\u2026", value: "set_id", key: "set_id" }
24805
+ ];
24806
+ if (gatewayId) {
24807
+ items.push({ label: " \u2192 Disable gateway", value: "off", key: "off" });
24808
+ items.push({
24809
+ label: ` skipCache: ${skipCache ? "true" : "false"}`,
24810
+ value: "__label_skip",
24811
+ key: "label_skip"
25159
24812
  });
25160
- let stdout = "";
25161
- let stderr = "";
25162
- child.stdout?.on("data", (d) => {
25163
- stdout += d.toString();
24813
+ items.push({
24814
+ label: ` \u2192 Toggle skip-cache`,
24815
+ value: "toggle_skip",
24816
+ key: "toggle_skip"
25164
24817
  });
25165
- child.stderr?.on("data", (d) => {
25166
- stderr += d.toString();
24818
+ items.push({
24819
+ label: ` collectLogs: ${collectLogs ? "true" : "false"}`,
24820
+ value: "__label_logs",
24821
+ key: "label_logs"
25167
24822
  });
25168
- if (opts2.input && child.stdin) {
25169
- child.stdin.write(opts2.input);
25170
- child.stdin.end();
25171
- }
25172
- const timer2 = opts2.timeoutMs ? setTimeout(() => child.kill("SIGKILL"), opts2.timeoutMs) : null;
25173
- child.on("close", (code) => {
25174
- if (timer2) clearTimeout(timer2);
25175
- resolve8({ stdout, stderr, code: code ?? -1 });
24823
+ items.push({
24824
+ label: ` \u2192 Toggle collect-logs`,
24825
+ value: "toggle_logs",
24826
+ key: "toggle_logs"
25176
24827
  });
25177
- child.on("error", (err) => {
25178
- if (timer2) clearTimeout(timer2);
25179
- resolve8({ stdout, stderr: stderr + String(err), code: -1 });
24828
+ items.push({
24829
+ label: ` metadata: ${metadataCount} key${metadataCount === 1 ? "" : "s"}`,
24830
+ value: "__label_meta",
24831
+ key: "label_meta"
25180
24832
  });
25181
- });
25182
- }
25183
- async function hasBinary(bin) {
25184
- const r = await runCmd(bin, ["--version"], { timeoutMs: 5e3 });
25185
- return r.code === 0;
25186
- }
25187
- function extractWorkerUrl(text) {
25188
- const match = text.match(/https:\/\/[^\s]+\.workers\.dev/);
25189
- return match ? match[0] : void 0;
25190
- }
25191
- function extractKvId(text) {
25192
- const match = text.match(/id\s*[:=]\s*"([a-f0-9]{16,})"/);
25193
- return match ? match[1] : void 0;
24833
+ items.push({ label: " \u2192 Clear metadata", value: "clear_meta", key: "clear_meta" });
24834
+ items.push({ label: " \u2192 Set cache TTL\u2026", value: "set_ttl", key: "set_ttl" });
24835
+ items.push({ label: " \u2192 Add metadata\u2026", value: "add_meta", key: "add_meta" });
24836
+ }
24837
+ items.push({ label: " (close)", value: "__close__", key: "close" });
24838
+ const selectable = items.filter((i) => !i.value.startsWith("__label_"));
24839
+ return /* @__PURE__ */ jsxs33(Box34, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24840
+ /* @__PURE__ */ jsx35(Text34, { color: theme.accent, bold: true, children: "AI Gateway" }),
24841
+ /* @__PURE__ */ jsx35(Text34, { color: theme.info.color, dimColor: false, children: "Arrow keys to navigate, Enter to select, Esc to close." }),
24842
+ /* @__PURE__ */ jsx35(Box34, { marginTop: 1, children: /* @__PURE__ */ jsx35(
24843
+ SelectInput16,
24844
+ {
24845
+ items: selectable,
24846
+ onSelect: (item) => {
24847
+ if (item.value === "__close__") {
24848
+ onDone();
24849
+ } else {
24850
+ onAction(item.value);
24851
+ }
24852
+ }
24853
+ }
24854
+ ) })
24855
+ ] });
25194
24856
  }
25195
- function explainWranglerFailure(cmd, stdout, stderr) {
25196
- const combined = `${stdout}
25197
- ${stderr}`;
25198
- const tail = combined.slice(-1200).trim();
25199
- const lower = combined.toLowerCase();
25200
- let hint = "";
25201
- if (lower.includes("authentication error") || lower.includes("unauthorized") || /\bcode: 10000\b/.test(lower) || /\bstatus 403\b/.test(lower) || lower.includes("permission") || lower.includes("not allowed")) {
25202
- hint = `
25203
-
25204
- \u26A0 Your Cloudflare API token is missing one or more required scopes.
25205
-
25206
- Open your tokens at:
25207
- ${TOKEN_TEMPLATE_URL}
25208
-
25209
- Find the token kimiflare is using \u2192 Edit \u2192 add these Account permissions:
25210
- \u2022 Workers Scripts:Edit
25211
- \u2022 Workers KV Storage:Edit
25212
- \u2022 Account Settings:Read
25213
-
25214
- Save the token. The value doesn't change, so no kimiflare config edit
25215
- is needed \u2014 just re-run /multi-agent \u2192 Set up.`;
25216
- } else if (lower.includes("not authenticated") || lower.includes("wrangler login")) {
25217
- hint = "\n\n\u26A0 Wrangler isn't picking up CLOUDFLARE_API_TOKEN.\nVerify the token is in your kimiflare config (`/init` if not),\nor set CLOUDFLARE_API_TOKEN in your shell.";
25218
- } else if (/IMAGE_REGISTRY_NOT_CONFIGURED/i.test(combined)) {
25219
- hint = "\n\n\u26A0 Cloudflare rejected the container image:\n IMAGE_REGISTRY_NOT_CONFIGURED\n\nContainers in your account can only pull from registries it knows about.\nBy default that's just Cloudflare's managed registry \u2014 populated by\n`wrangler deploy` building your Dockerfile locally.\n\nVerify Docker is running: docker --version (then try R to retry).\nIf you want to use an external registry instead, add it under\nWorkers & Pages \u2192 <Worker> \u2192 Container registries in the dashboard.";
25220
- } else if (/\bforbidden\b/i.test(combined) || /containers? .*(not enabled|disabled|denied|forbidden)/i.test(combined)) {
25221
- const containersHit = /containers\/applications/i.test(combined);
25222
- const logMatch = combined.match(/Logs were written to "([^"]+)"/);
25223
- const logHint = logMatch ? `
25224
-
25225
- Full Cloudflare API response is in:
25226
- ${logMatch[1]}
25227
- tail -n 80 "${logMatch[1]}" | grep -iE "error|forbidden|denied|status"` : "";
25228
- if (containersHit) {
25229
- hint = '\n\n\u26A0 Cloudflare Containers API returned 403 (Authentication error).\n\nThe Worker script uploaded fine. The failure is on the step where\nwrangler registers the container application. Your API token is\nscoped for Workers Scripts:Edit etc., but the Containers API is\nnot covered by those scopes \u2014 it requires either:\n\n (a) A Containers-specific token permission (Cloudflare\'s exact\n label varies \u2014 look in the token-edit picker for anything\n containing "Container"), OR\n\n (b) OAuth auth via `wrangler login` \u2014 gives wrangler full\n account access. THIS IS HOW MOST EXISTING DEPLOYMENTS\n WORKED. If your other Containers-using Workers were\n deployed successfully, this is almost certainly how.\n\nFastest fix (option b):\n 1. In another terminal: wrangler login\n (opens a browser; sign in to your CF account)\n 2. Press R here to retry \u2014 the deploy will auto-detect your\n OAuth session and use it instead of the scoped API token.' + logHint;
25230
- } else {
25231
- hint = `
25232
-
25233
- \u26A0 Cloudflare returned a bare "Forbidden". Common causes:
24857
+ var init_gateway_picker = __esm({
24858
+ "src/ui/gateway-picker.tsx"() {
24859
+ "use strict";
24860
+ init_theme_context();
24861
+ }
24862
+ });
25234
24863
 
25235
- \u2022 Token missing a required scope (Workers Scripts:Edit,
25236
- Workers KV Storage:Edit, Account Settings:Read).
25237
- \u2022 Account isn't on Workers Paid plan ($5/mo).
25238
- \u2022 Sub-account with restricted features.
24864
+ // src/ui/skills-picker.tsx
24865
+ import { Box as Box35, Text as Text35 } from "ink";
24866
+ import SelectInput17 from "ink-select-input";
24867
+ import { jsx as jsx36, jsxs as jsxs34 } from "react/jsx-runtime";
24868
+ function SkillsPicker({ onAction, onDone }) {
24869
+ const theme = useTheme();
24870
+ const items = [
24871
+ { label: " List skills", value: "list", key: "list" },
24872
+ { label: " Add skill\u2026", value: "add", key: "add" },
24873
+ { label: " Edit skill\u2026", value: "edit", key: "edit" },
24874
+ { label: " Delete skill\u2026", value: "delete", key: "delete" },
24875
+ { label: " Enable skill\u2026", value: "enable", key: "enable" },
24876
+ { label: " Disable skill\u2026", value: "disable", key: "disable" },
24877
+ { label: " (close)", value: "__close__", key: "close" }
24878
+ ];
24879
+ return /* @__PURE__ */ jsxs34(Box35, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
24880
+ /* @__PURE__ */ jsx36(Text35, { color: theme.accent, bold: true, children: "Skills" }),
24881
+ /* @__PURE__ */ jsx36(Text35, { color: theme.info.color, dimColor: false, children: "Arrow keys to navigate, Enter to select, Esc to close." }),
24882
+ /* @__PURE__ */ jsx36(Box35, { marginTop: 1, children: /* @__PURE__ */ jsx36(
24883
+ SelectInput17,
24884
+ {
24885
+ items,
24886
+ onSelect: (item) => {
24887
+ if (item.value === "__close__") {
24888
+ onDone();
24889
+ } else {
24890
+ onAction(item.value);
24891
+ }
24892
+ }
24893
+ }
24894
+ ) })
24895
+ ] });
24896
+ }
24897
+ var init_skills_picker = __esm({
24898
+ "src/ui/skills-picker.tsx"() {
24899
+ "use strict";
24900
+ init_theme_context();
24901
+ }
24902
+ });
25239
24903
 
25240
- Edit your token at https://dash.cloudflare.com/profile/api-tokens` + logHint;
25241
- }
24904
+ // src/remote/worker-client.ts
24905
+ var worker_client_exports = {};
24906
+ __export(worker_client_exports, {
24907
+ cancelRemoteSession: () => cancelRemoteSession,
24908
+ getRemoteStatus: () => getRemoteStatus,
24909
+ startRemoteSession: () => startRemoteSession,
24910
+ streamRemoteProgress: () => streamRemoteProgress
24911
+ });
24912
+ async function startRemoteSession(opts2) {
24913
+ const workerUrl = opts2.cfg.remoteWorkerUrl;
24914
+ if (!workerUrl) {
24915
+ throw new Error("Remote worker URL not configured. Set remoteWorkerUrl in config.");
25242
24916
  }
25243
- return `${cmd} failed:
25244
- ${tail}${hint}`;
24917
+ const githubToken = opts2.cfg.githubOAuthToken;
24918
+ if (!githubToken) {
24919
+ throw new Error("GitHub token not found. Run `kimiflare auth github` first.");
24920
+ }
24921
+ const res = await fetch(`${workerUrl}/remote/start`, {
24922
+ method: "POST",
24923
+ headers: {
24924
+ "Content-Type": "application/json",
24925
+ Authorization: `Bearer ${opts2.cfg.remoteAuthSecret ?? ""}`
24926
+ },
24927
+ body: JSON.stringify({
24928
+ prompt: opts2.prompt,
24929
+ repo: opts2.repo,
24930
+ githubToken,
24931
+ accountId: opts2.cfg.accountId,
24932
+ apiToken: opts2.cfg.apiToken,
24933
+ model: opts2.cfg.model,
24934
+ reasoningEffort: opts2.cfg.reasoningEffort,
24935
+ ttlMinutes: opts2.ttlMinutes ?? opts2.cfg.remoteTtlMinutes,
24936
+ tokensBudget: opts2.tokensBudget ?? opts2.cfg.remoteMaxInputTokens
24937
+ })
24938
+ });
24939
+ if (!res.ok) {
24940
+ const text = await res.text();
24941
+ throw new Error(`Failed to start remote session: ${res.status} ${text}`);
24942
+ }
24943
+ const data = await res.json();
24944
+ await saveRemoteSession({
24945
+ sessionId: data.sessionId,
24946
+ prompt: opts2.prompt,
24947
+ repo: `${opts2.repo.owner}/${opts2.repo.name}`,
24948
+ workerUrl,
24949
+ status: "running",
24950
+ branch: `kimiflare/remote/${data.sessionId}`,
24951
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
24952
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
24953
+ });
24954
+ return data;
25245
24955
  }
25246
- async function findExistingCommuteWorkers() {
25247
- const cfg = await loadConfig();
25248
- if (!cfg?.accountId || !cfg?.apiToken) return [];
25249
- const env2 = {
25250
- CLOUDFLARE_ACCOUNT_ID: cfg.accountId,
25251
- CLOUDFLARE_API_TOKEN: cfg.apiToken,
25252
- WRANGLER_LOG_SANITIZE: "false"
25253
- };
25254
- const candidates = ["kimiflare-multi-agent", "kimiflare-commute"];
25255
- const exists = [];
25256
- for (const name of candidates) {
25257
- const r = await runCmd("wrangler", ["deployments", "list", "--name", name], {
25258
- env: env2,
25259
- timeoutMs: 15e3
25260
- });
25261
- if (r.code === 0 && !/(no deployments|could not find|not found)/i.test(r.stdout + r.stderr)) {
25262
- exists.push(name);
24956
+ async function* streamRemoteProgress(workerUrl, sessionId, signal) {
24957
+ const res = await fetch(`${workerUrl}/remote/stream/${sessionId}`, { signal });
24958
+ if (!res.ok) {
24959
+ throw new Error(`Failed to connect to stream: ${res.status}`);
24960
+ }
24961
+ if (!res.body) {
24962
+ throw new Error("No response body");
24963
+ }
24964
+ for await (const line of readSSE(res.body, signal)) {
24965
+ try {
24966
+ yield JSON.parse(line);
24967
+ } catch {
25263
24968
  }
25264
24969
  }
25265
- return exists;
25266
24970
  }
25267
- async function* deployCommute(opts2 = {}) {
25268
- const workerName = opts2.workerName ?? WORKER_NAME;
25269
- const kvTitle = workerName === WORKER_NAME ? KV_TITLE : `${workerName}-OAUTH_KV`;
25270
- const cfg = await loadConfig();
25271
- if (!cfg?.accountId || !cfg?.apiToken) {
25272
- yield { message: "Cloudflare credentials missing \u2014 run /init to set them up first.", error: true };
25273
- throw new Error("missing CF creds");
25274
- }
25275
- const oauthCheck = await runCmd("wrangler", ["whoami"], { timeoutMs: 8e3 });
25276
- const hasOAuth = oauthCheck.code === 0 && /Account ID|Email|You are logged in/i.test(oauthCheck.stdout + oauthCheck.stderr);
25277
- const cfEnv = hasOAuth ? {
25278
- CLOUDFLARE_ACCOUNT_ID: cfg.accountId,
25279
- WRANGLER_LOG_SANITIZE: "false"
25280
- } : {
25281
- CLOUDFLARE_ACCOUNT_ID: cfg.accountId,
25282
- CLOUDFLARE_API_TOKEN: cfg.apiToken,
25283
- WRANGLER_LOG_SANITIZE: "false"
25284
- };
25285
- yield { message: "Checking prerequisites\u2026" };
25286
- if (!await hasBinary("git")) {
25287
- yield { message: "git not found. Install git and retry.", error: true };
25288
- throw new Error("git missing");
24971
+ async function getRemoteStatus(workerUrl, sessionId, authSecret) {
24972
+ const res = await fetch(`${workerUrl}/remote/status/${sessionId}`, {
24973
+ headers: authSecret ? { Authorization: `Bearer ${authSecret}` } : {}
24974
+ });
24975
+ if (!res.ok) {
24976
+ const text = await res.text();
24977
+ throw new Error(`Failed to get status: ${res.status} ${text}`);
25289
24978
  }
25290
- if (!await hasBinary("docker")) {
25291
- yield {
25292
- message: "docker not found. Cloudflare Containers requires Docker for building\nthe sandbox image locally. Install: https://docs.docker.com/get-docker/\n(macOS: `brew install --cask docker`)",
25293
- error: true
25294
- };
25295
- throw new Error("docker missing");
24979
+ return res.json();
24980
+ }
24981
+ async function cancelRemoteSession(workerUrl, sessionId, authSecret) {
24982
+ const res = await fetch(`${workerUrl}/remote/cancel/${sessionId}`, {
24983
+ method: "POST",
24984
+ headers: authSecret ? { Authorization: `Bearer ${authSecret}` } : {}
24985
+ });
24986
+ if (!res.ok) {
24987
+ const text = await res.text();
24988
+ throw new Error(`Failed to cancel session: ${res.status} ${text}`);
25296
24989
  }
25297
- yield { message: "Installing/upgrading wrangler to latest\u2026" };
25298
- const wranglerInstall = await runCmd("npm", ["install", "-g", "wrangler@latest"], { timeoutMs: 18e4 });
25299
- if (wranglerInstall.code !== 0) {
25300
- yield {
25301
- message: `wrangler install failed. Install manually: npm install -g wrangler@latest
25302
- ${wranglerInstall.stderr.slice(-600)}`,
25303
- error: true
25304
- };
25305
- throw new Error("wrangler install failed");
24990
+ }
24991
+ var init_worker_client = __esm({
24992
+ "src/remote/worker-client.ts"() {
24993
+ "use strict";
24994
+ init_session_store();
24995
+ init_sse();
25306
24996
  }
25307
- const ver = await runCmd("wrangler", ["--version"], { timeoutMs: 5e3 });
25308
- const verStr = (ver.stdout || ver.stderr).trim().split("\n")[0] ?? "(unknown)";
25309
- yield { message: `wrangler ready (${verStr})`, ok: true };
25310
- yield {
25311
- message: hasOAuth ? "Using wrangler OAuth session (full account access \u2014 best for Containers)" : "Using CLOUDFLARE_API_TOKEN from your kimiflare config (limited to the token's scopes)",
25312
- ok: true
25313
- };
25314
- yield { message: "Prerequisites ready", ok: true };
25315
- const tmpRoot = await mkdtemp(join28(tmpdir3(), "kimiflare-commute-"));
25316
- const repoDir = join28(tmpRoot, "kimiflare-commute");
25317
- yield { message: `Fetching worker source from GitHub (${COMMUTE_REPO})\u2026` };
25318
- const clone = await runCmd("git", ["clone", "--depth", "1", "--branch", COMMUTE_BRANCH, COMMUTE_REPO, repoDir], { timeoutMs: 6e4 });
25319
- if (clone.code !== 0) {
25320
- yield { message: `git clone failed:
25321
- ${(clone.stderr || clone.stdout).slice(0, 400)}`, error: true };
25322
- throw new Error("clone failed");
24997
+ });
24998
+
24999
+ // src/ui/remote-dashboard.tsx
25000
+ import { useEffect as useEffect10, useState as useState20 } from "react";
25001
+ import { Box as Box36, Text as Text36, useInput as useInput14 } from "ink";
25002
+ import SelectInput18 from "ink-select-input";
25003
+ import { jsx as jsx37, jsxs as jsxs35 } from "react/jsx-runtime";
25004
+ function RemoteDashboard({ onSelect, onCancel }) {
25005
+ const theme = useTheme();
25006
+ const [sessions, setSessions] = useState20([]);
25007
+ const [loading, setLoading] = useState20(true);
25008
+ const [error, setError] = useState20(null);
25009
+ const [refreshing, setRefreshing] = useState20(false);
25010
+ useEffect10(() => {
25011
+ loadSessions();
25012
+ }, []);
25013
+ async function loadSessions() {
25014
+ try {
25015
+ setRefreshing(true);
25016
+ const list = await listRemoteSessions();
25017
+ const updated = await Promise.all(
25018
+ list.map(async (s) => {
25019
+ if (s.status === "running" || s.status === "pending") {
25020
+ try {
25021
+ const status = await getRemoteStatus(s.workerUrl, s.sessionId);
25022
+ return {
25023
+ ...s,
25024
+ status: status.status,
25025
+ prUrl: status.prUrl ?? s.prUrl,
25026
+ tokensUsed: status.tokensUsed ?? s.tokensUsed,
25027
+ tokensBudget: status.tokensBudget ?? s.tokensBudget,
25028
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
25029
+ };
25030
+ } catch {
25031
+ return s;
25032
+ }
25033
+ }
25034
+ return s;
25035
+ })
25036
+ );
25037
+ setSessions(updated.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()));
25038
+ setError(null);
25039
+ } catch (err) {
25040
+ setError(err instanceof Error ? err.message : String(err));
25041
+ } finally {
25042
+ setLoading(false);
25043
+ setRefreshing(false);
25044
+ }
25323
25045
  }
25324
- yield { message: "Source fetched from GitHub", ok: true };
25325
- const workerDir = join28(repoDir, "remote", "worker");
25326
- const wranglerToml = join28(workerDir, "wrangler.toml");
25327
- yield { message: "Installing Worker dependencies (npm install)\u2026" };
25328
- const install = await runCmd("npm", ["install", "--no-audit", "--no-fund", "--loglevel=error"], {
25329
- cwd: workerDir,
25330
- timeoutMs: 18e4
25046
+ useInput14((input, key) => {
25047
+ if (input === "r" || input === "R") {
25048
+ void loadSessions();
25049
+ }
25050
+ if (key.escape && onCancel) {
25051
+ onCancel();
25052
+ }
25331
25053
  });
25332
- if (install.code !== 0) {
25333
- yield {
25334
- message: `npm install failed in the cloned worker:
25335
- ${(install.stderr || install.stdout).slice(-1200).trim()}`,
25336
- error: true
25337
- };
25338
- throw new Error("npm install failed");
25054
+ const items = sessions.map((s) => ({
25055
+ label: formatSessionLine(s),
25056
+ value: s.sessionId
25057
+ }));
25058
+ if (loading) {
25059
+ return /* @__PURE__ */ jsx37(Box36, { flexDirection: "column", padding: 1, children: /* @__PURE__ */ jsx37(Text36, { color: theme.accent, children: "Loading remote sessions..." }) });
25339
25060
  }
25340
- yield { message: "Worker dependencies installed", ok: true };
25341
- let finalKvId = "";
25342
- const findKvByTitle = async () => {
25343
- const r = await runCmd("wrangler", ["kv", "namespace", "list"], { env: cfEnv, timeoutMs: 3e4 });
25344
- if (r.code !== 0) {
25345
- throw new Error(explainWranglerFailure("wrangler kv namespace list", r.stdout, r.stderr));
25061
+ if (error) {
25062
+ return /* @__PURE__ */ jsxs35(Box36, { flexDirection: "column", padding: 1, children: [
25063
+ /* @__PURE__ */ jsxs35(Text36, { color: theme.error, children: [
25064
+ "Error: ",
25065
+ error
25066
+ ] }),
25067
+ /* @__PURE__ */ jsx37(Text36, { dimColor: true, children: "Press R to retry, Esc to close" })
25068
+ ] });
25069
+ }
25070
+ if (sessions.length === 0) {
25071
+ return /* @__PURE__ */ jsxs35(Box36, { flexDirection: "column", padding: 1, children: [
25072
+ /* @__PURE__ */ jsx37(Text36, { color: theme.accent, children: "No remote sessions yet." }),
25073
+ /* @__PURE__ */ jsx37(Text36, { dimColor: true, children: "Type /remote <prompt> to start one." }),
25074
+ /* @__PURE__ */ jsx37(Text36, { dimColor: true, children: "Press Esc to close" })
25075
+ ] });
25076
+ }
25077
+ return /* @__PURE__ */ jsxs35(Box36, { flexDirection: "column", padding: 1, children: [
25078
+ /* @__PURE__ */ jsxs35(Text36, { bold: true, color: theme.accent, children: [
25079
+ "Recent remote tasks ",
25080
+ refreshing ? "(refreshing...)" : ""
25081
+ ] }),
25082
+ /* @__PURE__ */ jsx37(Box36, { marginTop: 1, children: /* @__PURE__ */ jsx37(
25083
+ SelectInput18,
25084
+ {
25085
+ items,
25086
+ onSelect: (item) => {
25087
+ const session = sessions.find((s) => s.sessionId === item.value);
25088
+ if (session) onSelect?.(session);
25089
+ }
25090
+ }
25091
+ ) }),
25092
+ /* @__PURE__ */ jsx37(Box36, { marginTop: 1, children: /* @__PURE__ */ jsx37(Text36, { dimColor: true, children: "\u2191\u2193 navigate \u2022 Enter select \u2022 R refresh \u2022 Esc close" }) })
25093
+ ] });
25094
+ }
25095
+ function formatSessionLine(s) {
25096
+ const icon = s.status === "done" ? "\u2705" : s.status === "error" ? "\u274C" : s.status === "cancelled" ? "\u23F9\uFE0F" : s.status === "running" ? "\u23F3" : "\u23F8";
25097
+ const ago = formatAgo(new Date(s.updatedAt));
25098
+ const prompt = s.prompt.slice(0, 30) + (s.prompt.length > 30 ? "\u2026" : "");
25099
+ const outcome = s.prUrl ? `PR ${s.prUrl.split("/").pop()}` : s.status;
25100
+ const cost = s.tokensUsed && s.tokensBudget ? ` (${formatTokens5(s.tokensUsed)}/${formatTokens5(s.tokensBudget)})` : s.tokensUsed ? ` (${formatTokens5(s.tokensUsed)})` : "";
25101
+ return `${icon} ${prompt} \u2192 ${outcome} ${ago}${cost}`;
25102
+ }
25103
+ function formatAgo(date) {
25104
+ const ms = Date.now() - date.getTime();
25105
+ const minutes = Math.floor(ms / 6e4);
25106
+ const hours = Math.floor(minutes / 60);
25107
+ const days = Math.floor(hours / 24);
25108
+ if (days > 0) return `${days}d ago`;
25109
+ if (hours > 0) return `${hours}h ago`;
25110
+ if (minutes > 0) return `${minutes}m ago`;
25111
+ return "just now";
25112
+ }
25113
+ function formatTokens5(n) {
25114
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
25115
+ if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
25116
+ return String(n);
25117
+ }
25118
+ function RemoteSessionDetail({
25119
+ session,
25120
+ onBack,
25121
+ onCancel
25122
+ }) {
25123
+ const theme = useTheme();
25124
+ const [cancelling, setCancelling] = useState20(false);
25125
+ useInput14((input, key) => {
25126
+ if (key.escape) {
25127
+ onBack();
25346
25128
  }
25347
- const jsonMatch = r.stdout.match(/\[\s*{[\s\S]*}\s*\]/);
25348
- if (!jsonMatch) return null;
25129
+ if ((input === "c" || input === "C") && onCancel && (session.status === "running" || session.status === "pending")) {
25130
+ void handleCancel();
25131
+ }
25132
+ });
25133
+ async function handleCancel() {
25134
+ if (!onCancel) return;
25135
+ setCancelling(true);
25349
25136
  try {
25350
- const items = JSON.parse(jsonMatch[0]);
25351
- const exact = items.find((it) => it.title === kvTitle && it.id);
25352
- if (exact?.id && exact.title) return { id: exact.id, title: exact.title };
25353
- const legacy = items.find((it) => typeof it.title === "string" && /OAUTH_KV$/i.test(it.title) && it.id);
25354
- if (legacy?.id && legacy.title) return { id: legacy.id, title: legacy.title };
25355
- return null;
25137
+ await cancelRemoteSession(session.workerUrl, session.sessionId);
25138
+ onCancel(session);
25356
25139
  } catch {
25357
- return null;
25358
- }
25359
- };
25360
- yield { message: `Looking up KV namespace "${kvTitle}"\u2026` };
25361
- try {
25362
- const existing = await findKvByTitle();
25363
- if (existing) {
25364
- finalKvId = existing.id;
25365
- yield { message: `KV namespace ready (reused ${existing.title} ${finalKvId.slice(0, 8)}\u2026)`, ok: true };
25140
+ } finally {
25141
+ setCancelling(false);
25366
25142
  }
25367
- } catch (err) {
25368
- yield { message: err instanceof Error ? err.message : String(err), error: true };
25369
- throw err;
25370
25143
  }
25371
- if (!finalKvId) {
25372
- yield { message: `Creating KV namespace "${kvTitle}"\u2026` };
25373
- const kvCreate = await runCmd("wrangler", ["kv", "namespace", "create", kvTitle], {
25374
- cwd: workerDir,
25375
- env: cfEnv,
25376
- timeoutMs: 3e4
25377
- });
25378
- finalKvId = extractKvId(kvCreate.stdout + "\n" + kvCreate.stderr) ?? "";
25379
- if (!finalKvId) {
25380
- if (/already exists/i.test(kvCreate.stdout + kvCreate.stderr)) {
25381
- yield { message: "Namespace already exists \u2014 re-checking\u2026" };
25382
- try {
25383
- const found = await findKvByTitle();
25384
- if (found?.id) {
25385
- finalKvId = found.id;
25386
- yield { message: `KV namespace ready (recovered ${found.title} ${finalKvId.slice(0, 8)}\u2026)`, ok: true };
25387
- }
25388
- } catch {
25144
+ const isRunning = session.status === "running" || session.status === "pending";
25145
+ return /* @__PURE__ */ jsxs35(Box36, { flexDirection: "column", padding: 1, children: [
25146
+ /* @__PURE__ */ jsx37(Text36, { bold: true, color: theme.accent, children: "Remote Session" }),
25147
+ /* @__PURE__ */ jsxs35(Box36, { marginTop: 1, flexDirection: "column", children: [
25148
+ /* @__PURE__ */ jsxs35(Text36, { children: [
25149
+ "ID: ",
25150
+ session.sessionId
25151
+ ] }),
25152
+ /* @__PURE__ */ jsxs35(Text36, { children: [
25153
+ "Repo: ",
25154
+ session.repo
25155
+ ] }),
25156
+ /* @__PURE__ */ jsxs35(Text36, { children: [
25157
+ "Status: ",
25158
+ session.status
25159
+ ] }),
25160
+ /* @__PURE__ */ jsxs35(Text36, { children: [
25161
+ "Prompt: ",
25162
+ session.prompt
25163
+ ] }),
25164
+ session.prUrl && /* @__PURE__ */ jsxs35(Text36, { children: [
25165
+ "PR: ",
25166
+ session.prUrl
25167
+ ] }),
25168
+ session.errorMessage && /* @__PURE__ */ jsxs35(Text36, { color: theme.error, children: [
25169
+ "Error: ",
25170
+ session.errorMessage
25171
+ ] }),
25172
+ session.tokensUsed !== void 0 && /* @__PURE__ */ jsxs35(Text36, { children: [
25173
+ "Tokens: ",
25174
+ formatTokens5(session.tokensUsed),
25175
+ session.tokensBudget ? ` / ${formatTokens5(session.tokensBudget)}` : ""
25176
+ ] }),
25177
+ /* @__PURE__ */ jsxs35(Text36, { children: [
25178
+ "Created: ",
25179
+ new Date(session.createdAt).toLocaleString()
25180
+ ] }),
25181
+ session.finishedAt && /* @__PURE__ */ jsxs35(Text36, { children: [
25182
+ "Finished: ",
25183
+ new Date(session.finishedAt).toLocaleString()
25184
+ ] })
25185
+ ] }),
25186
+ /* @__PURE__ */ jsxs35(Box36, { marginTop: 1, flexDirection: "row", gap: 2, children: [
25187
+ isRunning && onCancel && /* @__PURE__ */ jsx37(Text36, { color: theme.error, children: cancelling ? "Cancelling..." : "[C] Cancel session" }),
25188
+ /* @__PURE__ */ jsx37(Text36, { dimColor: true, children: "Esc back" })
25189
+ ] })
25190
+ ] });
25191
+ }
25192
+ var init_remote_dashboard = __esm({
25193
+ "src/ui/remote-dashboard.tsx"() {
25194
+ "use strict";
25195
+ init_theme_context();
25196
+ init_session_store();
25197
+ init_worker_client();
25198
+ }
25199
+ });
25200
+
25201
+ // src/ui/inbox-modal.tsx
25202
+ import { useState as useState21, useCallback as useCallback6 } from "react";
25203
+ import { Box as Box37, Text as Text37, useInput as useInput15 } from "ink";
25204
+ import { Fragment as Fragment3, jsx as jsx38, jsxs as jsxs36 } from "react/jsx-runtime";
25205
+ function InboxModal({ onDone, onOpen }) {
25206
+ const theme = useTheme();
25207
+ const [step, setStep] = useState21("twitter");
25208
+ const [twitter, setTwitter] = useState21("");
25209
+ const [secret, setSecret] = useState21("");
25210
+ const [messages, setMessages] = useState21([]);
25211
+ const [selectedIndex, setSelectedIndex] = useState21(0);
25212
+ const [error, setError] = useState21(null);
25213
+ const checkInbox = useCallback6(
25214
+ async (u, s) => {
25215
+ setStep("checking");
25216
+ setError(null);
25217
+ try {
25218
+ const res = await fetch(
25219
+ `${FEEDBACK_WORKER_URL2}/inbox/check?u=${encodeURIComponent(u)}&s=${encodeURIComponent(s)}`
25220
+ );
25221
+ if (!res.ok) {
25222
+ throw new Error(`Server returned ${res.status}`);
25389
25223
  }
25224
+ const data = await res.json();
25225
+ const sorted = (data.messages ?? []).sort((a, b) => b.createdAt - a.createdAt);
25226
+ setMessages(sorted);
25227
+ setSelectedIndex(0);
25228
+ setStep("result");
25229
+ } catch (e) {
25230
+ setError(e instanceof Error ? e.message : String(e));
25231
+ setMessages([]);
25232
+ setStep("result");
25390
25233
  }
25391
- }
25392
- if (!finalKvId) {
25393
- yield {
25394
- message: explainWranglerFailure(`wrangler kv namespace create ${kvTitle}`, kvCreate.stdout, kvCreate.stderr),
25395
- error: true
25396
- };
25397
- throw new Error("kv create failed");
25398
- }
25399
- if (!finalKvId.length) {
25400
- } else if (!/already exists/i.test(kvCreate.stdout + kvCreate.stderr)) {
25401
- yield { message: `KV namespace ready (created ${finalKvId.slice(0, 8)}\u2026)`, ok: true };
25402
- }
25403
- }
25404
- yield { message: `Checking if "${workerName}" already exists\u2026` };
25405
- const deployments = await runCmd("wrangler", ["deployments", "list", "--name", workerName], {
25406
- env: cfEnv,
25407
- timeoutMs: 3e4
25408
- });
25409
- const workerExists = deployments.code === 0 && !/(no deployments|could not find|not found)/i.test(deployments.stdout + deployments.stderr);
25410
- yield {
25411
- message: workerExists ? `Found existing Worker "${workerName}" \u2014 preserving its migration history` : `No Worker named "${workerName}" \u2014 will provision from scratch`,
25412
- ok: true
25413
- };
25414
- yield { message: "Patching wrangler.toml\u2026" };
25415
- let toml = await readFile20(wranglerToml, "utf8");
25416
- toml = toml.replace(/^name\s*=\s*"[^"]+"/m, `name = "${workerName}"`);
25417
- toml = toml.replace(
25418
- /(\[\[kv_namespaces\]\][\s\S]*?binding\s*=\s*"OAUTH_KV"[\s\S]*?id\s*=\s*")[^"]+(")/,
25419
- `$1${finalKvId}$2`
25234
+ },
25235
+ []
25420
25236
  );
25421
- toml = toml.replace(/\n\[\[artifacts\]\][\s\S]*?(?=\n\[|\n*$)/g, "\n");
25422
- if (!workerExists) {
25423
- const existingMigrations = toml.match(/\[\[migrations\]\]/);
25424
- if (existingMigrations) {
25425
- toml = toml.replace(
25426
- /\[\[migrations\]\][\s\S]*?new_sqlite_classes\s*=\s*\[[^\]]*\]/,
25427
- `[[migrations]]
25428
- tag = "v1"
25429
- new_sqlite_classes = ["SessionDO", "WorkerDO", "Sandbox"]`
25430
- );
25431
- } else {
25432
- toml += `
25433
- # Auto-added by kimiflare /multi-agent \u2192 Set up (fresh deploy only)
25434
- [[migrations]]
25435
- tag = "v1"
25436
- new_sqlite_classes = ["SessionDO", "WorkerDO", "Sandbox"]
25437
- `;
25438
- }
25439
- }
25440
- if (!/\[observability\.logs\]/.test(toml)) {
25441
- toml += `
25442
- # Auto-added by kimiflare /multi-agent \u2192 Set up
25443
- [observability.logs]
25444
- enabled = false
25445
- invocation_logs = true
25446
- `;
25237
+ const handleTwitterSubmit = useCallback6(
25238
+ (value) => {
25239
+ const trimmed = value.trim();
25240
+ if (!trimmed) {
25241
+ onDone();
25242
+ return;
25243
+ }
25244
+ setTwitter(trimmed);
25245
+ setStep("secret");
25246
+ },
25247
+ [onDone]
25248
+ );
25249
+ const handleSecretSubmit = useCallback6(
25250
+ (value) => {
25251
+ const trimmed = value.trim();
25252
+ if (!trimmed) {
25253
+ onDone();
25254
+ return;
25255
+ }
25256
+ setSecret(trimmed);
25257
+ void checkInbox(twitter, trimmed);
25258
+ },
25259
+ [twitter, checkInbox, onDone]
25260
+ );
25261
+ const openSelected = useCallback6(() => {
25262
+ if (messages.length === 0) return;
25263
+ const msg = messages[selectedIndex];
25264
+ if (!msg) return;
25265
+ const url = `${FEEDBACK_WORKER_URL2}/inbox?u=${encodeURIComponent(twitter)}&s=${encodeURIComponent(secret)}&m=${encodeURIComponent(msg.id)}`;
25266
+ onOpen(url);
25267
+ onDone();
25268
+ }, [messages, selectedIndex, twitter, secret, onOpen, onDone]);
25269
+ useInput15(
25270
+ (_input, key) => {
25271
+ if (key.escape) {
25272
+ onDone();
25273
+ return;
25274
+ }
25275
+ if (step === "result") {
25276
+ if (key.upArrow) {
25277
+ setSelectedIndex((i) => Math.max(0, i - 1));
25278
+ return;
25279
+ }
25280
+ if (key.downArrow) {
25281
+ setSelectedIndex((i) => Math.min(messages.length - 1, i + 1));
25282
+ return;
25283
+ }
25284
+ if (key.return && messages.length > 0) {
25285
+ openSelected();
25286
+ }
25287
+ }
25288
+ },
25289
+ { isActive: step === "result" }
25290
+ );
25291
+ return /* @__PURE__ */ jsxs36(Box37, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [
25292
+ /* @__PURE__ */ jsx38(Text37, { color: theme.accent, bold: true, children: "/inbox" }),
25293
+ step === "twitter" && /* @__PURE__ */ jsxs36(Fragment3, { children: [
25294
+ /* @__PURE__ */ jsx38(Text37, { color: theme.palette.foreground, children: "Enter your Twitter username (or press Enter to cancel):" }),
25295
+ /* @__PURE__ */ jsx38(Box37, { marginTop: 1, children: /* @__PURE__ */ jsx38(
25296
+ CustomTextInput,
25297
+ {
25298
+ value: twitter,
25299
+ onChange: setTwitter,
25300
+ onSubmit: handleTwitterSubmit,
25301
+ focus: true
25302
+ }
25303
+ ) })
25304
+ ] }),
25305
+ step === "secret" && /* @__PURE__ */ jsxs36(Fragment3, { children: [
25306
+ /* @__PURE__ */ jsx38(Text37, { color: theme.palette.foreground, children: "Enter your secret (or press Enter to cancel):" }),
25307
+ /* @__PURE__ */ jsx38(Box37, { marginTop: 1, children: /* @__PURE__ */ jsx38(
25308
+ CustomTextInput,
25309
+ {
25310
+ value: secret,
25311
+ onChange: setSecret,
25312
+ onSubmit: handleSecretSubmit,
25313
+ mask: "*",
25314
+ focus: true
25315
+ }
25316
+ ) })
25317
+ ] }),
25318
+ step === "checking" && /* @__PURE__ */ jsx38(Text37, { color: theme.info.color, children: "Checking your inbox\u2026" }),
25319
+ step === "result" && /* @__PURE__ */ jsxs36(Fragment3, { children: [
25320
+ error ? /* @__PURE__ */ jsxs36(Text37, { color: theme.error, children: [
25321
+ "Error: ",
25322
+ error
25323
+ ] }) : messages.length > 0 ? /* @__PURE__ */ jsxs36(Fragment3, { children: [
25324
+ /* @__PURE__ */ jsxs36(Text37, { color: theme.palette.foreground, children: [
25325
+ "You have ",
25326
+ messages.length,
25327
+ " message",
25328
+ messages.length === 1 ? "" : "s",
25329
+ messages.some((m) => !m.seen) ? " (\u{1F534} new)" : "",
25330
+ ":"
25331
+ ] }),
25332
+ /* @__PURE__ */ jsx38(Box37, { flexDirection: "column", marginTop: 1, children: messages.map((msg, idx) => {
25333
+ const isSelected = idx === selectedIndex;
25334
+ const dateStr = new Date(msg.createdAt).toLocaleString();
25335
+ const marker = msg.seen ? " " : "\u{1F534} ";
25336
+ return /* @__PURE__ */ jsxs36(
25337
+ Text37,
25338
+ {
25339
+ color: isSelected ? theme.accent : theme.palette.foreground,
25340
+ bold: isSelected,
25341
+ dimColor: !isSelected && msg.seen,
25342
+ children: [
25343
+ isSelected ? "> " : " ",
25344
+ marker,
25345
+ dateStr,
25346
+ msg.seen ? " (played)" : " (new)"
25347
+ ]
25348
+ },
25349
+ msg.id
25350
+ );
25351
+ }) }),
25352
+ /* @__PURE__ */ jsx38(Box37, { marginTop: 1, children: /* @__PURE__ */ jsx38(Text37, { color: theme.info.color, children: "\u2191\u2193 to select \xB7 Enter to open in browser" }) })
25353
+ ] }) : /* @__PURE__ */ jsxs36(Text37, { color: theme.muted?.color ?? theme.palette.secondary, children: [
25354
+ "No messages yet for @",
25355
+ twitter,
25356
+ " / ",
25357
+ secret.replace(/./g, "*"),
25358
+ "."
25359
+ ] }),
25360
+ /* @__PURE__ */ jsx38(Text37, { dimColor: true, children: "Press Esc to close." })
25361
+ ] })
25362
+ ] });
25363
+ }
25364
+ var FEEDBACK_WORKER_URL2;
25365
+ var init_inbox_modal = __esm({
25366
+ "src/ui/inbox-modal.tsx"() {
25367
+ "use strict";
25368
+ init_text_input();
25369
+ init_theme_context();
25370
+ FEEDBACK_WORKER_URL2 = "https://hello.kimiflare.com";
25447
25371
  }
25448
- await writeFile12(wranglerToml, toml, "utf8");
25449
- yield {
25450
- message: workerExists ? "wrangler.toml patched (name, KV id, [[artifacts]] stripped)" : "wrangler.toml patched (name, KV id, [[artifacts]] stripped, DO migrations added)",
25451
- ok: true
25452
- };
25453
- const workerApiKey = generateSecret2();
25454
- yield { message: "Setting WORKER_API_KEY secret\u2026" };
25455
- const secret = await runCmd("wrangler", ["secret", "put", "WORKER_API_KEY"], {
25456
- cwd: workerDir,
25457
- env: cfEnv,
25458
- input: workerApiKey + "\n",
25459
- timeoutMs: 3e4
25372
+ });
25373
+
25374
+ // src/remote/deploy-commute.ts
25375
+ import { spawn as spawn6 } from "child_process";
25376
+ import { mkdtemp, readFile as readFile20, writeFile as writeFile12, rm } from "fs/promises";
25377
+ import { tmpdir as tmpdir3 } from "os";
25378
+ import { join as join29 } from "path";
25379
+ import { randomBytes as randomBytes2 } from "crypto";
25380
+ async function cfApiFetch(accountId, apiToken, path, init) {
25381
+ const url = `${CF_API2}/accounts/${encodeURIComponent(accountId)}${path}`;
25382
+ const res = await fetch(url, {
25383
+ ...init,
25384
+ headers: {
25385
+ Authorization: `Bearer ${apiToken}`,
25386
+ "Content-Type": "application/json",
25387
+ "User-Agent": getUserAgent(),
25388
+ ...init?.headers
25389
+ }
25460
25390
  });
25461
- if (secret.code !== 0) {
25462
- yield {
25463
- message: explainWranglerFailure("wrangler secret put WORKER_API_KEY", secret.stdout, secret.stderr),
25464
- error: true
25465
- };
25466
- throw new Error("secret put failed");
25391
+ const json2 = await res.json();
25392
+ return json2;
25393
+ }
25394
+ async function listDurableObjectNamespaces(accountId, apiToken) {
25395
+ const json2 = await cfApiFetch(
25396
+ accountId,
25397
+ apiToken,
25398
+ "/workers/durable_objects/namespaces"
25399
+ );
25400
+ if (!json2.success || !json2.result) {
25401
+ throw new Error(
25402
+ json2.errors?.map((e) => e.message).join(", ") ?? "Failed to list DO namespaces"
25403
+ );
25467
25404
  }
25468
- await runCmd("wrangler", ["secret", "put", "ACCOUNT_ID"], { cwd: workerDir, env: cfEnv, input: cfg.accountId + "\n", timeoutMs: 3e4 });
25469
- await runCmd("wrangler", ["secret", "put", "CF_API_TOKEN"], { cwd: workerDir, env: cfEnv, input: cfg.apiToken + "\n", timeoutMs: 3e4 });
25470
- yield { message: "Worker secrets uploaded (WORKER_API_KEY, ACCOUNT_ID, CF_API_TOKEN)", ok: true };
25471
- yield { message: "Deploying Worker (this can take ~30s)\u2026" };
25472
- const deploy = await runCmd("wrangler", ["deploy"], {
25473
- cwd: workerDir,
25474
- env: cfEnv,
25475
- timeoutMs: 18e4
25476
- });
25477
- if (deploy.code !== 0) {
25478
- yield {
25479
- message: explainWranglerFailure("wrangler deploy", deploy.stdout, deploy.stderr),
25480
- error: true
25481
- };
25482
- throw new Error("deploy failed");
25405
+ return json2.result;
25406
+ }
25407
+ async function deleteDurableObjectNamespace(accountId, apiToken, namespaceId) {
25408
+ const json2 = await cfApiFetch(
25409
+ accountId,
25410
+ apiToken,
25411
+ `/workers/durable_objects/namespaces/${encodeURIComponent(namespaceId)}`,
25412
+ { method: "DELETE" }
25413
+ );
25414
+ if (!json2.success) {
25415
+ throw new Error(
25416
+ json2.errors?.map((e) => e.message).join(", ") ?? "Failed to delete DO namespace"
25417
+ );
25483
25418
  }
25484
- const workerUrl = extractWorkerUrl(deploy.stdout + "\n" + deploy.stderr);
25485
- if (!workerUrl) {
25486
- yield { message: "Deploy succeeded but couldn't parse the Worker URL \u2014 set it manually via /multi-agent.", error: true };
25487
- throw new Error("url parse failed");
25419
+ }
25420
+ async function listContainerApplications(accountId, apiToken) {
25421
+ const json2 = await cfApiFetch(
25422
+ accountId,
25423
+ apiToken,
25424
+ "/containers/applications"
25425
+ );
25426
+ if (!json2.success || !json2.result) {
25427
+ return [];
25488
25428
  }
25489
- yield { message: `Worker deployed at ${workerUrl}`, ok: true };
25490
- const next = {
25491
- ...cfg,
25492
- workerEndpoint: workerUrl,
25493
- workerApiKey,
25494
- workerName,
25495
- multiAgentEnabled: true
25496
- };
25497
- await saveConfig(next);
25498
- yield { message: "Saved to ~/.config/kimiflare/config.json", ok: true };
25499
- await rm(tmpRoot, { recursive: true, force: true }).catch(() => {
25429
+ return json2.result;
25430
+ }
25431
+ async function deleteContainerApplication(accountId, apiToken, appId) {
25432
+ const json2 = await cfApiFetch(
25433
+ accountId,
25434
+ apiToken,
25435
+ `/containers/applications/${encodeURIComponent(appId)}`,
25436
+ { method: "DELETE" }
25437
+ );
25438
+ if (!json2.success) {
25439
+ throw new Error(
25440
+ json2.errors?.map((e) => e.message).join(", ") ?? "Failed to delete container application"
25441
+ );
25442
+ }
25443
+ }
25444
+ function generateSecret2() {
25445
+ return randomBytes2(32).toString("hex");
25446
+ }
25447
+ function runCmd(cmd, args, opts2 = {}) {
25448
+ return new Promise((resolve8) => {
25449
+ const child = spawn6(cmd, args, {
25450
+ cwd: opts2.cwd,
25451
+ env: { ...process.env, ...opts2.env ?? {} },
25452
+ stdio: [opts2.input ? "pipe" : "ignore", "pipe", "pipe"]
25453
+ });
25454
+ let stdout = "";
25455
+ let stderr = "";
25456
+ child.stdout?.on("data", (d) => {
25457
+ stdout += d.toString();
25458
+ });
25459
+ child.stderr?.on("data", (d) => {
25460
+ stderr += d.toString();
25461
+ });
25462
+ if (opts2.input && child.stdin) {
25463
+ child.stdin.write(opts2.input);
25464
+ child.stdin.end();
25465
+ }
25466
+ const timer2 = opts2.timeoutMs ? setTimeout(() => child.kill("SIGKILL"), opts2.timeoutMs) : null;
25467
+ child.on("close", (code) => {
25468
+ if (timer2) clearTimeout(timer2);
25469
+ resolve8({ stdout, stderr, code: code ?? -1 });
25470
+ });
25471
+ child.on("error", (err) => {
25472
+ if (timer2) clearTimeout(timer2);
25473
+ resolve8({ stdout, stderr: stderr + String(err), code: -1 });
25474
+ });
25500
25475
  });
25501
- yield { message: "Setup complete \u2014 multi-agent is ready to use.", done: true };
25502
- return { workerEndpoint: workerUrl, workerApiKey };
25503
25476
  }
25504
- async function* teardownCommute(opts2 = {}) {
25505
- const cfg = await loadConfig();
25506
- if (!cfg?.accountId || !cfg?.apiToken) {
25507
- yield { message: "Cloudflare credentials missing \u2014 nothing to tear down.", error: true };
25508
- throw new Error("missing CF creds");
25477
+ async function hasBinary(bin) {
25478
+ const r = await runCmd(bin, ["--version"], { timeoutMs: 5e3 });
25479
+ return r.code === 0;
25480
+ }
25481
+ function extractWorkerUrl(text) {
25482
+ const match = text.match(/https:\/\/[^\s]+\.workers\.dev/);
25483
+ return match ? match[0] : void 0;
25484
+ }
25485
+ function extractKvId(text) {
25486
+ const match = text.match(/id\s*[:=]\s*"([a-f0-9]{16,})"/);
25487
+ return match ? match[1] : void 0;
25488
+ }
25489
+ function explainWranglerFailure(cmd, stdout, stderr) {
25490
+ const combined = `${stdout}
25491
+ ${stderr}`;
25492
+ const tail = combined.slice(-1200).trim();
25493
+ const lower = combined.toLowerCase();
25494
+ let hint = "";
25495
+ if (lower.includes("authentication error") || lower.includes("unauthorized") || /\bcode: 10000\b/.test(lower) || /\bstatus 403\b/.test(lower) || lower.includes("permission") || lower.includes("not allowed")) {
25496
+ hint = `
25497
+
25498
+ \u26A0 Your Cloudflare API token is missing one or more required scopes.
25499
+
25500
+ Open your tokens at:
25501
+ ${TOKEN_TEMPLATE_URL}
25502
+
25503
+ Find the token kimiflare is using \u2192 Edit \u2192 add these Account permissions:
25504
+ \u2022 Workers Scripts:Edit
25505
+ \u2022 Workers KV Storage:Edit
25506
+ \u2022 Account Settings:Read
25507
+
25508
+ Save the token. The value doesn't change, so no kimiflare config edit
25509
+ is needed \u2014 just re-run /multi-agent \u2192 Set up.`;
25510
+ } else if (lower.includes("not authenticated") || lower.includes("wrangler login")) {
25511
+ hint = "\n\n\u26A0 Wrangler isn't picking up CLOUDFLARE_API_TOKEN.\nVerify the token is in your kimiflare config (`/init` if not),\nor set CLOUDFLARE_API_TOKEN in your shell.";
25512
+ } else if (/IMAGE_REGISTRY_NOT_CONFIGURED/i.test(combined)) {
25513
+ hint = "\n\n\u26A0 Cloudflare rejected the container image:\n IMAGE_REGISTRY_NOT_CONFIGURED\n\nContainers in your account can only pull from registries it knows about.\nBy default that's just Cloudflare's managed registry \u2014 populated by\n`wrangler deploy` building your Dockerfile locally.\n\nVerify Docker is running: docker --version (then try R to retry).\nIf you want to use an external registry instead, add it under\nWorkers & Pages \u2192 <Worker> \u2192 Container registries in the dashboard.";
25514
+ } else if (/\bforbidden\b/i.test(combined) || /containers? .*(not enabled|disabled|denied|forbidden)/i.test(combined)) {
25515
+ const containersHit = /containers\/applications/i.test(combined);
25516
+ const logMatch = combined.match(/Logs were written to "([^"]+)"/);
25517
+ const logHint = logMatch ? `
25518
+
25519
+ Full Cloudflare API response is in:
25520
+ ${logMatch[1]}
25521
+ tail -n 80 "${logMatch[1]}" | grep -iE "error|forbidden|denied|status"` : "";
25522
+ if (containersHit) {
25523
+ hint = '\n\n\u26A0 Cloudflare Containers API returned 403 (Authentication error).\n\nThe Worker script uploaded fine. The failure is on the step where\nwrangler registers the container application. Your API token is\nscoped for Workers Scripts:Edit etc., but the Containers API is\nnot covered by those scopes \u2014 it requires either:\n\n (a) A Containers-specific token permission (Cloudflare\'s exact\n label varies \u2014 look in the token-edit picker for anything\n containing "Container"), OR\n\n (b) OAuth auth via `wrangler login` \u2014 gives wrangler full\n account access. THIS IS HOW MOST EXISTING DEPLOYMENTS\n WORKED. If your other Containers-using Workers were\n deployed successfully, this is almost certainly how.\n\nFastest fix (option b):\n 1. In another terminal: wrangler login\n (opens a browser; sign in to your CF account)\n 2. Press R here to retry \u2014 the deploy will auto-detect your\n OAuth session and use it instead of the scoped API token.' + logHint;
25524
+ } else {
25525
+ hint = `
25526
+
25527
+ \u26A0 Cloudflare returned a bare "Forbidden". Common causes:
25528
+
25529
+ \u2022 Token missing a required scope (Workers Scripts:Edit,
25530
+ Workers KV Storage:Edit, Account Settings:Read).
25531
+ \u2022 Account isn't on Workers Paid plan ($5/mo).
25532
+ \u2022 Sub-account with restricted features.
25533
+
25534
+ Edit your token at https://dash.cloudflare.com/profile/api-tokens` + logHint;
25535
+ }
25509
25536
  }
25510
- const workerName = opts2.workerName ?? cfg.workerName ?? WORKER_NAME;
25511
- const cfEnv = {
25537
+ return `${cmd} failed:
25538
+ ${tail}${hint}`;
25539
+ }
25540
+ async function findExistingCommuteWorkers() {
25541
+ const cfg = await loadConfig();
25542
+ if (!cfg?.accountId || !cfg?.apiToken) return [];
25543
+ const env2 = {
25512
25544
  CLOUDFLARE_ACCOUNT_ID: cfg.accountId,
25513
- CLOUDFLARE_API_TOKEN: cfg.apiToken
25545
+ CLOUDFLARE_API_TOKEN: cfg.apiToken,
25546
+ WRANGLER_LOG_SANITIZE: "false"
25514
25547
  };
25515
- if (!await hasBinary("wrangler")) {
25516
- yield { message: "wrangler not found. Install: npm install -g wrangler", error: true };
25517
- throw new Error("wrangler missing");
25518
- }
25519
- yield { message: `Deleting Worker "${workerName}"\u2026` };
25520
- const del = await runCmd("wrangler", ["delete", "--name", workerName], {
25521
- env: cfEnv,
25522
- input: "y\n",
25523
- timeoutMs: 6e4
25524
- });
25525
- if (del.code === 0) {
25526
- yield { message: `Worker "${workerName}" deleted`, ok: true };
25527
- } else {
25528
- const combined = (del.stdout + del.stderr).toLowerCase();
25529
- if (combined.includes("not found") || combined.includes("does not exist") || combined.includes("10007")) {
25530
- yield { message: "Worker not found (already deleted or never created)", ok: true };
25531
- } else {
25532
- yield {
25533
- message: explainWranglerFailure(`wrangler delete --name ${workerName}`, del.stdout, del.stderr),
25534
- error: true
25535
- };
25548
+ const candidates = ["kimiflare-multi-agent", "kimiflare-commute"];
25549
+ const exists = [];
25550
+ for (const name of candidates) {
25551
+ const r = await runCmd("wrangler", ["deployments", "list", "--name", name], {
25552
+ env: env2,
25553
+ timeoutMs: 15e3
25554
+ });
25555
+ if (r.code === 0 && !/(no deployments|could not find|not found)/i.test(r.stdout + r.stderr)) {
25556
+ exists.push(name);
25536
25557
  }
25537
25558
  }
25538
- yield { message: `Looking up Durable Object namespaces for "${workerName}"\u2026` };
25539
- try {
25540
- const namespaces = await listDurableObjectNamespaces(cfg.accountId, cfg.apiToken);
25541
- const targets = namespaces.filter(
25542
- (ns) => ns.script === workerName || ns.name.startsWith(`${workerName}_`)
25543
- );
25544
- if (targets.length === 0) {
25545
- yield { message: "No Durable Object namespaces found (nothing to delete)", ok: true };
25546
- } else {
25547
- for (const ns of targets) {
25548
- try {
25549
- await deleteDurableObjectNamespace(cfg.accountId, cfg.apiToken, ns.id);
25550
- yield { message: `DO namespace ${ns.name} deleted (${ns.id.slice(0, 8)}\u2026)`, ok: true };
25551
- } catch (err) {
25552
- yield {
25553
- message: `DO namespace ${ns.name} delete warning: ${err instanceof Error ? err.message : String(err)}`
25554
- };
25555
- }
25556
- }
25557
- }
25558
- } catch (err) {
25559
+ return exists;
25560
+ }
25561
+ async function* deployCommute(opts2 = {}) {
25562
+ const workerName = opts2.workerName ?? WORKER_NAME;
25563
+ const kvTitle = workerName === WORKER_NAME ? KV_TITLE : `${workerName}-OAUTH_KV`;
25564
+ const cfg = await loadConfig();
25565
+ if (!cfg?.accountId || !cfg?.apiToken) {
25566
+ yield { message: "Cloudflare credentials missing \u2014 run /init to set them up first.", error: true };
25567
+ throw new Error("missing CF creds");
25568
+ }
25569
+ const oauthCheck = await runCmd("wrangler", ["whoami"], { timeoutMs: 8e3 });
25570
+ const hasOAuth = oauthCheck.code === 0 && /Account ID|Email|You are logged in/i.test(oauthCheck.stdout + oauthCheck.stderr);
25571
+ const cfEnv = hasOAuth ? {
25572
+ CLOUDFLARE_ACCOUNT_ID: cfg.accountId,
25573
+ WRANGLER_LOG_SANITIZE: "false"
25574
+ } : {
25575
+ CLOUDFLARE_ACCOUNT_ID: cfg.accountId,
25576
+ CLOUDFLARE_API_TOKEN: cfg.apiToken,
25577
+ WRANGLER_LOG_SANITIZE: "false"
25578
+ };
25579
+ yield { message: "Checking prerequisites\u2026" };
25580
+ if (!await hasBinary("git")) {
25581
+ yield { message: "git not found. Install git and retry.", error: true };
25582
+ throw new Error("git missing");
25583
+ }
25584
+ if (!await hasBinary("docker")) {
25559
25585
  yield {
25560
- message: `DO namespace lookup warning: ${err instanceof Error ? err.message : String(err)}`
25586
+ message: "docker not found. Cloudflare Containers requires Docker for building\nthe sandbox image locally. Install: https://docs.docker.com/get-docker/\n(macOS: `brew install --cask docker`)",
25587
+ error: true
25561
25588
  };
25589
+ throw new Error("docker missing");
25562
25590
  }
25563
- yield { message: `Looking up container applications for "${workerName}"\u2026` };
25564
- try {
25565
- const apps = await listContainerApplications(cfg.accountId, cfg.apiToken);
25566
- const appPrefix = `${workerName}-`.toLowerCase();
25567
- const targets = apps.filter((app) => app.name.toLowerCase().startsWith(appPrefix));
25568
- if (targets.length === 0) {
25569
- yield { message: "No container applications found (nothing to delete)", ok: true };
25570
- } else {
25571
- for (const app of targets) {
25572
- try {
25573
- await deleteContainerApplication(cfg.accountId, cfg.apiToken, app.id);
25574
- yield { message: `Container application ${app.name} deleted (${app.id.slice(0, 8)}\u2026)`, ok: true };
25575
- } catch (err) {
25576
- yield {
25577
- message: `Container application ${app.name} delete warning: ${err instanceof Error ? err.message : String(err)}`
25578
- };
25579
- }
25580
- }
25581
- }
25582
- } catch {
25583
- yield { message: "(could not list container applications \u2014 skipping container cleanup)" };
25584
- }
25585
- yield { message: "Listing KV namespaces to find OAUTH_KV\u2026" };
25586
- const kvList = await runCmd("wrangler", ["kv", "namespace", "list"], { env: cfEnv, timeoutMs: 3e4 });
25587
- if (kvList.code === 0) {
25588
- try {
25589
- const items = JSON.parse(kvList.stdout);
25590
- const targets = items.filter((it) => typeof it.title === "string" && /OAUTH_KV$/i.test(it.title));
25591
- if (targets.length === 0) {
25592
- yield { message: "No OAUTH_KV namespaces found (nothing to delete)", ok: true };
25593
- } else {
25594
- for (const t of targets) {
25595
- if (!t.id) continue;
25596
- const r = await runCmd("wrangler", ["kv", "namespace", "delete", "--namespace-id", t.id], {
25597
- env: cfEnv,
25598
- input: "y\n",
25599
- timeoutMs: 3e4
25600
- });
25601
- if (r.code === 0) {
25602
- yield { message: `KV namespace ${t.title} deleted (${t.id.slice(0, 8)}\u2026)`, ok: true };
25603
- } else {
25604
- yield { message: `KV namespace ${t.title} delete warning: ${(r.stderr || r.stdout).slice(0, 200)}` };
25605
- }
25606
- }
25607
- }
25608
- } catch {
25609
- yield { message: "(could not parse KV list \u2014 skipping KV cleanup)" };
25610
- }
25611
- } else {
25612
- yield { message: "(could not list KV namespaces \u2014 skipping KV cleanup)" };
25591
+ yield { message: "Installing/upgrading wrangler to latest\u2026" };
25592
+ const wranglerInstall = await runCmd("npm", ["install", "-g", "wrangler@latest"], { timeoutMs: 18e4 });
25593
+ if (wranglerInstall.code !== 0) {
25594
+ yield {
25595
+ message: `wrangler install failed. Install manually: npm install -g wrangler@latest
25596
+ ${wranglerInstall.stderr.slice(-600)}`,
25597
+ error: true
25598
+ };
25599
+ throw new Error("wrangler install failed");
25613
25600
  }
25614
- const next = {
25615
- ...cfg,
25616
- workerEndpoint: void 0,
25617
- workerApiKey: void 0,
25618
- workerName: void 0,
25619
- multiAgentEnabled: false,
25620
- autoExecute: false
25601
+ const ver = await runCmd("wrangler", ["--version"], { timeoutMs: 5e3 });
25602
+ const verStr = (ver.stdout || ver.stderr).trim().split("\n")[0] ?? "(unknown)";
25603
+ yield { message: `wrangler ready (${verStr})`, ok: true };
25604
+ yield {
25605
+ message: hasOAuth ? "Using wrangler OAuth session (full account access \u2014 best for Containers)" : "Using CLOUDFLARE_API_TOKEN from your kimiflare config (limited to the token's scopes)",
25606
+ ok: true
25621
25607
  };
25622
- await saveConfig(next);
25623
- yield { message: "Local multi-agent config cleared", ok: true };
25624
- yield { message: "Tear-down complete \u2014 multi-agent is fully removed.", done: true };
25625
- }
25626
- var COMMUTE_REPO, COMMUTE_BRANCH, WORKER_NAME, KV_TITLE, CF_API2, TOKEN_TEMPLATE_URL;
25627
- var init_deploy_commute = __esm({
25628
- "src/remote/deploy-commute.ts"() {
25629
- "use strict";
25630
- init_config();
25631
- init_version();
25632
- COMMUTE_REPO = "https://github.com/sinameraji/kimiflare-commute.git";
25633
- COMMUTE_BRANCH = "main";
25634
- WORKER_NAME = "kimiflare-multi-agent";
25635
- KV_TITLE = "kimiflare-multi-agent-OAUTH_KV";
25636
- CF_API2 = "https://api.cloudflare.com/client/v4";
25637
- TOKEN_TEMPLATE_URL = "https://dash.cloudflare.com/profile/api-tokens";
25608
+ yield { message: "Prerequisites ready", ok: true };
25609
+ const tmpRoot = await mkdtemp(join29(tmpdir3(), "kimiflare-commute-"));
25610
+ const repoDir = join29(tmpRoot, "kimiflare-commute");
25611
+ yield { message: `Fetching worker source from GitHub (${COMMUTE_REPO})\u2026` };
25612
+ const clone = await runCmd("git", ["clone", "--depth", "1", "--branch", COMMUTE_BRANCH, COMMUTE_REPO, repoDir], { timeoutMs: 6e4 });
25613
+ if (clone.code !== 0) {
25614
+ yield { message: `git clone failed:
25615
+ ${(clone.stderr || clone.stdout).slice(0, 400)}`, error: true };
25616
+ throw new Error("clone failed");
25638
25617
  }
25639
- });
25640
-
25641
- // src/ui/app-helpers.ts
25642
- var app_helpers_exports = {};
25643
- __export(app_helpers_exports, {
25644
- AUTO_COMPACT_THRESHOLD: () => AUTO_COMPACT_THRESHOLD,
25645
- CONTEXT_LIMIT: () => CONTEXT_LIMIT,
25646
- DEFAULT_AUTO_FRESH_SUGGESTION_TURNS: () => DEFAULT_AUTO_FRESH_SUGGESTION_TURNS,
25647
- FEEDBACK_WORKER_URL: () => FEEDBACK_WORKER_URL2,
25648
- MAX_EVENTS: () => MAX_EVENTS,
25649
- MAX_IMAGES_PER_MESSAGE: () => MAX_IMAGES_PER_MESSAGE,
25650
- buildFilePickerIgnoreList: () => buildFilePickerIgnoreList,
25651
- capEvents: () => capEvents,
25652
- compactEventsVisual: () => compactEventsVisual,
25653
- detectGitBranch: () => detectGitBranch,
25654
- detectGitHubRepo: () => detectGitHubRepo,
25655
- findImagePaths: () => findImagePaths,
25656
- formatTokens: () => formatTokens5,
25657
- gatewayFromConfig: () => gatewayFromConfig,
25658
- gatewayUsageLookupFromConfig: () => gatewayUsageLookupFromConfig,
25659
- makePrefixMessages: () => makePrefixMessages,
25660
- mkAssistantId: () => mkAssistantId,
25661
- mkKey: () => mkKey,
25662
- openBrowser: () => openBrowser,
25663
- rebuildSystemPromptForMode: () => rebuildSystemPromptForMode,
25664
- trackRecentFile: () => trackRecentFile
25665
- });
25666
- import { execSync as execSync3, spawn as spawn6 } from "child_process";
25667
- import { existsSync as existsSync4, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
25668
- import { join as join29 } from "path";
25669
- import { platform as platform4 } from "os";
25670
- function buildFilePickerIgnoreList(cwd) {
25671
- const hardcoded = [
25672
- // Dependencies
25673
- "**/node_modules/**",
25674
- "**/vendor/**",
25675
- "**/.bundle/**",
25676
- "**/bower_components/**",
25677
- // Version control
25678
- "**/.git/**",
25679
- "**/.svn/**",
25680
- "**/.hg/**",
25681
- // Build / output directories
25682
- "**/dist/**",
25683
- "**/build/**",
25684
- "**/out/**",
25685
- "**/public/**",
25686
- "**/.next/**",
25687
- "**/.nuxt/**",
25688
- "**/.svelte-kit/**",
25689
- "**/.vercel/**",
25690
- "**/.netlify/**",
25691
- "**/target/**",
25692
- "**/bin/**",
25693
- "**/obj/**",
25694
- "**/Debug/**",
25695
- "**/Release/**",
25696
- "**/.gradle/**",
25697
- // Caches
25698
- "**/.cache/**",
25699
- "**/.parcel-cache/**",
25700
- "**/.turbo/**",
25701
- "**/.eslintcache",
25702
- "**/.stylelintcache",
25703
- "**/.rpt2_cache/**",
25704
- "**/.rts2_cache/**",
25705
- // Temporary
25706
- "**/tmp/**",
25707
- "**/temp/**",
25708
- "**/*.tmp",
25709
- // Coverage
25710
- "**/coverage/**",
25711
- "**/.nyc_output/**",
25712
- // OS files
25713
- "**/.DS_Store",
25714
- "**/Thumbs.db",
25715
- // Logs
25716
- "**/*.log",
25717
- "**/logs/**",
25718
- // Lock files (auto-generated, usually huge)
25719
- "**/package-lock.json",
25720
- "**/yarn.lock",
25721
- "**/pnpm-lock.yaml",
25722
- "**/bun.lockb",
25723
- "**/Cargo.lock",
25724
- "**/Gemfile.lock",
25725
- "**/composer.lock",
25726
- "**/Pipfile.lock",
25727
- "**/poetry.lock",
25728
- "**/go.sum",
25729
- // Minified / source maps
25730
- "**/*.min.js",
25731
- "**/*.min.css",
25732
- "**/*.map",
25733
- // kimiflare internal
25734
- "**/.kimiflare/**",
25735
- // IDE (usually not relevant to mention)
25736
- "**/.idea/**"
25737
- ];
25738
- const gitignorePatterns = [];
25618
+ yield { message: "Source fetched from GitHub", ok: true };
25619
+ const workerDir = join29(repoDir, "remote", "worker");
25620
+ const wranglerToml = join29(workerDir, "wrangler.toml");
25621
+ yield { message: "Installing Worker dependencies (npm install)\u2026" };
25622
+ const install = await runCmd("npm", ["install", "--no-audit", "--no-fund", "--loglevel=error"], {
25623
+ cwd: workerDir,
25624
+ timeoutMs: 18e4
25625
+ });
25626
+ if (install.code !== 0) {
25627
+ yield {
25628
+ message: `npm install failed in the cloned worker:
25629
+ ${(install.stderr || install.stdout).slice(-1200).trim()}`,
25630
+ error: true
25631
+ };
25632
+ throw new Error("npm install failed");
25633
+ }
25634
+ yield { message: "Worker dependencies installed", ok: true };
25635
+ let finalKvId = "";
25636
+ const findKvByTitle = async () => {
25637
+ const r = await runCmd("wrangler", ["kv", "namespace", "list"], { env: cfEnv, timeoutMs: 3e4 });
25638
+ if (r.code !== 0) {
25639
+ throw new Error(explainWranglerFailure("wrangler kv namespace list", r.stdout, r.stderr));
25640
+ }
25641
+ const jsonMatch = r.stdout.match(/\[\s*{[\s\S]*}\s*\]/);
25642
+ if (!jsonMatch) return null;
25643
+ try {
25644
+ const items = JSON.parse(jsonMatch[0]);
25645
+ const exact = items.find((it) => it.title === kvTitle && it.id);
25646
+ if (exact?.id && exact.title) return { id: exact.id, title: exact.title };
25647
+ const legacy = items.find((it) => typeof it.title === "string" && /OAUTH_KV$/i.test(it.title) && it.id);
25648
+ if (legacy?.id && legacy.title) return { id: legacy.id, title: legacy.title };
25649
+ return null;
25650
+ } catch {
25651
+ return null;
25652
+ }
25653
+ };
25654
+ yield { message: `Looking up KV namespace "${kvTitle}"\u2026` };
25739
25655
  try {
25740
- const gitignorePath = join29(cwd, ".gitignore");
25741
- const stats = statSync4(gitignorePath);
25742
- if (stats.size > MAX_GITIGNORE_SIZE) {
25743
- return hardcoded;
25656
+ const existing = await findKvByTitle();
25657
+ if (existing) {
25658
+ finalKvId = existing.id;
25659
+ yield { message: `KV namespace ready (reused ${existing.title} ${finalKvId.slice(0, 8)}\u2026)`, ok: true };
25744
25660
  }
25745
- const content = readFileSync4(gitignorePath, "utf-8");
25746
- for (const line of content.split(/\r?\n/)) {
25747
- const trimmed = line.trim();
25748
- if (!trimmed || trimmed.startsWith("#")) continue;
25749
- if (trimmed.startsWith("!")) continue;
25750
- let pattern = trimmed;
25751
- const isAnchored = pattern.startsWith("/");
25752
- const isDir = pattern.endsWith("/");
25753
- if (isAnchored) pattern = pattern.slice(1);
25754
- if (isDir) pattern = pattern.slice(0, -1);
25755
- if (!pattern) continue;
25756
- if (isAnchored) {
25757
- gitignorePatterns.push(isDir ? pattern + "/**" : pattern);
25758
- } else {
25759
- gitignorePatterns.push(isDir ? "**/" + pattern + "/**" : "**/" + pattern);
25661
+ } catch (err) {
25662
+ yield { message: err instanceof Error ? err.message : String(err), error: true };
25663
+ throw err;
25664
+ }
25665
+ if (!finalKvId) {
25666
+ yield { message: `Creating KV namespace "${kvTitle}"\u2026` };
25667
+ const kvCreate = await runCmd("wrangler", ["kv", "namespace", "create", kvTitle], {
25668
+ cwd: workerDir,
25669
+ env: cfEnv,
25670
+ timeoutMs: 3e4
25671
+ });
25672
+ finalKvId = extractKvId(kvCreate.stdout + "\n" + kvCreate.stderr) ?? "";
25673
+ if (!finalKvId) {
25674
+ if (/already exists/i.test(kvCreate.stdout + kvCreate.stderr)) {
25675
+ yield { message: "Namespace already exists \u2014 re-checking\u2026" };
25676
+ try {
25677
+ const found = await findKvByTitle();
25678
+ if (found?.id) {
25679
+ finalKvId = found.id;
25680
+ yield { message: `KV namespace ready (recovered ${found.title} ${finalKvId.slice(0, 8)}\u2026)`, ok: true };
25681
+ }
25682
+ } catch {
25683
+ }
25760
25684
  }
25761
25685
  }
25762
- } catch {
25686
+ if (!finalKvId) {
25687
+ yield {
25688
+ message: explainWranglerFailure(`wrangler kv namespace create ${kvTitle}`, kvCreate.stdout, kvCreate.stderr),
25689
+ error: true
25690
+ };
25691
+ throw new Error("kv create failed");
25692
+ }
25693
+ if (!finalKvId.length) {
25694
+ } else if (!/already exists/i.test(kvCreate.stdout + kvCreate.stderr)) {
25695
+ yield { message: `KV namespace ready (created ${finalKvId.slice(0, 8)}\u2026)`, ok: true };
25696
+ }
25763
25697
  }
25764
- return [...hardcoded, ...gitignorePatterns];
25765
- }
25766
- function gatewayFromConfig(cfg) {
25767
- if (process.env.KIMIFLARE_DISABLE_AI_GATEWAY === "1") return void 0;
25768
- if (!cfg.aiGatewayId) return void 0;
25769
- return {
25770
- id: cfg.aiGatewayId,
25771
- cacheTtl: cfg.aiGatewayCacheTtl,
25772
- skipCache: cfg.aiGatewaySkipCache,
25773
- collectLogPayload: cfg.aiGatewayCollectLogPayload,
25774
- metadata: cfg.aiGatewayMetadata
25775
- };
25776
- }
25777
- function gatewayUsageLookupFromConfig(cfg, meta) {
25778
- if (process.env.KIMIFLARE_DISABLE_AI_GATEWAY === "1") return void 0;
25779
- if (!cfg.aiGatewayId || !meta) return void 0;
25780
- return {
25781
- accountId: cfg.accountId,
25782
- apiToken: cfg.apiToken,
25783
- gatewayId: cfg.aiGatewayId,
25784
- meta
25698
+ yield { message: `Checking if "${workerName}" already exists\u2026` };
25699
+ const deployments = await runCmd("wrangler", ["deployments", "list", "--name", workerName], {
25700
+ env: cfEnv,
25701
+ timeoutMs: 3e4
25702
+ });
25703
+ const workerExists = deployments.code === 0 && !/(no deployments|could not find|not found)/i.test(deployments.stdout + deployments.stderr);
25704
+ yield {
25705
+ message: workerExists ? `Found existing Worker "${workerName}" \u2014 preserving its migration history` : `No Worker named "${workerName}" \u2014 will provision from scratch`,
25706
+ ok: true
25785
25707
  };
25786
- }
25787
- function openBrowser(url) {
25788
- const cmd = platform4() === "darwin" ? "open" : platform4() === "win32" ? "start" : "xdg-open";
25789
- const child = spawn6(cmd, [url], { detached: true, stdio: "ignore" });
25790
- child.unref();
25791
- }
25792
- function detectGitHubRepo(cachedRepo) {
25793
- if (cachedRepo) {
25794
- const parts = cachedRepo.split("/");
25795
- if (parts.length === 2) return { owner: parts[0], name: parts[1] };
25708
+ yield { message: "Patching wrangler.toml\u2026" };
25709
+ let toml = await readFile20(wranglerToml, "utf8");
25710
+ toml = toml.replace(/^name\s*=\s*"[^"]+"/m, `name = "${workerName}"`);
25711
+ toml = toml.replace(
25712
+ /(\[\[kv_namespaces\]\][\s\S]*?binding\s*=\s*"OAUTH_KV"[\s\S]*?id\s*=\s*")[^"]+(")/,
25713
+ `$1${finalKvId}$2`
25714
+ );
25715
+ toml = toml.replace(/\n\[\[artifacts\]\][\s\S]*?(?=\n\[|\n*$)/g, "\n");
25716
+ if (!workerExists) {
25717
+ const existingMigrations = toml.match(/\[\[migrations\]\]/);
25718
+ if (existingMigrations) {
25719
+ toml = toml.replace(
25720
+ /\[\[migrations\]\][\s\S]*?new_sqlite_classes\s*=\s*\[[^\]]*\]/,
25721
+ `[[migrations]]
25722
+ tag = "v1"
25723
+ new_sqlite_classes = ["SessionDO", "WorkerDO", "Sandbox"]`
25724
+ );
25725
+ } else {
25726
+ toml += `
25727
+ # Auto-added by kimiflare /multi-agent \u2192 Set up (fresh deploy only)
25728
+ [[migrations]]
25729
+ tag = "v1"
25730
+ new_sqlite_classes = ["SessionDO", "WorkerDO", "Sandbox"]
25731
+ `;
25732
+ }
25796
25733
  }
25797
- try {
25798
- const remoteUrl = execSync3("git remote get-url origin", { cwd: process.cwd(), encoding: "utf8" }).trim().replace(/\/+$/, "");
25799
- const httpsMatch = remoteUrl.match(/github\.com\/([^\/]+)\/([^\/]+?)(?:\.git)?$/);
25800
- if (httpsMatch) return { owner: httpsMatch[1], name: httpsMatch[2] };
25801
- const sshMatch = remoteUrl.match(/github\.com:([^\/]+)\/([^\/]+?)(?:\.git)?$/);
25802
- if (sshMatch) return { owner: sshMatch[1], name: sshMatch[2] };
25803
- } catch {
25734
+ if (!/\[observability\.logs\]/.test(toml)) {
25735
+ toml += `
25736
+ # Auto-added by kimiflare /multi-agent \u2192 Set up
25737
+ [observability.logs]
25738
+ enabled = false
25739
+ invocation_logs = true
25740
+ `;
25804
25741
  }
25805
- return null;
25806
- }
25807
- function detectGitBranch() {
25808
- try {
25809
- return execSync3("git branch --show-current", { cwd: process.cwd(), encoding: "utf8" }).trim() || null;
25810
- } catch {
25811
- return null;
25742
+ await writeFile12(wranglerToml, toml, "utf8");
25743
+ yield {
25744
+ message: workerExists ? "wrangler.toml patched (name, KV id, [[artifacts]] stripped)" : "wrangler.toml patched (name, KV id, [[artifacts]] stripped, DO migrations added)",
25745
+ ok: true
25746
+ };
25747
+ const workerApiKey = generateSecret2();
25748
+ yield { message: "Setting WORKER_API_KEY secret\u2026" };
25749
+ const secret = await runCmd("wrangler", ["secret", "put", "WORKER_API_KEY"], {
25750
+ cwd: workerDir,
25751
+ env: cfEnv,
25752
+ input: workerApiKey + "\n",
25753
+ timeoutMs: 3e4
25754
+ });
25755
+ if (secret.code !== 0) {
25756
+ yield {
25757
+ message: explainWranglerFailure("wrangler secret put WORKER_API_KEY", secret.stdout, secret.stderr),
25758
+ error: true
25759
+ };
25760
+ throw new Error("secret put failed");
25812
25761
  }
25813
- }
25814
- function formatTokens5(n) {
25815
- if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
25816
- if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
25817
- return String(n);
25818
- }
25819
- function trackRecentFile(ref, path, max = 10) {
25820
- ref.current.set(path, Date.now());
25821
- if (ref.current.size > max) {
25822
- let oldest = null;
25823
- let oldestTime = Infinity;
25824
- for (const [p, t] of ref.current) {
25825
- if (t < oldestTime) {
25826
- oldestTime = t;
25827
- oldest = p;
25828
- }
25829
- }
25830
- if (oldest) ref.current.delete(oldest);
25762
+ await runCmd("wrangler", ["secret", "put", "ACCOUNT_ID"], { cwd: workerDir, env: cfEnv, input: cfg.accountId + "\n", timeoutMs: 3e4 });
25763
+ await runCmd("wrangler", ["secret", "put", "CF_API_TOKEN"], { cwd: workerDir, env: cfEnv, input: cfg.apiToken + "\n", timeoutMs: 3e4 });
25764
+ yield { message: "Worker secrets uploaded (WORKER_API_KEY, ACCOUNT_ID, CF_API_TOKEN)", ok: true };
25765
+ yield { message: "Deploying Worker (this can take ~30s)\u2026" };
25766
+ const deploy = await runCmd("wrangler", ["deploy"], {
25767
+ cwd: workerDir,
25768
+ env: cfEnv,
25769
+ timeoutMs: 18e4
25770
+ });
25771
+ if (deploy.code !== 0) {
25772
+ yield {
25773
+ message: explainWranglerFailure("wrangler deploy", deploy.stdout, deploy.stderr),
25774
+ error: true
25775
+ };
25776
+ throw new Error("deploy failed");
25831
25777
  }
25832
- }
25833
- function capEvents(prev) {
25834
- if (prev.length <= MAX_EVENTS) return prev;
25835
- return prev.slice(prev.length - MAX_EVENTS);
25836
- }
25837
- function compactEventsVisual(prev, keepLastTurns) {
25838
- let seen = 0;
25839
- let cutoff = -1;
25840
- for (let i = prev.length - 1; i >= 0; i--) {
25841
- if (prev[i].kind === "user") {
25842
- seen++;
25843
- if (seen === keepLastTurns + 1) {
25844
- cutoff = i;
25845
- break;
25846
- }
25847
- }
25778
+ const workerUrl = extractWorkerUrl(deploy.stdout + "\n" + deploy.stderr);
25779
+ if (!workerUrl) {
25780
+ yield { message: "Deploy succeeded but couldn't parse the Worker URL \u2014 set it manually via /multi-agent.", error: true };
25781
+ throw new Error("url parse failed");
25848
25782
  }
25849
- if (cutoff <= 0) return prev;
25850
- const kept = prev.slice(cutoff);
25851
- return [
25852
- { kind: "info", key: mkKey(), text: `\xB7\xB7\xB7 ${cutoff} earlier messages compacted \xB7\xB7\xB7` },
25853
- ...kept
25854
- ];
25783
+ yield { message: `Worker deployed at ${workerUrl}`, ok: true };
25784
+ const next = {
25785
+ ...cfg,
25786
+ workerEndpoint: workerUrl,
25787
+ workerApiKey,
25788
+ workerName,
25789
+ multiAgentEnabled: true
25790
+ };
25791
+ await saveConfig(next);
25792
+ yield { message: "Saved to ~/.config/kimiflare/config.json", ok: true };
25793
+ await rm(tmpRoot, { recursive: true, force: true }).catch(() => {
25794
+ });
25795
+ yield { message: "Setup complete \u2014 multi-agent is ready to use.", done: true };
25796
+ return { workerEndpoint: workerUrl, workerApiKey };
25855
25797
  }
25856
- function makePrefixMessages(cacheStable, model, mode, tools, preferPullRequests) {
25857
- if (cacheStable) {
25858
- return buildSystemMessages({ cwd: process.cwd(), tools, model, mode, preferPullRequests });
25798
+ async function* teardownCommute(opts2 = {}) {
25799
+ const cfg = await loadConfig();
25800
+ if (!cfg?.accountId || !cfg?.apiToken) {
25801
+ yield { message: "Cloudflare credentials missing \u2014 nothing to tear down.", error: true };
25802
+ throw new Error("missing CF creds");
25859
25803
  }
25860
- return [
25861
- {
25862
- role: "system",
25863
- content: buildSystemPrompt({ cwd: process.cwd(), tools, model, mode, preferPullRequests })
25804
+ const workerName = opts2.workerName ?? cfg.workerName ?? WORKER_NAME;
25805
+ const cfEnv = {
25806
+ CLOUDFLARE_ACCOUNT_ID: cfg.accountId,
25807
+ CLOUDFLARE_API_TOKEN: cfg.apiToken
25808
+ };
25809
+ if (!await hasBinary("wrangler")) {
25810
+ yield { message: "wrangler not found. Install: npm install -g wrangler", error: true };
25811
+ throw new Error("wrangler missing");
25812
+ }
25813
+ yield { message: `Deleting Worker "${workerName}"\u2026` };
25814
+ const del = await runCmd("wrangler", ["delete", "--name", workerName], {
25815
+ env: cfEnv,
25816
+ input: "y\n",
25817
+ timeoutMs: 6e4
25818
+ });
25819
+ if (del.code === 0) {
25820
+ yield { message: `Worker "${workerName}" deleted`, ok: true };
25821
+ } else {
25822
+ const combined = (del.stdout + del.stderr).toLowerCase();
25823
+ if (combined.includes("not found") || combined.includes("does not exist") || combined.includes("10007")) {
25824
+ yield { message: "Worker not found (already deleted or never created)", ok: true };
25825
+ } else {
25826
+ yield {
25827
+ message: explainWranglerFailure(`wrangler delete --name ${workerName}`, del.stdout, del.stderr),
25828
+ error: true
25829
+ };
25864
25830
  }
25865
- ];
25866
- }
25867
- function rebuildSystemPromptForMode(messages, cacheStable, model, mode, tools, preferPullRequests) {
25868
- if (cacheStable) {
25869
- const rebuilt = buildSystemMessages({ cwd: process.cwd(), tools, model, mode, preferPullRequests });
25870
- messages[0] = rebuilt[0];
25871
- if (rebuilt[1]) {
25872
- messages[1] = rebuilt[1];
25831
+ }
25832
+ yield { message: `Looking up Durable Object namespaces for "${workerName}"\u2026` };
25833
+ try {
25834
+ const namespaces = await listDurableObjectNamespaces(cfg.accountId, cfg.apiToken);
25835
+ const targets = namespaces.filter(
25836
+ (ns) => ns.script === workerName || ns.name.startsWith(`${workerName}_`)
25837
+ );
25838
+ if (targets.length === 0) {
25839
+ yield { message: "No Durable Object namespaces found (nothing to delete)", ok: true };
25840
+ } else {
25841
+ for (const ns of targets) {
25842
+ try {
25843
+ await deleteDurableObjectNamespace(cfg.accountId, cfg.apiToken, ns.id);
25844
+ yield { message: `DO namespace ${ns.name} deleted (${ns.id.slice(0, 8)}\u2026)`, ok: true };
25845
+ } catch (err) {
25846
+ yield {
25847
+ message: `DO namespace ${ns.name} delete warning: ${err instanceof Error ? err.message : String(err)}`
25848
+ };
25849
+ }
25850
+ }
25873
25851
  }
25874
- } else {
25875
- messages[0] = {
25876
- role: "system",
25877
- content: buildSystemPrompt({ cwd: process.cwd(), tools, model, mode, preferPullRequests })
25852
+ } catch (err) {
25853
+ yield {
25854
+ message: `DO namespace lookup warning: ${err instanceof Error ? err.message : String(err)}`
25878
25855
  };
25879
25856
  }
25880
- }
25881
- function findImagePaths(text) {
25882
- const paths = [];
25883
- const quotedRegex = /"([^"]+)"|'([^']+)'/g;
25884
- let match;
25885
- while ((match = quotedRegex.exec(text)) !== null) {
25886
- const path = match[1] ?? match[2];
25887
- if (path && isImagePath(path) && existsSync4(path)) {
25888
- paths.push(path);
25857
+ yield { message: `Looking up container applications for "${workerName}"\u2026` };
25858
+ try {
25859
+ const apps = await listContainerApplications(cfg.accountId, cfg.apiToken);
25860
+ const appPrefix = `${workerName}-`.toLowerCase();
25861
+ const targets = apps.filter((app) => app.name.toLowerCase().startsWith(appPrefix));
25862
+ if (targets.length === 0) {
25863
+ yield { message: "No container applications found (nothing to delete)", ok: true };
25864
+ } else {
25865
+ for (const app of targets) {
25866
+ try {
25867
+ await deleteContainerApplication(cfg.accountId, cfg.apiToken, app.id);
25868
+ yield { message: `Container application ${app.name} deleted (${app.id.slice(0, 8)}\u2026)`, ok: true };
25869
+ } catch (err) {
25870
+ yield {
25871
+ message: `Container application ${app.name} delete warning: ${err instanceof Error ? err.message : String(err)}`
25872
+ };
25873
+ }
25874
+ }
25889
25875
  }
25876
+ } catch {
25877
+ yield { message: "(could not list container applications \u2014 skipping container cleanup)" };
25890
25878
  }
25891
- const remaining = text.replace(/"[^"]+"|'[^']+'/g, "");
25892
- const ESCAPED_SPACE = "\0";
25893
- const processed = remaining.replace(/\\ /g, ESCAPED_SPACE);
25894
- for (const token of processed.split(/\s+/)) {
25895
- const clean = token.replace(new RegExp(ESCAPED_SPACE, "g"), " ").replace(/^["']|["',;:!?]$/g, "").replace(/[.,;:!?]$/, "");
25896
- if (clean && isImagePath(clean) && existsSync4(clean) && !paths.includes(clean)) {
25897
- paths.push(clean);
25879
+ yield { message: "Listing KV namespaces to find OAUTH_KV\u2026" };
25880
+ const kvList = await runCmd("wrangler", ["kv", "namespace", "list"], { env: cfEnv, timeoutMs: 3e4 });
25881
+ if (kvList.code === 0) {
25882
+ try {
25883
+ const items = JSON.parse(kvList.stdout);
25884
+ const targets = items.filter((it) => typeof it.title === "string" && /OAUTH_KV$/i.test(it.title));
25885
+ if (targets.length === 0) {
25886
+ yield { message: "No OAUTH_KV namespaces found (nothing to delete)", ok: true };
25887
+ } else {
25888
+ for (const t of targets) {
25889
+ if (!t.id) continue;
25890
+ const r = await runCmd("wrangler", ["kv", "namespace", "delete", "--namespace-id", t.id], {
25891
+ env: cfEnv,
25892
+ input: "y\n",
25893
+ timeoutMs: 3e4
25894
+ });
25895
+ if (r.code === 0) {
25896
+ yield { message: `KV namespace ${t.title} deleted (${t.id.slice(0, 8)}\u2026)`, ok: true };
25897
+ } else {
25898
+ yield { message: `KV namespace ${t.title} delete warning: ${(r.stderr || r.stdout).slice(0, 200)}` };
25899
+ }
25900
+ }
25901
+ }
25902
+ } catch {
25903
+ yield { message: "(could not parse KV list \u2014 skipping KV cleanup)" };
25898
25904
  }
25905
+ } else {
25906
+ yield { message: "(could not list KV namespaces \u2014 skipping KV cleanup)" };
25899
25907
  }
25900
- return paths;
25908
+ const next = {
25909
+ ...cfg,
25910
+ workerEndpoint: void 0,
25911
+ workerApiKey: void 0,
25912
+ workerName: void 0,
25913
+ multiAgentEnabled: false,
25914
+ autoExecute: false
25915
+ };
25916
+ await saveConfig(next);
25917
+ yield { message: "Local multi-agent config cleared", ok: true };
25918
+ yield { message: "Tear-down complete \u2014 multi-agent is fully removed.", done: true };
25901
25919
  }
25902
- var MAX_GITIGNORE_SIZE, CONTEXT_LIMIT, AUTO_COMPACT_THRESHOLD, MAX_EVENTS, DEFAULT_AUTO_FRESH_SUGGESTION_TURNS, MAX_IMAGES_PER_MESSAGE, FEEDBACK_WORKER_URL2, nextKey, mkKey, nextAssistantId, mkAssistantId;
25903
- var init_app_helpers = __esm({
25904
- "src/ui/app-helpers.ts"() {
25920
+ var COMMUTE_REPO, COMMUTE_BRANCH, WORKER_NAME, KV_TITLE, CF_API2, TOKEN_TEMPLATE_URL;
25921
+ var init_deploy_commute = __esm({
25922
+ "src/remote/deploy-commute.ts"() {
25905
25923
  "use strict";
25906
- init_system_prompt();
25907
- init_image();
25908
- MAX_GITIGNORE_SIZE = 1 * 1024 * 1024;
25909
- CONTEXT_LIMIT = 262e3;
25910
- AUTO_COMPACT_THRESHOLD = 0.8;
25911
- MAX_EVENTS = 500;
25912
- DEFAULT_AUTO_FRESH_SUGGESTION_TURNS = 30;
25913
- MAX_IMAGES_PER_MESSAGE = 10;
25914
- FEEDBACK_WORKER_URL2 = "https://hello.kimiflare.com";
25915
- nextKey = 1;
25916
- mkKey = () => `evt_${nextKey++}`;
25917
- nextAssistantId = 1;
25918
- mkAssistantId = () => nextAssistantId++;
25924
+ init_config();
25925
+ init_version();
25926
+ COMMUTE_REPO = "https://github.com/sinameraji/kimiflare-commute.git";
25927
+ COMMUTE_BRANCH = "main";
25928
+ WORKER_NAME = "kimiflare-multi-agent";
25929
+ KV_TITLE = "kimiflare-multi-agent-OAUTH_KV";
25930
+ CF_API2 = "https://api.cloudflare.com/client/v4";
25931
+ TOKEN_TEMPLATE_URL = "https://dash.cloudflare.com/profile/api-tokens";
25919
25932
  }
25920
25933
  });
25921
25934
 
@@ -28883,7 +28896,7 @@ function dispatchSlashCommand(ctx, cmd) {
28883
28896
  if (!handler) return false;
28884
28897
  return handler(ctx, rest, arg);
28885
28898
  }
28886
- var handleExit, handleClear, handleFresh, handleReasoning, handleCost, handleShell, handleModel, handleGateway, handleMode, handleMultiAgent, handleTheme, handleUi, handlePlan, handleAuto, handleEdit, handleSkills, handleMemory, handleResume, handleCheckpoint, handleCompact, handleInit, handleUpdate, handleMcp, handleLsp, handleHooks, handleHello, handleInbox, handleReport, handleLogout, handleUpgrade, handleCommand, handleRemote, handleHelp, handleChangelogImage, handlers;
28899
+ var handleExit, handleClear, handleFresh, handleReasoning, handleCost, handleShell, handleModel, handleGateway, handleMode, handleMultiAgent, handleTheme, handleUi, handlePlan, handleAuto, handleEdit, handleSkills, handleMemory, handleResume, handleCheckpoint, handleCompact, handleInit, handleUpdate, handleMcp, handleLsp, handleHooks, handleHello, handleInbox, handleReport, handleLogout, handleUpgrade, handleTopup, handleManage, handleCommand, handleRemote, handleHelp, handleChangelogImage, handlers;
28887
28900
  var init_slash_commands = __esm({
28888
28901
  "src/ui/slash-commands.ts"() {
28889
28902
  "use strict";
@@ -29115,6 +29128,13 @@ var init_slash_commands = __esm({
29115
29128
  };
29116
29129
  handleModel = (ctx, rest, arg) => {
29117
29130
  const { cfg, setCfg, setEvents, mkKey: mkKey2 } = ctx;
29131
+ if (cfg?.cloudMode) {
29132
+ setEvents((e) => [
29133
+ ...e,
29134
+ { kind: "info", key: mkKey2(), text: "Model selection isn't available on KimiFlare Cloud \u2014 the model is managed for you." }
29135
+ ]);
29136
+ return true;
29137
+ }
29118
29138
  const sub = rest[0]?.toLowerCase() ?? "";
29119
29139
  if (!arg) {
29120
29140
  ctx.setShowModelPicker(true);
@@ -29978,7 +29998,7 @@ project: ${projectSettingsPath(cwd)}`
29978
29998
  handleHello = (ctx) => {
29979
29999
  const { setEvents, mkKey: mkKey2 } = ctx;
29980
30000
  const session = crypto.randomUUID();
29981
- const url = `${FEEDBACK_WORKER_URL2}/?s=${session}&v=${getAppVersion()}`;
30001
+ const url = `${FEEDBACK_WORKER_URL}/?s=${session}&v=${getAppVersion()}`;
29982
30002
  openBrowser(url);
29983
30003
  void (async () => {
29984
30004
  try {
@@ -30066,6 +30086,14 @@ project: ${projectSettingsPath(cwd)}`
30066
30086
  void ctx.upgrade();
30067
30087
  return true;
30068
30088
  };
30089
+ handleTopup = (ctx) => {
30090
+ void ctx.topup();
30091
+ return true;
30092
+ };
30093
+ handleManage = (ctx) => {
30094
+ void ctx.manageMembership();
30095
+ return true;
30096
+ };
30069
30097
  handleCommand = (ctx, rest) => {
30070
30098
  const { setEvents, mkKey: mkKey2 } = ctx;
30071
30099
  const sub = rest[0]?.toLowerCase() ?? "";
@@ -30177,7 +30205,7 @@ project: ${projectSettingsPath(cwd)}`
30177
30205
  setEvents((e) => [
30178
30206
  ...e,
30179
30207
  { kind: "info", key: mkKey2(), text: `Starting remote session for ${repo.owner}/${repo.name}...` },
30180
- { kind: "info", key: mkKey2(), text: `Budget: ${formatTokens5(budget)} tokens. TTL: ${ttl} min.` }
30208
+ { kind: "info", key: mkKey2(), text: `Budget: ${formatTokens4(budget)} tokens. TTL: ${ttl} min.` }
30181
30209
  ]);
30182
30210
  try {
30183
30211
  const data = await startRemoteSession({
@@ -30389,6 +30417,8 @@ project: ${projectSettingsPath(cwd)}`
30389
30417
  "/report": handleReport,
30390
30418
  "/logout": handleLogout,
30391
30419
  "/upgrade": handleUpgrade,
30420
+ "/topup": handleTopup,
30421
+ "/manage": handleManage,
30392
30422
  "/command": handleCommand,
30393
30423
  "/remote": handleRemote,
30394
30424
  "/changelog-image": handleChangelogImage,
@@ -32005,6 +32035,7 @@ var billing_exports = {};
32005
32035
  __export(billing_exports, {
32006
32036
  createCheckoutSession: () => createCheckoutSession,
32007
32037
  createCustomerPortalSession: () => createCustomerPortalSession,
32038
+ createTopupSession: () => createTopupSession,
32008
32039
  fetchBillingStatus: () => fetchBillingStatus
32009
32040
  });
32010
32041
  function authHeaders2(token, deviceId) {
@@ -32044,6 +32075,21 @@ async function createCheckoutSession(token, deviceId, priceId) {
32044
32075
  if (typeof data.url !== "string") return null;
32045
32076
  return { url: data.url };
32046
32077
  }
32078
+ async function createTopupSession(token, deviceId) {
32079
+ const res = await fetch(`${CLOUD_API_URL}/v1/billing/topup`, {
32080
+ method: "POST",
32081
+ headers: authHeaders2(token, deviceId),
32082
+ body: JSON.stringify({})
32083
+ });
32084
+ await detectKillSwitch(res);
32085
+ if (!res.ok) {
32086
+ const err = await res.json().catch(() => ({}));
32087
+ throw new Error(err.error || `Top-up failed: ${res.statusText}`);
32088
+ }
32089
+ const data = await res.json();
32090
+ if (typeof data.url !== "string") return null;
32091
+ return { url: data.url };
32092
+ }
32047
32093
  async function createCustomerPortalSession(token, deviceId) {
32048
32094
  const res = await fetch(`${CLOUD_API_URL}/v1/billing/portal`, {
32049
32095
  method: "POST",
@@ -32424,8 +32470,13 @@ ${wcagWarnings.join("\n")}` }
32424
32470
  description: c.description ?? "",
32425
32471
  source: c.source
32426
32472
  }));
32427
- const cloudCommands = cfg?.cloudMode ? [{ name: "upgrade", description: "Upgrade to KimiFlare Pro", source: "builtin" }] : [];
32428
- return [...BUILTIN_COMMANDS, ...cloudCommands, ...customs];
32473
+ const cloudCommands = cfg?.cloudMode ? [
32474
+ { name: "upgrade", description: "Upgrade to KimiFlare Pro", source: "builtin" },
32475
+ { name: "topup", description: "Buy a one-time token top-up (+50M)", source: "builtin" },
32476
+ { name: "manage", description: "Manage membership, billing & invoices", source: "builtin" }
32477
+ ] : [];
32478
+ const builtins = cfg?.cloudMode ? BUILTIN_COMMANDS.filter((c) => c.name !== "model") : BUILTIN_COMMANDS;
32479
+ return [...builtins, ...cloudCommands, ...customs];
32429
32480
  }, [customCommandsVersion, cfg?.cloudMode]);
32430
32481
  const modalActive = commandWizard !== null || commandPicker !== null || commandToDelete !== null || showCommandList || showLspWizard || resumeSessions !== null || checkpointSession !== null || resuming || perm !== null || limitModal !== null || loopModal !== null || showInboxModal || showHelpMenu || showModePicker || showMemoryPicker || showGatewayPicker || showSkillsPicker || showShellPicker;
32431
32482
  const loadFilePickerItems = useCallback10(async () => {
@@ -33165,6 +33216,98 @@ ${wcagWarnings.join("\n")}` }
33165
33216
  ]);
33166
33217
  }
33167
33218
  }, [cloudToken, cloudDeviceId, initialCloudToken, initialCloudDeviceId, mkKey, setEvents, setCloudBudget]);
33219
+ const handleTopup2 = useCallback10(async () => {
33220
+ const token = cloudToken ?? initialCloudToken;
33221
+ const did = cloudDeviceId ?? initialCloudDeviceId;
33222
+ if (!token) {
33223
+ setEvents((e) => [
33224
+ ...e,
33225
+ { kind: "error", key: mkKey(), text: "Cloud authentication required to buy a top-up." }
33226
+ ]);
33227
+ return;
33228
+ }
33229
+ setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "Opening top-up checkout\u2026" }]);
33230
+ try {
33231
+ const { createTopupSession: createTopupSession2 } = await Promise.resolve().then(() => (init_billing(), billing_exports));
33232
+ const session = await createTopupSession2(token, did);
33233
+ if (session?.url) {
33234
+ const { openBrowser: openBrowser2 } = await Promise.resolve().then(() => (init_app_helpers(), app_helpers_exports));
33235
+ openBrowser2(session.url);
33236
+ setEvents((e) => [
33237
+ ...e,
33238
+ { kind: "info", key: mkKey(), text: "Complete the one-time payment in your browser \u2014 your tokens are added automatically." }
33239
+ ]);
33240
+ const { fetchCloudUsage: fetchCloudUsage2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
33241
+ const baseline = cloudBudget?.limit ?? 0;
33242
+ for (let i = 0; i < 40; i++) {
33243
+ await new Promise((r) => setTimeout(r, 5e3));
33244
+ let usage2;
33245
+ try {
33246
+ usage2 = await fetchCloudUsage2(token, did);
33247
+ } catch {
33248
+ continue;
33249
+ }
33250
+ if (usage2 && usage2.input_token_limit > baseline) {
33251
+ setCloudBudget({ remaining: usage2.remaining, limit: usage2.input_token_limit });
33252
+ setEvents((e) => [
33253
+ ...e,
33254
+ { kind: "info", key: mkKey(), text: "\u2713 Top-up applied \u2014 your token balance has been increased. Thank you!" }
33255
+ ]);
33256
+ return;
33257
+ }
33258
+ }
33259
+ } else {
33260
+ setEvents((e) => [...e, { kind: "error", key: mkKey(), text: "Top-up unavailable. Please try again later." }]);
33261
+ }
33262
+ } catch (err) {
33263
+ setEvents((e) => [
33264
+ ...e,
33265
+ { kind: "error", key: mkKey(), text: `Top-up failed: ${err instanceof Error ? err.message : String(err)}` }
33266
+ ]);
33267
+ }
33268
+ }, [cloudToken, cloudDeviceId, initialCloudToken, initialCloudDeviceId, cloudBudget, mkKey, setEvents, setCloudBudget]);
33269
+ const handleManageMembership = useCallback10(async () => {
33270
+ const token = cloudToken ?? initialCloudToken;
33271
+ const did = cloudDeviceId ?? initialCloudDeviceId;
33272
+ if (!token) {
33273
+ setEvents((e) => [
33274
+ ...e,
33275
+ { kind: "error", key: mkKey(), text: "Cloud authentication required to manage your membership." }
33276
+ ]);
33277
+ return;
33278
+ }
33279
+ if (cloudBudget) {
33280
+ const used = cloudBudget.limit - cloudBudget.remaining;
33281
+ const fmt = (n) => `${(n / 1e6).toFixed(1)}M`;
33282
+ setEvents((e) => [
33283
+ ...e,
33284
+ { kind: "info", key: mkKey(), text: `Tokens this period: ${fmt(used)} used \xB7 ${fmt(cloudBudget.remaining)} left of ${fmt(cloudBudget.limit)}.` }
33285
+ ]);
33286
+ }
33287
+ setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "Opening your billing portal\u2026" }]);
33288
+ try {
33289
+ const { createCustomerPortalSession: createCustomerPortalSession2 } = await Promise.resolve().then(() => (init_billing(), billing_exports));
33290
+ const session = await createCustomerPortalSession2(token, did);
33291
+ if (session?.url) {
33292
+ const { openBrowser: openBrowser2 } = await Promise.resolve().then(() => (init_app_helpers(), app_helpers_exports));
33293
+ openBrowser2(session.url);
33294
+ setEvents((e) => [
33295
+ ...e,
33296
+ { kind: "info", key: mkKey(), text: "Manage your card, invoices, or cancel anytime in the browser tab that just opened." }
33297
+ ]);
33298
+ } else {
33299
+ setEvents((e) => [
33300
+ ...e,
33301
+ { kind: "error", key: mkKey(), text: "Billing portal unavailable. You may not have an active subscription \u2014 run /upgrade to start one." }
33302
+ ]);
33303
+ }
33304
+ } catch (err) {
33305
+ setEvents((e) => [
33306
+ ...e,
33307
+ { kind: "error", key: mkKey(), text: `Couldn't open billing portal: ${err instanceof Error ? err.message : String(err)}` }
33308
+ ]);
33309
+ }
33310
+ }, [cloudToken, cloudDeviceId, initialCloudToken, initialCloudDeviceId, cloudBudget, mkKey, setEvents]);
33168
33311
  const handleSaveProviderKey = useCallback10(
33169
33312
  (model, result) => {
33170
33313
  setKeyEntryFor(null);
@@ -33263,6 +33406,8 @@ ${wcagWarnings.join("\n")}` }
33263
33406
  initLsp: initLsp2,
33264
33407
  ensureSessionId,
33265
33408
  upgrade: handleUpgrade2,
33409
+ topup: handleTopup2,
33410
+ manageMembership: handleManageMembership,
33266
33411
  lspManagerRef,
33267
33412
  mcpManagerRef,
33268
33413
  hooksManagerRef,