code-ollama 0.9.0 → 0.9.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.
@@ -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,
@@ -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
  });
@@ -1061,45 +1142,53 @@ function abbreviatePath(dir) {
1061
1142
  const home = homedir();
1062
1143
  return dir.startsWith(home) ? `~${dir.slice(home.length)}` : dir;
1063
1144
  }
1064
- function Header({ model }) {
1145
+ function Header({ model, onLoad }) {
1065
1146
  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, {
1147
+ useEffect(() => {
1148
+ onLoad();
1149
+ }, []);
1150
+ return /* @__PURE__ */ jsx(Static, {
1151
+ items: [0],
1152
+ children: (key) => /* @__PURE__ */ jsxs(Box, {
1153
+ borderStyle: "round",
1154
+ flexDirection: "column",
1155
+ paddingX: 1,
1156
+ children: [
1157
+ /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsxs(Text, {
1158
+ bold: true,
1159
+ children: [HEADER_PREFIX, "Code Ollama"]
1160
+ }), /* @__PURE__ */ jsxs(Text, {
1085
1161
  dimColor: true,
1086
- children: "model:".padEnd(11)
1087
- }),
1088
- /* @__PURE__ */ jsxs(Text, { children: [model, " "] }),
1089
- /* @__PURE__ */ jsx(Text, {
1090
- color: "cyan",
1091
- children: "/model"
1162
+ children: [
1163
+ " (v",
1164
+ VERSION,
1165
+ ")"
1166
+ ]
1167
+ })] }),
1168
+ /* @__PURE__ */ jsxs(Box, {
1169
+ marginTop: 1,
1170
+ children: [
1171
+ /* @__PURE__ */ jsx(Text, {
1172
+ dimColor: true,
1173
+ children: "model:".padEnd(11)
1174
+ }),
1175
+ /* @__PURE__ */ jsx(Text, { children: model.padEnd(model.length + 3) }),
1176
+ /* @__PURE__ */ jsx(Text, {
1177
+ color: "cyan",
1178
+ children: "/model"
1179
+ }),
1180
+ /* @__PURE__ */ jsx(Text, {
1181
+ dimColor: true,
1182
+ children: " to switch"
1183
+ })
1184
+ ]
1092
1185
  }),
1093
- /* @__PURE__ */ jsx(Text, {
1186
+ /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
1094
1187
  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
- ]
1188
+ children: "directory:".padEnd(11)
1189
+ }), /* @__PURE__ */ jsx(Text, { children: directory })] })
1190
+ ]
1191
+ }, key)
1103
1192
  });
1104
1193
  }
1105
1194
  //#endregion
@@ -1149,6 +1238,10 @@ function App() {
1149
1238
  const [picking, setPicking] = useState(false);
1150
1239
  const [mode, setMode] = useState(NAME.SAFE);
1151
1240
  const [sessionId, setSessionId] = useState(0);
1241
+ const [isHeaderLoaded, setIsHeaderLoaded] = useState(false);
1242
+ const handleHeaderLoad = useCallback(() => {
1243
+ setIsHeaderLoaded(true);
1244
+ }, []);
1152
1245
  const handleCommand = useCallback((command) => {
1153
1246
  switch (command) {
1154
1247
  case "/model":
@@ -1156,6 +1249,7 @@ function App() {
1156
1249
  break;
1157
1250
  case "/clear":
1158
1251
  resetSystemMessage();
1252
+ clear();
1159
1253
  setPicking(false);
1160
1254
  setSessionId((sessionId) => sessionId + 1);
1161
1255
  break;
@@ -1204,8 +1298,11 @@ function App() {
1204
1298
  return /* @__PURE__ */ jsxs(Box, {
1205
1299
  flexDirection: "column",
1206
1300
  children: [
1207
- /* @__PURE__ */ jsx(Header, { model }),
1208
- body,
1301
+ /* @__PURE__ */ jsx(Header, {
1302
+ model,
1303
+ onLoad: handleHeaderLoad
1304
+ }),
1305
+ isHeaderLoaded && body,
1209
1306
  /* @__PURE__ */ jsx(Footer, {
1210
1307
  mode,
1211
1308
  onToggleMode: handleToggleMode
@@ -1216,14 +1313,14 @@ function App() {
1216
1313
  //#endregion
1217
1314
  //#region src/tui.tsx
1218
1315
  function renderApp() {
1219
- const tree = /* @__PURE__ */ jsx(App, {});
1220
- const app = render(tree, {
1316
+ let resetKey = 0;
1317
+ const app = render(/* @__PURE__ */ jsx(App, {}, resetKey), {
1221
1318
  exitOnCtrlC: false,
1222
1319
  maxFps: 60
1223
1320
  });
1224
1321
  setClearHandler(() => {
1225
- app.clear();
1226
- app.rerender(tree);
1322
+ reset();
1323
+ app.rerender(/* @__PURE__ */ jsx(App, {}, ++resetKey));
1227
1324
  });
1228
1325
  }
1229
1326
  //#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.9.1";
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-_1XJg3dh.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.9.1",
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",