Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
16 changes: 16 additions & 0 deletions .agents/plugins/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "cardstack-boxel",
"interface": {
"displayName": "Cardstack Boxel"
},
"plugins": [
{
"name": "boxel-cli",
"source": {
"source": "local",
"path": "./packages/boxel-cli/plugin"
},
"description": "Skills for working with Boxel realms via @cardstack/boxel-cli."
Comment thread
jurgenwerk marked this conversation as resolved.
Outdated
}
]
}
22 changes: 14 additions & 8 deletions .github/workflows/boxel-cli-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -167,14 +167,19 @@ jobs:
echo "package.json → $NEXT_NPM"
fi
if [ -n "$NEXT_PLUGIN" ]; then
node -e '
const fs = require("fs");
const p = "packages/boxel-cli/plugin/.claude-plugin/plugin.json";
const j = JSON.parse(fs.readFileSync(p, "utf8"));
j.version = process.env.NEXT_PLUGIN;
fs.writeFileSync(p, JSON.stringify(j, null, 2) + "\n");
'
echo "plugin.json → $NEXT_PLUGIN"
# The Claude and Codex manifests version together; build:plugin
# syncs the rest of the Codex manifest from the Claude one.
for p in packages/boxel-cli/plugin/.claude-plugin/plugin.json \
packages/boxel-cli/plugin/.codex-plugin/plugin.json; do
PLUGIN_MANIFEST="$p" node -e '
const fs = require("fs");
const p = process.env.PLUGIN_MANIFEST;
const j = JSON.parse(fs.readFileSync(p, "utf8"));
j.version = process.env.NEXT_PLUGIN;
fs.writeFileSync(p, JSON.stringify(j, null, 2) + "\n");
'
echo "$p → $NEXT_PLUGIN"
done
fi

- name: Generate release notes
Expand Down Expand Up @@ -231,6 +236,7 @@ jobs:
set -euo pipefail
git add packages/boxel-cli/package.json \
packages/boxel-cli/plugin/.claude-plugin/plugin.json \
packages/boxel-cli/plugin/.codex-plugin/plugin.json \
packages/boxel-cli/plugin/skills \
packages/boxel-cli/plugin/commands \
packages/boxel-cli/plugin/README.md \
Expand Down
21 changes: 21 additions & 0 deletions packages/boxel-cli/plugin/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "boxel-cli",
"version": "0.10.1",
"description": "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).",
"skills": "./skills/",
"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",
"interface": {
"displayName": "Boxel CLI",
"shortDescription": "Author and sync Boxel cards, realms, and workspaces",
"longDescription": "Create and edit Boxel cards, fields, and templates, then sync them between local disk and a Boxel realm — with skills covering card authoring, theming, catalog listings, federated search, and realm indexing diagnostics.",
"developerName": "Cardstack",
"category": "Developer Tools",
"websiteURL": "https://boxel.ai"
Comment thread
jurgenwerk marked this conversation as resolved.
}
}
33 changes: 27 additions & 6 deletions packages/boxel-cli/plugin/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# `boxel-cli` Claude Code plugin
# `boxel-cli` agent plugin

