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 ROLE, n as TOOLS_REQUIRING_APPROVAL, o as loadConfig, r as executeTool, s as saveConfig, t as TOOLS, u as VERSION } from "../cli.js";
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/constants/commands.ts
8
- var COMMANDS = [{
9
- name: "/model",
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 = (isCommandMode && matches.length > 0 && matches[selectedIndex] ? matches[selectedIndex].name : input).trim();
47
- if (trimmed) {
48
- onSubmit(trimmed);
49
- setValue("");
50
- setSelectedIndex(0);
51
- setInputKey((key) => key + 1);
52
- }
53
- }, [
54
- isCommandMode,
55
- matches,
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(Autocomplete, {
214
+ !pendingToolCall && /* @__PURE__ */ jsx(ChatInput, {
281
215
  isDisabled: isLoading,
282
- onSubmit: (val) => {
283
- handleSubmit(val);
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.1.1";
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: Make precise edits to a 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([NAME.WRITE_FILE, NAME.RUN_SHELL]);
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-DSR1MJGd.js");
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, ROLE as l, main, TOOLS_REQUIRING_APPROVAL as n, loadConfig as o, executeTool as r, saveConfig as s, TOOLS as t, VERSION as u };
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.1.1",
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.1",
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.18",
64
+ "publint": "0.3.19",
65
65
  "tsx": "4.21.0",
66
66
  "typescript": "6.0.3",
67
67
  "typescript-eslint": "8.59.2",