code-ollama 0.18.1 → 0.19.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.
@@ -1,7 +1,7 @@
1
- import { A as SYSTEM, B as REJECT, C as resetSystemMessage, D as LIST$1, E as WARNING, F as AUTO, H as LIST, I as LABEL, L as PLAN, M as PLAN_GENERATION_INSTRUCTION, N as BACK, O as getTheme, P as CATALOG, R as SAFE, S as saveConfig, T as HEADER_PREFIX, V as VERSION, _ as deleteModel, a as color, b as streamChat, c as createSession, d as listSessions, f as loadSession, g as setClearHandler, h as reset, i as WRITE_TOOLS, j as USER, k as ASSISTANT, l as deleteSession, m as clear, n as READ_TOOLS, o as write, p as updateSessionModel, r as TOOLS, s as appendMessage, t as executeTool, u as deleteSessionIfEmpty, v as listModels, w as withSystemMessage, x as loadConfig, y as pullModel, z as APPROVE } from "../cli.js";
2
- import { readdirSync } from "node:fs";
1
+ import { A as WARNING, B as LABEL, C as loadConfig, D as resetSystemMessage, E as saveClipboardImage, F as USER, G as VERSION, H as SAFE, I as PLAN_GENERATION_INSTRUCTION, K as LIST, L as BACK, M as getTheme, N as ASSISTANT, O as withSystemMessage, P as SYSTEM, R as CATALOG, S as streamChat, T as removeClipboardImage, U as APPROVE, V as PLAN, W as REJECT, _ as setClearHandler, a as tick, b as listModels, c as appendMessage, d as deleteSessionIfEmpty, f as listSessions, g as reset, h as clear, i as WRITE_TOOLS, j as LIST$1, k as HEADER_PREFIX, l as createSession, m as updateSessionModel, n as READ_TOOLS, o as color, p as loadSession, r as TOOLS, s as write, t as executeTool, u as deleteSession, v as checkHealth, w as saveConfig, x as pullModel, y as deleteModel, z as AUTO } from "../cli.js";
2
+ import { existsSync, readdirSync, statSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
- import { join, relative } from "node:path";
4
+ import { basename, extname, isAbsolute, join, relative, resolve } from "node:path";
5
5
  import { exec } from "node:child_process";
6
6
  import { Box, Static, Text, render, useApp, useInput, useStdout } from "ink";
7
7
  import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
@@ -416,6 +416,26 @@ function Message({ message, isStreaming = false, theme }) {
416
416
  children: message.content
417
417
  })
418
418
  });
419
+ if (isUser) {
420
+ // v8 ignore start
421
+ const attachmentPrefix = (message.images ?? []).map((path) => `[${path.split(/[\\/]/).at(-1) ?? path}]`).join(" ");
422
+ // v8 ignore stop
423
+ return /* @__PURE__ */ jsx(Box, {
424
+ flexDirection: "column",
425
+ marginBottom: 1,
426
+ children: /* @__PURE__ */ jsxs(Text, {
427
+ color: messageColor,
428
+ children: [
429
+ "> ",
430
+ attachmentPrefix ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Text, {
431
+ color: theme.colors.accent,
432
+ children: attachmentPrefix
433
+ }), message.content ? " " : ""] }) : null,
434
+ message.content
435
+ ]
436
+ })
437
+ });
438
+ }
419
439
  const segments = parseContent(message.content);
420
440
  const availableWidth = getAssistantContentWidth(stdout.columns);
