assistant-ui 0.0.91 → 0.0.92

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.
@@ -1,10 +1,49 @@
1
1
  import { Command } from "commander";
2
- import { spawn } from "cross-spawn";
3
2
  import { logger } from "../lib/utils/logger";
4
3
  import { hasConfig } from "../lib/utils/config";
4
+ import {
5
+ dlxCommand,
6
+ resolvePackageManager,
7
+ resolvePackageManagerForCwd,
8
+ type PackageManagerName,
9
+ } from "../lib/create-project";
10
+ import { runSpawn, SpawnExitError } from "../lib/run-spawn";
5
11
 
6
12
  const REGISTRY_BASE_URL = "https://r.assistant-ui.com";
7
13
 
14
+ export interface AddComponentsPlan {
15
+ command: string;
16
+ args: string[];
17
+ }
18
+
19
+ export function createAddComponentsPlan(params: {
20
+ components: string[];
21
+ packageManager: PackageManagerName;
22
+ yes?: boolean;
23
+ overwrite?: boolean;
24
+ cwd?: string;
25
+ path?: string;
26
+ }): AddComponentsPlan {
27
+ const componentsToAdd = params.components.map((c) => {
28
+ if (!/^[a-zA-Z0-9-/]+$/.test(c)) {
29
+ throw new Error(`Invalid component name: ${c}`);
30
+ }
31
+ return `${REGISTRY_BASE_URL}/${encodeURIComponent(c)}.json`;
32
+ });
33
+
34
+ const [command, dlxArgs] = dlxCommand(params.packageManager);
35
+ const args = [...dlxArgs, "shadcn@latest", "add", ...componentsToAdd];
36
+
37
+ // For npm, dlxArgs may already include `--yes` for npx auto-install.
38
+ // This flag is for shadcn's own confirmation prompt.
39
+ if (params.yes) args.push("--yes");
40
+ if (params.overwrite) args.push("--overwrite");
41
+ if (params.cwd) args.push("--cwd", params.cwd);
42
+ if (params.path) args.push("--path", params.path);
43
+
44
+ return { command, args };
45
+ }
46
+
8
47
  export const add = new Command()
9
48
  .name("add")
10
49
  .description("add a component to your project")
@@ -17,7 +56,11 @@ export const add = new Command()
17
56
  process.cwd(),
18
57
  )
19
58
  .option("-p, --path <path>", "the path to add the component to.")
