@olhapi/maestro 0.1.5-rc.14 → 0.1.5-rc.15

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.md CHANGED
@@ -29,7 +29,7 @@ The docs site is organized around the same operator flow the product uses:
29
29
 
30
30
  ### npm
31
31
 
32
- Install the launcher package:
32
+ Current public npm install on supported platforms:
33
33
 
34
34
  ```bash
35
35
  npm install -g @olhapi/maestro
@@ -43,47 +43,19 @@ npm install -g @olhapi/maestro@next
43
43
 
44
44
  The installed command name is still `maestro`.
45
45
 
46
- This package no longer ships native platform binaries. It installs a thin host
47
- launcher that runs Maestro from the published Docker image, so Docker is now a
48
- first-party runtime requirement for normal CLI use.
46
+ Official npm builds currently cover:
49
47
 
50
- The npm launcher requires Node 24 or newer because it uses the built-in
51
- `node:sqlite` module to inspect the Maestro database before Docker starts.
48
+ | Platform | Arch | Notes |
49
+ | --- | --- | --- |
50
+ | macOS | arm64 | native package |
51
+ | macOS | x64 | native package |
52
+ | Linux | x64 | glibc only |
53
+ | Linux | arm64 | glibc only |
54
+ | Windows | x64 | native package |
52
55
 
53
- The launcher resolves its runtime image in this order:
56
+ Linux npm packages currently target glibc only. Alpine and other musl-based distros should build from source or use Docker.
54
57
 
55
- - `MAESTRO_IMAGE`
56
- - the locally pinned image from `~/.maestro/launcher/runtime.json`
57
- - the npm package version you installed
58
-
59
- Pull and pin the latest runtime image explicitly:
60
-
61
- ```bash
62
- maestro self-update
63
- ```
64
-
65
- Pin a specific image version:
66
-
67
- ```bash
68
- maestro self-update --version 0.117.0
69
- ```
70
-
71
- Validate the local launcher install, Docker access, and runtime pin:
72
-
73
- ```bash
74
- maestro doctor install
75
- ```
76
-
77
- ### Curl Installer
78
-
79
- If you want the same launcher without npm, install it with the repository
80
- script:
81
-
82
- ```bash
83
- curl -fsSL https://raw.githubusercontent.com/olhapi/maestro/main/scripts/install_maestro.sh | sh
84
- ```
85
-
86
- ### Docker Runtime
58
+ ### Docker
87
59
 
88
60
  Published image:
89
61
 
@@ -105,8 +77,7 @@ docker run --rm -v ./repo:/repo -v ./data:/data ghcr.io/olhapi/maestro:latest ru
105
77
 
106
78
  ### Build From Source
107
79
 
108
- For local development, contributor workflows, or environments where Docker is
109
- not the right runtime:
80
+ For local development or unsupported platforms:
110
81
 
111
82
  ```bash
112
83
  go build -o maestro ./cmd/maestro
@@ -118,7 +89,7 @@ development package for the standard `make build` / `make test` flow.
118
89
  Local contributor Docker build:
119
90
 
120
91
  ```bash
121
- docker build -t maestro-local --build-arg CODEX_VERSION="$(./scripts/codex_supported_version.sh)" .
92
+ docker build -t maestro-local .
122
93
  ```
123
94
 
124
95
  ## Quick Start
