code-ollama 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
- import { _ 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 { _ as NAME, a as setClearHandler, b as NAMES, c as loadConfig, d as withSystemMessage, f as HEADER_PREFIX, g as LABEL, h as VERSION, i as executeTool, l as saveConfig, m as PLAN_GENERATION_INSTRUCTION, n as READ_ONLY_TOOLS, o as listModels, p as ROLE, r as TOOLS, s as streamChat, t as DANGEROUS_TOOLS, u as resetSystemMessage, v as APPROVE, y as REJECT } 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, useState } from "react";
5
5
  import { Select, Spinner, TextInput } from "@inkjs/ui";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
7
7
  //#region src/components/Messages.tsx
@@ -13,21 +13,39 @@ function getMessageColor(role) {
13
13
  default: return;
14
14
  }
15
15
  }
16
- function Messages({ messages, isLoading }) {
16
+ var MessageRow = memo(function MessageRow({ message }) {
17
+ return /* @__PURE__ */ jsx(Box, {
18
+ marginBottom: 1,
19
+ children: /* @__PURE__ */ jsxs(Text, {
20
+ color: getMessageColor(message.role),
21
+ dimColor: message.role === ROLE.SYSTEM,
22
+ children: [message.role === ROLE.USER ? "> " : "", message.content]
23
+ })
24
+ });
25
+ });
26
+ function Messages({ messages, isLoading, streamingMessage }) {
17
27
  return /* @__PURE__ */ jsxs(Box, {
18
28
  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]
29
+ children: [
30
+ messages.map((message, index) => /* @__PURE__ */ jsx(MessageRow, { message }, `${String(index)}-${message.role}-${message.content.slice(0, 16)}`)),
31
+ streamingMessage && /* @__PURE__ */ jsx(MessageRow, { message: streamingMessage }),
32
+ isLoading && !streamingMessage?.content && /* @__PURE__ */ jsx(Box, {
33
+ marginTop: -1,
34
+ marginBottom: 1,
35
+ children: /* @__PURE__ */ jsx(Spinner, { label: "Thinking..." })
25
36
  })
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
- })]
37
+ ]
38
+ });
39
+ }
40
+ //#endregion
41
+ //#region src/components/SelectPrompt.tsx
42
+ function SelectPrompt({ children, onEscape, ...selectProps }) {
43
+ useInput((_, key) => {
44
+ if (key.escape) onEscape?.();
45
+ });
46
+ return /* @__PURE__ */ jsxs(Box, {
47
+ flexDirection: "column",
48
+ children: [children, /* @__PURE__ */ jsx(Select, { ...selectProps })]
31
49
  });
32
50
  }
33
51
  //#endregion
@@ -47,34 +65,33 @@ var options$1 = [
47
65
  }
48
66
  ];
