diff --git a/README.md b/README.md index 12ac9c52..1b7d3d9b 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ Using the GitHub Action? `uses: dgesteves/onsystem@v0` checks the lines a pull r **Also works with** [![Codex CLI](https://img.shields.io/badge/Codex_CLI-0d0f12?style=flat-square)](https://onsystem.vercel.app/docs/clients#codex-cli) [![Gemini CLI](https://img.shields.io/badge/Gemini_CLI-0d0f12?style=flat-square&logo=googlegemini&logoColor=white)](https://onsystem.vercel.app/docs/clients#gemini-cli) +[![Antigravity](https://img.shields.io/badge/Antigravity-0d0f12?style=flat-square)](https://onsystem.vercel.app/docs/clients#antigravity-cli-and-ide) [![Grok Build](https://img.shields.io/badge/Grok_Build-0d0f12?style=flat-square)](https://onsystem.vercel.app/docs/clients#grok-build) [![GitHub Copilot CLI](https://img.shields.io/badge/GitHub_Copilot_CLI-0d0f12?style=flat-square&logo=githubcopilot&logoColor=white)](https://onsystem.vercel.app/docs/clients#github-copilot-cli) [![Claude Desktop](https://img.shields.io/badge/Claude_Desktop-0d0f12?style=flat-square&logo=claude&logoColor=white)](https://onsystem.vercel.app/docs/clients#claude-desktop) @@ -91,7 +92,7 @@ After each Write or Edit of a `.tsx` or `.jsx` file, the hook runs `onsystem che **Other agents.** `npx skills add dgesteves/onsystem` adds the skill to any agent with Agent Skills, and there are [plugins for Codex, GitHub Copilot CLI, VS Code and Cursor](https://onsystem.vercel.app/docs/clients#plugins-for-other-agents). -**Any MCP client.** Run `npx -y onsystem` as a stdio server. There are [configs for Claude Desktop, Codex CLI, GitHub Copilot CLI, Windsurf, JetBrains IDEs, Zed, Gemini CLI and Grok Build](https://onsystem.vercel.app/docs/clients#other-clients). +**Any MCP client.** Run `npx -y onsystem` as a stdio server. There are [configs for Claude Desktop, Codex CLI, GitHub Copilot CLI, Windsurf, JetBrains IDEs, Zed, Gemini CLI, Antigravity and Grok Build](https://onsystem.vercel.app/docs/clients#other-clients). **CI.** Install it as a dev dependency, accept the findings the codebase already has once, and fail pull requests only on new ones: diff --git a/docs/clients.md b/docs/clients.md index 64a26109..23e95483 100644 --- a/docs/clients.md +++ b/docs/clients.md @@ -1,12 +1,28 @@ # Set up your agent -The server speaks MCP over stdio, in both protocol eras: the 2026-07-28 revision (stateless, with `server/discover`) and the 2025 one (the `initialize` handshake), the era chosen by the client's first message. It finds the project from `--root`, a config file in the working directory, or the working directory itself when it holds a project (a `package.json` or `components.json`). MCP roots, which 2026-07-28 deprecates, are the fallback: when the working directory is none of those (Claude Desktop starts servers in `/`), a 2025-era client that reports roots has its first `file://` root used. When none of these gives a project (a client that starts servers in your home or the plugin's folder and reports no roots), each tool call's project is found from the absolute path it names, `path` (or `filename`, for `check_ui` with `code`): the nearest folder at or above it with a `package.json`, `components.json` or config, or the monorepo root above that, but never your home folder or the filesystem root. A project found this way loads a JSON config (`onsystem.config.json`) only: the server runs a JavaScript or TypeScript config only in a project it was started in. Until a call names one, the tools return an error that asks for it; after that, a call without one answers from the project found, as long as only one has been. Started at a [monorepo root](configuration.md#monorepo-roots), it serves every app and design-system package in it: the tools take the `path` of the file the agent is editing and answer for that file's project. +Pick your client below and add the server. Run it from the project, or pass `--root` with the app's absolute path: [how the server finds your project](#how-the-server-finds-your-project) has the rest. Every client gets the same five tools, including `check_ui`, which the agent runs on its own output. Claude Code and Codex CLI also run the [plugin](plugin.md)'s hook, which checks each edit as it happens, so there use the plugin instead; with other agents, add the [CI check](ci.md) so what the agent skips still gets caught. +> [!TIP] +> **One `.mcp.json` for the team.** Commit this file at the repository root, and Claude Code, VS Code, GitHub Copilot CLI and Grok Build start the server from it once each person trusts the folder or approves the server: +> +> ```json +> { +> "mcpServers": { +> "onsystem": { +> "command": "npx", +> "args": ["-y", "onsystem"] +> } +> } +> } +> ``` +> +> It needs no `--root`: the server finds the project from the folder it starts in, the roots the client reports, or the files the tools are given. In Claude Code it registers the same tools as the plugin, so use it there instead of the plugin, not next to it; [Roll out to a team](rollout.md#give-every-agent-the-design-system) commits the plugin, hook included. Cursor and the other clients read their own files, below. + ## Plugins for other agents -The skill and the MCP server also come packaged for other agents, pinned to the same release as the Claude Code plugin. Only the Claude Code plugin has the hook that checks each edit, and Codex runs it too; elsewhere, the [CI check](ci.md) catches what the agent skips. +The skill and the MCP server also come packaged for other agents, pinned to the same release as the Claude Code plugin. The hook that checks each edit runs in Claude Code and Codex CLI; elsewhere, the [CI check](ci.md) catches what the agent skips. - **Any agent with Agent Skills** (Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, OpenCode and others): the skill, from the repository. It goes to `.agents/skills/onsystem`, which most agents read, and is linked into `.claude/skills` for Claude Code. Add the MCP server with your client's config below. @@ -21,13 +37,14 @@ The skill and the MCP server also come packaged for other agents, pinned to the codex plugin add onsystem@dgesteves ``` -- **GitHub Copilot CLI:** the [Agent Plugins 1.0](https://github.com/agentplugins/agent-plugins-spec) package, with the server and the skill: +- **GitHub Copilot CLI:** Copilot CLI reads the same marketplace as Claude Code and installs that plugin's server and skill. Install it from the marketplace: Copilot CLI has deprecated installing a plugin straight from a repository. ```sh - copilot plugin install dgesteves/onsystem:plugins/onsystem-agent + copilot plugin marketplace add dgesteves/onsystem + copilot plugin install onsystem@dgesteves ``` -- **VS Code** (GitHub Copilot agent plugins): run **Chat: Install Plugin From Source** from the Command Palette and enter `dgesteves/onsystem`. VS Code reads the same marketplace as Claude Code and installs that plugin's server and skill. To use the Agent Plugins package instead, add a clone's `plugins/onsystem-agent` folder to the `chat.pluginLocations` setting. +- **VS Code** (GitHub Copilot agent plugins): run **Chat: Install Plugin From Source** from the Command Palette and enter `dgesteves/onsystem`. VS Code reads the same marketplace as Claude Code and installs that plugin's server and skill. To use the [Agent Plugins 1.0](https://github.com/agentplugins/agent-plugins-spec) package instead, add a clone's `plugins/onsystem-agent` folder to the `chat.pluginLocations` setting. - **Cursor:** the Cursor plugin, in `plugins/onsystem-cursor`. Until it is listed in the Cursor Marketplace, copy that folder to `~/.cursor/plugins/local/onsystem` and run **Developer: Reload Window**. A client that loads the Agent Plugins format starts the server in the plugin's folder rather than in your project, so the server finds the project through the workspace roots the client reports, as VS Code and Cursor do. If a client reports none, the server finds the project from the absolute path of the file the agent passes to the tools, and asks for one when a call has none. To have it found from the start, add the server with the client's MCP config below instead. @@ -138,7 +155,7 @@ That writes `~/.copilot/mcp-config.json`: } ``` -In a session, `/mcp show onsystem` lists its tools. +In a session, `/mcp show onsystem` lists its tools. Copilot CLI also reads the team's `.mcp.json` (or `.github/mcp.json`) in a folder you trust. @@ -206,7 +223,31 @@ In Zed's `settings.json`:
Gemini CLI -`.gemini/settings.json` in the project, or `~/.gemini/settings.json` for every project: +```sh +gemini mcp add -s user onsystem npx -y onsystem --root /absolute/path/to/app +``` + +That writes `~/.gemini/settings.json`, for every project; without `-s user`, it writes `.gemini/settings.json` in the project: + +```json +{ + "mcpServers": { + "onsystem": { + "command": "npx", + "args": ["-y", "onsystem", "--root", "/absolute/path/to/app"] + } + } +} +``` + +Gemini CLI starts MCP servers only in trusted folders: trust the project when Gemini CLI asks, or later with `/permissions`. Until then, `gemini mcp list` shows onsystem as Disabled, even from the user file. A headless run (`gemini -p`) cannot ask: pass `--skip-trust`, or set `GEMINI_CLI_TRUST_WORKSPACE=true`. + +
+ +
+Antigravity (CLI and IDE) + +Antigravity CLI replaced Gemini CLI for unpaid-tier and Google One users in June 2026, and it keeps MCP servers in a different file: the CLI and the IDE both read `~/.gemini/config/mcp_config.json` for every project, or `.agents/mcp_config.json` in the project. In the IDE, open the file from the **…** menu of the agent panel: **MCP Servers**, then **Manage MCP Servers**, then **View raw config**. In the CLI, `/mcp` opens the MCP manager. ```json { @@ -249,21 +290,18 @@ Use this instead of the plugin, not next to it: both register the same tools. claude mcp add onsystem -- npx -y onsystem ``` -To share it with your team, add `--scope project`, which writes `.mcp.json` at the repository root: - -```json -{ - "mcpServers": { - "onsystem": { - "command": "npx", - "args": ["-y", "onsystem"] - } - } -} -``` +To share it with your team, add `--scope project`, which writes the same entry to `.mcp.json` at the repository root: the file at the top of this page.
Any other MCP client: run `npx -y onsystem --root /absolute/path/to/app` as a stdio server. On native Windows, wrap it as `cmd /c npx ...`. The server sends usage instructions in its `initialize` result and in `server/discover`. Clients that ignore them benefit from one line in `CLAUDE.md`, `AGENTS.md` or `.cursor/rules`: _"Before writing UI, use the onsystem tools. Run check_ui on every file you change and fix all errors."_ Run in a terminal, `onsystem init` offers to add a short section that says so to the project's `AGENTS.md` or `CLAUDE.md`, with `npx -y onsystem check ` for agents without the tools. It sits between `BEGIN:onsystem-agent-rules` and `END:onsystem-agent-rules` comment markers, which a later run updates in place. + +## How the server finds your project + +The server finds the project from `--root`, a config file in the working directory, or the working directory itself when it holds a project (a `package.json` or `components.json`). Started at a [monorepo root](configuration.md#monorepo-roots), it serves every app and design-system package in it: the tools take the `path` of the file the agent is editing and answer for that file's project. + +MCP roots, which the 2026-07-28 protocol revision deprecates, are the fallback: when the working directory holds no project (Claude Desktop starts servers in `/`), a 2025-era client that reports roots has its first `file://` root used. When none of these gives a project (a client that starts servers in your home or the plugin's folder and reports no roots), each tool call's project is found from the absolute path it names, `path` (or `filename`, for `check_ui` with `code`): the nearest folder at or above it with a `package.json`, `components.json` or config, or the monorepo root above that, but never your home folder or the filesystem root. A project found this way loads a JSON config (`onsystem.config.json`) only: the server runs a JavaScript or TypeScript config only in a project it was started in. Until a call names one, the tools return an error that asks for it; after that, a call without one answers from the project found, as long as only one has been. + +The server speaks MCP over stdio, in both protocol eras: the 2026-07-28 revision (stateless, with `server/discover`) and the 2025 one (the `initialize` handshake), the era chosen by the client's first message. diff --git a/site/test/clients-page.test.ts b/site/test/clients-page.test.ts new file mode 100644 index 00000000..81b39d44 --- /dev/null +++ b/site/test/clients-page.test.ts @@ -0,0 +1,56 @@ +import { createElement } from 'react'; +import { renderToStaticMarkup } from 'react-dom/server'; +import { describe, expect, it, vi } from 'vitest'; + +import ClientsPage from '@/app/docs/clients/page'; + +vi.mock('next/navigation', () => ({ usePathname: () => '/docs/clients' })); + +// /docs/clients is the setup page for every agent but Claude Code with the plugin: after the +// install buttons comes the setup, and how the server finds the project waits for the end. + +const html = renderToStaticMarkup(createElement(ClientsPage)); +const text = (fragment: string) => + fragment + .replace(/<[^>]+>/g, ' ') + .replace(/'/g, "'") + .replace(/"/g, '"') + .replace(/&/g, '&') + .replace(/\s+/g, ' ') + .trim(); +const at = (id: string) => { + const index = html.indexOf(`id="${id}"`); + if (index === -1) throw new Error(`no element with id ${id}`); + return index; +}; + +describe('/docs/clients', () => { + it('opens with the setup, right after the install buttons', () => { + // From the install buttons, the page's first links, to its first section. + const body = html.slice(html.indexOf('Install in VS Code'), at('plugins-for-other-agents')); + const lead = text(/

([\s\S]*?)<\/p>/.exec(body)?.[1] ?? ''); + expect(lead).toContain('Run it from the project, or pass --root'); + expect(lead.split(' ').length).toBeLessThanOrEqual(50); + expect(text(body)).not.toContain('2026-07-28'); + // The one .mcp.json, as a box above the first section. + expect(body).toMatch(/role="note"[\s\S]*\.mcp\.json/); + }); + + it('explains how the server finds the project after every client, before the docs search', () => { + const ids = [...html.matchAll(/

m[1]); + expect(ids.slice(-2)).toEqual([ + 'how-the-server-finds-your-project', + 'search-these-docs-from-your-agent', + ]); + const protocol = html.indexOf('2026-07-28'); + expect(protocol).toBeGreaterThan(at('how-the-server-finds-your-project')); + expect(at('other-clients')).toBeLessThan(at('how-the-server-finds-your-project')); + }); + + it('has the Antigravity and Gemini CLI setups under other clients', () => { + expect(at('antigravity-cli-and-ide')).toBeGreaterThan(at('other-clients')); + const gemini = text(html.slice(at('gemini-cli'), at('antigravity-cli-and-ide'))); + expect(gemini).toContain('gemini mcp add -s user onsystem npx -y onsystem'); + expect(gemini).toContain('only in trusted folders'); + }); +}); diff --git a/test/clients-docs.test.ts b/test/clients-docs.test.ts new file mode 100644 index 00000000..88280897 --- /dev/null +++ b/test/clients-docs.test.ts @@ -0,0 +1,102 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +import { describe, expect, it } from 'vitest'; + +import { blocks, section } from './markdown.js'; + +// docs/clients.md (/docs/clients on the website) is the setup page for every agent but Claude +// Code with the plugin. It opens with the setup, offers one committed file that several clients +// read, and keeps each client's commands to what that client runs today. + +const root = path.resolve(import.meta.dirname, '..'); +const read = (file: string) => + fs.readFileSync(path.join(root, file), 'utf8').replace(/\r\n/g, '\n'); +const clients = read('docs/clients.md'); +const readme = read('README.md'); + +/** What the page says before its first `##` heading. */ +const intro = clients.slice(0, clients.indexOf('\n## ')); + +/** The body of the `
` block whose summary is `title`. */ +function details(title: string): string { + const start = clients.indexOf(`${title}`); + if (start === -1) throw new Error(`no
block for ${title}`); + return clients.slice(start, clients.indexOf('
', start)); +} + +/** The first JSON code block in `markdown`, parsed. */ +function json(markdown: string): unknown { + const [block] = blocks(markdown, 'json'); + if (block === undefined) throw new Error('no JSON code block'); + return JSON.parse(block); +} + +describe('docs/clients.md', () => { + it('opens with the setup, and leaves how the server finds the project for the end', () => { + const lead = intro.split('\n\n')[1] ?? ''; + expect(lead.split(/\s+/).length).toBeLessThanOrEqual(50); + expect(lead).toContain('Run it from the project, or pass `--root`'); + expect([...clients.matchAll(/^## (.+)$/gm)].map((m) => m[1]).at(-1)).toBe( + 'How the server finds your project', + ); + // The protocol eras and the roots fallback come after every client's setup. + const finding = section(clients, 'How the server finds your project'); + for (const detail of ['2026-07-28', '`initialize`', '`file://`', 'MCP roots']) { + expect(intro, detail).not.toContain(detail); + expect(finding, detail).toContain(detail); + } + }); + + it('offers one .mcp.json to commit, at the top, naming the clients that read it', () => { + const box = /^> \[!TIP\]\n((?:>.*\n)+)/m.exec(intro)?.[1]?.replace(/^> ?/gm, '') ?? ''; + expect(box).toContain('`.mcp.json`'); + for (const client of ['Claude Code', 'VS Code', 'GitHub Copilot CLI', 'Grok Build']) { + expect(box, client).toContain(client); + } + expect(json(box)).toEqual({ + mcpServers: { onsystem: { command: 'npx', args: ['-y', 'onsystem'] } }, + }); + // The Claude Code plugin registers the same tools: one or the other, not both. + expect(box).toMatch(/instead of the plugin/); + }); + + it('installs the Copilot CLI plugin from the marketplace, not by the deprecated direct install', () => { + const plugins = section(clients, 'Plugins for other agents'); + expect(plugins).toContain( + '```sh\n copilot plugin marketplace add dgesteves/onsystem\n copilot plugin install onsystem@dgesteves\n ```', + ); + expect(clients).not.toMatch(/copilot plugin install dgesteves\//); + }); + + it('says the hook runs in Codex too, on the files apply_patch changes', () => { + expect(clients).not.toMatch(/only (?:the )?Claude Code(?: plugin)? has the hook/i); + expect(intro).toContain('Claude Code and Codex CLI'); + expect(section(clients, 'Plugins for other agents')).toContain('`apply_patch`'); + }); + + it('adds Gemini CLI from the terminal, and says it starts servers only in trusted folders', () => { + const gemini = details('Gemini CLI'); + expect(gemini).toContain( + 'gemini mcp add -s user onsystem npx -y onsystem --root /absolute/path/to/app', + ); + expect(gemini).toContain('only in trusted folders'); + expect(gemini).toContain('`/permissions`'); + expect(gemini).toContain('`--skip-trust`'); + }); + + it('has a block for Antigravity, which reads its own file, with the same server entry', () => { + const antigravity = details('Antigravity (CLI and IDE)'); + expect(antigravity).toContain('`~/.gemini/config/mcp_config.json`'); + expect(antigravity).toContain('`.agents/mcp_config.json`'); + expect(json(antigravity)).toEqual(json(details('Gemini CLI'))); + }); +}); + +describe('the README', () => { + it('lists Antigravity with the other clients', () => { + const top = readme.slice(0, readme.indexOf('')); + expect(top).toContain('](https://onsystem.vercel.app/docs/clients#antigravity-cli-and-ide)'); + expect(readme).toMatch(/\[configs for [^\]]*Antigravity[^\]]*\]/); + }); +});