Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
cb818d9
cli: quiet dependency install, spinner, and thesys next.config workar…
AbhinRustagi Aug 27, 2026
9668dc7
Update next.config.ts
AbhinRustagi Aug 27, 2026
114c19e
fix: format
AbhinRustagi Aug 28, 2026
220feac
Merge branch 'feat/cli-ux-prereqs' of github.com:thesysdev/openui int…
AbhinRustagi Aug 28, 2026
2a87cb7
cli: add --verbose to create for streaming install logs
AbhinRustagi Aug 28, 2026
1642311
Update create-types.ts
AbhinRustagi Aug 28, 2026
62ad9a3
cli: rename dumpFailureLog to printLogTail
AbhinRustagi Aug 28, 2026
3ce6761
cli: add openui deploy for Vercel
AbhinRustagi Aug 27, 2026
5ea539f
cli: rename dumpFailureLog to printLogTail in deploy path
AbhinRustagi Aug 28, 2026
ac23dc5
Merge branch 'main' of github.com:thesysdev/openui into feat/cli-verc…
AbhinRustagi Aug 31, 2026
f708cd1
fix: format
AbhinRustagi Aug 31, 2026
66506ed
Merge branch 'main' of github.com:thesysdev/openui into feat/cli-verc…
AbhinRustagi Sep 1, 2026
5a9e094
Merge branch 'main' of github.com:thesysdev/openui into feat/cli-verc…
AbhinRustagi Sep 2, 2026
528d543
cli: fold deploy env helpers into shared lib/env
AbhinRustagi Sep 2, 2026
94510eb
cli: use yocto-spinner instead of a homemade spinner
AbhinRustagi Sep 2, 2026
d4dc451
Revert "cli: use yocto-spinner instead of a homemade spinner"
AbhinRustagi Sep 2, 2026
3eebb6a
Merge branch 'main' of github.com:thesysdev/openui into feat/cli-verc…
AbhinRustagi Sep 2, 2026
9ec6136
Merge branch 'main' of github.com:thesysdev/openui into feat/cli-verc…
AbhinRustagi Sep 8, 2026
96ec3ba
Merge branch 'main' of github.com:thesysdev/openui into feat/cli-verc…
AbhinRustagi Sep 9, 2026
7c12983
fix: docs changes
AbhinRustagi Sep 9, 2026
90c7269
fix: update version
AbhinRustagi Sep 9, 2026
fddbc68
fix: copy changes
AbhinRustagi Sep 9, 2026
1c07631
fix: deploy.ts
AbhinRustagi Sep 9, 2026
9b102fe
fix: deploy.ts and index.ts
AbhinRustagi Sep 9, 2026
aa61807
fix: format
AbhinRustagi Sep 9, 2026
8b41c97
fix: add export
AbhinRustagi Sep 9, 2026
132cb61
fix: make templates deployable
AbhinRustagi Sep 9, 2026
871f7c0
fix: move throwCommandFailure elsewhere
AbhinRustagi Sep 9, 2026
206aa2b
fix: add check for openui project
AbhinRustagi Sep 9, 2026
5b3518b
fix: remove peer
AbhinRustagi Sep 9, 2026
5643991
fix: package manager
AbhinRustagi Sep 9, 2026
4c7d9e8
Merge branch 'main' of github.com:thesysdev/openui into feat/cli-verc…
AbhinRustagi Sep 10, 2026
f6c26b0
Update CLI API reference for Gateway API key
AbhinRustagi Sep 10, 2026
3e86e71
Update index.mdx
AbhinRustagi Sep 10, 2026
227e8c6
fix: bugs
AbhinRustagi Sep 10, 2026
4404e15
Merge branch 'feat/cli-vercel-deploy-split' of github.com:thesysdev/o…
AbhinRustagi Sep 10, 2026
ea87807
fix: vercel url parse
AbhinRustagi Sep 10, 2026
b119e8d
fix: rename Cloud to Gateway
AbhinRustagi Sep 10, 2026
d086365
Merge branch 'main' of github.com:thesysdev/openui into feat/cli-verc…
AbhinRustagi Sep 10, 2026
bedce84
fix: vercel cli env
AbhinRustagi Sep 10, 2026
24d3762
Sync lockfile after dropping thesys-server from docs.
AbhinRustagi Sep 10, 2026
df17106
fix: bugs
AbhinRustagi Sep 10, 2026
564006e
Merge branch 'main' of github.com:thesysdev/openui into feat/cli-verc…
AbhinRustagi Sep 10, 2026
028e713
fix: ci
AbhinRustagi Sep 10, 2026
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
80 changes: 77 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, mint Gateway API keys, and generate system prompts or library specs.
description: API reference for the OpenUI CLI to scaffold apps, mint Gateway API keys, and generate system prompts or library specs, and deploy projects.
---

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

