---
title: "Widget CLI"
description: "All CLI commands for creating, building, testing, and publishing GlassHome widgets."
canonical: https://glasshome.app/docs/widgets/widget-cli
section: "CLI & Publishing"
updated: 2026-08-16
---
# Widget CLI

The package is `@glasshome/widget-cli`; the binary is `glasshome-widget`. Scaffolded projects pin it in `devDependencies` behind a `"widget": "glasshome-widget"` script alias, so commands below read `bun widget <command>` (project-local, kept current with `bun update`). Elsewhere use `bunx @glasshome/widget-cli@latest <command>`; without `@latest`, `bunx` can reuse a stale cached version.

## Version requirements

Hub enforces a minimum CLI version, currently **0.5.0**, served from `/api/widgets/cli-version`. CLIs **0.4.14** and newer fetch it on `login` and `publish` and hard-stop below the floor with an actionable message instead of a cryptic OAuth or HTTP error; older CLIs lack the check, so the floor only protects clients that can read it.

On a "widget CLI is no longer supported" error, update:

```bash
# one-off, no install:
bunx @glasshome/widget-cli@latest publish

# or update the project dev dependency:
bun add -D @glasshome/widget-cli@latest
```

## Two auth flows

[`connect`](#connect) authenticates against your local Dash instance for live testing; [`login`](#login) against GlassHome Hub for publishing. Tokens are stored separately and are not interchangeable.

## Commands

  Scaffolds a widget project: `package.json`, `vite.config.ts`, `tsconfig`, and a starter widget whose details it prompts for (no extra `add` call needed). Run it outside an existing project.

  Prompts for a name and description, then creates `src/{name}/index.tsx` and `src/{name}/manifest.json`.

  Builds each widget into a self-contained JS bundle in `dist/` and generates `dist/registry.json` with metadata for all widgets.

  Checks manifests, bundle sizes, and registry consistency. Pass a name to check one widget, omit for all.

  Prints the project header, local registry summary, and per-widget metadata + bundle size.

 [--re-auth]">
  Live testing against a running Dash instance; widgets hot-reload as you edit. Full sequence in [Local Testing Workflow](#local-testing-workflow). Auth is an **OAuth device-code flow** against the local Dash instance; approve in the browser and the CLI stores a host-scoped bearer token. Distinct from `login`, which targets GlassHome Hub.
  Force a fresh device-authorization flow, discarding stored credentials.

  Validates, prompts for scope/widget/version (or takes flags), then builds and uploads via a presigned R2 URL. Runs `login` automatically if no token is found. Scopes come from `GET /api/widgets/scopes`: your personal scope (your username) plus any org scopes you can publish to. See [Publishing Scopes](/docs/widgets/widget-publishing#publishing-scopes).
  ">Publish only the named widget.
  Pre-pick the version bump; updates `version` in the widget's manifest.
  ">Pre-pick the publishing scope (e.g. `@my-team`).

  Authenticates with **GlassHome Hub** via OAuth PKCE (not local Dash): opens a browser, listens on `http://127.0.0.1:9274/callback` (120 s timeout), stores the token locally. `publish` runs it automatically when no token is present.

]">
  Moves the project to a newer `@glasshome/widget-sdk` and syncs the `sdkVersion` in every `manifest.json` to what is then installed. In a **standalone project**: `bun add`s the SDK at `latest` (or `--to <version>`) into whichever section already declares it, which also pulls in the `@glasshome/ui` it peers, then syncs the manifests and re-runs `bun widget validate`. In a **monorepo workspace** (a `package.json` with a `workspaces` field at or above the project): reads the SDK version from the workspace package, runs `bun install` from the root, syncs every manifest, then re-runs `bun widget validate`.

  Rewrites widgets' raw-Zod config (`z.object`, `widgetFields`) to the `defineConfig` + `field.*` API. Assistive, not lossy: fields it cannot translate stay raw Zod and are reported as manual TODOs; validation is never dropped.
  ">Migrate only the named widget.
  Preview without writing.

> **Global flag:** Use `--dir <path>` with any project-scoped command (`add`, `build`, `validate`, `info`, `connect`, `publish`, `upgrade`, `migrate`) to point at a different widget project directory. `create` ignores it; it scaffolds into a new directory under the current working directory.

## Local Testing Workflow

What `connect` does, in order:

- Builds all widgets.
- Runs the **OAuth device-code flow** against the Dash URL (browser opens to the Dash approval page).
- Uploads each bundle to the Dash API under the `local` scope.
- Registers the widgets via the Dash internal tRPC endpoint and enables dev mode.
- Watches `src/`; on save, rebuilds and re-uploads the affected widget.
- On `Ctrl+C`, unregisters the widgets and exits.

No local HTTP server is started; Dash talks to its own API for the bundle bytes.