nothumanallowed 9.0.5 → 9.1.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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Your agents. Your machine. Your rules.**
4
4
 
5
- 38 specialized AI agents + 50 productivity tools. Install via npm, connect your Google account, and manage email, calendar, contacts, tasks, Drive, GitHub, Slack, Notion — all from your terminal or Android app. 100% local. Zero data on our servers.
5
+ 38 specialized AI agents + 50 productivity tools + web search. Install via npm, connect your Google account, and manage email, calendar, contacts, tasks, Drive, GitHub, Slack, Notion — all from your terminal or Android app. Streaming responses, multi-conversation history, export. 100% local. Zero data on our servers.
6
6
 
7
7
  ## Install
8
8
 
@@ -28,13 +28,15 @@ nha chat
28
28
 
29
29
  ## What You Can Do
30
30
 
31
- ### Chat with 50 Tools
31
+ ### Chat with 50 Tools + Web Search
32
32
 
33
33
  ```bash
34
34
  nha chat
35
35
  ```
36
36
 
37
- Ask naturally. The AI reads your email, manages your calendar, handles tasksall through conversation:
37
+ **Streaming responses** tokens appear as they're generated. **Multi-conversation** `/new`, `/list`, `/switch`, `/rename`, `/delete`. **Export** `/export md`, `/export json`. **Web search** built in.
38
+
39
+ Ask naturally. The AI reads your email, manages your calendar, searches the web, handles tasks — all through conversation:
38
40
 
39
41
  ```
40
42
  You: Read my latest emails
@@ -68,6 +70,7 @@ NHA: Available 60-min slots:
68
70
  | **GitHub** | Notifications, issues, PRs, create issues |
69
71
  | **Slack** | List channels, read messages, send messages |
70
72
  | **Notion** | Search pages/databases, read page content |
73
+ | **Web** | Web search (DuckDuckGo), fetch URL content, deep search with page extraction |
71
74
  | **Other** | Maps directions, reminders, file reading |
72
75
 
73
76
  Every tool is called directly from your machine to the provider's API. NHA servers are never involved.
@@ -226,10 +229,35 @@ Your Machine Provider APIs
226
229
 
227
230
  Anthropic (Claude), OpenAI (GPT), Google (Gemini), DeepSeek, xAI (Grok), Mistral, Cohere.
228
231
 
232
+ ## Chat Commands
233
+
234
+ Inside `nha chat`, use slash commands:
235
+
236
+ ```
237
+ /new Start a new conversation
238
+ /list List all conversations
239
+ /switch <id> Switch to a conversation
240
+ /rename <title> Rename current conversation
241
+ /delete <id> Delete a conversation
242
+ /export Export as Markdown (saved to ~/)
243
+ /export json Export as JSON (saved to ~/)
244
+ /agents List available agents
245
+ /agent <name> Switch to chatting with a specific agent
246
+ /agent off Return to NHA Chat
247
+ /create-agent Create a custom agent
248
+ /tasks Show today's tasks
249
+ /plan Run daily planner
250
+ /clear Clear current conversation
251
+ /help Show all commands
252
+ /quit Exit
253
+ ```
254
+
255
+ Inline agent routing: `@saber audit this function for SQL injection`
256
+
229
257
  ## Commands
230
258
 
231
259
  ```bash
232
- # Chat (50 tools)
260
+ # Chat (50 tools + web search, streaming)
233
261
  nha chat # Interactive chat with tools
234
262
  nha voice # Voice chat with TTS
