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.
- 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 +804 -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 +562 -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
package/src/cli/runner.ts
CHANGED
|
@@ -32,6 +32,15 @@ export const COMMANDS: readonly CommandSpec[] = [
|
|
|
32
32
|
return args => m.runAuthCommand(args);
|
|
33
33
|
},
|
|
34
34
|
},
|
|
35
|
+
{
|
|
36
|
+
name: "export",
|
|
37
|
+
summary: "Export a saved session transcript to Markdown (or --json).",
|
|
38
|
+
usage: "export [id] [--json] [--system]",
|
|
39
|
+
loader: async () => {
|
|
40
|
+
const m = await import("../commands/export");
|
|
41
|
+
return args => m.runExportCommand(args);
|
|
42
|
+
},
|
|
43
|
+
},
|
|
35
44
|
{
|
|
36
45
|
name: "deep-interview",
|
|
37
46
|
summary: "Execute Socratic requirements interview (locks tools while ambiguity > 20%).",
|
|
@@ -84,24 +93,16 @@ export const COMMANDS: readonly CommandSpec[] = [
|
|
|
84
93
|
},
|
|
85
94
|
{
|
|
86
95
|
name: "mcp",
|
|
87
|
-
summary: "Run
|
|
96
|
+
summary: "Run jeo as an MCP stdio server (subcommand: serve|tools).",
|
|
88
97
|
usage: "mcp [serve|tools]",
|
|
89
98
|
loader: async () => {
|
|
90
99
|
const m = await import("../commands/mcp");
|
|
91
100
|
return args => m.runMcpCommand(args);
|
|
92
101
|
},
|
|
93
102
|
},
|
|
94
|
-
{
|
|
95
|
-
name: "models",
|
|
96
|
-
summary: "List model aliases + probe local/compatible models.",
|
|
97
|
-
loader: async () => {
|
|
98
|
-
const m = await import("../commands/models");
|
|
99
|
-
return args => m.runModelsCommand(args);
|
|
100
|
-
},
|
|
101
|
-
},
|
|
102
103
|
{
|
|
103
104
|
name: "skills",
|
|
104
|
-
summary: "List bundled workflow skills (
|
|
105
|
+
summary: "List bundled workflow skills (jeo skills <name> for details).",
|
|
105
106
|
usage: "skills [name]",
|
|
106
107
|
loader: async () => {
|
|
107
108
|
const m = await import("../commands/skills");
|
|
@@ -110,7 +111,7 @@ export const COMMANDS: readonly CommandSpec[] = [
|
|
|
110
111
|
},
|
|
111
112
|
{
|
|
112
113
|
name: "resume",
|
|
113
|
-
summary: "Resume the latest interactive session (or '
|
|
114
|
+
summary: "Resume the latest interactive session (or 'jeo resume <id>').",
|
|
114
115
|
usage: "resume [id]",
|
|
115
116
|
loader: async () => {
|
|
116
117
|
const m = await import("../commands/resume");
|
|
@@ -135,6 +136,95 @@ export const COMMANDS: readonly CommandSpec[] = [
|
|
|
135
136
|
return args => m.runEvolveCommand(args);
|
|
136
137
|
},
|
|
137
138
|
},
|
|
139
|
+
{
|
|
140
|
+
name: "memory-distill",
|
|
141
|
+
summary: "(internal) Background session-memory distillation worker spawned on exit.",
|
|
142
|
+
usage: "memory-distill <payload.json>",
|
|
143
|
+
loader: async () => {
|
|
144
|
+
const m = await import("../agent/memory");
|
|
145
|
+
return args => m.runMemoryDistillCommand(args);
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: "state",
|
|
150
|
+
summary: "Read or update workflow state receipts under .jeo/state (gjc-state parity).",
|
|
151
|
+
usage: "state <deep-interview|ralplan|team|ultragoal> <read|write|clear|handoff> [--input '<json>'] [--to <skill>] [--json]",
|
|
152
|
+
loader: async () => {
|
|
153
|
+
const m = await import("../commands/state");
|
|
154
|
+
return args => m.runStateCommand(args);
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: "session",
|
|
159
|
+
summary: "List, attach, or remove jeo-managed tmux sessions.",
|
|
160
|
+
usage: "session [list|attach <name>|rm <name>] [--json]",
|
|
161
|
+
loader: async () => {
|
|
162
|
+
const m = await import("../commands/session");
|
|
163
|
+
return args => m.runSessionCommand(args);
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
name: "update",
|
|
168
|
+
summary: "Check for (and install) a newer jeo-code release from the npm registry.",
|
|
169
|
+
usage: "update [--check|--install] [--json] [--strict]",
|
|
170
|
+
loader: async () => {
|
|
171
|
+
const m = await import("../commands/update");
|
|
172
|
+
return args => m.runUpdateCommand(args);
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: "gjc",
|
|
177
|
+
summary: "Run the gjc workflow skill as an autonomous build loop (plan → implement → verify).",
|
|
178
|
+
usage: "gjc \"<intent>\"",
|
|
179
|
+
loader: async () => {
|
|
180
|
+
const m = await import("../commands/gjc");
|
|
181
|
+
return args => m.runGjcCommand(args);
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: "ooo-seed",
|
|
186
|
+
summary: "Generate an immutable ooo seed from a specification (spec-first automation).",
|
|
187
|
+
usage: "ooo-seed [args]",
|
|
188
|
+
loader: async () => {
|
|
189
|
+
const m = await import("../commands/ooo-seed");
|
|
190
|
+
return args => m.runOooSeedCommand(args);
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: "status",
|
|
195
|
+
summary: "Show evolution status + engine performance metrics.",
|
|
196
|
+
loader: async () => {
|
|
197
|
+
const m = await import("../commands/status");
|
|
198
|
+
return async () => m.runStatusCommand();
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
name: "evolve-core",
|
|
203
|
+
summary: "Trigger a self-evolution turn using gjc as a guide (Dev Mode).",
|
|
204
|
+
usage: "evolve-core [args]",
|
|
205
|
+
loader: async () => {
|
|
206
|
+
const m = await import("../commands/evolve-core");
|
|
207
|
+
return args => m.runEvolveCoreCommand(args);
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
name: "autopilot",
|
|
212
|
+
summary: "Autonomous build loop (autopilot × autoresearch ratchet).",
|
|
213
|
+
usage: "autopilot <subcommand> [flags]",
|
|
214
|
+
loader: async () => {
|
|
215
|
+
const m = await import("../autopilot");
|
|
216
|
+
return args => Promise.resolve(m.runAutopilot(args));
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: "ledger",
|
|
221
|
+
summary: "Cross-plan append-only ledger (ledger / review / cleanup).",
|
|
222
|
+
usage: "ledger <subcommand> [flags]",
|
|
223
|
+
loader: async () => {
|
|
224
|
+
const m = await import("../ledger");
|
|
225
|
+
return args => Promise.resolve(m.runLedger(args));
|
|
226
|
+
},
|
|
227
|
+
},
|
|
138
228
|
];
|
|
139
229
|
|
|
140
230
|
export function findCommand(name: string): CommandSpec | undefined {
|
|
@@ -190,6 +280,10 @@ export function renderHelp(ctx: DispatchContext): string {
|
|
|
190
280
|
lines.push("Options:");
|
|
191
281
|
lines.push(" -v, --version Show version.");
|
|
192
282
|
lines.push(" -h, --help Show help.");
|
|
283
|
+
lines.push(" --model <id> Use a session model for launch/one-shot.");
|
|
284
|
+
lines.push(" --provider <name> Start launch on a provider default (anthropic/openai/gemini/antigravity/ollama).");
|
|
285
|
+
lines.push(" --smol|--slow|--plan Start launch with the configured model role tier.");
|
|
286
|
+
lines.push(" --thinking <level> Set thinking budget (minimal/low/medium/high/xhigh).");
|
|
193
287
|
lines.push("");
|
|
194
288
|
return lines.join("\n");
|
|
195
289
|
}
|
|
@@ -204,6 +298,38 @@ export function renderCommandHelp(spec: CommandSpec, ctx: DispatchContext): stri
|
|
|
204
298
|
].join("\n");
|
|
205
299
|
}
|
|
206
300
|
|
|
301
|
+
const VALUE_FLAGS = new Set(["--worktree", "--model", "--provider", "--thinking", "--max-steps", "--append-system-prompt", "--skills", "--tools", "--system-prompt"]);
|
|
302
|
+
const OPTIONAL_UUID_FLAGS = new Set(["--resume", "--continue", "-c"]);
|
|
303
|
+
const VALUE_PREFIXES = ["--worktree=", "--model=", "--provider=", "--thinking=", "--max-steps=", "--append-system-prompt=", "--skills=", "--tools=", "--system-prompt="];
|
|
304
|
+
const LAUNCH_ONLY_FLAGS = new Set(["--tmux", "--no-tui", "--no-session", "--list", "--smol", "--slow", "--plan", "-p", "--print", "--no-skills", "--no-tools"]);
|
|
305
|
+
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
306
|
+
|
|
307
|
+
function flagName(arg: string): string {
|
|
308
|
+
const eq = arg.indexOf("=");
|
|
309
|
+
return eq === -1 ? arg : arg.slice(0, eq);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
function leadingGlobalFlag(argv: string[], targets: readonly string[]): boolean {
|
|
314
|
+
const wanted = new Set(targets);
|
|
315
|
+
for (let i = 0; i < argv.length; i++) {
|
|
316
|
+
const a = argv[i]!;
|
|
317
|
+
if (a === "--") break;
|
|
318
|
+
const name = flagName(a);
|
|
319
|
+
if (wanted.has(a) || wanted.has(name)) return true;
|
|
320
|
+
if (LAUNCH_ONLY_FLAGS.has(name)) continue;
|
|
321
|
+
if (VALUE_FLAGS.has(name) || VALUE_PREFIXES.some(prefix => a.startsWith(prefix))) {
|
|
322
|
+
if (!a.includes("=") && argv[i + 1] && !argv[i + 1]!.startsWith("-")) i++;
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (OPTIONAL_UUID_FLAGS.has(name)) {
|
|
326
|
+
if (argv[i + 1] && UUID_REGEX.test(argv[i + 1]!)) i++;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
207
333
|
export async function dispatch(argv: string[], ctx: DispatchContext): Promise<number> {
|
|
208
334
|
const first = argv[0];
|
|
209
335
|
|
|
@@ -215,8 +341,16 @@ export async function dispatch(argv: string[], ctx: DispatchContext): Promise<nu
|
|
|
215
341
|
console.log(renderHelp(ctx));
|
|
216
342
|
return 0;
|
|
217
343
|
}
|
|
218
|
-
|
|
219
|
-
|
|
344
|
+
if (leadingGlobalFlag(argv, ["--version", "-v"])) {
|
|
345
|
+
console.log(`${ctx.appName} v${ctx.version}`);
|
|
346
|
+
return 0;
|
|
347
|
+
}
|
|
348
|
+
if (leadingGlobalFlag(argv, ["--help", "-h"])) {
|
|
349
|
+
console.log(renderHelp(ctx));
|
|
350
|
+
return 0;
|
|
351
|
+
}
|
|
352
|
+
// Bare invocation or a leading global flag (e.g. `jeo`, `jeo --tmux`,
|
|
353
|
+
// `jeo --tmux --worktree <path>`) routes to the interactive agent — gjc parity.
|
|
220
354
|
if (!first || first.startsWith("-")) {
|
|
221
355
|
const run = await findCommand("launch")!.loader();
|
|
222
356
|
await run(argv);
|
|
@@ -232,7 +366,7 @@ export async function dispatch(argv: string[], ctx: DispatchContext): Promise<nu
|
|
|
232
366
|
return 1;
|
|
233
367
|
}
|
|
234
368
|
|
|
235
|
-
// Per-command help: `
|
|
369
|
+
// Per-command help: `jeo <cmd> --help`.
|
|
236
370
|
const rest = argv.slice(1);
|
|
237
371
|
if (rest.includes("--help") || rest.includes("-h")) {
|
|
238
372
|
console.log(renderCommandHelp(spec, ctx));
|
package/src/cli.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { dispatch } from "./cli/runner";
|
|
3
|
+
import pkg from "../package.json";
|
|
3
4
|
|
|
4
|
-
const APP_NAME = "
|
|
5
|
-
|
|
5
|
+
const APP_NAME = "jeo";
|
|
6
|
+
// Single source of truth: package.json. A hardcoded copy here drifted from the
|
|
7
|
+
// published version (`jeo update` compares the local version against the registry).
|
|
8
|
+
const VERSION = pkg.version;
|
|
6
9
|
const MIN_BUN_VERSION = "1.3.14";
|
|
7
10
|
|
|
8
11
|
if (typeof Bun !== "undefined" && Bun.semver?.order(Bun.version, MIN_BUN_VERSION) < 0) {
|
|
@@ -13,5 +16,11 @@ if (typeof Bun !== "undefined" && Bun.semver?.order(Bun.version, MIN_BUN_VERSION
|
|
|
13
16
|
}
|
|
14
17
|
process.title = APP_NAME;
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
try {
|
|
20
|
+
const code = await dispatch(process.argv.slice(2), { appName: APP_NAME, version: VERSION });
|
|
21
|
+
if (code !== 0) process.exit(code);
|
|
22
|
+
} catch (err) {
|
|
23
|
+
// Service-readiness: never surface a raw stack trace to users; clean error + non-zero exit.
|
|
24
|
+
process.stderr.write(`error: ${(err as Error)?.message ?? String(err)}\n`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<!-- Parent: ../AGENTS.md -->
|
|
2
|
+
<!-- Generated: 2026-06-11 | Updated: 2026-06-11 -->
|
|
3
|
+
|
|
4
|
+
# commands
|
|
5
|
+
|
|
6
|
+
## Purpose
|
|
7
|
+
Concrete implementations of `jeo` subcommands (e.g., launch, setup, team, ultragoal).
|
|
8
|
+
|
|
9
|
+
## Key Files
|
|
10
|
+
| File | Description |
|
|
11
|
+
|------|-------------|
|
|
12
|
+
| `launch.ts` | The primary interactive/one-shot execution command |
|
|
13
|
+
| `setup.ts` | Guided configuration command |
|
|
14
|
+
| `team.ts` | Multi-agent coordination command |
|
|
15
|
+
| `status.ts` / `update.ts` | Inspection and maintenance commands |
|
|
16
|
+
|
|
17
|
+
## Subdirectories
|
|
18
|
+
*(None)*
|
|
19
|
+
|
|
20
|
+
## For AI Agents
|
|
21
|
+
|
|
22
|
+
### Working In This Directory
|
|
23
|
+
- Commands should handle their own specific setup but delegate core logic to `src/agent/` or `src/tui/`.
|
|
24
|
+
- Maintain clean separation between interactive (TTY) and non-interactive modes.
|
|
25
|
+
|
|
26
|
+
### Testing Requirements
|
|
27
|
+
- Mock standard streams (stdout/stdin) to test command outputs.
|
|
28
|
+
|
|
29
|
+
### Common Patterns
|
|
30
|
+
- Command handlers take parsed options, initialize context, and run the main loop or utility function.
|
|
31
|
+
|
|
32
|
+
## Dependencies
|
|
33
|
+
|
|
34
|
+
### Internal
|
|
35
|
+
- Connects `src/cli/` routing to `src/agent/` and `src/tui/`.
|
|
36
|
+
|
|
37
|
+
### External
|
|
38
|
+
*(None)*
|
|
39
|
+
|
|
40
|
+
<!-- MANUAL: -->
|
package/src/commands/approve.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import {
|
|
4
|
+
readGlobalConfig,
|
|
4
5
|
readWorkflowState,
|
|
5
6
|
writeWorkflowState,
|
|
6
7
|
} from "../agent/state";
|
|
8
|
+
import { PlanSchema, normalizePlanShape, parseYaml } from "../agent/plan";
|
|
9
|
+
import { getSubagentRole, subagentRoleIds } from "../agent/subagents";
|
|
7
10
|
|
|
8
11
|
export async function runApproveCommand(args: string[] = []): Promise<void> {
|
|
9
12
|
const cwd = process.cwd();
|
|
@@ -29,7 +32,7 @@ export async function runApproveCommand(args: string[] = []): Promise<void> {
|
|
|
29
32
|
// Read ralplan state
|
|
30
33
|
const ralplanState = await readWorkflowState("ralplan", cwd);
|
|
31
34
|
if (!ralplanState) {
|
|
32
|
-
console.log(`[ERROR] No ralplan workflow state found. Please run '
|
|
35
|
+
console.log(`[ERROR] No ralplan workflow state found. Please run 'jeo ralplan' first.`);
|
|
33
36
|
process.exitCode = 1;
|
|
34
37
|
return;
|
|
35
38
|
}
|
|
@@ -40,10 +43,23 @@ export async function runApproveCommand(args: string[] = []): Promise<void> {
|
|
|
40
43
|
return;
|
|
41
44
|
}
|
|
42
45
|
|
|
46
|
+
// Compare canonical (symlink-resolved) paths so a relative arg, an absolute arg,
|
|
47
|
+
// or a /var↔/private/var (macOS) form all match the stored plan path.
|
|
48
|
+
const canonical = async (p: string): Promise<string> => {
|
|
49
|
+
try {
|
|
50
|
+
return await fs.realpath(p);
|
|
51
|
+
} catch {
|
|
52
|
+
return p;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
43
55
|
const resolvedStatePath = path.resolve(cwd, ralplanState.plan_path);
|
|
44
|
-
|
|
56
|
+
const [canonInput, canonState] = await Promise.all([canonical(resolvedInputPath), canonical(resolvedStatePath)]);
|
|
57
|
+
if (canonInput !== canonState) {
|
|
45
58
|
console.log(
|
|
46
|
-
`[ERROR] Provided plan path does not match the active plan in the ralplan state
|
|
59
|
+
`[ERROR] Provided plan path does not match the active plan in the ralplan state.\n` +
|
|
60
|
+
` provided: ${resolvedInputPath}\n` +
|
|
61
|
+
` active: ${resolvedStatePath}\n` +
|
|
62
|
+
` Run 'jeo approve "${resolvedStatePath}"' to approve the active plan.`
|
|
47
63
|
);
|
|
48
64
|
process.exitCode = 1;
|
|
49
65
|
return;
|
|
@@ -55,6 +71,49 @@ export async function runApproveCommand(args: string[] = []): Promise<void> {
|
|
|
55
71
|
return;
|
|
56
72
|
}
|
|
57
73
|
|
|
74
|
+
// Round-10 #4 (architect ref 8-Round10Planning): approval is a GATE, not a
|
|
75
|
+
// rubber stamp — validate the plan against the exact contract `jeo team`
|
|
76
|
+
// enforces, so a schema-invalid/unknown-role plan is refused HERE instead of
|
|
77
|
+
// aborting later at execution time.
|
|
78
|
+
try {
|
|
79
|
+
const parsed = PlanSchema.safeParse(normalizePlanShape(parseYaml(await fs.readFile(resolvedInputPath, "utf-8"))));
|
|
80
|
+
if (!parsed.success) {
|
|
81
|
+
console.log(
|
|
82
|
+
`[ERROR] Refusing to approve: the plan is not in the shape 'jeo team' executes (top-level 'steps:' list of { name, role? }).\n` +
|
|
83
|
+
` ${parsed.error.issues[0]?.message ?? "schema mismatch"}\n` +
|
|
84
|
+
` Fix ${resolvedInputPath} or re-run 'jeo ralplan'.`,
|
|
85
|
+
);
|
|
86
|
+
process.exitCode = 1;
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const cfg = await readGlobalConfig();
|
|
90
|
+
const unknown = [...new Set(parsed.data.steps.map(s => s.role?.trim()).filter((r): r is string => !!r && !getSubagentRole(r, cfg)))];
|
|
91
|
+
if (unknown.length > 0) {
|
|
92
|
+
console.log(
|
|
93
|
+
`[ERROR] Refusing to approve: plan references unknown subagent role(s): ${unknown.join(", ")}.\n` +
|
|
94
|
+
` Known roles: ${subagentRoleIds(cfg).join(", ")}. Fix ${resolvedInputPath} or re-run 'jeo ralplan'.`,
|
|
95
|
+
);
|
|
96
|
+
process.exitCode = 1;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
} catch (err: any) {
|
|
100
|
+
console.log(`[ERROR] Refusing to approve: the plan file is not parseable YAML (${err.message}). Fix ${resolvedInputPath} or re-run 'jeo ralplan'.`);
|
|
101
|
+
process.exitCode = 1;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Round-11: approval also requires the PERSISTED consensus verdict — a plan
|
|
106
|
+
// that never passed (or failed) the critic gate cannot be approved. States
|
|
107
|
+
// from older ralplan runs lack the field; re-running ralplan heals them.
|
|
108
|
+
if (ralplanState.consensus !== "okay") {
|
|
109
|
+
console.log(
|
|
110
|
+
`[ERROR] Refusing to approve: the plan lacks an [OKAY] consensus verdict (recorded: ${ralplanState.consensus ?? "none"}).\n` +
|
|
111
|
+
` Re-run 'jeo ralplan' so the consensus critic can review the plan, then approve again.`,
|
|
112
|
+
);
|
|
113
|
+
process.exitCode = 1;
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
58
117
|
// Update ralplan-state.json to approved: true
|
|
59
118
|
ralplanState.approved = true;
|
|
60
119
|
await writeWorkflowState("ralplan", ralplanState, cwd);
|
package/src/commands/auth.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as fs from "node:fs/promises";
|
|
4
|
+
import { type StoredOAuth } from "../agent/state";
|
|
1
5
|
import { createInterface } from "node:readline/promises";
|
|
2
6
|
import { readGlobalConfig } from "../agent/state";
|
|
7
|
+
import { jeoEnv } from "../util/env";
|
|
3
8
|
import {
|
|
4
9
|
OAUTH_FLOWS,
|
|
5
10
|
OAUTH_FLOW_REGISTRY,
|
|
@@ -17,12 +22,24 @@ export async function runAuthCommand(args: string[]): Promise<void> {
|
|
|
17
22
|
const sub = args[0];
|
|
18
23
|
if (!sub || sub === "status") return runAuthStatus();
|
|
19
24
|
if (sub === "login") return runAuthLogin(args.slice(1));
|
|
25
|
+
if (sub === "import") return runAuthImport(args.slice(1));
|
|
20
26
|
if (sub === "logout") return runAuthLogout(args[1] as AuthProvider | undefined);
|
|
21
27
|
if (sub === "refresh") return runAuthRefresh(args[1] as AuthProvider | undefined);
|
|
22
|
-
console.log(`Unknown auth subcommand: ${sub}\nUsage:
|
|
28
|
+
console.log(`Unknown auth subcommand: ${sub}\nUsage: jeo auth [login|logout|refresh|status|import] [provider] [--token <bearer>] [--import]`);
|
|
23
29
|
process.exitCode = 1;
|
|
24
30
|
}
|
|
25
31
|
|
|
32
|
+
const CLOUD_PROVIDERS: readonly AuthProvider[] = ["anthropic", "openai", "gemini", "antigravity"]
|
|
33
|
+
/** True (and prints an error + sets exit code) when `p` is given but not a known provider. */
|
|
34
|
+
function rejectInvalidProvider(p: string | undefined): boolean {
|
|
35
|
+
if (p !== undefined && !(CLOUD_PROVIDERS as readonly string[]).includes(p)) {
|
|
36
|
+
console.log(`Unknown provider '${p}'. Use one of: ${CLOUD_PROVIDERS.join(", ")}.`);
|
|
37
|
+
process.exitCode = 1;
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
26
43
|
function fmtExpiry(expires?: number): string {
|
|
27
44
|
if (!expires) return "";
|
|
28
45
|
const ms = expires - Date.now();
|
|
@@ -34,13 +51,20 @@ function fmtExpiry(expires?: number): string {
|
|
|
34
51
|
|
|
35
52
|
async function runAuthStatus(): Promise<void> {
|
|
36
53
|
const cfg = await readGlobalConfig();
|
|
37
|
-
console.log("\n===
|
|
54
|
+
console.log("\n=== jeo auth status ===");
|
|
38
55
|
console.log("Provider API key OAuth");
|
|
39
|
-
for (const p of ["anthropic", "openai", "gemini"] as AuthProvider[]) {
|
|
56
|
+
for (const p of ["anthropic", "openai", "gemini", "antigravity"] as AuthProvider[]) {
|
|
40
57
|
const snap = await snapshotProvider(p);
|
|
41
|
-
const key = snap.apiKey ? "set" : "—";
|
|
58
|
+
const key = p === "antigravity" ? "—" : (snap.apiKey ? "set" : "—");
|
|
42
59
|
let oauth = "—";
|
|
43
|
-
if (
|
|
60
|
+
if (p === "antigravity") {
|
|
61
|
+
const fallback = snap.oauth ? snap : await snapshotProvider("gemini");
|
|
62
|
+
if (fallback.oauth) {
|
|
63
|
+
oauth = snap.oauth
|
|
64
|
+
? `set (refreshable)${fmtExpiry(snap.oauthExpires)}${snap.oauthEmail ? ` <${snap.oauthEmail}>` : ""}`
|
|
65
|
+
: "via gemini fallback";
|
|
66
|
+
}
|
|
67
|
+
} else if (snap.oauth) {
|
|
44
68
|
oauth = snap.oauthHasRefresh ? "set (refreshable)" : "set (manual)";
|
|
45
69
|
oauth += fmtExpiry(snap.oauthExpires);
|
|
46
70
|
if (snap.oauthEmail) oauth += ` <${snap.oauthEmail}>`;
|
|
@@ -55,7 +79,19 @@ async function runAuthStatus(): Promise<void> {
|
|
|
55
79
|
async function runAuthLogin(rest: string[]): Promise<void> {
|
|
56
80
|
const tokenIdx = rest.indexOf("--token");
|
|
57
81
|
const manualToken = tokenIdx >= 0 ? rest[tokenIdx + 1] : undefined;
|
|
58
|
-
const
|
|
82
|
+
const isImport = rest.includes("--import");
|
|
83
|
+
const provider = rest.find((a, i) => a !== "--token" && rest[i - 1] !== "--token" && a !== "--import") as AuthProvider | undefined;
|
|
84
|
+
|
|
85
|
+
if (rejectInvalidProvider(provider)) return;
|
|
86
|
+
|
|
87
|
+
if (isImport) {
|
|
88
|
+
if (provider !== "gemini") {
|
|
89
|
+
console.log(`[FAILED] Import is only supported for 'gemini' provider.`);
|
|
90
|
+
process.exitCode = 1;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
return runAuthImport(["gemini"]);
|
|
94
|
+
}
|
|
59
95
|
|
|
60
96
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
61
97
|
const chosen = provider ?? (await selectProvider(rl));
|
|
@@ -72,37 +108,142 @@ async function runAuthLogin(rest: string[]): Promise<void> {
|
|
|
72
108
|
return;
|
|
73
109
|
}
|
|
74
110
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
111
|
+
try {
|
|
112
|
+
const { email } = await interactiveOAuthLogin(chosen, rl);
|
|
113
|
+
console.log(`\n[SUCCESS] OAuth login complete for ${chosen}${email ? ` (${email})` : ""}.`);
|
|
114
|
+
console.log("Stored access + refresh tokens in ~/.jeo/config.json; jeo will auto-refresh on expiry.");
|
|
115
|
+
} catch (err) {
|
|
116
|
+
console.log(`\n[FAILED] ${(err as Error).message}`);
|
|
117
|
+
console.log("Tip: paste the redirect URL when prompted, or use 'jeo auth login <provider> --token <bearer>'.");
|
|
118
|
+
process.exitCode = 1;
|
|
119
|
+
} finally {
|
|
120
|
+
rl.close();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function runAuthImport(rest: string[]): Promise<void> {
|
|
125
|
+
const provider = rest.find(a => a !== "--import") as AuthProvider | undefined;
|
|
126
|
+
if (!provider) {
|
|
127
|
+
console.log("Usage: jeo auth import <provider>");
|
|
128
|
+
process.exitCode = 1;
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (rejectInvalidProvider(provider)) return;
|
|
132
|
+
if (provider !== "gemini") {
|
|
133
|
+
console.log(`[FAILED] Import is only supported for 'gemini' provider.`);
|
|
134
|
+
process.exitCode = 1;
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
80
137
|
|
|
138
|
+
try {
|
|
139
|
+
const credsPath = jeoEnv("GEMINI_CREDS_PATH") || path.join(os.homedir(), ".gemini", "oauth_creds.json");
|
|
140
|
+
let content: string;
|
|
141
|
+
try {
|
|
142
|
+
content = await fs.readFile(credsPath, "utf-8");
|
|
143
|
+
} catch (err: any) {
|
|
144
|
+
console.log(`[FAILED] Failed to read Gemini credentials file: ${err.message}`);
|
|
145
|
+
process.exitCode = 1;
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let creds: any;
|
|
150
|
+
try {
|
|
151
|
+
creds = JSON.parse(content);
|
|
152
|
+
} catch (err: any) {
|
|
153
|
+
console.log(`[FAILED] Failed to parse Gemini credentials JSON: ${err.message}`);
|
|
154
|
+
process.exitCode = 1;
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!creds || !creds.access_token) {
|
|
159
|
+
console.log(`[FAILED] Missing access_token in Gemini credentials.`);
|
|
160
|
+
process.exitCode = 1;
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let email: string | undefined;
|
|
165
|
+
if (creds.id_token) {
|
|
166
|
+
const parts = creds.id_token.split(".");
|
|
167
|
+
if (parts.length >= 2) {
|
|
168
|
+
try {
|
|
169
|
+
const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
170
|
+
const payload = JSON.parse(atob(base64));
|
|
171
|
+
email = payload.email;
|
|
172
|
+
} catch {}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
let expires = typeof creds.expiry_date === "number" ? creds.expiry_date : (typeof creds.expiry_date === "string" ? parseInt(creds.expiry_date, 10) : undefined);
|
|
177
|
+
if (isNaN(expires as number)) expires = undefined;
|
|
178
|
+
|
|
179
|
+
const imported: StoredOAuth = {
|
|
180
|
+
access: creds.access_token,
|
|
181
|
+
refresh: creds.refresh_token,
|
|
182
|
+
expires,
|
|
183
|
+
email,
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const { setOauthCredential } = await import("../auth/storage");
|
|
187
|
+
await setOauthCredential("gemini", imported);
|
|
188
|
+
|
|
189
|
+
console.log(`[SUCCESS] Imported OAuth credentials for gemini (also usable as the Antigravity fallback when the backend accepts gemini-cli tokens).`);
|
|
190
|
+
if (email) {
|
|
191
|
+
console.log(`Account email: ${email}`);
|
|
192
|
+
}
|
|
193
|
+
const expiryMsg = expires ? fmtExpiry(expires) : "";
|
|
194
|
+
console.log(`Expiry status:${expiryMsg || " valid"}`);
|
|
195
|
+
} catch (err: any) {
|
|
196
|
+
console.log(`[FAILED] ${err.message}`);
|
|
197
|
+
process.exitCode = 1;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Prompt object the OAuth manual-code fallback needs (a readline interface satisfies it). */
|
|
202
|
+
export interface OAuthPrompt {
|
|
203
|
+
question(query: string, options?: { signal?: AbortSignal }): Promise<string>;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Run the interactive OAuth login flow for a provider using an existing prompt
|
|
208
|
+
* (readline) interface. Shared by `jeo auth login` and the REPL `/provider login`.
|
|
209
|
+
* Prints flow instructions, opens the browser, and resolves with the account email.
|
|
210
|
+
*/
|
|
211
|
+
export async function interactiveOAuthLogin(
|
|
212
|
+
provider: AuthProvider,
|
|
213
|
+
prompt: OAuthPrompt,
|
|
214
|
+
log: (s: string) => void = console.log,
|
|
215
|
+
): Promise<{ email?: string }> {
|
|
216
|
+
const flow = OAUTH_FLOW_REGISTRY[provider];
|
|
217
|
+
log(`\n=== OAuth login — ${flow.label} ===`);
|
|
218
|
+
if (!flow.verifiedEndToEnd && flow.note) log(`Note: ${flow.note}`);
|
|
219
|
+
for (const line of OAUTH_FLOWS[provider].instructions) log(" " + line);
|
|
220
|
+
log("");
|
|
221
|
+
|
|
222
|
+
// Abort the pending "Paste redirect URL…" question once the flow settles.
|
|
223
|
+
// Without this the dangling readline question survives a SUCCESS/FAILED
|
|
224
|
+
// result, reprints its prompt over the result line, and queues in FRONT of
|
|
225
|
+
// any follow-up question (the `jeo setup` API-key fallback appeared hung).
|
|
226
|
+
const ac = new AbortController();
|
|
81
227
|
const ctrl: OAuthController = {
|
|
228
|
+
signal: ac.signal,
|
|
82
229
|
onAuth: ({ url, instructions }) => {
|
|
83
|
-
|
|
84
|
-
if (instructions)
|
|
230
|
+
log(`Opening browser:\n ${url}\n`);
|
|
231
|
+
if (instructions) log(instructions + "\n");
|
|
85
232
|
void openInBrowser(url);
|
|
86
233
|
},
|
|
87
|
-
onProgress: msg =>
|
|
234
|
+
onProgress: msg => log(` … ${msg}`),
|
|
88
235
|
onManualCodeInput: async () =>
|
|
89
|
-
(await
|
|
236
|
+
(await prompt.question("Paste redirect URL or code (or wait for the browser callback): ", { signal: ac.signal })).trim(),
|
|
90
237
|
};
|
|
91
|
-
|
|
92
238
|
try {
|
|
93
|
-
|
|
94
|
-
console.log(`\n[SUCCESS] OAuth login complete for ${chosen}${email ? ` (${email})` : ""}.`);
|
|
95
|
-
console.log("Stored access + refresh tokens in ~/.joc/config.json; joc will auto-refresh on expiry.");
|
|
96
|
-
} catch (err) {
|
|
97
|
-
console.log(`\n[FAILED] ${(err as Error).message}`);
|
|
98
|
-
console.log("Tip: paste the redirect URL when prompted, or use 'joc auth login <provider> --token <bearer>'.");
|
|
99
|
-
process.exitCode = 1;
|
|
239
|
+
return await interactiveLogin(provider, ctrl);
|
|
100
240
|
} finally {
|
|
101
|
-
|
|
241
|
+
ac.abort();
|
|
102
242
|
}
|
|
103
243
|
}
|
|
104
244
|
|
|
105
245
|
async function runAuthLogout(provider?: AuthProvider): Promise<void> {
|
|
246
|
+
if (rejectInvalidProvider(provider)) return;
|
|
106
247
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
107
248
|
const chosen = provider ?? (await selectProvider(rl, "logout"));
|
|
108
249
|
rl.close();
|
|
@@ -113,10 +254,11 @@ async function runAuthLogout(provider?: AuthProvider): Promise<void> {
|
|
|
113
254
|
|
|
114
255
|
async function runAuthRefresh(provider?: AuthProvider): Promise<void> {
|
|
115
256
|
if (!provider) {
|
|
116
|
-
console.log("Usage:
|
|
257
|
+
console.log("Usage: jeo auth refresh <provider>");
|
|
117
258
|
process.exitCode = 1;
|
|
118
259
|
return;
|
|
119
260
|
}
|
|
261
|
+
if (rejectInvalidProvider(provider)) return;
|
|
120
262
|
const result = await refreshOAuthToken(provider);
|
|
121
263
|
console.log(
|
|
122
264
|
result.refreshed
|