netheriteai-code 0.4.0 → 1.0.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/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "netheriteai-code",
3
- "version": "0.4.0",
3
+ "version": "1.0.0",
4
4
  "description": "NetheriteAI:Code by hurdacu. High-performance coding assistant.",
5
5
  "author": "hurdacu",
6
+ "license": "MIT",
6
7
  "type": "module",
7
8
  "bin": {
8
9
  "netheriteai-code": "bin/netheriteai-code.js",
@@ -14,5 +15,15 @@
14
15
  "models": "node ./bin/netheriteai-code.js models",
15
16
  "chat": "node ./bin/netheriteai-code.js chat",
16
17
  "tui": "node ./bin/netheriteai-code.js tui"
17
- }
18
+ },
19
+ "engines": {
20
+ "node": ">=18.0.0"
21
+ },
22
+ "keywords": [
23
+ "ai",
24
+ "coding",
25
+ "assistant",
26
+ "tui",
27
+ "terminal"
28
+ ]
18
29
  }
package/src/agent.js CHANGED
@@ -245,6 +245,7 @@ export async function runAgentTurn({ workspaceRoot, model, history, userPrompt,
245
245
  };
246
246
  }
247
247
 
248
+ const observations = [];
248
249
  for (const toolCall of toolCalls) {
249
250
  onEvent?.({
250
251
  type: "tool_start",
@@ -269,11 +270,7 @@ export async function runAgentTurn({ workspaceRoot, model, history, userPrompt,
269
270
  result,
270
271
  });
271
272
 
272
- // Workaround: Use 'user' role instead of 'tool' because the remote server rejects 'tool' role.
273
- messages.push({
274
- role: "user",
275
- content: `Observation from ${toolCall.function.name}:\n${JSON.stringify(result, null, 2)}`,
276
- });
273
+ observations.push(`Observation from ${toolCall.function.name}:\n${JSON.stringify(result, null, 2)}`);
277
274
  } catch (error) {
278
275
  onEvent?.({
279
276
  type: "tool_result",
@@ -283,15 +280,16 @@ export async function runAgentTurn({ workspaceRoot, model, history, userPrompt,
283
280
  error: error instanceof Error ? error.message : String(error),
284
281
  },
285
282
  });
286
- messages.push({
287
- role: "tool",
288
- tool_call_id: toolCall.id,
289
- content: JSON.stringify({
290
- ok: false,
291
- error: error instanceof Error ? error.message : String(error),
292
- }),
293
- });
283
+ observations.push(`Error from ${toolCall.function.name}:\n${error instanceof Error ? error.message : String(error)}`);
294
284
  }
295
285
  }
286
+
287
+ // Workaround: Use 'user' role instead of 'tool' because the remote server rejects 'tool' role.
288
+ if (observations.length > 0) {
289
+ messages.push({
290
+ role: "user",
291
+ content: observations.join("\n\n"),
292
+ });
293
+ }
296
294
  }
297
295
  }
package/src/cli.js CHANGED
@@ -11,7 +11,7 @@ import { printTable, resolveWorkspaceRoot } from "./utils.js";
11
11
 
12
12
  const execFileAsync = promisify(execFile);
13
13
 
14
- const VERSION = "0.4.0";
14
+ const VERSION = "1.0.0";
15
15
 
