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
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

For each top-level card definition, write a Catalog Spec card instance in the target realm's `Spec/` folder. This makes the card discoverable in the Boxel catalog.

Specs adopt from `https://cardstack.com/base/spec#Spec` — that module lives in the base realm, not your target realm. Fetch the authoritative schema by calling the `get_card_schema` factory tool:
Specs adopt from the `Spec` class exported by `https://cardstack.com/base/spec` — that module lives in the base realm, not your target realm. Fetch the authoritative schema by calling the `get_card_schema` factory tool:

```
get_card_schema({ module: 'https://cardstack.com/base/spec', name: 'Spec' })
Expand Down
2 changes: 1 addition & 1 deletion packages/boxel-cli/plugin/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "boxel-cli",
"description": "Claude Code skills for working with Boxel realms via @cardstack/boxel-cli. Requires @cardstack/boxel-cli >= 0.0.1 installed on PATH (npm install -g @cardstack/boxel-cli).",
"version": "0.1.2",
"version": "0.1.3",
"author": {
"name": "Cardstack",
"url": "https://boxel.ai"
Expand Down
182 changes: 182 additions & 0 deletions packages/boxel-cli/plugin/skills/boxel-api/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
---
name: boxel-api
description: Use when calling Boxel realm-server APIs from code — primarily federated search across realms. Documents the boxel-cli programmatic surface (`BoxelCLIClient`) and the matching CLI commands. Read this whenever you need to query a realm's index.
---

# Boxel API

Canonical home for Boxel platform API knowledge.

**Architectural principle:** boxel-cli owns the entire Boxel API surface. Any code that talks to the realm server or Matrix lives in boxel-cli; consumers (the software-factory, custom scripts, hand-written tools) import `BoxelCLIClient` from `@cardstack/boxel-cli/api` and call its methods. Auth (tokens, refresh, retries) is fully internal — if you're holding a JWT or calling `fetch` against a realm URL directly, you're in the wrong layer.

```ts
import { BoxelCLIClient } from '@cardstack/boxel-cli/api';

let client = new BoxelCLIClient(); // reads the active Boxel profile
```

All examples below assume `client` is a `BoxelCLIClient` instance.

## Federated search

Search across one or more realms via `/_federated-search`. Query syntax matches the Boxel realm search format.

### CLI

```
boxel search --realm <realm-url> [--realm <realm-url>...] --query '<json>'
```

`--realm` is repeatable. `--query` takes a JSON string. Append `--json` for raw output.

### Programmatic

```ts
let result = await client.search(realmUrl, query); // single realm
let result = await client.search([realmA, realmB], query); // federated
```

Returns `{ ok, status, data?, error? }`. `data` is an array of card resources.

### Query syntax

```json
{
"filter": { ... },
"sort": [ ... ],
"page": { "size": 10 }
}
```

All top-level fields are optional. An empty query `{}` returns all cards in the targeted realms.

#### Filter by card type

```json
{
"filter": {
"type": {
"module": "http://localhost:4201/software-factory/darkfactory",
"name": "Project"
}
}
}
```

Returns all cards that adopt from (or extend) the specified type. Wildcards (`*`) in `module` or `name` are **not** supported — always use a specific CodeRef.

#### Filter by field value (`eq`)

`eq` requires an `on` to scope the field to a card type:

```json
{
"filter": {
"on": { "module": "...", "name": "Issue" },
"eq": { "status": "in_progress" }
}
}
```

Multiple fields in `eq` are ANDed. Use dot paths for nested fields (e.g. `"author.firstName": "Carl"`). Use `null` to match empty/missing fields.

#### Substring search (`contains`)

Case-insensitive substring match:

```json
{ "filter": { "contains": { "cardTitle": "sticky" } } }
```

Scoped form same as `eq` (`on` + `contains`).

#### Range filters

```json
{
"filter": {
"on": { "module": "...", "name": "Post" },
"range": { "views": { "lte": 10, "gt": 5 } }
}
}
```

Operators: `gt`, `gte`, `lt`, `lte`. Works on numeric, date, and string fields.

#### Boolean combinators

```json
// AND
{ "filter": { "on": {...}, "every": [ {...}, {...} ] } }

