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
24 changes: 24 additions & 0 deletions .agents/skills/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "data-client",
"displayName": "Reactive Data Client",
"description": "Sets up Data Client, then points your agent at the skills shipped in your installed @data-client packages, so React or Vue apps get the guidance for their version",
"author": {
"name": "Nathaniel Tucker",
"email": "me@ntucker.me"
},
"homepage": "https://dataclient.io/docs/getting-started/agent-skills",
"repository": "https://github.com/reactive/data-client",
"license": "Apache-2.0",
"keywords": [
"data-client",
"react",
"vue",
"rest",
"graphql",
"normalized-cache"
],
"skills": ["./data-client", "./data-client-setup"],
"icon": "./assets/logo.svg",
"documentationUrl": "https://dataclient.io/docs/getting-started/agent-skills",
"supportUrl": "https://github.com/reactive/data-client/issues"
}
6 changes: 3 additions & 3 deletions .agents/skills/.cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"name": "data-client",
"displayName": "Reactive Data Client",
"version": "1.0.0",
"description": "Sets up Data Client, then points your agent at the skills shipped in your installed @data-client packages, so React or Vue apps get the guidance for their version",
"author": {
"name": "Nathaniel Tucker",
Expand All @@ -18,8 +17,9 @@
"graphql",
"normalized-cache"
],
"skills": ["./data-client", "./data-client-setup"],
"version": "1.0.0",
"category": "developer-tools",
"tags": ["react", "vue", "data-fetching"],
"logo": "assets/logo.svg",
"skills": ["./data-client", "./data-client-setup"]
"logo": "assets/logo.svg"
}
55 changes: 55 additions & 0 deletions .agents/skills/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Reactive Data Client

