code-ollama 0.8.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,12 +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 { Select, Spinner, TextInput } from "@inkjs/ui";
9
- import { jsx, jsxs } from "react/jsx-runtime";
8
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
+ import { Select, Spinner } from "@inkjs/ui";
10
+ import { marked } from "marked";
11
+ import { markedTerminal } from "marked-terminal";
10
12
  //#region src/constants/command.ts
11
13
  var LIST = [
12
14
  {
@@ -42,6 +44,97 @@ var LABEL = {
42
44
  //#region src/constants/ui.ts
43
45
  var HEADER_PREFIX = "🦙 ";
44
46
  //#endregion
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
+ }
70
+ async function highlightCode(code, language = "text") {
71
+ const { codeToANSI } = await import("@shikijs/cli");
72
+ try {
73
+ return await codeToANSI(code, language, "github-light");
74
+ } catch {
75
+ // v8 ignore next
76
+ return code;
77
+ }
78
+ }
79
+ var CodeBlock = memo(function CodeBlock({ code, language, role }) {
80
+ const cacheKey = `${language ?? ""}:${code}`;
81
+ const [highlighted, setHighlighted] = useState(() => highlightCache.get(cacheKey) ?? code);
82
+ useEffect(() => {
83
+ let canceled = false;
84
+ async function loadHighlight() {
85
+ try {
86
+ const result = await highlightCode(code, language);
87
+ highlightCache.set(cacheKey, result);
88
+ if (!canceled) setHighlighted(result);
89
+ } catch {}
90
+ }
91
+ loadHighlight();
92
+ return () => {
93
+ canceled = true;
94
+ };
95
+ }, [
96
+ cacheKey,
97
+ code,
98
+ language
99
+ ]);
100
+ const isSystem = role === ROLE.SYSTEM;
101
+ return /* @__PURE__ */ jsx(Box, {
102
+ flexDirection: "column",
103
+ borderStyle: "round",
104
+ borderColor: isSystem ? "gray" : "dim",
105
+ paddingX: 1,
106
+ marginY: 1,
107
+ children: /* @__PURE__ */ jsx(Text, {
108
+ dimColor: isSystem,
109
+ children: highlighted
110
+ })
111
+ });
112
+ });
113
+ //#endregion
114
+ //#region src/components/Markdown/Markdown.tsx
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
127
+ }
128
+ var Markdown = memo(function Markdown({ content, color, dimColor }) {
129
+ const { stdout } = useStdout();
130
+ const availableWidth = stdout.columns - 4;
131
+ return /* @__PURE__ */ jsx(Text, {
132
+ color,
133
+ dimColor,
134
+ children: useMemo(() => renderMarkdown(content, availableWidth), [content, availableWidth])
135
+ });
136
+ });
137
+ //#endregion
45
138
  //#region src/components/Messages/constants.ts
46
139
  var TURN_ABORTED_MESSAGE = [
47
140
  "<turn_aborted>",
@@ -53,30 +146,106 @@ var TURN_ABORTED_MESSAGE = [
53
146
  function getMessageColor(role) {
54
147
  switch (role) {
55
148
  case ROLE.USER: return "black";
56
- case ROLE.ASSISTANT: return "blue";
149
+ case ROLE.ASSISTANT: return "cyan";
57
150
  case ROLE.SYSTEM: return "gray";
58
151
  default: return;
59
152
  }
60
153
  }
154
+ function parseContent(content) {
155
+ const segments = [];
156
+ let lastIndex = 0;
157
+ let match;
158
+ CODE_BLOCK_REGEX.lastIndex = 0;
159
+ while ((match = CODE_BLOCK_REGEX.exec(content)) !== null) {
160
+ if (match.index > lastIndex) {
161
+ const textContent = content.slice(lastIndex, match.index).trim();
162
+ // v8 ignore next 2 - Defensive check for empty trimmed content
163
+ if (textContent) segments.push({
164
+ type: "text",
165
+ content: textContent
166
+ });
167
+ }
168
+ const language = match[2];
169
+ const codeContent = match[3].trim();
170
+ // v8 ignore next 2 - Defensive check for empty code block
171
+ if (codeContent) segments.push({
172
+ type: "code",
173
+ content: codeContent,
174
+ language
175
+ });
176
+ lastIndex = match.index + match[0].length;
177
+ }
178
+ if (lastIndex < content.length) {
179
+ const textContent = content.slice(lastIndex).trim();
180
+ // v8 ignore next 2 - Defensive check for empty trimmed content
181
+ if (textContent) segments.push({
182
+ type: "text",
183
+ content: textContent
184
+ });
185
+ }
186
+ // v8 ignore next 2 - Defensive fallback for edge case
187
+ if (!segments.length && content.trim()) segments.push({
188
+ type: "text",
189
+ content: content.trim()
190
+ });
191
+ return segments;
192
+ }
61
193
  var Message = memo(function Message({ message }) {
194
+ const messageColor = getMessageColor(message.role);
195
+ const isSystem = message.role === ROLE.SYSTEM;
196
+ const isUser = message.role === ROLE.USER;
197
+ if (isSystem) return /* @__PURE__ */ jsx(Box, {
198
+ flexDirection: "column",
199
+ marginBottom: 1,
200
+ marginX: 2,
201
+ children: /* @__PURE__ */ jsx(Text, {
202
+ color: messageColor,
203
+ dimColor: true,
204
+ children: message.content
205
+ })
206
+ });
62
207
  return /* @__PURE__ */ jsx(Box, {
208
+ flexDirection: "column",
63
209
  marginBottom: 1,
64
- children: /* @__PURE__ */ jsxs(Text, {
65
- color: getMessageColor(message.role),
66
- dimColor: message.role === ROLE.SYSTEM,
67
- children: [message.role === ROLE.USER && "> ", message.content]
210
+ children: parseContent(message.content).map((segment, index) => {
211
+ const prefix = isUser && index === 0 ? "> " : "";
212
+ if (segment.type === "code") return isUser ? /* @__PURE__ */ jsx(Text, {
213
+ color: messageColor,
214
+ children: segment.content
215
+ }, index) : /* @__PURE__ */ jsx(Box, {
216
+ marginX: 2,
217
+ children: /* @__PURE__ */ jsx(CodeBlock, {
218
+ code: segment.content,
219
+ language: segment.language,
220
+ role: message.role
221
+ })
222
+ }, index);
223
+ return isUser ? /* @__PURE__ */ jsx(Text, {
224
+ color: messageColor,
225
+ children: prefix + segment.content
226
+ }, index) : /* @__PURE__ */ jsx(Box, {
227
+ marginX: 2,
228
+ children: /* @__PURE__ */ jsx(Markdown, {
229
+ content: segment.content,
230
+ color: messageColor
231
+ })
232
+ }, index);
68
233
  })
69
234
  });
70
235
  });
71
- function Messages({ messages, isLoading, streamingMessage }) {
236
+ function Messages({ messages, isLoading, sessionId = 0, streamingMessage }) {
72
237
  return /* @__PURE__ */ jsxs(Box, {
73
238
  flexDirection: "column",
74
239
  children: [
75
- 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),
76
244
  streamingMessage && /* @__PURE__ */ jsx(Message, { message: streamingMessage }),
77
245
  isLoading && !streamingMessage?.content && /* @__PURE__ */ jsx(Box, {
78
246
  marginTop: -1,
79
247
  marginBottom: 1,
248
+ marginX: 2,
80
249
  children: /* @__PURE__ */ jsx(Spinner, { label: "Thinking..." })
81
250
  })
82
251
  ]
@@ -239,6 +408,93 @@ var INTERRUPT_REASON = /* @__PURE__ */ function(INTERRUPT_REASON) {
239
408
  return INTERRUPT_REASON;
240
409
  }({});
241
410
  //#endregion
411
+ //#region src/components/TextInput/TextInput.tsx
412
+ function TextInput({ value, isDisabled = false, placeholder, cursorPosition: externalCursorPosition, onChange, onSubmit }) {
413
+ const [cursorPosition, setCursorPosition] = useState(value.length);
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]);
422
+ useEffect(() => {
423
+ const prevValue = prevValueRef.current;
424
+ prevValueRef.current = value;
425
+ if (value === "") setCursorPosition(0);
426
+ else if (value.length > prevValue.length + 1 && cursorPosition <= prevValue.length && externalCursorPosition === void 0) setCursorPosition(value.length);
427
+ else if (cursorPosition > value.length) setCursorPosition(value.length);
428
+ }, [
429
+ value,
430
+ cursorPosition,
431
+ externalCursorPosition
432
+ ]);
433
+ useInput((input, key) => {
434
+ // v8 ignore next
435
+ if (isDisabled) return;
436
+ if (key.return) {
437
+ onSubmit(value);
438
+ setCursorPosition(0);
439
+ return;
440
+ }
441
+ if (key.backspace) {
442
+ if (cursorPosition > 0) {
443
+ onChange(value.slice(0, cursorPosition - 1) + value.slice(cursorPosition));
444
+ setCursorPosition(cursorPosition - 1);
445
+ }
446
+ return;
447
+ }
448
+ // v8 ignore start
449
+ if (key.delete) {
450
+ if (cursorPosition < value.length) onChange(value.slice(0, cursorPosition) + value.slice(cursorPosition + 1));
451
+ return;
452
+ }
453
+ // v8 ignore stop
454
+ if (key.leftArrow) {
455
+ setCursorPosition(Math.max(0, cursorPosition - 1));
456
+ return;
457
+ }
458
+ if (key.rightArrow) {
459
+ setCursorPosition(Math.min(value.length, cursorPosition + 1));
460
+ return;
461
+ }
462
+ if (key.home) {
463
+ setCursorPosition(0);
464
+ return;
465
+ }
466
+ if (key.end) {
467
+ setCursorPosition(value.length);
468
+ return;
469
+ }
470
+ // v8 ignore start
471
+ if (input) {
472
+ onChange(value.slice(0, cursorPosition) + input + value.slice(cursorPosition));
473
+ setCursorPosition(cursorPosition + input.length);
474
+ }
475
+ // v8 ignore stop
476
+ }, { isActive: !isDisabled });
477
+ const displayValue = value || (placeholder ?? "");
478
+ const isPlaceholder = Boolean(!value && placeholder);
479
+ const cursorChar = displayValue[cursorPosition] || " ";
480
+ const before = displayValue.slice(0, cursorPosition);
481
+ const after = displayValue.slice(cursorPosition + 1);
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
+ ] });
496
+ }
497
+ //#endregion
242
498
  //#region src/components/Chat/CommandMenu.tsx
243
499
  function getMatchingCommands(input) {
244
500
  const normalizedInput = input.trim().toLowerCase();
@@ -260,7 +516,7 @@ function CommandMenu({ input, onSubmit }) {
260
516
  //#endregion
261
517
  //#region src/components/Chat/FileSuggestions.tsx
262
518
  var MAX_VISIBLE_OPTIONS = 5;
263
- var MENTION_PATTERN = /(^|\s)@(\S+)$/;
519
+ var MENTION_PATTERN = /(^|.)@(\S+)/;
264
520
  var RIPGREP_MAX_BUFFER = 10 * 1024 * 1024;
265
521
  function normalizePath(filePath) {
266
522
  return filePath.replaceAll("\\", "/");
@@ -276,8 +532,17 @@ function getMentionMatch(input) {
276
532
  function buildNextInput(input, filePath) {
277
533
  const mentionMatch = getMentionMatch(input);
278
534
  // v8 ignore next 3
279
- if (!mentionMatch) return input;
280
- 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
+ };
281
546
  }
282
547
  function listProjectFilesFallback(rootDir) {
283
548
  const filePaths = [];
@@ -348,7 +613,7 @@ function FileSuggestions({ input, isDisabled = false, onChange, onSelect }) {
348
613
  onChange(null);
349
614
  return;
350
615
  }
351
- onChange(buildNextInput(input, options[focusedIndex]));
616
+ onChange(buildNextInput(input, options[focusedIndex]).value);
352
617
  }, [
353
618
  focusedIndex,
354
619
  input,
@@ -386,31 +651,45 @@ function FileSuggestions({ input, isDisabled = false, onChange, onSelect }) {
386
651
  //#endregion
387
652
  //#region src/components/Chat/Input.tsx
388
653
  function hasFileSuggestionQuery(input) {
389
- return /(^|\s)@\S+$/.test(input);
654
+ return /(^|.)@\S+/.test(input);
390
655
  }
391
656
  function Input({ isDisabled = false, onInterrupt, onSubmit }) {
392
657
  const { exit } = useApp();
393
658
  const [input, setInput] = useState("");
394
- const [inputKey, setInputKey] = useState(0);
659
+ const [cursorPosition, setCursorPosition] = useState(void 0);
395
660
  const fileSuggestionRef = useRef(null);
396
- const remountTextInput = useCallback(() => {
397
- setInputKey((key) => key + 1);
398
- }, [setInputKey]);
661
+ const resetInput = useCallback(() => {
662
+ setInput("");
663
+ }, []);
399
664
  const handleSelectFileSuggestion = useCallback((nextInput) => {
400
- setInput(nextInput);
401
- remountTextInput();
402
- }, [remountTextInput]);
403
- const handleFileSuggestionChange = useCallback((nextInput) => {
404
- fileSuggestionRef.current = nextInput;
665
+ setInput(nextInput.value);
666
+ setCursorPosition(nextInput.cursorPosition);
405
667
  }, []);
668
+ const handleFileSuggestionChange = useCallback((nextInput) => {
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]);
406
686
  const submitAndReset = useCallback((input) => {
407
687
  const trimmedInput = input.trim();
408
688
  if (!trimmedInput) return;
409
689
  onSubmit(trimmedInput);
410
- setInput("");
690
+ resetInput();
411
691
  fileSuggestionRef.current = null;
412
- remountTextInput();
413
- }, [onSubmit, remountTextInput]);
692
+ }, [onSubmit, resetInput]);
414
693
  const showCommandMenu = input.startsWith("/");
415
694
  const showFileSuggestions = !showCommandMenu && hasFileSuggestionQuery(input);
416
695
  const handleSubmitText = useCallback(async (input) => {
@@ -433,8 +712,7 @@ function Input({ isDisabled = false, onInterrupt, onSubmit }) {
433
712
  }
434
713
  if (isCtrlC) {
435
714
  if (input) {
436
- setInput("");
437
- remountTextInput();
715
+ resetInput();
438
716
  return;
439
717
  }
440
718
  exit();
@@ -444,12 +722,13 @@ function Input({ isDisabled = false, onInterrupt, onSubmit }) {
444
722
  flexDirection: "column",
445
723
  children: [
446
724
  /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, { children: "> " }), /* @__PURE__ */ jsx(TextInput, {
447
- defaultValue: input,
725
+ value: input,
448
726
  isDisabled,
727
+ cursorPosition,
449
728
  onChange: setInput,
450
729
  onSubmit: handleSubmitText,
451
730
  placeholder: "Ask anything... (/ commands, @ files)"
452
- }, inputKey)] }),
731
+ })] }),
453
732
  showCommandMenu && /* @__PURE__ */ jsx(CommandMenu, {
454
733
  input,
455
734
  onSubmit: handleSubmitCommand
@@ -580,11 +859,13 @@ function Chat({ model, onCommand, mode, onModeChange, sessionId }) {
580
859
  return;
581
860
  }
582
861
  }
862
+ await prewarmCodeBlocks(assistantMessage.content);
583
863
  commitAssistantMessage();
584
864
  } catch (error) {
585
865
  // v8 ignore next
586
866
  if (!controller.signal.aborted) {
587
867
  assistantMessage.content = `Error: ${error instanceof Error ? error.message : String(error)}`;
868
+ await prewarmCodeBlocks(assistantMessage.content);
588
869
  commitAssistantMessage();
589
870
  }
590
871
  } finally {
@@ -650,6 +931,7 @@ function Chat({ model, onCommand, mode, onModeChange, sessionId }) {
650
931
  return;
651
932
  }
652
933
  }
934
+ await prewarmCodeBlocks(assistantMessage.content);
653
935
  const researchMessages = commitAssistantMessage();
654
936
  const planInstruction = {
655
937
  role: ROLE.SYSTEM,
@@ -690,6 +972,7 @@ function Chat({ model, onCommand, mode, onModeChange, sessionId }) {
690
972
  // v8 ignore next
691
973
  if (!controller.signal.aborted) {
692
974
  assistantMessage.content = `Error: ${error instanceof Error ? error.message : String(error)}`;
975
+ await prewarmCodeBlocks(assistantMessage.content);
693
976
  commitAssistantMessage();
694
977
  }
695
978
  } finally {
@@ -790,6 +1073,7 @@ function Chat({ model, onCommand, mode, onModeChange, sessionId }) {
790
1073
  /* @__PURE__ */ jsx(Messages, {
791
1074
  messages,
792
1075
  isLoading,
1076
+ sessionId,
793
1077
  streamingMessage
794
1078
  }),
795
1079
  pendingPlan && /* @__PURE__ */ jsx(PlanApproval, {
@@ -807,10 +1091,13 @@ function Chat({ model, onCommand, mode, onModeChange, sessionId }) {
807
1091
  children: interruptReason === INTERRUPT_REASON.REJECTED ? "❗ Tool call rejected." : "❗ Execution interrupted."
808
1092
  })
809
1093
  }),
810
- !pendingPlan && !pendingToolCall && /* @__PURE__ */ jsx(Input, {
811
- isDisabled: isLoading,
812
- onInterrupt: handleInterrupt,
813
- 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
+ })
814
1101
  })
815
1102
  ]
816
1103
  });
