Skip to content
Closed
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
83 changes: 80 additions & 3 deletions docs/content/docs/api-reference/cli.mdx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
title: "@openuidev/cli"
description: API reference for the OpenUI CLI to scaffold apps and generate system prompts or library specs.
description: API reference for the OpenUI CLI to scaffold apps, generate system prompts or library specs, and deploy projects.
---

A command-line tool for scaffolding OpenUI chat apps and generating system prompts, JSON schemas, or serialized library specs from library definitions.
A command-line tool for scaffolding OpenUI chat apps, generating system prompts, JSON schemas, or serialized library specs from library definitions, and deploying those apps.

## Installation

Expand Down Expand Up @@ -201,6 +201,80 @@ npx @openuidev/cli@latest create --name my-app --skill
npx @openuidev/cli@latest create --name my-app --no-skill
```

## `openui deploy`

Deploys an OpenUI project. The default platform supported is **Vercel**.

```
openui deploy [dir] [options]
```

**Arguments**

| Argument | Description |
| -------- | ---------------------------------------------- |
| `[dir]` | Project directory (default: current directory) |

**Options**

| Flag | Description |
| --------------------- | --------------------------------------------------------------------------- |
| `-y, --yes` | Skip confirmation prompts (also saves missing env keys to the Vercel project) |
| `--skip-env` | Do not pass or save local `.env` / `.env.local` values |
| `--no-interactive` | Skip prompts (implies `--yes`) |
| `--verbose` | Stream full Vercel build logs (hidden by default; failures print a log tail) |
| `--agent-name <name>` | Declare the invoking coding-agent slug (default: `unknown`) |

Extra flags after `deploy` are forwarded as-is to the target deployment platform, which validates them (for example `--prod` or `--force`). `--skip-env` is OpenUI-specific so it does not collide with Vercel's `--env KEY=value`.

Unlinked projects run `vercel link` first. Allowlisted keys from `.env` / `.env.local` that are missing on production, preview, or development can be saved to the project (prompted; auto-accepted with `--yes`). Existing project keys are left unchanged. The current deployment still receives those keys via `--env` / `--build-env`. Build logs are quiet by default; use `--verbose` to stream them.

**Examples**

```bash tab="pnpm" tab-group="pkg"
# Preview deploy from the project directory
pnpx @openuidev/cli@latest deploy

# Production deploy
pnpx @openuidev/cli@latest deploy --prod --yes

# Deploy a specific directory without forwarding local env
pnpx @openuidev/cli@latest deploy ./my-app --skip-env
```

```bash tab="bun" tab-group="pkg"
# Preview deploy from the project directory
bunx @openuidev/cli@latest deploy

# Production deploy
bunx @openuidev/cli@latest deploy --prod --yes

# Deploy a specific directory without forwarding local env
bunx @openuidev/cli@latest deploy ./my-app --skip-env
```

```bash tab="yarn" tab-group="pkg"
# Preview deploy from the project directory
yarn dlx @openuidev/cli@latest deploy

# Production deploy
yarn dlx @openuidev/cli@latest deploy --prod --yes

# Deploy a specific directory without forwarding local env
yarn dlx @openuidev/cli@latest deploy ./my-app --skip-env
```

```bash tab="npm" tab-group="pkg"
# Preview deploy from the project directory
npx @openuidev/cli@latest deploy

# Production deploy
npx @openuidev/cli@latest deploy --prod --yes

