Single-file React/TSX bespoke web artifacts via the bwa CLI.
  • TypeScript 96.6%
  • JavaScript 3.4%
Find a file
dikkadev b713c9e523 refactor(bg): replace pid-based background registry with socket manager and run IDs
Replace file-backed background run tracking with a per-user socket manager that owns run IDs, lifecycle, wait/close/kill operations, and short-lived in-memory retention. This aligns the CLI, tests, build outputs, and docs around manager-controlled background execution instead of PID-based registry files.
2026-07-15 16:32:47 +02:00
.dev fix(run): preallocate a random port before starting the server 2026-07-15 14:31:24 +02:00
dist refactor(bg): replace pid-based background registry with socket manager and run IDs 2026-07-15 16:32:47 +02:00
skills/bespoke-web-artifact refactor(bg): replace pid-based background registry with socket manager and run IDs 2026-07-15 16:32:47 +02:00
src refactor(bg): replace pid-based background registry with socket manager and run IDs 2026-07-15 16:32:47 +02:00
tests refactor(bg): replace pid-based background registry with socket manager and run IDs 2026-07-15 16:32:47 +02:00
.gitignore fix(run): preallocate a random port before starting the server 2026-07-15 14:31:24 +02:00
AGENTS.md feat(package): rename CLI package to @lab/bwa 2026-06-02 14:27:02 +02:00
LICENSE feat(cli): bootstrap bwa single-file artifact runner 2026-05-30 09:25:19 +02:00
package.json feat(primitives): add built-in deck and feedback subpath exports 2026-06-07 17:08:58 +02:00
pnpm-lock.yaml feat(package): rename CLI package to @lab/bwa 2026-06-02 14:27:02 +02:00
pnpm-workspace.yaml feat(cli): bootstrap bwa single-file artifact runner 2026-05-30 09:25:19 +02:00
README.md refactor(bg): replace pid-based background registry with socket manager and run IDs 2026-07-15 16:32:47 +02:00
tsconfig.json feat(cli): bootstrap bwa single-file artifact runner 2026-05-30 09:25:19 +02:00
tsup.config.ts refactor(bg): replace pid-based background registry with socket manager and run IDs 2026-07-15 16:32:47 +02:00
vitest.config.ts feat(cli): bootstrap bwa single-file artifact runner 2026-05-30 09:25:19 +02:00

bespoke-web-artifact

@lab/bwa provides bwa, a CLI for writing one-file React/TSX web artifacts without creating a local Vite project beside the artifact.

The artifact stays as one meaningful .tsx file. bwa creates the temporary Vite runtime elsewhere, serves it on a random local port by default, and lets the artifact send a result back to stdout when useful.

CLI overview

bwa add artifact.tsx lucide-react
bwa check artifact.tsx
bwa run artifact.tsx
bwa run artifact.tsx --port 5173
bwa run artifact.tsx --json
bwa run artifact.tsx --bg
bwa bg list [--json]
bwa wait <RUN_ID> [--timeout 60]
bwa bg close <RUN_ID...> --all
bwa bg kill <RUN_ID...> --all
bwa completion bash

bwa build is present only as a reserved command. It intentionally fails until export/share/deploy semantics are designed.

Artifact format

A BWA artifact is a single .tsx file with an optional metadata block at the top:

// /// bwa
// title = "Artifact title"
// vibe = "specific visual and interaction direction"
// deps = ["lucide-react"]
// ///

The artifact should default-export a React component:

import { submit, close } from "@lab/bwa/runtime";

export default function Artifact() {
  return (
    <main>
      <button onClick={() => submit({ choice: "yes" })}>Submit</button>
      <button onClick={() => close()}>Close</button>
    </main>
  );
}

submit(data) prints the result to bwa run/bwa wait, shows a small success/error toast in the page, and shuts the server down. The default output is TOON; pass --json to bwa run when a script needs JSON. close() shuts the server down without a submit payload.

