@kernelonpanic/kitcode 1.0.0-beta → 1.1.1

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
@@ -62,7 +62,7 @@ var diagnosticsSchema = z.object({
62
62
  commands: z.array(z.string().trim().min(1).max(2e4)).max(8).default([])
63
63
  });
64
64
  var updatesSchema = z.object({
65
- checkOnStart: z.boolean().default(false)
65
+ checkOnStart: z.boolean().default(true)
66
66
  });
67
67
  var configSchema = z.object({
68
68
  version: z.literal(1).default(1),
@@ -658,6 +658,7 @@ import path3 from "path";
658
658
  import { randomUUID } from "crypto";
659
659
  import { readFile, realpath, rename, rm, writeFile } from "fs/promises";
660
660
  import path2 from "path";
661
+ var trustLock = Promise.resolve();
661
662
  var emptyTrust = () => ({ version: 1, workspaces: [] });
662
663
  async function canonicalWorkspace(cwd) {
663
664
  const resolved = await workspaceRootFor(cwd);
@@ -686,6 +687,7 @@ async function revokeWorkspaceTrust(cwd) {
686
687
  return workspace;
687
688
  }
688
689
  async function loadTrust() {
690
+ await trustLock;
689
691
  let parsed;
690
692
  try {
691
693
  parsed = JSON.parse(await readFile(trustPath, "utf8"));
@@ -702,15 +704,25 @@ async function loadTrust() {
702
704
  };
703
705
  }
704
706
  async function saveTrust(value) {
705
- await ensureDir(path2.dirname(trustPath));
706
- const temp = `${trustPath}.${randomUUID()}.tmp`;
707
+ const release = trustLock;
708
+ let resolveLock;
709
+ trustLock = new Promise((resolve3) => {
710
+ resolveLock = resolve3;
711
+ });
712
+ await release;
707
713
  try {
708
- await writeFile(temp, `${JSON.stringify(value, null, 2)}
714
+ await ensureDir(path2.dirname(trustPath));
715
+ const temp = `${trustPath}.${randomUUID()}.tmp`;
716
+ try {
717
+ await writeFile(temp, `${JSON.stringify(value, null, 2)}
709
718
  `, { encoding: "utf8", mode: 384 });
710
- await rename(temp, trustPath);
711
- } catch (error) {
712
- await rm(temp, { force: true });
713
- throw error;
719
+ await rename(temp, trustPath);
720
+ } catch (error) {
721
+ await rm(temp, { force: true });
722
+ throw error;
723
+ }
724
+ } finally {
725
+ resolveLock();
714
726
  }
715
727
  }
716
728
 
@@ -1631,11 +1643,12 @@ function createTurnBudget(limits, resolvePricing = pricingFor) {
1631
1643
  };
1632
1644
  }
1633
1645
  const pricing = resolvePricing(request.modelRef);
1634
- if (pricing && current.costUsd !== null) {
1646
+ if (current.costUsd !== null) {
1635
1647
  const remainingUsd = limits.maxCostUsdPerTurn - current.costUsd;
1636
- const inputUsd = estimatedInput * pricing.input / 1e6;
1648
+ const effectivePricing = pricing ?? { input: 3e-3, output: 0.015 };
1649
+ const inputUsd = estimatedInput * effectivePricing.input / 1e6;
1637
1650
  const affordableOutput = Math.floor(
1638
- (remainingUsd - inputUsd) * 1e6 / pricing.output
1651
+ (remainingUsd - inputUsd) * 1e6 / effectivePricing.output
1639
1652
  );
1640
1653
  outputLimit = Math.min(outputLimit, affordableOutput);
1641
1654
  if (outputLimit < 1) {
@@ -1813,7 +1826,7 @@ function beginCheckpoint(options) {
1813
1826
  lastCheckpointTimestamp = Math.max(Date.now(), lastCheckpointTimestamp + 1);
1814
1827
  const id = `${String(lastCheckpointTimestamp).padStart(13, "0")}-${randomBytes2(4).toString("hex")}`;
1815
1828
  await writeCheckpoint(path5.join(sessionDir, `${id}.json`), stored);
1816
- await pruneCheckpoints(sessionDir);
1829
+ pruneCheckpoints(sessionDir).catch(() => void 0);
1817
1830
  return { id, paths: entries.map((entry) => entry.path) };
1818
1831
  }
1819
1832
  };
@@ -2315,7 +2328,7 @@ async function isFile2(file) {
2315
2328
 
2316
2329
  // src/core/attachments.ts
2317
2330
  import { execFile } from "child_process";
2318
- import { readFile as readFile6, stat as stat5 } from "fs/promises";
2331
+ import { lstat as lstat2, readFile as readFile6, stat as stat5 } from "fs/promises";
2319
2332
  import os2 from "os";
2320
2333
  import path7 from "path";
2321
2334
  import { fileURLToPath } from "url";
@@ -2328,6 +2341,8 @@ var SENSITIVE_AUTO_NAMES = /* @__PURE__ */ new Set([
2328
2341
  ".netrc",
2329
2342
  ".npmrc",
2330
2343
  ".pypirc",
2344
+ ".pgpass",
2345
+ ".my.cnf",
2331
2346
  "auth.json",
2332
2347
  "credentials.json",
2333
2348
  "id_dsa",
@@ -2337,11 +2352,17 @@ var SENSITIVE_AUTO_NAMES = /* @__PURE__ */ new Set([
2337
2352
  "kitcode.json",
2338
2353
  "secret.json",
2339
2354
  "secrets.json",
2340
- "tokens.json"
2355
+ "tokens.json",
2356
+ "kubeconfig",
2357
+ "service-account.json"
2341
2358
  ]);
2342
- var SENSITIVE_AUTO_EXTENSIONS = /* @__PURE__ */ new Set([".jks", ".key", ".keystore", ".p12", ".pem", ".pfx"]);
2359
+ var SENSITIVE_AUTO_EXTENSIONS = /* @__PURE__ */ new Set([".jks", ".key", ".keystore", ".p12", ".pem", ".pfx", ".p8", ".der"]);
2343
2360
  async function loadAttachment(cwd, requestedPath) {
2344
2361
  const resolved = resolveAttachmentPath(cwd, requestedPath);
2362
+ const linkInfo = await lstat2(resolved).catch(() => null);
2363
+ if (linkInfo?.isSymbolicLink()) {
2364
+ throw new Error(`Cannot attach symbolic links: ${path7.basename(resolved)}`);
2365
+ }
2345
2366
  const info = await stat5(resolved).catch((error) => {
2346
2367
  if (error.code === "ENOENT") throw new Error(`Attachment not found: ${resolved}`);
2347
2368
  throw error;
@@ -2352,16 +2373,23 @@ async function loadAttachment(cwd, requestedPath) {
2352
2373
  async function loadAutomaticAttachment(cwd, requestedPath) {
2353
2374
  if (!looksLikeAttachmentPath(requestedPath)) return null;
2354
2375
  const resolved = resolveAttachmentPath(cwd, requestedPath);
2355
- const info = await stat5(resolved).catch((error) => {
2356
- if (error.code === "ENOENT" || error.code === "ENOTDIR") return null;
2357
- throw error;
2358
- });
2359
- if (!info?.isFile()) return null;
2360
2376
  if (isSensitiveAutomaticPath(resolved)) {
2361
2377
  throw new Error(
2362
2378
  `For safety, sensitive-looking files must be attached explicitly with /attach: ${path7.basename(resolved)}`
2363
2379
  );
2364
2380
  }
2381
+ if (path7.isAbsolute(resolved)) {
2382
+ const normalized = resolved.toLowerCase();
2383
+ const isProjectFile = normalized.includes(path7.sep + "src" + path7.sep) || normalized.includes(path7.sep + "lib" + path7.sep) || normalized.includes(path7.sep + "app" + path7.sep) || normalized.includes(path7.sep + "test" + path7.sep) || normalized.includes(path7.sep + "tests" + path7.sep) || normalized.includes(path7.sep + "public" + path7.sep) || normalized.includes(path7.sep + "assets" + path7.sep) || normalized.includes(path7.sep + "static" + path7.sep) || normalized.endsWith(".ts") || normalized.endsWith(".js") || normalized.endsWith(".tsx") || normalized.endsWith(".jsx") || normalized.endsWith(".py") || normalized.endsWith(".go") || normalized.endsWith(".rs") || normalized.endsWith(".java") || normalized.endsWith(".c") || normalized.endsWith(".cpp") || normalized.endsWith(".h") || normalized.endsWith(".hpp") || normalized.endsWith(".css") || normalized.endsWith(".html") || normalized.endsWith(".json") || normalized.endsWith(".yaml") || normalized.endsWith(".yml") || normalized.endsWith(".md") || normalized.endsWith(".txt") || normalized.endsWith(".toml") || normalized.endsWith(".ini") || normalized.endsWith(".cfg") || normalized.endsWith(".sh") || normalized.endsWith(".bat") || normalized.endsWith(".cmd") || normalized.endsWith(".ps1") || normalized.endsWith(".dockerfile") || normalized.endsWith(".makefile");
2384
+ if (!isProjectFile) return null;
2385
+ }
2386
+ const linkInfo = await lstat2(resolved).catch(() => null);
2387
+ if (linkInfo?.isSymbolicLink()) return null;
2388
+ const info = await stat5(resolved).catch((error) => {
2389
+ if (error.code === "ENOENT" || error.code === "ENOTDIR") return null;
2390
+ throw error;
2391
+ });
2392
+ if (!info?.isFile()) return null;
2365
2393
  return loadResolvedAttachment(resolved, info.size);
2366
2394
  }
2367
2395
  function looksLikeAttachmentPath(value) {
@@ -2622,7 +2650,7 @@ function normalizeInputPath(value) {
2622
2650
  return value.slice(1, -1);
2623
2651
  }
2624
2652
  }
2625
- return value.replace(/\\([\\ "'()\[\]{}&;#$!])/g, (_match, character) => character);
2653
+ return value.replace(/\\([\\ "'()\[\]&;#$!])/g, (_match, character) => character);
2626
2654
  }
2627
2655
  function safeName(value) {
2628
2656
  return value.replace(/[\r\n\0-\x1f\x7f]/g, "").slice(0, 200) || "attachment";
@@ -2708,7 +2736,8 @@ function compactCutIndex(history) {
2708
2736
  function estimateCompactTokens(history) {
2709
2737
  const cut = compactCutIndex(history);
2710
2738
  if (cut <= 0) return 1;
2711
- return Math.max(1, renderHistory(history.slice(0, cut)).length + 1e3);
2739
+ const rendered = renderHistory(history.slice(0, cut));
2740
+ return Math.max(1, Math.min(rendered.length, MAX_SOURCE_CHARS) + 1e3);
2712
2741
  }
2713
2742
  function isConversationUser(message) {
2714
2743
  if (message.role !== "user") return false;
@@ -2797,6 +2826,8 @@ var STEP_LIMIT_NOTE = `The subagent was stopped after ${MAX_SUBAGENT_STEPS} mode
2797
2826
  var SUBAGENT_SYSTEM = [
2798
2827
  "You are a subagent launched by the main kitcode agent to carry out one task on your own.",
2799
2828
  "",
2829
+ "SECURITY CRITICAL: You must NEVER execute commands that read, modify, or exfiltrate sensitive files including but not limited to: /etc/passwd, /etc/shadow, ~/.ssh/*, ~/.aws/*, ~/.env*, .git/config, credentials files, or any files outside the workspace. Never run `curl ... | bash`, `wget ... | bash`, or similar patterns. Never send data to external servers.",
2830
+ "",
2800
2831
  "Nobody is reading along and nobody can answer you. Never ask a question, never stop to propose a plan, never wait for approval. If the request is ambiguous, take the most reasonable reading, do the work, and state the assumption in your answer.",
2801
2832
  "",
2802
2833
  'Your final message is the return value \u2014 it is the only thing the caller ever sees. End with the answer itself: the findings, the file paths, the conclusion. Never end with "I will now..." or a recap of the steps you took. Carry over every concrete detail the caller needs (paths, names, line numbers) and leave out the narration of how you found them.'
@@ -2970,10 +3001,14 @@ function printableInput(input) {
2970
3001
  }
2971
3002
  async function invoke(call, tool, input, signal) {
2972
3003
  try {
2973
- const result = await call(tool, input, signal);
3004
+ if (input !== null && typeof input !== "object" || Array.isArray(input)) {
3005
+ return { content: `MCP tool "${tool}" received invalid input: arguments must be a JSON object.`, isError: true };
3006
+ }
3007
+ const result = await call(tool, input ?? {}, signal);
2974
3008
  return { content: flattenResult(result), isError: result.isError };
2975
3009
  } catch (error) {
2976
- return { content: error instanceof Error ? error.message : String(error), isError: true };
3010
+ const message = error instanceof Error ? error.message : String(error);
3011
+ return { content: redactSecrets(message), isError: true };
2977
3012
  }
2978
3013
  }
2979
3014
  function flattenResult(result) {
@@ -3015,10 +3050,18 @@ function clip(value) {
3015
3050
  // package.json
3016
3051
  var package_default = {
3017
3052
  name: "@kernelonpanic/kitcode",
3018
- version: "1.0.0-beta",
3053
+ version: "1.1.1",
3019
3054
  description: "Terminal coding agent with a config you never have to write by hand",
3020
3055
  type: "module",
3021
3056
  license: "MIT",
3057
+ homepage: "https://github.com/KernelEditor/KitCode#readme",
3058
+ bugs: {
3059
+ url: "https://github.com/KernelEditor/KitCode/issues"
3060
+ },
3061
+ repository: {
3062
+ type: "git",
3063
+ url: "git+https://github.com/KernelEditor/KitCode.git"
3064
+ },
3022
3065
  bin: {
3023
3066
  kitcode: "dist/index.js"
3024
3067
  },
@@ -3074,8 +3117,8 @@ var package_default = {
3074
3117
 
3075
3118
  // src/version.ts
3076
3119
  var KITCODE_VERSION = package_default.version;
3077
- var KITCODE_REPOSITORY = "PanicOnKernel/KitCode";
3078
- var KITCODE_COMMIT = true ? "e745690ed8d8519a62eada43de2138b5ba2fd754" : "development";
3120
+ var KITCODE_REPOSITORY = "KernelEditor/KitCode";
3121
+ var KITCODE_COMMIT = true ? "a57e54102a5e1ca1e60cec4ee9c629c890d27057" : "development";
3079
3122
 
3080
3123
  // src/mcp/client.ts
3081
3124
  var clientInfo = { name: "kitcode", version: KITCODE_VERSION };
@@ -3099,6 +3142,36 @@ var inheritedEnvKeys = /* @__PURE__ */ new Set([
3099
3142
  "APPDATA",
3100
3143
  "LOCALAPPDATA"
3101
3144
  ]);
3145
+ var blockedEnvKeys = /* @__PURE__ */ new Set([
3146
+ "ANTHROPIC_API_KEY",
3147
+ "OPENAI_API_KEY",
3148
+ "AWS_ACCESS_KEY_ID",
3149
+ "AWS_SECRET_ACCESS_KEY",
3150
+ "AWS_SESSION_TOKEN",
3151
+ "GITHUB_TOKEN",
3152
+ "GH_TOKEN",
3153
+ "GIT_TOKEN",
3154
+ "NPM_TOKEN",
3155
+ "PYPI_TOKEN",
3156
+ "TWINE_PASSWORD",
3157
+ "DOCKER_PASSWORD",
3158
+ "DATABASE_URL",
3159
+ "REDIS_URL",
3160
+ "MONGODB_URI",
3161
+ "POSTGRES_PASSWORD",
3162
+ "MYSQL_PASSWORD",
3163
+ "PRIVATE_KEY",
3164
+ "SSH_KEY",
3165
+ "GPG_KEY",
3166
+ "SIGNING_KEY",
3167
+ "COOKIE",
3168
+ "SESSION_SECRET",
3169
+ "JWT_SECRET",
3170
+ "ENCRYPTION_KEY",
3171
+ "MASTER_KEY",
3172
+ "API_KEY",
3173
+ "API_SECRET"
3174
+ ]);
3102
3175
  function createMcpManager(servers) {
3103
3176
  const serverStates = /* @__PURE__ */ new Map();
3104
3177
  const sessions = /* @__PURE__ */ new Map();
@@ -3245,7 +3318,9 @@ function configuredSecrets(config) {
3245
3318
  function inheritedEnv(source = process.env) {
3246
3319
  const env = {};
3247
3320
  for (const [key, value] of Object.entries(source)) {
3248
- if (value !== void 0 && inheritedEnvKeys.has(key)) env[key] = value;
3321
+ if (value !== void 0 && inheritedEnvKeys.has(key) && !blockedEnvKeys.has(key)) {
3322
+ env[key] = value;
3323
+ }
3249
3324
  }
3250
3325
  return env;
3251
3326
  }
@@ -4070,7 +4145,7 @@ function isFileEdit(tool) {
4070
4145
  }
4071
4146
 
4072
4147
  // src/tools/edit.ts
4073
- import { readFile as readFile8, stat as stat6, writeFile as writeFile6 } from "fs/promises";
4148
+ import { lstat as lstat3, readFile as readFile8, stat as stat6, writeFile as writeFile6 } from "fs/promises";
4074
4149
  var MAX_FILE_BYTES2 = 5e6;
4075
4150
  function countOccurrences(haystack, needle) {
4076
4151
  if (needle === "") return 0;
@@ -4126,6 +4201,10 @@ var editTool = {
4126
4201
  const { path: path14, oldString, newString, replaceAll = false } = input;
4127
4202
  const safe = resolveInside(ctx.cwd, path14);
4128
4203
  if (!safe.ok) return { content: safe.reason, isError: true };
4204
+ const linkInfo = await lstat3(safe.path).catch(() => null);
4205
+ if (linkInfo?.isSymbolicLink()) {
4206
+ return { content: `Cannot edit ${path14}: the path is a symbolic link. Remove the symlink first.`, isError: true };
4207
+ }
4129
4208
  const info = await stat6(safe.path).catch(() => null);
4130
4209
  if (info && info.size > MAX_FILE_BYTES2) {
4131
4210
  return {
@@ -4243,13 +4322,25 @@ var sensitiveNames = /* @__PURE__ */ new Set([
4243
4322
  ".npmrc",
4244
4323
  ".pypirc",
4245
4324
  ".git-credentials",
4325
+ ".netrc",
4326
+ ".pgpass",
4327
+ ".my.cnf",
4328
+ ".docker/config.json",
4246
4329
  "auth.json",
4247
4330
  "credentials",
4248
4331
  "credentials.json",
4249
4332
  "id_rsa",
4250
- "id_ed25519"
4333
+ "id_ed25519",
4334
+ "id_dsa",
4335
+ "id_ecdsa",
4336
+ "kubeconfig",
4337
+ "known_hosts",
4338
+ "secret.json",
4339
+ "secrets.json",
4340
+ "tokens.json",
4341
+ "service-account.json"
4251
4342
  ]);
4252
- var sensitiveExtensions = /* @__PURE__ */ new Set([".pem", ".key", ".p12", ".pfx"]);
4343
+ var sensitiveExtensions = /* @__PURE__ */ new Set([".pem", ".key", ".p12", ".pfx", ".jks", ".keystore", ".p8", ".der"]);
4253
4344
  function isSensitivePath(value) {
4254
4345
  if (typeof value !== "string") return false;
4255
4346
  const normalized = value.replaceAll("\\", "/").toLowerCase();
@@ -4261,7 +4352,7 @@ function isSensitivePath(value) {
4261
4352
  function mentionsSensitivePattern(value) {
4262
4353
  if (typeof value !== "string") return false;
4263
4354
  const lower = value.toLowerCase();
4264
- return isSensitivePath(lower) || lower.includes(".env") || lower.includes(".npmrc") || lower.includes("auth.json") || /\.(?:pem|key|p12|pfx)(?:$|[^a-z0-9])/.test(lower);
4355
+ return isSensitivePath(lower) || lower.includes(".env") || lower.includes(".npmrc") || lower.includes(".netrc") || lower.includes(".pgpass") || lower.includes("auth.json") || lower.includes("credentials") || lower.includes("kubeconfig") || lower.includes("secret") || lower.includes("tokens.json") || /\.(?:pem|key|p12|pfx|jks|keystore|p8|der)(?:$|[^a-z0-9])/.test(lower);
4265
4356
  }
4266
4357
 
4267
4358
  // src/tools/regex.ts
@@ -4538,7 +4629,7 @@ function toToolSchema(tool) {
4538
4629
  }
4539
4630
 
4540
4631
  // src/tools/write.ts
4541
- import { mkdir as mkdir3, readFile as readFile11, stat as stat9, writeFile as writeFile7 } from "fs/promises";
4632
+ import { lstat as lstat4, mkdir as mkdir3, readFile as readFile11, stat as stat9, writeFile as writeFile7 } from "fs/promises";
4542
4633
  import { dirname as dirname2 } from "path";
4543
4634
  var MAX_FILE_BYTES5 = 5e6;
4544
4635
  var writeTool = {
@@ -4589,6 +4680,10 @@ var writeTool = {
4589
4680
  }
4590
4681
  const safe = resolveInside(ctx.cwd, path14);
4591
4682
  if (!safe.ok) return { content: safe.reason, isError: true };
4683
+ const linkInfo = await lstat4(safe.path).catch(() => null);
4684
+ if (linkInfo?.isSymbolicLink()) {
4685
+ return { content: `Cannot write ${path14}: the path is a symbolic link. Remove the symlink first.`, isError: true };
4686
+ }
4592
4687
  const beforeInfo = await stat9(safe.path).catch(() => null);
4593
4688
  if (beforeInfo && beforeInfo.size > MAX_FILE_BYTES5) {
4594
4689
  return {
@@ -5438,6 +5533,28 @@ async function boot(options) {
5438
5533
  }
5439
5534
  return { removed: providerId, wasActive, ...nextModel ? { nextModel } : {} };
5440
5535
  },
5536
+ async changeProviderKey(providerId, newKey) {
5537
+ const provider = config.providers[providerId];
5538
+ if (!provider) throw new Error(`Provider "${providerId}" is not configured.`);
5539
+ const previousKey = auth[providerId];
5540
+ auth[providerId] = newKey;
5541
+ try {
5542
+ const testRegistry = createRegistry(config, auth);
5543
+ await loadModels(testRegistry.get(providerId));
5544
+ } catch (error) {
5545
+ auth[providerId] = previousKey;
5546
+ throw new Error(
5547
+ `Key verification failed for "${providerId}": ${error instanceof Error ? error.message : String(error)}`
5548
+ );
5549
+ }
5550
+ await saveAuth(auth);
5551
+ registry = createRegistry(config, auth);
5552
+ if (parseModelRef(modelRef)?.provider === providerId) {
5553
+ const models = await loadModels(registry.get(providerId));
5554
+ rememberModels(providerId, models);
5555
+ void refreshModelContextWindow();
5556
+ }
5557
+ },
5441
5558
  sessionId: () => session.id,
5442
5559
  async newSession() {
5443
5560
  await persistQueue.catch(() => void 0);
@@ -5744,6 +5861,17 @@ ${lines.join("\n")}` : null;
5744
5861
  startupUpdateCheck: () => startupUpdate,
5745
5862
  async run(history, hooks, signal) {
5746
5863
  syncMcpTools();
5864
+ const runProviderId = parseModelRef(modelRef)?.provider;
5865
+ if (runProviderId) {
5866
+ try {
5867
+ const models = await loadModels(registry.get(runProviderId));
5868
+ rememberModels(runProviderId, models);
5869
+ } catch (error) {
5870
+ warnings.push(
5871
+ `Could not refresh models for "${runProviderId}": ${error instanceof Error ? error.message : String(error)}`
5872
+ );
5873
+ }
5874
+ }
5747
5875
  let requestHistory = history;
5748
5876
  const budget = createTurnBudget(config.budget, resolvePricing);
5749
5877
  const previousContext = runtime.modelContext();
@@ -6037,7 +6165,7 @@ function validateKey(value) {
6037
6165
  import { render } from "ink";
6038
6166
 
6039
6167
  // src/ui/App.tsx
6040
- import { Box as Box12, useApp } from "ink";
6168
+ import { Box as Box12, Text as Text12, useApp, useInput as useInput2 } from "ink";
6041
6169
  import { execFile as execFile2, execFileSync as execFileSync2 } from "child_process";
6042
6170
  import { useCallback, useEffect as useEffect2, useMemo as useMemo4, useRef as useRef4, useState as useState5 } from "react";
6043
6171
 
@@ -6079,6 +6207,7 @@ var COMMANDS = [
6079
6207
  { name: "login" },
6080
6208
  { name: "provider" },
6081
6209
  { name: "logout", args: "[provider]" },
6210
+ { name: "key", args: "[provider]" },
6082
6211
  { name: "effort" },
6083
6212
  { name: "thinking" },
6084
6213
  { name: "budget", args: "[tokens]" },
@@ -6166,6 +6295,43 @@ function editDistance(a, b) {
6166
6295
  return previous[b.length] ?? 0;
6167
6296
  }
6168
6297
 
6298
+ // src/ui/App.tsx
6299
+ import TextInput2 from "ink-text-input";
6300
+
6301
+ // src/ui/theme.ts
6302
+ import { createContext, useContext } from "react";
6303
+ var PRESETS = {
6304
+ purple: "#a78bfa",
6305
+ violet: "#8b5cf6",
6306
+ blue: "#60a5fa",
6307
+ cyan: "#22d3ee",
6308
+ green: "#4ade80",
6309
+ orange: "#fb923c",
6310
+ pink: "#f472b6",
6311
+ red: "#f87171",
6312
+ yellow: "#fbbf24",
6313
+ white: "#e5e7eb"
6314
+ };
6315
+ var DEFAULT_ACCENT = PRESETS.purple;
6316
+ function resolveAccent(value) {
6317
+ if (!value) return DEFAULT_ACCENT;
6318
+ const preset = PRESETS[value.toLowerCase()];
6319
+ if (preset) return preset;
6320
+ return /^#[0-9a-f]{6}$/i.test(value) ? value : DEFAULT_ACCENT;
6321
+ }
6322
+ function makeTheme(accent) {
6323
+ return {
6324
+ accent: resolveAccent(accent),
6325
+ ok: PRESETS.green,
6326
+ warn: PRESETS.yellow,
6327
+ error: PRESETS.red
6328
+ };
6329
+ }
6330
+ var ThemeContext = createContext(makeTheme(void 0));
6331
+ function useTheme() {
6332
+ return useContext(ThemeContext);
6333
+ }
6334
+
6169
6335
  // src/ui/components/Confirm.tsx
6170
6336
  import { Box, Text } from "ink";
6171
6337
 
@@ -6199,7 +6365,7 @@ function normalizeHotkey(char) {
6199
6365
  }
6200
6366
 
6201
6367
  // src/ui/i18n.ts
6202
- import { createContext, useContext } from "react";
6368
+ import { createContext as createContext2, useContext as useContext2 } from "react";
6203
6369
  var LANGS = [
6204
6370
  { key: "en", label: "English" },
6205
6371
  { key: "ru", label: "\u0420\u0443\u0441\u0441\u043A\u0438\u0439" }
@@ -6229,6 +6395,7 @@ var en = {
6229
6395
  workingFor: (elapsed) => `working ${elapsed}`,
6230
6396
  titleProvider: "Provider",
6231
6397
  titleLogout: "Sign out of a provider",
6398
+ titleKeyChange: "Change API key for a provider",
6232
6399
  titleSessions: "Resume a session",
6233
6400
  titleSessionAction: "Session action",
6234
6401
  resumed: (id, count) => `Resumed ${id} \u2014 ${count} messages restored`,
@@ -6316,6 +6483,9 @@ var en = {
6316
6483
  logoutAskBody: "Its API key is removed from auth.json and the provider is dropped from the config.",
6317
6484
  loggedOut: (provider) => `Signed out of "${provider}".`,
6318
6485
  logoutNothing: "No provider is signed in.",
6486
+ keyChangeAsk: (provider) => `Change API key for "${provider}"?`,
6487
+ keyChangeAskBody: "The new key replaces the old one. Models are re-fetched automatically.",
6488
+ keyChanged: (provider) => `API key updated for "${provider}".`,
6319
6489
  promptUsage: "Usage: /prompt save <name>",
6320
6490
  promptNothing: "Nothing to save yet \u2014 send a message first.",
6321
6491
  promptSaved: (name) => `Saved prompt "${name}"`,
@@ -6347,6 +6517,7 @@ var en = {
6347
6517
  login: "add a provider (url + key)",
6348
6518
  provider: "switch between configured providers",
6349
6519
  logout: "choose a provider to sign out from",
6520
+ key: "change API key for a provider",
6350
6521
  effort: "set reasoning depth",
6351
6522
  thinking: "toggle reasoning",
6352
6523
  budget: "set token budget per turn",
@@ -6407,6 +6578,7 @@ var ru = {
6407
6578
  workingFor: (elapsed) => `\u0434\u0443\u043C\u0430\u0435\u0442 ${elapsed}`,
6408
6579
  titleProvider: "\u041F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440",
6409
6580
  titleLogout: "\u0412\u044B\u0431\u0440\u0430\u0442\u044C API \u0434\u043B\u044F \u0432\u044B\u0445\u043E\u0434\u0430",
6581
+ titleKeyChange: "\u0421\u043C\u0435\u043D\u0438\u0442\u044C \u043A\u043B\u044E\u0447 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430",
6410
6582
  titleSessions: "\u0412\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C \u0441\u0435\u0441\u0441\u0438\u044E",
6411
6583
  titleSessionAction: "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0441 \u0441\u0435\u0441\u0441\u0438\u0435\u0439",
6412
6584
  resumed: (id, count) => `\u0421\u0435\u0441\u0441\u0438\u044F ${id} \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430 \u2014 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439: ${count}`,
@@ -6494,6 +6666,9 @@ var ru = {
6494
6666
  logoutAskBody: "\u041A\u043B\u044E\u0447 \u0431\u0443\u0434\u0435\u0442 \u0443\u0434\u0430\u043B\u0451\u043D \u0438\u0437 auth.json, \u0430 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440 \u2014 \u0438\u0437 \u043A\u043E\u043D\u0444\u0438\u0433\u0430.",
6495
6667
  loggedOut: (provider) => `\u0412\u044B\u043F\u043E\u043B\u043D\u0435\u043D \u0432\u044B\u0445\u043E\u0434 \u0438\u0437 \xAB${provider}\xBB.`,
6496
6668
  logoutNothing: "\u041D\u0438 \u043E\u0434\u0438\u043D \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440 \u043D\u0435 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D.",
6669
+ keyChangeAsk: (provider) => `\u0421\u043C\u0435\u043D\u0438\u0442\u044C \u043A\u043B\u044E\u0447 \u0434\u043B\u044F \xAB${provider}\xBB?`,
6670
+ keyChangeAskBody: "\u041D\u043E\u0432\u044B\u0439 \u043A\u043B\u044E\u0447 \u0437\u0430\u043C\u0435\u043D\u0438\u0442 \u0441\u0442\u0430\u0440\u044B\u0439. \u041C\u043E\u0434\u0435\u043B\u0438 \u043E\u0431\u043D\u043E\u0432\u044F\u0442\u0441\u044F \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438.",
6671
+ keyChanged: (provider) => `\u041A\u043B\u044E\u0447 \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D \u0434\u043B\u044F \xAB${provider}\xBB.`,
6497
6672
  promptUsage: "\u0418\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D\u0438\u0435: /prompt save <\u0438\u043C\u044F>",
6498
6673
  promptNothing: "\u041F\u043E\u043A\u0430 \u043D\u0435\u0447\u0435\u0433\u043E \u0441\u043E\u0445\u0440\u0430\u043D\u044F\u0442\u044C \u2014 \u0441\u043D\u0430\u0447\u0430\u043B\u0430 \u043E\u0442\u043F\u0440\u0430\u0432\u044C \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435.",
6499
6674
  promptSaved: (name) => `\u041F\u0440\u043E\u043C\u0442 \xAB${name}\xBB \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D`,
@@ -6525,6 +6700,7 @@ var ru = {
6525
6700
  login: "\u0434\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430 (url + \u043A\u043B\u044E\u0447)",
6526
6701
  provider: "\u043F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0438\u0442\u044C\u0441\u044F \u043C\u0435\u0436\u0434\u0443 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430\u043C\u0438",
6527
6702
  logout: "\u0432\u044B\u0431\u0440\u0430\u0442\u044C \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430 \u0438 \u0443\u0434\u0430\u043B\u0438\u0442\u044C \u0435\u0433\u043E \u043A\u043B\u044E\u0447",
6703
+ key: "\u0441\u043C\u0435\u043D\u0438\u0442\u044C \u043A\u043B\u044E\u0447 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430",
6528
6704
  effort: "\u0433\u043B\u0443\u0431\u0438\u043D\u0430 \u0440\u0430\u0437\u043C\u044B\u0448\u043B\u0435\u043D\u0438\u0439",
6529
6705
  thinking: "\u0432\u043A\u043B\u044E\u0447\u0438\u0442\u044C/\u0432\u044B\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0440\u0430\u0437\u043C\u044B\u0448\u043B\u0435\u043D\u0438\u044F",
6530
6706
  budget: "\u043B\u0438\u043C\u0438\u0442 \u0442\u043E\u043A\u0435\u043D\u043E\u0432 \u0437\u0430 \u0445\u043E\u0434 (0 = \u0431\u0435\u0437\u043B\u0438\u043C\u0438\u0442)",
@@ -6562,43 +6738,9 @@ var DICTIONARIES = { en, ru };
6562
6738
  function stringsFor(lang) {
6563
6739
  return DICTIONARIES[lang ?? "en"];
6564
6740
  }
6565
- var StringsContext = createContext(en);
6741
+ var StringsContext = createContext2(en);
6566
6742
  function useStrings() {
6567
- return useContext(StringsContext);
6568
- }
6569
-
6570
- // src/ui/theme.ts
6571
- import { createContext as createContext2, useContext as useContext2 } from "react";
6572
- var PRESETS = {
6573
- purple: "#a78bfa",
6574
- violet: "#8b5cf6",
6575
- blue: "#60a5fa",
6576
- cyan: "#22d3ee",
6577
- green: "#4ade80",
6578
- orange: "#fb923c",
6579
- pink: "#f472b6",
6580
- red: "#f87171",
6581
- yellow: "#fbbf24",
6582
- white: "#e5e7eb"
6583
- };
6584
- var DEFAULT_ACCENT = PRESETS.purple;
6585
- function resolveAccent(value) {
6586
- if (!value) return DEFAULT_ACCENT;
6587
- const preset = PRESETS[value.toLowerCase()];
6588
- if (preset) return preset;
6589
- return /^#[0-9a-f]{6}$/i.test(value) ? value : DEFAULT_ACCENT;
6590
- }
6591
- function makeTheme(accent) {
6592
- return {
6593
- accent: resolveAccent(accent),
6594
- ok: PRESETS.green,
6595
- warn: PRESETS.yellow,
6596
- error: PRESETS.red
6597
- };
6598
- }
6599
- var ThemeContext = createContext2(makeTheme(void 0));
6600
- function useTheme() {
6601
- return useContext2(ThemeContext);
6743
+ return useContext2(StringsContext);
6602
6744
  }
6603
6745
 
6604
6746
  // src/ui/components/Confirm.tsx
@@ -7375,7 +7517,10 @@ function StatusBar({ status }) {
7375
7517
  }
7376
7518
  )
7377
7519
  ] }),
7378
- details.length > 0 && /* @__PURE__ */ jsx9(Box9, { flexWrap: "wrap", children: /* @__PURE__ */ jsx9(Segments, { items: details }) })
7520
+ details.length > 0 && /* @__PURE__ */ jsx9(Box9, { flexWrap: "wrap", children: details.map((item, index) => /* @__PURE__ */ jsxs9(Fragment2, { children: [
7521
+ index > 0 && /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " \xB7 " }),
7522
+ item
7523
+ ] }, index)) })
7379
7524
  ]
7380
7525
  }
7381
7526
  );
@@ -7385,11 +7530,7 @@ function Segments({ items }) {
7385
7530
  const elements = [];
7386
7531
  if (index > 0) {
7387
7532
  elements.push(
7388
- /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
7389
- " ",
7390
- "\xB7",
7391
- " "
7392
- ] }, `sep-${index}`)
7533
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " \xB7 " }) }, `sep-${index}`)
7393
7534
  );
7394
7535
  }
7395
7536
  elements.push(/* @__PURE__ */ jsx9(Fragment2, { children: item }, `item-${index}`));
@@ -7475,7 +7616,7 @@ import Spinner2 from "ink-spinner";
7475
7616
  // src/ui/markdown.tsx
7476
7617
  import { Box as Box10, Text as Text10 } from "ink";
7477
7618
  import { useMemo as useMemo2 } from "react";
7478
- import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
7619
+ import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
7479
7620
  function Markdown({ children }) {
7480
7621
  const blocks = useMemo2(() => extractBlocks(children), [children]);
7481
7622
  return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: blocks.map((block, i) => /* @__PURE__ */ jsx10(BlockView, { block }, i)) });
@@ -7488,38 +7629,108 @@ function BlockView({ block }) {
7488
7629
  return /* @__PURE__ */ jsx10(Box10, { marginTop: block.level === 1 ? 1 : 0, children: /* @__PURE__ */ jsx10(Text10, { bold: true, children: truncateBySize(block.text ?? "", size) }) });
7489
7630
  }
7490
7631
  case "blockquote":
7491
- return /* @__PURE__ */ jsx10(Box10, { marginLeft: 2, children: /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
7492
- "\u2502 ",
7493
- block.text
7494
- ] }) });
7632
+ return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", marginLeft: 2, children: (block.text ?? "").split("\n").map((line, i) => /* @__PURE__ */ jsxs10(Box10, { children: [
7633
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "\u2502 " }),
7634
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: line })
7635
+ ] }, i)) });
7495
7636
  case "ul":
7496
7637
  return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: (block.items ?? []).map((item, i) => /* @__PURE__ */ jsxs10(Box10, { children: [
7497
7638
  /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "\u2022 " }),
7498
- /* @__PURE__ */ jsx10(Text10, { children: inline(item) })
7639
+ /* @__PURE__ */ jsx10(Box10, { marginLeft: 2, children: /* @__PURE__ */ jsx10(BlockView, { block: item }) })
7499
7640
  ] }, i)) });
7500
7641
  case "ol":
7501
7642
  return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: (block.items ?? []).map((item, i) => /* @__PURE__ */ jsxs10(Box10, { children: [
7502
7643
  /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: `${i + 1}. ` }),
7503
- /* @__PURE__ */ jsx10(Text10, { children: inline(item) })
7644
+ /* @__PURE__ */ jsx10(Box10, { marginLeft: 2, children: /* @__PURE__ */ jsx10(BlockView, { block: item }) })
7504
7645
  ] }, i)) });
7505
7646
  case "paragraph":
7506
7647
  default:
7507
7648
  return /* @__PURE__ */ jsx10(Text10, { children: inline(block.text ?? "") });
7649
+ case "table":
7650
+ return /* @__PURE__ */ jsx10(TableView, { block });
7651
+ case "code":
7652
+ return /* @__PURE__ */ jsx10(CodeBlock, { lang: block.lang, code: block.text ?? "" });
7653
+ case "hr":
7654
+ return /* @__PURE__ */ jsx10(Box10, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "\u2500".repeat(60) }) });
7655
+ }
7656
+ }
7657
+ function CodeBlock({ lang, code }) {
7658
+ const theme = useTheme();
7659
+ const lines = code.split("\n");
7660
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", marginTop: 1, children: [
7661
+ lang && /* @__PURE__ */ jsx10(Text10, { dimColor: true, color: theme.accent, children: lang }),
7662
+ /* @__PURE__ */ jsx10(Box10, { borderColor: "gray", borderStyle: "round", paddingX: 1, children: /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: lines.map((line, i) => /* @__PURE__ */ jsx10(Text10, { color: "cyan", children: line }, i)) }) })
7663
+ ] });
7664
+ }
7665
+ function TableView({ block }) {
7666
+ const headers = block.headers ?? [];
7667
+ const rows = block.rows ?? [];
7668
+ if (headers.length === 0 && rows.length === 0) {
7669
+ return /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "(empty table)" });
7508
7670
  }
7671
+ const colCount = Math.max(headers.length, ...rows.map((r) => r?.length ?? 0));
7672
+ if (colCount === 0) {
7673
+ return /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "(empty table)" });
7674
+ }
7675
+ const colWidths = new Array(colCount).fill(0);
7676
+ const allRows = [headers, ...rows];
7677
+ for (const row of allRows) {
7678
+ for (let i = 0; i < colCount; i++) {
7679
+ const cell = row[i] ?? "";
7680
+ colWidths[i] = Math.max(colWidths[i], [...cell].length);
7681
+ }
7682
+ }
7683
+ const separator = colWidths.map((w) => "\u2500".repeat(w)).join("\u253C");
7684
+ const lines = [];
7685
+ allRows.forEach((row, rowIdx) => {
7686
+ const cells = [];
7687
+ for (let i = 0; i < colCount; i++) {
7688
+ const cell = row[i] ?? "";
7689
+ const align = block.colAligns?.[i] ?? "left";
7690
+ const visualWidth = [...cell].length;
7691
+ const padWidth = colWidths[i] + (cell.length - visualWidth);
7692
+ let padded;
7693
+ if (align === "right") {
7694
+ padded = cell.padStart(padWidth);
7695
+ } else if (align === "center") {
7696
+ const totalPad = padWidth - visualWidth;
7697
+ const left = Math.floor(totalPad / 2);
7698
+ padded = " ".repeat(left) + cell + " ".repeat(totalPad - left);
7699
+ } else {
7700
+ padded = cell.padEnd(padWidth);
7701
+ }
7702
+ cells.push(padded);
7703
+ }
7704
+ lines.push(
7705
+ /* @__PURE__ */ jsx10(Text10, { bold: rowIdx === 0, children: cells.join(" \u2502 ") }, `row-${rowIdx}`)
7706
+ );
7707
+ if (rowIdx === 0) {
7708
+ lines.push(/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: separator }, `sep-${rowIdx}`));
7709
+ }
7710
+ });
7711
+ return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: lines });
7509
7712
  }
7713
+ var CHARS_PER_FONT_LEVEL = 3;
7510
7714
  function truncateBySize(text, size) {
7511
- return text.length > size * 3 ? text.slice(0, size * 3) + "\u2026" : text;
7715
+ const maxLen = size * CHARS_PER_FONT_LEVEL;
7716
+ return text.length > maxLen ? text.slice(0, maxLen) + "\u2026" : text;
7512
7717
  }
7513
7718
  var HEADING_RE = /^(#{1,6})\s+(.*)$/;
7514
7719
  var UL_RE = /^([-*+])\s+(.*)$/;
7515
7720
  var OL_RE = /^(\d+)\.\s+(.*)$/;
7516
7721
  var QUOTE_RE = /^>\s?(.*)$/;
7722
+ var TASK_RE = /^[-*+]\s+\[([ xX])\]\s+(.*)$/;
7723
+ var INDENT_RE = /^(\s*)(.*)$/;
7724
+ var HR_RE = /^([-*_])\1{2,}$/;
7725
+ var FENCE_RE = /^(`{3,}|~{3,})(\w*)\s*$/;
7517
7726
  function extractBlocks(src) {
7518
7727
  const lines = src.replace(/\r\n/g, "\n").split("\n");
7519
7728
  const blocks = [];
7520
7729
  let paragraph = [];
7521
7730
  let list = null;
7522
7731
  let quote = [];
7732
+ let code = null;
7733
+ let table = null;
7523
7734
  const flushParagraph = () => {
7524
7735
  if (paragraph.length) {
7525
7736
  blocks.push({ type: "paragraph", text: paragraph.join(" ").trim() });
@@ -7527,7 +7738,7 @@ function extractBlocks(src) {
7527
7738
  }
7528
7739
  };
7529
7740
  const flushList = () => {
7530
- if (list) {
7741
+ if (list && list.items.length) {
7531
7742
  blocks.push({ type: list.ordered ? "ol" : "ul", items: list.items });
7532
7743
  list = null;
7533
7744
  }
@@ -7538,100 +7749,271 @@ function extractBlocks(src) {
7538
7749
  quote = [];
7539
7750
  }
7540
7751
  };
7752
+ const flushCode = () => {
7753
+ if (code) {
7754
+ blocks.push({ type: "code", lang: code.lang, text: code.lines.join("\n") });
7755
+ code = null;
7756
+ }
7757
+ };
7541
7758
  const flushAll = () => {
7542
7759
  flushParagraph();
7543
7760
  flushList();
7544
7761
  flushQuote();
7762
+ flushCode();
7763
+ };
7764
+ const flushTable = () => {
7765
+ if (table && table.headers.length > 0) {
7766
+ blocks.push({
7767
+ type: "table",
7768
+ headers: table.headers,
7769
+ rows: table.rows,
7770
+ colAligns: table.colAligns
7771
+ });
7772
+ }
7773
+ table = null;
7774
+ };
7775
+ const parseListItem = (text, indent) => {
7776
+ const task = text.match(TASK_RE);
7777
+ if (task) {
7778
+ const done = task[1].toLowerCase() === "x";
7779
+ return {
7780
+ type: "paragraph",
7781
+ text: `${done ? "\u2611" : "\u2610"} ${task[2]}`
7782
+ };
7783
+ }
7784
+ return { type: "paragraph", text };
7545
7785
  };
7546
- for (const line of lines) {
7786
+ for (let i = 0; i < lines.length; i++) {
7787
+ const line = lines[i];
7547
7788
  const trimmed = line.trim();
7789
+ if (code) {
7790
+ const endMatch = trimmed.match(FENCE_RE);
7791
+ if (endMatch && endMatch[1][0] === code.fence[0] && endMatch[1].length >= code.fence.length) {
7792
+ flushCode();
7793
+ continue;
7794
+ }
7795
+ code.lines.push(line);
7796
+ continue;
7797
+ }
7798
+ const fenceMatch = trimmed.match(FENCE_RE);
7799
+ if (fenceMatch) {
7800
+ flushAll();
7801
+ flushTable();
7802
+ code = { fence: fenceMatch[1], lang: fenceMatch[2], lines: [] };
7803
+ continue;
7804
+ }
7548
7805
  if (trimmed === "") {
7806
+ flushTable();
7807
+ flushAll();
7808
+ continue;
7809
+ }
7810
+ const hrMatch = trimmed.match(HR_RE);
7811
+ if (hrMatch) {
7812
+ flushTable();
7549
7813
  flushAll();
7814
+ blocks.push({ type: "hr" });
7550
7815
  continue;
7551
7816
  }
7552
7817
  const heading = trimmed.match(HEADING_RE);
7553
7818
  if (heading) {
7819
+ flushTable();
7554
7820
  flushAll();
7555
7821
  blocks.push({ type: "heading", level: heading[1].length, text: heading[2].trim() });
7556
7822
  continue;
7557
7823
  }
7558
7824
  const quoteMatch = trimmed.match(QUOTE_RE);
7559
7825
  if (quoteMatch) {
7826
+ flushTable();
7560
7827
  flushParagraph();
7561
7828
  flushList();
7829
+ flushCode();
7562
7830
  quote.push(quoteMatch[1]);
7563
7831
  continue;
7564
7832
  }
7833
+ const indentMatch = line.match(INDENT_RE);
7834
+ const indent = indentMatch?.[1].length ?? 0;
7835
+ const taskMatch = trimmed.match(TASK_RE);
7836
+ if (taskMatch) {
7837
+ flushTable();
7838
+ flushParagraph();
7839
+ flushQuote();
7840
+ flushCode();
7841
+ if (!list || list.ordered || indent !== list.indent) {
7842
+ flushList();
7843
+ list = { ordered: false, items: [], indent };
7844
+ }
7845
+ list.items.push(parseListItem(trimmed, indent));
7846
+ continue;
7847
+ }
7565
7848
  const ulMatch = trimmed.match(UL_RE);
7566
7849
  if (ulMatch) {
7850
+ flushTable();
7567
7851
  flushParagraph();
7568
7852
  flushQuote();
7569
- if (!list || list.ordered) {
7853
+ flushCode();
7854
+ if (!list || list.ordered || indent !== list.indent) {
7570
7855
  flushList();
7571
- list = { ordered: false, items: [] };
7856
+ list = { ordered: false, items: [], indent };
7572
7857
  }
7573
- list.items.push(ulMatch[2].trim());
7858
+ list.items.push(parseListItem(ulMatch[2], indent));
7574
7859
  continue;
7575
7860
  }
7576
7861
  const olMatch = trimmed.match(OL_RE);
7577
7862
  if (olMatch) {
7863
+ flushTable();
7578
7864
  flushParagraph();
7579
7865
  flushQuote();
7580
- if (!list || !list.ordered) {
7866
+ flushCode();
7867
+ if (!list || !list.ordered || indent !== list.indent) {
7581
7868
  flushList();
7582
- list = { ordered: true, items: [] };
7869
+ list = { ordered: true, items: [], indent };
7583
7870
  }
7584
- list.items.push(olMatch[2].trim());
7871
+ list.items.push(parseListItem(olMatch[2], indent));
7585
7872
  continue;
7586
7873
  }
7874
+ const sepMatch = trimmed.match(/^\|?\s*(\s*:?-+:?\s*\|)+\s*:?-+:?\s*\|?$/);
7875
+ if (sepMatch) {
7876
+ const cells = trimmed.split("|").map((c) => c.trim()).filter((c) => c.length > 0);
7877
+ if (cells.length >= 2 && cells.every((c) => /^:?-+:?$/.test(c))) {
7878
+ if (table) {
7879
+ table.colAligns = cells.map((c) => {
7880
+ if (c.startsWith(":") && c.endsWith(":")) return "center";
7881
+ if (c.endsWith(":")) return "right";
7882
+ return "left";
7883
+ });
7884
+ continue;
7885
+ }
7886
+ }
7887
+ }
7888
+ const rowMatch = trimmed.match(/^\|?.+\|.+\|?$/);
7889
+ if (rowMatch) {
7890
+ const cells = trimmed.split("|").map((c) => c.trim()).filter((c, idx, arr) => {
7891
+ if (idx === 0 && c === "") return false;
7892
+ if (idx === arr.length - 1 && c === "") return false;
7893
+ return true;
7894
+ });
7895
+ const isWindowsPath = cells.length === 2 && (/^[A-Za-z]:\\/.test(cells[0]) || /^\\\\/.test(cells[0]));
7896
+ if (cells.length >= 2 && cells.every((c) => c.length > 0) && !isWindowsPath) {
7897
+ if (table) {
7898
+ table.rows.push(cells);
7899
+ } else {
7900
+ flushAll();
7901
+ table = { headers: cells, rows: [], colAligns: [] };
7902
+ }
7903
+ continue;
7904
+ }
7905
+ }
7906
+ const asciiSepMatch = trimmed.match(/^[\s\-─┄┈━┅]{3,}$/);
7907
+ if (asciiSepMatch) {
7908
+ if (table && table.headers.length > 0 && table.colAligns.length === 0) {
7909
+ table.colAligns = table.headers.map(() => "left");
7910
+ continue;
7911
+ }
7912
+ if (paragraph.length === 1) {
7913
+ const prevLine = paragraph[0].trim();
7914
+ const tokens3 = prevLine.split(/\s{2,}/).map((c) => c.trim()).filter((c) => c.length > 0);
7915
+ if (tokens3.length >= 2 && tokens3.length <= 10) {
7916
+ paragraph = [];
7917
+ flushList();
7918
+ flushQuote();
7919
+ flushCode();
7920
+ table = { headers: tokens3, rows: [], colAligns: tokens3.map(() => "left") };
7921
+ continue;
7922
+ }
7923
+ }
7924
+ }
7925
+ if (table && table.headers.length > 0 && !trimmed.includes("|")) {
7926
+ const tokens3 = trimmed.split(/\s{2,}/).map((c) => c.trim()).filter((c) => c.length > 0);
7927
+ if (tokens3.length >= 2 && tokens3.length <= table.headers.length + 2) {
7928
+ const looksLikeCode = tokens3.some((t) => t.startsWith("//") || t.startsWith("#") || t.startsWith("/*"));
7929
+ const looksLikePath = tokens3.length === 2 && (/^[A-Za-z]:\\/.test(tokens3[0]) || /^\\\\/.test(tokens3[0]));
7930
+ if (!looksLikeCode && !looksLikePath) {
7931
+ table.rows.push(tokens3);
7932
+ continue;
7933
+ }
7934
+ }
7935
+ }
7936
+ flushTable();
7587
7937
  flushList();
7588
7938
  flushQuote();
7939
+ flushCode();
7589
7940
  paragraph.push(trimmed);
7590
7941
  }
7942
+ flushTable();
7591
7943
  flushAll();
7592
7944
  return blocks;
7593
7945
  }
7594
- function inline(text) {
7946
+ var MAX_INLINE_DEPTH = 10;
7947
+ function inline(text, depth = 0) {
7948
+ if (depth > MAX_INLINE_DEPTH) {
7949
+ return text;
7950
+ }
7595
7951
  const nodes = [];
7596
7952
  let rest = text;
7597
7953
  let key = 0;
7598
- const RE = /(`+)([^`]+?)\1|(\*\*)(.+?)\2|(~~)(.+?)\4|(\*)(.+?)\6|(__)(.+?)\8/;
7954
+ const matchers = [
7955
+ {
7956
+ re: /(!\[)([^\]]*)\]\(([^)]+)\)/,
7957
+ handler: (m) => /* @__PURE__ */ jsxs10(Text10, { color: "cyan", bold: true, children: [
7958
+ "[img: ",
7959
+ m[2],
7960
+ "]"
7961
+ ] }, key++)
7962
+ },
7963
+ {
7964
+ re: /(\[)([^\]]*)\]\(([^)]+)\)/,
7965
+ handler: (m) => /* @__PURE__ */ jsxs10(Fragment4, { children: [
7966
+ /* @__PURE__ */ jsx10(Text10, { color: "cyan", underline: true, children: inline(m[2], depth + 1) }, key++),
7967
+ /* @__PURE__ */ jsxs10(Text10, { dimColor: true, color: "gray", children: [
7968
+ "(",
7969
+ m[3],
7970
+ ")"
7971
+ ] }, key++)
7972
+ ] })
7973
+ },
7974
+ {
7975
+ re: /(`+)([^`]+?)\1/,
7976
+ handler: (m) => /* @__PURE__ */ jsx10(Text10, { bold: true, color: "cyan", children: m[2] }, key++)
7977
+ },
7978
+ {
7979
+ re: /(\*\*)(.+?)\1/,
7980
+ handler: (m) => /* @__PURE__ */ jsx10(Text10, { bold: true, children: inline(m[2], depth + 1) }, key++)
7981
+ },
7982
+ {
7983
+ re: /(__)(.+?)\1/,
7984
+ handler: (m) => /* @__PURE__ */ jsx10(Text10, { bold: true, children: inline(m[2], depth + 1) }, key++)
7985
+ },
7986
+ {
7987
+ re: /(~~)(.+?)\1/,
7988
+ handler: (m) => /* @__PURE__ */ jsx10(Text10, { strikethrough: true, children: m[2] }, key++)
7989
+ },
7990
+ {
7991
+ re: /(\*)(.+?)\1/,
7992
+ handler: (m) => /* @__PURE__ */ jsx10(Text10, { italic: true, children: inline(m[2], depth + 1) }, key++)
7993
+ }
7994
+ ];
7599
7995
  while (rest.length > 0) {
7600
- const m = rest.match(RE);
7601
- if (!m) {
7996
+ let best = null;
7997
+ for (const { re, handler } of matchers) {
7998
+ const m = rest.match(re);
7999
+ if (m && (best === null || (m.index ?? 0) < (best.match.index ?? 0))) {
8000
+ best = { match: m, handler };
8001
+ }
8002
+ }
8003
+ if (!best) {
7602
8004
  nodes.push(rest);
7603
8005
  break;
7604
8006
  }
7605
- const idx = m.index ?? 0;
8007
+ const idx = best.match.index ?? 0;
7606
8008
  if (idx > 0) nodes.push(rest.slice(0, idx));
7607
- if (m[1]) {
7608
- nodes.push(
7609
- /* @__PURE__ */ jsx10(Text10, { bold: true, color: "cyan", children: m[2] }, key++)
7610
- );
7611
- } else if (m[3]) {
7612
- nodes.push(
7613
- /* @__PURE__ */ jsx10(Text10, { bold: true, children: inline(m[4]) }, key++)
7614
- );
7615
- } else if (m[5]) {
7616
- nodes.push(
7617
- /* @__PURE__ */ jsx10(Text10, { strikethrough: true, children: m[6] }, key++)
7618
- );
7619
- } else if (m[7]) {
7620
- nodes.push(
7621
- /* @__PURE__ */ jsx10(Text10, { italic: true, children: inline(m[8]) }, key++)
7622
- );
7623
- } else if (m[9]) {
7624
- nodes.push(
7625
- /* @__PURE__ */ jsx10(Text10, { bold: true, children: inline(m[10]) }, key++)
7626
- );
7627
- }
7628
- rest = rest.slice(idx + m[0].length);
8009
+ nodes.push(best.handler(best.match));
8010
+ rest = rest.slice(idx + best.match[0].length);
7629
8011
  }
7630
8012
  return nodes.length === 1 ? nodes[0] : nodes;
7631
8013
  }
7632
8014
 
7633
8015
  // src/ui/components/Transcript.tsx
7634
- import { Fragment as Fragment4, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
8016
+ import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
7635
8017
  var HEADER = { kind: "header" };
7636
8018
  var Transcript = memo2(function Transcript2({ bubbles, workspace }) {
7637
8019
  const liveAt = bubbles.findLastIndex(
@@ -7698,7 +8080,7 @@ function ToolView({ bubble }) {
7698
8080
  mark,
7699
8081
  " ",
7700
8082
  bubble.summary,
7701
- hasChanges && /* @__PURE__ */ jsxs11(Fragment4, { children: [
8083
+ hasChanges && /* @__PURE__ */ jsxs11(Fragment5, { children: [
7702
8084
  diff.added > 0 && /* @__PURE__ */ jsx11(Text11, { color: theme.ok, bold: true, children: ` +${diff.added}` }),
7703
8085
  diff.removed > 0 && /* @__PURE__ */ jsx11(Text11, { color: theme.error, bold: true, children: ` -${diff.removed}` })
7704
8086
  ] })
@@ -7823,7 +8205,7 @@ function applyEvent(state, event) {
7823
8205
  };
7824
8206
  case "subagent_event": {
7825
8207
  const idx = state.bubbles.findLastIndex(
7826
- (b) => b.kind === "subagent" && b.id === event.id && b.state === "running"
8208
+ (b) => b.kind === "subagent" && b.id === event.id
7827
8209
  );
7828
8210
  if (idx === -1) return state;
7829
8211
  const sub = state.bubbles[idx];
@@ -8077,7 +8459,7 @@ function App({
8077
8459
  return () => clearInterval(timer);
8078
8460
  }, [turnStart]);
8079
8461
  useEffect2(() => {
8080
- if (process.platform === "darwin") return;
8462
+ if (process.platform === "darwin" || process.platform === "win32") return;
8081
8463
  let last = "";
8082
8464
  let cancelling = false;
8083
8465
  let giveUp = false;
@@ -8151,6 +8533,12 @@ function App({
8151
8533
  }),
8152
8534
  []
8153
8535
  );
8536
+ const promptInput = useCallback(
8537
+ (title, mask) => new Promise((resolve3) => {
8538
+ setOverlay({ kind: "textinput", title, mask, resolve: resolve3, cancel: () => resolve3(null) });
8539
+ }),
8540
+ []
8541
+ );
8154
8542
  const runAgent = useCallback(async () => {
8155
8543
  const controller = new AbortController();
8156
8544
  abort.current = controller;
@@ -8468,6 +8856,35 @@ ${strings.modelSet(result.nextModel)}` : ""}`;
8468
8856
  forceRender((n) => n + 1);
8469
8857
  return;
8470
8858
  }
8859
+ case "key": {
8860
+ const providers = runtime.listProviderItems();
8861
+ if (providers.length === 0) {
8862
+ notice("warn", strings.logoutNothing);
8863
+ return;
8864
+ }
8865
+ let providerId = rest[0];
8866
+ if (!providerId) {
8867
+ providerId = providers.length === 1 ? providers[0]?.key : await pick(strings.titleKeyChange, providers) ?? void 0;
8868
+ }
8869
+ if (!providerId) return;
8870
+ if (!providers.some((provider) => provider.key === providerId)) {
8871
+ notice("warn", `Provider "${providerId}" is not configured.`);
8872
+ return;
8873
+ }
8874
+ if (!await ask2(strings.keyChangeAsk(providerId), strings.keyChangeAskBody, true)) return;
8875
+ const newKey = await promptInput(strings.apiKey, true);
8876
+ if (!newKey || newKey.trim() === "") {
8877
+ notice("warn", "Empty key \u2014 nothing changed.");
8878
+ return;
8879
+ }
8880
+ try {
8881
+ await runtime.changeProviderKey(providerId, newKey.trim());
8882
+ notice("info", strings.keyChanged(providerId));
8883
+ } catch (error) {
8884
+ notice("error", error instanceof Error ? error.message : String(error));
8885
+ }
8886
+ return;
8887
+ }
8471
8888
  case "lang": {
8472
8889
  const choice = await pick(
8473
8890
  strings.titleLang,
@@ -9201,6 +9618,21 @@ ${strings.configAt(runtime.configPath())}`);
9201
9618
  }
9202
9619
  }
9203
9620
  ),
9621
+ overlay.kind === "textinput" && /* @__PURE__ */ jsx12(
9622
+ TextInputOverlay,
9623
+ {
9624
+ title: overlay.title,
9625
+ mask: overlay.mask,
9626
+ onAnswer: (value) => {
9627
+ overlay.resolve(value);
9628
+ setOverlay({ kind: "none" });
9629
+ },
9630
+ onCancel: () => {
9631
+ overlay.cancel?.();
9632
+ setOverlay({ kind: "none" });
9633
+ }
9634
+ }
9635
+ ),
9204
9636
  overlay.kind === "none" && /* @__PURE__ */ jsx12(
9205
9637
  PromptInput,
9206
9638
  {
@@ -9241,6 +9673,34 @@ ${strings.configAt(runtime.configPath())}`);
9241
9673
  ] })
9242
9674
  );
9243
9675
  }
9676
+ function TextInputOverlay({
9677
+ title,
9678
+ mask,
9679
+ onAnswer,
9680
+ onCancel
9681
+ }) {
9682
+ const [value, setValue] = useState5("");
9683
+ const theme = useTheme();
9684
+ const submit = useCallback(() => {
9685
+ onAnswer(value || null);
9686
+ }, [value, onAnswer]);
9687
+ useInput2((_input, key) => {
9688
+ if (key.escape) onCancel();
9689
+ });
9690
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
9691
+ /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: title }),
9692
+ /* @__PURE__ */ jsx12(
9693
+ TextInput2,
9694
+ {
9695
+ value,
9696
+ onChange: setValue,
9697
+ onSubmit: submit,
9698
+ mask: mask ? "\u2022" : void 0
9699
+ }
9700
+ ),
9701
+ /* @__PURE__ */ jsx12(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx12(Text12, { dimColor: true, color: theme.accent, children: "enter submit \xB7 esc cancel" }) })
9702
+ ] });
9703
+ }
9244
9704
  function slashNeedsIdle(line) {
9245
9705
  const [command = "", subcommand = ""] = line.slice(1).trim().toLowerCase().split(/\s+/);
9246
9706
  return NEEDS_IDLE.has(command) || command === "mcp" && (subcommand === "add" || subcommand === "delete" || subcommand === "enable" || subcommand === "disable");
@@ -9322,6 +9782,13 @@ function copyToClipboard(text) {
9322
9782
  }
9323
9783
  return;
9324
9784
  }
9785
+ if (process.platform === "win32") {
9786
+ try {
9787
+ run("clip", []);
9788
+ } catch {
9789
+ }
9790
+ return;
9791
+ }
9325
9792
  const candidates = [
9326
9793
  ["wl-copy", []],
9327
9794
  ["xclip", ["-selection", "clipboard"]],