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/commands/setup.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { qualifyModelId } from "../ai/model-manager";
|
|
2
|
+
import type { ProviderName } from "../ai/types";
|
|
1
3
|
import { createInterface } from "node:readline/promises";
|
|
2
|
-
import { saveGlobalConfig, readGlobalConfig, type Config } from "../agent/state";
|
|
4
|
+
import { saveGlobalConfig, readGlobalConfig, readRawGlobalConfig, type Config } from "../agent/state";
|
|
3
5
|
import {
|
|
4
6
|
interactiveLogin,
|
|
5
7
|
getStoredOAuth,
|
|
@@ -24,7 +26,7 @@ function reportModelChoice(r: { warning?: string; suggestions: string[] }): void
|
|
|
24
26
|
type ProviderChoice = "anthropic" | "openai" | "gemini" | "ollama" | "lmstudio" | "openai-compatible";
|
|
25
27
|
|
|
26
28
|
const DEFAULT_MODELS: Record<ProviderChoice, string> = {
|
|
27
|
-
anthropic: "claude-
|
|
29
|
+
anthropic: "claude-sonnet-4-5",
|
|
28
30
|
openai: "gpt-4o",
|
|
29
31
|
gemini: "gemini-2.0-flash",
|
|
30
32
|
ollama: "ollama/llama3.1:8b",
|
|
@@ -64,10 +66,20 @@ async function listOpenAiCompatibleModels(baseUrl: string, apiKey?: string): Pro
|
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
export async function runSetupCommand(): Promise<void> {
|
|
69
|
+
// `setup` is interactive; on a non-TTY (piped/CI) stdin readline can't prompt and
|
|
70
|
+
// would reject with a cryptic "readline was closed". Fail with clear guidance instead.
|
|
71
|
+
if (!process.stdin.isTTY) {
|
|
72
|
+
console.log(
|
|
73
|
+
"jeo setup needs an interactive terminal (TTY).\n" +
|
|
74
|
+
"Non-interactive options: set env vars (ANTHROPIC_API_KEY / OPENAI_API_KEY / GEMINI_API_KEY, JEO_DEFAULT_MODEL, OLLAMA_HOST),\n" +
|
|
75
|
+
"run 'jeo auth login <anthropic|openai|gemini|antigravity>' for OAuth, or edit ~/.jeo/config.json directly. Verify with 'jeo doctor'.",
|
|
76
|
+
);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
67
79
|
const current = await readGlobalConfig();
|
|
68
80
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
69
81
|
|
|
70
|
-
console.log("\n=== @jeo-code CLI Configuration (
|
|
82
|
+
console.log("\n=== @jeo-code CLI Configuration (jeo setup) ===");
|
|
71
83
|
console.log("Configure providers, API keys / OAuth tokens, and default model.\n");
|
|
72
84
|
|
|
73
85
|
console.log("Available provider types:");
|
|
@@ -87,7 +99,11 @@ export async function runSetupCommand(): Promise<void> {
|
|
|
87
99
|
};
|
|
88
100
|
const choice = map[sel] ?? "skip";
|
|
89
101
|
|
|
90
|
-
|
|
102
|
+
// Build the persisted config from the RAW on-disk state (NOT the env-overlaid
|
|
103
|
+
// `current`), so `jeo setup` never bakes env-only values — OAuth bearer tokens
|
|
104
|
+
// (*_OAUTH_TOKEN), JEO_DEFAULT_MODEL, JEO_*_MODEL roles, OLLAMA_HOST/OPENAI_BASE_URL —
|
|
105
|
+
// permanently into ~/.jeo/config.json. `current` is still used for display defaults.
|
|
106
|
+
const next: Config = JSON.parse(JSON.stringify(await readRawGlobalConfig())) as Config;
|
|
91
107
|
next.providers = next.providers || {};
|
|
92
108
|
next.oauth = next.oauth || {};
|
|
93
109
|
|
|
@@ -104,7 +120,12 @@ export async function runSetupCommand(): Promise<void> {
|
|
|
104
120
|
} else {
|
|
105
121
|
const flow = OAUTH_FLOW_REGISTRY[choice as AuthProvider];
|
|
106
122
|
if (!flow.verifiedEndToEnd && flow.note) console.log(`Note: ${flow.note}`);
|
|
123
|
+
// Abort the pending "Paste redirect URL…" question once the flow settles —
|
|
124
|
+
// otherwise it survives the SUCCESS/FAILED result, reprints its prompt, and
|
|
125
|
+
// QUEUES IN FRONT of the API-key fallback question below (setup looked hung).
|
|
126
|
+
const ac = new AbortController();
|
|
107
127
|
const ctrl: OAuthController = {
|
|
128
|
+
signal: ac.signal,
|
|
108
129
|
onAuth: ({ url, instructions }) => {
|
|
109
130
|
console.log(`Opening browser:\n ${url}\n`);
|
|
110
131
|
if (instructions) console.log(instructions + "\n");
|
|
@@ -112,10 +133,17 @@ export async function runSetupCommand(): Promise<void> {
|
|
|
112
133
|
},
|
|
113
134
|
onProgress: msg => console.log(` … ${msg}`),
|
|
114
135
|
onManualCodeInput: async () =>
|
|
115
|
-
(await rl.question("Paste redirect URL or code (or wait for the browser callback): ")).trim(),
|
|
136
|
+
(await rl.question("Paste redirect URL or code (or wait for the browser callback): ", { signal: ac.signal })).trim(),
|
|
116
137
|
};
|
|
117
138
|
try {
|
|
118
|
-
|
|
139
|
+
let email: string | undefined;
|
|
140
|
+
try {
|
|
141
|
+
({ email } = await interactiveLogin(choice as AuthProvider, ctrl));
|
|
142
|
+
} finally {
|
|
143
|
+
// Must fire BEFORE the catch's API-key question below, or that
|
|
144
|
+
// question queues behind the stale paste prompt.
|
|
145
|
+
ac.abort();
|
|
146
|
+
}
|
|
119
147
|
const stored = await getStoredOAuth(choice as AuthProvider);
|
|
120
148
|
if (stored) next.oauth[choice] = stored;
|
|
121
149
|
console.log(`[SUCCESS] OAuth login complete for ${choice}${email ? ` (${email})` : ""}.`);
|
|
@@ -126,12 +154,17 @@ export async function runSetupCommand(): Promise<void> {
|
|
|
126
154
|
if (key.trim()) next.providers[choice] = key.trim();
|
|
127
155
|
}
|
|
128
156
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
|
|
157
|
+
const openAiCodexOnly = choice === "openai" && !!next.oauth.openai && !next.providers.openai;
|
|
158
|
+
const recommended = recommendedModelsFor(choice, 5, { codex: openAiCodexOnly });
|
|
159
|
+
const fallbackModel = recommended[0]?.split(" ")[0] ?? DEFAULT_MODELS[choice];
|
|
160
|
+
console.log(`\nRecommended ${choice}${openAiCodexOnly ? " Codex OAuth" : ""} models:`);
|
|
161
|
+
for (const m of recommended) console.log(` - ${m}`);
|
|
162
|
+
const dm = await rl.question(`Default model for ${choice} [${fallbackModel}]: `);
|
|
163
|
+
const rawInput = dm.trim();
|
|
164
|
+
const qualified = rawInput ? qualifyModelId(rawInput, choice as ProviderName) : fallbackModel;
|
|
165
|
+
const picked = chooseDefaultModel(qualified, choice as ProviderName);
|
|
133
166
|
reportModelChoice(picked);
|
|
134
|
-
next.defaultModel = picked.model ||
|
|
167
|
+
next.defaultModel = picked.model || fallbackModel;
|
|
135
168
|
} else if (choice === "ollama") {
|
|
136
169
|
const url = await rl.question(`Ollama base URL [${current.ollamaBaseUrl || DEFAULT_BASE_URLS.ollama}]: `);
|
|
137
170
|
next.ollamaBaseUrl = normalizeBaseUrl(url, current.ollamaBaseUrl || DEFAULT_BASE_URLS.ollama!);
|
|
@@ -141,11 +174,15 @@ export async function runSetupCommand(): Promise<void> {
|
|
|
141
174
|
console.log("Detected local Ollama models:");
|
|
142
175
|
models.slice(0, 20).forEach((m, i) => console.log(` - ${m}`));
|
|
143
176
|
const def = await rl.question(`Default model (ollama/<name>) [${"ollama/" + (models[0] ?? "llama3.1:8b")}]: `);
|
|
144
|
-
|
|
177
|
+
const rawInput = def.trim();
|
|
178
|
+
const qualified = rawInput ? qualifyModelId(rawInput, "ollama") : `ollama/${models[0] ?? "llama3.1:8b"}`;
|
|
179
|
+
next.defaultModel = qualified;
|
|
145
180
|
reportModelChoice(chooseDefaultModel(next.defaultModel, "ollama"));
|
|
146
181
|
} else {
|
|
147
182
|
console.log(" (no models detected — Ollama not reachable, defaulting to llama3.1:8b)");
|
|
148
|
-
const
|
|
183
|
+
const rawInput = (await rl.question(`Default model [${DEFAULT_MODELS.ollama}]: `)).trim();
|
|
184
|
+
const qualified = rawInput ? qualifyModelId(rawInput, "ollama") : DEFAULT_MODELS.ollama;
|
|
185
|
+
const picked = chooseDefaultModel(qualified, "ollama");
|
|
149
186
|
reportModelChoice(picked);
|
|
150
187
|
next.defaultModel = picked.model || DEFAULT_MODELS.ollama;
|
|
151
188
|
}
|
|
@@ -163,11 +200,15 @@ export async function runSetupCommand(): Promise<void> {
|
|
|
163
200
|
console.log("Detected models:");
|
|
164
201
|
models.slice(0, 20).forEach(m => console.log(` - ${m}`));
|
|
165
202
|
const def = await rl.question(`Default model (openai/<name>) [openai/${models[0]}]: `);
|
|
166
|
-
|
|
203
|
+
const rawInput = def.trim();
|
|
204
|
+
const qualified = rawInput ? qualifyModelId(rawInput, "openai") : `openai/${models[0]}`;
|
|
205
|
+
next.defaultModel = qualified;
|
|
167
206
|
reportModelChoice(chooseDefaultModel(next.defaultModel, "openai"));
|
|
168
207
|
} else {
|
|
169
208
|
console.log(" (no models detected — endpoint not reachable yet)");
|
|
170
|
-
const
|
|
209
|
+
const rawInput = (await rl.question(`Default model [${DEFAULT_MODELS[choice]}]: `)).trim();
|
|
210
|
+
const qualified = rawInput ? qualifyModelId(rawInput, "openai") : DEFAULT_MODELS[choice];
|
|
211
|
+
const picked = chooseDefaultModel(qualified, "openai");
|
|
171
212
|
reportModelChoice(picked);
|
|
172
213
|
next.defaultModel = picked.model || DEFAULT_MODELS[choice];
|
|
173
214
|
}
|
|
@@ -184,7 +225,7 @@ export async function runSetupCommand(): Promise<void> {
|
|
|
184
225
|
if (next.oauth && !next.oauth.anthropic && !next.oauth.openai && !next.oauth.gemini) delete next.oauth;
|
|
185
226
|
|
|
186
227
|
await saveGlobalConfig(next);
|
|
187
|
-
console.log("\n[SUCCESS] Configuration saved to ~/.
|
|
228
|
+
console.log("\n[SUCCESS] Configuration saved to ~/.jeo/config.json");
|
|
188
229
|
for (const line of buildSetupSummary(next)) console.log(line);
|
|
189
230
|
console.log("");
|
|
190
231
|
}
|
package/src/commands/skills.ts
CHANGED
|
@@ -1,38 +1,98 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { SKILLS,
|
|
4
|
-
import {
|
|
3
|
+
import { SKILLS, getSkillFrom, formatSkill, loadSkills, skillDirs } from "../skills/catalog";
|
|
4
|
+
import { getLocalJeoDir } from "../agent/state";
|
|
5
|
+
|
|
6
|
+
function editDistance(a: string, b: string): number {
|
|
7
|
+
const m = a.length, n = b.length;
|
|
8
|
+
if (m === 0) return n;
|
|
9
|
+
if (n === 0) return m;
|
|
10
|
+
const d = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
11
|
+
for (let i = 0; i <= m; i++) d[i][0] = i;
|
|
12
|
+
for (let j = 0; j <= n; j++) d[0][j] = j;
|
|
13
|
+
for (let i = 1; i <= m; i++) {
|
|
14
|
+
for (let j = 1; j <= n; j++) {
|
|
15
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
16
|
+
d[i][j] = Math.min(
|
|
17
|
+
d[i - 1][j] + 1, // deletion
|
|
18
|
+
d[i][j - 1] + 1, // insertion
|
|
19
|
+
d[i - 1][j - 1] + cost // substitution
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return d[m][n];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function suggestSkills(name: string, known: string[]): string[] {
|
|
27
|
+
const q = name.toLowerCase();
|
|
28
|
+
if (!q) return [];
|
|
29
|
+
return known.filter(n => n.toLowerCase().startsWith(q) || editDistance(n.toLowerCase(), q) <= 2);
|
|
30
|
+
}
|
|
5
31
|
|
|
6
32
|
export async function runSkillsCommand(args: string[] = []): Promise<void> {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
33
|
+
const cwd = process.cwd();
|
|
34
|
+
const isJson = args.includes("--json");
|
|
35
|
+
const cleanArgs = args.filter(a => a !== "--json");
|
|
36
|
+
|
|
37
|
+
// `jeo skills --write [dir]` materializes bundled skill docs to disk (gjc-style SKILL.md files).
|
|
38
|
+
if (cleanArgs[0] === "--write") {
|
|
39
|
+
const dir = cleanArgs[1] ? path.resolve(cwd, cleanArgs[1]) : path.join(getLocalJeoDir(cwd), "skills");
|
|
11
40
|
await fs.mkdir(dir, { recursive: true });
|
|
12
41
|
for (const s of SKILLS) {
|
|
13
42
|
const file = path.join(dir, `${s.name}.md`);
|
|
14
|
-
await fs.writeFile(file, `# ${s.name}\n\n${formatSkill(s)}\n`, "utf-8");
|
|
43
|
+
await fs.writeFile(file, s.raw || `# ${s.name}\n\n${formatSkill(s)}\n`, "utf-8");
|
|
15
44
|
}
|
|
16
45
|
console.log(`Wrote ${SKILLS.length} skill docs to ${dir}`);
|
|
17
46
|
return;
|
|
18
47
|
}
|
|
19
48
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
49
|
+
// List/lookup over the MERGED set (bundled + ~/.jeo/skills + ~/.agents/skills + project dirs), matching the REPL /skill.
|
|
50
|
+
const skills = await loadSkills(cwd);
|
|
51
|
+
const command = cleanArgs[0];
|
|
52
|
+
|
|
53
|
+
if (!command || command === "list") {
|
|
54
|
+
if (isJson) {
|
|
55
|
+
console.log(JSON.stringify(skills.map(s => ({ name: s.name, summary: s.summary })), null, 2));
|
|
56
|
+
} else {
|
|
57
|
+
console.log("\n=== jeo skills ===");
|
|
58
|
+
console.log("Workflow skills (bundled + ~/.jeo/skills, ~/.agents/skills, project dirs) — 'jeo skills <name>' for details, --write to export:\n");
|
|
59
|
+
for (const s of skills) {
|
|
60
|
+
console.log(` ${s.name.padEnd(16)} ${s.summary}`);
|
|
61
|
+
}
|
|
62
|
+
console.log("\nInvoke: /skill <name> [intent] · $<name> [intent] · skill-owned slash aliases (e.g. /speckit.plan)");
|
|
63
|
+
console.log("Discovery dirs (later wins on name clash; JEO_SKILLS_DIR adds more):");
|
|
64
|
+
for (const d of skillDirs(cwd)) console.log(` ${d}`);
|
|
65
|
+
console.log("");
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let name: string;
|
|
71
|
+
if (command === "read") {
|
|
72
|
+
name = cleanArgs[1];
|
|
73
|
+
if (!name) {
|
|
74
|
+
console.log("Error: Missing skill name for 'read' command.");
|
|
25
75
|
process.exitCode = 1;
|
|
26
76
|
return;
|
|
27
77
|
}
|
|
28
|
-
|
|
78
|
+
} else {
|
|
79
|
+
name = command;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const skill = getSkillFrom(skills, name);
|
|
83
|
+
if (!skill) {
|
|
84
|
+
const knownNames = skills.map(s => s.name);
|
|
85
|
+
const suggestions = suggestSkills(name, knownNames);
|
|
86
|
+
const hint = suggestions.length ? ` Did you mean: ${suggestions.join(", ")}?` : "";
|
|
87
|
+
console.log(`Unknown skill: ${name}.${hint}\nAvailable: ${knownNames.join(", ")}`);
|
|
88
|
+
process.exitCode = 1;
|
|
29
89
|
return;
|
|
30
90
|
}
|
|
31
91
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
92
|
+
if (isJson) {
|
|
93
|
+
const content = skill.raw || `# ${skill.name}\n\n${formatSkill(skill)}\n`;
|
|
94
|
+
console.log(JSON.stringify({ name: skill.name, content }, null, 2));
|
|
95
|
+
} else {
|
|
96
|
+
console.log(formatSkill(skill));
|
|
36
97
|
}
|
|
37
|
-
console.log("");
|
|
38
98
|
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import {
|
|
3
|
+
readWorkflowState,
|
|
4
|
+
writeWorkflowState,
|
|
5
|
+
clearWorkflowState,
|
|
6
|
+
getLocalJeoDir,
|
|
7
|
+
type WorkflowState,
|
|
8
|
+
} from "../agent/state";
|
|
9
|
+
|
|
10
|
+
export interface ExtendedWorkflowState extends WorkflowState {
|
|
11
|
+
handoff_from?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const allowedSkills = ["deep-interview", "ralplan", "team", "ultragoal"] as const;
|
|
15
|
+
type Skill = typeof allowedSkills[number];
|
|
16
|
+
|
|
17
|
+
function isSkill(val: string): val is Skill {
|
|
18
|
+
return allowedSkills.includes(val as Skill);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const allowedVerbs = ["read", "write", "clear", "handoff"] as const;
|
|
22
|
+
type Verb = typeof allowedVerbs[number];
|
|
23
|
+
|
|
24
|
+
function isVerb(val: string): val is Verb {
|
|
25
|
+
return allowedVerbs.includes(val as Verb);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function printUsage(): void {
|
|
29
|
+
console.log("Usage:");
|
|
30
|
+
console.log(" jeo state <skill> read [--json]");
|
|
31
|
+
console.log(" jeo state <skill> write --input '<json>' [--json]");
|
|
32
|
+
console.log(" jeo state <skill> clear");
|
|
33
|
+
console.log(" jeo state <skill> handoff --to <skill> [--json]");
|
|
34
|
+
console.log("");
|
|
35
|
+
console.log("Skills:");
|
|
36
|
+
console.log(" deep-interview, ralplan, team, ultragoal");
|
|
37
|
+
console.log("");
|
|
38
|
+
console.log("Verbs:");
|
|
39
|
+
console.log(" read, write, clear, handoff");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function runStateCommand(args: string[] = []): Promise<void> {
|
|
43
|
+
const cwd = process.cwd();
|
|
44
|
+
|
|
45
|
+
const isHelp = args.includes("--help") || args.includes("-h");
|
|
46
|
+
if (isHelp) {
|
|
47
|
+
printUsage();
|
|
48
|
+
process.exitCode = 0;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (args.length < 2) {
|
|
53
|
+
printUsage();
|
|
54
|
+
process.exitCode = 1;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const skill = args[0];
|
|
59
|
+
const verb = args[1];
|
|
60
|
+
|
|
61
|
+
if (!isSkill(skill) || !isVerb(verb)) {
|
|
62
|
+
printUsage();
|
|
63
|
+
process.exitCode = 1;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const isJson = args.includes("--json");
|
|
68
|
+
|
|
69
|
+
if (verb === "read") {
|
|
70
|
+
const state = await readWorkflowState(skill, cwd);
|
|
71
|
+
if (!state) {
|
|
72
|
+
console.log(`No state found for skill: ${skill}`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (isJson) {
|
|
76
|
+
console.log(JSON.stringify(state, null, 2));
|
|
77
|
+
} else {
|
|
78
|
+
const statePath = path.join(getLocalJeoDir(cwd), "state", `${skill}-state.json`);
|
|
79
|
+
console.log(`Skill: ${state.skill}`);
|
|
80
|
+
console.log(`Current Phase: ${state.current_phase}`);
|
|
81
|
+
console.log(`File Path: ${statePath}`);
|
|
82
|
+
console.log("Details:");
|
|
83
|
+
for (const [key, val] of Object.entries(state)) {
|
|
84
|
+
if (key !== "skill" && key !== "current_phase") {
|
|
85
|
+
console.log(` ${key}: ${typeof val === "object" ? JSON.stringify(val) : val}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (verb === "write") {
|
|
93
|
+
let inputJson: string | null = null;
|
|
94
|
+
for (let i = 2; i < args.length; i++) {
|
|
95
|
+
if (args[i] === "--input") {
|
|
96
|
+
inputJson = args[i + 1] || null;
|
|
97
|
+
i++;
|
|
98
|
+
} else if (args[i].startsWith("--input=")) {
|
|
99
|
+
inputJson = args[i].substring("--input=".length);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (inputJson === null) {
|
|
104
|
+
console.log("[ERROR] --input option is required for write verb.");
|
|
105
|
+
process.exitCode = 1;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let parsed: any;
|
|
110
|
+
try {
|
|
111
|
+
parsed = JSON.parse(inputJson);
|
|
112
|
+
} catch (err) {
|
|
113
|
+
console.log(`[ERROR] Malformed JSON input: ${(err as Error).message}`);
|
|
114
|
+
process.exitCode = 1;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
119
|
+
console.log("[ERROR] Input must be a valid JSON object.");
|
|
120
|
+
process.exitCode = 1;
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const existing = await readWorkflowState(skill, cwd);
|
|
125
|
+
const merged: WorkflowState = {
|
|
126
|
+
active: false,
|
|
127
|
+
current_phase: "",
|
|
128
|
+
...existing,
|
|
129
|
+
...parsed,
|
|
130
|
+
skill,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const statePath = await writeWorkflowState(skill, merged, cwd);
|
|
134
|
+
if (isJson) {
|
|
135
|
+
console.log(`State path: ${statePath}`);
|
|
136
|
+
console.log(JSON.stringify(merged, null, 2));
|
|
137
|
+
} else {
|
|
138
|
+
console.log(`State path: ${statePath}`);
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (verb === "clear") {
|
|
144
|
+
await clearWorkflowState(skill, cwd);
|
|
145
|
+
console.log(`Cleared state for skill: ${skill}`);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (verb === "handoff") {
|
|
150
|
+
let toSkill: string | null = null;
|
|
151
|
+
for (let i = 2; i < args.length; i++) {
|
|
152
|
+
if (args[i] === "--to") {
|
|
153
|
+
toSkill = args[i + 1] || null;
|
|
154
|
+
i++;
|
|
155
|
+
} else if (args[i].startsWith("--to=")) {
|
|
156
|
+
toSkill = args[i].substring("--to=".length);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!toSkill || !isSkill(toSkill)) {
|
|
161
|
+
console.log(`[ERROR] Target skill must be specified with --to <skill>. Allowed skills: ${allowedSkills.join(", ")}`);
|
|
162
|
+
process.exitCode = 1;
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const sourceState = await readWorkflowState(skill, cwd) || {
|
|
167
|
+
active: false,
|
|
168
|
+
current_phase: "",
|
|
169
|
+
skill,
|
|
170
|
+
};
|
|
171
|
+
sourceState.current_phase = "handoff";
|
|
172
|
+
const sourcePath = await writeWorkflowState(skill, sourceState, cwd);
|
|
173
|
+
|
|
174
|
+
const targetState = await readWorkflowState(toSkill, cwd) || {
|
|
175
|
+
active: false,
|
|
176
|
+
current_phase: "",
|
|
177
|
+
skill: toSkill,
|
|
178
|
+
};
|
|
179
|
+
const updatedTarget: ExtendedWorkflowState = {
|
|
180
|
+
...targetState,
|
|
181
|
+
active: true,
|
|
182
|
+
handoff_from: skill,
|
|
183
|
+
};
|
|
184
|
+
const targetPath = await writeWorkflowState(toSkill, updatedTarget, cwd);
|
|
185
|
+
|
|
186
|
+
if (isJson) {
|
|
187
|
+
console.log(JSON.stringify({
|
|
188
|
+
source: { path: sourcePath, state: sourceState },
|
|
189
|
+
target: { path: targetPath, state: updatedTarget }
|
|
190
|
+
}, null, 2));
|
|
191
|
+
} else {
|
|
192
|
+
console.log(`Handoff completed.`);
|
|
193
|
+
console.log(`Source (${skill}) state updated: ${sourcePath}`);
|
|
194
|
+
console.log(`Target (${toSkill}) state updated: ${targetPath}`);
|
|
195
|
+
}
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
|
|
5
|
+
export async function runStatusCommand(): Promise<void> {
|
|
6
|
+
const cwd = process.cwd();
|
|
7
|
+
const logPath = path.join(cwd, "logs", "evolution-log.json");
|
|
8
|
+
const perfPath = path.join(cwd, ".jeo", "state", "performance-metrics.json");
|
|
9
|
+
const planPath = path.join(cwd, ".specify", "plan.md");
|
|
10
|
+
|
|
11
|
+
console.log(chalk.bold("\n=== jeo Core Engine Status ==="));
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const planContent = await fs.readFile(planPath, "utf-8");
|
|
15
|
+
const tasks = planContent.match(/- \[[x ]\] .+/g) || [];
|
|
16
|
+
const completedTasks = tasks.filter(t => t.startsWith("- [x]")).length;
|
|
17
|
+
const totalTasks = tasks.length;
|
|
18
|
+
|
|
19
|
+
if (totalTasks > 0) {
|
|
20
|
+
const percentage = (completedTasks / totalTasks) * 100;
|
|
21
|
+
const barWidth = 30;
|
|
22
|
+
const filledWidth = Math.round((completedTasks / totalTasks) * barWidth);
|
|
23
|
+
const bar = chalk.green("█").repeat(filledWidth) + chalk.gray("░").repeat(barWidth - filledWidth);
|
|
24
|
+
|
|
25
|
+
console.log(chalk.bold("Overall Project Progress: [" + bar + "] " + percentage.toFixed(1) + "%"));
|
|
26
|
+
console.log(chalk.dim("(" + completedTasks + "/" + totalTasks + " tasks completed from plan.md)\n"));
|
|
27
|
+
}
|
|
28
|
+
} catch (e) {}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const content = await fs.readFile(logPath, "utf-8");
|
|
32
|
+
const logs = JSON.parse(content);
|
|
33
|
+
|
|
34
|
+
if (logs.length === 0) {
|
|
35
|
+
console.log("No evolution history found.");
|
|
36
|
+
} else {
|
|
37
|
+
const activeTask = logs.find((l: any) => l.status === "in_progress");
|
|
38
|
+
if (activeTask) {
|
|
39
|
+
console.log(chalk.yellow.bold("▶ ACTIVE EVOLUTION: ") + chalk.cyan(activeTask.target));
|
|
40
|
+
if (activeTask.stage) {
|
|
41
|
+
console.log(chalk.magenta(" Stage: ") + chalk.bold(activeTask.stage.toUpperCase()));
|
|
42
|
+
}
|
|
43
|
+
console.log(chalk.dim(" Started: " + activeTask.timestamp + "\n"));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log(chalk.bold("Recent Evolution Turns:"));
|
|
47
|
+
const recent = logs.slice(-5).reverse();
|
|
48
|
+
for (const entry of recent) {
|
|
49
|
+
const statusColor = entry.status === "success" ? chalk.green : (entry.status === "failed" ? chalk.red : chalk.yellow);
|
|
50
|
+
const stageLabel = entry.stage ? ` [${entry.stage.toUpperCase()}]` : "";
|
|
51
|
+
console.log("- " + chalk.dim("[" + new Date(entry.timestamp).toLocaleTimeString() + "]") + stageLabel.padEnd(16) + " " + statusColor(entry.status.toUpperCase().padEnd(11)) + " " + chalk.cyan(entry.target));
|
|
52
|
+
}
|
|
53
|
+
console.log("");
|
|
54
|
+
}
|
|
55
|
+
} catch (e) {
|
|
56
|
+
console.log("Status: Idle (No evolution logs)");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log(chalk.bold("=== Engine Performance Metrics ==="));
|
|
60
|
+
try {
|
|
61
|
+
const perfPathAlt = path.join(cwd, "logs", "performance-metrics.json");
|
|
62
|
+
let perfContent = "";
|
|
63
|
+
try {
|
|
64
|
+
perfContent = await fs.readFile(perfPath, "utf-8");
|
|
65
|
+
} catch {
|
|
66
|
+
perfContent = await fs.readFile(perfPathAlt, "utf-8");
|
|
67
|
+
}
|
|
68
|
+
const metrics = JSON.parse(perfContent);
|
|
69
|
+
if (metrics.length > 0) {
|
|
70
|
+
const recent = metrics.slice(-50);
|
|
71
|
+
const avgDuration = recent.reduce((sum: number, m: any) => sum + m.duration, 0) / recent.length;
|
|
72
|
+
const successRate = (recent.filter((m: any) => m.success).length / recent.length) * 100;
|
|
73
|
+
|
|
74
|
+
console.log("Average Tool Time: " + chalk.magenta(avgDuration.toFixed(0) + "ms"));
|
|
75
|
+
const successColor = successRate > 90 ? chalk.green : chalk.yellow;
|
|
76
|
+
console.log("Tool Success Rate: " + successColor(successRate.toFixed(1) + "%"));
|
|
77
|
+
} else {
|
|
78
|
+
console.log("No performance metrics collected yet.");
|
|
79
|
+
}
|
|
80
|
+
} catch (e) {
|
|
81
|
+
console.log("Performance metrics unavailable.");
|
|
82
|
+
}
|
|
83
|
+
console.log("");
|
|
84
|
+
}
|