# Deploy a specific directory without forwarding local env
npx @openuidev/cli@latest deploy ./my-app --skip-env
```

## `openui generate`

Generates the system prompt **and** the serialized library spec from a file that exports a `createLibrary()` result. A single run emits both artifacts — they derive from the same `Library` instance, so they can never drift apart. Use the spec with `generateSystemPrompt` in backend routes; the prompt file supports static or legacy integrations.
Expand Down Expand Up @@ -377,7 +451,10 @@ and `detected_agent_name`, inferred best-effort from known product environment m
value can be spoofed, inherited, missing, or ambiguous, so neither should be treated as an
authentication or security signal. Every invocation gets an ephemeral, unpersisted `cli_run_id` so
its events can be correlated. For `create`, analytics also include `package_manager`, the
immediate-start selection, and best-effort dev-command start and result events. Failure events use
immediate-start selection, and best-effort dev-command start and result events. For `deploy`, analytics include the target (currently `vercel`), production vs preview, whether
local env was passed, CLI resolution source, and process status — not env
values, project paths, or command output.
Failure events use
bounded `failure_stage`, `error_class`, and `error_code` values instead of raw error messages.
Dependency failures distinguish peer, registry, network, install-script, workspace, and
package-compatibility errors. Process failures include duration, exit code, and signal; Cloud-auth
Expand Down
8 changes: 4 additions & 4 deletions docs/content/docs/api-reference/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ The OpenUI SDK is split into packages that build on each other:

- **`@openuidev/devtools`** — Development-only floating widget that surfaces the events captured by `@openuidev/observability`, with error messages and stack traces.

- **`@openuidev/cli`** — Command-line tool for scaffolding new OpenUI chat apps and generating system prompts or JSON schemas from library definitions.
- **`@openuidev/cli`** — Command-line tool for scaffolding new OpenUI chat apps, generating system prompts or JSON schemas from library definitions, and deploying projects.

## Choosing a package

Expand All @@ -44,7 +44,7 @@ The OpenUI SDK is split into packages that build on each other:
| Svelte integration | [`@openuidev/svelte-lang`](https://github.com/thesysdev/openui/tree/main/packages/svelte-lang) |
| Script-tag, CDN, or iframe embeds | [`@openuidev/browser-bundle`](https://github.com/thesysdev/openui/tree/main/packages/browser-bundle) |
| An in-app panel showing captured errors during development | [`@openuidev/devtools`](/docs/api-reference/devtools) |
| App scaffolding and prompt/schema generation from the command line | [`@openuidev/cli`](/docs/api-reference/cli) |
| App scaffolding, prompt/schema generation, and deploy from the command line | [`@openuidev/cli`](/docs/api-reference/cli) |

## Packages

Expand Down Expand Up @@ -100,7 +100,7 @@ The OpenUI SDK is split into packages that build on each other:
Development-only floating widget surfacing captured events with error messages and stack traces.
</Card>
<Card title="@openuidev/cli" href="/docs/api-reference/cli">
openui create (scaffold a Next.js app) and openui generate (system prompt + library spec from a
library definition).
openui create (scaffold a Next.js app), openui deploy, and openui generate (system prompt +
library spec from a library definition).
</Card>
</Cards>
49 changes: 46 additions & 3 deletions packages/openui-cli/README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
# @openuidev/cli

Command-line tools for starting OpenUI projects and generating model instructions from component libraries.
Command-line tools for starting OpenUI projects, generating model instructions from component libraries, and deploying apps to Vercel.

[![npm](https://img.shields.io/npm/v/@openuidev/cli)](https://www.npmjs.com/package/@openuidev/cli)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/thesysdev/openui/blob/main/LICENSE)

**Links:** [CLI docs](https://openui.com/docs/api-reference/cli) | [GitHub repo](https://github.com/thesysdev/openui)

It currently supports two workflows:
It currently supports:

- scaffolding a new OpenUI app from one of two templates:
- **OpenUI Cloud (recommended)** — hosted models with managed conversations, streaming, built-in tools, and ready-to-use report and presentation artifacts
- **Self-hosted** — bring an OpenAI-compatible model key and own the AI route and persistence
- keeping the default minimal SDK route or adding a LangGraph or Vercel AI SDK backend to either template
- generating a system prompt or JSON Schema from a `createLibrary()` export
- deploying a project with `openui deploy`

## Install

Expand Down Expand Up @@ -61,6 +62,13 @@ Generate JSON Schema instead:
npx @openuidev/cli@latest generate ./src/library.ts --json-schema
```

