Single-file React/TSX bespoke web artifacts via the bwa CLI.
  • TypeScript 97.6%
  • JavaScript 2.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
dikkadev 61a72ee754 docs: document GitHub Copilot skill integration
This documents the supported GitHub Copilot workflow, including CLI installation, personal and repository skill locations, remote-environment guidance, and known platform limitations while preserving the existing Pi instructions.
2026-07-22 09:14:27 +02:00
.dev fix(run): preallocate a random port before starting the server 2026-07-15 14:31:24 +02:00
dist feat(deps): add refresh command and generation-based dependency cache 2026-07-17 11:25:26 +02:00
skills/bespoke-web-artifact docs: document GitHub Copilot skill integration 2026-07-22 09:14:27 +02:00
src feat(deps): add refresh command and generation-based dependency cache 2026-07-17 11:25:26 +02:00
tests feat(deps): add refresh command and generation-based dependency cache 2026-07-17 11:25:26 +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(deps): add refresh command and generation-based dependency cache 2026-07-17 11:25:26 +02:00
pnpm-lock.yaml feat(deps): add refresh command and generation-based dependency cache 2026-07-17 11:25:26 +02:00
pnpm-workspace.yaml feat(cli): bootstrap bwa single-file artifact runner 2026-05-30 09:25:19 +02:00
README.md docs: document GitHub Copilot skill integration 2026-07-22 09:14:27 +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 deps refresh artifact.tsx
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...>
bwa bg close --all
bwa bg kill <RUN_ID...>
bwa bg kill --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 and preserves the artifact body. BWA validates the complete merged dependency set before atomically replacing the file; invalid or conflicting additions leave the artifact unchanged.

Named dependency sources follow npm package-spec grammar. Registry versions, ranges, tags, npm aliases, and named file/URL/git sources are accepted, for example:

// deps = [
//   "kleur@4.1.5",
//   "semver@^7",
//   "diff-view@npm:@pierre/diffs@1.2.12",
//   "local-widget@file:./widget",
//   "repo-widget@github:owner/repository"
// ]

Every dependency needs an import name, so a bare URL, git source, or file path is rejected; use name@source. Declare package roots rather than import subpaths: add @pierre/diffs and then import @pierre/diffs/react. BWA can normalize an accidentally declared scoped import subpath, but rejects versions attached to subpaths. Ambiguous unscoped slash forms such as lodash/fp or owner/repository are rejected: declare the registry package root, or use an explicit named source such as repo-widget@github:owner/repository.

On the first check or run, BWA resolves dependencies with npm in a private staging project, keeps lifecycle scripts disabled, and atomically publishes a locked cache. Concurrent cold starts are serialized with recoverable per-project leases. Warm runs verify the npm resolution policy, lockfile, platform/runtime identity, and installed file inventory; damaged or policy-stale caches are rebuilt.

BWA inherits npm's user/global/environment resolution policy, including before, min-release-age, min-release-age-exclude, and relevant registry settings. It does not replace those rules with a separate release-age implementation, and artifact metadata cannot weaken npm options. Because npm runs in BWA's cache staging directory, put shared registry/release-age policy in user/global npm config or npm environment variables rather than an .npmrc beside the artifact.

The lock keeps bare/ranged/tagged resolutions stable after the first successful install. Re-resolve them explicitly when wanted:

bwa deps refresh artifact.tsx

A refresh publishes a new immutable generation and retains older generations so an already-running artifact can keep using its resolved files. BWA does not prune retained generations yet; remove the artifact's BWA cache when old generations are no longer worth keeping.

Dependency lifecycle scripts remain disabled at npm CLI priority, so packages that require postinstall compilation are unsupported. Treat an artifact and its dependencies as trusted code anyway: bwa check server-renders them in the CLI process, and bwa run executes them in the browser.

React, Vite, TypeScript, lucide-react, and recharts are carried by BWA and need no declaration. Bare roots/subpaths are ignored, while explicit versions, ranges, tags, aliases, or source replacements for carried packages are rejected rather than silently substituted. Do not add BWA's own subpaths such as @lab/bwa/deck or @lab/bwa/feedback; 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.

GitHub Copilot in VS Code

BWA is not tied to Pi at runtime. It has two parts: the bwa command, which is an ordinary CLI, and skills/bespoke-web-artifact/SKILL.md, which already follows the open Agent Skills format supported by GitHub Copilot. No MCP server or dedicated VS Code extension is required.

First install the CLI in the same environment where VS Code's agent terminal runs. For a Remote - WSL, SSH, or dev-container workspace, run this inside that remote environment rather than on the host:

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

Then expose the bundled skill to Copilot. For a personal skill available in every workspace, copy it from the installed npm package into Copilot's personal skills directory:

mkdir -p "$HOME/.copilot/skills"
cp -R "$(npm root -g)/@lab/bwa/skills/bespoke-web-artifact" \
  "$HOME/.copilot/skills/"

Rerun the copy after updating @lab/bwa so Copilot receives skill changes. For a repository-owned installation that should be committed and shared with the project, put the directory at .github/skills/bespoke-web-artifact/ instead.

In VS Code, open Copilot Chat, select Agent, and invoke the skill directly or let Copilot choose it from the request:

/bespoke-web-artifact Create an interactive decision aid as one TSX file,
run bwa check, then start it with bwa run --bg and give me the local URL.

The skill should appear under Chat: Open Customizations → Skills and in the / menu. If it does not appear after installation, reload the VS Code window and verify that the skill is installed in the home directory of the local or remote extension host that owns the workspace.

Local VS Code Agent mode in WSL/Linux is the best-tested fit because Copilot can edit the artifact, run bwa, and hand back a browser-reachable localhost URL. Copilot cloud agents can still write and check an artifact, but their localhost server is not directly reachable from your desktop; BWA does not yet define build/export semantics for that handoff. Native Windows CLI behavior has not been validated yet, so use WSL rather than assuming the background manager works unchanged on Windows.

Pi

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.