[Reactive Data Client](https://dataclient.io) manages server state in React and Vue apps: fetching, caching,
mutations and real-time updates, all kept consistent through a normalized store. This plugin teaches your coding
agent to use it well.

The plugin carries two skills:

- **`data-client`** starts any task in an app that uses Data Client, or that should. It sends the agent to the
skill for the work at hand: schemas, REST endpoints, React hooks, Vue composables, tests, or real-time managers.
- **`data-client-setup`** adds Data Client to an app. It runs only when you ask for it. It detects your framework
(NextJS, Expo, React Native, Vue or plain React) and protocol (REST, GraphQL or other async code), tells you which
packages it will install, installs them with your package manager, adds the provider, and follows the setup guide
for your protocol.

Every other skill ships inside the `@data-client/*` package it covers, so its guidance always matches the version
your project installs. The setup skill links them into your project's `.agents/skills` folder (and `.claude/skills`)
by running `npx --no data-client-skills`, which uses only your installed packages and never downloads one.

## Install

In Claude Code:

```
/plugin install data-client --marketplace reactive/data-client
```

In Cursor's agent chat:

```
/add-plugin data-client@https://github.com/reactive/data-client
```

In Codex:

```bash
codex plugin marketplace add reactive/data-client
codex plugin add data-client@data-client
```

Then ask your agent to set up Data Client, or start the setup skill yourself: `/data-client:data-client-setup` in
Claude Code, `/data-client-setup` in Cursor, `$data-client:data-client-setup` in Codex.

## What it runs

The plugin has no hooks, MCP servers or scripts of its own, and it sends no data anywhere. When you ask it to set up
Data Client, the agent runs your package manager to install the `@data-client/*` packages and runs the
`data-client-skills` command those packages include. Projects on packages older than 0.19 install the skills with
the [skills CLI](https://www.npmjs.com/package/skills) instead.

## Learn more

- [Agent skills guide](https://dataclient.io/docs/getting-started/agent-skills)
- [Documentation](https://dataclient.io/docs)
- [Source](https://github.com/reactive/data-client), licensed under Apache-2.0
39 changes: 17 additions & 22 deletions .agents/skills/assets/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 21 additions & 5 deletions .claude-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,28 @@ endpoint, rest, graphql, react and vue, implemented in `packages/core/agents/` a
| File | What |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `marketplace.json` | The marketplace Claude Code reads (`claude plugin marketplace add reactive/data-client`). `data-client` is the plugin; `core`, `react`, `vue` and `migrations` are old names kept as copies so existing installs keep updating (`plans/next-breaking-release.md` removes them). |
| `cursor.mjs` | Writes Cursor's copy of the plugin (see below). |
| `*.test.mjs` | `node --test '.claude-plugin/*.test.mjs'`: `marketplace.json` and the Cursor copy against the repo. |
| `plugin-dir.mjs` | Writes the manifests that make `.agents/skills` a self-contained copy of the plugin, for Cursor and Claude's plugin directory (see below). |
| `openai-zip.mjs` | Packs the plugin as the ZIP OpenAI's plugin directory uploads (see below). |
| `*.test.mjs` | `node --test '.claude-plugin/*.test.mjs'`: `marketplace.json` and the plugin directory copies against the repo. |

## Codex and Cursor

Codex reads `marketplace.json` as is (`codex plugin add data-client@data-client`). Cursor plugins can't reach files outside their folder, so `cursor.mjs` writes a copy of the `data-client` entry rooted at `.agents/skills` (`.cursor-plugin/marketplace.json`, `.agents/skills/.cursor-plugin/plugin.json`), and `marketplace.test.mjs` fails when they drift. The marketplace source is a `git-subdir` pinned to `master`: Cursor's `/add-plugin` otherwise checks out `main`, which this repo does not have. Cursor's published `marketplace.schema.json` still types `source` as a string; the object form comes from its plugin docs, and the Cursor agent's install resolved it.
Codex reads `marketplace.json` as is (`codex plugin add data-client@data-client`). Cursor plugins can't reach files outside their folder, so `plugin-dir.mjs` writes a copy of the `data-client` entry rooted at `.agents/skills` (`.cursor-plugin/marketplace.json`, `.agents/skills/.cursor-plugin/plugin.json`, and the `.agents/skills/.claude-plugin/plugin.json` Claude's directory reads), and `marketplace.test.mjs` fails when they drift. The marketplace source is a `git-subdir` pinned to `master`: Cursor's `/add-plugin` otherwise checks out `main`, which this repo does not have. Cursor's published `marketplace.schema.json` still types `source` as a string; the object form comes from its plugin docs, and the Cursor agent's install resolved it.

## Public listings

Each listing reads the plugin from `.agents/skills`, whose `README.md` is the listing's description.

- **Claude's plugin directory** ([claude.ai/directory/manage](https://claude.ai/directory/manage), plugin path
`.agents/skills`, branch `master`) reads `.agents/skills/.claude-plugin/plugin.json`, written without a `version` so
every commit is an update. The `agent-rules` workflow checks its manifest and skills with `claude plugin validate ./.agents/skills`; the
directory's safety scan and policy review run only on submission. The
directory refuses repositories whose GitHub archive passes 50 MiB.
- **Cursor Marketplace** ([cursor.com/marketplace/publish](https://cursor.com/marketplace/publish)) reviews the
repository link and `.cursor-plugin/marketplace.json`.
- **OpenAI's plugin directory** (ChatGPT and Codex) takes an uploaded ZIP, not the repository:
`node .claude-plugin/openai-zip.mjs` writes `data-client-plugin.zip`, versioned by its build time. Upload a new one whenever the plugin's skills
change.

## What it reads from the rest of the repo

Expand All @@ -27,5 +43,5 @@ Codex reads `marketplace.json` as is (`codex plugin add data-client@data-client`
## CI

The `agent-rules` workflow runs these tests and the `data-client-skills` tests (`packages/core/agents/*.test.mjs`), with
no install. Its `paths` cover this directory and the inputs above, and `marketplace.test.mjs` fails when the old names
drift from `data-client` or a plugin skill goes missing or stops shipping in a package. `cursor-plugin.test.mjs` checks that the Cursor manifests still install.
no install, then fetches a pinned Claude Code CLI for `claude plugin validate`. Its `paths` cover this directory and the inputs above, and `marketplace.test.mjs` fails when the old names
drift from `data-client` or a plugin skill goes missing or stops shipping in a package. `plugin-dir.test.mjs` checks the skill names, logo and README the plugin directory listings read.
82 changes: 0 additions & 82 deletions .claude-plugin/cursor.mjs

This file was deleted.

8 changes: 4 additions & 4 deletions .claude-plugin/marketplace.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ test('plugin skills exist and ship in a package', () => {
}
});

test('the Cursor plugin matches marketplace.json', async () => {
// regenerate with node .claude-plugin/cursor.mjs
const { cursorFiles } = await import('./cursor.mjs');
for (const [file, json] of Object.entries(cursorFiles()))
test('the plugin directory matches marketplace.json', async () => {
// regenerate with node .claude-plugin/plugin-dir.mjs
const { pluginDirFiles } = await import('./plugin-dir.mjs');
for (const [file, json] of Object.entries(pluginDirFiles()))
assert.deepEqual(readJSON(file), json, file);
});
45 changes: 45 additions & 0 deletions .claude-plugin/openai-zip.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Packs the `data-client` plugin as the ZIP OpenAI's plugin directory
// (ChatGPT and Codex) uploads: node .claude-plugin/openai-zip.mjs [out.zip]
// The directory takes a Claude plugin archive with each skill at
// skills/<name>/SKILL.md, and doesn't follow the repo, so upload a new ZIP
// whenever the plugin's skills change.
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';

import { pluginDirFiles, root } from './plugin-dir.mjs';

const skills = path.join(root, '.agents/skills');
const out = path.resolve(process.argv[2] ?? 'data-client-plugin.zip');
const manifest = pluginDirFiles()['.agents/skills/.claude-plugin/plugin.json'];
// OpenAI requires a semver `version` that grows with each upload: the UTC
// date and time of the build. The repo's manifest has none so Claude Code
// updates on every commit.
const [date, time] = new Date().toISOString().replace(/[-:]/g, '').split('T');
const version = `1.${date}.${Number(time.slice(0, 6))}`;
const stage = fs.mkdtempSync(path.join(os.tmpdir(), 'data-client-plugin-'));

try {
for (const skill of manifest.skills)
fs.cpSync(path.join(skills, skill), path.join(stage, 'skills', skill), {
recursive: true,
// repo tooling, left out like scripts/pack-skills.mjs does
filter: src => !['evals', 'references.json'].includes(path.basename(src)),
});
for (const file of ['README.md', manifest.icon])
fs.cpSync(path.join(skills, file), path.join(stage, file));
fs.cpSync(path.join(root, 'LICENSE'), path.join(stage, 'LICENSE'));
fs.mkdirSync(path.join(stage, '.claude-plugin'));
fs.writeFileSync(
path.join(stage, '.claude-plugin/plugin.json'),
`${JSON.stringify({ ...manifest, version, skills: './skills/' }, null, 2)}\n`,
);

fs.rmSync(out, { force: true });
// needs the zip CLI
execFileSync('zip', ['-qrX', out, '.'], { cwd: stage });
} finally {
fs.rmSync(stage, { recursive: true });
}
console.log(`Wrote ${path.relative(process.cwd(), out)}`);
Loading
Loading