horizon-code 0.1.2 → 0.2.0
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/bin/horizon.js +18 -1
- package/package.json +1 -1
- package/src/app.ts +85 -11
- package/src/components/code-panel.ts +141 -14
- package/src/platform/session-sync.ts +1 -1
- package/src/state/types.ts +1 -0
- package/src/strategy/code-stream.ts +3 -1
- package/src/strategy/dashboard.ts +189 -18
- package/src/strategy/prompts.ts +426 -6
- package/src/strategy/tools.ts +311 -54
- package/src/strategy/validator.ts +98 -0
- package/src/updater.ts +118 -0
package/src/updater.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Auto-updater — checks npm registry on startup, silently upgrades if newer version exists.
|
|
2
|
+
// Runs before the app loads so the user always gets the latest version.
|
|
3
|
+
|
|
4
|
+
const PACKAGE_NAME = "horizon-code";
|
|
5
|
+
const CHECK_INTERVAL_MS = 4 * 60 * 60 * 1000; // 4 hours between checks
|
|
6
|
+
const CACHE_FILE = `${process.env.HOME ?? "/tmp"}/.horizon/update-check.json`;
|
|
7
|
+
|
|
8
|
+
interface UpdateCache {
|
|
9
|
+
lastCheck: number;
|
|
10
|
+
latestVersion: string | null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function readCache(): UpdateCache {
|
|
14
|
+
try {
|
|
15
|
+
const text = require("fs").readFileSync(CACHE_FILE, "utf-8");
|
|
16
|
+
return JSON.parse(text);
|
|
17
|
+
} catch {
|
|
18
|
+
return { lastCheck: 0, latestVersion: null };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function writeCache(cache: UpdateCache): void {
|
|
23
|
+
try {
|
|
24
|
+
const dir = CACHE_FILE.replace(/\/[^/]+$/, "");
|
|
25
|
+
require("fs").mkdirSync(dir, { recursive: true });
|
|
26
|
+
require("fs").writeFileSync(CACHE_FILE, JSON.stringify(cache));
|
|
27
|
+
} catch {}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getCurrentVersion(): string {
|
|
31
|
+
try {
|
|
32
|
+
const pkg = require("fs").readFileSync(
|
|
33
|
+
new URL("../package.json", import.meta.url).pathname,
|
|
34
|
+
"utf-8"
|
|
35
|
+
);
|
|
36
|
+
return JSON.parse(pkg).version ?? "0.0.0";
|
|
37
|
+
} catch {
|
|
38
|
+
return "0.0.0";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function semverGt(a: string, b: string): boolean {
|
|
43
|
+
const pa = a.split(".").map(Number);
|
|
44
|
+
const pb = b.split(".").map(Number);
|
|
45
|
+
for (let i = 0; i < 3; i++) {
|
|
46
|
+
if ((pa[i] ?? 0) > (pb[i] ?? 0)) return true;
|
|
47
|
+
if ((pa[i] ?? 0) < (pb[i] ?? 0)) return false;
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function fetchLatestVersion(): Promise<string | null> {
|
|
53
|
+
try {
|
|
54
|
+
const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, {
|
|
55
|
+
signal: AbortSignal.timeout(5000),
|
|
56
|
+
});
|
|
57
|
+
if (!res.ok) return null;
|
|
58
|
+
const data = (await res.json()) as any;
|
|
59
|
+
return data.version ?? null;
|
|
60
|
+
} catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function runUpgrade(): boolean {
|
|
66
|
+
try {
|
|
67
|
+
const result = Bun.spawnSync(["bun", "install", "-g", `${PACKAGE_NAME}@latest`], {
|
|
68
|
+
stdout: "pipe",
|
|
69
|
+
stderr: "pipe",
|
|
70
|
+
timeout: 30000,
|
|
71
|
+
});
|
|
72
|
+
return result.exitCode === 0;
|
|
73
|
+
} catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Check for updates and auto-install if available.
|
|
80
|
+
* Returns true if the app was updated and should restart.
|
|
81
|
+
*/
|
|
82
|
+
export async function checkForUpdates(): Promise<{ updated: boolean; from?: string; to?: string }> {
|
|
83
|
+
// Skip if this is a re-exec after update (prevents infinite loop)
|
|
84
|
+
if (process.env.__HORIZON_SKIP_UPDATE === "1") return { updated: false };
|
|
85
|
+
|
|
86
|
+
const current = getCurrentVersion();
|
|
87
|
+
const cache = readCache();
|
|
88
|
+
|
|
89
|
+
// Skip if checked recently
|
|
90
|
+
if (Date.now() - cache.lastCheck < CHECK_INTERVAL_MS) {
|
|
91
|
+
// Still check cached version in case a previous update failed
|
|
92
|
+
if (cache.latestVersion && semverGt(cache.latestVersion, current)) {
|
|
93
|
+
// Try the upgrade
|
|
94
|
+
process.stderr.write(`\x1b[2mUpdating ${PACKAGE_NAME} ${current} → ${cache.latestVersion}...\x1b[0m\n`);
|
|
95
|
+
if (runUpgrade()) {
|
|
96
|
+
writeCache({ lastCheck: Date.now(), latestVersion: cache.latestVersion });
|
|
97
|
+
return { updated: true, from: current, to: cache.latestVersion };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return { updated: false };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Fetch latest version from npm
|
|
104
|
+
const latest = await fetchLatestVersion();
|
|
105
|
+
writeCache({ lastCheck: Date.now(), latestVersion: latest });
|
|
106
|
+
|
|
107
|
+
if (!latest || !semverGt(latest, current)) {
|
|
108
|
+
return { updated: false };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// New version available — upgrade silently
|
|
112
|
+
process.stderr.write(`\x1b[2mUpdating ${PACKAGE_NAME} ${current} → ${latest}...\x1b[0m\n`);
|
|
113
|
+
if (runUpgrade()) {
|
|
114
|
+
return { updated: true, from: current, to: latest };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return { updated: false };
|
|
118
|
+
}
|