opencode-conductor-cdd-plugin 1.0.0-beta.20 → 1.0.0-beta.22

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 (30) hide show
  1. package/dist/index.js +2 -3
  2. package/dist/index.test.js +5 -0
  3. package/dist/prompts/strategies/delegate.md +124 -10
  4. package/dist/prompts/strategies/manual.md +138 -6
  5. package/dist/test/integration/modelConfigIntegration.test.d.ts +1 -0
  6. package/dist/test/integration/modelConfigIntegration.test.js +222 -0
  7. package/dist/test/integration/omo3-delegation.test.d.ts +1 -0
  8. package/dist/test/integration/omo3-delegation.test.js +583 -0
  9. package/dist/test/integration/rebrand.test.js +5 -1
  10. package/dist/tools/delegate.d.ts +12 -0
  11. package/dist/tools/delegate.js +84 -33
  12. package/dist/utils/agentInitialization.test.d.ts +1 -0
  13. package/dist/utils/agentInitialization.test.js +262 -0
  14. package/dist/utils/commandDefaults.d.ts +38 -0
  15. package/dist/utils/commandDefaults.js +54 -0
  16. package/dist/utils/commandDefaults.test.d.ts +1 -0
  17. package/dist/utils/commandDefaults.test.js +101 -0
  18. package/dist/utils/configDetection.d.ts +16 -0
  19. package/dist/utils/configDetection.js +103 -1
  20. package/dist/utils/configDetection.test.js +116 -1
  21. package/dist/utils/documentGeneration.d.ts +3 -0
  22. package/dist/utils/documentGeneration.js +29 -9
  23. package/dist/utils/interactiveMenu.test.js +5 -0
  24. package/dist/utils/languageSupport.d.ts +5 -0
  25. package/dist/utils/languageSupport.js +163 -0
  26. package/dist/utils/languageSupport.test.d.ts +1 -0
  27. package/dist/utils/languageSupport.test.js +158 -0
  28. package/dist/utils/modelConfigInjection.test.d.ts +1 -0
  29. package/dist/utils/modelConfigInjection.test.js +137 -0
  30. package/package.json +2 -2
@@ -2,6 +2,38 @@ import { join } from "path";
2
2
  import { existsSync, readFileSync } from "fs";
3
3
  import { homedir } from "os";
