jeo-code 0.1.0 → 0.4.5
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.ja.md +160 -0
- package/README.ko.md +160 -0
- package/README.md +115 -297
- package/README.zh.md +160 -0
- package/package.json +11 -6
- package/scripts/install.sh +28 -28
- package/scripts/uninstall.sh +17 -15
- package/src/AGENTS.md +50 -0
- package/src/agent/AGENTS.md +49 -0
- package/src/agent/bash-fixups.ts +103 -0
- package/src/agent/compaction.ts +410 -19
- package/src/agent/config-schema.ts +119 -5
- package/src/agent/context-files.ts +314 -17
- package/src/agent/dev/AGENTS.md +36 -0
- package/src/agent/dev/advanced-analyzer.ts +12 -0
- package/src/agent/dev/evolution-bridge.ts +82 -0
- package/src/agent/dev/evolution-logger.ts +41 -0
- package/src/agent/dev/self-analysis.ts +64 -0
- package/src/agent/dev/self-improve.ts +24 -0
- package/src/agent/dev/spec-automation.ts +49 -0
- package/src/agent/engine.ts +808 -54
- package/src/agent/hooks.ts +273 -0
- package/src/agent/loop.ts +21 -1
- package/src/agent/memory.ts +201 -0
- package/src/agent/model-recency.ts +32 -0
- package/src/agent/output-minimizer.ts +108 -0
- package/src/agent/output-util.ts +64 -0
- package/src/agent/plan.ts +187 -0
- package/src/agent/seed.ts +52 -0
- package/src/agent/session.ts +235 -21
- package/src/agent/state.ts +286 -39
- package/src/agent/step-budget.ts +232 -0
- package/src/agent/subagents.ts +223 -26
- package/src/agent/task-tool.ts +272 -0
- package/src/agent/todo-tool.ts +87 -0
- package/src/agent/tokenizer.ts +117 -0
- package/src/agent/tool-registry.ts +54 -0
- package/src/agent/tools.ts +624 -103
- package/src/agent/web-search.ts +538 -0
- package/src/ai/AGENTS.md +44 -0
- package/src/ai/index.ts +1 -0
- package/src/ai/model-catalog-compat.ts +3 -1
- package/src/ai/model-catalog.ts +74 -9
- package/src/ai/model-discovery.ts +215 -17
- package/src/ai/model-manager.ts +346 -32
- package/src/ai/model-picker.ts +1 -1
- package/src/ai/model-registry.ts +4 -2
- package/src/ai/pricing.ts +84 -0
- package/src/ai/provider-registry.ts +23 -0
- package/src/ai/provider-status.ts +60 -16
- package/src/ai/providers/AGENTS.md +42 -0
- package/src/ai/providers/anthropic.ts +250 -31
- package/src/ai/providers/antigravity.ts +219 -0
- package/src/ai/providers/errors.ts +15 -1
- package/src/ai/providers/gemini.ts +196 -13
- package/src/ai/providers/ollama.ts +37 -7
- package/src/ai/providers/openai-responses.ts +173 -0
- package/src/ai/providers/openai.ts +64 -12
- package/src/ai/sse.ts +4 -1
- package/src/ai/types.ts +18 -1
- package/src/auth/AGENTS.md +41 -0
- package/src/auth/callback-server.ts +6 -1
- package/src/auth/flows/AGENTS.md +32 -0
- package/src/auth/flows/antigravity.ts +151 -0
- package/src/auth/flows/google-project.ts +190 -0
- package/src/auth/flows/google.ts +39 -18
- package/src/auth/flows/index.ts +15 -5
- package/src/auth/flows/openai.ts +2 -2
- package/src/auth/oauth.ts +8 -0
- package/src/auth/refresh.ts +44 -27
- package/src/auth/storage.ts +149 -26
- package/src/auth/types.ts +1 -1
- package/src/autopilot.ts +362 -0
- package/src/bun-imports.d.ts +4 -0
- package/src/cli/AGENTS.md +39 -0
- package/src/cli/runner.ts +148 -14
- package/src/cli.ts +13 -4
- package/src/commands/AGENTS.md +40 -0
- package/src/commands/approve.ts +62 -3
- package/src/commands/auth.ts +167 -25
- package/src/commands/chat.ts +37 -8
- package/src/commands/deep-interview.ts +633 -175
- package/src/commands/doctor.ts +84 -37
- package/src/commands/evolve-core.ts +18 -0
- package/src/commands/evolve.ts +2 -1
- package/src/commands/export.ts +176 -0
- package/src/commands/gjc.ts +52 -0
- package/src/commands/launch.ts +3549 -240
- package/src/commands/mcp.ts +3 -3
- package/src/commands/ooo-seed.ts +19 -0
- package/src/commands/ralplan.ts +253 -35
- package/src/commands/resume.ts +1 -1
- package/src/commands/session.ts +183 -0
- package/src/commands/setup-helpers.ts +10 -3
- package/src/commands/setup.ts +57 -16
- package/src/commands/skills.ts +78 -18
- package/src/commands/state.ts +198 -0
- package/src/commands/status.ts +84 -0
- package/src/commands/team.ts +340 -212
- package/src/commands/ultragoal.ts +122 -61
- package/src/commands/update.ts +244 -0
- package/src/ledger.ts +270 -0
- package/src/mcp/AGENTS.md +38 -0
- package/src/mcp/server.ts +115 -14
- package/src/mcp/tools.ts +42 -22
- package/src/md-modules.d.ts +4 -0
- package/src/prompts/AGENTS.md +41 -0
- package/src/prompts/agents/AGENTS.md +35 -0
- package/src/prompts/agents/architect.md +35 -0
- package/src/prompts/agents/critic.md +37 -0
- package/src/prompts/agents/executor.md +36 -0
- package/src/prompts/agents/planner.md +37 -0
- package/src/prompts/skills/AGENTS.md +36 -0
- package/src/prompts/skills/deep-dive/AGENTS.md +31 -0
- package/src/prompts/skills/deep-dive/SKILL.md +13 -0
- package/src/prompts/skills/deep-interview/AGENTS.md +31 -0
- package/src/prompts/skills/deep-interview/SKILL.md +12 -0
- package/src/prompts/skills/gjc/AGENTS.md +31 -0
- package/src/prompts/skills/gjc/SKILL.md +15 -0
- package/src/prompts/skills/ralplan/AGENTS.md +31 -0
- package/src/prompts/skills/ralplan/SKILL.md +11 -0
- package/src/prompts/skills/team/AGENTS.md +31 -0
- package/src/prompts/skills/team/SKILL.md +11 -0
- package/src/prompts/skills/ultragoal/AGENTS.md +31 -0
- package/src/prompts/skills/ultragoal/SKILL.md +11 -0
- package/src/skills/AGENTS.md +38 -0
- package/src/skills/catalog.ts +565 -31
- package/src/tui/AGENTS.md +43 -0
- package/src/tui/app.ts +1181 -92
- package/src/tui/components/AGENTS.md +42 -0
- package/src/tui/components/ascii-art.ts +257 -15
- package/src/tui/components/autocomplete.ts +98 -16
- package/src/tui/components/autopilot-status.ts +65 -0
- package/src/tui/components/category-index.ts +49 -0
- package/src/tui/components/code-view.ts +54 -11
- package/src/tui/components/color.ts +171 -2
- package/src/tui/components/config-panel.ts +82 -15
- package/src/tui/components/duration.ts +38 -0
- package/src/tui/components/evolution.ts +3 -3
- package/src/tui/components/footer.ts +91 -42
- package/src/tui/components/forge.ts +426 -31
- package/src/tui/components/hints.ts +54 -0
- package/src/tui/components/hud.ts +73 -0
- package/src/tui/components/index.ts +4 -0
- package/src/tui/components/input-box.ts +150 -0
- package/src/tui/components/layout.ts +11 -3
- package/src/tui/components/live-model-picker.ts +108 -0
- package/src/tui/components/markdown-table.ts +140 -0
- package/src/tui/components/markdown-text.ts +97 -0
- package/src/tui/components/meter.ts +4 -1
- package/src/tui/components/model-picker.ts +3 -2
- package/src/tui/components/provider-picker.ts +3 -2
- package/src/tui/components/section.ts +70 -0
- package/src/tui/components/select-list.ts +40 -10
- package/src/tui/components/skill-picker.ts +25 -0
- package/src/tui/components/slash.ts +244 -21
- package/src/tui/components/status.ts +272 -11
- package/src/tui/components/step-timeline.ts +218 -0
- package/src/tui/components/stream.ts +26 -9
- package/src/tui/components/themes.ts +212 -6
- package/src/tui/components/todo-card.ts +47 -0
- package/src/tui/components/tool-list.ts +58 -12
- package/src/tui/components/transcript.ts +120 -0
- package/src/tui/components/update-box.ts +31 -0
- package/src/tui/components/welcome.ts +162 -0
- package/src/tui/components/width.ts +163 -0
- package/src/tui/monitoring/AGENTS.md +31 -0
- package/src/tui/monitoring/hud-view.ts +55 -0
- package/src/tui/renderer.ts +112 -3
- package/src/tui/terminal.ts +40 -33
- package/src/util/AGENTS.md +39 -0
- package/src/util/clipboard-image.ts +118 -0
- package/src/util/env.ts +12 -0
- package/src/util/provider-error.ts +78 -0
- package/src/util/retry.ts +91 -6
- package/src/util/update-check.ts +64 -0
- package/src/commands/models.ts +0 -104
|
@@ -7,9 +7,13 @@
|
|
|
7
7
|
* layout — only the look. `cosmic` is the default; `mono` is the colorless
|
|
8
8
|
* fallback for plain terminals.
|
|
9
9
|
*/
|
|
10
|
+
import chalk from "chalk";
|
|
10
11
|
import { EVOLUTION_STAGE_GRADIENTS, EVOLUTION_STAGE_COUNT, type StageGradient } from "./evolution";
|
|
11
12
|
import { clampStageIndex } from "./evolution";
|
|
12
|
-
import type
|
|
13
|
+
import { detectColorLevel, ColorLevel, detectAppearance, type EnvLike } from "./color";
|
|
14
|
+
import * as fs from "node:fs";
|
|
15
|
+
import * as path from "node:path";
|
|
16
|
+
import * as os from "node:os";
|
|
13
17
|
|
|
14
18
|
export interface EvolutionTheme {
|
|
15
19
|
name: string;
|
|
@@ -18,13 +22,35 @@ export interface EvolutionTheme {
|
|
|
18
22
|
gradients: readonly StageGradient[];
|
|
19
23
|
/** Whether the theme emits color at all (`mono` = false → plain output). */
|
|
20
24
|
color: boolean;
|
|
25
|
+
/** Accent hex for UI chrome — borders, prompt mark, model status bar (gjc-style). */
|
|
26
|
+
accent: string;
|
|
27
|
+
/** Shadow hex for the "shaded" box edges (bottom/right). A REAL darker hue —
|
|
28
|
+
* not ANSI dim — so the lit/shaded two-tone reads as depth even on terminals
|
|
29
|
+
* that render `dim` poorly. Falls back to dim(accent) when unset. */
|
|
30
|
+
accentShadow?: string;
|
|
31
|
+
/** Diff palette: themed +/- contrast for /diff, edit cards, and code views.
|
|
32
|
+
* `addBg`/`delBg` are full-row background tints that give added/removed
|
|
33
|
+
* lines block-level separation, not just a colored sign. */
|
|
34
|
+
diff?: { add: string; del: string; addBg: string; delBg: string; hunk: string };
|
|
21
35
|
}
|
|
22
36
|
|
|
37
|
+
/** Default diff palette (used when a theme defines none): high-contrast
|
|
38
|
+
* green/red foregrounds over deep complementary background tints. */
|
|
39
|
+
export const DEFAULT_DIFF_PALETTE = {
|
|
40
|
+
add: "#9ece6a",
|
|
41
|
+
del: "#f7768e",
|
|
42
|
+
addBg: "#16261c",
|
|
43
|
+
delBg: "#2a1a20",
|
|
44
|
+
hunk: "#7dcfff",
|
|
45
|
+
} as const;
|
|
46
|
+
|
|
23
47
|
const COSMIC: EvolutionTheme = {
|
|
24
48
|
name: "cosmic",
|
|
25
49
|
description: "Default — deep-space arc from cyan tide to white-hot singularity.",
|
|
26
50
|
gradients: EVOLUTION_STAGE_GRADIENTS,
|
|
27
51
|
color: true,
|
|
52
|
+
accent: "#48dbfb",
|
|
53
|
+
accentShadow: "#1b6f8c",
|
|
28
54
|
};
|
|
29
55
|
|
|
30
56
|
const MATRIX: EvolutionTheme = {
|
|
@@ -38,6 +64,9 @@ const MATRIX: EvolutionTheme = {
|
|
|
38
64
|
{ from: "#00ff41", to: "#ccffcc" },
|
|
39
65
|
],
|
|
40
66
|
color: true,
|
|
67
|
+
accent: "#39ff14",
|
|
68
|
+
accentShadow: "#0b6623",
|
|
69
|
+
diff: { add: "#7fff00", del: "#ff5f5f", addBg: "#0c2410", delBg: "#2a1212", hunk: "#00e5a0" },
|
|
41
70
|
};
|
|
42
71
|
|
|
43
72
|
const SOLAR: EvolutionTheme = {
|
|
@@ -51,6 +80,87 @@ const SOLAR: EvolutionTheme = {
|
|
|
51
80
|
{ from: "#ff8c00", to: "#fff5cc" },
|
|
52
81
|
],
|
|
53
82
|
color: true,
|
|
83
|
+
accent: "#ff8c00",
|
|
84
|
+
accentShadow: "#8a4500",
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const RED_CLAW: EvolutionTheme = {
|
|
88
|
+
name: "red-claw",
|
|
89
|
+
description: "Dark theme — crimson claw glowing from deep embers.",
|
|
90
|
+
gradients: [
|
|
91
|
+
{ from: "#2b0000", to: "#4a0000" },
|
|
92
|
+
{ from: "#4a0000", to: "#8b0000" },
|
|
93
|
+
{ from: "#8b0000", to: "#b22222" },
|
|
94
|
+
{ from: "#b22222", to: "#dc143c" },
|
|
95
|
+
{ from: "#dc143c", to: "#ff0000" },
|
|
96
|
+
],
|
|
97
|
+
color: true,
|
|
98
|
+
accent: "#e25656",
|
|
99
|
+
accentShadow: "#5c0f0f",
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const BLUE_CRAB: EvolutionTheme = {
|
|
103
|
+
name: "blue-crab",
|
|
104
|
+
description: "Bioluminescent ocean — abyssal navy through reef teal to seafoam glow (light-background friendly).",
|
|
105
|
+
gradients: [
|
|
106
|
+
{ from: "#03045e", to: "#0077b6" },
|
|
107
|
+
{ from: "#0077b6", to: "#00b4d8" },
|
|
108
|
+
{ from: "#00b4d8", to: "#06d6a0" },
|
|
109
|
+
{ from: "#0096c7", to: "#48cae4" },
|
|
110
|
+
{ from: "#48cae4", to: "#caf0f8" },
|
|
111
|
+
],
|
|
112
|
+
color: true,
|
|
113
|
+
accent: "#0096c7",
|
|
114
|
+
accentShadow: "#023e8a",
|
|
115
|
+
diff: { add: "#06d6a0", del: "#ef476f", addBg: "#0a2922", delBg: "#2b1320", hunk: "#48cae4" },
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const AURORA: EvolutionTheme = {
|
|
119
|
+
name: "aurora",
|
|
120
|
+
description: "Northern lights — arctic teal ribbons bending into violet sky.",
|
|
121
|
+
gradients: [
|
|
122
|
+
{ from: "#0b1f3a", to: "#16c79a" },
|
|
123
|
+
{ from: "#16c79a", to: "#3ddad7" },
|
|
124
|
+
{ from: "#3ddad7", to: "#7c83fd" },
|
|
125
|
+
{ from: "#7c83fd", to: "#b388eb" },
|
|
126
|
+
{ from: "#b388eb", to: "#e7f9f3" },
|
|
127
|
+
],
|
|
128
|
+
color: true,
|
|
129
|
+
accent: "#3ddad7",
|
|
130
|
+
accentShadow: "#1d5c8f",
|
|
131
|
+
diff: { add: "#16c79a", del: "#fd7c9b", addBg: "#0c2620", delBg: "#2a1626", hunk: "#7c83fd" },
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const SYNTHWAVE: EvolutionTheme = {
|
|
135
|
+
name: "synthwave",
|
|
136
|
+
description: "Retro neon — sunset-grid magenta pulsing against electric cyan.",
|
|
137
|
+
gradients: [
|
|
138
|
+
{ from: "#2b1055", to: "#7303c0" },
|
|
139
|
+
{ from: "#7303c0", to: "#ec38bc" },
|
|
140
|
+
{ from: "#ec38bc", to: "#ff5e99" },
|
|
141
|
+
{ from: "#ff5e99", to: "#03e9f4" },
|
|
142
|
+
{ from: "#03e9f4", to: "#fdeff9" },
|
|
143
|
+
],
|
|
144
|
+
color: true,
|
|
145
|
+
accent: "#ec38bc",
|
|
146
|
+
accentShadow: "#5b1a8a",
|
|
147
|
+
diff: { add: "#03e9f4", del: "#ff5e99", addBg: "#0a2330", delBg: "#33122a", hunk: "#b388eb" },
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const SAKURA: EvolutionTheme = {
|
|
151
|
+
name: "sakura",
|
|
152
|
+
description: "Cherry blossom — soft petal pink deepening to spring magenta (light-background friendly).",
|
|
153
|
+
gradients: [
|
|
154
|
+
{ from: "#5c2a3d", to: "#b85c79" },
|
|
155
|
+
{ from: "#b85c79", to: "#d6336c" },
|
|
156
|
+
{ from: "#d6336c", to: "#f06595" },
|
|
157
|
+
{ from: "#f06595", to: "#faa2c1" },
|
|
158
|
+
{ from: "#faa2c1", to: "#fff0f6" },
|
|
159
|
+
],
|
|
160
|
+
color: true,
|
|
161
|
+
accent: "#d6336c",
|
|
162
|
+
accentShadow: "#862e59",
|
|
163
|
+
diff: { add: "#37b24d", del: "#e03131", addBg: "#13260f", delBg: "#2b1212", hunk: "#cc5de8" },
|
|
54
164
|
};
|
|
55
165
|
|
|
56
166
|
const MONO: EvolutionTheme = {
|
|
@@ -58,9 +168,10 @@ const MONO: EvolutionTheme = {
|
|
|
58
168
|
description: "Colorless — plain text for NO_COLOR / minimal terminals.",
|
|
59
169
|
gradients: EVOLUTION_STAGE_GRADIENTS,
|
|
60
170
|
color: false,
|
|
171
|
+
accent: "#ffffff",
|
|
61
172
|
};
|
|
62
173
|
|
|
63
|
-
export const THEMES: readonly EvolutionTheme[] = [COSMIC, MATRIX, SOLAR, MONO];
|
|
174
|
+
export const THEMES: readonly EvolutionTheme[] = [COSMIC, MATRIX, SOLAR, RED_CLAW, BLUE_CRAB, AURORA, SYNTHWAVE, SAKURA, MONO];
|
|
64
175
|
|
|
65
176
|
/** Look up a theme by name (case-insensitive); unknown names fall back to cosmic. */
|
|
66
177
|
export function getTheme(name: string | undefined): EvolutionTheme {
|
|
@@ -69,14 +180,63 @@ export function getTheme(name: string | undefined): EvolutionTheme {
|
|
|
69
180
|
return THEMES.find(t => t.name === lc) ?? COSMIC;
|
|
70
181
|
}
|
|
71
182
|
|
|
72
|
-
/** Theme names + descriptions for `
|
|
183
|
+
/** Theme names + descriptions for `jeo evolve --list-themes`. */
|
|
73
184
|
export function listThemes(): { name: string; description: string }[] {
|
|
74
185
|
return THEMES.map(t => ({ name: t.name, description: t.description }));
|
|
75
186
|
}
|
|
76
187
|
|
|
77
|
-
/**
|
|
78
|
-
|
|
79
|
-
|
|
188
|
+
/** Process-lifetime memo: this runs on the keystroke-hot path (theme resolution
|
|
189
|
+
* inside the input-box repaint), and an UNCACHED call does a synchronous
|
|
190
|
+
* existsSync + readFileSync + JSON.parse of ~/.jeo/config.json per keystroke.
|
|
191
|
+
* Env (`JEO_TUI_THEME`) takes precedence over this value in `resolveTheme`, so
|
|
192
|
+
* `/theme` switches stay live; external config edits apply on the next run. */
|
|
193
|
+
let configThemeCache: { dir: string; value: string | undefined } | undefined;
|
|
194
|
+
|
|
195
|
+
/** Test-only: clear the config-theme memo. */
|
|
196
|
+
export function resetThemeConfigCache(): void {
|
|
197
|
+
configThemeCache = undefined;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function getExplicitThemeFromConfig(env: EnvLike): string | undefined {
|
|
201
|
+
const home = os.homedir ? os.homedir() : undefined;
|
|
202
|
+
const dir = env.JEO_CONFIG_DIR || env.JEO_CONFIG_DIR || (home ? path.join(home, ".jeo") : undefined);
|
|
203
|
+
if (!dir) return undefined;
|
|
204
|
+
if (configThemeCache && configThemeCache.dir === dir) return configThemeCache.value;
|
|
205
|
+
const filePath = path.join(dir, "config.json");
|
|
206
|
+
let value: string | undefined;
|
|
207
|
+
try {
|
|
208
|
+
if (fs.existsSync(filePath)) {
|
|
209
|
+
const data = fs.readFileSync(filePath, "utf-8");
|
|
210
|
+
const config = JSON.parse(data);
|
|
211
|
+
value = config.theme || config.tuiTheme || config.tui?.theme;
|
|
212
|
+
}
|
|
213
|
+
} catch {
|
|
214
|
+
// ignore — treated as "no configured theme"
|
|
215
|
+
}
|
|
216
|
+
configThemeCache = { dir, value };
|
|
217
|
+
return value;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Resolve the active theme from the environment (`JEO_TUI_THEME`) or config, default cosmic. */
|
|
221
|
+
export function resolveTheme(
|
|
222
|
+
env: EnvLike = process.env,
|
|
223
|
+
config?: { theme?: string; tuiTheme?: string; tui?: { theme?: string } }
|
|
224
|
+
): EvolutionTheme {
|
|
225
|
+
const explicit = env.JEO_TUI_THEME || config?.theme || config?.tuiTheme || config?.tui?.theme || getExplicitThemeFromConfig(env);
|
|
226
|
+
if (explicit) {
|
|
227
|
+
return getTheme(explicit);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (detectColorLevel(env) === ColorLevel.None) {
|
|
231
|
+
return getTheme("mono");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const appearance = detectAppearance(env);
|
|
235
|
+
if (appearance === "light") {
|
|
236
|
+
return getTheme("blue-crab");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return getTheme("cosmic");
|
|
80
240
|
}
|
|
81
241
|
|
|
82
242
|
/** The gradient palette for a stage index under a theme (clamped). */
|
|
@@ -84,3 +244,49 @@ export function themeGradient(theme: EvolutionTheme, index: number): StageGradie
|
|
|
84
244
|
const i = clampStageIndex(index);
|
|
85
245
|
return theme.gradients[i] ?? theme.gradients[EVOLUTION_STAGE_COUNT - 1]!;
|
|
86
246
|
}
|
|
247
|
+
|
|
248
|
+
/** A chalk-style painter for the theme's accent color; identity when the theme is colorless. */
|
|
249
|
+
export function accentPaint(theme: EvolutionTheme): (s: string) => string {
|
|
250
|
+
if (!theme.color) return (s: string) => s;
|
|
251
|
+
const hex = theme.accent;
|
|
252
|
+
return (s: string) => chalk.hex(hex)(s);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** The shaded-edge painter for box bottoms/rights. Pairs with `accentPaint` on
|
|
256
|
+
* the lit top/left edges — the two-tone contrast gives every bordered panel a
|
|
257
|
+
* pseudo-3D depth cue. Themes with an explicit `accentShadow` get a REAL
|
|
258
|
+
* darker hue (richer separation than ANSI dim); others fall back to
|
|
259
|
+
* dim(accent). Identity when the theme is colorless. */
|
|
260
|
+
export function accentShadowPaint(theme: EvolutionTheme): (s: string) => string {
|
|
261
|
+
if (!theme.color) return (s: string) => s;
|
|
262
|
+
if (theme.accentShadow) {
|
|
263
|
+
const hex = theme.accentShadow;
|
|
264
|
+
return (s: string) => chalk.hex(hex)(s);
|
|
265
|
+
}
|
|
266
|
+
const hex = theme.accent;
|
|
267
|
+
return (s: string) => chalk.dim(chalk.hex(hex)(s));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** Themed diff painters: foreground + full-row background tints for added /
|
|
271
|
+
* removed lines (block-level separation, not just a colored sign) and a
|
|
272
|
+
* distinct hunk-header color. Identity painters when the theme is colorless. */
|
|
273
|
+
export function diffPaint(theme: EvolutionTheme): {
|
|
274
|
+
add: (s: string) => string;
|
|
275
|
+
del: (s: string) => string;
|
|
276
|
+
hunk: (s: string) => string;
|
|
277
|
+
addHead: (s: string) => string;
|
|
278
|
+
delHead: (s: string) => string;
|
|
279
|
+
} {
|
|
280
|
+
if (!theme.color) {
|
|
281
|
+
const id = (s: string) => s;
|
|
282
|
+
return { add: id, del: id, hunk: id, addHead: id, delHead: id };
|
|
283
|
+
}
|
|
284
|
+
const p = theme.diff ?? DEFAULT_DIFF_PALETTE;
|
|
285
|
+
return {
|
|
286
|
+
add: (s: string) => chalk.bgHex(p.addBg).hex(p.add)(s),
|
|
287
|
+
del: (s: string) => chalk.bgHex(p.delBg).hex(p.del)(s),
|
|
288
|
+
hunk: (s: string) => chalk.hex(p.hunk).bold(s),
|
|
289
|
+
addHead: (s: string) => chalk.hex(p.add).bold(s),
|
|
290
|
+
delHead: (s: string) => chalk.hex(p.del).bold(s),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
|
|
3
|
+
export interface TodoCardItem {
|
|
4
|
+
title: string;
|
|
5
|
+
status: "pending" | "in_progress" | "done";
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface TodoCardOptions {
|
|
9
|
+
unicode?: boolean;
|
|
10
|
+
color?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* jeo-ref "Todo Write" scrollback card: a ✓-led header with a tree-connector
|
|
15
|
+
* checklist — done items get ☑ + strikethrough, the active item highlights,
|
|
16
|
+
* pending stays dim. Pure `string[]`; the caller flushes it into the ledger so
|
|
17
|
+
* the plan's evolution (items checking off turn by turn) reads as transcript
|
|
18
|
+
* history, exactly like the reference TUI.
|
|
19
|
+
*/
|
|
20
|
+
export function formatTodoWriteCard(items: TodoCardItem[], opts: TodoCardOptions = {}): string[] {
|
|
21
|
+
if (items.length === 0) return [];
|
|
22
|
+
const unicode = opts.unicode !== false;
|
|
23
|
+
const color = opts.color !== false;
|
|
24
|
+
const check = unicode ? "✓" : "v";
|
|
25
|
+
const boxDone = unicode ? "☑" : "[x]";
|
|
26
|
+
const boxOpen = unicode ? "☐" : "[ ]";
|
|
27
|
+
const tee = unicode ? "├─" : "|-";
|
|
28
|
+
const ell = unicode ? "└─" : "`-";
|
|
29
|
+
const count = `${items.length} task${items.length === 1 ? "" : "s"}`;
|
|
30
|
+
const head = color
|
|
31
|
+
? `${chalk.green(check)} ${chalk.bold("Todo Write")} ${chalk.dim(count)}`
|
|
32
|
+
: `${check} Todo Write ${count}`;
|
|
33
|
+
const lines = [head];
|
|
34
|
+
items.forEach((item, i) => {
|
|
35
|
+
const conn = i === items.length - 1 ? ell : tee;
|
|
36
|
+
if (item.status === "done") {
|
|
37
|
+
const box = color ? chalk.green(boxDone) : boxDone;
|
|
38
|
+
const label = color ? chalk.dim.strikethrough(item.title) : item.title;
|
|
39
|
+
lines.push(` ${conn} ${box} ${label}`);
|
|
40
|
+
} else if (item.status === "in_progress") {
|
|
41
|
+
lines.push(` ${conn} ${boxOpen} ${color ? chalk.cyan.bold(item.title) : item.title}`);
|
|
42
|
+
} else {
|
|
43
|
+
lines.push(` ${conn} ${boxOpen} ${color ? chalk.dim(item.title) : item.title}`);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
return lines;
|
|
47
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
+
import { categoryBadge, categoryForTool } from "./category-index";
|
|
2
3
|
|
|
3
4
|
export type ToolStatus = "running" | "ok" | "fail";
|
|
4
5
|
|
|
@@ -16,52 +17,97 @@ export interface ToolStats {
|
|
|
16
17
|
|
|
17
18
|
export class ToolList {
|
|
18
19
|
private rows: ToolRow[] = [];
|
|
20
|
+
// Rows trimmed from the front to keep memory + per-frame cost flat on a
|
|
21
|
+
// pathologically long turn. `start()` returns an ABSOLUTE index so `finish()`
|
|
22
|
+
// stays valid across trims (a no-op in normal turns, which stay well under cap).
|
|
23
|
+
private dropped = 0;
|
|
24
|
+
private readonly cap: number;
|
|
25
|
+
|
|
26
|
+
constructor(cap = 500) {
|
|
27
|
+
this.cap = Math.max(1, cap);
|
|
28
|
+
}
|
|
19
29
|
|
|
20
30
|
start(tool: string): number {
|
|
21
31
|
this.rows.push({ tool, status: "running" });
|
|
22
|
-
|
|
32
|
+
const absIndex = this.dropped + this.rows.length - 1;
|
|
33
|
+
if (this.rows.length > this.cap) {
|
|
34
|
+
const drop = this.rows.length - this.cap;
|
|
35
|
+
this.rows.splice(0, drop);
|
|
36
|
+
this.dropped += drop;
|
|
37
|
+
}
|
|
38
|
+
return absIndex;
|
|
23
39
|
}
|
|
24
40
|
|
|
25
41
|
finish(index: number, ok: boolean): void {
|
|
26
|
-
|
|
27
|
-
|
|
42
|
+
const i = index - this.dropped;
|
|
43
|
+
if (i >= 0 && this.rows[i]) {
|
|
44
|
+
this.rows[i].status = ok ? "ok" : "fail";
|
|
28
45
|
}
|
|
29
46
|
}
|
|
30
47
|
|
|
31
|
-
render(maxRows?: number): string[] {
|
|
48
|
+
render(maxRows?: number, optionsOrColor?: boolean | { color?: boolean; indexed?: boolean }): string[] {
|
|
49
|
+
let color = true;
|
|
50
|
+
let indexed = false;
|
|
51
|
+
if (typeof optionsOrColor === "boolean") {
|
|
52
|
+
color = optionsOrColor;
|
|
53
|
+
} else if (optionsOrColor && typeof optionsOrColor === "object") {
|
|
54
|
+
if (optionsOrColor.color !== undefined) {
|
|
55
|
+
color = optionsOrColor.color;
|
|
56
|
+
}
|
|
57
|
+
indexed = optionsOrColor.indexed === true;
|
|
58
|
+
}
|
|
59
|
+
|
|
32
60
|
const rows =
|
|
33
61
|
maxRows !== undefined && maxRows > 0 && this.rows.length > maxRows
|
|
34
62
|
? this.rows.slice(this.rows.length - (maxRows - 1)) // keep the most recent rows
|
|
35
63
|
: this.rows;
|
|
36
|
-
const hidden = this.rows.length - rows.length;
|
|
37
|
-
|
|
64
|
+
const hidden = this.dropped + (this.rows.length - rows.length);
|
|
65
|
+
|
|
66
|
+
const cyanBullet = color ? chalk.cyan("◓") : "◓";
|
|
67
|
+
const cyanRunning = color ? chalk.cyan.bold("running...") : "running...";
|
|
68
|
+
const greenCheck = color ? chalk.green("✔") : "✔";
|
|
69
|
+
const redCross = color ? chalk.red("✖") : "✖";
|
|
70
|
+
const redFailed = color ? chalk.red.bold("FAILED") : "FAILED";
|
|
71
|
+
const grayLine = (s: string) => color ? chalk.gray(s) : s;
|
|
72
|
+
|
|
73
|
+
const lines = rows.map((row, i) => {
|
|
74
|
+
const badge = indexed ? `${categoryBadge(categoryForTool(row.tool), { index: hidden + i + 1, color })} ` : "";
|
|
38
75
|
if (row.status === "running") {
|
|
39
|
-
return ` ${
|
|
76
|
+
return ` ${cyanBullet} ${badge}${row.tool} ${cyanRunning}`;
|
|
40
77
|
} else if (row.status === "ok") {
|
|
41
78
|
// Faded decay for completed successful tools
|
|
42
|
-
return
|
|
79
|
+
return ` ${greenCheck} ${badge}${grayLine(row.tool + " ok")}`;
|
|
43
80
|
} else {
|
|
44
81
|
// Bright red for failures
|
|
45
|
-
return ` ${
|
|
82
|
+
return ` ${redCross} ${badge}${row.tool} ${redFailed}`;
|
|
46
83
|
}
|
|
47
84
|
});
|
|
48
85
|
if (hidden > 0) {
|
|
49
|
-
lines.unshift(
|
|
86
|
+
lines.unshift(grayLine(` · (+${hidden} earlier)`));
|
|
50
87
|
}
|
|
51
88
|
return lines;
|
|
52
89
|
}
|
|
53
90
|
|
|
54
91
|
currentTool(): string | undefined {
|
|
55
|
-
|
|
92
|
+
for (let i = this.rows.length - 1; i >= 0; i--) {
|
|
93
|
+
if (this.rows[i]!.status === "running") return this.rows[i]!.tool;
|
|
94
|
+
}
|
|
95
|
+
return undefined;
|
|
56
96
|
}
|
|
57
97
|
|
|
58
98
|
stats(): ToolStats {
|
|
59
|
-
const stats: ToolStats = { running: 0, ok: 0, fail: 0, total: this.rows.length };
|
|
99
|
+
const stats: ToolStats = { running: 0, ok: 0, fail: 0, total: this.dropped + this.rows.length };
|
|
60
100
|
for (const row of this.rows) stats[row.status]++;
|
|
61
101
|
return stats;
|
|
62
102
|
}
|
|
63
103
|
|
|
64
104
|
reset(): void {
|
|
65
105
|
this.rows = [];
|
|
106
|
+
this.dropped = 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Immutable snapshot of the tool rows (for the step timeline / summaries). */
|
|
110
|
+
snapshot(): { tool: string; status: ToolStatus }[] {
|
|
111
|
+
return this.rows.map(r => ({ tool: r.tool, status: r.status }));
|
|
66
112
|
}
|
|
67
113
|
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-app turn-history viewer (`/history`) — re-prints the worked session as a
|
|
3
|
+
* readable transcript INTO normal scrollback, so past prompts, tool steps and
|
|
4
|
+
* replies can be reviewed by scrolling up even when the terminal's own history
|
|
5
|
+
* (tmux without mouse mode, cleared screens, resumed sessions) can't reach them.
|
|
6
|
+
*
|
|
7
|
+
* The engine history interleaves protocol traffic with the conversation:
|
|
8
|
+
* user → real prompts, but ALSO tool-result feedback
|
|
9
|
+
* (`Tool [name] result (ok|fail):\n…`) and parse-correction bounces
|
|
10
|
+
* assistant → raw JSON tool calls, or plain prose (final/legacy replies)
|
|
11
|
+
* This formatter folds that back into a gjc-style ledger: `user ▸` prompt blocks,
|
|
12
|
+
* one compact `✔/✗ title` line per tool step, and `jeo ◂` reply blocks.
|
|
13
|
+
*/
|
|
14
|
+
import chalk from "chalk";
|
|
15
|
+
import type { Message } from "../../ai/types";
|
|
16
|
+
import { summarizeForgeInvocation } from "./forge";
|
|
17
|
+
|
|
18
|
+
export interface TranscriptOptions {
|
|
19
|
+
color?: boolean;
|
|
20
|
+
unicode?: boolean;
|
|
21
|
+
/** Keep only the LAST n prompt-anchored turns (Infinity/undefined = all). */
|
|
22
|
+
maxTurns?: number;
|
|
23
|
+
/** Cap body lines per prompt/reply block. */
|
|
24
|
+
bodyLines?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const TOOL_RESULT_RE = /^Tool \[([^\]]+)\] result \((ok|fail)\):/;
|
|
28
|
+
const BOUNCE_PREFIXES = ["Your last reply", "The budget for this turn is exhausted", "The step budget for this turn is exhausted", "You are cycling through the same"];
|
|
29
|
+
|
|
30
|
+
function clipBody(text: string, cap: number): string[] {
|
|
31
|
+
const rows = text.replace(/\r/g, "").split("\n");
|
|
32
|
+
const trimmed = rows.length > cap ? rows.slice(0, cap) : rows;
|
|
33
|
+
const out = trimmed.map(r => ` ${r}`);
|
|
34
|
+
if (rows.length > cap) out.push(` … (+${rows.length - cap} more lines)`);
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function firstToolResultLine(text: string | undefined): string {
|
|
39
|
+
if (!text) return "";
|
|
40
|
+
return text
|
|
41
|
+
.replace(TOOL_RESULT_RE, "")
|
|
42
|
+
.split("\n")
|
|
43
|
+
.map(l => l.trim())
|
|
44
|
+
.find(Boolean)
|
|
45
|
+
?.replace(/\s+/g, " ")
|
|
46
|
+
.slice(0, 96) ?? "";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Format engine history as a scrollback-friendly transcript. */
|
|
50
|
+
export function formatTranscript(messages: readonly Message[], opts: TranscriptOptions = {}): string[] {
|
|
51
|
+
const color = opts.color !== false;
|
|
52
|
+
const unicode = opts.unicode !== false;
|
|
53
|
+
const bodyCap = Math.max(1, opts.bodyLines ?? 8);
|
|
54
|
+
const ok = unicode ? "✔" : "v";
|
|
55
|
+
const bad = unicode ? "✗" : "x";
|
|
56
|
+
const userMark = unicode ? "▸" : ">";
|
|
57
|
+
const jeoMark = unicode ? "◂" : "<";
|
|
58
|
+
const cyanBold = color ? chalk.cyan.bold : (s: string) => s;
|
|
59
|
+
const magentaBold = color ? chalk.magenta.bold : (s: string) => s;
|
|
60
|
+
const dim = color ? chalk.dim : (s: string) => s;
|
|
61
|
+
const green = color ? chalk.green : (s: string) => s;
|
|
62
|
+
const red = color ? chalk.red : (s: string) => s;
|
|
63
|
+
|
|
64
|
+
// Anchor turns on real user prompts so `maxTurns` slices whole exchanges.
|
|
65
|
+
const promptIdx: number[] = [];
|
|
66
|
+
messages.forEach((m, i) => {
|
|
67
|
+
if (m.role !== "user") return;
|
|
68
|
+
if (TOOL_RESULT_RE.test(m.content)) return;
|
|
69
|
+
if (BOUNCE_PREFIXES.some(p => m.content.startsWith(p))) return;
|
|
70
|
+
promptIdx.push(i);
|
|
71
|
+
});
|
|
72
|
+
const promptNumber = new Map(promptIdx.map((idx, i) => [idx, i + 1]));
|
|
73
|
+
const totalTurns = promptIdx.length;
|
|
74
|
+
if (totalTurns === 0) return [dim("(no worked history yet — ask something first)")];
|
|
75
|
+
const keep = Math.max(1, Math.min(totalTurns, opts.maxTurns ?? totalTurns));
|
|
76
|
+
const start = promptIdx[totalTurns - keep]!;
|
|
77
|
+
|
|
78
|
+
const lines: string[] = [];
|
|
79
|
+
if (keep < totalTurns) lines.push(dim(`… ${totalTurns - keep} earlier turn(s) hidden — /history all shows everything`));
|
|
80
|
+
for (let i = start; i < messages.length; i++) {
|
|
81
|
+
const m = messages[i]!;
|
|
82
|
+
if (m.role === "system") continue;
|
|
83
|
+
if (m.role === "user") {
|
|
84
|
+
const result = m.content.match(TOOL_RESULT_RE);
|
|
85
|
+
if (result) continue; // outcome is folded into the assistant tool line below
|
|
86
|
+
if (BOUNCE_PREFIXES.some(p => m.content.startsWith(p))) continue; // protocol noise
|
|
87
|
+
if (lines.length) lines.push("");
|
|
88
|
+
const turnNo = promptNumber.get(i) ?? 1;
|
|
89
|
+
lines.push(dim(`${unicode ? "─" : "-"} turn ${turnNo}/${totalTurns}`));
|
|
90
|
+
const imgs = m.images?.length ? dim(` ⧉ ${m.images.length} image(s)`) : "";
|
|
91
|
+
lines.push(`${cyanBold(`user ${userMark}`)}${imgs}`);
|
|
92
|
+
lines.push(...clipBody(m.content, bodyCap));
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
// assistant: a JSON tool call (one compact ledger line) or a prose reply.
|
|
96
|
+
let invocation: { tool?: unknown; arguments?: unknown } | null = null;
|
|
97
|
+
try {
|
|
98
|
+
const parsed = JSON.parse(m.content) as { tool?: unknown; arguments?: unknown };
|
|
99
|
+
if (parsed && typeof parsed === "object" && typeof parsed.tool === "string") invocation = parsed;
|
|
100
|
+
} catch { /* prose reply */ }
|
|
101
|
+
if (invocation && typeof invocation.tool === "string" && invocation.tool !== "done") {
|
|
102
|
+
// The matching `Tool [x] result (ok|fail)` user message tells success/failure.
|
|
103
|
+
const next = messages[i + 1];
|
|
104
|
+
const verdict = next?.role === "user" ? next.content.match(TOOL_RESULT_RE) : null;
|
|
105
|
+
const mark = verdict?.[2] === "fail" ? red(bad) : green(ok);
|
|
106
|
+
const title = summarizeForgeInvocation(invocation.tool, invocation.arguments).title;
|
|
107
|
+
const resultLine = firstToolResultLine(next?.content);
|
|
108
|
+
const suffix = resultLine ? dim(` — ${resultLine}`) : "";
|
|
109
|
+
lines.push(` ${mark} ${title}${suffix}`);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const reason = invocation
|
|
113
|
+
? String((invocation.arguments as { reason?: unknown } | undefined)?.reason ?? "")
|
|
114
|
+
: m.content;
|
|
115
|
+
if (!reason.trim()) continue;
|
|
116
|
+
lines.push(`${magentaBold(`jeo ${jeoMark}`)}`);
|
|
117
|
+
lines.push(...clipBody(reason.trim(), bodyCap));
|
|
118
|
+
}
|
|
119
|
+
return lines;
|
|
120
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { padLineTo } from "./layout";
|
|
3
|
+
import { truncate } from "../terminal";
|
|
4
|
+
|
|
5
|
+
export function renderUpdateBox(
|
|
6
|
+
current: string,
|
|
7
|
+
latest: string,
|
|
8
|
+
opts?: { cols?: number; unicode?: boolean; color?: boolean }
|
|
9
|
+
): string[] {
|
|
10
|
+
void current;
|
|
11
|
+
|
|
12
|
+
const cols = opts?.cols ?? 80;
|
|
13
|
+
const useColor = opts?.color !== false;
|
|
14
|
+
const useUnicode = opts?.unicode !== false;
|
|
15
|
+
const width = Math.max(24, Math.min(120, cols));
|
|
16
|
+
|
|
17
|
+
const ruleChar = useUnicode ? "─" : "-";
|
|
18
|
+
const paintRule = useColor ? chalk.hex("#f2b84b") : (s: string) => s;
|
|
19
|
+
const paintTitle = useColor ? chalk.hex("#f2b84b").bold : (s: string) => s;
|
|
20
|
+
const paintCommand = useColor ? chalk.hex("#ff6b4a") : (s: string) => s;
|
|
21
|
+
|
|
22
|
+
const rule = paintRule(ruleChar.repeat(width));
|
|
23
|
+
const fit = (line: string) => padLineTo(truncate(line, width), width, "left");
|
|
24
|
+
|
|
25
|
+
return [
|
|
26
|
+
rule,
|
|
27
|
+
fit(paintTitle("Update Available")),
|
|
28
|
+
fit(`New version ${latest} is available. Run: ${paintCommand("jeo update")}`),
|
|
29
|
+
rule,
|
|
30
|
+
];
|
|
31
|
+
}
|