Skip to content
This repository was archived by the owner on Oct 14, 2026. It is now read-only.
Merged
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
14 changes: 14 additions & 0 deletions .changeset/products-and-auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"fulldev": minor
---

Work with every Fulldev product, and sign in per product with tokens in the OS keychain.

Breaking: commands now start with the product. `fulldev tools`, `fulldev call`, `fulldev instructions` and `fulldev form wait` are now `fulldev cms tools`, `fulldev cms call`, `fulldev cms instructions` and `fulldev cms form wait`, and the same commands work for `connect` and `scan`. `FULLDEV_URL` is replaced by `FULLDEV_CMS_URL`, `FULLDEV_CONNECT_URL` and `FULLDEV_SCAN_URL`. The old commands are gone.

- `fulldev login [product...]`, `fulldev logout [product...]` and `fulldev status` work per product, for all products by default.
- Sign-in uses Fulldev's pre-registered OAuth client, and each token is only valid for its product. Logout revokes the refresh token.
- Tokens are stored in the OS keychain, with a private file only where no keychain is available. The sign-in from 0.1.0 is removed; run `fulldev login cms` once.
- Without a terminal, commands never start a sign-in: they exit with 3 and name the login to run.
- Every error, including a wrong command, is one JSON object on stderr. A wrong command exits with 64. `--help` works for every command, and `fulldev <product> tools <name>` also shows the output schema and annotations.
- `fulldev cms form wait` stops at its timeout also in the middle of a request.
102 changes: 72 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
# Fulldev CLI

