netheriteai-code 0.2.5 → 0.3.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,6 +1,6 @@
1
1
  {
2
2
  "name": "netheriteai-code",
3
- "version": "0.2.5",
3
+ "version": "0.3.0",
4
4
  "description": "NetheriteAI:Code by hurdacu. High-performance coding assistant.",
5
5
  "author": "hurdacu",
6
6
  "type": "module",
package/src/cli.js CHANGED
@@ -338,6 +338,79 @@ async function runChatSession({ workspaceRoot, model, useTui, session }) {
338
338
  }
339
339
  }
340
340
 
341
+ export async function main() {
342
+ await handleAutoUpdate();
343
+ const { positional, flags } = parseArgs(process.argv.slice(2));
344
+ const command = positional[0] || "tui";
345
+ const requestedWorkspaceRoot = resolveWorkspaceRoot(flags.dir || flags.d || process.cwd());
346
+
347
+ if (!fs.existsSync(requestedWorkspaceRoot)) {
348
+ throw new Error(`Workspace does not exist: ${requestedWorkspaceRoot}`);
349
+ }
350
+
351
+ if (command === "help" || flags.help || flags.h) {
352
+ printHelp();
353
+ return;
354
+ }
355
+
356
+ if (command === "tools") {
357
+ printToolList();
358
+ return;
359
+ }
360
+
361
+ let session = null;
362
+ let workspaceRoot = requestedWorkspaceRoot;
363
+
364
+ if (command === "session") {
365
+ const sessionId = positional[1];
366
+ if (!sessionId) {
367
+ throw new Error("Usage: netheritecode session <id>");
368
+ }
369
+ session = loadSessionById(sessionId);
370
+ if (!session) {
371
+ throw new Error(`Session not found: ${sessionId}`);
372
+ }
373
+ workspaceRoot = session.workspaceRoot;
374
+ } else {
375
+ session = createSession(workspaceRoot);
376
+ }
377
+
378
+ const model = await resolveModel(flags.model || flags.m);
379
+ setSelectedModel(model);
380
+
381
+ if (command === "chat") {
382
+ await runChatSession({ workspaceRoot, model, useTui: false, session });
383
+ return;
384
+ }
385
+
386
+ if (command === "tui" || command === "session") {
387
+ await runChatSession({ workspaceRoot, model, useTui: true, session });
388
+ return;
389
+ }
390
+
391
+ if (!command.startsWith("-")) {
392
+ const prompt = positional.join(" ");
393
+ const result = await runAgentTurn({
394
+ workspaceRoot,
395
+ model,
396
+ history: session.messages,
397
+ userPrompt: prompt,
398
+ });
399
+ session.messages = result.messages;
400
+ saveSession(session);
401
+ console.log(result.reply);
402
+ return;
403
+ }
404
+
405
+ printHelp();
406
+ }
407
+ break;
408
+ }
409
+ await submit(prompt);
410
+ rl.prompt();
411
+ }
412
+ }
413
+
341
414
  export async function main() {
342
415
  const { positional, flags } = parseArgs(process.argv.slice(2));
343
416
  const command = positional[0] || "tui";
package/src/ollama.js CHANGED
@@ -7,16 +7,6 @@ const PREFERRED_DEFAULT_MODELS = [
7
7
  "glm-5:cloud",
8
8
  ];
9
9
 
10
- function getThinkMode(model) {
11
- const override = process.env.NETHERITEAI_THINK_MODE;
12
- if (override === "true") return true;
13
- if (override === "false") return false;
14
- if (override === "medium" || override === "low" || override === "high") return override;
15
- const name = String(model || "").toLowerCase();
16
- if (name.includes("gpt-oss")) return "medium";
17
- return true;
18
- }
19
-
20
10
  async function request(pathname, body, signal) {
21
11
  try {
22
12
  const response = await fetch(`${BASE_URL}${pathname}`, {
@@ -31,7 +21,8 @@ async function request(pathname, body, signal) {
31
21
  }
32
22
 
33
23
  return response;
34
- } catch {
24
+ } catch (err) {
25
+ if (err.name === "AbortError") throw err;
35
26
  throw new Error("Server down");
36
27
  }
37
28
  }
@@ -64,26 +55,16 @@ export async function pickDefaultModel() {
64
55
  const preferredMatch = models.find((model) =>
65
56
  PREFERRED_DEFAULT_MODELS.some((preferred) => preferred.toLowerCase() === model.name.toLowerCase()),
66
57
  );
67
- if (preferredMatch) {
68
- return preferredMatch.name;
69
- }
70
-
71
- const familyMatch = models.find((model) => model.name.toLowerCase().startsWith("netheriteai"));
72
- return familyMatch?.name || models[0].name;
58
+ return preferredMatch ? preferredMatch.name : models[0].name;
73
59
  } catch {
74
60
  throw new Error("Server down");
75
61
  }
76
62
  }
77
63
 
78
64
  export async function chat({ model, messages, tools, signal }) {
79
- const body = {
80
- model,
81
- messages,
82
- stream: false,
83
- };
84
- if (tools && tools.length) {
85
- body.tools = tools;
86
- }
65
+ const body = { model, messages, stream: false };
66
+ if (tools && tools.length) body.tools = tools;
67
+
87
68
  const response = await request("/api/chat", body, signal);
88
69
  const json = await response.json();
89
70
  return {
@@ -98,13 +79,8 @@ function createTaggedStreamParser({ onContent, onReasoning }) {
98
79
  const openTag = "<think>";
99
80
  const closeTag = "</think>";
100
81
 
101
- function emitVisible(text) {
102
- if (text) onContent?.(text);
103
- }
104
-
105
- function emitReasoning(text) {
106
- if (text) onReasoning?.(text);
107
- }
82
+ function emitVisible(text) { if (text) onContent?.(text); }
83
+ function emitReasoning(text) { if (text) onReasoning?.(text); }
108
84
 
109
85
  function flush(final = false) {
110
86
  while (buffer.length) {
@@ -149,34 +125,23 @@ function createTaggedStreamParser({ onContent, onReasoning }) {
149
125
  }
150
126
 
151
127
  return {
152
- push(text) {
153
- buffer += text;
154
- flush(false);
155
- },
156
- finish() {
157
- flush(true);
158
- },
128
+ push(text) { buffer += text; flush(false); },
129
+ finish() { flush(true); },
159
130
  };
160
131
  }
161
132
 
162
133
  export async function chatStream({ model, messages, tools, onChunk, onReasoningChunk, signal }) {
163
- const body = {
164
- model,
165
- messages,
166
- stream: true,
167
- };
168
- if (tools && tools.length) {
169
- body.tools = tools;
170
- }
134
+ const body = { model, messages, stream: true };
135
+ if (tools && tools.length) body.tools = tools;
136
+
171
137
  const response = await request("/api/chat", body, signal);
172
138
 
173
- const reader = response.body?.getReader();
174
- if (!reader) {
139
+ if (!response.body) {
175
140
  throw new Error("Server down");
176
141
  }
177
142
 
178
143
  const decoder = new TextDecoder();
179
- let buffer = "";
144
+ let lineBuffer = "";
180
145
  let lastMessage = { role: "assistant", content: "", reasoning: "", tool_calls: [] };
181
146
  const parser = createTaggedStreamParser({
182
147
  onContent(text) {
@@ -189,17 +154,41 @@ export async function chatStream({ model, messages, tools, onChunk, onReasoningC
189
154
  },
190
155
  });
191
156
 
192
- while (true) {
193
- const { value, done } = await reader.read();
194
- if (done) break;
195
- buffer += decoder.decode(value, { stream: true });
196
-
197
- let newlineIndex = buffer.indexOf("\n");
198
- while (newlineIndex !== -1) {
199
- const line = buffer.slice(0, newlineIndex).trim();
200
- buffer = buffer.slice(newlineIndex + 1);
201
- if (line) {
202
- const json = JSON.parse(line);
157
+ try {
158
+ // Use async iterator for better Node.js compatibility
159
+ for await (const chunk of response.body) {
160
+ lineBuffer += decoder.decode(chunk, { stream: true });
161
+
162
+ let lines = lineBuffer.split("\n");
163
+ lineBuffer = lines.pop() || "";
164
+
165
+ for (const line of lines) {
166
+ if (!line.trim()) continue;
167
+ try {
168
+ const json = JSON.parse(line);
169
+ const message = json.message || {};
170
+ if (message.thinking) {
171
+ lastMessage.reasoning += message.thinking;
172
+ onReasoningChunk?.(message.thinking);
173
+ }
174
+ if (message.content) {
175
+ parser.push(message.content);
176
+ }
177
+ if (message.tool_calls?.length) {
178
+ lastMessage.tool_calls = message.tool_calls;
179
+ }
180
+ if (message.role) {
181
+ lastMessage.role = message.role;
182
+ }
183
+ } catch {
184
+ // Ignore parse errors from partial lines or server noise
185
+ }
186
+ }
187
+ }
188
+
189
+ if (lineBuffer.trim()) {
190
+ try {
191
+ const json = JSON.parse(lineBuffer);
203
192
  const message = json.message || {};
204
193
  if (message.thinking) {
205
194
  lastMessage.reasoning += message.thinking;
@@ -211,33 +200,15 @@ export async function chatStream({ model, messages, tools, onChunk, onReasoningC
211
200
  if (message.tool_calls?.length) {
212
201
  lastMessage.tool_calls = message.tool_calls;
213
202
  }
214
- if (message.role) {
215
- lastMessage.role = message.role;
216
- }
203
+ } catch {
204
+ // Final line noise
217
205
  }
218
- newlineIndex = buffer.indexOf("\n");
219
- }
220
- }
221
-
222
- if (buffer.trim()) {
223
- const json = JSON.parse(buffer.trim());
224
- const message = json.message || {};
225
- if (message.thinking) {
226
- lastMessage.reasoning += message.thinking;
227
- onReasoningChunk?.(message.thinking);
228
- }
229
- if (message.content) {
230
- parser.push(message.content);
231
- }
232
- if (message.tool_calls?.length) {
233
- lastMessage.tool_calls = message.tool_calls;
234
- }
235
- if (message.role) {
236
- lastMessage.role = message.role;
237
206
  }
207
+ } catch (err) {
208
+ if (err.name === "AbortError") throw err;
209
+ throw new Error("Server down");
238
210
  }
239
211
 
240
212
  parser.finish();
241
-
242
213
  return lastMessage;
243
214
  }
package/src/tools.js CHANGED
@@ -38,12 +38,12 @@ export function getToolDefinitions() {
38
38
  type: "function",
39
39
  function: {
40
40
  name: "list_files",
41
- description: "List files or folders inside the workspace.",
41
+ description: "List files in the workspace.",
42
42
  parameters: {
43
43
  type: "object",
44
44
  properties: {
45
- path: { type: "string", description: "Relative path inside the workspace." },
46
- recursive: { type: "boolean", description: "Whether to recurse into child directories." },
45
+ path: { type: "string" },
46
+ recursive: { type: "boolean" },
47
47
  },
48
48
  },
49
49
  },
@@ -52,56 +52,24 @@ export function getToolDefinitions() {
52
52
  type: "function",
53
53
  function: {
54
54
  name: "read_file",
55
- description: "Read a text file inside the workspace.",
55
+ description: "Read a file.",
56
56
  parameters: {
57
57
  type: "object",
58
- properties: {
59
- path: { type: "string", description: "Relative file path." },
60
- },
58
+ properties: { path: { type: "string" } },
61
59
  required: ["path"],
62
60
  },
63
61
  },
64
62
  },
65
- {
66
- type: "function",
67
- function: {
68
- name: "create_file",
69
- description: "Create a new text file inside the workspace. Fails if the file already exists.",
70
- parameters: {
71
- type: "object",
72
- properties: {
73
- path: { type: "string", description: "Relative file path." },
74
- content: { type: "string", description: "Initial file content." },
75
- },
76
- required: ["path", "content"],
77
- },
78
- },
79
- },
80
63
  {
81
64
  type: "function",
82
65
  function: {
83
66
  name: "write_file",
84
- description: "Create or overwrite a text file inside the workspace.",
85
- parameters: {
86
- type: "object",
87
- properties: {
88
- path: { type: "string", description: "Relative file path." },
89
- content: { type: "string", description: "New file content." },
90
- },
91
- required: ["path", "content"],
92
- },
93
- },
94
- },
95
- {
96
- type: "function",
97
- function: {
98
- name: "append_file",
99
- description: "Append text to the end of a file inside the workspace, creating it if needed.",
67
+ description: "Create or overwrite a file.",
100
68
  parameters: {
101
69
  type: "object",
102
70
  properties: {
103
- path: { type: "string", description: "Relative file path." },
104
- content: { type: "string", description: "Text to append." },
71
+ path: { type: "string" },
72
+ content: { type: "string" },
105
73
  },
106
74
  required: ["path", "content"],
107
75
  },
@@ -111,126 +79,29 @@ export function getToolDefinitions() {
111
79
  type: "function",
112
80
  function: {
113
81
  name: "edit_file",
114
- description: "Edit a text file by replacing an exact string match. Safer than overwriting the whole file.",
82
+ description: "Edit a file by replacing text.",
115
83
  parameters: {
116
84
  type: "object",
117
85
  properties: {
118
- path: { type: "string", description: "Relative file path." },
119
- oldText: { type: "string", description: "Exact text to replace." },
120
- newText: { type: "string", description: "Replacement text." },
121
- replaceAll: { type: "boolean", description: "Replace all occurrences instead of just the first." },
86
+ path: { type: "string" },
87
+ oldText: { type: "string" },
88
+ newText: { type: "string" },
122
89
  },
123
90
  required: ["path", "oldText", "newText"],
124
91
  },
125
92
  },
126
93
  },
127
- {
128
- type: "function",
129
- function: {
130
- name: "make_dir",
131
- description: "Create a directory inside the workspace.",
132
- parameters: {
133
- type: "object",
134
- properties: {
135
- path: { type: "string", description: "Relative directory path." },
136
- },
137
- required: ["path"],
138
- },
139
- },
140
- },
141
- {
142
- type: "function",
143
- function: {
144
- name: "remove_path",
145
- description: "Remove a file or directory inside the workspace.",
146
- parameters: {
147
- type: "object",
148
- properties: {
149
- path: { type: "string", description: "Relative path to delete." },
150
- recursive: { type: "boolean", description: "Allow recursive directory deletion." },
151
- },
152
- required: ["path"],
153
- },
154
- },
155
- },
156
94
  {
157
95
  type: "function",
158
96
  function: {
159
97
  name: "run_command",
160
- description: "Run a shell command inside the workspace and capture stdout/stderr.",
161
- parameters: {
162
- type: "object",
163
- properties: {
164
- command: { type: "string", description: "Executable name, for example `npm` or `git`." },
165
- args: { type: "array", items: { type: "string" }, description: "Argument array." },
166
- commandLine: {
167
- type: "string",
168
- description: "Optional raw shell command line. Use it for pipes, redirects, globs, and shell syntax.",
169
- },
170
- },
171
- },
172
- },
173
- },
174
- {
175
- type: "function",
176
- function: {
177
- name: "batch_command",
178
- description: "Run multiple shell commands sequentially in the workspace.",
179
- parameters: {
180
- type: "object",
181
- properties: {
182
- commands: {
183
- type: "array",
184
- items: {
185
- type: "object",
186
- properties: {
187
- command: { type: "string" },
188
- args: { type: "array", items: { type: "string" } },
189
- commandLine: { type: "string" },
190
- },
191
- },
192
- },
193
- },
194
- required: ["commands"],
195
- },
196
- },
197
- },
198
- {
199
- type: "function",
200
- function: {
201
- name: "todo_create",
202
- description: "Create a todo item for the current workspace.",
203
- parameters: {
204
- type: "object",
205
- properties: {
206
- text: { type: "string" },
207
- },
208
- required: ["text"],
209
- },
210
- },
211
- },
212
- {
213
- type: "function",
214
- function: {
215
- name: "todo_list",
216
- description: "List todo items for the current workspace.",
217
- parameters: {
218
- type: "object",
219
- properties: {},
220
- },
221
- },
222
- },
223
- {
224
- type: "function",
225
- function: {
226
- name: "todo_complete",
227
- description: "Mark a todo item complete by numeric id.",
98
+ description: "Run a shell command.",
228
99
  parameters: {
229
100
  type: "object",
230
101
  properties: {
231
- id: { type: "number" },
102
+ commandLine: { type: "string" },
232
103
  },
233
- required: ["id"],
104
+ required: ["commandLine"],
234
105
  },
235
106
  },
236
107
  },