assistant-ui 0.0.81 → 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-ui-starter-cloud";
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;AAIpC,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,64 +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
- const templates = {
9
- default: {
10
- 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",
11
13
  label: "Default",
12
- 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,
13
18
  },
14
- minimal: {
15
- url: "https://github.com/assistant-ui/assistant-ui-starter-minimal",
19
+ {
20
+ name: "minimal",
16
21
  label: "Minimal",
17
- hint: "Bare-bones starting point",
22
+ description: "Bare-bones starting point",
23
+ category: "template",
24
+ path: "templates/minimal",
25
+ hasLocalComponents: true,
18
26
  },
19
- cloud: {
20
- url: "https://github.com/assistant-ui/assistant-ui-starter-cloud",
27
+ {
28
+ name: "cloud",
21
29
  label: "Cloud",
22
- hint: "Cloud-backed persistence starter",
30
+ description: "Cloud-backed persistence starter",
31
+ category: "template",
32
+ path: "templates/cloud",
33
+ hasLocalComponents: true,
23
34
  },
24
- "cloud-clerk": {
25
- url: "https://github.com/assistant-ui/assistant-ui-starter-cloud-clerk",
35
+ {
36
+ name: "cloud-clerk",
26
37
  label: "Cloud + Clerk",
27
- 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,
28
42
  },
29
- langgraph: {
30
- url: "https://github.com/assistant-ui/assistant-ui-starter-langgraph",
43
+ {
44
+ name: "langgraph",
31
45
  label: "LangGraph",
32
- hint: "LangGraph starter template",
46
+ description: "LangGraph starter template",
47
+ category: "template",
48
+ path: "templates/langgraph",
49
+ hasLocalComponents: true,
33
50
  },
34
- mcp: {
35
- url: "https://github.com/assistant-ui/assistant-ui-starter-mcp",
51
+ {
52
+ name: "mcp",
36
53
  label: "MCP",
37
- hint: "MCP starter template",
38
- },
39
- };
40
- const templateNames = Object.keys(templates);
41
- const templatePickerOptions = templateNames.map((name) => ({
42
- value: name,
43
- label: templates[name].label,
44
- hint: templates[name].hint,
45
- }));
46
- export async function resolveCreateTemplateName(params) {
47
- 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;
48
185
  if (template) {
49
- 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;
50
202
  }
51
203
  if (!stdinIsTTY) {
52
- return "default";
204
+ return PROJECT_METADATA.find((m) => m.name === "default");
53
205
  }
54
206
  const selected = await select({
55
- message: "Select a template:",
56
- 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
+ ],
57
230
  });
58
231
  if (isCancel(selected)) {
59
232
  return null;
60
233
  }
61
- 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;
62
240
  }
63
241
  class SpawnExitError extends Error {
64
242
  code;
@@ -84,24 +262,6 @@ async function runSpawn(command, args, cwd) {
84
262
  });
85
263
  });
86
264
  }
87
- export function buildCreateNextAppArgs(params) {
88
- const { projectDirectory, useNpm, usePnpm, useYarn, useBun, skipInstall, templateUrl, } = params;
89
- const args = ["create-next-app@latest"];
90
- if (projectDirectory)
91
- args.push(projectDirectory);
92
- if (useNpm)
93
- args.push("--use-npm");
94
- if (usePnpm)
95
- args.push("--use-pnpm");
96
- if (useYarn)
97
- args.push("--use-yarn");
98
- if (useBun)
99
- args.push("--use-bun");
100
- if (skipInstall)
101
- args.push("--skip-install");
102
- args.push("-e", templateUrl);
103
- return args;
104
- }
105
265
  export function resolveCreateProjectDirectory(params) {
106
266
  const { projectDirectory, stdinIsTTY = process.stdin.isTTY } = params;
107
267
  if (projectDirectory)
@@ -110,8 +270,23 @@ export function resolveCreateProjectDirectory(params) {
110
270
  return "my-aui-app";
111
271
  return undefined;
112
272
  }
113
- function buildPresetAddArgs(presetUrl) {
114
- 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)}`;
115
290
  }
116
291
  export const create = new Command()
117
292
  .name("create")
@@ -119,80 +294,151 @@ export const create = new Command()
119
294
  .argument("[project-directory]")
120
295
  .usage(`${chalk.green("[project-directory]")} [options]`)
121
296
  .option("-t, --template <template>", `template to use (${templateNames.join(", ")})`)
122
- .option("-e, --example <example>", "create from an example (e.g., with-langgraph, with-ai-sdk-v6)")
123
- .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)")
124
299
  .option("--use-npm", "explicitly use npm")
125
300
  .option("--use-pnpm", "explicitly use pnpm")
126
301
  .option("--use-yarn", "explicitly use yarn")
127
302
  .option("--use-bun", "explicitly use bun")
128
303
  .option("--skip-install", "skip installing packages")
129
304
  .action(async (projectDirectory, opts) => {
130
- const resolvedProjectDirectory = resolveCreateProjectDirectory({
131
- projectDirectory,
132
- });
133
305
  if (opts.example && opts.preset) {
134
306
  logger.error("Cannot use --preset with --example.");
135
307
  process.exit(1);
136
308
  }
137
- if (opts.preset && !resolvedProjectDirectory) {
138
- logger.error("Project directory is required when using --preset.");
309
+ if (opts.template && opts.example) {
310
+ logger.error("Cannot use both --template and --example.");
139
311
  process.exit(1);
140
312
  }
141
- // Handle --example option
142
- if (opts.example) {
143
- if (!resolvedProjectDirectory) {
144
- 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`);
145
347
  process.exit(1);