235
263
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nothumanallowed",
3
- "version": "9.0.5",
4
- "description": "NotHumanAllowed — 38 AI agents + unified productivity suite. Gmail, Calendar, Drive, Contacts, Tasks, GitHub, Notion, Slack, voice chat, smart scheduler. Zero-dependency CLI.",
3
+ "version": "9.1.0",
4
+ "description": "NotHumanAllowed — 38 AI agents + 50 tools + web search. Streaming chat, multi-conversation, export. Gmail, Calendar, Drive, GitHub, Notion, Slack. Zero-dependency CLI.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "nha": "./bin/nha.mjs",
@@ -1,9 +1,12 @@
1
1
  /**
2
2
  * nha chat — Interactive conversational REPL for PAO (Personal Agent Ops).
3
3
  *
4
- * The user types natural language; an LLM interprets intent, optionally
5
- * invokes Gmail / Calendar / Tasks / GitHub / Notion / Slack APIs via a
6
- * structured JSON action protocol, and responds conversationally.
4
+ * Features:
5
+ * - Streaming responses (token-by-token display)
6
+ * - Multi-conversation management (/new, /list, /switch, /delete, /rename)
7
+ * - Export conversations (/export md, /export json)
8
+ * - @agent inline routing and /agent persistent mode
9
+ * - Tool execution with confirmation for destructive actions
7
10
  *
8
11
  * All tool definitions, parsing, and execution are in tool-executor.mjs (DRY).
9
12
  *
@@ -13,11 +16,12 @@
13
16
  import readline from 'readline';
14
17
  import fs from 'fs';
15
18
  import path from 'path';
19
+ import os from 'os';
16
20
  import { loadConfig } from '../config.mjs';
17
21
  import { AGENTS_DIR, AGENTS } from '../constants.mjs';
18
- import { callLLM, parseAgentFile } from '../services/llm.mjs';
22
+ import { callLLMStream, parseAgentFile } from '../services/llm.mjs';
19
23
 
20
- import { loadChatHistory, saveChatHistory, extractMemory } from '../services/memory.mjs';
24
+ import { extractMemory } from '../services/memory.mjs';
21
25
  import { fail, info, ok, warn, C, G, Y, D, W, BOLD, NC, R } from '../ui.mjs';
22
26
  import {
23
27
  DESTRUCTIVE_ACTIONS,
@@ -33,6 +37,20 @@ import {
33
37
  getUnreadImportant,
34
38
  } from '../services/mail-router.mjs';
35
39
  import { getTasks } from '../services/task-store.mjs';
40
+ import {
41
+ createConversation,
42
+ loadConversation,
43
+ saveConversation,
44
+ deleteConversation,
45
+ listConversations,
46
+ getOrCreateActive,
47
+ setActiveId,
48
+ getHistory,
49
+ addMessages,
50
+ exportAsMarkdown,
51
+ exportAsJson,
52
+ migrateOldHistory,
53
+ } from '../services/conversations.mjs';
36
54
 
37
55
  // ── Constants ────────────────────────────────────────────────────────────────
38
56
 
@@ -119,9 +137,23 @@ async function fetchInitialContext(config) {
119
137
  return parts.join('\n\n');
120
138
  }
121
139
 
140
+ // ── Relative Time ────────────────────────────────────────────────────────────
141
+
142
+ function relativeTime(isoString) {
143
+ const ms = Date.now() - new Date(isoString).getTime();
144
+ const minutes = Math.floor(ms / 60000);
145
+ if (minutes < 1) return 'just now';
146
+ if (minutes < 60) return `${minutes}m ago`;
147
+ const hours = Math.floor(minutes / 60);
148
+ if (hours < 24) return `${hours}h ago`;
149
+ const days = Math.floor(hours / 24);
150
+ if (days < 7) return `${days}d ago`;
151
+ return new Date(isoString).toLocaleDateString();
152
+ }
153
+
122
154
  // ── Slash Command Handlers ───────────────────────────────────────────────────
123
155
 
124
- async function handleSlashCommand(input, config, history) {
156
+ async function handleSlashCommand(input, config, conv, rl) {
125
157
  const trimmed = input.trim();
126
158
 
127
159
  if (trimmed === '/quit' || trimmed === '/exit' || trimmed === '/q') {
@@ -129,12 +161,106 @@ async function handleSlashCommand(input, config, history) {
129
161
  process.exit(0);
130
162
  }
131
163
 
164
+ // ── Multi-conversation commands ──────────────────────────────────────────
165
+
166
+ if (trimmed === '/new') {
167
+ const newConv = createConversation();
168
+ // Update the outer reference via return
169
+ console.log(` ${G}New conversation started.${NC} ${D}(${newConv.id})${NC}`);
170
+ return { handled: true, switchTo: newConv };
171
+ }
172
+
173
+ if (trimmed === '/list' || trimmed === '/conversations') {
174
+ const convs = listConversations();
175
+ if (convs.length === 0) {
176
+ console.log(` ${D}No conversations yet.${NC}`);
177
+ } else {
178
+ console.log(`\n ${BOLD}Conversations${NC} (${convs.length})\n`);
179
+ for (const c of convs) {
180
+ const active = c.id === conv.id ? ` ${G}<- active${NC}` : '';
181
+ const turns = Math.floor(c.messageCount / 2);
182
+ console.log(` ${C}${c.id}${NC} ${c.title} ${D}(${turns} turns, ${relativeTime(c.updatedAt)})${NC}${active}`);
183
+ }
184
+ console.log(`\n ${D}Switch: /switch <id> | New: /new | Delete: /delete <id>${NC}`);
185
+ }
186
+ return { handled: true };
187
+ }
188
+
189
+ if (trimmed.startsWith('/switch ')) {
190
+ const targetId = trimmed.slice(8).trim();
191
+ const target = loadConversation(targetId);
192
+ if (!target) {
193
+ console.log(` ${R}Conversation "${targetId}" not found. Use /list to see all.${NC}`);
194
+ return { handled: true };
195
+ }
196
+ setActiveId(targetId);
197
+ const turns = Math.floor(target.messages.length / 2);
198
+ console.log(` ${G}Switched to:${NC} ${target.title} ${D}(${turns} turns)${NC}`);
199
+ return { handled: true, switchTo: target };
200
+ }
201
+
202
+ if (trimmed.startsWith('/delete ')) {
203
+ const targetId = trimmed.slice(8).trim();
204
+ if (targetId === conv.id) {
205
+ console.log(` ${R}Cannot delete active conversation. Switch to another first.${NC}`);
206
+ return { handled: true };
207
+ }
208
+ if (deleteConversation(targetId)) {
209
+ console.log(` ${G}Deleted conversation ${targetId}.${NC}`);
210
+ } else {
211
+ console.log(` ${R}Conversation "${targetId}" not found.${NC}`);
212
+ }
213
+ return { handled: true };
214
+ }
215
+
216
+ if (trimmed.startsWith('/rename ')) {
217
+ const newTitle = trimmed.slice(8).trim();
218
+ if (!newTitle) {
219
+ console.log(` ${R}Usage: /rename <new title>${NC}`);
220
+ return { handled: true };
221
+ }
222
+ conv.title = newTitle;
223
+ saveConversation(conv);
224
+ console.log(` ${G}Renamed to:${NC} ${newTitle}`);
225
+ return { handled: true };
226
+ }
227
+
228
+ // ── Export commands ──────────────────────────────────────────────────────
229
+
230
+ if (trimmed === '/export' || trimmed === '/export md' || trimmed === '/export markdown') {
231
+ if (conv.messages.length === 0) {
232
+ console.log(` ${D}Nothing to export — conversation is empty.${NC}`);
233
+ return { handled: true };
234
+ }
235
+ const md = exportAsMarkdown(conv);
236
+ const filename = `nha-chat-${conv.id}.md`;
237
+ const filePath = path.join(os.homedir(), filename);
238
+ fs.writeFileSync(filePath, md, 'utf-8');
239
+ console.log(` ${G}Exported as Markdown:${NC} ~/${filename}`);
240
+ return { handled: true };
241
+ }
242
+
243
+ if (trimmed === '/export json') {
244
+ if (conv.messages.length === 0) {
245
+ console.log(` ${D}Nothing to export — conversation is empty.${NC}`);
246
+ return { handled: true };
247
+ }
248
+ const json = exportAsJson(conv);
249
+ const filename = `nha-chat-${conv.id}.json`;
250
+ const filePath = path.join(os.homedir(), filename);
251
+ fs.writeFileSync(filePath, json, 'utf-8');
252
+ console.log(` ${G}Exported as JSON:${NC} ~/${filename}`);
253
+ return { handled: true };
254
+ }
255
+
256
+ // ── Original commands ────────────────────────────────────────────────────
257
+
132
258
  if (trimmed === '/clear') {
133
- history.length = 0;
134
- try { saveChatHistory([]); } catch { /* non-critical */ }
259
+ conv.messages.length = 0;
260
+ saveConversation(conv);
135
261
  console.clear();
