Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ Add `data-test-*` attributes to card templates for stable test selectors:

When tests fail, the orchestrator feeds test failure details back to the agent. For more detail:

- **TestRun cards** live in the target realm's `Validations/` folder with a `test_` prefix (e.g., `Validations/test_issue-slug-1.json`). To find all test runs, search by the TestRun card type in the target realm. Each TestRun has a `sequenceNumber` that increases with each iteration. Use `read_file` on a specific TestRun for full details.
- **TestRun cards** live in the target realm's `Validations/` folder with a `test_` prefix (e.g., `Validations/test_issue-slug-1.json`). To find all test runs, run `Glob` over `Validations/test_*.json` or shell out via `Bash` to `boxel search --realm <url>` filtered on the TestRun card type. Each TestRun has a `sequenceNumber` that increases with each iteration. Use native `Read` on a specific TestRun for full details — paths are workspace-relative.

## Rules

Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,52 @@
# Catalog Spec Card Instances

For each top-level card definition, create a Catalog Spec card instance in the target realm's `Spec/` folder using the `create_catalog_spec` tool. This makes the card discoverable in the Boxel catalog.
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.

The `create_catalog_spec` tool has the authoritative JSON schema for Spec card fields — use its parameter definitions to know which attributes and relationships are available. The tool auto-constructs the document with the correct `adoptsFrom` (`https://cardstack.com/base/spec#Spec`).
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:

## Usage
```
get_card_schema({ module: 'https://cardstack.com/base/spec', name: 'Spec' })
```

Use the `create_catalog_spec` tool to create a Spec card. The tool's parameters define the available fields dynamically from the card definition — consult the tool schema for the exact field names and types.
The result gives you the exact `attributes` and `relationships` shape. Write the JSON file with native `Write` (paths are workspace-relative, e.g. `Spec/sticky-note.json`); `boxel sync` pushes it to the realm between iterations.

## Required Shape

```json
{
"data": {
"type": "card",
"attributes": {
"specType": "card",
"ref": { "module": "../sticky-note", "name": "StickyNote" },
"readMe": "...",
"cardInfo": { "name": "Sticky Note", "summary": "..." }
},
"relationships": {
"linkedExamples.0": { "links": { "self": "../StickyNote/welcome-note" } }
},
"meta": {
"adoptsFrom": {
"module": "https://cardstack.com/base/spec",
"name": "Spec"
}
}
}
}
```

Key concepts:

- `ref` — a CodeRef pointing to the card definition (module path + exported class name). The module path is relative from the Spec card to the `.gts` file (e.g., `../sticky-note` from `Spec/sticky-note.json`).
- `specType` — `"card"` for CardDef, `"field"` for FieldDef, `"component"` for standalone components.
- `linkedExamples` — a relationship pointing to sample card instances. Create at least one sample instance and link it here.
- `linkedExamples` — a `linksToMany` relationship pointing to sample card instances. Use dotted keys (`linkedExamples.0`, `linkedExamples.1`, …) — the array form is rejected by the indexer. Create at least one sample instance and link it here.
- **Do NOT call `run_instantiate` on the Spec file itself.** Spec's module lives in the base realm; the prerender enforces same-origin module loads and the call always fails. To validate Specs, call `run_instantiate` WITHOUT a `path`; it discovers Specs in the target realm and exercises their `linkedExamples` against the card classes you wrote.

## Sample Card Instances

Create at least one sample instance with realistic data for each top-level card. Sample instances serve as both catalog examples and test fixtures.

Place sample instances in a folder named after the card type (e.g., `StickyNote/welcome-note.json`). Use `write_file` to create them. The `linkedExamples` relationship in the Spec card points to these using a relative path (e.g., `../StickyNote/welcome-note`).
Place sample instances in a folder named after the card type (e.g., `StickyNote/welcome-note.json`) and write them with native `Write`. The `linkedExamples` relationship in the Spec card points to these using a relative path without the `.json` suffix (e.g., `../StickyNote/welcome-note`).

---

Expand Down
182 changes: 182 additions & 0 deletions packages/boxel-cli/.agents/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/.agents/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