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,102 +1,163 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import * as path from "node:path";
3
- import { readWorkflowState, getLocalJocDir } from "../agent/state";
3
+ import { readWorkflowState, writeWorkflowState, getLocalJeoDir, type WorkflowState } from "../agent/state";
4
4
  import { bashTool } from "../agent/tools";
5
+ import { parseSeedAcceptanceCriteria } from "../agent/seed";
5
6
 
6
- export async function runUltragoalCommand(): Promise<void> {
7
- const cwd = process.cwd();
7
+ export interface UltragoalEngineOptions {
8
+ cwd?: string;
9
+ signal?: AbortSignal;
10
+ onProgress?: (e: { skill: string; phase: string; detail?: string }) => void;
11
+ io?: {
12
+ output?: (line: string) => void;
13
+ };
14
+ }
15
+
16
+ export async function runUltragoalEngine(opts: UltragoalEngineOptions = {}): Promise<{ ok: boolean; reason?: string }> {
17
+ const cwd = opts.cwd ?? process.cwd();
18
+
19
+ const log = (msg?: any) => {
20
+ const str = msg !== undefined ? String(msg) : "";
21
+ if (opts.io?.output) {
22
+ const lines = str.split("\n");
23
+ for (const line of lines) {
24
+ opts.io.output(line);
25
+ }
26
+ } else {
27
+ console.log(str);
28
+ }
29
+ };
30
+
31
+ if (opts.onProgress) {
32
+ opts.onProgress({ skill: "ultragoal", phase: "start" });
33
+ }
34
+
35
+ if (opts.signal?.aborted) {
36
+ return { ok: false, reason: "aborted" };
37
+ }
8
38
 
9
39
  // Read state to find acceptance criteria
10
40
  const interviewState = await readWorkflowState("deep-interview", cwd);
11
41
  if (!interviewState || !interviewState.seed_path) {
12
- console.log(
13
- `[ERROR] No crystallized requirements found. Please run 'joc deep-interview' first.`
42
+ log(
43
+ `[ERROR] No crystallized requirements found. Please run 'jeo deep-interview' first.`
14
44
  );
15
- return;
45
+ return { ok: false, reason: "No crystallized requirements found" };
16
46
  }
17
47
 
18
48
  const seedPath = interviewState.seed_path;
19
- console.log(`\n=== Starting Ultragoal Verification Stage ===`);
20
- console.log(`Reading requirements and acceptance criteria from: ${seedPath}`);
49
+ log(`\n=== Starting Ultragoal Verification Stage ===`);
50
+ log(`Reading requirements and acceptance criteria from: ${seedPath}`);
51
+
52
+ // Thread team execution state: verification should run AFTER the plan was executed.
53
+ const teamState = await readWorkflowState("team", cwd);
54
+ if (!teamState || teamState.current_phase !== "complete") {
55
+ log(
56
+ `[WARN] No completed 'jeo team' execution found (run deep-interview → ralplan → approve → team first).\n` +
57
+ ` Verifying current repository state anyway — results reflect whatever is on disk now.`
58
+ );
59
+ } else {
60
+ log(`Verifying against team execution (plan: ${teamState.plan_path ?? "?"}).`);
61
+ }
21
62
 
22
63
  let seedContent = "";
23
64
  try {
24
65
  seedContent = await fs.readFile(seedPath, "utf-8");
25
66
  } catch (err: any) {
26
- console.log(`[ERROR] Failed to read seed file: ${err.message}`);
27
- return;
67
+ log(`[ERROR] Failed to read seed file: ${err.message}`);
68
+ return { ok: false, reason: err.message };
28
69
  }
29
70
 
30
- // Parse acceptance criteria from seed YAML
31
- const criteria: string[] = [];
32
- const lines = seedContent.split("\n");
33
- let parsingCriteria = false;
34
- for (const line of lines) {
35
- const trimmed = line.trim();
36
- if (trimmed.startsWith("acceptance_criteria:")) {
37
- parsingCriteria = true;
38
- continue;
39
- }
40
- if (parsingCriteria) {
41
- if (trimmed.startsWith("-")) {
42
- criteria.push(trimmed.replace(/^-\s*/, "").replace(/"/g, "").trim());
43
- } else if (trimmed === "" || trimmed.includes(":")) {
44
- // End of list or next section
45
- parsingCriteria = false;
46
- }
47
- }
48
- }
71
+ // Parse acceptance criteria via the SHARED seed module (round-12) — the old
72
+ // inline scan stripped EVERY double quote and mangled criteria like
73
+ // `Display "Done" message`; the shared parser JSON-decodes what the
74
+ // deep-interview writer JSON-encoded, so values round-trip exactly.
75
+ const criteria: string[] = parseSeedAcceptanceCriteria(seedContent);
49
76
 
50
77
  if (criteria.length === 0) {
51
78
  criteria.push("Runs successfully in the terminal");
52
79
  }
53
80
 
54
- console.log(`Loaded ${criteria.length} acceptance criteria for verification.\n`);
81
+ log(`Loaded ${criteria.length} acceptance criteria for verification.\n`);
55
82
 
56
- const results: { criterion: string; passed: boolean; output: string }[] = [];
57
-
58
- for (const criterion of criteria) {
59
- console.log(`[CHECK] Verifying: "${criterion}"`);
60
-
61
- // We can execute a automatic verification pass.
62
- // E.g., if there are tests, we run bun test. If not, we do a smoke check by running bun src/cli.ts setup or compile.
63
- let cmd = "bun test";
64
- if (criterion.toLowerCase().includes("run") || criterion.toLowerCase().includes("cli")) {
65
- cmd = "bun run src/cli.ts --help";
66
- }
83
+ // Round-7 #2 (architect ref 7-Round7Workflow): the previous per-criterion loop
84
+ // was verification THEATER — every criterion ran the same global `bun test`
85
+ // (or a guaranteed-green `--help` when the text mentioned run/cli) and a
86
+ // fabricated per-criterion ✅/❌ matrix was written to the ledger. Honest
87
+ // contract: run the suite ONCE as a global signal; individual criteria are
88
+ // UNVERIFIED unless individually proven. SUCCESS is not claimable from a
89
+ // signal that cannot fail for the cases it pretends to cover.
90
+ log(`[CHECK] Running the verification suite once ('bun test') — a global signal, not per-criterion proof.`);
91
+ if (opts.onProgress) {
92
+ opts.onProgress({ skill: "ultragoal", phase: "verifying", detail: "Running verification suite" });
93
+ }
94
+ const suite = await bashTool("bun test", cwd);
95
+ log(` └─ Suite: ${suite.success ? "GREEN" : "FAILED"}`);
67
96
 
68
- console.log(` └─ Running validation command: '${cmd}'`);
69
- const res = await bashTool(cmd, cwd);
70
-
71
- results.push({
72
- criterion,
73
- passed: res.success,
74
- output: res.output.slice(0, 300) + (res.output.length > 300 ? "..." : "")
75
- });
97
+ const results: { criterion: string; status: "unverified" | "failed"; note: string }[] = criteria.map(criterion =>
98
+ suite.success
99
+ ? { criterion, status: "unverified", note: "suite green — criterion not individually verified" }
100
+ : { criterion, status: "failed", note: "verification suite failed" },
101
+ );
76
102
 
77
- console.log(` └─ Result: ${res.success ? "PASSED" : "FAILED"}`);
103
+ if (opts.signal?.aborted) {
104
+ return { ok: false, reason: "aborted" };
78
105
  }
79
106
 
80
107
  // Write verification report
81
- const reportDir = path.join(getLocalJocDir(cwd), "state");
108
+ const reportDir = path.join(getLocalJeoDir(cwd), "state");
82
109
  await fs.mkdir(reportDir, { recursive: true });
83
110
  const reportPath = path.join(reportDir, "ultragoal-report.md");
84
111
 
85
- const passedCount = results.filter(r => r.passed).length;
86
112
  const totalCount = results.length;
87
- const status = passedCount === totalCount ? "SUCCESS" : "DEGRADED";
113
+ const status = suite.success ? "SUITE_GREEN" : "FAILED";
88
114
 
89
- const reportContent =
115
+ const reportContent =
90
116
  `# Ultragoal Verification Report: ${interviewState.slug}\n` +
91
117
  `Date: ${new Date().toISOString()}\n` +
92
- `Status: ${status} (${passedCount}/${totalCount} criteria passed)\n\n` +
93
- `## Criteria Verification Matrix\n` +
94
- `| Criterion | Status | Verification Output |\n` +
118
+ `Status: ${status} — suite ${suite.success ? "green" : "FAILED"}; ${totalCount} acceptance criteria recorded (not individually verified)\n` +
119
+ `Plan: ${teamState?.plan_path ?? "(team not run)"}\n` +
120
+ `Execution: ${teamState?.current_phase === "complete" ? "team complete" : "team NOT complete — verified current disk state"}\n\n` +
121
+ `## Criteria Record\n` +
122
+ `| Criterion | Status | Note |\n` +
95
123
  `|---|---|---|\n` +
96
- results.map(r => `| ${r.criterion} | ${r.passed ? " PASSED" : " FAILED"} | \`${r.output.replace(/\n/g, " ")}\` |`).join("\n") +
124
+ results.map(r => `| ${r.criterion} | ${r.status === "failed" ? " FAILED" : "⚠️ UNVERIFIED"} | ${r.note} |`).join("\n") +
97
125
  `\n`;
98
126
 
99
- await fs.writeFile(reportPath, reportContent, "utf-8");
100
- console.log(`\n[VERIFICATION COMPLETE] Report saved to: ${reportPath}`);
101
- console.log(`Overall status: ${status} (${passedCount}/${totalCount} passed)`);
127
+ // Atomic temp+rename (zeroclaw): a torn report must not disagree with the state JSON.
128
+ const tmpReport = `${reportPath}.${Math.random().toString(36).slice(2)}.tmp`;
129
+ try {
130
+ await fs.writeFile(tmpReport, reportContent, "utf-8");
131
+ await fs.rename(tmpReport, reportPath);
132
+ } catch (err) {
133
+ await fs.unlink(tmpReport).catch(() => {});
134
+ throw err;
135
+ }
136
+
137
+ // Persist a machine-readable terminal phase so the chain is queryable end-to-end.
138
+ const ultragoalState: WorkflowState = {
139
+ active: false,
140
+ current_phase: "complete",
141
+ skill: "ultragoal",
142
+ slug: interviewState.slug,
143
+ seed_path: seedPath,
144
+ plan_path: teamState?.plan_path,
145
+ status,
146
+ suite_green: suite.success,
147
+ total: totalCount,
148
+ };
149
+ await writeWorkflowState("ultragoal", ultragoalState, cwd);
150
+
151
+ log(`\n[VERIFICATION COMPLETE] Report saved to: ${reportPath}`);
152
+ log(`Overall status: ${status} — ${totalCount} criteria recorded; none individually verified (add per-criterion checks for stronger claims).`);
153
+
154
+ if (opts.onProgress) {
155
+ opts.onProgress({ skill: "ultragoal", phase: "complete" });
156
+ }
157
+
158
+ return { ok: suite.success };
159
+ }
160
+
161
+ export async function runUltragoalCommand(): Promise<void> {
162
+ await runUltragoalEngine();
102
163
  }
@@ -0,0 +1,244 @@
1
+ import pkg from "../../package.json";
2
+
3
+ /**
4
+ * Compares two semver-ish version strings.
5
+ * Choice: A version with a prerelease suffix is considered older than the same version without a prerelease suffix (e.g. 1.0.0 > 1.0.0-alpha).
6
+ * If both versions have prerelease suffixes, they are compared lexicographically as a string tiebreak (e.g. 1.0.0-beta > 1.0.0-alpha).
7
+ */
8
+ export function compareVersions(a: string, b: string): -1 | 0 | 1 {
9
+ const cleanA = a.split("+")[0] || "";
10
+ const cleanB = b.split("+")[0] || "";
11
+
12
+ const [aBase = "", aPre] = cleanA.split("-");
13
+ const [bBase = "", bPre] = cleanB.split("-");
14
+
15
+ const aParts = aBase.split(".").map(x => parseInt(x, 10) || 0);
16
+ const bParts = bBase.split(".").map(x => parseInt(x, 10) || 0);
17
+
18
+ const maxLen = Math.max(aParts.length, bParts.length);
19
+ for (let i = 0; i < maxLen; i++) {
20
+ const aVal = aParts[i] ?? 0;
21
+ const bVal = bParts[i] ?? 0;
22
+ if (aVal > bVal) return 1;
23
+ if (aVal < bVal) return -1;
24
+ }
25
+
26
+ if (aPre === undefined && bPre !== undefined) return 1;
27
+ if (aPre !== undefined && bPre === undefined) return -1;
28
+ if (aPre === undefined && bPre === undefined) return 0;
29
+
30
+ if (aPre > bPre) return 1;
31
+ if (aPre < bPre) return -1;
32
+ return 0;
33
+ }
34
+
35
+ export interface UpdateDeps {
36
+ fetchJson: (url: string, options?: { signal?: AbortSignal }) => Promise<any>;
37
+ localVersion: () => string;
38
+ install: () => Promise<{ success: boolean; stdout?: string; stderr?: string }>;
39
+ }
40
+
41
+ export const defaultDeps: UpdateDeps = {
42
+ fetchJson: async (url: string, options?: { signal?: AbortSignal }) => {
43
+ const res = await fetch(url, options);
44
+ if (res.status === 404) {
45
+ const err = new Error("Package not found on registry");
46
+ (err as any).status = 404;
47
+ throw err;
48
+ }
49
+ if (!res.ok) {
50
+ const err = new Error(`HTTP status ${res.status}`);
51
+ (err as any).status = res.status;
52
+ throw err;
53
+ }
54
+ return res.json();
55
+ },
56
+ localVersion: () => {
57
+ return pkg.version;
58
+ },
59
+ install: async () => {
60
+ const proc = Bun.spawnSync(["bun", "install", "-g", "jeo-code"], {
61
+ stdout: "inherit",
62
+ stderr: "inherit",
63
+ });
64
+ return { success: proc.success };
65
+ }
66
+ };
67
+
68
+ export async function runUpdateCommand(args: string[] = []): Promise<void> {
69
+ return runUpdateCommandWith(args, defaultDeps);
70
+ }
71
+
72
+ export async function runUpdateCommandWith(args: string[], deps: UpdateDeps): Promise<void> {
73
+ const isHelp = args.includes("--help") || args.includes("-h");
74
+ const hasInstall = args.includes("--install");
75
+ const hasJson = args.includes("--json");
76
+ const hasStrict = args.includes("--strict");
77
+
78
+ const KNOWN_FLAGS = new Set(["--check", "--install", "--json", "--strict", "-h", "--help"]);
79
+
80
+ if (isHelp) {
81
+ printUsage();
82
+ return;
83
+ }
84
+
85
+ // Check for unknown flags
86
+ for (const arg of args) {
87
+ if (!KNOWN_FLAGS.has(arg)) {
88
+ console.log(`Unknown flag: ${arg}`);
89
+ printUsage();
90
+ process.exitCode = 1;
91
+ return;
92
+ }
93
+ }
94
+
95
+ const current = deps.localVersion();
96
+ let latest: string | null = null;
97
+ let upToDate = false;
98
+
99
+ try {
100
+ const signal = AbortSignal.timeout(10000);
101
+ const data = await deps.fetchJson("https://registry.npmjs.org/jeo-code/latest", { signal });
102
+ if (!data || typeof data.version !== "string") {
103
+ throw new Error("Invalid response format from npm registry");
104
+ }
105
+ latest = data.version as string;
106
+ const cmp = compareVersions(current, latest);
107
+ upToDate = cmp >= 0;
108
+ } catch (err: any) {
109
+ const is404 = err.status === 404 || String(err.message).includes("404") || String(err.message).toLowerCase().includes("not found");
110
+ if (is404) {
111
+ if (hasJson) {
112
+ console.log(JSON.stringify({
113
+ current,
114
+ latest: null,
115
+ upToDate: true,
116
+ error: "Package not found on registry"
117
+ }));
118
+ } else {
119
+ console.log("Package not found on registry: jeo-code");
120
+ }
121
+ return;
122
+ } else {
123
+ // Network failure
124
+ const errMsg = err.message || String(err);
125
+ if (hasJson) {
126
+ console.log(JSON.stringify({
127
+ current,
128
+ latest: null,
129
+ upToDate: false,
130
+ error: `Network failure: ${errMsg}`
131
+ }));
132
+ } else {
133
+ if (hasStrict) {
134
+ console.error(`Error: Network failure: ${errMsg}`);
135
+ } else {
136
+ console.warn(`Warning: Network failure: ${errMsg}`);
137
+ }
138
+ }
139
+ if (hasStrict) {
140
+ process.exitCode = 1;
141
+ }
142
+ return;
143
+ }
144
+ }
145
+
146
+ // We got the version successfully
147
+ if (hasInstall) {
148
+ if (upToDate) {
149
+ if (hasJson) {
150
+ console.log(JSON.stringify({
151
+ current,
152
+ latest,
153
+ upToDate: true,
154
+ installed: false
155
+ }));
156
+ } else {
157
+ console.log(`jeo-code is up-to-date (${current}). Skipping installation.`);
158
+ }
159
+ } else {
160
+ if (!hasJson) {
161
+ console.log(`Installing update: ${current} -> ${latest}...`);
162
+ }
163
+ try {
164
+ const result = await deps.install();
165
+ if (result.success) {
166
+ if (hasJson) {
167
+ console.log(JSON.stringify({
168
+ current,
169
+ latest,
170
+ upToDate: false,
171
+ installed: true
172
+ }));
173
+ } else {
174
+ console.log(`Successfully installed jeo-code@${latest}`);
175
+ }
176
+ } else {
177
+ if (hasJson) {
178
+ console.log(JSON.stringify({
179
+ current,
180
+ latest,
181
+ upToDate: false,
182
+ installed: false,
183
+ error: "Installation failed"
184
+ }));
185
+ } else {
186
+ console.error("Failed to install update.");
187
+ }
188
+ process.exitCode = 1;
189
+ }
190
+ } catch (installErr: any) {
191
+ const installErrMsg = installErr.message || String(installErr);
192
+ if (hasJson) {
193
+ console.log(JSON.stringify({
194
+ current,
195
+ latest,
196
+ upToDate: false,
197
+ installed: false,
198
+ error: `Installation error: ${installErrMsg}`
199
+ }));
200
+ } else {
201
+ console.error(`Failed to install update: ${installErrMsg}`);
202
+ }
203
+ process.exitCode = 1;
204
+ }
205
+ }
206
+ } else {
207
+ // Just checking
208
+ if (upToDate) {
209
+ if (hasJson) {
210
+ console.log(JSON.stringify({
211
+ current,
212
+ latest,
213
+ upToDate: true
214
+ }));
215
+ } else {
216
+ console.log(`jeo-code is up-to-date (${current}).`);
217
+ }
218
+ } else {
219
+ if (hasJson) {
220
+ console.log(JSON.stringify({
221
+ current,
222
+ latest,
223
+ upToDate: false
224
+ }));
225
+ } else {
226
+ console.log(`Newer version available: ${latest} (current: ${current}).`);
227
+ console.log("Run 'bun install -g jeo-code' to upgrade.");
228
+ }
229
+ }
230
+ }
231
+ }
232
+
233
+ function printUsage() {
234
+ console.log("Usage: jeo update [options]");
235
+ console.log("");
236
+ console.log("Check for and install updates for jeo-code.");
237
+ console.log("");
238
+ console.log("Options:");
239
+ console.log(" --check Check for updates (default)");
240
+ console.log(" --install Check and install if newer");
241
+ console.log(" --json Output result in JSON format");
242
+ console.log(" --strict Exit with code 1 on network/registry errors");
243
+ console.log(" -h, --help Show this help message");
244
+ }