Metadata and dependencies

Prefer bwa add for third-party dependency metadata edits:

bwa add artifact.tsx lucide-react recharts

If the file does not exist, bwa add creates a starter artifact. If it exists, the command updates only the deps list in the metadata block and preserves the artifact body.

On the first check or run, BWA installs declared npm dependencies into the artifact's cache project under the BWA cache directory. The artifact remains the only source file beside the user's work; subsequent runs reuse the cached installation. Package versions and npm aliases are accepted, for example kleur@4.1.5 or diff-view@npm:@pierre/diffs@1.2.12.

Declare package roots rather than import subpaths: add @pierre/diffs to metadata and import @pierre/diffs/react in code. BWA currently normalizes an accidentally declared subpath to its package root. Dependency lifecycle scripts are disabled during installation, so packages that require postinstall compilation are not supported yet.

Treat an artifact and its declared dependencies as trusted code. bwa check server-renders the artifact in the CLI process, and bwa run executes it in the browser; --ignore-scripts blocks install hooks but does not make package code safe.

React, Vite, lucide-react, and recharts are carried by BWA and do not need a separate cached installation. Do not add BWA's own subpaths such as @lab/bwa/deck or @lab/bwa/feedback to metadata deps; import them directly.

Primitive imports

BWA-owned subpaths are available without adding them to artifact metadata deps:

import { submit } from "@lab/bwa/runtime";
import { Deck, Slide, DeckNav, DeckProgress, deckStyles } from "@lab/bwa/deck";
import {
  Gauge,
  RatingScale,
  LikertScale,
  useFeedbackState,
  feedbackStyles,
} from "@lab/bwa/feedback";

@lab/bwa/deck owns small deck behavior: slide state, previous/next/go-to controls, keyboard navigation, opt-in swipe, progress, and print-friendly structural CSS. Keyboard navigation supports j/ArrowRight/PageDown/Space for next, k/ArrowLeft/PageUp for previous, and Home/End jumps. Pointer gestures are off by default; pass swipe={true} only when an artifact intentionally wants drag/swipe navigation. It intentionally does not provide a visual theme; include deckStyles if useful and override with artifact-specific CSS variables/classes.

@lab/bwa/feedback is for bounded reactions such as ratings, confidence/risk/effort gauges, Likert scales, and choice chips. Each control includes its own colocated comment textarea by default, so the user can augment or caveat the bounded answer in context. It intentionally does not export a standalone generic text/comment box; use showComment={false} only when free text would be noise.

Example payload flow:

const feedback = useFeedbackState();

<RatingScale {...feedback.bindRating({ id: "confidence", label: "Confidence", min: 0, max: 5 })} />
<Gauge {...feedback.bindGauge({ id: "risk", label: "Risk", min: 0, max: 10, commentLabel: "Risk context" })} />
<button onClick={() => submit({ feedback: feedback.payload() })}>Submit</button>

Payload entries are keyed by stable IDs. Unanswered registered controls are included as value: null by default, so 0, the first option, and empty multi-choice arrays are not confused with missing answers. If the user adds comment text, the entry includes comment beside the bounded value.

Running artifacts

Foreground run:

bwa run artifact.tsx

bwa run prints the artifact title and local URL to stderr, does not open a browser, and keeps running until the page submits, closes, the floating Exit button is clicked, or the process receives Ctrl-C/SIGTERM. By default it asks the OS for a random available port.

Use a specific preferred port only when that exact port matters. Explicit ports are strict; if the port is already in use, BWA fails instead of moving to another port:

bwa run artifact.tsx --port 5173

Use JSON submit output for command chains:

bwa run artifact.tsx --json

Background runs for agent workflows

Use --bg when an agent needs to keep working after starting the artifact server, especially before registering the random port with local-router:

