beercan 0.6.12 → 0.6.14

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.
Files changed (91) hide show
  1. package/README.md +60 -10
  2. package/dist/chat/index.d.ts +3 -3
  3. package/dist/chat/index.d.ts.map +1 -1
  4. package/dist/chat/index.js +4 -4
  5. package/dist/chat/index.js.map +1 -1
  6. package/dist/chat/intent.d.ts +2 -2
  7. package/dist/chat/intent.d.ts.map +1 -1
  8. package/dist/chat/intent.js +7 -7
  9. package/dist/chat/intent.js.map +1 -1
  10. package/dist/cli.js +269 -23
  11. package/dist/cli.js.map +1 -1
  12. package/dist/config.d.ts +12 -0
  13. package/dist/config.d.ts.map +1 -1
  14. package/dist/config.js +9 -0
  15. package/dist/config.js.map +1 -1
  16. package/dist/core/gatekeeper.d.ts +3 -3
  17. package/dist/core/gatekeeper.d.ts.map +1 -1
  18. package/dist/core/gatekeeper.js +12 -12
  19. package/dist/core/gatekeeper.js.map +1 -1
  20. package/dist/core/reflection.d.ts +3 -3
  21. package/dist/core/reflection.d.ts.map +1 -1
  22. package/dist/core/reflection.js +10 -10
  23. package/dist/core/reflection.js.map +1 -1
  24. package/dist/core/roles.js +1 -1
  25. package/dist/core/roles.js.map +1 -1
  26. package/dist/core/runner.d.ts +3 -3
  27. package/dist/core/runner.d.ts.map +1 -1
  28. package/dist/core/runner.js +12 -14
  29. package/dist/core/runner.js.map +1 -1
  30. package/dist/events/daemon.js +3 -3
  31. package/dist/events/daemon.js.map +1 -1
  32. package/dist/index.d.ts +30 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +50 -5
  35. package/dist/index.js.map +1 -1
  36. package/dist/providers/anthropic.d.ts +8 -0
  37. package/dist/providers/anthropic.d.ts.map +1 -0
  38. package/dist/providers/anthropic.js +70 -0
  39. package/dist/providers/anthropic.js.map +1 -0
  40. package/dist/providers/factory.d.ts +3 -0
  41. package/dist/providers/factory.d.ts.map +1 -0
  42. package/dist/providers/factory.js +58 -0
  43. package/dist/providers/factory.js.map +1 -0
  44. package/dist/providers/index.d.ts +5 -0
  45. package/dist/providers/index.d.ts.map +1 -0
  46. package/dist/providers/index.js +4 -0
  47. package/dist/providers/index.js.map +1 -0
  48. package/dist/providers/openai.d.ts +16 -0
  49. package/dist/providers/openai.d.ts.map +1 -0
  50. package/dist/providers/openai.js +176 -0
  51. package/dist/providers/openai.js.map +1 -0
  52. package/dist/providers/types.d.ts +54 -0
  53. package/dist/providers/types.d.ts.map +1 -0
  54. package/dist/providers/types.js +4 -0
  55. package/dist/providers/types.js.map +1 -0
  56. package/dist/skills/index.d.ts.map +1 -1
  57. package/dist/skills/index.js +44 -0
  58. package/dist/skills/index.js.map +1 -1
  59. package/dist/tools/builtin/email.d.ts +5 -0
  60. package/dist/tools/builtin/email.d.ts.map +1 -0
  61. package/dist/tools/builtin/email.js +171 -0
  62. package/dist/tools/builtin/email.js.map +1 -0
  63. package/dist/tools/registry.d.ts +3 -12
  64. package/dist/tools/registry.d.ts.map +1 -1
  65. package/dist/tools/registry.js +3 -3
  66. package/dist/tools/registry.js.map +1 -1
  67. package/dist/training/curriculum.d.ts +4 -0
  68. package/dist/training/curriculum.d.ts.map +1 -0
  69. package/dist/training/curriculum.js +512 -0
  70. package/dist/training/curriculum.js.map +1 -0
  71. package/dist/training/evaluator.d.ts +21 -0
  72. package/dist/training/evaluator.d.ts.map +1 -0
  73. package/dist/training/evaluator.js +163 -0
  74. package/dist/training/evaluator.js.map +1 -0
  75. package/dist/training/exporter.d.ts +35 -0
  76. package/dist/training/exporter.d.ts.map +1 -0
  77. package/dist/training/exporter.js +377 -0
  78. package/dist/training/exporter.js.map +1 -0
  79. package/dist/training/index.d.ts +5 -0
  80. package/dist/training/index.d.ts.map +1 -0
  81. package/dist/training/index.js +5 -0
  82. package/dist/training/index.js.map +1 -0
  83. package/dist/training/sandbox-manager.d.ts +58 -0
  84. package/dist/training/sandbox-manager.d.ts.map +1 -0
  85. package/dist/training/sandbox-manager.js +416 -0
  86. package/dist/training/sandbox-manager.js.map +1 -0
  87. package/dist/training/types.d.ts +790 -0
  88. package/dist/training/types.d.ts.map +1 -0
  89. package/dist/training/types.js +154 -0
  90. package/dist/training/types.js.map +1 -0
  91. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -77,34 +77,72 @@ async function runSetup() {
77
77
  }
