Skip to content
Merged
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ jobs:
gzip -d "$RUNNER_TEMP/celld-x86_64-unknown-linux-gnu.gz"
chmod +x "$RUNNER_TEMP/celld-x86_64-unknown-linux-gnu"
echo "CELLD_BIN=$RUNNER_TEMP/celld-x86_64-unknown-linux-gnu" >> "$GITHUB_ENV"
- run: CELLD_PACKAGE_INTEGRATION=1 bun run test:package:prebuilt
- run: bun run test:celld
71 changes: 71 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: Publish npm package

on:
workflow_dispatch:
inputs:
version:
description: Exact package.json version to publish
required: true
type: string

permissions:
contents: read

concurrency:
group: npm-publish
cancel-in-progress: false

jobs:
package:
if: github.repository == 'sidequery/canvas' && github.ref == 'refs/heads/main'
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.4.0
- name: Verify release version
env:
RELEASE_VERSION: ${{ inputs.version }}
run: |
bun -e 'const p = await Bun.file("package.json").json(); if (p.name !== "@sidequery/canvas" || p.version !== process.env.RELEASE_VERSION) throw new Error("Release input must match @sidequery/canvas package.json version");'
- run: bun install --frozen-lockfile
- run: bun run typecheck
- run: bun run typecheck:cloudflare
- run: bun run test
- run: bun run build:package
- name: Pack once
run: bun pm pack --ignore-scripts --filename "$RUNNER_TEMP/canvas.tgz"
- name: Test release archive including native server persistence
env:
CANVAS_PACKAGE_ARCHIVE: ${{ runner.temp }}/canvas.tgz
CELLD_PACKAGE_INTEGRATION: '1'
run: bun test scripts/package.integration.test.ts --timeout 360000
- uses: actions/upload-artifact@v4
with:
name: npm-package
path: ${{ runner.temp }}/canvas.tgz
if-no-files-found: error
retention-days: 7

