diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1209d4c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,247 @@ +name: Release + +# Keep this workflow hand-curated. The prepare job produces the verified tarball, +# checksum, and metadata that future publish steps (for example npm) should +# reuse instead of rebuilding from scratch. +on: + workflow_dispatch: + inputs: + tag: + description: Git tag to release (for example v0.1.0) + required: true + type: string + push: + tags: + - 'v*' + +concurrency: + group: release-${{ github.workflow }}-${{ github.event.inputs.tag || github.ref }} + cancel-in-progress: false + +permissions: + contents: write + +jobs: + prepare-release: + runs-on: ubuntu-latest + timeout-minutes: 30 + outputs: + release_tag: ${{ steps.release-metadata.outputs.release_tag }} + package_name: ${{ steps.release-metadata.outputs.package_name }} + package_version: ${{ steps.release-metadata.outputs.package_version }} + tarball_filename: ${{ steps.release-metadata.outputs.tarball_filename }} + checksum_filename: ${{ steps.release-metadata.outputs.checksum_filename }} + checksum_sha256: ${{ steps.release-metadata.outputs.checksum_sha256 }} + steps: + - name: Resolve release tag + id: release-tag + shell: bash + run: | + set -euo pipefail + if [[ "${GITHUB_EVENT_NAME}" == 'workflow_dispatch' ]]; then + release_tag='${{ github.event.inputs.tag }}' + else + release_tag="${GITHUB_REF_NAME}" + fi + + if [[ -z "$release_tag" ]]; then + echo 'Release tag must not be empty.' >&2 + exit 1 + fi + if [[ "$release_tag" != v* ]]; then + echo "Release tag must start with v: $release_tag" >&2 + exit 1 + fi + + echo "release_tag=$release_tag" >> "$GITHUB_OUTPUT" + + - name: Check out repository + uses: actions/checkout@v6 + with: + ref: ${{ steps.release-tag.outputs.release_tag }} + + - name: Set up mise + uses: jdx/mise-action@v3 + + - name: Install CI dependencies + run: mise run bootstrap-ci + + # bootstrap-ci intentionally skips browser downloads so jobs that do not + # need Chromium can stay on deterministic `npm ci`. This release workflow + # runs the full Linux quality bar, so install Chromium explicitly just as + # `.github/workflows/ci.yml` does. + - name: Install Playwright Chromium + run: npx playwright install chromium + + # Keep the tag/package check strict so release assets always match the + # committed package metadata. Maintainers can cut both together with + # `npm version` (documented in docs/RELEASE-PROCESS.md). + - name: Validate release tag matches package version + shell: bash + env: + RELEASE_TAG: ${{ steps.release-tag.outputs.release_tag }} + run: | + set -euo pipefail + package_version="$(node --input-type=module <<'EOF' + import { readFileSync } from 'node:fs'; + const packageJson = JSON.parse(readFileSync('package.json', 'utf8')); + process.stdout.write(packageJson.version); + EOF + )" + expected_tag="v${package_version}" + + if [[ "$RELEASE_TAG" != "$expected_tag" ]]; then + echo "Release tag mismatch: expected $expected_tag from package.json, got $RELEASE_TAG" >&2 + exit 1 + fi + + - name: Run release quality gates + run: mise run ci + + - name: Pack verified release tarball + env: + RELEASE_DIR: ${{ runner.temp }}/release-assets + run: | + set -euo pipefail + mkdir -p "$RELEASE_DIR" + npm run pack:release -- \ + --pack-destination "$RELEASE_DIR" \ + --metadata-file "$RELEASE_DIR/package-metadata.json" + + - name: Export release metadata + id: release-metadata + shell: bash + env: + METADATA_FILE: ${{ runner.temp }}/release-assets/package-metadata.json + RELEASE_TAG: ${{ steps.release-tag.outputs.release_tag }} + run: | + set -euo pipefail + node --input-type=module <<'EOF' + import assert from 'node:assert/strict'; + import { appendFileSync, readFileSync } from 'node:fs'; + + const metadata = JSON.parse(readFileSync(process.env.METADATA_FILE, 'utf8')); + assert(metadata !== null && typeof metadata === 'object', 'metadata must be an object'); + + const outputs = { + release_tag: process.env.RELEASE_TAG, + package_name: metadata.packageName, + package_version: metadata.packageVersion, + tarball_filename: metadata.tarballFilename, + checksum_filename: metadata.checksumFilename, + checksum_sha256: metadata.checksumSha256, + }; + + for (const [key, value] of Object.entries(outputs)) { + assert(typeof value === 'string' && value.length > 0, `${key} must not be empty`); + appendFileSync(process.env.GITHUB_OUTPUT, `${key}=${value}\n`); + } + EOF + + - name: Upload release workflow artifacts + uses: actions/upload-artifact@v4 + with: + name: release-assets-${{ steps.release-metadata.outputs.release_tag }} + if-no-files-found: error + path: | + ${{ runner.temp }}/release-assets/${{ steps.release-metadata.outputs.tarball_filename }} + ${{ runner.temp }}/release-assets/${{ steps.release-metadata.outputs.checksum_filename }} + ${{ runner.temp }}/release-assets/package-metadata.json + + publish-github-release: + runs-on: ubuntu-latest + timeout-minutes: 10 + needs: prepare-release + steps: + - name: Download verified release artifacts + uses: actions/download-artifact@v4 + with: + name: release-assets-${{ needs.prepare-release.outputs.release_tag }} + path: ${{ runner.temp }}/release-assets + + - name: Write release notes + shell: bash + env: + RELEASE_TAG: ${{ needs.prepare-release.outputs.release_tag }} + PACKAGE_VERSION: ${{ needs.prepare-release.outputs.package_version }} + TARBALL_FILENAME: ${{ needs.prepare-release.outputs.tarball_filename }} + CHECKSUM_FILENAME: ${{ needs.prepare-release.outputs.checksum_filename }} + CHECKSUM_SHA256: ${{ needs.prepare-release.outputs.checksum_sha256 }} + RELEASE_NOTES_FILE: ${{ runner.temp }}/release-notes.md + run: | + set -euo pipefail + tarball_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/download/${RELEASE_TAG}/${TARBALL_FILENAME}" + + cat > "$RELEASE_NOTES_FILE" </dev/null 2>&1; then + gh release edit \ + "$RELEASE_TAG" \ + --repo "$GITHUB_REPOSITORY" \ + --title "$RELEASE_TAG" \ + --notes-file "$RELEASE_NOTES_FILE" + gh release upload \ + "$RELEASE_TAG" \ + "$RELEASE_DIR/$TARBALL_FILENAME" \ + "$RELEASE_DIR/$CHECKSUM_FILENAME" \ + --repo "$GITHUB_REPOSITORY" \ + --clobber + else + gh release create \ + "$RELEASE_TAG" \ + "$RELEASE_DIR/$TARBALL_FILENAME" \ + "$RELEASE_DIR/$CHECKSUM_FILENAME" \ + --repo "$GITHUB_REPOSITORY" \ + --verify-tag \ + --title "$RELEASE_TAG" \ + --notes-file "$RELEASE_NOTES_FILE" \ + "${create_flags[@]}" + fi + + # Future follow-up: + # publish-npm: + # needs: prepare-release + # runs-on: ubuntu-latest + # steps: + # - name: Publish verified tarball to npm + # run: echo 'Reserved for a future npm publish job.' diff --git a/README.md b/README.md index fd52f06..6e6b643 100644 --- a/README.md +++ b/README.md @@ -6,42 +6,48 @@ It is built for agent workflows that need both semantic state and visual artifac ## Installation `agent-terminal` currently supports Node `24.x`. -Released builds install from npm. For prerelease/private use, the guaranteed install path is a built tarball; direct GitHub installs are best-effort and may still fail in some environments. +Today, the supported hosted install path is the GitHub Release tarball asset. npm publication is intentionally not wired yet, and direct git dependency installs remain best-effort because they build from source. -### npm registry installation +### GitHub Release tarball installation -#### Global installation +#### Direct release asset install ```bash -npm install -g agent-terminal +VERSION= +RELEASE_TAG="v${VERSION}" +RELEASE_TGZ="agent-terminal-${VERSION}.tgz" +TARBALL_URL="https://github.com/coder/agent-terminal/releases/download/${RELEASE_TAG}/${RELEASE_TGZ}" + +npm install -g "$TARBALL_URL" agent-terminal version --json ``` -#### Project installation +#### Authenticated or private release install ```bash -npm install agent-terminal -./node_modules/.bin/agent-terminal version --json -``` +VERSION= +RELEASE_TAG="v${VERSION}" +RELEASE_TGZ="agent-terminal-${VERSION}.tgz" -### Direct GitHub installation - -```bash -npm install -g github:coder/agent-terminal +gh release download "$RELEASE_TAG" --repo coder/agent-terminal --pattern "$RELEASE_TGZ" +npm install -g "./$RELEASE_TGZ" agent-terminal version --json +agent-terminal --home "$(mktemp -d)" doctor --json ``` -GitHub installs attempt to build from source via npm's `prepare` hook. -Use this when you want the latest default-branch snapshot and your npm/git-dependency environment can build native dependencies cleanly. +#### Project-local install from a downloaded tarball -Today, the guaranteed prerelease path is still the built tarball route below. -The repository's install smoke now treats tarball install as the required path and records the current git-install caveat separately, because native dependencies such as `node-pty` can still fail during npm's git-dependency flow in some environments. +```bash +VERSION= +RELEASE_TGZ="./agent-terminal-${VERSION}.tgz" -If your shell setup injects `mise activate` (or similar trust-checked tooling) into npm lifecycle subprocesses, trust the checkout path first or use the tarball route below. +npm install "$RELEASE_TGZ" +./node_modules/.bin/agent-terminal version --json +``` -### Private tarball installation +### Local tarball build from a source checkout -When you need a deterministic prerelease artifact before the package is published, prefer a built tarball: +When you need a deterministic local artifact before publishing a GitHub Release, build the tarball from a checkout: ```bash TARBALL_DIR=$(mktemp -d) @@ -54,9 +60,20 @@ npm install -g --prefix "$INSTALL_PREFIX" "$TARBALL_DIR"/agent-terminal-*.tgz "$INSTALL_PREFIX"/bin/agent-terminal --home "$(mktemp -d)" doctor --json ``` -`npm run pack:private` always rebuilds `dist/` before packing, so the tarball matches the private artifact reviewers should install. -Keep the tarball route as the guaranteed private-distribution fallback even when GitHub installs are convenient. +`npm run pack:private` always rebuilds `dist/` before packing. Release automation instead uses `npm run pack:release` after the CI-quality build step so GitHub Releases upload the same verified tarball plus a checksum file. + +### Git source installation (best-effort) + +```bash +npm install -g github:coder/agent-terminal +agent-terminal version --json +``` + +GitHub installs attempt to build from source via npm's `prepare` hook. +Use this only when you explicitly want the latest default-branch snapshot and your npm/git-dependency environment can build native dependencies such as `node-pty` cleanly. +The repository's install smoke treats tarball install as the required path and records the current git-install caveat separately. +If your shell setup injects `mise activate` (or similar trust-checked tooling) into npm lifecycle subprocesses, trust the checkout path first or prefer the release tarball route. If `doctor --json` reports a missing Playwright browser cache on a fresh machine, run `npx playwright install chromium` once before renderer-backed workflows. ## Quick start @@ -117,22 +134,21 @@ Recommended sequence: ## AI agent skill -The public skill lives under `skills/agent-terminal/` and ships in the npm package. -You can install it directly for Mux-style skill loaders, or let TanStack Intent discover and map it for compatible coding agents. +The public skill lives under `skills/agent-terminal/` and ships in the release tarball package. +Install `agent-terminal` from a GitHub Release tarball first, then either use the packaged skill directly or let TanStack Intent map it into your agent config. For coding agents that can ingest instructions on demand, `agent-terminal skill` prints the packaged `SKILL.md` directly to stdout after installation. ```bash -npm install -g agent-terminal agent-terminal skill ``` ### TanStack Intent integration -If your agent supports Intent-compatible skill mappings, install `agent-terminal` in the project and let Intent wire the mapping into `AGENTS.md`, `CLAUDE.md`, or another supported agent config file. +After downloading `agent-terminal-.tgz` from GitHub Releases, install it in the project and let Intent wire the mapping into `AGENTS.md`, `CLAUDE.md`, or another supported agent config file. ```bash -npm install agent-terminal +npm install ./agent-terminal-.tgz npx @tanstack/intent@latest list npx @tanstack/intent@latest install ``` @@ -141,18 +157,18 @@ That workflow keeps the skill version aligned with the installed `agent-terminal ### Mux skill installation -```bash -npm install -g agent-terminal +After installing the release tarball globally: +```bash mkdir -p ~/.mux/skills/agent-terminal cp -R "$(npm root -g)/agent-terminal/skills/agent-terminal/." ~/.mux/skills/agent-terminal/ ``` ### Direct skill copy for other skill loaders -```bash -npm install -g agent-terminal +After installing the release tarball globally: +```bash mkdir -p ~/.claude/skills/agent-terminal cp -R "$(npm root -g)/agent-terminal/skills/agent-terminal/." ~/.claude/skills/agent-terminal/ ``` diff --git a/docs/RELEASE-PROCESS.md b/docs/RELEASE-PROCESS.md index 1f717bc..936bcac 100644 --- a/docs/RELEASE-PROCESS.md +++ b/docs/RELEASE-PROCESS.md @@ -1,23 +1,29 @@ # Release process -`RELEASE.md` defines the shipping contract. This document describes how maintainers should validate and present that contract. +`RELEASE.md` defines the shipping contract. This document describes how maintainers should validate, package, and publish that contract on GitHub Releases. -## Before cutting a release +## Release prerequisites 1. Re-read [`../RELEASE.md`](../RELEASE.md) and confirm it still matches the shipped surface. 2. Re-read [`../ROADMAP.md`](../ROADMAP.md) and confirm deferred work is not mixed back into the release contract. 3. Verify the primary docs route correctly from [`../README.md`](../README.md) to release, roadmap, design, and dogfood materials. 4. Review [`../dogfood/CATALOG.md`](../dogfood/CATALOG.md) and make sure the release-signoff bundle is current and easy to find. +5. Prefer cutting the release with npm's built-in version command so the package metadata and git tag are created together in one step. +6. Confirm npm publication is still intentionally out of scope for this workflow; the supported hosted install path today is the GitHub Release tarball asset. ## Validation bar -Run the full repo validation command: +Preferred local validation uses `mise`: ```bash -npm run verify +mise run ci ``` -That command now includes the tarball packaging smoke plus a git-install caveat check, so release candidates exercise the guaranteed private-distribution path and record the current git-dependency behavior before publish. +If `mise` is unavailable, run: + +```bash +npm run verify +``` If the public skill changed, also run: @@ -25,8 +31,99 @@ If the public skill changed, also run: npm run intent:validate ``` +`mise run ci` exercises formatting, lint, typecheck, tests, build, and the install smoke. The install smoke now validates the shared release tarball packer plus the guaranteed tarball install route before any publish step runs. + +## Prepare the release asset locally (optional but recommended) + +Use the same release packer that CI relies on: + +```bash +RELEASE_DIR=$(mktemp -d) +npm run build +npm run pack:release -- --pack-destination "$RELEASE_DIR" --metadata-file "$RELEASE_DIR/package-metadata.json" +cat "$RELEASE_DIR/package-metadata.json" +sha256sum -c "$RELEASE_DIR"/*.tgz.sha256 +``` + +That command produces the same tarball, checksum, and metadata shape that the GitHub release workflow uploads. + +## Cut the release commit and tag + +The recommended maintainer flow is to use npm's built-in version command rather than editing `package.json` and creating a matching tag separately: + +```bash +npm version patch -m "chore(release): %s" +# or: npm version minor -m "chore(release): %s" +# or: npm version major -m "chore(release): %s" +``` + +That updates `package.json` (and lockfiles if present), creates the release commit, and creates the matching git tag in one step. +After that, push the commit and tag together: + +```bash +git push origin HEAD --follow-tags +``` + +If you prefer to choose the exact version yourself, you can also run `npm version -m "chore(release): %s"`. +The release workflow still validates that the checked-out `package.json` version matches the `vX.Y.Z` tag before publishing assets. + +## Publish the GitHub Release + +The hand-curated workflow lives at [`.github/workflows/release.yml`](../.github/workflows/release.yml). +Trigger it in one of two ways: + +1. Push the release commit and tag created by `npm version`: + + ```bash + git push origin HEAD --follow-tags + ``` + +2. Or, if the release commit/tag already exists remotely, open the GitHub Actions UI, choose the **Release** workflow, and run it manually with the `tag` input set to `vX.Y.Z`. + +The workflow will: + +- resolve the release tag and check out that exact ref, +- validate that the tag matches the `package.json` version, +- run `mise run ci`, +- pack the verified tarball with `npm run pack:release`, +- upload the tarball, checksum, and metadata JSON as workflow artifacts, +- and create or update the GitHub Release with the `.tgz` and `.sha256` assets attached. + +The workflow intentionally splits artifact preparation from GitHub release publication so a future npm-publish job can depend on the verified `prepare-release` outputs instead of rebuilding the package. + +## Verify the published release assets + +After the workflow succeeds, verify the hosted asset before announcing the release: + +```bash +VERSION= +RELEASE_TAG="v${VERSION}" +RELEASE_TGZ="agent-terminal-${VERSION}.tgz" + +DOWNLOAD_DIR=$(mktemp -d) +INSTALL_PREFIX=$(mktemp -d) +AGENT_TERMINAL_HOME=$(mktemp -d) + +gh release download "$RELEASE_TAG" --repo coder/agent-terminal --dir "$DOWNLOAD_DIR" --pattern "$RELEASE_TGZ" +gh release download "$RELEASE_TAG" --repo coder/agent-terminal --dir "$DOWNLOAD_DIR" --pattern "${RELEASE_TGZ}.sha256" +sha256sum -c "$DOWNLOAD_DIR/${RELEASE_TGZ}.sha256" + +npm install -g --prefix "$INSTALL_PREFIX" "$DOWNLOAD_DIR/$RELEASE_TGZ" +"$INSTALL_PREFIX"/bin/agent-terminal version --json +"$INSTALL_PREFIX"/bin/agent-terminal --home "$AGENT_TERMINAL_HOME" doctor --json +``` + +For private releases, authenticated download is the expected verification route. +If you are testing a public release and the direct asset URL is reachable in your environment, you can also verify the hosted install path directly with `npm install -g `. + ## Proof expectations - Keep at least one current release-readiness bundle under `dogfood/`. - Keep evergreen scenario bundles easy to discover from the dogfood catalog. -- When a change affects renderer, screenshot, wait, export, or review UX, include screenshots and recordings in the relevant proof bundle when feasible. +- When a change affects release, packaging, install, renderer, screenshot, wait, export, or review UX, include screenshots and recordings in the relevant proof bundle when feasible. + +## Future npm publish seam + +The release workflow intentionally stops at the GitHub Release asset. +A future npm-publish job should depend on `prepare-release`, consume the verified tarball and metadata JSON that job already emits, and publish without rebuilding the package. +Package name and registry decisions remain a separate follow-up because the current npm name is not settled. diff --git a/dogfood/20260410-release-tarball/README.md b/dogfood/20260410-release-tarball/README.md new file mode 100644 index 0000000..01e4997 --- /dev/null +++ b/dogfood/20260410-release-tarball/README.md @@ -0,0 +1,15 @@ +# Release tarball proof bundle + +This bundle captures a 2026-04-10 local verification pass for the GitHub-release tarball workflow. + +## What it verifies + +- `npm run pack:release` produced a tarball, checksum file, and metadata JSON under `release-artifact/`. +- The tarball installed successfully into an isolated prefix. +- The installed CLI passed `version --json` and `doctor --json` checks when invoked with a Node 24 runtime. +- `screenshots/01-release-proof.png`, `recordings/release-proof.cast`, and `videos/release-proof.webm` provide reviewer-facing proof of the summarized validation output. + +## Important limitation + +This workspace does not have the GitHub release credentials and remote tag context needed to publish or inspect a live GitHub Actions release run. +The hosted workflow itself is implemented in `.github/workflows/release.yml`; this bundle covers the local pack/install/verify leg that the workflow reuses. diff --git a/dogfood/20260410-release-tarball/checksum-path.txt b/dogfood/20260410-release-tarball/checksum-path.txt new file mode 100644 index 0000000..cb537d7 --- /dev/null +++ b/dogfood/20260410-release-tarball/checksum-path.txt @@ -0,0 +1 @@ +/home/coder/.mux/src/agent-terminal/build-deps-zkez/dogfood/20260410-release-tarball/release-artifact/agent-terminal-0.1.0.tgz.sha256 diff --git a/dogfood/20260410-release-tarball/checksum-verify.txt b/dogfood/20260410-release-tarball/checksum-verify.txt new file mode 100644 index 0000000..a786c8d --- /dev/null +++ b/dogfood/20260410-release-tarball/checksum-verify.txt @@ -0,0 +1 @@ +agent-terminal-0.1.0.tgz: OK diff --git a/dogfood/20260410-release-tarball/commands.sh b/dogfood/20260410-release-tarball/commands.sh new file mode 100755 index 0000000..cc72ac8 --- /dev/null +++ b/dogfood/20260410-release-tarball/commands.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Local proof commands used: +# npm run pack:release -- --pack-destination dogfood/20260410-release-tarball/release-artifact --metadata-file dogfood/20260410-release-tarball/package-metadata.json +# npm install -g --prefix +# PATH=":$PATH" /bin/agent-terminal version --json +# PATH=":$PATH" /bin/agent-terminal --home doctor --json +# PATH=":$PATH" dist/cli/main.js --home create/run/wait/screenshot/record export ... diff --git a/dogfood/20260410-release-tarball/destroy.json b/dogfood/20260410-release-tarball/destroy.json new file mode 100644 index 0000000..cf6e7e9 --- /dev/null +++ b/dogfood/20260410-release-tarball/destroy.json @@ -0,0 +1,9 @@ +{ + "ok": true, + "command": "destroy", + "timestamp": "2026-04-10T08:39:57.289Z", + "result": { + "sessionId": "01KNV8RAQB9PKZQRVQQ50QGGPK", + "destroyed": true + } +} diff --git a/dogfood/20260410-release-tarball/doctor.json b/dogfood/20260410-release-tarball/doctor.json new file mode 100644 index 0000000..d0dbe1b --- /dev/null +++ b/dogfood/20260410-release-tarball/doctor.json @@ -0,0 +1,130 @@ +{ + "ok": true, + "command": "doctor", + "timestamp": "2026-04-10T08:39:50.278Z", + "result": { + "ok": true, + "checks": { + "environment": [ + { + "name": "node-runtime", + "status": "pass", + "message": "Node 24.14.0 ok", + "durationMs": 0 + }, + { + "name": "cwd-access", + "status": "pass", + "message": "cwd read/write: /home/coder/.mux/src/agent-terminal/build-deps-zkez", + "durationMs": 1 + }, + { + "name": "temp-dir", + "status": "pass", + "message": "temp dir ok: /tmp", + "durationMs": 1 + }, + { + "name": "home_isolation", + "status": "pass", + "message": "Agent-terminal home is isolated from system home: /tmp/tmp.BqxaylkCMY", + "durationMs": 0 + }, + { + "name": "home-writable", + "status": "pass", + "message": "home writable: /tmp/tmp.BqxaylkCMY", + "durationMs": 1 + }, + { + "name": "pty-spawn", + "status": "pass", + "message": "spawned /home/coder/.npm/_npx/387698761821791d/node_modules/node/bin/node", + "durationMs": 25 + }, + { + "name": "socket-viable", + "status": "pass", + "message": "socket ok: /tmp/tmp.BqxaylkCMY/sessions/doctor-1336636-mnsnnmcn-2/host.sock", + "durationMs": 3 + }, + { + "name": "artifact-atomicity", + "status": "pass", + "message": "atomic rename ok: /tmp/tmp.BqxaylkCMY/sessions/doctor-1336636-mnsnnmcq-3/artifacts", + "durationMs": 2 + }, + { + "name": "event-log-writable", + "status": "pass", + "message": "append ok: /tmp/tmp.BqxaylkCMY/sessions/doctor-1336636-mnsnnmcs-5/events.jsonl", + "durationMs": 1 + } + ], + "renderer": [ + { + "name": "playwright_available", + "status": "pass", + "message": "available", + "durationMs": 0 + }, + { + "name": "browser_cache_accessible", + "status": "pass", + "message": "browser cache accessible: /home/coder/.cache/ms-playwright", + "durationMs": 0 + }, + { + "name": "browser_launch", + "status": "pass", + "message": "chromium launches", + "durationMs": 111 + }, + { + "name": "ghostty_web_available", + "status": "pass", + "message": "WASM available", + "durationMs": 6 + }, + { + "name": "screenshot_viable", + "status": "pass", + "message": "viable", + "durationMs": 163 + } + ] + }, + "capabilities": [ + { + "name": "snapshot", + "status": "available", + "reason": "built-in capability", + "detail": "available without external renderer dependencies" + }, + { + "name": "wait", + "status": "available", + "reason": "built-in capability", + "detail": "available without external renderer dependencies" + }, + { + "name": "screenshot", + "status": "available", + "reason": "renderer smoke checks passed", + "detail": "playwright_available: available; browser_launch: chromium launches; ghostty_web_available: WASM available; screenshot_viable: viable" + }, + { + "name": "record-export-asciicast", + "status": "available", + "reason": "built-in capability", + "detail": "available without external renderer dependencies" + }, + { + "name": "record-export-webm", + "status": "available", + "reason": "browser-backed export dependencies available", + "detail": "playwright_available: available; browser_launch: chromium launches; ghostty_web_available: WASM available" + } + ] + } +} diff --git a/dogfood/20260410-release-tarball/install-prefix.txt b/dogfood/20260410-release-tarball/install-prefix.txt new file mode 100644 index 0000000..b39b4b9 --- /dev/null +++ b/dogfood/20260410-release-tarball/install-prefix.txt @@ -0,0 +1 @@ +/tmp/tmp.WWkpeBJvhV diff --git a/dogfood/20260410-release-tarball/node24-path.txt b/dogfood/20260410-release-tarball/node24-path.txt new file mode 100644 index 0000000..f1b7e41 --- /dev/null +++ b/dogfood/20260410-release-tarball/node24-path.txt @@ -0,0 +1 @@ +/home/coder/.npm/_npx/387698761821791d/node_modules/node/bin/node diff --git a/dogfood/20260410-release-tarball/package-metadata.json b/dogfood/20260410-release-tarball/package-metadata.json new file mode 100644 index 0000000..ca88b9d --- /dev/null +++ b/dogfood/20260410-release-tarball/package-metadata.json @@ -0,0 +1,287 @@ +{ + "packageName": "agent-terminal", + "packageVersion": "0.1.0", + "tarballFilename": "agent-terminal-0.1.0.tgz", + "tarballPath": "/home/coder/.mux/src/agent-terminal/build-deps-zkez/dogfood/20260410-release-tarball/release-artifact/agent-terminal-0.1.0.tgz", + "tarballSizeBytes": 1771872, + "checksumFilename": "agent-terminal-0.1.0.tgz.sha256", + "checksumPath": "/home/coder/.mux/src/agent-terminal/build-deps-zkez/dogfood/20260410-release-tarball/release-artifact/agent-terminal-0.1.0.tgz.sha256", + "checksumSha256": "c241559f565989e3c21db48a86eff0e4b4decd22b17e3717a0a793c18829aa61", + "npmShasum": "65ea545bf96c47aa24818392d9f311dd407de182", + "packedPaths": [ + "README.md", + "dist/cli/commands/create.d.ts", + "dist/cli/commands/create.d.ts.map", + "dist/cli/commands/create.js", + "dist/cli/commands/create.js.map", + "dist/cli/commands/destroy.d.ts", + "dist/cli/commands/destroy.d.ts.map", + "dist/cli/commands/destroy.js", + "dist/cli/commands/destroy.js.map", + "dist/cli/commands/doctor.d.ts", + "dist/cli/commands/doctor.d.ts.map", + "dist/cli/commands/doctor.js", + "dist/cli/commands/doctor.js.map", + "dist/cli/commands/gc.d.ts", + "dist/cli/commands/gc.d.ts.map", + "dist/cli/commands/gc.js", + "dist/cli/commands/gc.js.map", + "dist/cli/commands/inputSource.d.ts", + "dist/cli/commands/inputSource.d.ts.map", + "dist/cli/commands/inputSource.js", + "dist/cli/commands/inputSource.js.map", + "dist/cli/commands/inspect.d.ts", + "dist/cli/commands/inspect.d.ts.map", + "dist/cli/commands/inspect.js", + "dist/cli/commands/inspect.js.map", + "dist/cli/commands/list.d.ts", + "dist/cli/commands/list.d.ts.map", + "dist/cli/commands/list.js", + "dist/cli/commands/list.js.map", + "dist/cli/commands/mark.d.ts", + "dist/cli/commands/mark.d.ts.map", + "dist/cli/commands/mark.js", + "dist/cli/commands/mark.js.map", + "dist/cli/commands/paste.d.ts", + "dist/cli/commands/paste.d.ts.map", + "dist/cli/commands/paste.js", + "dist/cli/commands/paste.js.map", + "dist/cli/commands/record-export.d.ts", + "dist/cli/commands/record-export.d.ts.map", + "dist/cli/commands/record-export.js", + "dist/cli/commands/record-export.js.map", + "dist/cli/commands/resize.d.ts", + "dist/cli/commands/resize.d.ts.map", + "dist/cli/commands/resize.js", + "dist/cli/commands/resize.js.map", + "dist/cli/commands/run.d.ts", + "dist/cli/commands/run.d.ts.map", + "dist/cli/commands/run.js", + "dist/cli/commands/run.js.map", + "dist/cli/commands/screenshot.d.ts", + "dist/cli/commands/screenshot.d.ts.map", + "dist/cli/commands/screenshot.js", + "dist/cli/commands/screenshot.js.map", + "dist/cli/commands/send-keys.d.ts", + "dist/cli/commands/send-keys.d.ts.map", + "dist/cli/commands/send-keys.js", + "dist/cli/commands/send-keys.js.map", + "dist/cli/commands/signal.d.ts", + "dist/cli/commands/signal.d.ts.map", + "dist/cli/commands/signal.js", + "dist/cli/commands/signal.js.map", + "dist/cli/commands/skill.d.ts", + "dist/cli/commands/skill.d.ts.map", + "dist/cli/commands/skill.js", + "dist/cli/commands/skill.js.map", + "dist/cli/commands/snapshot.d.ts", + "dist/cli/commands/snapshot.d.ts.map", + "dist/cli/commands/snapshot.js", + "dist/cli/commands/snapshot.js.map", + "dist/cli/commands/type.d.ts", + "dist/cli/commands/type.d.ts.map", + "dist/cli/commands/type.js", + "dist/cli/commands/type.js.map", + "dist/cli/commands/version.d.ts", + "dist/cli/commands/version.d.ts.map", + "dist/cli/commands/version.js", + "dist/cli/commands/version.js.map", + "dist/cli/commands/wait.d.ts", + "dist/cli/commands/wait.d.ts.map", + "dist/cli/commands/wait.js", + "dist/cli/commands/wait.js.map", + "dist/cli/context.d.ts", + "dist/cli/context.d.ts.map", + "dist/cli/context.js", + "dist/cli/context.js.map", + "dist/cli/errors.d.ts", + "dist/cli/errors.d.ts.map", + "dist/cli/errors.js", + "dist/cli/errors.js.map", + "dist/cli/exitCodes.d.ts", + "dist/cli/exitCodes.d.ts.map", + "dist/cli/exitCodes.js", + "dist/cli/exitCodes.js.map", + "dist/cli/main.d.ts", + "dist/cli/main.d.ts.map", + "dist/cli/main.js", + "dist/cli/main.js.map", + "dist/cli/output.d.ts", + "dist/cli/output.d.ts.map", + "dist/cli/output.js", + "dist/cli/output.js.map", + "dist/config/defaults.d.ts", + "dist/config/defaults.d.ts.map", + "dist/config/defaults.js", + "dist/config/defaults.js.map", + "dist/config/resolveConfig.d.ts", + "dist/config/resolveConfig.d.ts.map", + "dist/config/resolveConfig.js", + "dist/config/resolveConfig.js.map", + "dist/export/asciicast.d.ts", + "dist/export/asciicast.d.ts.map", + "dist/export/asciicast.js", + "dist/export/asciicast.js.map", + "dist/export/webm.d.ts", + "dist/export/webm.d.ts.map", + "dist/export/webm.js", + "dist/export/webm.js.map", + "dist/host/eventLog.d.ts", + "dist/host/eventLog.d.ts.map", + "dist/host/eventLog.js", + "dist/host/eventLog.js.map", + "dist/host/hostMain.d.ts", + "dist/host/hostMain.d.ts.map", + "dist/host/hostMain.js", + "dist/host/hostMain.js.map", + "dist/host/lifecycle.d.ts", + "dist/host/lifecycle.d.ts.map", + "dist/host/lifecycle.js", + "dist/host/lifecycle.js.map", + "dist/host/renderer.d.ts", + "dist/host/renderer.d.ts.map", + "dist/host/renderer.js", + "dist/host/renderer.js.map", + "dist/host/replay.d.ts", + "dist/host/replay.d.ts.map", + "dist/host/replay.js", + "dist/host/replay.js.map", + "dist/host/rpcClient.d.ts", + "dist/host/rpcClient.d.ts.map", + "dist/host/rpcClient.js", + "dist/host/rpcClient.js.map", + "dist/host/rpcServer.d.ts", + "dist/host/rpcServer.d.ts.map", + "dist/host/rpcServer.js", + "dist/host/rpcServer.js.map", + "dist/host/sessionState.d.ts", + "dist/host/sessionState.d.ts.map", + "dist/host/sessionState.js", + "dist/host/sessionState.js.map", + "dist/index.d.ts", + "dist/index.d.ts.map", + "dist/index.js", + "dist/index.js.map", + "dist/protocol/envelope.d.ts", + "dist/protocol/envelope.d.ts.map", + "dist/protocol/envelope.js", + "dist/protocol/envelope.js.map", + "dist/protocol/errors.d.ts", + "dist/protocol/errors.d.ts.map", + "dist/protocol/errors.js", + "dist/protocol/errors.js.map", + "dist/protocol/messages.d.ts", + "dist/protocol/messages.d.ts.map", + "dist/protocol/messages.js", + "dist/protocol/messages.js.map", + "dist/protocol/schemas.d.ts", + "dist/protocol/schemas.d.ts.map", + "dist/protocol/schemas.js", + "dist/protocol/schemas.js.map", + "dist/protocol/terminationCategory.d.ts", + "dist/protocol/terminationCategory.d.ts.map", + "dist/protocol/terminationCategory.js", + "dist/protocol/terminationCategory.js.map", + "dist/pty/createPty.d.ts", + "dist/pty/createPty.d.ts.map", + "dist/pty/createPty.js", + "dist/pty/createPty.js.map", + "dist/pty/keyEncoder.d.ts", + "dist/pty/keyEncoder.d.ts.map", + "dist/pty/keyEncoder.js", + "dist/pty/keyEncoder.js.map", + "dist/pty/pasteEncoder.d.ts", + "dist/pty/pasteEncoder.d.ts.map", + "dist/pty/pasteEncoder.js", + "dist/pty/pasteEncoder.js.map", + "dist/renderer/backend.d.ts", + "dist/renderer/backend.d.ts.map", + "dist/renderer/backend.js", + "dist/renderer/backend.js.map", + "dist/renderer/browserPath.d.ts", + "dist/renderer/browserPath.d.ts.map", + "dist/renderer/browserPath.js", + "dist/renderer/browserPath.js.map", + "dist/renderer/bundledFont.d.ts", + "dist/renderer/bundledFont.d.ts.map", + "dist/renderer/bundledFont.js", + "dist/renderer/bundledFont.js.map", + "dist/renderer/capabilities.d.ts", + "dist/renderer/capabilities.d.ts.map", + "dist/renderer/capabilities.js", + "dist/renderer/capabilities.js.map", + "dist/renderer/ghosttyWeb/assets/FONT-LICENSE.txt", + "dist/renderer/ghosttyWeb/assets/JetBrainsMono-Regular-latin.woff2", + "dist/renderer/ghosttyWeb/assets/SymbolsNerdFontMono-Regular.ttf", + "dist/renderer/ghosttyWeb/backend.d.ts", + "dist/renderer/ghosttyWeb/backend.d.ts.map", + "dist/renderer/ghosttyWeb/backend.js", + "dist/renderer/ghosttyWeb/backend.js.map", + "dist/renderer/ghosttyWeb/index.d.ts", + "dist/renderer/ghosttyWeb/index.d.ts.map", + "dist/renderer/ghosttyWeb/index.js", + "dist/renderer/ghosttyWeb/index.js.map", + "dist/renderer/index.d.ts", + "dist/renderer/index.d.ts.map", + "dist/renderer/index.js", + "dist/renderer/index.js.map", + "dist/renderer/profiles.d.ts", + "dist/renderer/profiles.d.ts.map", + "dist/renderer/profiles.js", + "dist/renderer/profiles.js.map", + "dist/renderer/types.d.ts", + "dist/renderer/types.d.ts.map", + "dist/renderer/types.js", + "dist/renderer/types.js.map", + "dist/replay/offlineReplay.d.ts", + "dist/replay/offlineReplay.d.ts.map", + "dist/replay/offlineReplay.js", + "dist/replay/offlineReplay.js.map", + "dist/storage/artifactHealth.d.ts", + "dist/storage/artifactHealth.d.ts.map", + "dist/storage/artifactHealth.js", + "dist/storage/artifactHealth.js.map", + "dist/storage/artifactManifest.d.ts", + "dist/storage/artifactManifest.d.ts.map", + "dist/storage/artifactManifest.js", + "dist/storage/artifactManifest.js.map", + "dist/storage/artifactPaths.d.ts", + "dist/storage/artifactPaths.d.ts.map", + "dist/storage/artifactPaths.js", + "dist/storage/artifactPaths.js.map", + "dist/storage/home.d.ts", + "dist/storage/home.d.ts.map", + "dist/storage/home.js", + "dist/storage/home.js.map", + "dist/storage/manifests.d.ts", + "dist/storage/manifests.d.ts.map", + "dist/storage/manifests.js", + "dist/storage/manifests.js.map", + "dist/storage/sessionPaths.d.ts", + "dist/storage/sessionPaths.d.ts.map", + "dist/storage/sessionPaths.js", + "dist/storage/sessionPaths.js.map", + "dist/tools/index.d.ts", + "dist/tools/index.d.ts.map", + "dist/tools/index.js", + "dist/tools/index.js.map", + "dist/tools/review-bundle.d.ts", + "dist/tools/review-bundle.d.ts.map", + "dist/tools/review-bundle.js", + "dist/tools/review-bundle.js.map", + "dist/tools/validate-bundle.d.ts", + "dist/tools/validate-bundle.d.ts.map", + "dist/tools/validate-bundle.js", + "dist/tools/validate-bundle.js.map", + "dist/util/assert.d.ts", + "dist/util/assert.d.ts.map", + "dist/util/assert.js", + "dist/util/assert.js.map", + "dist/util/logger.d.ts", + "dist/util/logger.d.ts.map", + "dist/util/logger.js", + "dist/util/logger.js.map", + "package.json", + "skills/agent-terminal/SKILL.md" + ] +} diff --git a/dogfood/20260410-release-tarball/proof-summary.txt b/dogfood/20260410-release-tarball/proof-summary.txt new file mode 100644 index 0000000..e9edaff --- /dev/null +++ b/dogfood/20260410-release-tarball/proof-summary.txt @@ -0,0 +1,18 @@ +# release tarball proof +package: agent-terminal@0.1.0 +tarball: agent-terminal-0.1.0.tgz +checksum: agent-terminal-0.1.0.tgz.sha256 +sha256: c241559f565989e3c21db48a86eff0e4b4decd22b17e3717a0a793c18829aa61 +size_bytes: 1771872 + +# installed binary checks +version.ok: true +version.command: version +version.cliVersion: 0.1.0 +version.node: v24.14.0 +doctor.ok: true +doctor.command: doctor +doctor.result.ok: true +doctor.node_check: pass + +See package-metadata.json, install.log, version.json, and doctor.json for the raw outputs. diff --git a/dogfood/20260410-release-tarball/record-asciicast.json b/dogfood/20260410-release-tarball/record-asciicast.json new file mode 100644 index 0000000..10090ac --- /dev/null +++ b/dogfood/20260410-release-tarball/record-asciicast.json @@ -0,0 +1,23 @@ +{ + "ok": true, + "command": "record export", + "timestamp": "2026-04-10T08:39:54.191Z", + "result": { + "sessionId": "01KNV8RAQB9PKZQRVQQ50QGGPK", + "format": "asciicast", + "artifactPath": "/home/coder/.mux/src/agent-terminal/build-deps-zkez/dogfood/20260410-release-tarball/recordings/release-proof.cast", + "bytes": 2667, + "sha256": "97f488f116befbe05215f02de3685f847b9ebae03f9acfb5b5d18d7f701442a1", + "capturedAtSeq": 9, + "durationMs": 572, + "metadata": { + "width": 80, + "height": 24, + "title": "01KNV8RAQB9PKZQRVQQ50QGGPK", + "timestamp": 1775810391, + "outputEventCount": 9, + "resizeEventCount": 0, + "markerCount": 0 + } + } +} diff --git a/dogfood/20260410-release-tarball/record-webm.json b/dogfood/20260410-release-tarball/record-webm.json new file mode 100644 index 0000000..804dd28 --- /dev/null +++ b/dogfood/20260410-release-tarball/record-webm.json @@ -0,0 +1,23 @@ +{ + "ok": true, + "command": "record export", + "timestamp": "2026-04-10T08:39:56.774Z", + "result": { + "sessionId": "01KNV8RAQB9PKZQRVQQ50QGGPK", + "format": "webm", + "artifactPath": "/home/coder/.mux/src/agent-terminal/build-deps-zkez/dogfood/20260410-release-tarball/videos/release-proof.webm", + "bytes": 106630, + "sha256": "b1230db443590ec1be88a3b4329ca030e617c979375c7a6a1ca223522f0bd84f", + "capturedAtSeq": 9, + "durationMs": 572, + "metadata": { + "width": 80, + "height": 24, + "profileName": "reference-dark", + "renderProfileHash": "8ffed6af301ec7c0e6b69599c3be0d1d12096f9fcdfc59d0bbb4cc474d64c53d", + "timingMode": "accelerated", + "outputEventCount": 9, + "resizeEventCount": 0 + } + } +} diff --git a/dogfood/20260410-release-tarball/recordings/release-proof.cast b/dogfood/20260410-release-tarball/recordings/release-proof.cast new file mode 100644 index 0000000..54b146d --- /dev/null +++ b/dogfood/20260410-release-tarball/recordings/release-proof.cast @@ -0,0 +1,10 @@ +{"version":2,"width":80,"height":24,"timestamp":1775810391,"title":"01KNV8RAQB9PKZQRVQQ50QGGPK","sessionId":"01KNV8RAQB9PKZQRVQQ50QGGPK","env":{"TERM":"xterm-256color"},"toolVersion":"0.1.0"} +[0,"o","bash: /home/linuxbrew/.linuxbrew/bin/brew: No such file or directory\r\n"] +[0.165,"o","\u001b[?2004h\u001b[1;33mcoder\u001b[0m in \u001b[1;2;32m🌐 aaaaaaa\u001b[0m in \u001b[1;36mbuild-deps-zkez\u001b[0m on \u001b[1;35m build-deps-zkez:main\u001b[0m \u001b[1;31m[!?]\u001b[0m is \u001b[1;38;5;208m📦 v0.1.0\u001b[0m via \u001b[1;32m v24.14.1 \u001b[0mon \u001b[1;34m☁️ \u001b[0m \r\n\u001b[1;2;31m⬢ [Docker]\u001b[0m \u001b[1;32mXY\u001b[0m "] +[0.238,"o","\u001b[7mset -euo pipefail\u001b[27m\r\n\r\u001b[7mprintf '\\033c'\u001b[27m\r\n\r\u001b[7mcat \"/home/coder/.mux/src/agent-terminal/build-deps-zkez/dogfood/20260410-releas\u001b[27m\u001b[7me\u001b[27m\r\u001b[7me-tarball/proof-summary.txt\"\u001b[27m\r\n\r\u001b[7mecho '__DOGFOOD_DONE__ release-tarball'\u001b[27m\r\n\r\n"] +[0.239,"o","\r\u001b[7mprintf '%s%s\\n' '__AT_MARKER_e7e11394a6d' 'f4d37bb6473c594cb00c8__'\u001b[27m\r\n\r\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\u001b[A\r\u001b[1;2;31m⬢ [Docker]\u001b[0m \u001b[1;32mXY\u001b[0m set -euo pipefail\r\n\rprintf '\\033c'\r\n\rcat \"/home/coder/.mux/src/agent-terminal/build-deps-zkez/dogfood/20260410-release-tarball/proof-summary.txt\"\r\n\recho '__DOGFOOD_DONE__ release-tarball'\r\n\r\n\rprintf '%s%s\\n' '__AT_MARKER_e7e11394a6d' 'f4d37bb6473c594cb00c8__'\r\n\r\u001b[A\r\n\u001b[?2004l\r"] +[0.299,"o","\u001bc"] +[0.338,"o","# release tarball proof\r\npackage: agent-terminal@0.1.0\r\ntarball: agent-terminal-0.1.0.tgz\r\nchecksum: agent-terminal-0.1.0.tgz.sha256\r\nsha256: c241559f565989e3c21db48a86eff0e4b4decd22b17e3717a0a793c18829aa61\r\nsize_bytes: 1771872\r\n\r\n# installed binary checks\r\nversion.ok: true\r\nversion.command: version\r\nversion.cliVersion: 0.1.0\r\nversion.node: v24.14.0\r\ndoctor.ok: true\r\ndoctor.command: doctor\r\ndoctor.result.ok: true\r\ndoctor.node_check: pass\r\n\r\nSee package-metadata.json, install.log, version.json, and doctor.json for the raw outputs.\r\n"] +[0.375,"o","__DOGFOOD_DONE__ release-tarball\r\n"] +[0.412,"o","__AT_MARKER_e7e11394a6df4d37bb6473c594cb00c8__\r\n"] +[0.572,"o","\u001b[?2004h\u001b[1;33mcoder\u001b[0m in \u001b[1;2;32m🌐 aaaaaaa\u001b[0m in \u001b[1;36mbuild-deps-zkez\u001b[0m on \u001b[1;35m build-deps-zkez:main\u001b[0m \u001b[1;31m[!?]\u001b[0m is \u001b[1;38;5;208m📦 v0.1.0\u001b[0m via \u001b[1;32m v24.14.1 \u001b[0mon \u001b[1;34m☁️ \u001b[0m \r\n\u001b[1;2;31m⬢ [Docker]\u001b[0m \u001b[1;32mXY\u001b[0m \u001b[K"] diff --git a/dogfood/20260410-release-tarball/release-artifact/agent-terminal-0.1.0.tgz b/dogfood/20260410-release-tarball/release-artifact/agent-terminal-0.1.0.tgz new file mode 100644 index 0000000..5345743 Binary files /dev/null and b/dogfood/20260410-release-tarball/release-artifact/agent-terminal-0.1.0.tgz differ diff --git a/dogfood/20260410-release-tarball/release-artifact/agent-terminal-0.1.0.tgz.sha256 b/dogfood/20260410-release-tarball/release-artifact/agent-terminal-0.1.0.tgz.sha256 new file mode 100644 index 0000000..3b619b3 --- /dev/null +++ b/dogfood/20260410-release-tarball/release-artifact/agent-terminal-0.1.0.tgz.sha256 @@ -0,0 +1 @@ +c241559f565989e3c21db48a86eff0e4b4decd22b17e3717a0a793c18829aa61 agent-terminal-0.1.0.tgz diff --git a/dogfood/20260410-release-tarball/render-proof.sh b/dogfood/20260410-release-tarball/render-proof.sh new file mode 100755 index 0000000..8e645e7 --- /dev/null +++ b/dogfood/20260410-release-tarball/render-proof.sh @@ -0,0 +1,4 @@ +set -euo pipefail +printf '\033c' +cat "/home/coder/.mux/src/agent-terminal/build-deps-zkez/dogfood/20260410-release-tarball/proof-summary.txt" +echo '__DOGFOOD_DONE__ release-tarball' diff --git a/dogfood/20260410-release-tarball/render-run.json b/dogfood/20260410-release-tarball/render-run.json new file mode 100644 index 0000000..1ba3d8e --- /dev/null +++ b/dogfood/20260410-release-tarball/render-run.json @@ -0,0 +1,13 @@ +{ + "ok": true, + "command": "run", + "timestamp": "2026-04-10T08:39:52.259Z", + "result": { + "accepted": true, + "completed": true, + "timedOut": false, + "seq": 2, + "durationMs": 666, + "marker": "__AT_MARKER_e7e11394a6df4d37bb6473c594cb00c8__" + } +} diff --git a/dogfood/20260410-release-tarball/render-wait.json b/dogfood/20260410-release-tarball/render-wait.json new file mode 100644 index 0000000..e7a0e15 --- /dev/null +++ b/dogfood/20260410-release-tarball/render-wait.json @@ -0,0 +1,13 @@ +{ + "ok": true, + "command": "wait", + "timestamp": "2026-04-10T08:39:53.073Z", + "result": { + "matched": true, + "timedOut": false, + "matchedText": "__DOGFOOD_DONE__ release-tarball", + "cursorRow": 23, + "cursorCol": 15, + "capturedAtSeq": 9 + } +} diff --git a/dogfood/20260410-release-tarball/screenshot.json b/dogfood/20260410-release-tarball/screenshot.json new file mode 100644 index 0000000..92549ad --- /dev/null +++ b/dogfood/20260410-release-tarball/screenshot.json @@ -0,0 +1,20 @@ +{ + "ok": true, + "command": "screenshot", + "timestamp": "2026-04-10T08:39:53.700Z", + "result": { + "sessionId": "01KNV8RAQB9PKZQRVQQ50QGGPK", + "capturedAtSeq": 9, + "profileName": "reference-dark", + "cols": 80, + "rows": 24, + "artifactPath": "/tmp/tmp.iIqPMuwKrT/sessions/01KNV8RAQB9PKZQRVQQ50QGGPK/artifacts/screenshot-9-reference-dark.png", + "pngSizeBytes": 40729, + "cursorVisible": false, + "rendererBackend": "ghostty-web", + "pixelWidth": 640, + "pixelHeight": 384, + "sha256": "aeda258c401be8f5b54f4cc9fe36119f617f59bdb357f149c787f172c1aba58a", + "renderProfileHash": "8ffed6af301ec7c0e6b69599c3be0d1d12096f9fcdfc59d0bbb4cc474d64c53d" + } +} diff --git a/dogfood/20260410-release-tarball/screenshots/01-release-proof.png b/dogfood/20260410-release-tarball/screenshots/01-release-proof.png new file mode 100644 index 0000000..51e43cf Binary files /dev/null and b/dogfood/20260410-release-tarball/screenshots/01-release-proof.png differ diff --git a/dogfood/20260410-release-tarball/session-home.txt b/dogfood/20260410-release-tarball/session-home.txt new file mode 100644 index 0000000..db1ff51 --- /dev/null +++ b/dogfood/20260410-release-tarball/session-home.txt @@ -0,0 +1 @@ +/tmp/tmp.iIqPMuwKrT diff --git a/dogfood/20260410-release-tarball/session-id.txt b/dogfood/20260410-release-tarball/session-id.txt new file mode 100644 index 0000000..30fb49b --- /dev/null +++ b/dogfood/20260410-release-tarball/session-id.txt @@ -0,0 +1 @@ +01KNV8RAQB9PKZQRVQQ50QGGPK diff --git a/dogfood/20260410-release-tarball/tarball-path.txt b/dogfood/20260410-release-tarball/tarball-path.txt new file mode 100644 index 0000000..aa45e08 --- /dev/null +++ b/dogfood/20260410-release-tarball/tarball-path.txt @@ -0,0 +1 @@ +/home/coder/.mux/src/agent-terminal/build-deps-zkez/dogfood/20260410-release-tarball/release-artifact/agent-terminal-0.1.0.tgz diff --git a/dogfood/20260410-release-tarball/verify-home.txt b/dogfood/20260410-release-tarball/verify-home.txt new file mode 100644 index 0000000..cadd9c3 --- /dev/null +++ b/dogfood/20260410-release-tarball/verify-home.txt @@ -0,0 +1 @@ +/tmp/tmp.BqxaylkCMY diff --git a/dogfood/20260410-release-tarball/version.json b/dogfood/20260410-release-tarball/version.json new file mode 100644 index 0000000..db4ff94 --- /dev/null +++ b/dogfood/20260410-release-tarball/version.json @@ -0,0 +1,37 @@ +{ + "ok": true, + "command": "version", + "timestamp": "2026-04-10T08:39:49.536Z", + "result": { + "cliVersion": "0.1.0", + "protocolVersion": "0.1.0", + "rendererBackends": ["ghostty-web"], + "runtime": { + "node": "v24.14.0", + "platform": "linux", + "arch": "x64" + }, + "capabilities": [ + { + "name": "snapshot", + "status": "available" + }, + { + "name": "wait", + "status": "available" + }, + { + "name": "screenshot", + "status": "available" + }, + { + "name": "record-export-asciicast", + "status": "available" + }, + { + "name": "record-export-webm", + "status": "available" + } + ] + } +} diff --git a/dogfood/20260410-release-tarball/videos/release-proof.webm b/dogfood/20260410-release-tarball/videos/release-proof.webm new file mode 100644 index 0000000..21cd31b Binary files /dev/null and b/dogfood/20260410-release-tarball/videos/release-proof.webm differ diff --git a/dogfood/CATALOG.md b/dogfood/CATALOG.md index a343647..515e10a 100644 --- a/dogfood/CATALOG.md +++ b/dogfood/CATALOG.md @@ -21,14 +21,15 @@ Paths below are relative to the repository root. ## Validation and release gates -| Bundle | Why it matters | -| ---------------------------------------------- | -------------------------------------------------------------------------- | -| `dogfood/20260326-week9-release-readiness/` | Current release-signoff bundle for the `0.1.0` bar. | -| `dogfood/20260325-week8-contract-locks/` | Contract-lock and reporting review evidence. | -| `dogfood/20260325-week8-bundle-validation/` | Validation of proof-bundle conventions. | -| `dogfood/20260325-week8-capability-inventory/` | Runtime capability inventory/reporting evidence. | -| `dogfood/20260325-week8-inspect-runtime/` | `inspect --json` runtime reporting review. | -| `dogfood/20260323-week5-platform-closure/` | Platform/documentation closeout evidence from the earlier hardening phase. | +| Bundle | Why it matters | +| ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| `dogfood/20260326-week9-release-readiness/` | Current release-signoff bundle for the `0.1.0` bar. | +| `dogfood/20260410-release-tarball/` | Local proof of the shared release tarball packer, checksum, and install flow used by the GitHub release workflow. | +| `dogfood/20260325-week8-contract-locks/` | Contract-lock and reporting review evidence. | +| `dogfood/20260325-week8-bundle-validation/` | Validation of proof-bundle conventions. | +| `dogfood/20260325-week8-capability-inventory/` | Runtime capability inventory/reporting evidence. | +| `dogfood/20260325-week8-inspect-runtime/` | `inspect --json` runtime reporting review. | +| `dogfood/20260323-week5-platform-closure/` | Platform/documentation closeout evidence from the earlier hardening phase. | | `dogfood/20260330-docs-navigation/` | Repository docs walkthrough with screenshots and a WebM recording of the new navigation path. | diff --git a/package.json b/package.json index e66371e..e25ecb3 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "lint": "eslint src test vitest.config.ts --max-warnings=0", "lint:fix": "eslint src test vitest.config.ts --fix", "pack:private": "npm run build && npm pack --json --ignore-scripts", + "pack:release": "node ./scripts/pack-release.mjs", "prepare": "npm run build", "prepublishOnly": "npm run verify", "review-bundle": "tsx src/tools/review-bundle.ts", diff --git a/scripts/pack-release.mjs b/scripts/pack-release.mjs new file mode 100644 index 0000000..8743c3a --- /dev/null +++ b/scripts/pack-release.mjs @@ -0,0 +1,392 @@ +#!/usr/bin/env node +import assert from 'node:assert/strict'; +import { spawnSync } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { constants as fsConstants } from 'node:fs'; +import { access, mkdir, readFile, stat, writeFile } from 'node:fs/promises'; +import { homedir, tmpdir } from 'node:os'; +import { delimiter, dirname, join, resolve } from 'node:path'; +import process from 'node:process'; +import { fileURLToPath } from 'node:url'; + +const projectRoot = resolve(fileURLToPath(new URL('..', import.meta.url))); +const npmCliPath = process.env.npm_execpath; +const currentScriptPath = fileURLToPath(import.meta.url); +const invokedScriptPath = + typeof process.argv[1] === 'string' ? resolve(process.argv[1]) : null; + +function sanitizeInheritedPath(pathValue) { + assert.equal(typeof pathValue, 'string', 'PATH must be a string'); + assert(pathValue.length > 0, 'PATH must not be empty'); + + const sanitizedEntries = pathValue + .split(delimiter) + .filter((entry) => entry.length > 0) + .filter( + (entry) => + !entry.endsWith(`${join('node_modules', '.bin')}`) && + !entry.endsWith('node-gyp-bin'), + ); + + assert( + sanitizedEntries.length > 0, + 'sanitized PATH must retain at least one executable directory', + ); + return sanitizedEntries.join(delimiter); +} + +function getTrustedConfigPaths(baseEnv, extraPaths) { + assert( + baseEnv !== null && typeof baseEnv === 'object', + 'env must be an object', + ); + assert(Array.isArray(extraPaths), 'extra paths must be an array'); + + const trustedPaths = new Set(); + const existingPaths = baseEnv.MISE_TRUSTED_CONFIG_PATHS; + if (typeof existingPaths === 'string' && existingPaths.length > 0) { + for (const trustedPath of existingPaths.split(delimiter)) { + if (trustedPath.length > 0) { + trustedPaths.add(trustedPath); + } + } + } + + const npmCachePath = + baseEnv.npm_config_cache ?? join(baseEnv.HOME ?? homedir(), '.npm'); + trustedPaths.add(npmCachePath); + trustedPaths.add(projectRoot); + trustedPaths.add(tmpdir()); + + for (const extraPath of extraPaths) { + if (typeof extraPath === 'string' && extraPath.length > 0) { + trustedPaths.add(extraPath); + } + } + + return [...trustedPaths].join(delimiter); +} + +function getDefaultEnv(extraPaths, baseEnv = process.env) { + return { + ...baseEnv, + PATH: sanitizeInheritedPath(baseEnv.PATH ?? ''), + MISE_TRUSTED_CONFIG_PATHS: getTrustedConfigPaths(baseEnv, extraPaths), + npm_config_audit: 'false', + npm_config_fund: 'false', + npm_config_update_notifier: 'false', + }; +} + +function formatCommand(command, args) { + return [command, ...args].join(' '); +} + +function relayOutput(target, content) { + assert(target === 'stdout' || target === 'stderr', 'invalid relay target'); + assert.equal(typeof content, 'string', 'relayed output must be a string'); + if (content.length === 0) { + return; + } + + if (target === 'stdout') { + process.stdout.write(content); + return; + } + + process.stderr.write(content); +} + +function run(command, args, options = {}) { + const { + cwd = projectRoot, + env, + expectedStatus = 0, + relayStdoutTo = null, + relayStderrTo = null, + } = options; + assert(typeof cwd === 'string' && cwd.length > 0, 'cwd must be set'); + assert(env !== null && typeof env === 'object', 'env must be set'); + assert( + Number.isInteger(expectedStatus), + 'expected status must be an integer', + ); + assert( + relayStdoutTo === null || + relayStdoutTo === 'stdout' || + relayStdoutTo === 'stderr', + 'invalid stdout relay target', + ); + assert( + relayStderrTo === null || + relayStderrTo === 'stdout' || + relayStderrTo === 'stderr', + 'invalid stderr relay target', + ); + + const result = spawnSync(command, args, { + cwd, + env, + encoding: 'utf8', + }); + + assert(result.error === undefined, result.error?.message ?? 'spawn failed'); + assert.equal( + result.status, + expectedStatus, + [ + `command failed: ${formatCommand(command, args)}`, + `cwd: ${cwd}`, + `expected exit code: ${String(expectedStatus)}`, + `actual exit code: ${String(result.status)}`, + result.stdout.length === 0 ? '' : `stdout:\n${result.stdout}`, + result.stderr.length === 0 ? '' : `stderr:\n${result.stderr}`, + ] + .filter((line) => line.length > 0) + .join('\n\n'), + ); + + if (relayStdoutTo !== null) { + relayOutput(relayStdoutTo, result.stdout); + } + if (relayStderrTo !== null) { + relayOutput(relayStderrTo, result.stderr); + } + + return result; +} + +function runNpm(args, options = {}) { + const { env = process.env } = options; + if (typeof npmCliPath === 'string' && npmCliPath.length > 0) { + return run(process.execPath, [npmCliPath, ...args], { ...options, env }); + } + + return run('npm', args, { ...options, env }); +} + +function parseJson(rawValue, description) { + assert.equal(typeof rawValue, 'string', `${description} must be a string`); + assert(rawValue.trim().length > 0, `${description} must not be empty`); + + try { + return JSON.parse(rawValue); + } catch (error) { + throw new Error(`${description} was not valid JSON`, { cause: error }); + } +} + +function assertString(value, description) { + assert.equal(typeof value, 'string', `${description} must be a string`); + assert(value.length > 0, `${description} must not be empty`); + return value; +} + +function logDefault(message) { + const normalizedMessage = assertString(message, 'log message'); + process.stderr.write(`${normalizedMessage}\n`); +} + +function parseCliArgs(argv) { + assert(Array.isArray(argv), 'argv must be an array'); + + let build = false; + let packDestination = projectRoot; + let metadataFile = null; + + for (let index = 0; index < argv.length; index += 1) { + const argument = argv[index]; + assertString(argument, 'CLI argument'); + + if (argument === '--build') { + build = true; + continue; + } + + if (argument === '--pack-destination') { + index += 1; + assert(index < argv.length, '--pack-destination requires a value'); + packDestination = assertString(argv[index], '--pack-destination value'); + continue; + } + + if (argument === '--metadata-file') { + index += 1; + assert(index < argv.length, '--metadata-file requires a value'); + metadataFile = assertString(argv[index], '--metadata-file value'); + continue; + } + + throw new Error(`unsupported argument: ${argument}`); + } + + return { + build, + packDestination, + metadataFile, + }; +} + +export async function packRelease(options = {}) { + const { + build = false, + packDestination = projectRoot, + metadataFile = null, + env = null, + log = logDefault, + } = options; + assert.equal(typeof build, 'boolean', 'build must be a boolean'); + assert.equal( + typeof packDestination, + 'string', + 'pack destination must be a string', + ); + assert(packDestination.length > 0, 'pack destination must not be empty'); + assert( + metadataFile === null || typeof metadataFile === 'string', + 'metadata file must be a string or null', + ); + assert(typeof log === 'function', 'log must be a function'); + + const resolvedPackDestination = resolve(projectRoot, packDestination); + const resolvedMetadataFile = + metadataFile === null ? null : resolve(projectRoot, metadataFile); + const resolvedEnv = + env ?? + getDefaultEnv([ + resolvedPackDestination, + resolvedMetadataFile === null ? '' : dirname(resolvedMetadataFile), + ]); + assert( + resolvedEnv !== null && typeof resolvedEnv === 'object', + 'resolved env must be an object', + ); + + const packageJson = parseJson( + await readFile(join(projectRoot, 'package.json'), 'utf8'), + 'package.json', + ); + assert( + packageJson !== null && typeof packageJson === 'object', + 'package.json must parse to an object', + ); + const packageName = assertString(packageJson.name, 'package.json name'); + const packageVersion = assertString( + packageJson.version, + 'package.json version', + ); + + await mkdir(resolvedPackDestination, { recursive: true }); + + if (build) { + log('Building package contents for release tarball...'); + runNpm(['run', 'build'], { + env: resolvedEnv, + relayStdoutTo: 'stderr', + relayStderrTo: 'stderr', + }); + } + + log(`Packing release tarball into ${resolvedPackDestination}...`); + const packResult = runNpm( + [ + 'pack', + '--json', + '--ignore-scripts', + '--pack-destination', + resolvedPackDestination, + ], + { + env: resolvedEnv, + relayStderrTo: 'stderr', + }, + ); + const packMetadata = parseJson(packResult.stdout, 'npm pack output'); + assert(Array.isArray(packMetadata), 'npm pack output must be an array'); + assert.equal( + packMetadata.length, + 1, + 'npm pack output must contain one entry', + ); + + const [packEntry] = packMetadata; + assert( + packEntry !== null && typeof packEntry === 'object', + 'pack entry must be an object', + ); + assert.equal(packEntry.name, packageName, 'packed package name mismatch'); + assert.equal( + packEntry.version, + packageVersion, + 'packed package version mismatch', + ); + + const tarballFilename = assertString( + packEntry.filename, + 'packed tarball filename', + ); + const tarballPath = join(resolvedPackDestination, tarballFilename); + await access(tarballPath, fsConstants.R_OK); + const tarballStats = await stat(tarballPath); + assert(tarballStats.isFile(), 'packed tarball must be a file'); + assert(tarballStats.size > 0, 'packed tarball must not be empty'); + + const tarballContents = await readFile(tarballPath); + const checksumSha256 = createHash('sha256') + .update(tarballContents) + .digest('hex'); + const checksumFilename = `${tarballFilename}.sha256`; + const checksumPath = join(resolvedPackDestination, checksumFilename); + const checksumContents = `${checksumSha256} ${tarballFilename}\n`; + await writeFile(checksumPath, checksumContents, 'utf8'); + + const packFiles = packEntry.files; + assert(Array.isArray(packFiles), 'pack entry must include files'); + const packedPaths = packFiles.map((entry) => { + assert( + entry !== null && typeof entry === 'object', + 'pack file entry must be an object', + ); + return assertString(entry.path, 'pack file path'); + }); + + const metadata = { + packageName, + packageVersion, + tarballFilename, + tarballPath, + tarballSizeBytes: tarballStats.size, + checksumFilename, + checksumPath, + checksumSha256, + npmShasum: + typeof packEntry.shasum === 'string' && packEntry.shasum.length > 0 + ? packEntry.shasum + : null, + packedPaths, + }; + + if (resolvedMetadataFile !== null) { + await mkdir(dirname(resolvedMetadataFile), { recursive: true }); + await writeFile( + resolvedMetadataFile, + `${JSON.stringify(metadata, null, 2)}\n`, + 'utf8', + ); + } + + return metadata; +} + +async function main() { + const options = parseCliArgs(process.argv.slice(2)); + const metadata = await packRelease(options); + process.stdout.write(`${JSON.stringify(metadata, null, 2)}\n`); +} + +if (invokedScriptPath === currentScriptPath) { + main().catch((error) => { + console.error(error); + process.exitCode = 1; + }); +} diff --git a/scripts/smoke-install.mjs b/scripts/smoke-install.mjs index 1094b2a..512b7db 100755 --- a/scripts/smoke-install.mjs +++ b/scripts/smoke-install.mjs @@ -2,21 +2,14 @@ import assert from 'node:assert/strict'; import { spawnSync } from 'node:child_process'; import { constants as fsConstants } from 'node:fs'; -import { - access, - cp, - mkdir, - mkdtemp, - readFile, - readdir, - rm, - stat, -} from 'node:fs/promises'; +import { access, cp, mkdtemp, readFile, readdir, rm } from 'node:fs/promises'; import { homedir, tmpdir } from 'node:os'; import { basename, delimiter, join, relative, resolve } from 'node:path'; import process from 'node:process'; import { fileURLToPath, pathToFileURL } from 'node:url'; +import { packRelease } from './pack-release.mjs'; + const projectRoot = resolve(fileURLToPath(new URL('..', import.meta.url))); const npmCliPath = process.env.npm_execpath; const supportedArgs = new Set(['--skip-build']); @@ -227,18 +220,10 @@ function getRequiredPackPaths(rendererAssets) { ]; } -function assertPackedFiles(packMetadata, requiredPaths, label) { - assert( - Array.isArray(packMetadata.files), - `${label} pack metadata must include files`, - ); +function assertPackedFiles(packPaths, requiredPaths, label) { + assert(Array.isArray(packPaths), `${label} pack metadata must include files`); const packedPaths = new Set( - packMetadata.files.map((entry) => { - assert( - typeof entry === 'object' && entry !== null, - `${label} pack entry must be an object`, - ); - const path = entry.path; + packPaths.map((path) => { assert.equal(typeof path, 'string', 'pack entry path must be a string'); return path; }), @@ -422,54 +407,34 @@ try { ); const requiredPaths = getRequiredPackPaths(rendererAssets); - if (!skipBuild) { - logStep('Building package contents for tarball smoke...'); - runNpm(['run', 'build']); - } - - logStep('Packing private tarball from built workspace...'); const tarballDirectory = join(tempRoot, 'tarball'); const tarballInstallPrefix = join(tempRoot, 'tarball-prefix'); - await mkdir(tarballDirectory, { recursive: true }); - const packResult = runNpm([ - 'pack', - '--json', - '--ignore-scripts', - '--pack-destination', - tarballDirectory, - ]); - const packMetadata = parseJsonOutput(packResult.stdout, 'npm pack output'); - assert(Array.isArray(packMetadata), 'npm pack output must be an array'); + const packMetadata = await packRelease({ + build: !skipBuild, + packDestination: tarballDirectory, + env: getDefaultEnv(), + log: logStep, + }); assert.equal( - packMetadata.length, - 1, - 'npm pack output must contain one entry', + packMetadata.packageName, + packageName, + 'packed package name mismatch', ); - const [packEntry] = packMetadata; - assert( - typeof packEntry === 'object' && packEntry !== null, - 'npm pack entry must be an object', - ); - assert.equal(packEntry.name, packageName, 'packed package name mismatch'); assert.equal( - packEntry.version, + packMetadata.packageVersion, packageVersion, 'packed package version mismatch', ); - assertPackedFiles(packEntry, requiredPaths, 'tarball'); - - const tarballFilename = packEntry.filename; - assert.equal( - typeof tarballFilename, - 'string', - 'packed tarball filename missing', - ); - const tarballPath = join(tarballDirectory, tarballFilename); - const tarballStats = await stat(tarballPath); - assert(tarballStats.size > 0, 'packed tarball must not be empty'); + assertPackedFiles(packMetadata.packedPaths, requiredPaths, 'tarball'); logStep('Installing built tarball into isolated prefix...'); - runNpm(['install', '-g', '--prefix', tarballInstallPrefix, tarballPath]); + runNpm([ + 'install', + '-g', + '--prefix', + tarballInstallPrefix, + packMetadata.tarballPath, + ]); await verifyInstalledCli('Tarball', tarballInstallPrefix); logStep( diff --git a/skills/agent-terminal/SKILL.md b/skills/agent-terminal/SKILL.md index 7620b38..1c514eb 100644 --- a/skills/agent-terminal/SKILL.md +++ b/skills/agent-terminal/SKILL.md @@ -6,7 +6,7 @@ advertise: true # Terminal Automation with agent-terminal -Install the CLI with `npm install -g agent-terminal`, then use `agent-terminal` directly. +If `agent-terminal` is not already available in the environment, fetch the current `README.md` from `coder/agent-terminal` on GitHub and follow its installation instructions before continuing. Otherwise use `agent-terminal` directly. Examples use `jq` for JSON parsing; any JSON-processing tool works. Prefer isolated homes, JSON envelopes, and renderer-backed artifacts so terminal workflows stay reviewable and reproducible.