Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
07c5f64
research: add webhid adapter poc
caryden Apr 12, 2026
c19ca6c
research: add electrobun desktop poc
caryden Apr 12, 2026
02d0046
refactor: extract shared core package
caryden Apr 12, 2026
f3c74c2
refactor: move repo into bun workspace layout
caryden Apr 12, 2026
2439a65
refactor: share probe ui and transports
caryden Apr 12, 2026
d31950f
feat: refresh shared probe ui shell
caryden Apr 12, 2026
d0dd2a0
feat: simplify the programmer ui shell
caryden Apr 12, 2026
e9c58c7
feat: polish desktop app chrome
caryden Apr 12, 2026
5f2f8cb
feat: reset programmer ui around setup tasks
caryden Apr 13, 2026
bba3f8e
feat: pivot ui toward simulation-first setup
caryden Apr 13, 2026
e509e68
fix: correct servo preview axis
caryden Apr 13, 2026
c7c4aea
feat: reshape sim pane around operating curves
caryden Apr 13, 2026
2e0bc99
refactor: tighten sim controls and status labels
caryden Apr 13, 2026
380d4f0
refactor: simplify live view states
caryden Apr 13, 2026
9739beb
feat: build servo programmer ui workflow
caryden Apr 14, 2026
ea30dd6
feat: wire web apply and flash flow
caryden Apr 14, 2026
6e6c1b7
refactor: consolidate sfw and flash runtime boundaries
caryden Apr 15, 2026
efbfb0f
feat: complete web and desktop recovery flows
caryden Apr 16, 2026
8b6e461
chore: add Axon web favicon
caryden Apr 17, 2026
1cd2565
feat: add GitHub Pages deploy for web app
caryden Apr 17, 2026
149cb9f
fix: make prompt helper portable across TS platforms
caryden Apr 17, 2026
3ce8e20
fix: make cli byte handling portable
caryden Apr 17, 2026
14f1b92
fix: normalize desktop byte conversions
caryden Apr 17, 2026
80b8f2d
fix: stabilize web CI setup
caryden Apr 17, 2026
0662ba6
test: isolate hid wrapper module mocking
caryden Apr 17, 2026
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
14 changes: 9 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,21 @@ jobs:
run: sudo apt-get update && sudo apt-get install -y libudev-dev libusb-1.0-0-dev

- name: Install dependencies
working-directory: axon
run: bun install --frozen-lockfile

- name: Install Playwright browsers (Linux)
if: runner.os == 'Linux'
run: bun x playwright install --with-deps chromium

- name: Install Playwright browsers (macOS/Windows)
if: runner.os != 'Linux'
run: bun x playwright install chromium

- name: Biome check
working-directory: axon
run: bun run check

- name: Typecheck
working-directory: axon
run: bun run typecheck

- name: Test
working-directory: axon
run: bun test
run: bun run test
54 changes: 54 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Pages

on:
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest

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

- name: Build Pages bundle
working-directory: apps/web
run: bun run build:pages

- name: Configure Pages
uses: actions/configure-pages@v5

- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: apps/web/.pages

deploy:
if: github.ref == 'refs/heads/main'
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
8 changes: 4 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ name: Release
# Bun's cross-target build because GitHub no longer guarantees a
# macOS 13 Intel runner on every plan.
#
# To cut a release, bump axon/package.json version, tag, and push:
# To cut a release, bump apps/cli/package.json version, tag, and push:
# git tag v1.0.0 && git push origin v1.0.0
#
# The workflow_dispatch trigger lets you manually re-run a build
Expand Down Expand Up @@ -95,15 +95,15 @@ jobs:
run: |
./scripts/build-release.sh \
--target ${{ matrix.bun_target }} \
--outfile axon/dist/${{ matrix.artifact }}${{ matrix.ext }}
--outfile apps/cli/dist/${{ matrix.artifact }}${{ matrix.ext }}

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact }}
path: |
axon/dist/${{ matrix.artifact }}${{ matrix.ext }}
axon/dist/${{ matrix.artifact }}${{ matrix.ext }}.sha256
apps/cli/dist/${{ matrix.artifact }}${{ matrix.ext }}
apps/cli/dist/${{ matrix.artifact }}${{ matrix.ext }}.sha256
if-no-files-found: error
retention-days: 30

Expand Down
9 changes: 7 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,14 @@ ENV/

# Node / Bun
node_modules/
.turbo/
*.log
axon/bin/
axon/dist/
apps/cli/bin/
apps/cli/dist/
apps/web/dist/
apps/web/.pages/
playwright-report/
test-results/

