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
@@ -1,10 +1,12 @@
1
1
  import { readGlobalConfig } from "../agent/state";
2
2
  import { resolveCredential, snapshotProvider, type AuthProvider, type Credential } from "../auth";
3
3
  import { resolveProvider } from "../ai";
4
+ import { effectiveCredentialForProvider } from "../ai/model-manager";
4
5
  import { resolveModelId } from "../ai/model-registry";
5
6
  import { meter } from "../tui/components/meter";
6
7
  import { size } from "../tui/terminal";
7
8
  import chalk from "chalk";
9
+ import { extractChatgptAccountId, CODEX_RESPONSES_URL } from "../ai/providers/openai-responses";
8
10
 
9
11
  interface ProbeResult {
10
12
  status: "ok" | "fail" | "skipped";
@@ -12,7 +14,7 @@ interface ProbeResult {
12
14
  latencyMs?: number;
13
15
  }
14
16
 
15
- const APP_NAME = "joc";
17
+ const APP_NAME = "jeo";
16
18
  const PROBE_TIMEOUT_MS = 4000;
17
19
 
18
20
  async function timedFetch(url: string, init: RequestInit): Promise<{ res: Response; latencyMs: number }> {
@@ -27,9 +29,40 @@ async function timedFetch(url: string, init: RequestInit): Promise<{ res: Respon
27
29
  }
28
30
  }
29
31
 
32
+ const NO_OPENAI_CREDENTIAL_DETAIL = "no credential (run 'jeo setup' or 'jeo auth login openai')";
33
+
30
34
  async function probeOpenAi(credential: Credential, baseUrl: string | undefined): Promise<ProbeResult> {
35
+ if (credential.kind === "none" && !baseUrl) {
36
+ return { status: "skipped", detail: NO_OPENAI_CREDENTIAL_DETAIL };
37
+ }
38
+ // ChatGPT/Codex OAuth can't use api.openai.com — verify the Codex backend instead.
39
+ // A deliberately-unsupported model returns 400 *after* auth but *before* any generation,
40
+ // so it confirms connectivity + credentials without burning subscription credit.
41
+ if (credential.kind === "oauth") {
42
+ const accountId = extractChatgptAccountId(credential.token);
43
+ const headers: Record<string, string> = {
44
+ "content-type": "application/json",
45
+ authorization: `Bearer ${credential.token}`,
46
+ "OpenAI-Beta": "responses=experimental",
47
+ originator: "codex_cli_rs",
48
+ accept: "text/event-stream",
49
+ };
50
+ if (accountId) headers["chatgpt-account-id"] = accountId;
51
+ try {
52
+ const { res, latencyMs } = await timedFetch(CODEX_RESPONSES_URL, {
53
+ method: "POST",
54
+ headers,
55
+ body: JSON.stringify({ model: "jeo-doctor-probe", input: [{ role: "user", content: [{ type: "input_text", text: "ping" }] }], stream: true, store: false }),
56
+ });
57
+ if (res.ok || res.status === 400) return { status: "ok", detail: "POST codex/responses (Codex backend reachable)", latencyMs };
58
+ if (res.status === 401 || res.status === 403) return { status: "fail", detail: `Codex auth rejected (${res.status})`, latencyMs };
59
+ return { status: "fail", detail: `POST codex/responses ${res.status}`, latencyMs };
60
+ } catch (err) {
61
+ return { status: "fail", detail: `network error: ${(err as Error).message}` };
62
+ }
63
+ }
31
64
  const base = (baseUrl ?? "https://api.openai.com/v1").replace(/\/$/, "");
32
- const token = credential.kind === "oauth" || credential.kind === "api_key" ? credential.token : "no-key";
65
+ const token = credential.kind === "api_key" ? credential.token : "no-key";
33
66
  try {
34
67
  const { res, latencyMs } = await timedFetch(`${base}/models`, {
35
68
  headers: { Authorization: `Bearer ${token}` },
@@ -43,18 +76,31 @@ async function probeOpenAi(credential: Credential, baseUrl: string | undefined):
43
76
 
44
77
  async function probeGemini(credential: Credential): Promise<ProbeResult> {
45
78
  if (credential.kind === "none") {
46
- return { status: "skipped", detail: "no credential (run 'joc setup' or 'joc auth login gemini')" };
79
+ return { status: "skipped", detail: "no credential (run 'jeo setup' or 'jeo auth login gemini')" };
47
80
  }
48
- const key = credential.kind === "api_key" ? credential.token : "";
49
- const url = credential.kind === "oauth"
50
- ? "https://generativelanguage.googleapis.com/v1beta/models"
51
- : `https://generativelanguage.googleapis.com/v1beta/models?key=${key}`;
81
+ // OAuth tokens are served via Cloud Code Assist (the REAL call path) — probe
82
+ // loadCodeAssist there; the public generativelanguage list rejects them.
83
+ if (credential.kind === "oauth") {
84
+ const { getGeminiCliHeaders } = await import("../ai/providers/gemini");
85
+ try {
86
+ const { res, latencyMs } = await timedFetch("https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist", {
87
+ method: "POST",
88
+ headers: {
89
+ authorization: `Bearer ${credential.token}`,
90
+ "content-type": "application/json",
91
+ ...getGeminiCliHeaders(),
92
+ },
93
+ body: JSON.stringify({ metadata: { ideType: "IDE_UNSPECIFIED", platform: "PLATFORM_UNSPECIFIED", pluginType: "GEMINI" } }),
94
+ });
95
+ if (res.ok) return { status: "ok", detail: "POST cloudcode-pa /v1internal:loadCodeAssist 200 (Cloud Code Assist)", latencyMs };
96
+ return { status: "fail", detail: `POST cloudcode-pa /v1internal:loadCodeAssist ${res.status}`, latencyMs };
97
+ } catch (err) {
98
+ return { status: "fail", detail: `network error: ${(err as Error).message}` };
99
+ }
100
+ }
101
+ const url = `https://generativelanguage.googleapis.com/v1beta/models?key=${credential.token}`;
52
102
  try {
53
- const { res, latencyMs } = await timedFetch(url, {
54
- headers: credential.kind === "oauth"
55
- ? { authorization: `Bearer ${credential.token}` }
56
- : {},
57
- });
103
+ const { res, latencyMs } = await timedFetch(url, { headers: {} });
58
104
  if (res.ok) return { status: "ok", detail: "GET /v1beta/models 200", latencyMs };
59
105
  return { status: "fail", detail: `GET /v1beta/models ${res.status}`, latencyMs };
60
106
  } catch (err) {
@@ -64,14 +110,12 @@ async function probeGemini(credential: Credential): Promise<ProbeResult> {
64
110
 
65
111
  async function probeAnthropic(credential: Credential): Promise<ProbeResult> {
66
112
  if (credential.kind === "none") {
67
- return { status: "skipped", detail: "no credential (run 'joc setup' or 'joc auth login anthropic')" };
68
- }
69
- // Anthropic has no free model-listing endpoint; verify auth by issuing a
70
- // 1-token request that returns quickly without burning meaningful credit.
71
- const headers: Record<string, string> = {
72
- "content-type": "application/json",
73
- "anthropic-version": "2023-06-01",
74
- };
113
+ return { status: "skipped", detail: "no credential (run 'jeo setup' or 'jeo auth login anthropic')" };
114
+ }
115
+ // GET /v1/models verifies auth without burning credit and without depending on a
116
+ // (possibly retired) model id — the old 1-token POST 404'd whenever the probe model
117
+ // was deprecated.
118
+ const headers: Record<string, string> = { "anthropic-version": "2023-06-01" };
75
119
  if (credential.kind === "oauth") {
76
120
  headers.authorization = `Bearer ${credential.token}`;
77
121
  headers["anthropic-beta"] = "oauth-2025-04-20";
@@ -79,20 +123,12 @@ async function probeAnthropic(credential: Credential): Promise<ProbeResult> {
79
123
  headers["x-api-key"] = credential.token;
80
124
  }
81
125
  try {
82
- const { res, latencyMs } = await timedFetch("https://api.anthropic.com/v1/messages", {
83
- method: "POST",
84
- headers,
85
- body: JSON.stringify({
86
- model: "claude-3-5-sonnet-20241022",
87
- max_tokens: 1,
88
- messages: [{ role: "user", content: "ping" }],
89
- }),
90
- });
91
- if (res.ok) return { status: "ok", detail: "POST /v1/messages 200 (1-token probe)", latencyMs };
126
+ const { res, latencyMs } = await timedFetch("https://api.anthropic.com/v1/models?limit=1", { headers });
127
+ if (res.ok) return { status: "ok", detail: "GET /v1/models 200", latencyMs };
92
128
  if (res.status === 401 || res.status === 403) {
93
129
  return { status: "fail", detail: `auth rejected (${res.status})`, latencyMs };
94
130
  }
95
- return { status: "fail", detail: `POST /v1/messages ${res.status}`, latencyMs };
131
+ return { status: "fail", detail: `GET /v1/models ${res.status}`, latencyMs };
96
132
  } catch (err) {
97
133
  return { status: "fail", detail: `network error: ${(err as Error).message}` };
98
134
  }
@@ -139,11 +175,22 @@ export async function runDoctorCommand(args: string[] = []): Promise<void> {
139
175
  const cloud = ["anthropic", "openai", "gemini"] as AuthProvider[];
140
176
  const cloudProbes = await Promise.all(
141
177
  cloud.map(async provider => {
142
- const credential = await resolveCredential(provider);
178
+ const rawCredential = await resolveCredential(provider);
179
+ let credential = rawCredential;
143
180
  let result: ProbeResult;
144
- if (provider === "openai") result = await probeOpenAi(credential, config.openaiBaseUrl);
145
- else if (provider === "gemini") result = await probeGemini(credential);
146
- else result = await probeAnthropic(credential);
181
+ try {
182
+ credential = effectiveCredentialForProvider(
183
+ provider,
184
+ rawCredential,
185
+ config,
186
+ provider === defaultProvider ? config.defaultModel : provider,
187
+ );
188
+ if (provider === "openai") result = await probeOpenAi(credential, config.openaiBaseUrl);
189
+ else if (provider === "gemini") result = await probeGemini(credential);
190
+ else result = await probeAnthropic(credential);
191
+ } catch (err) {
192
+ result = { status: "fail", detail: (err as Error).message };
193
+ }
147
194
  return { name: provider, credKind: credential.kind, result };
148
195
  })
149
196
  );
@@ -200,7 +247,7 @@ export async function runDoctorCommand(args: string[] = []): Promise<void> {
200
247
  console.log("");
201
248
  console.log(`Bun runtime: v${Bun.version}`);
202
249
  console.log(`Default model: ${config.defaultModel}${resolvedModel !== config.defaultModel ? ` → ${resolvedModel}` : ""} → ${defaultProvider}`);
203
- console.log(`Config: ${process.env.HOME}/.joc/config.json`);
250
+ console.log(`Config: ${process.env.HOME}/.jeo/config.json`);
204
251
  if (config.openaiBaseUrl) console.log(`OpenAI base: ${config.openaiBaseUrl}`);
205
252
  console.log(`Ollama base: ${ollamaBase}`);
206
253
 
@@ -236,7 +283,7 @@ export async function runDoctorCommand(args: string[] = []): Promise<void> {
236
283
  } else if (defaultProbe?.result.status === "skipped") {
237
284
  console.log(
238
285
  `${chalk.red("[NOT READY]")} Default model '${config.defaultModel}' resolves to '${defaultProvider}', ` +
239
- `but no credential is configured. Run 'joc setup' or 'joc auth login ${defaultProvider}'.`
286
+ `but no credential is configured. Run 'jeo setup' or 'jeo auth login ${defaultProvider}'.`
240
287
  );
241
288
  } else {
242
289
  console.log(
@@ -0,0 +1,18 @@
1
+ import { consultGjcForEvolution } from "../agent/dev/evolution-bridge";
2
+ import chalk from "chalk";
3
+
4
+ /**
5
+ * jeo evolve-core: Triggers a self-evolution turn using gjc as a guide.
6
+ */
7
+ export async function runEvolveCoreCommand(args: string[]): Promise<void> {
8
+ const cwd = process.cwd();
9
+ console.log("\n=== " + chalk.bold.magenta("jeo") + " Autonomous Core Evolution ===");
10
+
11
+ try {
12
+ await consultGjcForEvolution(cwd);
13
+ console.log("\n" + chalk.green("✔") + " Evolution turn completed.");
14
+ } catch (err: any) {
15
+ console.error("\n" + chalk.red("✘") + " Evolution turn failed: " + err.message);
16
+ process.exit(1);
17
+ }
18
+ }
@@ -114,7 +114,7 @@ function stageModelJson() {
114
114
  }
115
115
 
116
116
  /**
117
- * Preview the joc "evolution" TUI identity: render the five stages (or one stage
117
+ * Preview the jeo "evolution" TUI identity: render the five stages (or one stage
118
118
  * for a given step) with art, evolution track, and a stage meter. Supports
119
119
  * theming, truecolor gradients, ASCII-only fallback, terminal-width fitting,
120
120
  * frame-loop animation, and machine-readable `--json` / `--list` output.
@@ -162,6 +162,7 @@ export async function runEvolveCommand(args: string[], opts: EvolveOptions = {})
162
162
  color: useColor,
163
163
  width: artWidth,
164
164
  height: stageHeight(),
165
+ unicode,
165
166
  ...(gradientOn ? { gradient: themeGradient(theme, index), colorLevel } : {}),
166
167
  };
167
168
 
@@ -0,0 +1,176 @@
1
+ import { exportSession, latestSessionId, loadSession } from "../agent/session";
2
+ import * as fs from "node:fs/promises";
3
+ import * as path from "node:path";
4
+
5
+ export function renderSessionHtml(
6
+ meta: { id: string; timestamp?: string; [key: string]: any },
7
+ messages: { role: string; content: string }[]
8
+ ): string {
9
+ const escapeHtml = (text: string): string => {
10
+ return text
11
+ .replace(/&/g, "&amp;")
12
+ .replace(/</g, "&lt;")
13
+ .replace(/>/g, "&gt;")
14
+ .replace(/"/g, "&quot;")
15
+ .replace(/'/g, "&#039;");
16
+ };
17
+
18
+ const messageHtml = messages
19
+ .map(m => {
20
+ const roleClass = (m.role === "system" || m.role === "user" || m.role === "assistant") ? m.role : "other";
21
+ return ` <div class="message ${roleClass}">
22
+ <div class="role-label">${escapeHtml(m.role)}</div>
23
+ <pre>${escapeHtml(m.content)}</pre>
24
+ </div>`;
25
+ })
26
+ .join("\n");
27
+
28
+ return `<!DOCTYPE html>
29
+ <html lang="en">
30
+ <head>
31
+ <meta charset="UTF-8">
32
+ <title>Session ${escapeHtml(meta.id)}</title>
33
+ <style>
34
+ body {
35
+ background-color: #121212;
36
+ color: #e0e0e0;
37
+ font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
38
+ padding: 24px;
39
+ margin: 0;
40
+ line-height: 1.5;
41
+ }
42
+ .header {
43
+ margin-bottom: 24px;
44
+ border-bottom: 1px solid #333;
45
+ padding-bottom: 16px;
46
+ }
47
+ .header h1 {
48
+ margin: 0 0 8px 0;
49
+ font-size: 24px;
50
+ color: #ffffff;
51
+ }
52
+ .header p {
53
+ margin: 0;
54
+ font-size: 14px;
55
+ color: #888888;
56
+ }
57
+ .message {
58
+ margin: 16px 0;
59
+ padding: 16px;
60
+ border-radius: 6px;
61
+ border-left: 4px solid #555;
62
+ }
63
+ .message.system {
64
+ background-color: #1c1c1c;
65
+ border-left-color: #888888;
66
+ }
67
+ .message.user {
68
+ background-color: #172a3a;
69
+ border-left-color: #3b82f6;
70
+ }
71
+ .message.assistant {
72
+ background-color: #143224;
73
+ border-left-color: #10b981;
74
+ }
75
+ .role-label {
76
+ font-weight: bold;
77
+ text-transform: uppercase;
78
+ font-size: 12px;
79
+ margin-bottom: 8px;
80
+ letter-spacing: 0.5px;
81
+ }
82
+ .message.system .role-label {
83
+ color: #aaaaaa;
84
+ }
85
+ .message.user .role-label {
86
+ color: #60a5fa;
87
+ }
88
+ .message.assistant .role-label {
89
+ color: #34d399;
90
+ }
91
+ pre {
92
+ margin: 0;
93
+ white-space: pre-wrap;
94
+ word-break: break-all;
95
+ font-family: inherit;
96
+ }
97
+ </style>
98
+ </head>
99
+ <body>
100
+ <div class="header">
101
+ <h1>Session: ${escapeHtml(meta.id)}</h1>
102
+ ${meta.timestamp ? `<p>Timestamp: ${escapeHtml(meta.timestamp)}</p>` : ""}
103
+ </div>
104
+ ${messageHtml}
105
+ </body>
106
+ </html>`;
107
+ }
108
+
109
+ /**
110
+ * `jeo export [id] [--json] [--system] [--html] [--out <path>]` — print a saved session transcript
111
+ * (Markdown by default; `--json` for structured; `--system` to include system
112
+ * messages). Defaults to the latest session when no id is given.
113
+ */
114
+ export async function runExportCommand(args: string[] = []): Promise<void> {
115
+ const htmlMode = args.includes("--html");
116
+ const jsonMode = args.includes("--json");
117
+ const includeSystem = args.includes("--system");
118
+
119
+ if (htmlMode && jsonMode) {
120
+ console.error("Error: --html and --json options are mutually exclusive.");
121
+ process.exitCode = 1;
122
+ return;
123
+ }
124
+
125
+ // Parse --out <path>
126
+ let outPath: string | undefined;
127
+ const outIdx = args.indexOf("--out");
128
+ if (outIdx !== -1 && outIdx + 1 < args.length) {
129
+ outPath = args[outIdx + 1];
130
+ }
131
+
132
+ // Find the session ID: first non-flag argument that is not the value after --out
133
+ let id: string | undefined;
134
+ for (let i = 0; i < args.length; i++) {
135
+ const arg = args[i];
136
+ if (arg.startsWith("--")) {
137
+ continue;
138
+ }
139
+ if (outIdx !== -1 && i === outIdx + 1) {
140
+ continue;
141
+ }
142
+ id = arg;
143
+ break;
144
+ }
145
+
146
+ if (!id) {
147
+ id = await latestSessionId();
148
+ }
149
+
150
+ if (!id) {
151
+ console.log("No session to export. Pass a session id or run a session first.");
152
+ return;
153
+ }
154
+
155
+ if (htmlMode) {
156
+ try {
157
+ const { header, messages } = await loadSession(id, process.cwd());
158
+ const picked = includeSystem ? messages : messages.filter(m => m.role !== "system");
159
+ const html = renderSessionHtml(header, picked);
160
+ const resolvedOutPath = outPath
161
+ ? path.resolve(process.cwd(), outPath)
162
+ : path.join(process.cwd(), `jeo-session-${id}.html`);
163
+ await fs.writeFile(resolvedOutPath, html, "utf8");
164
+ console.log(resolvedOutPath);
165
+ } catch (err) {
166
+ console.log(`Could not export session ${id}: ${(err as Error).message}`);
167
+ }
168
+ } else {
169
+ const format: "markdown" | "json" = jsonMode ? "json" : "markdown";
170
+ try {
171
+ console.log(await exportSession(id, format, process.cwd(), { includeSystem }));
172
+ } catch (err) {
173
+ console.log(`Could not export session ${id}: ${(err as Error).message}`);
174
+ }
175
+ }
176
+ }
@@ -0,0 +1,52 @@
1
+ import { runAgentLoop, executorSystemPrompt, DEFAULT_TOOLS } from "../agent/engine";
2
+ import { loadSkills, buildSkillTask, getSkillFrom } from "../skills/catalog";
3
+ import { readGlobalConfig, isDevMode } from "../agent/state";
4
+ import { runPostImplementationHooks } from "../agent/hooks";
5
+
6
+ export async function runGjcCommand(args: string[]): Promise<void> {
7
+ const intent = args.join(" ").trim();
8
+ const config = await readGlobalConfig();
9
+
10
+ if (intent.toLowerCase().includes("self-improve") && !isDevMode()) {
11
+ console.error("Error: Self-improvement tasks are only allowed in JEO_DEV_MODE=1.");
12
+ process.exit(1);
13
+ }
14
+
15
+ const model = config.defaultModel || "fast";
16
+ const skills = await loadSkills();
17
+ const gjcSkill = getSkillFrom(skills, "gjc");
18
+
19
+ if (!gjcSkill) {
20
+ console.error("Error: gjc skill not found.");
21
+ process.exit(1);
22
+ }
23
+
24
+ // Correct signature: executorSystemPrompt(role?, protocol?, verificationDirective?)
25
+ const systemPrompt = executorSystemPrompt();
26
+ const task = buildSkillTask(gjcSkill, intent);
27
+
28
+ await runAgentLoop([{ role: "user", content: task }], {
29
+ cwd: process.cwd(),
30
+ systemPrompt,
31
+ model,
32
+ tools: DEFAULT_TOOLS,
33
+ maxSteps: 50,
34
+ });
35
+
36
+ console.log("\n[jeo] Verifying implementation...");
37
+ const verify = await runPostImplementationHooks(process.cwd(), intent);
38
+
39
+ if (!verify.success) {
40
+ console.error("\n[jeo] Verification FAILED. Auto-repairing...");
41
+ const repairTask = `Previous implementation failed verification.\nErrors:\n${verify.output}\n\nPlease fix.`;
42
+ await runAgentLoop([{ role: "user", content: repairTask }], {
43
+ cwd: process.cwd(),
44
+ systemPrompt,
45
+ model,
46
+ tools: DEFAULT_TOOLS,
47
+ maxSteps: 30,
48
+ });
49
+ } else {
50
+ console.log("\n[jeo] Verification SUCCESSFUL.");
51
+ }
52
+ }