jeo-code 0.1.0 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/README.ja.md +160 -0
  2. package/README.ko.md +160 -0
  3. package/README.md +115 -297
  4. package/README.zh.md +160 -0
  5. package/package.json +11 -6
  6. package/scripts/install.sh +28 -28
  7. package/scripts/uninstall.sh +17 -15
  8. package/src/AGENTS.md +50 -0
  9. package/src/agent/AGENTS.md +49 -0
  10. package/src/agent/bash-fixups.ts +103 -0
  11. package/src/agent/compaction.ts +410 -19
  12. package/src/agent/config-schema.ts +119 -5
  13. package/src/agent/context-files.ts +314 -17
  14. package/src/agent/dev/AGENTS.md +36 -0
  15. package/src/agent/dev/advanced-analyzer.ts +12 -0
  16. package/src/agent/dev/evolution-bridge.ts +82 -0
  17. package/src/agent/dev/evolution-logger.ts +41 -0
  18. package/src/agent/dev/self-analysis.ts +64 -0
  19. package/src/agent/dev/self-improve.ts +24 -0
  20. package/src/agent/dev/spec-automation.ts +49 -0
  21. package/src/agent/engine.ts +804 -54
  22. package/src/agent/hooks.ts +273 -0
  23. package/src/agent/loop.ts +21 -1
  24. package/src/agent/memory.ts +201 -0
  25. package/src/agent/model-recency.ts +32 -0
  26. package/src/agent/output-minimizer.ts +108 -0
  27. package/src/agent/output-util.ts +64 -0
  28. package/src/agent/plan.ts +187 -0
  29. package/src/agent/seed.ts +52 -0
  30. package/src/agent/session.ts +235 -21
  31. package/src/agent/state.ts +286 -39
  32. package/src/agent/step-budget.ts +232 -0
  33. package/src/agent/subagents.ts +223 -26
  34. package/src/agent/task-tool.ts +272 -0
  35. package/src/agent/todo-tool.ts +87 -0
  36. package/src/agent/tokenizer.ts +117 -0
  37. package/src/agent/tool-registry.ts +54 -0
  38. package/src/agent/tools.ts +562 -103
  39. package/src/agent/web-search.ts +538 -0
  40. package/src/ai/AGENTS.md +44 -0
  41. package/src/ai/index.ts +1 -0
  42. package/src/ai/model-catalog-compat.ts +3 -1
  43. package/src/ai/model-catalog.ts +74 -9
  44. package/src/ai/model-discovery.ts +215 -17
  45. package/src/ai/model-manager.ts +346 -32
  46. package/src/ai/model-picker.ts +1 -1
  47. package/src/ai/model-registry.ts +4 -2
  48. package/src/ai/pricing.ts +84 -0
  49. package/src/ai/provider-registry.ts +23 -0
  50. package/src/ai/provider-status.ts +60 -16
  51. package/src/ai/providers/AGENTS.md +42 -0
  52. package/src/ai/providers/anthropic.ts +250 -31
  53. package/src/ai/providers/antigravity.ts +219 -0
  54. package/src/ai/providers/errors.ts +15 -1
  55. package/src/ai/providers/gemini.ts +196 -13
  56. package/src/ai/providers/ollama.ts +37 -7
  57. package/src/ai/providers/openai-responses.ts +173 -0
  58. package/src/ai/providers/openai.ts +64 -12
  59. package/src/ai/sse.ts +4 -1
  60. package/src/ai/types.ts +18 -1
  61. package/src/auth/AGENTS.md +41 -0
  62. package/src/auth/callback-server.ts +6 -1
  63. package/src/auth/flows/AGENTS.md +32 -0
  64. package/src/auth/flows/antigravity.ts +151 -0
  65. package/src/auth/flows/google-project.ts +190 -0
  66. package/src/auth/flows/google.ts +39 -18
  67. package/src/auth/flows/index.ts +15 -5
  68. package/src/auth/flows/openai.ts +2 -2
  69. package/src/auth/oauth.ts +8 -0
  70. package/src/auth/refresh.ts +44 -27
  71. package/src/auth/storage.ts +149 -26
  72. package/src/auth/types.ts +1 -1
  73. package/src/autopilot.ts +362 -0
  74. package/src/bun-imports.d.ts +4 -0
  75. package/src/cli/AGENTS.md +39 -0
  76. package/src/cli/runner.ts +148 -14
  77. package/src/cli.ts +13 -4
  78. package/src/commands/AGENTS.md +40 -0
  79. package/src/commands/approve.ts +62 -3
  80. package/src/commands/auth.ts +167 -25
  81. package/src/commands/chat.ts +37 -8
  82. package/src/commands/deep-interview.ts +633 -175
  83. package/src/commands/doctor.ts +84 -37
  84. package/src/commands/evolve-core.ts +18 -0
  85. package/src/commands/evolve.ts +2 -1
  86. package/src/commands/export.ts +176 -0
  87. package/src/commands/gjc.ts +52 -0
  88. package/src/commands/launch.ts +3549 -240
  89. package/src/commands/mcp.ts +3 -3
  90. package/src/commands/ooo-seed.ts +19 -0
  91. package/src/commands/ralplan.ts +253 -35
  92. package/src/commands/resume.ts +1 -1
  93. package/src/commands/session.ts +183 -0
  94. package/src/commands/setup-helpers.ts +10 -3
  95. package/src/commands/setup.ts +57 -16
  96. package/src/commands/skills.ts +78 -18
  97. package/src/commands/state.ts +198 -0
  98. package/src/commands/status.ts +84 -0
  99. package/src/commands/team.ts +340 -212
  100. package/src/commands/ultragoal.ts +122 -61
  101. package/src/commands/update.ts +244 -0
  102. package/src/ledger.ts +270 -0
  103. package/src/mcp/AGENTS.md +38 -0
  104. package/src/mcp/server.ts +115 -14
  105. package/src/mcp/tools.ts +42 -22
  106. package/src/md-modules.d.ts +4 -0
  107. package/src/prompts/AGENTS.md +41 -0
  108. package/src/prompts/agents/AGENTS.md +35 -0
  109. package/src/prompts/agents/architect.md +35 -0
  110. package/src/prompts/agents/critic.md +37 -0
  111. package/src/prompts/agents/executor.md +36 -0
  112. package/src/prompts/agents/planner.md +37 -0
  113. package/src/prompts/skills/AGENTS.md +36 -0
  114. package/src/prompts/skills/deep-dive/AGENTS.md +31 -0
  115. package/src/prompts/skills/deep-dive/SKILL.md +13 -0
  116. package/src/prompts/skills/deep-interview/AGENTS.md +31 -0
  117. package/src/prompts/skills/deep-interview/SKILL.md +12 -0
  118. package/src/prompts/skills/gjc/AGENTS.md +31 -0
  119. package/src/prompts/skills/gjc/SKILL.md +15 -0
  120. package/src/prompts/skills/ralplan/AGENTS.md +31 -0
  121. package/src/prompts/skills/ralplan/SKILL.md +11 -0
  122. package/src/prompts/skills/team/AGENTS.md +31 -0
  123. package/src/prompts/skills/team/SKILL.md +11 -0
  124. package/src/prompts/skills/ultragoal/AGENTS.md +31 -0
  125. package/src/prompts/skills/ultragoal/SKILL.md +11 -0
  126. package/src/skills/AGENTS.md +38 -0
  127. package/src/skills/catalog.ts +565 -31
  128. package/src/tui/AGENTS.md +43 -0
  129. package/src/tui/app.ts +1181 -92
  130. package/src/tui/components/AGENTS.md +42 -0
  131. package/src/tui/components/ascii-art.ts +257 -15
  132. package/src/tui/components/autocomplete.ts +98 -16
  133. package/src/tui/components/autopilot-status.ts +65 -0
  134. package/src/tui/components/category-index.ts +49 -0
  135. package/src/tui/components/code-view.ts +54 -11
  136. package/src/tui/components/color.ts +171 -2
  137. package/src/tui/components/config-panel.ts +82 -15
  138. package/src/tui/components/duration.ts +38 -0
  139. package/src/tui/components/evolution.ts +3 -3
  140. package/src/tui/components/footer.ts +91 -42
  141. package/src/tui/components/forge.ts +426 -31
  142. package/src/tui/components/hints.ts +54 -0
  143. package/src/tui/components/hud.ts +73 -0
  144. package/src/tui/components/index.ts +4 -0
  145. package/src/tui/components/input-box.ts +150 -0
  146. package/src/tui/components/layout.ts +11 -3
  147. package/src/tui/components/live-model-picker.ts +108 -0
  148. package/src/tui/components/markdown-table.ts +140 -0
  149. package/src/tui/components/markdown-text.ts +97 -0
  150. package/src/tui/components/meter.ts +4 -1
  151. package/src/tui/components/model-picker.ts +3 -2
  152. package/src/tui/components/provider-picker.ts +3 -2
  153. package/src/tui/components/section.ts +70 -0
  154. package/src/tui/components/select-list.ts +40 -10
  155. package/src/tui/components/skill-picker.ts +25 -0
  156. package/src/tui/components/slash.ts +244 -21
  157. package/src/tui/components/status.ts +272 -11
  158. package/src/tui/components/step-timeline.ts +218 -0
  159. package/src/tui/components/stream.ts +26 -9
  160. package/src/tui/components/themes.ts +212 -6
  161. package/src/tui/components/todo-card.ts +47 -0
  162. package/src/tui/components/tool-list.ts +58 -12
  163. package/src/tui/components/transcript.ts +120 -0
  164. package/src/tui/components/update-box.ts +31 -0
  165. package/src/tui/components/welcome.ts +162 -0
  166. package/src/tui/components/width.ts +163 -0
  167. package/src/tui/monitoring/AGENTS.md +31 -0
  168. package/src/tui/monitoring/hud-view.ts +55 -0
  169. package/src/tui/renderer.ts +112 -3
  170. package/src/tui/terminal.ts +40 -33
  171. package/src/util/AGENTS.md +39 -0
  172. package/src/util/clipboard-image.ts +118 -0
  173. package/src/util/env.ts +12 -0
  174. package/src/util/provider-error.ts +78 -0
  175. package/src/util/retry.ts +91 -6
  176. package/src/util/update-check.ts +64 -0
  177. package/src/commands/models.ts +0 -104
