jeo-code 0.1.0 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/README.ja.md +160 -0
  2. package/README.ko.md +160 -0
  3. package/README.md +115 -297
  4. package/README.zh.md +160 -0
  5. package/package.json +11 -6
  6. package/scripts/install.sh +28 -28
  7. package/scripts/uninstall.sh +17 -15
  8. package/src/AGENTS.md +50 -0
  9. package/src/agent/AGENTS.md +49 -0
  10. package/src/agent/bash-fixups.ts +103 -0
  11. package/src/agent/compaction.ts +410 -19
  12. package/src/agent/config-schema.ts +119 -5
  13. package/src/agent/context-files.ts +314 -17
  14. package/src/agent/dev/AGENTS.md +36 -0
  15. package/src/agent/dev/advanced-analyzer.ts +12 -0
  16. package/src/agent/dev/evolution-bridge.ts +82 -0
  17. package/src/agent/dev/evolution-logger.ts +41 -0
  18. package/src/agent/dev/self-analysis.ts +64 -0
  19. package/src/agent/dev/self-improve.ts +24 -0
  20. package/src/agent/dev/spec-automation.ts +49 -0
  21. package/src/agent/engine.ts +808 -54
  22. package/src/agent/hooks.ts +273 -0
  23. package/src/agent/loop.ts +21 -1
  24. package/src/agent/memory.ts +201 -0
  25. package/src/agent/model-recency.ts +32 -0
  26. package/src/agent/output-minimizer.ts +108 -0
  27. package/src/agent/output-util.ts +64 -0
  28. package/src/agent/plan.ts +187 -0
  29. package/src/agent/seed.ts +52 -0
  30. package/src/agent/session.ts +235 -21
  31. package/src/agent/state.ts +286 -39
  32. package/src/agent/step-budget.ts +232 -0
  33. package/src/agent/subagents.ts +223 -26
  34. package/src/agent/task-tool.ts +272 -0
  35. package/src/agent/todo-tool.ts +87 -0
  36. package/src/agent/tokenizer.ts +117 -0
  37. package/src/agent/tool-registry.ts +54 -0
  38. package/src/agent/tools.ts +624 -103
  39. package/src/agent/web-search.ts +538 -0
  40. package/src/ai/AGENTS.md +44 -0
  41. package/src/ai/index.ts +1 -0
  42. package/src/ai/model-catalog-compat.ts +3 -1
  43. package/src/ai/model-catalog.ts +74 -9
  44. package/src/ai/model-discovery.ts +215 -17
  45. package/src/ai/model-manager.ts +346 -32
  46. package/src/ai/model-picker.ts +1 -1
  47. package/src/ai/model-registry.ts +4 -2
  48. package/src/ai/pricing.ts +84 -0
  49. package/src/ai/provider-registry.ts +23 -0
  50. package/src/ai/provider-status.ts +60 -16
  51. package/src/ai/providers/AGENTS.md +42 -0
  52. package/src/ai/providers/anthropic.ts +250 -31
  53. package/src/ai/providers/antigravity.ts +219 -0
  54. package/src/ai/providers/errors.ts +15 -1
  55. package/src/ai/providers/gemini.ts +196 -13
  56. package/src/ai/providers/ollama.ts +37 -7
  57. package/src/ai/providers/openai-responses.ts +173 -0
  58. package/src/ai/providers/openai.ts +64 -12
  59. package/src/ai/sse.ts +4 -1
  60. package/src/ai/types.ts +18 -1
  61. package/src/auth/AGENTS.md +41 -0
  62. package/src/auth/callback-server.ts +6 -1
  63. package/src/auth/flows/AGENTS.md +32 -0
  64. package/src/auth/flows/antigravity.ts +151 -0
  65. package/src/auth/flows/google-project.ts +190 -0
  66. package/src/auth/flows/google.ts +39 -18
  67. package/src/auth/flows/index.ts +15 -5
  68. package/src/auth/flows/openai.ts +2 -2
  69. package/src/auth/oauth.ts +8 -0
  70. package/src/auth/refresh.ts +44 -27
  71. package/src/auth/storage.ts +149 -26
  72. package/src/auth/types.ts +1 -1
  73. package/src/autopilot.ts +362 -0
  74. package/src/bun-imports.d.ts +4 -0
  75. package/src/cli/AGENTS.md +39 -0
  76. package/src/cli/runner.ts +148 -14
  77. package/src/cli.ts +13 -4
  78. package/src/commands/AGENTS.md +40 -0
  79. package/src/commands/approve.ts +62 -3
  80. package/src/commands/auth.ts +167 -25
  81. package/src/commands/chat.ts +37 -8
  82. package/src/commands/deep-interview.ts +633 -175
  83. package/src/commands/doctor.ts +84 -37
  84. package/src/commands/evolve-core.ts +18 -0
  85. package/src/commands/evolve.ts +2 -1
  86. package/src/commands/export.ts +176 -0
  87. package/src/commands/gjc.ts +52 -0
  88. package/src/commands/launch.ts +3549 -240
  89. package/src/commands/mcp.ts +3 -3
  90. package/src/commands/ooo-seed.ts +19 -0
  91. package/src/commands/ralplan.ts +253 -35
  92. package/src/commands/resume.ts +1 -1
  93. package/src/commands/session.ts +183 -0
  94. package/src/commands/setup-helpers.ts +10 -3
  95. package/src/commands/setup.ts +57 -16
  96. package/src/commands/skills.ts +78 -18
  97. package/src/commands/state.ts +198 -0
  98. package/src/commands/status.ts +84 -0
  99. package/src/commands/team.ts +340 -212
  100. package/src/commands/ultragoal.ts +122 -61
  101. package/src/commands/update.ts +244 -0
  102. package/src/ledger.ts +270 -0
  103. package/src/mcp/AGENTS.md +38 -0
  104. package/src/mcp/server.ts +115 -14
  105. package/src/mcp/tools.ts +42 -22
  106. package/src/md-modules.d.ts +4 -0
  107. package/src/prompts/AGENTS.md +41 -0
  108. package/src/prompts/agents/AGENTS.md +35 -0
  109. package/src/prompts/agents/architect.md +35 -0
  110. package/src/prompts/agents/critic.md +37 -0
  111. package/src/prompts/agents/executor.md +36 -0
  112. package/src/prompts/agents/planner.md +37 -0
  113. package/src/prompts/skills/AGENTS.md +36 -0
  114. package/src/prompts/skills/deep-dive/AGENTS.md +31 -0
  115. package/src/prompts/skills/deep-dive/SKILL.md +13 -0
  116. package/src/prompts/skills/deep-interview/AGENTS.md +31 -0
  117. package/src/prompts/skills/deep-interview/SKILL.md +12 -0
  118. package/src/prompts/skills/gjc/AGENTS.md +31 -0
  119. package/src/prompts/skills/gjc/SKILL.md +15 -0
  120. package/src/prompts/skills/ralplan/AGENTS.md +31 -0
  121. package/src/prompts/skills/ralplan/SKILL.md +11 -0
  122. package/src/prompts/skills/team/AGENTS.md +31 -0
  123. package/src/prompts/skills/team/SKILL.md +11 -0
  124. package/src/prompts/skills/ultragoal/AGENTS.md +31 -0
  125. package/src/prompts/skills/ultragoal/SKILL.md +11 -0
  126. package/src/skills/AGENTS.md +38 -0
  127. package/src/skills/catalog.ts +565 -31
  128. package/src/tui/AGENTS.md +43 -0
  129. package/src/tui/app.ts +1181 -92
  130. package/src/tui/components/AGENTS.md +42 -0
  131. package/src/tui/components/ascii-art.ts +257 -15
  132. package/src/tui/components/autocomplete.ts +98 -16
  133. package/src/tui/components/autopilot-status.ts +65 -0
  134. package/src/tui/components/category-index.ts +49 -0
  135. package/src/tui/components/code-view.ts +54 -11
  136. package/src/tui/components/color.ts +171 -2
  137. package/src/tui/components/config-panel.ts +82 -15
  138. package/src/tui/components/duration.ts +38 -0
  139. package/src/tui/components/evolution.ts +3 -3
  140. package/src/tui/components/footer.ts +91 -42
  141. package/src/tui/components/forge.ts +426 -31
  142. package/src/tui/components/hints.ts +54 -0
  143. package/src/tui/components/hud.ts +73 -0
  144. package/src/tui/components/index.ts +4 -0
  145. package/src/tui/components/input-box.ts +150 -0
  146. package/src/tui/components/layout.ts +11 -3
  147. package/src/tui/components/live-model-picker.ts +108 -0
  148. package/src/tui/components/markdown-table.ts +140 -0
  149. package/src/tui/components/markdown-text.ts +97 -0
  150. package/src/tui/components/meter.ts +4 -1
  151. package/src/tui/components/model-picker.ts +3 -2
  152. package/src/tui/components/provider-picker.ts +3 -2
  153. package/src/tui/components/section.ts +70 -0
  154. package/src/tui/components/select-list.ts +40 -10
  155. package/src/tui/components/skill-picker.ts +25 -0
  156. package/src/tui/components/slash.ts +244 -21
  157. package/src/tui/components/status.ts +272 -11
  158. package/src/tui/components/step-timeline.ts +218 -0
  159. package/src/tui/components/stream.ts +26 -9
  160. package/src/tui/components/themes.ts +212 -6
  161. package/src/tui/components/todo-card.ts +47 -0
  162. package/src/tui/components/tool-list.ts +58 -12
  163. package/src/tui/components/transcript.ts +120 -0
  164. package/src/tui/components/update-box.ts +31 -0
  165. package/src/tui/components/welcome.ts +162 -0
  166. package/src/tui/components/width.ts +163 -0
  167. package/src/tui/monitoring/AGENTS.md +31 -0
  168. package/src/tui/monitoring/hud-view.ts +55 -0
  169. package/src/tui/renderer.ts +112 -3
  170. package/src/tui/terminal.ts +40 -33
  171. package/src/util/AGENTS.md +39 -0
  172. package/src/util/clipboard-image.ts +118 -0
  173. package/src/util/env.ts +12 -0
  174. package/src/util/provider-error.ts +78 -0
  175. package/src/util/retry.ts +91 -6
  176. package/src/util/update-check.ts +64 -0
  177. package/src/commands/models.ts +0 -104
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: "joc-mcp", version: "0.1.0" };
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(`joc-mcp v${SERVER_INFO.version} listening on stdio (${tools.length} tools)`);
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
- let newlineIndex: number;
34
- while ((newlineIndex = buffer.indexOf("\n")) !== -1) {
35
- const line = buffer.slice(0, newlineIndex).trim();
36
- buffer = buffer.slice(newlineIndex + 1);
37
- if (!line) continue;
38
- const response = await handleLine(line, tools);
39
- if (response) writeResponse(response);
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 req: JsonRpcRequest;
134
+ export async function handleLine(line: string, tools: ToolDefinition[]): Promise<JsonRpcResponse | null> {
135
+ let parsed: unknown;
46
136
  try {
47
- req = JSON.parse(line) as JsonRpcRequest;
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
- process.stdout.write(`${JSON.stringify(response)}\n`);
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: "joc_resolve_provider",
15
- description: "Given a model name (e.g. 'claude-3-5-sonnet', 'openai/mock-1', 'gemini-2.0-flash', 'ollama/llama3'), return which provider joc would route the call to.",
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: "joc_credential_status",
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 credential = await resolveCredential(provider as AuthProvider);
45
- return textResult(JSON.stringify({ provider, kind: credential.kind }));
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: "joc_config_snapshot",
50
- description: "Return a redacted snapshot of joc's current config: default model, openai/ollama base URLs, and which providers have credentials.",
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: (await resolveCredential("anthropic")).kind,
61
- openai: (await resolveCredential("openai")).kind,
62
- gemini: (await resolveCredential("gemini")).kind,
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: "joc_doctor",
70
- description: "Run joc's health probe and return a structured report of provider connectivity. Same probe used by 'joc doctor' CLI.",
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: "joc_deep_interview",
106
- description: "DANGER: WRITES FILES + BURNS LLM CREDITS. Runs Socratic requirements interview. Writes .joc/spec.json. Requires default model credential.",
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: "joc_ralplan",
124
- description: "DANGER: WRITES FILES + BURNS LLM CREDITS. Reads .joc/spec.json, writes .joc/plan.yaml via Planner/Architect/Critic.",
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: "joc_team",
134
- description: "DANGER: WRITES FILES + EDITS CODE + BURNS LLM CREDITS. Executes .joc/plan.yaml via Executor subagent. Modifies the working tree.",
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: "joc_ultragoal",
144
- description: "DANGER: BURNS LLM CREDITS. Runs acceptance verification against .joc/spec.json + .joc/plan.yaml.",
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 (process.env.JOC_MCP_PIPELINE === "1") {
174
+ if (jeoEnv("MCP_PIPELINE") === "1") {
155
175
  TOOLS.push(...PIPELINE_TOOLS);
156
176
  }
@@ -0,0 +1,4 @@
1
+ declare module "*.md" {
2
+ const content: string;
3
+ export default content;
4
+ }
@@ -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: -->