Edit your website through the [Fulldev CMS](https://cms.full.dev) from a terminal or an AI agent. The CLI is a thin client of the CMS's MCP server: it reads the tools, their input schemas, and the instructions from the server each time, so it stays in step with the CMS.
Use Fulldev products from a terminal or an AI agent. The CLI is a thin client of each product's MCP server: it reads the tools, their schemas, and the instructions from the server each time, so it stays in step with the product.

| Product | Server | For |
| --------- | ------------------------------ | ----------------------------------------------------------------------------------------- |
| `cms` | `https://cms.full.dev/mcp` | Editing your website through the [Fulldev CMS](https://cms.full.dev) |
| `connect` | `https://connect.full.dev/mcp` | Your business tools, such as Shopify, through [Fulldev Connect](https://connect.full.dev) |
| `scan` | `https://scan.full.dev/mcp` | Scanning websites with [Fulldev Scan](https://scan.full.dev), for administrators only |

## Install

Run it from npm. It needs Node.js 24 or later.

```sh
npx -y fulldev help
pnpm dlx fulldev help
npx -y fulldev --help
pnpm dlx fulldev --help
```

Or install it once:

```sh
npm install -g fulldev
fulldev help
fulldev --help
```

Add the agent skill, which tells an agent when and how to use the CLI:
Expand All @@ -27,59 +33,95 @@ npx skills add fulldotdev/cli
## Sign in

```sh
fulldev login
fulldev login # every product, one after the other
fulldev login cms # one product
fulldev status # per product: signed in, valid, email, organization, expiry
fulldev logout # every product
fulldev logout cms # one product
```

This opens your browser to sign in with your Fulldev account and choose your organization. Any other command starts a sign-in too when you are not signed in; add `--no-login` to fail with exit code 3 instead, and `--no-browser` to only print the link.
`fulldev login` opens your browser to sign in with your Fulldev account and choose your organization. Add `--no-browser` to only print the link.

```sh
fulldev status # who is signed in, and whether the sign-in still works
fulldev logout # forget the sign-in for this server
```
There is one Fulldev account for all products, but each product gets its own tokens, which only that product accepts. The browser remembers your account, so signing in to the next product is quick. The organization is chosen at sign-in; to switch organization, run `fulldev login <product>` again.

`fulldev logout` revokes the product's refresh token at the authorization server (access tokens cannot be revoked and expire within a day) and deletes them from this computer. When revoking fails, for example offline, it still deletes them and says so.

How it works:

The sign-in uses OAuth with PKCE and a redirect to `127.0.0.1`. The CLI registers itself with the authorization server the first time. Tokens are saved in `~/.config/fulldev/credentials.json` (or `$XDG_CONFIG_HOME/fulldev/credentials.json`), readable only by you, per server, and refreshed when they expire.
- Browser sign-in uses OAuth with PKCE (S256) and a redirect to `http://127.0.0.1:<random port>/callback`. It asks for the product's MCP URL as the resource, so the token is only valid for that product.
- The CLI is a pre-registered public OAuth client of [Clerk](https://clerk.full.dev). For another authorization server, such as the Clerk development instance behind a deploy preview, it registers a client itself the first time.
- Tokens are stored in the OS keychain (macOS Keychain, Windows Credential Manager, or the Secret Service on Linux) under the service `fulldev`, one entry per product server. Where no keychain is available, such as in many containers and CI runners, they go to `~/.config/fulldev/auth.json` (or `$XDG_CONFIG_HOME/fulldev/auth.json`), readable only by you; `fulldev status` says which one is used.
- Access tokens are refreshed when they expire. Parallel commands take turns through a lock file next to it, so they never refresh the same token twice.
- Version 0.1.0 kept a sign-in for the CMS in `~/.config/fulldev/credentials.json`. The first command of this version removes that file and asks you to sign in again with `fulldev login cms`.

Product commands never start a sign-in without a terminal (when stdin or stdout is not a TTY): they fail at once with exit code 3 and say which `fulldev login <product>` to run. At a terminal they open the browser sign-in themselves; `--no-login` turns that off.

## Use

```sh
fulldev instructions # how to use the tools, from the server
fulldev tools # name, title, and description of each tool
fulldev tools commit_files # one tool's description and input schema
fulldev call list_projects
fulldev call read_file '{"branchId":"<id>","path":"src/content/home.md"}'
fulldev call commit_files --file changes.json
echo '{"branchId":"<id>"}' | fulldev call get_preview -
fulldev form wait <branchId> <formId> --timeout 60
fulldev cms instructions # how to use the tools, from the server; read first
fulldev cms tools # name, title and description of each tool
fulldev cms tools commit_files # description, input schema, output schema, annotations
fulldev cms call list_projects
fulldev cms call read_file '{"branchId":"<id>","path":"src/content/home.md"}'
fulldev cms call commit_files --file changes.json
echo '{"branchId":"<id>"}' | fulldev cms call get_preview -
fulldev cms form wait <branchId> <formId> --timeout 60
fulldev connect tools
fulldev help cms call # help for any command
```

`call` prints the tool's result as JSON on stdout. When the tool fails, it prints the error as JSON on stderr and exits with 1.
`call` prints the tool's result as JSON on stdout. When the tool fails, it prints the error on stderr and exits with 1.

`fulldev cms form wait` keeps calling `wait_for_form` until the person sends the form, with a progress line on stderr each round, then prints the result. After `--timeout` minutes (default 30) it stops, also in the middle of a request, prints the form id, and exits with 2, so you can run it again.

## Output and exit codes

`form wait` keeps calling `wait_for_form` until the person sends the form, with a progress line on stderr each round, then prints the result. After `--timeout` minutes (default 30) it prints the form id and exits with 2, so you can run it again.
Data is JSON on stdout. `instructions` and help are plain text. Progress lines go to stderr. Every error, including a wrong command, is one JSON object on stderr:

```json
{
"error": {
"code": "SIGN_IN_REQUIRED",
"message": "Not signed in to Fulldev CMS. Run: fulldev login cms",
"command": "fulldev login cms"
}
}
```

Exit codes: 0 done, 1 error, 2 form wait timed out, 3 sign-in needed.
| Code | Meaning |
| ---- | ------------------------------- |
| 0 | Done |
| 1 | Error, from the tool or the CLI |
| 2 | `form wait` timed out |
| 3 | Sign-in needed |
| 64 | Usage error |

## Other servers
## Deploy previews

`--url <mcp url>` or `FULLDEV_URL` selects another server, such as a deploy preview of the CMS. The default is `https://cms.full.dev/mcp`. Each server has its own sign-in.
`--url <mcp url>` or `FULLDEV_<PRODUCT>_URL` (such as `FULLDEV_CMS_URL` or `FULLDEV_CONNECT_URL`) selects another server for one product. Each server has its own sign-in.

```sh
FULLDEV_URL=https://deploy-preview-12--fulldev-cms.netlify.app/mcp fulldev login
FULLDEV_CMS_URL=https://deploy-preview-12--fulldev-cms.netlify.app/mcp fulldev login cms
fulldev cms tools --url https://deploy-preview-12--fulldev-cms.netlify.app/mcp
```

## Develop

```sh
pnpm install
pnpm check # format, lint, type check, and tests
pnpm check # format, lint, type check, tests, and that dist/ is up to date
pnpm build # writes dist/cli.mjs
```

`dist/` is committed, because `npx github:fulldotdev/cli` runs it without a build step. Run `pnpm build` and commit `dist/` with every source change.

## License
A product is one entry in `src/products.ts`. The pre-registered OAuth client ids are in `clientIds` in `src/oauth.ts`.

[MIT](LICENSE)
`dist/` is committed, because `npx github:fulldotdev/cli` runs it without a build step. Run `pnpm build` and commit `dist/` with every source change.

## Releases

Every pull request that changes what users get adds a changeset with `pnpm changeset`. Merging to `main` opens a release pull request; merging that publishes the [`fulldev` package](https://www.npmjs.com/package/fulldev) to npm from GitHub Actions with trusted publishing and provenance, without an npm token. `dist/` stays committed, so `npx github:fulldotdev/cli` keeps working; `pnpm check` fails when it is out of date.

## License

[MIT](LICENSE)
Loading
Loading