Deploy the current project:

```bash
npx @openuidev/cli@latest deploy
npx @openuidev/cli@latest deploy --prod
```

## Commands

### `openui create`
Expand Down Expand Up @@ -157,6 +165,39 @@ openui create --name my-app --no-skill --no-install
openui create --no-interactive --name my-app --template openui-cloud --api-key tk_your_key
```

### `openui deploy`

Deploys an OpenUI project. The default platform supported is **Vercel**.

```bash
openui deploy [dir] [options]
```

Arguments:

- `dir`: Project directory (default: current directory)

Options:

- `-y, --yes`: Skip confirmation prompts (also saves missing env keys to the Vercel project)
- `--skip-env`: Do not pass or save local `.env` / `.env.local` values
- `--no-interactive`: Skip prompts (implies `--yes`)
- `--verbose`: Stream full Vercel build logs (hidden by default; failure still prints a log tail)
- `--agent-name <name>`: Declare the invoking coding agent as a lowercase kebab-case product slug (default: `unknown`)

Extra flags after `deploy` are forwarded as-is to the target deployment platform, which validates them (for example `--prod` or `--force`). `--skip-env` is OpenUI-specific so it does not collide with Vercel's `--env KEY=value`.

Unlinked projects run `vercel link` first (so env can be saved before the build). Allowlisted keys from `.env` / `.env.local` that are missing on production, preview, or development can be saved to the project (prompted; auto-accepted with `--yes`). Existing project keys are never overwritten. Env is still attached to the current deployment via `--env` / `--build-env`. Build logs are quiet by default.

Examples:

```bash
openui deploy
openui deploy ./my-app
openui deploy ./my-app --prod
openui deploy --skip-env -- --force
```

### `openui generate`

Generates a system prompt and serialized library spec from a file that exports a `createLibrary()` result. Use the spec with `generateSystemPrompt` in backend routes; the prompt file remains available for static or legacy integrations.
Expand Down Expand Up @@ -233,6 +274,7 @@ Run the built CLI:
```bash
node dist/index.js --help
node dist/index.js create --help
node dist/index.js deploy --help
node dist/index.js generate --help
```

Expand All @@ -242,7 +284,7 @@ The CLI sends usage analytics; OAuth sign-ins may link usage to your OIDC accoun

When a coding agent invokes the CLI, it should pass `--agent-name` using its stable, lowercase kebab-case product slug—for example, `codex`, `claude-code`, `cline`, `factory-droid`, or `pi`. Do not pass a model/version, user name, session ID, or other unique value. Humans can omit the flag; it defaults to `unknown`.

Telemetry includes both `agent_name` (the CLI declaration) and `detected_agent_name` (best-effort environment detection). Either can be spoofed, inherited, missing, or ambiguous; neither is an authentication signal. Every invocation gets an ephemeral, unpersisted `cli_run_id` so its events can be correlated. Failure events include bounded `failure_stage`, `error_class`, and `error_code` values, never raw error messages. Dependency failures distinguish peer, registry, network, install-script, workspace, and package-compatibility errors. Process failures include duration, exit code, and signal; Cloud-auth failures include a bounded auth substage and HTTP status when known; cancellations use separate events. For `create`, telemetry also includes `package_manager`, the immediate-start selection, and best-effort dev-command start and result events. Dev-command events contain status, duration, exit code, and signal—not project paths, command output, code, or environment values. Disable telemetry with `--no-telemetry` or `DO_NOT_TRACK=1`.
Telemetry includes both `agent_name` (the CLI declaration) and `detected_agent_name` (best-effort environment detection). Either can be spoofed, inherited, missing, or ambiguous; neither is an authentication signal. Every invocation gets an ephemeral, unpersisted `cli_run_id` so its events can be correlated. Failure events include bounded `failure_stage`, `error_class`, and `error_code` values, never raw error messages. Dependency failures distinguish peer, registry, network, install-script, workspace, and package-compatibility errors. Process failures include duration, exit code, and signal; Cloud-auth failures include a bounded auth substage and HTTP status when known; cancellations use separate events. For `create`, telemetry also includes `package_manager`, the immediate-start selection, and best-effort dev-command start and result events. Dev-command events contain status, duration, exit code, and signal—not project paths, command output, code, or environment values. For `deploy`, telemetry includes the target (currently `vercel`), production vs preview, whether the Vercel CLI was logged in, whether local env was passed, CLI resolution source, and process status—not env values, project paths, or command output. Disable telemetry with `--no-telemetry` or `DO_NOT_TRACK=1`.

```bash
openui create --no-telemetry
Expand All @@ -253,6 +295,7 @@ openui create --no-telemetry
- interactive prompts can be cancelled without creating output
- `create` requires the selected template's files to be present in the built package
- `generate` exits with a non-zero code if the file is missing or no valid library export is found
- `deploy` exits with a non-zero code if the directory has no `package.json` or the Vercel CLI fails

