Skip to content
Draft
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
44 changes: 44 additions & 0 deletions .github/workflows/ccusage-compatibility.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: ccusage compatibility

on:
schedule:
- cron: "17 15 * * 1"
workflow_dispatch:

permissions:
contents: read

jobs:
latest:
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- uses: actions/checkout@v7

- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.3

- uses: actions/setup-node@v7
with:
node-version: 20

- name: Install frozen Straude dependencies
run: bun install --frozen-lockfile

- name: Install ccusage latest in isolation
id: latest
shell: bash
run: |
set -euo pipefail
canary_dir="$(mktemp -d)"
npm install --prefix "$canary_dir" --no-audit --no-fund ccusage@latest
echo "package_dir=$canary_dir/node_modules/ccusage" >> "$GITHUB_OUTPUT"
echo "node_executable=$(node -p 'process.execPath')" >> "$GITHUB_OUTPUT"

- name: Run latest through the production parser
run: bun packages/cli/scripts/check-ccusage-compatibility.ts --package-dir "${{ steps.latest.outputs.package_dir }}"
env:
STRAUDE_CCUSAGE_CANARY_MAX_MS: "60000"
STRAUDE_CCUSAGE_NODE_EXECUTABLE: ${{ steps.latest.outputs.node_executable }}
96 changes: 83 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ on:
branches: [main]
pull_request:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
Expand All @@ -14,21 +17,21 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7

- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
bun-version: 1.3.3

- uses: actions/cache@v4
- uses: actions/cache@v6
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-

- name: Install dependencies
run: bun install
run: bun install --frozen-lockfile
env:
BUN_INSTALL_CACHE_DIR: ~/.bun/install/cache

Expand All @@ -53,11 +56,11 @@ jobs:
echo "No phantom dependencies."
working-directory: apps/web

- name: Typecheck
run: bun run typecheck
- name: Typecheck web and shared
run: bun run typecheck --filter=@straude/web

- name: Build
run: bun run build
- name: Build web and shared
run: bun run build --filter=@straude/web
env:
NEXT_PUBLIC_SUPABASE_URL: http://localhost:54321
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY: sb_publishable_placeholder
Expand All @@ -71,12 +74,8 @@ jobs:
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY: sb_publishable_placeholder
SUPABASE_SECRET_KEY: sb_secret_placeholder

- name: Test (cli)
run: bun run test
working-directory: packages/cli

- name: Setup Supabase CLI
uses: supabase/setup-cli@v1
uses: supabase/setup-cli@v3
with:
version: latest

Expand All @@ -99,3 +98,74 @@ jobs:
NEXT_PUBLIC_SUPABASE_URL: http://localhost:54321
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY: sb_publishable_placeholder
SUPABASE_SECRET_KEY: sb_secret_placeholder

cli-package:
name: CLI package
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v7

- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.3

- uses: actions/setup-node@v7
with:
node-version: 20

- name: Install dependencies
run: bun install --frozen-lockfile

- name: Typecheck CLI
run: bun run typecheck
working-directory: packages/cli

- name: Test CLI
run: bun run test
working-directory: packages/cli

- name: Pack release candidate
run: |
mkdir -p ../../artifacts
npm pack --json --pack-destination ../../artifacts
working-directory: packages/cli

