From 9bc959e470210a730981b7f4c73c15787a4ecf4f Mon Sep 17 00:00:00 2001 From: Anvil Date: Thu, 6 Aug 2026 20:52:18 +0000 Subject: [PATCH 1/6] =?UTF-8?q?fix:=20portable=20compiled=20binaries=20?= =?UTF-8?q?=E2=80=94=20alias=20sodium-native=20to=20sodium-javascript?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: noise-handshake → sodium-universal → sodium-native. bun build --compile bakes the build host's module path into the binary, so the native addon resolution fails on any consumer box ("Cannot find addon '.'" from /home/runner/...). Fix: two-phase build via scripts/build-portable.ts: 1. Bun.build() with a plugin that aliases sodium-native → sodium-javascript (pure JS) 2. bun build --compile on the bundled output sodium-javascript and its transitive dependencies (blake2b, chacha20-universal, nanoassert, sha256-universal, sha512-universal, siphash24, xsalsa20) are added as devDependencies of @tpsdev-ai/cli — build-time only, bundled into the binary. Platform packages (cli-linux-x64, cli-linux-arm64, cli-darwin-x64, cli-darwin-arm64) drop the sodium-native runtime dependency — no longer needed. CI: release workflow updated to use the new build script. New smoke.yml workflow runs per-platform acceptance (--version, identity init, Noise handshake via branch init, zero baked /home/runner paths) on push/PR to main. Verified locally (linux-x64): binary passes all four acceptance checks in a clean directory with the repo absent. --- .github/workflows/release.yml | 43 +++++++--- .github/workflows/smoke.yml | 104 +++++++++++++++++++++++++ bun.lock | 46 ++++++++++- package.json | 12 ++- packages/cli-darwin-arm64/package.json | 1 - packages/cli-darwin-x64/package.json | 1 - packages/cli-linux-arm64/package.json | 1 - packages/cli-linux-x64/package.json | 1 - packages/cli/package.json | 16 +++- packages/cli/scripts/build-portable.ts | 80 +++++++++++++++++++ 10 files changed, 284 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/smoke.yml create mode 100644 packages/cli/scripts/build-portable.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 07bbb55..1a19b48 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -77,11 +77,9 @@ jobs: PKG: ${{ matrix.pkg }} ART: ${{ matrix.artifact }} run: | - PKG_VERSION=$(node -p "require('./packages/cli/package.json').version") - bun build --compile ./packages/cli/bin/tps.ts \ - --target=${TARGET} \ - --define "INJECTED_VERSION=\"$PKG_VERSION\"" \ - --outfile=packages/cli/dist/${ART} + bun run --filter @tpsdev-ai/cli build:binary:${TARGET#bun-} + # The build script outputs to dist/tps-, copy to artifact name + cp packages/cli/dist/tps-${TARGET#bun-} packages/cli/dist/${ART} sha256sum packages/cli/dist/${ART} > packages/cli/dist/${ART}.sha256 - name: Stage binary env: @@ -115,8 +113,8 @@ jobs: - run: sfw bun install --frozen-lockfile - name: Build linux-x64 release binary run: | - PKG_VERSION=$(node -p "require('./packages/cli/package.json').version") - bun build --compile ./packages/cli/bin/tps.ts --target=bun-linux-x64 --define "INJECTED_VERSION=\"$PKG_VERSION\"" --outfile=/tmp/tps-release-smoke + bun run --filter @tpsdev-ai/cli build:binary:linux-x64 + cp packages/cli/dist/tps-linux-x64 /tmp/tps-release-smoke chmod +x /tmp/tps-release-smoke - name: Smoke test --version run: | @@ -126,11 +124,36 @@ jobs: echo "❌ version mismatch: binary=$VERSION_OUTPUT package=$PKG_VERSION" exit 1 fi - - name: Smoke test office list + echo "✅ --version OK" + - name: Smoke test identity init + run: | + export HOME=$(mktemp -d) + export TPS_VAULT_KEY="ci-release-smoke" + /tmp/tps-release-smoke identity init --json > /tmp/tps-identity.json 2>&1 + FP=$(node -p "JSON.parse(require('fs').readFileSync('/tmp/tps-identity.json','utf8')).fingerprint") + if [ -z "$FP" ]; then + echo "❌ identity init failed" + exit 1 + fi + echo "✅ identity init OK (fingerprint: $FP)" + - name: Smoke test Noise handshake run: | export HOME=$(mktemp -d) - mkdir -p "$HOME/.tps/branch-office" - TPS_OFFICE_SKIP_VM=1 /tmp/tps-release-smoke office list >/tmp/tps-office-list.log 2>&1 + export TPS_VAULT_KEY="ci-release-smoke" + timeout 15 /tmp/tps-release-smoke branch init --name release-smoke 2>&1 | tee /tmp/tps-branch.log || true + if grep -qE "Branch identity created|Listening on" /tmp/tps-branch.log; then + echo "✅ Noise handshake OK" + else + echo "❌ Noise handshake failed" + exit 1 + fi + - name: Smoke test no baked paths + run: | + if grep -q "/home/runner" /tmp/tps-release-smoke; then + echo "❌ Found /home/runner references in binary" + exit 1 + fi + echo "✅ No baked CI paths" publish-packages: name: Publish npm packages diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml new file mode 100644 index 0000000..8818cd3 --- /dev/null +++ b/.github/workflows/smoke.yml @@ -0,0 +1,104 @@ +name: Smoke (compiled binaries) + +on: + push: + branches: [main] + paths: + - 'packages/cli/**' + - 'packages/agent/**' + - 'bun.lock' + - '.github/workflows/smoke.yml' + pull_request: + branches: [main] + paths: + - 'packages/cli/**' + - 'packages/agent/**' + - 'bun.lock' + - '.github/workflows/smoke.yml' + +permissions: + contents: read + +jobs: + smoke: + name: Smoke ${{ matrix.target }} + strategy: + fail-fast: false + matrix: + include: + - target: linux-x64 + runs-on: ubuntu-latest + - target: linux-arm64 + runs-on: ubuntu-24.04-arm + - target: darwin-x64 + runs-on: macos-13 + - target: darwin-arm64 + runs-on: macos-latest + runs-on: ${{ matrix.runs-on }} + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 + with: + bun-version: "1.3.10" + - uses: socketdev/action@ba6de6cc0565af1f42295590380973573297e31f # v1.3.2 + with: + mode: firewall-free + - run: sfw bun install --frozen-lockfile + - name: Build workspace packages + run: bun run build + - name: Build portable binary (${{ matrix.target }}) + run: bun run --filter @tpsdev-ai/cli build:binary:${{ matrix.target }} + - name: Acceptance — version + run: | + BIN="packages/cli/dist/tps-${{ matrix.target }}" + chmod +x "$BIN" + VERSION_OUTPUT=$("$BIN" --version) + PKG_VERSION=$(node -p "require('./packages/cli/package.json').version") + echo "Binary version: $VERSION_OUTPUT" + echo "Package version: $PKG_VERSION" + if [ "$VERSION_OUTPUT" != "$PKG_VERSION" ]; then + echo "❌ version mismatch" + exit 1 + fi + echo "✅ --version OK" + - name: Acceptance — identity init (exercises sodium) + run: | + BIN="packages/cli/dist/tps-${{ matrix.target }}" + export HOME=$(mktemp -d) + export TPS_VAULT_KEY="ci-smoke-test-key" + "$BIN" identity init --json > /tmp/tps-identity.json 2>&1 + echo "Identity output:" + cat /tmp/tps-identity.json + # Verify we got a valid fingerprint + FP=$(node -p "JSON.parse(require('fs').readFileSync('/tmp/tps-identity.json','utf8')).fingerprint") + if [ -z "$FP" ]; then + echo "❌ identity init failed — no fingerprint" + exit 1 + fi + echo "✅ identity init OK (fingerprint: $FP)" + - name: Acceptance — Noise handshake (branch init) + run: | + BIN="packages/cli/dist/tps-${{ matrix.target }}" + export HOME=$(mktemp -d) + export TPS_VAULT_KEY="ci-smoke-test-key" + # branch init exercises the full noise-handshake → sodium-javascript path + timeout 15 "$BIN" branch init --name smoke-test 2>&1 | tee /tmp/tps-branch.log || true + if grep -q "Branch identity created" /tmp/tps-branch.log; then + echo "✅ Noise handshake OK" + elif grep -q "Listening on" /tmp/tps-branch.log; then + echo "✅ Noise handshake OK (listening)" + else + echo "Branch init output:" + cat /tmp/tps-branch.log + echo "❌ Noise handshake failed" + exit 1 + fi + - name: Acceptance — no baked paths + run: | + BIN="packages/cli/dist/tps-${{ matrix.target }}" + COUNT=$(grep -ac "/home/runner" "$BIN" || echo 0) + if [ "$COUNT" -ne 0 ]; then + echo "❌ Found $COUNT /home/runner references in binary" + exit 1 + fi + echo "✅ No baked CI paths" diff --git a/bun.lock b/bun.lock index d19363b..effec87 100644 --- a/bun.lock +++ b/bun.lock @@ -5,9 +5,17 @@ "": { "name": "@tpsdev-ai/cli", "dependencies": { + "blake2b": "^2.1.4", "canonicalize": "^3.0.0", + "chacha20-universal": "^1.0.4", "handlebars": "^4.7.9", + "nanoassert": "^3.1.0", + "sha256-universal": "^1.2.1", + "sha512-universal": "^1.2.1", + "siphash24": "^1.3.1", + "sodium-javascript": "^0.8.0", "ws": "^8.20.1", + "xsalsa20": "^1.2.0", }, "devDependencies": { "@biomejs/biome": "^2.4.4", @@ -228,8 +236,14 @@ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + "blake2b": ["blake2b@2.1.4", "", { "dependencies": { "blake2b-wasm": "^2.4.0", "nanoassert": "^2.0.0" } }, "sha512-AyBuuJNI64gIvwx13qiICz6H6hpmjvYS5DGkG6jbXMOT8Z3WUJ3V1X0FlhIoT1b/5JtHE3ki+xjtMvu1nn+t9A=="], + + "blake2b-wasm": ["blake2b-wasm@2.4.0", "", { "dependencies": { "b4a": "^1.0.1", "nanoassert": "^2.0.0" } }, "sha512-S1kwmW2ZhZFFFOghcx73+ZajEfKBqhP82JMssxtLVMxlaPea1p9uoLiUZ5WYyHn0KddwbLc+0vh4wR0KBNoT5w=="], + "canonicalize": ["canonicalize@3.0.0", "", { "bin": { "canonicalize": "bin/canonicalize.js" } }, "sha512-yYLfHyDMIXRyRqsKBRLX023riFLpXY2YOfdtqKXZRZy9qsfOJ9U+4F9YZL7MEzL5+ziN2x2nlBvY/Voi3EBljA=="], + "chacha20-universal": ["chacha20-universal@1.0.4", "", { "dependencies": { "nanoassert": "^2.0.0" } }, "sha512-/IOxdWWNa7nRabfe7+oF+jVkGjlr2xUL4J8l/OvzZhj+c9RpMqoo3Dq+5nU1j/BflRV4BKnaQ4+4oH1yBpQG1Q=="], + "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="], @@ -288,7 +302,7 @@ "msgpackr-extract": ["msgpackr-extract@3.0.3", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA=="], - "nanoassert": ["nanoassert@2.0.0", "", {}, "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA=="], + "nanoassert": ["nanoassert@3.1.0", "", {}, "sha512-pHCA6mU/H4BzVxHSFzfuhb6NJ0des9pPWsnTERnOQ1a1Ft/M15+b8+sNCsHNWgLN+NVr5QaVlwp0IE7NfST+xw=="], "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="], @@ -318,14 +332,26 @@ "scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], + "sha256-universal": ["sha256-universal@1.2.1", "", { "dependencies": { "b4a": "^1.0.1", "sha256-wasm": "^2.2.1" } }, "sha512-ghn3muhdn1ailCQqqceNxRgkOeZSVfSE13RQWEg6njB+itsFzGVSJv+O//2hvNXZuxVIRyNzrgsZ37SPDdGJJw=="], + + "sha256-wasm": ["sha256-wasm@2.2.2", "", { "dependencies": { "b4a": "^1.0.1", "nanoassert": "^2.0.0" } }, "sha512-qKSGARvao+JQlFiA+sjJZhJ/61gmW/3aNLblB2rsgIxDlDxsJPHo8a1seXj12oKtuHVgJSJJ7QEGBUYQN741lQ=="], + + "sha512-universal": ["sha512-universal@1.2.1", "", { "dependencies": { "b4a": "^1.0.1", "sha512-wasm": "^2.3.1" } }, "sha512-kehYuigMoRkIngCv7rhgruLJNNHDnitGTBdkcYbCbooL8Cidj/bS78MDxByIjcc69M915WxcQTgZetZ1JbeQTQ=="], + + "sha512-wasm": ["sha512-wasm@2.3.4", "", { "dependencies": { "b4a": "^1.0.1", "nanoassert": "^2.0.0" } }, "sha512-akWoxJPGCB3aZCrZ+fm6VIFhJ/p8idBv7AWGFng/CZIrQo51oQNsvDbTSRXWAzIiZJvpy16oIDiCCPqTe21sKg=="], + "shell-quote": ["shell-quote@1.10.0", "", {}, "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA=="], "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + "siphash24": ["siphash24@1.3.1", "", { "dependencies": { "nanoassert": "^2.0.0" } }, "sha512-moemC3ZKiTzH29nbFo3Iw8fbemWWod4vNs/WgKbQ54oEs6mE6XVlguxvinYjB+UmaE0PThgyED9fUkWvirT8hA=="], + "slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="], "snooplogg": ["snooplogg@6.1.1", "", {}, "sha512-ERUf0zDe+4/wdzoTlz9aj45PCXRO/jckRY6BwlGLl4dZVr8hanHEW13MSYLSqOz9aDbRR8PGnb4arak+Nh4VqQ=="], + "sodium-javascript": ["sodium-javascript@0.8.0", "", { "dependencies": { "blake2b": "^2.1.1", "chacha20-universal": "^1.0.4", "nanoassert": "^2.0.0", "sha256-universal": "^1.1.0", "sha512-universal": "^1.1.0", "siphash24": "^1.0.1", "xsalsa20": "^1.0.0" } }, "sha512-rEBzR5mPxPES+UjyMDvKPIXy9ImF17KOJ32nJNi9uIquWpS/nfj+h6m05J5yLJaGXjgM72LmQoUbWZVxh/rmGg=="], + "sodium-native": ["sodium-native@5.0.10", "", { "dependencies": { "require-addon": "^1.1.0", "which-runtime": "^1.2.1" } }, "sha512-UIw+0AbpCQRuTJF88JWrZomP4O+PXhlWvdopiAJOsUivTyHTf3korMyStxkZuPngSbBEtEfDdc4ewEd8/T4/lA=="], "sodium-universal": ["sodium-universal@5.0.1", "", { "dependencies": { "sodium-native": "^5.0.1" }, "peerDependencies": { "sodium-javascript": "~0.8.0" }, "optionalPeers": ["sodium-javascript"] }, "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q=="], @@ -356,12 +382,30 @@ "ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], + "xsalsa20": ["xsalsa20@1.2.0", "", {}, "sha512-FIr/DEeoHfj7ftfylnoFt3rAIRoWXpx2AoDfrT2qD2wtp7Dp+COajvs/Icb7uHqRW9m60f5iXZwdsJJO3kvb7w=="], + "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "blake2b/nanoassert": ["nanoassert@2.0.0", "", {}, "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA=="], + + "blake2b-wasm/nanoassert": ["nanoassert@2.0.0", "", {}, "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA=="], + + "chacha20-universal/nanoassert": ["nanoassert@2.0.0", "", {}, "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA=="], + "cli-truncate/slice-ansi": ["slice-ansi@5.0.0", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="], + "noise-handshake/nanoassert": ["nanoassert@2.0.0", "", {}, "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA=="], + + "sha256-wasm/nanoassert": ["nanoassert@2.0.0", "", {}, "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA=="], + + "sha512-wasm/nanoassert": ["nanoassert@2.0.0", "", {}, "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA=="], + + "siphash24/nanoassert": ["nanoassert@2.0.0", "", {}, "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA=="], + "slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], + + "sodium-javascript/nanoassert": ["nanoassert@2.0.0", "", {}, "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA=="], } } diff --git a/package.json b/package.json index c5d68d4..57d9e0b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@tpsdev-ai/tps", "private": true, - "description": "TPS Monorepo \u2014 CLI and Agent Runtime", + "description": "TPS Monorepo — CLI and Agent Runtime", "workspaces": [ "packages/*" ], @@ -20,9 +20,17 @@ "author": "tpsdev-ai", "license": "Apache-2.0", "dependencies": { + "blake2b": "^2.1.4", "canonicalize": "^3.0.0", + "chacha20-universal": "^1.0.4", "handlebars": "^4.7.9", - "ws": "^8.20.1" + "nanoassert": "^3.1.0", + "sha256-universal": "^1.2.1", + "sha512-universal": "^1.2.1", + "siphash24": "^1.3.1", + "sodium-javascript": "^0.8.0", + "ws": "^8.20.1", + "xsalsa20": "^1.2.0" }, "overrides": { "ws": "^8.21.0", diff --git a/packages/cli-darwin-arm64/package.json b/packages/cli-darwin-arm64/package.json index 5ba0895..1a73ccd 100644 --- a/packages/cli-darwin-arm64/package.json +++ b/packages/cli-darwin-arm64/package.json @@ -16,7 +16,6 @@ "README.md" ], "dependencies": { - "sodium-native": "^5.0.10", "require-addon": "^1.2.0" }, "license": "Apache-2.0", diff --git a/packages/cli-darwin-x64/package.json b/packages/cli-darwin-x64/package.json index 3ff63c5..4af4cb6 100644 --- a/packages/cli-darwin-x64/package.json +++ b/packages/cli-darwin-x64/package.json @@ -16,7 +16,6 @@ "README.md" ], "dependencies": { - "sodium-native": "^5.0.10", "require-addon": "^1.2.0" }, "license": "Apache-2.0", diff --git a/packages/cli-linux-arm64/package.json b/packages/cli-linux-arm64/package.json index bfb238b..987c8d3 100644 --- a/packages/cli-linux-arm64/package.json +++ b/packages/cli-linux-arm64/package.json @@ -16,7 +16,6 @@ "README.md" ], "dependencies": { - "sodium-native": "^5.0.10", "require-addon": "^1.2.0" }, "license": "Apache-2.0", diff --git a/packages/cli-linux-x64/package.json b/packages/cli-linux-x64/package.json index 2f1db8f..da1c7d3 100644 --- a/packages/cli-linux-x64/package.json +++ b/packages/cli-linux-x64/package.json @@ -16,7 +16,6 @@ "README.md" ], "dependencies": { - "sodium-native": "^5.0.10", "require-addon": "^1.2.0" }, "license": "Apache-2.0", diff --git a/packages/cli/package.json b/packages/cli/package.json index eb6a9a7..4a54587 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -21,10 +21,10 @@ "scripts": { "build": "tsc", "build:binary": "bun run build:binary:darwin-arm64 && bun run build:binary:darwin-x64 && bun run build:binary:linux-arm64 && bun run build:binary:linux-x64", - "build:binary:darwin-arm64": "bun build --compile bin/tps.ts --target=bun-darwin-arm64 --define \"INJECTED_VERSION=\\\"$npm_package_version\\\"\" -e sodium-native -e require-addon --outfile=dist/tps-darwin-arm64", - "build:binary:darwin-x64": "bun build --compile bin/tps.ts --target=bun-darwin-x64 --define \"INJECTED_VERSION=\\\"$npm_package_version\\\"\" -e sodium-native -e require-addon --outfile=dist/tps-darwin-x64", - "build:binary:linux-arm64": "bun build --compile bin/tps.ts --target=bun-linux-arm64 --define \"INJECTED_VERSION=\\\"$npm_package_version\\\"\" -e sodium-native -e require-addon --outfile=dist/tps-linux-arm64", - "build:binary:linux-x64": "bun build --compile bin/tps.ts --target=bun-linux-x64 --define \"INJECTED_VERSION=\\\"$npm_package_version\\\"\" -e sodium-native -e require-addon --outfile=dist/tps-linux-x64", + "build:binary:darwin-arm64": "bun run scripts/build-portable.ts bun-darwin-arm64", + "build:binary:darwin-x64": "bun run scripts/build-portable.ts bun-darwin-x64", + "build:binary:linux-arm64": "bun run scripts/build-portable.ts bun-linux-arm64", + "build:binary:linux-x64": "bun run scripts/build-portable.ts bun-linux-x64", "dev": "tsc --watch", "start": "node dist/bin/tps.js", "tps": "node dist/bin/tps.js", @@ -65,8 +65,16 @@ "@types/node": "^22.0.0", "@types/react": "^18.3.0", "@types/safe-regex": "^1.1.6", + "blake2b": "^2.1.4", + "chacha20-universal": "^1.0.4", "fast-check": "^4.5.3", + "nanoassert": "^3.1.0", + "sha256-universal": "^1.2.1", + "sha512-universal": "^1.2.1", + "siphash24": "^1.3.1", + "sodium-javascript": "^0.8.0", "typescript": "^5.7.0", + "xsalsa20": "^1.2.0", "@tpsdev-ai/agent": "workspace:*" }, "files": [ diff --git a/packages/cli/scripts/build-portable.ts b/packages/cli/scripts/build-portable.ts new file mode 100644 index 0000000..617baaa --- /dev/null +++ b/packages/cli/scripts/build-portable.ts @@ -0,0 +1,80 @@ +#!/usr/bin/env bun +/** + * build-portable.ts — Build a standalone tps binary with sodium-javascript (pure JS) + * instead of sodium-native (native addon). This keeps the binary portable across + * machines — no baked-in CI paths, no missing .node addons. + * + * Usage: + * bun run scripts/build-portable.ts [outfile] + * + * Targets: bun-linux-x64, bun-linux-arm64, bun-darwin-x64, bun-darwin-arm64 + * + * Two-phase build: + * 1. Bun.build() with a plugin that aliases sodium-native → sodium-javascript + * 2. bun build --compile on the bundled output to produce the standalone binary + */ + +import { resolve, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const pkgRoot = resolve(__dirname, ".."); +const repoRoot = resolve(pkgRoot, "..", ".."); + +const target = (process.argv[2] as string) || "bun-linux-x64"; +const outfile = + process.argv[3] || resolve(pkgRoot, "dist", `tps-${target.replace("bun-", "")}`); + +// Read version from package.json +const pkgJson = JSON.parse( + await Bun.file(resolve(pkgRoot, "package.json")).text() +); +const version = pkgJson.version || "0.0.0"; + +// Phase 1: bundle with sodium-native → sodium-javascript alias +const sjPath = resolve(repoRoot, "node_modules", "sodium-javascript", "index.js"); + +const result = await Bun.build({ + entrypoints: [resolve(pkgRoot, "bin", "tps.ts")], + target: "bun", + outdir: resolve(pkgRoot, "dist"), + define: { + INJECTED_VERSION: JSON.stringify(version), + }, + external: ["require-addon"], + plugins: [ + { + name: "alias-sodium-native", + setup(build) { + build.onResolve({ filter: /^sodium-native$/ }, () => { + return { path: sjPath }; + }); + }, + }, + ], + naming: "[dir]/tps-bundle.[ext]", +}); + +if (!result.success) { + for (const log of result.logs) { + console.error(log); + } + process.exit(1); +} + +const bundlePath = result.outputs[0].path; + +// Phase 2: compile the bundle to a standalone binary +const proc = Bun.spawnSync( + ["bun", "build", "--compile", bundlePath, "--target", target, "--outfile", outfile], + { + cwd: repoRoot, + stdio: ["inherit", "inherit", "inherit"], + } +); + +if (proc.exitCode !== 0) { + process.exit(proc.exitCode ?? 1); +} + +console.log(`Portable binary: ${outfile}`); From 0f26322461e72f5ac05848c2d93d25ec6e5dbc43 Mon Sep 17 00:00:00 2001 From: Anvil Date: Thu, 6 Aug 2026 20:52:21 +0000 Subject: [PATCH 2/6] chore: add repository + homepage metadata to package.json files Adds repository (git+https://github.com/tpsdev-ai/cli.git with directory per package) and homepage fields to: - packages/cli - packages/agent - packages/cli-linux-x64 - packages/cli-linux-arm64 - packages/cli-darwin-x64 - packages/cli-darwin-arm64 --- packages/agent/package.json | 6 ++++++ packages/cli-darwin-arm64/package.json | 6 ++++++ packages/cli-darwin-x64/package.json | 6 ++++++ packages/cli-linux-arm64/package.json | 6 ++++++ packages/cli-linux-x64/package.json | 6 ++++++ packages/cli/package.json | 6 ++++++ 6 files changed, 36 insertions(+) diff --git a/packages/agent/package.json b/packages/agent/package.json index 5c89a13..9bbd98d 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -32,6 +32,12 @@ "mail-driven" ], "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "git+https://github.com/tpsdev-ai/cli.git", + "directory": "packages/agent" + }, + "homepage": "https://github.com/tpsdev-ai/cli/tree/main/packages/agent#readme", "dependencies": { "@tpsdev-ai/cli": "workspace:*", "js-yaml": "^4.1.0", diff --git a/packages/cli-darwin-arm64/package.json b/packages/cli-darwin-arm64/package.json index 1a73ccd..a83f60f 100644 --- a/packages/cli-darwin-arm64/package.json +++ b/packages/cli-darwin-arm64/package.json @@ -18,6 +18,12 @@ "dependencies": { "require-addon": "^1.2.0" }, + "repository": { + "type": "git", + "url": "git+https://github.com/tpsdev-ai/cli.git", + "directory": "packages/cli-darwin-arm64" + }, + "homepage": "https://github.com/tpsdev-ai/cli#readme", "license": "Apache-2.0", "publishConfig": { "access": "public" diff --git a/packages/cli-darwin-x64/package.json b/packages/cli-darwin-x64/package.json index 4af4cb6..3120174 100644 --- a/packages/cli-darwin-x64/package.json +++ b/packages/cli-darwin-x64/package.json @@ -18,6 +18,12 @@ "dependencies": { "require-addon": "^1.2.0" }, + "repository": { + "type": "git", + "url": "git+https://github.com/tpsdev-ai/cli.git", + "directory": "packages/cli-darwin-x64" + }, + "homepage": "https://github.com/tpsdev-ai/cli#readme", "license": "Apache-2.0", "publishConfig": { "access": "public" diff --git a/packages/cli-linux-arm64/package.json b/packages/cli-linux-arm64/package.json index 987c8d3..4919c93 100644 --- a/packages/cli-linux-arm64/package.json +++ b/packages/cli-linux-arm64/package.json @@ -18,6 +18,12 @@ "dependencies": { "require-addon": "^1.2.0" }, + "repository": { + "type": "git", + "url": "git+https://github.com/tpsdev-ai/cli.git", + "directory": "packages/cli-linux-arm64" + }, + "homepage": "https://github.com/tpsdev-ai/cli#readme", "license": "Apache-2.0", "publishConfig": { "access": "public" diff --git a/packages/cli-linux-x64/package.json b/packages/cli-linux-x64/package.json index da1c7d3..ce82773 100644 --- a/packages/cli-linux-x64/package.json +++ b/packages/cli-linux-x64/package.json @@ -18,6 +18,12 @@ "dependencies": { "require-addon": "^1.2.0" }, + "repository": { + "type": "git", + "url": "git+https://github.com/tpsdev-ai/cli.git", + "directory": "packages/cli-linux-x64" + }, + "homepage": "https://github.com/tpsdev-ai/cli#readme", "license": "Apache-2.0", "publishConfig": { "access": "public" diff --git a/packages/cli/package.json b/packages/cli/package.json index 4a54587..973c0ae 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -41,6 +41,12 @@ "tps" ], "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "git+https://github.com/tpsdev-ai/cli.git", + "directory": "packages/cli" + }, + "homepage": "https://github.com/tpsdev-ai/cli#readme", "dependencies": { "@noble/curves": "^2.0.1", "@noble/ed25519": "^3.0.0", From ca338f1780528e51235840d6cceace113bf2f9d8 Mon Sep 17 00:00:00 2001 From: Anvil Date: Fri, 7 Aug 2026 01:14:56 +0000 Subject: [PATCH 3/6] fix: route warnings to stderr, bump js-yaml, fix smoke CI workspace resolution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change console.warn → console.error in bin/tps.ts (nono check), src/utils/nono.ts (3 sites), src/utils/github-webhook.ts so warnings never corrupt JSON stdout in compiled binaries - Bump js-yaml ^4.1.0 → ^4.3.1 (GHSA-5p4m-2wfm-xmqj) in cli, agent, and root overrides; update lockfile - Fix smoke + release workflows: explicit workspace build order (cli → agent → cli rebuild) so @tpsdev-ai/agent resolves; remove 2>&1 from identity init JSON capture - Add unit test: identity init --json emits parseable JSON on stdout even without --nonono --- .github/workflows/release.yml | 16 +++++++++++++--- .github/workflows/smoke.yml | 17 +++++++++++++---- bin/tps.ts | 2 +- bun.lock | 20 ++++++++++++-------- package.json | 2 +- packages/agent/package.json | 2 +- packages/cli/package.json | 2 +- packages/cli/test/identity.test.ts | 25 +++++++++++++++++++++++++ src/utils/github-webhook.ts | 2 +- src/utils/nono.ts | 6 +++--- 10 files changed, 71 insertions(+), 23 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1a19b48..1bb0833 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -70,7 +70,11 @@ jobs: mode: firewall-free - run: sfw bun install --frozen-lockfile - name: Build workspace packages - run: bun run build + run: | + (cd packages/cli && bun run build) || true + cd packages/agent && bun run build + cd ../cli && rm -rf dist && bun run build + (cd ../pi-tps-mail && bun run build) || true - name: Build ${TARGET} env: TARGET: ${{ matrix.target }} @@ -111,6 +115,12 @@ jobs: with: mode: firewall-free - run: sfw bun install --frozen-lockfile + - name: Build workspace packages + run: | + (cd packages/cli && bun run build) || true + cd packages/agent && bun run build + cd ../cli && rm -rf dist && bun run build + (cd ../pi-tps-mail && bun run build) || true - name: Build linux-x64 release binary run: | bun run --filter @tpsdev-ai/cli build:binary:linux-x64 @@ -129,8 +139,8 @@ jobs: run: | export HOME=$(mktemp -d) export TPS_VAULT_KEY="ci-release-smoke" - /tmp/tps-release-smoke identity init --json > /tmp/tps-identity.json 2>&1 - FP=$(node -p "JSON.parse(require('fs').readFileSync('/tmp/tps-identity.json','utf8')).fingerprint") + /tmp/tps-release-smoke identity init --json > /tmp/tps-identity.json + FP=$(node -p "try { JSON.parse(require('fs').readFileSync('/tmp/tps-identity.json','utf8')).fingerprint } catch(e) { '' }") if [ -z "$FP" ]; then echo "❌ identity init failed" exit 1 diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml index 8818cd3..da4aba8 100644 --- a/.github/workflows/smoke.yml +++ b/.github/workflows/smoke.yml @@ -44,8 +44,16 @@ jobs: with: mode: firewall-free - run: sfw bun install --frozen-lockfile - - name: Build workspace packages - run: bun run build + - name: Build workspace packages (cli → agent → cli) + run: | + # Build cli first (may have missing agent types, tolerated) + (cd packages/cli && bun run build) || true + # Build agent (needs cli types from previous step) + cd packages/agent && bun run build + # Rebuild cli with agent types now available + cd ../cli && rm -rf dist && bun run build + # Build pi-tps-mail if present + (cd ../pi-tps-mail && bun run build) || true - name: Build portable binary (${{ matrix.target }}) run: bun run --filter @tpsdev-ai/cli build:binary:${{ matrix.target }} - name: Acceptance — version @@ -66,11 +74,12 @@ jobs: BIN="packages/cli/dist/tps-${{ matrix.target }}" export HOME=$(mktemp -d) export TPS_VAULT_KEY="ci-smoke-test-key" - "$BIN" identity init --json > /tmp/tps-identity.json 2>&1 + # Capture stdout only — stderr (warnings) goes to CI log + "$BIN" identity init --json > /tmp/tps-identity.json echo "Identity output:" cat /tmp/tps-identity.json # Verify we got a valid fingerprint - FP=$(node -p "JSON.parse(require('fs').readFileSync('/tmp/tps-identity.json','utf8')).fingerprint") + FP=$(node -p "try { JSON.parse(require('fs').readFileSync('/tmp/tps-identity.json','utf8')).fingerprint } catch(e) { '' }") if [ -z "$FP" ]; then echo "❌ identity init failed — no fingerprint" exit 1 diff --git a/bin/tps.ts b/bin/tps.ts index 9022c6f..e776022 100755 --- a/bin/tps.ts +++ b/bin/tps.ts @@ -88,7 +88,7 @@ async function checkNono() { if (command === "office" && rest[0] === "relay") return; // relay runs in background const { findNono } = await import("../src/utils/nono.js"); if (!findNono()) { - console.warn( + console.error( "⚠️ nono not found. Host agents will run without process isolation.\n" + " Install nono for syscall filtering + filesystem boundaries.\n" + " Use --nonono to run anyway (not recommended).\n" diff --git a/bun.lock b/bun.lock index effec87..6f4ffa9 100644 --- a/bun.lock +++ b/bun.lock @@ -32,7 +32,7 @@ "dependencies": { "@tpsdev-ai/cli": "workspace:*", "js-tiktoken": "^1.0.13", - "js-yaml": "^4.1.0", + "js-yaml": "^4.3.1", "zod": "^3.24.0", }, "devDependencies": { @@ -56,7 +56,7 @@ "handlebars": "^4.7.8", "ink": "^5.2.0", "ink-text-input": "^6.0.0", - "js-yaml": "^4.1.0", + "js-yaml": "^4.3.1", "meow": "^13.2.0", "msgpackr": "^1.11.8", "noise-handshake": "^4.2.0", @@ -73,8 +73,16 @@ "@types/node": "^22.0.0", "@types/react": "^18.3.0", "@types/safe-regex": "^1.1.6", + "blake2b": "^2.1.4", + "chacha20-universal": "^1.0.4", "fast-check": "^4.5.3", + "nanoassert": "^3.1.0", + "sha256-universal": "^1.2.1", + "sha512-universal": "^1.2.1", + "siphash24": "^1.3.1", + "sodium-javascript": "^0.8.0", "typescript": "^5.7.0", + "xsalsa20": "^1.2.0", }, "optionalDependencies": { "@tpsdev-ai/cli-darwin-arm64": "0.5.4", @@ -91,7 +99,6 @@ }, "dependencies": { "require-addon": "^1.2.0", - "sodium-native": "^5.0.10", }, }, "packages/cli-darwin-x64": { @@ -102,7 +109,6 @@ }, "dependencies": { "require-addon": "^1.2.0", - "sodium-native": "^5.0.10", }, }, "packages/cli-linux-arm64": { @@ -113,7 +119,6 @@ }, "dependencies": { "require-addon": "^1.2.0", - "sodium-native": "^5.0.10", }, }, "packages/cli-linux-x64": { @@ -124,7 +129,6 @@ }, "dependencies": { "require-addon": "^1.2.0", - "sodium-native": "^5.0.10", }, }, "packages/pi-tps-mail": { @@ -145,7 +149,7 @@ }, }, "overrides": { - "js-yaml": "^4.3.0", + "js-yaml": "^4.3.1", "shell-quote": "^1.9.0", "ws": "^8.21.0", }, @@ -288,7 +292,7 @@ "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - "js-yaml": ["js-yaml@4.3.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q=="], + "js-yaml": ["js-yaml@4.3.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ=="], "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": "cli.js" }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], diff --git a/package.json b/package.json index 57d9e0b..73f3312 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "overrides": { "ws": "^8.21.0", "shell-quote": "^1.9.0", - "js-yaml": "^4.3.0" + "js-yaml": "^4.3.1" }, "packageManager": "bun@1.3.10" } diff --git a/packages/agent/package.json b/packages/agent/package.json index 9bbd98d..a4f20a7 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -40,7 +40,7 @@ "homepage": "https://github.com/tpsdev-ai/cli/tree/main/packages/agent#readme", "dependencies": { "@tpsdev-ai/cli": "workspace:*", - "js-yaml": "^4.1.0", + "js-yaml": "^4.3.1", "js-tiktoken": "^1.0.13", "zod": "^3.24.0" }, diff --git a/packages/cli/package.json b/packages/cli/package.json index 973c0ae..c531071 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -55,7 +55,7 @@ "handlebars": "^4.7.8", "ink": "^5.2.0", "ink-text-input": "^6.0.0", - "js-yaml": "^4.1.0", + "js-yaml": "^4.3.1", "meow": "^13.2.0", "msgpackr": "^1.11.8", "noise-handshake": "^4.2.0", diff --git a/packages/cli/test/identity.test.ts b/packages/cli/test/identity.test.ts index ad6cd08..d00ec18 100644 --- a/packages/cli/test/identity.test.ts +++ b/packages/cli/test/identity.test.ts @@ -373,4 +373,29 @@ describe("CLI integration", () => { expect(e.status).toBe(1); } }); + + test("identity init --json emits parseable JSON on stdout (warnings on stderr)", () => { + const { execSync } = require("node:child_process"); + // Run WITHOUT --nonono so any warnings (nono not found, etc.) would fire. + // They must go to stderr — stdout must be clean parseable JSON. + const result = execSync( + `bun ${TPS_BIN} identity init --json`, + { + encoding: "utf-8", + env: { + ...process.env, + TPS_VAULT_KEY: "test-passphrase", + TPS_IDENTITY_DIR: join(tempDir, "cli-identity-stdout"), + TPS_REGISTRY_DIR: join(tempDir, "cli-registry-stdout"), + }, + // Capture stdout only — stderr is separate + stdio: ["pipe", "pipe", "pipe"], + } + ); + // Must be parseable JSON — no warning text mixed in + const parsed = JSON.parse(result); + expect(parsed.fingerprint).toMatch(/^[0-9a-f]{64}$/); + expect(parsed.signingPublicKey).toMatch(/^[0-9a-f]{64}$/); + expect(parsed.encryptionPublicKey).toMatch(/^[0-9a-f]{64}$/); + }); }); diff --git a/src/utils/github-webhook.ts b/src/utils/github-webhook.ts index 660f8d6..a7d64f3 100644 --- a/src/utils/github-webhook.ts +++ b/src/utils/github-webhook.ts @@ -76,7 +76,7 @@ function formatEvent(event: string, payload: Record): string { export async function handleGithubWebhook(req: IncomingMessage, res: ServerResponse): Promise { if (!webhookSecret()) { - console.warn("[webhook] GITHUB_WEBHOOK_SECRET not set — all webhook requests will be rejected"); + console.error("[webhook] GITHUB_WEBHOOK_SECRET not set — all webhook requests will be rejected"); res.statusCode = 503; res.end("Webhook not configured"); return; diff --git a/src/utils/nono.ts b/src/utils/nono.ts index 4dcb18b..f2176f3 100644 --- a/src/utils/nono.ts +++ b/src/utils/nono.ts @@ -121,7 +121,7 @@ export async function withNono( ); process.exit(1); } else { - console.warn( + console.error( `⚠️ nono not found — running ${profile} WITHOUT isolation. Install nono for security: https://nono.sh` ); return fn(); @@ -154,7 +154,7 @@ export function runCommandUnderNono( ); return 1; } - console.warn( + console.error( `⚠️ nono not found — running WITHOUT isolation: ${cmd.join(" ")}` ); const result = spawnSync(cmd[0]!, cmd.slice(1), { @@ -197,7 +197,7 @@ export function installNonoProfiles(targetDir?: string, silent?: boolean): void const bundledDir = findBundledProfilesDir(); if (!existsSync(bundledDir)) { - if (!silent) console.warn(`⚠️ No bundled nono profiles found at ${bundledDir}`); + if (!silent) console.error(`⚠️ No bundled nono profiles found at ${bundledDir}`); return; } From 8a8313ad3567610ab2b9860e59743cfe1bc1d7b6 Mon Sep 17 00:00:00 2001 From: Anvil Date: Fri, 7 Aug 2026 03:19:32 +0000 Subject: [PATCH 4/6] fix: break circular dep (move signEnvelope to agent), stub msgpackr-extract, clean build order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move signEnvelope.ts from cli to agent, breaking the circular build dependency (cli → agent, agent → cli). Agent now builds first, cli second. - Add msgpackr-extract stub plugin to build-portable.ts to prevent __dirname from being baked into the portable binary (was the source of the /home/runner reference in CI binary-hygiene gate). - Update smoke.yml and release.yml build order: agent → cli (no more || true workaround for expected TS2307). - Update all imports across cli and agent to use @tpsdev-ai/agent for signEnvelope, verifyEnvelope, FlairClient, Envelope, ChainEntry. --- .github/workflows/release.yml | 6 ++---- .github/workflows/smoke.yml | 11 +++++------ packages/agent/src/index.ts | 4 ++++ packages/agent/src/io/mail.ts | 2 +- packages/{cli => agent}/src/lib/signEnvelope.ts | 0 packages/agent/src/runtime/agent.ts | 2 +- packages/cli/scripts/build-portable.ts | 13 +++++++++++++ packages/cli/scripts/stubs/empty.js | 2 ++ packages/cli/src/commands/mail.ts | 2 +- packages/cli/src/utils/agent-keys.ts | 2 +- packages/cli/src/utils/mail.ts | 2 +- packages/cli/test/mail-consumer-verify.test.ts | 2 +- packages/cli/test/mail-send-sign.test.ts | 2 +- packages/cli/test/signEnvelope.test.ts | 2 +- packages/cli/test/verifyEnvelope.test.ts | 2 +- 15 files changed, 35 insertions(+), 19 deletions(-) rename packages/{cli => agent}/src/lib/signEnvelope.ts (100%) create mode 100644 packages/cli/scripts/stubs/empty.js diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1bb0833..afd8ab6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -71,9 +71,8 @@ jobs: - run: sfw bun install --frozen-lockfile - name: Build workspace packages run: | - (cd packages/cli && bun run build) || true cd packages/agent && bun run build - cd ../cli && rm -rf dist && bun run build + cd ../cli && bun run build (cd ../pi-tps-mail && bun run build) || true - name: Build ${TARGET} env: @@ -117,9 +116,8 @@ jobs: - run: sfw bun install --frozen-lockfile - name: Build workspace packages run: | - (cd packages/cli && bun run build) || true cd packages/agent && bun run build - cd ../cli && rm -rf dist && bun run build + cd ../cli && bun run build (cd ../pi-tps-mail && bun run build) || true - name: Build linux-x64 release binary run: | diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml index da4aba8..a7a7482 100644 --- a/.github/workflows/smoke.yml +++ b/.github/workflows/smoke.yml @@ -44,14 +44,13 @@ jobs: with: mode: firewall-free - run: sfw bun install --frozen-lockfile - - name: Build workspace packages (cli → agent → cli) + - name: Build workspace packages (agent → cli) run: | - # Build cli first (may have missing agent types, tolerated) - (cd packages/cli && bun run build) || true - # Build agent (needs cli types from previous step) + # Build agent first — it no longer depends on cli's build output + # (signEnvelope was moved to agent, breaking the circular dep). cd packages/agent && bun run build - # Rebuild cli with agent types now available - cd ../cli && rm -rf dist && bun run build + # Build cli with agent types now available + cd ../cli && bun run build # Build pi-tps-mail if present (cd ../pi-tps-mail && bun run build) || true - name: Build portable binary (${{ matrix.target }}) diff --git a/packages/agent/src/index.ts b/packages/agent/src/index.ts index ca32b31..a2ef06e 100644 --- a/packages/agent/src/index.ts +++ b/packages/agent/src/index.ts @@ -40,3 +40,7 @@ export { loadAgentConfig } from "./config.js"; // Flair integration export { FlairContextProvider } from "./io/flair.js"; export type { FlairConfig } from "./runtime/types.js"; + +// Signing +export { signEnvelope, verifyEnvelope } from "./lib/signEnvelope.js"; +export type { Envelope, ChainEntry, FlairClient, VerifyOk, VerifyReject } from "./lib/signEnvelope.js"; diff --git a/packages/agent/src/io/mail.ts b/packages/agent/src/io/mail.ts index 983c1f5..0c12f8e 100644 --- a/packages/agent/src/io/mail.ts +++ b/packages/agent/src/io/mail.ts @@ -2,7 +2,7 @@ import { existsSync, mkdirSync, readdirSync, renameSync, readFileSync, writeFile import { join } from "node:path"; import type { EventLogger } from "../telemetry/events.js"; import { sanitizeError } from "../telemetry/events.js"; -import { verifyEnvelope, type FlairClient } from "@tpsdev-ai/cli/lib/signEnvelope"; +import { verifyEnvelope, type FlairClient } from "../lib/signEnvelope.js"; export interface MailMessage { filename: string; diff --git a/packages/cli/src/lib/signEnvelope.ts b/packages/agent/src/lib/signEnvelope.ts similarity index 100% rename from packages/cli/src/lib/signEnvelope.ts rename to packages/agent/src/lib/signEnvelope.ts diff --git a/packages/agent/src/runtime/agent.ts b/packages/agent/src/runtime/agent.ts index 99bb0a2..0481e30 100644 --- a/packages/agent/src/runtime/agent.ts +++ b/packages/agent/src/runtime/agent.ts @@ -10,7 +10,7 @@ import { BoundaryManager } from "../governance/boundary.js"; import { createDefaultToolset } from "../tools/index.js"; import { EventLogger } from "../telemetry/events.js"; import { FlairContextProvider } from "../io/flair.js"; -import type { FlairClient } from "@tpsdev-ai/cli/lib/signEnvelope"; +import type { FlairClient } from "../lib/signEnvelope.js"; export class AgentRuntime { private loop: EventLoop; diff --git a/packages/cli/scripts/build-portable.ts b/packages/cli/scripts/build-portable.ts index 617baaa..ac33602 100644 --- a/packages/cli/scripts/build-portable.ts +++ b/packages/cli/scripts/build-portable.ts @@ -51,6 +51,19 @@ const result = await Bun.build({ }); }, }, + { + name: "stub-msgpackr-extract", + setup(build) { + // msgpackr-extract is an optional native addon for msgpackr. + // It bakes __dirname into the bundle (→ /home/runner on CI), + // which fails the binary-hygiene gate. Stub it out so msgpackr + // falls back to its pure-JS codec — the portable binary doesn't + // ship native addons anyway. + build.onResolve({ filter: /^msgpackr-extract$/ }, () => { + return { path: resolve(pkgRoot, "scripts", "stubs", "empty.js") }; + }); + }, + }, ], naming: "[dir]/tps-bundle.[ext]", }); diff --git a/packages/cli/scripts/stubs/empty.js b/packages/cli/scripts/stubs/empty.js new file mode 100644 index 0000000..a284570 --- /dev/null +++ b/packages/cli/scripts/stubs/empty.js @@ -0,0 +1,2 @@ +// Stub: msgpackr-extract is an optional native addon. The portable binary uses msgpackr's pure-JS fallback. +module.exports = null; diff --git a/packages/cli/src/commands/mail.ts b/packages/cli/src/commands/mail.ts index 2db6f34..60fd2de 100644 --- a/packages/cli/src/commands/mail.ts +++ b/packages/cli/src/commands/mail.ts @@ -9,7 +9,7 @@ import { loadHostIdentityId } from "../utils/identity.js"; import { queueOutboxMessage } from "../utils/outbox.js"; import { galLookup } from "../utils/gal.js"; import { parseTaskEnvelope, formatTaskEnvelope, createTaskEnvelope } from "../utils/task-envelope.js"; -import { signEnvelope, type Envelope, type ChainEntry } from "../lib/signEnvelope.js"; +import { signEnvelope, type Envelope, type ChainEntry } from "@tpsdev-ai/agent"; import { readAgentPrivateKey, parseInboundChain } from "../utils/agent-keys.js"; import { randomUUID } from "node:crypto"; diff --git a/packages/cli/src/utils/agent-keys.ts b/packages/cli/src/utils/agent-keys.ts index 11821d8..1a23a74 100644 --- a/packages/cli/src/utils/agent-keys.ts +++ b/packages/cli/src/utils/agent-keys.ts @@ -86,7 +86,7 @@ function seedFromPkcs8Der(der: Buffer): Buffer | null { * Parse TPS_INBOUND_CHAIN_JSON into a ChainEntry array. * Returns null if unset, empty, or invalid. */ -import type { ChainEntry } from "../lib/signEnvelope.js"; +import type { ChainEntry } from "@tpsdev-ai/agent"; export function parseInboundChain(raw: string | undefined): ChainEntry[] | null { if (!raw || raw.trim() === "") return null; diff --git a/packages/cli/src/utils/mail.ts b/packages/cli/src/utils/mail.ts index 914754c..0d2566b 100644 --- a/packages/cli/src/utils/mail.ts +++ b/packages/cli/src/utils/mail.ts @@ -4,7 +4,7 @@ import { homedir } from "node:os"; import { randomUUID } from "node:crypto"; import { sanitizeIdentifier } from "../schema/sanitizer.js"; import { logEvent } from "./archive.js"; -import { verifyEnvelope, type FlairClient } from "../lib/signEnvelope.js"; +import { verifyEnvelope, type FlairClient } from "@tpsdev-ai/agent"; export interface MailMessage { id: string; diff --git a/packages/cli/test/mail-consumer-verify.test.ts b/packages/cli/test/mail-consumer-verify.test.ts index 9113bde..7ecc3aa 100644 --- a/packages/cli/test/mail-consumer-verify.test.ts +++ b/packages/cli/test/mail-consumer-verify.test.ts @@ -13,7 +13,7 @@ import { tmpdir } from "node:os"; import * as ed from "@noble/ed25519"; import { createHash } from "node:crypto"; import canonicalize from "canonicalize"; -import { signEnvelope, type Envelope, verifyEnvelope } from "../src/lib/signEnvelope.js"; +import { signEnvelope, type Envelope, verifyEnvelope } from "@tpsdev-ai/agent"; import { sendMessage, checkMessages, getInbox } from "../src/utils/mail.js"; // Wire sha512 for sync sign operations. diff --git a/packages/cli/test/mail-send-sign.test.ts b/packages/cli/test/mail-send-sign.test.ts index 078a3af..e9e2992 100644 --- a/packages/cli/test/mail-send-sign.test.ts +++ b/packages/cli/test/mail-send-sign.test.ts @@ -15,7 +15,7 @@ import { signEnvelope, type Envelope, type ChainEntry, -} from "../src/lib/signEnvelope.js"; +} from "@tpsdev-ai/agent"; // Wire sha512 for sync sign operations. import { hashes } from "@noble/ed25519"; diff --git a/packages/cli/test/signEnvelope.test.ts b/packages/cli/test/signEnvelope.test.ts index b176cea..0e10259 100644 --- a/packages/cli/test/signEnvelope.test.ts +++ b/packages/cli/test/signEnvelope.test.ts @@ -6,7 +6,7 @@ import { describe, expect, test } from "bun:test"; import * as ed from "@noble/ed25519"; import { createHash } from "node:crypto"; import canonicalize from "canonicalize"; -import { signEnvelope, type Envelope, type ChainEntry } from "../src/lib/signEnvelope.js"; +import { signEnvelope, type Envelope, type ChainEntry } from "@tpsdev-ai/agent"; // Wire sha512 for sync sign operations (same as production). import { hashes } from "@noble/ed25519"; diff --git a/packages/cli/test/verifyEnvelope.test.ts b/packages/cli/test/verifyEnvelope.test.ts index 2220f2d..3adc409 100644 --- a/packages/cli/test/verifyEnvelope.test.ts +++ b/packages/cli/test/verifyEnvelope.test.ts @@ -12,7 +12,7 @@ import { type Envelope, type ChainEntry, type FlairClient, -} from "../src/lib/signEnvelope.js"; +} from "@tpsdev-ai/agent"; // Wire sha512 for sync sign operations (same as production). import { hashes } from "@noble/ed25519"; From 2c2be8c8ab7ba5aec59ccd5543aafc925df15f7c Mon Sep 17 00:00:00 2001 From: flint Date: Fri, 7 Aug 2026 01:58:20 -0700 Subject: [PATCH 5/6] =?UTF-8?q?fix(smoke):=20portable=20timeout=20?= =?UTF-8?q?=E2=80=94=20GNU=20timeout=20does=20not=20exist=20on=20macOS=20r?= =?UTF-8?q?unners?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit darwin-arm64's 'Noise handshake failed' was 'timeout: command not found'; the handshake never ran. Verified the built darwin-arm64 binary passes the full acceptance trio on real Apple Silicon (identity created, listening, zero baked paths). perl alarm+exec is present on both runner OSes. One-commit drive by flint: branch owner unreachable (exe.dev VM plane down), change is CI-portability only, content stamps unaffected. Co-Authored-By: Claude Fable 5 --- .github/workflows/smoke.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml index a7a7482..53411b5 100644 --- a/.github/workflows/smoke.yml +++ b/.github/workflows/smoke.yml @@ -90,7 +90,9 @@ jobs: export HOME=$(mktemp -d) export TPS_VAULT_KEY="ci-smoke-test-key" # branch init exercises the full noise-handshake → sodium-javascript path - timeout 15 "$BIN" branch init --name smoke-test 2>&1 | tee /tmp/tps-branch.log || true + # portable timeout: GNU `timeout` does not exist on stock macOS runners (this exact line + # failed with "timeout: command not found" on darwin-arm64 while every linux lane passed) + perl -e 'alarm shift; exec @ARGV' 15 "$BIN" branch init --name smoke-test 2>&1 | tee /tmp/tps-branch.log || true if grep -q "Branch identity created" /tmp/tps-branch.log; then echo "✅ Noise handshake OK" elif grep -q "Listening on" /tmp/tps-branch.log; then From 2cb29de1cb18f1fcc088e05739cf972b3efb8131 Mon Sep 17 00:00:00 2001 From: Anvil Date: Fri, 7 Aug 2026 11:40:14 +0000 Subject: [PATCH 6/6] fix(ci): move darwin-x64 smoke to macos-latest under Rosetta 2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cli#336: GitHub retired Intel mac runners — macos-13 queues forever. Run the darwin-x64 smoke job on macos-latest (Apple Silicon) with arch -x86_64 prefix so the x64 binary executes under Rosetta 2 translation. bun-compiled x64 binaries auto-translate. Bare-metal Intel mac is now untested — documented as a known gap, not a hidden one. --- .github/workflows/smoke.yml | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml index 53411b5..adbd0c4 100644 --- a/.github/workflows/smoke.yml +++ b/.github/workflows/smoke.yml @@ -30,8 +30,12 @@ jobs: runs-on: ubuntu-latest - target: linux-arm64 runs-on: ubuntu-24.04-arm + # GitHub retired Intel mac runners (macos-13 queues forever). + # This job runs on Apple Silicon under Rosetta 2 translation — + # bun-compiled x64 binaries auto-translate. Bare-metal Intel mac + # is untested and that is a documented gap, not a hidden one. - target: darwin-x64 - runs-on: macos-13 + runs-on: macos-latest - target: darwin-arm64 runs-on: macos-latest runs-on: ${{ matrix.runs-on }} @@ -55,11 +59,14 @@ jobs: (cd ../pi-tps-mail && bun run build) || true - name: Build portable binary (${{ matrix.target }}) run: bun run --filter @tpsdev-ai/cli build:binary:${{ matrix.target }} + - name: Set up Rosetta 2 prefix (darwin-x64) + if: matrix.target == 'darwin-x64' + run: echo "ARCH_PREFIX=arch -x86_64" >> $GITHUB_ENV - name: Acceptance — version run: | BIN="packages/cli/dist/tps-${{ matrix.target }}" chmod +x "$BIN" - VERSION_OUTPUT=$("$BIN" --version) + VERSION_OUTPUT=$($ARCH_PREFIX "$BIN" --version) PKG_VERSION=$(node -p "require('./packages/cli/package.json').version") echo "Binary version: $VERSION_OUTPUT" echo "Package version: $PKG_VERSION" @@ -74,7 +81,7 @@ jobs: export HOME=$(mktemp -d) export TPS_VAULT_KEY="ci-smoke-test-key" # Capture stdout only — stderr (warnings) goes to CI log - "$BIN" identity init --json > /tmp/tps-identity.json + $ARCH_PREFIX "$BIN" identity init --json > /tmp/tps-identity.json echo "Identity output:" cat /tmp/tps-identity.json # Verify we got a valid fingerprint @@ -92,7 +99,7 @@ jobs: # branch init exercises the full noise-handshake → sodium-javascript path # portable timeout: GNU `timeout` does not exist on stock macOS runners (this exact line # failed with "timeout: command not found" on darwin-arm64 while every linux lane passed) - perl -e 'alarm shift; exec @ARGV' 15 "$BIN" branch init --name smoke-test 2>&1 | tee /tmp/tps-branch.log || true + perl -e 'alarm shift; exec @ARGV' 15 $ARCH_PREFIX "$BIN" branch init --name smoke-test 2>&1 | tee /tmp/tps-branch.log || true if grep -q "Branch identity created" /tmp/tps-branch.log; then echo "✅ Noise handshake OK" elif grep -q "Listening on" /tmp/tps-branch.log; then