20
- .action((components: string[], opts) => {
59
+ .option("--use-npm", "explicitly use npm")
60
+ .option("--use-pnpm", "explicitly use pnpm")
61
+ .option("--use-yarn", "explicitly use yarn")
62
+ .option("--use-bun", "explicitly use bun")
63
+ .action(async (components: string[], opts) => {
21
64
  // Check if project is initialized
22
65
  if (!hasConfig(opts.cwd)) {
23
66
  logger.warn(
@@ -26,38 +69,32 @@ export const add = new Command()
26
69
  logger.break();
27
70
  }
28
71
 
29
- const componentsToAdd = components.map((c) => {
30
- if (!/^[a-zA-Z0-9-/]+$/.test(c)) {
31
- throw new Error(`Invalid component name: ${c}`);
32
- }
33
- return `${REGISTRY_BASE_URL}/${encodeURIComponent(c)}.json`;
34
- });
35
-
36
72
  logger.step(`Adding ${components.length} component(s)...`);
37
73
 
38
- const args = [`shadcn@latest`, "add", ...componentsToAdd];
39
-
40
- if (opts.yes) args.push("--yes");
41
- if (opts.overwrite) args.push("--overwrite");
42
- if (opts.cwd) args.push("--cwd", opts.cwd);
43
- if (opts.path) args.push("--path", opts.path);
44
-
45
- const child = spawn("npx", args, {
46
- stdio: "inherit",
47
- shell: true,
74
+ const packageManager = await resolvePackageManagerForCwd(
75
+ opts.cwd,
76
+ resolvePackageManager(opts),
77
+ );
78
+ const { command, args } = createAddComponentsPlan({
79
+ components,
80
+ packageManager,
81
+ yes: opts.yes,
82
+ overwrite: opts.overwrite,
83
+ cwd: opts.cwd,
84
+ path: opts.path,
48
85
  });
49
86
 
50
- child.on("error", (error) => {
51
- logger.error(`Failed to add components: ${error.message}`);
87
+ try {
88
+ await runSpawn(command, args, opts.cwd);
89
+ } catch (error) {
90
+ if (error instanceof SpawnExitError) {
91
+ logger.error(`Process exited with code ${error.code}`);
92
+ process.exit(error.code);
93
+ }
94
+ const message = error instanceof Error ? error.message : String(error);
95
+ logger.error(`Failed to add components: ${message}`);
52
96
  process.exit(1);
53
- });
97
+ }
54
98
 
55
- child.on("close", (code) => {
56
- if (code !== 0) {
57
- logger.error(`Process exited with code ${code}`);
58
- process.exit(code || 1);
59
- } else {
60
- logger.success("Components added successfully!");
61
- }
62
- });
99
+ logger.success("Components added successfully!");
63
100
  });
@@ -1,6 +1,5 @@
1
- import { Command } from "commander";
1
+ import { Command, Option } from "commander";
2
2
  import chalk from "chalk";
3
- import { spawn } from "cross-spawn";
4
3
  import fs from "node:fs";
5
4
  import path from "node:path";
6
5
  import * as p from "@clack/prompts";
@@ -9,10 +8,12 @@ import {
9
8
  dlxCommand,
10
9
  downloadProject,
11
10
  resolveLatestReleaseRef,
12
- resolvePackageManagerName,
11
+ resolvePackageManager,
12
+ resolvePackageManagerForCwd,
13
+ scaffoldProject,
13
14
  transformProject,
14
- type PackageManagerName,
15
15
  } from "../lib/create-project";
16
+ import { runSpawn, SpawnExitError } from "../lib/run-spawn";
16
17
 
17
18
  export interface ProjectMetadata {
18
19
  name: string;
@@ -31,7 +32,7 @@ export const PROJECT_METADATA: ProjectMetadata[] = [
31
32
  description: "Default template with Vercel AI SDK",
32
33
  category: "template",
33
34
  path: "templates/default",
34
- hasLocalComponents: true,
35
+ hasLocalComponents: false,
35
36
  },
36
37
  {
37
38
  name: "minimal",
@@ -47,7 +48,7 @@ export const PROJECT_METADATA: ProjectMetadata[] = [
47
48
  description: "Cloud-backed persistence starter",
48
49
  category: "template",
49
50
  path: "templates/cloud",
50
- hasLocalComponents: true,
51
+ hasLocalComponents: false,
51
52
  },
52
53
  {
53
54
  name: "cloud-clerk",
@@ -55,7 +56,7 @@ export const PROJECT_METADATA: ProjectMetadata[] = [
55
56
  description: "Cloud-backed starter with Clerk auth",
56
57
  category: "template",
57
58
  path: "templates/cloud-clerk",
58
- hasLocalComponents: true,
59
+ hasLocalComponents: false,
59
60
  },
60
61
  {
61
62
  name: "langgraph",
@@ -63,15 +64,15 @@ export const PROJECT_METADATA: ProjectMetadata[] = [
63
64
  description: "LangGraph starter template",
64
65
  category: "template",
65
66
  path: "templates/langgraph",
66
- hasLocalComponents: true,
67
+ hasLocalComponents: false,
67
68
  },
68
69
  {
69
70
  name: "mcp",
70
71
  label: "MCP",
71
- description: "MCP starter template",
72
+ description: "MCP tools + MCP Apps renderer starter",
72
73
  category: "template",
73
74
  path: "templates/mcp",
74
- hasLocalComponents: true,
75
+ hasLocalComponents: false,
75
76
  },
76
77
  // Examples
77
78
  {
@@ -276,7 +277,7 @@ export async function resolveProject(params: {
276
277
  isCancel = p.isCancel,
277
278
  } = params;
278
279
 
279
- if (template) {
280
+ if (template !== undefined) {
280
281
  const meta = PROJECT_METADATA.find(
281
282
  (m) => m.name === template && m.category === "template",
282
283
  );
@@ -288,7 +289,7 @@ export async function resolveProject(params: {
288
289
  return meta;
289
290
  }
290
291
 
291
- if (example) {
292
+ if (example !== undefined) {
292
293
  const meta = PROJECT_METADATA.find(
293
294
  (m) => m.name === example && m.category === "example",
294
295
  );
@@ -342,37 +343,6 @@ export async function resolveProject(params: {
342
343
  return meta;
343
344
  }
344
345
 
345
- class SpawnExitError extends Error {
346
- code: number;
347
-
348
- constructor(code: number) {
349
- super(`Process exited with code ${code}`);
350
- this.code = code;
351
- }
352
- }
353
-
354
- async function runSpawn(
355
- command: string,
356
- args: string[],
357
- cwd?: string,
358
- ): Promise<void> {
359
- return new Promise((resolve, reject) => {
360
- const child = spawn(command, args, {
361
- stdio: "inherit",
362
- cwd,
363
- });
364
-
365
- child.on("error", (error) => reject(error));
366
- child.on("close", (code) => {
367
- if (code !== 0) {
368
- reject(new SpawnExitError(code || 1));
369
- } else {
370
- resolve();
371
- }
372
- });
373
- });
374
- }
375
-
376
346
  export function resolveCreateProjectDirectory(params: {
377
347
  projectDirectory?: string;
378
348
  stdinIsTTY?: boolean;
@@ -384,19 +354,6 @@ export function resolveCreateProjectDirectory(params: {
384
354
  return undefined;
385
355
  }
386
356
 
387
- export function resolvePackageManager(opts: {
388
- useNpm?: boolean;
389
- usePnpm?: boolean;
390
- useYarn?: boolean;
391
- useBun?: boolean;
392
- }): PackageManagerName | undefined {
393
- if (opts.useNpm) return "npm";
394
- if (opts.usePnpm) return "pnpm";
395
- if (opts.useYarn) return "yarn";
396
- if (opts.useBun) return "bun";
397
- return undefined;
398
- }
399
-
400
357
  const PLAYGROUND_PRESET_BASE_URL =
401
358
  "https://www.assistant-ui.com/playground/init";
402
359
 
@@ -407,6 +364,68 @@ export function resolvePresetUrl(preset: string): string {
407
364
  return `${PLAYGROUND_PRESET_BASE_URL}?preset=${encodeURIComponent(preset)}`;
408
365
  }
409
366
 
367
+ export interface ScaffoldSelectorOptions {
368
+ template?: string;
369
+ example?: string;
370
+ preset?: string;
371
+ native?: boolean;
372
+ ink?: boolean;
373
+ }
374
+
375
+ export interface ResolvedScaffoldSelector {
376
+ template?: string;
377
+ example?: string;
378
+ preset?: string;
379
+ }
380
+
381
+ const scaffoldSelectorHelp =
382
+ "Choose one scaffold selector: --template <name>, --example <name>, --native, or --ink. --preset <name-or-url> can be used with --template or by itself.";
383
+
384
+ function getPresetConflict(opts: ScaffoldSelectorOptions): string | undefined {
385
+ if (opts.example !== undefined) return "--example";
386
+ if (opts.native) return "--native";
387
+ if (opts.ink) return "--ink";
388
+ return undefined;
389
+ }
390
+
391
+ export function resolveScaffoldSelector(
392
+ opts: ScaffoldSelectorOptions,
393
+ ): ResolvedScaffoldSelector {
394
+ const hasPreset = opts.preset !== undefined;
395
+ const presetConflict = getPresetConflict(opts);
396
+ const selectors = [
397
+ opts.template !== undefined ? "--template" : undefined,
398
+ opts.example !== undefined ? "--example" : undefined,
399
+ opts.native ? "--native" : undefined,
400
+ opts.ink ? "--ink" : undefined,
401
+ ].filter((selector): selector is string => selector !== undefined);
402
+
403
+ if (selectors.length > 1) {
404
+ throw new Error(
405
+ `Only one scaffold selector can be provided (${selectors.join(", ")}). ${scaffoldSelectorHelp}`,
406
+ );
407
+ }
408
+
409
+ if (hasPreset && presetConflict) {
410
+ throw new Error(
411
+ `Cannot use --preset with ${presetConflict}. ${scaffoldSelectorHelp}`,
412
+ );
413
+ }
414
+
415
+ if (opts.native) return { example: "with-expo" };
416
+ if (opts.ink) return { example: "with-react-ink" };
417
+
418
+ if (opts.preset !== undefined && opts.template === undefined) {
419
+ return { template: "default", preset: opts.preset };
420
+ }
421
+
422
+ return {
423
+ ...(opts.template !== undefined && { template: opts.template }),
424
+ ...(opts.example !== undefined && { example: opts.example }),
425
+ ...(hasPreset && { preset: opts.preset }),
426
+ };
427
+ }
428
+
410
429
  export const create = new Command()
411
430
  .name("create")
412
431
  .description("create a new project")
@@ -431,27 +450,30 @@ export const create = new Command()
431
450
  .option("--native", "create an Expo / React Native project")
432
451
  .option("--ink", "create a React Ink terminal project")
433
452
  .option("--skip-install", "skip installing packages")
453
+ .addOption(
454
+ new Option(
455
+ "--debug-source-root <path>",
456
+ "copy templates/examples from a local assistant-ui repo root",
457
+ ).hideHelp(),
458
+ )
434
459
  .action(async (projectDirectory, opts) => {
435
- if (opts.native) {
436
- opts.example = "with-expo";
437
- }
438
-
439
- if (opts.ink) {
440
- opts.example = "with-react-ink";
441
- }
442
-
443
- if (opts.example && opts.preset) {
444
- logger.error("Cannot use --preset with --example.");
460
+ let scaffoldSelector: ResolvedScaffoldSelector;
461
+ try {
462
+ scaffoldSelector = resolveScaffoldSelector(opts);
463
+ } catch (error) {
464
+ const message = error instanceof Error ? error.message : String(error);
465
+ logger.error(message);
445
466
  process.exit(1);
446
467
  }
447
468
 
448
- if (opts.template && opts.example) {
449
- logger.error("Cannot use both --template and --example.");
450
- process.exit(1);
451
- }
469
+ const localSourceRoot = opts.debugSourceRoot
470
+ ? path.resolve(opts.debugSourceRoot)
471
+ : undefined;
452
472
 
453
473
  // Start release ref resolution early (runs during user prompts)
454
- const refPromise = resolveLatestReleaseRef();
474
+ const refPromise = localSourceRoot
475
+ ? Promise.resolve(undefined)
476
+ : resolveLatestReleaseRef();
455
477
 
456
478
  // 1. Resolve project directory
457
479
  let resolvedProjectDirectory = resolveCreateProjectDirectory({
@@ -510,10 +532,7 @@ export const create = new Command()
510
532
  }
511
533
 
512
534
  // 2. Resolve scaffold target
513
- const project = await resolveProject({
514
- template: opts.template,
515
- example: opts.example,
516
- });
535
+ const project = await resolveProject(scaffoldSelector);
517
536
  if (!project) {
518
537
  p.cancel("Project creation cancelled.");
519
538
  process.exit(0);
@@ -522,8 +541,8 @@ export const create = new Command()
522
541
  logger.info(`Creating project from ${project.category}: ${project.label}`);
523
542
  logger.break();
524
543
 
525
- const pm = await resolvePackageManagerName(
526
- absoluteProjectDir,
544
+ const pm = await resolvePackageManagerForCwd(
545
+ path.dirname(absoluteProjectDir),
527
546
  resolvePackageManager(opts),
528
547
  );
529
548
 
@@ -535,19 +554,32 @@ export const create = new Command()
535
554
 
536
555
  try {
537
556
  // 3. Resolve latest release ref (started before prompts)
538
- logger.step("Resolving latest release...");
557
+ if (!localSourceRoot) {
558
+ logger.step("Resolving latest release...");
559
+ }
539
560
  const ref = await refPromise;
540
- if (!ref) {
561
+ if (!localSourceRoot && !ref) {
541
562
  logger.warn("Could not resolve latest release, downloading from HEAD");
542
563
  }
543
564
 
544
- // 4. Download project
545
- logger.step("Downloading project...");
565
+ // 4. Scaffold project
566
+ logger.step(
567
+ localSourceRoot
568
+ ? `Copying project from local source: ${localSourceRoot}`
569
+ : "Downloading project...",
570
+ );
546
571
  try {
547
- await downloadProject(project.path, absoluteProjectDir, ref);
572
+ const source = localSourceRoot
573
+ ? { kind: "local" as const, rootDir: localSourceRoot }
574
+ : {
575
+ kind: "github" as const,
576
+ ref,
577
+ };
578
+ await scaffoldProject(project.path, absoluteProjectDir, source);
548
579
 
549
580
  // If the template didn't exist at the release tag, retry from HEAD
550
581
  if (
582
+ !localSourceRoot &&
551
583
  ref &&
552
584
  !fs.existsSync(path.join(absoluteProjectDir, "package.json"))
553
585
  ) {
@@ -571,8 +603,8 @@ export const create = new Command()
571
603
  }
572
604
 
573
605
  // 6. Apply preset if provided
574
- if (opts.preset) {
575
- const presetUrl = resolvePresetUrl(opts.preset);
606
+ if (scaffoldSelector.preset) {
607
+ const presetUrl = resolvePresetUrl(scaffoldSelector.preset);
576
608
  logger.info("Applying preset configuration...");
577
609
  logger.break();
578
610
  const [dlxCmd, dlxArgs] = dlxCommand(pm);
@@ -1,23 +1,18 @@
1
1
  import { Command, Option } from "commander";
2
- import { spawn } from "cross-spawn";
3
2
  import fs from "node:fs";
4
3
  import path from "node:path";
5
- import { dlxCommand, resolvePackageManagerName } from "../lib/create-project";
4
+ import {
5
+ dlxCommand,
6
+ resolvePackageManager,
7
+ resolvePackageManagerForCwd,
8
+ } from "../lib/create-project";
9
+ import { runSpawn, SpawnExitError } from "../lib/run-spawn";
6
10
  import { logger } from "../lib/utils/logger";
7
- import { create, resolvePackageManager } from "./create";
11
+ import { create } from "./create";
8
12
 
9
13
  const DEFAULT_REGISTRY_URL =
10
14
  "https://r.assistant-ui.com/chat/b/ai-sdk-quick-start/json";
11
15
 
12
- class SpawnExitError extends Error {
13
- code: number;
14
-
15
- constructor(code: number) {
16
- super(`Process exited with code ${code}`);
17
- this.code = code;
18
- }
19
- }
20
-
21
16
  interface ExistingProjectInitPlan {
22
17
  initArgs: string[] | null;
23
18
  addArgs: string[];
@@ -52,31 +47,6 @@ export function isNonInteractiveShell(
52
47
  return !stdinIsTTY;
53
48
  }
54
49
 
55
- async function runSpawn(
56
- command: string,
57
- args: string[],
58
- cwd: string,
59
- ): Promise<void> {
60
- return new Promise((resolve, reject) => {
61
- const child = spawn(command, args, {
62
- stdio: "inherit",
63
- cwd,
64
- });
65
-
66
- child.on("error", (error) => {
67
- reject(error);
68
- });
69
-
70
- child.on("close", (code) => {
71
- if (code !== 0) {
72
- reject(new SpawnExitError(code || 1));
73
- } else {
74
- resolve();
75
- }
76
- });
77
- });
78
- }
79
-
80
50
  export const init = new Command()
81
51
  .name("init")
82
52
  .description("initialize assistant-ui in an existing project")
@@ -152,11 +122,8 @@ export const init = new Command()
152
122
  }
153
123
 
154
124
  try {
155
- // resolvePackageManagerName calls detect({ cwd: path.dirname(dir) }),
156
- // which is designed for `create` where the dir doesn't exist yet.
157
- // For init, targetDir IS the project root, so append a dummy segment.
158
- const pm = await resolvePackageManagerName(
159
- path.join(targetDir, "_"),
125
+ const pm = await resolvePackageManagerForCwd(
126
+ targetDir,
160
127
  resolvePackageManager(opts),
161
128
  );
162
129
  const [dlxCmd, dlxArgs] = dlxCommand(pm);