Claude Code skills for working with Boxel realms via [`@cardstack/boxel-cli`](https://www.npmjs.com/package/@cardstack/boxel-cli).
Agent skills for working with Boxel realms via [`@cardstack/boxel-cli`](https://www.npmjs.com/package/@cardstack/boxel-cli). Packaged for both Claude Code (`.claude-plugin/`) and OpenAI Codex (`.codex-plugin/`); the two manifests share the same `skills/` directory.

## Prerequisites

Expand All @@ -20,23 +20,44 @@ The plugin documents commands in `@cardstack/boxel-cli >= 0.0.1`. Newer plugin v

## Install

### External users (marketplace)
### Claude Code

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

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

From a checkout of `cardstack/boxel`:
For internal development, from a checkout of `cardstack/boxel`:

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

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

### OpenAI Codex

Codex discovers the plugin through the marketplace manifest at
`.agents/plugins/marketplace.json` in the repo root:

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

Skills are invoked with the `$` prefix (`$boxel`, `$realm-sync`, …) or picked
up implicitly by description match.
Comment thread
jurgenwerk marked this conversation as resolved.
Outdated

Without installing the plugin, a checkout also works directly: Codex reads
skills from `~/.agents/skills` (or a project's `.agents/skills`), expecting
`<name>/SKILL.md` one level down. Copy each skill in — Codex does not follow
symlinked skill directories:

```bash
mkdir -p ~/.agents/skills
cp -R /path/to/boxel/packages/boxel-cli/plugin/skills/*/ ~/.agents/skills/
```

## What you get

Skills appear under the `/boxel-cli:` namespace. Two surfaces:
Expand Down
56 changes: 54 additions & 2 deletions packages/boxel-cli/scripts/build-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
/**
* Generate the `<!-- generated:commands -->` synopsis blocks in the plugin's
* SKILL.md files from the Commander program. Run via `pnpm build:plugin`.
* SKILL.md files from the Commander program, and sync the Codex plugin
* manifest (`.codex-plugin/plugin.json`) from the Claude Code one. Run via
* `pnpm build:plugin`.
*
* CI runs this and `git diff --exit-code` to fail PRs whose CLI changes
* weren't reflected in the plugin. The "synopsis-bump coupling" check (see
* .github/workflows/ci-lint.yaml) further ensures `plugin.json` is bumped
* whenever a generated block changes.
*/
import type { Command, Option } from 'commander';
import { readFileSync, writeFileSync } from 'fs';
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
import { resolve } from 'path';
import { buildBoxelProgram } from '../src/build-program.ts';

const PLUGIN_DIR = resolve(import.meta.dirname, '..', 'plugin');
const CLAUDE_MANIFEST_PATH = resolve(
PLUGIN_DIR,
'.claude-plugin',
'plugin.json',
);
const CODEX_MANIFEST_PATH = resolve(PLUGIN_DIR, '.codex-plugin', 'plugin.json');

interface SkillSpec {
/** Skill folder name under plugin/skills/ */
Expand Down Expand Up @@ -174,6 +182,46 @@ function rewriteSkillFile(skill: string, body: string): boolean {
return true;
}

/**
* Derive `.codex-plugin/plugin.json` from `.claude-plugin/plugin.json` so the
* two manifests can't drift: name/version/metadata come from the Claude
* manifest (which the publish workflow bumps in both files), while the
* description, `skills` pointer, and `interface` block are Codex-specific.
*/
function syncCodexManifest(): boolean {
const claude = JSON.parse(readFileSync(CLAUDE_MANIFEST_PATH, 'utf8'));
const codex = {
name: claude.name,
version: claude.version,
description: claude.description.replace(/^Claude Code skills/, 'Skills'),
skills: './skills/',
Comment thread
jurgenwerk marked this conversation as resolved.
author: claude.author,
homepage: claude.homepage,
repository: claude.repository,
license: claude.license,
interface: {
displayName: 'Boxel CLI',
shortDescription: 'Author and sync Boxel cards, realms, and workspaces',
longDescription:
'Create and edit Boxel cards, fields, and templates, then sync them ' +
'between local disk and a Boxel realm — with skills covering card ' +
'authoring, theming, catalog listings, federated search, and realm ' +
'indexing diagnostics.',
developerName: 'Cardstack',
category: 'Developer Tools',
websiteURL: 'https://boxel.ai',
},
};
const next = JSON.stringify(codex, null, 2) + '\n';
const prior = existsSync(CODEX_MANIFEST_PATH)
? readFileSync(CODEX_MANIFEST_PATH, 'utf8')
: null;
if (next === prior) return false;
mkdirSync(resolve(PLUGIN_DIR, '.codex-plugin'), { recursive: true });
writeFileSync(CODEX_MANIFEST_PATH, next);
return true;
}

function main(): void {
const program = buildBoxelProgram('0.0.0');
let changed = 0;
Expand All @@ -184,6 +232,10 @@ function main(): void {
console.log(`updated plugin/skills/${spec.skill}/SKILL.md`);
}
}
if (syncCodexManifest()) {
changed++;
console.log('updated plugin/.codex-plugin/plugin.json');
}
console.log(
changed === 0
? 'Plugin synopsis already up to date.'
Expand Down
Loading