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.
- 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 +808 -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 +624 -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
|
@@ -1,102 +1,163 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { readWorkflowState,
|
|
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
|
|
7
|
-
|
|
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
|
-
|
|
13
|
-
`[ERROR] No crystallized requirements found. Please run '
|
|
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
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
81
|
+
log(`Loaded ${criteria.length} acceptance criteria for verification.\n`);
|
|
55
82
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
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}
|
|
93
|
-
|
|
94
|
-
|
|
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.
|
|
124
|
+
results.map(r => `| ${r.criterion} | ${r.status === "failed" ? "❌ FAILED" : "⚠️ UNVERIFIED"} | ${r.note} |`).join("\n") +
|
|
97
125
|
`\n`;
|
|
98
126
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
+
}
|