Skip to content

CLI

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.

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.

Terminal window
npm install -D cac @devframes/agentic

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

Terminal window
[lit-devtools] this command needs the optional peer "cac". Install it and retry:
npm install -D cac

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

Terminal window
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.

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:

<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 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.

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:

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.

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

Terminal window
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:

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

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.