code-ollama 0.3.1 → 0.5.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.
package/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  > [!NOTE]
2
2
  > TUI is under active development. APIs may change.
3
3
 
4
+ <p align="center">
5
+ <img alt="Ollama" height="200" src="https://github.com/ai-action/assets/blob/master/logos/ollama.svg?raw=true">
6
+ </p>
7
+
4
8
  # Code Ollama
5
9
 
6
10
  [![NPM](https://nodei.co/npm/code-ollama.svg)](https://www.npmjs.com/package/code-ollama)
@@ -1,9 +1,37 @@
1
- import { _ as REJECT, a as listModels, c as saveConfig, d as ROLE, f as PLAN_GENERATION_INSTRUCTION, g as APPROVE, h as NAME, i as executeTool, l as createSystemMessage, m as LABEL, n as READ_ONLY_TOOLS, o as streamChat, p as VERSION, r as TOOLS, s as loadConfig, t as DANGEROUS_TOOLS, u as HEADER_PREFIX, v as NAMES } from "../cli.js";
1
+ import { a as setClearHandler, c as loadConfig, d as withSystemMessage, f as ROLE, i as executeTool, l as saveConfig, m as VERSION, n as READ_ONLY_TOOLS, o as listModels, p as PLAN_GENERATION_INSTRUCTION, r as TOOLS, s as streamChat, t as DANGEROUS_TOOLS, u as resetSystemMessage } from "../cli.js";
2
2
  import { homedir } from "node:os";
3
3
  import { Box, Text, render, useInput } from "ink";
4
- import { useCallback, useEffect, useState } from "react";
4
+ import { memo, useCallback, useEffect, useMemo, useState } from "react";
5
5
  import { Select, Spinner, TextInput } from "@inkjs/ui";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
7
+ //#region src/constants/command.ts
8
+ var LIST = [{
9
+ name: "/clear",
10
+ description: "clear the current session"
11
+ }, {
12
+ name: "/model",
13
+ description: "switch the model"
14
+ }];
15
+ //#endregion
16
+ //#region src/constants/decision.ts
17
+ var APPROVE = "approve";
18
+ var REJECT = "reject";
19
+ //#endregion
20
+ //#region src/constants/mode.ts
21
+ var NAME = {
22
+ SAFE: "safe",
23
+ AUTO: "auto",
24
+ PLAN: "plan"
25
+ };
26
+ var LABEL = {
27
+ safe: "Safe",
28
+ auto: "Auto",
29
+ plan: "Plan"
30
+ };
31
+ //#endregion
32
+ //#region src/constants/ui.ts
33
+ var HEADER_PREFIX = "🦙";
34
+ //#endregion
7
35
  //#region src/components/Messages.tsx
8
36
  function getMessageColor(role) {
9
37
  switch (role) {
@@ -13,21 +41,28 @@ function getMessageColor(role) {
13
41
  default: return;
14
42
  }
15
43
  }
16
- function Messages({ messages, isLoading }) {
44
+ var MessageRow = memo(function MessageRow({ message }) {
45
+ return /* @__PURE__ */ jsx(Box, {
46
+ marginBottom: 1,
47
+ children: /* @__PURE__ */ jsxs(Text, {
48
+ color: getMessageColor(message.role),
49
+ dimColor: message.role === ROLE.SYSTEM,
50
+ children: [message.role === ROLE.USER ? "> " : "", message.content]
51
+ })
52
+ });
53
+ });
54
+ function Messages({ messages, isLoading, streamingMessage }) {
17
55
  return /* @__PURE__ */ jsxs(Box, {
18
56
  flexDirection: "column",
19
- children: [messages.map((message, index) => /* @__PURE__ */ jsx(Box, {
20
- marginBottom: 1,
21
- children: /* @__PURE__ */ jsxs(Text, {
22
- color: getMessageColor(message.role),
23
- dimColor: message.role === ROLE.SYSTEM,
24
- children: [message.role === ROLE.USER ? "> " : "", message.content]
57
+ children: [
58
+ messages.map((message, index) => /* @__PURE__ */ jsx(MessageRow, { message }, `${String(index)}-${message.role}-${message.content.slice(0, 16)}`)),
59
+ streamingMessage && /* @__PURE__ */ jsx(MessageRow, { message: streamingMessage }),
60
+ isLoading && !streamingMessage?.content && /* @__PURE__ */ jsx(Box, {
61
+ marginTop: -1,
62
+ marginBottom: 1,
63
+ children: /* @__PURE__ */ jsx(Spinner, { label: "Thinking..." })
25
64
  })
26
- }, index)), isLoading && messages[messages.length - 1]?.content === "" && /* @__PURE__ */ jsx(Box, {
27
- marginTop: -1,
28
- marginBottom: 1,
29
- children: /* @__PURE__ */ jsx(Spinner, { label: "Thinking..." })
30
- })]
65
+ ]
31
66
  });
32
67
  }
33
68
  //#endregion
@@ -146,20 +181,56 @@ var ACTION_NOT_PERFORMED = "The requested action was NOT performed";
146
181
  var PLAN_CHECKLIST_REMINDER = "Then display the execution plan as an unchecked Markdown checklist only";
147
182
  var PLAN_EXECUTION_REMINDER = "Do not claim success and do not call write_file or run_shell until the user approves execution";
148
183
  //#endregion
184
+ //#region src/components/Chat/CommandMenu.tsx
185
+ function getMatchingCommands(input) {
186
+ const normalizedInput = input.trim().toLowerCase();
187
+ if (!normalizedInput.startsWith("/")) return [];
188
+ return LIST.filter(({ name }) => name.toLowerCase().startsWith(normalizedInput)).map(({ name, description }) => ({
189
+ label: `${name} - ${description}`,
190
+ value: name
191
+ }));
192
+ }
193
+ function CommandMenu({ input, onSubmit }) {
194
+ const commandOptions = useMemo(() => getMatchingCommands(input), [input]);
195
+ if (!commandOptions.length) return null;
196
+ return /* @__PURE__ */ jsx(SelectPrompt, {
197
+ highlightText: input,
198
+ onChange: onSubmit,
199
+ options: commandOptions
200
+ });
201
+ }
202
+ //#endregion
149
203
  //#region src/components/Chat/Input.tsx
150
204
  function Input({ isDisabled = false, onSubmit }) {
205
+ const [input, setInput] = useState("");
151
206
  const [resetKey, setResetKey] = useState(0);
152
- const handleSubmit = useCallback((input) => {
153
- const trimmed = input.trim();
154
- if (!trimmed) return;
155
- onSubmit(trimmed);
207
+ const handleSubmitText = useCallback((input) => {
208
+ setTimeout(() => {
209
+ if (input.startsWith("/")) return;
210
+ const trimmedInput = input.trim();
211
+ if (!trimmedInput) return;
212
+ onSubmit(trimmedInput);
213
+ setInput("");
214
+ setResetKey((key) => key + 1);
215
+ });
216
+ }, [onSubmit]);
217
+ const handleSubmitCommand = useCallback((input) => {
218
+ if (!LIST.find(({ name }) => name === input)) return;
219
+ onSubmit(input);
220
+ setInput("");
156
221
  setResetKey((key) => key + 1);
157
222
  }, [onSubmit]);
158
- return /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, { children: "> " }), /* @__PURE__ */ jsx(TextInput, {
159
- isDisabled,
160
- suggestions: NAMES,
161
- onSubmit: handleSubmit
162
- }, resetKey)] });
223
+ return /* @__PURE__ */ jsxs(Box, {
224
+ flexDirection: "column",
225
+ children: [/* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, { children: "> " }), /* @__PURE__ */ jsx(TextInput, {
226
+ isDisabled,
227
+ onChange: setInput,
228
+ onSubmit: handleSubmitText
229
+ }, resetKey)] }), input.startsWith("/") && /* @__PURE__ */ jsx(CommandMenu, {
230
+ input,
231
+ onSubmit: handleSubmitCommand
232
+ })]
233
+ });
163
234
  }
164
235
  //#endregion
165
236
  //#region src/components/Chat/plan.ts
@@ -171,11 +242,19 @@ function hasExecutablePlan(content) {
171
242
  }
172
243
  //#endregion
173
244
  //#region src/components/Chat/Chat.tsx
174
- function Chat({ model, onCommand, mode, onModeChange }) {
175
- const [messages, setMessages] = useState([createSystemMessage()]);
245
+ function Chat({ model, onCommand, mode, onModeChange, sessionId }) {
246
+ const [messages, setMessages] = useState([]);
247
+ const [streamingMessage, setStreamingMessage] = useState(null);
176
248
  const [isLoading, setIsLoading] = useState(false);
177
249
  const [pendingToolCall, setPendingToolCall] = useState(null);
178
250
  const [pendingPlan, setPendingPlan] = useState(null);
251
+ useEffect(() => {
252
+ setMessages([]);
253
+ setStreamingMessage(null);
254
+ setIsLoading(false);
255
+ setPendingToolCall(null);
256
+ setPendingPlan(null);
257
+ }, [sessionId]);
179
258
  const buildToolResultMessage = useCallback((toolName, result) => {
180
259
  if (result.error?.startsWith("Tool not allowed:")) return {
181
260
  role: ROLE.SYSTEM,
@@ -206,20 +285,38 @@ function Chat({ model, onCommand, mode, onModeChange }) {
206
285
  role: ROLE.ASSISTANT,
207
286
  content: ""
208
287
  };
209
- setMessages((previousMessages) => [...previousMessages, assistantMessage]);
288
+ let committedMessages = currentMessages;
289
+ let assistantCommitted = false;
290
+ const commitAssistantMessage = () => {
291
+ if (assistantCommitted) {
292
+ // v8 ignore next
293
+ if (committedMessages.at(-1)?.role === ROLE.ASSISTANT) {
294
+ committedMessages = [...committedMessages.slice(0, -1), { ...assistantMessage }];
295
+ setMessages(committedMessages);
296
+ }
297
+ return committedMessages;
298
+ }
299
+ assistantCommitted = true;
300
+ setStreamingMessage(null);
301
+ if (!assistantMessage.content) {
302
+ setMessages(committedMessages);
303
+ return committedMessages;
304
+ }
305
+ committedMessages = [...committedMessages, { ...assistantMessage }];
306
+ setMessages(committedMessages);
307
+ return committedMessages;
308
+ };
309
+ setStreamingMessage(assistantMessage);
210
310
  try {
211
- for await (const chunk of streamChat(currentMessages, model, TOOLS)) if (chunk.type === "content") {
311
+ for await (const chunk of streamChat(withSystemMessage(currentMessages), model, TOOLS)) if (chunk.type === "content") {
212
312
  assistantMessage.content += chunk.content;
213
- setMessages((previousMessages) => {
214
- const newMessages = [...previousMessages];
215
- newMessages[newMessages.length - 1] = { ...assistantMessage };
216
- return newMessages;
217
- });
313
+ setStreamingMessage({ ...assistantMessage });
218
314
  } else if (chunk.type === "tool_calls") for (const toolCall of chunk.tool_calls) {
219
315
  const requiresApproval = DANGEROUS_TOOLS.has(toolCall.function.name);
220
316
  // v8 ignore start
221
317
  const allowedTools = executionMode === NAME.PLAN ? READ_ONLY_TOOLS : void 0;
222
318
  // v8 ignore stop
319
+ const updatedMessages = commitAssistantMessage();
223
320
  if (executionMode === NAME.SAFE && requiresApproval) {
224
321
  setPendingToolCall(toolCall);
225
322
  setIsLoading(false);
@@ -227,22 +324,15 @@ function Chat({ model, onCommand, mode, onModeChange }) {
227
324
  }
228
325
  const result = await executeTool(toolCall.function.name, toolCall.function.arguments, { allowedTools });
229
326
  const toolResultMessage = buildToolResultMessage(toolCall.function.name, result);
230
- const newMessages = [
231
- ...currentMessages,
232
- assistantMessage,
233
- toolResultMessage
234
- ];
235
- setMessages((previousMessages) => [...previousMessages, toolResultMessage]);
327
+ const newMessages = [...updatedMessages, toolResultMessage];
328
+ setMessages(newMessages);
236
329
  await processStream(newMessages, executionMode);
237
330
  return;
238
331
  }
332
+ commitAssistantMessage();
239
333
  } catch (error) {
240
334
  assistantMessage.content = `Error: ${error instanceof Error ? error.message : String(error)}`;
241
- setMessages((previousMessages) => {
242
- const newMessages = [...previousMessages];
243
- newMessages[newMessages.length - 1] = { ...assistantMessage };
244
- return newMessages;
245
- });
335
+ commitAssistantMessage();
246
336
  } finally {
247
337
  setIsLoading(false);
248
338
  }
@@ -256,73 +346,83 @@ function Chat({ model, onCommand, mode, onModeChange }) {
256
346
  role: ROLE.ASSISTANT,
257
347
  content: ""
258
348
  };
259
- setMessages((previousMessages) => [...previousMessages, assistantMessage]);
349
+ let committedMessages = currentMessages;
350
+ let assistantCommitted = false;
351
+ const commitAssistantMessage = () => {
352
+ if (assistantCommitted) {
353
+ // v8 ignore next
354
+ if (committedMessages.at(-1)?.role === ROLE.ASSISTANT) {
355
+ committedMessages = [...committedMessages.slice(0, -1), { ...assistantMessage }];
356
+ setMessages(committedMessages);
357
+ }
358
+ return committedMessages;
359
+ }
360
+ assistantCommitted = true;
361
+ setStreamingMessage(null);
362
+ if (!assistantMessage.content) {
363
+ setMessages(committedMessages);
364
+ return committedMessages;
365
+ }
366
+ committedMessages = [...committedMessages, { ...assistantMessage }];
367
+ setMessages(committedMessages);
368
+ return committedMessages;
369
+ };
370
+ setStreamingMessage(assistantMessage);
260
371
  try {
261
372
  const readOnlyTools = TOOLS.filter((tool) => READ_ONLY_TOOLS.has(tool.function.name));
262
- for await (const chunk of streamChat(currentMessages, model, readOnlyTools)) if (chunk.type === "content") {
373
+ for await (const chunk of streamChat(withSystemMessage(currentMessages), model, readOnlyTools)) if (chunk.type === "content") {
263
374
  assistantMessage.content += chunk.content;
264
- setMessages((previousMessages) => {
265
- const newMessages = [...previousMessages];
266
- newMessages[newMessages.length - 1] = { ...assistantMessage };
267
- return newMessages;
268
- });
375
+ setStreamingMessage({ ...assistantMessage });
269
376
  } else if (chunk.type === "tool_calls") for (const toolCall of chunk.tool_calls) {
377
+ const updatedMessages = commitAssistantMessage();
270
378
  if (!READ_ONLY_TOOLS.has(toolCall.function.name)) {
271
379
  const correctionMessage = buildPlanModeCorrectionMessage(toolCall.function.name);
272
- const newMessages = [
273
- ...currentMessages,
274
- assistantMessage,
275
- correctionMessage
276
- ];
277
- setMessages((previousMessages) => [...previousMessages, correctionMessage]);
380
+ const newMessages = [...updatedMessages, correctionMessage];
381
+ setMessages(newMessages);
278
382
  await processStreamReadOnly(newMessages);
279
383
  return;
280
384
  }
281
385
  const result = await executeTool(toolCall.function.name, toolCall.function.arguments, { allowedTools: READ_ONLY_TOOLS });
282
386
  const toolResultMessage = buildToolResultMessage(toolCall.function.name, result);
283
- const newMessages = [
284
- ...currentMessages,
285
- assistantMessage,
286
- toolResultMessage
287
- ];
288
- setMessages((previousMessages) => [...previousMessages, toolResultMessage]);
387
+ const newMessages = [...updatedMessages, toolResultMessage];
388
+ setMessages(newMessages);
289
389
  await processStreamReadOnly(newMessages);
290
390
  return;
291
391
  }
392
+ const researchMessages = commitAssistantMessage();
292
393
  const planInstruction = {
293
394
  role: ROLE.SYSTEM,
294
395
  content: PLAN_GENERATION_INSTRUCTION
295
396
  };
296
- const planMessages = [
297
- ...currentMessages,
298
- assistantMessage,
299
- planInstruction
300
- ];
397
+ const planMessages = [...researchMessages, planInstruction];
301
398
  const planAssistantMessage = {
302
399
  role: ROLE.ASSISTANT,
303
400
  content: ""
304
401
  };
305
- setMessages((previousMessages) => [...previousMessages, planAssistantMessage]);
306
- for await (const chunk of streamChat(planMessages, model, [])) if (chunk.type === "content") {
307
- planAssistantMessage.content += chunk.content;
308
- setMessages((previousMessages) => {
309
- const newMessages = [...previousMessages];
310
- newMessages[newMessages.length - 1] = { ...planAssistantMessage };
311
- return newMessages;
312
- });
402
+ setStreamingMessage(planAssistantMessage);
403
+ try {
404
+ for await (const chunk of streamChat(withSystemMessage(planMessages), model, [])) if (chunk.type === "content") {
405
+ planAssistantMessage.content += chunk.content;
406
+ setStreamingMessage({ ...planAssistantMessage });
407
+ }
408
+ } catch (error) {
409
+ planAssistantMessage.content = `Error: ${error instanceof Error ? error.message : String(error)}`;
410
+ setMessages([...planMessages, { ...planAssistantMessage }]);
411
+ setStreamingMessage(null);
412
+ setIsLoading(false);
413
+ return;
313
414
  }
415
+ const finalPlanMessages = [...planMessages, { ...planAssistantMessage }];
416
+ setMessages(finalPlanMessages);
417
+ setStreamingMessage(null);
314
418
  if (hasExecutablePlan(planAssistantMessage.content)) setPendingPlan({
315
419
  planContent: planAssistantMessage.content,
316
- messages: [...planMessages, planAssistantMessage]
420
+ messages: finalPlanMessages
317
421
  });
318
422
  setIsLoading(false);
319
423
  } catch (error) {
320
424
  assistantMessage.content = `Error: ${error instanceof Error ? error.message : String(error)}`;
321
- setMessages((previousMessages) => {
322
- const newMessages = [...previousMessages];
323
- newMessages[newMessages.length - 1] = { ...assistantMessage };
324
- return newMessages;
325
- });
425
+ commitAssistantMessage();
326
426
  } finally {
327
427
  setIsLoading(false);
328
428
  }
@@ -404,8 +504,8 @@ function Chat({ model, onCommand, mode, onModeChange }) {
404
504
  role: ROLE.USER,
405
505
  content: userContent
406
506
  };
407
- setMessages((previousMessages) => [...previousMessages, userMessage]);
408
507
  const updatedMessages = [...messages, userMessage];
508
+ setMessages(updatedMessages);
409
509
  if (mode === NAME.PLAN) await processStreamReadOnly(updatedMessages);
410
510
  else await processStream(updatedMessages);
411
511
  }, [
@@ -419,8 +519,9 @@ function Chat({ model, onCommand, mode, onModeChange }) {
419
519
  flexDirection: "column",
420
520
  children: [
421
521
  /* @__PURE__ */ jsx(Messages, {
422
- messages: messages.slice(1),
423
- isLoading
522
+ messages,
523
+ isLoading,
524
+ streamingMessage
424
525
  }),
425
526
  pendingPlan && /* @__PURE__ */ jsx(PlanApproval, {
426
527
  planContent: pendingPlan.planContent,
@@ -566,8 +667,18 @@ function App() {
566
667
  const [model, setModel] = useState(() => loadConfig().model);
567
668
  const [picking, setPicking] = useState(false);
568
669
  const [mode, setMode] = useState(NAME.SAFE);
670
+ const [sessionId, setSessionId] = useState(0);
569
671
  const handleCommand = useCallback((command) => {
570
- if (command === "/model") setPicking(true);
672
+ switch (command) {
673
+ case "/model":
674
+ setPicking(true);
675
+ break;
676
+ case "/clear":
677
+ resetSystemMessage();
678
+ setPicking(false);
679
+ setSessionId((sessionId) => sessionId + 1);
680
+ break;
681
+ }
571
682
  }, []);
572
683
  const handleSelect = useCallback((selected) => {
573
684
  setModel(selected);
@@ -577,32 +688,43 @@ function App() {
577
688
  const handleClose = useCallback(() => {
578
689
  setPicking(false);
579
690
  }, []);
580
- return /* @__PURE__ */ jsxs(Box, {
581
- flexDirection: "column",
582
- children: [
583
- /* @__PURE__ */ jsx(Header, { model }),
584
- picking ? /* @__PURE__ */ jsx(ModelPicker, {
691
+ const handleToggleMode = useCallback(() => {
692
+ setMode((mode) => {
693
+ switch (mode) {
694
+ case NAME.SAFE: return NAME.AUTO;
695
+ case NAME.AUTO: return NAME.PLAN;
696
+ case NAME.PLAN:
697
+ default: return NAME.SAFE;
698
+ }
699
+ });
700
+ }, []);
701
+ let body;
702
+ switch (true) {
703
+ case picking:
704
+ body = /* @__PURE__ */ jsx(ModelPicker, {
585
705
  currentModel: model,
586
706
  onSelect: handleSelect,
587
707
  onClose: handleClose
588
- }) : /* @__PURE__ */ jsx(Chat, {
708
+ });
709
+ break;
710
+ default:
711
+ body = /* @__PURE__ */ jsx(Chat, {
589
712
  model,
590
713
  onCommand: handleCommand,
591
714
  mode,
592
- onModeChange: setMode
593
- }),
715
+ onModeChange: setMode,
716
+ sessionId
717
+ });
718
+ break;
719
+ }
720
+ return /* @__PURE__ */ jsxs(Box, {
721
+ flexDirection: "column",
722
+ children: [
723
+ /* @__PURE__ */ jsx(Header, { model }),
724
+ body,
594
725
  /* @__PURE__ */ jsx(Footer, {
595
726
  mode,
596
- onToggleMode: () => {
597
- setMode((mode) => {
598
- switch (mode) {
599
- case NAME.SAFE: return NAME.AUTO;
600
- case NAME.AUTO: return NAME.PLAN;
601
- case NAME.PLAN:
602
- default: return NAME.SAFE;
603
- }
604
- });
605
- }
727
+ onToggleMode: handleToggleMode
606
728
  })
607
729
  ]
608
730
  });
@@ -610,7 +732,15 @@ function App() {
610
732
  //#endregion
611
733
  //#region src/tui.tsx
612
734
  function renderApp() {
613
- render(/* @__PURE__ */ jsx(App, {}));
735
+ const tree = /* @__PURE__ */ jsx(App, {});
736
+ const app = render(tree, {
737
+ incrementalRendering: true,
738
+ maxFps: 60
739
+ });
740
+ setClearHandler(() => {
741
+ app.clear();
742
+ app.rerender(tree);
743
+ });
614
744
  }
615
745
  //#endregion
616
746
  export { renderApp };
package/dist/cli.js CHANGED
@@ -6,29 +6,9 @@ import { homedir } from "node:os";
6
6
  import { Ollama } from "ollama";
7
7
  import { exec } from "node:child_process";
8
8
  import { promisify } from "node:util";
9
- var NAMES = [{
10
- name: "/model",
11
- description: "switch the model"
12
- }].map(({ name }) => name);
13
- //#endregion
14
- //#region src/constants/decision.ts
15
- var APPROVE = "approve";
16
- var REJECT = "reject";
17
- //#endregion
18
- //#region src/constants/mode.ts
19
- var NAME$1 = {
20
- SAFE: "safe",
21
- AUTO: "auto",
22
- PLAN: "plan"
23
- };
24
- var LABEL = {
25
- safe: "Safe",
26
- auto: "Auto",
27
- plan: "Plan"
28
- };
29
9
  //#endregion
30
10
  //#region src/constants/package.ts
31
- var VERSION = "0.3.1";
11
+ var VERSION = "0.5.0";
32
12
  //#endregion
33
13
  //#region src/constants/prompt.ts
34
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
@@ -87,9 +67,6 @@ var NAME = {
87
67
  VIEW_RANGE: "view_range"
88
68
  };
89
69
  //#endregion
90
- //#region src/constants/ui.ts
91
- var HEADER_PREFIX = "🦙";
92
- //#endregion
93
70
  //#region src/utils/agents.ts
94
71
  var AGENTS_FILE = "AGENTS.md";
95
72
  function loadAgentsContent() {
@@ -108,12 +85,20 @@ function buildSystemPrompt() {
108
85
  parts.push("\n\n", TOOL_INSTRUCTIONS);
109
86
  return parts.join("");
110
87
  }
88
+ var systemMessage = null;
111
89
  function createSystemMessage() {
112
90
  return {
113
91
  role: ROLE.SYSTEM,
114
92
  content: buildSystemPrompt()
115
93
  };
116
94
  }
95
+ function resetSystemMessage() {
96
+ systemMessage = null;
97
+ }
98
+ function withSystemMessage(messages) {
99
+ systemMessage ??= createSystemMessage();
100
+ return [systemMessage, ...messages];
101
+ }
117
102
  //#endregion
118
103
  //#region src/utils/config.ts
119
104
  var CONFIG_DIR = join(homedir(), ".code-ollama");
@@ -171,12 +156,7 @@ async function listModels() {
171
156
  const { models } = await client.list();
172
157
  return models.map(({ name }) => name);
173
158
  }
174
- //#endregion
175
- //#region src/utils/screen.ts
176
- var CLEAR = "\x1Bc";
177
- function clear() {
178
- process.stdout.write(CLEAR);
179
- }
159
+ function setClearHandler(handler) {}
180
160
  //#endregion
181
161
  //#region src/utils/tools.ts
182
162
  var execAsync = promisify(exec);
@@ -519,8 +499,8 @@ async function processRunStream(messages, model) {
519
499
  }
520
500
  async function main(args = process.argv.slice(2)) {
521
501
  if (!args.length) {
522
- const { renderApp } = await import("./assets/tui-CVsodXv3.js");
523
- clear();
502
+ const { renderApp } = await import("./assets/tui-Da6uWrqo.js");
503
+ process.stdout.write("\x1Bc");
524
504
  renderApp();
525
505
  return;
526
506
  }
@@ -530,7 +510,7 @@ async function main(args = process.argv.slice(2)) {
530
510
  ...args
531
511
  ]);
532
512
  }
533
- /* v8 ignore start */
513
+ // v8 ignore start
534
514
  function isEntrypoint(argv1 = process.argv[1]) {
535
515
  if (!argv1) return false;
536
516
  try {
@@ -540,6 +520,6 @@ function isEntrypoint(argv1 = process.argv[1]) {
540
520
  }
541
521
  }
542
522
  if (isEntrypoint()) main();
543
- /* v8 ignore stop */
523
+ // v8 ignore stop
544
524
  //#endregion
545
- export { REJECT as _, listModels as a, saveConfig as c, ROLE as d, PLAN_GENERATION_INSTRUCTION as f, APPROVE as g, NAME$1 as h, executeTool as i, createSystemMessage as l, LABEL as m, main, READ_ONLY_TOOLS as n, streamChat as o, VERSION as p, TOOLS as r, loadConfig as s, DANGEROUS_TOOLS as t, HEADER_PREFIX as u, NAMES as v };
525
+ export { setClearHandler as a, loadConfig as c, withSystemMessage as d, ROLE as f, executeTool as i, saveConfig as l, VERSION as m, main, READ_ONLY_TOOLS as n, listModels as o, PLAN_GENERATION_INSTRUCTION as p, TOOLS as r, streamChat as s, DANGEROUS_TOOLS as t, resetSystemMessage as u };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-ollama",
3
- "version": "0.3.1",
3
+ "version": "0.5.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",
@@ -50,7 +50,7 @@
50
50
  "@commitlint/config-conventional": "20.5.3",
51
51
  "@eslint/compat": "2.0.5",
52
52
  "@eslint/js": "10.0.1",
53
- "@types/node": "25.6.0",
53
+ "@types/node": "25.6.1",
54
54
  "@types/react": "19.2.14",
55
55
  "@vitest/coverage-v8": "4.1.5",
56
56
  "eslint": "10.3.0",
@@ -65,7 +65,7 @@
65
65
  "tsx": "4.21.0",
66
66
  "typescript": "6.0.3",
67
67
  "typescript-eslint": "8.59.2",
68
- "vite": "8.0.10",
68
+ "vite": "8.0.11",
69
69
  "vitest": "4.1.5"
70
70
  },
71
71
  "files": [