run=$(bwa run artifact.tsx --bg)
run_id=${run%%;*}
# e.g. "K7M4Z; pid: 12345; url: http://127.0.0.1:43210/ port: 43210"
bwa wait "$run_id"

Background contract:

  • bwa run --bg connects to one on-demand per-user manager and prints exactly <RUN_ID>; pid: <PID>; url: <URL> port: <PORT> after the worker is listening. The five-character uppercase RUN_ID is authoritative; PID, URL, and port are diagnostic metadata.
  • The manager is discovered only through its fixed per-user Unix socket. It owns the worker child and in-memory lifecycle: starting, running, submitted, closed, failed, or lost.
  • bwa bg list prints manager-owned active and retained runs as TOON; --json prints the same snapshots with authoritative runId fields (not legacy id fields).
  • bwa wait <RUN_ID> blocks through that socket until terminal state, then prints the retained submit output exactly as foreground bwa run would. --timeout <seconds> bounds that wait. Closed runs print nothing; failed/lost runs return an error.
  • Terminal results are retained in manager memory for 30 minutes, then disappear. There are no registry/result files, recovery, migration, pruning, snapshots, or PID-based control. The manager exits when no worker, waiter, in-flight request, or retained result remains.
  • bwa bg close <RUN_ID...> asks workers to shut down gracefully and succeeds only after acknowledgement and child exit. Graceful close has a five-second deadline; a timeout leaves the run manageable for retry or kill. Use --all for all active runs.
  • bwa bg kill <RUN_ID...> forcefully terminates manager-owned workers and confirms child exit. Use --all for all active runs.
  • If the manager disconnects, its workers close through their inherited IPC channel. An unexpected worker exit becomes lost; rerun the artifact rather than attempting recovery.

BWA_RUNTIME_DIR is an isolated runtime-directory override for tests/scripts. Normal users do not need it; BWA_CACHE_DIR remains only for cached Vite projects and dependencies.

Checking artifacts

bwa check artifact.tsx

bwa check parses the metadata, installs any uncached declared dependencies, prepares the temporary Vite entry, starts Vite in middleware mode, and performs a React server-side render smoke check to catch obvious load/render breakage such as components throwing before the page can display. It is still intentionally forgiving and is not a full browser QA pass: if the artifact depends on browser-only behavior or visual interaction, briefly run it too.

Shell completion

Bash completion can be generated with:

bwa completion bash

Source the output from your shell setup or redirect it into a completion file managed by your environment.

Install and update

Install the CLI from the Forgejo lab npm registry:

npm install -g @lab/bwa --@lab:registry=https://forge.dikka.dev/api/packages/lab/npm/
bwa --help

The CLI package exports the artifact runtime as @lab/bwa/runtime, deck primitives as @lab/bwa/deck, gauge/scale feedback primitives as @lab/bwa/feedback, and installs the command as bwa.

This repo also declares its Pi skill in package.json:

"pi": {
  "skills": ["skills"]
}

Install or update the Pi package when Pi should load the bundled bespoke-web-artifact skill:

pi install https://forge.dikka.dev/lab/bespoke-web-artifact.git
pi update https://forge.dikka.dev/lab/bespoke-web-artifact.git

From a local checkout

From this checkout, install dependencies, build the CLI, and expose the local bwa command:

vp install
vp run build
mkdir -p ~/.local/bin
ln -sf "$(pwd)/dist/cli.js" ~/.local/bin/bwa
chmod +x dist/cli.js
bwa --help

Then install the local package into Pi so Pi can load the skill from this checkout:

pi install "$(pwd)"

A local path Pi install points Pi at this checkout, so skill edits are picked up from the working tree after restarting Pi. After pulling local changes, rebuild the CLI:

vp install
vp run build
chmod +x dist/cli.js
bwa --help

Avoid vp add -g ., vp add -g "$(pwd)", and vp link for this local CLI flow; they are not reliable for this project setup.

Status

Early implementation. bwa build is intentionally not defined yet.