136
- console.log(` ${G}Conversation cleared (memory preserved, chat history reset).${NC}`);
137
- return true;
262
+ console.log(` ${G}Conversation cleared (memory preserved).${NC}`);
263
+ return { handled: true };
138
264
  }
139
265
 
140
266
  if (trimmed === '/tasks') {
@@ -151,7 +277,7 @@ async function handleSlashCommand(input, config, history) {
151
277
  } catch (err) {
152
278
  console.log(` ${R}Could not load tasks: ${err.message}${NC}`);
153
279
  }
154
- return true;
280
+ return { handled: true };
155
281
  }
156
282
 
157
283
  if (trimmed === '/plan') {
@@ -161,34 +287,34 @@ async function handleSlashCommand(input, config, history) {
161
287
  } catch (err) {
162
288
  console.log(` ${R}Plan error: ${err.message}${NC}`);
163
289
  }
164
- return true;
290
+ return { handled: true };
165
291
  }
166
292
 
167
293
  // /agent <name> — switch to talking with a specific agent
168
294
  if (trimmed.startsWith('/agent ')) {
169
295
  const agentName = trimmed.slice(7).trim().toLowerCase();
296
+
297
+ if (agentName === 'off' || agentName === 'reset') {
298
+ delete config._chatAgent;
299
+ console.log(` ${G}Switched back to NHA Chat.${NC}`);
300
+ return { handled: true };
301
+ }
302
+
170
303
  const agentFile = path.join(AGENTS_DIR, `${agentName}.mjs`);
171
304
  if (!fs.existsSync(agentFile)) {
172
305
  console.log(` ${R}Agent "${agentName}" not found. Available: ${AGENTS.join(', ')}${NC}`);
173
- return true;
306
+ return { handled: true };
174
307
  }
175
308
  const agentSource = fs.readFileSync(agentFile, 'utf-8');
176
309
  const { card, systemPrompt: agentSysPrompt } = parseAgentFile(agentSource, agentName);
177
310
  if (agentSysPrompt) {
178
- // Store agent context for subsequent messages
179
311
  config._chatAgent = { name: agentName, systemPrompt: agentSysPrompt, card };
180
312
  console.log(` ${G}Now chatting with ${BOLD}${card?.displayName || agentName.toUpperCase()}${NC}${G} (${card?.tagline || 'agent'})${NC}`);
181
313
  console.log(` ${D}Type /agent off to return to NHA Chat${NC}`);
182
314
  } else {
183
315
  console.log(` ${R}Agent "${agentName}" has no system prompt.${NC}`);
184
316
  }
185
- return true;
186
- }
187
-
188
- if (trimmed === '/agent off' || trimmed === '/agent reset') {
189
- delete config._chatAgent;
190
- console.log(` ${G}Switched back to NHA Chat.${NC}`);
191
- return true;
317
+ return { handled: true };
192
318
  }
