@agntk/cli 0.1.2 → 0.2.1
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/dist/cli-v2.d.ts +14 -0
- package/dist/cli-v2.d.ts.map +1 -0
- package/dist/cli-v2.js +848 -0
- package/dist/cli-v2.js.map +1 -0
- package/dist/cli.d.ts +7 -4
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +923 -74
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +10 -40
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +40 -39
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -4
- package/dist/index.js.map +1 -1
- package/package.json +13 -13
- package/LICENSE +0 -22
package/dist/cli.js
CHANGED
|
@@ -1,126 +1,975 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* @fileoverview CLI entry point for agntk.
|
|
3
|
+
* @fileoverview CLI entry point for agntk — zero-config AI agent.
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
6
|
* npx agntk "do something"
|
|
7
|
-
* npx agntk
|
|
8
|
-
* npx agntk -
|
|
9
|
-
*
|
|
7
|
+
* npx agntk whats up
|
|
8
|
+
* npx agntk -n "my-agent" "fix the failing tests"
|
|
9
|
+
* npx agntk -n "my-agent" --instructions "you are a deploy bot" "roll back staging"
|
|
10
|
+
* npx agntk -n "my-agent" -i
|
|
11
|
+
* npx agntk list
|
|
12
|
+
* cat error.log | npx agntk -n "debugger" "explain these errors"
|
|
10
13
|
*/
|
|
11
14
|
// Load .env files before anything else reads process.env
|
|
12
15
|
import 'dotenv/config';
|
|
16
|
+
import { createInterface } from 'node:readline';
|
|
17
|
+
import { readdirSync, existsSync, writeFileSync, unlinkSync, readFileSync, statSync } from 'node:fs';
|
|
18
|
+
import { resolve, join } from 'node:path';
|
|
19
|
+
import { homedir } from 'node:os';
|
|
13
20
|
import { getVersion } from './version.js';
|
|
14
|
-
import {
|
|
15
|
-
import { resolveConfig } from './config.js';
|
|
16
|
-
// Note: environment.js and runner.js are dynamically imported below
|
|
17
|
-
// to avoid loading @agntk/core for --version and --help (fast paths)
|
|
21
|
+
import { detectApiKey } from './config.js';
|
|
18
22
|
// ============================================================================
|
|
19
|
-
//
|
|
23
|
+
// Constants
|
|
24
|
+
// ============================================================================
|
|
25
|
+
const AGENTS_DIR = resolve(homedir(), '.agntk', 'agents');
|
|
26
|
+
function createColors(enabled) {
|
|
27
|
+
if (!enabled) {
|
|
28
|
+
const identity = (s) => s;
|
|
29
|
+
return { dim: identity, bold: identity, cyan: identity, yellow: identity, green: identity, red: identity, magenta: identity, blue: identity, white: identity, reset: '' };
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
dim: (s) => `\x1b[2m${s}\x1b[22m`,
|
|
33
|
+
bold: (s) => `\x1b[1m${s}\x1b[22m`,
|
|
34
|
+
cyan: (s) => `\x1b[36m${s}\x1b[39m`,
|
|
35
|
+
yellow: (s) => `\x1b[33m${s}\x1b[39m`,
|
|
36
|
+
green: (s) => `\x1b[32m${s}\x1b[39m`,
|
|
37
|
+
red: (s) => `\x1b[31m${s}\x1b[39m`,
|
|
38
|
+
magenta: (s) => `\x1b[35m${s}\x1b[39m`,
|
|
39
|
+
blue: (s) => `\x1b[34m${s}\x1b[39m`,
|
|
40
|
+
white: (s) => `\x1b[97m${s}\x1b[39m`,
|
|
41
|
+
reset: '\x1b[0m',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// Spinner — braille-pattern loading indicator
|
|
46
|
+
// ============================================================================
|
|
47
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
48
|
+
const CLEAR_LINE = '\x1b[2K\r';
|
|
49
|
+
function createSpinner(stream, colors, enabled) {
|
|
50
|
+
let interval = null;
|
|
51
|
+
let frameIdx = 0;
|
|
52
|
+
return {
|
|
53
|
+
start(label) {
|
|
54
|
+
if (!enabled)
|
|
55
|
+
return;
|
|
56
|
+
this.stop();
|
|
57
|
+
frameIdx = 0;
|
|
58
|
+
interval = setInterval(() => {
|
|
59
|
+
const frame = SPINNER_FRAMES[frameIdx % SPINNER_FRAMES.length];
|
|
60
|
+
stream.write(`${CLEAR_LINE} ${colors.cyan(frame)} ${colors.dim(label)}`);
|
|
61
|
+
frameIdx++;
|
|
62
|
+
}, 80);
|
|
63
|
+
},
|
|
64
|
+
stop() {
|
|
65
|
+
if (interval) {
|
|
66
|
+
clearInterval(interval);
|
|
67
|
+
interval = null;
|
|
68
|
+
stream.write(CLEAR_LINE);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function parseCLIArgs(argv) {
|
|
74
|
+
const args = {
|
|
75
|
+
name: null,
|
|
76
|
+
instructions: null,
|
|
77
|
+
prompt: null,
|
|
78
|
+
interactive: false,
|
|
79
|
+
workspace: process.cwd(),
|
|
80
|
+
outputLevel: 'normal',
|
|
81
|
+
maxSteps: 25,
|
|
82
|
+
help: false,
|
|
83
|
+
version: false,
|
|
84
|
+
list: false,
|
|
85
|
+
};
|
|
86
|
+
const positionals = [];
|
|
87
|
+
for (let i = 0; i < argv.length; i++) {
|
|
88
|
+
const arg = argv[i];
|
|
89
|
+
switch (arg) {
|
|
90
|
+
case '--name':
|
|
91
|
+
case '-n':
|
|
92
|
+
args.name = argv[++i] ?? null;
|
|
93
|
+
break;
|
|
94
|
+
case '--instructions':
|
|
95
|
+
args.instructions = argv[++i] ?? null;
|
|
96
|
+
break;
|
|
97
|
+
case '-i':
|
|
98
|
+
case '--interactive':
|
|
99
|
+
args.interactive = true;
|
|
100
|
+
break;
|
|
101
|
+
case '--workspace':
|
|
102
|
+
args.workspace = argv[++i] ?? process.cwd();
|
|
103
|
+
break;
|
|
104
|
+
case '--verbose':
|
|
105
|
+
args.outputLevel = 'verbose';
|
|
106
|
+
break;
|
|
107
|
+
case '-q':
|
|
108
|
+
case '--quiet':
|
|
109
|
+
args.outputLevel = 'quiet';
|
|
110
|
+
break;
|
|
111
|
+
case '--max-steps':
|
|
112
|
+
args.maxSteps = parseInt(argv[++i] ?? '25', 10);
|
|
113
|
+
break;
|
|
114
|
+
case '-h':
|
|
115
|
+
case '--help':
|
|
116
|
+
args.help = true;
|
|
117
|
+
break;
|
|
118
|
+
case '-v':
|
|
119
|
+
case '--version':
|
|
120
|
+
args.version = true;
|
|
121
|
+
break;
|
|
122
|
+
case 'list':
|
|
123
|
+
if (positionals.length === 0) {
|
|
124
|
+
args.list = true;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
positionals.push(arg);
|
|
128
|
+
}
|
|
129
|
+
break;
|
|
130
|
+
default:
|
|
131
|
+
if (!arg.startsWith('-')) {
|
|
132
|
+
positionals.push(arg);
|
|
133
|
+
}
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Interpret positionals:
|
|
138
|
+
// All positionals join into the prompt. Use --name/-n for agent name.
|
|
139
|
+
// agntk "do something" → prompt = "do something"
|
|
140
|
+
// agntk whats up → prompt = "whats up"
|
|
141
|
+
// agntk -n myagent fix the tests → name = "myagent", prompt = "fix the tests"
|
|
142
|
+
if (positionals.length > 0) {
|
|
143
|
+
args.prompt = positionals.join(' ');
|
|
144
|
+
}
|
|
145
|
+
return args;
|
|
146
|
+
}
|
|
147
|
+
// ============================================================================
|
|
148
|
+
// Help
|
|
20
149
|
// ============================================================================
|
|
21
150
|
function printHelp() {
|
|
22
151
|
const version = getVersion();
|
|
23
152
|
console.log(`
|
|
24
|
-
agntk
|
|
153
|
+
agntk (${version}) — zero-config AI agent
|
|
25
154
|
|
|
26
155
|
Usage:
|
|
27
|
-
agntk
|
|
156
|
+
agntk "prompt"
|
|
157
|
+
agntk -n <name> "prompt"
|
|
158
|
+
agntk -n <name> -i
|
|
159
|
+
agntk list
|
|
28
160
|
|
|
29
161
|
Options:
|
|
30
|
-
-
|
|
31
|
-
|
|
32
|
-
-
|
|
33
|
-
--
|
|
34
|
-
--
|
|
35
|
-
--
|
|
36
|
-
--
|
|
37
|
-
|
|
38
|
-
--
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
-h, --help Show help
|
|
162
|
+
-n, --name <name> Agent name (enables persistent memory)
|
|
163
|
+
--instructions <text> What the agent does (injected as system prompt)
|
|
164
|
+
-i, --interactive Interactive REPL mode
|
|
165
|
+
--workspace <path> Workspace root (default: cwd)
|
|
166
|
+
--max-steps <n> Max tool-loop steps (default: 25)
|
|
167
|
+
--verbose Show full tool args and output
|
|
168
|
+
-q, --quiet Text output only (for piping)
|
|
169
|
+
-v, --version Show version
|
|
170
|
+
-h, --help Show help
|
|
171
|
+
|
|
172
|
+
Commands:
|
|
173
|
+
list List all known agents
|
|
43
174
|
|
|
44
175
|
Examples:
|
|
45
|
-
agntk "
|
|
46
|
-
agntk
|
|
47
|
-
agntk
|
|
48
|
-
|
|
176
|
+
agntk "fix the failing tests"
|
|
177
|
+
agntk whats up
|
|
178
|
+
agntk -n coder "fix the failing tests"
|
|
179
|
+
agntk -n ops --instructions "you manage k8s deploys" "roll back staging"
|
|
180
|
+
agntk -n coder -i
|
|
181
|
+
agntk list
|
|
182
|
+
cat error.log | agntk -n debugger "explain"
|
|
183
|
+
|
|
184
|
+
API Key:
|
|
185
|
+
Save permanently: mkdir -p ~/.agntk && echo "OPENROUTER_API_KEY=sk-or-..." > ~/.agntk/.env
|
|
186
|
+
Or per session: export OPENROUTER_API_KEY=sk-or-...
|
|
49
187
|
`);
|
|
50
188
|
}
|
|
51
189
|
// ============================================================================
|
|
190
|
+
// List Agents
|
|
191
|
+
// ============================================================================
|
|
192
|
+
/** Check if a PID is still alive */
|
|
193
|
+
function isPidAlive(pid) {
|
|
194
|
+
try {
|
|
195
|
+
process.kill(pid, 0);
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/** Format a timestamp as relative time (e.g. "2m ago", "3d ago") */
|
|
203
|
+
function relativeTime(date) {
|
|
204
|
+
const now = Date.now();
|
|
205
|
+
const diffMs = now - date.getTime();
|
|
206
|
+
const seconds = Math.floor(diffMs / 1000);
|
|
207
|
+
if (seconds < 60)
|
|
208
|
+
return 'just now';
|
|
209
|
+
const minutes = Math.floor(seconds / 60);
|
|
210
|
+
if (minutes < 60)
|
|
211
|
+
return `${minutes}m ago`;
|
|
212
|
+
const hours = Math.floor(minutes / 60);
|
|
213
|
+
if (hours < 24)
|
|
214
|
+
return `${hours}h ago`;
|
|
215
|
+
const days = Math.floor(hours / 24);
|
|
216
|
+
if (days < 7)
|
|
217
|
+
return `${days}d ago`;
|
|
218
|
+
const weeks = Math.floor(days / 7);
|
|
219
|
+
if (weeks < 4)
|
|
220
|
+
return `${weeks}w ago`;
|
|
221
|
+
const months = Math.floor(days / 30);
|
|
222
|
+
return `${months}mo ago`;
|
|
223
|
+
}
|
|
224
|
+
/** Get lock info for an agent — returns PID if running, null if idle */
|
|
225
|
+
function getAgentLockInfo(agentDir) {
|
|
226
|
+
const lockPath = join(agentDir, '.lock');
|
|
227
|
+
if (!existsSync(lockPath))
|
|
228
|
+
return null;
|
|
229
|
+
try {
|
|
230
|
+
const content = readFileSync(lockPath, 'utf-8').trim();
|
|
231
|
+
const pid = parseInt(content, 10);
|
|
232
|
+
if (isNaN(pid))
|
|
233
|
+
return null;
|
|
234
|
+
const alive = isPidAlive(pid);
|
|
235
|
+
if (!alive) {
|
|
236
|
+
try {
|
|
237
|
+
unlinkSync(lockPath);
|
|
238
|
+
}
|
|
239
|
+
catch { /* ignore */ }
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
return { pid, alive };
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/** Acquire a lockfile for an agent */
|
|
249
|
+
function acquireLock(name) {
|
|
250
|
+
const lockPath = join(AGENTS_DIR, name, '.lock');
|
|
251
|
+
try {
|
|
252
|
+
writeFileSync(lockPath, String(process.pid), 'utf-8');
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
// Agent dir may not exist yet — that's fine, it gets created by the SDK
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/** Release a lockfile for an agent */
|
|
259
|
+
function releaseLock(name) {
|
|
260
|
+
const lockPath = join(AGENTS_DIR, name, '.lock');
|
|
261
|
+
try {
|
|
262
|
+
unlinkSync(lockPath);
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
// Already cleaned up
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function listAgents() {
|
|
269
|
+
const colors = createColors(process.stdout.isTTY ?? false);
|
|
270
|
+
if (!existsSync(AGENTS_DIR)) {
|
|
271
|
+
console.log(colors.dim('No agents found. Create one with: agntk --name "my-agent" "do something"'));
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const entries = readdirSync(AGENTS_DIR, { withFileTypes: true });
|
|
275
|
+
const agents = entries.filter((e) => e.isDirectory());
|
|
276
|
+
if (agents.length === 0) {
|
|
277
|
+
console.log(colors.dim('No agents found. Create one with: agntk --name "my-agent" "do something"'));
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
console.log(`\n${colors.bold(`Agents (${agents.length})`)}\n`);
|
|
281
|
+
// Calculate max name length for alignment
|
|
282
|
+
const maxNameLen = Math.max(...agents.map((a) => a.name.length));
|
|
283
|
+
for (const agent of agents) {
|
|
284
|
+
const agentDir = join(AGENTS_DIR, agent.name);
|
|
285
|
+
const memoryPath = join(agentDir, 'memory.md');
|
|
286
|
+
const contextPath = join(agentDir, 'context.md');
|
|
287
|
+
const hasMemory = existsSync(memoryPath);
|
|
288
|
+
const hasContext = existsSync(contextPath);
|
|
289
|
+
// Running detection
|
|
290
|
+
const lockInfo = getAgentLockInfo(agentDir);
|
|
291
|
+
const isRunning = lockInfo !== null;
|
|
292
|
+
// Last active — most recent mtime of any file in the agent dir
|
|
293
|
+
let lastActive = null;
|
|
294
|
+
try {
|
|
295
|
+
const agentFiles = readdirSync(agentDir);
|
|
296
|
+
for (const f of agentFiles) {
|
|
297
|
+
if (f === '.lock')
|
|
298
|
+
continue;
|
|
299
|
+
try {
|
|
300
|
+
const fStat = statSync(join(agentDir, f));
|
|
301
|
+
if (!lastActive || fStat.mtime > lastActive) {
|
|
302
|
+
lastActive = fStat.mtime;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
catch { /* skip */ }
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
catch { /* skip */ }
|
|
309
|
+
// Build output line
|
|
310
|
+
const statusIcon = isRunning
|
|
311
|
+
? colors.green('●')
|
|
312
|
+
: colors.dim('○');
|
|
313
|
+
const nameStr = isRunning
|
|
314
|
+
? colors.green(colors.bold(agent.name))
|
|
315
|
+
: agent.name;
|
|
316
|
+
const padding = ' '.repeat(maxNameLen - agent.name.length + 2);
|
|
317
|
+
const parts = [];
|
|
318
|
+
if (isRunning) {
|
|
319
|
+
parts.push(colors.green(`running`) + colors.dim(` (pid ${lockInfo.pid})`));
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
parts.push(colors.dim('idle'));
|
|
323
|
+
}
|
|
324
|
+
if (lastActive) {
|
|
325
|
+
parts.push(colors.dim(relativeTime(lastActive)));
|
|
326
|
+
}
|
|
327
|
+
if (hasMemory) {
|
|
328
|
+
parts.push(colors.cyan('🧠 memory'));
|
|
329
|
+
}
|
|
330
|
+
else if (hasContext) {
|
|
331
|
+
parts.push(colors.dim('has context'));
|
|
332
|
+
}
|
|
333
|
+
console.log(` ${statusIcon} ${nameStr}${padding}${parts.join(colors.dim(' · '))}`);
|
|
334
|
+
}
|
|
335
|
+
console.log('');
|
|
336
|
+
}
|
|
337
|
+
// ============================================================================
|
|
338
|
+
// Formatting Helpers
|
|
339
|
+
// ============================================================================
|
|
340
|
+
/** Compact summary of tool args — show key names and short values */
|
|
341
|
+
function summarizeArgs(input, colors) {
|
|
342
|
+
if (!input || typeof input !== 'object')
|
|
343
|
+
return '';
|
|
344
|
+
const obj = input;
|
|
345
|
+
const parts = [];
|
|
346
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
347
|
+
if (val === undefined || val === null)
|
|
348
|
+
continue;
|
|
349
|
+
const str = typeof val === 'string' ? val : JSON.stringify(val);
|
|
350
|
+
const display = str.length > 60 ? str.slice(0, 57) + '...' : str;
|
|
351
|
+
parts.push(`${colors.dim(key + '=')}${colors.yellow(display)}`);
|
|
352
|
+
}
|
|
353
|
+
return parts.join(' ');
|
|
354
|
+
}
|
|
355
|
+
/** Compact summary of tool output */
|
|
356
|
+
function summarizeOutput(output) {
|
|
357
|
+
const raw = typeof output === 'string' ? output : JSON.stringify(output);
|
|
358
|
+
let display = raw;
|
|
359
|
+
try {
|
|
360
|
+
const parsed = JSON.parse(raw);
|
|
361
|
+
if (parsed && typeof parsed.output === 'string') {
|
|
362
|
+
display = parsed.output;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
// use raw
|
|
367
|
+
}
|
|
368
|
+
const lines = display.split('\n');
|
|
369
|
+
if (lines.length > 3) {
|
|
370
|
+
return lines.slice(0, 3).join('\n') + `\n ... (${lines.length} lines total)`;
|
|
371
|
+
}
|
|
372
|
+
if (display.length > 200) {
|
|
373
|
+
return display.slice(0, 197) + '...';
|
|
374
|
+
}
|
|
375
|
+
return display;
|
|
376
|
+
}
|
|
377
|
+
/** Format milliseconds into human-readable duration */
|
|
378
|
+
function formatDuration(ms) {
|
|
379
|
+
if (ms < 1000)
|
|
380
|
+
return `${ms}ms`;
|
|
381
|
+
if (ms < 60000)
|
|
382
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
383
|
+
const mins = Math.floor(ms / 60000);
|
|
384
|
+
const secs = Math.floor((ms % 60000) / 1000);
|
|
385
|
+
return `${mins}m${secs}s`;
|
|
386
|
+
}
|
|
387
|
+
async function consumeStream(stream, opts) {
|
|
388
|
+
const { output, status, level, colors } = opts;
|
|
389
|
+
const quiet = level === 'quiet';
|
|
390
|
+
const verbose = level === 'verbose';
|
|
391
|
+
const spinner = createSpinner(status, colors, !quiet && (opts.isTTY ?? false));
|
|
392
|
+
const stats = {
|
|
393
|
+
steps: 0,
|
|
394
|
+
toolCalls: 0,
|
|
395
|
+
startTime: Date.now(),
|
|
396
|
+
inputTokens: 0,
|
|
397
|
+
outputTokens: 0,
|
|
398
|
+
};
|
|
399
|
+
let afterToolResult = false;
|
|
400
|
+
let currentStepStart = Date.now();
|
|
401
|
+
let inReasoning = false;
|
|
402
|
+
let hasTextOutput = false;
|
|
403
|
+
let lastToolOutput = null;
|
|
404
|
+
// Reflection tag filtering state machine.
|
|
405
|
+
let inReflection = false;
|
|
406
|
+
let reflectionBuffer = '';
|
|
407
|
+
const REFLECTION_OPEN = '<reflection>';
|
|
408
|
+
const REFLECTION_CLOSE = '</reflection>';
|
|
409
|
+
for await (const chunk of stream) {
|
|
410
|
+
switch (chunk.type) {
|
|
411
|
+
case 'start-step': {
|
|
412
|
+
stats.steps++;
|
|
413
|
+
currentStepStart = Date.now();
|
|
414
|
+
if (!quiet) {
|
|
415
|
+
status.write(`\n${colors.dim('──')} ${colors.blue(colors.bold(`step ${stats.steps}`))} ${colors.dim('──────────────────────────────────────')}\n`);
|
|
416
|
+
}
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
case 'finish-step': {
|
|
420
|
+
if (reflectionBuffer && !inReflection) {
|
|
421
|
+
if (reflectionBuffer.trim()) {
|
|
422
|
+
if (afterToolResult && !quiet) {
|
|
423
|
+
output.write('\n');
|
|
424
|
+
afterToolResult = false;
|
|
425
|
+
}
|
|
426
|
+
hasTextOutput = true;
|
|
427
|
+
output.write(reflectionBuffer);
|
|
428
|
+
}
|
|
429
|
+
reflectionBuffer = '';
|
|
430
|
+
}
|
|
431
|
+
inReflection = false;
|
|
432
|
+
if (!quiet) {
|
|
433
|
+
const elapsed = Date.now() - currentStepStart;
|
|
434
|
+
const reason = chunk.finishReason ?? 'unknown';
|
|
435
|
+
const usage = chunk.usage;
|
|
436
|
+
const tokensIn = usage?.inputTokens ?? 0;
|
|
437
|
+
const tokensOut = usage?.outputTokens ?? 0;
|
|
438
|
+
stats.inputTokens += tokensIn;
|
|
439
|
+
stats.outputTokens += tokensOut;
|
|
440
|
+
const parts = [
|
|
441
|
+
colors.dim(` ${formatDuration(elapsed)}`),
|
|
442
|
+
`${colors.cyan(String(tokensIn))}${colors.dim('→')}${colors.cyan(String(tokensOut))} ${colors.dim('tok')}`,
|
|
443
|
+
];
|
|
444
|
+
if (reason === 'tool-calls') {
|
|
445
|
+
parts.push(colors.dim('→ tool loop'));
|
|
446
|
+
}
|
|
447
|
+
else if (reason === 'stop') {
|
|
448
|
+
parts.push(colors.green(colors.bold('done')));
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
parts.push(colors.yellow(reason));
|
|
452
|
+
}
|
|
453
|
+
status.write(`${parts.join(colors.dim(' | '))}\n`);
|
|
454
|
+
}
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
case 'reasoning-start': {
|
|
458
|
+
if (!quiet) {
|
|
459
|
+
inReasoning = true;
|
|
460
|
+
status.write(colors.magenta('\n 💭 '));
|
|
461
|
+
}
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
case 'reasoning-delta': {
|
|
465
|
+
if (!quiet && inReasoning) {
|
|
466
|
+
const text = chunk.text ?? '';
|
|
467
|
+
const compacted = text.replace(/\n/g, ' ');
|
|
468
|
+
status.write(colors.magenta(colors.dim(compacted)));
|
|
469
|
+
}
|
|
470
|
+
break;
|
|
471
|
+
}
|
|
472
|
+
case 'reasoning-end': {
|
|
473
|
+
if (!quiet && inReasoning) {
|
|
474
|
+
status.write('\n');
|
|
475
|
+
inReasoning = false;
|
|
476
|
+
}
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
case 'tool-call': {
|
|
480
|
+
stats.toolCalls++;
|
|
481
|
+
if (!quiet) {
|
|
482
|
+
const toolName = chunk.toolName;
|
|
483
|
+
if (verbose) {
|
|
484
|
+
const argsStr = chunk.input ? JSON.stringify(chunk.input, null, 2) : '';
|
|
485
|
+
status.write(`\n ${colors.cyan('▶')} ${colors.cyan(colors.bold(toolName))}\n`);
|
|
486
|
+
if (argsStr) {
|
|
487
|
+
const indented = argsStr.split('\n').map((l) => ` ${l}`).join('\n');
|
|
488
|
+
status.write(`${colors.dim(indented)}\n`);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
const argsSummary = summarizeArgs(chunk.input, colors);
|
|
493
|
+
const display = argsSummary
|
|
494
|
+
? ` ${colors.cyan('▶')} ${colors.cyan(colors.bold(toolName))} ${argsSummary}`
|
|
495
|
+
: ` ${colors.cyan('▶')} ${colors.cyan(colors.bold(toolName))}`;
|
|
496
|
+
status.write(`${display}\n`);
|
|
497
|
+
}
|
|
498
|
+
spinner.start(`running ${toolName}...`);
|
|
499
|
+
}
|
|
500
|
+
afterToolResult = false;
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
case 'tool-result': {
|
|
504
|
+
spinner.stop();
|
|
505
|
+
const toolOutputRaw = typeof chunk.output === 'string' ? chunk.output : JSON.stringify(chunk.output);
|
|
506
|
+
try {
|
|
507
|
+
const parsed = JSON.parse(toolOutputRaw);
|
|
508
|
+
lastToolOutput = (parsed && typeof parsed.output === 'string') ? parsed.output : toolOutputRaw;
|
|
509
|
+
}
|
|
510
|
+
catch {
|
|
511
|
+
lastToolOutput = toolOutputRaw;
|
|
512
|
+
}
|
|
513
|
+
if (!quiet) {
|
|
514
|
+
const toolName = chunk.toolName;
|
|
515
|
+
if (verbose) {
|
|
516
|
+
let displayOutput = lastToolOutput ?? '';
|
|
517
|
+
const maxLen = 2000;
|
|
518
|
+
if (displayOutput.length > maxLen) {
|
|
519
|
+
displayOutput = displayOutput.slice(0, maxLen) + `\n... (${displayOutput.length} chars total)`;
|
|
520
|
+
}
|
|
521
|
+
const indented = displayOutput.split('\n').map((l) => ` ${l}`).join('\n');
|
|
522
|
+
status.write(` ${colors.green('✔')} ${colors.green(colors.dim(toolName))} ${colors.dim('returned')}\n`);
|
|
523
|
+
status.write(`${colors.dim(indented)}\n`);
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
const summary = summarizeOutput(chunk.output);
|
|
527
|
+
const firstLine = summary.split('\n')[0];
|
|
528
|
+
const truncated = firstLine.length > 100
|
|
529
|
+
? firstLine.slice(0, 97) + '...'
|
|
530
|
+
: firstLine;
|
|
531
|
+
status.write(` ${colors.green('✔')} ${colors.green(colors.dim(toolName + ': '))}${colors.dim(truncated)}\n`);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
afterToolResult = true;
|
|
535
|
+
break;
|
|
536
|
+
}
|
|
537
|
+
case 'tool-error': {
|
|
538
|
+
spinner.stop();
|
|
539
|
+
if (!quiet) {
|
|
540
|
+
const toolName = chunk.toolName;
|
|
541
|
+
const error = chunk.error instanceof Error
|
|
542
|
+
? chunk.error.message
|
|
543
|
+
: String(chunk.error ?? 'unknown error');
|
|
544
|
+
status.write(` ${colors.red('✖')} ${colors.red(colors.bold(toolName))} ${colors.red(error)}\n`);
|
|
545
|
+
}
|
|
546
|
+
afterToolResult = true;
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
case 'text-delta': {
|
|
550
|
+
const rawText = chunk.text ?? '';
|
|
551
|
+
if (!rawText)
|
|
552
|
+
break;
|
|
553
|
+
reflectionBuffer += rawText;
|
|
554
|
+
while (reflectionBuffer.length > 0) {
|
|
555
|
+
if (inReflection) {
|
|
556
|
+
const closeIdx = reflectionBuffer.indexOf(REFLECTION_CLOSE);
|
|
557
|
+
if (closeIdx !== -1) {
|
|
558
|
+
const content = reflectionBuffer.slice(0, closeIdx);
|
|
559
|
+
reflectionBuffer = reflectionBuffer.slice(closeIdx + REFLECTION_CLOSE.length);
|
|
560
|
+
inReflection = false;
|
|
561
|
+
if (verbose) {
|
|
562
|
+
const trimmed = content.trim();
|
|
563
|
+
if (trimmed) {
|
|
564
|
+
status.write(colors.dim(` ... ${trimmed}\n`));
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
const openIdx = reflectionBuffer.indexOf(REFLECTION_OPEN);
|
|
574
|
+
if (openIdx !== -1) {
|
|
575
|
+
const before = reflectionBuffer.slice(0, openIdx);
|
|
576
|
+
if (before) {
|
|
577
|
+
if (afterToolResult && !quiet) {
|
|
578
|
+
output.write('\n');
|
|
579
|
+
afterToolResult = false;
|
|
580
|
+
}
|
|
581
|
+
if (before.trim())
|
|
582
|
+
hasTextOutput = true;
|
|
583
|
+
output.write(before);
|
|
584
|
+
}
|
|
585
|
+
reflectionBuffer = reflectionBuffer.slice(openIdx + REFLECTION_OPEN.length);
|
|
586
|
+
inReflection = true;
|
|
587
|
+
}
|
|
588
|
+
else {
|
|
589
|
+
let partialAt = -1;
|
|
590
|
+
for (let i = Math.max(0, reflectionBuffer.length - REFLECTION_OPEN.length); i < reflectionBuffer.length; i++) {
|
|
591
|
+
if (reflectionBuffer[i] === '<') {
|
|
592
|
+
const partial = reflectionBuffer.slice(i);
|
|
593
|
+
if (REFLECTION_OPEN.startsWith(partial)) {
|
|
594
|
+
partialAt = i;
|
|
595
|
+
break;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
if (partialAt !== -1) {
|
|
600
|
+
const safe = reflectionBuffer.slice(0, partialAt);
|
|
601
|
+
if (safe) {
|
|
602
|
+
if (afterToolResult && !quiet) {
|
|
603
|
+
output.write('\n');
|
|
604
|
+
afterToolResult = false;
|
|
605
|
+
}
|
|
606
|
+
if (safe.trim())
|
|
607
|
+
hasTextOutput = true;
|
|
608
|
+
output.write(safe);
|
|
609
|
+
}
|
|
610
|
+
reflectionBuffer = reflectionBuffer.slice(partialAt);
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
if (afterToolResult && !quiet) {
|
|
615
|
+
output.write('\n');
|
|
616
|
+
afterToolResult = false;
|
|
617
|
+
}
|
|
618
|
+
if (reflectionBuffer.trim())
|
|
619
|
+
hasTextOutput = true;
|
|
620
|
+
output.write(reflectionBuffer);
|
|
621
|
+
reflectionBuffer = '';
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
break;
|
|
627
|
+
}
|
|
628
|
+
case 'finish': {
|
|
629
|
+
spinner.stop();
|
|
630
|
+
if (!quiet) {
|
|
631
|
+
const elapsed = Date.now() - stats.startTime;
|
|
632
|
+
const totalUsage = chunk.totalUsage;
|
|
633
|
+
if (totalUsage) {
|
|
634
|
+
stats.inputTokens = totalUsage.inputTokens ?? stats.inputTokens;
|
|
635
|
+
stats.outputTokens = totalUsage.outputTokens ?? stats.outputTokens;
|
|
636
|
+
}
|
|
637
|
+
const stepLabel = `${stats.steps} step${stats.steps !== 1 ? 's' : ''}`;
|
|
638
|
+
const toolLabel = `${stats.toolCalls} tool call${stats.toolCalls !== 1 ? 's' : ''}`;
|
|
639
|
+
const tokLabel = `${colors.cyan(String(stats.inputTokens))}${colors.dim('→')}${colors.cyan(String(stats.outputTokens))} ${colors.dim('tok')}`;
|
|
640
|
+
const timeLabel = colors.dim(formatDuration(elapsed));
|
|
641
|
+
status.write(`\n${colors.dim('──')} ${colors.green(colors.bold('done'))} ${colors.dim('──')} ${colors.dim(stepLabel)} ${colors.dim('|')} ${colors.dim(toolLabel)} ${colors.dim('|')} ${tokLabel} ${colors.dim('|')} ${timeLabel} ${colors.dim('──')}\n`);
|
|
642
|
+
}
|
|
643
|
+
break;
|
|
644
|
+
}
|
|
645
|
+
case 'error': {
|
|
646
|
+
spinner.stop();
|
|
647
|
+
const error = chunk.error instanceof Error
|
|
648
|
+
? chunk.error.message
|
|
649
|
+
: String(chunk.error ?? 'unknown error');
|
|
650
|
+
status.write(`\n${colors.red(colors.bold('✖ Error:'))} ${colors.red(error)}\n`);
|
|
651
|
+
break;
|
|
652
|
+
}
|
|
653
|
+
default:
|
|
654
|
+
break;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
const hitStepLimit = opts.maxSteps && stats.steps >= opts.maxSteps;
|
|
658
|
+
if (hitStepLimit && !quiet) {
|
|
659
|
+
status.write(`\n${colors.yellow('Warning: step limit reached')} ${colors.dim(`(${opts.maxSteps} steps). Use --max-steps to increase.`)}\n`);
|
|
660
|
+
}
|
|
661
|
+
if (!hasTextOutput && lastToolOutput && stats.toolCalls > 0 && !hitStepLimit) {
|
|
662
|
+
if (!quiet) {
|
|
663
|
+
output.write('\n');
|
|
664
|
+
}
|
|
665
|
+
output.write(lastToolOutput);
|
|
666
|
+
if (!lastToolOutput.endsWith('\n')) {
|
|
667
|
+
output.write('\n');
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return stats;
|
|
671
|
+
}
|
|
672
|
+
// ============================================================================
|
|
673
|
+
// Read piped stdin
|
|
674
|
+
// ============================================================================
|
|
675
|
+
async function readStdin(timeoutMs = 100) {
|
|
676
|
+
if (process.stdin.isTTY)
|
|
677
|
+
return null;
|
|
678
|
+
return new Promise((resolve) => {
|
|
679
|
+
const chunks = [];
|
|
680
|
+
let resolved = false;
|
|
681
|
+
const finish = () => {
|
|
682
|
+
if (resolved)
|
|
683
|
+
return;
|
|
684
|
+
resolved = true;
|
|
685
|
+
resolve(chunks.length === 0 ? null : Buffer.concat(chunks).toString('utf-8'));
|
|
686
|
+
};
|
|
687
|
+
process.stdin.on('data', (chunk) => chunks.push(chunk));
|
|
688
|
+
process.stdin.on('end', finish);
|
|
689
|
+
process.stdin.on('error', () => finish());
|
|
690
|
+
setTimeout(finish, timeoutMs);
|
|
691
|
+
process.stdin.resume();
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
// ============================================================================
|
|
695
|
+
// One-Shot Mode
|
|
696
|
+
// ============================================================================
|
|
697
|
+
async function runOneShot(prompt, args) {
|
|
698
|
+
const { createAgent } = await import('@agntk/core');
|
|
699
|
+
const colors = createColors(process.stderr.isTTY ?? false);
|
|
700
|
+
const agent = createAgent({
|
|
701
|
+
name: args.name,
|
|
702
|
+
instructions: args.instructions ?? undefined,
|
|
703
|
+
workspaceRoot: args.workspace,
|
|
704
|
+
maxSteps: args.maxSteps,
|
|
705
|
+
});
|
|
706
|
+
// Acquire lockfile
|
|
707
|
+
acquireLock(args.name);
|
|
708
|
+
const cleanup = () => releaseLock(args.name);
|
|
709
|
+
process.on('exit', cleanup);
|
|
710
|
+
process.on('SIGINT', () => { cleanup(); process.exit(130); });
|
|
711
|
+
process.on('SIGTERM', () => { cleanup(); process.exit(143); });
|
|
712
|
+
if (args.outputLevel !== 'quiet') {
|
|
713
|
+
const toolCount = agent.getToolNames().length;
|
|
714
|
+
process.stderr.write(`${colors.bold('agntk')} ${colors.dim('|')} ${colors.cyan(args.name)} ${colors.dim('|')} ${colors.dim(`${toolCount} tools`)} ${colors.dim('|')} ${colors.dim(`workspace: ${args.workspace}`)}\n`);
|
|
715
|
+
}
|
|
716
|
+
const result = await agent.stream({ prompt });
|
|
717
|
+
await consumeStream(result.fullStream, {
|
|
718
|
+
output: process.stdout,
|
|
719
|
+
status: process.stderr,
|
|
720
|
+
level: args.outputLevel,
|
|
721
|
+
colors,
|
|
722
|
+
maxSteps: args.maxSteps,
|
|
723
|
+
isTTY: process.stderr.isTTY ?? false,
|
|
724
|
+
});
|
|
725
|
+
const finalText = await result.text;
|
|
726
|
+
if (finalText && !finalText.endsWith('\n')) {
|
|
727
|
+
process.stdout.write('\n');
|
|
728
|
+
}
|
|
729
|
+
if (args.outputLevel === 'verbose') {
|
|
730
|
+
const usage = await result.usage;
|
|
731
|
+
if (usage) {
|
|
732
|
+
process.stderr.write(colors.dim(`[usage] ${usage.inputTokens ?? 0} input + ${usage.outputTokens ?? 0} output tokens\n`));
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
// ============================================================================
|
|
737
|
+
// REPL Mode
|
|
738
|
+
// ============================================================================
|
|
739
|
+
async function runRepl(args) {
|
|
740
|
+
const { createAgent } = await import('@agntk/core');
|
|
741
|
+
const colors = createColors(process.stdout.isTTY ?? false);
|
|
742
|
+
const agent = createAgent({
|
|
743
|
+
name: args.name,
|
|
744
|
+
instructions: args.instructions ?? undefined,
|
|
745
|
+
workspaceRoot: args.workspace,
|
|
746
|
+
maxSteps: args.maxSteps,
|
|
747
|
+
});
|
|
748
|
+
// Acquire lockfile
|
|
749
|
+
acquireLock(args.name);
|
|
750
|
+
const cleanup = () => releaseLock(args.name);
|
|
751
|
+
process.on('exit', cleanup);
|
|
752
|
+
process.on('SIGINT', () => { cleanup(); process.exit(130); });
|
|
753
|
+
process.on('SIGTERM', () => { cleanup(); process.exit(143); });
|
|
754
|
+
const version = getVersion();
|
|
755
|
+
const output = process.stdout;
|
|
756
|
+
const toolCount = agent.getToolNames().length;
|
|
757
|
+
output.write(`\n${colors.bold('⚡ agntk')} ${colors.dim(`(${version})`)}\n`);
|
|
758
|
+
output.write(`${colors.cyan(colors.bold(args.name))} ${colors.dim('|')} ${colors.dim(`${toolCount} tools`)} ${colors.dim('|')} ${colors.green('memory: on')}\n`);
|
|
759
|
+
output.write(`${colors.dim('Type /help for commands, /exit or Ctrl+C to quit.')}\n\n`);
|
|
760
|
+
const rl = createInterface({
|
|
761
|
+
input: process.stdin,
|
|
762
|
+
output: process.stdout,
|
|
763
|
+
prompt: `${colors.cyan(colors.bold(args.name + ' ❯'))} `,
|
|
764
|
+
terminal: true,
|
|
765
|
+
});
|
|
766
|
+
const history = [];
|
|
767
|
+
const pendingLines = [];
|
|
768
|
+
let busy = false;
|
|
769
|
+
let closed = false;
|
|
770
|
+
async function processLine(trimmed) {
|
|
771
|
+
if (trimmed === '/exit' || trimmed === '/quit') {
|
|
772
|
+
rl.close();
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
if (trimmed === '/help') {
|
|
776
|
+
output.write(`\n ${colors.bold('/help')} Show commands\n ${colors.bold('/tools')} List available tools\n ${colors.bold('/verbose')} Toggle verbose output\n ${colors.bold('/exit')} Quit\n\n`);
|
|
777
|
+
rl.prompt();
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
if (trimmed === '/tools') {
|
|
781
|
+
const tools = agent.getToolNames();
|
|
782
|
+
output.write(`\n${colors.bold(`Tools (${tools.length})`)}:\n ${tools.join(', ')}\n\n`);
|
|
783
|
+
rl.prompt();
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
if (trimmed === '/verbose') {
|
|
787
|
+
if (args.outputLevel === 'verbose') {
|
|
788
|
+
args.outputLevel = 'normal';
|
|
789
|
+
output.write(`${colors.dim('Verbose output: off')}\n`);
|
|
790
|
+
}
|
|
791
|
+
else {
|
|
792
|
+
args.outputLevel = 'verbose';
|
|
793
|
+
output.write(`${colors.dim('Verbose output: on')}\n`);
|
|
794
|
+
}
|
|
795
|
+
rl.prompt();
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
busy = true;
|
|
799
|
+
rl.pause();
|
|
800
|
+
history.push({ role: 'user', content: trimmed });
|
|
801
|
+
const maxHistoryPairs = 10;
|
|
802
|
+
const recentHistory = history.length > maxHistoryPairs * 2
|
|
803
|
+
? history.slice(-maxHistoryPairs * 2)
|
|
804
|
+
: history;
|
|
805
|
+
const historyLines = recentHistory.map((h) => h.role === 'user' ? `[User]: ${h.content}` : `[Assistant]: ${h.content}`);
|
|
806
|
+
const contextPrompt = [
|
|
807
|
+
'<conversation_history>',
|
|
808
|
+
...historyLines.slice(0, -1),
|
|
809
|
+
'</conversation_history>',
|
|
810
|
+
'',
|
|
811
|
+
recentHistory[recentHistory.length - 1].content,
|
|
812
|
+
].join('\n');
|
|
813
|
+
try {
|
|
814
|
+
output.write('\n');
|
|
815
|
+
const result = await agent.stream({ prompt: contextPrompt });
|
|
816
|
+
await consumeStream(result.fullStream, {
|
|
817
|
+
output,
|
|
818
|
+
status: process.stderr,
|
|
819
|
+
level: args.outputLevel,
|
|
820
|
+
colors,
|
|
821
|
+
maxSteps: args.maxSteps,
|
|
822
|
+
isTTY: process.stderr.isTTY ?? false,
|
|
823
|
+
});
|
|
824
|
+
const responseText = (await result.text) ?? '';
|
|
825
|
+
if (responseText && !responseText.endsWith('\n')) {
|
|
826
|
+
output.write('\n');
|
|
827
|
+
}
|
|
828
|
+
history.push({ role: 'assistant', content: responseText });
|
|
829
|
+
}
|
|
830
|
+
catch (error) {
|
|
831
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
832
|
+
output.write(`\n${colors.red('Error:')} ${msg}\n`);
|
|
833
|
+
}
|
|
834
|
+
output.write('\n');
|
|
835
|
+
busy = false;
|
|
836
|
+
rl.resume();
|
|
837
|
+
while (pendingLines.length > 0) {
|
|
838
|
+
const next = pendingLines.shift();
|
|
839
|
+
if (next) {
|
|
840
|
+
await processLine(next);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
if (!closed) {
|
|
844
|
+
rl.prompt();
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
return new Promise((resolvePromise) => {
|
|
848
|
+
rl.prompt();
|
|
849
|
+
rl.on('line', (line) => {
|
|
850
|
+
const trimmed = line.trim();
|
|
851
|
+
if (!trimmed) {
|
|
852
|
+
rl.prompt();
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
if (busy) {
|
|
856
|
+
pendingLines.push(trimmed);
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
processLine(trimmed).catch((err) => {
|
|
860
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
861
|
+
output.write(`\n${colors.red('Error:')} ${msg}\n`);
|
|
862
|
+
rl.prompt();
|
|
863
|
+
});
|
|
864
|
+
});
|
|
865
|
+
rl.on('close', () => {
|
|
866
|
+
closed = true;
|
|
867
|
+
const finish = () => {
|
|
868
|
+
output.write(`\n${colors.dim('👋 Goodbye!')}\n`);
|
|
869
|
+
resolvePromise();
|
|
870
|
+
};
|
|
871
|
+
if (busy) {
|
|
872
|
+
const interval = setInterval(() => {
|
|
873
|
+
if (!busy) {
|
|
874
|
+
clearInterval(interval);
|
|
875
|
+
finish();
|
|
876
|
+
}
|
|
877
|
+
}, 100);
|
|
878
|
+
}
|
|
879
|
+
else {
|
|
880
|
+
finish();
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
rl.on('SIGINT', () => {
|
|
884
|
+
output.write('\n');
|
|
885
|
+
rl.close();
|
|
886
|
+
});
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
// ============================================================================
|
|
52
890
|
// Main
|
|
53
891
|
// ============================================================================
|
|
54
892
|
async function main() {
|
|
55
|
-
const args =
|
|
56
|
-
//
|
|
893
|
+
const args = parseCLIArgs(process.argv.slice(2));
|
|
894
|
+
// Fast paths — no heavy imports
|
|
57
895
|
if (args.version) {
|
|
58
|
-
console.log(`agntk
|
|
896
|
+
console.log(`agntk (${getVersion()})`);
|
|
59
897
|
process.exit(0);
|
|
60
898
|
}
|
|
61
|
-
// Handle --help
|
|
62
899
|
if (args.help) {
|
|
63
900
|
printHelp();
|
|
64
901
|
process.exit(0);
|
|
65
902
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
// Handle --init: create .agntk/ directory with template files
|
|
69
|
-
if (config.init) {
|
|
70
|
-
const { initMemoryDirectory } = await import('./init.js');
|
|
71
|
-
await initMemoryDirectory(config.workspace);
|
|
903
|
+
if (args.list) {
|
|
904
|
+
listAgents();
|
|
72
905
|
process.exit(0);
|
|
73
906
|
}
|
|
74
|
-
//
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
907
|
+
// Validate: need a name
|
|
908
|
+
if (!args.name) {
|
|
909
|
+
if (!args.prompt && process.stdin.isTTY) {
|
|
910
|
+
console.error('Error: No prompt provided.\n' +
|
|
911
|
+
'Usage: agntk "your prompt"\n' +
|
|
912
|
+
' agntk -n <name> "your prompt"\n' +
|
|
913
|
+
' agntk -n <name> -i\n' +
|
|
914
|
+
' agntk list\n' +
|
|
915
|
+
' agntk -h');
|
|
916
|
+
process.exit(1);
|
|
917
|
+
}
|
|
918
|
+
// Default name if no --name flag was given
|
|
919
|
+
args.name = 'default';
|
|
920
|
+
}
|
|
921
|
+
// Check API key
|
|
922
|
+
const apiKeyResult = detectApiKey();
|
|
923
|
+
if (!apiKeyResult) {
|
|
924
|
+
console.error('Error: No API key found.\n\n' +
|
|
925
|
+
' 1. Get a key at https://openrouter.ai/keys\n' +
|
|
926
|
+
' 2. Save it permanently:\n\n' +
|
|
927
|
+
' mkdir -p ~/.agntk && echo "OPENROUTER_API_KEY=sk-or-..." > ~/.agntk/.env\n\n' +
|
|
928
|
+
' Or export for this session only:\n\n' +
|
|
929
|
+
' export OPENROUTER_API_KEY=sk-or-...\n');
|
|
89
930
|
process.exit(1);
|
|
90
931
|
}
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
932
|
+
// Warn if workspace is the home directory (likely unintentional)
|
|
933
|
+
if (args.workspace === homedir()) {
|
|
934
|
+
process.stderr.write('Warning: Workspace is your home directory.\n' +
|
|
935
|
+
' Run from a project directory, or use --workspace <path>\n\n');
|
|
936
|
+
}
|
|
937
|
+
// Interactive mode
|
|
938
|
+
if (args.interactive) {
|
|
939
|
+
await runRepl(args);
|
|
940
|
+
process.exit(0);
|
|
941
|
+
}
|
|
942
|
+
// Build final prompt (handle piped stdin)
|
|
943
|
+
let prompt = args.prompt;
|
|
96
944
|
const pipedInput = await readStdin();
|
|
97
945
|
if (pipedInput) {
|
|
98
|
-
|
|
99
|
-
prompt = `${pipedInput}\n\n${prompt}`;
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
prompt = pipedInput;
|
|
103
|
-
}
|
|
946
|
+
prompt = prompt ? `${pipedInput}\n\n${prompt}` : pipedInput;
|
|
104
947
|
}
|
|
105
948
|
if (!prompt) {
|
|
106
949
|
console.error('Error: No prompt provided.\n' +
|
|
107
|
-
'Usage: agntk "your prompt
|
|
108
|
-
' agntk -
|
|
109
|
-
' agntk -
|
|
950
|
+
'Usage: agntk "your prompt"\n' +
|
|
951
|
+
' agntk -n <name> "your prompt"\n' +
|
|
952
|
+
' agntk -n <name> -i\n' +
|
|
953
|
+
' agntk -h');
|
|
110
954
|
process.exit(1);
|
|
111
955
|
}
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
956
|
+
// One-shot mode
|
|
957
|
+
await runOneShot(prompt, args);
|
|
958
|
+
// Flush observability traces before exit
|
|
959
|
+
try {
|
|
960
|
+
const { shutdownObservability } = await import('@agntk/core');
|
|
961
|
+
await shutdownObservability();
|
|
962
|
+
}
|
|
963
|
+
catch {
|
|
964
|
+
// Observability not available — that's fine
|
|
119
965
|
}
|
|
120
966
|
process.exit(0);
|
|
121
967
|
}
|
|
122
968
|
main().catch((err) => {
|
|
123
969
|
console.error('Fatal error:', err instanceof Error ? err.message : String(err));
|
|
970
|
+
if (process.env.DEBUG) {
|
|
971
|
+
console.error(err instanceof Error ? err.stack : '');
|
|
972
|
+
}
|
|
124
973
|
process.exit(1);
|
|
125
974
|
});
|
|
126
975
|
//# sourceMappingURL=cli.js.map
|