Skip to content

Commit cc85b7f

Browse files
fricobenclaude
andauthored
feat(cli): hybrid publishing with GitHub releases for binaries (#21)
- Remove @starknetid/vibetracking-core npm dependency - Download native binaries from GitHub releases on first CLI run - Cache binaries in ~/.vibetracking/bin/{version}/ - Simplify publishing to 1 npm package instead of 9 - Update release workflow to upload binaries to GitHub releases - Update CLAUDE.md and publish-cli skill documentation This avoids npm OIDC/auth issues by hosting binaries on GitHub. Users still run `bunx vibetracking` - binary downloads automatically. Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 1085b0a commit cc85b7f

8 files changed

Lines changed: 302 additions & 139 deletions

File tree

Lines changed: 74 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,53 @@
11
---
22
name: publish-cli
33
description: >
4-
CLI publishing workflow for npm releases via GitHub Actions.
4+
CLI publishing workflow using GitHub releases for binaries and local npm publish.
55
Trigger terms: publish, release, npm, cli, version, tag, github actions,
6-
vibetracking-core, package, deploy cli.
6+
vibetracking, package, deploy cli.
77
---
88

99
## When to Use
1010

1111
- Publishing a new version of the CLI to npm
12-
- Checking which packages are published
13-
- Understanding the release workflow
14-
15-
## Published Packages
16-
17-
| Package | Description |
18-
|---------|-------------|
19-
| `vibetracking` | Main CLI package |
20-
| `@starknetid/vibetracking-core` | Native Rust core (auto-selects platform) |
21-
| `@starknetid/vibetracking-core-darwin-x64` | macOS Intel |
22-
| `@starknetid/vibetracking-core-darwin-arm64` | macOS Apple Silicon |
23-
| `@starknetid/vibetracking-core-darwin-universal` | macOS Universal |
24-
| `@starknetid/vibetracking-core-linux-x64-gnu` | Linux x64 (glibc) |
25-
| `@starknetid/vibetracking-core-linux-arm64-gnu` | Linux ARM64 (glibc) |
26-
| `@starknetid/vibetracking-core-win32-x64-msvc` | Windows x64 |
27-
| `@starknetid/vibetracking-core-win32-arm64-msvc` | Windows ARM64 |
12+
- Checking the release workflow
13+
- Understanding the hybrid publishing model
14+
15+
## Architecture: Hybrid Publishing
16+
17+
The CLI uses a hybrid publishing approach:
18+
19+
| Component | Host | Description |
20+
|-----------|------|-------------|
21+
| `vibetracking` | npm | CLI JavaScript wrapper (you publish locally) |
22+
| Native binaries | GitHub Releases | `.node` files for each platform |
23+
24+
**Why?** This simplifies publishing to 1 npm package instead of 9, avoiding npm OIDC/auth issues.
25+
26+
**How it works**: On first run, the CLI downloads the correct native binary from GitHub releases to `~/.vibetracking/bin/{version}/`.
2827

2928
## Procedure: Publish a New Version
3029

3130
### Step 1: Update Version Numbers
3231

33-
Update versions in **both** package.json files:
34-
- `packages/cli/package.json`
35-
- `packages/core/package.json`
32+
Update version in `packages/cli/package.json`:
33+
```json
34+
{
35+
"version": "X.Y.Z"
36+
}
37+
```
38+
39+
Update `BINARY_VERSION` constant in:
40+
- `packages/cli/src/native.ts`
41+
- `packages/cli/src/native-runner.ts`
3642

37-
Also update `optionalDependencies` versions in core's package.json.
43+
```typescript
44+
const BINARY_VERSION = "X.Y.Z";
45+
```
3846

3947
### Step 2: Commit and Push
4048

4149
```bash
42-
git add packages/cli/package.json packages/core/package.json
50+
git add packages/cli/
4351
git commit -m "chore: bump CLI version to X.Y.Z"
4452
git push origin main
4553
```
@@ -51,57 +59,66 @@ git tag cli-vX.Y.Z
5159
git push origin cli-vX.Y.Z
5260
```
5361

54-
### Step 4: Verify Release
62+
### Step 4: Wait for CI
5563

56-
GitHub Actions will automatically:
64+
GitHub Actions will:
5765
1. Build native binaries for all 6 platforms
5866
2. Create universal macOS binary
59-
3. Publish `@starknetid/vibetracking-core` and all platform packages
60-
4. Publish `vibetracking` CLI
67+
3. Upload all binaries to GitHub Release
6168

6269
Monitor progress at: Actions > Release CLI
6370

71+
### Step 5: Publish CLI to npm (Locally)
72+
73+
Once CI completes, publish from your local machine:
74+
75+
```bash
76+
cd packages/cli
77+
pnpm build
78+
npm publish --access public
79+
```
80+
81+
### Step 6: Verify
82+
83+
```bash
84+
# Check npm
85+
npm view vibetracking versions
86+
87+
# Test fresh install (should download binary)
88+
rm -rf ~/.vibetracking/bin
89+
bunx vibetracking@X.Y.Z --version
90+
```
91+
6492
## Workflow Configuration
6593

6694
- **File**: `.github/workflows/release.yml`
6795
- **Trigger**: Push tags matching `cli-v*`
68-
- **Authentication**: OIDC Trusted Publishing (no tokens required)
69-
70-
### Setting Up Trusted Publishers
71-
72-
Each package must be configured on npmjs.com to trust this workflow:
73-
74-
1. Go to `https://www.npmjs.com/package/<package-name>/access`
75-
2. Scroll to **"Trusted Publisher"** section
76-
3. Click **"GitHub Actions"**
77-
4. Configure:
78-
- **Organization/User**: `lfglabs-dev`
79-
- **Repository**: `louisville`
80-
- **Workflow filename**: `release.yml`
81-
- **Environment**: *(leave blank)*
82-
83-
Packages requiring trusted publisher setup:
84-
- `vibetracking`
85-
- `@starknetid/vibetracking-core`
86-
- `@starknetid/vibetracking-core-darwin-x64`
87-
- `@starknetid/vibetracking-core-darwin-arm64`
88-
- `@starknetid/vibetracking-core-darwin-universal`
89-
- `@starknetid/vibetracking-core-linux-x64-gnu`
90-
- `@starknetid/vibetracking-core-linux-arm64-gnu`
91-
- `@starknetid/vibetracking-core-win32-x64-msvc`
92-
- `@starknetid/vibetracking-core-win32-arm64-msvc`
93-
94-
## Manual Trigger (Dry Run)
95-
96-
Test the workflow without publishing:
96+
- **Output**: Native binaries uploaded to GitHub Release
97+
98+
### Manual Trigger (Dry Run)
99+
100+
Test the build without uploading:
97101
1. Go to Actions > Release CLI
98102
2. Click "Run workflow"
99103
3. Check "Dry run" option
100104

105+
## Native Binary Locations
106+
107+
Binaries are downloaded to `~/.vibetracking/bin/{version}/`:
108+
109+
| Platform | Binary Name |
110+
|----------|-------------|
111+
| macOS Intel | `vibetracking-core.darwin-x64.node` |
112+
| macOS ARM | `vibetracking-core.darwin-arm64.node` |
113+
| Linux x64 | `vibetracking-core.linux-x64-gnu.node` |
114+
| Linux ARM64 | `vibetracking-core.linux-arm64-gnu.node` |
115+
| Windows x64 | `vibetracking-core.win32-x64-msvc.node` |
116+
| Windows ARM64 | `vibetracking-core.win32-arm64-msvc.node` |
117+
101118
## User Installation
102119

103120
After publishing, users can install with:
104121
```bash
105-
bunx vibetracking # One-off execution
122+
bunx vibetracking # One-off execution (downloads binary on first run)
106123
bun add -g vibetracking # Global install
107124
```

.github/workflows/release.yml

Lines changed: 41 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
workflow_dispatch:
88
inputs:
99
dry_run:
10-
description: 'Dry run (skip npm publish)'
10+
description: 'Dry run (skip GitHub release upload)'
1111
required: false
1212
default: false
1313
type: boolean
@@ -27,7 +27,6 @@ env:
2727

2828
permissions:
2929
contents: write
30-
id-token: write
3130
actions: read
3231

3332
jobs:
@@ -134,8 +133,8 @@ jobs:
134133
path: packages/core/*.node
135134
if-no-files-found: error
136135

137-
publish:
138-
name: Publish to npm
136+
upload-release:
137+
name: Upload to GitHub Release
139138
runs-on: ubuntu-latest
140139
needs:
141140
- build
@@ -145,77 +144,60 @@ jobs:
145144
steps:
146145
- uses: actions/checkout@v4
147146

148-
- name: Setup Bun
149-
uses: oven-sh/setup-bun@v2
150-
with:
151-
bun-version: latest
152-
153-
- name: Setup Node.js
154-
uses: actions/setup-node@v4
155-
with:
156-
node-version: '22'
157-
registry-url: 'https://registry.npmjs.org'
158-
159-
- name: Install dependencies
160-
run: bun install
161-
162147
- name: Download artifacts from current run
163148
if: ${{ github.event.inputs.skip_build != 'true' }}
164149
uses: actions/download-artifact@v4
165150
with:
166-
path: packages/core/artifacts
151+
path: artifacts
152+
pattern: bindings-*
153+
merge-multiple: true
167154

168155
- name: Download artifacts from previous run
169156
if: ${{ github.event.inputs.skip_build == 'true' }}
170157
uses: actions/download-artifact@v4
171158
with:
172-
path: packages/core/artifacts
159+
path: artifacts
160+
pattern: bindings-*
161+
merge-multiple: true
173162
github-token: ${{ secrets.GITHUB_TOKEN }}
174163
run-id: ${{ github.event.inputs.run_id }}
175164

176-
- name: Move artifacts
177-
run: bun x @napi-rs/cli artifacts
178-
working-directory: packages/core
179-
180-
- name: List packages
181-
run: ls -la npm/ || echo "No npm directory"
182-
working-directory: packages/core
183-
184-
- name: Update npm (required for OIDC)
185-
run: npm install -g npm@latest
165+
- name: List artifacts
166+
run: ls -la artifacts/
186167

187-
- name: Publish platform packages
188-
if: ${{ github.event.inputs.dry_run != 'true' }}
168+
- name: Get tag name
169+
id: tag
189170
run: |
190-
for dir in npm/*/; do
191-
if [ -d "$dir" ]; then
192-
echo "Publishing $dir"
193-
(cd "$dir" && npm publish --access public)
194-
fi
195-
done
196-
working-directory: packages/core
197-
198-
- name: Publish @starknetid/vibetracking-core
171+
if [[ "${{ github.ref_type }}" == "tag" ]]; then
172+
echo "name=${{ github.ref_name }}" >> $GITHUB_OUTPUT
173+
else
174+
# For manual runs, use the version from package.json
175+
VERSION=$(jq -r .version packages/cli/package.json)
176+
echo "name=cli-v${VERSION}" >> $GITHUB_OUTPUT
177+
fi
178+
179+
- name: Create or update GitHub Release
199180
if: ${{ github.event.inputs.dry_run != 'true' }}
200-
run: npm publish --access public
201-
working-directory: packages/core
202-
203-
- name: Build CLI
204-
run: bun run build
205-
working-directory: packages/cli
206-
207-
- name: Publish vibetracking CLI
208-
if: ${{ github.event.inputs.dry_run != 'true' }}
209-
run: npm publish --access public
210-
working-directory: packages/cli
181+
uses: softprops/action-gh-release@v2
182+
with:
183+
tag_name: ${{ steps.tag.outputs.name }}
184+
name: CLI ${{ steps.tag.outputs.name }}
185+
files: artifacts/*.node
186+
fail_on_unmatched_files: true
187+
generate_release_notes: true
188+
env:
189+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
211190

212191
- name: Summary
213192
run: |
214-
echo "## Published Packages" >> $GITHUB_STEP_SUMMARY
215-
echo "- @starknetid/vibetracking-core" >> $GITHUB_STEP_SUMMARY
216-
echo "- vibetracking" >> $GITHUB_STEP_SUMMARY
193+
echo "## Release ${{ steps.tag.outputs.name }}" >> $GITHUB_STEP_SUMMARY
194+
echo "" >> $GITHUB_STEP_SUMMARY
195+
echo "### Binaries uploaded to GitHub Release" >> $GITHUB_STEP_SUMMARY
196+
echo "" >> $GITHUB_STEP_SUMMARY
197+
ls -1 artifacts/*.node | while read f; do
198+
echo "- $(basename $f)" >> $GITHUB_STEP_SUMMARY
199+
done
217200
echo "" >> $GITHUB_STEP_SUMMARY
218-
echo "### Install" >> $GITHUB_STEP_SUMMARY
219-
echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY
220-
echo "bunx vibetracking" >> $GITHUB_STEP_SUMMARY
221-
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
201+
echo "### Next Steps" >> $GITHUB_STEP_SUMMARY
202+
echo "1. Publish CLI to npm locally: \`cd packages/cli && npm publish --access public\`" >> $GITHUB_STEP_SUMMARY
203+
echo "2. Verify: \`bunx vibetracking@latest --version\`" >> $GITHUB_STEP_SUMMARY

CLAUDE.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,33 @@ Cache TTL: 24 hours
575575

576576
---
577577

578-
## CLI Publishing (npm)
578+
## CLI Publishing
579579

580-
See the `publish-cli` skill for the full release workflow. Trigger terms: publish, release, npm, cli, version, tag.
580+
### Hybrid Publishing Model
581+
582+
The CLI uses a hybrid publishing approach:
583+
584+
- **npm**: Only the `vibetracking` CLI package is published (JavaScript wrapper)
585+
- **GitHub Releases**: Native binaries (`.node` files) are hosted as release assets
586+
- **First-run download**: CLI automatically downloads the correct binary on first run
587+
588+
This simplifies publishing to just 1 npm package instead of 9 (CLI + core + 7 platform packages).
589+
590+
### How It Works
591+
592+
1. User runs `bunx vibetracking`
593+
2. CLI checks for binary in `~/.vibetracking/bin/{version}/`
594+
3. If not found, downloads from GitHub release
595+
4. Binary is cached for future runs
596+
597+
### Publishing Workflow
598+
599+
1. **Bump version** in `packages/cli/package.json`
600+
2. **Update BINARY_VERSION** in `packages/cli/src/native.ts` and `native-runner.ts`
601+
3. **Commit and push** the version change
602+
4. **Create and push tag**: `git tag cli-vX.Y.Z && git push origin cli-vX.Y.Z`
603+
5. **Wait for CI** to build and upload binaries to GitHub release
604+
6. **Publish CLI locally**: `cd packages/cli && pnpm build && npm publish --access public`
605+
7. **Verify**: `bunx vibetracking@latest --version`
606+
607+
See the `publish-cli` skill for detailed steps. Trigger terms: publish, release, npm, cli, version, tag.

packages/cli/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
"prepublishOnly": "bun run build"
3333
},
3434
"dependencies": {
35-
"@starknetid/vibetracking-core": "workspace:*",
3635
"clipboardy": "^5.0.2",
3736
"commander": "^14.0.2",
3837
"csv-parse": "^5.6.0",

packages/cli/src/cli.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
isCursorInstalled,
2626
} from "./cursor.js";
2727
import {
28+
initNativeModule,
2829
parseLocalSourcesAsync,
2930
finalizeGraphAsync,
3031
type ParsedMessages,
@@ -207,6 +208,14 @@ async function openBrowserWithData(inviterUsername?: string) {
207208
await promptForCursorLogin();
208209
}
209210

211+
// Initialize native module (downloads binary on first run)
212+
try {
213+
await initNativeModule();
214+
} catch (e) {
215+
console.error(pc.red(`\n Failed to initialize: ${(e as Error).message}\n`));
216+
process.exit(1);
217+
}
218+
210219
const spinner = createSpinner({ color: "cyan" });
211220
spinner.start(pc.gray("Scanning AI coding tool data..."));
212221

0 commit comments

Comments
 (0)