193
319
 
194
320
  // /create-agent <name> "<tagline>" "<system prompt>"
@@ -198,24 +324,21 @@ async function handleSlashCommand(input, config, history) {
198
324
  console.log(`\n ${BOLD}${Y}Create Custom Agent${NC}`);
199
325
  console.log(` Usage: ${C}/create-agent mybot "Short description" "You are an expert in..."${NC}`);
200
326
  console.log(` Example: ${D}/create-agent chef "Italian cooking expert" "You are a master Italian chef. Always suggest authentic recipes with step-by-step instructions."${NC}\n`);
201
- return true;
327
+ return { handled: true };
202
328
  }
203
- // Parse: name "tagline" "system prompt"
204
329
  const nameMatch = parts.match(/^(\S+)\s+(.+)/);
205
330
  if (!nameMatch) {
206
331
  console.log(` ${R}Usage: /create-agent <name> "<tagline>" "<system prompt>"${NC}`);
207
- return true;
332
+ return { handled: true };
208
333
  }
209
334
  const name = nameMatch[1].toLowerCase().replace(/[^a-z0-9_-]/g, '');
210
335
  const rest = nameMatch[2];
211
- // Split remaining by quotes
212
336
  const quoteParts = rest.match(/"([^"]*)"/g);
213
337
  let tagline = '', sysPrompt = '';
