zor-ai 0.8.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 zorai contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # zorai
2
+
3
+ This npm package installs the zorai launcher and downloads the platform-specific zorai binaries on install or first run.
4
+
5
+ Full project documentation lives in the main repository README:
6
+
7
+ - https://github.com/mkurman/zorai/blob/main/README.md
8
+
9
+ Project homepage:
10
+
11
+ - https://zorai.app
12
+
13
+ Quick start:
14
+
15
+ ```bash
16
+ npm install -g zorai
17
+ zorai --help
18
+ ```
19
+
20
+ The npm installer downloads the platform release bundle and installs:
21
+
22
+ - launcher binaries under the package `bin/` directory used by `npm` and `npx`
23
+ - built-in skills into the canonical runtime root at `~/.zorai/skills` on Unix or `%LOCALAPPDATA%\\zorai\\skills` on Windows
package/bin/zorai.js ADDED
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+ // bin/zorai.js -- launcher wrapper that spawns the native zorai binary.
3
+ // Falls back to downloading binaries if they are missing (two-layer fallback).
4
+ "use strict";
5
+
6
+ var fs = require("fs");
7
+ var path = require("path");
8
+ var child_process = require("child_process");
9
+
10
+ var installMeta = require("../install");
11
+ var GITHUB_OWNER = installMeta.GITHUB_OWNER;
12
+ var GITHUB_REPO = installMeta.GITHUB_REPO;
13
+
14
+ var binaryName = process.platform === "win32" ? "zorai.exe" : "zorai";
15
+ var binaryPath = path.join(__dirname, binaryName);
16
+
17
+ /**
18
+ * Attempt to download binaries using the postinstall script.
19
+ * This is the second layer of the fallback (first is postinstall during npm install).
20
+ */
21
+ function tryFallbackDownload() {
22
+ console.log("zorai: binary not found, attempting download...");
23
+ try {
24
+ var install = require("../install");
25
+ install().catch(function (err) {
26
+ console.error("zorai: fallback download failed: " + err.message);
27
+ });
28
+ } catch (err) {
29
+ console.error(
30
+ "zorai: fallback download failed: " + err.message
31
+ );
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Spawn the native binary with all CLI arguments forwarded.
37
+ * @param {string} binPath
38
+ */
39
+ function spawnBinary(binPath) {
40
+ var env = installMeta.prependDirectoryToPath(process.env, __dirname);
41
+ env.ZORAI_INSTALL_SOURCE = "npm";
42
+ var child = child_process.spawn(binPath, process.argv.slice(2), {
43
+ stdio: "inherit",
44
+ env: env,
45
+ });
46
+
47
+ // Forward signals to the child process
48
+ process.on("SIGINT", function () {
49
+ child.kill("SIGINT");
50
+ });
51
+ process.on("SIGTERM", function () {
52
+ child.kill("SIGTERM");
53
+ });
54
+
55
+ child.on("error", function (err) {
56
+ console.error("zorai: failed to start binary: " + err.message);
57
+ process.exit(1);
58
+ });
59
+
60
+ child.on("close", function (code) {
61
+ process.exit(code !== null ? code : 1);
62
+ });
63
+ }
64
+
65
+ // Main logic: find binary or download it
66
+ if (fs.existsSync(binaryPath)) {
67
+ spawnBinary(binaryPath);
68
+ } else {
69
+ // Binary not found -- attempt fallback download
70
+ tryFallbackDownload();
71
+
72
+ // The install module runs async; use a short poll to wait for the binary
73
+ // to appear (up to 60 seconds). This covers the case where postinstall
74
+ // was skipped (e.g., --ignore-scripts) and we need a runtime download.
75
+ var attempts = 0;
76
+ var maxAttempts = 120;
77
+ var pollInterval = 500; // ms
78
+
79
+ var timer = setInterval(function () {
80
+ attempts++;
81
+ if (fs.existsSync(binaryPath)) {
82
+ clearInterval(timer);
83
+ spawnBinary(binaryPath);
84
+ } else if (attempts >= maxAttempts) {
85
+ clearInterval(timer);
86
+ console.error(
87
+ "zorai: could not download binary for your platform. " +
88
+ "Visit https://github.com/" + GITHUB_OWNER + "/" + GITHUB_REPO + "/releases for manual download."
89
+ );
90
+ process.exit(1);
91
+ }
92
+ }, pollInterval);
93
+ }
package/install.js ADDED
@@ -0,0 +1,687 @@
1
+ #!/usr/bin/env node
2
+ // install.js -- download platform-specific zorai binaries on npm install
3
+ "use strict";
4
+
5
+ const https = require("https");
6
+ const http = require("http");
7
+ const fs = require("fs");
8
+ const path = require("path");
9
+ const os = require("os");
10
+ const crypto = require("crypto");
11
+ const childProcess = require("child_process");
12
+
13
+ const VERSION = require("./package.json").version;
14
+ const BIN_DIR = path.join(__dirname, "bin");
15
+
16
+ // GitHub owner/repo for release asset downloads.
17
+ const GITHUB_OWNER = "mkurman";
18
+ const GITHUB_REPO = "zorai";
19
+
20
+ const PLATFORM_MAP = {
21
+ "linux-x64": {
22
+ archivePlatform: "linux-x86_64",
23
+ checksumPlatform: "linux-x86_64",
24
+ requiredBinaries: ["zorai", "zorai-daemon", "zorai-tui", "zorai-gateway", "zorai-mcp"],
25
+ },
26
+ "linux-arm64": {
27
+ archivePlatform: "linux-aarch64",
28
+ checksumPlatform: "linux-aarch64",
29
+ requiredBinaries: ["zorai", "zorai-daemon", "zorai-tui", "zorai-gateway", "zorai-mcp"],
30
+ },
31
+ "darwin-arm64": {
32
+ archivePlatform: "darwin-arm64",
33
+ checksumPlatform: "darwin-arm64",
34
+ requiredBinaries: ["zorai", "zorai-daemon", "zorai-tui", "zorai-gateway", "zorai-mcp"],
35
+ },
36
+ "darwin-x64": {
37
+ archivePlatform: "darwin-x86_64",
38
+ checksumPlatform: "darwin-x86_64",
39
+ requiredBinaries: ["zorai", "zorai-daemon", "zorai-tui", "zorai-gateway", "zorai-mcp"],
40
+ },
41
+ "win32-x64": {
42
+ archivePlatform: "windows-x64",
43
+ checksumPlatform: "windows-x64",
44
+ requiredBinaries: ["zorai.exe", "zorai-daemon.exe", "zorai-tui.exe", "zorai-gateway.exe", "zorai-mcp.exe"],
45
+ },
46
+ };
47
+
48
+ const BASE_URL = `https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}/releases/download/v${VERSION}`;
49
+ const PROCESS_STOP_TIMEOUT_MS = 5000;
50
+ const PROCESS_POLL_INTERVAL_MS = 100;
51
+
52
+ /**
53
+ * Download a URL to a Buffer, following up to maxRedirects HTTP 301/302 redirects.
54
+ * @param {string} url
55
+ * @param {number} [maxRedirects=5]
56
+ * @returns {Promise<Buffer>}
57
+ */
58
+ function download(url, maxRedirects) {
59
+ if (maxRedirects === undefined) maxRedirects = 5;
60
+ return new Promise(function (resolve, reject) {
61
+ var proto = url.startsWith("https://") ? https : http;
62
+ proto
63
+ .get(url, function (res) {
64
+ // Follow redirects
65
+ if (
66
+ (res.statusCode === 301 || res.statusCode === 302) &&
67
+ res.headers.location
68
+ ) {
69
+ if (maxRedirects <= 0) {
70
+ reject(new Error("Too many redirects"));
71
+ return;
72
+ }
73
+ resolve(download(res.headers.location, maxRedirects - 1));
74
+ return;
75
+ }
76
+
77
+ if (res.statusCode !== 200) {
78
+ reject(
79
+ new Error("Download failed: HTTP " + res.statusCode + " for " + url)
80
+ );
81
+ return;
82
+ }
83
+
84
+ var chunks = [];
85
+ res.on("data", function (chunk) {
86
+ chunks.push(chunk);
87
+ });
88
+ res.on("end", function () {
89
+ resolve(Buffer.concat(chunks));
90
+ });
91
+ res.on("error", reject);
92
+ })
93
+ .on("error", reject);
94
+ });
95
+ }
96
+
97
+ /**
98
+ * Verify SHA256 checksum of a file against an expected hex digest.
99
+ * @param {string} filePath
100
+ * @param {string} expectedHash
101
+ * @returns {Promise<boolean>}
102
+ */
103
+ function verifyChecksum(filePath, expectedHash) {
104
+ return new Promise(function (resolve, reject) {
105
+ var hash = crypto.createHash("sha256");
106
+ var stream = fs.createReadStream(filePath);
107
+ stream.on("data", function (chunk) {
108
+ hash.update(chunk);
109
+ });
110
+ stream.on("end", function () {
111
+ resolve(hash.digest("hex") === expectedHash);
112
+ });
113
+ stream.on("error", reject);
114
+ });
115
+ }
116
+
117
+ /**
118
+ * Parse a SHA256SUMS file to extract the hash for a given filename.
119
+ * Expected format: "<hex_hash> <filename>" (two spaces, matching sha256sum output).
120
+ * @param {string} content
121
+ * @param {string} filename
122
+ * @returns {string|null}
123
+ */
124
+ function parseChecksumFile(content, filename) {
125
+ var lines = content.toString("utf8").trim().split("\n");
126
+ for (var i = 0; i < lines.length; i++) {
127
+ var line = lines[i].trim();
128
+ if (!line) continue;
129
+ // Format: hash filename OR hash *filename
130
+ var parts = line.split(/\s+/);
131
+ if (parts.length >= 2) {
132
+ var hash = parts[0];
133
+ var name = parts[parts.length - 1].replace(/^\*/, "");
134
+ if (name === filename) return hash;
135
+ }
136
+ }
137
+ return null;
138
+ }
139
+
140
+ function getArchiveChecksum(checksumsData, releaseInfo) {
141
+ if (!releaseInfo) {
142
+ return null;
143
+ }
144
+
145
+ return parseChecksumFile(checksumsData, releaseInfo.archiveName);
146
+ }
147
+
148
+ function getRuntimeZoraiRoot(platform, env) {
149
+ var sourceEnv = env || process.env;
150
+ if (platform === "win32") {
151
+ var localAppData = sourceEnv.LOCALAPPDATA;
152
+ if (localAppData) {
153
+ return path.win32.join(localAppData, "zorai");
154
+ }
155
+
156
+ var userProfile = sourceEnv.USERPROFILE || "";
157
+ return path.win32.join(userProfile, "AppData", "Local", "zorai");
158
+ }
159
+
160
+ return path.posix.join(sourceEnv.HOME || "", ".zorai");
161
+ }
162
+
163
+ function getLegacyTamuxRoot(platform, env) {
164
+ var sourceEnv = env || process.env;
165
+ if (platform === "win32") {
166
+ var localAppData = sourceEnv.LOCALAPPDATA;
167
+ if (localAppData) {
168
+ return path.join(localAppData, "tamux");
169
+ }
170
+
171
+ var userProfile = sourceEnv.USERPROFILE || "";
172
+ return path.join(userProfile, "AppData", "Local", "tamux");
173
+ }
174
+
175
+ return path.join(sourceEnv.HOME || "", ".tamux");
176
+ }
177
+
178
+ function getNativeRuntimeZoraiRoot(platform, env) {
179
+ var sourceEnv = env || process.env;
180
+ if (platform === "win32") {
181
+ var localAppData = sourceEnv.LOCALAPPDATA;
182
+ if (localAppData) {
183
+ return path.join(localAppData, "zorai");
184
+ }
185
+
186
+ var userProfile = sourceEnv.USERPROFILE || "";
187
+ return path.join(userProfile, "AppData", "Local", "zorai");
188
+ }
189
+
190
+ return path.join(sourceEnv.HOME || "", ".zorai");
191
+ }
192
+
193
+ function migrateLegacyTamuxRoot(platform, env) {
194
+ var sourceEnv = env || process.env;
195
+ var legacyRoot = getLegacyTamuxRoot(platform, sourceEnv);
196
+ var targetRoot = getNativeRuntimeZoraiRoot(platform, sourceEnv);
197
+
198
+ if (!fs.existsSync(legacyRoot) || fs.existsSync(targetRoot)) {
199
+ return targetRoot;
200
+ }
201
+
202
+ fs.renameSync(legacyRoot, targetRoot);
203
+ return targetRoot;
204
+ }
205
+
206
+ function getRuntimeSkillsDir(platform, env) {
207
+ var pathModule = platform === "win32" ? path.win32 : path.posix;
208
+ return pathModule.join(getRuntimeZoraiRoot(platform, env), "skills");
209
+ }
210
+
211
+ function getRuntimeGuidelinesDir(platform, env) {
212
+ var pathModule = platform === "win32" ? path.win32 : path.posix;
213
+ return pathModule.join(getRuntimeZoraiRoot(platform, env), "guidelines");
214
+ }
215
+
216
+ function getRuntimeCustomAuthPath(platform, env) {
217
+ var pathModule = platform === "win32" ? path.win32 : path.posix;
218
+ return pathModule.join(getRuntimeZoraiRoot(platform, env), "custom-auth.yaml");
219
+ }
220
+
221
+ function ensureCustomAuthTemplate(platform, env) {
222
+ var customAuthPath = getRuntimeCustomAuthPath(platform, env);
223
+ fs.mkdirSync(path.dirname(customAuthPath), { recursive: true });
224
+
225
+ if (fs.existsSync(customAuthPath)) {
226
+ return customAuthPath;
227
+ }
228
+
229
+ fs.writeFileSync(
230
+ customAuthPath,
231
+ "# Add named custom providers here. The daemon reloads this file before\n" +
232
+ "# provider/model setup in the TUI and desktop app.\n" +
233
+ "# Prefer api_key_env for secrets, for example:\n" +
234
+ "# providers:\n" +
235
+ "# - id: local-openai\n" +
236
+ "# name: Local OpenAI-Compatible\n" +
237
+ "# default_base_url: http://127.0.0.1:11434/v1\n" +
238
+ "# default_model: llama3.3\n" +
239
+ "# api_key_env: LOCAL_OPENAI_API_KEY\n" +
240
+ "providers: []\n"
241
+ );
242
+ return customAuthPath;
243
+ }
244
+
245
+ function verifyBufferChecksum(buffer, expectedHash) {
246
+ return crypto.createHash("sha256").update(buffer).digest("hex") === expectedHash;
247
+ }
248
+
249
+ function getReleaseAssetInfo(platform, arch, version) {
250
+ var key = platform + "-" + arch;
251
+ var target = PLATFORM_MAP[key];
252
+ void version;
253
+
254
+ if (!target) {
255
+ return null;
256
+ }
257
+
258
+ return {
259
+ archiveName: "zorai-" + target.archivePlatform + ".zip",
260
+ checksumName: "SHA256SUMS-" + target.checksumPlatform + ".txt",
261
+ bundleChecksumName: "SHA256SUMS.txt",
262
+ requiredBinaries: target.requiredBinaries.slice(),
263
+ skillsArchiveRoot: "skills",
264
+ guidelinesArchiveRoot: "guidelines",
265
+ };
266
+ }
267
+
268
+ function getGlobalBinDir(prefix, platform) {
269
+ if (!prefix) {
270
+ return null;
271
+ }
272
+
273
+ if (platform === "win32") {
274
+ return prefix;
275
+ }
276
+
277
+ return path.join(prefix, "bin");
278
+ }
279
+
280
+ function getInstallUsageHint(isGlobalInstall, globalBinDir) {
281
+ if (isGlobalInstall) {
282
+ if (globalBinDir) {
283
+ return (
284
+ "zorai: if 'zorai' is not found, add '" +
285
+ globalBinDir +
286
+ "' to PATH, then open a new shell and run 'zorai --help'"
287
+ );
288
+ }
289
+
290
+ return "zorai: run 'zorai --help' once your npm global bin directory is on PATH, and open a new shell if it is not recognized immediately";
291
+ }
292
+
293
+ return "zorai: run with 'npx zorai --help' (or 'npm exec zorai -- --help') after a local install";
294
+ }
295
+
296
+ function prependDirectoryToPath(env, directory) {
297
+ var nextEnv = Object.assign({}, env);
298
+ var pathKey =
299
+ Object.keys(nextEnv).find(function (key) {
300
+ return key.toUpperCase() === "PATH";
301
+ }) || "PATH";
302
+ var currentPath = nextEnv[pathKey] || "";
303
+ var nextPath = currentPath
304
+ ? directory + path.delimiter + currentPath
305
+ : directory;
306
+
307
+ nextEnv[pathKey] = nextPath;
308
+ if (pathKey !== "PATH" && nextEnv.PATH === undefined) {
309
+ nextEnv.PATH = nextPath;
310
+ }
311
+
312
+ return nextEnv;
313
+ }
314
+
315
+ function getManagedProcessName(target, platform) {
316
+ if (platform === "win32") {
317
+ return target + ".exe";
318
+ }
319
+
320
+ return target;
321
+ }
322
+
323
+ function getKillCommand(platform, target) {
324
+ var processName = getManagedProcessName(target, platform);
325
+ if (platform === "win32") {
326
+ return {
327
+ command: "taskkill",
328
+ args: ["/IM", processName, "/F"],
329
+ };
330
+ }
331
+
332
+ return {
333
+ command: "pkill",
334
+ args: ["-x", processName],
335
+ };
336
+ }
337
+
338
+ function getProbeCommand(platform, target) {
339
+ var processName = getManagedProcessName(target, platform);
340
+ if (platform === "win32") {
341
+ return {
342
+ command: "tasklist",
343
+ args: ["/FI", "IMAGENAME eq " + processName],
344
+ processName: processName,
345
+ };
346
+ }
347
+
348
+ return {
349
+ command: "pgrep",
350
+ args: ["-x", processName],
351
+ processName: processName,
352
+ };
353
+ }
354
+
355
+ function sleep(ms) {
356
+ return new Promise(function (resolve) {
357
+ setTimeout(resolve, ms);
358
+ });
359
+ }
360
+
361
+ function isManagedProcessRunning(platform, target, execFileSyncImpl) {
362
+ var execFileSync = execFileSyncImpl || childProcess.execFileSync;
363
+ var probe = getProbeCommand(platform, target);
364
+
365
+ try {
366
+ var stdout = execFileSync(probe.command, probe.args, {
367
+ encoding: "utf8",
368
+ stdio: ["ignore", "pipe", "pipe"],
369
+ });
370
+
371
+ if (platform === "win32") {
372
+ return stdout.toLowerCase().includes(probe.processName.toLowerCase());
373
+ }
374
+
375
+ return true;
376
+ } catch (error) {
377
+ if (platform === "win32") {
378
+ var output = String(error.stdout || "");
379
+ return output.toLowerCase().includes(probe.processName.toLowerCase());
380
+ }
381
+
382
+ if (error && error.status === 1) {
383
+ return false;
384
+ }
385
+
386
+ throw error;
387
+ }
388
+ }
389
+
390
+ async function stopManagedZoraiProcesses(platform, deps) {
391
+ var options = deps || {};
392
+ var execFileSync = options.execFileSync || childProcess.execFileSync;
393
+ var sleepImpl = options.sleep || sleep;
394
+ var targets = ["zorai-gateway", "zorai-daemon"];
395
+
396
+ for (var i = 0; i < targets.length; i++) {
397
+ var target = targets[i];
398
+ var kill = getKillCommand(platform, target);
399
+
400
+ try {
401
+ execFileSync(kill.command, kill.args, {
402
+ encoding: "utf8",
403
+ stdio: ["ignore", "pipe", "pipe"],
404
+ });
405
+ } catch (_error) {
406
+ if (!isManagedProcessRunning(platform, target, execFileSync)) {
407
+ continue;
408
+ }
409
+ }
410
+
411
+ var deadline = Date.now() + PROCESS_STOP_TIMEOUT_MS;
412
+ while (Date.now() < deadline) {
413
+ if (!isManagedProcessRunning(platform, target, execFileSync)) {
414
+ break;
415
+ }
416
+
417
+ await sleepImpl(PROCESS_POLL_INTERVAL_MS);
418
+ }
419
+
420
+ if (isManagedProcessRunning(platform, target, execFileSync)) {
421
+ throw new Error("Timed out waiting for " + target + " to exit");
422
+ }
423
+ }
424
+ }
425
+
426
+ function startManagedDaemon(platform, binDir, spawnImpl) {
427
+ var spawn = spawnImpl || childProcess.spawn;
428
+ var daemonPath = path.join(binDir, getManagedProcessName("zorai-daemon", platform));
429
+ var child = spawn(daemonPath, [], {
430
+ detached: true,
431
+ stdio: "ignore",
432
+ windowsHide: true,
433
+ });
434
+
435
+ if (typeof child.unref === "function") {
436
+ child.unref();
437
+ }
438
+
439
+ return daemonPath;
440
+ }
441
+
442
+ async function maybeRefreshDaemonAfterInstall(options, installWork, deps) {
443
+ var settings = options || {};
444
+ var helpers = deps || {};
445
+ var stopProcesses = helpers.stopProcesses || stopManagedZoraiProcesses;
446
+ var startDaemon = helpers.startDaemon || startManagedDaemon;
447
+
448
+ if (!settings.isGlobalInstall) {
449
+ return installWork();
450
+ }
451
+
452
+ console.log("zorai: stopping existing daemon before replacing binaries...");
453
+ await stopProcesses(settings.platform);
454
+ var result = await installWork();
455
+ var daemonPath = startDaemon(settings.platform, settings.binDir);
456
+ console.log("zorai: restarted daemon from " + daemonPath);
457
+ return result;
458
+ }
459
+
460
+ function extractRequiredBinaries(archiveData, releaseInfo) {
461
+ var AdmZip = require("adm-zip");
462
+ var archive = new AdmZip(archiveData);
463
+ var entries = archive.getEntries();
464
+
465
+ for (var i = 0; i < releaseInfo.requiredBinaries.length; i++) {
466
+ var binaryName = releaseInfo.requiredBinaries[i];
467
+ var entry = entries.find(function (item) {
468
+ return item.entryName === binaryName;
469
+ });
470
+
471
+ if (!entry) {
472
+ throw new Error(
473
+ "Release bundle is missing required binary " + binaryName
474
+ );
475
+ }
476
+
477
+ fs.writeFileSync(path.join(BIN_DIR, binaryName), entry.getData());
478
+ }
479
+ }
480
+
481
+ function extractBundledTree(archiveData, archiveRootName, targetDir, skipExisting) {
482
+ var AdmZip = require("adm-zip");
483
+ var archive = new AdmZip(archiveData);
484
+ var entries = archive.getEntries();
485
+ var archiveRoot = archiveRootName + "/";
486
+
487
+ fs.mkdirSync(targetDir, { recursive: true });
488
+
489
+ for (var i = 0; i < entries.length; i++) {
490
+ var entry = entries[i];
491
+ if (!entry.entryName.startsWith(archiveRoot) || entry.isDirectory) {
492
+ continue;
493
+ }
494
+
495
+ var relativePath = entry.entryName.slice(archiveRoot.length);
496
+ if (!relativePath) {
497
+ continue;
498
+ }
499
+
500
+ var destinationPath = path.join(targetDir, relativePath);
501
+ if (skipExisting && fs.existsSync(destinationPath)) {
502
+ continue;
503
+ }
504
+
505
+ fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
506
+ fs.writeFileSync(destinationPath, entry.getData());
507
+ }
508
+ }
509
+
510
+ function extractBundledSkills(archiveData, releaseInfo, skillsDir) {
511
+ extractBundledTree(archiveData, releaseInfo.skillsArchiveRoot, skillsDir, false);
512
+ }
513
+
514
+ function extractBundledGuidelines(archiveData, releaseInfo, guidelinesDir) {
515
+ extractBundledTree(archiveData, releaseInfo.guidelinesArchiveRoot, guidelinesDir, true);
516
+ }
517
+
518
+ async function verifyExtractedBinaries(checksumsData, releaseInfo) {
519
+ for (var i = 0; i < releaseInfo.requiredBinaries.length; i++) {
520
+ var binaryName = releaseInfo.requiredBinaries[i];
521
+ var expectedHash = parseChecksumFile(checksumsData, binaryName);
522
+ if (!expectedHash) {
523
+ throw new Error(
524
+ "Could not find checksum for required binary " + binaryName
525
+ );
526
+ }
527
+
528
+ var binaryPath = path.join(BIN_DIR, binaryName);
529
+ if (!fs.existsSync(binaryPath)) {
530
+ throw new Error("Required binary was not extracted: " + binaryName);
531
+ }
532
+
533
+ var valid = await verifyChecksum(binaryPath, expectedHash);
534
+ if (!valid) {
535
+ throw new Error("SHA256 checksum mismatch for " + binaryName);
536
+ }
537
+ }
538
+ }
539
+
540
+ function cleanupExtractedBinaries(releaseInfo) {
541
+ if (!releaseInfo) {
542
+ return;
543
+ }
544
+
545
+ for (var i = 0; i < releaseInfo.requiredBinaries.length; i++) {
546
+ try {
547
+ fs.unlinkSync(path.join(BIN_DIR, releaseInfo.requiredBinaries[i]));
548
+ } catch (_e) {
549
+ /* ignore cleanup errors */
550
+ }
551
+ }
552
+ }
553
+
554
+ async function main() {
555
+ var releaseInfo = getReleaseAssetInfo(os.platform(), os.arch(), VERSION);
556
+ var platformKey = os.platform() + "-" + os.arch();
557
+ var targetLabel = releaseInfo
558
+ ? releaseInfo.archiveName
559
+ .replace("zorai-", "")
560
+ .replace(/\.zip$/, "")
561
+ : "unsupported";
562
+
563
+ // --dry-run: print platform detection info and exit without downloading
564
+ if (process.argv.includes("--dry-run")) {
565
+ console.log("zorai install.js dry-run");
566
+ console.log(" platform key: " + platformKey);
567
+ console.log(" target: " + targetLabel);
568
+ console.log(" version: " + VERSION);
569
+ console.log(" bin dir: " + BIN_DIR);
570
+ if (releaseInfo) {
571
+ console.log(" download URL: " + BASE_URL + "/" + releaseInfo.archiveName);
572
+ console.log(" checksum URL: " + BASE_URL + "/" + releaseInfo.checksumName);
573
+ }
574
+ process.exit(0);
575
+ }
576
+
577
+ if (!releaseInfo) {
578
+ console.warn(
579
+ "zorai: unsupported platform " + platformKey + ", skipping binary download"
580
+ );
581
+ process.exit(0);
582
+ }
583
+
584
+ var archiveUrl = BASE_URL + "/" + releaseInfo.archiveName;
585
+ var checksumsUrl = BASE_URL + "/" + releaseInfo.checksumName;
586
+ var isGlobalInstall = process.env.npm_config_global === "true";
587
+ var globalBinDir = getGlobalBinDir(process.env.npm_config_prefix, os.platform());
588
+ migrateLegacyTamuxRoot(os.platform(), process.env);
589
+ var runtimeSkillsDir = getRuntimeSkillsDir(os.platform(), process.env);
590
+ var runtimeGuidelinesDir = getRuntimeGuidelinesDir(os.platform(), process.env);
591
+
592
+ // 1. Ensure bin directory exists
593
+ fs.mkdirSync(BIN_DIR, { recursive: true });
594
+
595
+ // 2. Download SHA256SUMS file
596
+ console.log("zorai: downloading checksums...");
597
+ var checksumsData = await download(checksumsUrl);
598
+
599
+ // 3. Download bundle zip
600
+ console.log("zorai: downloading binaries for " + releaseInfo.archiveName + "...");
601
+ try {
602
+ var archiveData = await download(archiveUrl);
603
+ var archiveChecksum = getArchiveChecksum(checksumsData, releaseInfo);
604
+
605
+ if (archiveChecksum) {
606
+ console.log("zorai: verifying SHA256 checksum...");
607
+ if (!verifyBufferChecksum(archiveData, archiveChecksum)) {
608
+ throw new Error("SHA256 checksum mismatch for " + releaseInfo.archiveName);
609
+ }
610
+ console.log("zorai: checksum OK");
611
+ }
612
+
613
+ await maybeRefreshDaemonAfterInstall(
614
+ {
615
+ isGlobalInstall: isGlobalInstall,
616
+ platform: os.platform(),
617
+ binDir: BIN_DIR,
618
+ },
619
+ async function () {
620
+ console.log("zorai: extracting binaries, skills, and guidelines...");
621
+ extractRequiredBinaries(archiveData, releaseInfo);
622
+ extractBundledSkills(archiveData, releaseInfo, runtimeSkillsDir);
623
+ extractBundledGuidelines(archiveData, releaseInfo, runtimeGuidelinesDir);
624
+ console.log(
625
+ "zorai: custom provider template ready at " +
626
+ ensureCustomAuthTemplate(os.platform(), process.env)
627
+ );
628
+
629
+ if (!archiveChecksum) {
630
+ console.log("zorai: verifying SHA256 checksum...");
631
+ await verifyExtractedBinaries(checksumsData, releaseInfo);
632
+ console.log("zorai: checksum OK");
633
+ }
634
+
635
+ if (os.platform() !== "win32") {
636
+ for (var i = 0; i < releaseInfo.requiredBinaries.length; i++) {
637
+ var binPath = path.join(BIN_DIR, releaseInfo.requiredBinaries[i]);
638
+ if (fs.existsSync(binPath)) {
639
+ fs.chmodSync(binPath, 0o755);
640
+ }
641
+ }
642
+ }
643
+ }
644
+ );
645
+ } catch (err) {
646
+ cleanupExtractedBinaries(releaseInfo);
647
+ throw err;
648
+ }
649
+
650
+ console.log("zorai: installation complete");
651
+ console.log(getInstallUsageHint(isGlobalInstall, globalBinDir));
652
+ }
653
+
654
+ module.exports = main;
655
+ module.exports.GITHUB_OWNER = GITHUB_OWNER;
656
+ module.exports.GITHUB_REPO = GITHUB_REPO;
657
+ module.exports.getGlobalBinDir = getGlobalBinDir;
658
+ module.exports.getArchiveChecksum = getArchiveChecksum;
659
+ module.exports.getReleaseAssetInfo = getReleaseAssetInfo;
660
+ module.exports.getInstallUsageHint = getInstallUsageHint;
661
+ module.exports.getKillCommand = getKillCommand;
662
+ module.exports.getManagedProcessName = getManagedProcessName;
663
+ module.exports.getProbeCommand = getProbeCommand;
664
+ module.exports.getRuntimeGuidelinesDir = getRuntimeGuidelinesDir;
665
+ module.exports.getRuntimeSkillsDir = getRuntimeSkillsDir;
666
+ module.exports.getRuntimeCustomAuthPath = getRuntimeCustomAuthPath;
667
+ module.exports.getRuntimeZoraiRoot = getRuntimeZoraiRoot;
668
+ module.exports.ensureCustomAuthTemplate = ensureCustomAuthTemplate;
669
+ module.exports.extractBundledGuidelines = extractBundledGuidelines;
670
+ module.exports.getLegacyTamuxRoot = getLegacyTamuxRoot;
671
+ module.exports.migrateLegacyTamuxRoot = migrateLegacyTamuxRoot;
672
+ module.exports.isManagedProcessRunning = isManagedProcessRunning;
673
+ module.exports.maybeRefreshDaemonAfterInstall = maybeRefreshDaemonAfterInstall;
674
+ module.exports.parseChecksumFile = parseChecksumFile;
675
+ module.exports.prependDirectoryToPath = prependDirectoryToPath;
676
+ module.exports.startManagedDaemon = startManagedDaemon;
677
+ module.exports.stopManagedZoraiProcesses = stopManagedZoraiProcesses;
678
+
679
+ // Auto-run only when executed directly (postinstall) or via tryFallbackDownload,
680
+ // not when required just for the exported constants.
681
+ if (require.main === module) {
682
+ main().catch(function (err) {
683
+ console.warn("zorai: postinstall binary download failed: " + err.message);
684
+ console.warn("zorai: binaries will be downloaded on first run");
685
+ process.exit(0);
686
+ });
687
+ }
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "zor-ai",
3
+ "version": "0.8.1",
4
+ "description": "The Agent That Lives - daemon-first AI agent runtime",
5
+ "license": "MIT",
6
+ "homepage": "https://zorai.app",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/mkurman/zorai.git"
10
+ },
11
+ "bin": {
12
+ "zorai": "bin/zorai.js",
13
+ "zoi": "bin/zorai.js"
14
+ },
15
+ "scripts": {
16
+ "postinstall": "node install.js"
17
+ },
18
+ "os": ["linux", "darwin", "win32"],
19
+ "cpu": ["x64", "arm64"],
20
+ "engines": {
21
+ "node": ">=18"
22
+ },
23
+ "dependencies": {
24
+ "adm-zip": "^0.5.16"
25
+ },
26
+ "files": [
27
+ "install.js",
28
+ "bin/",
29
+ "README.md",
30
+ "LICENSE"
31
+ ]
32
+ }