ikie-cli 0.1.39 → 0.1.40
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/agent.d.ts +9 -11
- package/dist/agent.js +138 -181
- package/dist/index.js +2 -1
- package/dist/repl.js +67 -9
- package/dist/session-manager.d.ts +29 -0
- package/dist/session-manager.js +145 -0
- package/dist/theme.d.ts +4 -1
- package/dist/theme.js +37 -5
- package/dist/tools.d.ts +1 -1
- package/dist/tools.js +249 -68
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -168,7 +168,7 @@ async function main() {
|
|
|
168
168
|
const modelsUrl = (config.baseURL ?? IKIE_API_BASE).includes('/api/v1')
|
|
169
169
|
? (config.baseURL ?? IKIE_API_BASE).replace('/api/v1', '/api/models')
|
|
170
170
|
: `${IKIE_HOST}/api/models`;
|
|
171
|
-
const res = await fetch(modelsUrl);
|
|
171
|
+
const res = await fetch(modelsUrl, { signal: AbortSignal.timeout(4000) });
|
|
172
172
|
if (res.ok) {
|
|
173
173
|
const data = await res.json();
|
|
174
174
|
const serverDefault = data.models?.find(m => m.is_default);
|
|
@@ -201,6 +201,7 @@ ${errorLine('Not signed in.')}
|
|
|
201
201
|
const client = new OpenAI({
|
|
202
202
|
apiKey,
|
|
203
203
|
baseURL: config.baseURL ?? IKIE_API_BASE,
|
|
204
|
+
timeout: 60000, // 60 seconds timeout to prevent indefinite hangs
|
|
204
205
|
});
|
|
205
206
|
const projectCtx = detectProjectContext();
|
|
206
207
|
const projectContextStr = formatContextForPrompt(projectCtx);
|
package/dist/repl.js
CHANGED
|
@@ -4,7 +4,7 @@ import { restoreStdinListeners, extractUpstreamError } from './agent.js';
|
|
|
4
4
|
import { c, PROMPT, CONTINUE_PROMPT, PROMPT_ARROW, printPromptHeader, promptHeaderText, modeTag, drawBanner, infoLine, successLine, errorLine, THEMES, setTheme, stripAnsi, contextRing, renderSlashMenu, supportsVT, CH, } from './theme.js';
|
|
5
5
|
import { renderMarkdown } from './renderer.js';
|
|
6
6
|
import { loadAllMemory } from './memory.js';
|
|
7
|
-
import { HOME_DIR, saveConfig, DEFAULT_MODEL, IKIE_HOST, IKIE_API_BASE, isLoggedIn, getApiKey } from './config.js';
|
|
7
|
+
import { HOME_DIR, saveConfig, DEFAULT_MODEL, IKIE_HOST, IKIE_API_BASE, isLoggedIn, getApiKey, loadConfig } from './config.js';
|
|
8
8
|
import { login, logout } from './auth.js';
|
|
9
9
|
import { join as pathJoin } from 'path';
|
|
10
10
|
import { deleteSession, listSessions, loadSession, normalizeSessionName, saveSession } from './session.js';
|
|
@@ -459,6 +459,12 @@ async function handleSlashCommand(input, agent, config, projectContext, rl, sess
|
|
|
459
459
|
}
|
|
460
460
|
case 'login': {
|
|
461
461
|
await login();
|
|
462
|
+
const reloaded = loadConfig();
|
|
463
|
+
Object.assign(config, reloaded);
|
|
464
|
+
const freshApiKey = getApiKey(config);
|
|
465
|
+
if (freshApiKey) {
|
|
466
|
+
agent.updateApiKey(freshApiKey);
|
|
467
|
+
}
|
|
462
468
|
return true;
|
|
463
469
|
}
|
|
464
470
|
case 'skills': {
|
|
@@ -545,6 +551,12 @@ async function handleSlashCommand(input, agent, config, projectContext, rl, sess
|
|
|
545
551
|
case 'onboarding': {
|
|
546
552
|
console.log();
|
|
547
553
|
await runOnboarding(config);
|
|
554
|
+
const reloaded = loadConfig();
|
|
555
|
+
Object.assign(config, reloaded);
|
|
556
|
+
const freshApiKey = getApiKey(config);
|
|
557
|
+
if (freshApiKey) {
|
|
558
|
+
agent.updateApiKey(freshApiKey);
|
|
559
|
+
}
|
|
548
560
|
return true;
|
|
549
561
|
}
|
|
550
562
|
case 'mcp': {
|
|
@@ -618,6 +630,8 @@ async function handleSlashCommand(input, agent, config, projectContext, rl, sess
|
|
|
618
630
|
}
|
|
619
631
|
else {
|
|
620
632
|
logout();
|
|
633
|
+
delete config.account;
|
|
634
|
+
agent.updateApiKey('');
|
|
621
635
|
}
|
|
622
636
|
return true;
|
|
623
637
|
}
|
|
@@ -1084,6 +1098,36 @@ function notify(message) {
|
|
|
1084
1098
|
exec(`powershell -NoProfile -NonInteractive -Command "& {${ps}}"`, () => { });
|
|
1085
1099
|
}
|
|
1086
1100
|
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Parse numbered plan steps from an agent's plan-mode response.
|
|
1103
|
+
* Looks for lines like `1. Do something` under a `## Plan` heading.
|
|
1104
|
+
*/
|
|
1105
|
+
function parsePlanSteps(conversation) {
|
|
1106
|
+
const lastAssistant = [...conversation].reverse().find(m => m.role === 'assistant');
|
|
1107
|
+
if (!lastAssistant || typeof lastAssistant.content !== 'string')
|
|
1108
|
+
return [];
|
|
1109
|
+
const text = lastAssistant.content;
|
|
1110
|
+
const lines = text.split('\n');
|
|
1111
|
+
const steps = [];
|
|
1112
|
+
let inPlan = false;
|
|
1113
|
+
for (const line of lines) {
|
|
1114
|
+
const t = line.trim();
|
|
1115
|
+
if (/^#{1,3}\s*plan/i.test(t)) {
|
|
1116
|
+
inPlan = true;
|
|
1117
|
+
continue;
|
|
1118
|
+
}
|
|
1119
|
+
if (inPlan) {
|
|
1120
|
+
const m = t.match(/^\d+[.)]\s+(.+)/);
|
|
1121
|
+
if (m) {
|
|
1122
|
+
steps.push(m[1].trim());
|
|
1123
|
+
continue;
|
|
1124
|
+
}
|
|
1125
|
+
if (/^#{1,3}\s/.test(t) || (t === '' && steps.length > 0))
|
|
1126
|
+
break;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
return steps;
|
|
1130
|
+
}
|
|
1087
1131
|
export async function startREPL(agent, config, projectContext, oneShot) {
|
|
1088
1132
|
void HISTORY_FILE;
|
|
1089
1133
|
// Context window size — default 131072 (128k), updated silently from model info.
|
|
@@ -1378,8 +1422,11 @@ export async function startREPL(agent, config, projectContext, oneShot) {
|
|
|
1378
1422
|
closeMenu();
|
|
1379
1423
|
const next = agent.getMode() === 'agent' ? 'plan' : 'agent';
|
|
1380
1424
|
agent.setMode(next);
|
|
1425
|
+
// When supportsVT is true showPrompt prints a tip line between the
|
|
1426
|
+
// header and the prompt, so we need to skip 2 lines to reach the header.
|
|
1427
|
+
const linesUp = supportsVT ? 2 : 1;
|
|
1381
1428
|
process.stdout.write('\x1b7' + // save cursor (on the input line)
|
|
1382
|
-
|
|
1429
|
+
`\x1b[${linesUp}A\r\x1b[2K` + // up past tip to header, clear it
|
|
1383
1430
|
promptHeaderText(next) +
|
|
1384
1431
|
'\x1b8');
|
|
1385
1432
|
return;
|
|
@@ -1474,6 +1521,10 @@ export async function startREPL(agent, config, projectContext, oneShot) {
|
|
|
1474
1521
|
}
|
|
1475
1522
|
else {
|
|
1476
1523
|
printPromptHeader(agent.getMode());
|
|
1524
|
+
if (supportsVT) {
|
|
1525
|
+
const tip = TIPS[tipIndex++ % TIPS.length];
|
|
1526
|
+
process.stdout.write(` ${c.secondary('Tip:')} ${c.dim(tip)}\n`);
|
|
1527
|
+
}
|
|
1477
1528
|
const tokens = agent.estimateConversationTokens();
|
|
1478
1529
|
const pct = tokens / contextWindow;
|
|
1479
1530
|
const ring = tokens > 0 ? contextRing(Math.min(pct, 1)) : '';
|
|
@@ -1487,11 +1538,6 @@ export async function startREPL(agent, config, projectContext, oneShot) {
|
|
|
1487
1538
|
currentPrompt = prompt;
|
|
1488
1539
|
rl.setPrompt(prompt);
|
|
1489
1540
|
rl.prompt();
|
|
1490
|
-
// Rotating tip below the prompt (only on VT-capable terminals)
|
|
1491
|
-
if (supportsVT) {
|
|
1492
|
-
const tip = TIPS[tipIndex++ % TIPS.length];
|
|
1493
|
-
process.stdout.write(`\x1b[s\n${c.dim(' ' + tip)}\x1b[u`);
|
|
1494
|
-
}
|
|
1495
1541
|
}
|
|
1496
1542
|
};
|
|
1497
1543
|
rl.on('close', () => {
|
|
@@ -1552,6 +1598,12 @@ export async function startREPL(agent, config, projectContext, oneShot) {
|
|
|
1552
1598
|
showPrompt();
|
|
1553
1599
|
return;
|
|
1554
1600
|
}
|
|
1601
|
+
if (!isLoggedIn(config)) {
|
|
1602
|
+
console.log(errorLine('Not signed in. Run /login to sign in and continue.'));
|
|
1603
|
+
process.stdout.write('\n');
|
|
1604
|
+
showPrompt();
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1555
1607
|
const agentInput = expandPastedBlocks(fullInput);
|
|
1556
1608
|
const imagesForTurn = [...imageState.pending];
|
|
1557
1609
|
const userContent = buildUserContent(agentInput, imagesForTurn);
|
|
@@ -1577,8 +1629,9 @@ export async function startREPL(agent, config, projectContext, oneShot) {
|
|
|
1577
1629
|
const b = data[0];
|
|
1578
1630
|
if (b === 0x1b && data.length === 1) {
|
|
1579
1631
|
if (!abortController.signal.aborted) {
|
|
1632
|
+
process.stdout.write('\r\x1b[2K');
|
|
1580
1633
|
abortController.abort();
|
|
1581
|
-
process.stdout.write(
|
|
1634
|
+
process.stdout.write(` ${c.warning('Cancelled')}\n`);
|
|
1582
1635
|
}
|
|
1583
1636
|
}
|
|
1584
1637
|
else if (b === 0x03) {
|
|
@@ -1586,8 +1639,9 @@ export async function startREPL(agent, config, projectContext, oneShot) {
|
|
|
1586
1639
|
if (ctrlCCount >= 2)
|
|
1587
1640
|
process.exit(0);
|
|
1588
1641
|
if (!abortController.signal.aborted) {
|
|
1642
|
+
process.stdout.write('\r\x1b[2K');
|
|
1589
1643
|
abortController.abort();
|
|
1590
|
-
process.stdout.write(
|
|
1644
|
+
process.stdout.write(` ${c.warning('Cancelled')}\n`);
|
|
1591
1645
|
}
|
|
1592
1646
|
setTimeout(() => { ctrlCCount = 0; }, 2000);
|
|
1593
1647
|
}
|
|
@@ -1624,6 +1678,10 @@ export async function startREPL(agent, config, projectContext, oneShot) {
|
|
|
1624
1678
|
if (go && !abortController.signal.aborted) {
|
|
1625
1679
|
agent.setMode('agent');
|
|
1626
1680
|
process.stdout.write(` ${c.muted('→ switching to')} ${modeTag('agent')} ${c.muted('mode, executing…')}\n`);
|
|
1681
|
+
const planSteps = parsePlanSteps(agent.getConversation());
|
|
1682
|
+
if (planSteps.length > 0) {
|
|
1683
|
+
agent.setPlanSteps(planSteps);
|
|
1684
|
+
}
|
|
1627
1685
|
await agent.send('Execute the plan you just proposed. Make the changes now.', {
|
|
1628
1686
|
autoApprove: config.autoApprove,
|
|
1629
1687
|
signal: abortController.signal,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
type AgentSession = {
|
|
2
|
+
id: number;
|
|
3
|
+
name: string;
|
|
4
|
+
buffer: string[];
|
|
5
|
+
};
|
|
6
|
+
export declare class SessionManager {
|
|
7
|
+
static activeId: number;
|
|
8
|
+
static nextAgentId: number;
|
|
9
|
+
static sessions: Map<number, AgentSession>;
|
|
10
|
+
static onRedraw?: () => void;
|
|
11
|
+
static init(mainAgentName?: string, onRedrawCallback?: () => void): void;
|
|
12
|
+
static register(name: string): number;
|
|
13
|
+
static remove(id: number): void;
|
|
14
|
+
static getActiveId(): number;
|
|
15
|
+
static setActiveId(id: number, afterRedraw?: () => void): void;
|
|
16
|
+
static switchToNext(afterRedraw?: () => void): void;
|
|
17
|
+
static switchToPrev(afterRedraw?: () => void): void;
|
|
18
|
+
static isActive(agentId: number): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Append already-rendered lines to an agent's scrollback WITHOUT echoing to
|
|
21
|
+
* stdout. Used by the live task board, which paints its own animation frames
|
|
22
|
+
* directly and only needs the *settled* result recorded for history/redraw.
|
|
23
|
+
*/
|
|
24
|
+
static recordHistory(agentId: number, text: string): void;
|
|
25
|
+
static write(agentId: number, text: string): void;
|
|
26
|
+
private static appendToBuffer;
|
|
27
|
+
static redraw(afterRedraw?: () => void): void;
|
|
28
|
+
}
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
export class SessionManager {
|
|
3
|
+
static activeId = 0;
|
|
4
|
+
static nextAgentId = 0;
|
|
5
|
+
static sessions = new Map();
|
|
6
|
+
static onRedraw;
|
|
7
|
+
static init(mainAgentName = 'Main', onRedrawCallback) {
|
|
8
|
+
const id = this.nextAgentId++;
|
|
9
|
+
this.sessions.set(id, { id, name: mainAgentName, buffer: [] });
|
|
10
|
+
this.activeId = id;
|
|
11
|
+
this.onRedraw = onRedrawCallback;
|
|
12
|
+
}
|
|
13
|
+
static register(name) {
|
|
14
|
+
const id = this.nextAgentId++;
|
|
15
|
+
this.sessions.set(id, { id, name, buffer: [] });
|
|
16
|
+
return id;
|
|
17
|
+
}
|
|
18
|
+
static remove(id) {
|
|
19
|
+
if (id !== 0) {
|
|
20
|
+
this.sessions.delete(id);
|
|
21
|
+
if (this.activeId === id) {
|
|
22
|
+
this.setActiveId(0);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
this.redraw();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
static getActiveId() {
|
|
30
|
+
return this.activeId;
|
|
31
|
+
}
|
|
32
|
+
static setActiveId(id, afterRedraw) {
|
|
33
|
+
if (this.sessions.has(id)) {
|
|
34
|
+
this.activeId = id;
|
|
35
|
+
this.redraw(afterRedraw);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
static switchToNext(afterRedraw) {
|
|
39
|
+
const ids = Array.from(this.sessions.keys());
|
|
40
|
+
const idx = ids.indexOf(this.activeId);
|
|
41
|
+
if (idx >= 0) {
|
|
42
|
+
this.setActiveId(ids[(idx + 1) % ids.length], afterRedraw);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
static switchToPrev(afterRedraw) {
|
|
46
|
+
const ids = Array.from(this.sessions.keys());
|
|
47
|
+
const idx = ids.indexOf(this.activeId);
|
|
48
|
+
if (idx >= 0) {
|
|
49
|
+
this.setActiveId(ids[(idx - 1 + ids.length) % ids.length], afterRedraw);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
static isActive(agentId) {
|
|
53
|
+
return this.activeId === agentId;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Append already-rendered lines to an agent's scrollback WITHOUT echoing to
|
|
57
|
+
* stdout. Used by the live task board, which paints its own animation frames
|
|
58
|
+
* directly and only needs the *settled* result recorded for history/redraw.
|
|
59
|
+
*/
|
|
60
|
+
static recordHistory(agentId, text) {
|
|
61
|
+
let session = this.sessions.get(agentId);
|
|
62
|
+
if (!session) {
|
|
63
|
+
session = { id: agentId, name: `Agent ${agentId}`, buffer: [] };
|
|
64
|
+
this.sessions.set(agentId, session);
|
|
65
|
+
}
|
|
66
|
+
this.appendToBuffer(session, text);
|
|
67
|
+
}
|
|
68
|
+
static write(agentId, text) {
|
|
69
|
+
let session = this.sessions.get(agentId);
|
|
70
|
+
if (!session) {
|
|
71
|
+
session = { id: agentId, name: `Agent ${agentId}`, buffer: [] };
|
|
72
|
+
this.sessions.set(agentId, session);
|
|
73
|
+
}
|
|
74
|
+
this.appendToBuffer(session, text);
|
|
75
|
+
if (agentId === this.activeId) {
|
|
76
|
+
process.stdout.write(text);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
static appendToBuffer(session, text) {
|
|
80
|
+
let currentLine = session.buffer.length > 0 ? session.buffer[session.buffer.length - 1] : '';
|
|
81
|
+
for (let i = 0; i < text.length; i++) {
|
|
82
|
+
const char = text[i];
|
|
83
|
+
if (char === '\n') {
|
|
84
|
+
session.buffer[session.buffer.length - 1] = currentLine;
|
|
85
|
+
session.buffer.push('');
|
|
86
|
+
currentLine = '';
|
|
87
|
+
}
|
|
88
|
+
else if (char === '\r') {
|
|
89
|
+
// simple CR behavior: overwrite current line
|
|
90
|
+
// Wait, typically \r\n are together. If we see \r and next is \n, we skip \r.
|
|
91
|
+
if (text[i + 1] === '\n') {
|
|
92
|
+
// ignore \r before \n
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
currentLine = ''; // reset line
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
currentLine += char;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Clean up ANSI escape code for "clear line" at start of current line if any
|
|
103
|
+
currentLine = currentLine.replace(/^\x1b\[2K/, '');
|
|
104
|
+
if (session.buffer.length === 0) {
|
|
105
|
+
session.buffer.push(currentLine);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
session.buffer[session.buffer.length - 1] = currentLine;
|
|
109
|
+
}
|
|
110
|
+
if (session.buffer.length > 1000) {
|
|
111
|
+
session.buffer.splice(0, session.buffer.length - 1000);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
static redraw(afterRedraw) {
|
|
115
|
+
const session = this.sessions.get(this.activeId);
|
|
116
|
+
if (!session)
|
|
117
|
+
return;
|
|
118
|
+
process.stdout.write('\x1b[2J\x1b[3J\x1b[H');
|
|
119
|
+
let out = '';
|
|
120
|
+
out += chalk.bgGray.white('--- AGENTS ---') + '\n';
|
|
121
|
+
const ids = Array.from(this.sessions.keys());
|
|
122
|
+
for (const id of ids) {
|
|
123
|
+
const s = this.sessions.get(id);
|
|
124
|
+
if (id === this.activeId) {
|
|
125
|
+
out += chalk.bgGreen.black(` [${id}: ${s.name}] `) + ' ';
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
out += chalk.bgBlack.gray(` [${id}: ${s.name}] `) + ' ';
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
out += '\n' + chalk.dim('(Alt+Right / Alt+Left to switch. Scroll up to read history)\n\n');
|
|
132
|
+
for (const line of session.buffer) {
|
|
133
|
+
out += line + '\n';
|
|
134
|
+
}
|
|
135
|
+
// Remove trailing newline so the cursor stays on the last line for spinners
|
|
136
|
+
if (out.endsWith('\n')) {
|
|
137
|
+
out = out.slice(0, -1);
|
|
138
|
+
}
|
|
139
|
+
process.stdout.write(out);
|
|
140
|
+
if (afterRedraw)
|
|
141
|
+
afterRedraw();
|
|
142
|
+
if (this.onRedraw)
|
|
143
|
+
this.onRedraw();
|
|
144
|
+
}
|
|
145
|
+
}
|
package/dist/theme.d.ts
CHANGED
|
@@ -34,6 +34,7 @@ export declare const c: {
|
|
|
34
34
|
readonly bold: import("chalk").ChalkInstance;
|
|
35
35
|
readonly dim: import("chalk").ChalkInstance;
|
|
36
36
|
readonly italic: import("chalk").ChalkInstance;
|
|
37
|
+
readonly strikethrough: import("chalk").ChalkInstance;
|
|
37
38
|
};
|
|
38
39
|
export declare function stripAnsi(str: string): string;
|
|
39
40
|
export declare const BANNER_ROWS = 9;
|
|
@@ -70,7 +71,9 @@ export declare class InlineSpinner {
|
|
|
70
71
|
private frameIdx;
|
|
71
72
|
private frames;
|
|
72
73
|
private visible;
|
|
73
|
-
|
|
74
|
+
private signal?;
|
|
75
|
+
private onAbort;
|
|
76
|
+
constructor(label?: string, elapsedSince?: number, signal?: AbortSignal);
|
|
74
77
|
start(): void;
|
|
75
78
|
private draw;
|
|
76
79
|
stop(successMessage?: string): void;
|
package/dist/theme.js
CHANGED
|
@@ -186,6 +186,7 @@ export const c = {
|
|
|
186
186
|
get bold() { return chalk.bold; },
|
|
187
187
|
get dim() { return chalk.dim; },
|
|
188
188
|
get italic() { return chalk.italic; },
|
|
189
|
+
get strikethrough() { return chalk.strikethrough; },
|
|
189
190
|
};
|
|
190
191
|
function truncate(str, max) {
|
|
191
192
|
if (str.length <= max)
|
|
@@ -313,9 +314,22 @@ export class InlineSpinner {
|
|
|
313
314
|
frameIdx = 0;
|
|
314
315
|
frames = ['.', 'o', 'O', 'o'];
|
|
315
316
|
visible = false;
|
|
316
|
-
|
|
317
|
+
signal;
|
|
318
|
+
onAbort = () => {
|
|
319
|
+
this.stop();
|
|
320
|
+
};
|
|
321
|
+
constructor(label = 'Thinking', elapsedSince, signal) {
|
|
317
322
|
this.label = label;
|
|
318
323
|
this.elapsedSince = elapsedSince;
|
|
324
|
+
if (signal) {
|
|
325
|
+
this.signal = signal;
|
|
326
|
+
if (signal.aborted) {
|
|
327
|
+
this.stop();
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
signal.addEventListener('abort', this.onAbort);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
319
333
|
}
|
|
320
334
|
start() {
|
|
321
335
|
if (this.timer || this.delayTimer)
|
|
@@ -338,9 +352,14 @@ export class InlineSpinner {
|
|
|
338
352
|
const elapsedFrom = this.elapsedSince ?? this.startTime;
|
|
339
353
|
const elapsed = ((Date.now() - elapsedFrom) / 1000).toFixed(1);
|
|
340
354
|
const frame = c.primary(this.frames[this.frameIdx]);
|
|
341
|
-
|
|
355
|
+
const text = `\r\x1b[2K ${frame} ${c.muted(this.label)} ${c.accent(`${elapsed}s`)} ${c.muted('· Esc to interrupt')}`;
|
|
356
|
+
process.stdout.write(text);
|
|
342
357
|
}
|
|
343
358
|
stop(successMessage) {
|
|
359
|
+
if (this.signal) {
|
|
360
|
+
this.signal.removeEventListener('abort', this.onAbort);
|
|
361
|
+
this.signal = undefined;
|
|
362
|
+
}
|
|
344
363
|
if (this.delayTimer) {
|
|
345
364
|
clearTimeout(this.delayTimer);
|
|
346
365
|
this.delayTimer = null;
|
|
@@ -351,12 +370,26 @@ export class InlineSpinner {
|
|
|
351
370
|
}
|
|
352
371
|
const wasVisible = this.visible;
|
|
353
372
|
this.visible = false;
|
|
373
|
+
let out = '';
|
|
354
374
|
if (successMessage) {
|
|
355
|
-
|
|
375
|
+
const elapsedFrom = this.elapsedSince ?? this.startTime;
|
|
376
|
+
const elapsed = ((Date.now() - elapsedFrom) / 1000).toFixed(1);
|
|
377
|
+
// If success message is just "ok" (default behavior) or something else
|
|
378
|
+
if (successMessage === 'ok') {
|
|
379
|
+
out = `\r\x1b[2K ${c.success('✓')} ${c.muted(this.label)} ${c.accent(`${elapsed}s`)}\n`;
|
|
380
|
+
}
|
|
381
|
+
else if (successMessage === 'failed') {
|
|
382
|
+
out = `\r\x1b[2K ${c.error('✗')} ${c.muted(this.label)} ${c.accent(`${elapsed}s`)}\n`;
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
out = `\r\x1b[2K ${c.success('✓')} ${c.muted(successMessage)} ${c.accent(`${elapsed}s`)}\n`;
|
|
386
|
+
}
|
|
356
387
|
}
|
|
357
388
|
else if (wasVisible) {
|
|
358
|
-
|
|
389
|
+
out = '\r\x1b[2K';
|
|
359
390
|
}
|
|
391
|
+
if (out)
|
|
392
|
+
process.stdout.write(out);
|
|
360
393
|
}
|
|
361
394
|
updateLabel(label) {
|
|
362
395
|
this.label = label;
|
|
@@ -375,7 +408,6 @@ export function toolMeta(rawName) {
|
|
|
375
408
|
case 'grep': return { verb: 'Grep', tint: c.info };
|
|
376
409
|
case 'search_files': return { verb: 'Search', tint: c.info };
|
|
377
410
|
case 'memory_write': return { verb: 'Remember', tint: c.secondary };
|
|
378
|
-
case 'spawn_agent': return { verb: 'Agent', tint: c.secondary };
|
|
379
411
|
case 'switch_mode': return { verb: 'SwitchMode', tint: c.warning };
|
|
380
412
|
case 'ask_user': return { verb: 'Ask', tint: c.info };
|
|
381
413
|
case 'fetch_url': return { verb: 'Fetch', tint: c.info };
|
package/dist/tools.d.ts
CHANGED
|
@@ -5,4 +5,4 @@ export declare const SAFE_TOOLS: Set<string>;
|
|
|
5
5
|
export declare const PLAN_TOOLS: Set<string>;
|
|
6
6
|
export declare function isRestrictedPath(path: string): boolean;
|
|
7
7
|
export declare function formatToolArgs(name: string, input: Record<string, unknown>): string;
|
|
8
|
-
export declare function executeTool(name: string, input: Record<string, unknown
|
|
8
|
+
export declare function executeTool(name: string, input: Record<string, unknown>, signal?: AbortSignal): Promise<string>;
|