78
78
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
79
79
  try {
80
- // Required
81
80
  const mask = (v) => v ? v.slice(0, 12) + "..." : "";
82
- console.log(chalk.bold("1. Anthropic API Key") + chalk.red(" (required)"));
83
- console.log(chalk.dim(" Get one at: https://console.anthropic.com/\n"));
84
- const apiKey = (await prompt(rl, chalk.cyan(` ANTHROPIC_API_KEY [${mask(existing.ANTHROPIC_API_KEY)}]: `))).trim()
85
- || existing.ANTHROPIC_API_KEY || "";
86
- if (!apiKey) {
87
- console.log(chalk.red("\n API key is required. Aborting setup."));
88
- return;
81
+ // Provider selection
82
+ console.log(chalk.bold("1. LLM Provider"));
83
+ console.log(chalk.dim(" Options: anthropic, openai, openai-compatible (LM Studio, Ollama, OpenRouter)\n"));
84
+ const llmProvider = (await prompt(rl, chalk.cyan(` BEERCAN_LLM_PROVIDER [${existing.BEERCAN_LLM_PROVIDER || "anthropic"}]: `))).trim()
85
+ || existing.BEERCAN_LLM_PROVIDER || "anthropic";
86
+ // API key(s) based on provider
87
+ let apiKey = "";
88
+ let openaiApiKey = "";
89
+ let llmApiKey = "";
90
+ let llmBaseUrl = "";
91
+ if (llmProvider === "anthropic") {
92
+ console.log(chalk.bold("\n2. Anthropic API Key") + chalk.red(" (required)"));
93
+ console.log(chalk.dim(" Get one at: https://console.anthropic.com/\n"));
94
+ apiKey = (await prompt(rl, chalk.cyan(` ANTHROPIC_API_KEY [${mask(existing.ANTHROPIC_API_KEY)}]: `))).trim()
95
+ || existing.ANTHROPIC_API_KEY || "";
96
+ if (!apiKey) {
97
+ console.log(chalk.red("\n API key is required. Aborting setup."));
98
+ return;
99
+ }
100
+ }
101
+ else if (llmProvider === "openai") {
102
+ console.log(chalk.bold("\n2. OpenAI API Key") + chalk.red(" (required)"));
103
+ console.log(chalk.dim(" Get one at: https://platform.openai.com/\n"));
104
+ openaiApiKey = (await prompt(rl, chalk.cyan(` OPENAI_API_KEY [${mask(existing.OPENAI_API_KEY)}]: `))).trim()
105
+ || existing.OPENAI_API_KEY || "";
106
+ if (!openaiApiKey) {
107
+ console.log(chalk.red("\n API key is required. Aborting setup."));
108
+ return;
109
+ }
89
110
  }
90
- // Optional — Models
91
- console.log(chalk.bold("\n2. Models") + chalk.dim(" (press Enter for defaults)"));
92
- const defaultModel = (await prompt(rl, chalk.cyan(` Default model [${existing.BEERCAN_DEFAULT_MODEL || "claude-sonnet-4-6"}]: `))).trim()
111
+ else {
112
+ console.log(chalk.bold("\n2. OpenAI-Compatible Endpoint") + chalk.red(" (required)"));
113
+ console.log(chalk.dim(" e.g., http://localhost:1234/v1 (LM Studio), http://localhost:11434/v1 (Ollama)\n"));
114
+ llmBaseUrl = (await prompt(rl, chalk.cyan(` BEERCAN_LLM_BASE_URL [${existing.BEERCAN_LLM_BASE_URL || ""}]: `))).trim()
115
+ || existing.BEERCAN_LLM_BASE_URL || "";
116
+ if (!llmBaseUrl) {
117
+ console.log(chalk.red("\n Base URL is required for openai-compatible. Aborting setup."));
118
+ return;
119
+ }
120
+ llmApiKey = (await prompt(rl, chalk.cyan(` BEERCAN_LLM_API_KEY [${mask(existing.BEERCAN_LLM_API_KEY) || "not-needed"}]: `))).trim()
121
+ || existing.BEERCAN_LLM_API_KEY || "";
122
+ }
123
+ // Models — show provider-appropriate defaults
124
+ const modelDefaults = llmProvider === "anthropic"
125
+ ? { default: "claude-sonnet-4-6", heavy: "claude-opus-4-6" }
126
+ : llmProvider === "openai"
127
+ ? { default: "gpt-4o", heavy: "gpt-4o" }
128
+ : { default: "default", heavy: "default" };
129
+ console.log(chalk.bold("\n3. Models") + chalk.dim(" (press Enter for defaults)"));
130
+ const defaultModel = (await prompt(rl, chalk.cyan(` Default model [${existing.BEERCAN_DEFAULT_MODEL || modelDefaults.default}]: `))).trim()
93
131
  || existing.BEERCAN_DEFAULT_MODEL || "";
94
- const heavyModel = (await prompt(rl, chalk.cyan(` Heavy model [${existing.BEERCAN_HEAVY_MODEL || "claude-opus-4-6"}]: `))).trim()
132
+ const heavyModel = (await prompt(rl, chalk.cyan(` Heavy model [${existing.BEERCAN_HEAVY_MODEL || modelDefaults.heavy}]: `))).trim()
95
133
  || existing.BEERCAN_HEAVY_MODEL || "";
96
134
  // Optional — Cloudflare
97
- console.log(chalk.bold("\n3. Cloudflare Browser Rendering") + chalk.dim(" (optional, for web_fetch)"));
135
+ console.log(chalk.bold("\n4. Cloudflare Browser Rendering") + chalk.dim(" (optional, for web_fetch)"));
98
136
  const cfToken = (await prompt(rl, chalk.cyan(` CLOUDFLARE_API_TOKEN [${mask(existing.CLOUDFLARE_API_TOKEN)}]: `))).trim()
99
137
  || existing.CLOUDFLARE_API_TOKEN || "";
100
138
  const cfAccount = (await prompt(rl, chalk.cyan(` CLOUDFLARE_ACCOUNT_ID [${mask(existing.CLOUDFLARE_ACCOUNT_ID)}]: `))).trim()
101
139
  || existing.CLOUDFLARE_ACCOUNT_ID || "";
102
140
  // Optional — Security
103
- console.log(chalk.bold("\n4. API Security") + chalk.dim(" (optional, for REST API auth)"));
141
+ console.log(chalk.bold("\n5. API Security") + chalk.dim(" (optional, for REST API auth)"));
104
142
  const beercanApiKey = (await prompt(rl, chalk.cyan(` BEERCAN_API_KEY [${mask(existing.BEERCAN_API_KEY)}]: `))).trim()
105
143
  || existing.BEERCAN_API_KEY || "";
106
144
  // Optional — Chat
107
- console.log(chalk.bold("\n5. Chat Providers") + chalk.dim(" (optional, for Telegram/Slack bots)"));
145
+ console.log(chalk.bold("\n6. Chat Providers") + chalk.dim(" (optional, for Telegram/Slack bots)"));
108
146
  const telegramToken = (await prompt(rl, chalk.cyan(` BEERCAN_TELEGRAM_TOKEN [${mask(existing.BEERCAN_TELEGRAM_TOKEN)}]: `))).trim()
109
147
  || existing.BEERCAN_TELEGRAM_TOKEN || "";
110
148
  const slackToken = (await prompt(rl, chalk.cyan(` BEERCAN_SLACK_TOKEN [${mask(existing.BEERCAN_SLACK_TOKEN)}]: `))).trim()
@@ -112,7 +150,7 @@ async function runSetup() {
112
150
  const slackSecret = (await prompt(rl, chalk.cyan(` BEERCAN_SLACK_SIGNING_SECRET [${mask(existing.BEERCAN_SLACK_SIGNING_SECRET)}]: `))).trim()
113
151
  || existing.BEERCAN_SLACK_SIGNING_SECRET || "";
114
152
  // Optional — Notifications
115
- console.log(chalk.bold("\n6. Notifications") + chalk.dim(" (optional)"));
153
+ console.log(chalk.bold("\n7. Notifications") + chalk.dim(" (optional)"));
116
154
  const webhookUrl = (await prompt(rl, chalk.cyan(` BEERCAN_NOTIFY_WEBHOOK_URL [${existing.BEERCAN_NOTIFY_WEBHOOK_URL || ""}]: `))).trim()
117
155
  || existing.BEERCAN_NOTIFY_WEBHOOK_URL || "";
118
156
  // Build .env content
@@ -120,10 +158,19 @@ async function runSetup() {
120
158
  `# BeerCan Configuration`,
121
159
  `# Generated by: beercan setup`,
122
160
  ``,
123
- `ANTHROPIC_API_KEY=${apiKey}`,
161
+ `# LLM Provider: anthropic, openai, openai-compatible`,
162
+ `BEERCAN_LLM_PROVIDER=${llmProvider}`,
124
163
  ];
164
+ if (apiKey)
165
+ lines.push(`ANTHROPIC_API_KEY=${apiKey}`);
166
+ if (openaiApiKey)
167
+ lines.push(`OPENAI_API_KEY=${openaiApiKey}`);
168
+ if (llmApiKey)
169
+ lines.push(`BEERCAN_LLM_API_KEY=${llmApiKey}`);
170
+ if (llmBaseUrl)
171
+ lines.push(`BEERCAN_LLM_BASE_URL=${llmBaseUrl}`);
125
172
  if (defaultModel)
126
- lines.push(`BEERCAN_DEFAULT_MODEL=${defaultModel}`);
173
+ lines.push(``, `# Models`, `BEERCAN_DEFAULT_MODEL=${defaultModel}`);
127
174
  if (heavyModel)
128
175
  lines.push(`BEERCAN_HEAVY_MODEL=${heavyModel}`);
129
176
  if (cfToken)
@@ -921,11 +968,11 @@ Do NOT rewrite everything — make focused, incremental changes.`,
921
968
  // ── Chat Mode ───────────────────────────────────────────
922
969
  case "chat": {
923
970
  const chatProject = args[1];
924
- const { createAnthropicClient } = await import("./client.js");
971
+ const { createLLMProvider } = await import("./providers/factory.js");
925
972
  const { ChatBridge } = await import("./chat/index.js");
926
973
  const { TerminalProvider } = await import("./chat/providers/terminal.js");
927
- const client = await createAnthropicClient();
928
- const bridge = new ChatBridge(engine, client);
974
+ const provider = await createLLMProvider();
975
+ const bridge = new ChatBridge(engine, provider);
929
976
  const terminal = new TerminalProvider();
930
977
  bridge.addProvider(terminal);
931
978
  if (chatProject) {
@@ -1255,6 +1302,197 @@ Do NOT rewrite everything — make focused, incremental changes.`,
1255
1302
  rl.close();
1256
1303
  break;
1257
1304
  }
1305
+ // ── Training Commands ───────────────────────────────────
1306
+ case "training:create": {
1307
+ const traineeName = args[1];
1308
+ if (!traineeName) {
1309
+ console.log(chalk.red("Usage: beercan training:create <name> [--work-dir <path>]"));
1310
+ console.log(chalk.dim("Creates a new training sandbox project for an agent."));
1311
+ break;
1312
+ }
1313
+ const wdIdx = args.indexOf("--work-dir");
1314
+ const workDir = wdIdx >= 0 ? args[wdIdx + 1] : undefined;
1315
+ try {
1316
+ const project = await engine.createTrainee(traineeName, workDir);
1317
+ console.log(chalk.green(`✓ Created training project: ${project.name}`));
1318
+ console.log(chalk.dim(` Slug: ${project.slug}`));
1319
+ console.log(chalk.dim(` ID: ${project.id}`));
1320
+ if (workDir)
1321
+ console.log(chalk.dim(` Work dir: ${workDir}`));
1322
+ console.log(chalk.dim(`\nNext: beercan training:run ${project.slug}`));
1323
+ }
1324
+ catch (err) {
1325
+ console.log(chalk.red(`Failed to create trainee: ${err.message}`));
1326
+ }
1327
+ break;
1328
+ }
1329
+ case "training:run": {
1330
+ const trainingProject = args[1];
1331
+ if (!trainingProject) {
1332
+ console.log(chalk.red("Usage: beercan training:run <project> [--scenario <id>]"));
1333
+ console.log(chalk.dim("Runs the next (or a specific) training scenario."));
1334
+ break;
1335
+ }
1336
+ const scenarioIdx = args.indexOf("--scenario");
1337
+ const scenarioId = scenarioIdx >= 0 ? args[scenarioIdx + 1] : undefined;
1338
+ try {
1339
+ const status = await engine.getTrainingStatus(trainingProject);
1340
+ if (scenarioId) {
1341
+ console.log(chalk.bold(`\nRunning scenario: ${scenarioId}`));
1342
+ }
1343
+ else if (status.nextScenario) {
1344
+ console.log(chalk.bold(`\nRunning next scenario: ${status.nextScenario.name}`));
1345
+ console.log(chalk.dim(` Difficulty: ${status.nextScenario.difficulty}`));
1346
+ console.log(chalk.dim(` Category: ${status.nextScenario.category}`));
1347
+ console.log(chalk.dim(` Goal: ${status.nextScenario.goal.slice(0, 80)}...`));
1348
+ }
1349
+ else {
1350
+ console.log(chalk.yellow("No scenarios available. Check status with: beercan training:status " + trainingProject));
1351
+ break;
1352
+ }
1353
+ console.log();
1354
+ const attempt = await engine.runTrainingScenario(trainingProject, scenarioId);
1355
+ const statusColor = attempt.status === "pass"
1356
+ ? chalk.green
1357
+ : attempt.status === "fail"
1358
+ ? chalk.yellow
1359
+ : chalk.red;
1360
+ console.log(statusColor(`\n${attempt.status === "pass" ? "✓ PASSED" : attempt.status === "fail" ? "✗ FAILED" : "! ERROR"}`));
1361
+ console.log(` Score: ${(attempt.score * 100).toFixed(0)}%`);
1362
+ console.log(` Feedback: ${attempt.feedback}`);
1363
+ console.log(chalk.dim(` Tokens: ${attempt.tokensUsed.toLocaleString()}`));
1364
+ console.log(chalk.dim(` Duration: ${(attempt.durationMs / 1000).toFixed(1)}s`));
1365
+ // Show updated status
1366
+ const newStatus = await engine.getTrainingStatus(trainingProject);
1367
+ if (newStatus.progress.graduationStatus === "graduated") {
1368
+ console.log(chalk.bold.green("\nCongratulations! Agent has graduated!"));
1369
+ }
1370
+ else if (newStatus.nextScenario) {
1371
+ console.log(chalk.dim(`\nNext scenario: ${newStatus.nextScenario.name} — run again to continue`));
1372
+ }
1373
+ }
1374
+ catch (err) {
1375
+ console.log(chalk.red(`Training run failed: ${err.message}`));
1376
+ }
1377
+ break;
1378
+ }
1379
+ case "training:status": {
1380
+ const statusProject = args[1];
1381
+ if (!statusProject) {
1382
+ console.log(chalk.red("Usage: beercan training:status <project>"));
1383
+ break;
1384
+ }
1385
+ try {
1386
+ const { progress, nextScenario, summary } = await engine.getTrainingStatus(statusProject);
1387
+ const gradColor = progress.graduationStatus === "graduated"
1388
+ ? chalk.bold.green
1389
+ : progress.graduationStatus === "failed"
1390
+ ? chalk.red
1391
+ : chalk.yellow;
1392
+ console.log(chalk.bold(`\nTraining Status: ${statusProject}\n`));
1393
+ console.log(` Graduation: ${gradColor(progress.graduationStatus)}`);
1394
+ console.log(` Level: ${progress.currentLevel}`);
1395
+ console.log(` Passed: ${progress.passedScenarios.length} scenarios`);
1396
+ console.log(` Bloops: ${progress.totalBloops}`);
1397
+ console.log(` Tokens: ${progress.totalTokensUsed.toLocaleString()}`);
1398
+ if (progress.startedAt) {
1399
+ console.log(chalk.dim(` Started: ${progress.startedAt.slice(0, 19)}`));
1400
+ }
1401
+ if (progress.graduatedAt) {
1402
+ console.log(chalk.dim(` Graduated: ${progress.graduatedAt.slice(0, 19)}`));
1403
+ }
1404
+ if (nextScenario) {
1405
+ console.log(chalk.bold(`\nNext Scenario:`));
1406
+ console.log(` ${chalk.cyan(nextScenario.name)} (${nextScenario.difficulty})`);
1407
+ console.log(chalk.dim(` Category: ${nextScenario.category}`));
1408
+ console.log(chalk.dim(` Tests: ${nextScenario.teaches.slice(0, 3).join(", ")}`));
1409
+ }
1410
+ if (progress.passedScenarios.length > 0) {
1411
+ console.log(chalk.bold(`\nPassed Scenarios:`));
1412
+ for (const id of progress.passedScenarios) {
1413
+ console.log(chalk.green(` ✓ ${id}`));
1414
+ }
1415
+ }
1416
+ if (progress.failedScenarios.length > 0) {
1417
+ console.log(chalk.bold(`\nFailed Scenarios:`));
1418
+ for (const f of progress.failedScenarios) {
1419
+ console.log(chalk.red(` ✗ ${f.id} (${f.attempts} attempt${f.attempts !== 1 ? "s" : ""})`));
1420
+ }
1421
+ }
1422
+ }
1423
+ catch (err) {
1424
+ console.log(chalk.red(`Failed to get training status: ${err.message}`));
1425
+ }
1426
+ break;
1427
+ }
1428
+ case "training:export": {
1429
+ const exportProject = args[1];
1430
+ if (!exportProject) {
1431
+ console.log(chalk.red("Usage: beercan training:export <project> [--output <path>]"));
1432
+ break;
1433
+ }
1434
+ const outIdx = args.indexOf("--output");
1435
+ const outputPath = outIdx >= 0
1436
+ ? args[outIdx + 1]
1437
+ : `./${exportProject}-agent-package.json`;
1438
+ try {
1439
+ console.log(chalk.dim(`Exporting agent: ${exportProject} → ${outputPath}`));
1440
+ await engine.exportAgent(exportProject, outputPath);
1441
+ console.log(chalk.green(`✓ Agent exported: ${outputPath}`));
1442
+ console.log(chalk.dim(` Import with: beercan training:import ${outputPath}`));
1443
+ }
1444
+ catch (err) {
1445
+ console.log(chalk.red(`Export failed: ${err.message}`));
1446
+ }
1447
+ break;
1448
+ }
1449
+ case "training:import": {
1450
+ const importPath = args[1];
1451
+ if (!importPath) {
1452
+ console.log(chalk.red("Usage: beercan training:import <package-path> [--name <slug>] [--global] [--project <slug>]"));
1453
+ console.log(chalk.dim(" --global Import only skills & tools globally (no project created)"));
1454
+ console.log(chalk.dim(" --project <slug> Import into an existing project"));
1455
+ console.log(chalk.dim(" --name <slug> Create a new project with this slug (default: from package)"));
1456
+ break;
1457
+ }
1458
+ const isGlobal = args.includes("--global");
1459
+ const projectIdx = args.indexOf("--project");
1460
+ const targetProject = projectIdx >= 0 ? args[projectIdx + 1] : undefined;
1461
+ const nameIdx = args.indexOf("--name");
1462
+ const targetSlug = nameIdx >= 0 ? args[nameIdx + 1] : undefined;
1463
+ try {
1464
+ if (isGlobal) {
1465
+ // Global import: skills + tools only, no project
1466
+ console.log(chalk.dim(`Importing skills & tools globally from: ${importPath}`));
1467
+ const { skills, tools } = await engine.importAgentGlobal(importPath);
1468
+ console.log(chalk.green(`✓ Global import complete`));
1469
+ console.log(chalk.dim(` Skills imported: ${skills}`));
1470
+ console.log(chalk.dim(` Tools imported: ${tools}`));
1471
+ }
1472
+ else if (targetProject) {
1473
+ // Import into existing project
1474
+ console.log(chalk.dim(`Importing agent into project: ${targetProject}`));
1475
+ const project = await engine.importAgentIntoProject(importPath, targetProject);
1476
+ console.log(chalk.green(`✓ Agent imported into: ${project.name}`));
1477
+ console.log(chalk.dim(` Slug: ${project.slug}`));
1478
+ }
1479
+ else {
1480
+ // Default: create new project from package
1481
+ console.log(chalk.dim(`Importing agent package: ${importPath}`));
1482
+ const project = await engine.importAgent(importPath, targetSlug);
1483
+ console.log(chalk.green(`✓ Agent imported: ${project.name}`));
1484
+ console.log(chalk.dim(` Slug: ${project.slug}`));
1485
+ console.log(chalk.dim(` ID: ${project.id}`));
1486
+ if (targetSlug && targetSlug !== project.slug) {
1487
+ console.log(chalk.dim(` Note: imported as ${project.slug}`));
1488
+ }
1489
+ }
1490
+ }
1491
+ catch (err) {
1492
+ console.log(chalk.red(`Import failed: ${err.message}`));
1493
+ }
1494
+ break;
1495
+ }
1258
1496
  // ── Help ────────────────────────────────────────────────
1259
1497
  default:
1260
1498
  console.log(chalk.bold("BeerCan") + chalk.dim(" — Autonomous agent system\n"));
@@ -1286,6 +1524,14 @@ Do NOT rewrite everything — make focused, incremental changes.`,
1286
1524
  console.log(chalk.cyan(" mcp:add <project> <name> <cmd> [args]") + chalk.dim(" Add MCP server"));
1287
1525
  console.log(chalk.cyan(" mcp:list <project>") + chalk.dim(" List MCP servers"));
1288
1526
  console.log();
1527
+ console.log(chalk.bold("Training:"));
1528
+ console.log(chalk.cyan(" training:create <name> [--work-dir <path>]") + chalk.dim(" Create a training sandbox project"));
1529
+ console.log(chalk.cyan(" training:run <project> [--scenario <id>]") + chalk.dim(" Run next (or specific) scenario"));
1530
+ console.log(chalk.cyan(" training:status <project>") + chalk.dim(" Show training progress"));
1531
+ console.log(chalk.cyan(" training:export <project> [--output <path>]") + chalk.dim(" Export agent package"));
1532
+ console.log(chalk.cyan(" training:import <package> [options]") + chalk.dim(" Import agent package"));
1533
+ console.log(chalk.dim(" --name <slug> Create new project with slug | --global Skills & tools only | --project <slug> Into existing"));
1534
+ console.log();
1289
1535
  console.log(chalk.bold("Encryption:"));
1290
1536
  console.log(chalk.cyan(" crypto:setup") + chalk.dim(" Configure encryption (passphrase or keyfile)"));
1291
1537
  console.log(chalk.cyan(" crypto:status") + chalk.dim(" Show encryption status"));
@@ -1311,8 +1557,8 @@ Do NOT rewrite everything — make focused, incremental changes.`,
1311
1557
  }
1312
1558
  main().catch((err) => {
1313
1559
  const msg = String(err?.message ?? err);
1314
- if (msg.includes("anthropicApiKey") || msg.includes("ANTHROPIC_API_KEY") || msg.includes("Required")) {
1315
- console.error(chalk.red("\nMissing API key.") + " Run " + chalk.cyan("beercan setup") + " to configure BeerCan.\n");
1560
+ if (msg.includes("API_KEY") || msg.includes("api key") || msg.includes("Api key") || msg.includes("not set") || msg.includes("Required")) {
1561
+ console.error(chalk.red("\nMissing API key or provider config.") + " Run " + chalk.cyan("beercan setup") + " to configure BeerCan.\n");
1316
1562
  }
1317
1563
  else {
1318
1564
  console.error(chalk.red("Fatal:"), msg);