@element47/ag 4.4.0
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/README.md +316 -0
- package/dist/cli/parser.d.ts +6 -0
- package/dist/cli/parser.d.ts.map +1 -0
- package/dist/cli/parser.js +62 -0
- package/dist/cli/parser.js.map +1 -0
- package/dist/cli/repl.d.ts +16 -0
- package/dist/cli/repl.d.ts.map +1 -0
- package/dist/cli/repl.js +599 -0
- package/dist/cli/repl.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +84 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/__tests__/agent.test.d.ts +2 -0
- package/dist/core/__tests__/agent.test.d.ts.map +1 -0
- package/dist/core/__tests__/agent.test.js +53 -0
- package/dist/core/__tests__/agent.test.js.map +1 -0
- package/dist/core/__tests__/config.test.d.ts +2 -0
- package/dist/core/__tests__/config.test.d.ts.map +1 -0
- package/dist/core/__tests__/config.test.js +58 -0
- package/dist/core/__tests__/config.test.js.map +1 -0
- package/dist/core/__tests__/constants.test.d.ts +2 -0
- package/dist/core/__tests__/constants.test.d.ts.map +1 -0
- package/dist/core/__tests__/constants.test.js +40 -0
- package/dist/core/__tests__/constants.test.js.map +1 -0
- package/dist/core/__tests__/context.test.d.ts +2 -0
- package/dist/core/__tests__/context.test.d.ts.map +1 -0
- package/dist/core/__tests__/context.test.js +120 -0
- package/dist/core/__tests__/context.test.js.map +1 -0
- package/dist/core/__tests__/environment.test.d.ts +2 -0
- package/dist/core/__tests__/environment.test.d.ts.map +1 -0
- package/dist/core/__tests__/environment.test.js +94 -0
- package/dist/core/__tests__/environment.test.js.map +1 -0
- package/dist/core/__tests__/permissions.test.d.ts +2 -0
- package/dist/core/__tests__/permissions.test.d.ts.map +1 -0
- package/dist/core/__tests__/permissions.test.js +66 -0
- package/dist/core/__tests__/permissions.test.js.map +1 -0
- package/dist/core/__tests__/registry.test.d.ts +2 -0
- package/dist/core/__tests__/registry.test.d.ts.map +1 -0
- package/dist/core/__tests__/registry.test.js +152 -0
- package/dist/core/__tests__/registry.test.js.map +1 -0
- package/dist/core/__tests__/streaming.test.d.ts +2 -0
- package/dist/core/__tests__/streaming.test.d.ts.map +1 -0
- package/dist/core/__tests__/streaming.test.js +54 -0
- package/dist/core/__tests__/streaming.test.js.map +1 -0
- package/dist/core/agent.d.ts +87 -0
- package/dist/core/agent.d.ts.map +1 -0
- package/dist/core/agent.js +711 -0
- package/dist/core/agent.js.map +1 -0
- package/dist/core/colors.d.ts +12 -0
- package/dist/core/colors.d.ts.map +1 -0
- package/dist/core/colors.js +37 -0
- package/dist/core/colors.js.map +1 -0
- package/dist/core/config.d.ts +13 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +30 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/constants.d.ts +5 -0
- package/dist/core/constants.d.ts.map +1 -0
- package/dist/core/constants.js +26 -0
- package/dist/core/constants.js.map +1 -0
- package/dist/core/context.d.ts +18 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/context.js +99 -0
- package/dist/core/context.js.map +1 -0
- package/dist/core/loader.d.ts +3 -0
- package/dist/core/loader.d.ts.map +1 -0
- package/dist/core/loader.js +39 -0
- package/dist/core/loader.js.map +1 -0
- package/dist/core/registry.d.ts +11 -0
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/core/registry.js +112 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/skills.d.ts +14 -0
- package/dist/core/skills.d.ts.map +1 -0
- package/dist/core/skills.js +100 -0
- package/dist/core/skills.js.map +1 -0
- package/dist/core/types.d.ts +64 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/version.d.ts +2 -0
- package/dist/core/version.d.ts.map +1 -0
- package/dist/core/version.js +7 -0
- package/dist/core/version.js.map +1 -0
- package/dist/memory/__tests__/memory.test.d.ts +2 -0
- package/dist/memory/__tests__/memory.test.d.ts.map +1 -0
- package/dist/memory/__tests__/memory.test.js +196 -0
- package/dist/memory/__tests__/memory.test.js.map +1 -0
- package/dist/memory/memory.d.ts +41 -0
- package/dist/memory/memory.d.ts.map +1 -0
- package/dist/memory/memory.js +206 -0
- package/dist/memory/memory.js.map +1 -0
- package/dist/tools/__tests__/bash.test.d.ts +2 -0
- package/dist/tools/__tests__/bash.test.d.ts.map +1 -0
- package/dist/tools/__tests__/bash.test.js +58 -0
- package/dist/tools/__tests__/bash.test.js.map +1 -0
- package/dist/tools/__tests__/file.test.d.ts +2 -0
- package/dist/tools/__tests__/file.test.d.ts.map +1 -0
- package/dist/tools/__tests__/file.test.js +115 -0
- package/dist/tools/__tests__/file.test.js.map +1 -0
- package/dist/tools/__tests__/git.test.d.ts +2 -0
- package/dist/tools/__tests__/git.test.d.ts.map +1 -0
- package/dist/tools/__tests__/git.test.js +19 -0
- package/dist/tools/__tests__/git.test.js.map +1 -0
- package/dist/tools/__tests__/grep.test.d.ts +2 -0
- package/dist/tools/__tests__/grep.test.d.ts.map +1 -0
- package/dist/tools/__tests__/grep.test.js +36 -0
- package/dist/tools/__tests__/grep.test.js.map +1 -0
- package/dist/tools/__tests__/memory-tool.test.d.ts +2 -0
- package/dist/tools/__tests__/memory-tool.test.d.ts.map +1 -0
- package/dist/tools/__tests__/memory-tool.test.js +39 -0
- package/dist/tools/__tests__/memory-tool.test.js.map +1 -0
- package/dist/tools/__tests__/plan.test.d.ts +2 -0
- package/dist/tools/__tests__/plan.test.d.ts.map +1 -0
- package/dist/tools/__tests__/plan.test.js +81 -0
- package/dist/tools/__tests__/plan.test.js.map +1 -0
- package/dist/tools/__tests__/schemas.test.d.ts +2 -0
- package/dist/tools/__tests__/schemas.test.d.ts.map +1 -0
- package/dist/tools/__tests__/schemas.test.js +40 -0
- package/dist/tools/__tests__/schemas.test.js.map +1 -0
- package/dist/tools/__tests__/web.test.d.ts +2 -0
- package/dist/tools/__tests__/web.test.d.ts.map +1 -0
- package/dist/tools/__tests__/web.test.js +51 -0
- package/dist/tools/__tests__/web.test.js.map +1 -0
- package/dist/tools/bash.d.ts +6 -0
- package/dist/tools/bash.d.ts.map +1 -0
- package/dist/tools/bash.js +89 -0
- package/dist/tools/bash.js.map +1 -0
- package/dist/tools/file.d.ts +7 -0
- package/dist/tools/file.d.ts.map +1 -0
- package/dist/tools/file.js +224 -0
- package/dist/tools/file.js.map +1 -0
- package/dist/tools/git.d.ts +3 -0
- package/dist/tools/git.d.ts.map +1 -0
- package/dist/tools/git.js +220 -0
- package/dist/tools/git.js.map +1 -0
- package/dist/tools/grep.d.ts +8 -0
- package/dist/tools/grep.d.ts.map +1 -0
- package/dist/tools/grep.js +265 -0
- package/dist/tools/grep.js.map +1 -0
- package/dist/tools/memory.d.ts +3 -0
- package/dist/tools/memory.d.ts.map +1 -0
- package/dist/tools/memory.js +33 -0
- package/dist/tools/memory.js.map +1 -0
- package/dist/tools/plan.d.ts +3 -0
- package/dist/tools/plan.d.ts.map +1 -0
- package/dist/tools/plan.js +60 -0
- package/dist/tools/plan.js.map +1 -0
- package/dist/tools/skill.d.ts +6 -0
- package/dist/tools/skill.d.ts.map +1 -0
- package/dist/tools/skill.js +20 -0
- package/dist/tools/skill.js.map +1 -0
- package/dist/tools/web.d.ts +3 -0
- package/dist/tools/web.d.ts.map +1 -0
- package/dist/tools/web.js +187 -0
- package/dist/tools/web.js.map +1 -0
- package/package.json +48 -0
package/dist/cli/repl.js
ADDED
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline';
|
|
2
|
+
import { loadConfig, saveConfig, configPath } from '../core/config.js';
|
|
3
|
+
import { searchRegistry, installSkill, removeSkill, formatInstalls } from '../core/registry.js';
|
|
4
|
+
import { C, renderMarkdown } from '../core/colors.js';
|
|
5
|
+
import { VERSION } from '../core/version.js';
|
|
6
|
+
function truncateCommand(command, maxLen = 80) {
|
|
7
|
+
const firstLine = command.split('\n')[0];
|
|
8
|
+
if (firstLine.length <= maxLen) {
|
|
9
|
+
return command.split('\n').length > 1 ? firstLine + ' ...' : firstLine;
|
|
10
|
+
}
|
|
11
|
+
return firstLine.slice(0, maxLen) + '...';
|
|
12
|
+
}
|
|
13
|
+
function formatToolSummary(toolName, args) {
|
|
14
|
+
switch (toolName) {
|
|
15
|
+
case 'bash': return `bash: ${truncateCommand(String(args.command || '(empty)'))}`;
|
|
16
|
+
case 'file': return `file(${args.action}): ${args.path || ''}`;
|
|
17
|
+
case 'git': return `git(${args.action})${args.message ? `: "${args.message}"` : ''}`;
|
|
18
|
+
case 'web': return `web(${args.action}): ${args.url || args.query || ''}`;
|
|
19
|
+
default: return `${toolName}(${JSON.stringify(args).slice(0, 80)})`;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function createConfirmCallback(sharedRl) {
|
|
23
|
+
const cb = async (toolName, args) => {
|
|
24
|
+
// Stop any active spinner before prompting
|
|
25
|
+
if (cb.pauseSpinner) {
|
|
26
|
+
cb.pauseSpinner();
|
|
27
|
+
cb.pauseSpinner = null;
|
|
28
|
+
}
|
|
29
|
+
const summary = formatToolSummary(toolName, args);
|
|
30
|
+
if (sharedRl) {
|
|
31
|
+
return new Promise(resolve => {
|
|
32
|
+
sharedRl.question(` ${C.yellow}?${C.reset} ${C.dim}${summary}${C.reset} ${C.yellow}(y/n)${C.reset} `, answer => {
|
|
33
|
+
resolve(answer.trim().toLowerCase() === 'n' ? 'deny' : 'allow');
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
// Fallback: create a temporary readline (non-REPL usage)
|
|
38
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
39
|
+
return new Promise(resolve => {
|
|
40
|
+
rl.question(` ${C.yellow}?${C.reset} ${C.dim}${summary}${C.reset} ${C.yellow}(y/n)${C.reset} `, answer => {
|
|
41
|
+
rl.close();
|
|
42
|
+
resolve(answer.trim().toLowerCase() === 'n' ? 'deny' : 'allow');
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
cb.pauseSpinner = null;
|
|
47
|
+
return cb;
|
|
48
|
+
}
|
|
49
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
50
|
+
function startSpinner(label) {
|
|
51
|
+
if (!process.stderr.isTTY)
|
|
52
|
+
return () => { };
|
|
53
|
+
let i = 0;
|
|
54
|
+
process.stderr.write(` ${C.dim}${SPINNER_FRAMES[0]} ${label}${C.reset}\n`);
|
|
55
|
+
const id = setInterval(() => {
|
|
56
|
+
process.stderr.write(`\x1b[A\x1b[K ${C.dim}${SPINNER_FRAMES[i++ % SPINNER_FRAMES.length]} ${label}${C.reset}\n`);
|
|
57
|
+
}, 80);
|
|
58
|
+
return () => { clearInterval(id); process.stderr.write('\x1b[A\x1b[K'); };
|
|
59
|
+
}
|
|
60
|
+
function maskKey(key) {
|
|
61
|
+
if (key.length <= 8)
|
|
62
|
+
return '****';
|
|
63
|
+
return key.slice(0, 8) + '****';
|
|
64
|
+
}
|
|
65
|
+
export class REPL {
|
|
66
|
+
agent;
|
|
67
|
+
rl;
|
|
68
|
+
confirmCb;
|
|
69
|
+
constructor(agent, confirmCb) {
|
|
70
|
+
this.agent = agent;
|
|
71
|
+
this.rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
72
|
+
// Rebind the confirm callback to use the shared readline
|
|
73
|
+
if (confirmCb) {
|
|
74
|
+
const shared = createConfirmCallback(this.rl);
|
|
75
|
+
shared.pauseSpinner = confirmCb.pauseSpinner;
|
|
76
|
+
this.confirmCb = shared;
|
|
77
|
+
this.agent.setConfirmToolCall(shared);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
this.confirmCb = null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async start() {
|
|
84
|
+
const stats = this.agent.getStats();
|
|
85
|
+
const skills = this.agent.getSkills();
|
|
86
|
+
console.error(`${C.bold}ag v${VERSION}${C.reset} ${C.dim}(${this.agent.getModel()} via OpenRouter)${C.reset}`);
|
|
87
|
+
const loaded = [
|
|
88
|
+
stats.globalMemory && 'global',
|
|
89
|
+
stats.projectMemory && 'project',
|
|
90
|
+
stats.planCount > 0 && `${stats.planCount} plan(s)`,
|
|
91
|
+
skills.length > 0 && `${skills.length} skill(s)`,
|
|
92
|
+
].filter(Boolean);
|
|
93
|
+
if (loaded.length > 0) {
|
|
94
|
+
console.error(`${C.dim}Loaded: ${loaded.join(', ')}${C.reset}`);
|
|
95
|
+
}
|
|
96
|
+
const activePlan = this.agent.getActivePlanName();
|
|
97
|
+
if (activePlan) {
|
|
98
|
+
const label = activePlan.replace(/^\d{4}-\d{2}-\d{2}T[\d-]+-?/, '').replace(/-/g, ' ').trim() || activePlan;
|
|
99
|
+
console.error(`${C.dim}Plan: ${label}${C.reset}`);
|
|
100
|
+
}
|
|
101
|
+
if (stats.historyLines > 0) {
|
|
102
|
+
console.error(`${C.dim}History: ${stats.historyLines} messages${C.reset}`);
|
|
103
|
+
}
|
|
104
|
+
// Resolve context window size
|
|
105
|
+
const tracker = this.agent.getContextTracker();
|
|
106
|
+
if (!tracker.getContextLength()) {
|
|
107
|
+
try {
|
|
108
|
+
const models = await this.agent.fetchModels(this.agent.getModel());
|
|
109
|
+
const exact = models.find(m => m.id === this.agent.getModel());
|
|
110
|
+
const match = exact ?? models[0];
|
|
111
|
+
if (match?.context_length)
|
|
112
|
+
tracker.setContextLength(match.context_length);
|
|
113
|
+
}
|
|
114
|
+
catch { /* proceed without — fallback handled by tracker */ }
|
|
115
|
+
}
|
|
116
|
+
tracker.estimateFromChars(this.agent.getSystemPromptSize());
|
|
117
|
+
const ctxLine = this.agent.getContextUsage();
|
|
118
|
+
console.error(`${C.dim}Commands: /help${C.reset}`);
|
|
119
|
+
if (ctxLine)
|
|
120
|
+
console.error(ctxLine);
|
|
121
|
+
console.error('');
|
|
122
|
+
while (true) {
|
|
123
|
+
try {
|
|
124
|
+
const input = await this.ask(`${C.green}you>${C.reset} `);
|
|
125
|
+
if (!input.trim())
|
|
126
|
+
continue;
|
|
127
|
+
if (input.startsWith('/')) {
|
|
128
|
+
await this.handleCommand(input);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
let hitMaxIterations = false;
|
|
132
|
+
const processStream = async (stream) => {
|
|
133
|
+
let hasText = false;
|
|
134
|
+
let hadTools = false;
|
|
135
|
+
let stopSpinnerFn = null;
|
|
136
|
+
let lineBuf = '';
|
|
137
|
+
// Keep confirm callback's pauseSpinner in sync with current spinner
|
|
138
|
+
const setSpinner = (fn) => {
|
|
139
|
+
stopSpinnerFn = fn;
|
|
140
|
+
if (this.confirmCb)
|
|
141
|
+
this.confirmCb.pauseSpinner = fn;
|
|
142
|
+
};
|
|
143
|
+
const clearSpinner = () => {
|
|
144
|
+
if (stopSpinnerFn) {
|
|
145
|
+
stopSpinnerFn();
|
|
146
|
+
setSpinner(null);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
// Render completed lines as markdown, write them out
|
|
150
|
+
const flushLines = (final) => {
|
|
151
|
+
const parts = lineBuf.split('\n');
|
|
152
|
+
// Keep the last partial line in the buffer (unless final flush)
|
|
153
|
+
lineBuf = final ? '' : (parts.pop() || '');
|
|
154
|
+
for (let i = 0; i < parts.length; i++) {
|
|
155
|
+
const rendered = renderMarkdown(parts[i]);
|
|
156
|
+
process.stderr.write(rendered + '\n');
|
|
157
|
+
}
|
|
158
|
+
if (final && lineBuf) {
|
|
159
|
+
process.stderr.write(renderMarkdown(lineBuf));
|
|
160
|
+
lineBuf = '';
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
for await (const chunk of stream) {
|
|
164
|
+
switch (chunk.type) {
|
|
165
|
+
case 'thinking':
|
|
166
|
+
clearSpinner();
|
|
167
|
+
if (hasText) {
|
|
168
|
+
flushLines(true);
|
|
169
|
+
process.stderr.write('\n');
|
|
170
|
+
hasText = false;
|
|
171
|
+
}
|
|
172
|
+
setSpinner(startSpinner(chunk.content || 'thinking'));
|
|
173
|
+
break;
|
|
174
|
+
case 'text':
|
|
175
|
+
clearSpinner();
|
|
176
|
+
if (!hasText) {
|
|
177
|
+
if (hadTools)
|
|
178
|
+
process.stderr.write('\n');
|
|
179
|
+
process.stderr.write(`${C.bold}agent>${C.reset} `);
|
|
180
|
+
hasText = true;
|
|
181
|
+
}
|
|
182
|
+
lineBuf += (chunk.content || '');
|
|
183
|
+
if (lineBuf.includes('\n'))
|
|
184
|
+
flushLines(false);
|
|
185
|
+
break;
|
|
186
|
+
case 'tool_start': {
|
|
187
|
+
clearSpinner();
|
|
188
|
+
if (hasText) {
|
|
189
|
+
flushLines(true);
|
|
190
|
+
process.stderr.write('\n');
|
|
191
|
+
hasText = false;
|
|
192
|
+
}
|
|
193
|
+
const cmdPreview = truncateCommand(chunk.content || '', 60);
|
|
194
|
+
setSpinner(startSpinner(`[${chunk.toolName}] ${cmdPreview}`));
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
case 'tool_end': {
|
|
198
|
+
clearSpinner();
|
|
199
|
+
hadTools = true;
|
|
200
|
+
// Resolve label at tool_end time — skills may have been activated during this batch
|
|
201
|
+
let endLabel = chunk.toolName || '';
|
|
202
|
+
const activeSkills = this.agent.getActiveSkillNames();
|
|
203
|
+
if (endLabel === 'bash' && activeSkills.length > 0) {
|
|
204
|
+
endLabel = `${endLabel} via ${activeSkills[activeSkills.length - 1]}`;
|
|
205
|
+
}
|
|
206
|
+
const icon = chunk.success ? `${C.green}✓` : `${C.red}✗`;
|
|
207
|
+
const preview = (chunk.content || '').slice(0, 150).split('\n')[0];
|
|
208
|
+
process.stderr.write(` ${icon} ${C.dim}[${endLabel}]${C.reset} ${C.dim}${preview}${(chunk.content || '').length > 150 ? '...' : ''}${C.reset}\n`);
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
case 'done':
|
|
212
|
+
clearSpinner();
|
|
213
|
+
if (hasText) {
|
|
214
|
+
flushLines(true);
|
|
215
|
+
process.stderr.write('\n\n');
|
|
216
|
+
}
|
|
217
|
+
else if (!hadTools)
|
|
218
|
+
process.stderr.write(`${C.bold}agent>${C.reset} ${renderMarkdown(chunk.content || '')}\n\n`);
|
|
219
|
+
break;
|
|
220
|
+
case 'max_iterations':
|
|
221
|
+
clearSpinner();
|
|
222
|
+
hitMaxIterations = true;
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
clearSpinner();
|
|
227
|
+
};
|
|
228
|
+
await processStream(this.agent.chatStream(input));
|
|
229
|
+
while (hitMaxIterations) {
|
|
230
|
+
console.error(`${C.yellow}Reached iteration limit.${C.reset}`);
|
|
231
|
+
const answer = await this.ask(`${C.yellow}Continue? (y/n)>${C.reset} `);
|
|
232
|
+
if (answer.trim().toLowerCase() !== 'y')
|
|
233
|
+
break;
|
|
234
|
+
hitMaxIterations = false;
|
|
235
|
+
await processStream(this.agent.chatStream('continue where you left off'));
|
|
236
|
+
}
|
|
237
|
+
const ctx = this.agent.getContextUsage();
|
|
238
|
+
if (ctx)
|
|
239
|
+
console.error(ctx);
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
243
|
+
console.error(`${C.red}Error: ${msg}${C.reset}\n`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
async handleCommand(command) {
|
|
248
|
+
const [cmd, ...args] = command.slice(1).trim().split(/\s+/);
|
|
249
|
+
const sub = cmd.toLowerCase();
|
|
250
|
+
switch (sub) {
|
|
251
|
+
case 'help':
|
|
252
|
+
console.error(`${C.bold}Commands:${C.reset}`);
|
|
253
|
+
console.error(` ${C.cyan}/help${C.reset} Show this help`);
|
|
254
|
+
console.error(` ${C.cyan}/model${C.reset} Show current model`);
|
|
255
|
+
console.error(` ${C.cyan}/model <name>${C.reset} Switch model (persists)`);
|
|
256
|
+
console.error(` ${C.cyan}/model search [query]${C.reset} Browse OpenRouter models`);
|
|
257
|
+
console.error(` ${C.cyan}/memory${C.reset} Show all memory + stats`);
|
|
258
|
+
console.error(` ${C.cyan}/memory global${C.reset} Show global memory`);
|
|
259
|
+
console.error(` ${C.cyan}/memory project${C.reset} Show project memory`);
|
|
260
|
+
console.error(` ${C.cyan}/memory clear <scope>${C.reset} Clear memory (project or all)`);
|
|
261
|
+
console.error(` ${C.cyan}/plan${C.reset} Show current plan`);
|
|
262
|
+
console.error(` ${C.cyan}/plan list${C.reset} List all plans`);
|
|
263
|
+
console.error(` ${C.cyan}/plan use <name>${C.reset} Activate an older plan`);
|
|
264
|
+
console.error(` ${C.cyan}/context${C.reset} Show context window usage`);
|
|
265
|
+
console.error(` ${C.cyan}/context compact${C.reset} Force context compaction now`);
|
|
266
|
+
console.error(` ${C.cyan}/config${C.reset} Show config + file paths`);
|
|
267
|
+
console.error(` ${C.cyan}/config set <k> <v>${C.reset} Set a config value`);
|
|
268
|
+
console.error(` ${C.cyan}/tools${C.reset} List loaded tools`);
|
|
269
|
+
console.error(` ${C.cyan}/skill${C.reset} List installed skills`);
|
|
270
|
+
console.error(` ${C.cyan}/skill search [query]${C.reset} Search skills.sh`);
|
|
271
|
+
console.error(` ${C.cyan}/skill add <source>${C.reset} Install skill from registry`);
|
|
272
|
+
console.error(` ${C.cyan}/skill remove <name>${C.reset} Uninstall a skill`);
|
|
273
|
+
console.error(` ${C.cyan}/exit${C.reset} Exit`);
|
|
274
|
+
console.error('');
|
|
275
|
+
break;
|
|
276
|
+
// ── /model ────────────────────────────────────────────────────────
|
|
277
|
+
case 'model': {
|
|
278
|
+
const subCmd = args[0]?.toLowerCase();
|
|
279
|
+
if (subCmd === 'search') {
|
|
280
|
+
const query = args.slice(1).join(' ');
|
|
281
|
+
console.error(`${C.dim}Fetching models from OpenRouter...${C.reset}`);
|
|
282
|
+
try {
|
|
283
|
+
const models = await this.agent.fetchModels(query || undefined);
|
|
284
|
+
if (models.length === 0) {
|
|
285
|
+
console.error(`${C.dim}No models found${query ? ` matching "${query}"` : ''}.${C.reset}\n`);
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
const current = this.agent.getModel();
|
|
289
|
+
const shown = models.slice(0, 30);
|
|
290
|
+
for (const m of shown) {
|
|
291
|
+
const marker = m.id === current ? C.green + '> ' : ' ';
|
|
292
|
+
const ctx = m.context_length ? `${C.dim}${Math.round(m.context_length / 1000)}k${C.reset}` : '';
|
|
293
|
+
const price = m.pricing?.prompt ? `${C.dim}$${(parseFloat(m.pricing.prompt) * 1_000_000).toFixed(2)}/M${C.reset}` : '';
|
|
294
|
+
console.error(`${marker}${C.cyan}${m.id}${C.reset} ${ctx} ${price}`);
|
|
295
|
+
}
|
|
296
|
+
if (models.length > 30)
|
|
297
|
+
console.error(`${C.dim} ...and ${models.length - 30} more. Filter with /model search <query>${C.reset}`);
|
|
298
|
+
console.error(`${C.dim} Use /model <id> to switch${C.reset}\n`);
|
|
299
|
+
}
|
|
300
|
+
catch (e) {
|
|
301
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
302
|
+
console.error(`${C.red}Error: ${msg}${C.reset}\n`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
else if (args[0]) {
|
|
306
|
+
this.agent.setModel(args.join('/'));
|
|
307
|
+
saveConfig({ model: this.agent.getModel() });
|
|
308
|
+
// Resolve context window for new model
|
|
309
|
+
const tracker = this.agent.getContextTracker();
|
|
310
|
+
if (!tracker.getContextLength()) {
|
|
311
|
+
try {
|
|
312
|
+
const models = await this.agent.fetchModels(this.agent.getModel());
|
|
313
|
+
const exact = models.find(m => m.id === this.agent.getModel());
|
|
314
|
+
const match = exact ?? models[0];
|
|
315
|
+
if (match?.context_length)
|
|
316
|
+
tracker.setContextLength(match.context_length);
|
|
317
|
+
}
|
|
318
|
+
catch { /* proceed without */ }
|
|
319
|
+
}
|
|
320
|
+
tracker.estimateFromChars(this.agent.getSystemPromptSize());
|
|
321
|
+
const ctxLine = this.agent.getContextUsage();
|
|
322
|
+
console.error(`${C.yellow}Model set to: ${this.agent.getModel()} (saved)${C.reset}`);
|
|
323
|
+
if (ctxLine)
|
|
324
|
+
console.error(ctxLine);
|
|
325
|
+
console.error('');
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
console.error(`${C.dim}Current model: ${this.agent.getModel()}${C.reset}\n`);
|
|
329
|
+
}
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
// ── /memory ───────────────────────────────────────────────────────
|
|
333
|
+
case 'memory': {
|
|
334
|
+
const subCmd = args[0]?.toLowerCase();
|
|
335
|
+
if (subCmd === 'global') {
|
|
336
|
+
const content = this.agent.getGlobalMemory();
|
|
337
|
+
console.error(content ? `${C.bold}Global memory:${C.reset}\n${renderMarkdown(content)}\n` : `${C.dim}No global memory. Edit ~/.ag/memory.md${C.reset}\n`);
|
|
338
|
+
}
|
|
339
|
+
else if (subCmd === 'project') {
|
|
340
|
+
const content = this.agent.getProjectMemory();
|
|
341
|
+
console.error(content ? `${C.bold}Project memory:${C.reset}\n${renderMarkdown(content)}\n` : `${C.dim}No project memory yet.${C.reset}\n`);
|
|
342
|
+
}
|
|
343
|
+
else if (subCmd === 'clear') {
|
|
344
|
+
const scope = args[1]?.toLowerCase();
|
|
345
|
+
if (scope === 'project') {
|
|
346
|
+
this.agent.clearProject();
|
|
347
|
+
console.error(`${C.yellow}Project memory, plans, and history cleared.${C.reset}\n`);
|
|
348
|
+
}
|
|
349
|
+
else if (scope === 'all') {
|
|
350
|
+
this.agent.clearAll();
|
|
351
|
+
console.error(`${C.yellow}All memory cleared.${C.reset}\n`);
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
console.error(`${C.dim}Usage: /memory clear project or /memory clear all${C.reset}\n`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
// Show all memory + stats
|
|
359
|
+
const stats = this.agent.getStats();
|
|
360
|
+
const global = this.agent.getGlobalMemory();
|
|
361
|
+
const project = this.agent.getProjectMemory();
|
|
362
|
+
console.error(`${C.bold}Memory${C.reset}`);
|
|
363
|
+
console.error(` Global: ${stats.globalMemory ? C.green + 'yes' : C.dim + 'none'}${C.reset}`);
|
|
364
|
+
console.error(` Project: ${stats.projectMemory ? C.green + 'yes' : C.dim + 'none'}${C.reset}`);
|
|
365
|
+
console.error(` Plans: ${C.cyan}${stats.planCount}${C.reset}`);
|
|
366
|
+
console.error(` History: ${C.cyan}${stats.historyLines}${C.reset} messages`);
|
|
367
|
+
if (global)
|
|
368
|
+
console.error(`\n${C.bold}Global:${C.reset}\n${renderMarkdown(global)}`);
|
|
369
|
+
if (project)
|
|
370
|
+
console.error(`\n${C.bold}Project:${C.reset}\n${renderMarkdown(project)}`);
|
|
371
|
+
console.error('');
|
|
372
|
+
}
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
// ── /plan ─────────────────────────────────────────────────────────
|
|
376
|
+
case 'plan': {
|
|
377
|
+
const subCmd = args[0]?.toLowerCase();
|
|
378
|
+
if (subCmd === 'list') {
|
|
379
|
+
const plans = this.agent.getPlans();
|
|
380
|
+
if (plans.length === 0) {
|
|
381
|
+
console.error(`${C.dim}No plans yet.${C.reset}\n`);
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
const activeName = this.agent.getActivePlanName();
|
|
385
|
+
console.error(`${C.bold}Plans (${plans.length}):${C.reset}`);
|
|
386
|
+
plans.forEach(p => console.error(` ${p.name === activeName ? C.green + '>' : ' '} ${p.name} ${C.dim}${p.path}${C.reset}`));
|
|
387
|
+
console.error('');
|
|
388
|
+
}
|
|
389
|
+
else if (subCmd === 'use' && args[1]) {
|
|
390
|
+
const name = args.slice(1).join(' ');
|
|
391
|
+
const plans = this.agent.getPlans();
|
|
392
|
+
const match = plans.find(p => p.name.includes(name));
|
|
393
|
+
if (!match) {
|
|
394
|
+
console.error(`${C.red}No plan matching "${name}". Use /plan list${C.reset}\n`);
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
this.agent.activatePlan(match.name);
|
|
398
|
+
console.error(`${C.yellow}Activated plan: ${match.name}${C.reset}\n`);
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
const content = this.agent.getPlan();
|
|
402
|
+
console.error(content ? `${C.bold}Current plan:${C.reset}\n${renderMarkdown(content)}\n` : `${C.dim}No plans yet.${C.reset}\n`);
|
|
403
|
+
}
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
// ── /context ──────────────────────────────────────────────────────
|
|
407
|
+
case 'context': {
|
|
408
|
+
if (args[0]?.toLowerCase() === 'compact') {
|
|
409
|
+
const tracker = this.agent.getContextTracker();
|
|
410
|
+
if (!tracker.getContextLength()) {
|
|
411
|
+
console.error(`${C.dim}Cannot compact: context window size unknown.${C.reset}\n`);
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
414
|
+
console.error(`${C.dim}Compacting conversation...${C.reset}`);
|
|
415
|
+
try {
|
|
416
|
+
await this.agent.compactNow();
|
|
417
|
+
console.error(`${C.green}Compaction complete.${C.reset}`);
|
|
418
|
+
const ctx = this.agent.getContextUsage();
|
|
419
|
+
if (ctx)
|
|
420
|
+
console.error(ctx);
|
|
421
|
+
}
|
|
422
|
+
catch (e) {
|
|
423
|
+
console.error(`${C.red}Compaction failed: ${e.message}${C.reset}`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
console.error(`${C.bold}Context Window${C.reset}`);
|
|
428
|
+
console.error(this.agent.getContextDetails());
|
|
429
|
+
}
|
|
430
|
+
console.error('');
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
433
|
+
// ── /config ───────────────────────────────────────────────────────
|
|
434
|
+
case 'config': {
|
|
435
|
+
if (args[0]?.toLowerCase() === 'set' && args[1]) {
|
|
436
|
+
const value = args.slice(2).join(' ');
|
|
437
|
+
if (!value) {
|
|
438
|
+
console.error(`${C.red}Usage: /config set <key> <value>${C.reset}\n`);
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
const validKeys = ['apiKey', 'model', 'baseURL', 'systemPrompt', 'maxIterations', 'tavilyApiKey', 'autoApprove'];
|
|
442
|
+
const keyAliases = {
|
|
443
|
+
'tavily_api_key': 'tavilyApiKey',
|
|
444
|
+
'openrouter_api_key': 'apiKey',
|
|
445
|
+
'api_key': 'apiKey',
|
|
446
|
+
'auto_approve': 'autoApprove',
|
|
447
|
+
'autoapprove': 'autoApprove',
|
|
448
|
+
};
|
|
449
|
+
const rawKey = args[1];
|
|
450
|
+
const key = keyAliases[rawKey.toLowerCase()] ?? rawKey;
|
|
451
|
+
if (!validKeys.includes(key)) {
|
|
452
|
+
console.error(`${C.red}Valid keys: ${validKeys.join(', ')}${C.reset}\n`);
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
455
|
+
let parsed = value;
|
|
456
|
+
if (key === 'maxIterations') {
|
|
457
|
+
parsed = parseInt(value, 10);
|
|
458
|
+
}
|
|
459
|
+
else if (key === 'autoApprove') {
|
|
460
|
+
parsed = ['true', '1', 'yes'].includes(value.toLowerCase());
|
|
461
|
+
// Apply immediately: toggle confirmation prompts in this session
|
|
462
|
+
if (parsed) {
|
|
463
|
+
this.agent.setConfirmToolCall(null);
|
|
464
|
+
console.error(`${C.green}Auto-approve enabled — tool calls will no longer prompt.${C.reset}`);
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
const freshCb = createConfirmCallback(this.rl);
|
|
468
|
+
if (this.confirmCb)
|
|
469
|
+
freshCb.pauseSpinner = this.confirmCb.pauseSpinner;
|
|
470
|
+
this.agent.setConfirmToolCall(freshCb);
|
|
471
|
+
console.error(`${C.yellow}Auto-approve disabled — tool calls will prompt again.${C.reset}`);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
saveConfig({ [key]: parsed });
|
|
475
|
+
const display = (key === 'apiKey' || key === 'tavilyApiKey') ? maskKey(value) : value;
|
|
476
|
+
console.error(`${C.yellow}Config: ${key} = ${display} (saved)${C.reset}\n`);
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
// Show config + file paths
|
|
480
|
+
const cfg = loadConfig();
|
|
481
|
+
const p = this.agent.getPaths();
|
|
482
|
+
console.error(`${C.bold}Config${C.reset} ${C.dim}(${configPath()})${C.reset}`);
|
|
483
|
+
if (cfg.apiKey)
|
|
484
|
+
console.error(` apiKey: ${C.dim}${maskKey(cfg.apiKey)}${C.reset}`);
|
|
485
|
+
if (cfg.model)
|
|
486
|
+
console.error(` model: ${C.cyan}${cfg.model}${C.reset}`);
|
|
487
|
+
if (cfg.baseURL)
|
|
488
|
+
console.error(` baseURL: ${C.dim}${cfg.baseURL}${C.reset}`);
|
|
489
|
+
if (cfg.maxIterations)
|
|
490
|
+
console.error(` maxIterations: ${C.cyan}${cfg.maxIterations}${C.reset}`);
|
|
491
|
+
if (cfg.tavilyApiKey)
|
|
492
|
+
console.error(` tavilyApiKey: ${C.dim}${maskKey(cfg.tavilyApiKey)}${C.reset}`);
|
|
493
|
+
if (cfg.autoApprove !== undefined)
|
|
494
|
+
console.error(` autoApprove: ${cfg.autoApprove ? C.green + 'true' : C.dim + 'false'}${C.reset}`);
|
|
495
|
+
if (!cfg.apiKey && !cfg.model && !cfg.baseURL && !cfg.maxIterations) {
|
|
496
|
+
console.error(`${C.dim} (using defaults + env vars)${C.reset}`);
|
|
497
|
+
}
|
|
498
|
+
console.error(`\n${C.bold}Paths${C.reset}`);
|
|
499
|
+
console.error(` Config: ${configPath()}`);
|
|
500
|
+
console.error(` Global: ${p.globalMemory}`);
|
|
501
|
+
console.error(` Project: ${p.projectMemory}`);
|
|
502
|
+
console.error(` Plans: ${p.plansDir}/`);
|
|
503
|
+
console.error(` History: ${p.history}`);
|
|
504
|
+
console.error('');
|
|
505
|
+
}
|
|
506
|
+
break;
|
|
507
|
+
}
|
|
508
|
+
// ── /tools ────────────────────────────────────────────────────────
|
|
509
|
+
case 'tools': {
|
|
510
|
+
const tools = this.agent.getTools();
|
|
511
|
+
console.error(`${C.bold}Tools (${tools.length}):${C.reset}`);
|
|
512
|
+
for (const t of tools) {
|
|
513
|
+
console.error(` ${C.cyan}${t.name}${C.reset} ${C.dim}${t.description.slice(0, 60)}${t.description.length > 60 ? '...' : ''}${C.reset}`);
|
|
514
|
+
}
|
|
515
|
+
console.error('');
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
518
|
+
// ── /skill ────────────────────────────────────────────────────────
|
|
519
|
+
case 'skill': {
|
|
520
|
+
const subCmd = args[0]?.toLowerCase();
|
|
521
|
+
if (subCmd === 'search') {
|
|
522
|
+
const query = args.slice(1).join(' ');
|
|
523
|
+
if (!query) {
|
|
524
|
+
console.error(`${C.dim}Usage: /skill search <query>${C.reset}\n`);
|
|
525
|
+
break;
|
|
526
|
+
}
|
|
527
|
+
console.error(`${C.dim}Searching skills.sh...${C.reset}`);
|
|
528
|
+
try {
|
|
529
|
+
const results = await searchRegistry(query);
|
|
530
|
+
if (results.length === 0) {
|
|
531
|
+
console.error(`${C.dim}No skills found for "${query}".${C.reset}\n`);
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
534
|
+
const shown = results.slice(0, 20);
|
|
535
|
+
for (const s of shown) {
|
|
536
|
+
console.error(` ${C.cyan}${s.source}@${s.skillId}${C.reset} ${C.dim}${formatInstalls(s.installs)} installs${C.reset}`);
|
|
537
|
+
}
|
|
538
|
+
if (results.length > 20)
|
|
539
|
+
console.error(`${C.dim} ...and ${results.length - 20} more${C.reset}`);
|
|
540
|
+
console.error(`${C.dim} Use /skill add <source>@<name> to install${C.reset}\n`);
|
|
541
|
+
}
|
|
542
|
+
catch (e) {
|
|
543
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
544
|
+
console.error(`${C.red}Error: ${msg}${C.reset}\n`);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
else if (subCmd === 'add' && args[1]) {
|
|
548
|
+
const source = args[1];
|
|
549
|
+
const stopSpinner = startSpinner(`Installing ${source}`);
|
|
550
|
+
try {
|
|
551
|
+
const msg = await installSkill(source);
|
|
552
|
+
stopSpinner();
|
|
553
|
+
this.agent.refreshSkills();
|
|
554
|
+
console.error(`${C.green}${msg}${C.reset}\n`);
|
|
555
|
+
}
|
|
556
|
+
catch (e) {
|
|
557
|
+
stopSpinner();
|
|
558
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
559
|
+
console.error(`${C.red}Error: ${msg}${C.reset}\n`);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
else if (subCmd === 'remove' && args[1]) {
|
|
563
|
+
const msg = removeSkill(args[1]);
|
|
564
|
+
this.agent.refreshSkills();
|
|
565
|
+
console.error(`${C.yellow}${msg}${C.reset}\n`);
|
|
566
|
+
}
|
|
567
|
+
else if (!subCmd) {
|
|
568
|
+
// List installed skills
|
|
569
|
+
const skills = this.agent.getSkills();
|
|
570
|
+
if (skills.length === 0) {
|
|
571
|
+
console.error(`${C.dim}No skills installed. Use /skill search <query> to browse skills.sh${C.reset}\n`);
|
|
572
|
+
break;
|
|
573
|
+
}
|
|
574
|
+
console.error(`${C.bold}Skills (${skills.length}):${C.reset}`);
|
|
575
|
+
for (const s of skills) {
|
|
576
|
+
const flags = [s.always && 'always-on', s.hasTools && 'has tools'].filter(Boolean).join(', ');
|
|
577
|
+
console.error(` ${C.cyan}${s.name}${C.reset} ${C.dim}${s.description.slice(0, 60)}${flags ? ` (${flags})` : ''}${C.reset}`);
|
|
578
|
+
}
|
|
579
|
+
console.error('');
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
console.error(`${C.dim}Usage: /skill, /skill search <q>, /skill add <source>, /skill remove <name>${C.reset}\n`);
|
|
583
|
+
}
|
|
584
|
+
break;
|
|
585
|
+
}
|
|
586
|
+
// ── /exit ─────────────────────────────────────────────────────────
|
|
587
|
+
case 'exit':
|
|
588
|
+
case 'quit':
|
|
589
|
+
console.error(`${C.dim}Goodbye!${C.reset}`);
|
|
590
|
+
process.exit(0);
|
|
591
|
+
default:
|
|
592
|
+
console.error(`${C.red}Unknown command: ${sub}. Type /help${C.reset}\n`);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
ask(prompt) {
|
|
596
|
+
return new Promise(resolve => this.rl.question(prompt, resolve));
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
//# sourceMappingURL=repl.js.map
|