@@ -0,0 +1,42 @@
1
+ <!-- Parent: ../../AGENTS.md -->
2
+ <!-- Generated: 2026-06-11 | Updated: 2026-06-11 -->
3
+
4
+ # components
5
+
6
+ ## Purpose
7
+ Reusable UI widgets and layout primitives for the terminal interface.
8
+
9
+ ## Key Files
10
+ | File | Description |
11
+ |------|-------------|
12
+ | `forge.ts` | Formats the boxed tool execution outputs |
13
+ | `status.ts` | The `[STEP]` / `[STATUS]` / `[TOOL]` HUD lines |
14
+ | `section.ts` | Shadcn-inspired card layout and spacing tokens |
15
+ | `layout.ts` | Low-level padding, boxing, and alignment math |
16
+ | `ascii-art.ts` | Cellular evolution graphics |
17
+
18
+ ## Subdirectories
19
+ *(None)*
20
+
21
+ ## For AI Agents
22
+
23
+ ### Working In This Directory
24
+ - Prioritize deterministic width/height calculations.
25
+ - Always strip ANSI codes (`\x1b[...m`) before measuring string lengths.
26
+ - Components should return string arrays (`string[]`) representing lines, not perform direct stdout writes.
27
+
28
+ ### Testing Requirements
29
+ - Snapshot or exact string matching in unit tests.
30
+
31
+ ### Common Patterns
32
+ - Theming via `themes.ts` and `chalk`.
33
+
34
+ ## Dependencies
35
+
36
+ ### Internal
37
+ - Consumed by `src/tui/app.ts`.
38
+
39
+ ### External
40
+ *(None)*
41
+
42
+ <!-- MANUAL: -->
@@ -1,6 +1,6 @@
1
1
  import chalk from "chalk";