- name: Upload exact package
uses: actions/upload-artifact@v7
with:
name: straude-cli-package
path: artifacts/*.tgz
if-no-files-found: error

cli-package-matrix:
name: CLI package (${{ matrix.os }}, Node ${{ matrix.node }})
needs: cli-package
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
node: 20
- os: ubuntu-latest
node: 22
- os: macos-latest
node: 22
- os: windows-latest
node: 22

steps:
- uses: actions/checkout@v7

- uses: actions/setup-node@v7
with:
node-version: ${{ matrix.node }}

- uses: actions/download-artifact@v8
with:
name: straude-cli-package
path: artifacts

- name: Test installed package
run: node packages/cli/scripts/packaged-cli-e2e.mjs --tarball artifacts
3 changes: 1 addition & 2 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
actions: read # Required for Claude to read CI results on PRs
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v7
with:
fetch-depth: 1

Expand All @@ -47,4 +47,3 @@ jobs:
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://code.claude.com/docs/en/cli-reference for available options
# claude_args: '--allowed-tools Bash(gh pr *)'

141 changes: 141 additions & 0 deletions .github/workflows/release-cli.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
name: Release CLI

on:
push:
tags:
- "straude@*"

permissions:
contents: read

jobs:
package:
name: Build release candidate
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v7

- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.3

- uses: actions/setup-node@v7
with:
node-version: 20
registry-url: https://registry.npmjs.org

- name: Verify tag matches package version
run: |
node -e "const pkg = require('./packages/cli/package.json'); const expected = 'straude@' + pkg.version; if (process.env.GITHUB_REF_NAME !== expected) { throw new Error('Expected tag ' + expected + ', got ' + process.env.GITHUB_REF_NAME); }"

- name: Install dependencies
run: bun install --frozen-lockfile

- name: Typecheck CLI
run: bun run typecheck
working-directory: packages/cli

- name: Test CLI
run: bun run test
working-directory: packages/cli

- name: Pack once
run: |
mkdir -p ../../artifacts
npm pack --json --pack-destination ../../artifacts
cd ../../artifacts
sha256sum ./*.tgz > SHA256SUMS
working-directory: packages/cli

- name: Upload exact package
uses: actions/upload-artifact@v7
with:
name: straude-cli-package
path: |
artifacts/*.tgz
artifacts/SHA256SUMS
if-no-files-found: error

- name: Upload CI source map
uses: actions/upload-artifact@v7
with:
name: straude-cli-sourcemap-${{ github.sha }}
path: packages/cli/dist/index.js.map
if-no-files-found: error
retention-days: 30

package-matrix:
name: Verify (${{ matrix.os }}, Node ${{ matrix.node }})
needs: package
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: [20, 22]

steps:
- uses: actions/checkout@v7

- uses: actions/setup-node@v7
with:
node-version: ${{ matrix.node }}

- uses: actions/download-artifact@v8
with:
name: straude-cli-package
path: artifacts

- name: Test installed package
run: node packages/cli/scripts/packaged-cli-e2e.mjs --tarball artifacts

publish:
name: Publish exact package
needs: package-matrix
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write

steps:
- uses: actions/checkout@v7

- uses: actions/setup-node@v7
with:
node-version: 22
registry-url: https://registry.npmjs.org

- uses: actions/download-artifact@v8
with:
name: straude-cli-package
path: artifacts

- name: Publish to npm with provenance
run: |
VERSION=$(node -p "require('./packages/cli/package.json').version")
LOCAL_INTEGRITY=$(node -e "const crypto = require('node:crypto'); const fs = require('node:fs'); const file = fs.readdirSync('artifacts').find((name) => name.endsWith('.tgz')); if (!file) throw new Error('Missing package artifact'); process.stdout.write('sha512-' + crypto.createHash('sha512').update(fs.readFileSync('artifacts/' + file)).digest('base64'));")
PUBLISHED_INTEGRITY=$(npm view "straude@${VERSION}" dist.integrity --json 2>/dev/null | tr -d '"' || true)
if [ -n "$PUBLISHED_INTEGRITY" ]; then
if [ "$PUBLISHED_INTEGRITY" != "$LOCAL_INTEGRITY" ]; then
echo "::error::straude@${VERSION} already exists with different integrity"
exit 1
fi
echo "straude@${VERSION} is already published with matching integrity"
else
npm publish artifacts/*.tgz --access public --provenance
fi

- name: Create matching GitHub release
run: |
VERSION=$(node -p "require('./packages/cli/package.json').version")
if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then
gh release upload "$GITHUB_REF_NAME" artifacts/*.tgz artifacts/SHA256SUMS --clobber
else
gh release create "$GITHUB_REF_NAME" artifacts/*.tgz artifacts/SHA256SUMS \
--verify-tag \
--generate-notes \
--title "Straude CLI v${VERSION}"
fi
env:
GH_TOKEN: ${{ github.token }}
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ npx straude@latest

The CLI reads your local [ccusage](https://github.com/ccusage/ccusage) data (cost, tokens, models, sessions), uploads it to Straude, and auto-creates a post on your feed. That includes every source ccusage detects, currently Claude Code, Codex, OpenCode, Amp, Droid, Codebuff, Hermes Agent, pi-agent, Goose, OpenClaw, Kilo, Kimi, Qwen, GitHub Copilot CLI, and Gemini CLI. First run opens a browser login; after that, just run `npx straude@latest` daily. It automatically pushes new stats since your last sync.

Options: `--date YYYY-MM-DD` to push a specific date, `--days N` to backfill the last N days (max 7), `--dry-run` to preview without posting. Run `npx straude@latest status` to check your streak and rank.
The first sync reads three days. Normal smart syncs resume after the last committed date and process up to seven contiguous days per run, so repeated runs catch up without skipping dates. `--days N` can explicitly backfill up to 30 days. Use `--date YYYY-MM-DD` for one day or `--dry-run` to preview without posting. Run `npx straude@latest status` to check your streak and rank.

## Features

Expand Down Expand Up @@ -91,7 +91,7 @@ The CLI runs [ccusage](https://github.com/ccusage/ccusage) locally on your machi

### Can Straude see my code or prompts?

No. The data pipeline is: local JSONL logs → ccusage (local aggregation) → daily totals sent to Straude. At no point does any conversation content, prompt text, code, or file path leave your machine. You can verify this yourself — the CLI is open source, and you can run `npx straude --dry-run` to see exactly what would be sent before it's sent.
No. The data pipeline is: local JSONL logs → ccusage (local aggregation) → daily totals sent to Straude. Conversation content, prompt text, and code stay on your machine. Aggregate operational telemetry is documented separately in the [CLI reference](docs/CLI.md#telemetry), and you can run `npx straude --dry-run` to inspect the usage payload before it is submitted.

### Is my profile public by default?

Expand All @@ -105,15 +105,15 @@ Straude is an entry in [**Built with Opus 4.6: a Claude Code hackathon**](https:

### Prerequisites

- [Bun](https://bun.sh/) (v1.3+)
- [Bun](https://bun.sh/) 1.3.3
- [Supabase CLI](https://supabase.com/docs/guides/local-development/cli/getting-started) (v2.x)
- [Docker](https://docs.docker.com/get-docker/) (required by Supabase local)

### Setup

```bash
# 1. Install dependencies
bun install
bun install --frozen-lockfile

# 2. Start local Supabase (Postgres, Auth, Storage via Docker)
bun run local:up
Expand All @@ -138,6 +138,7 @@ The app will be available at `http://localhost:3000`.
| Document | Description |
|----------|-------------|
| [Changelog](docs/CHANGELOG.md) | Release history and what changed |
| [CLI operations](docs/CLI_OPERATIONS.md) | Protocol rollout, alerts, repair, rollback, and audit closure |
| [Decisions](docs/DECISIONS.md) | Architecture and design decisions with rationale |
| [Roadmap](docs/ROADMAP.md) | Planned features and future work |
| [Security](docs/SECURITY.md) | Security audit findings and status |
Expand Down
Loading