## Documentation

Expand Down
4 changes: 2 additions & 2 deletions packages/openui-cli/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@openuidev/cli",
"version": "0.2.11",
"description": "CLI for OpenUI — scaffold generative UI chat apps and generate LLM system prompts from component libraries",
"version": "0.2.12",
"description": "CLI for OpenUI — scaffold generative UI chat apps, generate LLM system prompts from component libraries, and deploy projects",
"bin": {
"openui": "dist/index.js"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/openui-cli/src/auth/mint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ export async function resolveCloudApiKey(opts: {

if (method === "manual") {
console.warn(
" --auth manual is deprecated. Use browser sign-in or pass --api-key for scripted setup.",
"[!] --auth manual is deprecated. Use browser sign-in or pass --api-key for scripted setup.",
);
const { password } = await import("@inquirer/prompts");
const key = await cloudAuthPrompt("manual_key_prompt", "MANUAL_KEY_PROMPT_FAILED", () =>
Expand Down
45 changes: 23 additions & 22 deletions packages/openui-cli/src/commands/create-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as path from "node:path";
import { resolveCloudApiKey, THESYS_KEYS_URL } from "../auth/mint";
import { aiSetupFromTemplate, createFunnelProps } from "../lib/create-telemetry";
import type { CreateAppOptions, EnvResult, OverlayName, TemplateName } from "../lib/create-types";
import { dumpFailureLog, QUIET_COMMAND_CAPTURE_LIMIT } from "../lib/deploy/quiet";
import {
resolveInstallPackageManager,
type PackageManagerName,
Expand All @@ -14,6 +15,7 @@ import { runSkillInstall, shouldInstallSkill } from "../lib/install-skill";
import { applyOverlay, OVERLAYS_DIR, resolveOverlay, type OverlayManifest } from "../lib/overlays";
import { runCommand } from "../lib/process-runner";
import { resolveArgs } from "../lib/resolve-args";
import { withSpinner } from "../lib/spinner";
import { resolveAvailableTarget } from "../lib/target-dir";
import { CliCancelledError, CreateError, telemetry } from "../lib/telemetry";
import { cliErrorProperties, processErrorProperties } from "../lib/utils";
Expand Down Expand Up @@ -416,22 +418,34 @@ export async function runCreateApp(options: CreateAppOptions): Promise<void> {
});
console.info(`Skipping dependency install (--no-install). Run \`${installCmd}\` later.\n`);
} else {
console.info(`Installing dependencies with: ${installCmd}\n`);
telemetry.capture("cli_dependency_install_started", {
...createFunnelProps("dependency_install_started"),
template,
ai_setup: aiSetup,
});
const installResult = await runCommand(packageManager.runCmd, installArgs, targetDir);
const installResult = await withSpinner("Installing dependencies...", () =>
runCommand(packageManager.runCmd, installArgs, targetDir, {
echo: false,
stdin: "ignore",
captureLimit: QUIET_COMMAND_CAPTURE_LIMIT,
env: {
...process.env,
npm_config_loglevel: "error",
NPM_CONFIG_LOGLEVEL: "error",
},
}),
);
if (!installResult.error && installResult.status === 0) {
dependencyInstalled = true;
console.info("✓ Dependencies installed\n");
telemetry.capture("cli_dependency_install_succeeded", {
...createFunnelProps("dependency_install_succeeded"),
template,
ai_setup: aiSetup,
dependency_installed: dependencyInstalled,
});
} else {
dumpFailureLog(installResult.diagnosticTail, "install log (tail)");
const properties = processErrorProperties(installResult, "dependency_install", {
error_class: "dependency",
error_code: "NONZERO_EXIT",
Expand Down Expand Up @@ -490,7 +504,6 @@ export async function runCreateApp(options: CreateAppOptions): Promise<void> {
skillInstalled,
envWritten: envResult.envWritten,
startDev,
devStartBlockedByMissingApiKey,
installCmd,
dependencyInstalled,
}),
Expand All @@ -501,15 +514,6 @@ export async function runCreateApp(options: CreateAppOptions): Promise<void> {
skip_reason: "missing_api_key",
required_env: apiKeyEnv,
});
const keyHint =
apiKeyEnv === "THESYS_API_KEY"
? `Get a key at ${THESYS_KEYS_URL}, then add:\n\n ${apiKeyEnv}=…\n\nto ${name}/.env`
: `Add your key to ${name}/.env:\n\n ${apiKeyEnv}=…`;
console.error(
`\nSkipped starting the development server — ${apiKeyEnv} is missing.\n\n` +
`${keyHint}\n\n` +
`Then run:\n\n> cd ${name}\n> ${devCmd} run dev\n`,
);
process.exitCode = 1;
return;
}
Expand Down Expand Up @@ -687,7 +691,7 @@ async function resolveCloudEnv(
auth_succeeded: false,
...properties,
});
console.error(`\n Could not obtain an API key: ${msg}`);
console.error(`\n[!] Could not obtain an API key: ${msg}`);
console.error(` Add THESYS_API_KEY to .env later (keys: ${THESYS_KEYS_URL}).\n`);
}
const lines = [`THESYS_API_KEY=${apiKey ?? ""}`, `DEMO_USER_ID=demo-user`];
Expand All @@ -707,7 +711,6 @@ function getStartedMessage(o: {
skillInstalled: boolean;
envWritten: boolean;
startDev: boolean;
devStartBlockedByMissingApiKey: boolean;
installCmd: string;
dependencyInstalled: boolean;
}): string {
Expand All @@ -719,20 +722,18 @@ function getStartedMessage(o: {
o.template === "openui-cloud"
? o.envWritten
? "✅ .env created with your OpenUI Cloud API key + base URL."
: ` .env created without a key. Add THESYS_API_KEY=… (get one at ${THESYS_KEYS_URL}).`
: `[!] .env created without a key. Add THESYS_API_KEY=… (get one at ${THESYS_KEYS_URL}).`
: o.envWritten
? "✅ .env created with your API key."
: "Add your API key to .env:\nOPENAI_API_KEY=sk-your-key-here";

const nextStep = o.startDev
? `Starting the development server in "${o.name}"...\n\n> ${o.devCmd} run dev`
: o.devStartBlockedByMissingApiKey
? ""
: [
`> cd ${o.name}`,
...(o.dependencyInstalled ? [] : [`> ${o.installCmd}`]),
`> ${o.devCmd} run dev`,
].join("\n");
: [
`> cd ${o.name}`,
...(o.dependencyInstalled ? [] : [`> ${o.installCmd}`]),
`> ${o.devCmd} run dev`,
].join("\n");

const frameworkNote = o.backendGettingStarted?.replaceAll("{{packageManager}}", o.devCmd) ?? "";

Expand Down
Loading
Loading