Skip to content
Open
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
15 changes: 15 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "cardstack-boxel",
"owner": {
"name": "Cardstack",
"url": "https://boxel.ai"
},
"description": "Claude Code plugins published from the cardstack/boxel monorepo.",
"plugins": [
{
"name": "boxel-cli",
"source": "./packages/boxel-cli/plugin",
"description": "Skills for working with Boxel realms via @cardstack/boxel-cli."
}
]
}
97 changes: 97 additions & 0 deletions .github/workflows/ci-lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,100 @@ jobs:
if: ${{ !cancelled() }}
run: pnpm run lint
working-directory: packages/boxel-cli
- name: Verify Boxel CLI plugin synopsis is fresh
# Regenerates the `<!-- generated:commands -->` blocks in plugin/skills/*/SKILL.md
# from the Commander tree and fails if the working tree changed — i.e. someone
# added or changed a CLI command without running `pnpm build:plugin`.
if: ${{ !cancelled() }}
run: |
pnpm run build:plugin
if ! git diff --exit-code -- plugin/skills; then
echo "::error::plugin/skills synopsis is stale. Run 'pnpm build:plugin' in packages/boxel-cli and commit the result."
exit 1
fi
working-directory: packages/boxel-cli
- name: Verify boxel-skills sync
# Regenerates plugin/skills/boxel-development and plugin/skills/boxel-design from
# cardstack/boxel-skills at the pinned tag in scripts/build-skills.ts. Fails if
# the working tree changed — i.e. someone hand-edited boxel-skills-derived
# content without bumping the pin and re-running `pnpm build:skills`.
if: ${{ !cancelled() }}
run: |
pnpm run build:skills
if ! git diff --exit-code -- plugin/skills; then
echo "::error::plugin/skills is out of sync with cardstack/boxel-skills. Bump BOXEL_SKILLS_VERSION in scripts/build-skills.ts (or fix upstream), run 'pnpm build:skills' in packages/boxel-cli, and commit the result."
exit 1
fi
working-directory: packages/boxel-cli
- name: Verify plugin version bumped when synopsis changed
# If a PR's diff against main touches any generated:commands block, the plugin's
# version must also bump in the same diff. Otherwise marketplace consumers won't
# see the update — Claude Code caches by version string.
if: ${{ !cancelled() && github.event_name == 'pull_request' }}
run: |
set -euo pipefail
BASE="${{ github.event.pull_request.base.sha }}"
HEAD="${{ github.event.pull_request.head.sha }}"
Comment thread
FadhlanR marked this conversation as resolved.
# `actions/checkout` uses default depth, so $BASE (a `main` SHA) usually
# isn't local. Fetch both explicitly so `git diff`/`git show` succeed.
git fetch --no-tags --depth=1 origin "$BASE" "$HEAD"
# Did the generated-commands block change in any SKILL.md? Compare the
# block at $BASE vs $HEAD per-file. Any difference inside the markers
# (heading, description, argument, blank line) flips SYNOPSIS_CHANGED.
# `git cat-file -e` is the explicit existence probe — using `git show`
# with `2>/dev/null` would still propagate exit 128 and trip pipefail.
extract_block() {
local rev="$1" path="$2"
if git cat-file -e "$rev:$path" 2>/dev/null; then
git show "$rev:$path" \
| awk '/<!-- generated:commands:start -->/,/<!-- generated:commands:end -->/'
fi
}
SYNOPSIS_CHANGED=
while IFS= read -r f; do
base_block=$(extract_block "$BASE" "$f")
head_block=$(extract_block "$HEAD" "$f")
if [ "$base_block" != "$head_block" ]; then
SYNOPSIS_CHANGED=1
break
fi
done < <(git ls-tree -r --name-only "$HEAD" -- packages/boxel-cli/plugin/skills | grep '/SKILL\.md$' || true)
if [ -z "$SYNOPSIS_CHANGED" ]; then
echo "No generated synopsis changes detected; skipping version-bump check."
exit 0
fi
# Was the plugin manifest version touched?
if ! git diff "$BASE" "$HEAD" -- packages/boxel-cli/plugin/.claude-plugin/plugin.json \
| grep -qE '^[+-]\s*"version"'; then
echo "::error::plugin/skills synopsis changed but plugin.json version was not bumped. Marketplace consumers won't see the update without a new version. Bump 'version' in packages/boxel-cli/plugin/.claude-plugin/plugin.json."
exit 1
fi
echo "Synopsis changed and plugin.json version was bumped — OK."
working-directory: .
- name: Verify plugin version bumped when boxel-skills content changed
# Mirrors the synopsis-bump check above, but gates on changes to skill folders
# generated by `pnpm build:skills` (boxel-development/, boxel-design/). Any
# content change there requires a plugin.json bump so marketplace consumers
# actually pick up the new content — Claude Code caches by version string.
if: ${{ !cancelled() && github.event_name == 'pull_request' }}
run: |
set -euo pipefail
BASE="${{ github.event.pull_request.base.sha }}"
HEAD="${{ github.event.pull_request.head.sha }}"
# See note in the previous step — fetch base/head explicitly because
# `actions/checkout` uses default depth and $BASE often isn't local.
git fetch --no-tags --depth=1 origin "$BASE" "$HEAD"
SKILLS_CHANGED=$(git diff --name-only "$BASE" "$HEAD" -- \
'packages/boxel-cli/plugin/skills/boxel-development/**' \
'packages/boxel-cli/plugin/skills/boxel-design/**')
if [ -z "$SKILLS_CHANGED" ]; then
echo "No boxel-skills-derived changes detected; skipping version-bump check."
exit 0
fi
if ! git diff "$BASE" "$HEAD" -- packages/boxel-cli/plugin/.claude-plugin/plugin.json \
| grep -qE '^[+-]\s*"version"'; then
echo "::error::boxel-skills-derived content changed but plugin.json version was not bumped. Marketplace consumers won't see the update without a new version. Bump 'version' in packages/boxel-cli/plugin/.claude-plugin/plugin.json."
exit 1
fi
echo "boxel-skills content changed and plugin.json version was bumped — OK."
working-directory: .
3 changes: 3 additions & 0 deletions packages/boxel-cli/.gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Build output
dist/

