diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1f4df76..959407f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,6 +15,7 @@ on: - analyze - mcp - cli + - relayburn version: description: 'Version bump type (ignored if custom_version is set)' required: true @@ -100,10 +101,12 @@ jobs: run: | case "${{ github.event.inputs.package }}" in all) - # Must be in dependency order: reader → ledger → analyze → mcp → cli. - echo "packages=reader ledger analyze mcp cli" >> "$GITHUB_OUTPUT" + # Dependency order: reader → ledger → analyze → mcp → cli → relayburn. + # `relayburn` is a thin wrapper that depends on `@relayburn/cli`, + # so cli must publish first. + echo "packages=reader ledger analyze mcp cli relayburn" >> "$GITHUB_OUTPUT" ;; - reader|ledger|analyze|mcp|cli) + reader|ledger|analyze|mcp|cli|relayburn) echo "packages=${{ github.event.inputs.package }}" >> "$GITHUB_OUTPUT" ;; *) @@ -125,7 +128,8 @@ jobs: set -euo pipefail for pkg in ${{ steps.targets.outputs.packages }}; do LOCAL=$(node -p "require('./packages/$pkg/package.json').version") - REMOTE=$(npm view "@relayburn/$pkg" versions --json 2>/dev/null \ + NPM_NAME=$(node -p "require('./packages/$pkg/package.json').name") + REMOTE=$(npm view "$NPM_NAME" versions --json 2>/dev/null \ | node -e ' const raw = require("fs").readFileSync(0, "utf8").trim() || "[]"; const parsed = JSON.parse(raw); @@ -142,7 +146,7 @@ jobs: process.stdout.write(stable.length ? stable[stable.length - 1] : ""); ' || echo "") if [ -z "$REMOTE" ]; then - echo "@relayburn/$pkg: no stable releases on npm yet — OK" + echo "$NPM_NAME: no stable releases on npm yet — OK" continue fi CMP=$(node -e ' @@ -157,10 +161,10 @@ jobs: console.log(0); ' "$LOCAL" "$REMOTE") if [ "$CMP" = "-1" ]; then - echo "::error title=npm/git version drift::@relayburn/$pkg: package.json is at $LOCAL but npm has $REMOTE published. A previous publish run likely succeeded on npm but failed before tagging. Bump packages/$pkg/package.json to >= $REMOTE on a branch, push, then re-run this workflow. (You may also want to tag the prior release commit as $pkg-v$REMOTE so the changelog generator picks the right baseline.)" + echo "::error title=npm/git version drift::$NPM_NAME: package.json is at $LOCAL but npm has $REMOTE published. A previous publish run likely succeeded on npm but failed before tagging. Bump packages/$pkg/package.json to >= $REMOTE on a branch, push, then re-run this workflow. (You may also want to tag the prior release commit as $pkg-v$REMOTE so the changelog generator picks the right baseline.)" exit 1 fi - echo "@relayburn/$pkg: local=$LOCAL, npm=$REMOTE — OK" + echo "$NPM_NAME: local=$LOCAL, npm=$REMOTE — OK" done - name: Bump versions @@ -198,12 +202,13 @@ jobs: for entry in ${{ steps.bump.outputs.versions }}; do pkg="${entry%%:*}" ver="${entry##*:}" - EXISTS=$(npm view "@relayburn/$pkg@$ver" version 2>/dev/null || true) + NPM_NAME=$(node -p "require('./packages/$pkg/package.json').name") + EXISTS=$(npm view "$NPM_NAME@$ver" version 2>/dev/null || true) if [ -n "$EXISTS" ]; then - echo "::error title=Version already published::@relayburn/$pkg@$ver is already on npm. Pick a different bump type or set custom_version to a higher version." + echo "::error title=Version already published::$NPM_NAME@$ver is already on npm. Pick a different bump type or set custom_version to a higher version." exit 1 fi - echo "@relayburn/$pkg@$ver: unpublished — OK" + echo "$NPM_NAME@$ver: unpublished — OK" done # Per-package CHANGELOG.md generation. For each package being published: @@ -374,7 +379,8 @@ jobs: // --- Step 3: write the file with Unreleased reset + new block. --- if (!existing) { - const header = `# Changelog\n\nAll notable changes to \`@relayburn/${pkg}\` will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [Unreleased]\n\n`; + const npmName = JSON.parse(readFileSync(`packages/${pkg}/package.json`, 'utf-8')).name; + const header = `# Changelog\n\nAll notable changes to \`${npmName}\` will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [Unreleased]\n\n`; writeFileSync(path, header + newEntry + '\n'); } else if (parts) { // Reset Unreleased body to empty (single blank line after header) @@ -518,7 +524,8 @@ jobs: for entry in ${{ steps.bump.outputs.versions }}; do pkg="${entry%%:*}" version="${entry##*:}" - MSG+=" @relayburn/$pkg@$version" + NPM_NAME=$(node -p "require('./packages/$pkg/package.json').name") + MSG+=" $NPM_NAME@$version" done git commit -m "$MSG" fi @@ -552,11 +559,17 @@ jobs: fi for pkg in ${{ steps.targets.outputs.packages }}; do - echo "==> Packing @relayburn/$pkg" - pnpm --filter "@relayburn/$pkg" pack --pack-destination "$PACK_DIR" - TARBALL=$(ls -1t "$PACK_DIR"/relayburn-$pkg-*.tgz | head -n1) - if [ -z "$TARBALL" ] || [ ! -f "$TARBALL" ]; then - echo "::error::could not find packed tarball for $pkg in $PACK_DIR" >&2 + NPM_NAME=$(node -p "require('./packages/$pkg/package.json').name") + VERSION=$(node -p "require('./packages/$pkg/package.json').version") + # `pnpm pack` writes -.tgz with `/` → `-`. + # @relayburn/cli@0.33.0 → relayburn-cli-0.33.0.tgz + # relayburn@0.33.0 → relayburn-0.33.0.tgz + TARBALL_BASENAME="$(echo "${NPM_NAME#@}" | tr '/' '-')-${VERSION}.tgz" + echo "==> Packing $NPM_NAME" + pnpm --filter "$NPM_NAME" pack --pack-destination "$PACK_DIR" + TARBALL="$PACK_DIR/$TARBALL_BASENAME" + if [ ! -f "$TARBALL" ]; then + echo "::error::could not find packed tarball $TARBALL_BASENAME in $PACK_DIR" >&2 ls -la "$PACK_DIR" >&2 || true exit 1 fi @@ -572,7 +585,8 @@ jobs: for entry in ${{ steps.bump.outputs.versions }}; do pkg="${entry%%:*}" version="${entry##*:}" - git tag -a "$pkg-v$version" -m "@relayburn/$pkg@$version" + NPM_NAME=$(node -p "require('./packages/$pkg/package.json').name") + git tag -a "$pkg-v$version" -m "$NPM_NAME@$version" done git push origin HEAD --follow-tags @@ -584,7 +598,8 @@ jobs: for entry in ${{ steps.bump.outputs.versions }}; do pkg="${entry%%:*}" version="${entry##*:}" - echo "- \`@relayburn/$pkg@$version\`" + NPM_NAME=$(node -p "require('./packages/$pkg/package.json').name") + echo "- \`$NPM_NAME@$version\`" done echo "" echo "- **dist-tag**: \`${{ github.event.inputs.tag }}\`" @@ -598,7 +613,7 @@ jobs: # One GitHub Release per package tag. Runs in parallel across packages; # each cell is gated on whether its package was published. create-release: - name: Release @relayburn/${{ matrix.package }} + name: Release ${{ matrix.package }} needs: publish if: ${{ github.event.inputs.dry_run != 'true' && (github.event.inputs.version != 'none' || github.event.inputs.custom_version != '') }} runs-on: ubuntu-latest @@ -607,7 +622,7 @@ jobs: strategy: fail-fast: false matrix: - package: [reader, ledger, analyze, mcp, cli] + package: [reader, ledger, analyze, mcp, cli, relayburn] steps: - name: Check if this package was published id: check @@ -636,6 +651,13 @@ jobs: # Need the tag that the publish job just pushed. ref: ${{ matrix.package }}-v${{ steps.check.outputs.version }} + - name: Resolve npm name + if: steps.check.outputs.published == 'true' + id: name + run: | + NPM_NAME=$(node -p "require('./packages/${{ matrix.package }}/package.json').name") + echo "npm_name=$NPM_NAME" >> "$GITHUB_OUTPUT" + # Prefer the per-package CHANGELOG section that was generated and # committed during publish. Falls back to auto-generated notes for # prereleases and first publishes (where no CHANGELOG entry exists). @@ -666,7 +688,7 @@ jobs: uses: softprops/action-gh-release@v3 with: tag_name: ${{ matrix.package }}-v${{ steps.check.outputs.version }} - name: "@relayburn/${{ matrix.package }}@${{ steps.check.outputs.version }}" + name: "${{ steps.name.outputs.npm_name }}@${{ steps.check.outputs.version }}" body_path: /tmp/release-notes.md - name: Create GitHub Release (stable, auto notes) @@ -674,7 +696,7 @@ jobs: uses: softprops/action-gh-release@v3 with: tag_name: ${{ matrix.package }}-v${{ steps.check.outputs.version }} - name: "@relayburn/${{ matrix.package }}@${{ steps.check.outputs.version }}" + name: "${{ steps.name.outputs.npm_name }}@${{ steps.check.outputs.version }}" generate_release_notes: true - name: Create GitHub Release (prerelease) @@ -682,6 +704,6 @@ jobs: uses: softprops/action-gh-release@v3 with: tag_name: ${{ matrix.package }}-v${{ steps.check.outputs.version }} - name: "@relayburn/${{ matrix.package }}@${{ steps.check.outputs.version }} (prerelease)" + name: "${{ steps.name.outputs.npm_name }}@${{ steps.check.outputs.version }} (prerelease)" prerelease: true generate_release_notes: true diff --git a/.github/workflows/verify-publish.yml b/.github/workflows/verify-publish.yml index f5211cc..4cf275c 100644 --- a/.github/workflows/verify-publish.yml +++ b/.github/workflows/verify-publish.yml @@ -15,6 +15,7 @@ on: type: choice options: - '@relayburn/cli' + - 'relayburn' - '@relayburn/reader' - '@relayburn/ledger' - '@relayburn/analyze' @@ -55,7 +56,7 @@ jobs: echo "Verifying ${{ github.event.inputs.package }}@$RESOLVED" - name: CLI smoke test - if: ${{ github.event.inputs.package == '@relayburn/cli' }} + if: ${{ github.event.inputs.package == '@relayburn/cli' || github.event.inputs.package == 'relayburn' }} run: | set -euo pipefail npm install -g "${{ github.event.inputs.package }}@${{ steps.resolve.outputs.version }}" @@ -78,7 +79,7 @@ jobs: echo "CLI smoke test passed." - name: Library smoke test (ESM import + exports surface) - if: ${{ github.event.inputs.package != '@relayburn/cli' }} + if: ${{ github.event.inputs.package != '@relayburn/cli' && github.event.inputs.package != 'relayburn' }} run: | set -euo pipefail WORKDIR=$(mktemp -d) diff --git a/AGENTS.md b/AGENTS.md index 671d45a..467e7c5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,7 +5,7 @@ Pairs with [`README.md`](./README.md) — README is what burn does, this file is ## Layout -pnpm workspace, five published packages in dependency order: +pnpm workspace, six published packages in dependency order: ``` @relayburn/reader — pure parsers (Claude Code / Codex / OpenCode session logs → TurnRecord) @@ -13,9 +13,10 @@ pnpm workspace, five published packages in dependency order: @relayburn/analyze — pricing + per-record cost derivation + comparison aggregator @relayburn/mcp — stdio MCP server exposing read-only ledger queries for in-session self-query @relayburn/cli — `burn` binary (summary, by-tool, compare, claude/codex/opencode wrappers, mcp-server, …) +relayburn — thin install-wrapper so `npm i -g relayburn` exposes the same `burn` bin as `@relayburn/cli` ``` -`reader → ledger → analyze → mcp → cli`. Always build the whole workspace; never touch a single package's `tsconfig.tsbuildinfo` to "skip" a dep. +`reader → ledger → analyze → mcp → cli → relayburn`. Always build the whole workspace; never touch a single package's `tsconfig.tsbuildinfo` to "skip" a dep. ## Common commands @@ -35,7 +36,7 @@ Tests run from `dist/` so a stale build will lie. If a test fails unexpectedly, Curate `[Unreleased]` in the relevant per-package `packages/*/CHANGELOG.md` as you land PRs — write the entry the way you'd want it to read in a release note. At publish time, the workflow (`.github/workflows/publish.yml`) **promotes** your `[Unreleased]` block verbatim into `## [x.y.z] - DATE` and resets `[Unreleased]` to empty. No double-writing, no post-release hand-editing. -The root `CHANGELOG.md` is the cross-package narrative. Packages release in lockstep, so each release in the root file is a single `## [x.y.z] - YYYY-MM-DD` header that applies to all five packages — no `**Versions:** ...` lines, no per-bullet `[reader, cli]` tags. Update `[Unreleased]` only when the work spans packages or warrants a top-level summary; single-package work belongs only in that package's CHANGELOG. +The root `CHANGELOG.md` is the cross-package narrative. Packages release in lockstep, so each release in the root file is a single `## [x.y.z] - YYYY-MM-DD` header that applies to all six packages — no `**Versions:** ...` lines, no per-bullet `[reader, cli]` tags. Update `[Unreleased]` only when the work spans packages or warrants a top-level summary; single-package work belongs only in that package's CHANGELOG. The publish workflow promotes the root `[Unreleased]` block the same way it does per-package files: at release time it stamps `## [x.y.z] - DATE` (using `max` of the versions bumped in the run) and resets `[Unreleased]` to empty. **No git-log fallback for the root file** — an empty `[Unreleased]` at release time means "no narrative-worthy changes this release" and the file is left alone. So if you want the root to record a release, write the bullet under `[Unreleased]` *before* the publish run. @@ -58,7 +59,7 @@ Breaking changes: append `!` to a Conventional Commits prefix (e.g. `feat!:`) to ```bash # from GitHub Actions: workflow_dispatch → "Publish Package" -# package: all | reader | ledger | analyze | mcp | cli +# package: all | reader | ledger | analyze | mcp | cli | relayburn # version: patch | minor | major | prepatch | … | none (re-publish current) # custom_version: 0.3.1 (overrides version type) # tag: latest | next | beta | alpha @@ -67,7 +68,7 @@ Breaking changes: append `!` to a Conventional Commits prefix (e.g. `feat!:`) to The workflow: 1. Builds + tests the whole workspace. -2. Bumps `package.json` versions in dep order (reader → ledger → analyze → mcp → cli). +2. Bumps `package.json` versions in dep order (reader → ledger → analyze → mcp → cli → relayburn). 3. Generates changelog entries from `git log -v..HEAD -- packages/`. 4. Publishes via `pnpm pack` + `npm publish` using OIDC trusted-publisher auth (no `NPM_TOKEN`). 5. Tags `-v` and creates a GitHub Release with the changelog body. diff --git a/CHANGELOG.md b/CHANGELOG.md index f3559b9..c1cc761 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Cross-package narrative for the relayburn monorepo. The per-package CHANGELOGs at `packages/*/CHANGELOG.md` are authoritative for exactly what shipped in each package; this file is the unified view. -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and the workspace adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Packages are released in lockstep, so each version below applies to all five (`reader`, `ledger`, `analyze`, `mcp`, `cli`). +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and the workspace adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Packages are released in lockstep, so each version below applies to all six (`reader`, `ledger`, `analyze`, `mcp`, `cli`, `relayburn`). ## [Unreleased] diff --git a/package.json b/package.json index 3d0e1f9..0aab29d 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "relayburn", + "name": "relayburn-monorepo", "private": true, "version": "0.0.1", "description": "Token usage & cost attribution for agent CLIs", diff --git a/packages/relayburn/CHANGELOG.md b/packages/relayburn/CHANGELOG.md new file mode 100644 index 0000000..2b21985 --- /dev/null +++ b/packages/relayburn/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +All notable changes to `relayburn` will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- Initial release. Thin wrapper around [`@relayburn/cli`](https://www.npmjs.com/package/@relayburn/cli) — installing `relayburn` globally exposes the same `burn` command. Lets users `npm i -g relayburn` without needing to know the scoped package name. diff --git a/packages/relayburn/README.md b/packages/relayburn/README.md new file mode 100644 index 0000000..d6b7dca --- /dev/null +++ b/packages/relayburn/README.md @@ -0,0 +1,17 @@ +# relayburn + +Install the [`burn`](https://github.com/AgentWorkforce/burn) CLI globally: + +```sh +npm i -g relayburn +``` + +This is a thin wrapper that depends on [`@relayburn/cli`](https://www.npmjs.com/package/@relayburn/cli) and exposes the `burn` command. Both names produce the same binary; pick whichever you find easier to type. + +```sh +burn --help +burn summary --since 7d +burn limits --watch +``` + +See the project [README](https://github.com/AgentWorkforce/burn#readme) for full documentation. diff --git a/packages/relayburn/bin/burn.js b/packages/relayburn/bin/burn.js new file mode 100755 index 0000000..8d5d331 --- /dev/null +++ b/packages/relayburn/bin/burn.js @@ -0,0 +1,2 @@ +#!/usr/bin/env node +import '@relayburn/cli/dist/cli.js'; diff --git a/packages/relayburn/package.json b/packages/relayburn/package.json new file mode 100644 index 0000000..5345726 --- /dev/null +++ b/packages/relayburn/package.json @@ -0,0 +1,32 @@ +{ + "name": "relayburn", + "version": "0.34.0", + "description": "Token usage & cost attribution for agent CLIs (installs the `burn` command)", + "type": "module", + "bin": { + "burn": "./bin/burn.js" + }, + "files": [ + "bin", + "README.md", + "CHANGELOG.md", + "package.json" + ], + "scripts": { + "build": "chmod +x bin/burn.js" + }, + "engines": { + "node": ">=22" + }, + "dependencies": { + "@relayburn/cli": "workspace:*" + }, + "repository": { + "type": "git", + "url": "https://github.com/AgentWorkforce/burn", + "directory": "packages/relayburn" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc57ced..0bbdfe1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -59,6 +59,12 @@ importers: packages/reader: {} + packages/relayburn: + dependencies: + '@relayburn/cli': + specifier: workspace:* + version: link:../cli + packages: '@types/node@22.19.17':