49
67
  function PlanApproval({ planContent, onModeChange }) {
50
- useInput((_, key) => {
51
- if (key.escape) onModeChange(NAME.PLAN);
52
- });
53
- const handleChange = useCallback((value) => {
54
- onModeChange(value);
55
- }, [onModeChange]);
56
- return /* @__PURE__ */ jsxs(Box, {
57
- flexDirection: "column",
58
- marginTop: 1,
59
- children: [
60
- /* @__PURE__ */ jsx(Text, {
61
- bold: true,
62
- color: "magenta",
63
- children: "Plan Generated - Choose execution mode:"
64
- }),
65
- /* @__PURE__ */ jsx(Box, {
66
- marginY: 1,
67
- children: /* @__PURE__ */ jsx(Text, { children: planContent })
68
- }),
69
- /* @__PURE__ */ jsx(Text, {
70
- dimColor: true,
71
- children: "Select execution mode (↑↓ + Enter to confirm, Esc to cancel)"
72
- }),
73
- /* @__PURE__ */ jsx(Select, {
74
- options: options$1,
75
- onChange: handleChange
76
- })
77
- ]
68
+ return /* @__PURE__ */ jsx(SelectPrompt, {
69
+ options: options$1,
70
+ onChange: useCallback((value) => {
71
+ onModeChange(value);
72
+ }, [onModeChange]),
73
+ onEscape: useCallback(() => {
74
+ onModeChange(NAME.PLAN);
75
+ }, [onModeChange]),
76
+ children: /* @__PURE__ */ jsxs(Box, {
77
+ flexDirection: "column",
78
+ marginTop: 1,
79
+ children: [
80
+ /* @__PURE__ */ jsx(Text, {
81
+ bold: true,
82
+ color: "magenta",
83
+ children: "Plan Generated - Choose execution mode:"
84
+ }),
85
+ /* @__PURE__ */ jsx(Box, {
86
+ marginY: 1,
87
+ children: /* @__PURE__ */ jsx(Text, { children: planContent })
88
+ }),
89
+ /* @__PURE__ */ jsx(Text, {
90
+ dimColor: true,
91
+ children: "Select execution mode (↑↓ + Enter to confirm, Esc to cancel)"
92
+ })
93
+ ]
94
+ })
78
95
  });
79
96
  }
80
97
  //#endregion
@@ -87,15 +104,17 @@ var options = [{
87
104
  value: REJECT
88
105
  }];
89
106
  function ToolApproval({ toolCall, onDecision }) {
90
- useInput((_, key) => {
91
- if (key.escape) onDecision(REJECT);
92
- });
93
107
  const handleChange = useCallback((value) => {
94
108
  onDecision(value);
95
109
  }, [onDecision]);
110
+ const handleEscape = useCallback(() => {
111
+ onDecision(REJECT);
112
+ }, [onDecision]);
96
113
  const args = JSON.stringify(toolCall.function.arguments, null, 2);
97
- return /* @__PURE__ */ jsxs(Box, {
98
- flexDirection: "column",
114
+ return /* @__PURE__ */ jsxs(SelectPrompt, {
115
+ options,
116
+ onChange: handleChange,
117
+ onEscape: handleEscape,
99
118
  children: [
100
119
  /* @__PURE__ */ jsx(Text, {
101
120
  color: "yellow",
@@ -124,10 +143,6 @@ function ToolApproval({ toolCall, onDecision }) {
124
143
  /* @__PURE__ */ jsx(Text, {
125
144
  dimColor: true,
126
145
  children: "Select approval action (↑↓ + Enter to confirm, Esc to reject)"
127
- }),
128
- /* @__PURE__ */ jsx(Select, {
129
- options,
130
- onChange: handleChange
131
146
  })
132
147
  ]
133
148
  });
@@ -163,11 +178,19 @@ function hasExecutablePlan(content) {
163
178
  }
164
179
  //#endregion
165
180
  //#region src/components/Chat/Chat.tsx
166
- function Chat({ model, onCommand, mode, onModeChange }) {
167
- const [messages, setMessages] = useState([createSystemMessage()]);
181
+ function Chat({ model, onCommand, mode, onModeChange, sessionId }) {
182
+ const [messages, setMessages] = useState([]);
183
+ const [streamingMessage, setStreamingMessage] = useState(null);
168
184
  const [isLoading, setIsLoading] = useState(false);
169
185
  const [pendingToolCall, setPendingToolCall] = useState(null);
170
186
  const [pendingPlan, setPendingPlan] = useState(null);
187
+ useEffect(() => {
188
+ setMessages([]);
189
+ setStreamingMessage(null);
190
+ setIsLoading(false);
191
+ setPendingToolCall(null);
192
+ setPendingPlan(null);
193
+ }, [sessionId]);
171
194
  const buildToolResultMessage = useCallback((toolName, result) => {
172
195
  if (result.error?.startsWith("Tool not allowed:")) return {
173
196
  role: ROLE.SYSTEM,
@@ -198,20 +221,38 @@ function Chat({ model, onCommand, mode, onModeChange }) {
198
221
  role: ROLE.ASSISTANT,
199
222
  content: ""
200
223
  };
201
- setMessages((previousMessages) => [...previousMessages, assistantMessage]);
224
+ let committedMessages = currentMessages;
225
+ let assistantCommitted = false;
226
+ const commitAssistantMessage = () => {
227
+ if (assistantCommitted) {
228
+ // v8 ignore next
229
+ if (committedMessages.at(-1)?.role === ROLE.ASSISTANT) {
230
+ committedMessages = [...committedMessages.slice(0, -1), { ...assistantMessage }];
231
+ setMessages(committedMessages);
232
+ }
233
+ return committedMessages;
234
+ }
235
+ assistantCommitted = true;
236
+ setStreamingMessage(null);
237
+ if (!assistantMessage.content) {
238
+ setMessages(committedMessages);
239
+ return committedMessages;
240
+ }
241
+ committedMessages = [...committedMessages, { ...assistantMessage }];
242
+ setMessages(committedMessages);
243
+ return committedMessages;
244
+ };
245
+ setStreamingMessage(assistantMessage);
202
246
  try {
203
- for await (const chunk of streamChat(currentMessages, model, TOOLS)) if (chunk.type === "content") {
247
+ for await (const chunk of streamChat(withSystemMessage(currentMessages), model, TOOLS)) if (chunk.type === "content") {
204
248
  assistantMessage.content += chunk.content;
205
- setMessages((previousMessages) => {
206
- const newMessages = [...previousMessages];
207
- newMessages[newMessages.length - 1] = { ...assistantMessage };
208
- return newMessages;
209
- });
249
+ setStreamingMessage({ ...assistantMessage });
210
250
  } else if (chunk.type === "tool_calls") for (const toolCall of chunk.tool_calls) {
211
251
  const requiresApproval = DANGEROUS_TOOLS.has(toolCall.function.name);
212
252
  // v8 ignore start
213
253
  const allowedTools = executionMode === NAME.PLAN ? READ_ONLY_TOOLS : void 0;
214
254
  // v8 ignore stop
255
+ const updatedMessages = commitAssistantMessage();
215
256
  if (executionMode === NAME.SAFE && requiresApproval) {
216
257
  setPendingToolCall(toolCall);
217
258
  setIsLoading(false);
@@ -219,22 +260,15 @@ function Chat({ model, onCommand, mode, onModeChange }) {
219
260
  }
220
261
  const result = await executeTool(toolCall.function.name, toolCall.function.arguments, { allowedTools });
221
262
  const toolResultMessage = buildToolResultMessage(toolCall.function.name, result);
222
- const newMessages = [
223
- ...currentMessages,
224
- assistantMessage,
225
- toolResultMessage
226
- ];
227
- setMessages((previousMessages) => [...previousMessages, toolResultMessage]);
263
+ const newMessages = [...updatedMessages, toolResultMessage];
264
+ setMessages(newMessages);
228
265
  await processStream(newMessages, executionMode);
229
266
  return;
230
267
  }
268
+ commitAssistantMessage();
231
269
  } catch (error) {
232
270
  assistantMessage.content = `Error: ${error instanceof Error ? error.message : String(error)}`;
233
- setMessages((previousMessages) => {
234
- const newMessages = [...previousMessages];
235
- newMessages[newMessages.length - 1] = { ...assistantMessage };
236
- return newMessages;
237
- });
271
+ commitAssistantMessage();
238
272
  } finally {
239
273
  setIsLoading(false);
240
274
  }
@@ -248,73 +282,83 @@ function Chat({ model, onCommand, mode, onModeChange }) {
248
282
  role: ROLE.ASSISTANT,
249
283
  content: ""
250
284
  };
251
- setMessages((previousMessages) => [...previousMessages, assistantMessage]);
285
+ let committedMessages = currentMessages;
286
+ let assistantCommitted = false;
287
+ const commitAssistantMessage = () => {
288
+ if (assistantCommitted) {
289
+ // v8 ignore next
290
+ if (committedMessages.at(-1)?.role === ROLE.ASSISTANT) {
291
+ committedMessages = [...committedMessages.slice(0, -1), { ...assistantMessage }];
292
+ setMessages(committedMessages);
293
+ }
294
+ return committedMessages;
295
+ }
296
+ assistantCommitted = true;
297
+ setStreamingMessage(null);
298
+ if (!assistantMessage.content) {
299
+ setMessages(committedMessages);
300
+ return committedMessages;
301
+ }
302
+ committedMessages = [...committedMessages, { ...assistantMessage }];
303
+ setMessages(committedMessages);
304
+ return committedMessages;
305
+ };
306
+ setStreamingMessage(assistantMessage);
252
307
  try {
253
308
  const readOnlyTools = TOOLS.filter((tool) => READ_ONLY_TOOLS.has(tool.function.name));
254
- for await (const chunk of streamChat(currentMessages, model, readOnlyTools)) if (chunk.type === "content") {
309
+ for await (const chunk of streamChat(withSystemMessage(currentMessages), model, readOnlyTools)) if (chunk.type === "content") {
255
310
  assistantMessage.content += chunk.content;
256
- setMessages((previousMessages) => {
257
- const newMessages = [...previousMessages];
258
- newMessages[newMessages.length - 1] = { ...assistantMessage };
259
- return newMessages;
260
- });
311
+ setStreamingMessage({ ...assistantMessage });
261
312
  } else if (chunk.type === "tool_calls") for (const toolCall of chunk.tool_calls) {
313
+ const updatedMessages = commitAssistantMessage();
262
314
  if (!READ_ONLY_TOOLS.has(toolCall.function.name)) {
263
315
  const correctionMessage = buildPlanModeCorrectionMessage(toolCall.function.name);
264
- const newMessages = [
265
- ...currentMessages,
266
- assistantMessage,
267
- correctionMessage
268
- ];
269
- setMessages((previousMessages) => [...previousMessages, correctionMessage]);
316
+ const newMessages = [...updatedMessages, correctionMessage];
317
+ setMessages(newMessages);
270
318
  await processStreamReadOnly(newMessages);
271
319
  return;
272
320
  }
273
321
  const result = await executeTool(toolCall.function.name, toolCall.function.arguments, { allowedTools: READ_ONLY_TOOLS });
274
322
  const toolResultMessage = buildToolResultMessage(toolCall.function.name, result);
275
- const newMessages = [
276
- ...currentMessages,
277
- assistantMessage,
278
- toolResultMessage
279
- ];
280
- setMessages((previousMessages) => [...previousMessages, toolResultMessage]);
323
+ const newMessages = [...updatedMessages, toolResultMessage];
324
+ setMessages(newMessages);
281
325
  await processStreamReadOnly(newMessages);
282
326
  return;
283
327
  }
328
+ const researchMessages = commitAssistantMessage();
284
329
  const planInstruction = {
285
330
  role: ROLE.SYSTEM,
286
331
  content: PLAN_GENERATION_INSTRUCTION
287
332
  };
288
- const planMessages = [
289
- ...currentMessages,
290
- assistantMessage,
291
- planInstruction
292
- ];
333
+ const planMessages = [...researchMessages, planInstruction];
293
334
  const planAssistantMessage = {
294
335
  role: ROLE.ASSISTANT,
295
336
  content: ""
296
337
  };
297
- setMessages((previousMessages) => [...previousMessages, planAssistantMessage]);
298
- for await (const chunk of streamChat(planMessages, model, [])) if (chunk.type === "content") {
299
- planAssistantMessage.content += chunk.content;
300
- setMessages((previousMessages) => {
301
- const newMessages = [...previousMessages];
302
- newMessages[newMessages.length - 1] = { ...planAssistantMessage };
303
- return newMessages;
304
- });
338
+ setStreamingMessage(planAssistantMessage);
339
+ try {
340
+ for await (const chunk of streamChat(withSystemMessage(planMessages), model, [])) if (chunk.type === "content") {
341
+ planAssistantMessage.content += chunk.content;
342
+ setStreamingMessage({ ...planAssistantMessage });
343
+ }
344
+ } catch (error) {
345
+ planAssistantMessage.content = `Error: ${error instanceof Error ? error.message : String(error)}`;
346
+ setMessages([...planMessages, { ...planAssistantMessage }]);
347
+ setStreamingMessage(null);
348
+ setIsLoading(false);
349
+ return;
305
350
  }
351
+ const finalPlanMessages = [...planMessages, { ...planAssistantMessage }];
352
+ setMessages(finalPlanMessages);
353
+ setStreamingMessage(null);
306
354
  if (hasExecutablePlan(planAssistantMessage.content)) setPendingPlan({
307
355
  planContent: planAssistantMessage.content,
308
- messages: [...planMessages, planAssistantMessage]
356
+ messages: finalPlanMessages
309
357
  });
310
358
  setIsLoading(false);
311
359
  } catch (error) {
312
360
  assistantMessage.content = `Error: ${error instanceof Error ? error.message : String(error)}`;
313
- setMessages((previousMessages) => {
314
- const newMessages = [...previousMessages];
315
- newMessages[newMessages.length - 1] = { ...assistantMessage };
316
- return newMessages;
317
- });
361
+ commitAssistantMessage();
318
362
  } finally {
319
363
  setIsLoading(false);
320
364
  }
@@ -396,8 +440,8 @@ function Chat({ model, onCommand, mode, onModeChange }) {
396
440
  role: ROLE.USER,
397
441
  content: userContent
398
442
  };
399
- setMessages((previousMessages) => [...previousMessages, userMessage]);
400
443
  const updatedMessages = [...messages, userMessage];
444
+ setMessages(updatedMessages);
401
445
  if (mode === NAME.PLAN) await processStreamReadOnly(updatedMessages);
402
446
  else await processStream(updatedMessages);
403
447
  }, [
@@ -411,8 +455,9 @@ function Chat({ model, onCommand, mode, onModeChange }) {
411
455
  flexDirection: "column",
412
456
  children: [
413
457
  /* @__PURE__ */ jsx(Messages, {
414
- messages: messages.slice(1),
415
- isLoading
458
+ messages,
459
+ isLoading,
460
+ streamingMessage
416
461
  }),
417
462
  pendingPlan && /* @__PURE__ */ jsx(PlanApproval, {
418
463
  planContent: pendingPlan.planContent,
@@ -512,40 +557,44 @@ function Header({ model }) {
512
557
  }
513
558
  //#endregion
514
559
  //#region src/components/ModelPicker.tsx
515
- function ModelPicker({ currentModel, onSelect, onCancel }) {
560
+ function ModelPicker({ currentModel, onSelect, onClose }) {
516
561
  const [options, setOptions] = useState([]);
517
562
  const [error, setError] = useState(null);
563
+ useInput((_, key) => {
564
+ if (options.length && key.return) setTimeout(onClose);
565
+ });
518
566
  useEffect(() => {
519
567
  async function load() {
520
568
  try {
521
- setOptions((await listModels()).map((name) => ({
522
- label: name,
523
- value: name
569
+ const models = await listModels();
570
+ if (models.includes(currentModel)) {
571
+ models.splice(models.indexOf(currentModel), 1);
572
+ models.unshift(currentModel);
573
+ }
574
+ setOptions(models.map((model) => ({
575
+ label: model,
576
+ value: model
524
577
  })));
525
- } catch (err) {
526
- setError(err instanceof Error ? err.message : String(err));
578
+ } catch (error) {
579
+ setError(error instanceof Error ? error.message : String(error));
527
580
  }
528
581
  }
529
582
  load();
530
- }, []);
531
- useInput((_, key) => {
532
- if (key.escape) onCancel();
533
- });
583
+ }, [currentModel]);
534
584
  if (error) return /* @__PURE__ */ jsxs(Text, {
535
585
  color: "red",
536
586
  children: ["Error loading models: ", error]
537
587
  });
538
588
  if (!options.length) return /* @__PURE__ */ jsx(Spinner, { label: "Loading models..." });
539
- return /* @__PURE__ */ jsxs(Box, {
540
- flexDirection: "column",
541
- children: [/* @__PURE__ */ jsx(Text, {
589
+ return /* @__PURE__ */ jsx(SelectPrompt, {
590
+ options,
591
+ defaultValue: currentModel,
592
+ onChange: onSelect,
593
+ onEscape: onClose,
594
+ children: /* @__PURE__ */ jsx(Text, {
542
595
  dimColor: true,
543
596
  children: "Select a model (↑↓ + Enter to confirm, Esc to cancel)"
544
- }), /* @__PURE__ */ jsx(Select, {
545
- options,
546
- defaultValue: currentModel,
547
- onChange: onSelect
548
- })]
597
+ })
549
598
  });
550
599
  }
551
600
  //#endregion
@@ -554,43 +603,64 @@ function App() {
554
603
  const [model, setModel] = useState(() => loadConfig().model);
555
604
  const [picking, setPicking] = useState(false);
556
605
  const [mode, setMode] = useState(NAME.SAFE);
606
+ const [sessionId, setSessionId] = useState(0);
557
607
  const handleCommand = useCallback((command) => {
558
- if (command === "/model") setPicking(true);
608
+ switch (command) {
609
+ case "/model":
610
+ setPicking(true);
611
+ break;
612
+ case "/clear":
613
+ resetSystemMessage();
614
+ setPicking(false);
615
+ setSessionId((sessionId) => sessionId + 1);
616
+ break;
617
+ }
559
618
  }, []);
560
619
  const handleSelect = useCallback((selected) => {
561
620
  setModel(selected);
562
621
  saveConfig({ model: selected });
563
622
  setPicking(false);
564
623
  }, []);
565
- const handleCancel = useCallback(() => {
624
+ const handleClose = useCallback(() => {
566
625
  setPicking(false);
567
626
  }, []);
568
- return /* @__PURE__ */ jsxs(Box, {
569
- flexDirection: "column",
570
- children: [
571
- /* @__PURE__ */ jsx(Header, { model }),
572
- picking ? /* @__PURE__ */ jsx(ModelPicker, {
627
+ const handleToggleMode = useCallback(() => {
628
+ setMode((mode) => {
629
+ switch (mode) {
630
+ case NAME.SAFE: return NAME.AUTO;
631
+ case NAME.AUTO: return NAME.PLAN;
632
+ case NAME.PLAN:
633
+ default: return NAME.SAFE;
634
+ }
635
+ });
636
+ }, []);
637
+ let body;
638
+ switch (true) {
639
+ case picking:
640
+ body = /* @__PURE__ */ jsx(ModelPicker, {
573
641
  currentModel: model,
574
642
  onSelect: handleSelect,
575
- onCancel: handleCancel
576
- }) : /* @__PURE__ */ jsx(Chat, {
643
+ onClose: handleClose
644
+ });
645
+ break;
646
+ default:
647
+ body = /* @__PURE__ */ jsx(Chat, {
577
648
  model,
578
649
  onCommand: handleCommand,
579
650
  mode,
580
- onModeChange: setMode
581
- }),
651
+ onModeChange: setMode,
652
+ sessionId
653
+ });
654
+ break;
655
+ }
656
+ return /* @__PURE__ */ jsxs(Box, {
657
+ flexDirection: "column",
658
+ children: [
659
+ /* @__PURE__ */ jsx(Header, { model }),
660
+ body,
582
661
  /* @__PURE__ */ jsx(Footer, {
583
662
  mode,
584
- onToggleMode: () => {
585
- setMode((mode) => {
586
- switch (mode) {
587
- case NAME.SAFE: return NAME.AUTO;
588
- case NAME.AUTO: return NAME.PLAN;
589
- case NAME.PLAN:
590
- default: return NAME.SAFE;
591
- }
592
- });
593
- }
663
+ onToggleMode: handleToggleMode
594
664
  })
595
665
  ]
596
666
  });
@@ -598,7 +668,15 @@ function App() {
598
668
  //#endregion
599
669
  //#region src/tui.tsx
600
670
  function renderApp() {
601
- render(/* @__PURE__ */ jsx(App, {}));
671
+ const tree = /* @__PURE__ */ jsx(App, {});
672
+ const app = render(tree, {
673
+ incrementalRendering: true,
674
+ maxFps: 60
675
+ });
676
+ setClearHandler(() => {
677
+ app.clear();
678
+ app.rerender(tree);
679
+ });
602
680
  }
603
681
  //#endregion
604
682
  export { renderApp };
package/dist/cli.js CHANGED
@@ -7,6 +7,9 @@ import { Ollama } from "ollama";
7
7
  import { exec } from "node:child_process";
8
8
  import { promisify } from "node:util";
9
9
  var NAMES = [{
10
+ name: "/clear",
11
+ description: "clear the current session"
12
+ }, {
10
13
  name: "/model",
11
14
  description: "switch the model"
12
15
  }].map(({ name }) => name);
@@ -28,7 +31,7 @@ var LABEL = {
28
31
  };
29
32
  //#endregion
30
33
  //#region src/constants/package.ts
31
- var VERSION = "0.3.0";
34
+ var VERSION = "0.4.0";
32
35
  //#endregion
33
36
  //#region src/constants/prompt.ts
34
37
  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
@@ -108,12 +111,20 @@ function buildSystemPrompt() {
108
111
  parts.push("\n\n", TOOL_INSTRUCTIONS);
109
112
  return parts.join("");
110
113
  }
114
+ var systemMessage = null;
111
115
  function createSystemMessage() {
112
116
  return {
113
117
  role: ROLE.SYSTEM,
114
118
  content: buildSystemPrompt()
115
119
  };
116
120
  }
121
+ function resetSystemMessage() {
122
+ systemMessage = null;
123
+ }
124
+ function withSystemMessage(messages) {
125
+ systemMessage ??= createSystemMessage();
126
+ return [systemMessage, ...messages];
127
+ }
117
128
  //#endregion
118
129
  //#region src/utils/config.ts
119
130
  var CONFIG_DIR = join(homedir(), ".code-ollama");
@@ -171,12 +182,7 @@ async function listModels() {
171
182
  const { models } = await client.list();
172
183
  return models.map(({ name }) => name);
173
184
  }
174
- //#endregion
175
- //#region src/utils/screen.ts
176
- var CLEAR = "\x1Bc";
177
- function clear() {
178
- process.stdout.write(CLEAR);
179
- }
185
+ function setClearHandler(handler) {}
180
186
  //#endregion
181
187
  //#region src/utils/tools.ts
182
188
  var execAsync = promisify(exec);
@@ -519,8 +525,8 @@ async function processRunStream(messages, model) {
519
525
  }
520
526
  async function main(args = process.argv.slice(2)) {
521
527
  if (!args.length) {
522
- const { renderApp } = await import("./assets/tui-CccSOcSC.js");
523
- clear();
528
+ const { renderApp } = await import("./assets/tui-BLJeczya.js");
529
+ process.stdout.write("\x1Bc");
524
530
  renderApp();
525
531
  return;
526
532
  }
@@ -530,7 +536,7 @@ async function main(args = process.argv.slice(2)) {
530
536
  ...args
531
537
  ]);
532
538
  }
533
- /* v8 ignore start */
539
+ // v8 ignore start
534
540
  function isEntrypoint(argv1 = process.argv[1]) {
535
541
  if (!argv1) return false;
536
542
  try {
@@ -540,6 +546,6 @@ function isEntrypoint(argv1 = process.argv[1]) {
540
546
  }
541
547
  }
542
548
  if (isEntrypoint()) main();
543
- /* v8 ignore stop */
549
+ // v8 ignore stop
544
550
  //#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 };
551
+ export { NAME$1 as _, setClearHandler as a, NAMES as b, loadConfig as c, withSystemMessage as d, HEADER_PREFIX as f, LABEL as g, VERSION as h, executeTool as i, saveConfig as l, PLAN_GENERATION_INSTRUCTION as m, main, READ_ONLY_TOOLS as n, listModels as o, ROLE as p, TOOLS as r, streamChat as s, DANGEROUS_TOOLS as t, resetSystemMessage as u, APPROVE as v, REJECT as y };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-ollama",
3
- "version": "0.3.0",
3
+ "version": "0.4.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",
@@ -43,7 +43,7 @@
43
43
  "cac": "7.0.0",
44
44
  "ink": "7.0.2",
45
45
  "ollama": "0.6.3",
46
- "react": "19.2.5"
46
+ "react": "19.2.6"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@commitlint/cli": "20.5.3",
@@ -59,7 +59,7 @@
59
59
  "globals": "17.6.0",
60
60
  "husky": "9.1.7",
61
61
  "ink-testing-library": "4.0.0",
62
- "lint-staged": "16.4.0",
62
+ "lint-staged": "17.0.2",
63
63
  "prettier": "3.8.3",
64
64
  "publint": "0.3.19",
65
65
  "tsx": "4.21.0",