421
441
  if (stickyHeightRef.current.columns !== stdout.columns) stickyHeightRef.current = {
@@ -436,11 +456,7 @@ function Message({ message, isStreaming = false, theme }) {
436
456
  flexDirection: "column",
437
457
  marginBottom: 1,
438
458
  children: [segments.map((segment, index) => {
439
- const prefix = isUser && index === 0 ? "> " : "";
440
- if (segment.type === "code") return isUser ? /* @__PURE__ */ jsx(Text, {
441
- color: messageColor,
442
- children: segment.content
443
- }, index) : /* @__PURE__ */ jsx(Box, {
459
+ if (segment.type === "code") return /* @__PURE__ */ jsx(Box, {
444
460
  marginX: 2,
445
461
  children: /* @__PURE__ */ jsx(CodeBlock, {
446
462
  code: segment.content,
@@ -461,17 +477,13 @@ function Message({ message, isStreaming = false, theme }) {
461
477
  })
462
478
  }, index);
463
479
  }
464
- const textParts = [{
465
- type: "markdown",
466
- content: segment.content
467
- }];
468
- return isUser ? /* @__PURE__ */ jsx(Text, {
469
- color: messageColor,
470
- children: prefix + segment.content
471
- }, index) : /* @__PURE__ */ jsx(Box, {
480
+ return /* @__PURE__ */ jsx(Box, {
472
481
  flexDirection: "column",
473
482
  marginX: 2,
474
- children: textParts.map((part, partIndex) => /* @__PURE__ */ jsx(Markdown, {
483
+ children: [{
484
+ type: "markdown",
485
+ content: segment.content
486
+ }].map((part, partIndex) => /* @__PURE__ */ jsx(Markdown, {
475
487
  content: part.content,
476
488
  theme
477
489
  }, partIndex))
@@ -509,20 +521,38 @@ function Messages({ messages, isLoading, sessionId, streamingMessage, theme = ge
509
521
  //#endregion
510
522
  //#region src/components/SelectPrompt/SelectPrompt.tsx
511
523
  function SelectPrompt({ borderStyle, children, onCancel, ...selectProps }) {
524
+ const [isInteractive, setIsInteractive] = useState(false);
525
+ useEffect(() => {
526
+ let isMounted = true;
527
+ tick().then(() => {
528
+ // v8 ignore next
529
+ if (isMounted) setIsInteractive(true);
530
+ });
531
+ return () => {
532
+ isMounted = false;
533
+ };
534
+ }, []);
512
535
  useInput((input, key) => {
513
536
  if (key.escape || key.ctrl && input === "c") onCancel?.();
514
537
  });
515
538
  return /* @__PURE__ */ jsxs(Box, {
516
539
  borderStyle,
517
540
  flexDirection: "column",
518
- children: [children, /* @__PURE__ */ jsx(Select, { ...selectProps })]
541
+ children: [children, /* @__PURE__ */ jsx(Select, {
542
+ ...selectProps,
543
+ isDisabled: selectProps.isDisabled ?? !isInteractive
544
+ })]
519
545
  });
520
546
  }
521
547
  //#endregion
522
548
  //#region src/components/SelectPrompt/SelectPromptHint.tsx
549
+ /**
550
+ * Select prompt hint component that displays:
551
+ * Select option (↑↓ + Enter to confirm, Esc/Ctrl+C to cancel)
552
+ */
523
553
  function SelectPromptHint({ message = "Select option", escapeLabel = "cancel" }) {
524
- return /* @__PURE__ */ jsxs(Box, {
525
- flexDirection: "row",
554
+ return /* @__PURE__ */ jsxs(Text, {
555
+ color: getTheme().colors.secondary,
526
556
  children: [
527
557
  /* @__PURE__ */ jsxs(Text, {
528
558
  dimColor: true,
@@ -548,6 +578,14 @@ function SelectPromptHint({ message = "Select option", escapeLabel = "cancel" })
548
578
  bold: true,
549
579
  children: "Esc"
550
580
  }),
581
+ /* @__PURE__ */ jsx(Text, {
582
+ dimColor: true,
583
+ children: "/"
584
+ }),
585
+ /* @__PURE__ */ jsx(Text, {
586
+ bold: true,
587
+ children: "Ctrl+C"
588
+ }),
551
589
  /* @__PURE__ */ jsxs(Text, {
552
590
  dimColor: true,
553
591
  children: [
@@ -560,7 +598,7 @@ function SelectPromptHint({ message = "Select option", escapeLabel = "cancel" })
560
598
  });
561
599
  }
562
600
  //#endregion
563
- //#region src/components/PlanApproval.tsx
601
+ //#region src/components/PlanApproval/PlanApproval.tsx
564
602
  var options$1 = [
565
603
  {
566
604
  label: "Auto - Execute tools automatically",
@@ -606,7 +644,7 @@ function PlanApproval({ planContent, onModeChange, theme = getTheme() }) {
606
644
  });
607
645
  }
608
646
  //#endregion
609
- //#region src/components/ToolApproval.tsx
647
+ //#region src/components/ToolApproval/ToolApproval.tsx
610
648
  var options = [{
611
649
  label: "Approve tool call",
612
650
  value: APPROVE
@@ -766,6 +804,7 @@ function TextInput({ value, isDisabled = false, placeholder, cursorPosition: ext
766
804
  setCursorPosition(value.length);
767
805
  return;
768
806
  }
807
+ if (key.ctrl) return;
769
808
  // v8 ignore start
770
809
  if (input) {
771
810
  onChange(value.slice(0, cursorPosition) + input + value.slice(cursorPosition));
@@ -802,6 +841,74 @@ function TextInput({ value, isDisabled = false, placeholder, cursorPosition: ext
802
841
  });
803
842
  }
804
843
  //#endregion
844
+ //#region src/components/Chat/attachments.ts
845
+ var IMAGE_EXTENSIONS = new Set([
846
+ ".avif",
847
+ ".bmp",
848
+ ".gif",
849
+ ".heic",
850
+ ".heif",
851
+ ".jpeg",
852
+ ".jpg",
853
+ ".png",
854
+ ".tif",
855
+ ".tiff",
856
+ ".webp"
857
+ ]);
858
+ var PATH_CANDIDATE_PATTERN = /"([^"\n\r]+\.(?:avif|bmp|gif|heic|heif|jpeg|jpg|png|tif|tiff|webp))"|'([^'\n\r]+\.(?:avif|bmp|gif|heic|heif|jpeg|jpg|png|tif|tiff|webp))'|([^\s"'`]+\.(?:avif|bmp|gif|heic|heif|jpeg|jpg|png|tif|tiff|webp))/gi;
859
+ function normalizeCandidatePath(value) {
860
+ return value.replaceAll(String.raw`\ `, " ");
861
+ }
862
+ function isPathLikeCandidate(candidate, matchedValue) {
863
+ return matchedValue.startsWith("\"") || matchedValue.startsWith("'") || candidate.includes("/") || candidate.includes("\\") || candidate.startsWith(".");
864
+ }
865
+ function getAttachmentLabel(path) {
866
+ return basename(path);
867
+ }
868
+ function isReadableImagePath(path) {
869
+ const normalizedPath = normalizeCandidatePath(path);
870
+ const extension = extname(normalizedPath).toLowerCase();
871
+ if (!IMAGE_EXTENSIONS.has(extension)) return false;
872
+ const resolvedPath = isAbsolute(normalizedPath) ? normalizedPath : resolve(normalizedPath);
873
+ if (!existsSync(resolvedPath)) return false;
874
+ try {
875
+ return statSync(resolvedPath).isFile();
876
+ } catch {
877
+ // v8 ignore next
878
+ return false;
879
+ }
880
+ }
881
+ function resolveAttachmentPath(path) {
882
+ const normalizedPath = normalizeCandidatePath(path);
883
+ return isAbsolute(normalizedPath) ? normalizedPath : resolve(normalizedPath);
884
+ }
885
+ function extractImageAttachments(input) {
886
+ const attachments = [];
887
+ const segments = [];
888
+ let lastIndex = 0;
889
+ for (const match of input.matchAll(PATH_CANDIDATE_PATTERN)) {
890
+ const matchedValue = match[0];
891
+ const candidate = match.slice(1).find((value) => Boolean(value));
892
+ // v8 ignore start
893
+ if (candidate === void 0) continue;
894
+ // v8 ignore stop
895
+ if (!isPathLikeCandidate(candidate, matchedValue)) continue;
896
+ if (!isReadableImagePath(candidate)) continue;
897
+ attachments.push(resolveAttachmentPath(candidate));
898
+ segments.push(input.slice(lastIndex, match.index));
899
+ lastIndex = match.index + matchedValue.length;
900
+ }
901
+ if (!attachments.length) return {
902
+ attachments,
903
+ remainingInput: input
904
+ };
905
+ segments.push(input.slice(lastIndex));
906
+ return {
907
+ attachments,
908
+ remainingInput: segments.join("").replaceAll(/\s{2,}/g, " ").trim()
909
+ };
910
+ }
911
+ //#endregion
805
912
  //#region src/components/Chat/CommandMenu.tsx
806
913
  function getMatchingCommands(input) {
807
914
  const normalizedInput = input.trim().toLowerCase();
@@ -821,7 +928,7 @@ function CommandMenu({ input, onSubmit }) {
821
928
  });
822
929
  }
823
930
  //#endregion
824
- //#region src/components/Suggestions.tsx
931
+ //#region src/components/Suggestions/Suggestions.tsx
825
932
  var DEFAULT_MAX_VISIBLE_OPTIONS = 5;
826
933
  function Suggestions({ options, isDisabled = false, maxVisibleOptions = DEFAULT_MAX_VISIBLE_OPTIONS, resetKey, onHighlight, onSelect }) {
827
934
  const [focusedIndex, setFocusedIndex] = useState(0);
@@ -992,28 +1099,81 @@ function FileSuggestions({ input, isDisabled = false, onChange, onSelect }) {
992
1099
  function hasFileSuggestionQuery(input) {
993
1100
  return /(^|.)@\S+/.test(input);
994
1101
  }
995
- function ChatInput({ history: sessionHistory, isDisabled = false, onInterrupt, onSubmit }) {
1102
+ function toAttachment(path, index, isTemp = false) {
1103
+ return {
1104
+ id: `${path}-${String(index)}`,
1105
+ isTemp,
1106
+ label: getAttachmentLabel(path),
1107
+ path
1108
+ };
1109
+ }
1110
+ function cleanupAttachments(attachments) {
1111
+ for (const attachment of attachments) if (attachment.isTemp) removeClipboardImage(attachment.path);
1112
+ }
1113
+ function ChatInput({ history: sessionHistory, isDisabled = false, onInterrupt, onSubmit, theme = getTheme() }) {
996
1114
  const { exit } = useApp();
997
1115
  const [history, setHistory] = useState(sessionHistory);
998
1116
  const [historyIndex, setHistoryIndex] = useState(null);
999
1117
  const [input, setInput] = useState("");
1000
1118
  const [cursorPosition, setCursorPosition] = useState(void 0);
1119
+ const [attachments, setAttachments] = useState([]);
1120
+ const [error, setError] = useState(null);
1001
1121
  const fileSuggestionRef = useRef(null);
1122
+ const nextClipboardImageRef = useRef(1);
1123
+ const hasAttachments = attachments.length > 0;
1002
1124
  useEffect(() => {
1003
1125
  setHistory(sessionHistory);
1004
1126
  setHistoryIndex(null);
1005
1127
  setInput("");
1006
1128
  setCursorPosition(void 0);
1129
+ setError(null);
1007
1130
  fileSuggestionRef.current = null;
1131
+ nextClipboardImageRef.current = 1;
1132
+ setAttachments((previousAttachments) => {
1133
+ cleanupAttachments(previousAttachments);
1134
+ return [];
1135
+ });
1008
1136
  }, [sessionHistory]);
1009
- const resetInput = useCallback(() => {
1137
+ const resetInput = useCallback((deleteTempAttachments = false) => {
1010
1138
  setInput("");
1011
1139
  setCursorPosition(void 0);
1012
1140
  setHistoryIndex(null);
1141
+ setError(null);
1142
+ if (deleteTempAttachments) {
1143
+ setAttachments((previousAttachments) => {
1144
+ cleanupAttachments(previousAttachments);
1145
+ return [];
1146
+ });
1147
+ nextClipboardImageRef.current = 1;
1148
+ return;
1149
+ }
1150
+ setAttachments([]);
1151
+ }, []);
1152
+ const removeLastAttachment = useCallback(() => {
1153
+ setAttachments((previousAttachments) => {
1154
+ const removedAttachment = previousAttachments.at(-1);
1155
+ if (removedAttachment?.isTemp) removeClipboardImage(removedAttachment.path);
1156
+ return previousAttachments.slice(0, -1);
1157
+ });
1158
+ setError(null);
1013
1159
  }, []);
1160
+ const stageAttachments = useCallback((paths, isTemp = false) => {
1161
+ setAttachments((previousAttachments) => [...previousAttachments, ...paths.map((path, index) => toAttachment(path, previousAttachments.length + index, isTemp))]);
1162
+ setError(null);
1163
+ }, []);
1164
+ const attachClipboardImage = useCallback(() => {
1165
+ try {
1166
+ const path = saveClipboardImage(`image-${String(nextClipboardImageRef.current)}`);
1167
+ nextClipboardImageRef.current += 1;
1168
+ stageAttachments([path], true);
1169
+ } catch (error) {
1170
+ setError(error instanceof Error ? error.message : String(error));
1171
+ }
1172
+ }, [stageAttachments]);
1014
1173
  const handleSelectFileSuggestion = useCallback((nextInput) => {
1015
1174
  setInput(nextInput.value);
1016
1175
  setCursorPosition(nextInput.cursorPosition);
1176
+ setError(null);
1017
1177
  }, []);
1018
1178
  const handleFileSuggestionChange = useCallback((nextInput) => {
1019
1179
  if (nextInput) {
@@ -1034,21 +1194,40 @@ function ChatInput({ history: sessionHistory, isDisabled = false, onInterrupt, o
1034
1194
  } else fileSuggestionRef.current = null;
1035
1195
  }, [input]);
1036
1196
  const handleInputChange = useCallback((nextInput) => {
1197
+ if (nextInput.length - input.length > 1) {
1198
+ const { attachments: nextAttachments, remainingInput } = extractImageAttachments(nextInput);
1199
+ if (nextAttachments.length) {
1200
+ stageAttachments(nextAttachments);
1201
+ setInput(remainingInput);
1202
+ setCursorPosition(remainingInput.length);
1203
+ setHistoryIndex(null);
1204
+ return;
1205
+ }
1206
+ }
1037
1207
  setInput(nextInput);
1038
1208
  setHistoryIndex(null);
1039
- }, []);
1209
+ setError(null);
1210
+ }, [input, stageAttachments]);
1040
1211
  const submitAndReset = useCallback((input) => {
1041
1212
  const trimmedInput = input.trim();
1042
- if (!trimmedInput) return;
1043
- onSubmit(trimmedInput);
1044
- if (!trimmedInput.startsWith("/")) setHistory((previousHistory) => [...previousHistory, trimmedInput]);
1045
- resetInput();
1213
+ const imagePaths = attachments.map(({ path }) => path);
1214
+ if (!trimmedInput && !imagePaths.length) return;
1215
+ onSubmit({
1216
+ content: trimmedInput,
1217
+ ...imagePaths.length ? { images: imagePaths } : {}
1218
+ });
1219
+ if (trimmedInput && !trimmedInput.startsWith("/")) setHistory((previousHistory) => [...previousHistory, trimmedInput]);
1220
+ resetInput(trimmedInput.startsWith("/"));
1046
1221
  fileSuggestionRef.current = null;
1047
- }, [onSubmit, resetInput]);
1222
+ }, [
1223
+ attachments,
1224
+ onSubmit,
1225
+ resetInput
1226
+ ]);
1048
1227
  const showCommandMenu = input.startsWith("/");
1049
1228
  const showFileSuggestions = !showCommandMenu && hasFileSuggestionQuery(input);
1050
1229
  const handleHistoryNavigation = useCallback((direction) => {
1051
- if (!history.length || showFileSuggestions) return;
1230
+ if (!history.length || showFileSuggestions || hasAttachments) return;
1052
1231
  if (direction === "up") {
1053
1232
  if (historyIndex === null) {
1054
1233
  if (input) return;
@@ -1080,21 +1259,22 @@ function ChatInput({ history: sessionHistory, isDisabled = false, onInterrupt, o
1080
1259
  setInput(nextInput);
1081
1260
  setCursorPosition(nextInput.length);
1082
1261
  }, [
1262
+ hasAttachments,
1083
1263
  history,
1084
1264
  historyIndex,
1085
1265
  input,
1086
1266
  showFileSuggestions
1087
1267
  ]);
1088
- const handleSubmitText = useCallback((input) => {
1089
- if (input.startsWith("/")) return;
1090
- if (hasFileSuggestionQuery(input)) {
1268
+ const handleSubmitText = useCallback((value) => {
1269
+ if (value.startsWith("/")) return;
1270
+ if (hasFileSuggestionQuery(value)) {
1091
1271
  if (fileSuggestionRef.current) handleSelectFileSuggestion(fileSuggestionRef.current);
1092
1272
  return;
1093
1273
  }
1094
- submitAndReset(input);
1274
+ submitAndReset(value);
1095
1275
  }, [handleSelectFileSuggestion, submitAndReset]);
1096
- const handleSubmitCommand = useCallback((input) => {
1097
- if (LIST.find(({ name }) => name === input)) submitAndReset(input);
1276
+ const handleSubmitCommand = useCallback((value) => {
1277
+ if (LIST.find(({ name }) => name === value)) submitAndReset(value);
1098
1278
  }, [submitAndReset]);
1099
1279
  useInput((inputKey, key) => {
1100
1280
  const isCtrlC = key.ctrl && inputKey === "c";
@@ -1102,6 +1282,14 @@ function ChatInput({ history: sessionHistory, isDisabled = false, onInterrupt, o
1102
1282
  if (key.escape || isCtrlC) onInterrupt?.();
1103
1283
  return;
1104
1284
  }
1285
+ if (key.ctrl && inputKey === "v") {
1286
+ attachClipboardImage();
1287
+ return;
1288
+ }
1289
+ if ((key.backspace || key.delete || inputKey === "") && !input) {
1290
+ if (hasAttachments) removeLastAttachment();
1291
+ return;
1292
+ }
1105
1293
  if (isCtrlC) {
1106
1294
  if (input) {
1107
1295
  resetInput();
@@ -1115,18 +1303,35 @@ function ChatInput({ history: sessionHistory, isDisabled = false, onInterrupt, o
1115
1303
  }
1116
1304
  if (key.downArrow) handleHistoryNavigation("down");
1117
1305
  });
1306
+ const attachmentPrefix = attachments.map(({ label }) => `[${label}]`).join(" ");
1307
+ const wrapIndent = 2 + (attachmentPrefix ? attachmentPrefix.length + 1 : 0);
1118
1308
  return /* @__PURE__ */ jsxs(Box, {
1119
1309
  flexDirection: "column",
1120
1310
  children: [
1121
- /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, { children: "> " }), /* @__PURE__ */ jsx(TextInput, {
1122
- value: input,
1123
- isDisabled,
1124
- cursorPosition,
1125
- wrapIndent: 2,
1126
- onChange: handleInputChange,
1127
- onSubmit: handleSubmitText,
1128
- placeholder: "Ask anything... (/ commands, @ files)"
1129
- })] }),
1311
+ error && /* @__PURE__ */ jsx(Box, {
1312
+ marginBottom: 1,
1313
+ marginX: 2,
1314
+ children: /* @__PURE__ */ jsx(Text, {
1315
+ color: theme.colors.error,
1316
+ children: error
1317
+ })
1318
+ }),
1319
+ /* @__PURE__ */ jsxs(Box, { children: [
1320
+ /* @__PURE__ */ jsx(Text, { children: "> " }),
1321
+ hasAttachments && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Text, {
1322
+ color: theme.colors.accent,
1323
+ children: attachmentPrefix
1324
+ }), /* @__PURE__ */ jsx(Text, { children: " " })] }),
1325
+ /* @__PURE__ */ jsx(TextInput, {
1326
+ value: input,
1327
+ isDisabled,
1328
+ cursorPosition,
1329
+ wrapIndent,
1330
+ onChange: handleInputChange,
1331
+ onSubmit: handleSubmitText,
1332
+ placeholder: hasAttachments ? void 0 : "Ask anything... (/ commands, @ files, Ctrl+V images)"
1333
+ })
1334
+ ] }),
1130
1335
  showCommandMenu && /* @__PURE__ */ jsx(CommandMenu, {
1131
1336
  input,
1132
1337
  onSubmit: handleSubmitCommand
@@ -1468,10 +1673,10 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
1468
1673
  messages,
1469
1674
  processStream
1470
1675
  ]);
1471
- const handleSubmit = useCallback(async (value) => {
1676
+ const handleSubmit = useCallback(async ({ content, images }) => {
1472
1677
  setInterruptReason(null);
1473
- const userContent = value.trim();
1474
- if (!userContent) return;
1678
+ const userContent = content.trim();
1679
+ if (!userContent && !images?.length) return;
1475
1680
  if (userContent.startsWith("/")) {
1476
1681
  onCommand(userContent);
1477
1682
  return;
@@ -1479,7 +1684,8 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
1479
1684
  setIsLoading(true);
1480
1685
  const userMessage = {
1481
1686
  role: USER,
1482
- content: userContent
1687
+ content: userContent,
1688
+ ...images?.length ? { images } : {}
1483
1689
  };
1484
1690
  const updatedMessages = [...messages, userMessage];
1485
1691
  setMessages(updatedMessages);
@@ -1525,14 +1731,15 @@ function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onMod
1525
1731
  history,
1526
1732
  isDisabled: isLoading,
1527
1733
  onInterrupt: handleInterrupt,
1528
- onSubmit: handleSubmit
1734
+ onSubmit: handleSubmit,
1735
+ theme
1529
1736
  })
1530
1737
  })
1531
1738
  ]
1532
1739
  });
1533
1740
  }
1534
1741
  //#endregion
1535
- //#region src/components/Footer.tsx
1742
+ //#region src/components/Footer/Footer.tsx
1536
1743
  function getModeColor(mode, theme) {
1537
1744
  switch (mode) {
1538
1745
  case PLAN: return theme.colors.modePlan;
@@ -1574,7 +1781,7 @@ function Footer({ mode, model, onToggleMode, theme = getTheme() }) {
1574
1781
  });
1575
1782
  }
1576
1783
  //#endregion
1577
- //#region src/components/Header.tsx
1784
+ //#region src/components/Header/Header.tsx
1578
1785
  function abbreviatePath(dir) {
1579
1786
  const home = homedir();
1580
1787
  return dir.startsWith(home) ? `~${dir.slice(home.length)}` : dir;
@@ -1813,11 +2020,15 @@ function ModelCustomDownloadView({ downloadDraft, notice, theme, onDraftChange,
1813
2020
  onSelect: onSelectSuggestion
1814
2021
  }),
1815
2022
  renderNotice(),
1816
- /* @__PURE__ */ jsx(Text, {
1817
- color: theme.colors.secondary,
1818
- dimColor: true,
1819
- children: "Press Enter to download, Esc or Ctrl+C to go back."
1820
- })
2023
+ /* @__PURE__ */ jsxs(Text, { children: [
2024
+ /* @__PURE__ */ jsx(Text, {
2025
+ color: theme.colors.secondary,
2026
+ dimColor: true,
2027
+ children: "Press Enter to download."
2028
+ }),
2029
+ " ",
2030
+ /* @__PURE__ */ jsx(ExitHint, {})
2031
+ ] })
1821
2032
  ]
1822
2033
  });
1823
2034
  }
@@ -1858,6 +2069,7 @@ function ModelDeleteConfirmView({ deleteCandidate, isDeleting, notice, theme, on
1858
2069
  //#endregion
1859
2070
  //#region src/components/ModelManager/ModelDeleteView.tsx
1860
2071
  function ModelDeleteView({ currentModel, installedModels, isLoading, notice, theme, onCancel, onSelect }) {
2072
+ // v8 ignore next
1861
2073
  if (isLoading) return /* @__PURE__ */ jsx(Spinner, { label: "Loading models..." });
1862
2074
  return /* @__PURE__ */ jsxs(SelectPrompt, {
1863
2075
  options: [...buildInstalledModelOptions(installedModels.filter((model) => model !== currentModel), currentModel), BACK],
@@ -2017,6 +2229,10 @@ function ModelManager({ currentModel, onSelect, onClose, theme = getTheme() }) {
2017
2229
  useInput((input, key) => {
2018
2230
  const isEscape = key.escape || input === "\x1B\x1B";
2019
2231
  const isCtrlC = key.ctrl && input === "c" || input === "";
2232
+ if (loadError && view !== View.Menu && (isEscape || isCtrlC)) {
2233
+ handleBackToMenu();
2234
+ return;
2235
+ }
2020
2236
  if (view === View.CustomDownload && (isEscape || isCtrlC)) {
2021
2237
  setNotice(null);
2022
2238
  setHighlightedSuggestion(null);
@@ -2167,17 +2383,10 @@ function ModelManager({ currentModel, onSelect, onClose, theme = getTheme() }) {
2167
2383
  color: notice.tone === "error" ? theme.colors.error : notice.tone === "success" ? theme.colors.status : theme.colors.secondary,
2168
2384
  children: notice.text
2169
2385
  }) : null;
2170
- if (loadError && view !== View.Menu) return /* @__PURE__ */ jsxs(Box, {
2171
- flexDirection: "column",
2172
- children: [/* @__PURE__ */ jsxs(Text, {
2173
- color: theme.colors.error,
2174
- children: ["Error loading models: ", loadError]
2175
- }), /* @__PURE__ */ jsx(Text, {
2176
- color: theme.colors.secondary,
2177
- dimColor: true,
2178
- children: "Press Esc to go back."
2179
- })]
2180
- });
2386
+ if (loadError && view !== View.Menu) return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Text, {
2387
+ color: theme.colors.error,
2388
+ children: ["Error loading models: ", loadError]
2389
+ }), /* @__PURE__ */ jsx(ExitHint, {})] });
2181
2390
  if (view === View.Downloading && downloadProgress) return /* @__PURE__ */ jsx(ModelDownloadingView, {
2182
2391
  progress: downloadProgress,
2183
2392
  theme,
@@ -2243,7 +2452,7 @@ function ModelManager({ currentModel, onSelect, onClose, theme = getTheme() }) {
2243
2452
  });
2244
2453
  }
2245
2454
  //#endregion
2246
- //#region src/components/SearchSettings.tsx
2455
+ //#region src/components/SearchSettings/SearchSettings.tsx
2247
2456
  function SearchSettings({ currentUrl, onClose, onSave, theme = getTheme() }) {
2248
2457
  const [view, setView] = useState("menu");
2249
2458
  const [draftUrl, setDraftUrl] = useState(currentUrl ?? "");
@@ -2319,11 +2528,15 @@ function SearchSettings({ currentUrl, onClose, onSave, theme = getTheme() }) {
2319
2528
  color: theme.colors.error,
2320
2529
  children: error
2321
2530
  }),
2322
- /* @__PURE__ */ jsx(Text, {
2323
- color: theme.colors.secondary,
2324
- dimColor: true,
2325
- children: "Press Enter to save, Esc to go back."
2326
- })
2531
+ /* @__PURE__ */ jsxs(Text, { children: [
2532
+ /* @__PURE__ */ jsx(Text, {
2533
+ color: theme.colors.secondary,
2534
+ dimColor: true,
2535
+ children: "Press Enter to save."
2536
+ }),
2537
+ " ",
2538
+ /* @__PURE__ */ jsx(ExitHint, {})
2539
+ ] })
2327
2540
  ]
2328
2541
  });
2329
2542
  return /* @__PURE__ */ jsxs(SelectPrompt, {
@@ -2345,7 +2558,7 @@ function SearchSettings({ currentUrl, onClose, onSave, theme = getTheme() }) {
2345
2558
  });
2346
2559
  }
2347
2560
  //#endregion
2348
- //#region src/components/SessionManager.tsx
2561
+ //#region src/components/SessionManager/SessionManager.tsx
2349
2562
  var ACTION = {
2350
2563
  CLOSE: "close",
2351
2564
  DELETE_MENU: "delete-menu",
@@ -2355,6 +2568,24 @@ var ACTION = {
2355
2568
  OPEN_PREFIX: "open:"
2356
2569
  };
2357
2570
  var SESSION_LABEL_PADDING = 4;
2571
+ var MAIN_OPTIONS = [
2572
+ {
2573
+ label: "New session",
2574
+ value: ACTION.NEW
2575
+ },
2576
+ {
2577
+ label: "Open session",
2578
+ value: ACTION.OPEN_MENU
2579
+ },
2580
+ {
2581
+ label: "Delete session",
2582
+ value: ACTION.DELETE_MENU
2583
+ },
2584
+ {
2585
+ label: "Close",
2586
+ value: ACTION.CLOSE
2587
+ }
2588
+ ];
2358
2589
  function truncate(value, maxLength) {
2359
2590
  return value.length > maxLength ? `${value.slice(0, maxLength - 1).trimEnd()}…` : value;
2360
2591
  }
@@ -2367,34 +2598,29 @@ function formatSessionLabel(session, maxWidth, prefix = "") {
2367
2598
  function SessionManager({ currentSessionId, onClose, onDelete, onNew, onOpen, theme = getTheme() }) {
2368
2599
  const [view, setView] = useState("main");
2369
2600
  const [error, setError] = useState();
2370
- const [, refreshSessionList] = useState(0);
2601
+ const [sessionListVersion, refreshSessionList] = useState(0);
2371
2602
  const { stdout } = useStdout();
2372
2603
  const sessions = listSessions();
2373
2604
  const maxLabelWidth = Math.max(1, stdout.columns - SESSION_LABEL_PADDING);
2374
- const options = view === "open" ? [...sessions.filter(({ id }) => id !== currentSessionId).map((session) => ({
2375
- label: formatSessionLabel(session, maxLabelWidth),
2376
- value: `${ACTION.OPEN_PREFIX}${session.id}`
2377
- })), BACK] : view === "delete" ? [...sessions.filter(({ id }) => id !== currentSessionId).map((session) => ({
2378
- label: formatSessionLabel(session, maxLabelWidth, "Delete "),
2379
- value: `${ACTION.DELETE_PREFIX}${session.id}`
2380
- })), BACK] : [
2381
- {
2382
- label: "New session",
2383
- value: ACTION.NEW
2384
- },
2385
- {
2386
- label: "Open session",
2387
- value: ACTION.OPEN_MENU
2388
- },
2389
- {
2390
- label: "Delete session",
2391
- value: ACTION.DELETE_MENU
2392
- },
2393
- {
2394
- label: "Close",
2395
- value: ACTION.CLOSE
2605
+ const options = useMemo(() => {
2606
+ switch (view) {
2607
+ case "open": return [...sessions.filter(({ id }) => id !== currentSessionId).map((session) => ({
2608
+ label: formatSessionLabel(session, maxLabelWidth),
2609
+ value: `${ACTION.OPEN_PREFIX}${session.id}`
2610
+ })), BACK];
2611
+ case "delete": return [...sessions.filter(({ id }) => id !== currentSessionId).map((session) => ({
2612
+ label: formatSessionLabel(session, maxLabelWidth, "Delete "),
2613
+ value: `${ACTION.DELETE_PREFIX}${session.id}`
2614
+ })), BACK];
2615
+ default: return MAIN_OPTIONS;
2396
2616
  }
2397
- ];
2617
+ }, [
2618
+ currentSessionId,
2619
+ maxLabelWidth,
2620
+ sessionListVersion,
2621
+ sessions,
2622
+ view
2623
+ ]);
2398
2624
  const handleChange = useCallback((value) => {
2399
2625
  switch (true) {
2400
2626
  case value === ACTION.CLOSE:
@@ -2452,12 +2678,12 @@ function SessionManager({ currentSessionId, onClose, onDelete, onNew, onOpen, th
2452
2678
  options,
2453
2679
  onCancel: onClose,
2454
2680
  onChange: handleChange
2455
- }, `${view}:${String(sessions.length)}`)
2681
+ })
2456
2682
  ]
2457
2683
  });
2458
2684
  }
2459
2685
  //#endregion
2460
- //#region src/components/ThemeSettings.tsx
2686
+ //#region src/components/ThemeSettings/ThemeSettings.tsx
2461
2687
  function ThemeSettings({ currentTheme, onClose, onPreview, onSave }) {
2462
2688
  const [selectedIndex, setSelectedIndex] = useState(() => {
2463
2689
  const initialIndex = LIST$1.findIndex(({ id }) => id === currentTheme);
@@ -2701,11 +2927,13 @@ var ReadinessState = /* @__PURE__ */ function(ReadinessState) {
2701
2927
  ReadinessState["Ready"] = "ready";
2702
2928
  ReadinessState["MissingModelConfig"] = "missing-model-config";
2703
2929
  ReadinessState["NoInstalledModels"] = "no-installed-models";
2930
+ ReadinessState["ServerUnavailable"] = "server-unavailable";
2704
2931
  ReadinessState["ModelLoadError"] = "model-load-error";
2705
2932
  return ReadinessState;
2706
2933
  }({});
2707
2934
  function getTitle(setupState) {
2708
2935
  switch (setupState) {
2936
+ case "server-unavailable": return "Ollama Server Unavailable";
2709
2937
  case "model-load-error": return "Connection Error";
2710
2938
  case "missing-model-config": return "No Model Configured";
2711
2939
  case "no-installed-models": return "No Model Installed";
@@ -2714,7 +2942,7 @@ function getTitle(setupState) {
2714
2942
  function getMessage(setupState, errorMessage) {
2715
2943
  const theme = getTheme();
2716
2944
  switch (setupState) {
2717
- case "checking": return /* @__PURE__ */ jsx(Text, { children: "Checking model setup..." });
2945
+ case "checking": return /* @__PURE__ */ jsx(Text, { children: "Checking Ollama server and model setup..." });
2718
2946
  case "missing-model-config": return /* @__PURE__ */ jsxs(Text, { children: [
2719
2947
  "Select or download a model with",
2720
2948
  " ",
@@ -2727,6 +2955,15 @@ function getMessage(setupState, errorMessage) {
2727
2955
  color: theme.colors.command,
2728
2956
  children: "/model"
2729
2957
  })] });
2958
+ case "server-unavailable": return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Text, { children: "Ollama server is not running or unreachable." }), /* @__PURE__ */ jsxs(Text, { children: [
2959
+ "Start it with ",
2960
+ /* @__PURE__ */ jsx(Text, {
2961
+ color: theme.colors.command,
2962
+ children: "ollama serve"
2963
+ }),
2964
+ " ",
2965
+ "and restart the app"
2966
+ ] })] });
2730
2967
  case "model-load-error": return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Text, { children: [
2731
2968
  "Error loading models",
2732
2969
  errorMessage ? `: ${errorMessage}` : "",
@@ -2756,7 +2993,10 @@ function ReadinessCheck({ errorMessage, onCommand, setupState, theme = getTheme(
2756
2993
  }), getMessage(setupState, errorMessage)]
2757
2994
  }), /* @__PURE__ */ jsx(ChatInput, {
2758
2995
  history: [],
2759
- onSubmit: onCommand
2996
+ onSubmit: ({ content }) => {
2997
+ onCommand(content);
2998
+ },
2999
+ theme
2760
3000
  })]
2761
3001
  });
2762
3002
  }
@@ -2792,9 +3032,13 @@ function App({ sessionId }) {
2792
3032
  setSetupState(ReadinessState.Checking);
2793
3033
  }
2794
3034
  try {
2795
- const installedModels = await listModels();
3035
+ const isHealthy = await checkHealth();
2796
3036
  if (!isMounted) return;
2797
- setSetupState(installedModels.length > 0 ? ReadinessState.Ready : ReadinessState.NoInstalledModels);
3037
+ if (!isHealthy) {
3038
+ setSetupState(ReadinessState.ServerUnavailable);
3039
+ return;
3040
+ }
3041
+ setSetupState((await listModels()).length > 0 ? ReadinessState.Ready : ReadinessState.NoInstalledModels);
2798
3042
  } catch (error) {
2799
3043
  // v8 ignore start
2800
3044
  if (!isMounted) return;
@@ -2937,6 +3181,43 @@ function App({ sessionId }) {
2937
3181
  });
2938
3182
  }
2939
3183
  //#endregion
3184
+ //#region src/components/ExitHint/ExitHint.tsx
3185
+ /**
3186
+ * Exit hint component that displays:
3187
+ * Press Esc/Ctrl+C to go back.
3188
+ */
3189
+ function ExitHint({ action = "go back" }) {
3190
+ return /* @__PURE__ */ jsxs(Text, {
3191
+ color: getTheme().colors.secondary,
3192
+ children: [
3193
+ /* @__PURE__ */ jsx(Text, {
3194
+ dimColor: true,
3195
+ children: "Press "
3196
+ }),
3197
+ /* @__PURE__ */ jsx(Text, {
3198
+ bold: true,
3199
+ children: "Esc"
3200
+ }),
3201
+ /* @__PURE__ */ jsx(Text, {
3202
+ dimColor: true,
3203
+ children: "/"
3204
+ }),
3205
+ /* @__PURE__ */ jsx(Text, {
3206
+ bold: true,
3207
+ children: "Ctrl+C"
3208
+ }),
3209
+ /* @__PURE__ */ jsxs(Text, {
3210
+ dimColor: true,
3211
+ children: [
3212
+ " to ",
3213
+ action,
3214
+ "."
3215
+ ]
3216
+ })
3217
+ ]
3218
+ });
3219
+ }
3220
+ //#endregion
2940
3221
  //#region src/tui.tsx
2941
3222
  function renderApp(sessionId) {
2942
3223
  let resetKey = 0;
package/dist/cli.js CHANGED
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
3
3
  import cac from "cac";
4
- import { homedir } from "node:os";
4
+ import { homedir, tmpdir } from "node:os";
5
5
  import { join } from "node:path";
6
+ import { exec, execFileSync, spawnSync } from "node:child_process";
7
+ import { randomUUID } from "node:crypto";
6
8
  import { Ollama } from "ollama";
7
9
  import { v7 } from "uuid";
8
- import { exec } from "node:child_process";
9
10
  import { promisify } from "node:util";
10
11
  //#region src/constants/command.ts
11
12
  var LIST$1 = [
@@ -37,7 +38,7 @@ var LIST$1 = [
37
38
  //#endregion
38
39
  //#region package.json
39
40
  var name = "code-ollama";
40
- var version = "0.18.1";
41
+ var version = "0.19.0";
41
42
  //#endregion
42
43
  //#region src/constants/package.ts
43
44
  var NAME = name;
@@ -326,6 +327,114 @@ function withSystemMessage(messages) {
326
327
  return [systemMessage, ...messages];
327
328
  }
328
329
  //#endregion
330
+ //#region src/utils/clipboard.ts
331
+ var TEMP_IMAGES_DIRECTORY = join(tmpdir(), "code-ollama", "images");
332
+ var WINDOWS_CLIPBOARD_EXIT_CODE = 11;
333
+ function ensureTempDirectory(directory) {
334
+ mkdirSync(directory, { recursive: true });
335
+ return directory;
336
+ }
337
+ function buildTargetPath(directory, extension) {
338
+ const uniqueName = `${randomUUID()}.${extension}`;
339
+ return join(ensureTempDirectory(directory), uniqueName);
340
+ }
341
+ function readMacClipboardImage(path) {
342
+ execFileSync("osascript", ["-e", `
343
+ set outputPath to POSIX file ${JSON.stringify(path)}
344
+ try
345
+ set clipboardData to the clipboard as «class PNGf»
346
+ on error
347
+ error "Clipboard does not contain an image"
348
+ end try
349
+ set fileHandle to open for access outputPath with write permission
350
+ try
351
+ set eof fileHandle to 0
352
+ write clipboardData to fileHandle
353
+ on error errorMessage
354
+ close access fileHandle
355
+ error errorMessage
356
+ end try
357
+ close access fileHandle
358
+ `], { stdio: "ignore" });
359
+ }
360
+ function getClipboardErrorMessage(error) {
361
+ if (error.message.includes("Clipboard does not contain an image")) return "Clipboard does not contain an image.";
362
+ return "Clipboard image paste failed. Paste an image path instead.";
363
+ }
364
+ function readWindowsClipboardImage(path) {
365
+ execFileSync("powershell", [
366
+ "-NoProfile",
367
+ "-Command",
368
+ `
369
+ Add-Type -AssemblyName System.Windows.Forms
370
+ Add-Type -AssemblyName System.Drawing
371
+ $image = [Windows.Forms.Clipboard]::GetImage()
372
+ if ($null -eq $image) { exit ${String(WINDOWS_CLIPBOARD_EXIT_CODE)} }
373
+ $image.Save($args[0], [System.Drawing.Imaging.ImageFormat]::Png)
374
+ `,
375
+ path
376
+ ], { stdio: "ignore" });
377
+ }
378
+ function readLinuxClipboardImage(directory) {
379
+ const wlPng = spawnSync("wl-paste", [
380
+ "--no-newline",
381
+ "--type",
382
+ "image/png"
383
+ ], { encoding: "buffer" });
384
+ if (wlPng.status === 0 && wlPng.stdout.length > 0) {
385
+ const path = buildTargetPath(directory, "png");
386
+ writeClipboardImageFile(path, wlPng.stdout);
387
+ return path;
388
+ }
389
+ const xclipPng = spawnSync("xclip", [
390
+ "-selection",
391
+ "clipboard",
392
+ "-t",
393
+ "image/png",
394
+ "-o"
395
+ ], { encoding: "buffer" });
396
+ if (xclipPng.status === 0 && xclipPng.stdout.length > 0) {
397
+ const path = buildTargetPath(directory, "png");
398
+ writeClipboardImageFile(path, xclipPng.stdout);
399
+ return path;
400
+ }
401
+ throw new Error("Clipboard image paste is unavailable. Paste an image path instead.");
402
+ }
403
+ function writeClipboardImageFile(path, data) {
404
+ writeFileSync(path, data, {
405
+ flag: "wx",
406
+ mode: 384
407
+ });
408
+ }
409
+ function saveClipboardImage(baseName, directory = TEMP_IMAGES_DIRECTORY) {
410
+ try {
411
+ switch (process.platform) {
412
+ case "darwin": {
413
+ const path = buildTargetPath(directory, "png");
414
+ readMacClipboardImage(path);
415
+ return path;
416
+ }
417
+ case "win32": {
418
+ const path = buildTargetPath(directory, "png");
419
+ readWindowsClipboardImage(path);
420
+ return path;
421
+ }
422
+ case "linux": return readLinuxClipboardImage(directory);
423
+ default: throw new Error("Clipboard image paste is not supported on this platform. Paste an image path instead.");
424
+ }
425
+ } catch (error) {
426
+ if (error instanceof Error && "status" in error && error.status === WINDOWS_CLIPBOARD_EXIT_CODE) throw new Error("Clipboard does not contain an image.", { cause: error });
427
+ const path = join(directory, `${baseName}.png`);
428
+ if (existsSync(path)) rmSync(path, { force: true });
429
+ if (error instanceof Error) throw new Error(getClipboardErrorMessage(error), { cause: error });
430
+ // v8 ignore next
431
+ throw error;
432
+ }
433
+ }
434
+ function removeClipboardImage(path) {
435
+ if (existsSync(path)) rmSync(path, { force: true });
436
+ }
437
+ //#endregion
329
438
  //#region src/utils/config.ts
330
439
  var CONFIG_PATH = join(DIRECTORY, "config.json");
331
440
  var DEFAULT_HOST = "http://localhost:11434";
@@ -358,6 +467,13 @@ function saveConfig(patch) {
358
467
  //#region src/utils/ollama.ts
359
468
  var { host } = loadConfig();
360
469
  var client = new Ollama({ host });
470
+ async function checkHealth() {
471
+ try {
472
+ return (await fetch(host)).ok;
473
+ } catch {
474
+ return false;
475
+ }
476
+ }
361
477
  async function* streamChat(messages, model, tools, signal) {
362
478
  const response = await client.chat({
363
479
  model,
@@ -577,6 +693,9 @@ function writeError(text) {
577
693
  process.stderr.write(text);
578
694
  }
579
695
  //#endregion
696
+ //#region src/utils/time.ts
697
+ var tick = (ms = 0) => new Promise((resolve) => setTimeout(resolve, ms));
698
+ //#endregion
580
699
  //#region src/utils/tools/definitions.ts
581
700
  /**
582
701
  * Helper to define tool parameters
@@ -1123,7 +1242,7 @@ async function main(args = process.argv.slice(2)) {
1123
1242
  else await launchTui();
1124
1243
  }
1125
1244
  async function launchTui(sessionId) {
1126
- const { renderApp } = await import("./assets/tui-GfzUJAWj.js");
1245
+ const { renderApp } = await import("./assets/tui-JyjdXasW.js");
1127
1246
  reset();
1128
1247
  renderApp(sessionId);
1129
1248
  }
@@ -1139,4 +1258,4 @@ function isEntrypoint(argv1 = process.argv[1]) {
1139
1258
  if (isEntrypoint()) main();
1140
1259
  // v8 ignore stop
1141
1260
  //#endregion
1142
- export { SYSTEM as A, REJECT as B, resetSystemMessage as C, LIST as D, WARNING as E, AUTO as F, LIST$1 as H, LABEL as I, PLAN as L, PLAN_GENERATION_INSTRUCTION as M, BACK as N, getTheme as O, CATALOG as P, SAFE as R, saveConfig as S, HEADER_PREFIX as T, VERSION as V, deleteModel as _, color as a, streamChat as b, createSession as c, listSessions as d, loadSession as f, setClearHandler as g, reset as h, WRITE_TOOLS as i, USER as j, ASSISTANT as k, deleteSession as l, clear as m, main, READ_TOOLS as n, write as o, updateSessionModel as p, TOOLS as r, appendMessage as s, executeTool as t, deleteSessionIfEmpty as u, listModels as v, withSystemMessage as w, loadConfig as x, pullModel as y, APPROVE as z };
1261
+ export { WARNING as A, LABEL as B, loadConfig as C, resetSystemMessage as D, saveClipboardImage as E, USER as F, VERSION as G, SAFE as H, PLAN_GENERATION_INSTRUCTION as I, LIST$1 as K, BACK as L, getTheme as M, ASSISTANT as N, withSystemMessage as O, SYSTEM as P, CATALOG as R, streamChat as S, removeClipboardImage as T, APPROVE as U, PLAN as V, REJECT as W, setClearHandler as _, tick as a, listModels as b, appendMessage as c, deleteSessionIfEmpty as d, listSessions as f, reset as g, clear as h, WRITE_TOOLS as i, LIST as j, HEADER_PREFIX as k, createSession as l, updateSessionModel as m, main, READ_TOOLS as n, color as o, loadSession as p, TOOLS as r, write as s, executeTool as t, deleteSession as u, checkHealth as v, saveConfig as w, pullModel as x, deleteModel as y, AUTO as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-ollama",
3
- "version": "0.18.1",
3
+ "version": "0.19.0",
4
4
  "description": "Ollama coding agent that runs in your terminal",
5
5
  "author": "Mark <mark@remarkablemark.org> (https://remarkablemark.org)",
6
6
  "type": "module",
@@ -40,7 +40,7 @@
40
40
  ],
41
41
  "dependencies": {
42
42
  "@inkjs/ui": "2.0.0",
43
- "@shikijs/cli": "4.0.2",
43
+ "@shikijs/cli": "4.1.0",
44
44
  "cac": "7.0.0",
45
45
  "ink": "7.0.3",
46
46
  "marked": "15.0.12",
@@ -53,7 +53,7 @@
53
53
  "@commitlint/cli": "21.0.1",
54
54
  "@commitlint/config-conventional": "21.0.1",
55
55
  "@eslint/js": "10.0.1",
56
- "@types/node": "25.8.0",
56
+ "@types/node": "25.9.0",
57
57
  "@types/react": "19.2.14",
58
58
  "@vitest/coverage-v8": "4.1.6",
59
59
  "eslint": "10.4.0",
@@ -65,9 +65,9 @@
65
65
  "lint-staged": "17.0.5",
66
66
  "prettier": "3.8.3",
67
67
  "publint": "0.3.21",
68
- "tsx": "4.22.1",
68
+ "tsx": "4.22.2",
69
69
  "typescript": "6.0.3",
70
- "typescript-eslint": "8.59.3",
70
+ "typescript-eslint": "8.59.4",
71
71
  "vite": "8.0.13",
72
72
  "vitest": "4.1.6"
73
73
  },