code-ollama 0.9.0 → 0.10.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,14 +1,14 @@
1
- import { a as tick, c as streamChat, d as resetSystemMessage, f as withSystemMessage, h as VERSION, i as executeTool, l as loadConfig, m as PLAN_GENERATION_INSTRUCTION, n as TOOLS, o as setClearHandler, p as ROLE, r as WRITE_TOOLS, s as listModels, t as READ_TOOLS, u as saveConfig } from "../cli.js";
1
+ import { _ as VERSION, a as tick, c as setClearHandler, d as loadConfig, f as saveConfig, g as PLAN_GENERATION_INSTRUCTION, h as ROLE, i as executeTool, l as listModels, m as withSystemMessage, n as TOOLS, o as clear, p as resetSystemMessage, r as WRITE_TOOLS, s as reset, t as READ_TOOLS, u as streamChat } from "../cli.js";
2
2
  import { readdirSync } from "node:fs";
3
3
  import { join, relative } from "node:path";
4
4
  import { homedir } from "node:os";
5
5
  import { exec } from "node:child_process";
6
- import { Box, Text, render, useApp, useInput } from "ink";
6
+ import { Box, Static, Text, render, useApp, useInput, useStdout } from "ink";
7
7
  import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
8
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
9
  import { Select, Spinner } from "@inkjs/ui";
9
- import { jsx, jsxs } from "react/jsx-runtime";
10
10
  import { marked } from "marked";
11
- import TerminalRenderer from "marked-terminal";
11
+ import { markedTerminal } from "marked-terminal";
12
12
  //#region src/constants/command.ts
