code-ollama 0.13.1 → 0.14.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-CCa_vqh0.js → tui-CoX71F7Y.js} +349 -133
- package/dist/cli.js +222 -21
- package/package.json +12 -11
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { A as LABEL, C as withSystemMessage, D as USER, E as SYSTEM, F as VERSION, I as LIST, M as SAFE, N as APPROVE, O as PLAN_GENERATION_INSTRUCTION, P as REJECT, S as resetSystemMessage, T as ASSISTANT, _ as setClearHandler, a as tick, b as loadConfig, c as appendMessage, d as deleteSessionIfEmpty, f as listSessions, g as reset, h as clear, i as WRITE_TOOLS, j as PLAN, k as AUTO, l as createSession$1, m as updateSessionModel, n as READ_TOOLS, o as color, p as loadSession, r as TOOLS, s as write, t as executeTool, u as deleteSession, v as listModels, w as HEADER_PREFIX, x as saveConfig, y as streamChat } from "../cli.js";
|
|
2
2
|
import { readdirSync } from "node:fs";
|
|
3
|
-
import { join, relative } from "node:path";
|
|
4
3
|
import { homedir } from "node:os";
|
|
4
|
+
import { join, relative } from "node:path";
|
|
5
5
|
import { exec } from "node:child_process";
|
|
6
6
|
import { Box, Static, Text, render, useApp, useInput, useStdout } from "ink";
|
|
7
7
|
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
@@ -9,43 +9,6 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
9
9
|
import { Select, Spinner } from "@inkjs/ui";
|
|
10
10
|
import { marked } from "marked";
|
|
11
11
|
import { markedTerminal } from "marked-terminal";
|
|
12
|
-
//#region src/constants/command.ts
|
|
13
|
-
var LIST = [
|
|
14
|
-
{
|
|
15
|
-
name: "/clear",
|
|
16
|
-
description: "clear the current session"
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
name: "/model",
|
|
20
|
-
description: "switch the model"
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
name: "/search",
|
|
24
|
-
description: "configure web search"
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
name: "/exit",
|
|
28
|
-
description: "exit the application"
|
|
29
|
-
}
|
|
30
|
-
];
|
|
31
|
-
//#endregion
|
|
32
|
-
//#region src/constants/decision.ts
|
|
33
|
-
var APPROVE = "approve";
|
|
34
|
-
var REJECT = "reject";
|
|
35
|
-
//#endregion
|
|
36
|
-
//#region src/constants/mode.ts
|
|
37
|
-
var SAFE = "safe";
|
|
38
|
-
var AUTO = "auto";
|
|
39
|
-
var PLAN = "plan";
|
|
40
|
-
var LABEL = {
|
|
41
|
-
safe: "Safe",
|
|
42
|
-
auto: "Auto",
|
|
43
|
-
plan: "Plan"
|
|
44
|
-
};
|
|
45
|
-
//#endregion
|
|
46
|
-
//#region src/constants/ui.ts
|
|
47
|
-
var HEADER_PREFIX = "🦙 ";
|
|
48
|
-
//#endregion
|
|
49
12
|
//#region src/components/CodeBlock/CodeBlock.tsx
|
|
50
13
|
var highlightCache = /* @__PURE__ */ new Map();
|
|
51
14
|
var CODE_BLOCK_REGEX = /^(`{3,})(\w+)?[ \t]*\n([\s\S]*?)^\1[ \t]*$/gm;
|
|
@@ -102,7 +65,7 @@ var CodeBlock = memo(function CodeBlock({ code, language, role }) {
|
|
|
102
65
|
const isSystem = role === SYSTEM;
|
|
103
66
|
return /* @__PURE__ */ jsx(Box, {
|
|
104
67
|
flexDirection: "column",
|
|
105
|
-
borderStyle: "
|
|
68
|
+
borderStyle: "bold",
|
|
106
69
|
borderColor: isSystem ? "gray" : "dim",
|
|
107
70
|
paddingX: 1,
|
|
108
71
|
marginY: 1,
|
|
@@ -345,11 +308,12 @@ function Messages({ messages, isLoading, sessionId = 0, streamingMessage }) {
|
|
|
345
308
|
}
|
|
346
309
|
//#endregion
|
|
347
310
|
//#region src/components/SelectPrompt.tsx
|
|
348
|
-
function SelectPrompt({ children, onCancel, ...selectProps }) {
|
|
311
|
+
function SelectPrompt({ borderStyle, children, onCancel, ...selectProps }) {
|
|
349
312
|
useInput((input, key) => {
|
|
350
313
|
if (key.escape || key.ctrl && input === "c") onCancel?.();
|
|
351
314
|
});
|
|
352
315
|
return /* @__PURE__ */ jsxs(Box, {
|
|
316
|
+
borderStyle,
|
|
353
317
|
flexDirection: "column",
|
|
354
318
|
children: [children, /* @__PURE__ */ jsx(Select, { ...selectProps })]
|
|
355
319
|
});
|
|
@@ -410,29 +374,32 @@ var options$1 = [
|
|
|
410
374
|
}
|
|
411
375
|
];
|
|
412
376
|
function PlanApproval({ planContent, onModeChange }) {
|
|
413
|
-
return /* @__PURE__ */ jsx(
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
children:
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
377
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
378
|
+
marginX: 2,
|
|
379
|
+
children: /* @__PURE__ */ jsx(SelectPrompt, {
|
|
380
|
+
borderStyle: "bold",
|
|
381
|
+
options: options$1,
|
|
382
|
+
onChange: useCallback((value) => {
|
|
383
|
+
onModeChange(value);
|
|
384
|
+
}, [onModeChange]),
|
|
385
|
+
onCancel: useCallback(() => {
|
|
386
|
+
onModeChange(PLAN);
|
|
387
|
+
}, [onModeChange]),
|
|
388
|
+
children: /* @__PURE__ */ jsxs(Box, {
|
|
389
|
+
flexDirection: "column",
|
|
390
|
+
children: [
|
|
391
|
+
/* @__PURE__ */ jsx(Text, {
|
|
392
|
+
bold: true,
|
|
393
|
+
color: "magenta",
|
|
394
|
+
children: "Plan Generated - Choose execution mode:"
|
|
395
|
+
}),
|
|
396
|
+
/* @__PURE__ */ jsx(Box, {
|
|
397
|
+
marginY: 1,
|
|
398
|
+
children: /* @__PURE__ */ jsx(Text, { children: planContent })
|
|
399
|
+
}),
|
|
400
|
+
/* @__PURE__ */ jsx(SelectPromptHint, { message: "Select execution mode" })
|
|
401
|
+
]
|
|
402
|
+
})
|
|
436
403
|
})
|
|
437
404
|
});
|
|
438
405
|
}
|
|
@@ -453,47 +420,51 @@ function ToolApproval({ toolCall, onDecision }) {
|
|
|
453
420
|
onDecision(REJECT);
|
|
454
421
|
}, [onDecision]);
|
|
455
422
|
const args = JSON.stringify(toolCall.function.arguments, null, 2);
|
|
456
|
-
return /* @__PURE__ */
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
423
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
424
|
+
marginX: 2,
|
|
425
|
+
children: /* @__PURE__ */ jsxs(SelectPrompt, {
|
|
426
|
+
borderStyle: "bold",
|
|
427
|
+
options,
|
|
428
|
+
onChange: handleChange,
|
|
429
|
+
onCancel: handleEscape,
|
|
430
|
+
children: [
|
|
431
|
+
/* @__PURE__ */ jsx(Text, {
|
|
432
|
+
color: "yellow",
|
|
433
|
+
children: "Tool requires approval ⚠️ "
|
|
434
|
+
}),
|
|
435
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
436
|
+
flexDirection: "column",
|
|
437
|
+
marginBottom: 1,
|
|
438
|
+
marginX: 2,
|
|
439
|
+
children: [/* @__PURE__ */ jsxs(Text, { children: [
|
|
440
|
+
/* @__PURE__ */ jsx(Text, {
|
|
441
|
+
dimColor: true,
|
|
442
|
+
children: "Tool:"
|
|
443
|
+
}),
|
|
444
|
+
" ",
|
|
445
|
+
toolCall.function.name
|
|
446
|
+
] }), /* @__PURE__ */ jsxs(Text, { children: [
|
|
447
|
+
/* @__PURE__ */ jsx(Text, {
|
|
448
|
+
dimColor: true,
|
|
449
|
+
children: "Arguments:"
|
|
450
|
+
}),
|
|
451
|
+
" ",
|
|
452
|
+
args
|
|
453
|
+
] })]
|
|
454
|
+
}),
|
|
455
|
+
/* @__PURE__ */ jsx(SelectPromptHint, {
|
|
456
|
+
message: "Select approval action",
|
|
457
|
+
escapeLabel: "reject"
|
|
458
|
+
})
|
|
459
|
+
]
|
|
460
|
+
})
|
|
490
461
|
});
|
|
491
462
|
}
|
|
492
463
|
//#endregion
|
|
493
464
|
//#region src/components/Chat/constants.ts
|
|
494
465
|
var ACTION_NOT_PERFORMED = "The requested action was NOT performed";
|
|
495
466
|
var PLAN_CHECKLIST_REMINDER = "Then display the execution plan as an unchecked Markdown checklist only";
|
|
496
|
-
var PLAN_EXECUTION_REMINDER =
|
|
467
|
+
var PLAN_EXECUTION_REMINDER = `Do not claim success and do not call ${Array.from(WRITE_TOOLS).join(", ")} until the user approves execution`;
|
|
497
468
|
var INTERRUPT_REASON = /* @__PURE__ */ function(INTERRUPT_REASON) {
|
|
498
469
|
INTERRUPT_REASON["INTERRUPTED"] = "interrupted";
|
|
499
470
|
INTERRUPT_REASON["REJECTED"] = "rejected";
|
|
@@ -501,7 +472,39 @@ var INTERRUPT_REASON = /* @__PURE__ */ function(INTERRUPT_REASON) {
|
|
|
501
472
|
}({});
|
|
502
473
|
//#endregion
|
|
503
474
|
//#region src/components/TextInput/TextInput.tsx
|
|
504
|
-
function
|
|
475
|
+
function buildLineSegments(displayValue, cursorPosition, width) {
|
|
476
|
+
const safeWidth = Math.max(1, width);
|
|
477
|
+
const cursorChar = displayValue[cursorPosition] || " ";
|
|
478
|
+
const renderValue = displayValue.slice(0, cursorPosition) + cursorChar + displayValue.slice(cursorPosition + 1);
|
|
479
|
+
const totalLength = Math.max(1, renderValue.length);
|
|
480
|
+
const lines = [];
|
|
481
|
+
for (let start = 0; start < totalLength; start += safeWidth) {
|
|
482
|
+
const end = start + safeWidth;
|
|
483
|
+
const text = renderValue.slice(start, end);
|
|
484
|
+
const hasCursor = cursorPosition >= start && cursorPosition < end;
|
|
485
|
+
if (!hasCursor) {
|
|
486
|
+
lines.push({
|
|
487
|
+
text,
|
|
488
|
+
hasCursor,
|
|
489
|
+
beforeCursor: "",
|
|
490
|
+
cursorChar: " ",
|
|
491
|
+
afterCursor: ""
|
|
492
|
+
});
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
const offset = cursorPosition - start;
|
|
496
|
+
lines.push({
|
|
497
|
+
text,
|
|
498
|
+
hasCursor,
|
|
499
|
+
beforeCursor: text.slice(0, offset),
|
|
500
|
+
cursorChar: text[offset] || " ",
|
|
501
|
+
afterCursor: text.slice(offset + 1)
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
return lines;
|
|
505
|
+
}
|
|
506
|
+
function TextInput({ value, isDisabled = false, placeholder, cursorPosition: externalCursorPosition, wrapIndent = 0, onChange, onSubmit }) {
|
|
507
|
+
const { stdout } = useStdout();
|
|
505
508
|
const [cursorPosition, setCursorPosition] = useState(value.length);
|
|
506
509
|
const prevValueRef = useRef(value);
|
|
507
510
|
const prevExternalCursorRef = useRef(externalCursorPosition);
|
|
@@ -559,6 +562,14 @@ function TextInput({ value, isDisabled = false, placeholder, cursorPosition: ext
|
|
|
559
562
|
setCursorPosition(value.length);
|
|
560
563
|
return;
|
|
561
564
|
}
|
|
565
|
+
if (key.ctrl && input === "a") {
|
|
566
|
+
setCursorPosition(0);
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
if (key.ctrl && input === "e") {
|
|
570
|
+
setCursorPosition(value.length);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
562
573
|
// v8 ignore start
|
|
563
574
|
if (input) {
|
|
564
575
|
onChange(value.slice(0, cursorPosition) + input + value.slice(cursorPosition));
|
|
@@ -568,23 +579,31 @@ function TextInput({ value, isDisabled = false, placeholder, cursorPosition: ext
|
|
|
568
579
|
}, { isActive: !isDisabled });
|
|
569
580
|
const displayValue = value || (placeholder ?? "");
|
|
570
581
|
const isPlaceholder = Boolean(!value && placeholder);
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
582
|
+
const availableWidth = Math.max(1, stdout.columns - wrapIndent);
|
|
583
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
584
|
+
flexDirection: "column",
|
|
585
|
+
children: useMemo(() => buildLineSegments(displayValue, cursorPosition, availableWidth), [
|
|
586
|
+
availableWidth,
|
|
587
|
+
cursorPosition,
|
|
588
|
+
displayValue
|
|
589
|
+
]).map((line, index) => /* @__PURE__ */ jsx(Text, { children: line.hasCursor ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
590
|
+
/* @__PURE__ */ jsx(Text, {
|
|
591
|
+
dimColor: isPlaceholder,
|
|
592
|
+
children: line.beforeCursor
|
|
593
|
+
}),
|
|
594
|
+
/* @__PURE__ */ jsx(Text, {
|
|
595
|
+
inverse: true,
|
|
596
|
+
children: line.cursorChar
|
|
597
|
+
}),
|
|
598
|
+
/* @__PURE__ */ jsx(Text, {
|
|
599
|
+
dimColor: isPlaceholder,
|
|
600
|
+
children: line.afterCursor
|
|
601
|
+
})
|
|
602
|
+
] }) : /* @__PURE__ */ jsx(Text, {
|
|
584
603
|
dimColor: isPlaceholder,
|
|
585
|
-
children:
|
|
586
|
-
})
|
|
587
|
-
|
|
604
|
+
children: line.text
|
|
605
|
+
}) }, `${String(index)}-${line.text}`))
|
|
606
|
+
});
|
|
588
607
|
}
|
|
589
608
|
//#endregion
|
|
590
609
|
//#region src/components/Chat/CommandMenu.tsx
|
|
@@ -794,8 +813,7 @@ function Input({ isDisabled = false, onInterrupt, onSubmit }) {
|
|
|
794
813
|
}, [onSubmit, resetInput]);
|
|
795
814
|
const showCommandMenu = input.startsWith("/");
|
|
796
815
|
const showFileSuggestions = !showCommandMenu && hasFileSuggestionQuery(input);
|
|
797
|
-
const handleSubmitText = useCallback(
|
|
798
|
-
await tick();
|
|
816
|
+
const handleSubmitText = useCallback((input) => {
|
|
799
817
|
if (input.startsWith("/")) return;
|
|
800
818
|
if (hasFileSuggestionQuery(input)) {
|
|
801
819
|
if (fileSuggestionRef.current) handleSelectFileSuggestion(fileSuggestionRef.current);
|
|
@@ -827,6 +845,7 @@ function Input({ isDisabled = false, onInterrupt, onSubmit }) {
|
|
|
827
845
|
value: input,
|
|
828
846
|
isDisabled,
|
|
829
847
|
cursorPosition,
|
|
848
|
+
wrapIndent: 2,
|
|
830
849
|
onChange: setInput,
|
|
831
850
|
onSubmit: handleSubmitText,
|
|
832
851
|
placeholder: "Ask anything... (/ commands, @ files)"
|
|
@@ -854,22 +873,31 @@ function hasExecutablePlan(content) {
|
|
|
854
873
|
}
|
|
855
874
|
//#endregion
|
|
856
875
|
//#region src/components/Chat/Chat.tsx
|
|
857
|
-
function Chat({ model, onCommand, mode, onModeChange, sessionId }) {
|
|
858
|
-
const
|
|
876
|
+
function Chat({ initialMessages, model, onCommand, onMessagesChange, mode, onModeChange, sessionId }) {
|
|
877
|
+
const sessionMessages = initialMessages ?? [];
|
|
878
|
+
const [messages, setMessages] = useState(sessionMessages);
|
|
859
879
|
const [streamingMessage, setStreamingMessage] = useState(null);
|
|
860
880
|
const [isLoading, setIsLoading] = useState(false);
|
|
861
881
|
const [pendingToolCall, setPendingToolCall] = useState(null);
|
|
862
882
|
const [pendingPlan, setPendingPlan] = useState(null);
|
|
863
883
|
const [interruptReason, setInterruptReason] = useState(null);
|
|
864
884
|
const abortControllerRef = useRef(null);
|
|
885
|
+
const persistedSnapshotRef = useRef("");
|
|
865
886
|
useEffect(() => {
|
|
866
|
-
setMessages(
|
|
887
|
+
setMessages(sessionMessages);
|
|
867
888
|
setStreamingMessage(null);
|
|
868
889
|
setIsLoading(false);
|
|
869
890
|
setPendingToolCall(null);
|
|
870
891
|
setPendingPlan(null);
|
|
871
892
|
setInterruptReason(null);
|
|
893
|
+
persistedSnapshotRef.current = JSON.stringify(sessionMessages);
|
|
872
894
|
}, [sessionId]);
|
|
895
|
+
useEffect(() => {
|
|
896
|
+
const snapshot = JSON.stringify(messages);
|
|
897
|
+
if (snapshot === persistedSnapshotRef.current) return;
|
|
898
|
+
persistedSnapshotRef.current = snapshot;
|
|
899
|
+
onMessagesChange?.(messages);
|
|
900
|
+
}, [messages, onMessagesChange]);
|
|
873
901
|
const buildToolResultMessage = useCallback((toolName, result) => {
|
|
874
902
|
if (result.error?.startsWith("Tool not allowed:")) return {
|
|
875
903
|
role: SYSTEM,
|
|
@@ -1257,7 +1285,7 @@ function Header({ model, onLoad }) {
|
|
|
1257
1285
|
return /* @__PURE__ */ jsx(Static, {
|
|
1258
1286
|
items: [0],
|
|
1259
1287
|
children: (key) => /* @__PURE__ */ jsxs(Box, {
|
|
1260
|
-
borderStyle: "
|
|
1288
|
+
borderStyle: "bold",
|
|
1261
1289
|
flexDirection: "column",
|
|
1262
1290
|
paddingX: 1,
|
|
1263
1291
|
children: [
|
|
@@ -1423,6 +1451,7 @@ function SearchSettings({ currentUrl, onClose, onSave }) {
|
|
|
1423
1451
|
/* @__PURE__ */ jsx(Text, { children: "Set the SearXNG base URL. DuckDuckGo remains the fallback." }),
|
|
1424
1452
|
/* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, { children: "> " }), /* @__PURE__ */ jsx(TextInput, {
|
|
1425
1453
|
value: draftUrl,
|
|
1454
|
+
wrapIndent: 2,
|
|
1426
1455
|
onChange: setDraftUrl,
|
|
1427
1456
|
onSubmit: handleSubmit,
|
|
1428
1457
|
placeholder: "http://localhost:8080"
|
|
@@ -1452,48 +1481,224 @@ function SearchSettings({ currentUrl, onClose, onSave }) {
|
|
|
1452
1481
|
});
|
|
1453
1482
|
}
|
|
1454
1483
|
//#endregion
|
|
1484
|
+
//#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
|
+
var ACTION = {
|
|
1491
|
+
BACK: "back",
|
|
1492
|
+
CLOSE: "close",
|
|
1493
|
+
DELETE_MENU: "delete-menu",
|
|
1494
|
+
DELETE_PREFIX: "delete:",
|
|
1495
|
+
NEW: "new",
|
|
1496
|
+
OPEN_PREFIX: "open:"
|
|
1497
|
+
};
|
|
1498
|
+
function formatSessionLabel(session) {
|
|
1499
|
+
const timestamp = new Date(session.updatedAt).toLocaleString();
|
|
1500
|
+
return `${session.title} (${timestamp})`;
|
|
1501
|
+
}
|
|
1502
|
+
function SessionManager({ currentSessionId, onClose, onDelete, onNew, onOpen }) {
|
|
1503
|
+
const [view, setView] = useState(VIEW.MAIN);
|
|
1504
|
+
const [error, setError] = useState();
|
|
1505
|
+
const [, refreshSessionList] = useState(0);
|
|
1506
|
+
const sessions = listSessions();
|
|
1507
|
+
const options = view === VIEW.DELETE ? [...sessions.filter(({ id }) => id !== currentSessionId).map((session) => ({
|
|
1508
|
+
label: `Delete ${formatSessionLabel(session)}`,
|
|
1509
|
+
value: `${ACTION.DELETE_PREFIX}${session.id}`
|
|
1510
|
+
})), {
|
|
1511
|
+
label: "Back",
|
|
1512
|
+
value: ACTION.BACK
|
|
1513
|
+
}] : [
|
|
1514
|
+
{
|
|
1515
|
+
label: "Start new session",
|
|
1516
|
+
value: ACTION.NEW
|
|
1517
|
+
},
|
|
1518
|
+
...sessions.map((session) => ({
|
|
1519
|
+
label: `${session.id === currentSessionId ? "Current: " : ""}${formatSessionLabel(session)}`,
|
|
1520
|
+
value: `${ACTION.OPEN_PREFIX}${session.id}`
|
|
1521
|
+
})),
|
|
1522
|
+
{
|
|
1523
|
+
label: "Delete a session",
|
|
1524
|
+
value: ACTION.DELETE_MENU
|
|
1525
|
+
},
|
|
1526
|
+
{
|
|
1527
|
+
label: "Close",
|
|
1528
|
+
value: ACTION.CLOSE
|
|
1529
|
+
}
|
|
1530
|
+
];
|
|
1531
|
+
const handleChange = useCallback((value) => {
|
|
1532
|
+
switch (true) {
|
|
1533
|
+
case value === ACTION.CLOSE:
|
|
1534
|
+
onClose();
|
|
1535
|
+
break;
|
|
1536
|
+
case value === ACTION.NEW:
|
|
1537
|
+
onNew();
|
|
1538
|
+
break;
|
|
1539
|
+
case value === ACTION.DELETE_MENU:
|
|
1540
|
+
setView(VIEW.DELETE);
|
|
1541
|
+
break;
|
|
1542
|
+
case value === ACTION.BACK:
|
|
1543
|
+
setView(VIEW.MAIN);
|
|
1544
|
+
break;
|
|
1545
|
+
case value.startsWith(ACTION.DELETE_PREFIX):
|
|
1546
|
+
try {
|
|
1547
|
+
onDelete(value.slice(ACTION.DELETE_PREFIX.length));
|
|
1548
|
+
setError(void 0);
|
|
1549
|
+
refreshSessionList((key) => key + 1);
|
|
1550
|
+
} catch (error) {
|
|
1551
|
+
setError(error instanceof Error ? error.message : "Failed to delete session");
|
|
1552
|
+
}
|
|
1553
|
+
break;
|
|
1554
|
+
case value.startsWith(ACTION.OPEN_PREFIX):
|
|
1555
|
+
try {
|
|
1556
|
+
onOpen(value.slice(ACTION.OPEN_PREFIX.length));
|
|
1557
|
+
setError(void 0);
|
|
1558
|
+
} catch (error) {
|
|
1559
|
+
setError(error instanceof Error ? error.message : "Failed to open session");
|
|
1560
|
+
}
|
|
1561
|
+
break;
|
|
1562
|
+
}
|
|
1563
|
+
}, [
|
|
1564
|
+
onClose,
|
|
1565
|
+
onDelete,
|
|
1566
|
+
onNew,
|
|
1567
|
+
onOpen
|
|
1568
|
+
]);
|
|
1569
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
1570
|
+
flexDirection: "column",
|
|
1571
|
+
children: [
|
|
1572
|
+
/* @__PURE__ */ jsx(Text, { children: "Sessions" }),
|
|
1573
|
+
/* @__PURE__ */ jsx(SelectPromptHint, { message: view === VIEW.DELETE ? "Delete session" : "Select session" }),
|
|
1574
|
+
error && /* @__PURE__ */ jsx(Box, {
|
|
1575
|
+
marginBottom: 1,
|
|
1576
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1577
|
+
color: "red",
|
|
1578
|
+
children: error
|
|
1579
|
+
})
|
|
1580
|
+
}),
|
|
1581
|
+
/* @__PURE__ */ jsx(SelectPrompt, {
|
|
1582
|
+
options,
|
|
1583
|
+
onCancel: onClose,
|
|
1584
|
+
onChange: handleChange
|
|
1585
|
+
}, `${view}:${String(sessions.length)}`)
|
|
1586
|
+
]
|
|
1587
|
+
});
|
|
1588
|
+
}
|
|
1589
|
+
//#endregion
|
|
1455
1590
|
//#region src/components/App.tsx
|
|
1456
1591
|
var SCREEN = /* @__PURE__ */ function(SCREEN) {
|
|
1457
1592
|
SCREEN["CHAT"] = "chat";
|
|
1458
1593
|
SCREEN["MODEL_PICKER"] = "model-picker";
|
|
1459
1594
|
SCREEN["SEARCH_SETTINGS"] = "search-settings";
|
|
1595
|
+
SCREEN["SESSION_MANAGER"] = "session-manager";
|
|
1460
1596
|
return SCREEN;
|
|
1461
1597
|
}(SCREEN || {});
|
|
1462
|
-
function
|
|
1598
|
+
function createSession(sessionId, model) {
|
|
1599
|
+
return sessionId ? loadSession(sessionId) : createSession$1(model);
|
|
1600
|
+
}
|
|
1601
|
+
function App({ sessionId }) {
|
|
1463
1602
|
const { exit } = useApp();
|
|
1464
1603
|
const [appConfig, setConfig] = useState(() => loadConfig());
|
|
1465
1604
|
const [currentScreen, setScreen] = useState(SCREEN.CHAT);
|
|
1466
1605
|
const [mode, setMode] = useState(SAFE);
|
|
1467
|
-
const [
|
|
1606
|
+
const [activeSession, setSession] = useState(() => createSession(sessionId, loadConfig().model));
|
|
1468
1607
|
const [isHeaderLoaded, setIsHeaderLoaded] = useState(false);
|
|
1608
|
+
const sessionRef = useRef(activeSession);
|
|
1609
|
+
useEffect(() => {
|
|
1610
|
+
sessionRef.current = activeSession;
|
|
1611
|
+
}, [activeSession]);
|
|
1612
|
+
useEffect(() => {
|
|
1613
|
+
return () => {
|
|
1614
|
+
const currentSession = sessionRef.current;
|
|
1615
|
+
if (!deleteSessionIfEmpty(currentSession.metadata.id) && currentSession.messages.length > 0) write(`Resume session: ${color(`code-ollama resume ${currentSession.metadata.id}`, "cyan")}\n`);
|
|
1616
|
+
};
|
|
1617
|
+
}, []);
|
|
1618
|
+
const setActiveSession = useCallback((nextSession) => {
|
|
1619
|
+
setSession((current) => {
|
|
1620
|
+
deleteSessionIfEmpty(current.metadata.id);
|
|
1621
|
+
return nextSession;
|
|
1622
|
+
});
|
|
1623
|
+
}, []);
|
|
1469
1624
|
const handleHeaderLoad = useCallback(() => {
|
|
1470
1625
|
setIsHeaderLoaded(true);
|
|
1471
1626
|
}, []);
|
|
1627
|
+
const handleCreateSession = useCallback(() => {
|
|
1628
|
+
const nextSession = createSession$1(appConfig.model);
|
|
1629
|
+
setActiveSession(nextSession);
|
|
1630
|
+
setScreen(SCREEN.CHAT);
|
|
1631
|
+
clear(nextSession.metadata.id);
|
|
1632
|
+
return nextSession;
|
|
1633
|
+
}, [appConfig.model, setActiveSession]);
|
|
1634
|
+
const handleOpenSession = useCallback((sessionId) => {
|
|
1635
|
+
if (sessionRef.current.metadata.id === sessionId) {
|
|
1636
|
+
setScreen(SCREEN.CHAT);
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1639
|
+
setActiveSession(loadSession(sessionId));
|
|
1640
|
+
setScreen(SCREEN.CHAT);
|
|
1641
|
+
clear(sessionId);
|
|
1642
|
+
}, [setActiveSession]);
|
|
1643
|
+
const handleDeleteSession = useCallback((sessionId) => {
|
|
1644
|
+
deleteSession(sessionId);
|
|
1645
|
+
setSession((current) => {
|
|
1646
|
+
if (current.metadata.id !== sessionId) return current;
|
|
1647
|
+
return createSession$1(appConfig.model);
|
|
1648
|
+
});
|
|
1649
|
+
setScreen(SCREEN.SESSION_MANAGER);
|
|
1650
|
+
}, [appConfig.model]);
|
|
1651
|
+
const handleMessagesChange = useCallback((messages) => {
|
|
1652
|
+
setSession((current) => {
|
|
1653
|
+
const persistedMessages = messages.filter(({ content }) => content !== TURN_ABORTED_MESSAGE);
|
|
1654
|
+
if (persistedMessages.length <= current.messages.length) return current;
|
|
1655
|
+
let metadata = current.metadata;
|
|
1656
|
+
for (const message of persistedMessages.slice(current.messages.length)) metadata = appendMessage(metadata.id, message, appConfig.model);
|
|
1657
|
+
return {
|
|
1658
|
+
metadata,
|
|
1659
|
+
messages: persistedMessages
|
|
1660
|
+
};
|
|
1661
|
+
});
|
|
1662
|
+
}, [appConfig.model]);
|
|
1472
1663
|
const handleCommand = useCallback((command) => {
|
|
1473
1664
|
switch (command) {
|
|
1665
|
+
case "/session":
|
|
1666
|
+
setScreen(SCREEN.SESSION_MANAGER);
|
|
1667
|
+
break;
|
|
1474
1668
|
case "/model":
|
|
1475
1669
|
setScreen(SCREEN.MODEL_PICKER);
|
|
1476
1670
|
break;
|
|
1477
1671
|
case "/search":
|
|
1478
1672
|
setScreen(SCREEN.SEARCH_SETTINGS);
|
|
1479
1673
|
break;
|
|
1480
|
-
case "/clear":
|
|
1674
|
+
case "/clear": {
|
|
1481
1675
|
resetSystemMessage();
|
|
1482
|
-
clear();
|
|
1483
1676
|
setScreen(SCREEN.CHAT);
|
|
1484
|
-
|
|
1677
|
+
const nextSession = createSession$1(appConfig.model);
|
|
1678
|
+
setActiveSession(nextSession);
|
|
1679
|
+
clear(nextSession.metadata.id);
|
|
1485
1680
|
break;
|
|
1681
|
+
}
|
|
1486
1682
|
case "/exit":
|
|
1487
1683
|
exit();
|
|
1488
1684
|
break;
|
|
1489
1685
|
}
|
|
1490
|
-
}, [
|
|
1686
|
+
}, [
|
|
1687
|
+
appConfig.model,
|
|
1688
|
+
exit,
|
|
1689
|
+
setActiveSession
|
|
1690
|
+
]);
|
|
1491
1691
|
const handleUpdateConfig = useCallback((update) => {
|
|
1492
1692
|
setConfig((current) => ({
|
|
1493
1693
|
...current,
|
|
1494
1694
|
...update
|
|
1495
1695
|
}));
|
|
1496
1696
|
saveConfig(update);
|
|
1697
|
+
const newModel = update.model;
|
|
1698
|
+
if (newModel) setSession((current) => ({
|
|
1699
|
+
...current,
|
|
1700
|
+
metadata: updateSessionModel(current.metadata.id, newModel)
|
|
1701
|
+
}));
|
|
1497
1702
|
setScreen(SCREEN.CHAT);
|
|
1498
1703
|
}, []);
|
|
1499
1704
|
const handleClose = useCallback(() => {
|
|
@@ -1525,13 +1730,24 @@ function App() {
|
|
|
1525
1730
|
onClose: handleClose
|
|
1526
1731
|
});
|
|
1527
1732
|
break;
|
|
1733
|
+
case SCREEN.SESSION_MANAGER:
|
|
1734
|
+
screenContent = /* @__PURE__ */ jsx(SessionManager, {
|
|
1735
|
+
currentSessionId: activeSession.metadata.id,
|
|
1736
|
+
onClose: handleClose,
|
|
1737
|
+
onDelete: handleDeleteSession,
|
|
1738
|
+
onNew: handleCreateSession,
|
|
1739
|
+
onOpen: handleOpenSession
|
|
1740
|
+
});
|
|
1741
|
+
break;
|
|
1528
1742
|
case SCREEN.CHAT:
|
|
1529
1743
|
screenContent = /* @__PURE__ */ jsx(Chat, {
|
|
1744
|
+
initialMessages: activeSession.messages,
|
|
1530
1745
|
model: appConfig.model,
|
|
1531
1746
|
onCommand: handleCommand,
|
|
1747
|
+
onMessagesChange: handleMessagesChange,
|
|
1532
1748
|
mode,
|
|
1533
1749
|
onModeChange: setMode,
|
|
1534
|
-
sessionId
|
|
1750
|
+
sessionId: activeSession.metadata.id
|
|
1535
1751
|
});
|
|
1536
1752
|
break;
|
|
1537
1753
|
}
|
|
@@ -1553,15 +1769,15 @@ function App() {
|
|
|
1553
1769
|
}
|
|
1554
1770
|
//#endregion
|
|
1555
1771
|
//#region src/tui.tsx
|
|
1556
|
-
function renderApp() {
|
|
1772
|
+
function renderApp(sessionId) {
|
|
1557
1773
|
let resetKey = 0;
|
|
1558
|
-
const app = render(/* @__PURE__ */ jsx(App, {}, resetKey), {
|
|
1774
|
+
const app = render(/* @__PURE__ */ jsx(App, { sessionId }, resetKey), {
|
|
1559
1775
|
exitOnCtrlC: false,
|
|
1560
1776
|
maxFps: 60
|
|
1561
1777
|
});
|
|
1562
|
-
setClearHandler(() => {
|
|
1778
|
+
setClearHandler((nextSessionId) => {
|
|
1563
1779
|
reset();
|
|
1564
|
-
app.rerender(/* @__PURE__ */ jsx(App, {}, ++resetKey));
|
|
1780
|
+
app.rerender(/* @__PURE__ */ jsx(App, { sessionId: nextSessionId ?? sessionId }, ++resetKey));
|
|
1565
1781
|
});
|
|
1566
1782
|
}
|
|
1567
1783
|
//#endregion
|
package/dist/cli.js
CHANGED
|
@@ -1,18 +1,60 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { t as runShell } from "./assets/shell-CipXM_WI.js";
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
4
4
|
import cac from "cac";
|
|
5
|
-
import { join } from "node:path";
|
|
6
5
|
import { homedir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
7
|
import { Ollama } from "ollama";
|
|
8
|
+
import { v7 } from "uuid";
|
|
9
|
+
//#region src/constants/command.ts
|
|
10
|
+
var LIST = [
|
|
11
|
+
{
|
|
12
|
+
name: "/clear",
|
|
13
|
+
description: "clear the current session"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: "/session",
|
|
17
|
+
description: "manage sessions"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: "/model",
|
|
21
|
+
description: "switch the model"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: "/search",
|
|
25
|
+
description: "configure web search"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "/exit",
|
|
29
|
+
description: "exit the application"
|
|
30
|
+
}
|
|
31
|
+
];
|
|
32
|
+
//#endregion
|
|
8
33
|
//#region package.json
|
|
9
34
|
var name = "code-ollama";
|
|
10
|
-
var version = "0.
|
|
35
|
+
var version = "0.14.1";
|
|
11
36
|
//#endregion
|
|
12
37
|
//#region src/constants/package.ts
|
|
13
38
|
var NAME = name;
|
|
14
39
|
var VERSION = version;
|
|
15
40
|
//#endregion
|
|
41
|
+
//#region src/constants/config.ts
|
|
42
|
+
var DIRECTORY = join(homedir(), `.${NAME}`);
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/constants/decision.ts
|
|
45
|
+
var APPROVE = "approve";
|
|
46
|
+
var REJECT = "reject";
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/constants/mode.ts
|
|
49
|
+
var SAFE = "safe";
|
|
50
|
+
var AUTO = "auto";
|
|
51
|
+
var PLAN = "plan";
|
|
52
|
+
var LABEL = {
|
|
53
|
+
safe: "Safe",
|
|
54
|
+
auto: "Auto",
|
|
55
|
+
plan: "Plan"
|
|
56
|
+
};
|
|
57
|
+
//#endregion
|
|
16
58
|
//#region src/constants/prompt.ts
|
|
17
59
|
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, searching code, and searching the web
|
|
18
60
|
|
|
@@ -70,6 +112,9 @@ var VIEW_RANGE = "view_range";
|
|
|
70
112
|
var WEB_SEARCH = "web_search";
|
|
71
113
|
var WEB_FETCH = "web_fetch";
|
|
72
114
|
//#endregion
|
|
115
|
+
//#region src/constants/ui.ts
|
|
116
|
+
var HEADER_PREFIX = "🦙 ";
|
|
117
|
+
//#endregion
|
|
73
118
|
//#region src/utils/agents.ts
|
|
74
119
|
var AGENTS_FILE = "AGENTS.md";
|
|
75
120
|
function loadAgentsContent() {
|
|
@@ -104,8 +149,7 @@ function withSystemMessage(messages) {
|
|
|
104
149
|
}
|
|
105
150
|
//#endregion
|
|
106
151
|
//#region src/utils/config.ts
|
|
107
|
-
var
|
|
108
|
-
var CONFIG_PATH = join(CONFIG_DIRECTORY, "config.json");
|
|
152
|
+
var CONFIG_PATH = join(DIRECTORY, "config.json");
|
|
109
153
|
var DEFAULT_HOST = "http://localhost:11434";
|
|
110
154
|
var DEFAULT_MODEL$1 = "gemma4";
|
|
111
155
|
function readFile$1() {
|
|
@@ -129,7 +173,7 @@ function saveConfig(patch) {
|
|
|
129
173
|
...readFile$1(),
|
|
130
174
|
...patch
|
|
131
175
|
};
|
|
132
|
-
mkdirSync(
|
|
176
|
+
mkdirSync(DIRECTORY, { recursive: true });
|
|
133
177
|
writeFileSync(CONFIG_PATH, JSON.stringify(updated, null, 2) + "\n", "utf8");
|
|
134
178
|
}
|
|
135
179
|
//#endregion
|
|
@@ -177,8 +221,8 @@ function setClearHandler(handler) {
|
|
|
177
221
|
/**
|
|
178
222
|
* Clear the screen with Ink.
|
|
179
223
|
*/
|
|
180
|
-
function clear() {
|
|
181
|
-
clearHandler?.();
|
|
224
|
+
function clear(sessionId) {
|
|
225
|
+
clearHandler?.(sessionId);
|
|
182
226
|
}
|
|
183
227
|
/**
|
|
184
228
|
* Reset the screen with ANSI escape sequence.
|
|
@@ -187,6 +231,156 @@ function reset() {
|
|
|
187
231
|
process.stdout.write("\x1Bc\x1B[?25l");
|
|
188
232
|
}
|
|
189
233
|
//#endregion
|
|
234
|
+
//#region src/utils/session.ts
|
|
235
|
+
var SESSIONS_DIRECTORY = join(DIRECTORY, "sessions");
|
|
236
|
+
var METADATA_FILE_NAME = "metadata.json";
|
|
237
|
+
var MESSAGES_FILE_NAME = "messages.jsonl";
|
|
238
|
+
var DEFAULT_TITLE = "New session";
|
|
239
|
+
var TITLE_MAX_LENGTH = 80;
|
|
240
|
+
function getSessionDirectory(id) {
|
|
241
|
+
return join(SESSIONS_DIRECTORY, id);
|
|
242
|
+
}
|
|
243
|
+
function getMetadataPath(id) {
|
|
244
|
+
return join(getSessionDirectory(id), METADATA_FILE_NAME);
|
|
245
|
+
}
|
|
246
|
+
function getMessagesPath(id) {
|
|
247
|
+
return join(getSessionDirectory(id), MESSAGES_FILE_NAME);
|
|
248
|
+
}
|
|
249
|
+
function ensureSessionsDirectory() {
|
|
250
|
+
mkdirSync(SESSIONS_DIRECTORY, { recursive: true });
|
|
251
|
+
}
|
|
252
|
+
function ensureSessionDirectory(id) {
|
|
253
|
+
const directory = getSessionDirectory(id);
|
|
254
|
+
mkdirSync(directory, { recursive: true });
|
|
255
|
+
return directory;
|
|
256
|
+
}
|
|
257
|
+
function readMetadata(id) {
|
|
258
|
+
const path = getMetadataPath(id);
|
|
259
|
+
if (!existsSync(path)) throw new Error(`Session not found: ${id}`);
|
|
260
|
+
try {
|
|
261
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
262
|
+
} catch {
|
|
263
|
+
throw new Error(`Invalid session metadata: ${id}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function writeMetadata(metadata) {
|
|
267
|
+
ensureSessionDirectory(metadata.id);
|
|
268
|
+
writeFileSync(getMetadataPath(metadata.id), JSON.stringify(metadata, null, 2) + "\n", "utf8");
|
|
269
|
+
}
|
|
270
|
+
function readMessages(id) {
|
|
271
|
+
const path = getMessagesPath(id);
|
|
272
|
+
if (!existsSync(path)) return [];
|
|
273
|
+
const content = readFileSync(path, "utf8").trim();
|
|
274
|
+
if (!content) return [];
|
|
275
|
+
try {
|
|
276
|
+
return content.split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
277
|
+
} catch {
|
|
278
|
+
throw new Error(`Invalid session messages: ${id}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function deriveTitle(message) {
|
|
282
|
+
// v8 ignore next - title derivation is only reached for user messages
|
|
283
|
+
if (message.role !== "user") return DEFAULT_TITLE;
|
|
284
|
+
const normalized = message.content.replace(/\s+/g, " ").trim();
|
|
285
|
+
if (!normalized) return DEFAULT_TITLE;
|
|
286
|
+
return normalized.length > TITLE_MAX_LENGTH ? normalized.slice(0, TITLE_MAX_LENGTH - 1).trimEnd() + "…" : normalized;
|
|
287
|
+
}
|
|
288
|
+
function updateTitle(metadata, message) {
|
|
289
|
+
if (metadata.title !== DEFAULT_TITLE || message.role !== "user") return metadata;
|
|
290
|
+
return {
|
|
291
|
+
...metadata,
|
|
292
|
+
title: deriveTitle(message)
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
function createSession(model) {
|
|
296
|
+
ensureSessionsDirectory();
|
|
297
|
+
const id = v7();
|
|
298
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
299
|
+
const metadata = {
|
|
300
|
+
id,
|
|
301
|
+
createdAt: now,
|
|
302
|
+
updatedAt: now,
|
|
303
|
+
title: DEFAULT_TITLE,
|
|
304
|
+
model
|
|
305
|
+
};
|
|
306
|
+
ensureSessionDirectory(id);
|
|
307
|
+
writeMetadata(metadata);
|
|
308
|
+
writeFileSync(getMessagesPath(id), "", "utf8");
|
|
309
|
+
return {
|
|
310
|
+
metadata,
|
|
311
|
+
messages: []
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
function listSessions() {
|
|
315
|
+
if (!existsSync(SESSIONS_DIRECTORY)) return [];
|
|
316
|
+
return readdirSync(SESSIONS_DIRECTORY, { withFileTypes: true }).filter((entry) => entry.isDirectory()).flatMap((entry) => {
|
|
317
|
+
try {
|
|
318
|
+
return [readMetadata(entry.name)];
|
|
319
|
+
} catch {
|
|
320
|
+
return [];
|
|
321
|
+
}
|
|
322
|
+
}).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
323
|
+
}
|
|
324
|
+
function loadSession(id) {
|
|
325
|
+
return {
|
|
326
|
+
metadata: readMetadata(id),
|
|
327
|
+
messages: readMessages(id)
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
function appendMessage(id, message, model) {
|
|
331
|
+
ensureSessionDirectory(id);
|
|
332
|
+
let metadata = readMetadata(id);
|
|
333
|
+
metadata = updateTitle(metadata, message);
|
|
334
|
+
metadata = {
|
|
335
|
+
...metadata,
|
|
336
|
+
model,
|
|
337
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
338
|
+
};
|
|
339
|
+
appendFileSync(getMessagesPath(id), JSON.stringify(message) + "\n", "utf8");
|
|
340
|
+
writeMetadata(metadata);
|
|
341
|
+
return metadata;
|
|
342
|
+
}
|
|
343
|
+
function updateSessionModel(id, model) {
|
|
344
|
+
const metadata = {
|
|
345
|
+
...readMetadata(id),
|
|
346
|
+
model
|
|
347
|
+
};
|
|
348
|
+
writeMetadata(metadata);
|
|
349
|
+
return metadata;
|
|
350
|
+
}
|
|
351
|
+
function deleteSessionIfEmpty(id) {
|
|
352
|
+
const directory = getSessionDirectory(id);
|
|
353
|
+
if (!existsSync(directory)) return false;
|
|
354
|
+
const messagesPath = getMessagesPath(id);
|
|
355
|
+
if (existsSync(messagesPath) && readFileSync(messagesPath, "utf8").trim() !== "") return false;
|
|
356
|
+
rmSync(directory, {
|
|
357
|
+
recursive: true,
|
|
358
|
+
force: false
|
|
359
|
+
});
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
function deleteSession(id) {
|
|
363
|
+
const directory = getSessionDirectory(id);
|
|
364
|
+
if (!existsSync(directory)) throw new Error(`Session not found: ${id}`);
|
|
365
|
+
rmSync(directory, {
|
|
366
|
+
recursive: true,
|
|
367
|
+
force: false
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
//#endregion
|
|
371
|
+
//#region src/utils/terminal.ts
|
|
372
|
+
var ANSI_COLOR = { cyan: ["\x1B[36m", "\x1B[39m"] };
|
|
373
|
+
function color(text, name) {
|
|
374
|
+
const [open, close] = ANSI_COLOR[name];
|
|
375
|
+
return `${open}${text}${close}`;
|
|
376
|
+
}
|
|
377
|
+
function write(text) {
|
|
378
|
+
process.stdout.write(text);
|
|
379
|
+
}
|
|
380
|
+
function writeError(text) {
|
|
381
|
+
process.stderr.write(text);
|
|
382
|
+
}
|
|
383
|
+
//#endregion
|
|
190
384
|
//#region src/utils/time.ts
|
|
191
385
|
var tick = (ms = 0) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
192
386
|
//#endregion
|
|
@@ -655,9 +849,16 @@ cli.command("run <model> <prompt>", "Run a one-off prompt").action(async (model,
|
|
|
655
849
|
try {
|
|
656
850
|
await runPrompt(model, prompt);
|
|
657
851
|
} catch (error) {
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
852
|
+
writeError(`Error: ${error instanceof Error ? error.message : "Unknown error"}\n`);
|
|
853
|
+
process.exitCode = 1;
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
cli.command("resume <sessionId>", "Resume a saved session").action(async (sessionId) => {
|
|
857
|
+
try {
|
|
858
|
+
loadSession(sessionId);
|
|
859
|
+
await launchTui(sessionId);
|
|
860
|
+
} catch (error) {
|
|
861
|
+
writeError(`Error: ${error instanceof Error ? error.message : "Unknown error"}\n`);
|
|
661
862
|
process.exitCode = 1;
|
|
662
863
|
}
|
|
663
864
|
});
|
|
@@ -666,7 +867,7 @@ async function runPrompt(model, prompt) {
|
|
|
666
867
|
role: USER,
|
|
667
868
|
content: prompt
|
|
668
869
|
}], model);
|
|
669
|
-
|
|
870
|
+
write("\n");
|
|
670
871
|
}
|
|
671
872
|
async function processRunStream(messages, model) {
|
|
672
873
|
const assistantMessage = {
|
|
@@ -676,7 +877,7 @@ async function processRunStream(messages, model) {
|
|
|
676
877
|
for await (const chunk of streamChat(messages, model, TOOLS)) {
|
|
677
878
|
if (chunk.type === "content") {
|
|
678
879
|
assistantMessage.content += chunk.content;
|
|
679
|
-
|
|
880
|
+
write(chunk.content);
|
|
680
881
|
continue;
|
|
681
882
|
}
|
|
682
883
|
for (const toolCall of chunk.tool_calls) {
|
|
@@ -695,17 +896,17 @@ async function processRunStream(messages, model) {
|
|
|
695
896
|
}
|
|
696
897
|
}
|
|
697
898
|
async function main(args = process.argv.slice(2)) {
|
|
698
|
-
if (
|
|
699
|
-
const { renderApp } = await import("./assets/tui-CCa_vqh0.js");
|
|
700
|
-
reset();
|
|
701
|
-
renderApp();
|
|
702
|
-
return;
|
|
703
|
-
}
|
|
704
|
-
cli.parse([
|
|
899
|
+
if (args.length) cli.parse([
|
|
705
900
|
"node",
|
|
706
901
|
"code-ollama",
|
|
707
902
|
...args
|
|
708
903
|
]);
|
|
904
|
+
else await launchTui();
|
|
905
|
+
}
|
|
906
|
+
async function launchTui(sessionId) {
|
|
907
|
+
const { renderApp } = await import("./assets/tui-CoX71F7Y.js");
|
|
908
|
+
reset();
|
|
909
|
+
renderApp(sessionId);
|
|
709
910
|
}
|
|
710
911
|
// v8 ignore start
|
|
711
912
|
function isEntrypoint(argv1 = process.argv[1]) {
|
|
@@ -719,4 +920,4 @@ function isEntrypoint(argv1 = process.argv[1]) {
|
|
|
719
920
|
if (isEntrypoint()) main();
|
|
720
921
|
// v8 ignore stop
|
|
721
922
|
//#endregion
|
|
722
|
-
export { USER as _, tick as a,
|
|
923
|
+
export { LABEL as A, withSystemMessage as C, USER as D, SYSTEM as E, VERSION as F, LIST as I, SAFE as M, APPROVE as N, PLAN_GENERATION_INSTRUCTION as O, REJECT as P, resetSystemMessage as S, ASSISTANT as T, setClearHandler as _, tick as a, loadConfig as b, appendMessage as c, deleteSessionIfEmpty as d, listSessions as f, reset as g, clear as h, WRITE_TOOLS as i, PLAN as j, AUTO as k, createSession as l, updateSessionModel as m, main, READ_TOOLS as n, color as o, loadSession as p, TOOLS as r, write as s, executeTool as t, deleteSession as u, listModels as v, HEADER_PREFIX as w, saveConfig as x, streamChat as y };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-ollama",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.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",
|
|
@@ -42,20 +42,21 @@
|
|
|
42
42
|
"@inkjs/ui": "2.0.0",
|
|
43
43
|
"@shikijs/cli": "4.0.2",
|
|
44
44
|
"cac": "7.0.0",
|
|
45
|
-
"ink": "7.0.
|
|
45
|
+
"ink": "7.0.3",
|
|
46
46
|
"marked": "15.0.12",
|
|
47
47
|
"marked-terminal": "7.3.0",
|
|
48
48
|
"ollama": "0.6.3",
|
|
49
|
-
"react": "19.2.6"
|
|
49
|
+
"react": "19.2.6",
|
|
50
|
+
"uuid": "14.0.0"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
52
|
-
"@commitlint/cli": "21.0.
|
|
53
|
-
"@commitlint/config-conventional": "21.0.
|
|
53
|
+
"@commitlint/cli": "21.0.1",
|
|
54
|
+
"@commitlint/config-conventional": "21.0.1",
|
|
54
55
|
"@eslint/config-helpers": "0.6.0",
|
|
55
56
|
"@eslint/js": "10.0.1",
|
|
56
|
-
"@types/node": "25.
|
|
57
|
+
"@types/node": "25.7.0",
|
|
57
58
|
"@types/react": "19.2.14",
|
|
58
|
-
"@vitest/coverage-v8": "4.1.
|
|
59
|
+
"@vitest/coverage-v8": "4.1.6",
|
|
59
60
|
"eslint": "10.3.0",
|
|
60
61
|
"eslint-plugin-prettier": "5.5.5",
|
|
61
62
|
"eslint-plugin-simple-import-sort": "13.0.0",
|
|
@@ -64,12 +65,12 @@
|
|
|
64
65
|
"ink-testing-library": "4.0.0",
|
|
65
66
|
"lint-staged": "17.0.4",
|
|
66
67
|
"prettier": "3.8.3",
|
|
67
|
-
"publint": "0.3.
|
|
68
|
+
"publint": "0.3.21",
|
|
68
69
|
"tsx": "4.21.0",
|
|
69
70
|
"typescript": "6.0.3",
|
|
70
|
-
"typescript-eslint": "8.59.
|
|
71
|
-
"vite": "8.0.
|
|
72
|
-
"vitest": "4.1.
|
|
71
|
+
"typescript-eslint": "8.59.3",
|
|
72
|
+
"vite": "8.0.12",
|
|
73
|
+
"vitest": "4.1.6"
|
|
73
74
|
},
|
|
74
75
|
"files": [
|
|
75
76
|
"dist/"
|