16
16
  async function handleAutoUpdate() {
17
17
  // If we were just restarted by an update, don't check again
package/src/ollama.js CHANGED
@@ -2,7 +2,7 @@ import { execFile } from "node:child_process";
2
2
  import { promisify } from "node:util";
3
3
 
4
4
  const execFileAsync = promisify(execFile);
5
- const BASE_URL = process.env.NETHERITE_BASE_URL || "http://176.88.249.119:11434";
5
+ const BASE_URL = process.env.OLLAMA_BASE_URL || process.env.NETHERITE_BASE_URL || "http://176.88.249.119:11434";
6
6
  const PREFERRED_DEFAULT_MODELS = ["glm-5:cloud"];
7
7
 
8
8
  async function request(pathname, body, signal, retries = 3) {
package/src/tools.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { spawn } from "node:child_process";
4
+ import crypto from "node:crypto";
4
5
  import {
5
6
  clampText,
6
7
  ensureDir,
@@ -11,6 +12,8 @@ import {
11
12
  toAbsoluteInsideRoot,
12
13
  } from "./utils.js";
13
14
 
15
+ const activeProcesses = new Map();
16
+
14
17
  function todoFile(root) {
15
18
  return path.join(root, ".netherite", "todos.json");
16
19
  }
@@ -60,6 +63,21 @@ export function getToolDefinitions() {
60
63
  },
61
64
  },
62
65
  },
66
+ {
67
+ type: "function",
68
+ function: {
69
+ name: "create_file",
70
+ description: "Create a new file.",
71
+ parameters: {
72
+ type: "object",
73
+ properties: {
74
+ path: { type: "string" },
75
+ content: { type: "string" },
76
+ },
77
+ required: ["path", "content"],
78
+ },
79
+ },
80
+ },
63
81
  {
64
82
  type: "function",
65
83
  function: {
@@ -75,6 +93,21 @@ export function getToolDefinitions() {
75
93
  },
76
94
  },
77
95
  },
96
+ {
97
+ type: "function",
98
+ function: {
99
+ name: "append_file",
100
+ description: "Append content to a file.",
101
+ parameters: {
102
+ type: "object",
103
+ properties: {
104
+ path: { type: "string" },
105
+ content: { type: "string" },
106
+ },
107
+ required: ["path", "content"],
108
+ },
109
+ },
110
+ },
78
111
  {
79
112
  type: "function",
80
113
  function: {
@@ -91,6 +124,50 @@ export function getToolDefinitions() {
91
124
  },
92
125
  },
93
126
  },
127
+ {
128
+ type: "function",
129
+ function: {
130
+ name: "make_dir",
131
+ description: "Create a new directory.",
132
+ parameters: {
133
+ type: "object",
134
+ properties: {
135
+ path: { type: "string" },
136
+ },
137
+ required: ["path"],
138
+ },
139
+ },
140
+ },
141
+ {
142
+ type: "function",
143
+ function: {
144
+ name: "remove_path",
145
+ description: "Delete a file or directory.",
146
+ parameters: {
147
+ type: "object",
148
+ properties: {
149
+ path: { type: "string" },
150
+ recursive: { type: "boolean" },
151
+ },
152
+ required: ["path"],
153
+ },
154
+ },
155
+ },
156
+ {
157
+ type: "function",
158
+ function: {
159
+ name: "rename_path",
160
+ description: "Rename a file or directory.",
161
+ parameters: {
162
+ type: "object",
163
+ properties: {
164
+ oldPath: { type: "string" },
165
+ newPath: { type: "string" },
166
+ },
167
+ required: ["oldPath", "newPath"],
168
+ },
169
+ },
170
+ },
94
171
  {
95
172
  type: "function",
96
173
  function: {
@@ -105,6 +182,21 @@ export function getToolDefinitions() {
105
182
  },
106
183
  },
107
184
  },
185
+ {
186
+ type: "function",
187
+ function: {
188
+ name: "send_input",
189
+ description: "Send input (stdin) to a background process_id that is waiting for input.",
190
+ parameters: {
191
+ type: "object",
192
+ properties: {
193
+ process_id: { type: "string" },
194
+ input: { type: "string" },
195
+ },
196
+ required: ["process_id", "input"],
197
+ },
198
+ },
199
+ },
108
200
  ];
109
201
  }
110
202
 
@@ -136,42 +228,61 @@ async function streamFilePreview(text, hooks = {}, maxLines = 12) {
136
228
  }
137
229
 
