code-ollama 0.14.1 → 0.15.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/dist/assets/{tui-CoX71F7Y.js → tui-DPx5MGHZ.js} +293 -116
- package/dist/cli.js +31 -4
- package/package.json +4 -4
- package/dist/assets/shell-CipXM_WI.js +0 -46
|
@@ -7,18 +7,24 @@ import { Box, Static, Text, render, useApp, useInput, useStdout } from "ink";
|
|
|
7
7
|
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
8
8
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
9
|
import { Select, Spinner } from "@inkjs/ui";
|
|
10
|
-
import {
|
|
10
|
+
import { Marked } from "marked";
|
|
11
11
|
import { markedTerminal } from "marked-terminal";
|
|
12
12
|
//#region src/components/CodeBlock/CodeBlock.tsx
|
|
13
13
|
var highlightCache = /* @__PURE__ */ new Map();
|
|
14
|
-
var CODE_BLOCK_REGEX = /^(`{3,})(\w+)?[ \t]*\n([\s\S]*?)^\
|
|
14
|
+
var CODE_BLOCK_REGEX = /^(?<indent>[ \t]*)(`{3,})(\w+)?[ \t]*\n([\s\S]*?)^\k<indent>\2[ \t]*$/gm;
|
|
15
|
+
function normalizeCodeBlockContent(content, indent = "") {
|
|
16
|
+
if (!indent) return content.trim();
|
|
17
|
+
const indentPattern = new RegExp(`^${indent}`, "gm");
|
|
18
|
+
return content.replace(indentPattern, "").trim();
|
|
19
|
+
}
|
|
15
20
|
async function prewarmCodeBlocks(content) {
|
|
16
21
|
const promises = [];
|
|
17
22
|
let match;
|
|
18
23
|
CODE_BLOCK_REGEX.lastIndex = 0;
|
|
19
24
|
while ((match = CODE_BLOCK_REGEX.exec(content)) !== null) {
|
|
20
|
-
const
|
|
21
|
-
const
|
|
25
|
+
const indent = match[1];
|
|
26
|
+
const language = match[3];
|
|
27
|
+
const code = normalizeCodeBlockContent(match[4], indent);
|
|
22
28
|
// v8 ignore next 2
|
|
23
29
|
if (code) promises.push(prewarmHighlight(code, language));
|
|
24
30
|
}
|
|
@@ -165,15 +171,29 @@ var inlineMathExtension = {
|
|
|
165
171
|
//#endregion
|
|
166
172
|
//#region src/components/Markdown/Markdown.tsx
|
|
167
173
|
var HR_PLACEHOLDER = "__CODE_OLLAMA_HR_PLACEHOLDER__";
|
|
168
|
-
marked.use(markedTerminal({ theme: "gitHub" }));
|
|
169
|
-
marked.use({
|
|
170
|
-
extensions: [inlineMathExtension],
|
|
171
|
-
renderer: { hr: () => `${HR_PLACEHOLDER}\n` }
|
|
172
|
-
});
|
|
173
174
|
function renderMarkdown(content, hrWidth) {
|
|
174
175
|
const hr = "─".repeat(Math.max(1, hrWidth));
|
|
176
|
+
const markdown = new Marked();
|
|
177
|
+
const rendererExtension = {
|
|
178
|
+
extensions: [inlineMathExtension],
|
|
179
|
+
useNewRenderer: true,
|
|
180
|
+
renderer: {
|
|
181
|
+
hr: () => `${HR_PLACEHOLDER}\n`,
|
|
182
|
+
text(token) {
|
|
183
|
+
const textToken = token;
|
|
184
|
+
if (typeof token === "object" && Array.isArray(textToken.tokens)) return this.parser.parseInline(textToken.tokens);
|
|
185
|
+
return String(textToken.text);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
markdown.use(markedTerminal({
|
|
190
|
+
theme: "gitHub",
|
|
191
|
+
reflowText: true,
|
|
192
|
+
width: Math.max(1, hrWidth)
|
|
193
|
+
}));
|
|
194
|
+
markdown.use(rendererExtension);
|
|
175
195
|
try {
|
|
176
|
-
const result =
|
|
196
|
+
const result = markdown.parse(content);
|
|
177
197
|
return (typeof result === "string" ? result.trim() : content).replaceAll(HR_PLACEHOLDER, hr);
|
|
178
198
|
} catch {
|
|
179
199
|
return content;
|
|
@@ -197,7 +217,92 @@ var TURN_ABORTED_MESSAGE = [
|
|
|
197
217
|
"</turn_aborted>"
|
|
198
218
|
].join("\n");
|
|
199
219
|
//#endregion
|
|
200
|
-
//#region src/components/Messages/
|
|
220
|
+
//#region src/components/Messages/utils.ts
|
|
221
|
+
var FENCE_LINE_REGEX = /^(?<indent>[ \t]*)(?<fence>`{3,})(?<language>\w+)?[ \t]*$/;
|
|
222
|
+
function flushTextSegment(segments, textLines) {
|
|
223
|
+
const textContent = textLines.join("\n").trim();
|
|
224
|
+
if (textContent) segments.push({
|
|
225
|
+
type: "text",
|
|
226
|
+
content: textContent
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
function flushCodeSegment(segments, codeLines, fenceState) {
|
|
230
|
+
if (fenceState.ambiguous) {
|
|
231
|
+
segments.push({
|
|
232
|
+
type: "raw",
|
|
233
|
+
content: fenceState.rawLines.join("\n")
|
|
234
|
+
});
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const codeContent = normalizeCodeBlockContent(codeLines.join("\n"), fenceState.indent);
|
|
238
|
+
if (codeContent) segments.push({
|
|
239
|
+
type: "code",
|
|
240
|
+
content: codeContent,
|
|
241
|
+
language: fenceState.language
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
function unwrapRawMarkdownFence(content) {
|
|
245
|
+
if (!content.startsWith("```markdown\n") || !content.endsWith("\n```")) return null;
|
|
246
|
+
return content.slice(12, -4);
|
|
247
|
+
}
|
|
248
|
+
function parseContent(content) {
|
|
249
|
+
const segments = [];
|
|
250
|
+
const lines = content.split("\n");
|
|
251
|
+
const textLines = [];
|
|
252
|
+
const codeLines = [];
|
|
253
|
+
let fenceState = null;
|
|
254
|
+
for (const line of lines) {
|
|
255
|
+
const fenceMatch = FENCE_LINE_REGEX.exec(line);
|
|
256
|
+
if (fenceMatch?.groups) {
|
|
257
|
+
const { indent, fence, language } = fenceMatch.groups;
|
|
258
|
+
if (!fenceState) {
|
|
259
|
+
flushTextSegment(segments, textLines);
|
|
260
|
+
textLines.length = 0;
|
|
261
|
+
fenceState = {
|
|
262
|
+
indent,
|
|
263
|
+
fence,
|
|
264
|
+
language,
|
|
265
|
+
rawLines: [line],
|
|
266
|
+
ambiguous: false,
|
|
267
|
+
rawFenceDepth: 1
|
|
268
|
+
};
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (indent === fenceState.indent && fence === fenceState.fence) {
|
|
272
|
+
fenceState.rawLines.push(line);
|
|
273
|
+
if (fenceState.ambiguous) {
|
|
274
|
+
if (language) {
|
|
275
|
+
fenceState.rawFenceDepth += 1;
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
fenceState.rawFenceDepth -= 1;
|
|
279
|
+
if (fenceState.rawFenceDepth === 0) {
|
|
280
|
+
flushCodeSegment(segments, codeLines, fenceState);
|
|
281
|
+
codeLines.length = 0;
|
|
282
|
+
fenceState = null;
|
|
283
|
+
}
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if (!language) {
|
|
287
|
+
flushCodeSegment(segments, codeLines, fenceState);
|
|
288
|
+
codeLines.length = 0;
|
|
289
|
+
fenceState = null;
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
fenceState.ambiguous = true;
|
|
293
|
+
fenceState.rawFenceDepth += 1;
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (fenceState) {
|
|
298
|
+
fenceState.rawLines.push(line);
|
|
299
|
+
codeLines.push(line);
|
|
300
|
+
} else textLines.push(line);
|
|
301
|
+
}
|
|
302
|
+
if (fenceState) textLines.push(...fenceState.rawLines);
|
|
303
|
+
flushTextSegment(segments, textLines);
|
|
304
|
+
return segments;
|
|
305
|
+
}
|
|
201
306
|
function getMessageColor(role) {
|
|
202
307
|
switch (role) {
|
|
203
308
|
case USER: return "black";
|
|
@@ -206,46 +311,102 @@ function getMessageColor(role) {
|
|
|
206
311
|
default: return;
|
|
207
312
|
}
|
|
208
313
|
}
|
|
209
|
-
function
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
314
|
+
function isWordCharacter(char) {
|
|
315
|
+
return char !== void 0 && /[A-Za-z0-9]/.test(char);
|
|
316
|
+
}
|
|
317
|
+
function isEscaped(content, index) {
|
|
318
|
+
let slashCount = 0;
|
|
319
|
+
for (let cursor = index - 1; cursor >= 0 && content[cursor] === "\\"; cursor--) slashCount += 1;
|
|
320
|
+
return slashCount % 2 === 1;
|
|
321
|
+
}
|
|
322
|
+
function canOpenEmphasis(content, index, length) {
|
|
323
|
+
const previous = content[index - 1];
|
|
324
|
+
const next = content[index + length];
|
|
325
|
+
if (!next || /\s/.test(next)) return false;
|
|
326
|
+
return !isWordCharacter(previous);
|
|
327
|
+
}
|
|
328
|
+
function canCloseEmphasis(content, index, length) {
|
|
329
|
+
const previous = content[index - 1];
|
|
330
|
+
const next = content[index + length];
|
|
331
|
+
if (!previous || /\s/.test(previous)) return false;
|
|
332
|
+
return !isWordCharacter(next);
|
|
333
|
+
}
|
|
334
|
+
function findUnmatchedInlineDelimiter(content) {
|
|
335
|
+
const stack = [];
|
|
336
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
337
|
+
const current = content[index];
|
|
338
|
+
if (isEscaped(content, index)) continue;
|
|
339
|
+
const top = stack.at(-1);
|
|
340
|
+
if (top?.kind === "code") {
|
|
341
|
+
if (current === "`") stack.pop();
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
if (top?.kind === "latex") {
|
|
345
|
+
if (current === "$") stack.pop();
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
if (current === "`") {
|
|
349
|
+
stack.push({
|
|
350
|
+
index,
|
|
351
|
+
length: 1,
|
|
352
|
+
kind: "code",
|
|
353
|
+
marker: "`"
|
|
221
354
|
});
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
if (current === "$") {
|
|
358
|
+
stack.push({
|
|
359
|
+
index,
|
|
360
|
+
length: 1,
|
|
361
|
+
kind: "latex",
|
|
362
|
+
marker: "$"
|
|
363
|
+
});
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
if (current !== "*") continue;
|
|
367
|
+
const marker = current;
|
|
368
|
+
const length = content[index + 1] === marker ? 2 : 1;
|
|
369
|
+
const token = marker.repeat(length);
|
|
370
|
+
const kind = length === 2 ? "bold" : "italic";
|
|
371
|
+
if (top?.marker === token && top.kind === kind && canCloseEmphasis(content, index, length)) {
|
|
372
|
+
stack.pop();
|
|
373
|
+
if (length === 2) index += 1;
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
if (canOpenEmphasis(content, index, length)) {
|
|
377
|
+
stack.push({
|
|
378
|
+
index,
|
|
379
|
+
length,
|
|
380
|
+
kind,
|
|
381
|
+
marker: token
|
|
382
|
+
});
|
|
383
|
+
if (length === 2) index += 1;
|
|
222
384
|
}
|
|
223
|
-
const language = match[2];
|
|
224
|
-
const codeContent = match[3].trim();
|
|
225
|
-
// v8 ignore next 2 - Defensive check for empty code block
|
|
226
|
-
if (codeContent) segments.push({
|
|
227
|
-
type: "code",
|
|
228
|
-
content: codeContent,
|
|
229
|
-
language
|
|
230
|
-
});
|
|
231
|
-
lastIndex = match.index + match[0].length;
|
|
232
|
-
}
|
|
233
|
-
if (lastIndex < content.length) {
|
|
234
|
-
const textContent = content.slice(lastIndex).trim();
|
|
235
|
-
// v8 ignore next 2 - Defensive check for empty trimmed content
|
|
236
|
-
if (textContent) segments.push({
|
|
237
|
-
type: "text",
|
|
238
|
-
content: textContent
|
|
239
|
-
});
|
|
240
385
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
386
|
+
return stack[0] ?? null;
|
|
387
|
+
}
|
|
388
|
+
function splitStreamingInlineContent(content) {
|
|
389
|
+
const unmatched = findUnmatchedInlineDelimiter(content);
|
|
390
|
+
if (!unmatched) return [{
|
|
391
|
+
type: "markdown",
|
|
392
|
+
content
|
|
393
|
+
}];
|
|
394
|
+
const parts = [];
|
|
395
|
+
const prefix = content.slice(0, unmatched.index);
|
|
396
|
+
const plainSuffix = content.slice(unmatched.index + unmatched.length);
|
|
397
|
+
if (prefix) parts.push({
|
|
398
|
+
type: "markdown",
|
|
399
|
+
content: prefix
|
|
245
400
|
});
|
|
246
|
-
|
|
401
|
+
if (plainSuffix) parts.push({
|
|
402
|
+
type: "plain",
|
|
403
|
+
content: plainSuffix
|
|
404
|
+
});
|
|
405
|
+
return parts;
|
|
247
406
|
}
|
|
248
|
-
|
|
407
|
+
//#endregion
|
|
408
|
+
//#region src/components/Messages/Messages.tsx
|
|
409
|
+
function Message({ message, isStreaming = false }) {
|
|
249
410
|
const messageColor = getMessageColor(message.role);
|
|
250
411
|
const isSystem = message.role === SYSTEM;
|
|
251
412
|
const isUser = message.role === USER;
|
|
@@ -275,20 +436,39 @@ var Message = memo(function Message({ message }) {
|
|
|
275
436
|
role: message.role
|
|
276
437
|
})
|
|
277
438
|
}, index);
|
|
439
|
+
if (segment.type === "raw") {
|
|
440
|
+
const markdownSource = unwrapRawMarkdownFence(segment.content);
|
|
441
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
442
|
+
marginX: 2,
|
|
443
|
+
children: /* @__PURE__ */ jsx(CodeBlock, {
|
|
444
|
+
code: markdownSource ?? segment.content,
|
|
445
|
+
language: markdownSource ? "markdown" : segment.language,
|
|
446
|
+
role: message.role
|
|
447
|
+
})
|
|
448
|
+
}, index);
|
|
449
|
+
}
|
|
450
|
+
const textParts = isStreaming && !isUser ? splitStreamingInlineContent(segment.content) : [{
|
|
451
|
+
type: "markdown",
|
|
452
|
+
content: segment.content
|
|
453
|
+
}];
|
|
278
454
|
return isUser ? /* @__PURE__ */ jsx(Text, {
|
|
279
455
|
color: messageColor,
|
|
280
456
|
children: prefix + segment.content
|
|
281
457
|
}, index) : /* @__PURE__ */ jsx(Box, {
|
|
458
|
+
flexDirection: "column",
|
|
282
459
|
marginX: 2,
|
|
283
|
-
children: /* @__PURE__ */ jsx(
|
|
284
|
-
|
|
460
|
+
children: textParts.map((part, partIndex) => part.type === "plain" ? /* @__PURE__ */ jsx(Text, {
|
|
461
|
+
color: messageColor,
|
|
462
|
+
children: part.content
|
|
463
|
+
}, partIndex) : /* @__PURE__ */ jsx(Markdown, {
|
|
464
|
+
content: part.content,
|
|
285
465
|
color: messageColor
|
|
286
|
-
})
|
|
466
|
+
}, partIndex))
|
|
287
467
|
}, index);
|
|
288
468
|
})
|
|
289
469
|
});
|
|
290
|
-
}
|
|
291
|
-
function Messages({ messages, isLoading, sessionId
|
|
470
|
+
}
|
|
471
|
+
function Messages({ messages, isLoading, sessionId, streamingMessage }) {
|
|
292
472
|
return /* @__PURE__ */ jsxs(Box, {
|
|
293
473
|
flexDirection: "column",
|
|
294
474
|
children: [
|
|
@@ -296,7 +476,10 @@ function Messages({ messages, isLoading, sessionId = 0, streamingMessage }) {
|
|
|
296
476
|
items: messages.filter(({ content }) => content !== TURN_ABORTED_MESSAGE),
|
|
297
477
|
children: (message, index) => /* @__PURE__ */ jsx(Message, { message }, index)
|
|
298
478
|
}, sessionId),
|
|
299
|
-
streamingMessage && /* @__PURE__ */ jsx(Message, {
|
|
479
|
+
streamingMessage && /* @__PURE__ */ jsx(Message, {
|
|
480
|
+
isStreaming: true,
|
|
481
|
+
message: streamingMessage
|
|
482
|
+
}),
|
|
300
483
|
isLoading && !streamingMessage?.content && /* @__PURE__ */ jsx(Box, {
|
|
301
484
|
marginTop: -1,
|
|
302
485
|
marginBottom: 1,
|
|
@@ -1373,47 +1556,35 @@ function ModelPicker({ currentModel, onSelect, onClose }) {
|
|
|
1373
1556
|
}
|
|
1374
1557
|
//#endregion
|
|
1375
1558
|
//#region src/components/SearchSettings.tsx
|
|
1376
|
-
var View = /* @__PURE__ */ function(View) {
|
|
1377
|
-
View["Menu"] = "menu";
|
|
1378
|
-
View["Edit"] = "edit";
|
|
1379
|
-
return View;
|
|
1380
|
-
}(View || {});
|
|
1381
|
-
var Action = /* @__PURE__ */ function(Action) {
|
|
1382
|
-
Action["Set"] = "set";
|
|
1383
|
-
Action["Clear"] = "clear";
|
|
1384
|
-
Action["Cancel"] = "cancel";
|
|
1385
|
-
return Action;
|
|
1386
|
-
}(Action || {});
|
|
1387
1559
|
function SearchSettings({ currentUrl, onClose, onSave }) {
|
|
1388
|
-
const [view, setView] = useState(
|
|
1560
|
+
const [view, setView] = useState("menu");
|
|
1389
1561
|
const [draftUrl, setDraftUrl] = useState(currentUrl ?? "");
|
|
1390
1562
|
const [error, setError] = useState(null);
|
|
1391
1563
|
const options = useMemo(() => {
|
|
1392
1564
|
const nextOptions = [{
|
|
1393
1565
|
label: currentUrl ? "Update SearXNG URL" : "Set SearXNG URL",
|
|
1394
|
-
value:
|
|
1566
|
+
value: "set"
|
|
1395
1567
|
}];
|
|
1396
1568
|
if (currentUrl) nextOptions.push({
|
|
1397
1569
|
label: "Clear SearXNG URL",
|
|
1398
|
-
value:
|
|
1570
|
+
value: "clear"
|
|
1399
1571
|
});
|
|
1400
1572
|
nextOptions.push({
|
|
1401
1573
|
label: "Cancel",
|
|
1402
|
-
value:
|
|
1574
|
+
value: "cancel"
|
|
1403
1575
|
});
|
|
1404
1576
|
return nextOptions;
|
|
1405
1577
|
}, [currentUrl]);
|
|
1406
1578
|
const handleChange = useCallback((value) => {
|
|
1407
1579
|
setError(null);
|
|
1408
1580
|
switch (value) {
|
|
1409
|
-
case
|
|
1581
|
+
case "set":
|
|
1410
1582
|
setDraftUrl(currentUrl ?? "");
|
|
1411
|
-
setView(
|
|
1583
|
+
setView("edit");
|
|
1412
1584
|
break;
|
|
1413
|
-
case
|
|
1585
|
+
case "clear":
|
|
1414
1586
|
onSave({ searxngBaseUrl: void 0 });
|
|
1415
1587
|
break;
|
|
1416
|
-
case Action.Cancel:
|
|
1417
1588
|
default: onClose();
|
|
1418
1589
|
}
|
|
1419
1590
|
}, [
|
|
@@ -1439,13 +1610,13 @@ function SearchSettings({ currentUrl, onClose, onSave }) {
|
|
|
1439
1610
|
}
|
|
1440
1611
|
}, [onSave]);
|
|
1441
1612
|
useInput((input, key) => {
|
|
1442
|
-
if (view ===
|
|
1613
|
+
if (view === "edit" && (key.escape || key.ctrl && input === "c")) {
|
|
1443
1614
|
setDraftUrl(currentUrl ?? "");
|
|
1444
1615
|
setError(null);
|
|
1445
|
-
setView(
|
|
1616
|
+
setView("menu");
|
|
1446
1617
|
}
|
|
1447
1618
|
});
|
|
1448
|
-
if (view ===
|
|
1619
|
+
if (view === "edit") return /* @__PURE__ */ jsxs(Box, {
|
|
1449
1620
|
flexDirection: "column",
|
|
1450
1621
|
children: [
|
|
1451
1622
|
/* @__PURE__ */ jsx(Text, { children: "Set the SearXNG base URL. DuckDuckGo remains the fallback." }),
|
|
@@ -1482,45 +1653,55 @@ function SearchSettings({ currentUrl, onClose, onSave }) {
|
|
|
1482
1653
|
}
|
|
1483
1654
|
//#endregion
|
|
1484
1655
|
//#region src/components/SessionManager.tsx
|
|
1485
|
-
var VIEW = /* @__PURE__ */ function(VIEW) {
|
|
1486
|
-
VIEW["MAIN"] = "main";
|
|
1487
|
-
VIEW["DELETE"] = "delete";
|
|
1488
|
-
return VIEW;
|
|
1489
|
-
}(VIEW || {});
|
|
1490
1656
|
var ACTION = {
|
|
1491
1657
|
BACK: "back",
|
|
1492
1658
|
CLOSE: "close",
|
|
1493
1659
|
DELETE_MENU: "delete-menu",
|
|
1494
1660
|
DELETE_PREFIX: "delete:",
|
|
1495
1661
|
NEW: "new",
|
|
1662
|
+
OPEN_MENU: "open-menu",
|
|
1496
1663
|
OPEN_PREFIX: "open:"
|
|
1497
1664
|
};
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
return `${
|
|
1665
|
+
var SESSION_LABEL_PADDING = 4;
|
|
1666
|
+
function truncate(value, maxLength) {
|
|
1667
|
+
return value.length > maxLength ? `${value.slice(0, maxLength - 1).trimEnd()}…` : value;
|
|
1668
|
+
}
|
|
1669
|
+
function formatSessionLabel(session, maxWidth, prefix = "") {
|
|
1670
|
+
const suffix = ` (${new Date(session.updatedAt).toLocaleString()})`;
|
|
1671
|
+
const availableTitleWidth = maxWidth - prefix.length - suffix.length;
|
|
1672
|
+
if (availableTitleWidth < 1) return truncate(`${prefix}${session.title}${suffix}`, maxWidth);
|
|
1673
|
+
return `${prefix}${truncate(session.title, availableTitleWidth)}${suffix}`;
|
|
1501
1674
|
}
|
|
1502
1675
|
function SessionManager({ currentSessionId, onClose, onDelete, onNew, onOpen }) {
|
|
1503
|
-
const [view, setView] = useState(
|
|
1676
|
+
const [view, setView] = useState("main");
|
|
1504
1677
|
const [error, setError] = useState();
|
|
1505
1678
|
const [, refreshSessionList] = useState(0);
|
|
1679
|
+
const { stdout } = useStdout();
|
|
1506
1680
|
const sessions = listSessions();
|
|
1507
|
-
const
|
|
1508
|
-
|
|
1681
|
+
const maxLabelWidth = Math.max(1, stdout.columns - SESSION_LABEL_PADDING);
|
|
1682
|
+
const options = view === "open" ? [...sessions.filter(({ id }) => id !== currentSessionId).map((session) => ({
|
|
1683
|
+
label: formatSessionLabel(session, maxLabelWidth),
|
|
1684
|
+
value: `${ACTION.OPEN_PREFIX}${session.id}`
|
|
1685
|
+
})), {
|
|
1686
|
+
label: "Back",
|
|
1687
|
+
value: ACTION.BACK
|
|
1688
|
+
}] : view === "delete" ? [...sessions.filter(({ id }) => id !== currentSessionId).map((session) => ({
|
|
1689
|
+
label: formatSessionLabel(session, maxLabelWidth, "Delete "),
|
|
1509
1690
|
value: `${ACTION.DELETE_PREFIX}${session.id}`
|
|
1510
1691
|
})), {
|
|
1511
1692
|
label: "Back",
|
|
1512
1693
|
value: ACTION.BACK
|
|
1513
1694
|
}] : [
|
|
1514
1695
|
{
|
|
1515
|
-
label: "
|
|
1696
|
+
label: "New session",
|
|
1516
1697
|
value: ACTION.NEW
|
|
1517
1698
|
},
|
|
1518
|
-
...sessions.map((session) => ({
|
|
1519
|
-
label: `${session.id === currentSessionId ? "Current: " : ""}${formatSessionLabel(session)}`,
|
|
1520
|
-
value: `${ACTION.OPEN_PREFIX}${session.id}`
|
|
1521
|
-
})),
|
|
1522
1699
|
{
|
|
1523
|
-
label: "
|
|
1700
|
+
label: "Open session",
|
|
1701
|
+
value: ACTION.OPEN_MENU
|
|
1702
|
+
},
|
|
1703
|
+
{
|
|
1704
|
+
label: "Delete session",
|
|
1524
1705
|
value: ACTION.DELETE_MENU
|
|
1525
1706
|
},
|
|
1526
1707
|
{
|
|
@@ -1537,10 +1718,13 @@ function SessionManager({ currentSessionId, onClose, onDelete, onNew, onOpen })
|
|
|
1537
1718
|
onNew();
|
|
1538
1719
|
break;
|
|
1539
1720
|
case value === ACTION.DELETE_MENU:
|
|
1540
|
-
setView(
|
|
1721
|
+
setView("delete");
|
|
1722
|
+
break;
|
|
1723
|
+
case value === ACTION.OPEN_MENU:
|
|
1724
|
+
setView("open");
|
|
1541
1725
|
break;
|
|
1542
1726
|
case value === ACTION.BACK:
|
|
1543
|
-
setView(
|
|
1727
|
+
setView("main");
|
|
1544
1728
|
break;
|
|
1545
1729
|
case value.startsWith(ACTION.DELETE_PREFIX):
|
|
1546
1730
|
try {
|
|
@@ -1570,7 +1754,7 @@ function SessionManager({ currentSessionId, onClose, onDelete, onNew, onOpen })
|
|
|
1570
1754
|
flexDirection: "column",
|
|
1571
1755
|
children: [
|
|
1572
1756
|
/* @__PURE__ */ jsx(Text, { children: "Sessions" }),
|
|
1573
|
-
/* @__PURE__ */ jsx(SelectPromptHint, { message: view ===
|
|
1757
|
+
/* @__PURE__ */ jsx(SelectPromptHint, { message: view === "delete" ? "Delete session" : view === "open" ? "Open session" : "Select session" }),
|
|
1574
1758
|
error && /* @__PURE__ */ jsx(Box, {
|
|
1575
1759
|
marginBottom: 1,
|
|
1576
1760
|
children: /* @__PURE__ */ jsx(Text, {
|
|
@@ -1588,20 +1772,13 @@ function SessionManager({ currentSessionId, onClose, onDelete, onNew, onOpen })
|
|
|
1588
1772
|
}
|
|
1589
1773
|
//#endregion
|
|
1590
1774
|
//#region src/components/App.tsx
|
|
1591
|
-
var SCREEN = /* @__PURE__ */ function(SCREEN) {
|
|
1592
|
-
SCREEN["CHAT"] = "chat";
|
|
1593
|
-
SCREEN["MODEL_PICKER"] = "model-picker";
|
|
1594
|
-
SCREEN["SEARCH_SETTINGS"] = "search-settings";
|
|
1595
|
-
SCREEN["SESSION_MANAGER"] = "session-manager";
|
|
1596
|
-
return SCREEN;
|
|
1597
|
-
}(SCREEN || {});
|
|
1598
1775
|
function createSession(sessionId, model) {
|
|
1599
1776
|
return sessionId ? loadSession(sessionId) : createSession$1(model);
|
|
1600
1777
|
}
|
|
1601
1778
|
function App({ sessionId }) {
|
|
1602
1779
|
const { exit } = useApp();
|
|
1603
1780
|
const [appConfig, setConfig] = useState(() => loadConfig());
|
|
1604
|
-
const [currentScreen, setScreen] = useState(
|
|
1781
|
+
const [currentScreen, setScreen] = useState("chat");
|
|
1605
1782
|
const [mode, setMode] = useState(SAFE);
|
|
1606
1783
|
const [activeSession, setSession] = useState(() => createSession(sessionId, loadConfig().model));
|
|
1607
1784
|
const [isHeaderLoaded, setIsHeaderLoaded] = useState(false);
|
|
@@ -1627,17 +1804,17 @@ function App({ sessionId }) {
|
|
|
1627
1804
|
const handleCreateSession = useCallback(() => {
|
|
1628
1805
|
const nextSession = createSession$1(appConfig.model);
|
|
1629
1806
|
setActiveSession(nextSession);
|
|
1630
|
-
setScreen(
|
|
1807
|
+
setScreen("chat");
|
|
1631
1808
|
clear(nextSession.metadata.id);
|
|
1632
1809
|
return nextSession;
|
|
1633
1810
|
}, [appConfig.model, setActiveSession]);
|
|
1634
1811
|
const handleOpenSession = useCallback((sessionId) => {
|
|
1635
1812
|
if (sessionRef.current.metadata.id === sessionId) {
|
|
1636
|
-
setScreen(
|
|
1813
|
+
setScreen("chat");
|
|
1637
1814
|
return;
|
|
1638
1815
|
}
|
|
1639
1816
|
setActiveSession(loadSession(sessionId));
|
|
1640
|
-
setScreen(
|
|
1817
|
+
setScreen("chat");
|
|
1641
1818
|
clear(sessionId);
|
|
1642
1819
|
}, [setActiveSession]);
|
|
1643
1820
|
const handleDeleteSession = useCallback((sessionId) => {
|
|
@@ -1646,7 +1823,7 @@ function App({ sessionId }) {
|
|
|
1646
1823
|
if (current.metadata.id !== sessionId) return current;
|
|
1647
1824
|
return createSession$1(appConfig.model);
|
|
1648
1825
|
});
|
|
1649
|
-
setScreen(
|
|
1826
|
+
setScreen("session-manager");
|
|
1650
1827
|
}, [appConfig.model]);
|
|
1651
1828
|
const handleMessagesChange = useCallback((messages) => {
|
|
1652
1829
|
setSession((current) => {
|
|
@@ -1663,17 +1840,17 @@ function App({ sessionId }) {
|
|
|
1663
1840
|
const handleCommand = useCallback((command) => {
|
|
1664
1841
|
switch (command) {
|
|
1665
1842
|
case "/session":
|
|
1666
|
-
setScreen(
|
|
1843
|
+
setScreen("session-manager");
|
|
1667
1844
|
break;
|
|
1668
1845
|
case "/model":
|
|
1669
|
-
setScreen(
|
|
1846
|
+
setScreen("model-picker");
|
|
1670
1847
|
break;
|
|
1671
1848
|
case "/search":
|
|
1672
|
-
setScreen(
|
|
1849
|
+
setScreen("search-settings");
|
|
1673
1850
|
break;
|
|
1674
1851
|
case "/clear": {
|
|
1675
1852
|
resetSystemMessage();
|
|
1676
|
-
setScreen(
|
|
1853
|
+
setScreen("chat");
|
|
1677
1854
|
const nextSession = createSession$1(appConfig.model);
|
|
1678
1855
|
setActiveSession(nextSession);
|
|
1679
1856
|
clear(nextSession.metadata.id);
|
|
@@ -1699,10 +1876,10 @@ function App({ sessionId }) {
|
|
|
1699
1876
|
...current,
|
|
1700
1877
|
metadata: updateSessionModel(current.metadata.id, newModel)
|
|
1701
1878
|
}));
|
|
1702
|
-
setScreen(
|
|
1879
|
+
setScreen("chat");
|
|
1703
1880
|
}, []);
|
|
1704
1881
|
const handleClose = useCallback(() => {
|
|
1705
|
-
setScreen(
|
|
1882
|
+
setScreen("chat");
|
|
1706
1883
|
}, []);
|
|
1707
1884
|
const handleToggleMode = useCallback(() => {
|
|
1708
1885
|
setMode((mode) => {
|
|
@@ -1716,21 +1893,21 @@ function App({ sessionId }) {
|
|
|
1716
1893
|
}, []);
|
|
1717
1894
|
let screenContent;
|
|
1718
1895
|
switch (currentScreen) {
|
|
1719
|
-
case
|
|
1896
|
+
case "model-picker":
|
|
1720
1897
|
screenContent = /* @__PURE__ */ jsx(ModelPicker, {
|
|
1721
1898
|
currentModel: appConfig.model,
|
|
1722
1899
|
onSelect: handleUpdateConfig,
|
|
1723
1900
|
onClose: handleClose
|
|
1724
1901
|
});
|
|
1725
1902
|
break;
|
|
1726
|
-
case
|
|
1903
|
+
case "search-settings":
|
|
1727
1904
|
screenContent = /* @__PURE__ */ jsx(SearchSettings, {
|
|
1728
1905
|
currentUrl: appConfig.searxngBaseUrl,
|
|
1729
1906
|
onSave: handleUpdateConfig,
|
|
1730
1907
|
onClose: handleClose
|
|
1731
1908
|
});
|
|
1732
1909
|
break;
|
|
1733
|
-
case
|
|
1910
|
+
case "session-manager":
|
|
1734
1911
|
screenContent = /* @__PURE__ */ jsx(SessionManager, {
|
|
1735
1912
|
currentSessionId: activeSession.metadata.id,
|
|
1736
1913
|
onClose: handleClose,
|
|
@@ -1739,7 +1916,7 @@ function App({ sessionId }) {
|
|
|
1739
1916
|
onOpen: handleOpenSession
|
|
1740
1917
|
});
|
|
1741
1918
|
break;
|
|
1742
|
-
case
|
|
1919
|
+
case "chat":
|
|
1743
1920
|
screenContent = /* @__PURE__ */ jsx(Chat, {
|
|
1744
1921
|
initialMessages: activeSession.messages,
|
|
1745
1922
|
model: appConfig.model,
|
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as runShell } from "./assets/shell-CipXM_WI.js";
|
|
3
2
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
4
3
|
import cac from "cac";
|
|
5
4
|
import { homedir } from "node:os";
|
|
6
5
|
import { join } from "node:path";
|
|
7
6
|
import { Ollama } from "ollama";
|
|
8
7
|
import { v7 } from "uuid";
|
|
8
|
+
import { exec } from "node:child_process";
|
|
9
|
+
import { promisify } from "node:util";
|
|
9
10
|
//#region src/constants/command.ts
|
|
10
11
|
var LIST = [
|
|
11
12
|
{
|
|
@@ -32,7 +33,7 @@ var LIST = [
|
|
|
32
33
|
//#endregion
|
|
33
34
|
//#region package.json
|
|
34
35
|
var name = "code-ollama";
|
|
35
|
-
var version = "0.
|
|
36
|
+
var version = "0.15.0";
|
|
36
37
|
//#endregion
|
|
37
38
|
//#region src/constants/package.ts
|
|
38
39
|
var NAME = name;
|
|
@@ -497,6 +498,33 @@ var WRITE_TOOLS = new Set([
|
|
|
497
498
|
RUN_SHELL
|
|
498
499
|
]);
|
|
499
500
|
//#endregion
|
|
501
|
+
//#region src/utils/tools/shell.ts
|
|
502
|
+
var execAsync = promisify(exec);
|
|
503
|
+
var SHELL_EXEC_OPTIONS = {
|
|
504
|
+
timeout: 3e4,
|
|
505
|
+
maxBuffer: 1024 * 1024
|
|
506
|
+
};
|
|
507
|
+
/**
|
|
508
|
+
* Execute shell command with shared options (throws on error)
|
|
509
|
+
*/
|
|
510
|
+
function execShell(command) {
|
|
511
|
+
return execAsync(command, SHELL_EXEC_OPTIONS);
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Execute shell command
|
|
515
|
+
*/
|
|
516
|
+
async function runShell(command) {
|
|
517
|
+
try {
|
|
518
|
+
const { stdout, stderr } = await execShell(command);
|
|
519
|
+
return { content: stdout || stderr };
|
|
520
|
+
} catch (error) {
|
|
521
|
+
return {
|
|
522
|
+
content: "",
|
|
523
|
+
error: `Command failed: ${error instanceof Error ? error.message : String(error)}`
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
//#endregion
|
|
500
528
|
//#region src/utils/tools/filesystem.ts
|
|
501
529
|
/**
|
|
502
530
|
* Read file contents
|
|
@@ -603,7 +631,6 @@ function listDir(dirPath) {
|
|
|
603
631
|
* Search for pattern in files using ripgrep if available, fallback to Node.js
|
|
604
632
|
*/
|
|
605
633
|
async function grepSearch(pattern, dirPath) {
|
|
606
|
-
const { execShell } = await import("./assets/shell-CipXM_WI.js").then((n) => n.n);
|
|
607
634
|
try {
|
|
608
635
|
const { stdout } = await execShell(`rg --line-number --no-heading --smart-case "${pattern.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}" "${dirPath.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`);
|
|
609
636
|
// v8 ignore next
|
|
@@ -904,7 +931,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
904
931
|
else await launchTui();
|
|
905
932
|
}
|
|
906
933
|
async function launchTui(sessionId) {
|
|
907
|
-
const { renderApp } = await import("./assets/tui-
|
|
934
|
+
const { renderApp } = await import("./assets/tui-DPx5MGHZ.js");
|
|
908
935
|
reset();
|
|
909
936
|
renderApp(sessionId);
|
|
910
937
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-ollama",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.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",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"@commitlint/config-conventional": "21.0.1",
|
|
55
55
|
"@eslint/config-helpers": "0.6.0",
|
|
56
56
|
"@eslint/js": "10.0.1",
|
|
57
|
-
"@types/node": "25.
|
|
57
|
+
"@types/node": "25.8.0",
|
|
58
58
|
"@types/react": "19.2.14",
|
|
59
59
|
"@vitest/coverage-v8": "4.1.6",
|
|
60
60
|
"eslint": "10.3.0",
|
|
@@ -66,10 +66,10 @@
|
|
|
66
66
|
"lint-staged": "17.0.4",
|
|
67
67
|
"prettier": "3.8.3",
|
|
68
68
|
"publint": "0.3.21",
|
|
69
|
-
"tsx": "4.
|
|
69
|
+
"tsx": "4.22.0",
|
|
70
70
|
"typescript": "6.0.3",
|
|
71
71
|
"typescript-eslint": "8.59.3",
|
|
72
|
-
"vite": "8.0.
|
|
72
|
+
"vite": "8.0.13",
|
|
73
73
|
"vitest": "4.1.6"
|
|
74
74
|
},
|
|
75
75
|
"files": [
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { exec } from "node:child_process";
|
|
2
|
-
import { promisify } from "node:util";
|
|
3
|
-
//#region \0rolldown/runtime.js
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __exportAll = (all, no_symbols) => {
|
|
6
|
-
let target = {};
|
|
7
|
-
for (var name in all) __defProp(target, name, {
|
|
8
|
-
get: all[name],
|
|
9
|
-
enumerable: true
|
|
10
|
-
});
|
|
11
|
-
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
12
|
-
return target;
|
|
13
|
-
};
|
|
14
|
-
//#endregion
|
|
15
|
-
//#region src/utils/tools/shell.ts
|
|
16
|
-
var shell_exports = /* @__PURE__ */ __exportAll({
|
|
17
|
-
execShell: () => execShell,
|
|
18
|
-
runShell: () => runShell
|
|
19
|
-
});
|
|
20
|
-
var execAsync = promisify(exec);
|
|
21
|
-
var SHELL_EXEC_OPTIONS = {
|
|
22
|
-
timeout: 3e4,
|
|
23
|
-
maxBuffer: 1024 * 1024
|
|
24
|
-
};
|
|
25
|
-
/**
|
|
26
|
-
* Execute shell command with shared options (throws on error)
|
|
27
|
-
*/
|
|
28
|
-
function execShell(command) {
|
|
29
|
-
return execAsync(command, SHELL_EXEC_OPTIONS);
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Execute shell command
|
|
33
|
-
*/
|
|
34
|
-
async function runShell(command) {
|
|
35
|
-
try {
|
|
36
|
-
const { stdout, stderr } = await execShell(command);
|
|
37
|
-
return { content: stdout || stderr };
|
|
38
|
-
} catch (error) {
|
|
39
|
-
return {
|
|
40
|
-
content: "",
|
|
41
|
-
error: `Command failed: ${error instanceof Error ? error.message : String(error)}`
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
//#endregion
|
|
46
|
-
export { shell_exports as n, runShell as t };
|