Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
81 changes: 78 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 Cloud API keys, and generate system prompts or library specs.
description: API reference for the OpenUI CLI to scaffold apps, mint Cloud API keys, and generate system prompts or library specs, and deploy projects.
---

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

## Installation

Expand Down Expand Up @@ -210,6 +210,78 @@ 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.

```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-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 @@ -428,7 +500,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
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 Cloud key), and openui generate (system prompt + library spec from a library
openui create (scaffold a Next.js app), openui generate-api-key (mint a Cloud key), openui deploy, and openui generate (system prompt + library spec from a library
definition).
</Card>
</Cards>
46 changes: 44 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,38 @@ 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.


```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 +310,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 +321,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 +332,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
2 changes: 1 addition & 1 deletion 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.12",
"description": "CLI for OpenUI — scaffold generative UI chat apps, mint Cloud API keys, and generate LLM system prompts from component libraries",
"description": "CLI for OpenUI — scaffold generative UI chat apps, mint Cloud API keys, and generate LLM system prompts from component libraries, and deploy projects",
"bin": {
"openui": "dist/index.js"
},
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
118 changes: 118 additions & 0 deletions packages/openui-cli/src/commands/deploy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import * as fs from "node:fs";
import * as path from "node:path";

import {
DEFAULT_DEPLOY_TARGET,
deployToTarget,
type DeployTargetOptions,
} 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[];
};

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 target = DEFAULT_DEPLOY_TARGET;
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,
prod,
yes,
skip_env: skipEnv,
verbose,
has_dir_arg: Boolean(resolved.projectDir),
});

await deployToTarget(target, targetOpts);
}

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 };
}

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;
}

function unsetIfFlag(value?: string): string | undefined {
return value?.startsWith("-") ? undefined : value;
}

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",
);
}
return projectDir;
}
3 changes: 1 addition & 2 deletions packages/openui-cli/src/commands/generate-api-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as fs from "node:fs";
import * as path from "node:path";

import { mintCloudApiKey } from "../auth/mint";
import { upsertEnvVar } from "../lib/env";
import { DEFAULT_ENV_FILE, upsertEnvVar } from "../lib/env";
import { telemetry } from "../lib/telemetry";

export interface GenerateApiKeyOptions {
Expand All @@ -11,7 +11,6 @@ export interface GenerateApiKeyOptions {
name?: string;
}

const DEFAULT_ENV_FILE = ".env";
const DEFAULT_ENV_KEY = "THESYS_API_KEY";

function resolveProjectName(explicit?: string): string {
Expand Down
Loading
Loading