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.
@@ -0,0 +1,36 @@
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
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+
9
+ function getPluginPath(): string {
10
+ // In dist/, plugin is at ../../plugin relative to dist/commands/agent.js
11
+ // In dev (src/), plugin is at ../../plugin relative to src/commands/
12
+ const candidates = [
13
+ resolve(__dirname, "..", "..", "plugin"),
14
+ resolve(__dirname, "..", "plugin"),
15
+ ];
16
+ for (const candidate of candidates) {
17
+ if (existsSync(candidate)) return candidate;
18
+ }
19
+ return candidates[0]!;
20
+ }
21
+
22
+ export const agent = new Command()
23
+ .name("agent")
24
+ .description("launch Claude Code with assistant-ui skills")
25
+ .argument("<prompt...>", "prompt for the agent")
26
+ .option("--dry", "print the command instead of running it")
27
+ .action((promptParts: string[], opts) => {
28
+ const prompt = promptParts.join(" ");
29
+
30
+ launch({
31
+ pluginDir: getPluginPath(),
32
+ skillName: "assistant-ui",
33
+ prompt,
34
+ dry: opts.dry,
35
+ });
36
+ });
@@ -1,89 +1,288 @@
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";
7
- import { createFromExample } from "../lib/create-from-example";
8
+ import {
9
+ dlxCommand,
10
+ downloadProject,
11
+ resolveLatestReleaseRef,
12
+ resolvePackageManagerName,
13
+ transformProject,
14
+ type PackageManagerName,
15
+ } from "../lib/create-project";
16
+
17
+ export interface ProjectMetadata {
18
+ name: string;
19
+ label: string;
20
+ description?: string;
21
+ category: "template" | "example";
22
+ path: string;
23
+ hasLocalComponents: boolean;
24
+ }
8
25
 
