code-ollama 0.8.0 → 0.9.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.
|
@@ -5,8 +5,10 @@ import { homedir } from "node:os";
|
|
|
5
5
|
import { exec } from "node:child_process";
|
|
6
6
|
import { Box, Text, render, useApp, useInput } from "ink";
|
|
7
7
|
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
8
|
-
import { Select, Spinner
|
|
8
|
+
import { Select, Spinner } from "@inkjs/ui";
|
|
9
9
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
import { marked } from "marked";
|
|
11
|
+
import TerminalRenderer from "marked-terminal";
|
|
10
12
|
//#region src/constants/command.ts
|
|
11
13
|
var LIST = [
|
|
12
14
|
{
|
|
@@ -42,6 +44,76 @@ 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
|
+
async function highlightCode(code, language = "text") {
|
|
49
|
+
const { codeToANSI } = await import("@shikijs/cli");
|
|
50
|
+
try {
|
|
51
|
+
return await codeToANSI(code, language, "github-light");
|
|
52
|
+
} catch {
|
|
53
|
+
// v8 ignore next - Defensive fallback for unsupported languages
|
|
54
|
+
return code;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
var CodeBlock = memo(function CodeBlock({ code, language, role }) {
|
|
58
|
+
const [highlighted, setHighlighted] = useState(code);
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
let canceled = false;
|
|
61
|
+
async function loadHighlight() {
|
|
62
|
+
try {
|
|
63
|
+
const result = await highlightCode(code, language);
|
|
64
|
+
if (!canceled) setHighlighted(result);
|
|
65
|
+
} catch {}
|
|
66
|
+
}
|
|
67
|
+
loadHighlight();
|
|
68
|
+
return () => {
|
|
69
|
+
canceled = true;
|
|
70
|
+
};
|
|
71
|
+
}, [code, language]);
|
|
72
|
+
const isSystem = role === ROLE.SYSTEM;
|
|
73
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
74
|
+
flexDirection: "column",
|
|
75
|
+
borderStyle: "round",
|
|
76
|
+
borderColor: isSystem ? "gray" : "dim",
|
|
77
|
+
paddingX: 1,
|
|
78
|
+
marginY: 1,
|
|
79
|
+
children: /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, {
|
|
80
|
+
dimColor: isSystem,
|
|
81
|
+
children: highlighted
|
|
82
|
+
}) })
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/components/Markdown/Markdown.tsx
|
|
87
|
+
marked.setOptions({ renderer: new TerminalRenderer({ theme: "gitHub" }) });
|
|
88
|
+
function renderMarkdown(content) {
|
|
89
|
+
const result = marked.parse(content);
|
|
90
|
+
// v8 ignore next - Defensive fallback for Promise return
|
|
91
|
+
return typeof result === "string" ? result.trim() : "";
|
|
92
|
+
}
|
|
93
|
+
var Markdown = memo(function Markdown({ content, color, dimColor }) {
|
|
94
|
+
const [rendered, setRendered] = useState(content);
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
let canceled = false;
|
|
97
|
+
function loadMarkdown() {
|
|
98
|
+
try {
|
|
99
|
+
const result = renderMarkdown(content);
|
|
100
|
+
// v8 ignore start
|
|
101
|
+
if (!canceled) setRendered(result);
|
|
102
|
+
} catch {}
|
|
103
|
+
// v8 ignore stop
|
|
104
|
+
}
|
|
105
|
+
loadMarkdown();
|
|
106
|
+
return () => {
|
|
107
|
+
canceled = true;
|
|
108
|
+
};
|
|
109
|
+
}, [content]);
|
|
110
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
111
|
+
color,
|
|
112
|
+
dimColor,
|
|
113
|
+
children: rendered
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
//#endregion
|
|
45
117
|
//#region src/components/Messages/constants.ts
|
|
46
118
|
var TURN_ABORTED_MESSAGE = [
|
|
47
119
|
"<turn_aborted>",
|
|
@@ -53,18 +125,90 @@ var TURN_ABORTED_MESSAGE = [
|
|
|
53
125
|
function getMessageColor(role) {
|
|
54
126
|
switch (role) {
|
|
55
127
|
case ROLE.USER: return "black";
|
|
56
|
-
case ROLE.ASSISTANT: return "
|
|
128
|
+
case ROLE.ASSISTANT: return "cyan";
|
|
57
129
|
case ROLE.SYSTEM: return "gray";
|
|
58
130
|
default: return;
|
|
59
131
|
}
|
|
60
132
|
}
|
|
133
|
+
function parseContent(content) {
|
|
134
|
+
const segments = [];
|
|
135
|
+
const codeBlockRegex = /```(\w+)?\n?([\s\S]*?)```/g;
|
|
136
|
+
let lastIndex = 0;
|
|
137
|
+
let match;
|
|
138
|
+
while ((match = codeBlockRegex.exec(content)) !== null) {
|
|
139
|
+
if (match.index > lastIndex) {
|
|
140
|
+
const textContent = content.slice(lastIndex, match.index).trim();
|
|
141
|
+
// v8 ignore next 2 - Defensive check for empty trimmed content
|
|
142
|
+
if (textContent) segments.push({
|
|
143
|
+
type: "text",
|
|
144
|
+
content: textContent
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
const language = match[1];
|
|
148
|
+
const codeContent = match[2].trim();
|
|
149
|
+
// v8 ignore next 2 - Defensive check for empty code block
|
|
150
|
+
if (codeContent) segments.push({
|
|
151
|
+
type: "code",
|
|
152
|
+
content: codeContent,
|
|
153
|
+
language
|
|
154
|
+
});
|
|
155
|
+
lastIndex = match.index + match[0].length;
|
|
156
|
+
}
|
|
157
|
+
if (lastIndex < content.length) {
|
|
158
|
+
const textContent = content.slice(lastIndex).trim();
|
|
159
|
+
// v8 ignore next 2 - Defensive check for empty trimmed content
|
|
160
|
+
if (textContent) segments.push({
|
|
161
|
+
type: "text",
|
|
162
|
+
content: textContent
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
// v8 ignore next 2 - Defensive fallback for edge case
|
|
166
|
+
if (segments.length === 0 && content.trim()) segments.push({
|
|
167
|
+
type: "text",
|
|
168
|
+
content: content.trim()
|
|
169
|
+
});
|
|
170
|
+
return segments;
|
|
171
|
+
}
|
|
61
172
|
var Message = memo(function Message({ message }) {
|
|
173
|
+
const messageColor = getMessageColor(message.role);
|
|
174
|
+
const isSystem = message.role === ROLE.SYSTEM;
|
|
175
|
+
const isUser = message.role === ROLE.USER;
|
|
176
|
+
if (isSystem) return /* @__PURE__ */ jsx(Box, {
|
|
177
|
+
flexDirection: "column",
|
|
178
|
+
marginBottom: 1,
|
|
179
|
+
marginX: 2,
|
|
180
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
181
|
+
color: messageColor,
|
|
182
|
+
dimColor: true,
|
|
183
|
+
children: message.content
|
|
184
|
+
})
|
|
185
|
+
});
|
|
62
186
|
return /* @__PURE__ */ jsx(Box, {
|
|
187
|
+
flexDirection: "column",
|
|
63
188
|
marginBottom: 1,
|
|
64
|
-
children:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
189
|
+
children: parseContent(message.content).map((segment, index) => {
|
|
190
|
+
const prefix = isUser && index === 0 ? "> " : "";
|
|
191
|
+
if (segment.type === "code") return isUser ? /* @__PURE__ */ jsx(Text, {
|
|
192
|
+
color: messageColor,
|
|
193
|
+
children: segment.content
|
|
194
|
+
}, index) : /* @__PURE__ */ jsx(Box, {
|
|
195
|
+
marginX: 2,
|
|
196
|
+
children: /* @__PURE__ */ jsx(CodeBlock, {
|
|
197
|
+
code: segment.content,
|
|
198
|
+
language: segment.language,
|
|
199
|
+
role: message.role
|
|
200
|
+
})
|
|
201
|
+
}, index);
|
|
202
|
+
return isUser ? /* @__PURE__ */ jsx(Text, {
|
|
203
|
+
color: messageColor,
|
|
204
|
+
children: prefix + segment.content
|
|
205
|
+
}, index) : /* @__PURE__ */ jsx(Box, {
|
|
206
|
+
marginX: 2,
|
|
207
|
+
children: /* @__PURE__ */ jsx(Markdown, {
|
|
208
|
+
content: segment.content,
|
|
209
|
+
color: messageColor
|
|
210
|
+
})
|
|
211
|
+
}, index);
|
|
68
212
|
})
|
|
69
213
|
});
|
|
70
214
|
});
|
|
@@ -77,6 +221,7 @@ function Messages({ messages, isLoading, streamingMessage }) {
|
|
|
77
221
|
isLoading && !streamingMessage?.content && /* @__PURE__ */ jsx(Box, {
|
|
78
222
|
marginTop: -1,
|
|
79
223
|
marginBottom: 1,
|
|
224
|
+
marginX: 2,
|
|
80
225
|
children: /* @__PURE__ */ jsx(Spinner, { label: "Thinking..." })
|
|
81
226
|
})
|
|
82
227
|
]
|
|
@@ -239,6 +384,71 @@ var INTERRUPT_REASON = /* @__PURE__ */ function(INTERRUPT_REASON) {
|
|
|
239
384
|
return INTERRUPT_REASON;
|
|
240
385
|
}({});
|
|
241
386
|
//#endregion
|
|
387
|
+
//#region src/components/TextInput/TextInput.tsx
|
|
388
|
+
function TextInput({ value, isDisabled = false, placeholder, onChange, onSubmit }) {
|
|
389
|
+
const [cursorPosition, setCursorPosition] = useState(value.length);
|
|
390
|
+
const prevValueRef = useRef(value);
|
|
391
|
+
useEffect(() => {
|
|
392
|
+
const prevValue = prevValueRef.current;
|
|
393
|
+
prevValueRef.current = value;
|
|
394
|
+
if (value === "") setCursorPosition(0);
|
|
395
|
+
else if (value.length > prevValue.length && cursorPosition <= prevValue.length) setCursorPosition(value.length);
|
|
396
|
+
else if (cursorPosition > value.length) setCursorPosition(value.length);
|
|
397
|
+
}, [value, cursorPosition]);
|
|
398
|
+
useInput((input, key) => {
|
|
399
|
+
// v8 ignore next
|
|
400
|
+
if (isDisabled) return;
|
|
401
|
+
if (key.return) {
|
|
402
|
+
onSubmit(value);
|
|
403
|
+
setCursorPosition(0);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
if (key.backspace) {
|
|
407
|
+
if (cursorPosition > 0) {
|
|
408
|
+
onChange(value.slice(0, cursorPosition - 1) + value.slice(cursorPosition));
|
|
409
|
+
setCursorPosition(cursorPosition - 1);
|
|
410
|
+
}
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
// v8 ignore start
|
|
414
|
+
if (key.delete) {
|
|
415
|
+
if (cursorPosition < value.length) onChange(value.slice(0, cursorPosition) + value.slice(cursorPosition + 1));
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
// v8 ignore stop
|
|
419
|
+
if (key.leftArrow) {
|
|
420
|
+
setCursorPosition(Math.max(0, cursorPosition - 1));
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (key.rightArrow) {
|
|
424
|
+
setCursorPosition(Math.min(value.length, cursorPosition + 1));
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
if (key.home) {
|
|
428
|
+
setCursorPosition(0);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (key.end) {
|
|
432
|
+
setCursorPosition(value.length);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
// v8 ignore start
|
|
436
|
+
if (input) {
|
|
437
|
+
onChange(value.slice(0, cursorPosition) + input + value.slice(cursorPosition));
|
|
438
|
+
setCursorPosition(cursorPosition + input.length);
|
|
439
|
+
}
|
|
440
|
+
// v8 ignore stop
|
|
441
|
+
}, { isActive: !isDisabled });
|
|
442
|
+
const displayValue = value || (placeholder ?? "");
|
|
443
|
+
const isPlaceholder = Boolean(!value && placeholder);
|
|
444
|
+
const char = displayValue[cursorPosition] || " ";
|
|
445
|
+
const before = displayValue.slice(0, cursorPosition);
|
|
446
|
+
const after = displayValue.slice(cursorPosition + 1);
|
|
447
|
+
const dimStyle = isPlaceholder ? "\x1B[2m" : "";
|
|
448
|
+
const resetDim = isPlaceholder ? "\x1B[22m" : "";
|
|
449
|
+
return /* @__PURE__ */ jsx(Text, { children: `${dimStyle}${before}${resetDim}\x1b[7m${char}\x1b[27m${dimStyle}${after}${resetDim}` });
|
|
450
|
+
}
|
|
451
|
+
//#endregion
|
|
242
452
|
//#region src/components/Chat/CommandMenu.tsx
|
|
243
453
|
function getMatchingCommands(input) {
|
|
244
454
|
const normalizedInput = input.trim().toLowerCase();
|
|
@@ -391,15 +601,13 @@ function hasFileSuggestionQuery(input) {
|
|
|
391
601
|
function Input({ isDisabled = false, onInterrupt, onSubmit }) {
|
|
392
602
|
const { exit } = useApp();
|
|
393
603
|
const [input, setInput] = useState("");
|
|
394
|
-
const [inputKey, setInputKey] = useState(0);
|
|
395
604
|
const fileSuggestionRef = useRef(null);
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
}, [
|
|
605
|
+
const resetInput = useCallback(() => {
|
|
606
|
+
setInput("");
|
|
607
|
+
}, []);
|
|
399
608
|
const handleSelectFileSuggestion = useCallback((nextInput) => {
|
|
400
609
|
setInput(nextInput);
|
|
401
|
-
|
|
402
|
-
}, [remountTextInput]);
|
|
610
|
+
}, []);
|
|
403
611
|
const handleFileSuggestionChange = useCallback((nextInput) => {
|
|
404
612
|
fileSuggestionRef.current = nextInput;
|
|
405
613
|
}, []);
|
|
@@ -407,10 +615,9 @@ function Input({ isDisabled = false, onInterrupt, onSubmit }) {
|
|
|
407
615
|
const trimmedInput = input.trim();
|
|
408
616
|
if (!trimmedInput) return;
|
|
409
617
|
onSubmit(trimmedInput);
|
|
410
|
-
|
|
618
|
+
resetInput();
|
|
411
619
|
fileSuggestionRef.current = null;
|
|
412
|
-
|
|
413
|
-
}, [onSubmit, remountTextInput]);
|
|
620
|
+
}, [onSubmit, resetInput]);
|
|
414
621
|
const showCommandMenu = input.startsWith("/");
|
|
415
622
|
const showFileSuggestions = !showCommandMenu && hasFileSuggestionQuery(input);
|
|
416
623
|
const handleSubmitText = useCallback(async (input) => {
|
|
@@ -433,8 +640,7 @@ function Input({ isDisabled = false, onInterrupt, onSubmit }) {
|
|
|
433
640
|
}
|
|
434
641
|
if (isCtrlC) {
|
|
435
642
|
if (input) {
|
|
436
|
-
|
|
437
|
-
remountTextInput();
|
|
643
|
+
resetInput();
|
|
438
644
|
return;
|
|
439
645
|
}
|
|
440
646
|
exit();
|
|
@@ -444,12 +650,12 @@ function Input({ isDisabled = false, onInterrupt, onSubmit }) {
|
|
|
444
650
|
flexDirection: "column",
|
|
445
651
|
children: [
|
|
446
652
|
/* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, { children: "> " }), /* @__PURE__ */ jsx(TextInput, {
|
|
447
|
-
|
|
653
|
+
value: input,
|
|
448
654
|
isDisabled,
|
|
449
655
|
onChange: setInput,
|
|
450
656
|
onSubmit: handleSubmitText,
|
|
451
657
|
placeholder: "Ask anything... (/ commands, @ files)"
|
|
452
|
-
}
|
|
658
|
+
})] }),
|
|
453
659
|
showCommandMenu && /* @__PURE__ */ jsx(CommandMenu, {
|
|
454
660
|
input,
|
|
455
661
|
onSubmit: handleSubmitCommand
|
|
@@ -1013,7 +1219,6 @@ function renderApp() {
|
|
|
1013
1219
|
const tree = /* @__PURE__ */ jsx(App, {});
|
|
1014
1220
|
const app = render(tree, {
|
|
1015
1221
|
exitOnCtrlC: false,
|
|
1016
|
-
incrementalRendering: true,
|
|
1017
1222
|
maxFps: 60
|
|
1018
1223
|
});
|
|
1019
1224
|
setClearHandler(() => {
|
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.0";
|
|
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
|
|
@@ -512,7 +512,7 @@ async function processRunStream(messages, model) {
|
|
|
512
512
|
}
|
|
513
513
|
async function main(args = process.argv.slice(2)) {
|
|
514
514
|
if (!args.length) {
|
|
515
|
-
const { renderApp } = await import("./assets/tui-
|
|
515
|
+
const { renderApp } = await import("./assets/tui-VKBxlYAz.js");
|
|
516
516
|
process.stdout.write("\x1Bc");
|
|
517
517
|
renderApp();
|
|
518
518
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-ollama",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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",
|
|
@@ -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
|
},
|