@agntk/cli 0.2.0 → 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/args.d.ts +40 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/args.js +90 -0
- package/dist/args.js.map +1 -0
- 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 +6 -5
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +251 -75
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +38 -0
- package/dist/config.js.map +1 -1
- package/dist/repl.d.ts +31 -0
- package/dist/repl.d.ts.map +1 -0
- package/dist/repl.js +217 -0
- package/dist/repl.js.map +1 -0
- package/dist/runner.d.ts +38 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +218 -0
- package/dist/runner.js.map +1 -0
- package/package.json +13 -13
- package/LICENSE +0 -22
package/dist/cli.js
CHANGED
|
@@ -3,17 +3,18 @@
|
|
|
3
3
|
* @fileoverview CLI entry point for agntk — zero-config AI agent.
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
* npx agntk
|
|
7
|
-
* npx agntk
|
|
8
|
-
* npx agntk
|
|
6
|
+
* npx agntk "do something"
|
|
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
|
|
9
11
|
* npx agntk list
|
|
10
|
-
* npx agntk
|
|
11
|
-
* cat error.log | npx agntk --name "debugger" "explain these errors"
|
|
12
|
+
* cat error.log | npx agntk -n "debugger" "explain these errors"
|
|
12
13
|
*/
|
|
13
14
|
// Load .env files before anything else reads process.env
|
|
14
15
|
import 'dotenv/config';
|
|
15
16
|
import { createInterface } from 'node:readline';
|
|
16
|
-
import { readdirSync, existsSync } from 'node:fs';
|
|
17
|
+
import { readdirSync, existsSync, writeFileSync, unlinkSync, readFileSync, statSync } from 'node:fs';
|
|
17
18
|
import { resolve, join } from 'node:path';
|
|
18
19
|
import { homedir } from 'node:os';
|
|
19
20
|
import { getVersion } from './version.js';
|
|
@@ -25,7 +26,7 @@ const AGENTS_DIR = resolve(homedir(), '.agntk', 'agents');
|
|
|
25
26
|
function createColors(enabled) {
|
|
26
27
|
if (!enabled) {
|
|
27
28
|
const identity = (s) => s;
|
|
28
|
-
return { dim: identity, bold: identity, cyan: identity, yellow: identity, green: identity, red: identity, magenta: identity, reset: '' };
|
|
29
|
+
return { dim: identity, bold: identity, cyan: identity, yellow: identity, green: identity, red: identity, magenta: identity, blue: identity, white: identity, reset: '' };
|
|
29
30
|
}
|
|
30
31
|
return {
|
|
31
32
|
dim: (s) => `\x1b[2m${s}\x1b[22m`,
|
|
@@ -35,9 +36,40 @@ function createColors(enabled) {
|
|
|
35
36
|
green: (s) => `\x1b[32m${s}\x1b[39m`,
|
|
36
37
|
red: (s) => `\x1b[31m${s}\x1b[39m`,
|
|
37
38
|
magenta: (s) => `\x1b[35m${s}\x1b[39m`,
|
|
39
|
+
blue: (s) => `\x1b[34m${s}\x1b[39m`,
|
|
40
|
+
white: (s) => `\x1b[97m${s}\x1b[39m`,
|
|
38
41
|
reset: '\x1b[0m',
|
|
39
42
|
};
|
|
40
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
|
+
}
|
|
41
73
|
function parseCLIArgs(argv) {
|
|
42
74
|
const args = {
|
|
43
75
|
name: null,
|
|
@@ -103,28 +135,12 @@ function parseCLIArgs(argv) {
|
|
|
103
135
|
}
|
|
104
136
|
}
|
|
105
137
|
// Interpret positionals:
|
|
106
|
-
//
|
|
107
|
-
// agntk "
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
args.prompt = positionals[0];
|
|
113
|
-
}
|
|
114
|
-
else if (positionals.length === 2) {
|
|
115
|
-
if (!args.name) {
|
|
116
|
-
args.name = positionals[0];
|
|
117
|
-
}
|
|
118
|
-
args.prompt = positionals[1];
|
|
119
|
-
}
|
|
120
|
-
else if (positionals.length > 2) {
|
|
121
|
-
if (!args.name) {
|
|
122
|
-
args.name = positionals[0];
|
|
123
|
-
args.prompt = positionals.slice(1).join(' ');
|
|
124
|
-
}
|
|
125
|
-
else {
|
|
126
|
-
args.prompt = positionals.join(' ');
|
|
127
|
-
}
|
|
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(' ');
|
|
128
144
|
}
|
|
129
145
|
return args;
|
|
130
146
|
}
|
|
@@ -137,13 +153,13 @@ function printHelp() {
|
|
|
137
153
|
agntk (${version}) — zero-config AI agent
|
|
138
154
|
|
|
139
155
|
Usage:
|
|
140
|
-
agntk
|
|
141
|
-
agntk
|
|
142
|
-
agntk <name>
|
|
156
|
+
agntk "prompt"
|
|
157
|
+
agntk -n <name> "prompt"
|
|
158
|
+
agntk -n <name> -i
|
|
143
159
|
agntk list
|
|
144
160
|
|
|
145
161
|
Options:
|
|
146
|
-
-n, --name <name> Agent name (
|
|
162
|
+
-n, --name <name> Agent name (enables persistent memory)
|
|
147
163
|
--instructions <text> What the agent does (injected as system prompt)
|
|
148
164
|
-i, --interactive Interactive REPL mode
|
|
149
165
|
--workspace <path> Workspace root (default: cwd)
|
|
@@ -157,36 +173,164 @@ function printHelp() {
|
|
|
157
173
|
list List all known agents
|
|
158
174
|
|
|
159
175
|
Examples:
|
|
160
|
-
agntk
|
|
161
|
-
agntk
|
|
162
|
-
agntk
|
|
163
|
-
agntk
|
|
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
|
|
164
181
|
agntk list
|
|
165
|
-
cat error.log | agntk
|
|
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-...
|
|
166
187
|
`);
|
|
167
188
|
}
|
|
168
189
|
// ============================================================================
|
|
169
190
|
// List Agents
|
|
170
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
|
+
}
|
|
171
268
|
function listAgents() {
|
|
269
|
+
const colors = createColors(process.stdout.isTTY ?? false);
|
|
172
270
|
if (!existsSync(AGENTS_DIR)) {
|
|
173
|
-
console.log('No agents found. Create one with: agntk --name "my-agent" "do something"');
|
|
271
|
+
console.log(colors.dim('No agents found. Create one with: agntk --name "my-agent" "do something"'));
|
|
174
272
|
return;
|
|
175
273
|
}
|
|
176
274
|
const entries = readdirSync(AGENTS_DIR, { withFileTypes: true });
|
|
177
275
|
const agents = entries.filter((e) => e.isDirectory());
|
|
178
276
|
if (agents.length === 0) {
|
|
179
|
-
console.log('No agents found. Create one with: agntk --name "my-agent" "do something"');
|
|
277
|
+
console.log(colors.dim('No agents found. Create one with: agntk --name "my-agent" "do something"'));
|
|
180
278
|
return;
|
|
181
279
|
}
|
|
182
|
-
console.log(`\
|
|
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));
|
|
183
283
|
for (const agent of agents) {
|
|
184
|
-
const
|
|
185
|
-
const
|
|
284
|
+
const agentDir = join(AGENTS_DIR, agent.name);
|
|
285
|
+
const memoryPath = join(agentDir, 'memory.md');
|
|
286
|
+
const contextPath = join(agentDir, 'context.md');
|
|
186
287
|
const hasMemory = existsSync(memoryPath);
|
|
187
288
|
const hasContext = existsSync(contextPath);
|
|
188
|
-
|
|
189
|
-
|
|
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(' · '))}`);
|
|
190
334
|
}
|
|
191
335
|
console.log('');
|
|
192
336
|
}
|
|
@@ -194,7 +338,7 @@ function listAgents() {
|
|
|
194
338
|
// Formatting Helpers
|
|
195
339
|
// ============================================================================
|
|
196
340
|
/** Compact summary of tool args — show key names and short values */
|
|
197
|
-
function summarizeArgs(input) {
|
|
341
|
+
function summarizeArgs(input, colors) {
|
|
198
342
|
if (!input || typeof input !== 'object')
|
|
199
343
|
return '';
|
|
200
344
|
const obj = input;
|
|
@@ -204,7 +348,7 @@ function summarizeArgs(input) {
|
|
|
204
348
|
continue;
|
|
205
349
|
const str = typeof val === 'string' ? val : JSON.stringify(val);
|
|
206
350
|
const display = str.length > 60 ? str.slice(0, 57) + '...' : str;
|
|
207
|
-
parts.push(`${key}
|
|
351
|
+
parts.push(`${colors.dim(key + '=')}${colors.yellow(display)}`);
|
|
208
352
|
}
|
|
209
353
|
return parts.join(' ');
|
|
210
354
|
}
|
|
@@ -244,6 +388,7 @@ async function consumeStream(stream, opts) {
|
|
|
244
388
|
const { output, status, level, colors } = opts;
|
|
245
389
|
const quiet = level === 'quiet';
|
|
246
390
|
const verbose = level === 'verbose';
|
|
391
|
+
const spinner = createSpinner(status, colors, !quiet && (opts.isTTY ?? false));
|
|
247
392
|
const stats = {
|
|
248
393
|
steps: 0,
|
|
249
394
|
toolCalls: 0,
|
|
@@ -267,7 +412,7 @@ async function consumeStream(stream, opts) {
|
|
|
267
412
|
stats.steps++;
|
|
268
413
|
currentStepStart = Date.now();
|
|
269
414
|
if (!quiet) {
|
|
270
|
-
status.write(`\n${colors.dim(
|
|
415
|
+
status.write(`\n${colors.dim('──')} ${colors.blue(colors.bold(`step ${stats.steps}`))} ${colors.dim('──────────────────────────────────────')}\n`);
|
|
271
416
|
}
|
|
272
417
|
break;
|
|
273
418
|
}
|
|
@@ -294,13 +439,13 @@ async function consumeStream(stream, opts) {
|
|
|
294
439
|
stats.outputTokens += tokensOut;
|
|
295
440
|
const parts = [
|
|
296
441
|
colors.dim(` ${formatDuration(elapsed)}`),
|
|
297
|
-
colors.
|
|
442
|
+
`${colors.cyan(String(tokensIn))}${colors.dim('→')}${colors.cyan(String(tokensOut))} ${colors.dim('tok')}`,
|
|
298
443
|
];
|
|
299
444
|
if (reason === 'tool-calls') {
|
|
300
445
|
parts.push(colors.dim('→ tool loop'));
|
|
301
446
|
}
|
|
302
447
|
else if (reason === 'stop') {
|
|
303
|
-
parts.push(colors.green('done'));
|
|
448
|
+
parts.push(colors.green(colors.bold('done')));
|
|
304
449
|
}
|
|
305
450
|
else {
|
|
306
451
|
parts.push(colors.yellow(reason));
|
|
@@ -312,7 +457,7 @@ async function consumeStream(stream, opts) {
|
|
|
312
457
|
case 'reasoning-start': {
|
|
313
458
|
if (!quiet) {
|
|
314
459
|
inReasoning = true;
|
|
315
|
-
status.write(colors.
|
|
460
|
+
status.write(colors.magenta('\n 💭 '));
|
|
316
461
|
}
|
|
317
462
|
break;
|
|
318
463
|
}
|
|
@@ -320,7 +465,7 @@ async function consumeStream(stream, opts) {
|
|
|
320
465
|
if (!quiet && inReasoning) {
|
|
321
466
|
const text = chunk.text ?? '';
|
|
322
467
|
const compacted = text.replace(/\n/g, ' ');
|
|
323
|
-
status.write(colors.dim(compacted));
|
|
468
|
+
status.write(colors.magenta(colors.dim(compacted)));
|
|
324
469
|
}
|
|
325
470
|
break;
|
|
326
471
|
}
|
|
@@ -337,24 +482,26 @@ async function consumeStream(stream, opts) {
|
|
|
337
482
|
const toolName = chunk.toolName;
|
|
338
483
|
if (verbose) {
|
|
339
484
|
const argsStr = chunk.input ? JSON.stringify(chunk.input, null, 2) : '';
|
|
340
|
-
status.write(`\n ${colors.cyan('
|
|
485
|
+
status.write(`\n ${colors.cyan('▶')} ${colors.cyan(colors.bold(toolName))}\n`);
|
|
341
486
|
if (argsStr) {
|
|
342
487
|
const indented = argsStr.split('\n').map((l) => ` ${l}`).join('\n');
|
|
343
488
|
status.write(`${colors.dim(indented)}\n`);
|
|
344
489
|
}
|
|
345
490
|
}
|
|
346
491
|
else {
|
|
347
|
-
const argsSummary = summarizeArgs(chunk.input);
|
|
492
|
+
const argsSummary = summarizeArgs(chunk.input, colors);
|
|
348
493
|
const display = argsSummary
|
|
349
|
-
? ` ${colors.cyan('
|
|
350
|
-
: ` ${colors.cyan('
|
|
494
|
+
? ` ${colors.cyan('▶')} ${colors.cyan(colors.bold(toolName))} ${argsSummary}`
|
|
495
|
+
: ` ${colors.cyan('▶')} ${colors.cyan(colors.bold(toolName))}`;
|
|
351
496
|
status.write(`${display}\n`);
|
|
352
497
|
}
|
|
498
|
+
spinner.start(`running ${toolName}...`);
|
|
353
499
|
}
|
|
354
500
|
afterToolResult = false;
|
|
355
501
|
break;
|
|
356
502
|
}
|
|
357
503
|
case 'tool-result': {
|
|
504
|
+
spinner.stop();
|
|
358
505
|
const toolOutputRaw = typeof chunk.output === 'string' ? chunk.output : JSON.stringify(chunk.output);
|
|
359
506
|
try {
|
|
360
507
|
const parsed = JSON.parse(toolOutputRaw);
|
|
@@ -372,7 +519,7 @@ async function consumeStream(stream, opts) {
|
|
|
372
519
|
displayOutput = displayOutput.slice(0, maxLen) + `\n... (${displayOutput.length} chars total)`;
|
|
373
520
|
}
|
|
374
521
|
const indented = displayOutput.split('\n').map((l) => ` ${l}`).join('\n');
|
|
375
|
-
status.write(` ${colors.green('
|
|
522
|
+
status.write(` ${colors.green('✔')} ${colors.green(colors.dim(toolName))} ${colors.dim('returned')}\n`);
|
|
376
523
|
status.write(`${colors.dim(indented)}\n`);
|
|
377
524
|
}
|
|
378
525
|
else {
|
|
@@ -381,19 +528,20 @@ async function consumeStream(stream, opts) {
|
|
|
381
528
|
const truncated = firstLine.length > 100
|
|
382
529
|
? firstLine.slice(0, 97) + '...'
|
|
383
530
|
: firstLine;
|
|
384
|
-
status.write(` ${colors.green('
|
|
531
|
+
status.write(` ${colors.green('✔')} ${colors.green(colors.dim(toolName + ': '))}${colors.dim(truncated)}\n`);
|
|
385
532
|
}
|
|
386
533
|
}
|
|
387
534
|
afterToolResult = true;
|
|
388
535
|
break;
|
|
389
536
|
}
|
|
390
537
|
case 'tool-error': {
|
|
538
|
+
spinner.stop();
|
|
391
539
|
if (!quiet) {
|
|
392
540
|
const toolName = chunk.toolName;
|
|
393
541
|
const error = chunk.error instanceof Error
|
|
394
542
|
? chunk.error.message
|
|
395
543
|
: String(chunk.error ?? 'unknown error');
|
|
396
|
-
status.write(` ${colors.red('
|
|
544
|
+
status.write(` ${colors.red('✖')} ${colors.red(colors.bold(toolName))} ${colors.red(error)}\n`);
|
|
397
545
|
}
|
|
398
546
|
afterToolResult = true;
|
|
399
547
|
break;
|
|
@@ -478,6 +626,7 @@ async function consumeStream(stream, opts) {
|
|
|
478
626
|
break;
|
|
479
627
|
}
|
|
480
628
|
case 'finish': {
|
|
629
|
+
spinner.stop();
|
|
481
630
|
if (!quiet) {
|
|
482
631
|
const elapsed = Date.now() - stats.startTime;
|
|
483
632
|
const totalUsage = chunk.totalUsage;
|
|
@@ -485,15 +634,20 @@ async function consumeStream(stream, opts) {
|
|
|
485
634
|
stats.inputTokens = totalUsage.inputTokens ?? stats.inputTokens;
|
|
486
635
|
stats.outputTokens = totalUsage.outputTokens ?? stats.outputTokens;
|
|
487
636
|
}
|
|
488
|
-
|
|
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`);
|
|
489
642
|
}
|
|
490
643
|
break;
|
|
491
644
|
}
|
|
492
645
|
case 'error': {
|
|
646
|
+
spinner.stop();
|
|
493
647
|
const error = chunk.error instanceof Error
|
|
494
648
|
? chunk.error.message
|
|
495
649
|
: String(chunk.error ?? 'unknown error');
|
|
496
|
-
status.write(`\n${colors.red('Error:')} ${error}\n`);
|
|
650
|
+
status.write(`\n${colors.red(colors.bold('✖ Error:'))} ${colors.red(error)}\n`);
|
|
497
651
|
break;
|
|
498
652
|
}
|
|
499
653
|
default:
|
|
@@ -549,9 +703,15 @@ async function runOneShot(prompt, args) {
|
|
|
549
703
|
workspaceRoot: args.workspace,
|
|
550
704
|
maxSteps: args.maxSteps,
|
|
551
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); });
|
|
552
712
|
if (args.outputLevel !== 'quiet') {
|
|
553
713
|
const toolCount = agent.getToolNames().length;
|
|
554
|
-
process.stderr.write(`${colors.
|
|
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`);
|
|
555
715
|
}
|
|
556
716
|
const result = await agent.stream({ prompt });
|
|
557
717
|
await consumeStream(result.fullStream, {
|
|
@@ -560,6 +720,7 @@ async function runOneShot(prompt, args) {
|
|
|
560
720
|
level: args.outputLevel,
|
|
561
721
|
colors,
|
|
562
722
|
maxSteps: args.maxSteps,
|
|
723
|
+
isTTY: process.stderr.isTTY ?? false,
|
|
563
724
|
});
|
|
564
725
|
const finalText = await result.text;
|
|
565
726
|
if (finalText && !finalText.endsWith('\n')) {
|
|
@@ -584,16 +745,22 @@ async function runRepl(args) {
|
|
|
584
745
|
workspaceRoot: args.workspace,
|
|
585
746
|
maxSteps: args.maxSteps,
|
|
586
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); });
|
|
587
754
|
const version = getVersion();
|
|
588
755
|
const output = process.stdout;
|
|
589
756
|
const toolCount = agent.getToolNames().length;
|
|
590
|
-
output.write(`\n${colors.bold('agntk')} ${colors.dim(`(${version})`)}\n`);
|
|
591
|
-
output.write(`${colors.cyan(args.name)} ${colors.dim(
|
|
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`);
|
|
592
759
|
output.write(`${colors.dim('Type /help for commands, /exit or Ctrl+C to quit.')}\n\n`);
|
|
593
760
|
const rl = createInterface({
|
|
594
761
|
input: process.stdin,
|
|
595
762
|
output: process.stdout,
|
|
596
|
-
prompt: `${colors.cyan(args.name + '
|
|
763
|
+
prompt: `${colors.cyan(colors.bold(args.name + ' ❯'))} `,
|
|
597
764
|
terminal: true,
|
|
598
765
|
});
|
|
599
766
|
const history = [];
|
|
@@ -652,6 +819,7 @@ async function runRepl(args) {
|
|
|
652
819
|
level: args.outputLevel,
|
|
653
820
|
colors,
|
|
654
821
|
maxSteps: args.maxSteps,
|
|
822
|
+
isTTY: process.stderr.isTTY ?? false,
|
|
655
823
|
});
|
|
656
824
|
const responseText = (await result.text) ?? '';
|
|
657
825
|
if (responseText && !responseText.endsWith('\n')) {
|
|
@@ -697,7 +865,7 @@ async function runRepl(args) {
|
|
|
697
865
|
rl.on('close', () => {
|
|
698
866
|
closed = true;
|
|
699
867
|
const finish = () => {
|
|
700
|
-
output.write(`\n${colors.dim('Goodbye!')}\n`);
|
|
868
|
+
output.write(`\n${colors.dim('👋 Goodbye!')}\n`);
|
|
701
869
|
resolvePromise();
|
|
702
870
|
};
|
|
703
871
|
if (busy) {
|
|
@@ -739,14 +907,15 @@ async function main() {
|
|
|
739
907
|
// Validate: need a name
|
|
740
908
|
if (!args.name) {
|
|
741
909
|
if (!args.prompt && process.stdin.isTTY) {
|
|
742
|
-
console.error('Error: No
|
|
743
|
-
'Usage: agntk
|
|
744
|
-
' agntk
|
|
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' +
|
|
745
914
|
' agntk list\n' +
|
|
746
915
|
' agntk -h');
|
|
747
916
|
process.exit(1);
|
|
748
917
|
}
|
|
749
|
-
// Default name if
|
|
918
|
+
// Default name if no --name flag was given
|
|
750
919
|
args.name = 'default';
|
|
751
920
|
}
|
|
752
921
|
// Check API key
|
|
@@ -754,11 +923,17 @@ async function main() {
|
|
|
754
923
|
if (!apiKeyResult) {
|
|
755
924
|
console.error('Error: No API key found.\n\n' +
|
|
756
925
|
' 1. Get a key at https://openrouter.ai/keys\n' +
|
|
757
|
-
' 2.
|
|
758
|
-
'
|
|
759
|
-
'
|
|
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');
|
|
760
930
|
process.exit(1);
|
|
761
931
|
}
|
|
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
|
+
}
|
|
762
937
|
// Interactive mode
|
|
763
938
|
if (args.interactive) {
|
|
764
939
|
await runRepl(args);
|
|
@@ -772,8 +947,9 @@ async function main() {
|
|
|
772
947
|
}
|
|
773
948
|
if (!prompt) {
|
|
774
949
|
console.error('Error: No prompt provided.\n' +
|
|
775
|
-
'Usage: agntk
|
|
776
|
-
' agntk
|
|
950
|
+
'Usage: agntk "your prompt"\n' +
|
|
951
|
+
' agntk -n <name> "your prompt"\n' +
|
|
952
|
+
' agntk -n <name> -i\n' +
|
|
777
953
|
' agntk -h');
|
|
778
954
|
process.exit(1);
|
|
779
955
|
}
|