Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 46 additions & 24 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ on:
- analyze
- mcp
- cli
- relayburn
version:
description: 'Version bump type (ignored if custom_version is set)'
required: true
Expand Down Expand Up @@ -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"
;;
*)
Expand All @@ -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);
Expand All @@ -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 '
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 <name-without-@>-<ver>.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
Expand All @@ -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

Expand All @@ -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 }}\`"
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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).
Expand Down Expand Up @@ -666,22 +688,22 @@ 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)
if: steps.check.outputs.published == 'true' && steps.check.outputs.prerelease != 'true' && steps.notes.outputs.has_notes != 'true'
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)
if: steps.check.outputs.published == 'true' && steps.check.outputs.prerelease == 'true'
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
5 changes: 3 additions & 2 deletions .github/workflows/verify-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ on:
type: choice
options:
- '@relayburn/cli'
- 'relayburn'
- '@relayburn/reader'
- '@relayburn/ledger'
- '@relayburn/analyze'
Expand Down Expand Up @@ -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 }}"
Expand All @@ -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)
Expand Down
11 changes: 6 additions & 5 deletions AGENTS.md
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@ 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)
@relayburn/ledger — append-only JSONL ledger + content sidecar at ~/.relayburn/
@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

Expand All @@ -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.

Expand All @@ -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
Expand All @@ -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 <pkg>-v<last>..HEAD -- packages/<pkg>`.
4. Publishes via `pnpm pack` + `npm publish` using OIDC trusted-publisher auth (no `NPM_TOKEN`).
5. Tags `<pkg>-v<version>` and creates a GitHub Release with the changelog body.
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "relayburn",
"name": "relayburn-monorepo",
"private": true,
"version": "0.0.1",
"description": "Token usage & cost attribution for agent CLIs",
Expand Down
12 changes: 12 additions & 0 deletions packages/relayburn/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
17 changes: 17 additions & 0 deletions packages/relayburn/README.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 2 additions & 0 deletions packages/relayburn/bin/burn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/usr/bin/env node
import '@relayburn/cli/dist/cli.js';
32 changes: 32 additions & 0 deletions packages/relayburn/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "relayburn",
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
"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"
}
}
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.