@su-record/vibe 2.6.28 → 2.6.30

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 (240) hide show
  1. package/CLAUDE.md +235 -202
  2. package/LICENSE +21 -21
  3. package/README.md +276 -267
  4. package/agents/architect-low.md +41 -41
  5. package/agents/architect-medium.md +59 -59
  6. package/agents/architect.md +80 -80
  7. package/agents/build-error-resolver.md +115 -115
  8. package/agents/compounder.md +261 -261
  9. package/agents/diagrammer.md +178 -178
  10. package/agents/docs/api-documenter.md +99 -99
  11. package/agents/docs/changelog-writer.md +93 -93
  12. package/agents/e2e-tester.md +266 -266
  13. package/agents/explorer-low.md +42 -42
  14. package/agents/explorer-medium.md +59 -59
  15. package/agents/explorer.md +48 -48
  16. package/agents/implementer-low.md +43 -43
  17. package/agents/implementer-medium.md +52 -52
  18. package/agents/implementer.md +54 -54
  19. package/agents/planning/requirements-analyst.md +84 -84
  20. package/agents/planning/ux-advisor.md +83 -83
  21. package/agents/qa/acceptance-tester.md +86 -86
  22. package/agents/qa/edge-case-finder.md +93 -93
  23. package/agents/refactor-cleaner.md +143 -143
  24. package/agents/research/best-practices-agent.md +199 -199
  25. package/agents/research/codebase-patterns-agent.md +157 -157
  26. package/agents/research/framework-docs-agent.md +188 -188
  27. package/agents/research/security-advisory-agent.md +213 -213
  28. package/agents/review/architecture-reviewer.md +107 -107
  29. package/agents/review/complexity-reviewer.md +116 -116
  30. package/agents/review/data-integrity-reviewer.md +88 -88
  31. package/agents/review/git-history-reviewer.md +103 -103
  32. package/agents/review/performance-reviewer.md +86 -86
  33. package/agents/review/python-reviewer.md +150 -150
  34. package/agents/review/rails-reviewer.md +139 -139
  35. package/agents/review/react-reviewer.md +144 -144
  36. package/agents/review/security-reviewer.md +80 -80
  37. package/agents/review/simplicity-reviewer.md +140 -140
  38. package/agents/review/test-coverage-reviewer.md +116 -116
  39. package/agents/review/typescript-reviewer.md +127 -127
  40. package/agents/searcher.md +54 -54
  41. package/agents/simplifier.md +120 -120
  42. package/agents/tester.md +49 -49
  43. package/agents/ui-previewer.md +268 -268
  44. package/commands/vibe.analyze.md +356 -356
  45. package/commands/vibe.reason.md +329 -329
  46. package/commands/vibe.review.md +423 -423
  47. package/commands/vibe.run.md +1423 -1313
  48. package/commands/vibe.spec.md +1054 -1054
  49. package/commands/vibe.spec.review.md +412 -412
  50. package/commands/vibe.trace.md +161 -161
  51. package/commands/vibe.utils.md +376 -376
  52. package/commands/vibe.verify.md +375 -375
  53. package/dist/cli/collaborator.js +52 -52
  54. package/dist/cli/detect.js +32 -32
  55. package/dist/cli/hud.js +20 -20
  56. package/dist/cli/index.js +112 -112
  57. package/dist/cli/llm.js +144 -144
  58. package/dist/cli/postinstall.js +858 -858
  59. package/dist/lib/DeepInit.js +24 -24
  60. package/dist/lib/IterationTracker.js +11 -11
  61. package/dist/lib/PythonParser.js +108 -108
  62. package/dist/lib/ReviewRace.js +96 -96
  63. package/dist/lib/SkillFrontmatter.js +28 -28
  64. package/dist/lib/SkillQualityGate.js +9 -9
  65. package/dist/lib/SkillRepository.js +159 -159
  66. package/dist/lib/UltraQA.js +77 -77
  67. package/dist/lib/gemini-api.js +5 -5
  68. package/dist/lib/gpt-api.js +4 -4
  69. package/dist/lib/memory/KnowledgeGraph.js +4 -4
  70. package/dist/lib/memory/MemorySearch.js +43 -43
  71. package/dist/lib/memory/MemoryStorage.js +130 -130
  72. package/dist/lib/memory/ObservationStore.js +28 -28
  73. package/dist/lib/memory/SessionRAGRetriever.js +7 -7
  74. package/dist/lib/memory/SessionRAGStore.js +216 -216
  75. package/dist/lib/memory/SessionSummarizer.js +9 -9
  76. package/dist/orchestrator/AgentManager.js +12 -12
  77. package/dist/orchestrator/MultiLlmResearch.js +8 -8
  78. package/dist/orchestrator/SmartRouter.js +11 -11
  79. package/dist/orchestrator/SwarmOrchestrator.test.js +16 -16
  80. package/dist/orchestrator/parallelResearch.js +24 -24
  81. package/dist/tools/convention/analyzeComplexity.test.js +115 -115
  82. package/dist/tools/convention/validateCodeQuality.test.js +104 -104
  83. package/dist/tools/spec/prdParser.test.js +171 -171
  84. package/dist/tools/spec/specGenerator.js +169 -169
  85. package/dist/tools/spec/traceabilityMatrix.js +64 -64
  86. package/dist/tools/spec/traceabilityMatrix.test.js +28 -28
  87. package/hooks/hooks.json +115 -115
  88. package/hooks/scripts/code-check.js +70 -70
  89. package/hooks/scripts/code-review.js +22 -22
  90. package/hooks/scripts/complexity.js +22 -22
  91. package/hooks/scripts/compound.js +23 -23
  92. package/hooks/scripts/context-save.js +53 -53
  93. package/hooks/scripts/gemini-ui-gen.js +281 -281
  94. package/hooks/scripts/generate-brand-assets.js +474 -474
  95. package/hooks/scripts/hud-multiline.js +262 -262
  96. package/hooks/scripts/hud-status.js +291 -291
  97. package/hooks/scripts/keyword-detector.js +214 -214
  98. package/hooks/scripts/llm-orchestrate.js +328 -171
  99. package/hooks/scripts/post-edit.js +97 -97
  100. package/hooks/scripts/post-tool-verify.js +210 -210
  101. package/hooks/scripts/pre-tool-guard.js +125 -125
  102. package/hooks/scripts/prompt-dispatcher.js +161 -161
  103. package/hooks/scripts/recall.js +22 -22
  104. package/hooks/scripts/session-start.js +30 -30
  105. package/hooks/scripts/skill-injector.js +191 -191
  106. package/hooks/scripts/utils.js +97 -97
  107. package/languages/csharp-unity.md +515 -515
  108. package/languages/gdscript-godot.md +470 -470
  109. package/languages/ruby-rails.md +489 -489
  110. package/languages/typescript-angular.md +433 -433
  111. package/languages/typescript-astro.md +416 -416
  112. package/languages/typescript-electron.md +406 -406
  113. package/languages/typescript-nestjs.md +524 -524
  114. package/languages/typescript-svelte.md +407 -407
  115. package/languages/typescript-tauri.md +365 -365
  116. package/package.json +84 -84
  117. package/skills/brand-assets.md +141 -141
  118. package/skills/commerce-patterns.md +361 -361
  119. package/skills/context7-usage.md +102 -102
  120. package/skills/e2e-commerce.md +304 -304
  121. package/skills/frontend-design.md +92 -92
  122. package/skills/git-worktree.md +181 -181
  123. package/skills/parallel-research.md +77 -77
  124. package/skills/priority-todos.md +239 -239
  125. package/skills/seo-checklist.md +244 -244
  126. package/skills/tool-fallback.md +190 -190
  127. package/skills/vibe-capabilities.md +161 -161
  128. package/vibe/constitution.md +227 -227
  129. package/vibe/rules/core/communication-guide.md +98 -98
  130. package/vibe/rules/core/development-philosophy.md +52 -52
  131. package/vibe/rules/core/quick-start.md +102 -102
  132. package/vibe/rules/quality/bdd-contract-testing.md +393 -393
  133. package/vibe/rules/quality/checklist.md +276 -276
  134. package/vibe/rules/quality/testing-strategy.md +440 -440
  135. package/vibe/rules/standards/anti-patterns.md +541 -541
  136. package/vibe/rules/standards/code-structure.md +291 -291
  137. package/vibe/rules/standards/complexity-metrics.md +313 -313
  138. package/vibe/rules/standards/naming-conventions.md +198 -198
  139. package/vibe/setup.sh +31 -31
  140. package/vibe/templates/constitution-template.md +252 -252
  141. package/vibe/templates/contract-backend-template.md +526 -526
  142. package/vibe/templates/contract-frontend-template.md +599 -599
  143. package/vibe/templates/feature-template.md +96 -96
  144. package/vibe/templates/spec-template.md +221 -221
  145. package/dist/cli/mcp.d.ts +0 -49
  146. package/dist/cli/mcp.d.ts.map +0 -1
  147. package/dist/cli/mcp.js +0 -169
  148. package/dist/cli/mcp.js.map +0 -1
  149. package/dist/lib/gemini-mcp.d.ts +0 -10
  150. package/dist/lib/gemini-mcp.d.ts.map +0 -1
  151. package/dist/lib/gemini-mcp.js +0 -353
  152. package/dist/lib/gemini-mcp.js.map +0 -1
  153. package/dist/lib/gpt-mcp.d.ts +0 -10
  154. package/dist/lib/gpt-mcp.d.ts.map +0 -1
  155. package/dist/lib/gpt-mcp.js +0 -352
  156. package/dist/lib/gpt-mcp.js.map +0 -1
  157. package/dist/tools/analytics/getUsageAnalytics.d.ts +0 -10
  158. package/dist/tools/analytics/getUsageAnalytics.d.ts.map +0 -1
  159. package/dist/tools/analytics/getUsageAnalytics.js +0 -246
  160. package/dist/tools/analytics/getUsageAnalytics.js.map +0 -1
  161. package/dist/tools/analytics/index.d.ts +0 -5
  162. package/dist/tools/analytics/index.d.ts.map +0 -1
  163. package/dist/tools/analytics/index.js +0 -5
  164. package/dist/tools/analytics/index.js.map +0 -1
  165. package/dist/tools/convention/getCodingGuide.d.ts +0 -7
  166. package/dist/tools/convention/getCodingGuide.d.ts.map +0 -1
  167. package/dist/tools/convention/getCodingGuide.js +0 -69
  168. package/dist/tools/convention/getCodingGuide.js.map +0 -1
  169. package/dist/tools/planning/analyzeRequirements.d.ts +0 -9
  170. package/dist/tools/planning/analyzeRequirements.d.ts.map +0 -1
  171. package/dist/tools/planning/analyzeRequirements.js +0 -171
  172. package/dist/tools/planning/analyzeRequirements.js.map +0 -1
  173. package/dist/tools/planning/createUserStories.d.ts +0 -9
  174. package/dist/tools/planning/createUserStories.d.ts.map +0 -1
  175. package/dist/tools/planning/createUserStories.js +0 -124
  176. package/dist/tools/planning/createUserStories.js.map +0 -1
  177. package/dist/tools/planning/featureRoadmap.d.ts +0 -10
  178. package/dist/tools/planning/featureRoadmap.d.ts.map +0 -1
  179. package/dist/tools/planning/featureRoadmap.js +0 -207
  180. package/dist/tools/planning/featureRoadmap.js.map +0 -1
  181. package/dist/tools/planning/generatePrd.d.ts +0 -11
  182. package/dist/tools/planning/generatePrd.d.ts.map +0 -1
  183. package/dist/tools/planning/generatePrd.js +0 -161
  184. package/dist/tools/planning/generatePrd.js.map +0 -1
  185. package/dist/tools/planning/index.d.ts +0 -8
  186. package/dist/tools/planning/index.d.ts.map +0 -1
  187. package/dist/tools/planning/index.js +0 -8
  188. package/dist/tools/planning/index.js.map +0 -1
  189. package/dist/tools/prompt/analyzePrompt.d.ts +0 -7
  190. package/dist/tools/prompt/analyzePrompt.d.ts.map +0 -1
  191. package/dist/tools/prompt/analyzePrompt.js +0 -150
  192. package/dist/tools/prompt/analyzePrompt.js.map +0 -1
  193. package/dist/tools/prompt/enhancePrompt.d.ts +0 -8
  194. package/dist/tools/prompt/enhancePrompt.d.ts.map +0 -1
  195. package/dist/tools/prompt/enhancePrompt.js +0 -110
  196. package/dist/tools/prompt/enhancePrompt.js.map +0 -1
  197. package/dist/tools/prompt/enhancePromptGemini.d.ts +0 -8
  198. package/dist/tools/prompt/enhancePromptGemini.d.ts.map +0 -1
  199. package/dist/tools/prompt/enhancePromptGemini.js +0 -332
  200. package/dist/tools/prompt/enhancePromptGemini.js.map +0 -1
  201. package/dist/tools/prompt/index.d.ts +0 -7
  202. package/dist/tools/prompt/index.d.ts.map +0 -1
  203. package/dist/tools/prompt/index.js +0 -7
  204. package/dist/tools/prompt/index.js.map +0 -1
  205. package/dist/tools/reasoning/applyReasoningFramework.d.ts +0 -8
  206. package/dist/tools/reasoning/applyReasoningFramework.d.ts.map +0 -1
  207. package/dist/tools/reasoning/applyReasoningFramework.js +0 -266
  208. package/dist/tools/reasoning/applyReasoningFramework.js.map +0 -1
  209. package/dist/tools/reasoning/index.d.ts +0 -5
  210. package/dist/tools/reasoning/index.d.ts.map +0 -1
  211. package/dist/tools/reasoning/index.js +0 -5
  212. package/dist/tools/reasoning/index.js.map +0 -1
  213. package/dist/tools/thinking/analyzeProblem.d.ts +0 -7
  214. package/dist/tools/thinking/analyzeProblem.d.ts.map +0 -1
  215. package/dist/tools/thinking/analyzeProblem.js +0 -55
  216. package/dist/tools/thinking/analyzeProblem.js.map +0 -1
  217. package/dist/tools/thinking/breakDownProblem.d.ts +0 -8
  218. package/dist/tools/thinking/breakDownProblem.d.ts.map +0 -1
  219. package/dist/tools/thinking/breakDownProblem.js +0 -145
  220. package/dist/tools/thinking/breakDownProblem.js.map +0 -1
  221. package/dist/tools/thinking/createThinkingChain.d.ts +0 -7
  222. package/dist/tools/thinking/createThinkingChain.d.ts.map +0 -1
  223. package/dist/tools/thinking/createThinkingChain.js +0 -44
  224. package/dist/tools/thinking/createThinkingChain.js.map +0 -1
  225. package/dist/tools/thinking/formatAsPlan.d.ts +0 -9
  226. package/dist/tools/thinking/formatAsPlan.d.ts.map +0 -1
  227. package/dist/tools/thinking/formatAsPlan.js +0 -78
  228. package/dist/tools/thinking/formatAsPlan.js.map +0 -1
  229. package/dist/tools/thinking/index.d.ts +0 -10
  230. package/dist/tools/thinking/index.d.ts.map +0 -1
  231. package/dist/tools/thinking/index.js +0 -10
  232. package/dist/tools/thinking/index.js.map +0 -1
  233. package/dist/tools/thinking/stepByStepAnalysis.d.ts +0 -8
  234. package/dist/tools/thinking/stepByStepAnalysis.d.ts.map +0 -1
  235. package/dist/tools/thinking/stepByStepAnalysis.js +0 -63
  236. package/dist/tools/thinking/stepByStepAnalysis.js.map +0 -1
  237. package/dist/tools/thinking/thinkAloudProcess.d.ts +0 -8
  238. package/dist/tools/thinking/thinkAloudProcess.d.ts.map +0 -1
  239. package/dist/tools/thinking/thinkAloudProcess.js +0 -80
  240. package/dist/tools/thinking/thinkAloudProcess.js.map +0 -1
