0agent 1.0.18 → 1.0.20

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.
Files changed (3) hide show
  1. package/bin/0agent.js +44 -11
  2. package/bin/chat.js +427 -0
  3. package/package.json +6 -3
package/bin/0agent.js CHANGED
@@ -45,7 +45,22 @@ if (cmd.startsWith('/')) {
45
45
 
46
46
  // ─── Command dispatch ────────────────────────────────────────────────────
47
47
  switch (cmd) {
48
- case '':
48
+ case '': {
49
+ // No args: if configured → open chat TUI; if not → run init wizard
50
+ if (existsSync(CONFIG_PATH)) {
51
+ const { spawn: spawnC } = await import('node:child_process');
52
+ const chatSc = resolve(dirname(new URL(import.meta.url).pathname), 'chat.js');
53
+ if (existsSync(chatSc)) {
54
+ const p = spawnC(process.execPath, [chatSc], { stdio: 'inherit' });
55
+ await new Promise(r => p.on('close', r));
56
+ } else {
57
+ await runChat();
58
+ }
59
+ } else {
60
+ await runInit();
61
+ }
62
+ break;
63
+ }
49
64
  case 'init':
50
65
  await runInit();
51
66
  break;
@@ -66,9 +81,18 @@ switch (cmd) {
66
81
  await runTask(args.slice(1));
67
82
  break;
68
83
 
69
- case 'chat':
70
- await runChat();
84
+ case 'chat': {
85
+ // Launch the persistent Claude Code-style TUI
86
+ const { spawn: spawnChat } = await import('node:child_process');
87
+ const chatScript = resolve(dirname(new URL(import.meta.url).pathname), 'chat.js');
88
+ if (existsSync(chatScript)) {
89
+ const p = spawnChat(process.execPath, [chatScript], { stdio: 'inherit' });
90
+ await new Promise(r => p.on('close', r));
91
+ } else {
92
+ await runChat(); // fallback to old simple REPL
93
+ }
71
94
  break;
95
+ }
72
96
 
73
97
  case 'skill':
74
98
  await runSkillCommand(args.slice(1));
@@ -112,20 +136,28 @@ switch (cmd) {
112
136
  // Arrow-key select using enquirer (falls back to number input if not available)
113
137
  async function arrowSelect(message, choices, initial = 0) {
114
138
  try {
115
- const { Select } = await import('enquirer');
116
- const prompt = new Select({ message, choices: choices.map((c, i) => ({ name: c, value: i })), initial });
139
+ const mod = await import('enquirer');
140
+ const Enquirer = mod.default ?? mod;
141
+ const prompt = new Enquirer.Select({
142
+ message,
143
+ choices: choices.map((c, i) => ({ name: c, value: String(i) })),
144
+ initial,
145
+ });
117
146
  const answer = await prompt.run();
118
- return choices.indexOf(answer);
147
+ // answer is the name string — find its index
148
+ const idx = choices.indexOf(answer);
149
+ return idx >= 0 ? idx : initial;
119
150
  } catch {
120
- // Fallback: number-based selection (no enquirer)
151
+ // Fallback: number-based selection if enquirer unavailable or non-TTY
121
152
  return choose(message, choices, initial);
122
153
  }
123
154
  }
124
155
 
125
156
  async function arrowInput(message, initial = '') {
126
157
  try {
127
- const { Input } = await import('enquirer');
128
- const prompt = new Input({ message, initial });
158
+ const mod = await import('enquirer');
159
+ const Enquirer = mod.default ?? mod;
160
+ const prompt = new Enquirer.Input({ message, initial });
129
161
  return await prompt.run();
130
162
  } catch {
131
163
  return ask(` ${message}: `);
@@ -134,8 +166,9 @@ async function arrowInput(message, initial = '') {
134
166
 
135
167
  async function arrowPassword(message) {
136
168
  try {
137
- const { Password } = await import('enquirer');
138
- const prompt = new Password({ message });
169
+ const mod = await import('enquirer');
170
+ const Enquirer = mod.default ?? mod;
171
+ const prompt = new Enquirer.Password({ message });
139
172
  return await prompt.run();
140
173
  } catch {
141
174
  return ask(` ${message}: `);
package/bin/chat.js ADDED
@@ -0,0 +1,427 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 0agent Chat — persistent, streaming TUI.
4
+ *
5
+ * Stays open like Claude Code. Commands start with /.
6
+ * Responses stream word-by-word. Subagents visible inline.
7
+ * /model to switch. /key to add provider keys. Never forgets previous keys.
8
+ */
9
+
10
+ import { createInterface } from 'node:readline';
11
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
12
+ import { resolve } from 'node:path';
13
+ import { homedir } from 'node:os';
14
+ import YAML from 'yaml';
15
+
16
+ const AGENT_DIR = resolve(homedir(), '.0agent');
17
+ const CONFIG_PATH = resolve(AGENT_DIR, 'config.yaml');
18
+ const BASE_URL = process.env['ZEROAGENT_URL'] ?? 'http://localhost:4200';
19
+
20
+ // ─── ANSI helpers ─────────────────────────────────────────────────────────────
21
+ const C = {
22
+ reset: '\x1b[0m',
23
+ bold: '\x1b[1m',
24
+ dim: '\x1b[2m',
25
+ cyan: '\x1b[36m',
26
+ green: '\x1b[32m',
27
+ yellow: '\x1b[33m',
28
+ red: '\x1b[31m',
29
+ blue: '\x1b[34m',
30
+ magenta: '\x1b[35m',
31
+ white: '\x1b[37m',
32
+ };
33
+ const fmt = (color, text) => `${color}${text}${C.reset}`;
34
+ const clearLine = () => process.stdout.write('\r\x1b[2K');
35
+
36
+ // ─── Config management ────────────────────────────────────────────────────────
37
+ function loadConfig() {
38
+ if (!existsSync(CONFIG_PATH)) return null;
39
+ try { return YAML.parse(readFileSync(CONFIG_PATH, 'utf8')); } catch { return null; }
40
+ }
41
+
42
+ function saveConfig(cfg) {
43
+ writeFileSync(CONFIG_PATH, YAML.stringify(cfg), 'utf8');
44
+ }
45
+
46
+ function getCurrentProvider(cfg) {
47
+ const def = cfg?.llm_providers?.find(p => p.is_default) ?? cfg?.llm_providers?.[0];
48
+ return def ?? null;
49
+ }
50
+
51
+ // ─── State ────────────────────────────────────────────────────────────────────
52
+ let cfg = loadConfig();
53
+ let sessionId = null; // current chat session
54
+ let streaming = false; // mid-token-stream
55
+ let ws = null; // WebSocket
56
+ let wsReady = false;
57
+ let pendingResolve = null;
58
+ let lineBuffer = ''; // accumulated line for stream
59
+ const history = []; // command history for arrow keys
60
+
61
+ // ─── Header ──────────────────────────────────────────────────────────────────
62
+ function printHeader() {
63
+ const provider = getCurrentProvider(cfg);
64
+ const modelStr = provider ? `${provider.provider}/${provider.model}` : 'no model';
65
+ console.log();
66
+ console.log(fmt(C.bold, ' 0agent') + fmt(C.dim, ` — ${modelStr}`));
67
+ console.log(fmt(C.dim, ' Type a task, or /help for commands. Ctrl+C to exit.\n'));
68
+ }
69
+
70
+ function printInsights() {
71
+ fetch(`${BASE_URL}/api/insights?seen=false`)
72
+ .then(r => r.json())
73
+ .then(insights => {
74
+ if (!Array.isArray(insights) || insights.length === 0) return;
75
+ console.log(fmt(C.yellow, ` ${insights.length} insight${insights.length > 1 ? 's' : ''} since last session:`));
76
+ for (const ins of insights.slice(0, 2)) {
77
+ console.log(` ${fmt(C.dim, '›')} ${ins.summary}`);
78
+ if (ins.suggested_action) console.log(` ${fmt(C.cyan, '→')} ${fmt(C.dim, ins.suggested_action)}`);
79
+ }
80
+ console.log();
81
+ })
82
+ .catch(() => {});
83
+ }
84
+
85
+ // ─── WebSocket ────────────────────────────────────────────────────────────────
86
+ async function connectWS() {
87
+ try {
88
+ const { default: WS } = await import('ws').catch(() => ({ default: globalThis.WebSocket }));
89
+ ws = new WS(`ws://localhost:4200/ws`);
90
+ ws.on('open', () => {
91
+ wsReady = true;
92
+ ws.send(JSON.stringify({ type: 'subscribe', topics: ['sessions', 'graph', 'insights'] }));
93
+ });
94
+ ws.on('message', data => handleWsEvent(JSON.parse(data.toString())));
95
+ ws.on('close', () => { wsReady = false; setTimeout(connectWS, 2000); });
96
+ ws.on('error', () => { wsReady = false; });
97
+ } catch {}
98
+ }
99
+
100
+ function handleWsEvent(event) {
101
+ if (!sessionId || event.session_id !== sessionId) return;
102
+
103
+ switch (event.type) {
104
+ case 'session.step': {
105
+ if (streaming) { process.stdout.write('\n'); streaming = false; }
106
+ console.log(` ${fmt(C.dim, '›')} ${event.step}`);
107
+ break;
108
+ }
109
+ case 'session.token': {
110
+ if (!streaming) { process.stdout.write('\n '); streaming = true; }
111
+ process.stdout.write(event.token);
112
+ lineBuffer += event.token;
113
+ break;
114
+ }
115
+ case 'session.completed': {
116
+ if (streaming) { process.stdout.write('\n'); streaming = false; }
117
+ const r = event.result ?? {};
118
+ if (r.files_written?.length) console.log(`\n ${fmt(C.green, '✓')} ${r.files_written.join(', ')}`);
119
+ if (r.tokens_used) process.stdout.write(fmt(C.dim, `\n ${r.tokens_used} tokens · ${r.model ?? ''}\n`));
120
+
121
+ // Confirm server if port mentioned
122
+ confirmServer(r, lineBuffer);
123
+ lineBuffer = '';
124
+ if (pendingResolve) { pendingResolve(); pendingResolve = null; }
125
+ rl.prompt();
126
+ break;
127
+ }
128
+ case 'session.failed': {
129
+ if (streaming) { process.stdout.write('\n'); streaming = false; }
130
+ console.log(`\n ${fmt(C.red, '✗')} ${event.error}\n`);
131
+ lineBuffer = '';
132
+ if (pendingResolve) { pendingResolve(); pendingResolve = null; }
133
+ rl.prompt();
134
+ break;
135
+ }
136
+ case 'agent.insight': {
137
+ // Show insight inline, non-interruptive
138
+ const ins = event.insight ?? {};
139
+ process.stdout.write(`\n ${fmt(C.yellow, '◆')} ${ins.summary}\n`);
140
+ if (ins.suggested_action) process.stdout.write(` ${fmt(C.dim, `→ ${ins.suggested_action}`)}\n`);
141
+ if (!streaming) rl.prompt(true);
142
+ break;
143
+ }
144
+ case 'graph.weight_updated': {
145
+ // Subtle dot — graph is learning
146
+ process.stdout.write(fmt(C.dim, '·'));
147
+ break;
148
+ }
149
+ }
150
+ }
151
+
152
+ async function confirmServer(result, output) {
153
+ const allText = [...(result.commands_run ?? []), output].join(' ');
154
+ const portMatch = allText.match(/(?:localhost:|port\s*[=:]?\s*)(\d{4,5})/i);
155
+ if (!portMatch) return;
156
+ const port = parseInt(portMatch[1], 10);
157
+ await new Promise(r => setTimeout(r, 1200));
158
+ try {
159
+ const res = await fetch(`http://localhost:${port}/`, { signal: AbortSignal.timeout(2000) });
160
+ console.log(`\n ${fmt(C.green, '⬡')} Live at ${fmt(C.cyan, `http://localhost:${port}`)} (HTTP ${res.status})`);
161
+ } catch {}
162
+ }
163
+
164
+ // ─── Task submission ──────────────────────────────────────────────────────────
165
+ async function runTask(input) {
166
+ let skillName, entityId;
167
+ let task = input;
168
+
169
+ // Parse inline flags: --skill /review, --entity sahil
170
+ const skillMatch = task.match(/--skill\s+([\w-]+)/);
171
+ if (skillMatch) { skillName = skillMatch[1]; task = task.replace(skillMatch[0], '').trim(); }
172
+ const entityMatch = task.match(/--entity\s+([\w-]+)/);
173
+ if (entityMatch) { entityId = entityMatch[1]; task = task.replace(entityMatch[0], '').trim(); }
174
+
175
+ // Slash-prefix → skill
176
+ if (task.startsWith('/') && !task.startsWith('/model') && !task.startsWith('/key')) {
177
+ const parts = task.split(/\s+/);
178
+ skillName = parts[0].slice(1);
179
+ task = parts.slice(1).join(' ') || `Run the /${skillName} skill`;
180
+ }
181
+
182
+ const body = { task, ...(skillName && { skill: skillName }), ...(entityId && { entity_id: entityId }) };
183
+
184
+ try {
185
+ const res = await fetch(`${BASE_URL}/api/sessions`, {
186
+ method: 'POST',
187
+ headers: { 'Content-Type': 'application/json' },
188
+ body: JSON.stringify(body),
189
+ });
190
+ const s = await res.json();
191
+ sessionId = s.session_id ?? s.id;
192
+ return new Promise(resolve => { pendingResolve = resolve; });
193
+ } catch (e) {
194
+ console.log(` ${fmt(C.red, '✗')} ${e.message}`);
195
+ }
196
+ }
197
+
198
+ // ─── Commands ─────────────────────────────────────────────────────────────────
199
+ async function handleCommand(input) {
200
+ const parts = input.trim().split(/\s+/);
201
+ const cmd = parts[0].toLowerCase();
202
+
203
+ switch (cmd) {
204
+ // /model — list or switch model
205
+ case '/model': {
206
+ if (!cfg) { console.log(fmt(C.red, ' No config found. Run: 0agent init')); break; }
207
+ const providers = cfg.llm_providers ?? [];
208
+ const current = getCurrentProvider(cfg);
209
+
210
+ if (parts.length === 1) {
211
+ // List current + available
212
+ console.log('\n Current model:');
213
+ console.log(` ${fmt(C.green, '●')} ${current?.provider}/${current?.model}\n`);
214
+ console.log(' All configured providers:');
215
+ for (const p of providers) {
216
+ const marker = p.is_default ? fmt(C.green, '●') : fmt(C.dim, '○');
217
+ console.log(` ${marker} ${p.provider}/${p.model}`);
218
+ }
219
+ console.log('\n ' + fmt(C.dim, 'Usage: /model anthropic claude-opus-4-6'));
220
+ console.log(' ' + fmt(C.dim, ' /model openai gpt-4o'));
221
+ console.log(' ' + fmt(C.dim, ' /model add anthropic sk-ant-...') + '\n');
222
+ } else if (parts[1] === 'add') {
223
+ // /model add <provider> <api-key>
224
+ const provider = parts[2];
225
+ const key = parts[3];
226
+ if (!provider || !key) { console.log(fmt(C.red, ' Usage: /model add <provider> <api-key>')); break; }
227
+ const defaultModels = { anthropic: 'claude-sonnet-4-6', openai: 'gpt-4o', xai: 'grok-3', gemini: 'gemini-2.0-flash', ollama: 'llama3.1' };
228
+ const existing = providers.findIndex(p => p.provider === provider);
229
+ if (existing >= 0) {
230
+ cfg.llm_providers[existing].api_key = key;
231
+ console.log(` ${fmt(C.green, '✓')} Updated ${provider} API key`);
232
+ } else {
233
+ cfg.llm_providers.push({ provider, model: defaultModels[provider] ?? provider, api_key: key, is_default: false });
234
+ console.log(` ${fmt(C.green, '✓')} Added ${provider}`);
235
+ }
236
+ saveConfig(cfg);
237
+ console.log(` ${fmt(C.dim, 'Restart daemon for changes to take effect: 0agent stop && 0agent start')}\n`);
238
+ } else {
239
+ // /model <provider> <model> OR /model <provider>
240
+ const provider = parts[1];
241
+ const model = parts[2];
242
+ // Set as default
243
+ for (const p of providers) p.is_default = false;
244
+ const match = providers.find(p => p.provider === provider);
245
+ if (match) {
246
+ match.is_default = true;
247
+ if (model) match.model = model;
248
+ saveConfig(cfg);
249
+ cfg = loadConfig();
250
+ console.log(` ${fmt(C.green, '✓')} Switched to ${fmt(C.cyan, `${provider}/${match.model}`)}`);
251
+ console.log(` ${fmt(C.dim, 'Restart daemon to apply: 0agent stop && 0agent start\n')}`);
252
+ } else {
253
+ console.log(` ${fmt(C.red, '✗')} Provider "${provider}" not found. Add it first with:`);
254
+ console.log(` ${fmt(C.dim, `/model add ${provider} <api-key>`)}\n`);
255
+ }
256
+ }
257
+ break;
258
+ }
259
+
260
+ // /key — add or list API keys
261
+ case '/key': {
262
+ if (!cfg) { console.log(fmt(C.red, ' No config found. Run: 0agent init')); break; }
263
+ if (parts.length === 1) {
264
+ // List masked keys
265
+ console.log('\n Stored API keys:\n');
266
+ for (const p of cfg.llm_providers ?? []) {
267
+ const masked = p.api_key ? p.api_key.slice(0, 10) + '••••••' : fmt(C.dim, '(not set)');
268
+ console.log(` ${p.provider.padEnd(12)} ${masked}`);
269
+ }
270
+ console.log('\n ' + fmt(C.dim, 'Usage: /key <provider> <api-key>') + '\n');
271
+ } else {
272
+ const provider = parts[1];
273
+ const key = parts[2];
274
+ if (!key) { console.log(fmt(C.red, ` Usage: /key ${provider} <api-key>`)); break; }
275
+ const match = cfg.llm_providers?.find(p => p.provider === provider);
276
+ if (match) {
277
+ match.api_key = key;
278
+ saveConfig(cfg);
279
+ cfg = loadConfig();
280
+ console.log(` ${fmt(C.green, '✓')} ${provider} key updated (${key.slice(0, 8)}••••)\n`);
281
+ } else {
282
+ console.log(` ${fmt(C.yellow, '⚠')} "${provider}" not configured. Use /model add ${provider} ${key}\n`);
283
+ }
284
+ }
285
+ break;
286
+ }
287
+
288
+ // /status
289
+ case '/status': {
290
+ try {
291
+ const h = await fetch(`${BASE_URL}/api/health`).then(r => r.json());
292
+ console.log(`\n ${fmt(C.green, '✓')} Daemon running`);
293
+ console.log(` Graph: ${h.graph_nodes} nodes · ${h.graph_edges} edges`);
294
+ console.log(` Sessions: ${h.active_sessions} active`);
295
+ console.log(` Sandbox: ${h.sandbox_backend}\n`);
296
+ } catch {
297
+ console.log(` ${fmt(C.red, '✗')} Daemon not running. Run: 0agent start\n`);
298
+ }
299
+ break;
300
+ }
301
+
302
+ // /skills
303
+ case '/skills': {
304
+ try {
305
+ const skills = await fetch(`${BASE_URL}/api/skills`).then(r => r.json());
306
+ const list = Array.isArray(skills) ? skills : skills.skills ?? [];
307
+ console.log('\n Available skills:\n');
308
+ for (const s of list.slice(0, 15)) {
309
+ console.log(` ${fmt(C.cyan, `/${s.name.padEnd(20)}`)} ${fmt(C.dim, s.description?.slice(0, 55) ?? '')}`);
310
+ }
311
+ if (list.length > 15) console.log(fmt(C.dim, ` ... and ${list.length - 15} more\n`));
312
+ else console.log();
313
+ } catch { console.log(fmt(C.dim, ' Daemon not running\n')); }
314
+ break;
315
+ }
316
+
317
+ // /graph
318
+ case '/graph': {
319
+ console.log(` ${fmt(C.cyan, 'Knowledge graph:')} ${fmt(C.dim, 'http://localhost:4200')}\n`);
320
+ try {
321
+ const { execSync } = await import('node:child_process');
322
+ execSync('open http://localhost:4200 2>/dev/null || xdg-open http://localhost:4200 2>/dev/null', { stdio: 'ignore' });
323
+ } catch {}
324
+ break;
325
+ }
326
+
327
+ // /clear
328
+ case '/clear':
329
+ process.stdout.write('\x1b[2J\x1b[H');
330
+ printHeader();
331
+ break;
332
+
333
+ // /help
334
+ case '/help':
335
+ default: {
336
+ console.log('\n Commands:\n');
337
+ const cmds = [
338
+ ['/model', 'Show or switch model (/model openai gpt-4o)'],
339
+ ['/model add', 'Add provider key (/model add anthropic sk-ant-...)'],
340
+ ['/key <provider>', 'Update stored API key'],
341
+ ['/status', 'Daemon health + graph stats'],
342
+ ['/skills', 'List available skills'],
343
+ ['/graph', 'Open 3D knowledge graph in browser'],
344
+ ['/clear', 'Clear screen'],
345
+ ['/<skill>', 'Run a skill (/review, /build, /qa, /debug...)'],
346
+ ['Ctrl+C', 'Exit'],
347
+ ];
348
+ for (const [c, d] of cmds) {
349
+ console.log(` ${fmt(C.cyan, c.padEnd(20))} ${fmt(C.dim, d)}`);
350
+ }
351
+ console.log();
352
+ break;
353
+ }
354
+ }
355
+ }
356
+
357
+ // ─── Main REPL ────────────────────────────────────────────────────────────────
358
+ const rl = createInterface({
359
+ input: process.stdin,
360
+ output: process.stdout,
361
+ prompt: `\n ${fmt(C.cyan, '›')} `,
362
+ historySize: 100,
363
+ completer: (line) => {
364
+ const commands = ['/model', '/key', '/status', '/skills', '/graph', '/clear', '/help',
365
+ '/review', '/build', '/debug', '/qa', '/research', '/refactor', '/test-writer', '/retro'];
366
+ const hits = commands.filter(c => c.startsWith(line));
367
+ return [hits.length ? hits : commands, line];
368
+ },
369
+ });
370
+
371
+ // Restore history from conversations if possible
372
+ rl.on('history', () => {});
373
+
374
+ printHeader();
375
+ printInsights();
376
+
377
+ // Connect WebSocket for live events
378
+ connectWS();
379
+
380
+ // Check if daemon is running
381
+ fetch(`${BASE_URL}/api/health`, { signal: AbortSignal.timeout(1500) })
382
+ .then(() => { rl.prompt(); })
383
+ .catch(async () => {
384
+ process.stdout.write(fmt(C.dim, ' Starting daemon'));
385
+ // Trigger auto-start via requireDaemon-equivalent
386
+ const { resolve: res, existsSync: ef } = await import('node:path').then(m => m);
387
+ const pkgRoot = res(new URL(import.meta.url).pathname, '..', '..');
388
+ const bundled = res(pkgRoot, 'dist', 'daemon.mjs');
389
+ if (ef(bundled) && ef(CONFIG_PATH)) {
390
+ const { spawn } = await import('node:child_process');
391
+ const child = spawn(process.execPath, [bundled], { detached: true, stdio: 'ignore', env: { ...process.env, ZEROAGENT_CONFIG: CONFIG_PATH } });
392
+ child.unref();
393
+ for (let i = 0; i < 20; i++) {
394
+ await new Promise(r => setTimeout(r, 500));
395
+ process.stdout.write(fmt(C.dim, '.'));
396
+ try { await fetch(`${BASE_URL}/api/health`, { signal: AbortSignal.timeout(500) }); process.stdout.write(fmt(C.green, ' ✓\n\n')); break; } catch {}
397
+ }
398
+ } else {
399
+ console.log(`\n ${fmt(C.dim, 'Run: 0agent start')}\n`);
400
+ }
401
+ rl.prompt();
402
+ });
403
+
404
+ rl.on('line', async (input) => {
405
+ const line = input.trim();
406
+ if (!line) { rl.prompt(); return; }
407
+
408
+ if (line.startsWith('/') || ['/model','/key','/status','/skills','/graph','/clear','/help'].some(c => line.startsWith(c))) {
409
+ await handleCommand(line);
410
+ rl.prompt();
411
+ } else {
412
+ await runTask(line);
413
+ // prompt() is called from WS handler after session.completed
414
+ // but fall back if WS not connected
415
+ if (!wsReady) rl.prompt();
416
+ }
417
+ });
418
+
419
+ rl.on('close', () => {
420
+ console.log(`\n ${fmt(C.dim, 'Goodbye.')}\n`);
421
+ process.exit(0);
422
+ });
423
+
424
+ process.on('SIGINT', () => {
425
+ console.log(`\n ${fmt(C.dim, 'Ctrl+C — type /help for commands or Ctrl+C again to exit')}\n`);
426
+ rl.prompt();
427
+ });
package/package.json CHANGED
@@ -1,18 +1,20 @@
1
1
  {
2
2
  "name": "0agent",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
4
4
  "description": "A persistent, learning AI agent that runs on your machine. An agent that learns.",
5
5
  "private": false,
6
6
  "license": "Apache-2.0",
7
7
  "type": "module",
8
8
  "bin": {
9
- "0agent": "./bin/0agent.js"
9
+ "0agent": "./bin/0agent.js",
10
+ "0agent-chat": "./bin/chat.js"
10
11
  },
11
12
  "files": [
12
13
  "bin/",
13
14
  "dist/",
14
15
  "skills/",
15
- "seeds/"
16
+ "seeds/",
17
+ "bin/chat.js"
16
18
  ],
17
19
  "scripts": {
18
20
  "build": "turbo run build",
@@ -24,6 +26,7 @@
24
26
  "dependencies": {
25
27
  "@hono/node-server": "^1.13.0",
26
28
  "better-sqlite3": "^11.6.0",
29
+ "enquirer": "^2.4.1",
27
30
  "hono": "^4.6.0",
28
31
  "ws": "^8.18.0",
29
32
  "yaml": "^2.6.0",