publish:
needs: package
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
contents: read
id-token: write
steps:
- uses: actions/setup-node@v6
with:
node-version: '24'
package-manager-cache: false
- uses: actions/download-artifact@v4
with:
name: npm-package
path: release
# npm CLI performs the OIDC exchange. No NPM_TOKEN or NODE_AUTH_TOKEN.
- run: npm publish ./release/canvas.tgz --access public --ignore-scripts
66 changes: 49 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,32 @@ Only Herdr pane opening requires Herdr 0.8.2+ and
[Terminal Browser](https://github.com/zenbu-labs/terminal-browser).
The gallery, source operations, history, typechecking and compilation work without them.

## Install

After the first registry release, install the CLI with Bun; a source checkout is
not required. The package is currently being prepared and is not published yet.

```bash
bun add --global @sidequery/canvas
canvas write overview --file overview.canvas.tsx
canvas web --port 4784
```

Open `http://127.0.0.1:4784`. The package includes the local CLI, stdio MCP
server, gallery, and the prebuilt Worker and assets used by the optional native
canvas server. It does not build from source during installation.

For a stdio MCP client, configure the installed command directly:

```json
{
"command": "canvas",
"args": ["mcp", "--dir", "/absolute/path/to/workspace/canvases"]
}
```

Agents without MCP can use the CLI for the same artifact operations.

## Run from source

From the checkout:
Expand All @@ -32,17 +58,7 @@ bun run canvas web --port 4784

Open `http://127.0.0.1:4784`. Your working canvases and UI state are ignored by
this repository; reusable checked-in samples belong in `examples/`.
The package is private and is not published to a registry. The CLI has a `canvas`
bin entry for future packaging; `bun run canvas` works directly from this checkout.

For a stdio MCP client, configure the command `bun` with arguments:

```json
["run", "/absolute/path/to/canvas/src/cli.ts", "mcp", "--dir", "/absolute/path/to/workspace/canvases"]
```

Use your actual checkout path, including `herdr-canvas` if that is its folder name.
Agents without MCP can use the CLI for the same artifact operations.
`bun run canvas` uses the same CLI directly from this checkout.

## Optional Herdr integration

Expand All @@ -68,11 +84,11 @@ Canvases live at `<workspace>/canvases/<name>.canvas.tsx` and may import only
from `sidequery/canvas` (`herdr/canvas` and `cursor/canvas` remain compatibility aliases). Use the CLI or MCP:

```bash
bun run src/cli.ts list
bun run src/cli.ts write overview --file examples/overview.canvas.tsx
bun run src/cli.ts typecheck overview
bun run src/cli.ts open overview
bun run src/cli.ts mcp
canvas list
canvas write overview --file examples/overview.canvas.tsx
canvas typecheck overview
canvas open overview
canvas mcp
```

`open` typechecks and bundles with Bun, then opens the `herdr.canvas` `canvas`
Expand Down Expand Up @@ -132,6 +148,20 @@ CLI `open` continues to open a Herdr pane.

## Native canvas servers and SQLite

The optional local native runtime starts in the foreground with `canvas server`.
It downloads a pinned, checksummed `celld` binary on first use and stores its
runtime and SQLite state under the operating system's application-data directory.
Use `canvas server start`, `canvas server stop`, `canvas server status`, and
`canvas server logs` for the opt-in background service. See
[`docs/daemon.md`](docs/daemon.md) for lifecycle and login-service details.

The native server exposes its gallery at `http://127.0.0.1:4786` and its HTTP MCP
endpoint at `http://127.0.0.1:4786/mcp`. Configure an HTTP MCP client against that
endpoint to create canvases with server source and call `canvas_request`. The
`canvas web` gallery and `canvas mcp` stdio server continue to use workspace files
and the local history database; they do not migrate or synchronize canvases with
the native server.

Hosted Cloudflare canvases can pair the browser `.canvas.tsx` with a native
`CanvasServer`. The server is a generated Durable Object class and uses its own
SQLite through `ctx.storage.sql` and key/value storage through `ctx.storage.kv` directly. The browser calls it with
Expand Down Expand Up @@ -328,6 +358,7 @@ or delete action; restoring working source remains an explicit CLI/MCP operation
bun run test
bun run test:e2e
bun run test:mcp-ui
bun run test:package
bun run typecheck
```

Expand All @@ -340,7 +371,8 @@ GitHub Actions runs the typecheck and unit/service suite with Bun 1.4.0.
The root Dockerfile is an integration-test environment, not a production image.
`bun run test:mcp-ui` exercises the real canvas in Chromium with an MCP Apps host.
Install its browser first with `bun x playwright install chromium`. CI runs this
browser suite as well as the unit/service suite.
browser suite, the unit/service suite, and a clean tarball install that exercises
the installed CLI, gallery, compilation, and stdio MCP server.

## Trust boundary

Expand Down
19 changes: 10 additions & 9 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

102 changes: 102 additions & 0 deletions docs/daemon.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Local Canvas server

The native Canvas server is optional. It runs the packaged Worker and its
SQLite/KV state through a pinned celld runtime on `127.0.0.1`. The ordinary
Canvas CLI, stdio MCP server, local gallery, and artifact commands do not start
or download celld.

## Foreground server

Run the server in the current terminal:

```sh
canvas server
```

The default address is `http://127.0.0.1:4786`. `--port` selects another port.
The server keeps its project and native state under the Canvas data directory;
`--state-dir PATH` overrides that project directory for a foreground run. Press
Ctrl-C to request a graceful stop. SIGTERM uses the same shutdown path.

On first use, Canvas downloads celld 0.4.1 for a supported platform, verifies
the pinned archive and executable SHA-256 values, and installs it in the Canvas
data directory with user-only permissions. This release supports Apple Silicon
macOS and glibc Linux on arm64 or x64. The ordinary Bun commands do not require this native runtime; `canvas server`
reports an explicit error when the native platform is unsupported.

## Background service

Use the operating system's user service manager for crash recovery:

```sh
canvas server start
canvas server status
canvas server logs
canvas server stop
```

`start` uses launchd on macOS and `systemd --user` on Linux. It first verifies
the packaged server assets and managed celld executable, then registers the
service and waits for owned-process readiness. A manual start does not enable
the service for future logins. `stop` unloads or stops the current job and waits
for graceful shutdown. It retains the server project and all SQLite/KV data.

`status` reports the service manager and four separate facts:

- `installedAtLogin`: the user explicitly enabled future login starts.
- `loaded`: the service definition is currently known to the manager.
- `running`: the manager reports a live main process.
- `ready`: that process published matching PID, port, and state-directory
readiness, and its loopback health endpoint responds successfully.

The URL appears only when `ready` is true, so an unrelated process on the same
port cannot be reported as this Canvas service. Service identities include a
stable hash of `CANVAS_DATA_HOME`, which prevents commands for one data root
from stopping the service for another. Two roots still cannot listen on the
same port; `start` checks for that conflict before registration.

`logs` prints the most recent 100 lines from the service log. Select between 1
and 1000 lines with `canvas server logs --lines N`. Each read is capped at 256
KiB even when the log is larger.

## Start at login

If the server is already running, stop it first. Then start it and opt in to
future login starts:

```sh
canvas server start --at-login
```

Install `@sidequery/canvas` globally before enabling this option, for example
with `bun add --global @sidequery/canvas`. The supervisor definition records
the exact Bun executable and absolute installed `src/cli.ts` path. Do not create
a login service from `bunx` or another transient package cache: cleanup or cache
rotation can remove that recorded path.

`canvas server stop` stops the current process but preserves the login setting,
so the service starts at the next login. Disable login startup, stop the service,
and remove its installed supervisor definition with:

```sh
canvas server uninstall
```

Run `uninstall` before moving or removing the global package. Enabling login is
never part of package installation, upgrade, or publication.

## Data and configuration

Set `CANVAS_DATA_HOME` to choose a data root. Otherwise Canvas uses:

- macOS: `~/Library/Application Support/sidequery-canvas`
- Linux: `${XDG_DATA_HOME:-~/.local/share}/sidequery-canvas`

The durable native project is under `server`, the managed runtime is under
`runtimes`, and lifecycle state and logs are under `daemon`. On Linux,
`XDG_CONFIG_HOME` selects the systemd user-unit directory when set.

Supervisor definitions forward only `CANVAS_DATA_HOME`; they do not copy the
invoking shell's environment or credentials. The native server binds only to
loopback. Its state is local application data and is not uploaded or backed up
automatically.
Loading
Loading