9
- // Keep in sync with packages/create-assistant-ui/src/index.ts
10
- const templates = {
11
- default: {
12
- url: "https://github.com/assistant-ui/assistant-ui-starter",
26
+ export const PROJECT_METADATA: ProjectMetadata[] = [
27
+ // Templates
28
+ {
29
+ name: "default",
13
30
  label: "Default",
14
- hint: "Default template with Vercel AI SDK",
31
+ description: "Default template with Vercel AI SDK",
32
+ category: "template",
33
+ path: "templates/default",
34
+ hasLocalComponents: true,
15
35
  },
16
- minimal: {
17
- url: "https://github.com/assistant-ui/assistant-ui-starter-minimal",
36
+ {
37
+ name: "minimal",
18
38
  label: "Minimal",
19
- hint: "Bare-bones starting point",
39
+ description: "Bare-bones starting point",
40
+ category: "template",
41
+ path: "templates/minimal",
42
+ hasLocalComponents: true,
20
43
  },
21
- cloud: {
22
- url: "https://github.com/assistant-ui/assistant-cloud-starter",
44
+ {
45
+ name: "cloud",
23
46
  label: "Cloud",
24
- hint: "Cloud-backed persistence starter",
47
+ description: "Cloud-backed persistence starter",
48
+ category: "template",
49
+ path: "templates/cloud",
50
+ hasLocalComponents: true,
25
51
  },
26
- "cloud-clerk": {
27
- url: "https://github.com/assistant-ui/assistant-ui-starter-cloud-clerk",
52
+ {
53
+ name: "cloud-clerk",
28
54
  label: "Cloud + Clerk",
29
- hint: "Cloud-backed starter with Clerk auth",
55
+ description: "Cloud-backed starter with Clerk auth",
56
+ category: "template",
57
+ path: "templates/cloud-clerk",
58
+ hasLocalComponents: true,
30
59
  },
31
- langgraph: {
32
- url: "https://github.com/assistant-ui/assistant-ui-starter-langgraph",
60
+ {
61
+ name: "langgraph",
33
62
  label: "LangGraph",
34
- hint: "LangGraph starter template",
63
+ description: "LangGraph starter template",
64
+ category: "template",
65
+ path: "templates/langgraph",
66
+ hasLocalComponents: true,
35
67
  },
36
- mcp: {
37
- url: "https://github.com/assistant-ui/assistant-ui-starter-mcp",
68
+ {
69
+ name: "mcp",
38
70
  label: "MCP",
39
- hint: "MCP starter template",
71
+ description: "MCP starter template",
72
+ category: "template",
73
+ path: "templates/mcp",
74
+ hasLocalComponents: true,
75
+ },
76
+ // Examples
77
+ {
78
+ name: "with-ag-ui",
79
+ label: "AG-UI",
80
+ description: "AG-UI protocol integration",
81
+ category: "example",
82
+ path: "examples/with-ag-ui",
83
+ hasLocalComponents: false,
84
+ },
85
+ {
86
+ name: "with-ai-sdk-v6",
87
+ label: "AI SDK v6",
88
+ description: "Vercel AI SDK v6",
89
+ category: "example",
90
+ path: "examples/with-ai-sdk-v6",
91
+ hasLocalComponents: false,
92
+ },
93
+ {
94
+ name: "with-artifacts",
95
+ label: "Artifacts",
96
+ description: "Artifact rendering",
97
+ category: "example",
98
+ path: "examples/with-artifacts",
99
+ hasLocalComponents: false,
100
+ },
101
+ {
102
+ name: "with-assistant-transport",
103
+ label: "Assistant Transport",
104
+ description: "Assistant transport protocol",
105
+ category: "example",
106
+ path: "examples/with-assistant-transport",
107
+ hasLocalComponents: false,
108
+ },
109
+ {
110
+ name: "with-chain-of-thought",
111
+ label: "Chain of Thought",
112
+ description: "Chain-of-thought rendering",
113
+ category: "example",
114
+ path: "examples/with-chain-of-thought",
115
+ hasLocalComponents: false,
116
+ },
117
+ {
118
+ name: "with-cloud",
119
+ label: "Cloud Example",
120
+ description: "Cloud integration example",
121
+ category: "example",
122
+ path: "examples/with-cloud",
123
+ hasLocalComponents: false,
124
+ },
125
+ {
126
+ name: "with-custom-thread-list",
127
+ label: "Custom Thread List",
128
+ description: "Custom thread list UI",
129
+ category: "example",
130
+ path: "examples/with-custom-thread-list",
131
+ hasLocalComponents: false,
132
+ },
133
+ {
134
+ name: "with-elevenlabs-scribe",
135
+ label: "ElevenLabs Scribe",
136
+ description: "Audio/speech integration",
137
+ category: "example",
138
+ path: "examples/with-elevenlabs-scribe",
139
+ hasLocalComponents: false,
140
+ },
141
+ {
142
+ name: "with-external-store",
143
+ label: "External Store",
144
+ description: "Custom message store",
145
+ category: "example",
146
+ path: "examples/with-external-store",
147
+ hasLocalComponents: false,
40
148
  },
41
- } as const;
149
+ {
150
+ name: "with-ffmpeg",
151
+ label: "FFmpeg",
152
+ description: "File processing",
153
+ category: "example",
154
+ path: "examples/with-ffmpeg",
155
+ hasLocalComponents: false,
156
+ },
157
+ {
158
+ name: "with-langgraph",
159
+ label: "LangGraph Example",
160
+ description: "LangGraph integration",
161
+ category: "example",
162
+ path: "examples/with-langgraph",
163
+ hasLocalComponents: false,
164
+ },
165
+ {
166
+ name: "with-parent-id-grouping",
167
+ label: "Parent ID Grouping",
168
+ description: "Message grouping strategy",
169
+ category: "example",
170
+ path: "examples/with-parent-id-grouping",
171
+ hasLocalComponents: false,
172
+ },
173
+ {
174
+ name: "with-react-hook-form",
175
+ label: "React Hook Form",
176
+ description: "Form integration",
177
+ category: "example",
178
+ path: "examples/with-react-hook-form",
179
+ hasLocalComponents: false,
180
+ },
181
+ {
182
+ name: "with-react-router",
183
+ label: "React Router",
184
+ description: "React Router v7 + Vite",
185
+ category: "example",
186
+ path: "examples/with-react-router",
187
+ hasLocalComponents: false,
188
+ },
189
+ {
190
+ name: "with-tanstack",
191
+ label: "TanStack",
192
+ description: "TanStack/React Router + Vite",
193
+ category: "example",
194
+ path: "examples/with-tanstack",
195
+ hasLocalComponents: false,
196
+ },
197
+ ];
42
198
 
43
- type TemplateName = keyof typeof templates;
44
- const templateNames = Object.keys(templates) as TemplateName[];
199
+ const templateNames = PROJECT_METADATA.filter(
200
+ (m) => m.category === "template",
201
+ ).map((m) => m.name);
45
202
 
46
- const templatePickerOptions: Array<{
47
- value: TemplateName;
48
- label: string;
49
- hint: string;
50
- }> = templateNames.map((name) => ({
51
- value: name,
52
- label: templates[name].label,
53
- hint: templates[name].hint,
54
- }));
55
-
56
- export async function resolveCreateTemplateName(params: {
203
+ const exampleNames = PROJECT_METADATA.filter(
204
+ (m) => m.category === "example",
205
+ ).map((m) => m.name);
206
+
207
+ export async function resolveProject(params: {
57
208
  template?: string;
209
+ example?: string;
58
210
  stdinIsTTY?: boolean;
59
211
  select?: typeof p.select;
60
212
  isCancel?: typeof p.isCancel;
61
- }): Promise<TemplateName | null> {
213
+ }): Promise<ProjectMetadata | null> {
62
214
  const {
63
215
  template,
216
+ example,
64
217
  stdinIsTTY = process.stdin.isTTY,
65
218
  select = p.select,
66
219
  isCancel = p.isCancel,
67
220
  } = params;
68
221
 
69
222
  if (template) {
70
- return template as TemplateName;
223
+ const meta = PROJECT_METADATA.find(
224
+ (m) => m.name === template && m.category === "template",
225
+ );
226
+ if (!meta) {
227
+ logger.error(`Unknown template: ${template}`);
228
+ logger.info(`Available templates: ${templateNames.join(", ")}`);
229
+ process.exit(1);
230
+ }
231
+ return meta;
232
+ }
233
+
234
+ if (example) {
235
+ const meta = PROJECT_METADATA.find(
236
+ (m) => m.name === example && m.category === "example",
237
+ );
238
+ if (!meta) {
239
+ logger.error(`Unknown example: ${example}`);
240
+ logger.info(`Available examples: ${exampleNames.join(", ")}`);
241
+ process.exit(1);
242
+ }
243
+ return meta;
71
244
  }
72
245
 
73
246
  if (!stdinIsTTY) {
74
- return "default";
247
+ return PROJECT_METADATA.find((m) => m.name === "default")!;
75
248
  }
76
249
 
77
250
  const selected = await select({
78
- message: "Select a template:",
79
- options: templatePickerOptions,
251
+ message: "Select a project to scaffold:",
252
+ options: [
253
+ {
254
+ value: "_separator",
255
+ label: "────── Starter Templates ──────",
256
+ disabled: true,
257
+ },
258
+ ...PROJECT_METADATA.filter((m) => m.category === "template").map((m) => ({
259
+ value: m.name,
260
+ label: m.label,
261
+ ...(m.description ? { hint: m.description } : {}),
262
+ })),
263
+ {
264
+ value: "_separator",
265
+ label: "────── Feature Examples ──────",
266
+ disabled: true,
267
+ },
268
+ ...PROJECT_METADATA.filter((m) => m.category === "example").map((m) => ({
269
+ value: m.name,
270
+ label: m.label,
271
+ ...(m.description ? { hint: m.description } : {}),
272
+ })),
273
+ ],
80
274
  });
81
275
 
82
276
  if (isCancel(selected)) {
83
277
  return null;
84
278
  }
85
279
 
86
- return selected as TemplateName;
280
+ const meta = PROJECT_METADATA.find((m) => m.name === selected);
281
+ if (!meta) {
282
+ logger.error(`Unknown selection: ${String(selected)}`);
283
+ process.exit(1);
284
+ }
285
+ return meta;
87
286
  }
88
287
 
89
288
  class SpawnExitError extends Error {
@@ -117,37 +316,6 @@ async function runSpawn(
117
316
  });
118
317
  }
119
318
 
120
- export function buildCreateNextAppArgs(params: {
121
- projectDirectory?: string;
122
- useNpm?: boolean;
123
- usePnpm?: boolean;
124
- useYarn?: boolean;
125
- useBun?: boolean;
126
- skipInstall?: boolean;
127
- templateUrl: string;
128
- }): string[] {
129
- const {
130
- projectDirectory,
131
- useNpm,
132
- usePnpm,
133
- useYarn,
134
- useBun,
135
- skipInstall,
136
- templateUrl,
137
- } = params;
138
-
139
- const args = ["create-next-app@latest"];
140
- if (projectDirectory) args.push(projectDirectory);
141
- if (useNpm) args.push("--use-npm");
142
- if (usePnpm) args.push("--use-pnpm");
143
- if (useYarn) args.push("--use-yarn");
144
- if (useBun) args.push("--use-bun");
145
- if (skipInstall) args.push("--skip-install");
146
-
147
- args.push("-e", templateUrl);
148
- return args;
149
- }
150
-
151
319
  export function resolveCreateProjectDirectory(params: {
152
320
  projectDirectory?: string;
153
321
  stdinIsTTY?: boolean;
@@ -159,8 +327,27 @@ export function resolveCreateProjectDirectory(params: {
159
327
  return undefined;
160
328
  }
161
329
 
162
- function buildPresetAddArgs(presetUrl: string): string[] {
163
- return ["shadcn@latest", "add", "--yes", presetUrl];
330
+ function resolvePackageManager(opts: {
331
+ useNpm?: boolean;
332
+ usePnpm?: boolean;
333
+ useYarn?: boolean;
334
+ useBun?: boolean;
335
+ }): PackageManagerName | undefined {
336
+ if (opts.useNpm) return "npm";
337
+ if (opts.usePnpm) return "pnpm";
338
+ if (opts.useYarn) return "yarn";
339
+ if (opts.useBun) return "bun";
340
+ return undefined;
341
+ }
342
+
343
+ const PLAYGROUND_PRESET_BASE_URL =
344
+ "https://www.assistant-ui.com/playground/init";
345
+
346
+ export function resolvePresetUrl(preset: string): string {
347
+ if (preset.startsWith("http://") || preset.startsWith("https://")) {
348
+ return preset;
349
+ }
350
+ return `${PLAYGROUND_PRESET_BASE_URL}?preset=${encodeURIComponent(preset)}`;
164
351
  }
165
352
 
166
353
  export const create = new Command()
@@ -174,11 +361,11 @@ export const create = new Command()
174
361
  )
175
362
  .option(
176
363
  "-e, --example <example>",
177
- "create from an example (e.g., with-langgraph, with-ai-sdk-v6)",
364
+ `create from an example (${exampleNames.join(", ")})`,
178
365
  )
179
366
  .option(
180
- "-p, --preset <url>",
181
- "preset URL from playground (e.g., https://www.assistant-ui.com/playground/init?preset=chatgpt)",
367
+ "-p, --preset <name-or-url>",
368
+ "preset name or URL (e.g., chatgpt or https://www.assistant-ui.com/playground/init?preset=chatgpt)",
182
369
  )
183
370
  .option("--use-npm", "explicitly use npm")
184
371
  .option("--use-pnpm", "explicitly use pnpm")
@@ -186,88 +373,174 @@ export const create = new Command()
186
373
  .option("--use-bun", "explicitly use bun")
187
374
  .option("--skip-install", "skip installing packages")
188
375
  .action(async (projectDirectory, opts) => {
189
- const resolvedProjectDirectory = resolveCreateProjectDirectory({
190
- projectDirectory,
191
- });
192
-
193
376
  if (opts.example && opts.preset) {
194
377
  logger.error("Cannot use --preset with --example.");
195
378
  process.exit(1);
196
379
  }
197
380
 
198
- if (opts.preset && !resolvedProjectDirectory) {
199
- logger.error("Project directory is required when using --preset.");
381
+ if (opts.template && opts.example) {
382
+ logger.error("Cannot use both --template and --example.");
200
383
  process.exit(1);
201
384
  }
202
385
 
203
- // Handle --example option
204
- if (opts.example) {
205
- if (!resolvedProjectDirectory) {
206
- logger.error("Project directory is required when using --example");
207
- process.exit(1);
208
- }
386
+ // Start release ref resolution early (runs during user prompts)
387
+ const refPromise = resolveLatestReleaseRef();
388
+
389
+ // 1. Resolve project directory
390
+ let resolvedProjectDirectory = resolveCreateProjectDirectory({
391
+ projectDirectory,
392
+ });
209
393
 
210
- await createFromExample(resolvedProjectDirectory, opts.example, {
211
- skipInstall: opts.skipInstall,
212
- useNpm: opts.useNpm,
213
- usePnpm: opts.usePnpm,
214
- useYarn: opts.useYarn,
215
- useBun: opts.useBun,
394
+ if (!resolvedProjectDirectory) {
395
+ const result = await p.text({
396
+ message: "Project name:",
397
+ placeholder: "my-aui-app",
398
+ defaultValue: "my-aui-app",
399
+ validate: (value?: string) => {
400
+ const name = (value ?? "").trim();
401
+ if (!name) return "Project name cannot be empty";
402
+ if (name === "." || name === "..")
403
+ return "Project name cannot be . or ..";
404
+ if (name.includes("/") || name.includes("\\"))
405
+ return "Project name cannot contain path separators";
406
+ return undefined;
407
+ },
216
408
  });
217
- return;
409
+
410
+ if (p.isCancel(result)) {
411
+ p.cancel("Project creation cancelled.");
412
+ process.exit(0);
413
+ }
414
+
415
+ resolvedProjectDirectory = result;
416
+ }
417
+
418
+ // Check directory
419
+ const absoluteProjectDir = path.resolve(resolvedProjectDirectory);
420
+ try {
421
+ const files = fs.readdirSync(absoluteProjectDir);
422
+ if (files.length > 0) {
423
+ logger.error(
424
+ `Directory ${resolvedProjectDirectory} already exists and is not empty`,
425
+ );
426
+ process.exit(1);
427
+ }
428
+ } catch (err: any) {
429
+ if (err.code === "ENOENT") {
430
+ // Directory doesn't exist — good, proceed
431
+ } else if (err.code === "ENOTDIR") {
432
+ logger.error(
433
+ `${resolvedProjectDirectory} already exists and is not a directory`,
434
+ );
435
+ process.exit(1);
436
+ } else {
437
+ logger.error(
438
+ `Cannot access ${resolvedProjectDirectory}: ${err.message}`,
439
+ );
440
+ process.exit(1);
441
+ }
218
442
  }
219
443
 
220
- // Handle --template option
221
- const templateName = await resolveCreateTemplateName({
444
+ // 2. Resolve scaffold target
445
+ const project = await resolveProject({
222
446
  template: opts.template,
447
+ example: opts.example,
223
448
  });
224
- if (!templateName) {
449
+ if (!project) {
225
450
  p.cancel("Project creation cancelled.");
226
451
  process.exit(0);
227
452
  }
228
453
 
229
- const templateUrl = templates[templateName]?.url;
230
-
231
- if (!templateUrl) {
232
- logger.error(`Unknown template: ${opts.template}`);
233
- logger.info(`Available templates: ${templateNames.join(", ")}`);
234
- process.exit(1);
235
- }
236
-
237
- logger.info(`Creating project with template: ${templateName}`);
454
+ logger.info(`Creating project from ${project.category}: ${project.label}`);
238
455
  logger.break();
239
456
 
240
- const createNextAppArgs = buildCreateNextAppArgs({
241
- ...(resolvedProjectDirectory
242
- ? { projectDirectory: resolvedProjectDirectory }
243
- : {}),
244
- ...(opts.useNpm ? { useNpm: true } : {}),
245
- ...(opts.usePnpm ? { usePnpm: true } : {}),
246
- ...(opts.useYarn ? { useYarn: true } : {}),
247
- ...(opts.useBun ? { useBun: true } : {}),
248
- ...(opts.skipInstall ? { skipInstall: true } : {}),
249
- templateUrl,
250
- });
457
+ const pm = await resolvePackageManagerName(
458
+ absoluteProjectDir,
459
+ resolvePackageManager(opts),
460
+ );
461
+
462
+ // Clean up partial project directory on unexpected exit (e.g. Ctrl+C)
463
+ const cleanupOnExit = () => {
464
+ fs.rmSync(absoluteProjectDir, { recursive: true, force: true });
465
+ };
466
+ process.once("exit", cleanupOnExit);
251
467
 
252
468
  try {
253
- await runSpawn("npx", createNextAppArgs);
469
+ // 3. Resolve latest release ref (started before prompts)
470
+ logger.step("Resolving latest release...");
471
+ const ref = await refPromise;
472
+ if (!ref) {
473
+ logger.warn("Could not resolve latest release, downloading from HEAD");
474
+ }
254
475
 
255
- if (opts.preset) {
256
- if (!resolvedProjectDirectory) {
257
- logger.error("Project directory is required when using --preset.");
258
- process.exit(1);
476
+ // 4. Download project
477
+ logger.step("Downloading project...");
478
+ try {
479
+ await downloadProject(project.path, absoluteProjectDir, ref);
480
+
481
+ // If the template didn't exist at the release tag, retry from HEAD
482
+ if (
483
+ ref &&
484
+ !fs.existsSync(path.join(absoluteProjectDir, "package.json"))
485
+ ) {
486
+ fs.rmSync(absoluteProjectDir, { recursive: true, force: true });
487
+ logger.warn(
488
+ "Template not found at release tag, downloading from HEAD",
489
+ );
490
+ await downloadProject(project.path, absoluteProjectDir);
259
491
  }
492
+
493
+ // 5. Run transform pipeline
494
+ await transformProject(absoluteProjectDir, {
495
+ hasLocalComponents: project.hasLocalComponents,
496
+ skipInstall: opts.skipInstall,
497
+ packageManager: pm,
498
+ });
499
+ } catch (err) {
500
+ // Clean up partially created project directory
501
+ fs.rmSync(absoluteProjectDir, { recursive: true, force: true });
502
+ throw err;
503
+ }
504
+
505
+ // 6. Apply preset if provided
506
+ if (opts.preset) {
507
+ const presetUrl = resolvePresetUrl(opts.preset);
260
508
  logger.info("Applying preset configuration...");
261
509
  logger.break();
262
- await runSpawn(
263
- "npx",
264
- buildPresetAddArgs(opts.preset),
265
- path.resolve(process.cwd(), resolvedProjectDirectory),
266
- );
510
+ const [dlxCmd, dlxArgs] = dlxCommand(pm);
511
+ try {
512
+ await runSpawn(
513
+ dlxCmd,
514
+ [
515
+ ...dlxArgs,
516
+ "shadcn@latest",
517
+ "add",
518
+ "--yes",
519
+ "--overwrite",
520
+ presetUrl,
521
+ ],
522
+ absoluteProjectDir,
523
+ );
524
+ } catch {
525
+ logger.warn(
526
+ `Preset application failed. You can retry manually with:\n ${dlxCmd} ${[...dlxArgs, "shadcn@latest", "add", presetUrl].join(" ")}`,
527
+ );
528
+ }
267
529
  }
268
530
 
531
+ process.removeListener("exit", cleanupOnExit);
532
+
269
533
  logger.break();
270
534
  logger.success("Project created successfully!");
535
+ logger.break();
536
+ const runCmd = pm === "npm" ? "npm run" : pm;
537
+ logger.info("Next steps:");
538
+ logger.info(` cd ${resolvedProjectDirectory}`);
539
+ if (opts.skipInstall) {
540
+ logger.info(` ${pm} install`);
541
+ }
542
+ logger.info(" # Set up your environment variables in .env.local");
543
+ logger.info(` ${runCmd} dev`);
271
544
  } catch (error) {
272
545
  if (error instanceof SpawnExitError) {
273
546
  logger.error(`Project creation failed with code ${error.code}`);
@@ -89,8 +89,8 @@ export const init = new Command()
89
89
  )
90
90
  .addOption(
91
91
  new Option(
92
- "-p, --preset <url>",
93
- "preset URL from playground (forwarded to 'assistant-ui create')",
92
+ "-p, --preset <name-or-url>",
93
+ "preset name or URL (forwarded to 'assistant-ui create')",
94
94
  ).hideHelp(),
95
95
  )
96
96
  .option("--use-npm", "explicitly use npm")