# CLI

> The lit-devtools command: dev, mcp and build, with their flags and their optional peers.

The `lit-devtools` command runs the panel on its own or bridges it to a coding agent. It needs two optional peers, and it talks to a dev server you already started.

## Install the peers

`cac` powers the command parsing, so every subcommand needs it. `@devframes/agentic` is needed by `mcp` alone. Both are optional peer dependencies, imported only on the path that uses them.

```sh
npm install -D cac @devframes/agentic
```

Without them the command exits 1 with the name of the missing package:

```text
[lit-devtools] this command needs the optional peer "cac". Install it and retry:

  npm install -D cac
```

## lit-devtools dev

Starts a standalone devframe server, the small framework the panel is built on.

```sh
npx lit-devtools dev --port 5180 --host localhost --open
```

| Flag                      | Default       | Meaning                                                                  |
| ------------------------- | ------------- | ------------------------------------------------------------------------ |
| `--port <port>`           | `5180`        | Port to listen on                                                        |
| `--host <host>`           | `localhost`   | Host to bind to                                                          |
| `--open`                  | off           | Open the browser on start                                                |
| `--no-auth`               | auth on       | Skip the one-time-code gate so a page can connect without a token        |
| `--allow-origin <origin>` | loopback only | Also let pages from this origin (`*` in the host ok) connect. Repeatable |

The default port is deliberately not the playground’s 5179, so both can run against one checkout.

`--no-auth` only combines with a loopback host (`localhost`, `127.x.x.x`, `::1`). Bound anywhere else, the command refuses to start: other machines could otherwise drive the panel without a code.

### Feeding it a live page

Started alone, the panel is empty: the server has no page. At startup it prints a script tag; add it to a page, before the scripts that define your components:

```html
<script src="http://localhost:5180/lit-devtools.js"></script>
```

The page can be anything that runs Lit, not only a Vite dev server, and it can be on another origin. The script starts the runtime and connects it to the server, so the panel, MCP tools and recording work as they do under Vite DevTools.

* **Auth.** Without `--no-auth` the server prints a one-time code. The page prompts for it, or you can open the page once with `#devframe_otp=<code>` appended to its URL. The trusted token is remembered per page origin.
* **Origins.** Pages on a loopback origin (any port) may connect. For any other origin, start the server with `--allow-origin https://myapp.test:8443`; repeat the flag for more than one. A `*` in the host stands for any subdomain, for origins only known once they are up: `--allow-origin 'https://*.webcontainer-api.io'` admits every port of a StackBlitz project. Scheme and port still have to match, and the wildcard needs at least two literal labels after it (`https://*.com` is refused).
* **Picking.** **⌖ Pick** in the Components tab, or Ctrl/⌘+Shift+S on the page, starts a picker on the page that targets any Lit element. Clicking one selects it in the panel and brings a panel tab forward on it. A browser only lets a page raise a tab it opened itself, so the first pick opens a second panel tab if you opened the panel yourself; later picks reuse that tab.
* **Where components are defined.** If the page ships sourcemaps, the panel reads the original file and line from them and shows it in the details pane’s **defined** row, the **Updates** view and the timeline’s span details, as the [browser extension](/guides/devtools/browser-extension/#where-a-component-is-defined) does. The page fetches its own scripts and maps for the server, and only from its own origin; the server never requests a page’s URLs itself. It only sees components defined after `lit-devtools.js` runs, so keep the tag in `<head>`, before the app. The location is text: there is no editor to open it in.
* **Limits.** Outside Vite there are no build-time transforms, so HMR patching and open-in-editor are unavailable, and the picker shows a tag name, not a file. The tree, inspector and timeline work.

### Production builds

The script tag also attaches to a minified production build, with Lit resolved to its production condition. An end-to-end test (`src/test/e2e/standalone-production_test.ts`) covers it.

* **Works.** The component tree, the inspector (properties, controllers and `@lit/task` instances, although Lit mangles its private controller set in production) and Record with the lifecycle layer: `performUpdate`, `willUpdate`, `update` and `updated` are all captured.
* **Empty.** The render layers. Lit’s production build never dispatches `lit-debug` events, so there is nothing to record.
* **Unavailable.** Open-in-editor and HMR, as for any page Vite does not serve; source locations too unless the build ships sourcemaps.
* **Blocked.** A page whose Content Security Policy does not allow the server’s origin in `script-src` (for instance `script-src 'self'`) refuses the script, and nothing attaches.
* **Cosmetic.** Names the minifier renamed show as it left them: a plain controller’s type reads as a short identifier.

A page served by a Vite dev server can also connect from its own code:

```ts
import {connectToDevServer} from '@oddsquad/vite-plugin-lit/connect.js';

// Resolves once the connection is trusted. Call the returned function to
// disconnect and hand the page back to its own Vite server.
const disconnect = await connectToDevServer('http://localhost:5180/');
```

Pass `{authToken}` as the second argument to skip the code prompt. From another origin, also pass `{connectionMeta}` (the JSON the server serves at `/__connection.json`), since the browser blocks the page from fetching it.

## lit-devtools mcp

Starts a stdio MCP server that proxies the dev servers already running on your machine.

```sh
npx lit-devtools mcp --port 5180 --token "$DEVTOOLS_TOKEN"
```

| Flag              | Repeatable | Meaning                                               |
| ----------------- | ---------- | ----------------------------------------------------- |
| `--port <port>`   | yes        | Also probe this port for an instance discovery missed |
| `--token <token>` | no         | Bearer token for a dev server with its auth gate on   |

Discovery is the default path. The plugin publishes each running dev server to devframe’s instance registry, and the connector lists them through two gateway tools, `devframe_connect_list-instances` and `devframe_connect_call-tool`. One entry therefore covers every project you have running.

`--port` probes `<origin>/__connection.json` at the root path only. That finds a standalone `lit-devtools dev` server, but not a Vite-hosted one, which is mounted under `/__devtools/`. For those, rely on registry discovery.

When discovery is unavailable, bridge one fixed URL instead:

```sh
claude mcp add lit-devtools -- npx -y mcp-remote http://localhost:5179/__devtools/__mcp
```

## lit-devtools build

Prints where to export a snapshot, then exits 1. It never writes one.

A snapshot worth attaching to an issue is a recorded session, and the session lives in the memory of the dev server that recorded it. A fresh CLI process has no page, no timeline, and no component tree, so anything it built would be an empty shell. Record what you want to report in the panel, then press **Export snapshot** in the Timeline tab. The dev server writes a self-contained panel directory you can zip.

## Related

[Ask your coding agent](/guides/devtools/agents/)The MCP tools this command exposes, and what they answer.

[Compatibility](/reference/compatibility/)Which optional peer unlocks which feature.
