assistant-ui 0.0.80 → 0.0.82

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,7 +20,7 @@ Passing `--preset` to `init` also forwards to `create` for compatibility.
20
20
 
21
21
  Use the `create` command to scaffold a new Next.js project with assistant-ui.
22
22
 
23
- The `create` command uses `create-next-app` with assistant-ui starter templates.
23
+ The `create` command scaffolds a project from assistant-ui starter templates or examples.
24
24
 
25
25
  ```bash
26
26
  npx assistant-ui@latest create my-app
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare const agent: Command;
3
+ //# sourceMappingURL=agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/commands/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqBpC,eAAO,MAAM,KAAK,SAcd,CAAC"}
@@ -0,0 +1,34 @@
1
+ import { Command } from "commander";
2
+ import { resolve, dirname } from "node:path";
3
+ import { existsSync } from "node:fs";
4
+ import { fileURLToPath } from "node:url";
5
+ import { launch } from "@assistant-ui/agent-launcher";
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ function getPluginPath() {
8
+ // In dist/, plugin is at ../../plugin relative to dist/commands/agent.js
9
+ // In dev (src/), plugin is at ../../plugin relative to src/commands/
10
+ const candidates = [
11
+ resolve(__dirname, "..", "..", "plugin"),
12
+ resolve(__dirname, "..", "plugin"),
13
+ ];
14
+ for (const candidate of candidates) {
15
+ if (existsSync(candidate))
16
+ return candidate;
17
+ }
18
+ return candidates[0];
19
+ }
20
+ export const agent = new Command()
21
+ .name("agent")
22
+ .description("launch Claude Code with assistant-ui skills")
23
+ .argument("<prompt...>", "prompt for the agent")
24
+ .option("--dry", "print the command instead of running it")
25
+ .action((promptParts, opts) => {
26
+ const prompt = promptParts.join(" ");
27
+ launch({
28
+ pluginDir: getPluginPath(),
29
+ skillName: "assistant-ui",
30
+ prompt,
31
+ dry: opts.dry,
32
+ });
33
+ });
34
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/commands/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAEtD,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE1D,SAAS,aAAa;IACpB,yEAAyE;IACzE,qEAAqE;IACrE,MAAM,UAAU,GAAG;QACjB,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC;QACxC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,QAAQ,CAAC;KACnC,CAAC;IACF,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,UAAU,CAAC,SAAS,CAAC;YAAE,OAAO,SAAS,CAAC;IAC9C,CAAC;IACD,OAAO,UAAU,CAAC,CAAC,CAAE,CAAC;AACxB,CAAC;AAED,MAAM,CAAC,MAAM,KAAK,GAAG,IAAI,OAAO,EAAE;KAC/B,IAAI,CAAC,OAAO,CAAC;KACb,WAAW,CAAC,6CAA6C,CAAC;KAC1D,QAAQ,CAAC,aAAa,EAAE,sBAAsB,CAAC;KAC/C,MAAM,CAAC,OAAO,EAAE,yCAAyC,CAAC;KAC1D,MAAM,CAAC,CAAC,WAAqB,EAAE,IAAI,EAAE,EAAE;IACtC,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAErC,MAAM,CAAC;QACL,SAAS,EAAE,aAAa,EAAE;QAC1B,SAAS,EAAE,cAAc;QACzB,MAAM;QACN,GAAG,EAAE,IAAI,CAAC,GAAG;KACd,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1,57 +1,25 @@
1
1
  import { Command } from "commander";
2
2
  import * as p from "@clack/prompts";
3
- declare const templates: {
4
- readonly default: {
5
- readonly url: "https://github.com/assistant-ui/assistant-ui-starter";
6
- readonly label: "Default";
7
- readonly hint: "Default template with Vercel AI SDK";
8
- };
9
- readonly minimal: {
10
- readonly url: "https://github.com/assistant-ui/assistant-ui-starter-minimal";
11
- readonly label: "Minimal";
12
- readonly hint: "Bare-bones starting point";
13
- };
14
- readonly cloud: {
15
- readonly url: "https://github.com/assistant-ui/assistant-cloud-starter";
16
- readonly label: "Cloud";
17
- readonly hint: "Cloud-backed persistence starter";
18
- };
19
- readonly "cloud-clerk": {
20
- readonly url: "https://github.com/assistant-ui/assistant-ui-starter-cloud-clerk";
21
- readonly label: "Cloud + Clerk";
22
- readonly hint: "Cloud-backed starter with Clerk auth";
23
- };
24
- readonly langgraph: {
25
- readonly url: "https://github.com/assistant-ui/assistant-ui-starter-langgraph";
26
- readonly label: "LangGraph";
27
- readonly hint: "LangGraph starter template";
28
- };
29
- readonly mcp: {
30
- readonly url: "https://github.com/assistant-ui/assistant-ui-starter-mcp";
31
- readonly label: "MCP";
32
- readonly hint: "MCP starter template";
33
- };
34
- };
35
- type TemplateName = keyof typeof templates;
36
- export declare function resolveCreateTemplateName(params: {
3
+ export interface ProjectMetadata {
4
+ name: string;
5
+ label: string;
6
+ description?: string;
7
+ category: "template" | "example";
8
+ path: string;
9
+ hasLocalComponents: boolean;
10
+ }
11
+ export declare const PROJECT_METADATA: ProjectMetadata[];
12
+ export declare function resolveProject(params: {
37
13
  template?: string;
14
+ example?: string;
38
15
  stdinIsTTY?: boolean;
39
16
  select?: typeof p.select;
40
17
  isCancel?: typeof p.isCancel;
41
- }): Promise<TemplateName | null>;
42
- export declare function buildCreateNextAppArgs(params: {
43
- projectDirectory?: string;
44
- useNpm?: boolean;
45
- usePnpm?: boolean;
46
- useYarn?: boolean;
47
- useBun?: boolean;
48
- skipInstall?: boolean;
49
- templateUrl: string;
50
- }): string[];
18
+ }): Promise<ProjectMetadata | null>;
51
19
  export declare function resolveCreateProjectDirectory(params: {
52
20
  projectDirectory?: string;
53
21
  stdinIsTTY?: boolean;
54
22
  }): string | undefined;
23
+ export declare function resolvePresetUrl(preset: string): string;
55
24
  export declare const create: Command;
56
- export {};
57
25
  //# sourceMappingURL=create.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/commands/create.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,OAAO,KAAK,CAAC,MAAM,gBAAgB,CAAC;AAKpC,QAAA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BL,CAAC;AAEX,KAAK,YAAY,GAAG,MAAM,OAAO,SAAS,CAAC;AAa3C,wBAAsB,yBAAyB,CAAC,MAAM,EAAE;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC;CAC9B,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CA0B/B;AAiCD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE;IAC7C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,MAAM,EAAE,CAqBX;AAED,wBAAgB,6BAA6B,CAAC,MAAM,EAAE;IACpD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,GAAG,MAAM,GAAG,SAAS,CAMrB;AAMD,eAAO,MAAM,MAAM,SAkHf,CAAC"}
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/commands/create.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,OAAO,KAAK,CAAC,MAAM,gBAAgB,CAAC;AAWpC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,UAAU,GAAG,SAAS,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED,eAAO,MAAM,gBAAgB,EAAE,eAAe,EA2K7C,CAAC;AAUF,wBAAsB,cAAc,CAAC,MAAM,EAAE;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC;CAC9B,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAyElC;AAiCD,wBAAgB,6BAA6B,CAAC,MAAM,EAAE;IACpD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,GAAG,MAAM,GAAG,SAAS,CAMrB;AAkBD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAKvD;AAED,eAAO,MAAM,MAAM,SAwMf,CAAC"}
@@ -1,65 +1,242 @@
1
1
  import { Command } from "commander";
2
2
  import chalk from "chalk";
3
3
  import { spawn } from "cross-spawn";
4
+ import fs from "node:fs";
4
5
  import path from "node:path";
5
6
  import * as p from "@clack/prompts";
6
7
  import { logger } from "../lib/utils/logger.js";
7
- import { createFromExample } from "../lib/create-from-example.js";
8
- // Keep in sync with packages/create-assistant-ui/src/index.ts
9
- const templates = {
10
- default: {
11
- url: "https://github.com/assistant-ui/assistant-ui-starter",
8
+ import { dlxCommand, downloadProject, resolveLatestReleaseRef, resolvePackageManagerName, transformProject, } from "../lib/create-project.js";
9
+ export const PROJECT_METADATA = [
10
+ // Templates
11
+ {
12
+ name: "default",
12
13
  label: "Default",
13
- hint: "Default template with Vercel AI SDK",
14
+ description: "Default template with Vercel AI SDK",
15
+ category: "template",
16
+ path: "templates/default",
17
+ hasLocalComponents: true,
14
18
  },
15
- minimal: {
16
- url: "https://github.com/assistant-ui/assistant-ui-starter-minimal",
19
+ {
20
+ name: "minimal",
17
21
  label: "Minimal",
18
- hint: "Bare-bones starting point",
22
+ description: "Bare-bones starting point",
23
+ category: "template",
24
+ path: "templates/minimal",
25
+ hasLocalComponents: true,
19
26
  },
20
- cloud: {
21
- url: "https://github.com/assistant-ui/assistant-cloud-starter",
27
+ {
28
+ name: "cloud",
22
29
  label: "Cloud",
23
- hint: "Cloud-backed persistence starter",
30
+ description: "Cloud-backed persistence starter",
31
+ category: "template",
32
+ path: "templates/cloud",
33
+ hasLocalComponents: true,
24
34
  },
25
- "cloud-clerk": {
26
- url: "https://github.com/assistant-ui/assistant-ui-starter-cloud-clerk",
35
+ {
36
+ name: "cloud-clerk",
27
37
  label: "Cloud + Clerk",
28
- hint: "Cloud-backed starter with Clerk auth",
38
+ description: "Cloud-backed starter with Clerk auth",
39
+ category: "template",
40
+ path: "templates/cloud-clerk",
41
+ hasLocalComponents: true,
29
42
  },
30
- langgraph: {
31
- url: "https://github.com/assistant-ui/assistant-ui-starter-langgraph",
43
+ {
44
+ name: "langgraph",
32
45
  label: "LangGraph",
33
- hint: "LangGraph starter template",
46
+ description: "LangGraph starter template",
47
+ category: "template",
48
+ path: "templates/langgraph",
49
+ hasLocalComponents: true,
34
50
  },
35
- mcp: {
36
- url: "https://github.com/assistant-ui/assistant-ui-starter-mcp",
51
+ {
52
+ name: "mcp",
37
53
  label: "MCP",
38
- hint: "MCP starter template",
39
- },
40
- };
41
- const templateNames = Object.keys(templates);
42
- const templatePickerOptions = templateNames.map((name) => ({
43
- value: name,
44
- label: templates[name].label,
45
- hint: templates[name].hint,
46
- }));
47
- export async function resolveCreateTemplateName(params) {
48
- const { template, stdinIsTTY = process.stdin.isTTY, select = p.select, isCancel = p.isCancel, } = params;
54
+ description: "MCP starter template",
55
+ category: "template",
56
+ path: "templates/mcp",
57
+ hasLocalComponents: true,
58
+ },
59
+ // Examples
60
+ {
61
+ name: "with-ag-ui",
62
+ label: "AG-UI",
63
+ description: "AG-UI protocol integration",
64
+ category: "example",
65
+ path: "examples/with-ag-ui",
66
+ hasLocalComponents: false,
67
+ },
68
+ {
69
+ name: "with-ai-sdk-v6",
70
+ label: "AI SDK v6",
71
+ description: "Vercel AI SDK v6",
72
+ category: "example",
73
+ path: "examples/with-ai-sdk-v6",
74
+ hasLocalComponents: false,
75
+ },
76
+ {
77
+ name: "with-artifacts",
78
+ label: "Artifacts",
79
+ description: "Artifact rendering",
80
+ category: "example",
81
+ path: "examples/with-artifacts",
82
+ hasLocalComponents: false,
83
+ },
84
+ {
85
+ name: "with-assistant-transport",
86
+ label: "Assistant Transport",
87
+ description: "Assistant transport protocol",
88
+ category: "example",
89
+ path: "examples/with-assistant-transport",
90
+ hasLocalComponents: false,
91
+ },
92
+ {
93
+ name: "with-chain-of-thought",
94
+ label: "Chain of Thought",
95
+ description: "Chain-of-thought rendering",
96
+ category: "example",
97
+ path: "examples/with-chain-of-thought",
98
+ hasLocalComponents: false,
99
+ },
100
+ {
101
+ name: "with-cloud",
102
+ label: "Cloud Example",
103
+ description: "Cloud integration example",
104
+ category: "example",
105
+ path: "examples/with-cloud",
106
+ hasLocalComponents: false,
107
+ },
108
+ {
109
+ name: "with-custom-thread-list",
110
+ label: "Custom Thread List",
111
+ description: "Custom thread list UI",
112
+ category: "example",
113
+ path: "examples/with-custom-thread-list",
114
+ hasLocalComponents: false,
115
+ },
116
+ {
117
+ name: "with-elevenlabs-scribe",
118
+ label: "ElevenLabs Scribe",
119
+ description: "Audio/speech integration",
120
+ category: "example",
121
+ path: "examples/with-elevenlabs-scribe",
122
+ hasLocalComponents: false,
123
+ },
124
+ {
125
+ name: "with-external-store",
126
+ label: "External Store",
127
+ description: "Custom message store",
128
+ category: "example",
129
+ path: "examples/with-external-store",
130
+ hasLocalComponents: false,
131
+ },
132
+ {
133
+ name: "with-ffmpeg",
134
+ label: "FFmpeg",
135
+ description: "File processing",
136
+ category: "example",
137
+ path: "examples/with-ffmpeg",
138
+ hasLocalComponents: false,
139
+ },
140
+ {
141
+ name: "with-langgraph",
142
+ label: "LangGraph Example",
143
+ description: "LangGraph integration",
144
+ category: "example",
145
+ path: "examples/with-langgraph",
146
+ hasLocalComponents: false,
147
+ },
148
+ {
149
+ name: "with-parent-id-grouping",
150
+ label: "Parent ID Grouping",
151
+ description: "Message grouping strategy",
152
+ category: "example",
153
+ path: "examples/with-parent-id-grouping",
154
+ hasLocalComponents: false,
155
+ },
156
+ {
157
+ name: "with-react-hook-form",
158
+ label: "React Hook Form",
159
+ description: "Form integration",
160
+ category: "example",
161
+ path: "examples/with-react-hook-form",
162
+ hasLocalComponents: false,
163
+ },
164
+ {
165
+ name: "with-react-router",
166
+ label: "React Router",
167
+ description: "React Router v7 + Vite",
168
+ category: "example",
169
+ path: "examples/with-react-router",
170
+ hasLocalComponents: false,
171
+ },
172
+ {
173
+ name: "with-tanstack",
174
+ label: "TanStack",
175
+ description: "TanStack/React Router + Vite",
176
+ category: "example",
177
+ path: "examples/with-tanstack",
178
+ hasLocalComponents: false,
179
+ },
180
+ ];
181
+ const templateNames = PROJECT_METADATA.filter((m) => m.category === "template").map((m) => m.name);
182
+ const exampleNames = PROJECT_METADATA.filter((m) => m.category === "example").map((m) => m.name);
183
+ export async function resolveProject(params) {
184
+ const { template, example, stdinIsTTY = process.stdin.isTTY, select = p.select, isCancel = p.isCancel, } = params;
49
185
  if (template) {
50
- return template;
186
+ const meta = PROJECT_METADATA.find((m) => m.name === template && m.category === "template");
187
+ if (!meta) {
188
+ logger.error(`Unknown template: ${template}`);
189
+ logger.info(`Available templates: ${templateNames.join(", ")}`);
190
+ process.exit(1);
191
+ }
192
+ return meta;
193
+ }
194
+ if (example) {
195
+ const meta = PROJECT_METADATA.find((m) => m.name === example && m.category === "example");
196
+ if (!meta) {
197
+ logger.error(`Unknown example: ${example}`);
198
+ logger.info(`Available examples: ${exampleNames.join(", ")}`);
199
+ process.exit(1);
200
+ }
201
+ return meta;
51
202
  }
52
203
  if (!stdinIsTTY) {
53
- return "default";
204
+ return PROJECT_METADATA.find((m) => m.name === "default");
54
205
  }
55
206
  const selected = await select({
56
- message: "Select a template:",
57
- options: templatePickerOptions,
207
+ message: "Select a project to scaffold:",
208
+ options: [
209
+ {
210
+ value: "_separator",
211
+ label: "────── Starter Templates ──────",
212
+ disabled: true,
213
+ },
214
+ ...PROJECT_METADATA.filter((m) => m.category === "template").map((m) => ({
215
+ value: m.name,
216
+ label: m.label,
217
+ ...(m.description ? { hint: m.description } : {}),
218
+ })),
219
+ {
220
+ value: "_separator",
221
+ label: "────── Feature Examples ──────",
222
+ disabled: true,
223
+ },
224
+ ...PROJECT_METADATA.filter((m) => m.category === "example").map((m) => ({
225
+ value: m.name,
226
+ label: m.label,
227
+ ...(m.description ? { hint: m.description } : {}),
228
+ })),
229
+ ],
58
230
  });
59
231
  if (isCancel(selected)) {
60
232
  return null;
61
233
  }
62
- return selected;
234
+ const meta = PROJECT_METADATA.find((m) => m.name === selected);
235
+ if (!meta) {
236
+ logger.error(`Unknown selection: ${String(selected)}`);
237
+ process.exit(1);
238
+ }
239
+ return meta;
63
240
  }
64
241
  class SpawnExitError extends Error {
65
242
  code;
@@ -85,24 +262,6 @@ async function runSpawn(command, args, cwd) {
85
262
  });
86
263
  });
87
264
  }
88
- export function buildCreateNextAppArgs(params) {
89
- const { projectDirectory, useNpm, usePnpm, useYarn, useBun, skipInstall, templateUrl, } = params;
90
- const args = ["create-next-app@latest"];
91
- if (projectDirectory)
92
- args.push(projectDirectory);
93
- if (useNpm)
94
- args.push("--use-npm");
95
- if (usePnpm)
96
- args.push("--use-pnpm");
97
- if (useYarn)
98
- args.push("--use-yarn");
99
- if (useBun)
100
- args.push("--use-bun");
101
- if (skipInstall)
102
- args.push("--skip-install");
103
- args.push("-e", templateUrl);
104
- return args;
105
- }
106
265
  export function resolveCreateProjectDirectory(params) {
107
266
  const { projectDirectory, stdinIsTTY = process.stdin.isTTY } = params;
108
267
  if (projectDirectory)
@@ -111,8 +270,23 @@ export function resolveCreateProjectDirectory(params) {
111
270
  return "my-aui-app";
112
271
  return undefined;
113
272
  }
114
- function buildPresetAddArgs(presetUrl) {
115
- return ["shadcn@latest", "add", "--yes", presetUrl];
273
+ function resolvePackageManager(opts) {
274
+ if (opts.useNpm)
275
+ return "npm";
276
+ if (opts.usePnpm)
277
+ return "pnpm";
278
+ if (opts.useYarn)
279
+ return "yarn";
280
+ if (opts.useBun)
281
+ return "bun";
282
+ return undefined;
283
+ }
284
+ const PLAYGROUND_PRESET_BASE_URL = "https://www.assistant-ui.com/playground/init";
285
+ export function resolvePresetUrl(preset) {
286
+ if (preset.startsWith("http://") || preset.startsWith("https://")) {
287
+ return preset;
288
+ }
289
+ return `${PLAYGROUND_PRESET_BASE_URL}?preset=${encodeURIComponent(preset)}`;
116
290
  }
117
291
  export const create = new Command()
118
292
  .name("create")
@@ -120,80 +294,151 @@ export const create = new Command()
120
294
  .argument("[project-directory]")
121
295
  .usage(`${chalk.green("[project-directory]")} [options]`)
122
296
  .option("-t, --template <template>", `template to use (${templateNames.join(", ")})`)
123
- .option("-e, --example <example>", "create from an example (e.g., with-langgraph, with-ai-sdk-v6)")
124
- .option("-p, --preset <url>", "preset URL from playground (e.g., https://www.assistant-ui.com/playground/init?preset=chatgpt)")
297
+ .option("-e, --example <example>", `create from an example (${exampleNames.join(", ")})`)
298
+ .option("-p, --preset <name-or-url>", "preset name or URL (e.g., chatgpt or https://www.assistant-ui.com/playground/init?preset=chatgpt)")
125
299
  .option("--use-npm", "explicitly use npm")
126
300
  .option("--use-pnpm", "explicitly use pnpm")
127
301
  .option("--use-yarn", "explicitly use yarn")
128
302
  .option("--use-bun", "explicitly use bun")
129
303
  .option("--skip-install", "skip installing packages")
130
304
  .action(async (projectDirectory, opts) => {
131
- const resolvedProjectDirectory = resolveCreateProjectDirectory({
132
- projectDirectory,
133
- });
134
305
  if (opts.example && opts.preset) {
135
306
  logger.error("Cannot use --preset with --example.");
136
307
  process.exit(1);
137
308
  }
138
- if (opts.preset && !resolvedProjectDirectory) {
139
- logger.error("Project directory is required when using --preset.");
309
+ if (opts.template && opts.example) {
310
+ logger.error("Cannot use both --template and --example.");
140
311
  process.exit(1);
141
312
  }
142
- // Handle --example option
143
- if (opts.example) {
144
- if (!resolvedProjectDirectory) {
145
- logger.error("Project directory is required when using --example");
313
+ // Start release ref resolution early (runs during user prompts)
314
+ const refPromise = resolveLatestReleaseRef();
315
+ // 1. Resolve project directory
316
+ let resolvedProjectDirectory = resolveCreateProjectDirectory({
317
+ projectDirectory,
318
+ });
319
+ if (!resolvedProjectDirectory) {
320
+ const result = await p.text({
321
+ message: "Project name:",
322
+ placeholder: "my-aui-app",
323
+ defaultValue: "my-aui-app",
324
+ validate: (value) => {
325
+ const name = (value ?? "").trim();
326
+ if (!name)
327
+ return "Project name cannot be empty";
328
+ if (name === "." || name === "..")
329
+ return "Project name cannot be . or ..";
330
+ if (name.includes("/") || name.includes("\\"))
331
+ return "Project name cannot contain path separators";
332
+ return undefined;
333
+ },
334
+ });
335
+ if (p.isCancel(result)) {
336
+ p.cancel("Project creation cancelled.");
337
+ process.exit(0);
338
+ }
339
+ resolvedProjectDirectory = result;
340
+ }
341
+ // Check directory
342
+ const absoluteProjectDir = path.resolve(resolvedProjectDirectory);
343
+ try {
344
+ const files = fs.readdirSync(absoluteProjectDir);
345
+ if (files.length > 0) {
346
+ logger.error(`Directory ${resolvedProjectDirectory} already exists and is not empty`);
146
347
  process.exit(1);
147
348
  }
148
- await createFromExample(resolvedProjectDirectory, opts.example, {
149
- skipInstall: opts.skipInstall,
150
- useNpm: opts.useNpm,
151
- usePnpm: opts.usePnpm,
152
- useYarn: opts.useYarn,
153
- useBun: opts.useBun,
154
- });
155
- return;
156
349
  }
157
- // Handle --template option
158
- const templateName = await resolveCreateTemplateName({
350
+ catch (err) {
351
+ if (err.code === "ENOENT") {
352
+ // Directory doesn't exist — good, proceed
353
+ }
354
+ else if (err.code === "ENOTDIR") {
355
+ logger.error(`${resolvedProjectDirectory} already exists and is not a directory`);
356
+ process.exit(1);
357
+ }
358
+ else {
359
+ logger.error(`Cannot access ${resolvedProjectDirectory}: ${err.message}`);
360
+ process.exit(1);
361
+ }
362
+ }
363
+ // 2. Resolve scaffold target
364
+ const project = await resolveProject({
159
365
  template: opts.template,
366
+ example: opts.example,
160
367
  });
161
- if (!templateName) {
368
+ if (!project) {
162
369
  p.cancel("Project creation cancelled.");
163
370
  process.exit(0);
164
371
  }
165
- const templateUrl = templates[templateName]?.url;
166
- if (!templateUrl) {
167
- logger.error(`Unknown template: ${opts.template}`);
168
- logger.info(`Available templates: ${templateNames.join(", ")}`);
169
- process.exit(1);
170
- }
171
- logger.info(`Creating project with template: ${templateName}`);
372
+ logger.info(`Creating project from ${project.category}: ${project.label}`);
172
373
  logger.break();
173
- const createNextAppArgs = buildCreateNextAppArgs({
174
- ...(resolvedProjectDirectory
175
- ? { projectDirectory: resolvedProjectDirectory }
176
- : {}),
177
- ...(opts.useNpm ? { useNpm: true } : {}),
178
- ...(opts.usePnpm ? { usePnpm: true } : {}),
179
- ...(opts.useYarn ? { useYarn: true } : {}),
180
- ...(opts.useBun ? { useBun: true } : {}),
181
- ...(opts.skipInstall ? { skipInstall: true } : {}),
182
- templateUrl,
183
- });
374
+ const pm = await resolvePackageManagerName(absoluteProjectDir, resolvePackageManager(opts));
375
+ // Clean up partial project directory on unexpected exit (e.g. Ctrl+C)
376
+ const cleanupOnExit = () => {
377
+ fs.rmSync(absoluteProjectDir, { recursive: true, force: true });
378
+ };
379
+ process.once("exit", cleanupOnExit);
184
380
  try {
185
- await runSpawn("npx", createNextAppArgs);
186
- if (opts.preset) {
187
- if (!resolvedProjectDirectory) {
188
- logger.error("Project directory is required when using --preset.");
189
- process.exit(1);
381
+ // 3. Resolve latest release ref (started before prompts)
382
+ logger.step("Resolving latest release...");
383
+ const ref = await refPromise;
384
+ if (!ref) {
385
+ logger.warn("Could not resolve latest release, downloading from HEAD");
386
+ }
387
+ // 4. Download project
388
+ logger.step("Downloading project...");
389
+ try {
390
+ await downloadProject(project.path, absoluteProjectDir, ref);
391
+ // If the template didn't exist at the release tag, retry from HEAD
392
+ if (ref &&
393
+ !fs.existsSync(path.join(absoluteProjectDir, "package.json"))) {
394
+ fs.rmSync(absoluteProjectDir, { recursive: true, force: true });
395
+ logger.warn("Template not found at release tag, downloading from HEAD");
396
+ await downloadProject(project.path, absoluteProjectDir);
190
397
  }
398
+ // 5. Run transform pipeline
399
+ await transformProject(absoluteProjectDir, {
400
+ hasLocalComponents: project.hasLocalComponents,
401
+ skipInstall: opts.skipInstall,
402
+ packageManager: pm,
403
+ });
404
+ }
405
+ catch (err) {
406
+ // Clean up partially created project directory
407
+ fs.rmSync(absoluteProjectDir, { recursive: true, force: true });
408
+ throw err;
409
+ }
410
+ // 6. Apply preset if provided
411
+ if (opts.preset) {
412
+ const presetUrl = resolvePresetUrl(opts.preset);
191
413
  logger.info("Applying preset configuration...");
192
414
  logger.break();
193
- await runSpawn("npx", buildPresetAddArgs(opts.preset), path.resolve(process.cwd(), resolvedProjectDirectory));
415
+ const [dlxCmd, dlxArgs] = dlxCommand(pm);
416
+ try {
417
+ await runSpawn(dlxCmd, [
418
+ ...dlxArgs,
419
+ "shadcn@latest",
420
+ "add",
421
+ "--yes",
422
+ "--overwrite",
423
+ presetUrl,
424
+ ], absoluteProjectDir);
425
+ }
426
+ catch {
427
+ logger.warn(`Preset application failed. You can retry manually with:\n ${dlxCmd} ${[...dlxArgs, "shadcn@latest", "add", presetUrl].join(" ")}`);
428
+ }
194
429
  }
430
+ process.removeListener("exit", cleanupOnExit);
195
431
  logger.break();
196
432
  logger.success("Project created successfully!");
433
+ logger.break();
434
+ const runCmd = pm === "npm" ? "npm run" : pm;
435
+ logger.info("Next steps:");
436
+ logger.info(` cd ${resolvedProjectDirectory}`);
437
+ if (opts.skipInstall) {
438
+ logger.info(` ${pm} install`);
439
+ }
440
+ logger.info(" # Set up your environment variables in .env.local");
441
+ logger.info(` ${runCmd} dev`);
197
442
  }
198
443
  catch (error) {
199
444
  if (error instanceof SpawnExitError) {