mycontext-cli 0.4.19 → 1.0.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.
Files changed (98) hide show
  1. package/README.md +14 -11
  2. package/dist/agents/implementations/BackendDevAgent.d.ts +39 -0
  3. package/dist/agents/implementations/BackendDevAgent.d.ts.map +1 -0
  4. package/dist/agents/implementations/BackendDevAgent.js +821 -0
  5. package/dist/agents/implementations/BackendDevAgent.js.map +1 -0
  6. package/dist/agents/implementations/CodeGenSubAgent.d.ts +1 -1
  7. package/dist/agents/implementations/CodeGenSubAgent.d.ts.map +1 -1
  8. package/dist/agents/implementations/CodeGenSubAgent.js +34 -22
  9. package/dist/agents/implementations/CodeGenSubAgent.js.map +1 -1
  10. package/dist/agents/implementations/DocsSubAgent.d.ts +7 -0
  11. package/dist/agents/implementations/DocsSubAgent.d.ts.map +1 -1
  12. package/dist/agents/implementations/DocsSubAgent.js +20 -11
  13. package/dist/agents/implementations/DocsSubAgent.js.map +1 -1
  14. package/dist/agents/implementations/EnhancementAgent.d.ts +1 -1
  15. package/dist/agents/implementations/EnhancementAgent.d.ts.map +1 -1
  16. package/dist/agents/implementations/EnhancementAgent.js +17 -12
  17. package/dist/agents/implementations/EnhancementAgent.js.map +1 -1
  18. package/dist/cli.js +191 -39
  19. package/dist/cli.js.map +1 -1
  20. package/dist/commands/analyze.d.ts +52 -0
  21. package/dist/commands/analyze.d.ts.map +1 -0
  22. package/dist/commands/analyze.js +740 -0
  23. package/dist/commands/analyze.js.map +1 -0
  24. package/dist/commands/auth.d.ts +19 -26
  25. package/dist/commands/auth.d.ts.map +1 -1
  26. package/dist/commands/auth.js +187 -180
  27. package/dist/commands/auth.js.map +1 -1
  28. package/dist/commands/build-app.d.ts +33 -0
  29. package/dist/commands/build-app.d.ts.map +1 -0
  30. package/dist/commands/build-app.js +507 -0
  31. package/dist/commands/build-app.js.map +1 -0
  32. package/dist/commands/generate-components.d.ts +2 -0
  33. package/dist/commands/generate-components.d.ts.map +1 -1
  34. package/dist/commands/generate-components.js +95 -49
  35. package/dist/commands/generate-components.js.map +1 -1
  36. package/dist/commands/generate.d.ts +3 -0
  37. package/dist/commands/generate.d.ts.map +1 -1
  38. package/dist/commands/generate.js +250 -93
  39. package/dist/commands/generate.js.map +1 -1
  40. package/dist/commands/init.d.ts +5 -0
  41. package/dist/commands/init.d.ts.map +1 -1
  42. package/dist/commands/init.js +86 -7
  43. package/dist/commands/init.js.map +1 -1
  44. package/dist/commands/list.d.ts.map +1 -1
  45. package/dist/commands/list.js +10 -7
  46. package/dist/commands/list.js.map +1 -1
  47. package/dist/commands/migrate.d.ts +29 -0
  48. package/dist/commands/migrate.d.ts.map +1 -0
  49. package/dist/commands/migrate.js +485 -0
  50. package/dist/commands/migrate.js.map +1 -0
  51. package/dist/commands/model.d.ts +13 -16
  52. package/dist/commands/model.d.ts.map +1 -1
  53. package/dist/commands/model.js +86 -320
  54. package/dist/commands/model.js.map +1 -1
  55. package/dist/commands/playbooks.d.ts +33 -0
  56. package/dist/commands/playbooks.d.ts.map +1 -0
  57. package/dist/commands/playbooks.js +662 -0
  58. package/dist/commands/playbooks.js.map +1 -0
  59. package/dist/commands/pricing.d.ts +15 -0
  60. package/dist/commands/pricing.d.ts.map +1 -0
  61. package/dist/commands/pricing.js +129 -0
  62. package/dist/commands/pricing.js.map +1 -0
  63. package/dist/commands/promote.d.ts +22 -0
  64. package/dist/commands/promote.d.ts.map +1 -0
  65. package/dist/commands/promote.js +204 -0
  66. package/dist/commands/promote.js.map +1 -0
  67. package/dist/commands/setup.d.ts +5 -18
  68. package/dist/commands/setup.d.ts.map +1 -1
  69. package/dist/commands/setup.js +180 -340
  70. package/dist/commands/setup.js.map +1 -1
  71. package/dist/config/ai-providers.json +37 -28
  72. package/dist/config/api.d.ts +9 -0
  73. package/dist/config/api.d.ts.map +1 -0
  74. package/dist/config/api.js +25 -0
  75. package/dist/config/api.js.map +1 -0
  76. package/dist/config/api.ts +29 -0
  77. package/dist/config/pricing.json +55 -0
  78. package/dist/types/index.d.ts +1 -1
  79. package/dist/types/index.d.ts.map +1 -1
  80. package/dist/utils/apiKeyManager.d.ts.map +1 -1
  81. package/dist/utils/apiKeyManager.js +7 -0
  82. package/dist/utils/apiKeyManager.js.map +1 -1
  83. package/dist/utils/fileSystem.d.ts.map +1 -1
  84. package/dist/utils/fileSystem.js +3 -1
  85. package/dist/utils/fileSystem.js.map +1 -1
  86. package/dist/utils/hostedApiClient.d.ts +32 -0
  87. package/dist/utils/hostedApiClient.d.ts.map +1 -0
  88. package/dist/utils/hostedApiClient.js +231 -0
  89. package/dist/utils/hostedApiClient.js.map +1 -0
  90. package/dist/utils/hybridAIClient.d.ts +7 -3
  91. package/dist/utils/hybridAIClient.d.ts.map +1 -1
  92. package/dist/utils/hybridAIClient.js +103 -27
  93. package/dist/utils/hybridAIClient.js.map +1 -1
  94. package/dist/utils/qwenClient.d.ts +22 -0
  95. package/dist/utils/qwenClient.d.ts.map +1 -0
  96. package/dist/utils/qwenClient.js +180 -0
  97. package/dist/utils/qwenClient.js.map +1 -0
  98. package/package.json +1 -1