214
338
  if (quoteParts && quoteParts.length >= 2) {
215
339
  tagline = quoteParts[0].replace(/"/g, '');
216
340
  sysPrompt = quoteParts[1].replace(/"/g, '');
217
341
  } else {
218
- // Fallback: first sentence is tagline, rest is prompt
219
342
  const firstDot = rest.indexOf('.');
220
343
  if (firstDot > 0) {
221
344
  tagline = rest.slice(0, firstDot).replace(/"/g, '').trim();
@@ -228,13 +351,13 @@ async function handleSlashCommand(input, config, history) {
228
351
 
229
352
  if (!name || !tagline || !sysPrompt) {
230
353
  console.log(` ${R}All fields required. Usage: /create-agent name "tagline" "system prompt"${NC}`);
231
- return true;
354
+ return { handled: true };
232
355
  }
233
356
 
234
357
  const agentFile = path.join(AGENTS_DIR, `${name}.mjs`);
235
358
  if (fs.existsSync(agentFile)) {
236
359
  console.log(` ${R}Agent "${name}" already exists.${NC}`);
237
- return true;
360
+ return { handled: true };
238
361
  }
239
362
 
240
363
  const content = `// NHA Custom Agent: ${name}\n// Created: ${new Date().toISOString()}\n\nexport const CARD = {\n name: '${name}',\n displayName: '${name.toUpperCase()}',\n category: 'custom',\n tagline: '${tagline.replace(/'/g, "\\'")}',\n};\n\nexport const SYSTEM_PROMPT = \`${sysPrompt.replace(/`/g, '\\`')}\`;\n`;
@@ -242,7 +365,7 @@ async function handleSlashCommand(input, config, history) {
242
365
  fs.writeFileSync(agentFile, content, 'utf-8');
243
366
  console.log(` ${G}Agent "${name}" created!${NC}`);
244
367
  console.log(` ${D}Switch to it: /agent ${name}${NC}`);
245
- return true;
368
+ return { handled: true };
246
369
  }
247
370
 
248
371
  // /agents — list available agents
@@ -258,34 +381,45 @@ async function handleSlashCommand(input, config, history) {
258
381
  console.log(` ${C}${a}${NC}`);
259
382
  }
260
383
  console.log(`\n ${D}Switch: /agent <name> | Create: /create-agent${NC}`);
261
- return true;
384
+ return { handled: true };
262
385
  }
263
386
 
264
387
  if (trimmed === '/help') {
265
388
  console.log(`
266
389
  ${BOLD}Chat Commands${NC}
267
390
 
268
- ${C}/tasks${NC} Show today's tasks
269
- ${C}/plan${NC} Run daily planner
391
+ ${BOLD}Conversations${NC}
392
+ ${C}/new${NC} Start a new conversation
393
+ ${C}/list${NC} List all conversations
394
+ ${C}/switch <id>${NC} Switch to a conversation
395
+ ${C}/rename <title>${NC} Rename current conversation
396
+ ${C}/delete <id>${NC} Delete a conversation
397
+ ${C}/export${NC} Export as Markdown (~/)
398
+ ${C}/export json${NC} Export as JSON (~/)
399
+
400
+ ${BOLD}Agents${NC}
270
401
  ${C}/agents${NC} List available agents
271
402
  ${C}/agent <name>${NC} Switch to chatting with a specific agent
272
403
  ${C}/agent off${NC} Return to NHA Chat
273
- ${C}/create-agent${NC} Create a new custom agent interactively
274
- ${C}/clear${NC} Clear conversation history
404
+ ${C}/create-agent${NC} Create a new custom agent
405
+
406
+ ${BOLD}Tools${NC}
407
+ ${C}/tasks${NC} Show today's tasks
408
+ ${C}/plan${NC} Run daily planner
409
+ ${C}/clear${NC} Clear current conversation
275
410
  ${C}/help${NC} Show this help
276
411
  ${C}/quit${NC} Exit chat
277
412
 
278
- ${D}You can also type @agent in any message to route it to that agent.
413
+ ${D}Tip: Type @agent in any message to route it inline.
279
414
  Example: "@saber audit this function for SQL injection"
280
415
 
281
- Otherwise, just type naturally — the AI understands
282
- requests like "show my unread emails", "add a task to review PR #42",
283
- "what's on my calendar tomorrow?", "list GitHub issues", etc.${NC}
416
+ Type naturally — "show my unread emails", "add a task",
417
+ "what's on my calendar tomorrow?", "list GitHub issues"${NC}
284
418
  `);
285
- return true;
419
+ return { handled: true };
286
420
  }
287
421
 
288
- return false;
422
+ return { handled: false };
289
423
  }
290
424
 
291
425
  // ── Main REPL ────────────────────────────────────────────────────────────────
@@ -298,12 +432,26 @@ export async function cmdChat(args) {
298
432
  process.exit(1);
299
433
  }
300
434
 
435
+ // Migrate old single-file chat history on first run
436
+ migrateOldHistory();
437
+
438
+ // Load or create active conversation
439
+ let conv = getOrCreateActive();
440
+
301
441
  console.log(`
302
442
  ${BOLD}${C}NHA Chat${NC} ${D}— Personal Operations Assistant${NC}
303
443
  ${D}Type naturally to manage emails, calendar, tasks, GitHub, Notion, Slack.${NC}
304
- ${D}Commands: /tasks /plan /clear /help /quit${NC}
444
+ ${D}Commands: /new /list /export /help /quit${NC}
305
445
  `);
306
446
 
447
+ // Show active conversation info
448
+ const turns = Math.floor(conv.messages.length / 2);
449
+ if (turns > 0) {
450
+ ok(`Conversation: "${conv.title}" (${turns} turns)`);
451
+ } else {
452
+ info(`New conversation started. (${conv.id})`);
453
+ }
454
+
307
455
  info('Loading today\'s context...');
308
456
  let initialContext = '';
309
457
  try {
@@ -322,10 +470,6 @@ export async function cmdChat(args) {
322
470
  terminal: true,
323
471
  });
324
472
 
325
- const history = loadChatHistory();
326
- if (history.length > 0) {
327
- ok(`Loaded ${Math.floor(history.length / 2)} previous conversation turns from memory.`);
328
- }
329
473
  const systemPrompt = buildSystemPrompt('NHA Chat', CHAT_PERSONA, config, initialContext);
330
474
 
331
475
  rl.on('close', () => {
@@ -356,8 +500,12 @@ export async function cmdChat(args) {
356
500
  }
357
501
 
358
502
  if (input.startsWith('/')) {
359
- const handled = await handleSlashCommand(input, config, history);
360
- if (handled) {
503
+ const result = await handleSlashCommand(input, config, conv, rl);
504
+ if (result.handled) {
505
+ // Handle conversation switch
506
+ if (result.switchTo) {
507
+ conv = result.switchTo;
508
+ }
361
509
  rl.prompt();
362
510
  continue;
363
511
  }
@@ -382,22 +530,29 @@ export async function cmdChat(args) {
382
530
  }
383
531
  }
384
532
  } else if (config._chatAgent) {
385
- // Persistent agent mode via /agent <name>
386
533
  effectiveSystemPrompt = config._chatAgent.systemPrompt;
387
534
  }
388
535
 
536
+ const history = getHistory(conv, MAX_HISTORY);
389
537
  const userMessage = serializeHistory(history, effectiveInput);
390
538
 
391
539
  process.stdout.write(`\n ${D}Thinking...${NC}`);
392
- const response = await callLLM(config, effectiveSystemPrompt, userMessage);
393
- process.stdout.write('\r' + ' '.repeat(40) + '\r');
540
+ let firstToken = true;
541
+ const response = await callLLMStream(config, effectiveSystemPrompt, userMessage, (chunk) => {
542
+ if (firstToken) {
543
+ process.stdout.write('\r' + ' '.repeat(40) + '\r\n ');
544
+ firstToken = false;
545
+ }
546
+ process.stdout.write(chunk);
547
+ });
548
+ if (firstToken) {
549
+ process.stdout.write('\r' + ' '.repeat(40) + '\r');
550
+ } else {
551
+ process.stdout.write('\n');
552
+ }
394
553
 
395
554
  const { textParts, actions } = parseActions(response);
396
-
397
- if (textParts.length > 0) {
398
- const text = textParts.join('\n\n');
399
- console.log(`\n ${W}${text}${NC}\n`);
400
- }
555
+ console.log('');
401
556
 
402
557
  for (const { action, params } of actions) {
403
558
  const isDestructive = DESTRUCTIVE_ACTIONS.has(action);
@@ -408,8 +563,7 @@ export async function cmdChat(args) {
408
563
 
409
564
  if (!confirmed) {
410
565
  console.log(` ${D}Cancelled.${NC}\n`);
411
- history.push({ role: 'user', content: input });
412
- history.push({ role: 'assistant', content: response + '\n[User cancelled this action]' });
566
+ addMessages(conv, input, response + '\n[User cancelled this action]');
413
567
  continue;
414
568
  }
415
569
  }
@@ -420,33 +574,18 @@ export async function cmdChat(args) {
420
574
  process.stdout.write('\r' + ' '.repeat(60) + '\r');
421
575
  console.log(` ${G}Result:${NC}\n ${result.split('\n').join('\n ')}\n`);
422
576
 
423
- history.push({ role: 'user', content: input });
424
- history.push({
425
- role: 'assistant',
426
- content: response + `\n\n[Tool ${action} executed. Result: ${result}]`,
427
- });
577
+ addMessages(conv, input, response + `\n\n[Tool ${action} executed. Result: ${result}]`);
428
578
  } catch (err) {
429
579
  process.stdout.write('\r' + ' '.repeat(60) + '\r');
430
580
  console.log(` ${R}Error executing ${action}: ${err.message}${NC}\n`);
431
- history.push({ role: 'user', content: input });
432
- history.push({
433
- role: 'assistant',
434
- content: response + `\n\n[Tool ${action} failed: ${err.message}]`,
435
- });
581
+ addMessages(conv, input, response + `\n\n[Tool ${action} failed: ${err.message}]`);
436
582
  }
437
583
  }
438
584
 
439
585
  if (actions.length === 0) {
440
- history.push({ role: 'user', content: input });
441
- history.push({ role: 'assistant', content: response });
442
- }
443
-
444
- while (history.length > MAX_HISTORY * 2) {
445
- history.shift();
446
- history.shift();
586
+ addMessages(conv, input, response);
447
587
  }
448
588
 
449
- try { saveChatHistory(history); } catch { /* non-critical */ }
450
589
  try { extractMemory('chat', input, response); } catch { /* non-critical */ }
451
590
  } catch (err) {
452
591
  process.stdout.write('\r' + ' '.repeat(40) + '\r');
package/src/constants.mjs CHANGED
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
5
5
  const __filename = fileURLToPath(import.meta.url);
6
6
  const __dirname = path.dirname(__filename);
7
7
 
8
- export const VERSION = '9.0.5';
8
+ export const VERSION = '9.1.0';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11