agent-rev 0.5.13 → 0.5.23

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.
@@ -10,7 +10,7 @@ import { writeJson, ensureDir, readJson, listDir, fileExists } from '../utils/fs
10
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
- import { qwenLogin, qwenAuthStatus, QWEN_AGENT_HOME, fetchQwenModels } from '../utils/qwen-auth.js';
13
+ import { qwenAuthStatus, QWEN_AGENT_HOME, fetchQwenModels, loadApiKeyConfig, saveApiKeyConfig, fetchApiKeyModels, DEFAULT_API_BASE_URL } from '../utils/qwen-auth.js';
14
14
  import { renderWelcomePanel, renderHelpHint, renderSectionBox, renderMultiSectionBox } from '../ui/theme.js';
15
15
  import { FixedInput } from '../ui/input.js';
16
16
  import { newSession, saveSession } from '../utils/sessions.js';
@@ -256,55 +256,52 @@ function buildCmd(cliName, model) {
256
256
  const promptFlag = info.promptFlag ? ` ${info.promptFlag}` : '';
257
257
  return `${info.command} ${info.modelFlag} ${model}${extra}${promptFlag}`.trim();
258
258
  }
259
- async function cmdLogin(rl) {
260
- console.log(chalk.bold.cyan('\n Login\n'));
261
- // If active provider is Qwen, use built-in OAuth device flow (no qwen CLI needed)
262
- const auth = await loadAuth();
263
- if (auth.activeProvider === 'qwen') {
264
- console.log(chalk.dim(` Credentials will be saved to: ${QWEN_AGENT_HOME}/\n`));
265
- try {
266
- const success = await qwenLogin();
267
- if (!success)
268
- return false;
269
- const emailInput = await ask(rl, ' Your email (optional, for display): ');
270
- auth.entries = auth.entries.filter((e) => e.provider !== 'qwen');
271
- auth.entries.push({ provider: 'qwen', method: 'oauth', ...(emailInput.trim() ? { email: emailInput.trim() } : {}) });
272
- await saveAuth(auth);
273
- return true;
274
- }
275
- catch (err) {
276
- console.log(chalk.red(' Login failed: ' + err.message));
277
- return false;
278
- }
259
+ async function promptApiKeySetup(rl, askFn) {
260
+ const existing = await loadApiKeyConfig();
261
+ if (existing) {
262
+ console.log(chalk.dim(` Current: ${existing.provider} / ${existing.model}`));
279
263
  }
280
- // For other providers, use their CLI
281
- console.log(chalk.dim(' Available providers:'));
282
- for (const [id, cmd] of Object.entries(CLI_AUTH_COMMANDS)) {
283
- const installed = isCliInstalled(id) ? chalk.green('✓') : chalk.red('✗');
284
- console.log(chalk.dim(` ${installed} ${id} - uses: ${cmd}`));
285
- }
286
- const provider = await ask(rl, '\n Provider: ');
287
- const authCmd = CLI_AUTH_COMMANDS[provider.trim()];
288
- if (!authCmd) {
289
- console.log(chalk.red(` No auth command for: ${provider}`));
264
+ const apiKey = await askFn(` API Key${existing ? ' [Enter to keep]' : ''}: `);
265
+ const resolvedKey = apiKey.trim() || existing?.api_key || '';
266
+ if (!resolvedKey) {
267
+ console.log(chalk.red(' API key is required.'));
290
268
  return false;
291
269
  }
292
- console.log(chalk.blue(` Running: ${authCmd}`));
293
- console.log(chalk.dim(' (This will open your browser for login)\n'));
294
- try {
295
- execSync(authCmd, { stdio: 'inherit' });
296
- const auth = await loadAuth();
297
- auth.entries = auth.entries.filter((e) => e.provider !== provider.trim());
298
- auth.entries.push({ provider: provider.trim(), method: 'oauth' });
299
- auth.activeProvider = provider.trim();
300
- await saveAuth(auth);
301
- console.log(chalk.green(` Auth recorded for ${provider}`));
302
- return true;
270
+ console.log(chalk.dim('\n Fetching available models...'));
271
+ const tempCfg = { provider: 'openai-compatible', api_key: resolvedKey, base_url: DEFAULT_API_BASE_URL, model: '' };
272
+ const models = await fetchApiKeyModels(tempCfg);
273
+ let chosenModel = existing?.model ?? 'qwen-plus';
274
+ if (models.length > 0) {
275
+ console.log(chalk.bold('\n Available models:'));
276
+ models.forEach((m, i) => console.log(chalk.dim(` ${i + 1}. ${m}`)));
277
+ const pick = await askFn(`\n Model [${chosenModel}]: `);
278
+ const num = parseInt(pick.trim());
279
+ if (!isNaN(num) && num >= 1 && num <= models.length) {
280
+ chosenModel = models[num - 1];
281
+ }
282
+ else if (pick.trim()) {
283
+ chosenModel = pick.trim();
284
+ }
303
285
  }
304
- catch {
305
- console.log(chalk.red(' Login failed.'));
306
- return false;
286
+ else {
287
+ console.log(chalk.yellow(' Could not fetch models — enter model name manually.'));
288
+ const pick = await askFn(` Model [${chosenModel}]: `);
289
+ if (pick.trim())
290
+ chosenModel = pick.trim();
307
291
  }
292
+ const cfg = {
293
+ provider: existing?.provider ?? 'openai-compatible',
294
+ api_key: resolvedKey,
295
+ base_url: DEFAULT_API_BASE_URL,
296
+ model: chosenModel,
297
+ };
298
+ await saveApiKeyConfig(cfg);
299
+ console.log(chalk.green(`\n ✓ API key saved — ${cfg.model}\n`));
300
+ return true;
301
+ }
302
+ async function cmdLogin(rl) {
303
+ console.log(chalk.bold.cyan('\n Configure API Key\n'));
304
+ return promptApiKeySetup(rl, (q) => ask(rl, q));
308
305
  }
309
306
  async function cmdSetup(rl) {
310
307
  console.log(chalk.bold.cyan('\n Setup Wizard\n'));
@@ -405,12 +402,15 @@ async function cmdSetup(rl) {
405
402
  await writeJson(path.join(projectDir, '.agent', 'config.json'), agentConfig);
406
403
  console.log(chalk.green(' .agent/config.json generated'));
407
404
  const templates = {
408
- '.agent/INDEX.md': `# ${projectName} - Agent Entry Point\n\n## Navegacion por rol\n| Si sos... | Lee primero... |\n|-----------|----------------|\n| ORCHESTRATOR | .agent/rules/workflow.md |\n| IMPLEMENTOR | .agent/tasks/{ID}/plan.json |\n| REVIEWER | .agent/rules/workflow.md |\n`,
409
- '.agent/rules/workflow.md': '# Agent Workflow Contract\n\n## REGLA #1 - Separacion de roles\nUn rol NUNCA ejecuta acciones de otro rol.\n\n## Roles\n- ORCHESTRATOR: planifica\n- IMPLEMENTOR: ejecuta\n- REVIEWER: valida\n',
410
- '.agent/rules/structure.md': '# Estructura\n\n## Aprobados\n.\n\n## Prohibidos\nnode_modules, dist, .git\n',
411
- '.agent/rules/patterns.md': '# Patrones\n\n- kebab-case: archivos\n- PascalCase: clases\n- camelCase: variables\n',
412
- '.agent/context/architecture.md': '# Arquitectura - NIVEL 0\n\n| Servicio | Stack | Puerto | Detalle |\n|----------|-------|--------|---------|\n| - | - | - | - |\n',
413
- '.agent/AGENT.md': '# Sub-Agentes\n\n1. Lee .agent/INDEX.md\n2. Lee .agent/rules/workflow.md\n3. Actua SOLO dentro de tu rol\n',
405
+ '.agent/INDEX.md': `# ${projectName} Agent Entry Point\n\n> Leé este archivo antes de cualquier accion.\n\n## Proyecto\n${projectName}\n\n## Navegacion por rol\n| Si sos... | Lee primero... | Luego... |\n|-----------|----------------|----------|\n| ORCHESTRATOR | .agent/rules/workflow.md | .agent/rules/orchestrator.md |\n| IMPLEMENTOR | .agent/rules/implementor.md | .agent/tasks/{ID}/plan.json |\n| REVIEWER | .agent/rules/reviewer.md | .agent/rules/workflow.md |\n\n## Contexto del proyecto\n- Arquitectura: .agent/context/architecture.md\n- Stack: ver .agent/config.json\n`,
406
+ '.agent/rules/workflow.md': `# Workflow Contract — ${projectName}\n\n## Flujo general\n\`\`\`\n[ORCHESTRATOR] planifica → genera plan.json\n ↓\n[IMPLEMENTOR] ejecuta el plan → modifica archivos\n ↓\n[REVIEWER] valida → genera result.md\n\`\`\`\n\n## REGLA #1 Separacion de roles\nCada CLI opera SOLO en su rol. Nunca ejecuta acciones de otro rol.\n\n## REGLA #2 — Orden estricto\nOrchestrator siempre primero. Implementor no actua sin plan. Reviewer no actua sin implementacion.\n\n## REGLA #3 — Sin inventar\nCada rol trabaja con los archivos reales del proyecto. No asume ni infiere sin leer.\n`,
407
+ '.agent/rules/orchestrator.md': `# Rol: ORCHESTRATOR — ${projectName}\n\n## Responsabilidad\nAnalizar el requerimiento, leer el contexto del proyecto y generar un plan de tareas claro y ejecutable.\n\n## Antes de planificar\n1. Leer .agent/context/architecture.md para entender los componentes\n2. Leer .agent/rules/structure.md para saber qué dirs son validos\n3. Leer .agent/rules/patterns.md para respetar convenciones del proyecto\n\n## Al generar el plan\n- Dividir el trabajo en pasos atomicos (1 paso = 1 cambio verificable)\n- Especificar rutas de archivos REALES (no genericas)\n- Indicar dependencias entre pasos si las hay\n\n## Prohibido\n- Modificar archivos de codigo directamente\n- Ejecutar comandos de build o test\n`,
408
+ '.agent/rules/implementor.md': `# Rol: IMPLEMENTOR — ${projectName}\n\n## Responsabilidad\nEjecutar el plan generado por el Orchestrator. Modificar archivos de codigo siguiendo las instrucciones exactas del plan.\n\n## Antes de implementar\n1. Leer .agent/tasks/{ID}/plan.json completo\n2. Leer .agent/rules/structure.md — no crear archivos en dirs prohibidos\n3. Leer .agent/rules/patterns.md — respetar convenciones\n\n## Al implementar\n- Seguir el plan paso a paso, en orden\n- No agregar cambios fuera del scope del plan\n- Marcar cada paso como completado al terminarlo\n\n## Prohibido\n- Modificar el plan\n- Hacer refactors no solicitados\n`,
409
+ '.agent/rules/reviewer.md': `# Rol: REVIEWER — ${projectName}\n\n## Responsabilidad\nValidar que la implementacion cumple el plan y respeta las reglas del proyecto.\n\n## Checklist de revision\n- [ ] Cada paso del plan fue implementado\n- [ ] No se modificaron archivos fuera del scope\n- [ ] Se respetan las convenciones de .agent/rules/patterns.md\n- [ ] La estructura cumple .agent/rules/structure.md\n- [ ] No hay codigo muerto ni imports sin usar\n\n## Al generar result.md\n- Indicar cada item como OK / FAIL / SKIP con razon\n- Si hay FAILs: describir exactamente que falta y en que archivo\n\n## Prohibido\n- Modificar archivos de codigo directamente\n- Aprobar implementaciones incompletas\n`,
410
+ '.agent/rules/structure.md': `# Estructura del proyecto — ${projectName}\n\n## Directorios aprobados\n- . (raiz del proyecto)\n\n## Directorios prohibidos\n- node_modules/\n- dist/\n- .git/\n- build/\n- target/\n- __pycache__/\n`,
411
+ '.agent/rules/patterns.md': `# Patrones y convenciones — ${projectName}\n\n## Nombrado\n- Archivos: kebab-case\n- Clases: PascalCase\n- Variables y funciones: camelCase\n- Constantes: UPPER_SNAKE_CASE\n\n## Reglas generales\n- No dejar console.log de debug en codigo productivo\n- Un archivo = una responsabilidad principal\n`,
412
+ '.agent/context/architecture.md': '# Arquitectura — NIVEL 0\n\n> Ejecuta `agent-mp explorer` para generar documentacion automatica.\n\n| Componente | Stack | Puerto | Proposito |\n|------------|-------|--------|-----------|\n| - | - | - | - |\n',
413
+ '.agent/AGENT.md': `# Instrucciones para Sub-Agentes — ${projectName}\n\n## Paso 1: Identificar tu rol\nLee .agent/INDEX.md para saber que archivo leer segun tu rol.\n\n## Paso 2: Leer el contexto\nAntes de cualquier accion, lee .agent/context/architecture.md.\n\n## Paso 3: Actua SOLO dentro de tu rol\nVer .agent/rules/workflow.md para las reglas de separacion de roles.\n`,
414
414
  };
415
415
  for (const [fp, content] of Object.entries(templates)) {
416
416
  await fs.mkdir(path.join(projectDir, path.dirname(fp)), { recursive: true });
@@ -751,7 +751,7 @@ function cmdHelp(fi) {
751
751
  { key: '/explorer [task]', value: 'Run explorer (shortcut)' },
752
752
  { key: '/models', value: 'List models for all installed CLIs' },
753
753
  { key: '/models <cli>', value: 'List models for a specific CLI' },
754
- { key: '/login', value: 'Login (Qwen OAuth or CLI auth)' },
754
+ { key: '/login', value: 'Configure API key' },
755
755
  { key: '/logout', value: 'Logout and clear credentials' },
756
756
  { key: '/auth-status', value: 'Show authentication status' },
757
757
  { key: '/usage', value: 'Check quota status for all role CLIs' },
@@ -806,101 +806,49 @@ export async function initCoordinator() {
806
806
  const auth = await loadAuth();
807
807
  const currentAuth = auth.activeProvider;
808
808
  let activeCli = installed.find((c) => c.name === currentAuth);
809
- // Check if corporate credentials exist for qwen
810
- const hasCorporateCreds = (() => {
811
- if (currentAuth === 'qwen') {
812
- const corporateCreds = path.join(QWEN_AGENT_HOME, 'oauth_creds.json');
813
- try {
814
- const stats = fsSync.statSync(corporateCreds);
815
- return stats.isFile();
816
- }
817
- catch {
818
- return false;
819
- }
820
- }
821
- return true;
822
- })();
823
- // If no active provider OR no corporate credentials, show selector
824
- if (!activeCli || !hasCorporateCreds) {
825
- // No coordinator configured - show selector
826
- const qwenInstalled = installed.find((c) => c.name === 'qwen');
827
- if (!qwenInstalled) {
828
- console.log(chalk.red(' Qwen CLI not found. Install with: npm install -g @qwen-code/qwen-code'));
809
+ // ── Fast-path: API key configured ────────────────────────────────────────
810
+ const apiKeyCfg = await loadApiKeyConfig();
811
+ if (apiKeyCfg?.api_key) {
812
+ const cliCfg = await loadCliConfig();
813
+ const model = cliCfg.coordinatorModel || apiKeyCfg.model;
814
+ gCoordinatorCmd = `qwen-direct -m ${model}`;
815
+ console.log(chalk.green(` ✓ Auth: ${apiKeyCfg.provider} / ${model}\n`));
816
+ const syntheticCli = {
817
+ name: apiKeyCfg.provider,
818
+ info: { command: 'qwen-direct', modelFlag: '-m', promptFlag: '-p', description: 'API key' },
819
+ path: 'qwen-direct',
820
+ };
821
+ return { coordinatorCmd: gCoordinatorCmd, activeCli: syntheticCli, installed, rl };
822
+ }
823
+ // ── No API key prompt to configure one ─────────────────────────────────
824
+ console.log(chalk.yellow('\n No auth configured.'));
825
+ console.log(chalk.dim(' Configure an API key to continue.\n'));
826
+ const doSetup = await new Promise((resolve) => {
827
+ rl.question(' Set up API key now? (y/N): ', (a) => resolve(a.trim().toLowerCase() === 'y'));
828
+ });
829
+ if (doSetup) {
830
+ const askFn = (q) => new Promise((resolve) => rl.question(q, resolve));
831
+ const ok = await promptApiKeySetup(rl, askFn);
832
+ if (!ok) {
829
833
  rl.close();
830
834
  return null;
831
835
  }
832
- // Show selector (even if only one option)
833
- const selectedName = await selectOption(rl, 'Select AI service for coordinator:', ['qwen']);
834
- activeCli = qwenInstalled;
835
- auth.activeProvider = 'qwen';
836
- auth.entries = auth.entries.filter((e) => e.provider !== 'qwen');
837
- auth.entries.push({ provider: 'qwen', method: 'oauth' });
838
- await saveAuth(auth);
839
- }
840
- // If activeCli is already set, skip selection and use existing coordinator
841
- console.log(chalk.dim(`\n Checking ${activeCli.name} auth...`));
842
- let authResult = await checkCliAuth(activeCli.name);
843
- if (!authResult.ok) {
844
- console.log(chalk.yellow(` ${activeCli.name} session not found.`));
845
- console.log(chalk.blue(` Opening browser for ${activeCli.name} login...`));
846
- console.log(chalk.dim(' (Use your corporate account)\n'));
847
- const authCmd = CLI_AUTH_COMMANDS[activeCli.name];
848
- if (authCmd) {
849
- try {
850
- // For qwen, do corporate login flow
851
- if (activeCli.name === 'qwen') {
852
- const personalCreds = path.join(os.homedir(), '.qwen', 'oauth_creds.json');
853
- const corporateCreds = path.join(QWEN_AGENT_HOME, 'oauth_creds.json');
854
- let personalBackup = null;
855
- if (await fileExists(personalCreds)) {
856
- personalBackup = await fs.readFile(personalCreds, 'utf-8');
857
- await fs.unlink(personalCreds);
858
- }
859
- await fs.unlink(corporateCreds).catch(() => { });
860
- execSync('qwen auth qwen-oauth', { stdio: 'inherit' });
861
- if (await fileExists(personalCreds)) {
862
- const newCreds = await fs.readFile(personalCreds, 'utf-8');
863
- await fs.writeFile(corporateCreds, newCreds);
864
- }
865
- if (personalBackup) {
866
- await fs.writeFile(personalCreds, personalBackup);
867
- }
868
- authResult = await checkCliAuth(activeCli.name);
869
- }
870
- else {
871
- execSync(authCmd, { stdio: 'inherit' });
872
- authResult = await checkCliAuth(activeCli.name);
873
- }
874
- if (authResult.ok) {
875
- console.log(chalk.green(` ${activeCli.name} authenticated successfully\n`));
876
- }
877
- else {
878
- console.log(chalk.red(` ${activeCli.name} auth failed. Try /login.\n`));
879
- }
880
- }
881
- catch {
882
- console.log(chalk.yellow(` Login interrupted. Type /login to retry.\n`));
883
- }
836
+ const saved = await loadApiKeyConfig();
837
+ if (!saved) {
838
+ rl.close();
839
+ return null;
884
840
  }
841
+ gCoordinatorCmd = `qwen-direct -m ${saved.model}`;
842
+ const syntheticCli = {
843
+ name: saved.provider,
844
+ info: { command: 'qwen-direct', modelFlag: '-m', promptFlag: '-p', description: 'API key' },
845
+ path: 'qwen-direct',
846
+ };
847
+ return { coordinatorCmd: gCoordinatorCmd, activeCli: syntheticCli, installed, rl };
885
848
  }
886
- else {
887
- const emailStr = authResult.email ? chalk.dim(` (${authResult.email})`) : '';
888
- console.log(chalk.green(` ${activeCli.name} session active${emailStr}\n`));
889
- }
890
- // Save email to auth store if we have it
891
- if (authResult.email) {
892
- const auth2 = await loadAuth();
893
- const entry = auth2.entries.find((e) => e.provider === activeCli.name);
894
- if (entry)
895
- entry.email = authResult.email;
896
- await saveAuth(auth2);
897
- }
898
- // Build coordinator command using the ACTIVE CLI (not orchestrator)
899
- // The coordinator converses naturally using whatever CLI is active (qwen, claude, etc.)
900
- const cliCfg = await loadCliConfig();
901
- const activeModel = cliCfg.coordinatorModel || detectModels(activeCli.name)[0] || 'default';
902
- gCoordinatorCmd = buildCmd(activeCli.name, activeModel);
903
- return { coordinatorCmd: gCoordinatorCmd, activeCli, installed, rl };
849
+ console.log(chalk.dim(' Run: agent-mp setup api-key'));
850
+ rl.close();
851
+ return null;
904
852
  }
905
853
  /** REPL mode — interactive loop */
906
854
  export async function runRepl(resumeSession) {
@@ -1105,8 +1053,9 @@ export async function runRepl(resumeSession) {
1105
1053
  await withRl(async (rl) => { await cmdLogin(rl); });
1106
1054
  break;
1107
1055
  case 'logout': {
1108
- const credsPath = path.join(QWEN_AGENT_HOME, 'oauth_creds.json');
1109
- await fs.unlink(credsPath).catch(() => { });
1056
+ const { getApiKeyConfigPath } = await import('../utils/qwen-auth.js');
1057
+ await fs.unlink(path.join(QWEN_AGENT_HOME, 'oauth_creds.json')).catch(() => { });
1058
+ await fs.unlink(await getApiKeyConfigPath()).catch(() => { });
1110
1059
  const authStore = await loadAuth();
1111
1060
  authStore.entries = [];
1112
1061
  delete authStore.activeProvider;
@@ -293,12 +293,15 @@ export function setupCommand(program) {
293
293
  console.log(chalk.green(' ✓ .agent/config.json generated'));
294
294
  // Generate templates
295
295
  const templates = {
296
- '.agent/INDEX.md': `# ${projectName} — Agent Entry Point\n\n> Leé este archivo antes de cualquier accion.\n\n## Proyecto\n\n## Navegacion por rol\n| Si sos... | Lee primero... |\n|-----------|----------------|\n| ORCHESTRATOR | .agent/rules/workflow.md |\n| IMPLEMENTOR | .agent/tasks/{ID}/plan.json |\n| REVIEWER | .agent/rules/workflow.md |\n`,
297
- '.agent/rules/workflow.md': '# Agent Workflow Contract\n\n## REGLA #1 — Separacion de roles\nUn rol NUNCA ejecuta acciones de otro rol.\n\n## REGLA #2 — Un CLI NO cambia de rol\n\n## Roles\n- ORCHESTRATOR: planifica, genera plan.json\n- IMPLEMENTOR: ejecuta el plan\n- REVIEWER: valida, genera result.md\n',
298
- '.agent/rules/structure.md': '# Estructura\n\n## Aprobados\n.\n\n## Prohibidos\nnode_modules, dist, .git\n',
299
- '.agent/rules/patterns.md': '# Patrones\n\n- kebab-case para archivos\n- PascalCase para clases\n- camelCase para variables\n',
300
- '.agent/context/architecture.md': '# ArquitecturaNIVEL 0\n\n| Servicio | Stack | Puerto | Detalle |\n|----------|-------|--------|---------|\n| - | - | - | - |\n',
301
- '.agent/AGENT.md': '# Sub-Agentes\n\n1. Lee .agent/INDEX.md\n2. Lee .agent/rules/workflow.md\n3. Actua SOLO dentro de tu rol\n',
296
+ '.agent/INDEX.md': `# ${projectName} — Agent Entry Point\n\n> Leé este archivo antes de cualquier accion.\n\n## Proyecto\n${projectName}\n\n## Navegacion por rol\n| Si sos... | Lee primero... | Luego... |\n|-----------|----------------|----------|\n| ORCHESTRATOR | .agent/rules/workflow.md | .agent/rules/orchestrator.md |\n| IMPLEMENTOR | .agent/rules/implementor.md | .agent/tasks/{ID}/plan.json |\n| REVIEWER | .agent/rules/reviewer.md | .agent/rules/workflow.md |\n\n## Contexto del proyecto\n- Arquitectura: .agent/context/architecture.md\n- Stack: ver .agent/config.json\n`,
297
+ '.agent/rules/workflow.md': `# Workflow Contract — ${projectName}\n\n## Flujo general\n\`\`\`\n[ORCHESTRATOR] planifica → genera plan.json\n ↓\n[IMPLEMENTOR] ejecuta el plan → modifica archivos\n ↓\n[REVIEWER] valida → genera result.md\n\`\`\`\n\n## REGLA #1 — Separacion de roles\nCada CLI opera SOLO en su rol. Nunca ejecuta acciones de otro rol.\n\n## REGLA #2 — Orden estricto\nOrchestrator siempre primero. Implementor no actua sin plan. Reviewer no actua sin implementacion.\n\n## REGLA #3 — Sin inventar\nCada rol trabaja con los archivos reales del proyecto. No asume ni infiere sin leer.\n\n## REGLA #4 Escalada\nSi un rol encuentra un bloqueante, lo reporta. No lo omite ni lo parchea silenciosamente.\n`,
298
+ '.agent/rules/orchestrator.md': `# Rol: ORCHESTRATOR — ${projectName}\n\n## Responsabilidad\nAnalizar el requerimiento, leer el contexto del proyecto y generar un plan de tareas claro y ejecutable.\n\n## Antes de planificar\n1. Leer .agent/context/architecture.md para entender los componentes\n2. Leer .agent/rules/structure.md para saber qué dirs son validos\n3. Leer .agent/rules/patterns.md para respetar convenciones del proyecto\n\n## Al generar el plan\n- Dividir el trabajo en pasos atómicos (1 paso = 1 cambio verificable)\n- Especificar rutas de archivos REALES (no genéricas)\n- Indicar dependencias entre pasos si las hay\n- Estimar impacto: qué puede romperse con cada cambio\n\n## Prohibido\n- Modificar archivos de código directamente\n- Ejecutar comandos de build o test\n- Tomar decisiones de arquitectura sin documentarlas en el plan\n`,
299
+ '.agent/rules/implementor.md': `# Rol: IMPLEMENTOR — ${projectName}\n\n## Responsabilidad\nEjecutar el plan generado por el Orchestrator. Modificar archivos de código siguiendo las instrucciones exactas del plan.\n\n## Antes de implementar\n1. Leer .agent/tasks/{ID}/plan.json completo\n2. Leer .agent/rules/structure.md — no crear archivos en dirs prohibidos\n3. Leer .agent/rules/patterns.md — respetar convenciones de nombres y estructura\n\n## Al implementar\n- Seguir el plan paso a paso, en orden\n- Si un paso no es claro, documentar la ambigüedad, no improvisar\n- Marcar cada paso como completado al terminarlo\n- No agregar cambios fuera del scope del plan\n\n## Prohibido\n- Modificar el plan (eso es rol del Orchestrator)\n- Hacer refactors no solicitados\n- Ignorar pasos del plan aunque parezcan redundantes\n`,
300
+ '.agent/rules/reviewer.md': `# Rol: REVIEWER ${projectName}\n\n## Responsabilidad\nValidar que la implementacion cumple el plan, no rompe nada existente y respeta las reglas del proyecto.\n\n## Checklist de revision\n- [ ] Cada paso del plan fue implementado\n- [ ] No se modificaron archivos fuera del scope del plan\n- [ ] Se respetan las convenciones de .agent/rules/patterns.md\n- [ ] La estructura de dirs cumple .agent/rules/structure.md\n- [ ] No hay codigo muerto ni imports sin usar introducidos\n- [ ] Si hay tests, pasaron\n\n## Al generar result.md\n- Indicar cada item del checklist como OK / FAIL / SKIP con razon\n- Si hay FAILs: describir exactamente qué falta y en qué archivo\n- Si todo OK: marcar la tarea como DONE\n\n## Prohibido\n- Modificar archivos de código directamente\n- Aprobar implementaciones incompletas\n- Omitir items del checklist\n`,
301
+ '.agent/rules/structure.md': `# Estructura del proyecto — ${projectName}\n\n## Directorios aprobados\n- . (raiz del proyecto)\n\n## Directorios prohibidos (no crear ni modificar archivos aca)\n- node_modules/\n- dist/\n- .git/\n- build/\n- target/\n- __pycache__/\n- .venv/\n\n## Convenciones de ubicacion\n- Codigo fuente: src/\n- Tests: src/**/*.test.* o tests/\n- Config: raiz del componente\n- Scripts: scripts/\n`,
302
+ '.agent/rules/patterns.md': `# Patrones y convenciones — ${projectName}\n\n## Nombrado\n- Archivos: kebab-case (mi-archivo.ts)\n- Clases: PascalCase (MiClase)\n- Variables y funciones: camelCase (miVariable)\n- Constantes: UPPER_SNAKE_CASE (MI_CONSTANTE)\n\n## Estructura de commits\n- feat: nueva funcionalidad\n- fix: correccion de bug\n- chore: tareas de mantenimiento\n- docs: solo documentacion\n\n## Reglas generales\n- No dejar console.log / print de debug en código productivo\n- Un archivo = una responsabilidad principal\n- Preferir cambios pequeños y verificables sobre refactors grandes\n`,
303
+ '.agent/context/architecture.md': '# Arquitectura — NIVEL 0\n\n> Ejecuta `agent-mp explorer` para generar documentacion automatica.\n\n| Componente | Stack | Puerto | Proposito |\n|------------|-------|--------|-----------|\n| - | - | - | - |\n',
304
+ '.agent/AGENT.md': `# Instrucciones para Sub-Agentes — ${projectName}\n\n## Paso 1: Identificar tu rol\nLee .agent/INDEX.md para saber que archivo leer segun tu rol.\n\n## Paso 2: Leer el contexto\nAntes de cualquier accion, lee .agent/context/architecture.md.\n\n## Paso 3: Actua SOLO dentro de tu rol\nVer .agent/rules/workflow.md para las reglas de separacion de roles.\n`,
302
305
  };
303
306
  for (const [fp, content] of Object.entries(templates)) {
304
307
  await fs.mkdir(path.join(projectDir, path.dirname(fp)), { recursive: true });
@@ -368,4 +371,59 @@ export function setupCommand(program) {
368
371
  addRoleCommand(setup, 'explorer', 'explorer', 'Configure explorer role only');
369
372
  addRoleCommand(setup, 'proposer', 'proposer', 'Configure proposer role only (deliberation)');
370
373
  addRoleCommand(setup, 'critic', 'critic', 'Configure critic role only (deliberation)');
374
+ // ── API key setup ─────────────────────────────────────────────────────────
375
+ setup
376
+ .command('api-key')
377
+ .description('Configure API key authentication (OpenAI-compatible, e.g. DashScope)')
378
+ .action(async () => {
379
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
380
+ const { saveApiKeyConfig, loadApiKeyConfig, fetchApiKeyModels, DEFAULT_API_BASE_URL } = await import('../utils/qwen-auth.js');
381
+ const existing = await loadApiKeyConfig();
382
+ console.log(chalk.bold.cyan('\n API Key Setup — OpenAI-compatible\n'));
383
+ if (existing) {
384
+ console.log(chalk.yellow(` Current: ${existing.provider} / ${existing.model}`));
385
+ console.log('');
386
+ }
387
+ const apiKey = await ask(rl, ` API Key${existing ? ' [Enter to keep]' : ''}: `);
388
+ const resolvedKey = apiKey.trim() || existing?.api_key || '';
389
+ if (!resolvedKey) {
390
+ console.log(chalk.red(' API key is required.'));
391
+ rl.close();
392
+ return;
393
+ }
394
+ // Fetch available models with the provided key
395
+ console.log(chalk.dim('\n Fetching available models...'));
396
+ const tempCfg = { provider: 'openai-compatible', api_key: resolvedKey, base_url: DEFAULT_API_BASE_URL, model: '' };
397
+ const models = await fetchApiKeyModels(tempCfg);
398
+ let chosenModel = existing?.model ?? 'qwen-plus';
399
+ if (models.length > 0) {
400
+ console.log(chalk.bold('\n Available models:'));
401
+ models.forEach((m, i) => console.log(chalk.dim(` ${i + 1}. ${m}`)));
402
+ const pick = await ask(rl, `\n Model [${chosenModel}]: `);
403
+ const num = parseInt(pick.trim());
404
+ if (!isNaN(num) && num >= 1 && num <= models.length) {
405
+ chosenModel = models[num - 1];
406
+ }
407
+ else if (pick.trim()) {
408
+ chosenModel = pick.trim();
409
+ }
410
+ }
411
+ else {
412
+ const pick = await ask(rl, ` Model [${chosenModel}]: `);
413
+ if (pick.trim())
414
+ chosenModel = pick.trim();
415
+ }
416
+ const cfg = {
417
+ provider: existing?.provider ?? 'openai-compatible',
418
+ api_key: resolvedKey,
419
+ base_url: DEFAULT_API_BASE_URL,
420
+ model: chosenModel,
421
+ };
422
+ await saveApiKeyConfig(cfg);
423
+ console.log(chalk.green('\n ✓ API key saved'));
424
+ console.log(chalk.dim(` Model: ${cfg.model}`));
425
+ console.log(chalk.dim(` Base URL: ${cfg.base_url}`));
426
+ console.log('');
427
+ rl.close();
428
+ });
371
429
  }
@@ -50,9 +50,16 @@ export declare class AgentEngine {
50
50
  * Allowed subdirectory files: <service>/architecture.md (not touched here)
51
51
  */
52
52
  private _cleanContextDir;
53
+ /**
54
+ * Pre-read the actual file contents (manifests, env, entry points, controllers, schemas)
55
+ * for every component in the project, so the LLM does not need tool-use to inspect them.
56
+ * This is critical when the explorer runs against the Qwen API (no tools available)
57
+ * to avoid the LLM hallucinating tool calls instead of producing the real document.
58
+ */
59
+ private _prefetchProjectFiles;
53
60
  runExplorer(task?: string): Promise<string>;
54
- /** Called when the current binary IS the configured explorer CLI (prevents recursion).
55
- * Builds the full exploration prompt and calls Qwen API using own credentials. */
61
+ /** Legacy entry point delegates to runExplorer.
62
+ * Kept for backward compatibility with anything that may still call it. */
56
63
  runExplorerDirect(task?: string): Promise<string>;
57
64
  runFullCycle(task: string): Promise<void>;
58
65
  }