# Capture files (large binary data)
captures/*.sal
Expand Down
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,10 @@ Full command surface: [docs/CLI_DESIGN.md](docs/CLI_DESIGN.md).
curl -fsSL https://bun.sh/install | bash

# 2. Clone, install dependencies, run from source
git clone https://github.com/caryden/servo-programmer
cd servo-programmer/axon
git clone https://github.com/caryden/servo-programmer.git
cd servo-programmer
bun install
cd apps/cli
bun run src/cli.ts status
```

Expand All @@ -87,6 +88,7 @@ prints a compact status bar plus the model docs link. For scripts and
agents, use JSON:

```bash
cd apps/cli
bun run src/cli.ts --json status
```

Expand All @@ -106,12 +108,24 @@ No `sudo` required on any platform.

**Got an Axon Max, another Micro capture, or an unknown model?** Please
help fill out the catalog. Plug it in, run
`bun run src/cli.ts read --svo > my-model.svo`, and
`cd apps/cli && bun run src/cli.ts read --svo > my-model.svo`, and
[open an issue](https://github.com/caryden/servo-programmer/issues/new)
with the file attached and the model ID string from `axon status`.
I'll add the model/defaults to
[`data/servo_catalog.json`](data/servo_catalog.json).

## Repo layout

The repo is now organized as a Bun workspace:

- [`apps/cli/`](apps/cli/) — production `axon` CLI
- [`apps/web/`](apps/web/) — browser WebHID PoC
- [`apps/desktop/`](apps/desktop/) — Electrobun desktop PoC
- [`packages/core/`](packages/core/) — shared transport-agnostic protocol/catalog logic
- [`packages/ui/`](packages/ui/) — shared probe UI used by the browser and desktop PoCs
- [`packages/transport-nodehid/`](packages/transport-nodehid/) — Node/Bun HID transport
- [`packages/transport-webhid/`](packages/transport-webhid/) — browser WebHID transport

## Firmware files

This repo does not redistribute Axon `.sfw` firmware files. Download
Expand Down
3 changes: 2 additions & 1 deletion axon/package.json → apps/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"test": "bun test"
},
"dependencies": {
"node-hid": "^3.3.0"
"@axon/core": "workspace:*",
"@axon/transport-nodehid": "workspace:*"
},
"devDependencies": {
"@biomejs/biome": "^2.4.11",
Expand Down
1 change: 1 addition & 0 deletions apps/cli/src/catalog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "@axon/core/catalog";
20 changes: 13 additions & 7 deletions axon/src/cli.ts → apps/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export interface GlobalFlags {
json: boolean;
quiet: boolean;
yes: boolean;
debug: boolean;
}

export interface ParsedArgs {
Expand Down Expand Up @@ -60,7 +61,7 @@ function isNegativeNumberToken(arg: string): boolean {
}

export function parseArgs(argv: string[]): ParsedArgs {
const global: GlobalFlags = { json: false, quiet: false, yes: false };
const global: GlobalFlags = { json: false, quiet: false, yes: false, debug: false };
const flags: Record<string, string | boolean> = {};
const positional: string[] = [];
let command: string | null = null;
Expand Down Expand Up @@ -100,6 +101,7 @@ export function parseArgs(argv: string[]): ParsedArgs {
if (name === "json") global.json = true;
else if (name === "quiet") global.quiet = true;
else if (name === "yes") global.yes = true;
else if (name === "debug") global.debug = true;
else flags[name] = value;
} else if (arg.startsWith("-") && arg.length > 1) {
// short flags: -y, -h, etc
Expand Down Expand Up @@ -196,7 +198,8 @@ export async function main(argv: string[]): Promise<number> {
if (parsed.flags.svo) format = "svo";
else if (parsed.flags.hex) format = "hex";
else if (parsed.flags.json || parsed.global.json) format = "json";
return await runRead(parsed.global, { format });
const debug = parsed.flags.debug === true;
return await runRead(parsed.global, { format, debug });
}

case "write": {
Expand Down Expand Up @@ -338,11 +341,14 @@ function printCommandHelp(command: string): void {
process.stdout.write(
`axon read — read the current config from the servo\n\n` +
`FLAGS:\n` +
` (default) Human-readable summary (named parameters not yet shown)\n` +
` --json Machine-readable JSON with raw bytes\n` +
` (default) Human-readable summary of decoded parameters\n` +
` --json Machine-readable JSON with decoded parameters\n` +
` --svo Raw 95-byte dump to stdout (vendor .svo compatible)\n` +
` --hex Annotated hex dump (for debugging)\n\n` +
`Example:\n` +
` --hex Annotated hex dump (for debugging)\n` +
` --debug Include raw block metadata in normal / --json output\n\n` +
`Examples:\n` +
` axon read\n` +
` axon read --json\n` +
` axon read --svo > backup.svo\n`,
);
break;
Expand All @@ -361,7 +367,7 @@ function printCommandHelp(command: string): void {
);
break;
case "get":
printGetHelp({ json: false, quiet: false, yes: false });
printGetHelp({ json: false, quiet: false, yes: false, debug: false });
break;
case "set":
process.stdout.write(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from "../driver/protocol.ts";
import type { DongleHandle } from "../driver/transport.ts";
import { AxonError, ExitCode } from "../errors.ts";
import { cloneBuffer, toUint8Array } from "../util/bytes.ts";

type CheckStatus = "pass" | "warn" | "fail";

Expand Down Expand Up @@ -116,7 +117,7 @@ class TracingDongle implements DongleHandle {

async read(timeoutMs?: number): Promise<Buffer> {
const rx = await this.inner.read(timeoutMs);
this.reads.push(Buffer.from(rx));
this.reads.push(cloneBuffer(rx));
return rx;
}

Expand Down Expand Up @@ -407,7 +408,7 @@ export async function runDoctor(
}

debug = debugReport(local.debug, traced, identifyReply, config);
const modelId = modelIdFromConfig(config);
const modelId = modelIdFromConfig(toUint8Array(config));
const model = findModel(catalog, modelId);
const servo = {
present: true,
Expand Down
3 changes: 2 additions & 1 deletion axon/src/commands/get.ts → apps/cli/src/commands/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
type ParameterSpec,
type ParameterValue,
} from "../parameters.ts";
import { toUint8Array } from "../util/bytes.ts";
import { renderStatusBar } from "../util/tui.ts";

export interface GetFlags {
Expand Down Expand Up @@ -67,7 +68,7 @@ export async function runGetWithHandle(

// 2. read config → get model id
const config = await readFullConfig(handle);
const modelId = modelIdFromConfig(config);
const modelId = modelIdFromConfig(toUint8Array(config));
const catalog = loadCatalog();
const model = findModel(catalog, modelId);
if (!model) {
Expand Down
Loading
Loading