138
230
  async function runSpawnedCommand(root, command, args = [], hooks = {}) {
139
- return await new Promise((resolve, reject) => {
140
- const child = spawn(command, args, {
141
- cwd: root,
142
- stdio: ["ignore", "pipe", "pipe"],
143
- });
231
+ const id = crypto.randomUUID();
232
+ const child = spawn(command, args, {
233
+ cwd: root,
234
+ stdio: ["pipe", "pipe", "pipe"],
235
+ });
144
236
 
145
- let stdout = "";
146
- let stderr = "";
237
+ const p = { child, stdout: "", stderr: "" };
238
+ activeProcesses.set(id, p);
147
239
 
148
- child.stdout?.on("data", (chunk) => {
149
- const text = chunk.toString("utf8");
150
- stdout += text;
151
- hooks.onProgress?.({ stream: "stdout", text });
152
- });
240
+ child.stdout?.on("data", (chunk) => {
241
+ const text = chunk.toString("utf8");
242
+ p.stdout += text;
243
+ hooks.onProgress?.({ stream: "stdout", text });
244
+ });
153
245
 
154
- child.stderr?.on("data", (chunk) => {
155
- const text = chunk.toString("utf8");
156
- stderr += text;
157
- hooks.onProgress?.({ stream: "stderr", text });
158
- });
246
+ child.stderr?.on("data", (chunk) => {
247
+ const text = chunk.toString("utf8");
248
+ p.stderr += text;
249
+ hooks.onProgress?.({ stream: "stderr", text });
250
+ });
159
251
 
160
- child.on("error", reject);
252
+ const exitPromise = new Promise((resolve, reject) => {
253
+ child.on("error", (err) => {
254
+ activeProcesses.delete(id);
255
+ reject(err);
256
+ });
161
257
  child.on("close", (code) => {
258
+ activeProcesses.delete(id);
162
259
  if (code === 0) {
163
260
  resolve({
164
261
  ok: true,
165
262
  command,
166
263
  args,
167
- stdout: clampText(stdout),
168
- stderr: clampText(stderr),
264
+ stdout: clampText(p.stdout),
265
+ stderr: clampText(p.stderr),
169
266
  });
170
267
  return;
171
268
  }
172
- reject(new Error((stderr || stdout || `${command} exited with code ${code}`).trim()));
269
+ reject(new Error((p.stderr || p.stdout || `${command} exited with code ${code}`).trim()));
173
270
  });
174
271
  });
272
+
273
+ const timeoutPromise = new Promise((resolve) => setTimeout(() => {
274
+ if (activeProcesses.has(id)) {
275
+ resolve({
276
+ status: "running",
277
+ process_id: id,
278
+ message: "Process is still running and may be waiting for input. Use 'send_input' to interact with it.",
279
+ stdout: clampText(p.stdout),
280
+ stderr: clampText(p.stderr),
281
+ });
282
+ }
283
+ }, 2000));
284
+
285
+ return await Promise.race([exitPromise, timeoutPromise]);
175
286
  }
176
287
 
177
288
  async function runOneCommand(root, command, args = [], hooks = {}) {
@@ -275,6 +386,40 @@ export async function executeToolCall(root, toolCall, hooks = {}) {
275
386
  fs.rmSync(target, { recursive: Boolean(args.recursive), force: false });
276
387
  return { ok: true, path: args.path };
277
388
  }
389
+ case "rename_path": {
390
+ const oldTarget = toAbsoluteInsideRoot(root, args.oldPath);
391
+ const newTarget = toAbsoluteInsideRoot(root, args.newPath);
392
+ if (!fs.existsSync(oldTarget)) throw new Error(`Source path not found: ${args.oldPath}`);
393
+ ensureDir(path.dirname(newTarget));
394
+ fs.renameSync(oldTarget, newTarget);
395
+ return { ok: true, oldPath: args.oldPath, newPath: args.newPath };
396
+ }
397
+ case "send_input": {
398
+ const id = args.process_id;
399
+ const p = activeProcesses.get(id);
400
+ if (!p) throw new Error(`Process ${id} not found`);
401
+ p.stdout = "";
402
+ p.stderr = "";
403
+ if (args.input) {
404
+ p.child.stdin.write(args.input);
405
+ hooks.onProgress?.({ stream: "stdout", text: args.input });
406
+ }
407
+ const exitPromise = new Promise((resolve, reject) => {
408
+ const onExit = (code) => {
409
+ activeProcesses.delete(id);
410
+ if (code === 0) resolve({ ok: true, code, stdout: clampText(p.stdout), stderr: clampText(p.stderr) });
411
+ else reject(new Error((p.stderr || p.stdout || `exited with code ${code}`).trim()));
412
+ };
413
+ p.child.once("close", onExit);
414
+ p.child.once("error", reject);
415
+ });
416
+ const timeoutPromise = new Promise((resolve) => setTimeout(() => {
417
+ if (activeProcesses.has(id)) {
418
+ resolve({ status: "running", process_id: id, stdout: clampText(p.stdout), stderr: clampText(p.stderr) });
419
+ }
420
+ }, 2000));
421
+ return await Promise.race([exitPromise, timeoutPromise]);
422
+ }
278
423
  case "run_command": {
279
424
  if (args.commandLine) {
280
425
  return await runShellLine(root, args.commandLine, hooks);