## Installation

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

## `deploy`

Deploys an OpenUI project. The default supported platform 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) |

Extra flags after `deploy` are forwarded as-is to the target deployment platform, which validates them. `--skip-env` is OpenUI-specific so it does not collide with the target platform's env specific args.

Unlinked projects are linked to the platform first. Allowlisted keys from `.env` / `.env.local` that are missing on different deployment environments can be saved to the project (auto-accepted with `--yes`). Build logs are quiet by default; use `--verbose` to stream them.

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

## `generate-api-key`

Signs in with Thesys in the browser, mints an OpenUI Gateway API key, and writes it to a project env file.
Expand Down Expand Up @@ -430,7 +501,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 (true/false), 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; Gateway-auth
Expand Down
6 changes: 3 additions & 3 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), openui generate-api-key (mint a Gateway key), and openui generate (system prompt + library spec from a library
openui create (scaffold a Next.js app), openui generate-api-key (mint a Gateway key), openui deploy, and openui generate (system prompt + library spec from a library
definition).
</Card>
</Cards>
45 changes: 43 additions & 2 deletions packages/openui-cli/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# @openuidev/cli

Command-line tools for starting OpenUI projects, minting OpenUI Cloud API keys, and generating model instructions from component libraries.
Command-line tools for starting OpenUI projects, minting OpenUI Cloud API keys, and 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)
Expand All @@ -15,6 +15,7 @@ It currently supports:
- keeping the default minimal SDK route or adding a LangGraph, Vercel AI SDK, or Vercel Eve backend to either template
- minting an OpenUI Cloud API key into an existing project's env file
- generating a system prompt or JSON Schema from a `createLibrary()` export
- deploying a project with `openui deploy`

## Install

Expand Down Expand Up @@ -71,6 +72,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 @@ -172,6 +180,37 @@ 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)

Extra flags after `deploy` are forwarded as-is to the target deployment platform, which validates them. `--skip-env` is OpenUI-specific so it does not collide with the target platform's env specific args.

Unlinked projects are linked to the platform first. Allowlisted keys from `.env` / `.env.local` that are missing on different deployment environments can be saved to the project (auto-accepted with `--yes`). Build logs are quiet by default; use `--verbose` to stream them.


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

### `openui generate-api-key`

Signs in with Thesys in the browser, mints an OpenUI Cloud API key, and writes it to a project env file.
Expand Down Expand Up @@ -270,6 +309,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-api-key --help
node dist/index.js generate --help
```
Expand All @@ -280,7 +320,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 @@ -291,6 +331,7 @@ openui create --no-telemetry
- interactive prompts can be cancelled without creating output
- `create` fetches `templates/templates.json` and the selected template from GitHub (`thesysdev/openui@main`)
- `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
5 changes: 3 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.13",
"description": "CLI for OpenUI — scaffold generative UI chat apps, mint Cloud API keys, and generate LLM system prompts from component libraries",
"version": "0.3.0",
"description": "CLI for OpenUI — scaffold generative UI chat apps, mint Gateway API keys, and generate LLM system prompts from component libraries, and deploy projects",
"bin": {
"openui": "dist/index.js"
},
Expand Down Expand Up @@ -53,6 +53,7 @@
"@inquirer/prompts": "^8.3.0",
"commander": "^14.0.3",
"cross-spawn": "^7.0.6",
"dotenv": "^17.2.2",
"esbuild": "^0.25.10",
"get-port": "^7.2.0",
"open": "^10.1.0",
Expand Down
8 changes: 2 additions & 6 deletions packages/openui-cli/src/commands/create-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
type OverlayManifest,
type TemplateOverlay,
} from "../lib/overlays";
import { runCommand } from "../lib/process-runner";
import { mutedNpmEnv, runCommand } from "../lib/process-runner";
import { resolveArgs } from "../lib/resolve-args";
import { resolveTemplateSource } from "../lib/scaffold-template";
import { withSpinner } from "../lib/spinner";
Expand Down Expand Up @@ -393,11 +393,7 @@ export async function runCreateApp(options: CreateAppOptions): Promise<void> {
echo: false,
stdin: "ignore",
captureLimit: QUIET_COMMAND_CAPTURE_LIMIT,
env: {
...process.env,
npm_config_loglevel: "error",
NPM_CONFIG_LOGLEVEL: "error",
},
env: mutedNpmEnv(),
});