# Cached shallow clone of cardstack/boxel-skills used by scripts/build-skills.ts
.boxel-skills-cache/

# Dependencies
node_modules/

Expand Down
2 changes: 2 additions & 0 deletions packages/boxel-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@
},
"scripts": {
"build": "pnpm clean && NODE_NO_WARNINGS=1 ts-node --transpileOnly scripts/build.ts",
"build:plugin": "NODE_NO_WARNINGS=1 ts-node --transpileOnly scripts/build-plugin.ts",
"build:skills": "NODE_NO_WARNINGS=1 ts-node --transpileOnly scripts/build-skills.ts",
"clean": "rm -rf dist/*",
"start": "NODE_NO_WARNINGS=1 ts-node --transpileOnly src/index.ts",
"lint": "concurrently \"pnpm:lint:*(!fix)\" --names \"lint:\"",
Expand Down
12 changes: 12 additions & 0 deletions packages/boxel-cli/plugin/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"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.1",
"author": {
"name": "Cardstack",
"url": "https://boxel.ai"
},
"homepage": "https://github.com/cardstack/boxel/tree/main/packages/boxel-cli/plugin",
"repository": "https://github.com/cardstack/boxel.git",
"license": "MIT"
}
105 changes: 105 additions & 0 deletions packages/boxel-cli/plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# `boxel-cli` Claude Code plugin

