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.
- package/dist/assets/{tui-BjeMfZFh.js → tui-_1XJg3dh.js} +380 -78
- package/dist/cli.js +22 -5
- package/package.json +4 -1
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import { a as tick, c as
|
|
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 {
|
|
9
|
-
import {
|
|
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 "
|
|
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:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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 = /(
|
|
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
|
|
280
|
-
|
|
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 /(
|
|
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 [
|
|
659
|
+
const [cursorPosition, setCursorPosition] = useState(void 0);
|
|
395
660
|
const fileSuggestionRef = useRef(null);
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
}, [
|
|
661
|
+
const resetInput = useCallback(() => {
|
|
662
|
+
setInput("");
|
|
663
|
+
}, []);
|
|
399
664
|
const handleSelectFileSuggestion = useCallback((nextInput) => {
|
|
400
|
-
setInput(nextInput);
|
|
401
|
-
|
|
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
|
-
|
|
690
|
+
resetInput();
|
|
411
691
|
fileSuggestionRef.current = null;
|
|
412
|
-
|
|
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
|
-
|
|
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
|
-
|
|
725
|
+
value: input,
|
|
448
726
|
isDisabled,
|
|
727
|
+
cursorPosition,
|
|
449
728
|
onChange: setInput,
|
|
450
729
|
onSubmit: handleSubmitText,
|
|
451
730
|
placeholder: "Ask anything... (/ commands, @ files)"
|
|
452
|
-
}
|
|
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(
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
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
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
children: [
|
|
871
|
-
|
|
872
|
-
|
|
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:
|
|
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(
|
|
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: "
|
|
890
|
-
})
|
|
891
|
-
]
|
|
892
|
-
|
|
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, {
|
|
1002
|
-
|
|
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
|
-
|
|
1014
|
-
const app = render(
|
|
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
|
-
|
|
1021
|
-
app.rerender(
|
|
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.
|
|
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
|
-
|
|
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-
|
|
516
|
-
|
|
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,
|
|
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.
|
|
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
|
},
|