if (options.verbose) {
Expand Down
124 changes: 124 additions & 0 deletions packages/openui-cli/src/commands/deploy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import * as fs from "node:fs";
import * as path from "node:path";

import {
assertOpenUiProject,
DEFAULT_DEPLOY_TARGET,
type DeployTargetOptions,
} from "../lib/deploy";
import { deployToTarget } from "../lib/deploy-targets";
import { resolveInstallPackageManager } from "../lib/detect-package-manager";
import { CreateError, telemetry } from "../lib/telemetry";

/** OpenUI-only flags. Everything else is forwarded for the target CLI to validate. */
const OWN_FLAGS = new Set(["--skip-env", "--no-interactive", "--verbose"]);

export type DeployOptions = {
dir?: string;
yes?: boolean;
skipEnv?: boolean;
noInteractive?: boolean;
verbose?: boolean;
extraArgs?: string[];
};

type ResolvedDeploy = {
projectDir?: string;
extraArgs: string[];
};

/** Resolve flags, validate the project dir, then hand off to the deploy target. */
export async function runDeploy(options: DeployOptions): Promise<void> {
const resolved = resolveDeployInvocation(options);
const projectDir = resolveProjectDir(resolved.projectDir);
const extraArgs = resolved.extraArgs.filter((arg) => arg !== "--verbose");
const prod = extraArgs.includes("--prod");
const yes =
Boolean(options.yes) ||
Boolean(options.noInteractive) ||
extraArgs.includes("--yes") ||
extraArgs.includes("-y");
const skipEnv = Boolean(options.skipEnv);
const verbose = Boolean(options.verbose) || (options.extraArgs ?? []).includes("--verbose");

const targetOpts: DeployTargetOptions = {
projectDir,
extraArgs,
prod,
yes,
skipEnv,
noInteractive: Boolean(options.noInteractive),
verbose,
};

telemetry.register({ package_manager: resolveInstallPackageManager().name });
telemetry.capture("cli_deploy_started", {
target: DEFAULT_DEPLOY_TARGET,
prod,
yes,
skip_env: skipEnv,
verbose,
has_dir_arg: Boolean(resolved.projectDir),
});

await deployToTarget(DEFAULT_DEPLOY_TARGET, targetOpts);
}

/** Split `[dir]` from extra args when Commander treats a flag as the dir. */
function resolveDeployInvocation(options: DeployOptions): ResolvedDeploy {
const projectDir = unsetIfFlag(options.dir);
const extraArgs = extraDeployArgs(options.extraArgs ?? [], { dir: projectDir });
if (options.dir?.startsWith("-") && !extraArgs.includes(options.dir)) {
extraArgs.unshift(options.dir);
}
return { projectDir, extraArgs };
}

/** Drop OpenUI-owned flags and the consumed dir so the rest can go to Vercel. */
function extraDeployArgs(args: string[], consumed: { dir?: string }): string[] {
const skip = new Set(
[consumed.dir].filter((value): value is string => Boolean(value && !value.startsWith("-"))),
);
const out: string[] = [];
for (const arg of args) {
if (skip.has(arg) || OWN_FLAGS.has(arg)) continue;
out.push(arg);
}
return out;
}

/** Treat a leading-dash value as a flag, not a project directory. */
function unsetIfFlag(value?: string): string | undefined {
return value?.startsWith("-") ? undefined : value;
}

/** Resolve and require a directory that contains package.json. */
function resolveProjectDir(dir?: string): string {
const projectDir = path.resolve(process.cwd(), dir ?? ".");
if (!fs.existsSync(projectDir)) {
throw new CreateError(
"args_resolution",
`Directory not found: ${projectDir}`,
"invalid_input",
"NOT_FOUND",
);
}
if (!fs.statSync(projectDir).isDirectory()) {
throw new CreateError(
"args_resolution",
`Not a directory: ${projectDir}`,
"invalid_input",
"NOT_A_DIRECTORY",
);
}
if (!fs.existsSync(path.join(projectDir, "package.json"))) {
throw new CreateError(
"args_resolution",
`No package.json in ${projectDir}. Run this from an OpenUI project, or pass its directory.`,
"invalid_input",
"PROJECT_NOT_FOUND",
);
}
assertOpenUiProject(projectDir);
return projectDir;
}
Loading
Loading