@@ -855,45 +1142,53 @@ function abbreviatePath(dir) {
855
1142
  const home = homedir();
856
1143
  return dir.startsWith(home) ? `~${dir.slice(home.length)}` : dir;
857
1144
  }
858
- function Header({ model }) {
1145
+ function Header({ model, onLoad }) {
859
1146
  const directory = abbreviatePath(process.cwd());
860
- return /* @__PURE__ */ jsxs(Box, {
861
- borderStyle: "round",
862
- flexDirection: "column",
863
- paddingX: 1,
864
- children: [
865
- /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsxs(Text, {
866
- bold: true,
867
- children: [HEADER_PREFIX, "Code Ollama"]
868
- }), /* @__PURE__ */ jsxs(Text, {
869
- dimColor: true,
870
- children: [
871
- " (v",
872
- VERSION,
873
- ")"
874
- ]
875
- })] }),
876
- /* @__PURE__ */ jsx(Text, { children: " " }),
877
- /* @__PURE__ */ jsxs(Box, { children: [
878
- /* @__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, {
879
1161
  dimColor: true,
880
- children: "model:".padEnd(11)
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
+ ]
881
1185
  }),
882
- /* @__PURE__ */ jsxs(Text, { children: [model, " "] }),
883
- /* @__PURE__ */ jsx(Text, {
884
- color: "cyan",
885
- children: "/model"
886
- }),
887
- /* @__PURE__ */ jsx(Text, {
1186
+ /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
888
1187
  dimColor: true,
889
- children: " to switch"
890
- })
891
- ] }),
892
- /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
893
- dimColor: true,
894
- children: "directory:".padEnd(11)
895
- }), /* @__PURE__ */ jsx(Text, { children: directory })] })
896
- ]
1188
+ children: "directory:".padEnd(11)
1189
+ }), /* @__PURE__ */ jsx(Text, { children: directory })] })
1190
+ ]
1191
+ }, key)
897
1192
  });
