@theharshitsingh/ao 0.10.1 → 0.10.2

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/ao.js CHANGED
@@ -1,41 +1,65 @@
1
1
  #!/usr/bin/env node
2
+ // Pure-Node shim: resolve the per-platform optionalDependency that holds the
3
+ // prebuilt Go `ao` binary for this host, then exec it transparently.
4
+ // Zero install scripts; zero third-party deps. The binary is delivered by npm
5
+ // installing only the matching `@theharshitsingh/ao-<platform>-<arch>` package (its
6
+ // os/cpu fields gate the rest out).
7
+
2
8
  "use strict";
3
9
 
4
- // `ao`: open the Agent Orchestrator desktop app. Self-healing: if the bundle is
5
- // missing (e.g. installed with --ignore-scripts, or postinstall deferred the
6
- // download), fetch it first, then launch. The app is the product; this is just
7
- // the launcher.
8
-
9
- const { spawn } = require("node:child_process");
10
- const { ensureBundle, appLaunchPath } = require("../lib/bootstrap");
11
-
12
- async function main() {
13
- await ensureBundle();
14
- const app = appLaunchPath();
15
-
16
- let child;
17
- if (process.platform === "darwin") {
18
- // `open` hands the .app to LaunchServices so it runs as a normal GUI app.
19
- child = spawn("open", [app, ...process.argv.slice(2)], {
20
- detached: true,
21
- stdio: "ignore",
22
- });
23
- } else {
24
- // linux: launch the packaged executable directly. Ensure the exec bit
25
- // survived unzip (some unzip impls drop it).
26
- const exe = `${app}/agent-orchestrator`;
27
- try {
28
- require("node:fs").chmodSync(exe, 0o755);
29
- } catch {}
30
- child = spawn(exe, process.argv.slice(2), {
31
- detached: true,
32
- stdio: "ignore",
33
- });
34
- }
35
- child.unref();
10
+ const { spawnSync } = require("node:child_process");
11
+ const path = require("node:path");
12
+
13
+ // npm cpu names match process.arch (x64/arm64); npm os names match
14
+ // process.platform (darwin/win32/linux). Our platform packages are named
15
+ // `@theharshitsingh/ao-<platform>-<arch>` to mirror that exactly.
16
+ const platform = process.platform;
17
+ const arch = process.arch;
18
+ const pkg = `@theharshitsingh/ao-${platform}-${arch}`;
19
+ const binName = platform === "win32" ? "ao.exe" : "ao";
20
+
21
+ function resolveBinary() {
22
+ // require.resolve the platform package's package.json to find its install
23
+ // dir (works whether hoisted to a parent node_modules or nested), then join
24
+ // the binary path. The platform package ships the binary under bin/.
25
+ let pkgJsonPath;
26
+ try {
27
+ pkgJsonPath = require.resolve(`${pkg}/package.json`);
28
+ } catch {
29
+ return null;
30
+ }
31
+ return path.join(path.dirname(pkgJsonPath), "bin", binName);
32
+ }
33
+
34
+ const binary = resolveBinary();
35
+
36
+ if (!binary) {
37
+ process.stderr.write(
38
+ `@theharshitsingh/ao: no prebuilt binary for ${platform}-${arch}.\n` +
39
+ `The optional dependency ${pkg} is not installed, which usually means\n` +
40
+ `this platform is unsupported. Supported: darwin-arm64, darwin-x64,\n` +
41
+ `win32-x64, linux-x64.\n`,
42
+ );
43
+ process.exit(1);
36
44
  }
37
45
 
38
- main().catch((err) => {
39
- console.error(err.message);
40
- process.exit(1);
41
- });
46
+ const result = spawnSync(binary, process.argv.slice(2), { stdio: "inherit" });
47
+
48
+ if (result.error) {
49
+ if (result.error.code === "ENOENT") {
50
+ process.stderr.write(
51
+ `@theharshitsingh/ao: binary not found at ${binary}.\n` +
52
+ `Reinstall @theharshitsingh/ao to restore the platform package.\n`,
53
+ );
54
+ } else {
55
+ process.stderr.write(`@theharshitsingh/ao: failed to run binary: ${result.error.message}\n`);
56
+ }
57
+ process.exit(1);
58
+ }
59
+
60
+ // Propagate signal-terminations as a conventional 128+signal code, else the
61
+ // child's own exit code.
62
+ if (result.signal) {
63
+ process.exit(1);
64
+ }
65
+ process.exit(result.status === null ? 1 : result.status);
package/package.json CHANGED
@@ -1,41 +1,18 @@
1
1
  {
2
- "name": "@theharshitsingh/ao",
3
- "version": "0.10.1",
4
- "description": "Bootstrapper for the Agent Orchestrator desktop app. Downloads the signed app for your platform and launches it.",
5
- "license": "MIT",
6
- "homepage": "https://github.com/harshitsinghbhandari/agent-orchestrator",
7
- "repository": {
8
- "type": "git",
9
- "url": "git+https://github.com/harshitsinghbhandari/agent-orchestrator.git"
10
- },
11
- "bin": {
12
- "ao": "bin/ao.js"
13
- },
14
- "scripts": {
15
- "postinstall": "node install.js",
16
- "test": "node test/selfcheck.js"
17
- },
18
- "files": [
19
- "bin/",
20
- "lib/",
21
- "install.js",
22
- "README.md"
23
- ],
24
- "engines": {
25
- "node": ">=20"
26
- },
27
- "publishConfig": {
28
- "access": "public"
29
- },
30
- "ao": {
31
- "releaseRepo": "harshitsinghbhandari/agent-orchestrator",
32
- "releaseTag": "npm-bootstrap-0.10.0",
33
- "checksums": "SHA256SUMS",
34
- "assets": {
35
- "darwin-arm64": "agent-orchestrator-darwin-arm64.zip",
36
- "darwin-x64": "agent-orchestrator-darwin-x64.zip",
37
- "linux-x64": "agent-orchestrator-linux-x64.zip",
38
- "linux-arm64": "agent-orchestrator-linux-arm64.zip"
39
- }
40
- }
2
+ "name": "@theharshitsingh/ao",
3
+ "version": "0.10.2",
4
+ "description": "Agent Orchestrator CLI. Resolves and runs the prebuilt Go binary for your platform.",
5
+ "license": "MIT",
6
+ "bin": {
7
+ "ao": "bin/ao.js"
8
+ },
9
+ "files": [
10
+ "bin/"
11
+ ],
12
+ "optionalDependencies": {
13
+ "@theharshitsingh/ao-darwin-arm64": "0.10.2",
14
+ "@theharshitsingh/ao-darwin-x64": "0.10.2",
15
+ "@theharshitsingh/ao-linux-x64": "0.10.2",
16
+ "@theharshitsingh/ao-win32-x64": "0.10.2"
17
+ }
41
18
  }
package/README.md DELETED
@@ -1,29 +0,0 @@
1
- # Agent Orchestrator (npm bootstrapper)
2
-
3
- Install the Agent Orchestrator desktop app from npm:
4
-
5
- ```bash
6
- npm i -g @theharshitsingh/ao
7
- ao
8
- ```
9
-
10
- `npm i -g` installs a small **bootstrapper**, not the app itself. On install it
11
- downloads the signed desktop bundle for your platform from the matching GitHub
12
- Release, verifies its SHA-256, and unpacks it. Running `ao` launches the app.
13
-
14
- This is a one-time delivery channel. Once the app is installed it updates itself
15
- through its built-in updater. `npm update` is **not** the update path.
16
-
17
- ## Supported platforms
18
-
19
- This build ships macOS `darwin-arm64` (Apple Silicon) and `darwin-x64` (Intel),
20
- both signed + notarized, plus Linux `linux-x64` and `linux-arm64` portable bundles
21
- (checksum-verified; Linux has no Apple-style notarization). Other platforms,
22
- including Windows, fail the install with a pointer to the
23
- [Releases page](https://github.com/harshitsinghbhandari/agent-orchestrator/releases),
24
- where direct installers (`.dmg` / `.deb` / `.exe`) live.
25
-
26
- ## `--ignore-scripts`
27
-
28
- If you install with `npm install --ignore-scripts`, the postinstall download is
29
- skipped; the first `ao` run downloads the bundle instead. Either way it works.
package/install.js DELETED
@@ -1,23 +0,0 @@
1
- "use strict";
2
-
3
- // postinstall: fetch the platform bundle eagerly so the first `ao` is instant.
4
- // On an unsupported platform we fail loudly and non-zero (no silent half-install);
5
- // on a transient download failure we DON'T fail the npm install, because the `ao`
6
- // launcher will retry the fetch on first run (this also covers --ignore-scripts).
7
-
8
- const { ensureBundle, unsupportedError, assetName } = require("./lib/bootstrap");
9
-
10
- async function main() {
11
- if (!assetName()) {
12
- console.error(String(unsupportedError().message));
13
- process.exit(1);
14
- }
15
- try {
16
- await ensureBundle();
17
- } catch (err) {
18
- console.error(`Agent Orchestrator: deferred download to first run (${err.message})`);
19
- // Non-fatal: bin/ao.js will fetch on first launch.
20
- }
21
- }
22
-
23
- main();
package/lib/bootstrap.js DELETED
@@ -1,163 +0,0 @@
1
- "use strict";
2
-
3
- // Shared bootstrapper logic: detect platform -> map to a GitHub Release asset ->
4
- // download -> verify SHA-256 -> extract -> expose the app path. Used by both the
5
- // postinstall (install.js) and the `ao` launcher (bin/ao.js), so a user who
6
- // installed with --ignore-scripts still gets a working app on first run.
7
- //
8
- // Zero runtime dependencies: Node 20+ global fetch + built-ins only.
9
-
10
- const fs = require("node:fs");
11
- const path = require("node:path");
12
- const crypto = require("node:crypto");
13
- const { spawnSync } = require("node:child_process");
14
- const { Readable } = require("node:stream");
15
- const { pipeline } = require("node:stream/promises");
16
-
17
- const pkg = require("../package.json");
18
- const cfg = pkg.ao;
19
-
20
- const packageRoot = path.resolve(__dirname, "..");
21
- const vendorDir = path.join(packageRoot, "vendor");
22
-
23
- function platformKey() {
24
- return `${process.platform}-${process.arch}`;
25
- }
26
-
27
- function assetName() {
28
- return cfg.assets[platformKey()];
29
- }
30
-
31
- function releasesPage() {
32
- return `https://github.com/${cfg.releaseRepo}/releases`;
33
- }
34
-
35
- function downloadUrl(name) {
36
- return `https://github.com/${cfg.releaseRepo}/releases/download/${cfg.releaseTag}/${name}`;
37
- }
38
-
39
- // The unpacked app bundle's launch target, per OS. Only darwin ships an asset
40
- // today; linux is mapped for when its archive lands.
41
- function appLaunchPath() {
42
- if (process.platform === "darwin") {
43
- return path.join(vendorDir, "Agent Orchestrator.app");
44
- }
45
- // linux: maker-zip lays down "Agent Orchestrator-linux-<arch>/" with the
46
- // executable inside it.
47
- return path.join(vendorDir, `Agent Orchestrator-linux-${process.arch}`);
48
- }
49
-
50
- function isInstalled() {
51
- return fs.existsSync(appLaunchPath());
52
- }
53
-
54
- function unsupportedError() {
55
- return new Error(
56
- `Agent Orchestrator has no npm bundle for ${platformKey()}.\n` +
57
- `Install the desktop app directly instead: ${releasesPage()}`,
58
- );
59
- }
60
-
61
- // Parse a `<sha256> <filename>` line out of a SHA256SUMS file.
62
- function expectedSha(sumsText, name) {
63
- for (const line of sumsText.split("\n")) {
64
- const m = line.trim().match(/^([0-9a-f]{64})\s+\*?(.+)$/i);
65
- if (m && path.basename(m[2]) === name) return m[1].toLowerCase();
66
- }
67
- return null;
68
- }
69
-
70
- async function fetchWithRetry(url, tries = 3) {
71
- let lastErr;
72
- for (let i = 0; i < tries; i++) {
73
- try {
74
- const res = await fetch(url, { redirect: "follow" });
75
- if (!res.ok) throw new Error(`HTTP ${res.status} for ${url}`);
76
- return res;
77
- } catch (err) {
78
- lastErr = err;
79
- // ponytail: linear backoff, fine for a 3-shot install download.
80
- await new Promise((r) => setTimeout(r, 750 * (i + 1)));
81
- }
82
- }
83
- throw lastErr;
84
- }
85
- // ponytail: no HTTP(S)_PROXY plumbing yet. Add an undici ProxyAgent dispatcher
86
- // here if corp-proxy installs need it; the GitHub CDN is reachable directly for
87
- // the common case.
88
-
89
- async function downloadToFile(url, dest) {
90
- const res = await fetchWithRetry(url);
91
- await pipeline(Readable.fromWeb(res.body), fs.createWriteStream(dest));
92
- }
93
-
94
- function sha256File(file) {
95
- const hash = crypto.createHash("sha256");
96
- hash.update(fs.readFileSync(file));
97
- return hash.digest("hex");
98
- }
99
-
100
- function extract(archive, dest) {
101
- fs.mkdirSync(dest, { recursive: true });
102
- if (archive.endsWith(".zip")) {
103
- // ditto preserves the code signature / xattrs of the signed .app better
104
- // than unzip. Present on every macOS.
105
- const tool = process.platform === "darwin" ? "ditto" : "unzip";
106
- const args = tool === "ditto" ? ["-x", "-k", archive, dest] : ["-q", archive, "-d", dest];
107
- const r = spawnSync(tool, args, { stdio: "inherit" });
108
- if (r.status !== 0) throw new Error(`extract failed (${tool} exit ${r.status})`);
109
- } else {
110
- const r = spawnSync("tar", ["-xzf", archive, "-C", dest], { stdio: "inherit" });
111
- if (r.status !== 0) throw new Error(`extract failed (tar exit ${r.status})`);
112
- }
113
- }
114
-
115
- // Idempotent: returns the app path, downloading+verifying+extracting only if the
116
- // bundle is not already present. Throws loudly (never half-installs) on any
117
- // unsupported platform, missing asset, or checksum mismatch.
118
- async function ensureBundle({ quiet = false } = {}) {
119
- if (isInstalled()) return appLaunchPath();
120
-
121
- const name = assetName();
122
- if (!name) throw unsupportedError();
123
-
124
- const log = quiet ? () => {} : (m) => console.error(m);
125
- const tmp = fs.mkdtempSync(path.join(require("node:os").tmpdir(), "ao-dl-"));
126
- const archive = path.join(tmp, name);
127
-
128
- try {
129
- log(`Agent Orchestrator: downloading ${name} ...`);
130
- await downloadToFile(downloadUrl(name), archive);
131
-
132
- log("Agent Orchestrator: verifying checksum ...");
133
- const sumsRes = await fetchWithRetry(downloadUrl(cfg.checksums));
134
- const want = expectedSha(await sumsRes.text(), name);
135
- if (!want) throw new Error(`no ${name} entry in ${cfg.checksums}`);
136
- const got = sha256File(archive);
137
- if (got !== want) {
138
- throw new Error(`checksum mismatch for ${name}\n expected ${want}\n got ${got}`);
139
- }
140
-
141
- log("Agent Orchestrator: unpacking ...");
142
- fs.rmSync(vendorDir, { recursive: true, force: true });
143
- extract(archive, vendorDir);
144
- if (!isInstalled()) {
145
- throw new Error(`unpacked archive did not contain the expected app at ${appLaunchPath()}`);
146
- }
147
- log("Agent Orchestrator: ready.");
148
- return appLaunchPath();
149
- } finally {
150
- fs.rmSync(tmp, { recursive: true, force: true });
151
- }
152
- }
153
-
154
- module.exports = {
155
- ensureBundle,
156
- isInstalled,
157
- appLaunchPath,
158
- assetName,
159
- platformKey,
160
- expectedSha,
161
- releasesPage,
162
- unsupportedError,
163
- };