@kernelonpanic/kitcode 1.2.4 → 1.2.6

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/README.md CHANGED
@@ -88,7 +88,7 @@ http://localhost:11434/v1
88
88
  | `kitcode -r <id>` | Resume a session by id. |
89
89
  | `kitcode --cwd <path>` | Use another working directory. |
90
90
  | `kitcode --model <provider/model>` | Start with a selected model. |
91
- | `kitcode --mode <normal\|accept\|plan>` | Select the initial agent mode. |
91
+ | `kitcode --mode <normal|accept|plan>` | Select the initial agent mode. |
92
92
  | `kitcode sessions` | List saved sessions. |
93
93
  | `kitcode ask "question"` | Run one request without the TUI. |
94
94
  | `kitcode add <url>` | Add a provider; the key is entered in a hidden prompt. |
@@ -112,17 +112,21 @@ Press `/` to open the command list.
112
112
  | `/effort` · `/thinking` | Configure reasoning depth and output. |
113
113
  | `/resume` · `/clear` | Resume a session or start a new one. |
114
114
  | `/sessions` | Search sessions, then resume, rename, delete, or export one. |
115
+ | `/sessions list` | List all saved sessions. |
115
116
  | `/sessions rename <id> <title>` | Give a saved session a title. |
117
+ | `/sessions delete <id>` | Delete a saved session. |
116
118
  | `/sessions delete all` | Delete every saved chat after two separate confirmations. |
117
119
  | `/sessions export <id> [path]` | Export a session to a private Markdown file (default: `.kitcode-exports/`). |
118
120
  | `/attach <path>` · `/attach clipboard` · `/attach clear` | Manage attachments for the next message. |
119
121
  | `/compact` | Replace older conversation context with a concise model-generated summary. |
122
+ | `/update` | Check npm for a newer KitCode version. |
120
123
  | `/subagents` | Show the current number of active sub-agents. |
121
124
  | `/undo` | Undo built-in file edits from the latest message. |
122
125
  | `/usage` | Show tokens, requests, cost, balance/key limits, and response rate-limit headers when available. |
123
126
  | `/checker` | Check the local setup and provider model listing without sending a paid chat request. |
124
127
  | `/prompt` | Insert a saved prompt. |
125
128
  | `/prompt save <name>` | Save the latest message as a prompt. |
129
+ | `/prompt delete <name>` | Delete a saved prompt. |
126
130
  | `/skills` | Show discovered skills. |
127
131
  | `/mcp add <name> <https://url>` | Add and connect a remote MCP server. |
128
132
  | `/mcp add <name> -- <command> [args]` | Add and connect a local MCP server. |
package/README.ru.md CHANGED
@@ -88,10 +88,10 @@ http://localhost:11434/v1
88
88
  | `kitcode -r <id>` | Восстановить сессию по идентификатору. |
89
89
  | `kitcode --cwd <path>` | Использовать другую рабочую директорию. |
90
90
  | `kitcode --model <provider/model>` | Запуститься с выбранной моделью. |
91
- | `kitcode --mode <normal\|accept\|plan>` | Выбрать начальный режим работы. |
91
+ | `kitcode --mode <normal|accept|plan>` | Выбрать начальный режим работы. |
92
92
  | `kitcode sessions` | Показать сохранённые сессии. |
93
93
  | `kitcode ask "вопрос"` | Выполнить один запрос без TUI. |
94
- | `kitcode add <url>` | Добавить провайдер; ключ вводится в скрытом поле. |
94
+ | `kitcode add <url>` | Добавить провайдера; ключ вводится в скрытом поле. |
95
95
  | `kitcode add <url> --key-env <name>` | Прочитать ключ из переменной окружения. |
96
96
  | `kitcode trust` | Включить конфиг и skills текущего проекта. |
97
97
  | `kitcode config` | Показать расположение конфига и ключей. |
@@ -112,19 +112,24 @@ http://localhost:11434/v1
112
112
  | `/effort` · `/thinking` | Настроить глубину и отображение reasoning. |
113
113
  | `/resume` · `/clear` | Восстановить сессию или начать новую. |
114
114
  | `/sessions` | Найти сессию и открыть, переименовать, удалить или экспортировать её. |
115
+ | `/sessions list` | Показать все сохранённые сессии. |
115
116
  | `/sessions rename <id> <название>` | Переименовать сохранённую сессию. |
117
+ | `/sessions delete <id>` | Удалить сохранённую сессию. |
116
118
  | `/sessions delete all` | Удалить все сохранённые чаты после двух отдельных подтверждений. |
