clipflow-ai 1.0.0
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.
- package/README.md +220 -0
- package/bin/clipflow.ts +2 -0
- package/dist/index.js +2269 -0
- package/package.json +66 -0
- package/src/auth/claude-credentials.ts +145 -0
- package/src/index.ts +3 -0
- package/src/index.tsx +4 -0
- package/src/lib/agents.ts +297 -0
- package/src/lib/content-style.ts +14 -0
- package/src/lib/pipeline.ts +541 -0
- package/src/lib/types.ts +38 -0
- package/src/mcp/search-server.ts +190 -0
- package/src/providers/auth.ts +405 -0
- package/src/providers/definitions.ts +106 -0
- package/src/providers/index.ts +3 -0
- package/src/providers/types.ts +40 -0
- package/src/tui/App.tsx +476 -0
- package/src/tui/theme.ts +48 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2269 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/lib/content-style.ts
|
|
3
|
+
var CONTENT_STYLE = `
|
|
4
|
+
You are part of Jugaadu AI \u2014 a universal AI content pipeline that helps creators make social media content from a single idea.
|
|
5
|
+
|
|
6
|
+
IMPORTANT RULES:
|
|
7
|
+
- Write in the user's preferred language and tone
|
|
8
|
+
- Be specific, not generic. No filler words.
|
|
9
|
+
- Every output should be actionable \u2014 the next agent in the pipeline will use your work directly
|
|
10
|
+
- Be concise but thorough. Quality over quantity.
|
|
11
|
+
- Format your output with clear headers and structure using markdown
|
|
12
|
+
- If the user's idea relates to news, use the most recent information available
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
// src/lib/agents.ts
|
|
16
|
+
var AGENTS = [
|
|
17
|
+
{
|
|
18
|
+
name: "brief",
|
|
19
|
+
label: "The Planner",
|
|
20
|
+
role: "Structures raw idea into creative brief",
|
|
21
|
+
icon: "\uD83D\uDCCB",
|
|
22
|
+
systemPrompt: CONTENT_STYLE + `
|
|
23
|
+
You are The Planner. Your job is to take a raw idea and transform it into a structured creative brief.
|
|
24
|
+
Output a clear brief with these sections:
|
|
25
|
+
- **Core Idea**: One-sentence summary of what the content is about
|
|
26
|
+
- **Target Audience**: Who this is for \u2014 be specific about demographics, interests, pain points
|
|
27
|
+
- **Key Message**: The single takeaway the viewer should remember
|
|
28
|
+
- **Tone & Style**: The emotional register (funny, serious, inspirational, educational, etc.)
|
|
29
|
+
- **Platform**: Best-fit platform(s) and format (Reel, TikTok, YouTube Short, carousel, etc.)
|
|
30
|
+
- **Success Metric**: What does "viral" look like for this content?
|
|
31
|
+
- **Constraints**: Any sensitive topics, brand guidelines, or limitations to keep in mind`,
|
|
32
|
+
buildUserMessage: (input) => `Create a creative brief from this idea:
|
|
33
|
+
|
|
34
|
+
${input}`
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "research",
|
|
38
|
+
label: "The Journalist",
|
|
39
|
+
role: "Researches topic, gathers facts/data",
|
|
40
|
+
icon: "\uD83D\uDD0D",
|
|
41
|
+
systemPrompt: CONTENT_STYLE + `
|
|
42
|
+
You are The Journalist. Your job is to deeply research the topic and provide factual, useful data.
|
|
43
|
+
Output your research with these sections:
|
|
44
|
+
- **Key Facts**: 5-10 verified facts, statistics, or data points relevant to the topic
|
|
45
|
+
- **Background Context**: Brief history or context that makes the topic interesting
|
|
46
|
+
- **Expert Opinions**: Notable quotes or perspectives from authorities in this space
|
|
47
|
+
- **Surprising Angles**: Counter-intuitive facts or little-known details that could hook viewers
|
|
48
|
+
- **Sources**: Where this information comes from (be specific)
|
|
49
|
+
- **Content Hooks**: Which facts are most shareable and why
|
|
50
|
+
Do NOT make up statistics. If you are unsure, say so explicitly.`,
|
|
51
|
+
buildUserMessage: (input, prev) => `Research this topic thoroughly.
|
|
52
|
+
|
|
53
|
+
**Creative Brief:**
|
|
54
|
+
${prev["brief"]}
|
|
55
|
+
|
|
56
|
+
**Original Idea:**
|
|
57
|
+
${input}`
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: "trends",
|
|
61
|
+
label: "The Scout",
|
|
62
|
+
role: "Finds trending angles, hashtags, competing content",
|
|
63
|
+
icon: "\uD83D\uDCE1",
|
|
64
|
+
systemPrompt: CONTENT_STYLE + `
|
|
65
|
+
You are The Scout. Your job is to identify what is trending and how to ride the wave.
|
|
66
|
+
Output your trend analysis with these sections:
|
|
67
|
+
- **Trending Formats**: Current viral formats that fit this topic (duets, stitches, POV, etc.)
|
|
68
|
+
- **Hashtag Strategy**: 10-15 hashtags ranked by reach vs. specificity, mix of broad and niche
|
|
69
|
+
- **Competitor Analysis**: What similar creators are doing with this topic \u2014 what works and what does not
|
|
70
|
+
- **Audio/Sound Trends**: Trending sounds or music that could pair with this content
|
|
71
|
+
- **Timing**: Best time to post based on topic relevance (news cycle, seasonal, evergreen)
|
|
72
|
+
- **Platform-Specific Trends**: What is uniquely trending on each platform right now
|
|
73
|
+
Be specific about WHY each trend is relevant to this particular content idea.`,
|
|
74
|
+
buildUserMessage: (input, prev) => `Find trending angles for this content.
|
|
75
|
+
|
|
76
|
+
**Creative Brief:**
|
|
77
|
+
${prev["brief"]}
|
|
78
|
+
|
|
79
|
+
**Research:**
|
|
80
|
+
${prev["research"]}
|
|
81
|
+
|
|
82
|
+
**Original Idea:**
|
|
83
|
+
${input}`
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "fact-check",
|
|
87
|
+
label: "The Editor",
|
|
88
|
+
role: "Verifies claims and numbers",
|
|
89
|
+
icon: "\u2705",
|
|
90
|
+
systemPrompt: CONTENT_STYLE + `
|
|
91
|
+
You are The Editor (Fact-Checker). Your job is to verify every claim, statistic, and data point.
|
|
92
|
+
Output your fact-check report with these sections:
|
|
93
|
+
- **Verified Claims**: Claims that check out \u2014 mark each with \u2705 and note the source
|
|
94
|
+
- **Unverified Claims**: Claims that cannot be confirmed \u2014 mark with \u26A0\uFE0F and explain why
|
|
95
|
+
- **False/Misleading Claims**: Claims that are wrong or misleading \u2014 mark with \u274C and provide correction
|
|
96
|
+
- **Nuance Needed**: Claims that are technically true but need context to avoid being misleading
|
|
97
|
+
- **Recommended Changes**: Specific edits to make the content more accurate
|
|
98
|
+
- **Confidence Score**: Overall reliability rating of the research (High / Medium / Low)
|
|
99
|
+
Be ruthless. Misinformation damages creator credibility permanently.`,
|
|
100
|
+
buildUserMessage: (_input, prev) => `Fact-check all claims in this research:
|
|
101
|
+
|
|
102
|
+
**Research:**
|
|
103
|
+
${prev["research"]}`
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "concept",
|
|
107
|
+
label: "The Creative Director",
|
|
108
|
+
role: "Builds 3 creative angles",
|
|
109
|
+
icon: "\uD83C\uDFA8",
|
|
110
|
+
systemPrompt: CONTENT_STYLE + `
|
|
111
|
+
You are The Creative Director. Your job is to develop 3 distinct creative concepts.
|
|
112
|
+
For each concept, provide:
|
|
113
|
+
- **Concept Name**: A catchy internal title
|
|
114
|
+
- **The Angle**: What makes this approach unique \u2014 the "twist"
|
|
115
|
+
- **Emotional Arc**: How the viewer's emotions should shift from start to finish
|
|
116
|
+
- **Visual Style**: Brief description of the look and feel
|
|
117
|
+
- **Why It Works**: The psychological or cultural reason this angle will resonate
|
|
118
|
+
- **Risk Level**: Safe / Medium / Bold \u2014 and what could go wrong
|
|
119
|
+
Present all 3 concepts, then recommend the best one with a clear rationale.
|
|
120
|
+
Push creative boundaries. The safe option is rarely the viral option.`,
|
|
121
|
+
buildUserMessage: (input, prev) => `Build 3 creative concepts for this content.
|
|
122
|
+
|
|
123
|
+
**Creative Brief:**
|
|
124
|
+
${prev["brief"]}
|
|
125
|
+
|
|
126
|
+
**Research:**
|
|
127
|
+
${prev["research"]}
|
|
128
|
+
|
|
129
|
+
**Trends:**
|
|
130
|
+
${prev["trends"]}
|
|
131
|
+
|
|
132
|
+
**Fact-Check:**
|
|
133
|
+
${prev["fact-check"]}
|
|
134
|
+
|
|
135
|
+
**Original Idea:**
|
|
136
|
+
${input}`
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: "hook",
|
|
140
|
+
label: "The Attention Grabber",
|
|
141
|
+
role: "Generates 5 scroll-stopping hooks",
|
|
142
|
+
icon: "\uD83E\uDE9D",
|
|
143
|
+
systemPrompt: CONTENT_STYLE + `
|
|
144
|
+
You are The Attention Grabber. Your job is to write hooks that stop the scroll in under 1.5 seconds.
|
|
145
|
+
Generate exactly 5 hooks. For each hook, provide:
|
|
146
|
+
- **The Hook**: The exact text/voiceover for the first 1-3 seconds
|
|
147
|
+
- **Visual Pairing**: What the viewer sees while hearing this hook
|
|
148
|
+
- **Psychology**: Which cognitive bias or emotional trigger this exploits (curiosity gap, fear of missing out, pattern interrupt, etc.)
|
|
149
|
+
- **Hook Type**: Question / Bold Claim / Controversy / Story / Shock / Relatable
|
|
150
|
+
- **Strength Rating**: Rate 1-5 on stopping power
|
|
151
|
+
Rank them from strongest to weakest. The best hook should feel impossible to scroll past.
|
|
152
|
+
Avoid clickbait that does not deliver \u2014 the hook must connect to the actual content.`,
|
|
153
|
+
buildUserMessage: (_input, prev) => `Generate 5 scroll-stopping hooks for this content.
|
|
154
|
+
|
|
155
|
+
**Creative Concept:**
|
|
156
|
+
${prev["concept"]}`
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: "script",
|
|
160
|
+
label: "The Scriptwriter",
|
|
161
|
+
role: "Writes full 30-45s reel script with timestamps",
|
|
162
|
+
icon: "\u270D\uFE0F",
|
|
163
|
+
systemPrompt: CONTENT_STYLE + `
|
|
164
|
+
You are The Scriptwriter. Your job is to write a complete, production-ready script for a 30-45 second reel.
|
|
165
|
+
Structure your script with:
|
|
166
|
+
- **[0:00-0:02] HOOK**: The opening moment \u2014 use the best hook provided
|
|
167
|
+
- **[0:02-0:08] SETUP**: Establish the context or problem
|
|
168
|
+
- **[0:08-0:25] BODY**: The core content \u2014 deliver value, tell the story, make the point
|
|
169
|
+
- **[0:25-0:35] CLIMAX**: The payoff, reveal, or emotional peak
|
|
170
|
+
- **[0:35-0:45] CTA**: Call to action \u2014 what should the viewer do next
|
|
171
|
+
For each timestamp block include: Voiceover/dialogue, on-screen text, visual direction, and audio cues.
|
|
172
|
+
Write conversationally \u2014 this should sound like a real person talking, not a corporate script.
|
|
173
|
+
The pacing must be tight. Every second must earn its place.`,
|
|
174
|
+
buildUserMessage: (input, prev) => `Write a full reel script.
|
|
175
|
+
|
|
176
|
+
**Creative Brief:**
|
|
177
|
+
${prev["brief"]}
|
|
178
|
+
|
|
179
|
+
**Creative Concept:**
|
|
180
|
+
${prev["concept"]}
|
|
181
|
+
|
|
182
|
+
**Best Hooks:**
|
|
183
|
+
${prev["hook"]}
|
|
184
|
+
|
|
185
|
+
**Original Idea:**
|
|
186
|
+
${input}`
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: "meme",
|
|
190
|
+
label: "The Comedy Writer",
|
|
191
|
+
role: "Identifies humor beats and meme references",
|
|
192
|
+
icon: "\uD83D\uDE02",
|
|
193
|
+
systemPrompt: CONTENT_STYLE + `
|
|
194
|
+
You are The Comedy Writer. Your job is to inject humor, relatability, and meme culture into the script.
|
|
195
|
+
Output your humor analysis with:
|
|
196
|
+
- **Humor Beats**: Identify 3-5 moments in the script where humor can be added or amplified
|
|
197
|
+
- **Meme References**: Specific memes, formats, or internet culture references that fit naturally
|
|
198
|
+
- **Comedic Timing**: Where to place pauses, cuts, or visual gags for maximum impact
|
|
199
|
+
- **Relatable Moments**: Universal experiences the audience will connect with
|
|
200
|
+
- **Sound Effects / Audio Gags**: Specific sounds that enhance the comedy
|
|
201
|
+
- **Revised Script Sections**: Rewrite the funny parts with your improvements marked
|
|
202
|
+
Do not force humor where it does not fit. Bad comedy is worse than no comedy.
|
|
203
|
+
Know your audience \u2014 gen Z humor is different from millennial humor.`,
|
|
204
|
+
buildUserMessage: (_input, prev) => `Add humor and meme references to this script.
|
|
205
|
+
|
|
206
|
+
**Script:**
|
|
207
|
+
${prev["script"]}`
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
name: "art-direction",
|
|
211
|
+
label: "The Art Director",
|
|
212
|
+
role: "Sets visual style, palette, typography",
|
|
213
|
+
icon: "\uD83C\uDFAD",
|
|
214
|
+
systemPrompt: CONTENT_STYLE + `
|
|
215
|
+
You are The Art Director. Your job is to define the complete visual identity for this content.
|
|
216
|
+
Output your art direction with:
|
|
217
|
+
- **Visual Style**: The overall aesthetic (cinematic, lo-fi, minimalist, maximalist, retro, etc.)
|
|
218
|
+
- **Color Palette**: 4-6 hex codes with names and where each is used
|
|
219
|
+
- **Typography**: Font recommendations for on-screen text (heading, body, accent)
|
|
220
|
+
- **Lighting & Mood**: How the lighting should feel \u2014 warm, cold, dramatic, natural
|
|
221
|
+
- **Transitions**: Specific transition styles between scenes (cut, swipe, zoom, morph)
|
|
222
|
+
- **On-Screen Graphics**: Lower thirds, text overlays, callouts \u2014 style and placement
|
|
223
|
+
- **Reference Board**: Describe 3-4 visual references (films, creators, brands) that capture the vibe
|
|
224
|
+
Every visual choice must serve the story. Style without substance is just noise.`,
|
|
225
|
+
buildUserMessage: (input, prev) => `Define the art direction for this content.
|
|
226
|
+
|
|
227
|
+
**Creative Brief:**
|
|
228
|
+
${prev["brief"]}
|
|
229
|
+
|
|
230
|
+
**Script:**
|
|
231
|
+
${prev["script"]}
|
|
232
|
+
|
|
233
|
+
**Original Idea:**
|
|
234
|
+
${input}`
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
name: "storyboard",
|
|
238
|
+
label: "The Storyboarder",
|
|
239
|
+
role: "Maps every shot with timing",
|
|
240
|
+
icon: "\uD83C\uDFAC",
|
|
241
|
+
systemPrompt: CONTENT_STYLE + `
|
|
242
|
+
You are The Storyboarder. Your job is to break the script into a shot-by-shot production plan.
|
|
243
|
+
For each shot, provide:
|
|
244
|
+
- **Shot Number & Timestamp**: e.g., Shot 1 [0:00-0:02]
|
|
245
|
+
- **Shot Type**: Wide, medium, close-up, extreme close-up, POV, overhead, etc.
|
|
246
|
+
- **Camera Movement**: Static, pan, tilt, dolly, handheld, tracking, etc.
|
|
247
|
+
- **Subject/Action**: Exactly what is in frame and what is happening
|
|
248
|
+
- **On-Screen Text**: Any text overlays with exact wording and position
|
|
249
|
+
- **Audio**: Voiceover line, music cue, sound effect
|
|
250
|
+
- **Transition In/Out**: How this shot starts and ends
|
|
251
|
+
Keep the total shot count between 8-15 for a 30-45 second piece.
|
|
252
|
+
Think like a director \u2014 every cut must have a purpose and maintain visual rhythm.`,
|
|
253
|
+
buildUserMessage: (_input, prev) => `Create a detailed storyboard.
|
|
254
|
+
|
|
255
|
+
**Script:**
|
|
256
|
+
${prev["script"]}
|
|
257
|
+
|
|
258
|
+
**Art Direction:**
|
|
259
|
+
${prev["art-direction"]}`
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
name: "generate",
|
|
263
|
+
label: "The Producer",
|
|
264
|
+
role: "Writes AI image generation prompts",
|
|
265
|
+
icon: "\uD83D\uDDBC\uFE0F",
|
|
266
|
+
systemPrompt: CONTENT_STYLE + `
|
|
267
|
+
You are The Producer. Your job is to write precise AI image generation prompts for each storyboard shot.
|
|
268
|
+
For each shot that needs a generated image, provide:
|
|
269
|
+
- **Shot Reference**: Which storyboard shot this corresponds to
|
|
270
|
+
- **Primary Prompt**: A detailed, comma-separated prompt optimized for AI image generation (Midjourney/DALL-E/Flux style)
|
|
271
|
+
- **Negative Prompt**: What to exclude (distortion, bad anatomy, text artifacts, etc.)
|
|
272
|
+
- **Aspect Ratio**: 9:16 for reels, 16:9 for YouTube, 1:1 for posts
|
|
273
|
+
- **Style Modifiers**: Photorealistic, illustration, 3D render, anime, etc.
|
|
274
|
+
- **Consistency Notes**: How to maintain visual consistency across all generated images
|
|
275
|
+
Write prompts that are specific enough to get usable results on the first generation.
|
|
276
|
+
Include lighting, camera angle, color grading, and mood in every prompt.`,
|
|
277
|
+
buildUserMessage: (_input, prev) => `Write AI image generation prompts for each shot.
|
|
278
|
+
|
|
279
|
+
**Storyboard:**
|
|
280
|
+
${prev["storyboard"]}`
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
name: "assemble",
|
|
284
|
+
label: "The Editor",
|
|
285
|
+
role: "Creates editing blueprint",
|
|
286
|
+
icon: "\uD83C\uDF9E\uFE0F",
|
|
287
|
+
systemPrompt: CONTENT_STYLE + `
|
|
288
|
+
You are The Editor (Assembly). Your job is to create a complete editing blueprint for post-production.
|
|
289
|
+
Output your editing plan with:
|
|
290
|
+
- **Timeline Overview**: Second-by-second breakdown of the final edit
|
|
291
|
+
- **Cut List**: Every cut point with timecode, type (hard cut, J-cut, L-cut, match cut), and motivation
|
|
292
|
+
- **Audio Mix**: Layers of audio \u2014 voiceover, music, sound effects \u2014 with relative volume levels
|
|
293
|
+
- **Motion Graphics**: Any animated text, lower thirds, or graphic overlays with timing
|
|
294
|
+
- **Color Grading Notes**: Shot-by-shot color adjustments to maintain visual consistency
|
|
295
|
+
- **Pacing Notes**: Where to speed up, slow down, or hold for emphasis
|
|
296
|
+
- **Export Settings**: Resolution, frame rate, codec recommendations per platform
|
|
297
|
+
This is the final production document. It should be detailed enough for an editor to assemble without questions.`,
|
|
298
|
+
buildUserMessage: (_input, prev) => `Create the editing blueprint.
|
|
299
|
+
|
|
300
|
+
**Storyboard:**
|
|
301
|
+
${prev["storyboard"]}
|
|
302
|
+
|
|
303
|
+
**Generated Assets:**
|
|
304
|
+
${prev["generate"]}`
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
name: "publish",
|
|
308
|
+
label: "The Social Manager",
|
|
309
|
+
role: "Writes caption, hashtags, strategy",
|
|
310
|
+
icon: "\uD83D\uDCF1",
|
|
311
|
+
systemPrompt: CONTENT_STYLE + `
|
|
312
|
+
You are The Social Manager. Your job is to write everything needed for publishing and distribution.
|
|
313
|
+
Output your publishing package with:
|
|
314
|
+
- **Caption**: Platform-optimized caption with hook, value, and CTA (write versions for Instagram, TikTok, YouTube)
|
|
315
|
+
- **Hashtags**: Final hashtag set \u2014 5 broad reach, 5 niche, 5 trending \u2014 with expected reach tiers
|
|
316
|
+
- **Posting Strategy**: Best day and time to post, considering the topic and target audience
|
|
317
|
+
- **Engagement Bait**: First comment to post, reply templates for common responses
|
|
318
|
+
- **Cross-Promotion**: How to tease this content on other platforms
|
|
319
|
+
- **SEO**: Keywords, alt text, and description optimized for platform search
|
|
320
|
+
- **A/B Test Plan**: 2 thumbnail options and 2 caption variants to test
|
|
321
|
+
Write captions that feel native to each platform. Instagram is not TikTok is not YouTube.`,
|
|
322
|
+
buildUserMessage: (input, prev) => `Write the publishing package.
|
|
323
|
+
|
|
324
|
+
**Creative Brief:**
|
|
325
|
+
${prev["brief"]}
|
|
326
|
+
|
|
327
|
+
**Script:**
|
|
328
|
+
${prev["script"]}
|
|
329
|
+
|
|
330
|
+
**Original Idea:**
|
|
331
|
+
${input}`
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
name: "repurpose",
|
|
335
|
+
label: "The Multiplier",
|
|
336
|
+
role: "Adapts for other platforms",
|
|
337
|
+
icon: "\uD83D\uDD04",
|
|
338
|
+
systemPrompt: CONTENT_STYLE + `
|
|
339
|
+
You are The Multiplier. Your job is to maximize content ROI by adapting for every platform.
|
|
340
|
+
Output your repurposing plan with:
|
|
341
|
+
- **Twitter/X Thread**: Break the content into a 5-7 tweet thread with hooks for each tweet
|
|
342
|
+
- **LinkedIn Post**: Professional angle on the same topic, written in LinkedIn-native style
|
|
343
|
+
- **YouTube Long-Form Outline**: How to expand this into a 5-10 minute video with chapters
|
|
344
|
+
- **Blog Post Outline**: SEO-optimized article structure with H2/H3 headings and target keywords
|
|
345
|
+
- **Carousel Slides**: 8-10 slide breakdown for Instagram carousel with text per slide
|
|
346
|
+
- **Newsletter Snippet**: Email-friendly version with subject line and preview text
|
|
347
|
+
- **Podcast Talking Points**: 5 discussion questions if this were a podcast segment
|
|
348
|
+
Each adaptation must feel native to its platform, not like a lazy copy-paste.`,
|
|
349
|
+
buildUserMessage: (_input, prev) => `Create the repurposing plan.
|
|
350
|
+
|
|
351
|
+
**Script:**
|
|
352
|
+
${prev["script"]}
|
|
353
|
+
|
|
354
|
+
**Publishing Package:**
|
|
355
|
+
${prev["publish"]}`
|
|
356
|
+
}
|
|
357
|
+
];
|
|
358
|
+
|
|
359
|
+
// src/lib/pipeline.ts
|
|
360
|
+
import { spawn } from "child_process";
|
|
361
|
+
async function* runPipeline(idea, description, options) {
|
|
362
|
+
const previousOutputs = {};
|
|
363
|
+
const input = description ? `${idea}
|
|
364
|
+
|
|
365
|
+
Additional context: ${description}` : idea;
|
|
366
|
+
for (const agent of AGENTS) {
|
|
367
|
+
yield { type: "agent_start", agent: agent.name };
|
|
368
|
+
try {
|
|
369
|
+
const userMessage = agent.buildUserMessage(input, previousOutputs);
|
|
370
|
+
const deltaQueue = [];
|
|
371
|
+
let resolveWaiter = null;
|
|
372
|
+
let done = false;
|
|
373
|
+
let finalOutput = "";
|
|
374
|
+
let spawnError = null;
|
|
375
|
+
const cliCmd = options.provider === "claude" ? "claude" : options.provider === "codex" ? "codex" : undefined;
|
|
376
|
+
const callPromise = (cliCmd ? callCliProvider(cliCmd, agent.systemPrompt, userMessage, options, (text) => {
|
|
377
|
+
deltaQueue.push(text);
|
|
378
|
+
resolveWaiter?.();
|
|
379
|
+
}) : callApiProvider(agent.systemPrompt, userMessage, options, (text) => {
|
|
380
|
+
deltaQueue.push(text);
|
|
381
|
+
resolveWaiter?.();
|
|
382
|
+
})).then((out) => {
|
|
383
|
+
finalOutput = out;
|
|
384
|
+
done = true;
|
|
385
|
+
resolveWaiter?.();
|
|
386
|
+
}).catch((err) => {
|
|
387
|
+
spawnError = err instanceof Error ? err : new Error(String(err));
|
|
388
|
+
done = true;
|
|
389
|
+
resolveWaiter?.();
|
|
390
|
+
});
|
|
391
|
+
while (!done || deltaQueue.length > 0) {
|
|
392
|
+
if (deltaQueue.length > 0) {
|
|
393
|
+
const text = deltaQueue.shift();
|
|
394
|
+
yield { type: "agent_delta", agent: agent.name, text };
|
|
395
|
+
} else if (!done) {
|
|
396
|
+
await new Promise((r) => {
|
|
397
|
+
resolveWaiter = r;
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
await callPromise;
|
|
402
|
+
if (spawnError)
|
|
403
|
+
throw spawnError;
|
|
404
|
+
previousOutputs[agent.name] = finalOutput;
|
|
405
|
+
yield { type: "agent_done", agent: agent.name, output: finalOutput };
|
|
406
|
+
} catch (error) {
|
|
407
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
408
|
+
yield { type: "agent_error", agent: agent.name, error: message };
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
yield { type: "pipeline_done" };
|
|
413
|
+
}
|
|
414
|
+
function callCliProvider(command, systemPrompt, userMessage, options, onDelta) {
|
|
415
|
+
return new Promise((resolve, reject) => {
|
|
416
|
+
let args;
|
|
417
|
+
const env = { ...process.env };
|
|
418
|
+
if (command === "claude") {
|
|
419
|
+
args = [
|
|
420
|
+
"-p",
|
|
421
|
+
"--output-format",
|
|
422
|
+
"stream-json",
|
|
423
|
+
"--verbose",
|
|
424
|
+
"--no-session-persistence",
|
|
425
|
+
"--system-prompt",
|
|
426
|
+
systemPrompt
|
|
427
|
+
];
|
|
428
|
+
if (options.authMethod === "api-key" && options.authToken) {
|
|
429
|
+
env["ANTHROPIC_API_KEY"] = options.authToken;
|
|
430
|
+
}
|
|
431
|
+
} else {
|
|
432
|
+
args = [
|
|
433
|
+
"-p",
|
|
434
|
+
"--quiet",
|
|
435
|
+
`${systemPrompt}
|
|
436
|
+
|
|
437
|
+
${userMessage}`
|
|
438
|
+
];
|
|
439
|
+
if (options.authMethod === "api-key" && options.authToken) {
|
|
440
|
+
env["OPENAI_API_KEY"] = options.authToken;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const proc = spawn(command, args, { env, stdio: ["pipe", "pipe", "pipe"] });
|
|
444
|
+
let fullOutput = "";
|
|
445
|
+
let stderrOutput = "";
|
|
446
|
+
proc.stdout.on("data", (data) => {
|
|
447
|
+
const lines = data.toString().split(`
|
|
448
|
+
`).filter((l) => l.trim());
|
|
449
|
+
for (const line of lines) {
|
|
450
|
+
try {
|
|
451
|
+
const event = JSON.parse(line);
|
|
452
|
+
if (event["type"] === "content_block_delta") {
|
|
453
|
+
const delta = event["delta"];
|
|
454
|
+
if (delta?.["type"] === "text_delta" && typeof delta["text"] === "string") {
|
|
455
|
+
fullOutput += delta["text"];
|
|
456
|
+
onDelta(delta["text"]);
|
|
457
|
+
}
|
|
458
|
+
} else if (event["type"] === "result") {
|
|
459
|
+
if (typeof event["result"] === "string")
|
|
460
|
+
fullOutput = event["result"];
|
|
461
|
+
}
|
|
462
|
+
} catch {
|
|
463
|
+
if (line.trim()) {
|
|
464
|
+
fullOutput += line;
|
|
465
|
+
onDelta(line);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
proc.stderr.on("data", (data) => {
|
|
471
|
+
stderrOutput += data.toString();
|
|
472
|
+
});
|
|
473
|
+
if (command === "claude") {
|
|
474
|
+
proc.stdin.write(userMessage);
|
|
475
|
+
proc.stdin.end();
|
|
476
|
+
} else {
|
|
477
|
+
proc.stdin.end();
|
|
478
|
+
}
|
|
479
|
+
proc.on("close", (code) => {
|
|
480
|
+
if (code === 0 || fullOutput.length > 0)
|
|
481
|
+
resolve(fullOutput);
|
|
482
|
+
else
|
|
483
|
+
reject(new Error(stderrOutput || `${command} exited with code ${code}`));
|
|
484
|
+
});
|
|
485
|
+
proc.on("error", (err) => {
|
|
486
|
+
reject(new Error(`Failed to spawn ${command}: ${err.message}`));
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
async function callApiProvider(systemPrompt, userMessage, options, onDelta) {
|
|
491
|
+
const { provider, authToken, model } = options;
|
|
492
|
+
let apiUrl;
|
|
493
|
+
let headers;
|
|
494
|
+
let body;
|
|
495
|
+
switch (provider) {
|
|
496
|
+
case "openai":
|
|
497
|
+
apiUrl = "https://api.openai.com/v1/chat/completions";
|
|
498
|
+
headers = { Authorization: `Bearer ${authToken}`, "Content-Type": "application/json" };
|
|
499
|
+
body = {
|
|
500
|
+
model: model || "gpt-4o",
|
|
501
|
+
messages: [{ role: "system", content: systemPrompt }, { role: "user", content: userMessage }],
|
|
502
|
+
max_tokens: 2048,
|
|
503
|
+
stream: true
|
|
504
|
+
};
|
|
505
|
+
break;
|
|
506
|
+
case "gemini":
|
|
507
|
+
apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${model || "gemini-2.0-flash"}:streamGenerateContent?alt=sse&key=${authToken}`;
|
|
508
|
+
headers = { "Content-Type": "application/json" };
|
|
509
|
+
body = {
|
|
510
|
+
contents: [{ parts: [{ text: `${systemPrompt}
|
|
511
|
+
|
|
512
|
+
${userMessage}` }] }],
|
|
513
|
+
generationConfig: { maxOutputTokens: 2048 }
|
|
514
|
+
};
|
|
515
|
+
break;
|
|
516
|
+
case "groq":
|
|
517
|
+
apiUrl = "https://api.groq.com/openai/v1/chat/completions";
|
|
518
|
+
headers = { Authorization: `Bearer ${authToken}`, "Content-Type": "application/json" };
|
|
519
|
+
body = {
|
|
520
|
+
model: model || "llama-3.3-70b-versatile",
|
|
521
|
+
messages: [{ role: "system", content: systemPrompt }, { role: "user", content: userMessage }],
|
|
522
|
+
max_tokens: 2048,
|
|
523
|
+
stream: true
|
|
524
|
+
};
|
|
525
|
+
break;
|
|
526
|
+
case "openrouter":
|
|
527
|
+
apiUrl = "https://openrouter.ai/api/v1/chat/completions";
|
|
528
|
+
headers = {
|
|
529
|
+
Authorization: `Bearer ${authToken}`,
|
|
530
|
+
"Content-Type": "application/json",
|
|
531
|
+
"HTTP-Referer": "https://jugaadu.ai",
|
|
532
|
+
"X-Title": "Jugaadu AI"
|
|
533
|
+
};
|
|
534
|
+
body = {
|
|
535
|
+
model: model || "anthropic/claude-sonnet-4",
|
|
536
|
+
messages: [{ role: "system", content: systemPrompt }, { role: "user", content: userMessage }],
|
|
537
|
+
max_tokens: 2048,
|
|
538
|
+
stream: true
|
|
539
|
+
};
|
|
540
|
+
break;
|
|
541
|
+
case "mistral":
|
|
542
|
+
apiUrl = "https://api.mistral.ai/v1/chat/completions";
|
|
543
|
+
headers = { Authorization: `Bearer ${authToken}`, "Content-Type": "application/json" };
|
|
544
|
+
body = {
|
|
545
|
+
model: model || "mistral-large-latest",
|
|
546
|
+
messages: [{ role: "system", content: systemPrompt }, { role: "user", content: userMessage }],
|
|
547
|
+
max_tokens: 2048,
|
|
548
|
+
stream: true
|
|
549
|
+
};
|
|
550
|
+
break;
|
|
551
|
+
case "kilo":
|
|
552
|
+
apiUrl = "https://api.kilo.ai/api/openrouter/chat/completions";
|
|
553
|
+
headers = {
|
|
554
|
+
Authorization: `Bearer ${authToken || "anonymous"}`,
|
|
555
|
+
"Content-Type": "application/json"
|
|
556
|
+
};
|
|
557
|
+
body = {
|
|
558
|
+
model: model || "kilo-auto/free",
|
|
559
|
+
messages: [{ role: "system", content: systemPrompt }, { role: "user", content: userMessage }],
|
|
560
|
+
max_tokens: 2048,
|
|
561
|
+
stream: true
|
|
562
|
+
};
|
|
563
|
+
break;
|
|
564
|
+
default:
|
|
565
|
+
throw new Error(`Unsupported API provider: ${provider}`);
|
|
566
|
+
}
|
|
567
|
+
const response = await fetch(apiUrl, {
|
|
568
|
+
method: provider === "gemini" ? "POST" : "POST",
|
|
569
|
+
headers,
|
|
570
|
+
body: JSON.stringify(body)
|
|
571
|
+
});
|
|
572
|
+
if (!response.ok) {
|
|
573
|
+
const errText = await response.text();
|
|
574
|
+
throw new Error(`${provider} API error (${response.status}): ${errText.slice(0, 200)}`);
|
|
575
|
+
}
|
|
576
|
+
let fullOutput = "";
|
|
577
|
+
if (provider === "gemini") {
|
|
578
|
+
const reader = response.body?.getReader();
|
|
579
|
+
if (!reader)
|
|
580
|
+
throw new Error("No response body");
|
|
581
|
+
const decoder = new TextDecoder;
|
|
582
|
+
let buffer = "";
|
|
583
|
+
while (true) {
|
|
584
|
+
const { done, value } = await reader.read();
|
|
585
|
+
if (done)
|
|
586
|
+
break;
|
|
587
|
+
buffer += decoder.decode(value, { stream: true });
|
|
588
|
+
const lines = buffer.split(`
|
|
589
|
+
`);
|
|
590
|
+
buffer = lines.pop() || "";
|
|
591
|
+
for (const line of lines) {
|
|
592
|
+
if (!line.startsWith("data: "))
|
|
593
|
+
continue;
|
|
594
|
+
const json = line.slice(6).trim();
|
|
595
|
+
if (!json)
|
|
596
|
+
continue;
|
|
597
|
+
try {
|
|
598
|
+
const data = JSON.parse(json);
|
|
599
|
+
const candidates = data["candidates"];
|
|
600
|
+
const text = candidates?.[0]?.["content"]?.["parts"];
|
|
601
|
+
const t = text?.[0]?.["text"];
|
|
602
|
+
if (typeof t === "string") {
|
|
603
|
+
fullOutput += t;
|
|
604
|
+
onDelta(t);
|
|
605
|
+
}
|
|
606
|
+
} catch {}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
} else {
|
|
610
|
+
const reader = response.body?.getReader();
|
|
611
|
+
if (!reader)
|
|
612
|
+
throw new Error("No response body");
|
|
613
|
+
const decoder = new TextDecoder;
|
|
614
|
+
let buffer = "";
|
|
615
|
+
while (true) {
|
|
616
|
+
const { done, value } = await reader.read();
|
|
617
|
+
if (done)
|
|
618
|
+
break;
|
|
619
|
+
buffer += decoder.decode(value, { stream: true });
|
|
620
|
+
const lines = buffer.split(`
|
|
621
|
+
`);
|
|
622
|
+
buffer = lines.pop() || "";
|
|
623
|
+
for (const line of lines) {
|
|
624
|
+
if (!line.startsWith("data: "))
|
|
625
|
+
continue;
|
|
626
|
+
const json = line.slice(6).trim();
|
|
627
|
+
if (json === "[DONE]" || !json)
|
|
628
|
+
continue;
|
|
629
|
+
try {
|
|
630
|
+
const data = JSON.parse(json);
|
|
631
|
+
const choices = data["choices"];
|
|
632
|
+
const delta = choices?.[0]?.["delta"];
|
|
633
|
+
const content = delta?.["content"];
|
|
634
|
+
if (typeof content === "string") {
|
|
635
|
+
fullOutput += content;
|
|
636
|
+
onDelta(content);
|
|
637
|
+
}
|
|
638
|
+
} catch {}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return fullOutput;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// src/providers/definitions.ts
|
|
646
|
+
var PROVIDERS = [
|
|
647
|
+
{
|
|
648
|
+
id: "claude",
|
|
649
|
+
name: "Claude (Anthropic)",
|
|
650
|
+
description: "Best quality \u2014 OAuth login or API key",
|
|
651
|
+
icon: "\uD83D\uDFE3",
|
|
652
|
+
recommended: true,
|
|
653
|
+
authMethods: ["oauth", "api-key"],
|
|
654
|
+
authEnvVar: "ANTHROPIC_API_KEY",
|
|
655
|
+
defaultModel: "claude-sonnet-4-20250514",
|
|
656
|
+
cliCommand: "claude"
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
id: "codex",
|
|
660
|
+
name: "Codex (OpenAI)",
|
|
661
|
+
description: "GPT-4o powered \u2014 requires Codex CLI",
|
|
662
|
+
icon: "\uD83D\uDFE2",
|
|
663
|
+
recommended: true,
|
|
664
|
+
authMethods: ["cli", "api-key"],
|
|
665
|
+
authEnvVar: "OPENAI_API_KEY",
|
|
666
|
+
defaultModel: "gpt-4o",
|
|
667
|
+
cliCommand: "codex"
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
id: "kilo",
|
|
671
|
+
name: "KiloCode",
|
|
672
|
+
description: "Free models \u2014 no signup needed",
|
|
673
|
+
icon: "\uD83D\uDD35",
|
|
674
|
+
recommended: false,
|
|
675
|
+
authMethods: ["anonymous", "api-key"],
|
|
676
|
+
authEnvVar: "KILO_API_KEY",
|
|
677
|
+
freeModels: ["kilo-auto/free"],
|
|
678
|
+
defaultModel: "kilo-auto/free",
|
|
679
|
+
warningMessage: "Free models may produce lower quality output. For best results, use Claude or Codex."
|
|
680
|
+
},
|
|
681
|
+
{
|
|
682
|
+
id: "openai",
|
|
683
|
+
name: "OpenAI",
|
|
684
|
+
description: "GPT-4o, o1 \u2014 API key required",
|
|
685
|
+
icon: "\u2B1C",
|
|
686
|
+
recommended: false,
|
|
687
|
+
authMethods: ["api-key"],
|
|
688
|
+
authEnvVar: "OPENAI_API_KEY",
|
|
689
|
+
defaultModel: "gpt-4o",
|
|
690
|
+
warningMessage: "OpenAI models work but Claude is recommended for best content quality."
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
id: "gemini",
|
|
694
|
+
name: "Google Gemini",
|
|
695
|
+
description: "Gemini Pro \u2014 free tier available",
|
|
696
|
+
icon: "\uD83D\uDD37",
|
|
697
|
+
recommended: false,
|
|
698
|
+
authMethods: ["api-key"],
|
|
699
|
+
authEnvVar: "GOOGLE_API_KEY",
|
|
700
|
+
freeModels: ["gemini-2.0-flash"],
|
|
701
|
+
defaultModel: "gemini-2.0-flash",
|
|
702
|
+
warningMessage: "Gemini works but may not match Claude's content quality."
|
|
703
|
+
},
|
|
704
|
+
{
|
|
705
|
+
id: "groq",
|
|
706
|
+
name: "Groq",
|
|
707
|
+
description: "Ultra-fast inference \u2014 free tier",
|
|
708
|
+
icon: "\uD83D\uDFE0",
|
|
709
|
+
recommended: false,
|
|
710
|
+
authMethods: ["api-key"],
|
|
711
|
+
authEnvVar: "GROQ_API_KEY",
|
|
712
|
+
freeModels: ["llama-3.3-70b-versatile"],
|
|
713
|
+
defaultModel: "llama-3.3-70b-versatile",
|
|
714
|
+
warningMessage: "Groq is fast but open-source models may produce lower quality content."
|
|
715
|
+
},
|
|
716
|
+
{
|
|
717
|
+
id: "openrouter",
|
|
718
|
+
name: "OpenRouter",
|
|
719
|
+
description: "Access 100+ models \u2014 API key required",
|
|
720
|
+
icon: "\uD83C\uDF10",
|
|
721
|
+
recommended: false,
|
|
722
|
+
authMethods: ["api-key"],
|
|
723
|
+
authEnvVar: "OPENROUTER_API_KEY",
|
|
724
|
+
defaultModel: "anthropic/claude-sonnet-4",
|
|
725
|
+
warningMessage: "Quality depends on the model you choose through OpenRouter."
|
|
726
|
+
},
|
|
727
|
+
{
|
|
728
|
+
id: "mistral",
|
|
729
|
+
name: "Mistral AI",
|
|
730
|
+
description: "Mistral Large \u2014 API key required",
|
|
731
|
+
icon: "\uD83D\uDFE1",
|
|
732
|
+
recommended: false,
|
|
733
|
+
authMethods: ["api-key"],
|
|
734
|
+
authEnvVar: "MISTRAL_API_KEY",
|
|
735
|
+
defaultModel: "mistral-large-latest",
|
|
736
|
+
warningMessage: "Mistral is capable but Claude is recommended for best results."
|
|
737
|
+
}
|
|
738
|
+
];
|
|
739
|
+
|
|
740
|
+
// node_modules/chalk/source/vendor/ansi-styles/index.js
|
|
741
|
+
var ANSI_BACKGROUND_OFFSET = 10;
|
|
742
|
+
var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
|
|
743
|
+
var wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
|
|
744
|
+
var wrapAnsi16m = (offset = 0) => (red, green, blue) => `\x1B[${38 + offset};2;${red};${green};${blue}m`;
|
|
745
|
+
var styles = {
|
|
746
|
+
modifier: {
|
|
747
|
+
reset: [0, 0],
|
|
748
|
+
bold: [1, 22],
|
|
749
|
+
dim: [2, 22],
|
|
750
|
+
italic: [3, 23],
|
|
751
|
+
underline: [4, 24],
|
|
752
|
+
overline: [53, 55],
|
|
753
|
+
inverse: [7, 27],
|
|
754
|
+
hidden: [8, 28],
|
|
755
|
+
strikethrough: [9, 29]
|
|
756
|
+
},
|
|
757
|
+
color: {
|
|
758
|
+
black: [30, 39],
|
|
759
|
+
red: [31, 39],
|
|
760
|
+
green: [32, 39],
|
|
761
|
+
yellow: [33, 39],
|
|
762
|
+
blue: [34, 39],
|
|
763
|
+
magenta: [35, 39],
|
|
764
|
+
cyan: [36, 39],
|
|
765
|
+
white: [37, 39],
|
|
766
|
+
blackBright: [90, 39],
|
|
767
|
+
gray: [90, 39],
|
|
768
|
+
grey: [90, 39],
|
|
769
|
+
redBright: [91, 39],
|
|
770
|
+
greenBright: [92, 39],
|
|
771
|
+
yellowBright: [93, 39],
|
|
772
|
+
blueBright: [94, 39],
|
|
773
|
+
magentaBright: [95, 39],
|
|
774
|
+
cyanBright: [96, 39],
|
|
775
|
+
whiteBright: [97, 39]
|
|
776
|
+
},
|
|
777
|
+
bgColor: {
|
|
778
|
+
bgBlack: [40, 49],
|
|
779
|
+
bgRed: [41, 49],
|
|
780
|
+
bgGreen: [42, 49],
|
|
781
|
+
bgYellow: [43, 49],
|
|
782
|
+
bgBlue: [44, 49],
|
|
783
|
+
bgMagenta: [45, 49],
|
|
784
|
+
bgCyan: [46, 49],
|
|
785
|
+
bgWhite: [47, 49],
|
|
786
|
+
bgBlackBright: [100, 49],
|
|
787
|
+
bgGray: [100, 49],
|
|
788
|
+
bgGrey: [100, 49],
|
|
789
|
+
bgRedBright: [101, 49],
|
|
790
|
+
bgGreenBright: [102, 49],
|
|
791
|
+
bgYellowBright: [103, 49],
|
|
792
|
+
bgBlueBright: [104, 49],
|
|
793
|
+
bgMagentaBright: [105, 49],
|
|
794
|
+
bgCyanBright: [106, 49],
|
|
795
|
+
bgWhiteBright: [107, 49]
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
var modifierNames = Object.keys(styles.modifier);
|
|
799
|
+
var foregroundColorNames = Object.keys(styles.color);
|
|
800
|
+
var backgroundColorNames = Object.keys(styles.bgColor);
|
|
801
|
+
var colorNames = [...foregroundColorNames, ...backgroundColorNames];
|
|
802
|
+
function assembleStyles() {
|
|
803
|
+
const codes = new Map;
|
|
804
|
+
for (const [groupName, group] of Object.entries(styles)) {
|
|
805
|
+
for (const [styleName, style] of Object.entries(group)) {
|
|
806
|
+
styles[styleName] = {
|
|
807
|
+
open: `\x1B[${style[0]}m`,
|
|
808
|
+
close: `\x1B[${style[1]}m`
|
|
809
|
+
};
|
|
810
|
+
group[styleName] = styles[styleName];
|
|
811
|
+
codes.set(style[0], style[1]);
|
|
812
|
+
}
|
|
813
|
+
Object.defineProperty(styles, groupName, {
|
|
814
|
+
value: group,
|
|
815
|
+
enumerable: false
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
Object.defineProperty(styles, "codes", {
|
|
819
|
+
value: codes,
|
|
820
|
+
enumerable: false
|
|
821
|
+
});
|
|
822
|
+
styles.color.close = "\x1B[39m";
|
|
823
|
+
styles.bgColor.close = "\x1B[49m";
|
|
824
|
+
styles.color.ansi = wrapAnsi16();
|
|
825
|
+
styles.color.ansi256 = wrapAnsi256();
|
|
826
|
+
styles.color.ansi16m = wrapAnsi16m();
|
|
827
|
+
styles.bgColor.ansi = wrapAnsi16(ANSI_BACKGROUND_OFFSET);
|
|
828
|
+
styles.bgColor.ansi256 = wrapAnsi256(ANSI_BACKGROUND_OFFSET);
|
|
829
|
+
styles.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET);
|
|
830
|
+
Object.defineProperties(styles, {
|
|
831
|
+
rgbToAnsi256: {
|
|
832
|
+
value(red, green, blue) {
|
|
833
|
+
if (red === green && green === blue) {
|
|
834
|
+
if (red < 8) {
|
|
835
|
+
return 16;
|
|
836
|
+
}
|
|
837
|
+
if (red > 248) {
|
|
838
|
+
return 231;
|
|
839
|
+
}
|
|
840
|
+
return Math.round((red - 8) / 247 * 24) + 232;
|
|
841
|
+
}
|
|
842
|
+
return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green / 255 * 5) + Math.round(blue / 255 * 5);
|
|
843
|
+
},
|
|
844
|
+
enumerable: false
|
|
845
|
+
},
|
|
846
|
+
hexToRgb: {
|
|
847
|
+
value(hex) {
|
|
848
|
+
const matches = /[a-f\d]{6}|[a-f\d]{3}/i.exec(hex.toString(16));
|
|
849
|
+
if (!matches) {
|
|
850
|
+
return [0, 0, 0];
|
|
851
|
+
}
|
|
852
|
+
let [colorString] = matches;
|
|
853
|
+
if (colorString.length === 3) {
|
|
854
|
+
colorString = [...colorString].map((character) => character + character).join("");
|
|
855
|
+
}
|
|
856
|
+
const integer = Number.parseInt(colorString, 16);
|
|
857
|
+
return [
|
|
858
|
+
integer >> 16 & 255,
|
|
859
|
+
integer >> 8 & 255,
|
|
860
|
+
integer & 255
|
|
861
|
+
];
|
|
862
|
+
},
|
|
863
|
+
enumerable: false
|
|
864
|
+
},
|
|
865
|
+
hexToAnsi256: {
|
|
866
|
+
value: (hex) => styles.rgbToAnsi256(...styles.hexToRgb(hex)),
|
|
867
|
+
enumerable: false
|
|
868
|
+
},
|
|
869
|
+
ansi256ToAnsi: {
|
|
870
|
+
value(code) {
|
|
871
|
+
if (code < 8) {
|
|
872
|
+
return 30 + code;
|
|
873
|
+
}
|
|
874
|
+
if (code < 16) {
|
|
875
|
+
return 90 + (code - 8);
|
|
876
|
+
}
|
|
877
|
+
let red;
|
|
878
|
+
let green;
|
|
879
|
+
let blue;
|
|
880
|
+
if (code >= 232) {
|
|
881
|
+
red = ((code - 232) * 10 + 8) / 255;
|
|
882
|
+
green = red;
|
|
883
|
+
blue = red;
|
|
884
|
+
} else {
|
|
885
|
+
code -= 16;
|
|
886
|
+
const remainder = code % 36;
|
|
887
|
+
red = Math.floor(code / 36) / 5;
|
|
888
|
+
green = Math.floor(remainder / 6) / 5;
|
|
889
|
+
blue = remainder % 6 / 5;
|
|
890
|
+
}
|
|
891
|
+
const value = Math.max(red, green, blue) * 2;
|
|
892
|
+
if (value === 0) {
|
|
893
|
+
return 30;
|
|
894
|
+
}
|
|
895
|
+
let result = 30 + (Math.round(blue) << 2 | Math.round(green) << 1 | Math.round(red));
|
|
896
|
+
if (value === 2) {
|
|
897
|
+
result += 60;
|
|
898
|
+
}
|
|
899
|
+
return result;
|
|
900
|
+
},
|
|
901
|
+
enumerable: false
|
|
902
|
+
},
|
|
903
|
+
rgbToAnsi: {
|
|
904
|
+
value: (red, green, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red, green, blue)),
|
|
905
|
+
enumerable: false
|
|
906
|
+
},
|
|
907
|
+
hexToAnsi: {
|
|
908
|
+
value: (hex) => styles.ansi256ToAnsi(styles.hexToAnsi256(hex)),
|
|
909
|
+
enumerable: false
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
return styles;
|
|
913
|
+
}
|
|
914
|
+
var ansiStyles = assembleStyles();
|
|
915
|
+
var ansi_styles_default = ansiStyles;
|
|
916
|
+
|
|
917
|
+
// node_modules/chalk/source/vendor/supports-color/index.js
|
|
918
|
+
import process2 from "process";
|
|
919
|
+
import os from "os";
|
|
920
|
+
import tty from "tty";
|
|
921
|
+
function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process2.argv) {
|
|
922
|
+
const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
|
|
923
|
+
const position = argv.indexOf(prefix + flag);
|
|
924
|
+
const terminatorPosition = argv.indexOf("--");
|
|
925
|
+
return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
|
|
926
|
+
}
|
|
927
|
+
var { env } = process2;
|
|
928
|
+
var flagForceColor;
|
|
929
|
+
if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
|
|
930
|
+
flagForceColor = 0;
|
|
931
|
+
} else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
|
|
932
|
+
flagForceColor = 1;
|
|
933
|
+
}
|
|
934
|
+
function envForceColor() {
|
|
935
|
+
if ("FORCE_COLOR" in env) {
|
|
936
|
+
if (env.FORCE_COLOR === "true") {
|
|
937
|
+
return 1;
|
|
938
|
+
}
|
|
939
|
+
if (env.FORCE_COLOR === "false") {
|
|
940
|
+
return 0;
|
|
941
|
+
}
|
|
942
|
+
return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
function translateLevel(level) {
|
|
946
|
+
if (level === 0) {
|
|
947
|
+
return false;
|
|
948
|
+
}
|
|
949
|
+
return {
|
|
950
|
+
level,
|
|
951
|
+
hasBasic: true,
|
|
952
|
+
has256: level >= 2,
|
|
953
|
+
has16m: level >= 3
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
|
|
957
|
+
const noFlagForceColor = envForceColor();
|
|
958
|
+
if (noFlagForceColor !== undefined) {
|
|
959
|
+
flagForceColor = noFlagForceColor;
|
|
960
|
+
}
|
|
961
|
+
const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
|
|
962
|
+
if (forceColor === 0) {
|
|
963
|
+
return 0;
|
|
964
|
+
}
|
|
965
|
+
if (sniffFlags) {
|
|
966
|
+
if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
|
|
967
|
+
return 3;
|
|
968
|
+
}
|
|
969
|
+
if (hasFlag("color=256")) {
|
|
970
|
+
return 2;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
if ("TF_BUILD" in env && "AGENT_NAME" in env) {
|
|
974
|
+
return 1;
|
|
975
|
+
}
|
|
976
|
+
if (haveStream && !streamIsTTY && forceColor === undefined) {
|
|
977
|
+
return 0;
|
|
978
|
+
}
|
|
979
|
+
const min = forceColor || 0;
|
|
980
|
+
if (env.TERM === "dumb") {
|
|
981
|
+
return min;
|
|
982
|
+
}
|
|
983
|
+
if (process2.platform === "win32") {
|
|
984
|
+
const osRelease = os.release().split(".");
|
|
985
|
+
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
986
|
+
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
987
|
+
}
|
|
988
|
+
return 1;
|
|
989
|
+
}
|
|
990
|
+
if ("CI" in env) {
|
|
991
|
+
if (["GITHUB_ACTIONS", "GITEA_ACTIONS", "CIRCLECI"].some((key) => (key in env))) {
|
|
992
|
+
return 3;
|
|
993
|
+
}
|
|
994
|
+
if (["TRAVIS", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE"].some((sign) => (sign in env)) || env.CI_NAME === "codeship") {
|
|
995
|
+
return 1;
|
|
996
|
+
}
|
|
997
|
+
return min;
|
|
998
|
+
}
|
|
999
|
+
if ("TEAMCITY_VERSION" in env) {
|
|
1000
|
+
return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
|
|
1001
|
+
}
|
|
1002
|
+
if (env.COLORTERM === "truecolor") {
|
|
1003
|
+
return 3;
|
|
1004
|
+
}
|
|
1005
|
+
if (env.TERM === "xterm-kitty") {
|
|
1006
|
+
return 3;
|
|
1007
|
+
}
|
|
1008
|
+
if (env.TERM === "xterm-ghostty") {
|
|
1009
|
+
return 3;
|
|
1010
|
+
}
|
|
1011
|
+
if (env.TERM === "wezterm") {
|
|
1012
|
+
return 3;
|
|
1013
|
+
}
|
|
1014
|
+
if ("TERM_PROGRAM" in env) {
|
|
1015
|
+
const version = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
|
|
1016
|
+
switch (env.TERM_PROGRAM) {
|
|
1017
|
+
case "iTerm.app": {
|
|
1018
|
+
return version >= 3 ? 3 : 2;
|
|
1019
|
+
}
|
|
1020
|
+
case "Apple_Terminal": {
|
|
1021
|
+
return 2;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
if (/-256(color)?$/i.test(env.TERM)) {
|
|
1026
|
+
return 2;
|
|
1027
|
+
}
|
|
1028
|
+
if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
|
|
1029
|
+
return 1;
|
|
1030
|
+
}
|
|
1031
|
+
if ("COLORTERM" in env) {
|
|
1032
|
+
return 1;
|
|
1033
|
+
}
|
|
1034
|
+
return min;
|
|
1035
|
+
}
|
|
1036
|
+
function createSupportsColor(stream, options = {}) {
|
|
1037
|
+
const level = _supportsColor(stream, {
|
|
1038
|
+
streamIsTTY: stream && stream.isTTY,
|
|
1039
|
+
...options
|
|
1040
|
+
});
|
|
1041
|
+
return translateLevel(level);
|
|
1042
|
+
}
|
|
1043
|
+
var supportsColor = {
|
|
1044
|
+
stdout: createSupportsColor({ isTTY: tty.isatty(1) }),
|
|
1045
|
+
stderr: createSupportsColor({ isTTY: tty.isatty(2) })
|
|
1046
|
+
};
|
|
1047
|
+
var supports_color_default = supportsColor;
|
|
1048
|
+
|
|
1049
|
+
// node_modules/chalk/source/utilities.js
|
|
1050
|
+
function stringReplaceAll(string, substring, replacer) {
|
|
1051
|
+
let index = string.indexOf(substring);
|
|
1052
|
+
if (index === -1) {
|
|
1053
|
+
return string;
|
|
1054
|
+
}
|
|
1055
|
+
const substringLength = substring.length;
|
|
1056
|
+
let endIndex = 0;
|
|
1057
|
+
let returnValue = "";
|
|
1058
|
+
do {
|
|
1059
|
+
returnValue += string.slice(endIndex, index) + substring + replacer;
|
|
1060
|
+
endIndex = index + substringLength;
|
|
1061
|
+
index = string.indexOf(substring, endIndex);
|
|
1062
|
+
} while (index !== -1);
|
|
1063
|
+
returnValue += string.slice(endIndex);
|
|
1064
|
+
return returnValue;
|
|
1065
|
+
}
|
|
1066
|
+
function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) {
|
|
1067
|
+
let endIndex = 0;
|
|
1068
|
+
let returnValue = "";
|
|
1069
|
+
do {
|
|
1070
|
+
const gotCR = string[index - 1] === "\r";
|
|
1071
|
+
returnValue += string.slice(endIndex, gotCR ? index - 1 : index) + prefix + (gotCR ? `\r
|
|
1072
|
+
` : `
|
|
1073
|
+
`) + postfix;
|
|
1074
|
+
endIndex = index + 1;
|
|
1075
|
+
index = string.indexOf(`
|
|
1076
|
+
`, endIndex);
|
|
1077
|
+
} while (index !== -1);
|
|
1078
|
+
returnValue += string.slice(endIndex);
|
|
1079
|
+
return returnValue;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// node_modules/chalk/source/index.js
|
|
1083
|
+
var { stdout: stdoutColor, stderr: stderrColor } = supports_color_default;
|
|
1084
|
+
var GENERATOR = Symbol("GENERATOR");
|
|
1085
|
+
var STYLER = Symbol("STYLER");
|
|
1086
|
+
var IS_EMPTY = Symbol("IS_EMPTY");
|
|
1087
|
+
var levelMapping = [
|
|
1088
|
+
"ansi",
|
|
1089
|
+
"ansi",
|
|
1090
|
+
"ansi256",
|
|
1091
|
+
"ansi16m"
|
|
1092
|
+
];
|
|
1093
|
+
var styles2 = Object.create(null);
|
|
1094
|
+
var applyOptions = (object, options = {}) => {
|
|
1095
|
+
if (options.level && !(Number.isInteger(options.level) && options.level >= 0 && options.level <= 3)) {
|
|
1096
|
+
throw new Error("The `level` option should be an integer from 0 to 3");
|
|
1097
|
+
}
|
|
1098
|
+
const colorLevel = stdoutColor ? stdoutColor.level : 0;
|
|
1099
|
+
object.level = options.level === undefined ? colorLevel : options.level;
|
|
1100
|
+
};
|
|
1101
|
+
var chalkFactory = (options) => {
|
|
1102
|
+
const chalk = (...strings) => strings.join(" ");
|
|
1103
|
+
applyOptions(chalk, options);
|
|
1104
|
+
Object.setPrototypeOf(chalk, createChalk.prototype);
|
|
1105
|
+
return chalk;
|
|
1106
|
+
};
|
|
1107
|
+
function createChalk(options) {
|
|
1108
|
+
return chalkFactory(options);
|
|
1109
|
+
}
|
|
1110
|
+
Object.setPrototypeOf(createChalk.prototype, Function.prototype);
|
|
1111
|
+
for (const [styleName, style] of Object.entries(ansi_styles_default)) {
|
|
1112
|
+
styles2[styleName] = {
|
|
1113
|
+
get() {
|
|
1114
|
+
const builder = createBuilder(this, createStyler(style.open, style.close, this[STYLER]), this[IS_EMPTY]);
|
|
1115
|
+
Object.defineProperty(this, styleName, { value: builder });
|
|
1116
|
+
return builder;
|
|
1117
|
+
}
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
styles2.visible = {
|
|
1121
|
+
get() {
|
|
1122
|
+
const builder = createBuilder(this, this[STYLER], true);
|
|
1123
|
+
Object.defineProperty(this, "visible", { value: builder });
|
|
1124
|
+
return builder;
|
|
1125
|
+
}
|
|
1126
|
+
};
|
|
1127
|
+
var getModelAnsi = (model, level, type, ...arguments_) => {
|
|
1128
|
+
if (model === "rgb") {
|
|
1129
|
+
if (level === "ansi16m") {
|
|
1130
|
+
return ansi_styles_default[type].ansi16m(...arguments_);
|
|
1131
|
+
}
|
|
1132
|
+
if (level === "ansi256") {
|
|
1133
|
+
return ansi_styles_default[type].ansi256(ansi_styles_default.rgbToAnsi256(...arguments_));
|
|
1134
|
+
}
|
|
1135
|
+
return ansi_styles_default[type].ansi(ansi_styles_default.rgbToAnsi(...arguments_));
|
|
1136
|
+
}
|
|
1137
|
+
if (model === "hex") {
|
|
1138
|
+
return getModelAnsi("rgb", level, type, ...ansi_styles_default.hexToRgb(...arguments_));
|
|
1139
|
+
}
|
|
1140
|
+
return ansi_styles_default[type][model](...arguments_);
|
|
1141
|
+
};
|
|
1142
|
+
var usedModels = ["rgb", "hex", "ansi256"];
|
|
1143
|
+
for (const model of usedModels) {
|
|
1144
|
+
styles2[model] = {
|
|
1145
|
+
get() {
|
|
1146
|
+
const { level } = this;
|
|
1147
|
+
return function(...arguments_) {
|
|
1148
|
+
const styler = createStyler(getModelAnsi(model, levelMapping[level], "color", ...arguments_), ansi_styles_default.color.close, this[STYLER]);
|
|
1149
|
+
return createBuilder(this, styler, this[IS_EMPTY]);
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
const bgModel = "bg" + model[0].toUpperCase() + model.slice(1);
|
|
1154
|
+
styles2[bgModel] = {
|
|
1155
|
+
get() {
|
|
1156
|
+
const { level } = this;
|
|
1157
|
+
return function(...arguments_) {
|
|
1158
|
+
const styler = createStyler(getModelAnsi(model, levelMapping[level], "bgColor", ...arguments_), ansi_styles_default.bgColor.close, this[STYLER]);
|
|
1159
|
+
return createBuilder(this, styler, this[IS_EMPTY]);
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
var proto = Object.defineProperties(() => {}, {
|
|
1165
|
+
...styles2,
|
|
1166
|
+
level: {
|
|
1167
|
+
enumerable: true,
|
|
1168
|
+
get() {
|
|
1169
|
+
return this[GENERATOR].level;
|
|
1170
|
+
},
|
|
1171
|
+
set(level) {
|
|
1172
|
+
this[GENERATOR].level = level;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
});
|
|
1176
|
+
var createStyler = (open, close, parent) => {
|
|
1177
|
+
let openAll;
|
|
1178
|
+
let closeAll;
|
|
1179
|
+
if (parent === undefined) {
|
|
1180
|
+
openAll = open;
|
|
1181
|
+
closeAll = close;
|
|
1182
|
+
} else {
|
|
1183
|
+
openAll = parent.openAll + open;
|
|
1184
|
+
closeAll = close + parent.closeAll;
|
|
1185
|
+
}
|
|
1186
|
+
return {
|
|
1187
|
+
open,
|
|
1188
|
+
close,
|
|
1189
|
+
openAll,
|
|
1190
|
+
closeAll,
|
|
1191
|
+
parent
|
|
1192
|
+
};
|
|
1193
|
+
};
|
|
1194
|
+
var createBuilder = (self, _styler, _isEmpty) => {
|
|
1195
|
+
const builder = (...arguments_) => applyStyle(builder, arguments_.length === 1 ? "" + arguments_[0] : arguments_.join(" "));
|
|
1196
|
+
Object.setPrototypeOf(builder, proto);
|
|
1197
|
+
builder[GENERATOR] = self;
|
|
1198
|
+
builder[STYLER] = _styler;
|
|
1199
|
+
builder[IS_EMPTY] = _isEmpty;
|
|
1200
|
+
return builder;
|
|
1201
|
+
};
|
|
1202
|
+
var applyStyle = (self, string) => {
|
|
1203
|
+
if (self.level <= 0 || !string) {
|
|
1204
|
+
return self[IS_EMPTY] ? "" : string;
|
|
1205
|
+
}
|
|
1206
|
+
let styler = self[STYLER];
|
|
1207
|
+
if (styler === undefined) {
|
|
1208
|
+
return string;
|
|
1209
|
+
}
|
|
1210
|
+
const { openAll, closeAll } = styler;
|
|
1211
|
+
if (string.includes("\x1B")) {
|
|
1212
|
+
while (styler !== undefined) {
|
|
1213
|
+
string = stringReplaceAll(string, styler.close, styler.open);
|
|
1214
|
+
styler = styler.parent;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
const lfIndex = string.indexOf(`
|
|
1218
|
+
`);
|
|
1219
|
+
if (lfIndex !== -1) {
|
|
1220
|
+
string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex);
|
|
1221
|
+
}
|
|
1222
|
+
return openAll + string + closeAll;
|
|
1223
|
+
};
|
|
1224
|
+
Object.defineProperties(createChalk.prototype, styles2);
|
|
1225
|
+
var chalk = createChalk();
|
|
1226
|
+
var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
|
|
1227
|
+
var source_default = chalk;
|
|
1228
|
+
|
|
1229
|
+
// src/tui/theme.ts
|
|
1230
|
+
var hex = source_default.hex;
|
|
1231
|
+
var colors = {
|
|
1232
|
+
bg: "#0a0a0a",
|
|
1233
|
+
panel: "#141414",
|
|
1234
|
+
element: "#1e1e1e",
|
|
1235
|
+
surface: "#252526",
|
|
1236
|
+
border: "#333333",
|
|
1237
|
+
borderActive: "#5c9cf5",
|
|
1238
|
+
borderDone: "#89d185",
|
|
1239
|
+
borderError: "#e06c75",
|
|
1240
|
+
text: "#eeeeee",
|
|
1241
|
+
textMuted: "#808080",
|
|
1242
|
+
textDim: "#555555",
|
|
1243
|
+
textDimmer: "#3a3a3a",
|
|
1244
|
+
primary: "#5c9cf5",
|
|
1245
|
+
secondary: "#c792ea",
|
|
1246
|
+
accent: "#f5a742",
|
|
1247
|
+
cyan: "#56b6c2",
|
|
1248
|
+
green: "#89d185",
|
|
1249
|
+
red: "#e06c75",
|
|
1250
|
+
yellow: "#e5c07b",
|
|
1251
|
+
orange: "#d19a66",
|
|
1252
|
+
magenta: "#c678dd",
|
|
1253
|
+
blue: "#61afef"
|
|
1254
|
+
};
|
|
1255
|
+
var theme = {
|
|
1256
|
+
text: hex(colors.text),
|
|
1257
|
+
textBold: hex(colors.text).bold,
|
|
1258
|
+
muted: hex(colors.textMuted),
|
|
1259
|
+
dim: hex(colors.textDim),
|
|
1260
|
+
dimmer: hex(colors.textDimmer),
|
|
1261
|
+
primary: hex(colors.primary),
|
|
1262
|
+
primaryBold: hex(colors.primary).bold,
|
|
1263
|
+
secondary: hex(colors.secondary),
|
|
1264
|
+
accent: hex(colors.accent),
|
|
1265
|
+
cyan: hex(colors.cyan),
|
|
1266
|
+
green: hex(colors.green),
|
|
1267
|
+
red: hex(colors.red),
|
|
1268
|
+
yellow: hex(colors.yellow),
|
|
1269
|
+
orange: hex(colors.orange),
|
|
1270
|
+
magenta: hex(colors.magenta),
|
|
1271
|
+
blue: hex(colors.blue),
|
|
1272
|
+
border: hex(colors.border),
|
|
1273
|
+
borderActive: hex(colors.borderActive),
|
|
1274
|
+
white: hex(colors.text),
|
|
1275
|
+
gradient: (text) => {
|
|
1276
|
+
const chars = [...text];
|
|
1277
|
+
return chars.map((char, i) => {
|
|
1278
|
+
const ratio = i / Math.max(chars.length - 1, 1);
|
|
1279
|
+
const r = Math.round(92 + (199 - 92) * ratio);
|
|
1280
|
+
const g = Math.round(156 + (146 - 156) * ratio);
|
|
1281
|
+
const b = Math.round(245 + (234 - 245) * ratio);
|
|
1282
|
+
return source_default.rgb(r, g, b)(char);
|
|
1283
|
+
}).join("");
|
|
1284
|
+
},
|
|
1285
|
+
gradientText: (text) => {
|
|
1286
|
+
const chars = [...text];
|
|
1287
|
+
return chars.map((char, i) => {
|
|
1288
|
+
const ratio = i / Math.max(chars.length - 1, 1);
|
|
1289
|
+
const r = Math.round(92 + (199 - 92) * ratio);
|
|
1290
|
+
const g = Math.round(156 + (146 - 156) * ratio);
|
|
1291
|
+
const b = Math.round(245 + (234 - 245) * ratio);
|
|
1292
|
+
return source_default.rgb(r, g, b)(char);
|
|
1293
|
+
}).join("");
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
1296
|
+
var box = {
|
|
1297
|
+
topLeft: "\u256D",
|
|
1298
|
+
topRight: "\u256E",
|
|
1299
|
+
bottomLeft: "\u2570",
|
|
1300
|
+
bottomRight: "\u256F",
|
|
1301
|
+
horizontal: "\u2500",
|
|
1302
|
+
vertical: "\u2502",
|
|
1303
|
+
teeRight: "\u251C",
|
|
1304
|
+
teeLeft: "\u2524",
|
|
1305
|
+
teeDown: "\u252C",
|
|
1306
|
+
teeUp: "\u2534",
|
|
1307
|
+
cross: "\u253C",
|
|
1308
|
+
hTopLeft: "\u250F",
|
|
1309
|
+
hTopRight: "\u2513",
|
|
1310
|
+
hBottomLeft: "\u2517",
|
|
1311
|
+
hBottomRight: "\u251B",
|
|
1312
|
+
hHorizontal: "\u2501",
|
|
1313
|
+
hVertical: "\u2503",
|
|
1314
|
+
hTeeRight: "\u2523",
|
|
1315
|
+
hTeeLeft: "\u252B",
|
|
1316
|
+
dTopLeft: "\u2554",
|
|
1317
|
+
dTopRight: "\u2557",
|
|
1318
|
+
dBottomLeft: "\u255A",
|
|
1319
|
+
dBottomRight: "\u255D",
|
|
1320
|
+
dHorizontal: "\u2550",
|
|
1321
|
+
dVertical: "\u2551"
|
|
1322
|
+
};
|
|
1323
|
+
var spinnerFrames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1324
|
+
function scannerFrame(frame, width = 12) {
|
|
1325
|
+
const pos = frame % (width * 2);
|
|
1326
|
+
const actualPos = pos < width ? pos : width * 2 - pos - 1;
|
|
1327
|
+
const result = [];
|
|
1328
|
+
for (let i = 0;i < width; i++) {
|
|
1329
|
+
const dist = Math.abs(i - actualPos);
|
|
1330
|
+
if (dist === 0)
|
|
1331
|
+
result.push(theme.primary("\u2B25"));
|
|
1332
|
+
else if (dist === 1)
|
|
1333
|
+
result.push(theme.blue("\u25C6"));
|
|
1334
|
+
else if (dist === 2)
|
|
1335
|
+
result.push(theme.dim("\u2B29"));
|
|
1336
|
+
else
|
|
1337
|
+
result.push(theme.dimmer("\xB7"));
|
|
1338
|
+
}
|
|
1339
|
+
return result.join("");
|
|
1340
|
+
}
|
|
1341
|
+
function progressBar(current, total, width = 20) {
|
|
1342
|
+
const filled = Math.round(current / total * width);
|
|
1343
|
+
const empty = width - filled;
|
|
1344
|
+
return theme.primary("\u2588".repeat(filled)) + theme.dimmer("\u2591".repeat(empty));
|
|
1345
|
+
}
|
|
1346
|
+
var LOGO = [
|
|
1347
|
+
" \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557",
|
|
1348
|
+
" \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551",
|
|
1349
|
+
" \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551",
|
|
1350
|
+
"\u2588\u2588 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551",
|
|
1351
|
+
"\u255A\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D",
|
|
1352
|
+
" \u255A\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D"
|
|
1353
|
+
];
|
|
1354
|
+
function renderLogo(small = false) {
|
|
1355
|
+
if (small)
|
|
1356
|
+
return ` ${theme.gradient("\u257A\u254B\u2578 Jugaadu AI")}`;
|
|
1357
|
+
return LOGO.map((line) => ` ${theme.gradient(line)}`).join(`
|
|
1358
|
+
`);
|
|
1359
|
+
}
|
|
1360
|
+
var style = {
|
|
1361
|
+
statusWaiting: (s) => theme.dim(s),
|
|
1362
|
+
statusRunning: (s) => theme.primaryBold(s),
|
|
1363
|
+
statusDone: (s) => theme.green(s),
|
|
1364
|
+
statusError: (s) => theme.red(s),
|
|
1365
|
+
panelTitle: (s) => theme.primaryBold(s),
|
|
1366
|
+
key: (s) => hex(colors.surface).bgHex(colors.textMuted)(` ${s} `),
|
|
1367
|
+
keyHint: (s) => theme.muted(s),
|
|
1368
|
+
selected: (s) => theme.primaryBold(s),
|
|
1369
|
+
selectedBg: (s) => source_default.bgHex(colors.element)(s),
|
|
1370
|
+
recommended: (s) => theme.green(s),
|
|
1371
|
+
providerWarning: (s) => theme.yellow(s),
|
|
1372
|
+
streamCursor: (s) => theme.primary(s),
|
|
1373
|
+
badge: (label, color) => hex(colors.bg).bgHex(color)(` ${label} `),
|
|
1374
|
+
freeBadge: () => hex(colors.bg).bgHex(colors.green)(" FREE "),
|
|
1375
|
+
recBadge: () => hex(colors.bg).bgHex(colors.primary)(" \u2605 "),
|
|
1376
|
+
progressFilled: (s) => theme.primary(s),
|
|
1377
|
+
progressEmpty: (s) => theme.dimmer(s)
|
|
1378
|
+
};
|
|
1379
|
+
|
|
1380
|
+
// src/tui/renderer.ts
|
|
1381
|
+
var ESC = "\x1B";
|
|
1382
|
+
var CLEAR_SCREEN = `${ESC}[2J${ESC}[H`;
|
|
1383
|
+
var HIDE_CURSOR = `${ESC}[?25l`;
|
|
1384
|
+
var SHOW_CURSOR = `${ESC}[?25h`;
|
|
1385
|
+
function clearScreen() {
|
|
1386
|
+
process.stdout.write(CLEAR_SCREEN);
|
|
1387
|
+
}
|
|
1388
|
+
function hideCursor() {
|
|
1389
|
+
process.stdout.write(HIDE_CURSOR);
|
|
1390
|
+
}
|
|
1391
|
+
function showCursor() {
|
|
1392
|
+
process.stdout.write(SHOW_CURSOR);
|
|
1393
|
+
}
|
|
1394
|
+
function writeFrame(content) {
|
|
1395
|
+
process.stdout.write(CLEAR_SCREEN + content);
|
|
1396
|
+
}
|
|
1397
|
+
function tw() {
|
|
1398
|
+
return process.stdout.columns || 100;
|
|
1399
|
+
}
|
|
1400
|
+
function th() {
|
|
1401
|
+
return process.stdout.rows || 40;
|
|
1402
|
+
}
|
|
1403
|
+
function strip(str) {
|
|
1404
|
+
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
1405
|
+
}
|
|
1406
|
+
function vlen(str) {
|
|
1407
|
+
return strip(str).length;
|
|
1408
|
+
}
|
|
1409
|
+
function trunc(str, max) {
|
|
1410
|
+
if (strip(str).length <= max)
|
|
1411
|
+
return str;
|
|
1412
|
+
let count = 0, result = "", inEsc = false;
|
|
1413
|
+
for (const ch of str) {
|
|
1414
|
+
if (ch === "\x1B") {
|
|
1415
|
+
inEsc = true;
|
|
1416
|
+
result += ch;
|
|
1417
|
+
continue;
|
|
1418
|
+
}
|
|
1419
|
+
if (inEsc) {
|
|
1420
|
+
result += ch;
|
|
1421
|
+
if (ch === "m")
|
|
1422
|
+
inEsc = false;
|
|
1423
|
+
continue;
|
|
1424
|
+
}
|
|
1425
|
+
if (count >= max - 1) {
|
|
1426
|
+
result += "\u2026";
|
|
1427
|
+
break;
|
|
1428
|
+
}
|
|
1429
|
+
result += ch;
|
|
1430
|
+
count++;
|
|
1431
|
+
}
|
|
1432
|
+
return result;
|
|
1433
|
+
}
|
|
1434
|
+
function pad(str, w) {
|
|
1435
|
+
const v = vlen(str);
|
|
1436
|
+
return v >= w ? str : str + " ".repeat(w - v);
|
|
1437
|
+
}
|
|
1438
|
+
function center(str, w) {
|
|
1439
|
+
const v = vlen(str);
|
|
1440
|
+
if (v >= w)
|
|
1441
|
+
return str;
|
|
1442
|
+
const l = Math.floor((w - v) / 2);
|
|
1443
|
+
return " ".repeat(l) + str + " ".repeat(w - v - l);
|
|
1444
|
+
}
|
|
1445
|
+
function elapsed(start, end) {
|
|
1446
|
+
if (!start)
|
|
1447
|
+
return "";
|
|
1448
|
+
return `${(((end || Date.now()) - start) / 1000).toFixed(1)}s`;
|
|
1449
|
+
}
|
|
1450
|
+
function wrap(text, w) {
|
|
1451
|
+
const out = [];
|
|
1452
|
+
for (const raw of text.split(`
|
|
1453
|
+
`)) {
|
|
1454
|
+
if (strip(raw).length <= w) {
|
|
1455
|
+
out.push(raw);
|
|
1456
|
+
continue;
|
|
1457
|
+
}
|
|
1458
|
+
let cur = "", len = 0;
|
|
1459
|
+
for (const word of raw.split(" ")) {
|
|
1460
|
+
const wl = strip(word).length;
|
|
1461
|
+
if (len + wl + (len > 0 ? 1 : 0) > w) {
|
|
1462
|
+
if (cur)
|
|
1463
|
+
out.push(cur);
|
|
1464
|
+
cur = word;
|
|
1465
|
+
len = wl;
|
|
1466
|
+
} else {
|
|
1467
|
+
cur += (len > 0 ? " " : "") + word;
|
|
1468
|
+
len += wl + (len > 0 ? 1 : 0);
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
if (cur)
|
|
1472
|
+
out.push(cur);
|
|
1473
|
+
}
|
|
1474
|
+
return out;
|
|
1475
|
+
}
|
|
1476
|
+
function bxTop(w, title, bs = theme.border, rounded = true) {
|
|
1477
|
+
const tl = rounded ? box.topLeft : box.topLeft;
|
|
1478
|
+
const tr = rounded ? box.topRight : box.topRight;
|
|
1479
|
+
const hz = box.horizontal;
|
|
1480
|
+
if (!title)
|
|
1481
|
+
return bs(tl + hz.repeat(w - 2) + tr);
|
|
1482
|
+
const t = ` ${title} `;
|
|
1483
|
+
const tLen = vlen(t);
|
|
1484
|
+
const right = Math.max(0, w - 3 - tLen);
|
|
1485
|
+
return bs(tl + hz) + t + bs(hz.repeat(right) + tr);
|
|
1486
|
+
}
|
|
1487
|
+
function bxBot(w, bs = theme.border, rounded = true) {
|
|
1488
|
+
const bl = rounded ? box.bottomLeft : box.bottomLeft;
|
|
1489
|
+
const br = rounded ? box.bottomRight : box.bottomRight;
|
|
1490
|
+
return bs(bl + box.horizontal.repeat(w - 2) + br);
|
|
1491
|
+
}
|
|
1492
|
+
function bxRow(content, w, bs = theme.border) {
|
|
1493
|
+
const cw = w - 4;
|
|
1494
|
+
return bs(box.vertical) + " " + pad(trunc(content, cw), cw) + " " + bs(box.vertical);
|
|
1495
|
+
}
|
|
1496
|
+
function bxDiv(w, bs = theme.border) {
|
|
1497
|
+
return bs(box.teeRight + box.horizontal.repeat(w - 2) + box.teeLeft);
|
|
1498
|
+
}
|
|
1499
|
+
function bxEmpty(w, bs = theme.border) {
|
|
1500
|
+
return bxRow("", w, bs);
|
|
1501
|
+
}
|
|
1502
|
+
function renderProviderPicker(state) {
|
|
1503
|
+
const W = Math.min(tw(), 80);
|
|
1504
|
+
const lines = [];
|
|
1505
|
+
const useSmallLogo = W < 70;
|
|
1506
|
+
lines.push("");
|
|
1507
|
+
if (useSmallLogo) {
|
|
1508
|
+
lines.push(renderLogo(true));
|
|
1509
|
+
} else {
|
|
1510
|
+
lines.push(...LOGO.map((l) => ` ${theme.gradient(l)}`));
|
|
1511
|
+
}
|
|
1512
|
+
lines.push("");
|
|
1513
|
+
lines.push(` ${theme.muted("\u2500".repeat(Math.min(W - 4, 60)))}`);
|
|
1514
|
+
lines.push(` ${theme.textBold("Choose your AI provider")}`);
|
|
1515
|
+
lines.push("");
|
|
1516
|
+
const rec = state.providers.filter((p) => p.recommended);
|
|
1517
|
+
const oth = state.providers.filter((p) => !p.recommended);
|
|
1518
|
+
if (rec.length > 0) {
|
|
1519
|
+
lines.push(` ${style.recBadge()} ${theme.textBold(" RECOMMENDED")}`);
|
|
1520
|
+
lines.push("");
|
|
1521
|
+
for (const p of rec) {
|
|
1522
|
+
const idx = state.providers.indexOf(p);
|
|
1523
|
+
const sel = idx === state.selectedIndex;
|
|
1524
|
+
const status = state.authStatuses.get(p.id) || p.description;
|
|
1525
|
+
const arrow = sel ? theme.primary("\u25B8") : " ";
|
|
1526
|
+
const name = sel ? theme.primaryBold(p.name) : theme.text(p.name);
|
|
1527
|
+
const desc = theme.muted(status);
|
|
1528
|
+
lines.push(` ${arrow} ${p.icon} ${pad(name, 26)} ${desc}`);
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
if (oth.length > 0) {
|
|
1532
|
+
lines.push("");
|
|
1533
|
+
lines.push(` ${theme.yellow("\u26A0")} ${theme.muted("OTHER PROVIDERS")} ${theme.dim("(may not match recommended quality)")}`);
|
|
1534
|
+
lines.push("");
|
|
1535
|
+
for (const p of oth) {
|
|
1536
|
+
const idx = state.providers.indexOf(p);
|
|
1537
|
+
const sel = idx === state.selectedIndex;
|
|
1538
|
+
const status = state.authStatuses.get(p.id) || p.description;
|
|
1539
|
+
const arrow = sel ? theme.primary("\u25B8") : " ";
|
|
1540
|
+
const name = sel ? theme.primaryBold(p.name) : theme.text(p.name);
|
|
1541
|
+
const desc = theme.muted(status);
|
|
1542
|
+
const freeMark = p.freeModels?.length ? ` ${style.freeBadge()}` : "";
|
|
1543
|
+
lines.push(` ${arrow} ${p.icon} ${pad(name, 26)} ${desc}${freeMark}`);
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
lines.push("");
|
|
1547
|
+
lines.push(` ${theme.muted("\u2500".repeat(Math.min(W - 4, 60)))}`);
|
|
1548
|
+
lines.push(` ${style.key("\u2191\u2193")} ${style.keyHint("select")} ${style.key("\u23CE")} ${style.keyHint("confirm")} ${style.key("q")} ${style.keyHint("quit")}`);
|
|
1549
|
+
lines.push("");
|
|
1550
|
+
return lines.join(`
|
|
1551
|
+
`);
|
|
1552
|
+
}
|
|
1553
|
+
function renderProviderWarning(provider) {
|
|
1554
|
+
const lines = [];
|
|
1555
|
+
lines.push("");
|
|
1556
|
+
lines.push(` ${theme.yellow("\u26A0")} ${theme.yellow("Heads up:")}`);
|
|
1557
|
+
lines.push(` ${theme.text(provider.warningMessage || "This provider may produce lower quality output.")}`);
|
|
1558
|
+
lines.push(` ${theme.muted("For best results, use Claude or Codex.")}`);
|
|
1559
|
+
lines.push("");
|
|
1560
|
+
lines.push(` ${theme.text("Continue anyway?")} ${style.key("y")} ${style.key("n")}`);
|
|
1561
|
+
lines.push("");
|
|
1562
|
+
return lines.join(`
|
|
1563
|
+
`);
|
|
1564
|
+
}
|
|
1565
|
+
function renderApiKeyPrompt(provider) {
|
|
1566
|
+
const lines = [];
|
|
1567
|
+
lines.push("");
|
|
1568
|
+
lines.push(renderLogo(true));
|
|
1569
|
+
lines.push("");
|
|
1570
|
+
lines.push(` \uD83D\uDD11 ${theme.textBold("Enter your " + provider.name + " API key")}`);
|
|
1571
|
+
if (provider.authEnvVar) {
|
|
1572
|
+
lines.push(` ${theme.dim("or set " + provider.authEnvVar + " environment variable")}`);
|
|
1573
|
+
}
|
|
1574
|
+
lines.push("");
|
|
1575
|
+
return lines.join(`
|
|
1576
|
+
`);
|
|
1577
|
+
}
|
|
1578
|
+
function renderHeader(info) {
|
|
1579
|
+
const W = Math.min(tw(), 120);
|
|
1580
|
+
const bs = theme.border;
|
|
1581
|
+
const lines = [];
|
|
1582
|
+
lines.push(bxTop(W, undefined, theme.borderActive, true));
|
|
1583
|
+
const brand = `${theme.gradient("\u257A\u254B\u2578")} ${theme.primaryBold("Jugaadu AI")} ${theme.dim("\u2502")} ${theme.muted(`${info.agentCount || 14} agents`)}${info.providerName ? ` ${theme.dim("\u2502")} ${theme.muted(info.providerName)}` : ""}`;
|
|
1584
|
+
lines.push(bxRow(brand, W, theme.borderActive));
|
|
1585
|
+
if (info.authInfo) {
|
|
1586
|
+
lines.push(bxRow(`${theme.dim("\uD83D\uDD10")} ${theme.muted(info.authInfo)}`, W, theme.borderActive));
|
|
1587
|
+
}
|
|
1588
|
+
if (info.idea) {
|
|
1589
|
+
lines.push(bxDiv(W, theme.borderActive));
|
|
1590
|
+
lines.push(bxRow(`${theme.muted("topic \u203A")} ${theme.text(trunc(info.idea, W - 20))}`, W, theme.borderActive));
|
|
1591
|
+
}
|
|
1592
|
+
lines.push(bxBot(W, theme.borderActive, true));
|
|
1593
|
+
return lines.join(`
|
|
1594
|
+
`);
|
|
1595
|
+
}
|
|
1596
|
+
function renderPipelinePanel(agents, selectedIndex, spinnerFrame, panelWidth, panelHeight) {
|
|
1597
|
+
const lines = [];
|
|
1598
|
+
const cw = panelWidth - 4;
|
|
1599
|
+
const activeBs = theme.border;
|
|
1600
|
+
lines.push(bxTop(panelWidth, style.panelTitle(" Pipeline "), activeBs));
|
|
1601
|
+
const colHeader = `${theme.dim(" \xB7 ")}${pad(theme.dim("Agent"), 20)}${theme.dim("Time")}`;
|
|
1602
|
+
lines.push(bxRow(colHeader, panelWidth, activeBs));
|
|
1603
|
+
lines.push(bxDiv(panelWidth, activeBs));
|
|
1604
|
+
const maxRows = panelHeight - 4;
|
|
1605
|
+
const startIdx = Math.max(0, Math.min(selectedIndex - Math.floor(maxRows / 2), agents.length - maxRows));
|
|
1606
|
+
const endIdx = Math.min(agents.length, startIdx + maxRows);
|
|
1607
|
+
for (let i = startIdx;i < endIdx; i++) {
|
|
1608
|
+
const a = agents[i];
|
|
1609
|
+
const sel = i === selectedIndex;
|
|
1610
|
+
let icon;
|
|
1611
|
+
let name;
|
|
1612
|
+
let time = "";
|
|
1613
|
+
switch (a.status) {
|
|
1614
|
+
case "waiting":
|
|
1615
|
+
icon = theme.dimmer("\u25CB");
|
|
1616
|
+
name = theme.dim(a.label);
|
|
1617
|
+
break;
|
|
1618
|
+
case "running":
|
|
1619
|
+
icon = theme.primary(spinnerFrames[spinnerFrame % spinnerFrames.length]);
|
|
1620
|
+
name = theme.primaryBold(a.label);
|
|
1621
|
+
time = theme.primary(elapsed(a.startedAt));
|
|
1622
|
+
break;
|
|
1623
|
+
case "done":
|
|
1624
|
+
icon = theme.green("\u25CF");
|
|
1625
|
+
name = theme.text(a.label);
|
|
1626
|
+
time = theme.dim(elapsed(a.startedAt, a.completedAt));
|
|
1627
|
+
break;
|
|
1628
|
+
case "error":
|
|
1629
|
+
icon = theme.red("\u2717");
|
|
1630
|
+
name = theme.red(a.label);
|
|
1631
|
+
time = theme.red("err");
|
|
1632
|
+
break;
|
|
1633
|
+
}
|
|
1634
|
+
const arrow = sel ? theme.primary("\u25B8") : " ";
|
|
1635
|
+
const row = `${arrow} ${icon} ${a.icon} ${pad(name, 19)}${pad(time, 6)}`;
|
|
1636
|
+
if (sel) {
|
|
1637
|
+
lines.push(bxRow(style.selectedBg(pad(strip(row), cw)), panelWidth, theme.borderActive));
|
|
1638
|
+
} else {
|
|
1639
|
+
lines.push(bxRow(row, panelWidth, activeBs));
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
for (let i = endIdx - startIdx;i < maxRows; i++) {
|
|
1643
|
+
lines.push(bxEmpty(panelWidth, activeBs));
|
|
1644
|
+
}
|
|
1645
|
+
lines.push(bxBot(panelWidth, activeBs));
|
|
1646
|
+
return lines;
|
|
1647
|
+
}
|
|
1648
|
+
function renderOutputPanel(agent, panelWidth, panelHeight, scrollOffset = 0) {
|
|
1649
|
+
const lines = [];
|
|
1650
|
+
const cw = panelWidth - 4;
|
|
1651
|
+
const bs = theme.border;
|
|
1652
|
+
let title = style.panelTitle(" Output ");
|
|
1653
|
+
if (agent) {
|
|
1654
|
+
const dot = agent.status === "running" ? theme.primary("\u25CF") : agent.status === "done" ? theme.green("\u25CF") : agent.status === "error" ? theme.red("\u25CF") : theme.dim("\u25CB");
|
|
1655
|
+
title = `${style.panelTitle(" Output ")}${theme.dim(" \u2500 ")}${agent.icon} ${theme.text(agent.label)} ${dot}`;
|
|
1656
|
+
}
|
|
1657
|
+
lines.push(bxTop(panelWidth, title, bs));
|
|
1658
|
+
const maxLines = panelHeight - 2;
|
|
1659
|
+
if (!agent || !agent.output) {
|
|
1660
|
+
for (let i = 0;i < Math.floor(maxLines / 2) - 2; i++)
|
|
1661
|
+
lines.push(bxEmpty(panelWidth, bs));
|
|
1662
|
+
lines.push(bxRow(center(theme.dim("Waiting for output..."), cw), panelWidth, bs));
|
|
1663
|
+
lines.push(bxRow(center(theme.dimmer("Use \u2191\u2193 to select agents"), cw), panelWidth, bs));
|
|
1664
|
+
lines.push(bxRow(center(theme.dimmer("Tab to switch panels"), cw), panelWidth, bs));
|
|
1665
|
+
for (let i = Math.floor(maxLines / 2) + 1;i < maxLines; i++)
|
|
1666
|
+
lines.push(bxEmpty(panelWidth, bs));
|
|
1667
|
+
} else {
|
|
1668
|
+
const wrapped = wrap(agent.output, cw);
|
|
1669
|
+
const total = wrapped.length;
|
|
1670
|
+
const maxScroll = Math.max(0, total - maxLines + 1);
|
|
1671
|
+
const offset = Math.min(scrollOffset, maxScroll);
|
|
1672
|
+
const visible = wrapped.slice(offset, offset + maxLines);
|
|
1673
|
+
for (let i = 0;i < maxLines; i++) {
|
|
1674
|
+
if (i < visible.length) {
|
|
1675
|
+
lines.push(bxRow(visible[i], panelWidth, bs));
|
|
1676
|
+
} else if (i === visible.length && agent.status === "running") {
|
|
1677
|
+
lines.push(bxRow(theme.primary("\u258A"), panelWidth, bs));
|
|
1678
|
+
} else {
|
|
1679
|
+
lines.push(bxEmpty(panelWidth, bs));
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
if (total > maxLines) {
|
|
1683
|
+
const barH = Math.max(2, Math.floor(maxLines / total * maxLines));
|
|
1684
|
+
const barTop = Math.floor(offset / maxScroll * (maxLines - barH));
|
|
1685
|
+
for (let i = 1;i <= maxLines; i++) {
|
|
1686
|
+
const lineIdx = i;
|
|
1687
|
+
if (lineIdx >= barTop + 1 && lineIdx < barTop + barH + 1 && lines[lineIdx]) {
|
|
1688
|
+
const line = lines[lineIdx];
|
|
1689
|
+
const stripped = line.slice(0, -vlen(bs(box.vertical)));
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
lines.push(bxBot(panelWidth, bs));
|
|
1695
|
+
return lines;
|
|
1696
|
+
}
|
|
1697
|
+
function renderFooter(agents, isRunning, totalWidth) {
|
|
1698
|
+
const done = agents.filter((a) => a.status === "done").length;
|
|
1699
|
+
const errs = agents.filter((a) => a.status === "error").length;
|
|
1700
|
+
const total = agents.length;
|
|
1701
|
+
const bar = progressBar(done, total, 14);
|
|
1702
|
+
const pct = `${done}/${total}`;
|
|
1703
|
+
const errStr = errs > 0 ? ` ${theme.red(`${errs} err`)}` : "";
|
|
1704
|
+
const controls = isRunning ? `${style.key("\u2191\u2193")} ${style.keyHint("navigate")} ${style.key("Tab")} ${style.keyHint("panel")} ${style.key("PgUp/Dn")} ${style.keyHint("scroll")} ${style.key("Esc")} ${style.keyHint("cancel")} ${style.key("q")} ${style.keyHint("quit")}` : `${style.key("\u2191\u2193")} ${style.keyHint("browse")} ${style.key("Tab")} ${style.keyHint("panel")} ${style.key("PgUp/Dn")} ${style.keyHint("scroll")} ${style.key("q")} ${style.keyHint("quit")}`;
|
|
1705
|
+
return ` ${controls} ${bar} ${theme.text(pct)}${errStr}`;
|
|
1706
|
+
}
|
|
1707
|
+
function renderScannerBar(frame) {
|
|
1708
|
+
return ` ${scannerFrame(frame, 30)}`;
|
|
1709
|
+
}
|
|
1710
|
+
function renderFull(state) {
|
|
1711
|
+
const W = Math.min(tw(), 140);
|
|
1712
|
+
const H = th();
|
|
1713
|
+
const lines = [];
|
|
1714
|
+
const header = renderHeader({
|
|
1715
|
+
idea: state.idea,
|
|
1716
|
+
authInfo: state.authInfo,
|
|
1717
|
+
agentCount: state.agents.length,
|
|
1718
|
+
providerName: state.providerName
|
|
1719
|
+
});
|
|
1720
|
+
const headerLines = header.split(`
|
|
1721
|
+
`);
|
|
1722
|
+
lines.push(...headerLines);
|
|
1723
|
+
if (state.phase === "running") {
|
|
1724
|
+
lines.push(renderScannerBar(state.spinnerFrame));
|
|
1725
|
+
} else {
|
|
1726
|
+
lines.push("");
|
|
1727
|
+
}
|
|
1728
|
+
const headerH = headerLines.length + 1;
|
|
1729
|
+
const footerH = 2;
|
|
1730
|
+
const panelH = Math.max(12, H - headerH - footerH);
|
|
1731
|
+
const SIDE_BY_SIDE_MIN = 80;
|
|
1732
|
+
if (W >= SIDE_BY_SIDE_MIN) {
|
|
1733
|
+
const pipeW = Math.min(40, Math.floor(W * 0.35));
|
|
1734
|
+
const outW = W - pipeW;
|
|
1735
|
+
const pipeLines = renderPipelinePanel(state.agents, state.selectedIndex, state.spinnerFrame, pipeW, panelH);
|
|
1736
|
+
const selAgent = state.agents.find((a) => a.name === state.selectedAgent) || state.agents.find((a) => a.status === "running") || null;
|
|
1737
|
+
const outLines = renderOutputPanel(selAgent, outW, panelH, state.outputScrollOffset);
|
|
1738
|
+
const maxLen = Math.max(pipeLines.length, outLines.length);
|
|
1739
|
+
for (let i = 0;i < maxLen; i++) {
|
|
1740
|
+
const left = pipeLines[i] || " ".repeat(pipeW);
|
|
1741
|
+
const right = outLines[i] || " ".repeat(outW);
|
|
1742
|
+
lines.push(left + right);
|
|
1743
|
+
}
|
|
1744
|
+
} else {
|
|
1745
|
+
const halfH = Math.floor((panelH - 1) / 2);
|
|
1746
|
+
lines.push(...renderPipelinePanel(state.agents, state.selectedIndex, state.spinnerFrame, W, halfH));
|
|
1747
|
+
const selAgent = state.agents.find((a) => a.name === state.selectedAgent) || state.agents.find((a) => a.status === "running") || null;
|
|
1748
|
+
lines.push(...renderOutputPanel(selAgent, W, halfH, state.outputScrollOffset));
|
|
1749
|
+
}
|
|
1750
|
+
lines.push(renderFooter(state.agents, state.phase === "running", W));
|
|
1751
|
+
return lines.join(`
|
|
1752
|
+
`);
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
// src/tui/app.ts
|
|
1756
|
+
import * as readline from "readline";
|
|
1757
|
+
function buildInitialAgents() {
|
|
1758
|
+
return AGENTS.map((a) => ({
|
|
1759
|
+
name: a.name,
|
|
1760
|
+
label: a.label,
|
|
1761
|
+
role: a.role,
|
|
1762
|
+
icon: a.icon,
|
|
1763
|
+
status: "waiting",
|
|
1764
|
+
output: ""
|
|
1765
|
+
}));
|
|
1766
|
+
}
|
|
1767
|
+
async function pickProvider(config) {
|
|
1768
|
+
if (config.provider && config.skipProviderPicker) {
|
|
1769
|
+
const provider = PROVIDERS.find((p) => p.id === config.provider);
|
|
1770
|
+
const auth = {
|
|
1771
|
+
provider: provider.id,
|
|
1772
|
+
method: config.authMethod || "api-key",
|
|
1773
|
+
token: config.authToken
|
|
1774
|
+
};
|
|
1775
|
+
return { provider, auth };
|
|
1776
|
+
}
|
|
1777
|
+
return new Promise((resolve, reject) => {
|
|
1778
|
+
let selectedIndex = 0;
|
|
1779
|
+
const providers = PROVIDERS;
|
|
1780
|
+
let phase = "pick";
|
|
1781
|
+
let chosenProvider = null;
|
|
1782
|
+
const authStatuses = new Map;
|
|
1783
|
+
for (const p of providers) {
|
|
1784
|
+
if (p.id === "claude") {
|
|
1785
|
+
authStatuses.set(p.id, "OAuth \u2714 or API key");
|
|
1786
|
+
} else if (p.id === "codex") {
|
|
1787
|
+
authStatuses.set(p.id, "Requires Codex CLI");
|
|
1788
|
+
} else if (p.authEnvVar && process.env[p.authEnvVar]) {
|
|
1789
|
+
authStatuses.set(p.id, `\u2714 ${p.authEnvVar} set`);
|
|
1790
|
+
} else if (p.freeModels && p.freeModels.length > 0) {
|
|
1791
|
+
authStatuses.set(p.id, "Free tier available");
|
|
1792
|
+
} else {
|
|
1793
|
+
authStatuses.set(p.id, "API Key required");
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
hideCursor();
|
|
1797
|
+
if (process.stdin.isTTY) {
|
|
1798
|
+
process.stdin.setRawMode(true);
|
|
1799
|
+
}
|
|
1800
|
+
process.stdin.resume();
|
|
1801
|
+
process.stdin.setEncoding("utf8");
|
|
1802
|
+
function renderPicker() {
|
|
1803
|
+
const pickerState = {
|
|
1804
|
+
providers,
|
|
1805
|
+
selectedIndex,
|
|
1806
|
+
authStatuses
|
|
1807
|
+
};
|
|
1808
|
+
let frame = renderProviderPicker(pickerState);
|
|
1809
|
+
if (phase === "warning" && chosenProvider) {
|
|
1810
|
+
frame += renderProviderWarning(chosenProvider);
|
|
1811
|
+
}
|
|
1812
|
+
writeFrame(frame);
|
|
1813
|
+
}
|
|
1814
|
+
function cleanup() {
|
|
1815
|
+
process.stdin.removeListener("data", onKey);
|
|
1816
|
+
}
|
|
1817
|
+
function onKey(key) {
|
|
1818
|
+
if (phase === "pick") {
|
|
1819
|
+
if (key === "q" || key === "\x03") {
|
|
1820
|
+
cleanup();
|
|
1821
|
+
showCursor();
|
|
1822
|
+
if (process.stdin.isTTY)
|
|
1823
|
+
process.stdin.setRawMode(false);
|
|
1824
|
+
process.stdin.pause();
|
|
1825
|
+
process.exit(0);
|
|
1826
|
+
}
|
|
1827
|
+
if (key === "\x1B[A") {
|
|
1828
|
+
selectedIndex = Math.max(0, selectedIndex - 1);
|
|
1829
|
+
renderPicker();
|
|
1830
|
+
} else if (key === "\x1B[B") {
|
|
1831
|
+
selectedIndex = Math.min(providers.length - 1, selectedIndex + 1);
|
|
1832
|
+
renderPicker();
|
|
1833
|
+
} else if (key === "\r" || key === `
|
|
1834
|
+
`) {
|
|
1835
|
+
chosenProvider = providers[selectedIndex];
|
|
1836
|
+
if (!chosenProvider.recommended) {
|
|
1837
|
+
phase = "warning";
|
|
1838
|
+
renderPicker();
|
|
1839
|
+
} else {
|
|
1840
|
+
cleanup();
|
|
1841
|
+
finishSelection(chosenProvider);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
} else if (phase === "warning") {
|
|
1845
|
+
if (key === "y" || key === "Y") {
|
|
1846
|
+
cleanup();
|
|
1847
|
+
finishSelection(chosenProvider);
|
|
1848
|
+
} else if (key === "n" || key === "N" || key === "\x1B") {
|
|
1849
|
+
phase = "pick";
|
|
1850
|
+
renderPicker();
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
function finishSelection(provider) {
|
|
1855
|
+
if (provider.id === "claude") {
|
|
1856
|
+
if (config.authToken) {
|
|
1857
|
+
resolve({
|
|
1858
|
+
provider,
|
|
1859
|
+
auth: { provider: provider.id, method: "api-key", token: config.authToken }
|
|
1860
|
+
});
|
|
1861
|
+
} else {
|
|
1862
|
+
resolve({
|
|
1863
|
+
provider,
|
|
1864
|
+
auth: { provider: provider.id, method: "oauth" }
|
|
1865
|
+
});
|
|
1866
|
+
}
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
if (provider.id === "codex") {
|
|
1870
|
+
resolve({
|
|
1871
|
+
provider,
|
|
1872
|
+
auth: { provider: provider.id, method: "cli" }
|
|
1873
|
+
});
|
|
1874
|
+
return;
|
|
1875
|
+
}
|
|
1876
|
+
if (provider.authEnvVar && process.env[provider.authEnvVar]) {
|
|
1877
|
+
resolve({
|
|
1878
|
+
provider,
|
|
1879
|
+
auth: { provider: provider.id, method: "api-key", token: process.env[provider.authEnvVar] }
|
|
1880
|
+
});
|
|
1881
|
+
return;
|
|
1882
|
+
}
|
|
1883
|
+
if (provider.authMethods.includes("anonymous")) {
|
|
1884
|
+
resolve({
|
|
1885
|
+
provider,
|
|
1886
|
+
auth: { provider: provider.id, method: "anonymous" }
|
|
1887
|
+
});
|
|
1888
|
+
return;
|
|
1889
|
+
}
|
|
1890
|
+
promptForApiKey(provider).then((token) => {
|
|
1891
|
+
resolve({
|
|
1892
|
+
provider,
|
|
1893
|
+
auth: { provider: provider.id, method: "api-key", token }
|
|
1894
|
+
});
|
|
1895
|
+
});
|
|
1896
|
+
}
|
|
1897
|
+
process.stdin.on("data", onKey);
|
|
1898
|
+
renderPicker();
|
|
1899
|
+
});
|
|
1900
|
+
}
|
|
1901
|
+
async function promptForApiKey(provider) {
|
|
1902
|
+
showCursor();
|
|
1903
|
+
if (process.stdin.isTTY) {
|
|
1904
|
+
process.stdin.setRawMode(false);
|
|
1905
|
+
}
|
|
1906
|
+
const rl = readline.createInterface({
|
|
1907
|
+
input: process.stdin,
|
|
1908
|
+
output: process.stdout
|
|
1909
|
+
});
|
|
1910
|
+
return new Promise((resolve) => {
|
|
1911
|
+
clearScreen();
|
|
1912
|
+
const prompt = renderApiKeyPrompt(provider);
|
|
1913
|
+
process.stdout.write(prompt);
|
|
1914
|
+
rl.question("", (answer) => {
|
|
1915
|
+
rl.close();
|
|
1916
|
+
resolve(answer.trim());
|
|
1917
|
+
});
|
|
1918
|
+
});
|
|
1919
|
+
}
|
|
1920
|
+
async function promptForIdea(providerName) {
|
|
1921
|
+
showCursor();
|
|
1922
|
+
if (process.stdin.isTTY) {
|
|
1923
|
+
process.stdin.setRawMode(false);
|
|
1924
|
+
}
|
|
1925
|
+
const rl = readline.createInterface({
|
|
1926
|
+
input: process.stdin,
|
|
1927
|
+
output: process.stdout
|
|
1928
|
+
});
|
|
1929
|
+
return new Promise((resolve) => {
|
|
1930
|
+
clearScreen();
|
|
1931
|
+
const width = Math.min(process.stdout.columns || 100, 80);
|
|
1932
|
+
const lines = [];
|
|
1933
|
+
lines.push("");
|
|
1934
|
+
lines.push(` ${theme.gradientText("\u26A1 Jugaadu AI")} ${theme.dim("\u2500")} ${theme.dim("Powered by")} ${theme.white(providerName)}`);
|
|
1935
|
+
lines.push("");
|
|
1936
|
+
lines.push(` ${theme.white("What's your topic?")}`);
|
|
1937
|
+
lines.push("");
|
|
1938
|
+
lines.push(` ${theme.dimmer("Examples:")}`);
|
|
1939
|
+
lines.push(` ${theme.dimmer("\u2022 RBI might cut rates next month")}`);
|
|
1940
|
+
lines.push(` ${theme.dimmer("\u2022 Apple Vision Pro 2 launch")}`);
|
|
1941
|
+
lines.push(` ${theme.dimmer("\u2022 Zomato vs Swiggy IPO battle")}`);
|
|
1942
|
+
lines.push(` ${theme.dimmer("\u2022 AI replacing jobs in India")}`);
|
|
1943
|
+
lines.push("");
|
|
1944
|
+
console.log(lines.join(`
|
|
1945
|
+
`));
|
|
1946
|
+
rl.question(` ${theme.cyan("\u25B8")} `, (answer) => {
|
|
1947
|
+
rl.close();
|
|
1948
|
+
resolve(answer.trim());
|
|
1949
|
+
});
|
|
1950
|
+
});
|
|
1951
|
+
}
|
|
1952
|
+
function buildAuthInfo(provider, auth) {
|
|
1953
|
+
const methodLabel = auth.method === "oauth" ? "OAuth" : auth.method === "api-key" ? "API Key" : auth.method === "cli" ? "CLI" : "Anonymous";
|
|
1954
|
+
if (auth.email) {
|
|
1955
|
+
return `${provider.name} ${methodLabel} (${auth.email}${auth.subscription ? ", " + auth.subscription : ""})`;
|
|
1956
|
+
}
|
|
1957
|
+
return `${provider.name} ${methodLabel}`;
|
|
1958
|
+
}
|
|
1959
|
+
async function runApp(config) {
|
|
1960
|
+
let provider;
|
|
1961
|
+
let auth;
|
|
1962
|
+
if (config.provider && config.authMethod && (config.authToken || config.authMethod === "oauth")) {
|
|
1963
|
+
provider = PROVIDERS.find((p) => p.id === config.provider);
|
|
1964
|
+
auth = {
|
|
1965
|
+
provider: provider.id,
|
|
1966
|
+
method: config.authMethod,
|
|
1967
|
+
token: config.authToken
|
|
1968
|
+
};
|
|
1969
|
+
} else if (config.authToken && config.authMethod) {
|
|
1970
|
+
provider = PROVIDERS.find((p) => p.id === "claude");
|
|
1971
|
+
auth = {
|
|
1972
|
+
provider: "claude",
|
|
1973
|
+
method: config.authMethod,
|
|
1974
|
+
token: config.authToken
|
|
1975
|
+
};
|
|
1976
|
+
} else {
|
|
1977
|
+
const picked = await pickProvider(config);
|
|
1978
|
+
provider = picked.provider;
|
|
1979
|
+
auth = picked.auth;
|
|
1980
|
+
}
|
|
1981
|
+
let idea = config.idea;
|
|
1982
|
+
if (!idea) {
|
|
1983
|
+
idea = await promptForIdea(provider.name);
|
|
1984
|
+
if (!idea) {
|
|
1985
|
+
console.log(`
|
|
1986
|
+
${theme.red("No topic provided. Exiting.")}
|
|
1987
|
+
`);
|
|
1988
|
+
process.exit(0);
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
const pipelineOptions = {
|
|
1992
|
+
provider: provider.id,
|
|
1993
|
+
authMethod: auth.method,
|
|
1994
|
+
authToken: auth.token,
|
|
1995
|
+
model: provider.defaultModel
|
|
1996
|
+
};
|
|
1997
|
+
const agents = buildInitialAgents();
|
|
1998
|
+
let selectedIndex = 0;
|
|
1999
|
+
let spinnerFrame = 0;
|
|
2000
|
+
let phase = "running";
|
|
2001
|
+
let aborted = false;
|
|
2002
|
+
let outputScrollOffset = 0;
|
|
2003
|
+
let activePanel = "pipeline";
|
|
2004
|
+
hideCursor();
|
|
2005
|
+
if (process.stdin.isTTY) {
|
|
2006
|
+
process.stdin.setRawMode(true);
|
|
2007
|
+
}
|
|
2008
|
+
process.stdin.resume();
|
|
2009
|
+
process.stdin.setEncoding("utf8");
|
|
2010
|
+
const authInfo = buildAuthInfo(provider, auth);
|
|
2011
|
+
function render() {
|
|
2012
|
+
const selectedAgent = agents[selectedIndex]?.name ?? null;
|
|
2013
|
+
const frame = renderFull({
|
|
2014
|
+
agents,
|
|
2015
|
+
selectedAgent,
|
|
2016
|
+
selectedIndex,
|
|
2017
|
+
spinnerFrame,
|
|
2018
|
+
authInfo,
|
|
2019
|
+
idea,
|
|
2020
|
+
phase,
|
|
2021
|
+
outputScrollOffset,
|
|
2022
|
+
activePanel,
|
|
2023
|
+
providerName: provider.name
|
|
2024
|
+
});
|
|
2025
|
+
writeFrame(frame);
|
|
2026
|
+
}
|
|
2027
|
+
function onKey(key) {
|
|
2028
|
+
if (key === "q" || key === "\x03") {
|
|
2029
|
+
cleanup();
|
|
2030
|
+
process.exit(0);
|
|
2031
|
+
}
|
|
2032
|
+
if (key === "\x1B" && key.length === 1) {
|
|
2033
|
+
aborted = true;
|
|
2034
|
+
return;
|
|
2035
|
+
}
|
|
2036
|
+
if (key === "\t") {
|
|
2037
|
+
activePanel = activePanel === "pipeline" ? "output" : "pipeline";
|
|
2038
|
+
render();
|
|
2039
|
+
return;
|
|
2040
|
+
}
|
|
2041
|
+
if (key === "\x1B[A") {
|
|
2042
|
+
if (activePanel === "pipeline") {
|
|
2043
|
+
selectedIndex = Math.max(0, selectedIndex - 1);
|
|
2044
|
+
outputScrollOffset = 0;
|
|
2045
|
+
} else {
|
|
2046
|
+
outputScrollOffset = Math.max(0, outputScrollOffset - 1);
|
|
2047
|
+
}
|
|
2048
|
+
render();
|
|
2049
|
+
return;
|
|
2050
|
+
}
|
|
2051
|
+
if (key === "\x1B[B") {
|
|
2052
|
+
if (activePanel === "pipeline") {
|
|
2053
|
+
selectedIndex = Math.min(agents.length - 1, selectedIndex + 1);
|
|
2054
|
+
outputScrollOffset = 0;
|
|
2055
|
+
} else {
|
|
2056
|
+
outputScrollOffset++;
|
|
2057
|
+
}
|
|
2058
|
+
render();
|
|
2059
|
+
return;
|
|
2060
|
+
}
|
|
2061
|
+
if (key === "\x1B[5~") {
|
|
2062
|
+
outputScrollOffset = Math.max(0, outputScrollOffset - 10);
|
|
2063
|
+
render();
|
|
2064
|
+
return;
|
|
2065
|
+
}
|
|
2066
|
+
if (key === "\x1B[6~") {
|
|
2067
|
+
outputScrollOffset += 10;
|
|
2068
|
+
render();
|
|
2069
|
+
return;
|
|
2070
|
+
}
|
|
2071
|
+
if (key === "\x1B[H" || key === "\x1B[1~") {
|
|
2072
|
+
if (activePanel === "pipeline") {
|
|
2073
|
+
selectedIndex = 0;
|
|
2074
|
+
outputScrollOffset = 0;
|
|
2075
|
+
} else {
|
|
2076
|
+
outputScrollOffset = 0;
|
|
2077
|
+
}
|
|
2078
|
+
render();
|
|
2079
|
+
return;
|
|
2080
|
+
}
|
|
2081
|
+
if (key === "\x1B[F" || key === "\x1B[4~") {
|
|
2082
|
+
if (activePanel === "pipeline") {
|
|
2083
|
+
selectedIndex = agents.length - 1;
|
|
2084
|
+
outputScrollOffset = 0;
|
|
2085
|
+
} else {
|
|
2086
|
+
outputScrollOffset = 99999;
|
|
2087
|
+
}
|
|
2088
|
+
render();
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
process.stdin.on("data", onKey);
|
|
2093
|
+
function cleanup() {
|
|
2094
|
+
showCursor();
|
|
2095
|
+
if (process.stdin.isTTY) {
|
|
2096
|
+
process.stdin.setRawMode(false);
|
|
2097
|
+
}
|
|
2098
|
+
process.stdin.pause();
|
|
2099
|
+
process.stdin.removeListener("data", onKey);
|
|
2100
|
+
clearInterval(spinnerInterval);
|
|
2101
|
+
}
|
|
2102
|
+
process.on("exit", () => {
|
|
2103
|
+
showCursor();
|
|
2104
|
+
});
|
|
2105
|
+
process.on("SIGINT", () => {
|
|
2106
|
+
cleanup();
|
|
2107
|
+
process.exit(0);
|
|
2108
|
+
});
|
|
2109
|
+
process.on("SIGTERM", () => {
|
|
2110
|
+
cleanup();
|
|
2111
|
+
process.exit(0);
|
|
2112
|
+
});
|
|
2113
|
+
function onResize() {
|
|
2114
|
+
if (phase !== "done" || phase === "done") {
|
|
2115
|
+
render();
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
process.stdout.on("resize", onResize);
|
|
2119
|
+
const spinnerInterval = setInterval(() => {
|
|
2120
|
+
spinnerFrame++;
|
|
2121
|
+
if (phase === "running")
|
|
2122
|
+
render();
|
|
2123
|
+
}, 80);
|
|
2124
|
+
render();
|
|
2125
|
+
try {
|
|
2126
|
+
for await (const event of runPipeline(idea, undefined, pipelineOptions)) {
|
|
2127
|
+
if (aborted)
|
|
2128
|
+
break;
|
|
2129
|
+
switch (event.type) {
|
|
2130
|
+
case "agent_start": {
|
|
2131
|
+
const agent = agents.find((a) => a.name === event.agent);
|
|
2132
|
+
if (agent) {
|
|
2133
|
+
agent.status = "running";
|
|
2134
|
+
agent.startedAt = Date.now();
|
|
2135
|
+
agent.output = "";
|
|
2136
|
+
}
|
|
2137
|
+
if (activePanel === "pipeline") {
|
|
2138
|
+
const idx = agents.findIndex((a) => a.name === event.agent);
|
|
2139
|
+
if (idx >= 0) {
|
|
2140
|
+
selectedIndex = idx;
|
|
2141
|
+
outputScrollOffset = 0;
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
render();
|
|
2145
|
+
break;
|
|
2146
|
+
}
|
|
2147
|
+
case "agent_delta": {
|
|
2148
|
+
const agent = agents.find((a) => a.name === event.agent);
|
|
2149
|
+
if (agent) {
|
|
2150
|
+
agent.output += event.text;
|
|
2151
|
+
}
|
|
2152
|
+
if (agents[selectedIndex]?.name === event.agent) {
|
|
2153
|
+
outputScrollOffset = 99999;
|
|
2154
|
+
}
|
|
2155
|
+
render();
|
|
2156
|
+
break;
|
|
2157
|
+
}
|
|
2158
|
+
case "agent_done": {
|
|
2159
|
+
const agent = agents.find((a) => a.name === event.agent);
|
|
2160
|
+
if (agent) {
|
|
2161
|
+
agent.status = "done";
|
|
2162
|
+
agent.output = event.output;
|
|
2163
|
+
agent.completedAt = Date.now();
|
|
2164
|
+
}
|
|
2165
|
+
render();
|
|
2166
|
+
break;
|
|
2167
|
+
}
|
|
2168
|
+
case "agent_error": {
|
|
2169
|
+
const agent = agents.find((a) => a.name === event.agent);
|
|
2170
|
+
if (agent) {
|
|
2171
|
+
agent.status = "error";
|
|
2172
|
+
agent.error = event.error;
|
|
2173
|
+
agent.output = `Error: ${event.error}`;
|
|
2174
|
+
agent.completedAt = Date.now();
|
|
2175
|
+
}
|
|
2176
|
+
render();
|
|
2177
|
+
break;
|
|
2178
|
+
}
|
|
2179
|
+
case "pipeline_done": {
|
|
2180
|
+
phase = "done";
|
|
2181
|
+
render();
|
|
2182
|
+
break;
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
} catch (err) {
|
|
2187
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
2188
|
+
const runningAgent = agents.find((a) => a.status === "running");
|
|
2189
|
+
if (runningAgent) {
|
|
2190
|
+
runningAgent.status = "error";
|
|
2191
|
+
runningAgent.error = errorMsg;
|
|
2192
|
+
runningAgent.output = `Error: ${errorMsg}`;
|
|
2193
|
+
runningAgent.completedAt = Date.now();
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
phase = "done";
|
|
2197
|
+
clearInterval(spinnerInterval);
|
|
2198
|
+
render();
|
|
2199
|
+
const summaryRender = () => {
|
|
2200
|
+
const selectedAgent = agents[selectedIndex]?.name ?? null;
|
|
2201
|
+
const frame = renderFull({
|
|
2202
|
+
agents,
|
|
2203
|
+
selectedAgent,
|
|
2204
|
+
selectedIndex,
|
|
2205
|
+
spinnerFrame,
|
|
2206
|
+
authInfo,
|
|
2207
|
+
idea,
|
|
2208
|
+
phase: "done",
|
|
2209
|
+
outputScrollOffset,
|
|
2210
|
+
activePanel,
|
|
2211
|
+
providerName: provider.name
|
|
2212
|
+
});
|
|
2213
|
+
writeFrame(frame);
|
|
2214
|
+
};
|
|
2215
|
+
summaryRender();
|
|
2216
|
+
await new Promise((resolve) => {
|
|
2217
|
+
const checkQuit = (key) => {
|
|
2218
|
+
if (key === "q" || key === "\x03") {
|
|
2219
|
+
process.stdin.removeListener("data", checkQuit);
|
|
2220
|
+
resolve();
|
|
2221
|
+
}
|
|
2222
|
+
if (key === "\x1B[A") {
|
|
2223
|
+
if (activePanel === "pipeline") {
|
|
2224
|
+
selectedIndex = Math.max(0, selectedIndex - 1);
|
|
2225
|
+
outputScrollOffset = 0;
|
|
2226
|
+
} else {
|
|
2227
|
+
outputScrollOffset = Math.max(0, outputScrollOffset - 1);
|
|
2228
|
+
}
|
|
2229
|
+
summaryRender();
|
|
2230
|
+
}
|
|
2231
|
+
if (key === "\x1B[B") {
|
|
2232
|
+
if (activePanel === "pipeline") {
|
|
2233
|
+
selectedIndex = Math.min(agents.length - 1, selectedIndex + 1);
|
|
2234
|
+
outputScrollOffset = 0;
|
|
2235
|
+
} else {
|
|
2236
|
+
outputScrollOffset++;
|
|
2237
|
+
}
|
|
2238
|
+
summaryRender();
|
|
2239
|
+
}
|
|
2240
|
+
if (key === "\t") {
|
|
2241
|
+
activePanel = activePanel === "pipeline" ? "output" : "pipeline";
|
|
2242
|
+
summaryRender();
|
|
2243
|
+
}
|
|
2244
|
+
if (key === "\x1B[5~") {
|
|
2245
|
+
outputScrollOffset = Math.max(0, outputScrollOffset - 10);
|
|
2246
|
+
summaryRender();
|
|
2247
|
+
}
|
|
2248
|
+
if (key === "\x1B[6~") {
|
|
2249
|
+
outputScrollOffset += 10;
|
|
2250
|
+
summaryRender();
|
|
2251
|
+
}
|
|
2252
|
+
};
|
|
2253
|
+
process.stdin.removeListener("data", onKey);
|
|
2254
|
+
process.stdin.on("data", checkQuit);
|
|
2255
|
+
});
|
|
2256
|
+
cleanup();
|
|
2257
|
+
process.stdout.removeListener("resize", onResize);
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
// src/index.ts
|
|
2261
|
+
var args = process.argv.slice(2);
|
|
2262
|
+
var idea = args.join(" ").trim();
|
|
2263
|
+
async function main() {
|
|
2264
|
+
await runApp({ idea: idea || undefined });
|
|
2265
|
+
}
|
|
2266
|
+
main().catch((err) => {
|
|
2267
|
+
console.error("Fatal error:", err);
|
|
2268
|
+
process.exit(1);
|
|
2269
|
+
});
|