code-ollama 0.1.1 → 0.2.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.
|
@@ -1,87 +1,23 @@
|
|
|
1
|
-
import { a as streamChat, c as createSystemMessage, i as listModels, l as
|
|
1
|
+
import { a as streamChat, c as createSystemMessage, d as VERSION, f as NAMES, i as listModels, l as HEADER_PREFIX, n as TOOLS_REQUIRING_APPROVAL, o as loadConfig, r as executeTool, s as saveConfig, t as TOOLS, u as ROLE } from "../cli.js";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { Box, Text, render, useInput } from "ink";
|
|
4
4
|
import { useCallback, useEffect, useState } from "react";
|
|
5
5
|
import { Select, Spinner, TextInput } from "@inkjs/ui";
|
|
6
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
-
//#region src/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
description: "switch the model"
|
|
11
|
-
}];
|
|
12
|
-
//#endregion
|
|
13
|
-
//#region src/constants/ui.ts
|
|
14
|
-
var HEADER_PREFIX = "🦙";
|
|
15
|
-
//#endregion
|
|
16
|
-
//#region src/components/Autocomplete.tsx
|
|
17
|
-
function getMatches(input) {
|
|
18
|
-
if (!input.startsWith("/")) return [];
|
|
19
|
-
return COMMANDS.filter((command) => command.name.startsWith(input));
|
|
20
|
-
}
|
|
21
|
-
function Autocomplete({ isDisabled = false, onSubmit }) {
|
|
22
|
-
const [value, setValue] = useState("");
|
|
23
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
24
|
-
const [inputKey, setInputKey] = useState(0);
|
|
25
|
-
const matches = getMatches(value);
|
|
26
|
-
const isCommandMode = value.startsWith("/");
|
|
27
|
-
useInput((_char, key) => {
|
|
28
|
-
// v8 ignore next
|
|
29
|
-
if (!isCommandMode) return;
|
|
30
|
-
if (key.upArrow) {
|
|
31
|
-
setSelectedIndex((i) => Math.max(0, i - 1));
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
if (key.downArrow) {
|
|
35
|
-
setSelectedIndex((i) => Math.min(matches.length - 1, i + 1));
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
if (key.tab && matches.length > 0) {
|
|
39
|
-
setValue((matches[selectedIndex] ?? matches[0]).name);
|
|
40
|
-
setSelectedIndex(0);
|
|
41
|
-
setInputKey((key) => key + 1);
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
}, { isActive: !isDisabled && isCommandMode });
|
|
7
|
+
//#region src/components/ChatInput.tsx
|
|
8
|
+
function ChatInput({ isDisabled = false, onSubmit }) {
|
|
9
|
+
const [resetKey, setResetKey] = useState(0);
|
|
45
10
|
const handleSubmit = useCallback((input) => {
|
|
46
|
-
const trimmed =
|
|
47
|
-
if (trimmed)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
onSubmit,
|
|
57
|
-
selectedIndex
|
|
58
|
-
]);
|
|
59
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
60
|
-
flexDirection: "column",
|
|
61
|
-
children: [/* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, { children: "> " }), /* @__PURE__ */ jsx(TextInput, {
|
|
62
|
-
isDisabled,
|
|
63
|
-
defaultValue: value,
|
|
64
|
-
onChange: setValue,
|
|
65
|
-
onSubmit: handleSubmit
|
|
66
|
-
}, inputKey)] }), isCommandMode && matches.length > 0 && /* @__PURE__ */ jsx(Box, {
|
|
67
|
-
flexDirection: "column",
|
|
68
|
-
marginLeft: 2,
|
|
69
|
-
children: matches.map((command, index) => {
|
|
70
|
-
const isHighlighted = index === selectedIndex;
|
|
71
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
72
|
-
gap: 3,
|
|
73
|
-
children: [/* @__PURE__ */ jsx(Text, {
|
|
74
|
-
color: isHighlighted ? "cyan" : void 0,
|
|
75
|
-
bold: isHighlighted,
|
|
76
|
-
children: command.name
|
|
77
|
-
}), /* @__PURE__ */ jsx(Text, {
|
|
78
|
-
dimColor: true,
|
|
79
|
-
children: command.description
|
|
80
|
-
})]
|
|
81
|
-
}, command.name);
|
|
82
|
-
})
|
|
83
|
-
})]
|
|
84
|
-
});
|
|
11
|
+
const trimmed = input.trim();
|
|
12
|
+
if (!trimmed) return;
|
|
13
|
+
onSubmit(trimmed);
|
|
14
|
+
setResetKey((key) => key + 1);
|
|
15
|
+
}, [onSubmit]);
|
|
16
|
+
return /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, { children: "> " }), /* @__PURE__ */ jsx(TextInput, {
|
|
17
|
+
isDisabled,
|
|
18
|
+
suggestions: NAMES,
|
|
19
|
+
onSubmit: handleSubmit
|
|
20
|
+
}, resetKey)] });
|
|
85
21
|
}
|
|
86
22
|
//#endregion
|
|
87
23
|
//#region src/components/Messages.tsx
|
|
@@ -167,7 +103,6 @@ function ToolApproval({ toolCall, onApprove, onReject }) {
|
|
|
167
103
|
//#region src/components/Chat.tsx
|
|
168
104
|
function Chat({ model, onCommand, autoExecute }) {
|
|
169
105
|
const [messages, setMessages] = useState([createSystemMessage()]);
|
|
170
|
-
const [submitKey, setSubmitKey] = useState(0);
|
|
171
106
|
const [isLoading, setIsLoading] = useState(false);
|
|
172
107
|
const [pendingToolCall, setPendingToolCall] = useState(null);
|
|
173
108
|
const processStream = useCallback(async (currentMessages) => {
|
|
@@ -248,7 +183,6 @@ function Chat({ model, onCommand, autoExecute }) {
|
|
|
248
183
|
const handleSubmit = useCallback(async (value) => {
|
|
249
184
|
const userContent = value.trim();
|
|
250
185
|
if (!userContent) return;
|
|
251
|
-
setSubmitKey((key) => key + 1);
|
|
252
186
|
if (userContent.startsWith("/")) {
|
|
253
187
|
onCommand(userContent);
|
|
254
188
|
return;
|
|
@@ -277,12 +211,10 @@ function Chat({ model, onCommand, autoExecute }) {
|
|
|
277
211
|
onApprove: () => void handleToolApproval(true),
|
|
278
212
|
onReject: () => void handleToolApproval(false)
|
|
279
213
|
}),
|
|
280
|
-
!pendingToolCall && /* @__PURE__ */ jsx(
|
|
214
|
+
!pendingToolCall && /* @__PURE__ */ jsx(ChatInput, {
|
|
281
215
|
isDisabled: isLoading,
|
|
282
|
-
onSubmit:
|
|
283
|
-
|
|
284
|
-
}
|
|
285
|
-
}, submitKey)
|
|
216
|
+
onSubmit: handleSubmit
|
|
217
|
+
})
|
|
286
218
|
]
|
|
287
219
|
});
|
|
288
220
|
}
|
package/dist/cli.js
CHANGED
|
@@ -6,9 +6,13 @@ import { homedir } from "node:os";
|
|
|
6
6
|
import { Ollama } from "ollama";
|
|
7
7
|
import { exec } from "node:child_process";
|
|
8
8
|
import { promisify } from "node:util";
|
|
9
|
+
var NAMES = [{
|
|
10
|
+
name: "/model",
|
|
11
|
+
description: "switch the model"
|
|
12
|
+
}].map(({ name }) => name);
|
|
9
13
|
//#endregion
|
|
10
14
|
//#region src/constants/package.ts
|
|
11
|
-
var VERSION = "0.
|
|
15
|
+
var VERSION = "0.2.1";
|
|
12
16
|
//#endregion
|
|
13
17
|
//#region src/constants/prompt.ts
|
|
14
18
|
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
|
|
@@ -25,7 +29,7 @@ When tools return results, incorporate them into your response naturally`;
|
|
|
25
29
|
var TOOL_INSTRUCTIONS = `Available tools:
|
|
26
30
|
- read_file: Read file contents at a path
|
|
27
31
|
- write_file: Write content to a file (requires approval)
|
|
28
|
-
- edit_file:
|
|
32
|
+
- edit_file: Replace one exact text match in a file (requires approval)
|
|
29
33
|
- list_dir: List files in a directory
|
|
30
34
|
- grep_search: Search code with regex
|
|
31
35
|
- run_shell: Execute shell commands (requires approval)
|
|
@@ -47,12 +51,16 @@ var ROLE = {
|
|
|
47
51
|
var NAME = {
|
|
48
52
|
READ_FILE: "read_file",
|
|
49
53
|
WRITE_FILE: "write_file",
|
|
54
|
+
EDIT_FILE: "edit_file",
|
|
50
55
|
RUN_SHELL: "run_shell",
|
|
51
56
|
LIST_DIR: "list_dir",
|
|
52
57
|
GREP_SEARCH: "grep_search",
|
|
53
58
|
VIEW_RANGE: "view_range"
|
|
54
59
|
};
|
|
55
60
|
//#endregion
|
|
61
|
+
//#region src/constants/ui.ts
|
|
62
|
+
var HEADER_PREFIX = "🦙";
|
|
63
|
+
//#endregion
|
|
56
64
|
//#region src/utils/agents.ts
|
|
57
65
|
var AGENTS_FILE = "AGENTS.md";
|
|
58
66
|
function loadAgentsContent() {
|
|
@@ -178,6 +186,24 @@ var TOOLS = [
|
|
|
178
186
|
description: "The content to write to the file"
|
|
179
187
|
}
|
|
180
188
|
}, ["path", "content"]),
|
|
189
|
+
defineTool(NAME.EDIT_FILE, "Replace one exact text match in an existing file at the specified path", {
|
|
190
|
+
path: {
|
|
191
|
+
type: "string",
|
|
192
|
+
description: "The path to the file to edit"
|
|
193
|
+
},
|
|
194
|
+
oldText: {
|
|
195
|
+
type: "string",
|
|
196
|
+
description: "The exact existing text to replace"
|
|
197
|
+
},
|
|
198
|
+
newText: {
|
|
199
|
+
type: "string",
|
|
200
|
+
description: "The replacement text to write in place of oldText"
|
|
201
|
+
}
|
|
202
|
+
}, [
|
|
203
|
+
"path",
|
|
204
|
+
"oldText",
|
|
205
|
+
"newText"
|
|
206
|
+
]),
|
|
181
207
|
defineTool(NAME.RUN_SHELL, "Execute a shell command", { command: {
|
|
182
208
|
type: "string",
|
|
183
209
|
description: "The shell command to execute"
|
|
@@ -215,7 +241,11 @@ var TOOLS = [
|
|
|
215
241
|
"end"
|
|
216
242
|
])
|
|
217
243
|
];
|
|
218
|
-
var TOOLS_REQUIRING_APPROVAL = new Set([
|
|
244
|
+
var TOOLS_REQUIRING_APPROVAL = new Set([
|
|
245
|
+
NAME.WRITE_FILE,
|
|
246
|
+
NAME.EDIT_FILE,
|
|
247
|
+
NAME.RUN_SHELL
|
|
248
|
+
]);
|
|
219
249
|
/**
|
|
220
250
|
* Execute a tool by name with arguments
|
|
221
251
|
*/
|
|
@@ -223,6 +253,7 @@ async function executeTool(name, args) {
|
|
|
223
253
|
switch (name) {
|
|
224
254
|
case NAME.READ_FILE: return readFile(args.path);
|
|
225
255
|
case NAME.WRITE_FILE: return writeFile(args.path, args.content);
|
|
256
|
+
case NAME.EDIT_FILE: return editFile(args.path, args.oldText, args.newText);
|
|
226
257
|
case NAME.RUN_SHELL: return runShell(args.command);
|
|
227
258
|
case NAME.LIST_DIR: return listDir(args.path);
|
|
228
259
|
case NAME.GREP_SEARCH: return await grepSearch(args.pattern, args.path);
|
|
@@ -264,6 +295,33 @@ function writeFile(filePath, content) {
|
|
|
264
295
|
};
|
|
265
296
|
}
|
|
266
297
|
}
|
|
298
|
+
/**
|
|
299
|
+
* Replace one exact text match in an existing file
|
|
300
|
+
*/
|
|
301
|
+
function editFile(filePath, oldText, newText) {
|
|
302
|
+
try {
|
|
303
|
+
if (!existsSync(filePath)) return {
|
|
304
|
+
content: "",
|
|
305
|
+
error: `File not found: ${filePath}`
|
|
306
|
+
};
|
|
307
|
+
const content = readFileSync(filePath, "utf8");
|
|
308
|
+
if (!content.includes(oldText)) return {
|
|
309
|
+
content: "",
|
|
310
|
+
error: `Exact text not found in file: ${filePath}`
|
|
311
|
+
};
|
|
312
|
+
if (content.split(oldText).length - 1 > 1) return {
|
|
313
|
+
content: "",
|
|
314
|
+
error: `Exact text matched multiple locations in file: ${filePath}`
|
|
315
|
+
};
|
|
316
|
+
writeFileSync(filePath, content.replace(oldText, newText), "utf8");
|
|
317
|
+
return { content: `File edited successfully: ${filePath}` };
|
|
318
|
+
} catch (error) {
|
|
319
|
+
return {
|
|
320
|
+
content: "",
|
|
321
|
+
error: `Failed to edit file: ${error instanceof Error ? error.message : String(error)}`
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
}
|
|
267
325
|
var SHELL_EXEC_OPTIONS = {
|
|
268
326
|
timeout: 3e4,
|
|
269
327
|
maxBuffer: 1024 * 1024
|
|
@@ -422,7 +480,7 @@ async function processRunStream(messages, model) {
|
|
|
422
480
|
}
|
|
423
481
|
async function main(args = process.argv.slice(2)) {
|
|
424
482
|
if (!args.length) {
|
|
425
|
-
const { renderApp } = await import("./assets/tui-
|
|
483
|
+
const { renderApp } = await import("./assets/tui-eBtT-5hi.js");
|
|
426
484
|
clear();
|
|
427
485
|
renderApp();
|
|
428
486
|
return;
|
|
@@ -445,4 +503,4 @@ function isEntrypoint(argv1 = process.argv[1]) {
|
|
|
445
503
|
if (isEntrypoint()) main();
|
|
446
504
|
/* v8 ignore stop */
|
|
447
505
|
//#endregion
|
|
448
|
-
export { streamChat as a, createSystemMessage as c, listModels as i,
|
|
506
|
+
export { streamChat as a, createSystemMessage as c, VERSION as d, NAMES as f, listModels as i, HEADER_PREFIX as l, main, TOOLS_REQUIRING_APPROVAL as n, loadConfig as o, executeTool as r, saveConfig as s, TOOLS as t, ROLE as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-ollama",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.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",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@inkjs/ui": "2.0.0",
|
|
43
43
|
"cac": "7.0.0",
|
|
44
|
-
"ink": "7.0.
|
|
44
|
+
"ink": "7.0.2",
|
|
45
45
|
"ollama": "0.6.3",
|
|
46
46
|
"react": "19.2.5"
|
|
47
47
|
},
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"ink-testing-library": "4.0.0",
|
|
62
62
|
"lint-staged": "16.4.0",
|
|
63
63
|
"prettier": "3.8.3",
|
|
64
|
-
"publint": "0.3.
|
|
64
|
+
"publint": "0.3.19",
|
|
65
65
|
"tsx": "4.21.0",
|
|
66
66
|
"typescript": "6.0.3",
|
|
67
67
|
"typescript-eslint": "8.59.2",
|