Claude Code skills for working with Boxel realms via [`@cardstack/boxel-cli`](https://www.npmjs.com/package/@cardstack/boxel-cli).

## Prerequisites

Install the boxel CLI globally so the plugin's skills can shell out to it:

```bash
npm install -g @cardstack/boxel-cli
```

Verify:

```bash
boxel --version
```

The plugin documents commands in `@cardstack/boxel-cli >= 0.0.1`. Newer plugin versions may document commands that older CLI versions do not have — keep both reasonably fresh.

## Install

### External users (marketplace)

```text
/plugin marketplace add cardstack/boxel
/plugin install boxel-cli
```

### Internal / development (`--plugin-dir`)

From a checkout of `cardstack/boxel`:

```bash
claude --plugin-dir packages/boxel-cli/plugin
```

`/reload-plugins` picks up local edits without restarting Claude Code.

## What you get

Skills appear under the `/boxel-cli:` namespace.

| Skill | Use it for |
|---|---|
| `/boxel-cli:boxel-development` | Authoring `.gts` card definitions and `.json` instances. The high-level Boxel patterns guide. Generated from [`cardstack/boxel-skills`](https://github.com/cardstack/boxel-skills). |
| `/boxel-cli:boxel-design` | Design-discovery prompts for distinctive Boxel UI. Generated from [`cardstack/boxel-skills`](https://github.com/cardstack/boxel-skills). |
| `/boxel-cli:boxel-file-structure` | File and directory naming rules, `adoptsFrom` module paths, link relationship semantics. |
| `/boxel-cli:realm-sync` | `boxel realm sync/push/pull/create/list` — moving files between local disk and a realm. |
| `/boxel-cli:realm-history` | `boxel realm history/wait-for-ready/cancel-indexing` — inspecting and steering realm indexing. |
| `/boxel-cli:file-ops` | `boxel file read/write/list/delete/lint/touch` — single-file operations against a realm. |
| `/boxel-cli:search` | `boxel search` — federated search across realms. |
| `/boxel-cli:profile` | `boxel profile list/add/switch/remove/migrate` — managing realm-server credentials. |

## Versioning

The plugin's `version` is independent of `@cardstack/boxel-cli`'s npm version. The plugin only describes the CLI; it does not bundle it.

| Change | `package.json` `version` (npm CLI) | `plugin.json` `version` |
|---|---|---|
| New / changed CLI command | bump | bump (synopsis regenerates) |
| Plugin prose only || bump |
| CLI refactor / bug fix (no Commander change) | bump ||
| New `cardstack/boxel-skills` release || bump (after re-running `pnpm build:skills`) |

See [Releasing](#releasing) below for how a `plugin.json` bump actually reaches users.

## Releasing

The plugin is **git-distributed** through `.claude-plugin/marketplace.json` at the monorepo root — there is no separate npm publish step for the plugin. A release is simply: merge to `main` with a bumped `plugin.json` `version`.

### Steps

1. If you changed CLI commands, run `pnpm build:plugin` from `packages/boxel-cli/` to regenerate the `<!-- generated:commands -->` blocks in each `SKILL.md`.
2. If `cardstack/boxel-skills` cut a new tag (or you want to pull in upstream edits), bump `BOXEL_SKILLS_VERSION` in `packages/boxel-cli/scripts/build-skills.ts` and run `pnpm build:skills` from `packages/boxel-cli/`. That regenerates `plugin/skills/boxel-development/` and `plugin/skills/boxel-design/` from the pinned tag.
3. Bump `packages/boxel-cli/plugin/.claude-plugin/plugin.json` `version` (semver — patch for prose, minor for new skills, major for breaking changes to skill names/contracts).
4. Open a PR. CI in `.github/workflows/ci-lint.yaml` will fail if the synopsis is stale (*synopsis freshness* check), if the boxel-skills sync is stale (*boxel-skills sync* check), or if the bump is missing when generated content changed (*synopsis-bump* / *boxel-skills-bump coupling* checks).
5. Merge to `main`. That's the publish — `cardstack/boxel`'s `.claude-plugin/marketplace.json` is the source of truth and Claude Code pulls from it directly.

### How users pick up the new version

- **Automatic:** Claude Code refreshes marketplaces on startup for public repos.
- **Manual:** `/plugin marketplace update && /plugin update` inside Claude Code.

The marketplace cache is keyed on `plugin.json` `version`**forgetting the bump means no user sees the change**, even after merge. The CI coupling check exists to prevent exactly this.

### Adding a new plugin to the marketplace

If a future ticket adds a second plugin under `packages/<other>/plugin`, append an entry to `.claude-plugin/marketplace.json` at the repo root:

```json
{
"name": "<plugin-name>",
"source": "./packages/<other>/plugin",
"description": "..."
}
```

Each plugin's own `plugin.json` `version` drives its update lifecycle — the marketplace catalog itself does not need a version bump.

### What a plugin release does *not* do

- It does **not** publish or update `@cardstack/boxel-cli` on npm. That ships separately via the `manual-boxel-cli-publish.yml` workflow.
- It does **not** require users to reinstall — `/plugin update` is in-place.
- It does **not** bundle the CLI. Users still need `npm install -g @cardstack/boxel-cli` (see [Prerequisites](#prerequisites)).
Loading