@@ -1,171 +1,328 @@
1
- /**
2
- * UserPromptSubmit Hook - LLM 오케스트레이션 (GPT/Gemini)
3
- *
4
- * Usage:
5
- * node llm-orchestrate.js <provider> <mode> "prompt"
6
- * node llm-orchestrate.js <provider> <mode> "systemPrompt" "prompt"
7
- *
8
- * provider: gpt | gemini
9
- * mode: orchestrate | orchestrate-json
10
- *
11
- * Features:
12
- * - Exponential backoff retry (3 attempts)
13
- * - Auto fallback: gemini gpt, gpt → gemini
14
- * - Overload/rate-limit detection
15
- *
16
- * Input: JSON from stdin with { prompt: string } (when no CLI args)
17
- */
18
- import { getLibBaseUrl } from './utils.js';
19
-
20
- const LIB_URL = getLibBaseUrl();
21
- const DEFAULT_SYSTEM_PROMPT = 'You are a helpful assistant.';
22
-
23
- const provider = process.argv[2] || 'gemini';
24
- const mode = process.argv[3] || 'orchestrate';
25
-
26
- // Retry configuration
27
- const MAX_RETRIES = 3;
28
- const INITIAL_DELAY_MS = 2000;
29
-
30
- // Errors that should skip retry and go to fallback immediately
31
- const SKIP_RETRY_PATTERNS = [
32
- /rate.?limit/i,
33
- /quota/i,
34
- /unauthorized/i,
35
- /forbidden/i,
36
- /401/,
37
- /403/,
38
- /429/,
39
- ];
40
-
41
- // Errors that should trigger retry with backoff
42
- const RETRY_PATTERNS = [
43
- /overload/i,
44
- /503/,
45
- /5\d\d/,
46
- /network/i,
47
- /timeout/i,
48
- /ECONNRESET/i,
49
- /ETIMEDOUT/i,
50
- ];
51
-
52
- function shouldSkipRetry(errorMsg) {
53
- return SKIP_RETRY_PATTERNS.some(pattern => pattern.test(errorMsg));
54
- }
55
-
56
- function shouldRetry(errorMsg) {
57
- return RETRY_PATTERNS.some(pattern => pattern.test(errorMsg));
58
- }
59
-
60
- function sleep(ms) {
61
- return new Promise(resolve => setTimeout(resolve, ms));
62
- }
63
-
64
- async function callProvider(providerName, prompt, sysPrompt, jsonMode) {
65
- const modulePath = `${LIB_URL}${providerName}-api.js`;
66
- const module = await import(modulePath);
67
-
68
- const orchestrateFn = providerName === 'gpt'
69
- ? module.vibeGptOrchestrate
70
- : module.vibeGeminiOrchestrate;
71
-
72
- return await orchestrateFn(prompt, sysPrompt, { jsonMode });
73
- }
74
-
75
- async function callWithRetry(providerName, prompt, sysPrompt, jsonMode) {
76
- let lastError;
77
-
78
- for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
79
- try {
80
- return { success: true, result: await callProvider(providerName, prompt, sysPrompt, jsonMode) };
81
- } catch (e) {
82
- lastError = e;
83
- const errorMsg = e.message || String(e);
84
-
85
- // Skip retry for auth/quota errors - go to fallback immediately
86
- if (shouldSkipRetry(errorMsg)) {
87
- return { success: false, error: errorMsg, skipToFallback: true };
88
- }
89
-
90
- // Retry with backoff for transient errors
91
- if (shouldRetry(errorMsg) && attempt < MAX_RETRIES) {
92
- const delay = INITIAL_DELAY_MS * Math.pow(2, attempt - 1);
93
- console.error(`[${providerName.toUpperCase()}] Retry ${attempt}/${MAX_RETRIES} after ${delay}ms...`);
94
- await sleep(delay);
95
- continue;
96
- }
97
-
98
- // Unknown error or max retries reached
99
- return { success: false, error: errorMsg, skipToFallback: false };
100
- }
101
- }
102
-
103
- return { success: false, error: lastError?.message || 'Max retries exceeded', skipToFallback: false };
104
- }
105
-
106
- async function main() {
107
- let prompt;
108
- let systemPrompt = DEFAULT_SYSTEM_PROMPT;
109
-
110
- // CLI argument가 있으면 사용
111
- // Usage 1: node script.js gpt orchestrate "system prompt" "user prompt"
112
- // Usage 2: node script.js gpt orchestrate "user prompt" (uses default system prompt)
113
- const arg4 = process.argv[4]?.trim();
114
- const arg5 = process.argv.slice(5).join(' ').trim();
115
-
116
- if (arg5) {
117
- // 5번째 인자가 있으면: arg4=시스템 프롬프트, arg5=사용자 프롬프트
118
- systemPrompt = arg4;
119
- prompt = arg5;
120
- } else if (arg4) {
121
- // 4번째 인자만 있으면: arg4=사용자 프롬프트 (시스템 프롬프트는 기본값)
122
- prompt = arg4;
123
- } else {
124
- // Hook에서 호출: stdin으로 JSON 입력
125
- let inputData = '';
126
- for await (const chunk of process.stdin) {
127
- inputData += chunk;
128
- }
129
-
130
- try {
131
- const parsed = JSON.parse(inputData);
132
- prompt = parsed.prompt;
133
- } catch {
134
- console.log(`[${provider.toUpperCase()}] Error: Invalid JSON input`);
135
- return;
136
- }
137
- }
138
-
139
- // 접두사 제거
140
- const prefixPatterns = {
141
- gpt: /^(gpt[-.\s]|지피티-|vibe-gpt-)\s*/i,
142
- gemini: /^(gemini[-.\s]|제미나이-|vibe-gemini-)\s*/i,
143
- };
144
- const cleanPrompt = prompt.replace(prefixPatterns[provider] || /^/, '').trim();
145
- const jsonMode = mode === 'orchestrate-json';
146
-
147
- // Provider chain: primary → fallback
148
- const fallbackProvider = provider === 'gpt' ? 'gemini' : 'gpt';
149
- const providerChain = [provider, fallbackProvider];
150
-
151
- for (const currentProvider of providerChain) {
152
- const label = currentProvider === 'gpt' ? 'GPT-5.2' : 'Gemini-3';
153
- const result = await callWithRetry(currentProvider, cleanPrompt, systemPrompt, jsonMode);
154
-
155
- if (result.success) {
156
- console.log(`${label} response: ${result.result}`);
157
- return;
158
- }
159
-
160
- // Log failure and try fallback
161
- if (currentProvider !== providerChain[providerChain.length - 1]) {
162
- const fallbackLabel = fallbackProvider === 'gpt' ? 'GPT' : 'Gemini';
163
- console.error(`[${currentProvider.toUpperCase()}] Failed: ${result.error}. Falling back to ${fallbackLabel}...`);
164
- } else {
165
- // All providers failed
166
- console.log(`[LLM] Error: All providers failed. Last error: ${result.error}`);
167
- }
168
- }
169
- }
170
-
171
- main();
1
+ /**
2
+ * UserPromptSubmit Hook - LLM 오케스트레이션 (GPT/Gemini)
3
+ *
4
+ * Usage:
5
+ * node llm-orchestrate.js <provider> <mode> "prompt"
6
+ * node llm-orchestrate.js <provider> <mode> "systemPrompt" "prompt"
7
+ *
8
+ * provider: gpt | gemini
9
+ * mode: orchestrate | orchestrate-json | image
10
+ *
11
+ * Image Mode:
12
+ * node llm-orchestrate.js gemini image "prompt" --output "./image.png"
13
+ * node llm-orchestrate.js gemini image "prompt" --output "./image.png" --size "1920x1080"
14
+ *
15
+ * Features:
16
+ * - Exponential backoff retry (3 attempts)
17
+ * - Auto fallback: gemini → gpt, gpt → gemini
18
+ * - Overload/rate-limit detection
19
+ * - Image generation (Gemini only, Nano Banana model)
20
+ *
21
+ * Input: JSON from stdin with { prompt: string } (when no CLI args)
22
+ */
23
+ import { getLibBaseUrl } from './utils.js';
24
+ import fs from 'fs';
25
+ import path from 'path';
26
+ import os from 'os';
27
+ import https from 'https';
28
+
29
+ const LIB_URL = getLibBaseUrl();
30
+ const DEFAULT_SYSTEM_PROMPT = 'You are a helpful assistant.';
31
+
32
+ const provider = process.argv[2] || 'gemini';
33
+ const mode = process.argv[3] || 'orchestrate';
34
+
35
+ // Retry configuration
36
+ const MAX_RETRIES = 3;
37
+ const INITIAL_DELAY_MS = 2000;
38
+
39
+ // Errors that should skip retry and go to fallback immediately
40
+ const SKIP_RETRY_PATTERNS = [
41
+ /rate.?limit/i,
42
+ /quota/i,
43
+ /unauthorized/i,
44
+ /forbidden/i,
45
+ /401/,
46
+ /403/,
47
+ /429/,
48
+ ];
49
+
50
+ // Errors that should trigger retry with backoff
51
+ const RETRY_PATTERNS = [
52
+ /overload/i,
53
+ /503/,
54
+ /5\d\d/,
55
+ /network/i,
56
+ /timeout/i,
57
+ /ECONNRESET/i,
58
+ /ETIMEDOUT/i,
59
+ ];
60
+
61
+ function shouldSkipRetry(errorMsg) {
62
+ return SKIP_RETRY_PATTERNS.some(pattern => pattern.test(errorMsg));
63
+ }
64
+
65
+ function shouldRetry(errorMsg) {
66
+ return RETRY_PATTERNS.some(pattern => pattern.test(errorMsg));
67
+ }
68
+
69
+ function sleep(ms) {
70
+ return new Promise(resolve => setTimeout(resolve, ms));
71
+ }
72
+
73
+ // ============================================
74
+ // Image Generation (Gemini only)
75
+ // ============================================
76
+
77
+ const GEMINI_CONFIG_PATH = path.join(os.homedir(), '.config', 'vibe', 'gemini.json');
78
+
79
+ function getGeminiApiKey() {
80
+ if (process.env.GEMINI_API_KEY) {
81
+ return process.env.GEMINI_API_KEY;
82
+ }
83
+ if (fs.existsSync(GEMINI_CONFIG_PATH)) {
84
+ try {
85
+ const config = JSON.parse(fs.readFileSync(GEMINI_CONFIG_PATH, 'utf8'));
86
+ if (config.apiKey) return config.apiKey;
87
+ } catch {
88
+ // ignore
89
+ }
90
+ }
91
+ return null;
92
+ }
93
+
94
+ async function generateImageWithGemini(prompt, options = {}) {
95
+ const apiKey = getGeminiApiKey();
96
+ if (!apiKey) {
97
+ throw new Error('Gemini API key not configured. Run "vibe gemini auth" to configure.');
98
+ }
99
+
100
+ const size = options.size || '1024x1024';
101
+ const [width, height] = size.split('x').map(Number);
102
+ const aspectRatio = width && height ? `${width}:${height}` : '1:1';
103
+
104
+ // Nano Banana (Gemini 2.5 Flash Image)
105
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-image-generation:generateContent?key=${apiKey}`;
106
+
107
+ const requestBody = {
108
+ contents: [{
109
+ parts: [{
110
+ text: `Generate an image: ${prompt}
111
+
112
+ Requirements:
113
+ - High quality, detailed image
114
+ - Aspect ratio: ${aspectRatio}
115
+ - Professional and polished look`
116
+ }]
117
+ }],
118
+ generationConfig: {
119
+ responseModalities: ['TEXT', 'IMAGE'],
120
+ }
121
+ };
122
+
123
+ return new Promise((resolve, reject) => {
124
+ const req = https.request(url, {
125
+ method: 'POST',
126
+ headers: { 'Content-Type': 'application/json' }
127
+ }, (res) => {
128
+ let data = '';
129
+ res.on('data', chunk => data += chunk);
130
+ res.on('end', () => {
131
+ try {
132
+ const result = JSON.parse(data);
133
+ if (result.error) {
134
+ reject(new Error(result.error.message));
135
+ return;
136
+ }
137
+ const parts = result.candidates?.[0]?.content?.parts || [];
138
+ for (const part of parts) {
139
+ if (part.inlineData?.mimeType?.startsWith('image/')) {
140
+ resolve({
141
+ data: Buffer.from(part.inlineData.data, 'base64'),
142
+ mimeType: part.inlineData.mimeType
143
+ });
144
+ return;
145
+ }
146
+ }
147
+ reject(new Error('No image in response'));
148
+ } catch (e) {
149
+ reject(e);
150
+ }
151
+ });
152
+ });
153
+ req.on('error', reject);
154
+ req.write(JSON.stringify(requestBody));
155
+ req.end();
156
+ });
157
+ }
158
+
159
+ function parseImageArgs(args) {
160
+ const result = { prompt: null, output: './generated-image.png', size: '1024x1024' };
161
+ for (let i = 0; i < args.length; i++) {
162
+ if (args[i] === '--output' || args[i] === '-o') {
163
+ result.output = args[++i];
164
+ } else if (args[i] === '--size' || args[i] === '-s') {
165
+ result.size = args[++i];
166
+ } else if (!args[i].startsWith('-') && !result.prompt) {
167
+ result.prompt = args[i];
168
+ }
169
+ }
170
+ return result;
171
+ }
172
+
173
+ async function callProvider(providerName, prompt, sysPrompt, jsonMode) {
174
+ const modulePath = `${LIB_URL}${providerName}-api.js`;
175
+ const module = await import(modulePath);
176
+
177
+ const orchestrateFn = providerName === 'gpt'
178
+ ? module.vibeGptOrchestrate
179
+ : module.vibeGeminiOrchestrate;
180
+
181
+ return await orchestrateFn(prompt, sysPrompt, { jsonMode });
182
+ }
183
+
184
+ async function callWithRetry(providerName, prompt, sysPrompt, jsonMode) {
185
+ let lastError;
186
+
187
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
188
+ try {
189
+ return { success: true, result: await callProvider(providerName, prompt, sysPrompt, jsonMode) };
190
+ } catch (e) {
191
+ lastError = e;
192
+ const errorMsg = e.message || String(e);
193
+
194
+ // Skip retry for auth/quota errors - go to fallback immediately
195
+ if (shouldSkipRetry(errorMsg)) {
196
+ return { success: false, error: errorMsg, skipToFallback: true };
197
+ }
198
+
199
+ // Retry with backoff for transient errors
200
+ if (shouldRetry(errorMsg) && attempt < MAX_RETRIES) {
201
+ const delay = INITIAL_DELAY_MS * Math.pow(2, attempt - 1);
202
+ console.error(`[${providerName.toUpperCase()}] Retry ${attempt}/${MAX_RETRIES} after ${delay}ms...`);
203
+ await sleep(delay);
204
+ continue;
205
+ }
206
+
207
+ // Unknown error or max retries reached
208
+ return { success: false, error: errorMsg, skipToFallback: false };
209
+ }
210
+ }
211
+
212
+ return { success: false, error: lastError?.message || 'Max retries exceeded', skipToFallback: false };
213
+ }
214
+
215
+ async function main() {
216
+ // Image mode handling
217
+ if (mode === 'image') {
218
+ if (provider !== 'gemini') {
219
+ console.log('[IMAGE] Error: Image generation only supports gemini provider');
220
+ return;
221
+ }
222
+
223
+ const imageArgs = parseImageArgs(process.argv.slice(4));
224
+ if (!imageArgs.prompt) {
225
+ console.log('[IMAGE] Error: --prompt is required');
226
+ return;
227
+ }
228
+
229
+ // Ensure output directory exists
230
+ const outputDir = path.dirname(imageArgs.output);
231
+ if (outputDir && outputDir !== '.' && !fs.existsSync(outputDir)) {
232
+ fs.mkdirSync(outputDir, { recursive: true });
233
+ }
234
+
235
+ console.error(`[IMAGE] Generating with Nano Banana...`);
236
+ console.error(` Prompt: ${imageArgs.prompt}`);
237
+ console.error(` Size: ${imageArgs.size}`);
238
+ console.error(` Output: ${imageArgs.output}`);
239
+
240
+ try {
241
+ const image = await generateImageWithGemini(imageArgs.prompt, { size: imageArgs.size });
242
+ fs.writeFileSync(imageArgs.output, image.data);
243
+ const stats = fs.statSync(imageArgs.output);
244
+ const sizeKB = (stats.size / 1024).toFixed(1);
245
+
246
+ console.log(JSON.stringify({
247
+ success: true,
248
+ path: path.resolve(imageArgs.output),
249
+ size: stats.size,
250
+ sizeKB: `${sizeKB} KB`,
251
+ prompt: imageArgs.prompt
252
+ }));
253
+ } catch (err) {
254
+ console.log(JSON.stringify({
255
+ success: false,
256
+ error: err.message,
257
+ prompt: imageArgs.prompt
258
+ }));
259
+ }
260
+ return;
261
+ }
262
+
263
+ // Text generation mode (orchestrate / orchestrate-json)
264
+ let prompt;
265
+ let systemPrompt = DEFAULT_SYSTEM_PROMPT;
266
+
267
+ // CLI argument가 있으면 사용
268
+ // Usage 1: node script.js gpt orchestrate "system prompt" "user prompt"
269
+ // Usage 2: node script.js gpt orchestrate "user prompt" (uses default system prompt)
270
+ const arg4 = process.argv[4]?.trim();
271
+ const arg5 = process.argv.slice(5).join(' ').trim();
272
+
273
+ if (arg5) {
274
+ // 5번째 인자가 있으면: arg4=시스템 프롬프트, arg5=사용자 프롬프트
275
+ systemPrompt = arg4;
276
+ prompt = arg5;
277
+ } else if (arg4) {
278
+ // 4번째 인자만 있으면: arg4=사용자 프롬프트 (시스템 프롬프트는 기본값)
279
+ prompt = arg4;
280
+ } else {
281
+ // Hook에서 호출: stdin으로 JSON 입력
282
+ let inputData = '';
283
+ for await (const chunk of process.stdin) {
284
+ inputData += chunk;
285
+ }
286
+
287
+ try {
288
+ const parsed = JSON.parse(inputData);
289
+ prompt = parsed.prompt;
290
+ } catch {
291
+ console.log(`[${provider.toUpperCase()}] Error: Invalid JSON input`);
292
+ return;
293
+ }
294
+ }
295
+
296
+ // 접두사 제거
297
+ const prefixPatterns = {
298
+ gpt: /^(gpt[-.\s]|지피티-|vibe-gpt-)\s*/i,
299
+ gemini: /^(gemini[-.\s]|제미나이-|vibe-gemini-)\s*/i,
300
+ };
301
+ const cleanPrompt = prompt.replace(prefixPatterns[provider] || /^/, '').trim();
302
+ const jsonMode = mode === 'orchestrate-json';
303
+
304
+ // Provider chain: primary → fallback
305
+ const fallbackProvider = provider === 'gpt' ? 'gemini' : 'gpt';
306
+ const providerChain = [provider, fallbackProvider];
307
+
308
+ for (const currentProvider of providerChain) {
309
+ const label = currentProvider === 'gpt' ? 'GPT-5.2' : 'Gemini-3';
310
+ const result = await callWithRetry(currentProvider, cleanPrompt, systemPrompt, jsonMode);
311
+
312
+ if (result.success) {
313
+ console.log(`${label} response: ${result.result}`);
314
+ return;
315
+ }
316
+
317
+ // Log failure and try fallback
318
+ if (currentProvider !== providerChain[providerChain.length - 1]) {
319
+ const fallbackLabel = fallbackProvider === 'gpt' ? 'GPT' : 'Gemini';
320
+ console.error(`[${currentProvider.toUpperCase()}] Failed: ${result.error}. Falling back to ${fallbackLabel}...`);
321
+ } else {
322
+ // All providers failed
323
+ console.log(`[LLM] Error: All providers failed. Last error: ${result.error}`);
324
+ }
325
+ }
326
+ }
327
+
328
+ main();