@@ -161,12 +132,6 @@ When `--db` is omitted, Maestro uses `~/.maestro/maestro.db` by default. When `-
161
132
 
162
133
  Running `maestro run` without `repo_path` starts the shared daemon for the current database. It does not infer the repo from your shell working directory.
163
134
 
164
- Before Docker starts, `maestro run` performs a one-time preflight pass. When a
165
- database file exists, the launcher reads the discovered workflow files, resolves
166
- their `workspace.root` values, and mounts the repo and workspace directories up
167
- front. If the database file does not exist yet, it skips discovery so fresh
168
- bootstrap flows still work.
169
-
170
135
  Issue images are stored next to the active database under `assets/images`. With the default database path, that means `~/.maestro/assets/images`. If you run with `--db /custom/path/maestro.db`, image assets move to `/custom/path/assets/images`.
171
136
 
172
137
  The preview warning on `run` is intentional. Pass `--i-understand-that-this-will-be-running-without-the-usual-guardrails` only when unattended Codex execution is actually what you want.
@@ -417,10 +382,9 @@ Repo-managed Git hooks stay targeted:
417
382
  - staged website changes run Astro checks and website tests
418
383
  - staged workspace and hook changes run the full `pnpm verify` suite
419
384
  - `pnpm verify` runs the JS lint/test/check/smoke flow, npm packaging unit test, and Go build/test/coverage/race gates
420
- - `pnpm run verify:pre-push` adds real Docker image build smoke, tarball install smoke, local-registry install smoke, curl-installer smoke, the shared retry stress test, and the full retry-safety harness on top of `pnpm verify`
421
- - `pnpm run verify:ci` is the lean GitHub Actions gate: web verification, launcher packaging tests, and `go test ./...`
385
+ - `pnpm run verify:pre-push` adds current-host npm packaging smoke, the shared retry stress test, and the full retry-safety harness on top of `pnpm verify`
422
386
  - package-scoped root commands such as `pnpm run frontend:test` and `pnpm run website:build` now go through `turbo --filter=...` so they benefit from task caching too
423
- - `pre-push` now runs `pnpm run verify:pre-push`, while GitHub Actions stays on the smaller `pnpm run verify:ci` gate
387
+ - `pre-push` now runs `pnpm run verify:pre-push`, leaving GitHub Actions with the cross-platform packaging matrix and registry smoke coverage
424
388
 
425
389
  ## License
426
390
 
package/bin/maestro.js CHANGED
@@ -1,15 +1,26 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const nodeMajor = Number.parseInt(process.versions.node.split(".")[0], 10);
4
- if (!Number.isInteger(nodeMajor) || nodeMajor < 24) {
5
- process.stderr.write(`Maestro's npm launcher requires Node 24 or newer; found ${process.versions.node}\n`);
6
- process.exit(1);
7
- }
8
-
9
- const { main } = require("../lib/cli");
3
+ const { spawnSync } = require("node:child_process");
4
+ const { getExePath } = require("../lib/get-exe-path");
10
5
 