117
119
  | `/sessions export <id> [путь]` | Экспортировать сессию в приватный Markdown-файл (по умолчанию `.kitcode-exports/`). |
118
120
  | `/attach <путь>` · `/attach clipboard` · `/attach clear` | Управлять вложениями следующего сообщения. |
119
121
  | `/compact` | Заменить старую часть контекста краткой сводкой модели. |
122
+ | `/update` | Проверить новую версию KitCode в npm. |
120
123
  | `/subagents` | Показать текущее количество активных суб-агентов. |
121
124
  | `/undo` | Откатить встроенные правки файлов из последнего сообщения. |
122
125
  | `/usage` | Показать токены, запросы, стоимость, доступный баланс и rate limits, если API их отдаёт. |
123
126
  | `/checker` | Проверить настройку и список моделей без платного chat-запроса. |
124
127
  | `/prompt` | Вставить сохранённый промт. |
125
128
  | `/prompt save <name>` | Сохранить последнее сообщение как промт. |
129
+ | `/prompt delete <name>` | Удалить сохранённый промт. |
126
130
  | `/skills` · `/mcp list` | Показать skills и состояние MCP. |
127
131
  | `/mcp add <имя> <url>` | Добавить и сразу подключить MCP-сервер. |
132
+ | `/mcp add <имя> -- <команда> [аргументы]` | Добавить и подключить локальный MCP-сервер. |
128
133
  | `/mcp enable <имя>` · `/mcp disable <имя>` | Подключить или отключить MCP без удаления. |
129
134
  | `/mcp delete <имя>` | Отключить и удалить MCP-сервер. |
130
135
  | `/theme` · `/lang` | Изменить тему или язык. |
package/dist/index.js CHANGED
@@ -2467,7 +2467,7 @@ async function isFile2(file) {
2467
2467
 
2468
2468
  // src/core/attachments.ts
2469
2469
  import { execFile } from "child_process";
2470
- import { lstat as lstat2, readFile as readFile6, stat as stat5 } from "fs/promises";
2470
+ import { lstat as lstat2, readdir as readdir3, readFile as readFile6, stat as stat5 } from "fs/promises";
2471
2471
  import os2 from "os";
2472
2472
  import path7 from "path";
2473
2473
  import { fileURLToPath } from "url";
@@ -2506,15 +2506,17 @@ async function loadAttachment(cwd, requestedPath) {
2506
2506
  if (error.code === "ENOENT") throw new Error(`Attachment not found: ${resolved}`);
2507
2507
  throw error;
2508
2508
  });
2509
+ if (info.isDirectory()) {
2510
+ const attachment = await loadFirstImageFromDirectory(resolved);
2511
+ if (attachment) return attachment;
2512
+ throw new Error(`No supported images found in directory: ${path7.basename(resolved)}`);
2513
+ }
2509
2514
  if (!info.isFile()) throw new Error(`Attachment is not a regular file: ${resolved}`);
2510
2515
  return loadResolvedAttachment(resolved, info.size);
2511
2516
  }