2
2
  import { stageIndexForStep, clampStageIndex, type StageGradient } from "./evolution";
3
- import { applyGradient, hexToRgb, ColorLevel } from "./color";
3
+ import { applyGradient, hexToRgb, ColorLevel, animatedGradientText } from "./color";
4
4
 
5
5
  export interface AsciiStage {
6
6
  name: string;
@@ -13,6 +13,14 @@ export interface AsciiStage {
13
13
  * the fallback when `frames` is absent. Frames should match `art`'s line count.
14
14
  */
15
15
  frames?: string[][];
16
+ /**
17
+ * Optional ASCII-only `art`/`frames` used when the caller passes `unicode: false`
18
+ * (terminals that cannot render box-drawing / geometric glyphs). When absent the
19
+ * stage's normal `art`/`frames` are used as-is (the other stages are already
20
+ * ASCII-clean). Row counts should match so per-line colors stay aligned.
21
+ */
22
+ asciiArt?: string[];
23
+ asciiFrames?: string[][];
16
24
  }
17
25
 
18
26
  export const EVOLUTION_STAGES: AsciiStage[] = [
@@ -20,33 +28,82 @@ export const EVOLUTION_STAGES: AsciiStage[] = [
20
28
  name: "Primordial Cell",
21
29
  color: s => chalk.cyan(s),
22
30
  art: [
23
- " .---. ",
24
- " / o o \\ ",
25
- " \\ - / ",
26
- " '---' ",
31
+ " ○ ○ ",
32
+ " ╲ ╱ ",
33
+ " ╭───────╮ ",
34
+ " │ ◕ § ◕ │ ",
35
+ " │ · ‿ · │ ",
36
+ " ╰───────╯ ",
37
+ " ╱ ╲ ╱ ╲ ",
27
38
  " [Primordial Cell]"
28
39
  ],
29
40
  lineColors: [
30
41
  chalk.cyan,
31
42
  chalk.cyan,
32
43
  chalk.cyan,
44
+ s => chalk.bold.cyan(s),
45
+ chalk.cyan,
46
+ chalk.cyan,
33
47
  chalk.cyan,
34
48
  s => chalk.bold.cyan(s)
35
49
  ],
36
- // Pulsing membrane + nucleus (a primordial cell "breathing").
50
+ // A glowing primordial cell after assets/character.png: antennae with pulsing
51
+ // tips, a round membrane, big kawaii eyes flanking an inner DNA helix (§/≋), a
52
+ // smile, and crab-like legs. Two frames "breathe": blink + helix pulse + leg swing.
53
+ // Every glyph is display-width 1 (box-drawing + ambiguous-width geometrics the
54
+ // rest of the TUI already uses) so renderAsciiArt's length-based padding and
55
+ // app.ts/welcome.ts's visibleWidth centering stay in lockstep.
37
56
  frames: [
38
57
  [
39
- " .---. ",
40
- " / o o \\ ",
41
- " \\ - / ",
42
- " '---' ",
58
+ " ○ ○ ",
59
+ " ╲ ╱ ",
60
+ " ╭───────╮ ",
61
+ " │ ◕ § ◕ │ ",
62
+ " │ · ‿ · │ ",
63
+ " ╰───────╯ ",
64
+ " ╱ ╲ ╱ ╲ ",
65
+ " [Primordial Cell]"
66
+ ],
67
+ [
68
+ " ◉ ◉ ",
69
+ " ╲ ╱ ",
70
+ " ╭───────╮ ",
71
+ " │ ◔ ≋ ◔ │ ",
72
+ " │ ° ‿ ° │ ",
73
+ " ╰───────╯ ",
74
+ " ╲ ╱ ╲ ╱ ",
75
+ " [Primordial Cell]"
76
+ ]
77
+ ],
78
+ asciiArt: [
79
+ " o o ",
80
+ " \\ / ",
81
+ " .-------. ",
82
+ " | o 8 o | ",
83
+ " | \\_/ | ",
84
+ " '-------' ",
85
+ " / \\ / \\ ",
86
+ " [Primordial Cell]"
87
+ ],
88
+ asciiFrames: [
89
+ [
90
+ " o o ",
91
+ " \\ / ",
92
+ " .-------. ",
93
+ " | o 8 o | ",
94
+ " | \\_/ | ",
95
+ " '-------' ",
96
+ " / \\ / \\ ",
43
97
  " [Primordial Cell]"
44
98
  ],
45
99
  [
46
- " .===. ",
47
- " / O O \\ ",
48
- " \\ ~ / ",
49
- " '===' ",
100
+ " O O ",
101
+ " \\ / ",
102
+ " .-------. ",
103
+ " | - % - | ",
104
+ " | \\_/ | ",
105
+ " '-------' ",
106
+ " \\ / \\ / ",
50
107
  " [Primordial Cell]"
51
108
  ]
52
109
  ]
@@ -242,6 +299,9 @@ export interface RenderAsciiOptions {
242
299
  gradient?: StageGradient;
243
300
  /** Color tier for gradient rendering (default TrueColor). */
244
301
  colorLevel?: ColorLevel;
302
+ /** Use the stage's ASCII-only `asciiArt`/`asciiFrames` fallback when false
303
+ * (terminals without box-drawing/geometric glyph support). Default true. */
304
+ unicode?: boolean;
245
305
  }
246
306
 
247
307
  /**
@@ -252,7 +312,21 @@ export interface RenderAsciiOptions {
252
312
  */
253
313
  export function renderAsciiArt(stage: AsciiStage, opts: RenderAsciiOptions = {}): string[] {
254
314
  const useColor = opts.color !== false;
255
- const source = opts.frame !== undefined ? stageFrame(stage, opts.frame) : stage.art;
315
+ const useUnicode = opts.unicode !== false;
316
+ // ASCII-fallback source set: a non-unicode terminal gets the stage's plain-ASCII
317
+ // art/frames (when defined) instead of box-drawing/geometric glyphs that would
318
+ // render as tofu boxes. stageBlocks/stageFrame keep using the unicode frames so
319
+ // stageWidth()/stageHeight() invariants are unaffected.
320
+ const frameSet =
321
+ !useUnicode && stage.asciiFrames && stage.asciiFrames.length > 0 ? stage.asciiFrames : stageBlocks(stage);
322
+ const baseArt = !useUnicode && stage.asciiArt ? stage.asciiArt : stage.art;
323
+ let source: string[];
324
+ if (opts.frame !== undefined) {
325
+ const t = Number.isFinite(opts.frame) ? Math.trunc(opts.frame) : 0;
326
+ source = frameSet[((t % frameSet.length) + frameSet.length) % frameSet.length]!;
327
+ } else {
328
+ source = baseArt;
329
+ }
256
330
  const width = opts.width ?? Math.max(0, ...source.map(l => l.length));
257
331
  if (opts.cols !== undefined && opts.cols < width) {
258
332
  return [];
@@ -338,3 +412,171 @@ export async function animateFrames(stage: AsciiStage, opts: AnimateFramesOption
338
412
  }
339
413
  return total;
340
414
  }
415
+ export const DNA_CLAW_ART: string[] = [
416
+ " ╭╯ ◆ ◆ ╰╮ ",
417
+ " ╭╯ ╱╲ ╱╲ ╰╮ ",
418
+ " ║ ╲ ╳ ╱ ║ ",
419
+ " ╰╮ ╳ ╳ ╭╯ ",
420
+ " ╰╮ ╱ ╳ ╲ ╭╯ ",
421
+ " ╚══○ ○══╝ ",
422
+ " ║ ║ ",
423
+ " [ DNA Claw ] "
424
+ ];
425
+
426
+ export const DNA_CLAW_ART_ASCII: string[] = [
427
+ " /{ * * }\\ ",
428
+ " /{ / \\ / \\ }\\ ",
429
+ " | \\ X / | ",
430
+ " \\{ X X }/ ",
431
+ " \\{ / X \\ }/ ",
432
+ " \\==o o==/ ",
433
+ " | | ",
434
+ " [ DNA Claw ] "
435
+ ];
436
+
437
+ /** Twist animation frames for the compact DNA Claw: the claw silhouette stays
438
+ * fixed while the inner helix lattice rotates. Frame 0 === DNA_CLAW_ART, so a
439
+ * frameless render is byte-identical to the static symbol. All lines are the
440
+ * same width (18) and every glyph is display-width 1. */
441
+ export const DNA_CLAW_FRAMES: string[][] = [
442
+ DNA_CLAW_ART,
443
+ [
444
+ " ╭╯ ◆ ◆ ╰╮ ",
445
+ " ╭╯ ╲╱ ╲╱ ╰╮ ",
446
+ " ║ ╳ ╳ ║ ",
447
+ " ╰╮ ╱ ╳ ╲ ╭╯ ",
448
+ " ╰╮ ╳ ╳ ╭╯ ",
449
+ " ╚══○ ○══╝ ",
450
+ " ║ ║ ",
451
+ " [ DNA Claw ] "
452
+ ],
453
+ [
454
+ " ╭╯ ◆ ◆ ╰╮ ",
455
+ " ╭╯ ╱╲ ╱╲ ╰╮ ",
456
+ " ║ ╳ ╳ ║ ",
457
+ " ╰╮ ╲ ╳ ╱ ╭╯ ",
458
+ " ╰╮ ╳ ╳ ╭╯ ",
459
+ " ╚══○ ○══╝ ",
460
+ " ║ ║ ",
461
+ " [ DNA Claw ] "
462
+ ]
463
+ ];
464
+
465
+ export const DNA_CLAW_FRAMES_ASCII: string[][] = [
466
+ DNA_CLAW_ART_ASCII,
467
+ [
468
+ " /{ * * }\\ ",
469
+ " /{ \\ / \\ / }\\ ",
470
+ " | X X | ",
471
+ " \\{ / X \\ }/ ",
472
+ " \\{ X X }/ ",
473
+ " \\==o o==/ ",
474
+ " | | ",
475
+ " [ DNA Claw ] "
476
+ ],
477
+ [
478
+ " /{ * * }\\ ",
479
+ " /{ / \\ / \\ }\\ ",
480
+ " | X X | ",
481
+ " \\{ \\ X / }/ ",
482
+ " \\{ X X }/ ",
483
+ " \\==o o==/ ",
484
+ " | | ",
485
+ " [ DNA Claw ] "
486
+ ]
487
+ ];
488
+
489
+ /** Number of twist frames in the compact DNA Claw animation cycle. */
490
+ export function dnaClawFrameCount(): number {
491
+ return DNA_CLAW_FRAMES.length;
492
+ }
493
+
494
+ /** Grand hero variant for the welcome forge box (gjc-style spacious banner):
495
+ * a wide claw whose pincers frame a twisting DNA helix. Width-1 glyphs only
496
+ * (box drawing + diagonals + geometrics) so padding/centering math stays exact. */
497
+ export const DNA_CLAW_ART_GRAND: string[] = [
498
+ " ◆◆ ◆◆ ",
499
+ " ╭──╯╰──╮ ╭──╯╰──╮ ",
500
+ " ╭╯ ╰╮ ╲╲ ╱╱ ╭╯ ╰╮ ",
501
+ " ╭╯ ║ ╲╳╳╱ ║ ╰╮ ",
502
+ " ║ ║ ╳╳ ║ ║ ",
503
+ " ║ ║ ╱╳╳╲ ║ ║ ",
504
+ " ╰╮ ║ ╱╱ ╲╲ ║ ╭╯ ",
505
+ " ╰╮ ║ ╲╲ ╱╱ ║ ╭╯ ",
506
+ " ╰──╮ ║ ╲╳╳╱ ║ ╭──╯ ",
507
+ " ╰════○ ╳╳ ○════╯ ",
508
+ " ║ ╱╳╳╲ ║ ",
509
+ " [ D N A · C L A W ] "
510
+ ];
511
+
512
+ export const DNA_CLAW_ART_GRAND_ASCII: string[] = [
513
+ " ** ** ",
514
+ " /--'`--\\ /--'`--\\ ",
515
+ " /' `\\ \\\\ // /' `\\ ",
516
+ " /' | \\XX/ | `\\ ",
517
+ " | | XX | | ",
518
+ " | | /XX\\ | | ",
519
+ " \\, | // \\\\ | ,/ ",
520
+ " \\, | \\\\ // | ,/ ",
521
+ " \\--, | \\XX/ | ,--/ ",
522
+ " \\====o XX o====/ ",
523
+ " | /XX\\ | ",
524
+ " [ D N A . C L A W ] "
525
+ ];
526
+
527
+ export function renderDnaClaw(opts: {
528
+ cols?: number;
529
+ phase?: number;
530
+ unicode?: boolean;
531
+ color?: boolean;
532
+ colorLevel?: ColorLevel;
533
+ /** Grand hero variant (welcome forge box); default is the compact in-turn symbol. */
534
+ grand?: boolean;
535
+ /** Twist-animation frame (compact symbol only; wraps). The helix lattice rotates
536
+ * while the claw silhouette stays fixed — combined with the flowing gradient
537
+ * `phase` this animates the forge identity without any frame-count growth. */
538
+ frame?: number;
539
+ }): string[] {
540
+ const useUnicode = opts.unicode !== false;
541
+ let source: string[];
542
+ if (opts.grand) {
543
+ source = useUnicode ? DNA_CLAW_ART_GRAND : DNA_CLAW_ART_GRAND_ASCII;
544
+ } else {
545
+ const frames = useUnicode ? DNA_CLAW_FRAMES : DNA_CLAW_FRAMES_ASCII;
546
+ const f = Math.abs(Math.trunc(opts.frame ?? 0)) % frames.length;
547
+ source = frames[f]!;
548
+ }
549
+ const width = Math.max(0, ...source.map(l => l.length));
550
+
551
+ if (opts.cols !== undefined && opts.cols < width) {
552
+ return [];
553
+ }
554
+
555
+ const phase = opts.phase ?? 0;
556
+ const useColor = opts.color !== false;
557
+ const colorLevel = opts.colorLevel ?? ColorLevel.TrueColor;
558
+ const palette = DNA_FLOW_PALETTE;
559
+
560
+ return source.map((line, idx) => {
561
+ const padded = line.length < width ? line + " ".repeat(width - line.length) : line;
562
+ if (!useColor || colorLevel < ColorLevel.TrueColor) {
563
+ return padded;
564
+ }
565
+ return animatedGradientText(padded, palette, phase + idx * 0.07, { colorLevel });
566
+ });
567
+ }
568
+
569
+ /** The DNA Claw identity palette (emerald → cyan → violet helix flow). Shared by
570
+ * the claw art and the forge-card border flow so the brand gradient is uniform. */
571
+ export const DNA_FLOW_PALETTE: readonly string[] = ["#10ac84", "#48dbfb", "#8e44ad"];
572
+
573
+ /** Width-1 "claw beat" glyph for an animation tick — the ◆/╳/○ motifs of the
574
+ * claw art cycling in place. Used as the live forge-card title mark. */
575
+ export function dnaClawBeat(frame: number, unicode = true): string {
576
+ const beats = unicode ? ["◆", "╳", "○"] : ["*", "X", "o"];
577
+ return beats[Math.abs(Math.trunc(frame)) % beats.length]!;
578
+ }
579
+
580
+ export function dnaClawHeight(): number {
581
+ return DNA_CLAW_ART.length;
582
+ }
@@ -2,11 +2,11 @@
2
2
  * Interactive autocomplete engine for the REPL.
3
3
  *
4
4
  * Completes slash-command *names* and their *arguments*:
5
- * - `/mod` → `/model`, `/models`
5
+ * - `/mod` → `/model`
6
6
  * - `/model gpt` → live (logged-in) model ids + aliases + catalog ids
7
7
  * - `/provider an` → provider names; second arg → that provider's live models
8
8
  * - `/agents exec` → subagent role ids; second arg → live model ids
9
- * - `/thinking h` → low/medium/high
9
+ * - `/thinking h` → minimal/low/medium/high/xhigh
10
10
  *
11
11
  * Pure + synchronous: the dynamic data (live models from the OAuth-authenticated
12
12
  * accounts, alias snapshot) is passed in via `CompletionContext`, so the readline
@@ -17,6 +17,8 @@ import { SLASH_COMMANDS } from "./slash";
17
17
  import { catalogIds } from "../../ai/model-catalog-compat";
18
18
  import { PROVIDER_NAMES } from "../../ai/provider-status";
19
19
  import { SUBAGENT_ROLES } from "../../agent/subagents";
20
+ import { skillNames } from "../../skills/catalog";
21
+ import { listThemes } from "./themes";
20
22
 
21
23
  export interface CompletionContext {
22
24
  slashCommands: string[];
@@ -29,8 +31,12 @@ export interface CompletionContext {
29
31
  providers: string[];
30
32
  roleIds: string[];
31
33
  thinkingLevels: string[];
34
+ /** Resolved skill names (bundled + user/project). Falls back to bundled when omitted. */
35
+ skillNames?: string[];
32
36
  /** Live model ids for a given provider (for `/provider <p> <model>`). */
33
37
  modelsForProvider: (provider: string) => string[];
38
+ /** Sync path suggestions for free-text `@path` mentions (relative to cwd). */
39
+ mentionPaths?: (prefix: string) => string[];
34
40
  }
35
41
 
36
42
  export interface CompletionResult {
@@ -42,8 +48,17 @@ export interface CompletionResult {
42
48
  kind: string;
43
49
  }
44
50
 
51
+ const PREVIEW_LABEL: Record<string, string> = {
52
+ command: "Commands",
53
+ model: "Models",
54
+ provider: "Providers",
55
+ role: "Subagent roles",
56
+ thinking: "Thinking levels",
57
+ subcommand: "Subcommands",
58
+ path: "Paths",
59
+ };
45
60
  const MAX_COMPLETIONS = 50;
46
- const THINKING_LEVELS = ["low", "medium", "high"];
61
+ const THINKING_LEVELS = ["minimal", "low", "medium", "high", "xhigh"];
47
62
 
48
63
  /** Static half of a completion context (no network/config needed). */
49
64
  export function staticCompletionContext(): Omit<CompletionContext, "liveModels" | "aliases" | "modelsForProvider"> {
@@ -88,16 +103,41 @@ function rankedModelPool(ctx: CompletionContext): string[] {
88
103
  }
89
104
 
90
105
  /**
91
- * Compute completions for the current input line. Returns an empty list for
92
- * non-slash input (free-text prompts are not completed).
106
+ * Compute completions for the current input line. Slash commands are completed as
107
+ * before; free-text input stays untouched except for `@path` mentions, which can
108
+ * surface local relative paths.
93
109
  */
94
110
  export function complete(line: string, ctx: CompletionContext): CompletionResult {
95
- if (!line.startsWith("/")) return { completions: [], token: line, kind: "none" };
96
-
97
111
  const { tokens, trailingSpace } = tokenize(line);
112
+ if (!line.startsWith("/")) {
113
+ const token = trailingSpace ? "" : tokens[tokens.length - 1] ?? "";
114
+ if (token.startsWith("@")) {
115
+ const prefix = token.slice(1);
116
+ const pool = (ctx.mentionPaths?.(prefix) ?? []).map(p => (p.startsWith("@") ? p : `@${p}`));
117
+ return { completions: dedupeCap(prefixHits(pool, token)), token, kind: "path" };
118
+ }
119
+ // `$skill` mention completion at ANY position in the line (mention-style;
120
+ // a leading `$name` is additionally the direct-invocation entrypoint).
121
+ if (token.startsWith("$")) {
122
+ const names = ctx.skillNames ?? skillNames();
123
+ return { completions: dedupeCap(prefixHits(names.map(n => `$${n}`), token)), token, kind: "skill" };
124
+ }
125
+ // `/command` mention completion mid-line (the leading-token case is the
126
+ // dedicated command branch below, which also completes arguments).
127
+ if (token.startsWith("/")) {
128
+ return { completions: dedupeCap(prefixHits(ctx.slashCommands, token)), token, kind: "command" };
129
+ }
130
+ return { completions: [], token: line, kind: "none" };
131
+ }
132
+
98
133
  // Completing the command name itself (single token, still typing it).
99
134
  if (tokens.length <= 1 && !trailingSpace) {
100
135
  const token = tokens[0] ?? "/";
136
+ if (token.toLowerCase().startsWith("/skill:")) {
137
+ const prefix = token.slice("/skill:".length);
138
+ const names = ctx.skillNames ?? skillNames();
139
+ return { completions: dedupeCap(prefixHits(names.map(n => `/skill:${n}`), `/skill:${prefix}`)), token, kind: "command" };
140
+ }
101
141
  return { completions: dedupeCap(prefixHits(ctx.slashCommands, token)), token, kind: "command" };
102
142
  }
103
143
 
@@ -116,31 +156,73 @@ export function complete(line: string, ctx: CompletionContext): CompletionResult
116
156
  switch (cmd) {
117
157
  case "/model": {
118
158
  if (token.startsWith("#")) return { completions: [], token, kind: "none" }; // numbered pick
119
- if (argIndex === 0) return finish(["save", ...rankedModelPool(ctx)], "model");
120
- // `/model save <id>` second arg models
121
- if (argIndex === 1 && tokens[1]?.toLowerCase() === "save") return finish(rankedModelPool(ctx), "model");
159
+ if (argIndex === 0) return finish(["save", "subagent", "role", "thinking", ...rankedModelPool(ctx)], "model");
160
+ if (argIndex === 1 && (tokens[1]?.toLowerCase() === "save")) return finish(rankedModelPool(ctx), "model");
161
+ if (argIndex === 1 && (tokens[1]?.toLowerCase() === "thinking" || tokens[1]?.toLowerCase() === "think")) return finish(ctx.thinkingLevels, "thinking");
162
+ if (argIndex === 1 && (tokens[1]?.toLowerCase() === "subagent" || tokens[1]?.toLowerCase() === "role")) return finish(ctx.roleIds, "role");
163
+ if (argIndex === 2 && (tokens[1]?.toLowerCase() === "subagent" || tokens[1]?.toLowerCase() === "role")) return finish(["thinking", ...rankedModelPool(ctx)], "model");
164
+ if (argIndex === 3 && (tokens[1]?.toLowerCase() === "subagent" || tokens[1]?.toLowerCase() === "role") && (tokens[3]?.toLowerCase() === "thinking" || tokens[3]?.toLowerCase() === "think")) return finish(["inherit", ...ctx.thinkingLevels], "thinking");
122
165
  return { completions: [], token, kind: "none" };
123
166
  }
124
- case "/models":
125
- return argIndex === 0 ? finish(["refresh"], "subcommand") : { completions: [], token, kind: "none" };
167
+ case "/fast":
168
+ return argIndex === 0 ? finish(["on", "off", "status"], "subcommand") : { completions: [], token, kind: "none" };
126
169
  case "/provider": {
127
- if (argIndex === 0) return finish(ctx.providers, "provider");
170
+ const cloud = ["anthropic", "openai", "gemini", "antigravity"];
171
+ if (argIndex === 0) return finish(["login", "auth", ...ctx.providers], "provider");
172
+ // `/provider login|auth <name>` → cloud provider names (OAuth-capable).
173
+ if (argIndex === 1 && (tokens[1]?.toLowerCase() === "login" || tokens[1]?.toLowerCase() === "auth")) return finish(cloud, "provider");
128
174
  if (argIndex === 1) return finish(ctx.modelsForProvider(tokens[1] ?? ""), "model");
129
175
  return { completions: [], token, kind: "none" };
130
176
  }
177
+ case "/logout":
178
+ return argIndex === 0 ? finish(["anthropic", "openai", "gemini", "antigravity"], "provider") : { completions: [], token, kind: "none" };
131
179
  case "/agents": {
132
- if (argIndex === 0) return finish(ctx.roleIds, "role");
133
- if (argIndex === 1) return finish(["maxSteps", ...rankedModelPool(ctx)], "model");
134
- if (argIndex === 2 && tokens[2]?.toLowerCase() === "maxsteps") return { completions: [], token, kind: "none" };
180
+ if (argIndex === 0) return finish(["edit", ...ctx.roleIds], "role");
181
+ if (argIndex === 1) return finish(["reset", "thinking", "maxSteps", ...rankedModelPool(ctx)], "model");
182
+ if (argIndex === 2 && (tokens[2]?.toLowerCase() === "thinking" || tokens[2]?.toLowerCase() === "think")) return finish(["inherit", ...ctx.thinkingLevels], "thinking");
183
+ if (argIndex === 2 && (tokens[2]?.toLowerCase() === "maxsteps" || tokens[2]?.toLowerCase() === "steps")) return { completions: [], token, kind: "none" };
184
+ return { completions: [], token, kind: "none" };
185
+ }
186
+ case "/skill":
187
+ return argIndex === 0 ? finish(ctx.skillNames ?? skillNames(), "subcommand") : { completions: [], token, kind: "none" };
188
+ case "/roles": {
189
+ const tiers = ["smol", "slow", "plan"];
190
+ if (argIndex === 0) return finish(tiers, "role");
191
+ if (argIndex === 1 && tiers.includes(tokens[1]?.toLowerCase() ?? "")) return finish(rankedModelPool(ctx), "model");
135
192
  return { completions: [], token, kind: "none" };
136
193
  }
137
194
  case "/thinking":
138
195
  return argIndex === 0 ? finish(ctx.thinkingLevels, "thinking") : { completions: [], token, kind: "none" };
196
+ case "/session":
197
+ return argIndex === 0 ? finish(["info", "delete"], "subcommand") : { completions: [], token, kind: "none" };
198
+ case "/theme":
199
+ return argIndex === 0 ? finish(listThemes().map(t => t.name), "subcommand") : { completions: [], token, kind: "none" };
200
+ case "/login":
201
+ return argIndex === 0 ? finish(["anthropic", "openai", "gemini", "antigravity"], "provider") : { completions: [], token, kind: "none" };
202
+ case "/export":
203
+ return argIndex <= 1 ? finish(["json", "markdown"], "subcommand") : { completions: [], token, kind: "none" };
139
204
  default:
140
205
  return { completions: [], token, kind: "none" };
141
206
  }
142
207
  }
143
208
 
209
+ /** Compact live preview for slash-command arguments (`/subagent `, `/provider login `, ...). */
210
+ export function formatCompletionPreview(line: string, ctx: CompletionContext, max = 6): string[] {
211
+ if (max <= 0) return [];
212
+ const result = complete(line, ctx);
213
+ if (result.kind === "none" || result.kind === "command" || result.completions.length === 0) return [];
214
+ const label = PREVIEW_LABEL[result.kind] ?? "Matches";
215
+ const budget = Math.max(1, max - 1);
216
+ const shown = result.completions.slice(0, budget);
217
+ const lines = [`${label}:`, ...shown.map(c => ` ${c}`)];
218
+ const hidden = result.completions.length - shown.length;
219
+ if (hidden > 0) {
220
+ if (lines.length >= max) lines[lines.length - 1] = ` …(+${hidden + 1} more)`;
221
+ else lines.push(` …(+${hidden} more)`);
222
+ }
223
+ return lines;
224
+ }
225
+
144
226
  /** Longest common prefix of a list (for tab "fill to ambiguity"). */
145
227
  export function commonPrefix(items: string[]): string {
146
228
  if (items.length === 0) return "";
@@ -0,0 +1,65 @@
1
+ import chalk from "chalk";
2
+ import { padLineTo } from "./layout";
3
+ import { truncate } from "../terminal";
4
+
5
+ export interface AutopilotStatusPanelData {
6
+ task: string;
7
+ goal: string;
8
+ eval: string;
9
+ baseline: string;
10
+ best: string;
11
+ attempts: number;
12
+ kept: number;
13
+ reverted: number;
14
+ sinceImprove: number;
15
+ converged: boolean;
16
+ recommendation: string;
17
+ }
18
+
19
+ export function renderAutopilotStatusPanel(
20
+ data: AutopilotStatusPanelData,
21
+ opts: { cols?: number; unicode?: boolean; color?: boolean } = {},
22
+ ): string[] {
23
+ const width = Math.max(40, Math.min(120, opts.cols ?? 88));
24
+ const useColor = opts.color !== false;
25
+ const unicode = opts.unicode !== false;
26
+ const ruleChar = unicode ? "─" : "-";
27
+ const arrow = unicode ? "→" : "->";
28
+ const keptMark = unicode ? "✓" : "v";
29
+ const revertedMark = unicode ? "↶" : "r";
30
+ const status = data.converged
31
+ ? "CONVERGED"
32
+ : data.recommendation.startsWith("continue")
33
+ ? "CONTINUE"
34
+ : "STOP";
35
+
36
+ const yellow = useColor ? chalk.hex("#f2b84b") : (s: string) => s;
37
+ const title = useColor ? chalk.hex("#f2b84b").bold : (s: string) => s;
38
+ const green = useColor ? chalk.green : (s: string) => s;
39
+ const red = useColor ? chalk.red : (s: string) => s;
40
+ const cyan = useColor ? chalk.cyan : (s: string) => s;
41
+ const dim = useColor ? chalk.dim : (s: string) => s;
42
+ const statusPaint = useColor
43
+ ? status === "CONTINUE"
44
+ ? chalk.green.bold
45
+ : status === "CONVERGED"
46
+ ? chalk.yellow.bold
47
+ : chalk.red.bold
48
+ : (s: string) => s;
49
+
50
+ const fit = (line: string) => padLineTo(truncate(line, width), width, "left");
51
+ const rule = yellow(ruleChar.repeat(width));
52
+ const score = `${dim("score")} ${cyan(data.baseline)} ${dim(arrow)} ${cyan(data.best)}`;
53
+ const attempts = `${dim("attempts")} ${data.attempts} · ${green(`${keptMark} ${data.kept} kept`)} · ${red(`${revertedMark} ${data.reverted} reverted`)} · patience ${data.sinceImprove}`;
54
+
55
+ return [
56
+ rule,
57
+ fit(`${title("Autopilot Ratchet")} ${statusPaint(status)}`),
58
+ fit(`${dim("task")} ${data.task}`),
59
+ fit(`${dim("eval")} ${data.goal} · ${data.eval}`),
60
+ fit(score),
61
+ fit(attempts),
62
+ fit(`${dim("next")} ${data.recommendation}`),
63
+ rule,
64
+ ];
65
+ }
@@ -0,0 +1,49 @@
1
+ import chalk from "chalk";
2
+
3
+ export type UiCategory = "progress" | "status" | "done" | "diff" | "subagent" | "code" | "file" | "cmd" | "tool" | "error" | "search";
4
+
5
+ interface CategoryMeta {
6
+ token: string;
7
+ label: string;
8
+ paint: (s: string) => string;
9
+ }
10
+
11
+ const META: Record<UiCategory, CategoryMeta> = {
12
+ progress: { token: "STEP", label: "progress", paint: chalk.cyan.bold },
13
+ status: { token: "STATUS", label: "status", paint: chalk.cyan.bold },
14
+ done: { token: "DONE", label: "completed", paint: chalk.green.bold },
15
+ diff: { token: "DIFF", label: "diff", paint: chalk.magenta.bold },
16
+ subagent: { token: "AGENT", label: "subagent", paint: chalk.blue.bold },
17
+ code: { token: "CODE", label: "code block", paint: chalk.cyan.bold },
18
+ file: { token: "FILE", label: "file path", paint: chalk.yellow.bold },
19
+ cmd: { token: "CMD", label: "command", paint: chalk.yellow.bold },
20
+ tool: { token: "TOOL", label: "tool", paint: chalk.magenta.bold },
21
+ error: { token: "ERR", label: "error", paint: chalk.red.bold },
22
+ search: { token: "SRCH", label: "search", paint: chalk.green.bold },
23
+ };
24
+
25
+ export function categoryMeta(category: UiCategory): { token: string; label: string } {
26
+ const m = META[category];
27
+ return { token: m.token, label: m.label };
28
+ }
29
+
30
+ export function categoryBadge(category: UiCategory, opts: { index?: number; color?: boolean } = {}): string {
31
+ const m = META[category];
32
+ const n = typeof opts.index === "number" ? `${String(Math.max(1, Math.trunc(opts.index))).padStart(2, "0")}:` : "";
33
+ const raw = `[${n}${m.token}]`;
34
+ return opts.color === false ? raw : m.paint(raw);
35
+ }
36
+
37
+ export function prefixCategory(category: UiCategory, text: string, opts: { index?: number; color?: boolean } = {}): string {
38
+ return `${categoryBadge(category, opts)} ${text}`;
39
+ }
40
+
41
+ export function categoryForTool(tool: string): UiCategory {
42
+ const normalized = (tool || "").toLowerCase();
43
+ if (normalized === "bash") return "cmd";
44
+ if (normalized === "read" || normalized === "write") return "file";
45
+ if (normalized === "edit") return "diff";
46
+ if (normalized === "search" || normalized === "find") return "search";
47
+ if (normalized === "task" || normalized.includes("agent")) return "subagent";
48
+ return "tool";
49
+ }