agent-mp 0.4.15 → 0.5.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/commands/repl.js +79 -20
- package/dist/index.js +1 -1
- package/dist/utils/config.d.ts +7 -0
- package/dist/utils/config.js +50 -0
- package/dist/utils/qwen-auth.js +23 -25
- package/package.json +1 -1
- package/dist/api/qwen.d.ts +0 -12
- package/dist/api/qwen.js +0 -150
- package/dist/commands/auth.d.ts +0 -31
- package/dist/commands/auth.js +0 -255
- package/dist/commands/config.d.ts +0 -2
- package/dist/commands/config.js +0 -42
- package/dist/core/prompts.d.ts +0 -11
- package/dist/core/prompts.js +0 -126
- package/dist/providers/index.d.ts +0 -2
- package/dist/providers/index.js +0 -120
package/dist/commands/repl.js
CHANGED
|
@@ -7,7 +7,7 @@ import chalk from 'chalk';
|
|
|
7
7
|
import { execSync } from 'child_process';
|
|
8
8
|
import { CLI_REGISTRY } from '../types.js';
|
|
9
9
|
import { writeJson, ensureDir, readJson, listDir, fileExists } from '../utils/fs.js';
|
|
10
|
-
import { loadAuth, saveAuth, loadCliConfig, saveCliConfig, loadProjectConfig } from '../utils/config.js';
|
|
10
|
+
import { loadAuth, saveAuth, loadCliConfig, saveCliConfig, loadProjectConfig, resolveProjectDir } from '../utils/config.js';
|
|
11
11
|
import { log } from '../utils/logger.js';
|
|
12
12
|
import { AgentEngine, ExitError } from '../core/engine.js';
|
|
13
13
|
import { qwenLogin, qwenAuthStatus, QWEN_AGENT_HOME, fetchQwenModels } from '../utils/qwen-auth.js';
|
|
@@ -594,7 +594,7 @@ async function cmdStatus(fi) {
|
|
|
594
594
|
roleRows.push({ key: 'global fallback', value: `${cliConfig.fallback_global.cli}/${cliConfig.fallback_global.model}` });
|
|
595
595
|
}
|
|
596
596
|
sections.push({ title: 'Roles — full cmd', rows: roleRows });
|
|
597
|
-
const dir = process.cwd();
|
|
597
|
+
const dir = await resolveProjectDir(process.cwd());
|
|
598
598
|
try {
|
|
599
599
|
const config = await loadProjectConfig(dir);
|
|
600
600
|
const projectRows = [
|
|
@@ -711,7 +711,7 @@ function cmdHelp(fi) {
|
|
|
711
711
|
fi.println(renderSectionBox('Commands', commands));
|
|
712
712
|
}
|
|
713
713
|
async function cmdTasks(fi) {
|
|
714
|
-
const dir = process.cwd();
|
|
714
|
+
const dir = await resolveProjectDir(process.cwd());
|
|
715
715
|
const tasksDir = path.join(dir, '.agent', 'tasks');
|
|
716
716
|
const tasks = await listDir(tasksDir);
|
|
717
717
|
if (tasks.length === 0) {
|
|
@@ -858,13 +858,15 @@ export async function runRepl(resumeSession) {
|
|
|
858
858
|
const { activeCli } = init;
|
|
859
859
|
const rl = init.rl;
|
|
860
860
|
const session = resumeSession ?? newSession(process.cwd());
|
|
861
|
+
// Resolve actual project root (may be deeper than cwd)
|
|
862
|
+
const projectDir = await resolveProjectDir(process.cwd());
|
|
861
863
|
// Show project status and FORCE role configuration if missing
|
|
862
864
|
let hasRoles = false;
|
|
863
865
|
let welcomeRoles;
|
|
864
|
-
let welcomeProject = path.basename(
|
|
866
|
+
let welcomeProject = path.basename(projectDir);
|
|
865
867
|
let welcomeStack = 'generic';
|
|
866
868
|
try {
|
|
867
|
-
const config = await loadProjectConfig(
|
|
869
|
+
const config = await loadProjectConfig(projectDir);
|
|
868
870
|
welcomeProject = config.project;
|
|
869
871
|
welcomeStack = config.stack;
|
|
870
872
|
if (config.roles?.orchestrator?.cli && config.roles?.implementor?.cli && config.roles?.reviewer?.cli) {
|
|
@@ -915,8 +917,7 @@ export async function runRepl(resumeSession) {
|
|
|
915
917
|
process.stdout.write(renderHelpHint() + '\n\n');
|
|
916
918
|
// Print coordinator header BEFORE fi.setup() so no blank lines appear between them
|
|
917
919
|
try {
|
|
918
|
-
const
|
|
919
|
-
const config = await loadProjectConfig(dir);
|
|
920
|
+
const config = await loadProjectConfig(projectDir);
|
|
920
921
|
if (config.roles?.orchestrator?.cli) {
|
|
921
922
|
process.stdout.write('\n');
|
|
922
923
|
process.stdout.write(chalk.bold.cyan('═'.repeat(46)) + '\n');
|
|
@@ -990,7 +991,7 @@ export async function runRepl(resumeSession) {
|
|
|
990
991
|
await cmdStatus(fi);
|
|
991
992
|
break;
|
|
992
993
|
case 'run': {
|
|
993
|
-
const dir = process.cwd();
|
|
994
|
+
const dir = await resolveProjectDir(process.cwd());
|
|
994
995
|
const config = await loadProjectConfig(dir);
|
|
995
996
|
if (args[0] === 'orch' || args[0] === 'orchestrator') {
|
|
996
997
|
const task = args.slice(1).join(' ');
|
|
@@ -1027,7 +1028,7 @@ export async function runRepl(resumeSession) {
|
|
|
1027
1028
|
}
|
|
1028
1029
|
case 'explorer':
|
|
1029
1030
|
case 'exp': {
|
|
1030
|
-
const dir = process.cwd();
|
|
1031
|
+
const dir = await resolveProjectDir(process.cwd());
|
|
1031
1032
|
const config = await loadProjectConfig(dir);
|
|
1032
1033
|
const task = args.join(' ');
|
|
1033
1034
|
const engine = new AgentEngine(config, dir, gCoordinatorCmd, rl, fi, handleCmd);
|
|
@@ -1099,7 +1100,7 @@ export async function runRepl(resumeSession) {
|
|
|
1099
1100
|
session.messages.push({ role: 'user', content: trimmed, ts: new Date().toISOString() });
|
|
1100
1101
|
await saveSession(session);
|
|
1101
1102
|
try {
|
|
1102
|
-
const dir = process.cwd();
|
|
1103
|
+
const dir = await resolveProjectDir(process.cwd());
|
|
1103
1104
|
const config = await loadProjectConfig(dir);
|
|
1104
1105
|
if (!config.roles?.orchestrator?.cli) {
|
|
1105
1106
|
fi.println(chalk.red(' No roles configured. Run /config-multi first.'));
|
|
@@ -1136,25 +1137,24 @@ export async function runRepl(resumeSession) {
|
|
|
1136
1137
|
* Prompt estricto: cada rol solo hace lo que debe, sin extras.
|
|
1137
1138
|
*/
|
|
1138
1139
|
export async function runRole(role, arg, model) {
|
|
1139
|
-
const
|
|
1140
|
-
if (
|
|
1141
|
-
|
|
1142
|
-
const
|
|
1143
|
-
const
|
|
1144
|
-
|
|
1140
|
+
const { PKG_NAME, getConfigDir } = await import('../utils/config.js');
|
|
1141
|
+
// Detect if running a native role binary directly (agent-orch, agent-impl, etc.)
|
|
1142
|
+
// In that case, skip REPL and run headless — no coordinator needed
|
|
1143
|
+
const ROLE_BINS = new Set(['agent-orch', 'agent-impl', 'agent-rev', 'agent-explorer']);
|
|
1144
|
+
const isNativeBin = ROLE_BINS.has(PKG_NAME);
|
|
1145
|
+
// Resolve the actual project root (may be deeper than cwd)
|
|
1146
|
+
const dir = await resolveProjectDir(process.cwd());
|
|
1145
1147
|
let config;
|
|
1146
1148
|
try {
|
|
1147
1149
|
config = await loadProjectConfig(dir);
|
|
1148
1150
|
}
|
|
1149
1151
|
catch {
|
|
1150
1152
|
console.log(chalk.red(' No project config found. Run /setup first.'));
|
|
1151
|
-
rl.close();
|
|
1152
1153
|
process.exit(1);
|
|
1153
1154
|
}
|
|
1154
1155
|
// Validate roles are configured
|
|
1155
1156
|
if (!config.roles?.orchestrator?.cli || !config.roles?.implementor?.cli || !config.roles?.reviewer?.cli) {
|
|
1156
1157
|
console.log(chalk.red(' Roles not configured. Run setup first.'));
|
|
1157
|
-
rl.close();
|
|
1158
1158
|
process.exit(1);
|
|
1159
1159
|
}
|
|
1160
1160
|
// Override model if passed via --model flag
|
|
@@ -1170,7 +1170,60 @@ export async function runRole(role, arg, model) {
|
|
|
1170
1170
|
r.cmd = r.cmd.replace(/(-m|--model)\s+\S+/, `$1 ${model}`);
|
|
1171
1171
|
}
|
|
1172
1172
|
}
|
|
1173
|
-
console.log(chalk.bold.cyan(`\n
|
|
1173
|
+
console.log(chalk.bold.cyan(`\n ${PKG_NAME} — Rol: ${role.toUpperCase()}${model ? ` (${model})` : ''}\n`));
|
|
1174
|
+
if (isNativeBin) {
|
|
1175
|
+
// Headless mode: create a dummy RL (no stdin), run engine directly
|
|
1176
|
+
const { Readable } = await import('stream');
|
|
1177
|
+
const emptyStream = new Readable({ read() { this.push(null); } });
|
|
1178
|
+
const dummyRl = readline.createInterface({ input: emptyStream, output: process.stdout });
|
|
1179
|
+
const engine = new AgentEngine(config, dir, '', dummyRl);
|
|
1180
|
+
try {
|
|
1181
|
+
switch (role.toLowerCase()) {
|
|
1182
|
+
case 'orchestrator':
|
|
1183
|
+
case 'orch': {
|
|
1184
|
+
const result = await engine.runOrchestrator(arg);
|
|
1185
|
+
log.ok(`Task ID: ${result.taskId}`);
|
|
1186
|
+
break;
|
|
1187
|
+
}
|
|
1188
|
+
case 'implementor':
|
|
1189
|
+
case 'impl': {
|
|
1190
|
+
const taskDir = path.join(dir, '.agent', 'tasks', arg);
|
|
1191
|
+
const plan = await readJson(path.join(taskDir, 'plan.json'));
|
|
1192
|
+
await engine.runImplementor(arg, plan);
|
|
1193
|
+
break;
|
|
1194
|
+
}
|
|
1195
|
+
case 'reviewer':
|
|
1196
|
+
case 'rev': {
|
|
1197
|
+
const taskDir = path.join(dir, '.agent', 'tasks', arg);
|
|
1198
|
+
const plan = await readJson(path.join(taskDir, 'plan.json'));
|
|
1199
|
+
const progress = await readJson(path.join(taskDir, 'progress.json'));
|
|
1200
|
+
await engine.runReviewer(arg, plan, progress);
|
|
1201
|
+
break;
|
|
1202
|
+
}
|
|
1203
|
+
case 'explorer':
|
|
1204
|
+
case 'exp': {
|
|
1205
|
+
const result = await engine.runExplorer(arg);
|
|
1206
|
+
log.ok(`Explorer: ${result.substring(0, 100)}...`);
|
|
1207
|
+
break;
|
|
1208
|
+
}
|
|
1209
|
+
default:
|
|
1210
|
+
console.log(chalk.red(` Unknown role: ${role}. Use: orchestrator, implementor, reviewer, explorer`));
|
|
1211
|
+
process.exit(1);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
catch (err) {
|
|
1215
|
+
console.log(chalk.red(` Error: ${err.message}`));
|
|
1216
|
+
process.exit(1);
|
|
1217
|
+
}
|
|
1218
|
+
dummyRl.close();
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
// REPL mode: needs coordinator
|
|
1222
|
+
const init = await initCoordinator();
|
|
1223
|
+
if (!init)
|
|
1224
|
+
process.exit(1);
|
|
1225
|
+
const { coordinatorCmd } = init;
|
|
1226
|
+
const rl = init.rl;
|
|
1174
1227
|
const engine = new AgentEngine(config, dir, coordinatorCmd, rl);
|
|
1175
1228
|
try {
|
|
1176
1229
|
switch (role.toLowerCase()) {
|
|
@@ -1195,6 +1248,12 @@ export async function runRole(role, arg, model) {
|
|
|
1195
1248
|
await engine.runReviewer(arg, plan, progress);
|
|
1196
1249
|
break;
|
|
1197
1250
|
}
|
|
1251
|
+
case 'explorer':
|
|
1252
|
+
case 'exp': {
|
|
1253
|
+
const result = await engine.runExplorer(arg);
|
|
1254
|
+
log.ok(`Explorer: ${result.substring(0, 100)}...`);
|
|
1255
|
+
break;
|
|
1256
|
+
}
|
|
1198
1257
|
case 'coordinator':
|
|
1199
1258
|
case 'coord': {
|
|
1200
1259
|
console.log(chalk.yellow(' Coordinator mode requires interactive REPL.'));
|
|
@@ -1202,7 +1261,7 @@ export async function runRole(role, arg, model) {
|
|
|
1202
1261
|
process.exit(1);
|
|
1203
1262
|
}
|
|
1204
1263
|
default:
|
|
1205
|
-
console.log(chalk.red(` Unknown role: ${role}. Use: orchestrator, implementor, reviewer`));
|
|
1264
|
+
console.log(chalk.red(` Unknown role: ${role}. Use: orchestrator, implementor, reviewer, explorer`));
|
|
1206
1265
|
process.exit(1);
|
|
1207
1266
|
}
|
|
1208
1267
|
}
|
package/dist/index.js
CHANGED
|
@@ -137,7 +137,7 @@ if (nativeRole) {
|
|
|
137
137
|
if (modelIdx !== -1 && args[modelIdx + 1]) {
|
|
138
138
|
model = args[modelIdx + 1];
|
|
139
139
|
}
|
|
140
|
-
const taskArg = args.filter((a, i) => !a.startsWith('-') && i !== modelIdx + 1).join(' ').trim();
|
|
140
|
+
const taskArg = args.filter((a, i) => !a.startsWith('-') && (modelIdx === -1 || i !== modelIdx + 1)).join(' ').trim();
|
|
141
141
|
if (!taskArg) {
|
|
142
142
|
console.log(chalk.bold.cyan(`\n ${PKG_NAME} — ${nativeRole} agent\n`));
|
|
143
143
|
console.log(chalk.dim(` Usage: ${PKG_NAME} [--model <model>] "<task>"`));
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { AgentConfig } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve the actual project root starting from a given directory.
|
|
4
|
+
* If .agent/config.json exists in the start dir, scan subdirectories
|
|
5
|
+
* for a deeper project root (indicated by common project files).
|
|
6
|
+
* Returns the deepest valid project root found, or the original dir.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveProjectDir(startDir: string): Promise<string>;
|
|
2
9
|
export declare const PKG_NAME: string;
|
|
3
10
|
export declare const AGENT_HOME: string;
|
|
4
11
|
export declare const AUTH_FILE: string;
|
package/dist/utils/config.js
CHANGED
|
@@ -3,6 +3,56 @@ import * as fsSync from 'fs';
|
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import * as os from 'os';
|
|
5
5
|
const KNOWN_PKGS = ['agent-mp', 'agent-orch', 'agent-impl', 'agent-rev', 'agent-explorer'];
|
|
6
|
+
// Files/dirs that indicate a directory is the real project root
|
|
7
|
+
const PROJECT_INDICATORS = [
|
|
8
|
+
'package.json', 'pyproject.toml', 'requirements.txt', 'setup.py', 'setup.cfg',
|
|
9
|
+
'Cargo.toml', 'go.mod', 'Gemfile', 'pom.xml', 'build.gradle', 'build.gradle.kts',
|
|
10
|
+
'CMakeLists.txt', 'Makefile', 'composer.json', 'mix.exs', 'pubspec.yaml',
|
|
11
|
+
'src', 'app', 'lib', 'main.py', 'app.py', 'index.js', 'index.ts',
|
|
12
|
+
'main.go', 'main.rs', 'lib.rs', 'app.js', 'app.ts',
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* Resolve the actual project root starting from a given directory.
|
|
16
|
+
* If .agent/config.json exists in the start dir, scan subdirectories
|
|
17
|
+
* for a deeper project root (indicated by common project files).
|
|
18
|
+
* Returns the deepest valid project root found, or the original dir.
|
|
19
|
+
*/
|
|
20
|
+
export async function resolveProjectDir(startDir) {
|
|
21
|
+
const hasAgentConfig = await fileExists(path.join(startDir, '.agent', 'config.json'));
|
|
22
|
+
if (!hasAgentConfig)
|
|
23
|
+
return startDir;
|
|
24
|
+
// Scan immediate subdirectories for a deeper project root
|
|
25
|
+
let bestDir = startDir;
|
|
26
|
+
let bestScore = 0;
|
|
27
|
+
try {
|
|
28
|
+
const entries = await fs.readdir(startDir, { withFileTypes: true });
|
|
29
|
+
for (const entry of entries) {
|
|
30
|
+
if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'node_modules')
|
|
31
|
+
continue;
|
|
32
|
+
const subDir = path.join(startDir, entry.name);
|
|
33
|
+
let score = 0;
|
|
34
|
+
for (const indicator of PROJECT_INDICATORS) {
|
|
35
|
+
if (await fileExists(path.join(subDir, indicator)))
|
|
36
|
+
score++;
|
|
37
|
+
}
|
|
38
|
+
if (score > bestScore) {
|
|
39
|
+
bestScore = score;
|
|
40
|
+
bestDir = subDir;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch { /* ignore */ }
|
|
45
|
+
return bestDir;
|
|
46
|
+
}
|
|
47
|
+
async function fileExists(p) {
|
|
48
|
+
try {
|
|
49
|
+
await fs.stat(p);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
6
56
|
function resolvePackageName() {
|
|
7
57
|
// 1. Read package.json (reliable for both global installs and dev mode)
|
|
8
58
|
const __filename = new URL(import.meta.url).pathname;
|
package/dist/utils/qwen-auth.js
CHANGED
|
@@ -42,9 +42,10 @@ async function loadToken() {
|
|
|
42
42
|
};
|
|
43
43
|
if (!token.accessToken)
|
|
44
44
|
return null;
|
|
45
|
-
// Refresh proactivo: si vence en menos de
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
// Refresh proactivo: si vence en menos de 5 minutos (o ya venció)
|
|
46
|
+
// Mismo threshold que el CLI original de qwen
|
|
47
|
+
const FIVE_MIN = 5 * 60 * 1000;
|
|
48
|
+
if (token.expiresAt - Date.now() < FIVE_MIN && token.refreshToken) {
|
|
48
49
|
const refreshed = await doRefreshToken(token.refreshToken);
|
|
49
50
|
if (refreshed) {
|
|
50
51
|
await saveToken(refreshed);
|
|
@@ -253,45 +254,41 @@ export async function getQwenAccessToken() {
|
|
|
253
254
|
return token?.accessToken || null;
|
|
254
255
|
}
|
|
255
256
|
async function callQwenAPIWithToken(token, prompt, model, onData) {
|
|
256
|
-
//
|
|
257
|
+
// Build base URL from token's resource_url (same logic as qwen CLI original)
|
|
257
258
|
const rawHost = token.resourceUrl || 'dashscope.aliyuncs.com/compatible-mode';
|
|
258
|
-
const
|
|
259
|
-
|
|
259
|
+
const baseUrl = rawHost.startsWith('http')
|
|
260
|
+
? (rawHost.endsWith('/v1') ? rawHost : rawHost.replace(/\/$/, '') + '/v1')
|
|
261
|
+
: `https://${rawHost}/v1`;
|
|
260
262
|
const useStream = !!onData;
|
|
261
|
-
|
|
262
|
-
//
|
|
263
|
-
const
|
|
263
|
+
// portal.qwen.ai requires: content as array of {type, text} objects + system message
|
|
264
|
+
// DashScope accepts: plain string content
|
|
265
|
+
const isPortalQwen = rawHost.includes('portal.qwen.ai') || rawHost.includes('chat.qwen.ai');
|
|
266
|
+
const toContent = (text) => isPortalQwen ? [{ type: 'text', text }] : text;
|
|
264
267
|
const response = await fetch(`${baseUrl}/chat/completions`, {
|
|
265
268
|
method: 'POST',
|
|
266
269
|
headers: {
|
|
267
270
|
'Authorization': `Bearer ${token.accessToken}`,
|
|
268
271
|
'Content-Type': 'application/json',
|
|
269
272
|
'Accept': 'application/json',
|
|
270
|
-
'User-Agent':
|
|
271
|
-
'
|
|
272
|
-
'
|
|
273
|
-
'
|
|
274
|
-
'x-stainless-lang': 'js',
|
|
275
|
-
'x-stainless-package-version': '5.11.0',
|
|
276
|
-
'x-stainless-os': process.platform,
|
|
277
|
-
'x-stainless-arch': process.arch,
|
|
278
|
-
'x-stainless-runtime': 'node',
|
|
279
|
-
'x-stainless-runtime-version': process.version,
|
|
280
|
-
'x-stainless-retry-count': '0',
|
|
273
|
+
'User-Agent': `QwenCode/0.14.2 (${process.platform}; ${process.arch})`,
|
|
274
|
+
'X-DashScope-AuthType': 'qwen-oauth',
|
|
275
|
+
'X-DashScope-CacheControl': 'enable',
|
|
276
|
+
'X-DashScope-UserAgent': `QwenCode/0.14.2 (${process.platform}; ${process.arch})`,
|
|
281
277
|
},
|
|
282
278
|
body: JSON.stringify({
|
|
283
279
|
model: model || 'coder-model',
|
|
284
280
|
messages: [
|
|
285
|
-
{ role: 'system', content:
|
|
286
|
-
{ role: 'user', content:
|
|
281
|
+
{ role: 'system', content: toContent('You are a helpful coding assistant.') },
|
|
282
|
+
{ role: 'user', content: toContent(prompt) },
|
|
287
283
|
],
|
|
288
284
|
stream: useStream,
|
|
289
285
|
}),
|
|
290
286
|
});
|
|
291
287
|
if (!response.ok) {
|
|
292
288
|
const errorText = await response.text();
|
|
293
|
-
if (response.status === 401)
|
|
289
|
+
if (response.status === 401 || (response.status === 429 && errorText.includes('insufficient_quota'))) {
|
|
294
290
|
throw new Error(`QWEN_AUTH_EXPIRED: ${errorText}`);
|
|
291
|
+
}
|
|
295
292
|
throw new Error(`Qwen API error: ${response.status} - ${errorText}`);
|
|
296
293
|
}
|
|
297
294
|
if (!useStream) {
|
|
@@ -380,8 +377,9 @@ export async function callQwenAPIFromCreds(prompt, model, credsPath, onData) {
|
|
|
380
377
|
if (!token.accessToken) {
|
|
381
378
|
throw new Error(`Invalid credentials at ${credsPath}. Run: ${cliName} --login`);
|
|
382
379
|
}
|
|
383
|
-
|
|
384
|
-
|
|
380
|
+
// Refresh proactivo: mismo threshold de 5 minutos que el CLI original
|
|
381
|
+
const FIVE_MIN = 5 * 60 * 1000;
|
|
382
|
+
if (token.expiresAt - Date.now() < FIVE_MIN && token.refreshToken) {
|
|
385
383
|
const refreshed = await doRefreshToken(token.refreshToken);
|
|
386
384
|
if (refreshed) {
|
|
387
385
|
token = refreshed;
|
package/package.json
CHANGED
package/dist/api/qwen.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
interface QwenMessage {
|
|
2
|
-
role: 'system' | 'user' | 'assistant';
|
|
3
|
-
content: string;
|
|
4
|
-
}
|
|
5
|
-
export declare function qwenLogin(): Promise<boolean>;
|
|
6
|
-
export declare function qwenAuthStatus(): Promise<{
|
|
7
|
-
authenticated: boolean;
|
|
8
|
-
email?: string;
|
|
9
|
-
}>;
|
|
10
|
-
export declare function qwenChat(messages: QwenMessage[], model?: string): Promise<string>;
|
|
11
|
-
export declare function qwenAsk(prompt: string, model?: string): Promise<string>;
|
|
12
|
-
export {};
|
package/dist/api/qwen.js
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
|
-
import * as fs from 'fs/promises';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import * as os from 'os';
|
|
5
|
-
import open from 'open';
|
|
6
|
-
const QWEN_API_BASE = 'https://dashscope-intl.aliyuncs.com/api/v1';
|
|
7
|
-
const QWEN_AUTH_URL = 'https://oauth.aliyun.com/v1/oauth/authorize';
|
|
8
|
-
const CLIENT_ID = 'your_client_id'; // Reemplazar con el client ID real de Qwen
|
|
9
|
-
const REDIRECT_URI = 'http://localhost:3000/callback';
|
|
10
|
-
let tokenCache = null;
|
|
11
|
-
async function getTokenPath() {
|
|
12
|
-
const homeDir = os.homedir();
|
|
13
|
-
const dir = path.join(homeDir, '.agent');
|
|
14
|
-
await fs.mkdir(dir, { recursive: true });
|
|
15
|
-
return path.join(dir, 'qwen-token.json');
|
|
16
|
-
}
|
|
17
|
-
async function loadToken() {
|
|
18
|
-
try {
|
|
19
|
-
const tokenPath = await getTokenPath();
|
|
20
|
-
const content = await fs.readFile(tokenPath, 'utf-8');
|
|
21
|
-
const token = JSON.parse(content);
|
|
22
|
-
if (token.expiresAt > Date.now()) {
|
|
23
|
-
return token;
|
|
24
|
-
}
|
|
25
|
-
// Token expirado, intentar refresh
|
|
26
|
-
if (token.refreshToken) {
|
|
27
|
-
return refreshAccessToken(token.refreshToken);
|
|
28
|
-
}
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
async function saveToken(token) {
|
|
36
|
-
const tokenPath = await getTokenPath();
|
|
37
|
-
await fs.writeFile(tokenPath, JSON.stringify(token, null, 2), 'utf-8');
|
|
38
|
-
}
|
|
39
|
-
async function refreshAccessToken(refreshToken) {
|
|
40
|
-
try {
|
|
41
|
-
const response = await axios.post('https://oauth.aliyun.com/v1/oauth/token', new URLSearchParams({
|
|
42
|
-
grant_type: 'refresh_token',
|
|
43
|
-
refresh_token: refreshToken,
|
|
44
|
-
client_id: CLIENT_ID,
|
|
45
|
-
}), {
|
|
46
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
47
|
-
});
|
|
48
|
-
const token = {
|
|
49
|
-
accessToken: response.data.access_token,
|
|
50
|
-
refreshToken: response.data.refresh_token || refreshToken,
|
|
51
|
-
expiresAt: Date.now() + (response.data.expires_in || 3600) * 1000,
|
|
52
|
-
};
|
|
53
|
-
await saveToken(token);
|
|
54
|
-
return token;
|
|
55
|
-
}
|
|
56
|
-
catch (error) {
|
|
57
|
-
console.error('Error refreshing token:', error);
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
export async function qwenLogin() {
|
|
62
|
-
const authUrl = `${QWEN_AUTH_URL}?client_id=${CLIENT_ID}&redirect_uri=${encodeURIComponent(REDIRECT_URI)}&response_type=code&scope=api`;
|
|
63
|
-
console.log('Abriendo navegador para autenticación con Qwen...');
|
|
64
|
-
console.log('URL:', authUrl);
|
|
65
|
-
await open(authUrl);
|
|
66
|
-
console.log('');
|
|
67
|
-
console.log('Por favor completá la autenticación en el navegador.');
|
|
68
|
-
console.log('Cuando obtengas el código de autorización, ingresalo abajo:');
|
|
69
|
-
return new Promise((resolve) => {
|
|
70
|
-
const readline = await import('readline');
|
|
71
|
-
const rl = readline.createInterface({
|
|
72
|
-
input: process.stdin,
|
|
73
|
-
output: process.stdout,
|
|
74
|
-
});
|
|
75
|
-
rl.question('Código de autorización: ', async (code) => {
|
|
76
|
-
try {
|
|
77
|
-
const response = await axios.post('https://oauth.aliyun.com/v1/oauth/token', new URLSearchParams({
|
|
78
|
-
grant_type: 'authorization_code',
|
|
79
|
-
code,
|
|
80
|
-
redirect_uri: REDIRECT_URI,
|
|
81
|
-
client_id: CLIENT_ID,
|
|
82
|
-
}), {
|
|
83
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
84
|
-
});
|
|
85
|
-
const token = {
|
|
86
|
-
accessToken: response.data.access_token,
|
|
87
|
-
refreshToken: response.data.refresh_token,
|
|
88
|
-
expiresAt: Date.now() + (response.data.expires_in || 3600) * 1000,
|
|
89
|
-
};
|
|
90
|
-
await saveToken(token);
|
|
91
|
-
console.log('¡Autenticación exitosa!');
|
|
92
|
-
rl.close();
|
|
93
|
-
resolve(true);
|
|
94
|
-
}
|
|
95
|
-
catch (error) {
|
|
96
|
-
console.error('Error en autenticación:', error);
|
|
97
|
-
rl.close();
|
|
98
|
-
resolve(false);
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
export async function qwenAuthStatus() {
|
|
104
|
-
const token = await loadToken();
|
|
105
|
-
if (token) {
|
|
106
|
-
return { authenticated: true };
|
|
107
|
-
}
|
|
108
|
-
return { authenticated: false };
|
|
109
|
-
}
|
|
110
|
-
export async function qwenChat(messages, model = 'qwen-coder') {
|
|
111
|
-
const token = await loadToken();
|
|
112
|
-
if (!token) {
|
|
113
|
-
throw new Error('No hay token de Qwen. Ejecutá /qwen-login primero.');
|
|
114
|
-
}
|
|
115
|
-
try {
|
|
116
|
-
const response = await axios.post(`${QWEN_API_BASE}/services/aigc/text-generation/generation`, {
|
|
117
|
-
model,
|
|
118
|
-
input: {
|
|
119
|
-
messages,
|
|
120
|
-
},
|
|
121
|
-
parameters: {
|
|
122
|
-
result_format: 'text',
|
|
123
|
-
},
|
|
124
|
-
}, {
|
|
125
|
-
headers: {
|
|
126
|
-
'Authorization': `Bearer ${token.accessToken}`,
|
|
127
|
-
'Content-Type': 'application/json',
|
|
128
|
-
},
|
|
129
|
-
});
|
|
130
|
-
return response.data.output.text;
|
|
131
|
-
}
|
|
132
|
-
catch (error) {
|
|
133
|
-
if (error.response?.status === 401) {
|
|
134
|
-
// Token inválido, intentar refresh
|
|
135
|
-
const newToken = token.refreshToken ? await refreshAccessToken(token.refreshToken) : null;
|
|
136
|
-
if (newToken) {
|
|
137
|
-
// Reintentar con el nuevo token
|
|
138
|
-
return qwenChat(messages, model);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
throw error;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
export async function qwenAsk(prompt, model = 'qwen-coder') {
|
|
145
|
-
const messages = [
|
|
146
|
-
{ role: 'system', content: 'Sos un asistente útil de programación.' },
|
|
147
|
-
{ role: 'user', content: prompt },
|
|
148
|
-
];
|
|
149
|
-
return qwenChat(messages, model);
|
|
150
|
-
}
|
package/dist/commands/auth.d.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
declare const CONFIG_DIR: string;
|
|
3
|
-
declare const CONFIG_FILE: string;
|
|
4
|
-
interface AuthEntry {
|
|
5
|
-
provider: string;
|
|
6
|
-
method: 'oauth' | 'apikey';
|
|
7
|
-
accessToken?: string;
|
|
8
|
-
refreshToken?: string;
|
|
9
|
-
expiresAt?: number;
|
|
10
|
-
apiKey?: string;
|
|
11
|
-
email?: string;
|
|
12
|
-
}
|
|
13
|
-
interface AuthStore {
|
|
14
|
-
entries: AuthEntry[];
|
|
15
|
-
activeProvider?: string;
|
|
16
|
-
}
|
|
17
|
-
interface CliConfig {
|
|
18
|
-
roles: Record<string, {
|
|
19
|
-
provider: string;
|
|
20
|
-
model: string;
|
|
21
|
-
}>;
|
|
22
|
-
deliberation: {
|
|
23
|
-
enabled: boolean;
|
|
24
|
-
max_rounds: number;
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
declare function loadAuth(): Promise<AuthStore>;
|
|
28
|
-
declare function loadCliConfig(): Promise<CliConfig>;
|
|
29
|
-
declare function saveCliConfig(cfg: CliConfig): Promise<void>;
|
|
30
|
-
export declare function authCommand(program: Command): void;
|
|
31
|
-
export { loadAuth, loadCliConfig, saveCliConfig, CONFIG_DIR, CONFIG_FILE };
|
package/dist/commands/auth.js
DELETED
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
import * as readline from 'readline';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import * as fs from 'fs/promises';
|
|
4
|
-
import { createServer } from 'http';
|
|
5
|
-
import { createHash, randomBytes } from 'crypto';
|
|
6
|
-
import open from 'open';
|
|
7
|
-
import chalk from 'chalk';
|
|
8
|
-
import { writeJson, ensureDir } from '../utils/fs.js';
|
|
9
|
-
const CONFIG_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '', '.agent-cli');
|
|
10
|
-
const AUTH_FILE = path.join(CONFIG_DIR, 'auth.json');
|
|
11
|
-
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
12
|
-
// ── OAuth providers ────────────────────────────────────────────────────────
|
|
13
|
-
const OAUTH_PROVIDERS = {
|
|
14
|
-
opencode: {
|
|
15
|
-
authorizeUrl: 'https://accounts.opencode.ai/oauth2/auth',
|
|
16
|
-
tokenUrl: 'https://accounts.opencode.ai/oauth2/token',
|
|
17
|
-
clientId: 'claw_code',
|
|
18
|
-
scopes: 'openid email profile offline_access',
|
|
19
|
-
name: 'Opencode',
|
|
20
|
-
},
|
|
21
|
-
google: {
|
|
22
|
-
authorizeUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
23
|
-
tokenUrl: 'https://oauth2.googleapis.com/token',
|
|
24
|
-
clientId: '',
|
|
25
|
-
scopes: 'openid email profile',
|
|
26
|
-
name: 'Google',
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
|
-
async function loadAuth() {
|
|
30
|
-
try {
|
|
31
|
-
const content = await fs.readFile(AUTH_FILE, 'utf-8');
|
|
32
|
-
return JSON.parse(content);
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
return { entries: [] };
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
async function saveAuth(auth) {
|
|
39
|
-
await ensureDir(CONFIG_DIR);
|
|
40
|
-
await writeJson(AUTH_FILE, auth);
|
|
41
|
-
}
|
|
42
|
-
async function loadCliConfig() {
|
|
43
|
-
try {
|
|
44
|
-
const content = await fs.readFile(CONFIG_FILE, 'utf-8');
|
|
45
|
-
return JSON.parse(content);
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
return { roles: {}, deliberation: { enabled: false, max_rounds: 4 } };
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
async function saveCliConfig(cfg) {
|
|
52
|
-
await ensureDir(CONFIG_DIR);
|
|
53
|
-
await writeJson(CONFIG_FILE, cfg);
|
|
54
|
-
}
|
|
55
|
-
function base64url(buf) {
|
|
56
|
-
return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
57
|
-
}
|
|
58
|
-
async function oauthFlow(providerId) {
|
|
59
|
-
const provider = OAUTH_PROVIDERS[providerId];
|
|
60
|
-
if (!provider)
|
|
61
|
-
throw new Error(`Unknown OAuth provider: ${providerId}`);
|
|
62
|
-
const port = 9800 + Math.floor(Math.random() * 1000);
|
|
63
|
-
const state = base64url(randomBytes(32));
|
|
64
|
-
const codeVerifier = base64url(randomBytes(32));
|
|
65
|
-
const codeChallenge = base64url(createHash('sha256').update(codeVerifier).digest());
|
|
66
|
-
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
67
|
-
const authUrl = new URL(provider.authorizeUrl);
|
|
68
|
-
authUrl.searchParams.set('response_type', 'code');
|
|
69
|
-
authUrl.searchParams.set('client_id', provider.clientId);
|
|
70
|
-
authUrl.searchParams.set('redirect_uri', redirectUri);
|
|
71
|
-
authUrl.searchParams.set('scope', provider.scopes);
|
|
72
|
-
authUrl.searchParams.set('state', state);
|
|
73
|
-
authUrl.searchParams.set('code_challenge', codeChallenge);
|
|
74
|
-
authUrl.searchParams.set('code_challenge_method', 'S256');
|
|
75
|
-
console.log(chalk.dim(` Opening browser for ${provider.name} login...`));
|
|
76
|
-
await open(authUrl.toString());
|
|
77
|
-
return new Promise((resolve, reject) => {
|
|
78
|
-
const server = createServer((req, res) => {
|
|
79
|
-
const url = new URL(req.url, `http://127.0.0.1:${port}`);
|
|
80
|
-
if (url.pathname !== '/callback') {
|
|
81
|
-
res.writeHead(404);
|
|
82
|
-
res.end();
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
const returnedState = url.searchParams.get('state');
|
|
86
|
-
const code = url.searchParams.get('code');
|
|
87
|
-
const error = url.searchParams.get('error');
|
|
88
|
-
if (error) {
|
|
89
|
-
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
90
|
-
res.end(`<h1>Login failed</h1><p>${error}</p>`);
|
|
91
|
-
server.close();
|
|
92
|
-
reject(new Error(`OAuth error: ${error}`));
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
if (returnedState !== state) {
|
|
96
|
-
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
97
|
-
res.end('<h1>Invalid state</h1>');
|
|
98
|
-
server.close();
|
|
99
|
-
reject(new Error('State mismatch'));
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
if (!code) {
|
|
103
|
-
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
104
|
-
res.end('<h1>No code</h1>');
|
|
105
|
-
server.close();
|
|
106
|
-
reject(new Error('No authorization code'));
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
110
|
-
res.end(`
|
|
111
|
-
<html><body style="font-family:system-ui;text-align:center;padding:60px">
|
|
112
|
-
<h1 style="color:#06b6d4">✓ Login successful</h1>
|
|
113
|
-
<p>You can close this tab and return to the terminal.</p>
|
|
114
|
-
</body></html>
|
|
115
|
-
`);
|
|
116
|
-
server.close();
|
|
117
|
-
// Exchange code for tokens
|
|
118
|
-
exchangeToken(provider, code, redirectUri, codeVerifier).then(resolve).catch(reject);
|
|
119
|
-
});
|
|
120
|
-
server.listen(port, '127.0.0.1', () => {
|
|
121
|
-
console.log(chalk.blue(` → Waiting for callback on port ${port}...`));
|
|
122
|
-
});
|
|
123
|
-
server.on('error', (err) => {
|
|
124
|
-
if (err.code === 'EADDRINUSE') {
|
|
125
|
-
reject(new Error(`Port ${port} is in use. Try again.`));
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
reject(err);
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
async function exchangeToken(provider, code, redirectUri, codeVerifier) {
|
|
134
|
-
const params = new URLSearchParams({
|
|
135
|
-
grant_type: 'authorization_code',
|
|
136
|
-
client_id: provider.clientId,
|
|
137
|
-
code,
|
|
138
|
-
redirect_uri: redirectUri,
|
|
139
|
-
code_verifier: codeVerifier,
|
|
140
|
-
});
|
|
141
|
-
const res = await fetch(provider.tokenUrl, {
|
|
142
|
-
method: 'POST',
|
|
143
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
144
|
-
body: params.toString(),
|
|
145
|
-
});
|
|
146
|
-
if (!res.ok) {
|
|
147
|
-
const text = await res.text();
|
|
148
|
-
throw new Error(`Token exchange failed (${res.status}): ${text}`);
|
|
149
|
-
}
|
|
150
|
-
const data = await res.json();
|
|
151
|
-
// Decode email from id_token if available
|
|
152
|
-
let email;
|
|
153
|
-
if (data.id_token) {
|
|
154
|
-
try {
|
|
155
|
-
const payload = JSON.parse(Buffer.from(data.id_token.split('.')[1], 'base64').toString());
|
|
156
|
-
email = payload.email;
|
|
157
|
-
}
|
|
158
|
-
catch { }
|
|
159
|
-
}
|
|
160
|
-
return {
|
|
161
|
-
provider: 'opencode',
|
|
162
|
-
method: 'oauth',
|
|
163
|
-
accessToken: data.access_token,
|
|
164
|
-
refreshToken: data.refresh_token,
|
|
165
|
-
expiresAt: data.expires_in ? Date.now() + data.expires_in * 1000 : undefined,
|
|
166
|
-
email,
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
function ask(rl, q) {
|
|
170
|
-
return new Promise((resolve) => rl.question(q, resolve));
|
|
171
|
-
}
|
|
172
|
-
export function authCommand(program) {
|
|
173
|
-
const auth = program.command('auth')
|
|
174
|
-
.description('Manage authentication');
|
|
175
|
-
auth
|
|
176
|
-
.command('login')
|
|
177
|
-
.description('Login to a provider (opens browser)')
|
|
178
|
-
.option('-p, --provider <name>', 'Provider name (opencode)')
|
|
179
|
-
.action(async (opts) => {
|
|
180
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
181
|
-
console.log(chalk.bold.cyan('\n Agent CLI — Login\n'));
|
|
182
|
-
let providerId = opts.provider;
|
|
183
|
-
if (!providerId) {
|
|
184
|
-
console.log(chalk.dim(' Available providers:'));
|
|
185
|
-
for (const [id, p] of Object.entries(OAUTH_PROVIDERS)) {
|
|
186
|
-
console.log(chalk.dim(` ${id} — ${p.name}`));
|
|
187
|
-
}
|
|
188
|
-
providerId = await ask(rl, '\n Provider: ');
|
|
189
|
-
}
|
|
190
|
-
providerId = providerId.trim();
|
|
191
|
-
try {
|
|
192
|
-
const entry = await oauthFlow(providerId);
|
|
193
|
-
const auth = await loadAuth();
|
|
194
|
-
// Remove existing entry for same provider
|
|
195
|
-
auth.entries = auth.entries.filter((e) => e.provider !== entry.provider);
|
|
196
|
-
auth.entries.push(entry);
|
|
197
|
-
auth.activeProvider = entry.provider;
|
|
198
|
-
await saveAuth(auth);
|
|
199
|
-
console.log(chalk.green(`\n ✓ Logged in as ${entry.email || 'user'} via ${entry.provider}`));
|
|
200
|
-
console.log(chalk.dim(' Auth saved to ~/.agent-cli/auth.json'));
|
|
201
|
-
}
|
|
202
|
-
catch (err) {
|
|
203
|
-
console.log(chalk.red(`\n ✗ Login failed: ${err.message}`));
|
|
204
|
-
}
|
|
205
|
-
rl.close();
|
|
206
|
-
});
|
|
207
|
-
auth
|
|
208
|
-
.command('logout')
|
|
209
|
-
.description('Logout from current provider')
|
|
210
|
-
.action(async () => {
|
|
211
|
-
const auth = await loadAuth();
|
|
212
|
-
if (!auth.activeProvider || auth.entries.length === 0) {
|
|
213
|
-
console.log(chalk.yellow(' Not logged in'));
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
auth.entries = auth.entries.filter((e) => e.provider !== auth.activeProvider);
|
|
217
|
-
auth.activeProvider = undefined;
|
|
218
|
-
await saveAuth(auth);
|
|
219
|
-
console.log(chalk.green(' ✓ Logged out'));
|
|
220
|
-
});
|
|
221
|
-
auth
|
|
222
|
-
.command('status')
|
|
223
|
-
.description('Show current auth status')
|
|
224
|
-
.action(async () => {
|
|
225
|
-
const auth = await loadAuth();
|
|
226
|
-
console.log(chalk.bold('\n Auth Status'));
|
|
227
|
-
console.log(chalk.dim(' ──────────────────────────'));
|
|
228
|
-
if (auth.entries.length === 0) {
|
|
229
|
-
console.log(chalk.yellow(' Not logged in to any provider'));
|
|
230
|
-
}
|
|
231
|
-
else {
|
|
232
|
-
for (const entry of auth.entries) {
|
|
233
|
-
const active = entry.provider === auth.activeProvider ? chalk.green(' (active)') : '';
|
|
234
|
-
const email = entry.email ? ` — ${entry.email}` : '';
|
|
235
|
-
const expired = entry.expiresAt && entry.expiresAt < Date.now() ? chalk.red(' (expired)') : '';
|
|
236
|
-
console.log(` ${entry.provider} (${entry.method})${email}${active}${expired}`);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
console.log('');
|
|
240
|
-
});
|
|
241
|
-
auth
|
|
242
|
-
.command('apikey')
|
|
243
|
-
.description('Set API key for a provider')
|
|
244
|
-
.argument('<provider>', 'Provider name')
|
|
245
|
-
.argument('<key>', 'API key')
|
|
246
|
-
.action(async (provider, key) => {
|
|
247
|
-
const auth = await loadAuth();
|
|
248
|
-
auth.entries = auth.entries.filter((e) => e.provider !== provider);
|
|
249
|
-
auth.entries.push({ provider, method: 'apikey', apiKey: key });
|
|
250
|
-
auth.activeProvider = provider;
|
|
251
|
-
await saveAuth(auth);
|
|
252
|
-
console.log(chalk.green(` ✓ API key saved for ${provider}`));
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
export { loadAuth, loadCliConfig, saveCliConfig, CONFIG_DIR, CONFIG_FILE };
|
package/dist/commands/config.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { loadCliConfig, saveCliConfig } from '../utils/config.js';
|
|
3
|
-
export function configCommand(program) {
|
|
4
|
-
program
|
|
5
|
-
.command('config')
|
|
6
|
-
.description('View or edit CLI configuration')
|
|
7
|
-
.option('--show', 'Show current configuration')
|
|
8
|
-
.option('--set <key=value>', 'Set a config value')
|
|
9
|
-
.action(async (opts) => {
|
|
10
|
-
const config = await loadCliConfig();
|
|
11
|
-
if (opts.show) {
|
|
12
|
-
console.log(chalk.bold('\n Agent CLI Configuration'));
|
|
13
|
-
console.log(chalk.dim(' ──────────────────────────'));
|
|
14
|
-
console.log(chalk.yellow(' Providers:'), Object.keys(config.apiKeys).join(', ') || 'none');
|
|
15
|
-
for (const [role, r] of Object.entries(config.roles)) {
|
|
16
|
-
if (r)
|
|
17
|
-
console.log(chalk.yellow(` ${role}:`), `${r.provider}/${r.model}`);
|
|
18
|
-
}
|
|
19
|
-
if (config.deliberation?.enabled) {
|
|
20
|
-
console.log(chalk.yellow(' Deliberation:'), `enabled (${config.deliberation.max_rounds} rounds)`);
|
|
21
|
-
}
|
|
22
|
-
console.log('');
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
if (opts.set) {
|
|
26
|
-
const [key, ...valParts] = opts.set.split('=');
|
|
27
|
-
const value = valParts.join('=');
|
|
28
|
-
const parts = key.split('.');
|
|
29
|
-
let obj = config;
|
|
30
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
31
|
-
if (!obj[parts[i]])
|
|
32
|
-
obj[parts[i]] = {};
|
|
33
|
-
obj = obj[parts[i]];
|
|
34
|
-
}
|
|
35
|
-
obj[parts[parts.length - 1]] = value;
|
|
36
|
-
await saveCliConfig(config);
|
|
37
|
-
console.log(chalk.green(` ✓ Set ${key} = ${value}`));
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
console.log('Use --show or --set key=value');
|
|
41
|
-
});
|
|
42
|
-
}
|
package/dist/core/prompts.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { TaskPlan } from '../types.js';
|
|
2
|
-
export declare function orchestratorSystem(): string;
|
|
3
|
-
export declare function orchestratorUser(taskId: string, task: string, context: string): string;
|
|
4
|
-
export declare function implementorSystem(approved: string[], forbidden: string[]): string;
|
|
5
|
-
export declare function implementorUser(taskId: string, plan: TaskPlan): string;
|
|
6
|
-
export declare function reviewerSystem(): string;
|
|
7
|
-
export declare function reviewerUser(taskId: string, plan: TaskPlan, progressSummary: string): string;
|
|
8
|
-
export declare function proposerSystem(): string;
|
|
9
|
-
export declare function proposerUser(task: string, question: string, prevRounds: string): string;
|
|
10
|
-
export declare function criticSystem(): string;
|
|
11
|
-
export declare function criticUser(task: string, question: string, proposal: string, prevCritiques: string): string;
|
package/dist/core/prompts.js
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
export function orchestratorSystem() {
|
|
2
|
-
return `Sos el ORCHESTRATOR de un sistema multi-agente. Tu UNICO trabajo es planificar.
|
|
3
|
-
|
|
4
|
-
REGLAS:
|
|
5
|
-
- NO ejecutar codigo
|
|
6
|
-
- NO crear archivos fuera de .agent/tasks/
|
|
7
|
-
- NO instalar dependencias
|
|
8
|
-
- NO modificar archivos existentes
|
|
9
|
-
- Solo planificar y escribir JSON
|
|
10
|
-
|
|
11
|
-
Tu output debe ser SOLO JSON valido, sin markdown, sin explicaciones.`;
|
|
12
|
-
}
|
|
13
|
-
export function orchestratorUser(taskId, task, context) {
|
|
14
|
-
return `TAREA: ${task}
|
|
15
|
-
ID: ${taskId}
|
|
16
|
-
|
|
17
|
-
CONTEXTO:
|
|
18
|
-
${context}
|
|
19
|
-
|
|
20
|
-
Responde SOLO con este JSON:
|
|
21
|
-
{
|
|
22
|
-
"plan": {
|
|
23
|
-
"task_id": "${taskId}",
|
|
24
|
-
"description": "${task}",
|
|
25
|
-
"steps": [
|
|
26
|
-
{"num": 1, "description": "paso detallado", "files": ["ruta/archivo"], "status": "pending"}
|
|
27
|
-
],
|
|
28
|
-
"acceptance_criteria": ["criterio verificable 1", "criterio verificable 2"],
|
|
29
|
-
"deliberation": {"needed": false, "question": ""}
|
|
30
|
-
},
|
|
31
|
-
"progress": {
|
|
32
|
-
"task_id": "${taskId}",
|
|
33
|
-
"created_at": "${new Date().toISOString()}",
|
|
34
|
-
"steps": [],
|
|
35
|
-
"status": "planned"
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
Cada step debe ser atomico. Los criterios deben ser verificables.`;
|
|
40
|
-
}
|
|
41
|
-
export function implementorSystem(approved, forbidden) {
|
|
42
|
-
return `Sos el IMPLEMENTOR. Tu UNICO trabajo es ejecutar el plan.
|
|
43
|
-
|
|
44
|
-
REGLAS:
|
|
45
|
-
- NO modificar plan.json
|
|
46
|
-
- NO crear archivos no contemplados en el plan
|
|
47
|
-
- Seguir el plan exactamente
|
|
48
|
-
- Actualizar progress.json con timestamps
|
|
49
|
-
|
|
50
|
-
APROBADOS: ${approved.join(', ')}
|
|
51
|
-
PROHIBIDOS: ${forbidden.join(', ')}
|
|
52
|
-
|
|
53
|
-
Tu output debe ser SOLO JSON valido con el progress actualizado.`;
|
|
54
|
-
}
|
|
55
|
-
export function implementorUser(taskId, plan) {
|
|
56
|
-
return `EJECUTA ESTE PLAN:
|
|
57
|
-
Task: ${taskId}
|
|
58
|
-
${JSON.stringify(plan, null, 2)}
|
|
59
|
-
|
|
60
|
-
Por cada step completado, cambia status a "completed" y agrega completed_at.
|
|
61
|
-
Al terminar todos los steps, status = "completed".
|
|
62
|
-
|
|
63
|
-
Responde SOLO con el JSON de progress actualizado.`;
|
|
64
|
-
}
|
|
65
|
-
export function reviewerSystem() {
|
|
66
|
-
return `Sos el REVIEWER. Tu UNICO trabajo es validar.
|
|
67
|
-
|
|
68
|
-
REGLAS:
|
|
69
|
-
- NO modificar codigo
|
|
70
|
-
- NO implementar correcciones
|
|
71
|
-
- Solo validar y reportar
|
|
72
|
-
|
|
73
|
-
Tu output debe ser SOLO JSON valido.`;
|
|
74
|
-
}
|
|
75
|
-
export function reviewerUser(taskId, plan, progressSummary) {
|
|
76
|
-
return `VALIDA ESTA TAREA:
|
|
77
|
-
Task: ${taskId}
|
|
78
|
-
|
|
79
|
-
PLAN:
|
|
80
|
-
${JSON.stringify({ description: plan.description, criteria: plan.acceptance_criteria }, null, 2)}
|
|
81
|
-
|
|
82
|
-
PROGRESO:
|
|
83
|
-
${progressSummary}
|
|
84
|
-
|
|
85
|
-
Responde SOLO con:
|
|
86
|
-
{
|
|
87
|
-
"result": {
|
|
88
|
-
"task_id": "${taskId}",
|
|
89
|
-
"criteria": [{"criterion": "...", "status": "PASS", "notes": "..."}],
|
|
90
|
-
"verdict": "PASS",
|
|
91
|
-
"explanation": "..."
|
|
92
|
-
},
|
|
93
|
-
"validation": {
|
|
94
|
-
"status": "PASS",
|
|
95
|
-
"reviewed_at": "${new Date().toISOString()}",
|
|
96
|
-
"reviewer": "reviewer"
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
Verdict = "PASS" si TODOS los criterios se cumplen, "FAIL" si alguno falla.`;
|
|
101
|
-
}
|
|
102
|
-
export function proposerSystem() {
|
|
103
|
-
return `Sos el PROPOSER en un debate tecnico. Generas propuestas con justificacion tecnica.
|
|
104
|
-
Responde SOLO con JSON valido.`;
|
|
105
|
-
}
|
|
106
|
-
export function proposerUser(task, question, prevRounds) {
|
|
107
|
-
return `TAREA: ${task}
|
|
108
|
-
PREGUNTA: ${question}
|
|
109
|
-
${prevRounds ? 'RONDAS ANTERIORES:\n' + prevRounds : 'Primera ronda.'}
|
|
110
|
-
|
|
111
|
-
Responde SOLO con:
|
|
112
|
-
{"proposal": "...", "justification": "...", "trade_offs": ["..."], "accept": false}`;
|
|
113
|
-
}
|
|
114
|
-
export function criticSystem() {
|
|
115
|
-
return `Sos el CRITIC en un debate tecnico. Buscas debilidades, edge cases y errores.
|
|
116
|
-
Responde SOLO con JSON valido.`;
|
|
117
|
-
}
|
|
118
|
-
export function criticUser(task, question, proposal, prevCritiques) {
|
|
119
|
-
return `TAREA: ${task}
|
|
120
|
-
PREGUNTA: ${question}
|
|
121
|
-
PROPUESTA: ${proposal}
|
|
122
|
-
${prevCritiques ? 'CRITICAS ANTERIORES:\n' + prevCritiques : ''}
|
|
123
|
-
|
|
124
|
-
Responde SOLO con:
|
|
125
|
-
{"analysis": "...", "weaknesses": ["..."], "edge_cases": ["..."], "suggestions": ["..."], "verdict": "ACCEPT"}`;
|
|
126
|
-
}
|
package/dist/providers/index.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import OpenAI from 'openai';
|
|
2
|
-
import { Anthropic } from '@anthropic-ai/sdk';
|
|
3
|
-
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
4
|
-
import { loadAuth } from '../commands/auth.js';
|
|
5
|
-
function detectProvider(model) {
|
|
6
|
-
const m = model.toLowerCase();
|
|
7
|
-
if (m.startsWith('gpt') || m.startsWith('o1') || m.startsWith('o3') || m.startsWith('o4'))
|
|
8
|
-
return 'openai';
|
|
9
|
-
if (m.startsWith('claude'))
|
|
10
|
-
return 'anthropic';
|
|
11
|
-
if (m.startsWith('gemini'))
|
|
12
|
-
return 'google';
|
|
13
|
-
if (m.includes('/'))
|
|
14
|
-
return 'opencode';
|
|
15
|
-
return 'openai';
|
|
16
|
-
}
|
|
17
|
-
async function getAuthToken(provider) {
|
|
18
|
-
// 1. Check env vars
|
|
19
|
-
const envKey = process.env[`${provider.toUpperCase()}_API_KEY`];
|
|
20
|
-
if (envKey)
|
|
21
|
-
return envKey;
|
|
22
|
-
// 2. Check OAuth auth store
|
|
23
|
-
try {
|
|
24
|
-
const auth = await loadAuth();
|
|
25
|
-
const entry = auth.entries.find((e) => e.provider === provider) ||
|
|
26
|
-
auth.entries.find((e) => e.provider === auth.activeProvider);
|
|
27
|
-
if (entry?.accessToken)
|
|
28
|
-
return entry.accessToken;
|
|
29
|
-
if (entry?.apiKey)
|
|
30
|
-
return entry.apiKey;
|
|
31
|
-
}
|
|
32
|
-
catch { }
|
|
33
|
-
return undefined;
|
|
34
|
-
}
|
|
35
|
-
async function buildClient(role) {
|
|
36
|
-
const provider = role.provider || detectProvider(role.model);
|
|
37
|
-
const token = await getAuthToken(provider);
|
|
38
|
-
switch (provider) {
|
|
39
|
-
case 'openai':
|
|
40
|
-
return { type: 'openai', client: new OpenAI({ apiKey: token }) };
|
|
41
|
-
case 'anthropic':
|
|
42
|
-
return { type: 'anthropic', client: new Anthropic({ apiKey: token }) };
|
|
43
|
-
case 'google':
|
|
44
|
-
return { type: 'google', client: new GoogleGenerativeAI(token || '') };
|
|
45
|
-
case 'opencode': {
|
|
46
|
-
// Opencode uses an OpenAI-compatible API
|
|
47
|
-
const baseURL = process.env.OPENCODE_BASE_URL || 'https://api.opencode.ai/v1';
|
|
48
|
-
return { type: 'opencode', client: new OpenAI({ apiKey: token || '', baseURL }) };
|
|
49
|
-
}
|
|
50
|
-
default:
|
|
51
|
-
throw new Error(`Unknown provider: ${provider}`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
export async function chat(role, systemPrompt, userPrompt) {
|
|
55
|
-
const client = await buildClient(role);
|
|
56
|
-
switch (client.type) {
|
|
57
|
-
case 'openai':
|
|
58
|
-
case 'opencode':
|
|
59
|
-
return chatOpenai(client.client, role.model, systemPrompt, userPrompt);
|
|
60
|
-
case 'anthropic':
|
|
61
|
-
return chatAnthropic(client.client, role.model, systemPrompt, userPrompt);
|
|
62
|
-
case 'google':
|
|
63
|
-
return chatGoogle(client.client, role.model, systemPrompt, userPrompt);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
async function chatOpenai(client, model, system, user) {
|
|
67
|
-
const res = await client.chat.completions.create({
|
|
68
|
-
model,
|
|
69
|
-
messages: [
|
|
70
|
-
{ role: 'system', content: system },
|
|
71
|
-
{ role: 'user', content: user },
|
|
72
|
-
],
|
|
73
|
-
});
|
|
74
|
-
const content = res.choices[0]?.message?.content || '';
|
|
75
|
-
const usage = res.usage;
|
|
76
|
-
return {
|
|
77
|
-
content,
|
|
78
|
-
usage: {
|
|
79
|
-
input_tokens: usage?.prompt_tokens || 0,
|
|
80
|
-
output_tokens: usage?.completion_tokens || 0,
|
|
81
|
-
cache_read_input_tokens: 0,
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
async function chatAnthropic(client, model, system, user) {
|
|
86
|
-
const res = await client.messages.create({
|
|
87
|
-
model,
|
|
88
|
-
system,
|
|
89
|
-
max_tokens: 8192,
|
|
90
|
-
messages: [{ role: 'user', content: user }],
|
|
91
|
-
});
|
|
92
|
-
const content = res.content
|
|
93
|
-
.filter((b) => b.type === 'text')
|
|
94
|
-
.map((b) => b.text)
|
|
95
|
-
.join('\n');
|
|
96
|
-
return {
|
|
97
|
-
content,
|
|
98
|
-
usage: {
|
|
99
|
-
input_tokens: res.usage?.input_tokens || 0,
|
|
100
|
-
output_tokens: res.usage?.output_tokens || 0,
|
|
101
|
-
cache_read_input_tokens: res.usage?.cache_read_input_tokens || 0,
|
|
102
|
-
},
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
async function chatGoogle(client, model, system, user) {
|
|
106
|
-
const genModel = client.getGenerativeModel({ model });
|
|
107
|
-
const result = await genModel.generateContent({
|
|
108
|
-
contents: [{ role: 'user', parts: [{ text: `${system}\n\n---\n\n${user}` }] }],
|
|
109
|
-
});
|
|
110
|
-
const text = result.response.text();
|
|
111
|
-
const usageMetadata = result.response.usageMetadata;
|
|
112
|
-
return {
|
|
113
|
-
content: text,
|
|
114
|
-
usage: {
|
|
115
|
-
input_tokens: usageMetadata?.promptTokenCount || 0,
|
|
116
|
-
output_tokens: usageMetadata?.candidatesTokenCount || 0,
|
|
117
|
-
cache_read_input_tokens: 0,
|
|
118
|
-
},
|
|
119
|
-
};
|
|
120
|
-
}
|