4
4
  export function detectCDDConfig() {
5
+ /**
6
+ * Detects and resolves CDD model configuration from synergy framework config files.
7
+ *
8
+ * Priority Order (highest to lowest):
9
+ * 1. oh-my-opencode-slim.json → agents.cdd.model
10
+ * 2. oh-my-opencode.json → agents.cdd.model
11
+ * 3. opencode.json → agent.cdd.model
12
+ * 4. No model (undefined)
13
+ *
14
+ * Synergy Framework Selection:
15
+ * - If oh-my-opencode-slim is present with cdd agent → use slim (highest priority)
16
+ * - Else if oh-my-opencode is present with cdd agent → use OMO
17
+ * - Else → no synergy (manual mode)
18
+ *
19
+ * Important: Synergy only activates if the selected framework explicitly
20
+ * has a "cdd" agent configured. File presence alone is insufficient.
21
+ *
22
+ * Example Config (oh-my-opencode-slim.json):
23
+ * {
24
+ * "agents": {
25
+ * "cdd": {
26
+ * "model": "anthropic/claude-3-5-sonnet"
27
+ * },
28
+ * "explorer": {
29
+ * "model": "google/gemini-flash-1.5"
30
+ * }
31
+ * },
32
+ * "disabled_agents": ["librarian"]
33
+ * }
34
+ *
35
+ * @returns ConfigDetectionResult with framework info and model string
36
+ */
5
37
  const opencodeConfigDir = join(homedir(), ".config", "opencode");
6
38
  const opencodeJsonPath = join(opencodeConfigDir, "opencode.json");
7
39
  const omoJsonPath = join(opencodeConfigDir, "oh-my-opencode.json");
@@ -12,6 +44,7 @@ export function detectCDDConfig() {
12
44
  let cddModel;
13
45
  let synergyFramework = 'none';
14
46
  let slimAgents;
47
+ let omoAgents;
15
48
  // Check oh-my-opencode-slim.json first (highest priority for synergy)
16
49
  if (existsSync(slimJsonPath)) {
17
50
  try {
@@ -38,7 +71,6 @@ export function detectCDDConfig() {
38
71
  }
39
72
  }
40
73
  // Check oh-my-opencode.json (only if slim doesn't have CDD)
41
- // NOTE: We still check OMO to set hasCDDInOMO flag even if slim has priority
42
74
  if (existsSync(omoJsonPath)) {
43
75
  try {
44
76
  const config = JSON.parse(readFileSync(omoJsonPath, "utf-8"));
@@ -52,6 +84,10 @@ export function detectCDDConfig() {
52
84
  if (!cddModel && config.agents.cdd.model) {
53
85
  cddModel = config.agents.cdd.model;
54
86
  }
87
+ // Extract available OMO agents (filter out disabled ones)
88
+ const allConfiguredAgents = Object.keys(config.agents || {});
89
+ const disabledAgents = new Set(config.disabled_agents ?? []);
90
+ omoAgents = allConfiguredAgents.filter(agent => !disabledAgents.has(agent));
55
91
  }
56
92
  }
57
93
  catch (e) {
@@ -82,5 +118,71 @@ export function detectCDDConfig() {
82
118
  cddModel,
83
119
  synergyFramework,
84
120
  slimAgents,
121
+ omoAgents,
85
122
  };
86
123
  }
124
+ export function getAvailableOMOAgents() {
125
+ const config = detectCDDConfig();
126
+ if (config.synergyFramework === 'oh-my-opencode-slim' && config.slimAgents) {
127
+ return config.slimAgents;
128
+ }
129
+ if (config.synergyFramework === 'oh-my-opencode' && config.omoAgents) {
130
+ return config.omoAgents;
131
+ }
132
+ return [];
133
+ }
134
+ /**
135
+ * Get the configured model for a specific agent from synergy framework config.
136
+ *
137
+ * Priority Order:
138
+ * 1. oh-my-opencode-slim.json → agents.<agentName>.model
139
+ * 2. oh-my-opencode.json → agents.<agentName>.model
140
+ * 3. opencode.json → agent.<agentName>.model
141
+ * 4. undefined (no model configured)
142
+ *
143
+ * @param agentName - Name of the agent (e.g., 'explorer', 'designer', 'cdd')
144
+ * @returns Model string if configured, undefined otherwise
145
+ */
146
+ export function getAgentModel(agentName) {
147
+ const opencodeConfigDir = join(homedir(), ".config", "opencode");
148
+ const opencodeJsonPath = join(opencodeConfigDir, "opencode.json");
149
+ const omoJsonPath = join(opencodeConfigDir, "oh-my-opencode.json");
150
+ const slimJsonPath = join(opencodeConfigDir, "oh-my-opencode-slim.json");
151
+ // Check oh-my-opencode-slim.json first (highest priority)
152
+ if (existsSync(slimJsonPath)) {
153
+ try {
154
+ const config = JSON.parse(readFileSync(slimJsonPath, "utf-8"));
155
+ if (config?.agents?.[agentName]?.model) {
156
+ return config.agents[agentName].model;
157
+ }
158
+ }
159
+ catch (e) {
160
+ // Silently fail on parse errors
161
+ }
162
+ }
163
+ // Check oh-my-opencode.json (second priority)
164
+ if (existsSync(omoJsonPath)) {
165
+ try {
166
+ const config = JSON.parse(readFileSync(omoJsonPath, "utf-8"));
167
+ if (config?.agents?.[agentName]?.model) {
168
+ return config.agents[agentName].model;
169
+ }
170
+ }
171
+ catch (e) {
172
+ // Silently fail on parse errors
173
+ }
174
+ }
175
+ // Check opencode.json (fallback)
176
+ if (existsSync(opencodeJsonPath)) {
177
+ try {
178
+ const config = JSON.parse(readFileSync(opencodeJsonPath, "utf-8"));
179
+ if (config?.agent?.[agentName]?.model) {
180
+ return config.agent[agentName].model;
181
+ }
182
+ }
183
+ catch (e) {
184
+ // Silently fail on parse errors
185
+ }
186
+ }
187
+ return undefined;
188
+ }
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect, vi, beforeEach } from "vitest";
2
2
  import { existsSync, readFileSync } from "fs";
3
- import { detectCDDConfig } from "./configDetection.js";
3
+ import { detectCDDConfig, getAvailableOMOAgents } from "./configDetection.js";
4
4
  vi.mock("fs", () => ({
5
5
  existsSync: vi.fn(),
6
6
  readFileSync: vi.fn(),
@@ -400,4 +400,119 @@ describe("configDetection", () => {
400
400
  expect(result.synergyActive).toBe(false);
401
401
  });
402
402
  });
403
+ describe("OMO agent availability detection", () => {
404
+ it("should extract available agents from oh-my-opencode config", () => {
405
+ vi.mocked(existsSync).mockImplementation((path) => path === omoJsonPath);
406
+ vi.mocked(readFileSync).mockImplementation((path) => {
407
+ if (path === omoJsonPath) {
408
+ return JSON.stringify({
409
+ agents: {
410
+ cdd: { model: "anthropic/claude-3-5-sonnet" },
411
+ sisyphus: { model: "anthropic/claude-3-5-sonnet" },
412
+ explore: { model: "google/gemini-3-flash" },
413
+ oracle: { model: "anthropic/claude-3-5-sonnet" }
414
+ }
415
+ });
416
+ }
417
+ return "";
418
+ });
419
+ const result = detectCDDConfig();
420
+ expect(result.omoAgents).toEqual(['cdd', 'sisyphus', 'explore', 'oracle']);
421
+ });
422
+ it("should filter out disabled agents from oh-my-opencode config", () => {
423
+ vi.mocked(existsSync).mockImplementation((path) => path === omoJsonPath);
424
+ vi.mocked(readFileSync).mockImplementation((path) => {
425
+ if (path === omoJsonPath) {
426
+ return JSON.stringify({
427
+ agents: {
428
+ cdd: { model: "anthropic/claude-3-5-sonnet" },
429
+ sisyphus: { model: "anthropic/claude-3-5-sonnet" },
430
+ explore: { model: "google/gemini-3-flash" },
431
+ oracle: { model: "anthropic/claude-3-5-sonnet" }
432
+ },
433
+ disabled_agents: ['explore', 'oracle']
434
+ });
435
+ }
436
+ return "";
437
+ });
438
+ const result = detectCDDConfig();
439
+ expect(result.omoAgents).toEqual(['cdd', 'sisyphus']);
440
+ });
441
+ it("should handle empty agents object in oh-my-opencode config", () => {
442
+ vi.mocked(existsSync).mockImplementation((path) => path === omoJsonPath);
443
+ vi.mocked(readFileSync).mockImplementation((path) => {
444
+ if (path === omoJsonPath) {
445
+ return JSON.stringify({
446
+ agents: {}
447
+ });
448
+ }
449
+ return "";
450
+ });
451
+ const result = detectCDDConfig();
452
+ expect(result.omoAgents).toBeUndefined();
453
+ });
454
+ });
455
+ describe("getAvailableOMOAgents", () => {
456
+ it("should return slim agents when slim framework is active", () => {
457
+ vi.mocked(existsSync).mockImplementation((path) => path === slimJsonPath);
458
+ vi.mocked(readFileSync).mockImplementation((path) => {
459
+ if (path === slimJsonPath) {
460
+ return JSON.stringify({
461
+ agents: {
462
+ cdd: { model: "anthropic/claude-3-5-sonnet" }
463
+ },
464
+ disabled_agents: ['oracle']
465
+ });
466
+ }
467
+ return "";
468
+ });
469
+ const agents = getAvailableOMOAgents();
470
+ expect(agents).toEqual(['explorer', 'librarian', 'designer']);
471
+ });
472
+ it("should return omo agents when oh-my-opencode framework is active", () => {
473
+ vi.mocked(existsSync).mockImplementation((path) => path === omoJsonPath);
474
+ vi.mocked(readFileSync).mockImplementation((path) => {
475
+ if (path === omoJsonPath) {
476
+ return JSON.stringify({
477
+ agents: {
478
+ cdd: { model: "anthropic/claude-3-5-sonnet" },
479
+ sisyphus: { model: "anthropic/claude-3-5-sonnet" },
480
+ explore: { model: "google/gemini-3-flash" }
481
+ }
482
+ });
483
+ }
484
+ return "";
485
+ });
486
+ const agents = getAvailableOMOAgents();
487
+ expect(agents).toEqual(['cdd', 'sisyphus', 'explore']);
488
+ });
489
+ it("should return empty array when no synergy framework is active", () => {
490
+ vi.mocked(existsSync).mockReturnValue(false);
491
+ const agents = getAvailableOMOAgents();
492
+ expect(agents).toEqual([]);
493
+ });
494
+ it("should prioritize slim agents over omo agents when both configs exist", () => {
495
+ vi.mocked(existsSync).mockReturnValue(true);
496
+ vi.mocked(readFileSync).mockImplementation((path) => {
497
+ if (path === slimJsonPath) {
498
+ return JSON.stringify({
499
+ agents: {
500
+ cdd: { model: "model-from-slim" }
501
+ }
502
+ });
503
+ }
504
+ if (path === omoJsonPath) {
505
+ return JSON.stringify({
506
+ agents: {
507
+ cdd: { model: "model-from-omo" },
508
+ sisyphus: { model: "model" }
509
+ }
510
+ });
511
+ }
512
+ return "";
513
+ });
514
+ const agents = getAvailableOMOAgents();
515
+ expect(agents).toEqual(['explorer', 'librarian', 'oracle', 'designer']);
516
+ });
517
+ });
403
518
  });
@@ -1,5 +1,6 @@
1
1
  import { Question, Section } from './questionGenerator.js';
2
2
  import { CodebaseAnalysis } from './codebaseAnalysis.js';
3
+ import { Language } from './languageSupport.js';
3
4
  /**
4
5
  * Document Generation Module
5
6
  *
@@ -65,6 +66,7 @@ export interface DocumentGenerationOptions {
65
66
  outputPath: string;
66
67
  maxRevisions?: number;
67
68
  customInputPrompt?: (question: Question) => Promise<string>;
69
+ language?: Language;
68
70
  }
69
71
  export interface DocumentGenerationResult {
70
72
  success: boolean;
@@ -76,6 +78,7 @@ export interface DocumentGenerationResult {
76
78
  export interface PresentQuestionsOptions {
77
79
  maxQuestions?: number;
78
80
  customInputPrompt?: (question: Question) => Promise<string>;
81
+ language?: Language;
79
82
  }
80
83
  /**
81
84
  * Present questions sequentially to the user
@@ -1,6 +1,7 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { validateSelection, parseSelection } from './interactiveMenu.js';
4
+ import { translateQuestion, detectLanguage } from './languageSupport.js';
4
5
  // Constants
5
6
  const MAX_QUESTIONS = 5;
6
7
  const MAX_REVISION_ATTEMPTS = 3;
@@ -18,6 +19,7 @@ const CHECKPOINT_MAP = {
18
19
  */
19
20
  export async function presentQuestionsSequentially(questions, responder, options = {}) {
20
21
  const { maxQuestions = MAX_QUESTIONS, customInputPrompt } = options;
22
+ let { language = 'en' } = options;
21
23
  const session = {
22
24
  section: questions[0]?.section || 'product',
23
25
  questionsAsked: [],
@@ -28,30 +30,47 @@ export async function presentQuestionsSequentially(questions, responder, options
28
30
  };
29
31
  for (let i = 0; i < Math.min(questions.length, maxQuestions); i++) {
30
32
  const question = questions[i];
31
- session.questionsAsked.push(question);
33
+ // Translate question if language is Spanish
34
+ const translatedQuestion = language === 'es' ? translateQuestion(question, 'es') : question;
35
+ session.questionsAsked.push(translatedQuestion);
32
36
  // Get user response
33
- let selections;
37
+ let selections = [];
34
38
  let isValid = false;
35
39
  // Keep asking until valid response
36
40
  while (!isValid) {
37
- selections = await responder(question, i + 1);
38
- isValid = validateSelection(selections.join(','), question.type);
41
+ selections = await responder(translatedQuestion, i + 1);
42
+ isValid = validateSelection(selections.join(','), translatedQuestion.type);
39
43
  }
40
- const parsedSelections = parseSelection(selections.join(','), question.type);
41
- // Map selections to actual option text
44
+ // Auto-detect language from first user response if custom input provided
45
+ if (i === 0 && selections.length > 0 && selections[0].length > 5) {
46
+ const detectedLang = detectLanguage(selections.join(' '));
47
+ if (detectedLang !== language) {
48
+ language = detectedLang;
49
+ }
50
+ }
51
+ const parsedSelections = parseSelection(selections.join(','), translatedQuestion.type);
52
+ // Map selections to actual option text (from translated question)
42
53
  const selectedOptions = parsedSelections.map(letter => {
43
54
  const index = letter.charCodeAt(0) - 'A'.charCodeAt(0);
44
- return question.options[index];
55
+ return translatedQuestion.options[index];
45
56
  });
46
57
  const answer = {
47
- questionId: question.id,
58
+ questionId: translatedQuestion.id,
48
59
  selections: parsedSelections,
49
60
  selectedOptions,
50
61
  timestamp: new Date().toISOString(),
51
62
  };
52
63
  // Handle option D (custom input)
53
64
  if (parsedSelections.includes('D') && customInputPrompt) {
54
- answer.customText = await customInputPrompt(question);
65
+ const customInput = await customInputPrompt(translatedQuestion);
66
+ answer.customText = customInput;
67
+ // Detect language from custom text input
68
+ if (customInput && customInput.length > 5) {
69
+ const detectedLang = detectLanguage(customInput);
70
+ if (detectedLang !== language) {
71
+ language = detectedLang;
72
+ }
73
+ }
55
74
  }
56
75
  session.answers.push(answer);
57
76
  // Handle option E (auto-generate) - stop asking questions
@@ -242,6 +261,7 @@ export async function generateDocument(options) {
242
261
  // Step 1: Present questions sequentially
243
262
  const session = await presentQuestionsSequentially(questions, responder, {
244
263
  customInputPrompt,
264
+ language: options.language || 'en',
245
265
  });
246
266
  // Step 2: Draft document from answers
247
267
  let draft = draftDocumentFromAnswers(session, analysis);
@@ -121,6 +121,11 @@ describe('interactiveMenu', () => {
121
121
  expect(validateSelection('', 'additive')).toBe(false);
122
122
  expect(validateSelection(' ', 'exclusive')).toBe(false);
123
123
  });
124
+ it('should reject input with only commas or whitespace', () => {
125
+ expect(validateSelection(',,,', 'exclusive')).toBe(false);
126
+ expect(validateSelection(', , ,', 'additive')).toBe(false);
127
+ expect(validateSelection(' , ', 'exclusive')).toBe(false);
128
+ });
124
129
  it('should reject option E in combination with others for additive', () => {
125
130
  expect(validateSelection('A,E', 'additive')).toBe(false);
126
131
  expect(validateSelection('E,A', 'additive')).toBe(false);
@@ -0,0 +1,5 @@
1
+ import { Question } from './questionGenerator.js';
2
+ export type Language = 'en' | 'es';
3
+ export declare function detectLanguage(text: string): Language;
4
+ export declare function translateQuestion(question: Question, language: Language): Question;
5
+ export declare function translateInstruction(key: string, language: Language): string;
@@ -0,0 +1,163 @@
1
+ const SPANISH_INDICATORS = [
2
+ '¿', '¡', 'á', 'é', 'í', 'ó', 'ú', 'ñ', 'ü',
3
+ 'cuál', 'qué', 'cómo', 'dónde', 'cuándo', 'quién',
4
+ 'los', 'las', 'una', 'del', 'con', 'para', 'por',
5
+ 'necesito', 'quiero', 'aplicación', 'proyecto', 'crear',
6
+ 'objetivo', 'principal', 'usuarios', 'principales',
7
+ 'añadir', 'configuración', 'nuevo', 'algo',
8
+ ];
9
+ const ENGLISH_INDICATORS = [
10
+ 'what', 'how', 'where', 'when', 'who', 'which',
11
+ 'the', 'a', 'an', 'of', 'with', 'for', 'by',
12
+ 'need', 'want', 'application', 'project', 'create',
13
+ 'main', 'primary', 'users', 'add', 'something', 'new',
14
+ ];
15
+ const TRANSLATIONS = {
16
+ 'menu.select_option': {
17
+ en: 'Select your option (e.g., "A", "B", "C")',
18
+ es: 'Selecciona tu opción (ej: "A", "B", "C")',
19
+ },
20
+ 'menu.custom_input': {
21
+ en: 'Enter custom text',
22
+ es: 'Ingresa texto personalizado',
23
+ },
24
+ 'menu.auto_generate': {
25
+ en: 'Auto-generate from context',
26
+ es: 'Auto-generar desde contexto',
27
+ },
28
+ 'approval.approve': {
29
+ en: 'Approve this document',
30
+ es: 'Aprobar este documento',
31
+ },
32
+ 'approval.revise': {
33
+ en: 'Request revisions',
34
+ es: 'Solicitar revisiones',
35
+ },
36
+ 'question.primary_purpose': {
37
+ en: 'What is the primary purpose of this project?',
38
+ es: '¿Cuál es el propósito principal de este proyecto?',
39
+ },
40
+ 'option.web_application': {
41
+ en: 'Build a web application',
42
+ es: 'Construir una aplicación web',
43
+ },
44
+ 'option.cli_tool': {
45
+ en: 'Build a CLI tool',
46
+ es: 'Construir una herramienta CLI',
47
+ },
48
+ 'option.library': {
49
+ en: 'Create a library',
50
+ es: 'Crear una biblioteca',
51
+ },
52
+ 'option.custom': {
53
+ en: 'Enter custom text',
54
+ es: 'Ingresar texto personalizado',
55
+ },
56
+ 'option.auto_generate': {
57
+ en: 'Auto-generate from context',
58
+ es: 'Auto-generar desde contexto',
59
+ },
60
+ };
61
+ export function detectLanguage(text) {
62
+ if (!text || text.trim().length === 0) {
63
+ return 'en';
64
+ }
65
+ const lowerText = text.toLowerCase();
66
+ const words = lowerText.split(/\s+/);
67
+ let spanishScore = 0;
68
+ let englishScore = 0;
69
+ for (let i = 0; i < words.length; i++) {
70
+ const word = words[i];
71
+ // First word gets significantly more weight (10x) to dominate language detection
72
+ const weight = i === 0 ? 10 : 1;
73
+ if (SPANISH_INDICATORS.includes(word)) {
74
+ spanishScore += weight;
75
+ }
76
+ if (ENGLISH_INDICATORS.includes(word)) {
77
+ englishScore += weight;
78
+ }
79
+ }
80
+ // Special characters strongly indicate Spanish
81
+ for (const char of ['¿', '¡', 'á', 'é', 'í', 'ó', 'ú', 'ñ', 'ü']) {
82
+ if (lowerText.includes(char)) {
83
+ spanishScore += 5;
84
+ }
85
+ }
86
+ return spanishScore > englishScore ? 'es' : 'en';
87
+ }
88
+ export function translateQuestion(question, language) {
89
+ if (language === 'en') {
90
+ return question;
91
+ }
92
+ const translated = {
93
+ ...question,
94
+ text: translateQuestionText(question.text, question.id),
95
+ options: question.options.map((opt, idx) => translateOption(opt, idx)),
96
+ };
97
+ return translated;
98
+ }
99
+ function translateQuestionText(text, questionId) {
100
+ const translationKey = `question.${questionId}`;
101
+ if (TRANSLATIONS[translationKey]) {
102
+ return TRANSLATIONS[translationKey].es;
103
+ }
104
+ if (text.includes('primary purpose')) {
105
+ return '¿Cuál es el propósito principal de este proyecto?';
106
+ }
107
+ if (text.includes('target users')) {
108
+ return '¿Quiénes son los usuarios objetivo?';
109
+ }
110
+ if (text.includes('key features')) {
111
+ return '¿Cuáles son las características principales?';
112
+ }
113
+ if (text.includes('programming language')) {
114
+ return '¿Qué lenguajes de programación usarás?';
115
+ }
116
+ if (text.includes('framework')) {
117
+ return '¿Qué frameworks usarás?';
118
+ }
119
+ return text;
120
+ }
121
+ function translateOption(option, index) {
122
+ const optionLower = option.toLowerCase();
123
+ if (optionLower.includes('web application')) {
124
+ return 'Construir una aplicación web';
125
+ }
126
+ if (optionLower.includes('cli tool')) {
127
+ return 'Construir una herramienta CLI';
128
+ }
129
+ if (optionLower.includes('library')) {
130
+ return 'Crear una biblioteca';
131
+ }
132
+ if (optionLower.includes('enter custom') || optionLower.includes('custom text')) {
133
+ return 'Ingresar texto personalizado';
134
+ }
135
+ if (optionLower.includes('auto-generate')) {
136
+ return 'Auto-generar desde contexto';
137
+ }
138
+ if (optionLower.includes('developers')) {
139
+ return 'Desarrolladores';
140
+ }
141
+ if (optionLower.includes('end users')) {
142
+ return 'Usuarios finales';
143
+ }
144
+ if (optionLower.includes('businesses')) {
145
+ return 'Empresas';
146
+ }
147
+ if (optionLower.includes('typescript')) {
148
+ return 'TypeScript';
149
+ }
150
+ if (optionLower.includes('python')) {
151
+ return 'Python';
152
+ }
153
+ if (optionLower.includes('javascript')) {
154
+ return 'JavaScript';
155
+ }
156
+ return option;
157
+ }
158
+ export function translateInstruction(key, language) {
159
+ if (TRANSLATIONS[key] && TRANSLATIONS[key][language]) {
160
+ return TRANSLATIONS[key][language];
161
+ }
162
+ return key;
163
+ }
@@ -0,0 +1 @@
1
+ export {};