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/ledger.ts
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* jeo ledger — cross-plan append-only ledger (ledger / review / cleanup).
|
|
3
|
+
*
|
|
4
|
+
* Rebranded from gjc/gajae-code → jeo/jeo-code. State lives under .jeo/.
|
|
5
|
+
* - Events appended to .jeo/ledger.jsonl (one JSON object per line).
|
|
6
|
+
* - State is ALWAYS derived by folding the event log.
|
|
7
|
+
* - Zero external dependencies (Node stdlib only).
|
|
8
|
+
*
|
|
9
|
+
* See ledger/schema.md for the event model.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as fs from "node:fs";
|
|
13
|
+
import * as path from "node:path";
|
|
14
|
+
|
|
15
|
+
const JEO_DIR = ".jeo";
|
|
16
|
+
const LEDGER = path.join(JEO_DIR, "ledger.jsonl");
|
|
17
|
+
|
|
18
|
+
type EventType =
|
|
19
|
+
| "plan_registered"
|
|
20
|
+
| "plan_reviewed"
|
|
21
|
+
| "goal_checkpointed"
|
|
22
|
+
| "cleanup_swept"
|
|
23
|
+
| "pr_linked";
|
|
24
|
+
|
|
25
|
+
interface LedgerEvent {
|
|
26
|
+
ts: string;
|
|
27
|
+
type: EventType;
|
|
28
|
+
planId: string;
|
|
29
|
+
[k: string]: unknown;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type LedgerEventInput = {
|
|
33
|
+
type: EventType;
|
|
34
|
+
planId: string;
|
|
35
|
+
[k: string]: unknown;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const REVIEW_STATUSES = ["CLEAR", "WATCH", "BLOCK"] as const;
|
|
39
|
+
const CHECKPOINT_STATUSES = ["complete", "failed"] as const;
|
|
40
|
+
|
|
41
|
+
function die(msg: string): never {
|
|
42
|
+
console.error(`jeo ledger: ${msg}`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function ensureInit(): void {
|
|
47
|
+
if (!fs.existsSync(LEDGER)) die(`no ledger at ${LEDGER} — run: jeo ledger init`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function append(ev: LedgerEventInput): LedgerEvent {
|
|
51
|
+
ensureInit();
|
|
52
|
+
const full: LedgerEvent = { ts: new Date().toISOString(), ...ev };
|
|
53
|
+
fs.appendFileSync(LEDGER, JSON.stringify(full) + "\n");
|
|
54
|
+
return full;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function readEvents(): LedgerEvent[] {
|
|
58
|
+
if (!fs.existsSync(LEDGER)) return [];
|
|
59
|
+
return fs
|
|
60
|
+
.readFileSync(LEDGER, "utf8")
|
|
61
|
+
.split("\n")
|
|
62
|
+
.filter((l) => l.trim().length > 0)
|
|
63
|
+
.map((l, i) => {
|
|
64
|
+
try {
|
|
65
|
+
return JSON.parse(l) as LedgerEvent;
|
|
66
|
+
} catch {
|
|
67
|
+
die(`corrupt ledger line ${i + 1}`);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function parseArgs(argv: string[]): { positionals: string[]; flags: Record<string, string> } {
|
|
73
|
+
const positionals: string[] = [];
|
|
74
|
+
const flags: Record<string, string> = {};
|
|
75
|
+
for (let i = 0; i < argv.length; i++) {
|
|
76
|
+
const a = argv[i];
|
|
77
|
+
if (a.startsWith("--")) {
|
|
78
|
+
const key = a.slice(2);
|
|
79
|
+
const next = argv[i + 1];
|
|
80
|
+
if (next === undefined || next.startsWith("--")) flags[key] = "true";
|
|
81
|
+
else {
|
|
82
|
+
flags[key] = next;
|
|
83
|
+
i++;
|
|
84
|
+
}
|
|
85
|
+
} else positionals.push(a);
|
|
86
|
+
}
|
|
87
|
+
return { positionals, flags };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function requirePlanId(positionals: string[]): string {
|
|
91
|
+
const id = positionals[0];
|
|
92
|
+
if (!id) die("missing <planId>");
|
|
93
|
+
return id;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface PlanState {
|
|
97
|
+
planId: string;
|
|
98
|
+
title: string;
|
|
99
|
+
brief?: string;
|
|
100
|
+
registeredAt?: string;
|
|
101
|
+
review?: { status: string; evidence: string; at: string };
|
|
102
|
+
goals: Record<string, { status: string; evidence: string; at: string }>;
|
|
103
|
+
swept: { evidence: string; at: string }[];
|
|
104
|
+
prs: string[];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function deriveState(events: LedgerEvent[]): Record<string, PlanState> {
|
|
108
|
+
const plans: Record<string, PlanState> = {};
|
|
109
|
+
const ensure = (id: string): PlanState => {
|
|
110
|
+
if (!plans[id]) plans[id] = { planId: id, title: id, goals: {}, swept: [], prs: [] };
|
|
111
|
+
return plans[id];
|
|
112
|
+
};
|
|
113
|
+
for (const ev of events) {
|
|
114
|
+
const p = ensure(ev.planId);
|
|
115
|
+
switch (ev.type) {
|
|
116
|
+
case "plan_registered":
|
|
117
|
+
p.title = (ev.title as string) ?? ev.planId;
|
|
118
|
+
if (ev.brief) p.brief = ev.brief as string;
|
|
119
|
+
p.registeredAt = ev.ts;
|
|
120
|
+
break;
|
|
121
|
+
case "plan_reviewed":
|
|
122
|
+
p.review = { status: ev.status as string, evidence: (ev.evidence as string) ?? "", at: ev.ts };
|
|
123
|
+
break;
|
|
124
|
+
case "goal_checkpointed":
|
|
125
|
+
p.goals[ev.goal as string] = {
|
|
126
|
+
status: ev.status as string,
|
|
127
|
+
evidence: (ev.evidence as string) ?? "",
|
|
128
|
+
at: ev.ts,
|
|
129
|
+
};
|
|
130
|
+
break;
|
|
131
|
+
case "cleanup_swept":
|
|
132
|
+
p.swept.push({ evidence: (ev.evidence as string) ?? "", at: ev.ts });
|
|
133
|
+
break;
|
|
134
|
+
case "pr_linked":
|
|
135
|
+
if (ev.pr) p.prs.push(ev.pr as string);
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return plans;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** A plan is "verified" when reviewed CLEAR, all goals complete, and swept at least once. */
|
|
143
|
+
function planVerdict(p: PlanState): string {
|
|
144
|
+
const goalList = Object.values(p.goals);
|
|
145
|
+
const allComplete = goalList.length > 0 && goalList.every((g) => g.status === "complete");
|
|
146
|
+
if (p.review?.status === "CLEAR" && allComplete && p.swept.length > 0) return "verified";
|
|
147
|
+
if (p.review?.status === "BLOCK") return "blocked";
|
|
148
|
+
if (goalList.some((g) => g.status === "failed")) return "failed";
|
|
149
|
+
return "in_progress";
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function cmdInit(): void {
|
|
153
|
+
fs.mkdirSync(JEO_DIR, { recursive: true });
|
|
154
|
+
if (!fs.existsSync(LEDGER)) {
|
|
155
|
+
fs.writeFileSync(LEDGER, "");
|
|
156
|
+
console.log(`jeo ledger: initialized ${LEDGER}`);
|
|
157
|
+
} else {
|
|
158
|
+
console.log(`jeo ledger: already exists at ${LEDGER}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function cmdStatus(flags: Record<string, string>): void {
|
|
163
|
+
const plans = deriveState(readEvents());
|
|
164
|
+
const list = Object.values(plans).map((p) => ({
|
|
165
|
+
planId: p.planId,
|
|
166
|
+
title: p.title,
|
|
167
|
+
verdict: planVerdict(p),
|
|
168
|
+
review: p.review?.status ?? "none",
|
|
169
|
+
goals: Object.fromEntries(Object.entries(p.goals).map(([g, v]) => [g, v.status])),
|
|
170
|
+
sweeps: p.swept.length,
|
|
171
|
+
prs: p.prs,
|
|
172
|
+
}));
|
|
173
|
+
if (flags.json === "true") {
|
|
174
|
+
console.log(JSON.stringify({ plans: list }, null, 2));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (list.length === 0) {
|
|
178
|
+
console.log("jeo ledger: no plans registered yet");
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
console.log("jeo ledger status\n");
|
|
182
|
+
for (const p of list) {
|
|
183
|
+
const goalStr =
|
|
184
|
+
Object.keys(p.goals).length === 0
|
|
185
|
+
? "—"
|
|
186
|
+
: Object.entries(p.goals).map(([g, s]) => `${g}:${s}`).join(" ");
|
|
187
|
+
console.log(`● ${p.planId} [${p.verdict}] ${p.title}`);
|
|
188
|
+
console.log(` review=${p.review} goals=${goalStr} sweeps=${p.sweeps} prs=${p.prs.length}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function runLedger(argv: string[]): void {
|
|
193
|
+
const [cmd, ...rest] = argv;
|
|
194
|
+
const { positionals, flags } = parseArgs(rest);
|
|
195
|
+
switch (cmd) {
|
|
196
|
+
case "init":
|
|
197
|
+
cmdInit();
|
|
198
|
+
break;
|
|
199
|
+
case "register": {
|
|
200
|
+
const planId = requirePlanId(positionals);
|
|
201
|
+
const ev = append({
|
|
202
|
+
type: "plan_registered",
|
|
203
|
+
planId,
|
|
204
|
+
title: flags.title ?? planId,
|
|
205
|
+
...(flags.brief ? { brief: flags.brief } : {}),
|
|
206
|
+
});
|
|
207
|
+
console.log(`jeo ledger: registered ${planId} @ ${ev.ts}`);
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
case "review": {
|
|
211
|
+
const planId = requirePlanId(positionals);
|
|
212
|
+
const status = (flags.status ?? "").toUpperCase();
|
|
213
|
+
if (!REVIEW_STATUSES.includes(status as (typeof REVIEW_STATUSES)[number]))
|
|
214
|
+
die(`--status must be one of ${REVIEW_STATUSES.join("|")}`);
|
|
215
|
+
if (!flags.evidence) die("review requires --evidence");
|
|
216
|
+
append({ type: "plan_reviewed", planId, status, evidence: flags.evidence });
|
|
217
|
+
console.log(`jeo ledger: reviewed ${planId} = ${status}`);
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
case "checkpoint": {
|
|
221
|
+
const planId = requirePlanId(positionals);
|
|
222
|
+
const status = (flags.status ?? "").toLowerCase();
|
|
223
|
+
if (!CHECKPOINT_STATUSES.includes(status as (typeof CHECKPOINT_STATUSES)[number]))
|
|
224
|
+
die(`--status must be one of ${CHECKPOINT_STATUSES.join("|")}`);
|
|
225
|
+
if (!flags.goal) die("checkpoint requires --goal");
|
|
226
|
+
if (!flags.evidence) die("checkpoint requires --evidence");
|
|
227
|
+
append({ type: "goal_checkpointed", planId, goal: flags.goal, status, evidence: flags.evidence });
|
|
228
|
+
console.log(`jeo ledger: checkpoint ${planId}/${flags.goal} = ${status}`);
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
case "sweep": {
|
|
232
|
+
const planId = requirePlanId(positionals);
|
|
233
|
+
if (!flags.evidence) die("sweep requires --evidence");
|
|
234
|
+
append({ type: "cleanup_swept", planId, evidence: flags.evidence });
|
|
235
|
+
console.log(`jeo ledger: swept ${planId}`);
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
case "link": {
|
|
239
|
+
const planId = requirePlanId(positionals);
|
|
240
|
+
if (!flags.pr) die("link requires --pr");
|
|
241
|
+
append({ type: "pr_linked", planId, pr: flags.pr });
|
|
242
|
+
console.log(`jeo ledger: linked ${flags.pr} to ${planId}`);
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
case "status":
|
|
246
|
+
cmdStatus(flags);
|
|
247
|
+
break;
|
|
248
|
+
case undefined:
|
|
249
|
+
case "help":
|
|
250
|
+
case "--help":
|
|
251
|
+
console.log(
|
|
252
|
+
[
|
|
253
|
+
"jeo ledger — cross-plan append-only ledger (ledger/review/cleanup)",
|
|
254
|
+
"",
|
|
255
|
+
" init",
|
|
256
|
+
" register <planId> --title <t> [--brief <b>]",
|
|
257
|
+
" review <planId> --status CLEAR|WATCH|BLOCK --evidence <e>",
|
|
258
|
+
" checkpoint <planId> --goal <id> --status complete|failed --evidence <e>",
|
|
259
|
+
" sweep <planId> --evidence <e>",
|
|
260
|
+
" link <planId> --pr <url>",
|
|
261
|
+
" status [--json]",
|
|
262
|
+
].join("\n"),
|
|
263
|
+
);
|
|
264
|
+
break;
|
|
265
|
+
default:
|
|
266
|
+
die(`unknown subcommand: ${cmd} (try: jeo ledger help)`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (import.meta.main) runLedger(process.argv.slice(2));
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<!-- Parent: ../AGENTS.md -->
|
|
2
|
+
<!-- Generated: 2026-06-11 | Updated: 2026-06-11 -->
|
|
3
|
+
|
|
4
|
+
# mcp
|
|
5
|
+
|
|
6
|
+
## Purpose
|
|
7
|
+
Model Context Protocol (MCP) integration, allowing `jeo-code` to expose tools or consume context from external MCP servers.
|
|
8
|
+
|
|
9
|
+
## Key Files
|
|
10
|
+
| File | Description |
|
|
11
|
+
|------|-------------|
|
|
12
|
+
| `server.ts` | MCP server implementation (if exposing tools) |
|
|
13
|
+
| `client.ts` | MCP client implementation (for consuming external tools) |
|
|
14
|
+
|
|
15
|
+
## Subdirectories
|
|
16
|
+
*(None)*
|
|
17
|
+
|
|
18
|
+
## For AI Agents
|
|
19
|
+
|
|
20
|
+
### Working In This Directory
|
|
21
|
+
- Adhere strictly to the MCP specification.
|
|
22
|
+
- Handle JSON-RPC serialization and deserialization safely.
|
|
23
|
+
|
|
24
|
+
### Testing Requirements
|
|
25
|
+
- Unit test protocol messaging.
|
|
26
|
+
|
|
27
|
+
### Common Patterns
|
|
28
|
+
- Transport layer abstraction (stdio vs SSE).
|
|
29
|
+
|
|
30
|
+
## Dependencies
|
|
31
|
+
|
|
32
|
+
### Internal
|
|
33
|
+
- Connects external capabilities into `src/agent/tools.ts`.
|
|
34
|
+
|
|
35
|
+
### External
|
|
36
|
+
- Protocol dependencies if not implemented natively.
|
|
37
|
+
|
|
38
|
+
<!-- MANUAL: -->
|
package/src/mcp/server.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from "./protocol";
|
|
13
13
|
import { TOOLS } from "./tools";
|
|
14
14
|
|
|
15
|
-
const SERVER_INFO = { name: "
|
|
15
|
+
const SERVER_INFO = { name: "jeo-mcp", version: "0.1.0" };
|
|
16
16
|
const PROTOCOL_VERSION = "2024-11-05";
|
|
17
17
|
|
|
18
18
|
interface ServerOptions {
|
|
@@ -20,34 +20,130 @@ interface ServerOptions {
|
|
|
20
20
|
log?: (line: string) => void;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
interface IncomingMessage {
|
|
24
|
+
payload: string;
|
|
25
|
+
framed: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
23
28
|
export async function runMcpServer(options: ServerOptions = {}): Promise<void> {
|
|
24
29
|
const tools = options.tools ?? TOOLS;
|
|
25
30
|
const log = options.log ?? (line => process.stderr.write(`${line}\n`));
|
|
26
|
-
log(`
|
|
31
|
+
log(`jeo-mcp v${SERVER_INFO.version} listening on stdio (${tools.length} tools)`);
|
|
27
32
|
|
|
28
33
|
let buffer = "";
|
|
29
34
|
const decoder = new TextDecoder();
|
|
30
35
|
|
|
31
36
|
for await (const chunk of (process.stdin as unknown as AsyncIterable<Uint8Array>)) {
|
|
32
37
|
buffer += decoder.decode(chunk, { stream: true });
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
const drained = drainIncomingMessages(buffer);
|
|
39
|
+
buffer = drained.remaining;
|
|
40
|
+
for (const message of drained.messages) {
|
|
41
|
+
let response: JsonRpcResponse | null = null;
|
|
42
|
+
try {
|
|
43
|
+
response = await handleLine(message.payload, tools);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
// A bad request must never kill the stdin read loop (the whole server).
|
|
46
|
+
response = fail(null, INTERNAL_ERROR, (err as Error)?.message ?? "internal error");
|
|
47
|
+
}
|
|
48
|
+
if (response) writeResponse(response, message.framed);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function drainIncomingMessages(buffer: string): { messages: IncomingMessage[]; remaining: string } {
|
|
54
|
+
const messages: IncomingMessage[] = [];
|
|
55
|
+
let remaining = buffer;
|
|
56
|
+
|
|
57
|
+
while (remaining.length > 0) {
|
|
58
|
+
const frame = readContentLengthFrame(remaining);
|
|
59
|
+
if (frame === "incomplete") break;
|
|
60
|
+
if (frame) {
|
|
61
|
+
messages.push({ payload: frame.payload, framed: true });
|
|
62
|
+
remaining = frame.remaining;
|
|
63
|
+
continue;
|
|
40
64
|
}
|
|
65
|
+
|
|
66
|
+
const newlineIndex = remaining.indexOf("\n");
|
|
67
|
+
if (newlineIndex === -1) break;
|
|
68
|
+
const line = remaining.slice(0, newlineIndex).trim();
|
|
69
|
+
remaining = remaining.slice(newlineIndex + 1);
|
|
70
|
+
if (line) messages.push({ payload: line, framed: false });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return { messages, remaining };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function readContentLengthFrame(buffer: string): { payload: string; remaining: string } | "incomplete" | null {
|
|
77
|
+
const headerEnd = findHeaderEnd(buffer);
|
|
78
|
+
if (headerEnd === null) return looksLikeHeaderPrefix(buffer) ? "incomplete" : null;
|
|
79
|
+
|
|
80
|
+
const header = buffer.slice(0, headerEnd.index);
|
|
81
|
+
const contentLength = parseContentLength(header);
|
|
82
|
+
if (contentLength === null) return null;
|
|
83
|
+
|
|
84
|
+
const bodyStart = headerEnd.index + headerEnd.separator.length;
|
|
85
|
+
const body = buffer.slice(bodyStart);
|
|
86
|
+
const bodyByteLength = Buffer.byteLength(body, "utf8");
|
|
87
|
+
if (bodyByteLength < contentLength) return "incomplete";
|
|
88
|
+
|
|
89
|
+
const split = splitAtUtf8ByteLength(body, contentLength);
|
|
90
|
+
if (!split) return null;
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
payload: split.prefix,
|
|
94
|
+
remaining: split.suffix,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function splitAtUtf8ByteLength(input: string, byteLength: number): { prefix: string; suffix: string } | null {
|
|
99
|
+
if (byteLength === 0) return { prefix: "", suffix: input };
|
|
100
|
+
|
|
101
|
+
let bytes = 0;
|
|
102
|
+
let endIndex = 0;
|
|
103
|
+
for (const char of input) {
|
|
104
|
+
bytes += Buffer.byteLength(char, "utf8");
|
|
105
|
+
endIndex += char.length;
|
|
106
|
+
if (bytes === byteLength) return { prefix: input.slice(0, endIndex), suffix: input.slice(endIndex) };
|
|
107
|
+
if (bytes > byteLength) return null;
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function findHeaderEnd(buffer: string): { index: number; separator: "\r\n\r\n" | "\n\n" } | null {
|
|
113
|
+
const crlf = buffer.indexOf("\r\n\r\n");
|
|
114
|
+
const lf = buffer.indexOf("\n\n");
|
|
115
|
+
if (crlf === -1 && lf === -1) return null;
|
|
116
|
+
if (crlf !== -1 && (lf === -1 || crlf < lf)) return { index: crlf, separator: "\r\n\r\n" };
|
|
117
|
+
return { index: lf, separator: "\n\n" };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function looksLikeHeaderPrefix(buffer: string): boolean {
|
|
121
|
+
return /^content-length\s*:/i.test(buffer) || /^[A-Za-z-]+\s*:/i.test(buffer);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function parseContentLength(header: string): number | null {
|
|
125
|
+
for (const line of header.split(/\r?\n/)) {
|
|
126
|
+
const match = /^content-length\s*:\s*(\d+)\s*$/i.exec(line);
|
|
127
|
+
if (!match) continue;
|
|
128
|
+
const length = Number(match[1]);
|
|
129
|
+
return Number.isSafeInteger(length) && length >= 0 ? length : null;
|
|
41
130
|
}
|
|
131
|
+
return null;
|
|
42
132
|
}
|
|
43
133
|
|
|
44
|
-
async function handleLine(line: string, tools: ToolDefinition[]): Promise<JsonRpcResponse | null> {
|
|
45
|
-
let
|
|
134
|
+
export async function handleLine(line: string, tools: ToolDefinition[]): Promise<JsonRpcResponse | null> {
|
|
135
|
+
let parsed: unknown;
|
|
46
136
|
try {
|
|
47
|
-
|
|
137
|
+
parsed = JSON.parse(line);
|
|
48
138
|
} catch {
|
|
49
139
|
return fail(null, PARSE_ERROR, "invalid JSON");
|
|
50
140
|
}
|
|
141
|
+
// `null`, arrays, and primitives are valid JSON but not JSON-RPC requests — accessing
|
|
142
|
+
// `req.jsonrpc` on them would throw and (without the loop guard) kill the server.
|
|
143
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
144
|
+
return fail(null, INVALID_REQUEST, "malformed JSON-RPC request");
|
|
145
|
+
}
|
|
146
|
+
const req = parsed as JsonRpcRequest;
|
|
51
147
|
if (req.jsonrpc !== "2.0" || typeof req.method !== "string") {
|
|
52
148
|
return fail(req.id ?? null, INVALID_REQUEST, "malformed JSON-RPC request");
|
|
53
149
|
}
|
|
@@ -92,6 +188,11 @@ async function handleToolCall(req: JsonRpcRequest, tools: ToolDefinition[]): Pro
|
|
|
92
188
|
return ok(req.id ?? null, result);
|
|
93
189
|
}
|
|
94
190
|
|
|
95
|
-
function writeResponse(response: JsonRpcResponse): void {
|
|
96
|
-
|
|
191
|
+
function writeResponse(response: JsonRpcResponse, framed: boolean): void {
|
|
192
|
+
const json = JSON.stringify(response);
|
|
193
|
+
if (framed) {
|
|
194
|
+
process.stdout.write(`Content-Length: ${Buffer.byteLength(json, "utf8")}\r\n\r\n${json}`);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
process.stdout.write(`${json}\n`);
|
|
97
198
|
}
|
package/src/mcp/tools.ts
CHANGED
|
@@ -2,17 +2,36 @@ import { resolveProvider } from "../ai";
|
|
|
2
2
|
import { resolveCredential, type AuthProvider } from "../auth";
|
|
3
3
|
import { readGlobalConfig } from "../agent/state";
|
|
4
4
|
import type { ToolDefinition, ToolResult } from "./protocol";
|
|
5
|
+
import { effectiveCredentialForProvider } from "../ai/model-manager";
|
|
6
|
+
import { jeoEnv } from "../util/env";
|
|
5
7
|
|
|
6
|
-
const AUTH_PROVIDERS: AuthProvider[] = ["anthropic", "openai", "gemini"]
|
|
8
|
+
const AUTH_PROVIDERS: AuthProvider[] = ["anthropic", "openai", "gemini", "antigravity"]
|
|
7
9
|
|
|
8
10
|
function textResult(text: string, isError = false): ToolResult {
|
|
9
11
|
return { content: [{ type: "text", text }], isError };
|
|
10
12
|
}
|
|
11
13
|
|
|
14
|
+
/**
|
|
15
|
+
* The credential kind jeo would ACTUALLY use for a provider (the effective credential):
|
|
16
|
+
* an API key wins over OAuth, and an OAuth-only login the bundled adapter can't serve
|
|
17
|
+
* (e.g. Gemini Cloud Code Assist) reports "none". This matches the real call path, unlike
|
|
18
|
+
* the bare resolveCredential() kind.
|
|
19
|
+
*/
|
|
20
|
+
async function effectiveKind(provider: AuthProvider): Promise<string> {
|
|
21
|
+
const cred = await resolveCredential(provider);
|
|
22
|
+
if (cred.kind === "none") return "none";
|
|
23
|
+
try {
|
|
24
|
+
const cfg = await readGlobalConfig();
|
|
25
|
+
return effectiveCredentialForProvider(provider, cred, cfg, provider).kind;
|
|
26
|
+
} catch {
|
|
27
|
+
return "none"; // OAuth-only but unusable by the bundled adapter
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
12
31
|
export const TOOLS: ToolDefinition[] = [
|
|
13
32
|
{
|
|
14
|
-
name: "
|
|
15
|
-
description: "Given a model name (e.g. 'claude-3-5-sonnet', 'openai/mock-1', 'gemini-2.0-flash', 'ollama/llama3'), return which provider
|
|
33
|
+
name: "jeo_resolve_provider",
|
|
34
|
+
description: "Given a model name (e.g. 'claude-3-5-sonnet', 'openai/mock-1', 'gemini-2.0-flash', 'ollama/llama3'), return which provider jeo would route the call to.",
|
|
16
35
|
inputSchema: {
|
|
17
36
|
type: "object",
|
|
18
37
|
properties: {
|
|
@@ -27,7 +46,7 @@ export const TOOLS: ToolDefinition[] = [
|
|
|
27
46
|
},
|
|
28
47
|
},
|
|
29
48
|
{
|
|
30
|
-
name: "
|
|
49
|
+
name: "jeo_credential_status",
|
|
31
50
|
description: "Report which auth method is configured for a provider (oauth|api_key|none). Does not reveal the secret.",
|
|
32
51
|
inputSchema: {
|
|
33
52
|
type: "object",
|
|
@@ -41,13 +60,14 @@ export const TOOLS: ToolDefinition[] = [
|
|
|
41
60
|
if (!AUTH_PROVIDERS.includes(provider as AuthProvider)) {
|
|
42
61
|
return textResult(`error: provider must be one of ${AUTH_PROVIDERS.join(", ")}`, true);
|
|
43
62
|
}
|
|
44
|
-
const
|
|
45
|
-
|
|
63
|
+
const configured = (await resolveCredential(provider as AuthProvider)).kind;
|
|
64
|
+
const usable = await effectiveKind(provider as AuthProvider);
|
|
65
|
+
return textResult(JSON.stringify({ provider, kind: usable, configured }));
|
|
46
66
|
},
|
|
47
67
|
},
|
|
48
68
|
{
|
|
49
|
-
name: "
|
|
50
|
-
description: "Return a redacted snapshot of
|
|
69
|
+
name: "jeo_config_snapshot",
|
|
70
|
+
description: "Return a redacted snapshot of jeo's current config: default model, openai/ollama base URLs, and which providers have credentials.",
|
|
51
71
|
inputSchema: { type: "object", properties: {} },
|
|
52
72
|
async handler() {
|
|
53
73
|
const cfg = await readGlobalConfig();
|
|
@@ -57,17 +77,17 @@ export const TOOLS: ToolDefinition[] = [
|
|
|
57
77
|
openaiBaseUrl: cfg.openaiBaseUrl ?? null,
|
|
58
78
|
ollamaBaseUrl: cfg.ollamaBaseUrl ?? "http://localhost:11434",
|
|
59
79
|
credentials: {
|
|
60
|
-
anthropic:
|
|
61
|
-
openai:
|
|
62
|
-
gemini:
|
|
80
|
+
anthropic: await effectiveKind("anthropic"),
|
|
81
|
+
openai: await effectiveKind("openai"),
|
|
82
|
+
gemini: await effectiveKind("gemini"),
|
|
63
83
|
},
|
|
64
84
|
};
|
|
65
85
|
return textResult(JSON.stringify(snapshot, null, 2));
|
|
66
86
|
},
|
|
67
87
|
},
|
|
68
88
|
{
|
|
69
|
-
name: "
|
|
70
|
-
description: "Run
|
|
89
|
+
name: "jeo_doctor",
|
|
90
|
+
description: "Run jeo's health probe and return a structured report of provider connectivity. Same probe used by 'jeo doctor' CLI.",
|
|
71
91
|
inputSchema: { type: "object", properties: {} },
|
|
72
92
|
async handler() {
|
|
73
93
|
const { runDoctorCommand } = await import("../commands/doctor");
|
|
@@ -102,8 +122,8 @@ async function captureCommand(run: () => Promise<void>): Promise<string> {
|
|
|
102
122
|
|
|
103
123
|
const PIPELINE_TOOLS: ToolDefinition[] = [
|
|
104
124
|
{
|
|
105
|
-
name: "
|
|
106
|
-
description: "DANGER: WRITES FILES + BURNS LLM CREDITS. Runs Socratic requirements interview. Writes .
|
|
125
|
+
name: "jeo_deep_interview",
|
|
126
|
+
description: "DANGER: WRITES FILES + BURNS LLM CREDITS. Runs Socratic requirements interview. Writes .jeo/spec.json. Requires default model credential.",
|
|
107
127
|
inputSchema: {
|
|
108
128
|
type: "object",
|
|
109
129
|
properties: {
|
|
@@ -120,8 +140,8 @@ const PIPELINE_TOOLS: ToolDefinition[] = [
|
|
|
120
140
|
},
|
|
121
141
|
},
|
|
122
142
|
{
|
|
123
|
-
name: "
|
|
124
|
-
description: "DANGER: WRITES FILES + BURNS LLM CREDITS. Reads .
|
|
143
|
+
name: "jeo_ralplan",
|
|
144
|
+
description: "DANGER: WRITES FILES + BURNS LLM CREDITS. Reads .jeo/spec.json, writes .jeo/plan.yaml via Planner/Architect/Critic.",
|
|
125
145
|
inputSchema: { type: "object", properties: {} },
|
|
126
146
|
async handler() {
|
|
127
147
|
const { runRalplanCommand } = await import("../commands/ralplan");
|
|
@@ -130,8 +150,8 @@ const PIPELINE_TOOLS: ToolDefinition[] = [
|
|
|
130
150
|
},
|
|
131
151
|
},
|
|
132
152
|
{
|
|
133
|
-
name: "
|
|
134
|
-
description: "DANGER: WRITES FILES + EDITS CODE + BURNS LLM CREDITS. Executes .
|
|
153
|
+
name: "jeo_team",
|
|
154
|
+
description: "DANGER: WRITES FILES + EDITS CODE + BURNS LLM CREDITS. Executes .jeo/plan.yaml via Executor subagent. Modifies the working tree.",
|
|
135
155
|
inputSchema: { type: "object", properties: {} },
|
|
136
156
|
async handler() {
|
|
137
157
|
const { runTeamCommand } = await import("../commands/team");
|
|
@@ -140,8 +160,8 @@ const PIPELINE_TOOLS: ToolDefinition[] = [
|
|
|
140
160
|
},
|
|
141
161
|
},
|
|
142
162
|
{
|
|
143
|
-
name: "
|
|
144
|
-
description: "DANGER: BURNS LLM CREDITS. Runs acceptance verification against .
|
|
163
|
+
name: "jeo_ultragoal",
|
|
164
|
+
description: "DANGER: BURNS LLM CREDITS. Runs acceptance verification against .jeo/spec.json + .jeo/plan.yaml.",
|
|
145
165
|
inputSchema: { type: "object", properties: {} },
|
|
146
166
|
async handler() {
|
|
147
167
|
const { runUltragoalCommand } = await import("../commands/ultragoal");
|
|
@@ -151,6 +171,6 @@ const PIPELINE_TOOLS: ToolDefinition[] = [
|
|
|
151
171
|
},
|
|
152
172
|
];
|
|
153
173
|
|
|
154
|
-
if (
|
|
174
|
+
if (jeoEnv("MCP_PIPELINE") === "1") {
|
|
155
175
|
TOOLS.push(...PIPELINE_TOOLS);
|
|
156
176
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<!-- Parent: ../AGENTS.md -->
|
|
2
|
+
<!-- Generated: 2026-06-11 | Updated: 2026-06-11 -->
|
|
3
|
+
|
|
4
|
+
# prompts
|
|
5
|
+
|
|
6
|
+
## Purpose
|
|
7
|
+
Bundled system prompts, role definitions, and workflow skill documentation for the agent.
|
|
8
|
+
|
|
9
|
+
## Key Files
|
|
10
|
+
| File | Description |
|
|
11
|
+
|------|-------------|
|
|
12
|
+
| `system.md` | Core system prompt template |
|
|
13
|
+
|
|
14
|
+
## Subdirectories
|
|
15
|
+
| Directory | Purpose |
|
|
16
|
+
|-----------|---------|
|
|
17
|
+
| `agents/` | Role-specific prompts (executor, planner, architect, critic) (see `agents/AGENTS.md`) |
|
|
18
|
+
| `skills/` | Bundled workflow skills (deep-interview, ralplan, etc.) (see `skills/AGENTS.md`) |
|
|
19
|
+
|
|
20
|
+
## For AI Agents
|
|
21
|
+
|
|
22
|
+
### Working In This Directory
|
|
23
|
+
- Prompts should be clear, concise, and definitive.
|
|
24
|
+
- Updates here directly alter the AI's behavior and personality.
|
|
25
|
+
- Do not remove existing constraints without explicit reason.
|
|
26
|
+
|
|
27
|
+
### Testing Requirements
|
|
28
|
+
- Prompt changes should be verified via end-to-end testing or `bun run run-evolution.sh`.
|
|
29
|
+
|
|
30
|
+
### Common Patterns
|
|
31
|
+
- XML-like tags for structural organization within prompts.
|
|
32
|
+
|
|
33
|
+
## Dependencies
|
|
34
|
+
|
|
35
|
+
### Internal
|
|
36
|
+
- Injected by `src/agent/session.ts` and `src/skills/`.
|
|
37
|
+
|
|
38
|
+
### External
|
|
39
|
+
*(None)*
|
|
40
|
+
|
|
41
|
+
<!-- MANUAL: -->
|