898
1193
  }
899
1194
  //#endregion
@@ -943,6 +1238,10 @@ function App() {
943
1238
  const [picking, setPicking] = useState(false);
944
1239
  const [mode, setMode] = useState(NAME.SAFE);
945
1240
  const [sessionId, setSessionId] = useState(0);
1241
+ const [isHeaderLoaded, setIsHeaderLoaded] = useState(false);
1242
+ const handleHeaderLoad = useCallback(() => {
1243
+ setIsHeaderLoaded(true);
1244
+ }, []);
946
1245
  const handleCommand = useCallback((command) => {
947
1246
  switch (command) {
948
1247
  case "/model":
@@ -950,6 +1249,7 @@ function App() {
950
1249
  break;
951
1250
  case "/clear":
952
1251
  resetSystemMessage();
1252
+ clear();
953
1253
  setPicking(false);
954
1254
  setSessionId((sessionId) => sessionId + 1);
955
1255
  break;
@@ -998,8 +1298,11 @@ function App() {
998
1298
  return /* @__PURE__ */ jsxs(Box, {
999
1299
  flexDirection: "column",
1000
1300
  children: [
1001
- /* @__PURE__ */ jsx(Header, { model }),
1002
- body,
1301
+ /* @__PURE__ */ jsx(Header, {
1302
+ model,
1303
+ onLoad: handleHeaderLoad
1304
+ }),
1305
+ isHeaderLoaded && body,
1003
1306
  /* @__PURE__ */ jsx(Footer, {
1004
1307
  mode,
1005
1308
  onToggleMode: handleToggleMode
@@ -1010,15 +1313,14 @@ function App() {
1010
1313
  //#endregion
1011
1314
  //#region src/tui.tsx
1012
1315
  function renderApp() {
1013
- const tree = /* @__PURE__ */ jsx(App, {});
1014
- const app = render(tree, {
1316
+ let resetKey = 0;
1317
+ const app = render(/* @__PURE__ */ jsx(App, {}, resetKey), {
1015
1318
  exitOnCtrlC: false,
1016
- incrementalRendering: true,
1017
1319
  maxFps: 60
1018
1320
  });
1019
1321
  setClearHandler(() => {
1020
- app.clear();
1021
- app.rerender(tree);
1322
+ reset();
1323
+ app.rerender(/* @__PURE__ */ jsx(App, {}, ++resetKey));
1022
1324
  });
1023
1325
  }
1024
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.8.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-BjeMfZFh.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.8.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",
@@ -40,8 +40,11 @@
40
40
  ],
41
41
  "dependencies": {
42
42
  "@inkjs/ui": "2.0.0",
43
+ "@shikijs/cli": "4.0.2",
43
44
  "cac": "7.0.0",
44
45
  "ink": "7.0.2",
46
+ "marked": "15.0.12",
47
+ "marked-terminal": "7.3.0",
45
48
  "ollama": "0.6.3",
46
49
  "react": "19.2.6"
47
50
  },