0agent 1.0.19 → 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.
- package/bin/0agent.js +27 -3
- package/bin/chat.js +427 -0
- package/package.json +5 -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
|
-
|
|
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));
|
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.
|
|
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",
|