146
348
  }
147
- await createFromExample(resolvedProjectDirectory, opts.example, {
148
- skipInstall: opts.skipInstall,
149
- useNpm: opts.useNpm,
150
- usePnpm: opts.usePnpm,
151
- useYarn: opts.useYarn,
152
- useBun: opts.useBun,
153
- });
154
- return;
155
349
  }
156
- // Handle --template option
157
- 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({
158
365
  template: opts.template,
366
+ example: opts.example,
159
367
  });
160
- if (!templateName) {
368
+ if (!project) {
161
369
  p.cancel("Project creation cancelled.");
162
370
  process.exit(0);
163
371
  }
164
- const templateUrl = templates[templateName]?.url;
165
- if (!templateUrl) {
166
- logger.error(`Unknown template: ${opts.template}`);
167
- logger.info(`Available templates: ${templateNames.join(", ")}`);
168
- process.exit(1);
169
- }
170
- logger.info(`Creating project with template: ${templateName}`);
372
+ logger.info(`Creating project from ${project.category}: ${project.label}`);
171
373
  logger.break();
172
- const createNextAppArgs = buildCreateNextAppArgs({
173
- ...(resolvedProjectDirectory
174
- ? { projectDirectory: resolvedProjectDirectory }
175
- : {}),
176
- ...(opts.useNpm ? { useNpm: true } : {}),
177
- ...(opts.usePnpm ? { usePnpm: true } : {}),
178
- ...(opts.useYarn ? { useYarn: true } : {}),
179
- ...(opts.useBun ? { useBun: true } : {}),
180
- ...(opts.skipInstall ? { skipInstall: true } : {}),
181
- templateUrl,
182
- });
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);
183
380
  try {
184
- await runSpawn("npx", createNextAppArgs);
185
- if (opts.preset) {
186
- if (!resolvedProjectDirectory) {
187
- logger.error("Project directory is required when using --preset.");
188
- 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);
189
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);
190
413
  logger.info("Applying preset configuration...");
191
414
  logger.break();
192
- 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
+ }
193
429
  }
430
+ process.removeListener("exit", cleanupOnExit);
194
431
  logger.break();
195
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`);
196
442
  }
197
443
  catch (error) {
198
444
  if (error instanceof SpawnExitError) {