// OR
{ "filter": { "any": [ {...}, {...} ] } }

// NOT
{ "filter": { "on": {...}, "not": { "eq": { ... } } } }
```

#### Sort

```json
{
"sort": [
{ "by": "author.lastName", "on": { "module": "...", "name": "Article" } }
],
"filter": { "type": { "module": "...", "name": "Article" } }
}
```

Add `"direction": "desc"` for descending.

#### Pagination

```json
{ "filter": {...}, "page": { "size": 10 } }
```

#### CodeRef field matching

CodeRef fields (e.g. `ref` on a Spec card) are matched against the full `{ module, name }`:

```json
{
"filter": {
"on": { "module": "https://cardstack.com/base/spec", "name": "Spec" },
"eq": {
"ref": {
"module": "http://localhost:4201/my-realm/sticky-note",
"name": "StickyNote"
}
}
}
}
```

### Common mistakes

- **Field names without `on`.** Fields like `title`, `status`, etc. are type-specific. The exceptions are `cardTitle` and `cardDescription` — those exist on the base `CardDef`.
- **Relative or bare module URLs.** Always use full absolute module URLs in CodeRefs.
- **Slash separators in dotted paths.** Use `author.firstName`, not `author/firstName`.
- **Searching relationships that aren't rendered in an embedded/fitted template.** The query engine indexes a linked field only if it appears in an embedded format. Otherwise the filter silently misses.

## When to use what

| Goal | Use |
| -------------------------------------------------------- | ------------------------------------------------------------------ |
| Find cards in your local synced workspace | Native `grep` / `find` — files are already on disk |
| Find cards by type / field across one or more realms | `boxel search` / `client.search` |
| Read a single card's source from a realm | `client.read(realmUrl, path)` / `boxel file read` |
| Read the transpiled (browser) version of a `.gts` module | `client.readTranspiled(...)` / `boxel read-transpiled` |
| List files in a realm | `client.listFiles(realmUrl)` / `boxel file list` |
| Push local changes to a realm | `client.sync(realmUrl, dir, { preferLocal: true })` / `boxel sync` |
| Pull a realm's state to a local dir | `client.pull(realmUrl, dir)` / `boxel pull` |
| Run a host command (prerendered) | See the `boxel-command` skill |

## What this skill is **not** for

- **Card development patterns** (`.gts` field declarations, templates, `linksTo` vs `contains`) — that's `boxel-development`.
- **JSON:API document structure** for card instances — that's `boxel-file-structure`.
- **Sync / pull / track / watch CLI ergonomics** — those have their own per-command skills (`boxel-sync`, `boxel-track`, `boxel-watch`).
- **Host commands via the prerenderer** (`/_run-command`) — that's the `boxel-command` skill.
- **Realm provisioning** (`createRealm` / `boxel realm create`) and **readiness polling** (`client.waitForReady` / `/_readiness-check`) — orchestration concerns. The software-factory creates target realms in `factory-target-realm.ts` before the agent loop starts; consumers needing those APIs should read `boxel-cli/src/api.ts` directly or run `boxel realm create --help`.
71 changes: 71 additions & 0 deletions packages/boxel-cli/plugin/skills/boxel-command/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
name: boxel-command
description: Use when running a Boxel host command via the realm server's prerenderer — invoking commands like `get-card-type-schema`, `evaluate-module`, `instantiate-card`, or any other module exposed at `@cardstack/boxel-host/commands/.../default`. Documents `boxel run-command` and the matching `client.runCommand()` method.
---

# Boxel Host Commands

Some Boxel operations only exist inside the host app's prerendered runtime — there's no realm-server HTTP endpoint for them, and they can't be reimplemented in plain Node. The realm server's `/_run-command` endpoint forwards a job to the prerenderer (a headless Chrome instance that has the full host runtime loaded), executes the named command there, and returns the serialized result. Schema introspection, module evaluation, card instantiation, transpiled-module fetches — all of these go through `run-command`.

This skill documents how to invoke that flow.

## When to use it

- **Card type schema lookup.** Get the live `{ attributes, relationships }` JSON Schema for a `CardDef` by introspecting its real class at runtime — not by reading the `.gts` source.
- **Module evaluation.** Load a `.gts` / `.ts` module in the prerender sandbox to surface broken imports, circular references, or top-level runtime errors before they hit a real consumer.
- **Card instantiation.** Construct a card instance from a JSON document inside the prerender — exercises the `CardDef` class against the document shape.
- **Anything else exposed at `@cardstack/boxel-host/commands/<name>/default`.** Each module is its own host command.

## CLI

```
boxel run-command <command-specifier> --realm <realm-url> [--input '<json>'] [--json]
```

- `<command-specifier>` — the module path of the command (e.g. `@cardstack/boxel-host/commands/get-card-type-schema/default`).
- `--realm` — the realm URL the command runs against. Required.
- `--input` — JSON string passed as the command's input. Optional; some commands take no input.
- `--json` — emit the raw response instead of the formatted summary.

### Example

```
boxel run-command @cardstack/boxel-host/commands/get-card-type-schema/default \
--realm http://localhost:4201/my-realm/ \
--input '{"codeRef":{"module":"http://localhost:4201/my-realm/sticky-note","name":"StickyNote"}}'
```

## Programmatic

```ts
import { BoxelCLIClient } from '@cardstack/boxel-cli/api';