@@ -44,16 +44,18 @@ const spinner_1 = require("../utils/spinner");
44
44
  const fileSystem_1 = require("../utils/fileSystem");
45
45
  const hybridAIClient_1 = require("../utils/hybridAIClient");
46
46
  const githubModelsClient_1 = require("../utils/githubModelsClient");
47
+ const hostedApiClient_1 = require("../utils/hostedApiClient");
47
48
  const fs = __importStar(require("fs-extra"));
48
49
  class GenerateCommand {
49
50
  constructor() {
50
51
  this.fs = new fileSystem_1.FileSystemManager();
51
52
  this.spinner = new spinner_1.EnhancedSpinner("Processing...");
52
53
  this.ai = new hybridAIClient_1.HybridAIClient();
54
+ this.hostedApi = new hostedApiClient_1.HostedApiClient();
53
55
  }
54
56
  async execute(options) {
55
57
  try {
56
- this.spinner.start();
58
+ this.spinner.start().updateText("Analyzing project context...");
57
59
  const projectContext = await this.resolveInputContext(options);
58
60
  if (!projectContext) {
59
61
  throw new Error("No project context found. Run 'mycontext init' or pass --description/--context-file.");
@@ -146,14 +148,16 @@ class GenerateCommand {
146
148
  else {
147
149
  console.log(chalk_1.default.yellow(`⚠️ Project structure generation skipped or failed: ${structureRes.error || "unknown"}`));
148
150
  }
149
- this.spinner.success({ text: "All artifacts generated" });
151
+ this.spinner.success({
152
+ text: "🎉 All artifacts generated successfully!",
153
+ });
150
154
  return;
151
155
  }
152
156
  default:
153
157
  throw new Error(`Unknown generation type: ${type}`);
154
158
  }
155
159
  if (result.success && result.content) {
156
- this.spinner.success({ text: "Generation completed successfully!" });
160
+ this.spinner.success({ text: `✅ ${type} generated successfully!` });
157
161
  // Save the generated content
158
162
  const outputPath = await this.saveGeneratedContent(type, result.content, options);
159
163
  console.log(chalk_1.default.green(`✅ Generated ${type} saved to: ${outputPath}`));
@@ -245,15 +249,24 @@ class GenerateCommand {
245
249
  // Friendlier handling for missing providers
246
250
  const message = error instanceof Error ? error.message : String(error);
247
251
  if (/No AI providers available/i.test(message)) {
248
- console.log(chalk_1.default.yellow("⚠️ No AI providers configured. You can rename .mycontext/.env.example to .env and add your MYCONTEXT_GITHUB_TOKEN (models:read), or start Ollama."));
249
- console.log(chalk_1.default.gray("Example: mv .mycontext/.env.example .mycontext/.env && echo 'MYCONTEXT_GITHUB_TOKEN=ghp_xxx' >> .mycontext/.env"));
252
+ console.log(chalk_1.default.yellow("\n⚠️ No AI providers configured. Choose your option:"));
253
+ console.log(chalk_1.default.blue("\n🔑 Option 1: Use your own AI keys (recommended)"));
254
+ console.log(chalk_1.default.gray(" 1. Copy: .mycontext/.env.example → .mycontext/.env"));
255
+ console.log(chalk_1.default.gray(" 2. Add: MYCONTEXT_GITHUB_TOKEN=ghp_xxx"));
256
+ console.log(chalk_1.default.gray(" 3. Or: MYCONTEXT_QWEN_API_KEY=sk-or-xxx (free via OpenRouter)"));
257
+ console.log(chalk_1.default.blue("\n🌐 Option 2: Use hosted MyContext AI"));
258
+ console.log(chalk_1.default.gray(" 1. Run: mycontext auth login"));
259
+ console.log(chalk_1.default.gray(" 2. Sign up for free at: https://mycontext.fbien.com"));
260
+ console.log(chalk_1.default.gray(" 3. Get $5/month unlimited plan"));
250
261
  // Fallback: write a minimal, valid skeleton so users can proceed
251
262
  try {
252
263
  const fallbackType = options?.type || "context";
253
264
  const fallbackProjectContext = (await this.getProjectContext()) || {};
254
265
  const skeleton = this.buildSkeleton(String(fallbackType), fallbackProjectContext);
255
266
  const outputPath = await this.saveGeneratedContent(String(fallbackType), skeleton, options);
256
- this.spinner.success({ text: `Wrote skeleton → ${outputPath}` });
267
+ this.spinner.success({
268
+ text: `✨ Generated basic template → ${outputPath}`,
269
+ });
257
270
  return;
258
271
  }
259
272
  catch (e) {
@@ -277,6 +290,18 @@ class GenerateCommand {
277
290
  return null;
278
291
  }
279
292
  }
293
+ async getActivePlaybook() {
294
+ try {
295
+ const playbookPath = path_1.default.join(process.cwd(), ".mycontext", "active-playbook.json");
296
+ if (await fs.pathExists(playbookPath)) {
297
+ return await fs.readJson(playbookPath);
298
+ }
299
+ }
300
+ catch (error) {
301
+ // Ignore errors
302
+ }
303
+ return null;
304
+ }
280
305
  async resolveInputContext(options) {
281
306
  // 1) Explicit description wins
282
307
  if (options.description && options.description.trim()) {
@@ -336,35 +361,31 @@ class GenerateCommand {
336
361
  catch { }
337
362
  // 5) Interactive prompt to capture description (when not in --yes)
338
363
  if (!options.yes) {
339
- const answer = await (0, prompts_1.default)({
340
- type: "toggle",
341
- name: "paste",
342
- message: "Provide a project description/PRD now? (Paste multi-line, finish with Ctrl-D)",
343
- initial: true,
344
- active: "yes",
345
- inactive: "no",
346
- });
347
- if (answer && answer.paste) {
348
- console.log(chalk_1.default.blue("\nPaste your description/PRD below. When done, press Ctrl-D (Linux/macOS) or Ctrl-Z then Enter (Windows).\n"));
349
- const pasted = await this.readStdinBlockUntilEOF();
350
- if (pasted && pasted.trim().length > 0) {
351
- const desc = pasted.trim();
352
- // Truncate long descriptions for terminal display
353
- const truncatedDesc = desc.length > 100 ? desc.substring(0, 100) + "..." : desc;
354
- console.log(chalk_1.default.gray(`📝 Interactive description: ${truncatedDesc}`));
355
- return { description: desc };
356
- }
364
+ this.spinner.stop();
365
+ console.log(chalk_1.default.blue("\n📝 Project Description"));
366
+ console.log(chalk_1.default.gray("Enter your project description or PRD. Press Ctrl-D when finished."));
367
+ const pasted = await this.readStdinBlockUntilEOF();
368
+ if (pasted && pasted.trim().length > 0) {
369
+ const desc = pasted.trim();
370
+ // Truncate long descriptions for terminal display
371
+ const truncatedDesc = desc.length > 100 ? desc.substring(0, 100) + "..." : desc;
372
+ console.log(chalk_1.default.gray(`📝 Description: ${truncatedDesc}`));
373
+ this.spinner.start().updateText("Processing description...");
374
+ return { description: desc };
357
375
  }
358
376
  else {
377
+ // Fallback to simple text input
359
378
  const short = await (0, prompts_1.default)({
360
379
  type: "text",
361
380
  name: "desc",
362
- message: "Short project description (optional):",
381
+ message: "Short project description:",
363
382
  });
364
383
  if (short?.desc && String(short.desc).trim().length > 0) {
384
+ this.spinner.start().updateText("Processing description...");
365
385
  return { description: String(short.desc).trim() };
366
386
  }
367
387
  }
388
+ this.spinner.start().updateText("Continuing without description...");
368
389
  }
369
390
  // 6) Fallback to project config
370
391
  return await this.getProjectContext();
@@ -391,10 +412,29 @@ class GenerateCommand {
391
412
  }
392
413
  }
393
414
  catch { }
415
+ // Get active playbook for additional context
416
+ const activePlaybook = await this.getActivePlaybook();
394
417
  const prompt = [
395
418
  `[mycontext] Plan: plan → generate → QA → docs → preview (→ checks)`,
396
419
  `Create a production-grade PRD for: ${projectContext.description || "MyContext project"}`,
397
420
  "",
421
+ // Include playbook context if available
422
+ ...(activePlaybook
423
+ ? [
424
+ "",
425
+ "## Proven Process Reference",
426
+ `**Active Playbook: ${activePlaybook.title}**`,
427
+ `Category: ${activePlaybook.category || "N/A"}`,
428
+ `Difficulty: ${activePlaybook.metadata?.difficulty || "N/A"}`,
429
+ `Estimated Time: ${activePlaybook.metadata?.estimatedTime || "N/A"}`,
430
+ "",
431
+ "**Process Guidelines:**",
432
+ activePlaybook.content,
433
+ "",
434
+ "**Important:** Follow the proven patterns and best practices outlined in the playbook above. This will ensure your implementation follows industry standards and proven approaches.",
435
+ "",
436
+ ]
437
+ : []),
398
438
  "Assume default stack unless user specifies otherwise:",
399
439
  "- Frontend: Next.js (App Router) with Shadcn UI",
400
440
  "- Auth/DB/File storage: InstantDB (auth, data, file persistence)",
@@ -455,22 +495,51 @@ class GenerateCommand {
455
495
  "Write concretely. Ground content in the provided description. No placeholders.",
456
496
  ].join("\n");
457
497
  try {
458
- this.spinner.updateText(`Generating PRD with AI (${await this.ai.getActiveProviderName()}/${await this.ai.getActiveTextModelName()})...`);
459
- const { text, provider } = await this.ai.generateText(prompt, {
460
- model: options.model || process.env.MYCONTEXT_MODEL,
461
- modelCandidates: this.getModelCandidates(options),
462
- });
463
- const cleaned = this.sanitizeMarkdownOutput(text);
464
- return {
465
- success: true,
466
- content: cleaned,
467
- provider: provider,
468
- metadata: {
469
- model: "auto",
470
- tokens: cleaned.length / 4, // Rough estimate
471
- latency: 1000,
472
- },
473
- };
498
+ // Check if user has local AI keys configured
499
+ const hasLocalKeys = this.hasLocalAIKeys();
500
+ if (hasLocalKeys) {
501
+ // Use local AI first (user's own keys)
502
+ this.spinner.updateText(`🤖 Generating PRD with ${await this.ai.getActiveProviderName()}...`);
503
+ const { text, provider } = await this.ai.generateText(prompt, {
504
+ model: options.model || process.env.MYCONTEXT_MODEL,
505
+ modelCandidates: this.getModelCandidates(options),
506
+ });
507
+ const cleaned = this.sanitizeMarkdownOutput(text);
508
+ return {
509
+ success: true,
510
+ content: cleaned,
511
+ provider: provider,
512
+ metadata: {
513
+ model: "local",
514
+ tokens: cleaned.length / 4,
515
+ latency: 1000,
516
+ },
517
+ };
518
+ }
519
+ else {
520
+ // No local keys - use hosted API (requires authentication)
521
+ this.spinner.updateText("🤖 Generating PRD with MyContext AI (hosted)...");
522
+ const hostedResult = await this.hostedApi.generateContext("context", prompt, {
523
+ model: options.model || "mycontext",
524
+ context: projectContext,
525
+ });
526
+ if (hostedResult.success && hostedResult.data) {
527
+ const cleaned = this.sanitizeMarkdownOutput(hostedResult.data);
528
+ return {
529
+ success: true,
530
+ content: cleaned,
531
+ provider: "hosted",
532
+ metadata: {
533
+ model: "mycontext",
534
+ tokens: cleaned.length / 4,
535
+ latency: 1000,
536
+ },
537
+ };
538
+ }
539
+ else {
540
+ throw new Error(hostedResult.error || "Hosted API failed");
541
+ }
542
+ }
474
543
  }
475
544
  catch (error) {
476
545
  return {
@@ -536,23 +605,53 @@ lib/types/
536
605
  - Use generics where appropriate
537
606
  - Keep types focused and single-purpose`;
538
607
  try {
539
- this.spinner.updateText(`Generating comprehensive TypeScript type system with AI (${await this.ai.getActiveProviderName()}/${await this.ai.getActiveTextModelName()})...`);
540
- const { text, provider } = await this.ai.generateText(prompt, {
541
- model: options.model || process.env.MYCONTEXT_MODEL,
542
- modelCandidates: this.getModelCandidates(options),
543
- });
544
- // Parse the generated content and create structured files
545
- const structuredContent = this.parseAndStructureTypes(text);
546
- return {
547
- success: true,
548
- content: structuredContent,
549
- provider: provider,
550
- metadata: {
551
- model: "auto",
552
- tokens: structuredContent.length / 4,
553
- latency: 600,
554
- },
555
- };
608
+ // Check if user has local AI keys configured
609
+ const hasLocalKeys = this.hasLocalAIKeys();
610
+ if (hasLocalKeys) {
611
+ // Use local AI first (user's own keys)
612
+ this.spinner.updateText(`🔧 Generating TypeScript types with ${await this.ai.getActiveProviderName()}...`);
613
+ const { text, provider } = await this.ai.generateText(prompt, {
614
+ model: options.model || process.env.MYCONTEXT_MODEL,
615
+ modelCandidates: this.getModelCandidates(options),
616
+ });
617
+ // Parse the generated content and create structured files
618
+ const structuredContent = this.parseAndStructureTypes(text);
619
+ return {
620
+ success: true,
621
+ content: structuredContent,
622
+ provider: provider,
623
+ metadata: {
624
+ model: "local",
625
+ tokens: structuredContent.length / 4,
626
+ latency: 600,
627
+ },
628
+ };
629
+ }
630
+ else {
631
+ // No local keys - use hosted API (requires authentication)
632
+ this.spinner.updateText("🔧 Generating TypeScript types with MyContext AI (hosted)...");
633
+ const hostedResult = await this.hostedApi.generateContext("types", prompt, {
634
+ model: options.model || "mycontext",
635
+ context: projectContext,
636
+ });
637
+ if (hostedResult.success && hostedResult.data) {
638
+ // Parse the generated content and create structured files
639
+ const structuredContent = this.parseAndStructureTypes(hostedResult.data);
640
+ return {
641
+ success: true,
642
+ content: structuredContent,
643
+ provider: "hosted",
644
+ metadata: {
645
+ model: "mycontext",
646
+ tokens: structuredContent.length / 4,
647
+ latency: 600,
648
+ },
649
+ };
650
+ }
651
+ else {
652
+ throw new Error(hostedResult.error || "Hosted API failed");
653
+ }
654
+ }
556
655
  }
557
656
  catch (error) {
558
657
  return {
@@ -1123,23 +1222,53 @@ Create a globals.css file with proper CSS custom properties following this exact
1123
1222
 
1124
1223
  Make the CSS immediately usable - no placeholders, actual working values!`;
1125
1224
  try {
1126
- this.spinner.updateText(`Generating comprehensive brand system with CSS custom properties (${await this.ai.getActiveProviderName()}/${await this.ai.getActiveTextModelName()})...`);
1127
- const { text, provider } = await this.ai.generateText(prompt, {
1128
- model: options.model || process.env.MYCONTEXT_MODEL,
1129
- modelCandidates: this.getModelCandidates(options),
1130
- });
1131
- // Parse and create the brand system files
1132
- const brandFiles = this.parseAndCreateBrandSystem(text);
1133
- return {
1134
- success: true,
1135
- content: brandFiles.guide,
1136
- provider: provider,
1137
- metadata: {
1138
- model: "auto",
1139
- tokens: brandFiles.guide.length / 4,
1140
- latency: 600,
1141
- },
1142
- };
1225
+ // Check if user has local AI keys configured
1226
+ const hasLocalKeys = this.hasLocalAIKeys();
1227
+ if (hasLocalKeys) {
1228
+ // Use local AI first (user's own keys)
1229
+ this.spinner.updateText(`🎨 Generating brand system with ${await this.ai.getActiveProviderName()}...`);
1230
+ const { text, provider } = await this.ai.generateText(prompt, {
1231
+ model: options.model || process.env.MYCONTEXT_MODEL,
1232
+ modelCandidates: this.getModelCandidates(options),
1233
+ });
1234
+ // Parse and create the brand system files
1235
+ const brandFiles = this.parseAndCreateBrandSystem(text);
1236
+ return {
1237
+ success: true,
1238
+ content: brandFiles.guide,
1239
+ provider: provider,
1240
+ metadata: {
1241
+ model: "local",
1242
+ tokens: brandFiles.guide.length / 4,
1243
+ latency: 600,
1244
+ },
1245
+ };
1246
+ }
1247
+ else {
1248
+ // No local keys - use hosted API (requires authentication)
1249
+ this.spinner.updateText("🎨 Generating brand system with MyContext AI (hosted)...");
1250
+ const hostedResult = await this.hostedApi.generateContext("brand", prompt, {
1251
+ model: options.model || "mycontext",
1252
+ context: projectContext,
1253
+ });
1254
+ if (hostedResult.success && hostedResult.data) {
1255
+ // Parse and create the brand system files
1256
+ const brandFiles = this.parseAndCreateBrandSystem(hostedResult.data);
1257
+ return {
1258
+ success: true,
1259
+ content: brandFiles.guide,
1260
+ provider: "hosted",
1261
+ metadata: {
1262
+ model: "mycontext",
1263
+ tokens: brandFiles.guide.length / 4,
1264
+ latency: 600,
1265
+ },
1266
+ };
1267
+ }
1268
+ else {
1269
+ throw new Error(hostedResult.error || "Hosted API failed");
1270
+ }
1271
+ }
1143
1272
  }
1144
1273
  catch (error) {
1145
1274
  return {
@@ -1279,31 +1408,51 @@ Make the CSS immediately usable - no placeholders, actual working values!`;
1279
1408
  "- Provide up to 3 'coreCandidates' that best represent the design anchor (canvas/layout), with a short reason.",
1280
1409
  ].join("\n");
1281
1410
  try {
1282
- this.spinner.updateText(`Generating component list with AI (${await this.ai.getActiveProviderName()}/${await this.ai.getActiveTextModelName()})...`);
1411
+ // Check if user has local AI keys configured
1412
+ const hasLocalKeys = this.hasLocalAIKeys();
1283
1413
  let text;
1284
1414
  let provider;
1285
- try {
1286
- const r = await this.ai.generateText(prompt, {
1287
- model: options.model || process.env.MYCONTEXT_MODEL,
1288
- modelCandidates: this.getModelCandidates(options),
1289
- });
1290
- text = r.text;
1291
- provider = r.provider;
1292
- }
1293
- catch (e) {
1294
- // Handle transient network issues like unexpected EOF with a single retry
1295
- const msg = String(e?.message || e);
1296
- if (/unexpected EOF|ECONNRESET|EPIPE/i.test(msg)) {
1297
- this.spinner.updateText("Retrying component list generation after transient error...");
1298
- const r2 = await this.ai.generateText(prompt, {
1415
+ if (hasLocalKeys) {
1416
+ // Use local AI first (user's own keys)
1417
+ this.spinner.updateText(`📋 Generating component list with ${await this.ai.getActiveProviderName()}...`);
1418
+ try {
1419
+ const r = await this.ai.generateText(prompt, {
1299
1420
  model: options.model || process.env.MYCONTEXT_MODEL,
1300
1421
  modelCandidates: this.getModelCandidates(options),
1301
1422
  });
1302
- text = r2.text;
1303
- provider = r2.provider;
1423
+ text = r.text;
1424
+ provider = r.provider;
1425
+ }
1426
+ catch (e) {
1427
+ // Handle transient network issues like unexpected EOF with a single retry
1428
+ const msg = String(e?.message || e);
1429
+ if (/unexpected EOF|ECONNRESET|EPIPE/i.test(msg)) {
1430
+ this.spinner.updateText("Retrying component list generation after transient error...");
1431
+ const r2 = await this.ai.generateText(prompt, {
1432
+ model: options.model || process.env.MYCONTEXT_MODEL,
1433
+ modelCandidates: this.getModelCandidates(options),
1434
+ });
1435
+ text = r2.text;
1436
+ provider = r2.provider;
1437
+ }
1438
+ else {
1439
+ throw e;
1440
+ }
1441
+ }
1442
+ }
1443
+ else {
1444
+ // No local keys - use hosted API (requires authentication)
1445
+ this.spinner.updateText("📋 Generating component list with MyContext AI (hosted)...");
1446
+ const hostedResult = await this.hostedApi.generateContext("components-list", prompt, {
1447
+ model: options.model || "mycontext",
1448
+ context: projectContext,
1449
+ });
1450
+ if (hostedResult.success && hostedResult.data) {
1451
+ text = hostedResult.data;
1452
+ provider = "hosted";
1304
1453
  }
1305
1454
  else {
1306
- throw e;
1455
+ throw new Error(hostedResult.error || "Hosted API failed");
1307
1456
  }
1308
1457
  }
1309
1458
  // Attempt to repair and extract valid JSON
@@ -2774,6 +2923,14 @@ ${isEcommerce
2774
2923
  }
2775
2924
  catch { }
2776
2925
  }
2926
+ hasLocalAIKeys() {
2927
+ // Check for any local AI provider keys
2928
+ return !!(process.env.MYCONTEXT_GITHUB_TOKEN ||
2929
+ process.env.MYCONTEXT_QWEN_API_KEY ||
2930
+ process.env.OPENAI_API_KEY ||
2931
+ process.env.ANTHROPIC_API_KEY ||
2932
+ process.env.HUGGINGFACE_API_KEY);
2933
+ }
2777
2934
  getModelCandidates(options) {
2778
2935
  const raw = options.modelCandidates ||
2779
2936
  options.models ||