11
- main(process.argv.slice(2)).catch((error) => {
6
+ let exePath;
7
+ try {
8
+ exePath = getExePath();
9
+ } catch (error) {
12
10
  const message = error instanceof Error ? error.message : String(error);
13
11
  process.stderr.write(`${message}\n`);
14
12
  process.exit(1);
15
- });
13
+ }
14
+
15
+ const result = spawnSync(exePath, process.argv.slice(2), { stdio: "inherit" });
16
+ if (result.error) {
17
+ process.stderr.write(`${result.error.message}\n`);
18
+ process.exit(1);
19
+ }
20
+ if (typeof result.status === "number") {
21
+ process.exit(result.status);
22
+ }
23
+ if (result.signal) {
24
+ process.stderr.write(`maestro terminated with signal ${result.signal}\n`);
25
+ }
26
+ process.exit(1);
@@ -0,0 +1,118 @@
1
+ const fs = require("node:fs");
2
+ const path = require("node:path");
3
+
4
+ const supportedTargets = Object.freeze({
5
+ "darwin:arm64": {
6
+ packageName: "@olhapi/maestro-darwin-arm64",
7
+ label: "darwin/arm64",
8
+ },
9
+ "darwin:x64": {
10
+ packageName: "@olhapi/maestro-darwin-x64",
11
+ label: "darwin/x64",
12
+ },
13
+ "linux:x64": {
14
+ packageName: "@olhapi/maestro-linux-x64-gnu",
15
+ label: "linux/x64 (glibc)",
16
+ },
17
+ "linux:arm64": {
18
+ packageName: "@olhapi/maestro-linux-arm64-gnu",
19
+ label: "linux/arm64 (glibc)",
20
+ },
21
+ "win32:x64": {
22
+ packageName: "@olhapi/maestro-win32-x64",
23
+ label: "win32/x64",
24
+ },
25
+ });
26
+
27
+ function resolveTarget(platform, arch) {
28
+ return supportedTargets[`${platform}:${arch}`] ?? null;
29
+ }
30
+
31
+ function supportedTargetSummary() {
32
+ return Object.values(supportedTargets)
33
+ .map((target) => target.label)
34
+ .join(", ");
35
+ }
36
+
37
+ function readProcessReport() {
38
+ if (!process.report || typeof process.report.getReport !== "function") {
39
+ return null;
40
+ }
41
+ try {
42
+ return process.report.getReport();
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+
48
+ function hasGlibcRuntime(report) {
49
+ const version = report && report.header && report.header.glibcVersionRuntime;
50
+ return typeof version === "string" && version.trim() !== "";
51
+ }
52
+
53
+ function buildInstallError(platform, arch, expectedPackageName, report) {
54
+ const parts = [
55
+ `Maestro npm install supports ${supportedTargetSummary()}.`,
56
+ `Could not resolve a packaged binary for ${platform}/${arch}.`,
57
+ ];
58
+ if (expectedPackageName) {
59
+ parts.push(`Expected package: ${expectedPackageName}.`);
60
+ }
61
+ if (platform === "linux") {
62
+ if (!hasGlibcRuntime(report)) {
63
+ parts.push("Linux npm packages currently target glibc only. Alpine and other musl-based distros should build from source or use Docker.");
64
+ } else {
65
+ parts.push("If your install is on glibc Linux, reinstalling can restore a missing optional dependency. Alpine and other musl-based distros should build from source or use Docker.");
66
+ }
67
+ } else {
68
+ parts.push("If your platform is unsupported, build from source or use Docker.");
69
+ }
70
+ return parts.join(" ");
71
+ }
72
+
73
+ function resolveExePath(options = {}) {
74
+ const platform = options.platform ?? process.platform;
75
+ const arch = options.arch ?? process.arch;
76
+ const pathModule = options.pathModule ?? path;
77
+ const resolvePackageJson =
78
+ options.resolvePackageJson ??
79
+ ((packageName) => require.resolve(`${packageName}/package.json`));
80
+ const existsSync = options.existsSync ?? fs.existsSync;
81
+ const report = options.report ?? readProcessReport();
82
+
83
+ const target = resolveTarget(platform, arch);
84
+ if (!target) {
85
+ throw new Error(buildInstallError(platform, arch, null, report));
86
+ }
87
+
88
+ let packageJsonPath;
89
+ try {
90
+ packageJsonPath = resolvePackageJson(target.packageName);
91
+ } catch {
92
+ throw new Error(buildInstallError(platform, arch, target.packageName, report));
93
+ }
94
+
95
+ let exePath = pathModule.join(
96
+ pathModule.dirname(packageJsonPath),
97
+ "lib",
98
+ platform === "win32" ? "maestro.exe" : "maestro",
99
+ );
100
+ if (platform === "win32" && exePath.length >= 248 && !exePath.startsWith("\\\\?\\")) {
101
+ exePath = `\\\\?\\${exePath}`;
102
+ }
103
+ if (!existsSync(exePath)) {
104
+ throw new Error(`Executable not found in ${target.packageName}: ${exePath}`);
105
+ }
106
+ return exePath;
107
+ }
108
+
109
+ function getExePath() {
110
+ return resolveExePath();
111
+ }
112
+
113
+ module.exports = {
114
+ buildInstallError,
115
+ getExePath,
116
+ resolveExePath,
117
+ resolveTarget,
118
+ };
package/package.json CHANGED
@@ -1,28 +1,24 @@
1
1
  {
2
2
  "name": "@olhapi/maestro",
3
- "version": "0.1.5-rc.14",
4
- "description": "Maestro Docker-backed launcher for global npm installation",
3
+ "version": "0.1.5-rc.15",
4
+ "description": "Maestro CLI packaged for global npm installation",
5
5
  "license": "MIT",
6
- "engines": {
7
- "node": ">=24"
8
- },
9
6
  "bin": {
10
- "maestro": "bin/maestro"
7
+ "maestro": "bin/maestro.js"
11
8
  },
12
9
  "files": [
13
- "bin/maestro",
14
- "bin/maestro.cmd",
15
- "bin/maestro.ps1",
16
10
  "bin/maestro.js",
17
- "lib/browser.js",
18
- "lib/cli.js",
19
- "lib/docker-plan.js",
20
- "lib/install-skills.js",
21
- "lib/runtime-state.js",
22
- "share/skills/maestro",
11
+ "lib/get-exe-path.js",
23
12
  "LICENSE",
24
13
  "README.md"
25
14
  ],
15
+ "optionalDependencies": {
16
+ "@olhapi/maestro-darwin-arm64": "0.1.5-rc.15",
17
+ "@olhapi/maestro-darwin-x64": "0.1.5-rc.15",
18
+ "@olhapi/maestro-linux-x64-gnu": "0.1.5-rc.15",
19
+ "@olhapi/maestro-linux-arm64-gnu": "0.1.5-rc.15",
20
+ "@olhapi/maestro-win32-x64": "0.1.5-rc.15"
21
+ },
26
22
  "repository": {
27
23
  "type": "git",
28
24
  "url": "git+https://github.com/olhapi/maestro.git"
package/bin/maestro DELETED
@@ -1,16 +0,0 @@
1
- #!/usr/bin/env sh
2
-
3
- set -eu
4
-
5
- SCRIPT_PATH="$0"
6
- while [ -L "$SCRIPT_PATH" ]; do
7
- LINK_TARGET=$(readlink "$SCRIPT_PATH")
8
- case "$LINK_TARGET" in
9
- /*) SCRIPT_PATH="$LINK_TARGET" ;;
10
- *) SCRIPT_PATH=$(dirname "$SCRIPT_PATH")/$LINK_TARGET ;;
11
- esac
12
- done
13
-
14
- SCRIPT_DIR=$(CDPATH= cd -- "$(dirname "$SCRIPT_PATH")" && pwd)
15
- NODE_BIN="${MAESTRO_NODE_BIN:-node}"
16
- exec "$NODE_BIN" "$SCRIPT_DIR/maestro.js" "$@"
package/bin/maestro.cmd DELETED
@@ -1,8 +0,0 @@
1
- @ECHO OFF
2
- SETLOCAL
3
- SET SCRIPT_DIR=%~dp0
4
- IF DEFINED MAESTRO_NODE_BIN (
5
- "%MAESTRO_NODE_BIN%" "%SCRIPT_DIR%maestro.js" %*
6
- ) ELSE (
7
- node "%SCRIPT_DIR%maestro.js" %*
8
- )
package/bin/maestro.ps1 DELETED
@@ -1,4 +0,0 @@
1
- $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
2
- $NodeBin = if ($env:MAESTRO_NODE_BIN) { $env:MAESTRO_NODE_BIN } else { "node" }
3
- & $NodeBin (Join-Path $ScriptDir "maestro.js") @args
4
- exit $LASTEXITCODE
package/lib/browser.js DELETED
@@ -1,79 +0,0 @@
1
- const { spawn } = require("node:child_process");
2
-
3
- const DEFAULT_BROWSER_TIMEOUT_MS = 3000;
4
- const DEFAULT_BROWSER_POLL_INTERVAL_MS = 50;
5
-
6
- function terminalsInteractive(streams = process) {
7
- return Boolean(streams.stdout && streams.stdout.isTTY && streams.stderr && streams.stderr.isTTY);
8
- }
9
-
10
- function browserOpenDisabled(env = process.env) {
11
- const value = env && env.MAESTRO_DISABLE_BROWSER_OPEN;
12
- return typeof value === "string" && value.trim() !== "";
13
- }
14
-
15
- function browserCommandFor(platform, url) {
16
- switch (platform) {
17
- case "darwin":
18
- return ["open", [url]];
19
- case "linux":
20
- case "freebsd":
21
- case "openbsd":
22
- case "netbsd":
23
- return ["xdg-open", [url]];
24
- case "win32":
25
- return ["rundll32", ["url.dll,FileProtocolHandler", url]];
26
- default:
27
- throw new Error(`unsupported platform ${platform}`);
28
- }
29
- }
30
-
31
- async function waitForHealthy(url, options = {}) {
32
- const timeoutMs = options.timeoutMs || DEFAULT_BROWSER_TIMEOUT_MS;
33
- const pollIntervalMs = options.pollIntervalMs || DEFAULT_BROWSER_POLL_INTERVAL_MS;
34
- const fetchImpl = options.fetchImpl || fetch;
35
- const deadline = Date.now() + timeoutMs;
36
- let lastError = null;
37
-
38
- while (Date.now() < deadline) {
39
- try {
40
- const response = await fetchImpl(url);
41
- if (response.ok) {
42
- await response.arrayBuffer();
43
- return;
44
- }
45
- lastError = new Error(`health returned ${response.status}`);
46
- } catch (error) {
47
- lastError = error;
48
- }
49
- await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
50
- }
51
-
52
- if (lastError) {
53
- throw lastError;
54
- }
55
- throw new Error(`timed out waiting for ${url}`);
56
- }
57
-
58
- async function openDashboardWhenReady(baseURL, options = {}) {
59
- if (!baseURL || browserOpenDisabled(options.env || process.env) || !terminalsInteractive(options.streams || process)) {
60
- return;
61
- }
62
-
63
- const normalizedBaseURL = String(baseURL).replace(/\/+$/, "");
64
- await waitForHealthy(`${normalizedBaseURL}/health`, options);
65
- const [command, args] = browserCommandFor(options.platform || process.platform, normalizedBaseURL);
66
- const child = spawn(command, args, {
67
- detached: true,
68
- stdio: "ignore",
69
- });
70
- child.unref();
71
- }
72
-
73
- module.exports = {
74
- browserOpenDisabled,
75
- browserCommandFor,
76
- openDashboardWhenReady,
77
- terminalsInteractive,
78
- waitForHealthy,
79
- };