2512
2517
  async function loadAutomaticAttachment(cwd, requestedPath) {
2513
2518
  if (!looksLikeAttachmentPath(requestedPath)) return null;
2514
- const requested = resolveAttachmentPath(cwd, requestedPath);
2515
- const safe = resolveInside(cwd, requested);
2516
- if (!safe.ok || safe.relative === "") return null;
2517
- const resolved = safe.path;
2519
+ const resolved = resolveAttachmentPath(cwd, requestedPath);
2518
2520
  if (isSensitiveAutomaticPath(resolved)) {
2519
2521
  throw new Error(
2520
2522
  `For safety, sensitive-looking files must be attached explicitly with /attach: ${path7.basename(resolved)}`
@@ -2526,17 +2528,23 @@ async function loadAutomaticAttachment(cwd, requestedPath) {
2526
2528
  if (error.code === "ENOENT" || error.code === "ENOTDIR") return null;
2527
2529
  throw error;
2528
2530
  });
2531
+ if (info?.isDirectory()) return loadFirstImageFromDirectory(resolved);
2529
2532
  if (!info?.isFile()) return null;
2530
2533
  return loadResolvedAttachment(resolved, info.size);
2531
2534
  }
2535
+ var MAX_PATH_CHARS = 2048;
2532
2536
  function looksLikeAttachmentPath(value) {
2533
2537
  const trimmed = value.trim();
2534
2538
  if (!trimmed || /[\r\n\0]/.test(trimmed)) return false;
2539
+ if (trimmed.length > MAX_PATH_CHARS) return false;
2535
2540
  const candidate = normalizeInputPath(trimmed);
2536
2541
  if (/^file:\/\//i.test(candidate)) return true;
2537
2542
  if (path7.isAbsolute(candidate)) return true;
2538
2543
  if (/^(?:~|\.{1,2})[\\/]/.test(candidate)) return true;
2539
- if (candidate.includes("/") || candidate.includes("\\")) return true;
2544
+ if (candidate.includes("/") || candidate.includes("\\")) {
2545
+ if (trimmed.length > 200) return false;
2546
+ return true;
2547
+ }
2540
2548
  const basename3 = path7.basename(candidate).toLowerCase();
2541
2549
  return path7.extname(basename3) !== "" || AUTO_PATH_NAMES.has(basename3);
2542
2550
  }
@@ -2600,6 +2608,18 @@ async function loadResolvedAttachment(resolved, size) {
2600
2608
  block: { type: "file", mediaType: textMime(resolved), text, name }
2601
2609
  };
2602
2610
  }
2611
+ async function loadFirstImageFromDirectory(dirPath) {
2612
+ const IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp"]);
2613
+ const entries = await readdir3(dirPath, { withFileTypes: true }).catch(() => null);
2614
+ if (!entries) return null;
2615
+ const sorted = entries.filter((entry) => entry.isFile() && IMAGE_EXTENSIONS.has(path7.extname(entry.name).toLowerCase())).sort((a, b) => a.name.localeCompare(b.name));
2616
+ if (sorted.length === 0) return null;
2617
+ const first2 = sorted[0];
2618
+ const filePath = path7.join(dirPath, first2.name);
2619
+ const info = await stat5(filePath).catch(() => null);
2620
+ if (!info?.isFile()) return null;
2621
+ return loadResolvedAttachment(filePath, info.size);
2622
+ }
2603
2623
  function resolveAttachmentPath(cwd, requestedPath) {
2604
2624
  const cleaned = normalizeInputPath(requestedPath.trim());
2605
2625
  if (!cleaned) throw new Error("Give a file path: /attach <path>");
@@ -3452,7 +3472,7 @@ function clip(value) {
3452
3472
  // package.json
3453
3473
  var package_default = {
3454
3474
  name: "@kernelonpanic/kitcode",
3455
- version: "1.2.4",
3475
+ version: "1.2.6",
3456
3476
  description: "Terminal coding agent with a config you never have to write by hand",
3457
3477
  type: "module",
3458
3478
  license: "MIT",
@@ -3520,7 +3540,7 @@ var package_default = {
3520
3540
 
3521
3541
  // src/version.ts
3522
3542
  var KITCODE_VERSION = package_default.version;
3523
- var KITCODE_COMMIT = true ? "84441dfba8c67477cf72b76552e72ffedc8d41a8" : "development";
3543
+ var KITCODE_COMMIT = true ? "f40cc2cb3cebf7fa8b3c20dbbff5b14047d1e983" : "development";
3524
3544
 
3525
3545
  // src/mcp/client.ts
3526
3546
  var clientInfo = { name: "kitcode", version: KITCODE_VERSION };
@@ -5313,7 +5333,7 @@ function createToolRegistry(tools) {
5313
5333
  }
5314
5334
 
5315
5335
  // src/prompts/library.ts
5316
- import { chmod as chmod6, readFile as readFile12, readdir as readdir3, unlink as unlink3, writeFile as writeFile6 } from "fs/promises";
5336
+ import { chmod as chmod6, readFile as readFile12, readdir as readdir4, unlink as unlink3, writeFile as writeFile6 } from "fs/promises";
5317
5337
  import path10 from "path";
5318
5338
  async function savePrompt(input) {
5319
5339
  const slug = slugify(input.name);
@@ -5346,7 +5366,7 @@ async function getPrompt(slug) {
5346
5366
  async function listPrompts() {
5347
5367
  let names;
5348
5368
  try {
5349
- names = await readdir3(promptsDir);
5369
+ names = await readdir4(promptsDir);
5350
5370
  } catch (error) {
5351
5371
  if (!isMissing(error)) throw error;
5352
5372
  return [];
@@ -5418,7 +5438,7 @@ function parse(slug, text) {
5418
5438
  }
5419
5439
 
5420
5440
  // src/skills/library.ts
5421
- import { lstat as lstat4, open as open2, readdir as readdir4 } from "fs/promises";
5441
+ import { lstat as lstat4, open as open2, readdir as readdir5 } from "fs/promises";
5422
5442
  import path11 from "path";
5423
5443
  var SKILL_FILE = "SKILL.md";
5424
5444
  var FRONTMATTER_BYTES = 8192;
@@ -5430,7 +5450,7 @@ async function discoverSkills(dirs) {
5430
5450
  for (const root of dirs) {
5431
5451
  const rootInfo = await lstat4(root).catch(() => null);
5432
5452
  if (!rootInfo?.isDirectory() || rootInfo.isSymbolicLink()) continue;
5433
- const entries = await readdir4(root, { withFileTypes: true }).catch(() => []);
5453
+ const entries = await readdir5(root, { withFileTypes: true }).catch(() => []);
5434
5454
  const found = await Promise.all(
5435
5455
  entries.filter((entry) => entry.isDirectory() && !entry.isSymbolicLink()).slice(0, MAX_SKILLS_PER_ROOT).map((entry) => readMeta(root, rootInfo, path11.join(root, entry.name)))
5436
5456
  );
@@ -7569,6 +7589,7 @@ ${url}`,
7569
7589
  lang: "\u0441\u043C\u0435\u043D\u0438\u0442\u044C \u044F\u0437\u044B\u043A \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430",
7570
7590
  prompt: "\u0432\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u0438\u043B\u0438 \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C \u043F\u0440\u043E\u043C\u0442",
7571
7591
  skills: "\u0441\u043F\u0438\u0441\u043E\u043A \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u044B\u0445 \u0441\u043A\u0438\u043B\u043B\u043E\u0432",
7592
+ "prompt delete": "\u0443\u0434\u0430\u043B\u0438\u0442\u044C \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D\u043D\u044B\u0439 \u043F\u0440\u043E\u043C\u0442",
7572
7593
  bypass: "\u043E\u0442\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F (\u0441\u043F\u0440\u043E\u0441\u0438\u0442 \u0434\u0432\u0430\u0436\u0434\u044B)",
7573
7594
  usage: "\u0442\u043E\u043A\u0435\u043D\u044B \u0438 \u0441\u0442\u043E\u0438\u043C\u043E\u0441\u0442\u044C",
7574
7595
  mcp: "\u043F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u0438\u043B\u0438 \u0434\u043E\u0431\u0430\u0432\u0438\u0442\u044C MCP-\u0441\u0435\u0440\u0432\u0435\u0440",
@@ -10526,9 +10547,8 @@ Rename the file if you want a different name.`);
10526
10547
  }
10527
10548
  notice("info", strings.attachmentAdded(attachmentLabel(block) ?? "attachment"));
10528
10549
  return true;
10529
- }).catch((error) => {
10530
- notice("error", error instanceof Error ? error.message : String(error));
10531
- return true;
10550
+ }).catch(() => {
10551
+ return false;
10532
10552
  });
10533
10553
  automaticAttachmentTask.current = task;
10534
10554
  void task.finally(() => {
@@ -10580,9 +10600,12 @@ Rename the file if you want a different name.`);
10580
10600
  submitSlash();
10581
10601
  return;
10582
10602
  }
10583
- if (text && looksLikeAttachmentPath(text) && await tryQueueAutomaticAttachment(text)) {
10584
- if (!detachedInput) setInput("");
10585
- return;
10603
+ if (text && looksLikeAttachmentPath(text)) {
10604
+ const attached = await tryQueueAutomaticAttachment(text);
10605
+ if (attached) {
10606
+ if (!detachedInput) setInput("");
10607
+ return;
10608
+ }
10586
10609
  }
10587
10610
  if (text.startsWith("/")) {
10588
10611
  submitSlash();
@@ -10617,8 +10640,11 @@ Rename the file if you want a different name.`);
10617
10640
  return;
10618
10641
  }
10619
10642
  if (!key.escape) return;
10620
- if (input !== "") {
10643
+ if (input !== "" || attachmentsRef.current.length > 0) {
10621
10644
  setInput("");
10645
+ replaceAttachments([]);
10646
+ if (automaticAttachmentTask.current) automaticAttachmentTask.current = null;
10647
+ if (clipboardPasteTask.current) clipboardPasteTask.current = null;
10622
10648
  return;
10623
10649
  }
10624
10650
  if (busy && abort.current) {