let client = new BoxelCLIClient();

let result = await client.runCommand(
realmServerUrl,
realmUrl,
'@cardstack/boxel-host/commands/get-card-type-schema/default',
{ codeRef: { module: '<absolute-module-url>', name: 'StickyNote' } },
);
```

Returns `{ status: 'ready' | 'error' | 'unusable', result?: string | null, error?: string | null }`. `result` is the command's serialized output (a JSON string — parse it yourself). `error` is set when `status !== 'ready'`.

## How it works under the hood

`/_run-command` enqueues a job for the realm worker. The worker hands it to the prerenderer (which has the host app, the realm's Loader, the CardAPI, and all field serializers loaded). The command module is imported, called with the input, and its result is serialized back through the queue to the HTTP response.

Three failure modes you'll see:

- `status: 'unusable'` — the prerender pool is broken (e.g. "No standby page available for prerender"). Not retryable from the caller's side; usually a sign the realm-server worker / prerender pool itself is unhealthy.
- `status: 'error'` with `error: "module URL not found"` — the realm's in-memory module map hasn't indexed the file yet. Common right after a `/_atomic` write; caller can retry briefly or use `client.sync(..., { waitForIndex: true })` upstream.
- `status: 'error'` with any other message — the command threw inside the prerender. The `error` is the thrown error's message; the original stack is usually in the worker logs.

The realm server itself enforces auth (server JWT via `BoxelCLIClient`); the prerender executes inside the realm's sandbox with the realm's permissions.

## What this skill is **not** for

- **Realm-side HTTP endpoints** (search, file read/write, atomic batches) — those are direct `BoxelCLIClient` methods. See the `boxel-api` skill.
- **Programmatic in-memory validators** (`runLintInMemory`, `runEvaluateInMemory`, `runParseInMemory`, `runInstantiateInMemory`) — those wrap `runCommand` internally but expose a flatter result shape; consumers usually want those, not raw `runCommand`. They live in the software-factory package.
- **Defining new host commands.** That's host-app development (`packages/host/app/commands/`).
Loading
Loading