13
13
  var LIST = [
14
14
  {
@@ -45,22 +45,46 @@ var LABEL = {
45
45
  var HEADER_PREFIX = "🦙 ";
46
46
  //#endregion
47
47
  //#region src/components/CodeBlock/CodeBlock.tsx
48
+ var highlightCache = /* @__PURE__ */ new Map();
49
+ var CODE_BLOCK_REGEX = /^(`{3,})(\w+)?[ \t]*\n([\s\S]*?)^\1[ \t]*$/gm;
50
+ async function prewarmCodeBlocks(content) {
51
+ const promises = [];
52
+ let match;
53
+ CODE_BLOCK_REGEX.lastIndex = 0;
54
+ while ((match = CODE_BLOCK_REGEX.exec(content)) !== null) {
55
+ const language = match[2];
56
+ const code = match[3].trim();
57
+ // v8 ignore next 2
58
+ if (code) promises.push(prewarmHighlight(code, language));
59
+ }
60
+ await Promise.all(promises);
61
+ }
62
+ async function prewarmHighlight(code, language) {
63
+ // v8 ignore start
64
+ const cacheKey = `${language ?? ""}:${code}`;
65
+ if (highlightCache.has(cacheKey)) return;
66
+ // v8 ignore stop
67
+ const result = await highlightCode(code, language);
68
+ highlightCache.set(cacheKey, result);
69
+ }
48
70
  async function highlightCode(code, language = "text") {
49
71
  const { codeToANSI } = await import("@shikijs/cli");
50
72
  try {
51
73
  return await codeToANSI(code, language, "github-light");
52
74
  } catch {
53
- // v8 ignore next - Defensive fallback for unsupported languages
75
+ // v8 ignore next
54
76
  return code;
55
77
  }
56
78
  }
57
79
  var CodeBlock = memo(function CodeBlock({ code, language, role }) {
58
- const [highlighted, setHighlighted] = useState(code);
80
+ const cacheKey = `${language ?? ""}:${code}`;
81
+ const [highlighted, setHighlighted] = useState(() => highlightCache.get(cacheKey) ?? code);
59
82
  useEffect(() => {
60
83
  let canceled = false;
61
84
  async function loadHighlight() {
62
85
  try {
63
86
  const result = await highlightCode(code, language);
87
+ highlightCache.set(cacheKey, result);
64
88
  if (!canceled) setHighlighted(result);
65
89
  } catch {}
66
90
  }
@@ -68,7 +92,11 @@ var CodeBlock = memo(function CodeBlock({ code, language, role }) {
68
92
  return () => {
69
93
  canceled = true;
70
94
  };
71
- }, [code, language]);
95
+ }, [
96
+ cacheKey,
97
+ code,
98
+ language
99
+ ]);
72
100
  const isSystem = role === ROLE.SYSTEM;
73
101
  return /* @__PURE__ */ jsx(Box, {
74
102
  flexDirection: "column",
@@ -76,41 +104,34 @@ var CodeBlock = memo(function CodeBlock({ code, language, role }) {
76
104
  borderColor: isSystem ? "gray" : "dim",
77
105
  paddingX: 1,
78
106
  marginY: 1,
79
- children: /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, {
107
+ children: /* @__PURE__ */ jsx(Text, {
80
108
  dimColor: isSystem,
81
109
  children: highlighted
82
- }) })
110
+ })
83
111
  });
84
112
  });
85
113
  //#endregion
86
114
  //#region src/components/Markdown/Markdown.tsx
87
- marked.setOptions({ renderer: new TerminalRenderer({ theme: "gitHub" }) });
88
- function renderMarkdown(content) {
89
- const result = marked.parse(content);
90
- // v8 ignore next - Defensive fallback for Promise return
91
- return typeof result === "string" ? result.trim() : "";
115
+ var HR_PLACEHOLDER = "__CODE_OLLAMA_HR_PLACEHOLDER__";
116
+ marked.use(markedTerminal({ theme: "gitHub" }));
117
+ marked.use({ renderer: { hr: () => `${HR_PLACEHOLDER}\n` } });
118
+ function renderMarkdown(content, hrWidth) {
119
+ const hr = "".repeat(Math.max(1, hrWidth));
120
+ try {
121
+ const result = marked.parse(content);
122
+ return (typeof result === "string" ? result.trim() : content).replaceAll(HR_PLACEHOLDER, hr);
123
+ } catch {
124
+ return content;
125
+ }
126
+ // v8 ignore stop
92
127
  }
93
128
  var Markdown = memo(function Markdown({ content, color, dimColor }) {
94
- const [rendered, setRendered] = useState(content);
95
- useEffect(() => {
96
- let canceled = false;
97
- function loadMarkdown() {
98
- try {
99
- const result = renderMarkdown(content);
100
- // v8 ignore start
101
- if (!canceled) setRendered(result);
102
- } catch {}
103
- // v8 ignore stop
104
- }
105
- loadMarkdown();
106
- return () => {
107
- canceled = true;
108
- };
109
- }, [content]);
129
+ const { stdout } = useStdout();
130
+ const availableWidth = stdout.columns - 4;
110
131
  return /* @__PURE__ */ jsx(Text, {
111
132
  color,
112
133
  dimColor,
113
- children: rendered
134
+ children: useMemo(() => renderMarkdown(content, availableWidth), [content, availableWidth])
114
135
  });
115
136
  });
116
137
  //#endregion
@@ -132,10 +153,10 @@ function getMessageColor(role) {
132
153
  }
133
154
  function parseContent(content) {
134
155
  const segments = [];
135
- const codeBlockRegex = /```(\w+)?\n?([\s\S]*?)```/g;
136
156
  let lastIndex = 0;
137
157
  let match;
138
- while ((match = codeBlockRegex.exec(content)) !== null) {
158
+ CODE_BLOCK_REGEX.lastIndex = 0;
159
+ while ((match = CODE_BLOCK_REGEX.exec(content)) !== null) {
139
160
  if (match.index > lastIndex) {
140
161
  const textContent = content.slice(lastIndex, match.index).trim();
141
162
  // v8 ignore next 2 - Defensive check for empty trimmed content
@@ -144,8 +165,8 @@ function parseContent(content) {
144
165
  content: textContent
145
166
  });
146
167
  }
147
- const language = match[1];
148
- const codeContent = match[2].trim();
168
+ const language = match[2];
169
+ const codeContent = match[3].trim();
149
170
  // v8 ignore next 2 - Defensive check for empty code block
150
171
  if (codeContent) segments.push({
151
172
  type: "code",
@@ -163,7 +184,7 @@ function parseContent(content) {
163
184
  });
164
185
  }
165
186
  // v8 ignore next 2 - Defensive fallback for edge case
166
- if (segments.length === 0 && content.trim()) segments.push({
187
+ if (!segments.length && content.trim()) segments.push({
167
188
  type: "text",
168
189
  content: content.trim()
169
190
  });
@@ -212,11 +233,14 @@ var Message = memo(function Message({ message }) {
212
233
  })
213
234
  });
214
235
  });
215
- function Messages({ messages, isLoading, streamingMessage }) {
236
+ function Messages({ messages, isLoading, sessionId = 0, streamingMessage }) {
216
237
  return /* @__PURE__ */ jsxs(Box, {
217
238
  flexDirection: "column",
218
239
  children: [
219
- messages.filter(({ content }) => content !== TURN_ABORTED_MESSAGE).map((message, index) => /* @__PURE__ */ jsx(Message, { message }, index)),
240
+ /* @__PURE__ */ jsx(Static, {
241
+ items: messages.filter(({ content }) => content !== TURN_ABORTED_MESSAGE),
242
+ children: (message, index) => /* @__PURE__ */ jsx(Message, { message }, index)
243
+ }, sessionId),
220
244
  streamingMessage && /* @__PURE__ */ jsx(Message, { message: streamingMessage }),
221
245
  isLoading && !streamingMessage?.content && /* @__PURE__ */ jsx(Box, {
222
246
  marginTop: -1,
@@ -229,9 +253,9 @@ function Messages({ messages, isLoading, streamingMessage }) {
229
253
  }
230
254
  //#endregion
231
255
  //#region src/components/SelectPrompt.tsx
232
- function SelectPrompt({ children, onEscape, ...selectProps }) {
233
- useInput((_, key) => {
234
- if (key.escape) onEscape?.();
256
+ function SelectPrompt({ children, onCancel, ...selectProps }) {
257
+ useInput((input, key) => {
258
+ if (key.escape || key.ctrl && input === "c") onCancel?.();
235
259
  });
236
260
  return /* @__PURE__ */ jsxs(Box, {
237
261
  flexDirection: "column",
@@ -299,7 +323,7 @@ function PlanApproval({ planContent, onModeChange }) {
299
323
  onChange: useCallback((value) => {
300
324
  onModeChange(value);
301
325
  }, [onModeChange]),
302
- onEscape: useCallback(() => {
326
+ onCancel: useCallback(() => {
303
327
  onModeChange(NAME.PLAN);
304
328
  }, [onModeChange]),
305
329
  children: /* @__PURE__ */ jsxs(Box, {
@@ -340,7 +364,7 @@ function ToolApproval({ toolCall, onDecision }) {
340
364
  return /* @__PURE__ */ jsxs(SelectPrompt, {
341
365
  options,
342
366
  onChange: handleChange,
343
- onEscape: handleEscape,
367
+ onCancel: handleEscape,
344
368
  children: [
345
369
  /* @__PURE__ */ jsx(Text, {
346
370
  color: "yellow",
@@ -385,16 +409,27 @@ var INTERRUPT_REASON = /* @__PURE__ */ function(INTERRUPT_REASON) {
385
409
  }({});
386
410
  //#endregion
387
411
  //#region src/components/TextInput/TextInput.tsx
388
- function TextInput({ value, isDisabled = false, placeholder, onChange, onSubmit }) {
412
+ function TextInput({ value, isDisabled = false, placeholder, cursorPosition: externalCursorPosition, onChange, onSubmit }) {
389
413
  const [cursorPosition, setCursorPosition] = useState(value.length);
390
414
  const prevValueRef = useRef(value);
415
+ const prevExternalCursorRef = useRef(externalCursorPosition);
416
+ useEffect(() => {
417
+ if (externalCursorPosition !== void 0 && externalCursorPosition !== prevExternalCursorRef.current) {
418
+ prevExternalCursorRef.current = externalCursorPosition;
419
+ setCursorPosition(externalCursorPosition);
420
+ }
421
+ }, [externalCursorPosition]);
391
422
  useEffect(() => {
392
423
  const prevValue = prevValueRef.current;
393
424
  prevValueRef.current = value;
394
425
  if (value === "") setCursorPosition(0);
395
- else if (value.length > prevValue.length && cursorPosition <= prevValue.length) setCursorPosition(value.length);
426
+ else if (value.length > prevValue.length + 1 && cursorPosition <= prevValue.length && externalCursorPosition === void 0) setCursorPosition(value.length);
396
427
  else if (cursorPosition > value.length) setCursorPosition(value.length);
397
- }, [value, cursorPosition]);
428
+ }, [
429
+ value,
430
+ cursorPosition,
431
+ externalCursorPosition
432
+ ]);
398
433
  useInput((input, key) => {
399
434
  // v8 ignore next
400
435
  if (isDisabled) return;
@@ -441,12 +476,23 @@ function TextInput({ value, isDisabled = false, placeholder, onChange, onSubmit
441
476
  }, { isActive: !isDisabled });
442
477
  const displayValue = value || (placeholder ?? "");
443
478
  const isPlaceholder = Boolean(!value && placeholder);
444
- const char = displayValue[cursorPosition] || " ";
479
+ const cursorChar = displayValue[cursorPosition] || " ";
445
480
  const before = displayValue.slice(0, cursorPosition);
446
481
  const after = displayValue.slice(cursorPosition + 1);
447
- const dimStyle = isPlaceholder ? "\x1B[2m" : "";
448
- const resetDim = isPlaceholder ? "\x1B[22m" : "";
449
- return /* @__PURE__ */ jsx(Text, { children: `${dimStyle}${before}${resetDim}\x1b[7m${char}\x1b[27m${dimStyle}${after}${resetDim}` });
482
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
483
+ /* @__PURE__ */ jsx(Text, {
484
+ dimColor: isPlaceholder,
485
+ children: before
486
+ }),
487
+ /* @__PURE__ */ jsx(Text, {
488
+ inverse: true,
489
+ children: cursorChar
490
+ }),
491
+ /* @__PURE__ */ jsx(Text, {
492
+ dimColor: isPlaceholder,
493
+ children: after
494
+ })
495
+ ] });
450
496
  }
451
497
  //#endregion
452
498
  //#region src/components/Chat/CommandMenu.tsx
@@ -470,7 +516,7 @@ function CommandMenu({ input, onSubmit }) {
470
516
  //#endregion
471
517
  //#region src/components/Chat/FileSuggestions.tsx
472
518
  var MAX_VISIBLE_OPTIONS = 5;
473
- var MENTION_PATTERN = /(^|\s)@(\S+)$/;
519
+ var MENTION_PATTERN = /(^|.)@(\S+)/;
474
520
  var RIPGREP_MAX_BUFFER = 10 * 1024 * 1024;
475
521
  function normalizePath(filePath) {
476
522
  return filePath.replaceAll("\\", "/");
@@ -486,8 +532,17 @@ function getMentionMatch(input) {
486
532
  function buildNextInput(input, filePath) {
487
533
  const mentionMatch = getMentionMatch(input);
488
534
  // v8 ignore next 3
489
- if (!mentionMatch) return input;
490
- return `${mentionMatch.prefix}${filePath} `;
535
+ if (!mentionMatch) return {
536
+ value: input,
537
+ cursorPosition: input.length
538
+ };
539
+ const mentionEndIndex = mentionMatch.prefix.length + 1 + mentionMatch.query.length;
540
+ const suffix = input.slice(mentionEndIndex);
541
+ const separator = !suffix.length || !/\s/.test(suffix[0]) ? " " : "";
542
+ return {
543
+ value: `${mentionMatch.prefix}${filePath}${separator}${suffix}`,
544
+ cursorPosition: mentionMatch.prefix.length + filePath.length + separator.length
545
+ };
491
546
  }
492
547
  function listProjectFilesFallback(rootDir) {
493
548
  const filePaths = [];
@@ -558,7 +613,7 @@ function FileSuggestions({ input, isDisabled = false, onChange, onSelect }) {
558
613
  onChange(null);
559
614
  return;
560
615
  }
561
- onChange(buildNextInput(input, options[focusedIndex]));
616
+ onChange(buildNextInput(input, options[focusedIndex]).value);
562
617
  }, [
563
618
  focusedIndex,
564
619
  input,
@@ -596,21 +651,38 @@ function FileSuggestions({ input, isDisabled = false, onChange, onSelect }) {
596
651
  //#endregion
597
652
  //#region src/components/Chat/Input.tsx
598
653
  function hasFileSuggestionQuery(input) {
599
- return /(^|\s)@\S+$/.test(input);
654
+ return /(^|.)@\S+/.test(input);
600
655
  }
601
656
  function Input({ isDisabled = false, onInterrupt, onSubmit }) {
602
657
  const { exit } = useApp();
603
658
  const [input, setInput] = useState("");
659
+ const [cursorPosition, setCursorPosition] = useState(void 0);
604
660
  const fileSuggestionRef = useRef(null);
605
661
  const resetInput = useCallback(() => {
606
662
  setInput("");
607
663
  }, []);
608
664
  const handleSelectFileSuggestion = useCallback((nextInput) => {
609
- setInput(nextInput);
665
+ setInput(nextInput.value);
666
+ setCursorPosition(nextInput.cursorPosition);
610
667
  }, []);
611
668
  const handleFileSuggestionChange = useCallback((nextInput) => {
612
- fileSuggestionRef.current = nextInput;
613
- }, []);
669
+ if (nextInput) {
670
+ const mentionMatch = /(^|.)@(\S+)/.exec(input);
671
+ // v8 ignore start
672
+ if (mentionMatch) {
673
+ const prefixLength = mentionMatch.index + mentionMatch[1].length;
674
+ const queryLength = mentionMatch[2].length;
675
+ const suffix = input.slice(prefixLength + 1 + queryLength);
676
+ fileSuggestionRef.current = {
677
+ value: nextInput,
678
+ cursorPosition: nextInput.length - suffix.length
679
+ };
680
+ } else fileSuggestionRef.current = {
681
+ value: nextInput,
682
+ cursorPosition: nextInput.length
683
+ };
684
+ } else fileSuggestionRef.current = null;
685
+ }, [input]);
614
686
  const submitAndReset = useCallback((input) => {
615
687
  const trimmedInput = input.trim();
616
688
  if (!trimmedInput) return;
@@ -652,6 +724,7 @@ function Input({ isDisabled = false, onInterrupt, onSubmit }) {
652
724
  /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, { children: "> " }), /* @__PURE__ */ jsx(TextInput, {
653
725
  value: input,
654
726
  isDisabled,
727
+ cursorPosition,
655
728
  onChange: setInput,
656
729
  onSubmit: handleSubmitText,
657
730
  placeholder: "Ask anything... (/ commands, @ files)"
@@ -786,11 +859,13 @@ function Chat({ model, onCommand, mode, onModeChange, sessionId }) {
786
859
  return;
787
860
  }
788
861
  }
862
+ await prewarmCodeBlocks(assistantMessage.content);
789
863
  commitAssistantMessage();
790
864
  } catch (error) {
791
865
  // v8 ignore next
792
866
  if (!controller.signal.aborted) {
793
867
  assistantMessage.content = `Error: ${error instanceof Error ? error.message : String(error)}`;
868
+ await prewarmCodeBlocks(assistantMessage.content);
794
869
  commitAssistantMessage();
795
870
  }
796
871
  } finally {
@@ -856,6 +931,7 @@ function Chat({ model, onCommand, mode, onModeChange, sessionId }) {
856
931
  return;
857
932
  }
858
933
  }
934
+ await prewarmCodeBlocks(assistantMessage.content);
859
935
  const researchMessages = commitAssistantMessage();
860
936
  const planInstruction = {
861
937
  role: ROLE.SYSTEM,
@@ -896,6 +972,7 @@ function Chat({ model, onCommand, mode, onModeChange, sessionId }) {
896
972
  // v8 ignore next
897
973
  if (!controller.signal.aborted) {
898
974
  assistantMessage.content = `Error: ${error instanceof Error ? error.message : String(error)}`;
975
+ await prewarmCodeBlocks(assistantMessage.content);
899
976
  commitAssistantMessage();
900
977
  }
901
978
  } finally {
@@ -996,6 +1073,7 @@ function Chat({ model, onCommand, mode, onModeChange, sessionId }) {
996
1073
  /* @__PURE__ */ jsx(Messages, {
997
1074
  messages,
998
1075
  isLoading,
1076
+ sessionId,
999
1077
  streamingMessage
1000
1078
  }),
1001
1079
  pendingPlan && /* @__PURE__ */ jsx(PlanApproval, {
@@ -1013,10 +1091,13 @@ function Chat({ model, onCommand, mode, onModeChange, sessionId }) {
1013
1091
  children: interruptReason === INTERRUPT_REASON.REJECTED ? "❗ Tool call rejected." : "❗ Execution interrupted."
1014
1092
  })
1015
1093
  }),
1016
- !pendingPlan && !pendingToolCall && /* @__PURE__ */ jsx(Input, {
1017
- isDisabled: isLoading,
1018
- onInterrupt: handleInterrupt,
1019
- onSubmit: handleSubmit
1094
+ !pendingPlan && !pendingToolCall && /* @__PURE__ */ jsx(Box, {
1095
+ marginTop: 1,
1096
+ children: /* @__PURE__ */ jsx(Input, {
1097
+ isDisabled: isLoading,
1098
+ onInterrupt: handleInterrupt,
1099
+ onSubmit: handleSubmit
1100
+ })
1020
1101
  })
1021
1102
  ]
1022
1103
  });
@@ -1027,11 +1108,12 @@ function getModeColor(mode) {
1027
1108
  switch (mode) {
1028
1109
  case NAME.PLAN: return "blue";
1029
1110
  case NAME.AUTO: return "red";
1030
- case NAME.SAFE:
1031
- default: return "green";
1111
+ case NAME.SAFE: return "green";
1112
+ // v8 ignore next
1113
+ default: return;
1032
1114
  }
1033
1115
  }
1034
- function Footer({ mode, onToggleMode }) {
1116
+ function Footer({ mode, model, onToggleMode }) {
1035
1117
  useInput((_, key) => {
1036
1118
  if (key.tab && key.shift) onToggleMode();
1037
1119
  });
@@ -1047,9 +1129,13 @@ function Footer({ mode, onToggleMode }) {
1047
1129
  color: getModeColor(mode),
1048
1130
  children: modeLabel
1049
1131
  }),
1132
+ " (Shift+Tab to toggle)",
1133
+ " ",
1134
+ "❖",
1135
+ " Model: ",
1050
1136
  /* @__PURE__ */ jsx(Text, {
1051
- dimColor: true,
1052
- children: " (Shift+Tab to toggle)"
1137
+ color: "cyan",
1138
+ children: model
1053
1139
  })
1054
1140
  ]
1055
1141
  })
@@ -1061,45 +1147,53 @@ function abbreviatePath(dir) {
1061
1147
  const home = homedir();
1062
1148
  return dir.startsWith(home) ? `~${dir.slice(home.length)}` : dir;
1063
1149
  }
1064
- function Header({ model }) {
1150
+ function Header({ model, onLoad }) {
1065
1151
  const directory = abbreviatePath(process.cwd());
1066
- return /* @__PURE__ */ jsxs(Box, {
1067
- borderStyle: "round",
1068
- flexDirection: "column",
1069
- paddingX: 1,
1070
- children: [
1071
- /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsxs(Text, {
1072
- bold: true,
1073
- children: [HEADER_PREFIX, "Code Ollama"]
1074
- }), /* @__PURE__ */ jsxs(Text, {
1075
- dimColor: true,
1076
- children: [
1077
- " (v",
1078
- VERSION,
1079
- ")"
1080
- ]
1081
- })] }),
1082
- /* @__PURE__ */ jsx(Text, { children: " " }),
1083
- /* @__PURE__ */ jsxs(Box, { children: [
1084
- /* @__PURE__ */ jsx(Text, {
1152
+ useEffect(() => {
1153
+ onLoad();
1154
+ }, []);
1155
+ return /* @__PURE__ */ jsx(Static, {
1156
+ items: [0],
1157
+ children: (key) => /* @__PURE__ */ jsxs(Box, {
1158
+ borderStyle: "round",
1159
+ flexDirection: "column",
1160
+ paddingX: 1,
1161
+ children: [
1162
+ /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsxs(Text, {
1163
+ bold: true,
1164
+ children: [HEADER_PREFIX, "Code Ollama"]
1165
+ }), /* @__PURE__ */ jsxs(Text, {
1085
1166
  dimColor: true,
1086
- children: "model:".padEnd(11)
1087
- }),
1088
- /* @__PURE__ */ jsxs(Text, { children: [model, " "] }),
1089
- /* @__PURE__ */ jsx(Text, {
1090
- color: "cyan",
1091
- children: "/model"
1167
+ children: [
1168
+ " (v",
1169
+ VERSION,
1170
+ ")"
1171
+ ]
1172
+ })] }),
1173
+ /* @__PURE__ */ jsxs(Box, {
1174
+ marginTop: 1,
1175
+ children: [
1176
+ /* @__PURE__ */ jsx(Text, {
1177
+ dimColor: true,
1178
+ children: "model:".padEnd(11)
1179
+ }),
1180
+ /* @__PURE__ */ jsx(Text, { children: model.padEnd(model.length + 3) }),
1181
+ /* @__PURE__ */ jsx(Text, {
1182
+ color: "cyan",
1183
+ children: "/model"
1184
+ }),
1185
+ /* @__PURE__ */ jsx(Text, {
1186
+ dimColor: true,
1187
+ children: " to switch"
1188
+ })
1189
+ ]
1092
1190
  }),
1093
- /* @__PURE__ */ jsx(Text, {
1191
+ /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
1094
1192
  dimColor: true,
1095
- children: " to switch"
1096
- })
1097
- ] }),
1098
- /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
1099
- dimColor: true,
1100
- children: "directory:".padEnd(11)
1101
- }), /* @__PURE__ */ jsx(Text, { children: directory })] })
1102
- ]
1193
+ children: "directory:".padEnd(11)
1194
+ }), /* @__PURE__ */ jsx(Text, { children: directory })] })
1195
+ ]
1196
+ }, key)
1103
1197
  });
1104
1198
  }
1105
1199
  //#endregion
@@ -1107,8 +1201,12 @@ function Header({ model }) {
1107
1201
  function ModelPicker({ currentModel, onSelect, onClose }) {
1108
1202
  const [options, setOptions] = useState([]);
1109
1203
  const [error, setError] = useState(null);
1110
- useInput((_, key) => {
1111
- if (options.length && key.return) setTimeout(onClose);
1204
+ useInput(async (_input, key) => {
1205
+ if (!options.length) return;
1206
+ if (key.return) {
1207
+ await tick();
1208
+ onClose();
1209
+ }
1112
1210
  });
1113
1211
  useEffect(() => {
1114
1212
  async function load() {
@@ -1137,7 +1235,7 @@ function ModelPicker({ currentModel, onSelect, onClose }) {
1137
1235
  options,
1138
1236
  defaultValue: currentModel,
1139
1237
  onChange: onSelect,
1140
- onEscape: onClose,
1238
+ onCancel: onClose,
1141
1239
  children: /* @__PURE__ */ jsx(SelectPromptHint, { message: "Select a model" })
1142
1240
  });
1143
1241
  }
@@ -1149,6 +1247,10 @@ function App() {
1149
1247
  const [picking, setPicking] = useState(false);
1150
1248
  const [mode, setMode] = useState(NAME.SAFE);
1151
1249
  const [sessionId, setSessionId] = useState(0);
1250
+ const [isHeaderLoaded, setIsHeaderLoaded] = useState(false);
1251
+ const handleHeaderLoad = useCallback(() => {
1252
+ setIsHeaderLoaded(true);
1253
+ }, []);
1152
1254
  const handleCommand = useCallback((command) => {
1153
1255
  switch (command) {
1154
1256
  case "/model":
@@ -1156,6 +1258,7 @@ function App() {
1156
1258
  break;
1157
1259
  case "/clear":
1158
1260
  resetSystemMessage();
1261
+ clear();
1159
1262
  setPicking(false);
1160
1263
  setSessionId((sessionId) => sessionId + 1);
1161
1264
  break;
@@ -1204,10 +1307,14 @@ function App() {
1204
1307
  return /* @__PURE__ */ jsxs(Box, {
1205
1308
  flexDirection: "column",
1206
1309
  children: [
1207
- /* @__PURE__ */ jsx(Header, { model }),
1208
- body,
1310
+ /* @__PURE__ */ jsx(Header, {
1311
+ model,
1312
+ onLoad: handleHeaderLoad
1313
+ }),
1314
+ isHeaderLoaded && body,
1209
1315
  /* @__PURE__ */ jsx(Footer, {
1210
1316
  mode,
1317
+ model,
1211
1318
  onToggleMode: handleToggleMode
1212
1319
  })
1213
1320
  ]
@@ -1216,14 +1323,14 @@ function App() {
1216
1323
  //#endregion
1217
1324
  //#region src/tui.tsx
1218
1325
  function renderApp() {
1219
- const tree = /* @__PURE__ */ jsx(App, {});
1220
- const app = render(tree, {
1326
+ let resetKey = 0;
1327
+ const app = render(/* @__PURE__ */ jsx(App, {}, resetKey), {
1221
1328
  exitOnCtrlC: false,
1222
1329
  maxFps: 60
1223
1330
  });
1224
1331
  setClearHandler(() => {
1225
- app.clear();
1226
- app.rerender(tree);
1332
+ reset();
1333
+ app.rerender(/* @__PURE__ */ jsx(App, {}, ++resetKey));
1227
1334
  });
1228
1335
  }
1229
1336
  //#endregion
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import { exec } from "node:child_process";
8
8
  import { promisify } from "node:util";
9
9
  //#endregion
10
10
  //#region src/constants/package.ts
11
- var VERSION = "0.9.0";
11
+ var VERSION = "0.10.0";
12
12
  //#endregion
13
13
  //#region src/constants/prompt.ts
14
14
  var BASE_SYSTEM_PROMPT = `You are a coding assistant that helps users write, edit, and understand code. You have access to tools for reading files, writing files, running shell commands, and searching code
@@ -166,7 +166,24 @@ async function listModels() {
166
166
  const { models } = await client.list();
167
167
  return models.map(({ name }) => name);
168
168
  }
169
- function setClearHandler(handler) {}
169
+ //#endregion
170
+ //#region src/utils/screen.ts
171
+ var clearHandler = null;
172
+ function setClearHandler(handler) {
173
+ clearHandler = handler;
174
+ }
175
+ /**
176
+ * Clear the screen with Ink.
177
+ */
178
+ function clear() {
179
+ clearHandler?.();
180
+ }
181
+ /**
182
+ * Reset the screen with ANSI escape sequence.
183
+ */
184
+ function reset() {
185
+ process.stdout.write("\x1Bc\x1B[?25l");
186
+ }
170
187
  //#endregion
171
188
  //#region src/utils/time.ts
172
189
  var tick = (ms = 0) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -512,8 +529,8 @@ async function processRunStream(messages, model) {
512
529
  }
513
530
  async function main(args = process.argv.slice(2)) {
514
531
  if (!args.length) {
515
- const { renderApp } = await import("./assets/tui-VKBxlYAz.js");
516
- process.stdout.write("\x1Bc");
532
+ const { renderApp } = await import("./assets/tui-D2NgQSV7.js");
533
+ reset();
517
534
  renderApp();
518
535
  return;
519
536
  }
@@ -535,4 +552,4 @@ function isEntrypoint(argv1 = process.argv[1]) {
535
552
  if (isEntrypoint()) main();
536
553
  // v8 ignore stop
537
554
  //#endregion
538
- export { tick as a, streamChat as c, resetSystemMessage as d, withSystemMessage as f, VERSION as h, executeTool as i, loadConfig as l, PLAN_GENERATION_INSTRUCTION as m, main, TOOLS as n, setClearHandler as o, ROLE as p, WRITE_TOOLS as r, listModels as s, READ_TOOLS as t, saveConfig as u };
555
+ export { VERSION as _, tick as a, setClearHandler as c, loadConfig as d, saveConfig as f, PLAN_GENERATION_INSTRUCTION as g, ROLE as h, executeTool as i, listModels as l, withSystemMessage as m, main, TOOLS as n, clear as o, resetSystemMessage as p, WRITE_TOOLS as r, reset as s, READ_TOOLS as t, streamChat as u };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-ollama",
3
- "version": "0.9.0",
3
+ "version": "0.10.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",