assistant-ui 0.0.105 → 0.0.107

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.
Files changed (87) hide show
  1. package/README.md +2 -2
  2. package/dist/codemods/utils/createTransformer.d.ts +0 -1
  3. package/dist/codemods/utils/createTransformer.d.ts.map +1 -1
  4. package/dist/codemods/v0-11/content-part-to-message-part.d.ts.map +1 -1
  5. package/dist/codemods/v0-12/assistant-api-to-aui.d.ts.map +1 -1
  6. package/dist/codemods/v0-12/assistant-api-to-aui.js.map +1 -1
  7. package/dist/codemods/v0-12/event-names-to-camelcase.d.ts.map +1 -1
  8. package/dist/codemods/v0-12/primitive-if-to-aui-if.d.ts.map +1 -1
  9. package/dist/codemods/v0-8/ui-package-split.d.ts.map +1 -1
  10. package/dist/codemods/v0-8/ui-package-split.js.map +1 -1
  11. package/dist/codemods/v0-9/edge-package-split.d.ts.map +1 -1
  12. package/dist/commands/add.d.ts +1 -1
  13. package/dist/commands/add.d.ts.map +1 -1
  14. package/dist/commands/add.js +8 -5
  15. package/dist/commands/add.js.map +1 -1
  16. package/dist/commands/agent.d.ts +0 -1
  17. package/dist/commands/agent.d.ts.map +1 -1
  18. package/dist/commands/create.d.ts +0 -1
  19. package/dist/commands/create.d.ts.map +1 -1
  20. package/dist/commands/create.js +26 -8
  21. package/dist/commands/create.js.map +1 -1
  22. package/dist/commands/doctor.d.ts +0 -1
  23. package/dist/commands/doctor.d.ts.map +1 -1
  24. package/dist/commands/doctor.js +66 -29
  25. package/dist/commands/doctor.js.map +1 -1
  26. package/dist/commands/info.d.ts +3 -2
  27. package/dist/commands/info.d.ts.map +1 -1
  28. package/dist/commands/info.js +22 -26
  29. package/dist/commands/info.js.map +1 -1
  30. package/dist/commands/init.d.ts +1 -3
  31. package/dist/commands/init.d.ts.map +1 -1
  32. package/dist/commands/init.js +14 -24
  33. package/dist/commands/init.js.map +1 -1
  34. package/dist/commands/mcp.d.ts +0 -1
  35. package/dist/commands/mcp.d.ts.map +1 -1
  36. package/dist/commands/mcp.js +14 -4
  37. package/dist/commands/mcp.js.map +1 -1
  38. package/dist/commands/update.d.ts +0 -1
  39. package/dist/commands/update.d.ts.map +1 -1
  40. package/dist/commands/update.js +14 -1
  41. package/dist/commands/update.js.map +1 -1
  42. package/dist/commands/upgrade.d.ts +0 -1
  43. package/dist/commands/upgrade.d.ts.map +1 -1
  44. package/dist/index.d.ts +1 -1
  45. package/dist/lib/agent-skill.d.ts +0 -1
  46. package/dist/lib/agent-skill.d.ts.map +1 -1
  47. package/dist/lib/create-project.d.ts +7 -2
  48. package/dist/lib/create-project.d.ts.map +1 -1
  49. package/dist/lib/create-project.js +34 -17
  50. package/dist/lib/create-project.js.map +1 -1
  51. package/dist/lib/install-ai-sdk-lib.d.ts.map +1 -1
  52. package/dist/lib/install-edge-lib.d.ts.map +1 -1
  53. package/dist/lib/install-ui-lib.d.ts.map +1 -1
  54. package/dist/lib/run-spawn.d.ts.map +1 -1
  55. package/dist/lib/transform-options.d.ts.map +1 -1
  56. package/dist/lib/transform.d.ts +0 -1
  57. package/dist/lib/transform.d.ts.map +1 -1
  58. package/dist/lib/upgrade.d.ts +0 -1
  59. package/dist/lib/upgrade.d.ts.map +1 -1
  60. package/dist/lib/utils/config.d.ts.map +1 -1
  61. package/dist/lib/utils/file-scanner.d.ts.map +1 -1
  62. package/dist/lib/utils/logger.d.ts.map +1 -1
  63. package/dist/lib/utils/package-installer.d.ts.map +1 -1
  64. package/dist/lib/utils/package-manager.d.ts.map +1 -1
  65. package/dist/lib/utils/registry.d.ts +7 -0
  66. package/dist/lib/utils/registry.d.ts.map +1 -0
  67. package/dist/lib/utils/registry.js +25 -0
  68. package/dist/lib/utils/registry.js.map +1 -0
  69. package/dist/lib/utils/workspace.d.ts +6 -0
  70. package/dist/lib/utils/workspace.d.ts.map +1 -0
  71. package/dist/lib/utils/workspace.js +28 -0
  72. package/dist/lib/utils/workspace.js.map +1 -0
  73. package/dist/program.d.ts +0 -1
  74. package/dist/program.d.ts.map +1 -1
  75. package/package.json +12 -9
  76. package/plugin/skills/assistant-ui/SKILL.md +24 -5
  77. package/src/commands/add.ts +9 -4
  78. package/src/commands/create.ts +28 -8
  79. package/src/commands/doctor.ts +97 -44
  80. package/src/commands/info.ts +49 -42
  81. package/src/commands/init.ts +16 -24
  82. package/src/commands/mcp.ts +20 -5
  83. package/src/commands/update.ts +17 -1
  84. package/src/lib/create-project.ts +64 -20
  85. package/src/lib/utils/registry.test.ts +114 -0
  86. package/src/lib/utils/registry.ts +43 -0
  87. package/src/lib/utils/workspace.ts +34 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assistant-ui",
3
- "version": "0.0.105",
3
+ "version": "0.0.107",
4
4
  "description": "CLI for assistant-ui",
5
5
  "keywords": [
6
6
  "cli",
@@ -26,27 +26,30 @@
26
26
  ],
27
27
  "sideEffects": false,
28
28
  "dependencies": {
29
- "@clack/prompts": "^1.6.0",
29
+ "@clack/prompts": "^1.7.0",
30
30
  "chalk": "^5.6.2",
31
31
  "cli-progress": "^3.12.0",
32
32
  "commander": "^15.0.0",
33
33
  "cross-spawn": "^7.0.6",
34
34
  "debug": "^4.4.3",
35
35
  "detect-package-manager": "^3.0.2",
36
- "giget": "^3.3.0",
36
+ "giget": "^3.3.1",
37
37
  "glob": "^13.0.6",
38
- "jscodeshift": "^17.3.0",
39
- "@assistant-ui/agent-launcher": "0.1.8"
38
+ "jscodeshift": "^17.4.0",
39
+ "jsonc-parser": "^3.3.1",
40
+ "semver": "^7.8.5",
41
+ "@assistant-ui/agent-launcher": "0.1.9"
40
42
  },
41
43
  "devDependencies": {
42
44
  "@types/cli-progress": "^3.11.6",
43
45
  "@types/cross-spawn": "^6.0.6",
44
46
  "@types/debug": "^4.1.13",
45
47
  "@types/jscodeshift": "^17.3.0",
46
- "@types/node": "^26.0.0",
47
- "@vitest/coverage-v8": "^4.1.9",
48
- "vitest": "^4.1.9",
49
- "@assistant-ui/x-buildutils": "0.0.17"
48
+ "@types/node": "^26.1.1",
49
+ "@types/semver": "^7.7.1",
50
+ "@vitest/coverage-v8": "^4.1.10",
51
+ "vitest": "^4.1.10",
52
+ "@assistant-ui/x-buildutils": "0.0.19"
50
53
  },
51
54
  "publishConfig": {
52
55
  "access": "public",
@@ -24,20 +24,39 @@ This initializes shadcn and installs the default assistant-ui chat components.
24
24
  Install components via the shadcn registry:
25
25
 
26
26
  ```bash
27
- npx shadcn@latest add "https://r.assistant-ui.com/chat/b/ai-sdk-quick-start/json"
27
+ npx shadcn@latest add "https://r.assistant-ui.com/base/chat/b/ai-sdk-quick-start/json"
28
28
  ```
29
29
 
30
+ For Radix-styled projects (`components.json` style not starting with `base-`), use `https://r.assistant-ui.com/chat/b/ai-sdk-quick-start/json` instead.
31
+
30
32
  Available component presets:
31
33
 
32
34
  | Preset | Registry URL |
33
35
  |--------|-------------|
34
- | AI SDK Quick Start | `https://r.assistant-ui.com/chat/b/ai-sdk-quick-start/json` |
36
+ | AI SDK Quick Start | `https://r.assistant-ui.com/base/chat/b/ai-sdk-quick-start/json` |
37
+
38
+ You can also add individual assistant-ui components with the assistant-ui CLI, which picks the right flavor from `components.json`:
39
+
40
+ ```bash
41
+ npx assistant-ui add thread
42
+ npx assistant-ui add markdown-text
43
+ ```
44
+
45
+ To use the shadcn CLI directly instead, add the assistant-ui registry to `components.json`:
46
+
47
+ ```json
48
+ {
49
+ "registries": {
50
+ "@assistant-ui": "https://r.assistant-ui.com/styles/{style}/{name}.json"
51
+ }
52
+ }
53
+ ```
35
54
 
36
- You can also add individual assistant-ui shadcn components:
55
+ Then add components with the namespaced form:
37
56
 
38
57
  ```bash
39
- npx shadcn@latest add assistant-ui/thread
40
- npx shadcn@latest add assistant-ui/markdown-text
58
+ npx shadcn@latest add @assistant-ui/thread
59
+ npx shadcn@latest add @assistant-ui/markdown-text
41
60
  ```
42
61
 
43
62
  ## Step 3: Runtime Setup
@@ -1,6 +1,10 @@
1
1
  import { Command } from "commander";
2
2
  import { logger } from "../lib/utils/logger";
3
3
  import { hasConfig } from "../lib/utils/config";
4
+ import {
5
+ getComponentsJsonStyle,
6
+ resolveRegistryItemUrl,
7
+ } from "../lib/utils/registry";
4
8
  import {
5
9
  dlxCommand,
6
10
  resolvePackageManager,
@@ -9,8 +13,6 @@ import {
9
13
  } from "../lib/create-project";
10
14
  import { runSpawn, SpawnExitError } from "../lib/run-spawn";
11
15
 
12
- const REGISTRY_BASE_URL = "https://r.assistant-ui.com";
13
-
14
16
  export interface AddComponentsPlan {
15
17
  command: string;
16
18
  args: string[];
@@ -23,12 +25,13 @@ export function createAddComponentsPlan(params: {
23
25
  overwrite?: boolean;
24
26
  cwd?: string;
25
27
  path?: string;
28
+ style?: string;
26
29
  }): AddComponentsPlan {
27
30
  const componentsToAdd = params.components.map((c) => {
28
31
  if (!/^[a-zA-Z0-9-/]+$/.test(c)) {
29
32
  throw new Error(`Invalid component name: ${c}`);
30
33
  }
31
- return `${REGISTRY_BASE_URL}/${encodeURIComponent(c)}.json`;
34
+ return resolveRegistryItemUrl(c, params.style);
32
35
  });
33
36
 
34
37
  const [command, dlxArgs] = dlxCommand(params.packageManager);
@@ -64,7 +67,7 @@ export const add = new Command()
64
67
  // Check if project is initialized
65
68
  if (!hasConfig(opts.cwd)) {
66
69
  logger.warn(
67
- "It looks like you haven't initialized your project yet. Run 'assistant-ui init' first.",
70
+ "It looks like you haven't initialized your project yet — defaulting to Base UI flavored components. Run 'assistant-ui init' first for a configured setup.",
68
71
  );
69
72
  logger.break();
70
73
  }
@@ -75,6 +78,7 @@ export const add = new Command()
75
78
  opts.cwd,
76
79
  resolvePackageManager(opts),
77
80
  );
81
+ const style = getComponentsJsonStyle(opts.cwd);
78
82
  const { command, args } = createAddComponentsPlan({
79
83
  components,
80
84
  packageManager,
@@ -82,6 +86,7 @@ export const add = new Command()
82
86
  overwrite: opts.overwrite,
83
87
  cwd: opts.cwd,
84
88
  path: opts.path,
89
+ ...(style === undefined ? {} : { style }),
85
90
  });
86
91
 
87
92
  try {
@@ -12,6 +12,7 @@ import {
12
12
  resolvePackageManagerForCwd,
13
13
  scaffoldProject,
14
14
  transformProject,
15
+ type TransformResult,
15
16
  } from "../lib/create-project";
16
17
  import { runSpawn, SpawnExitError } from "../lib/run-spawn";
17
18
  import {
@@ -104,11 +105,11 @@ export const PROJECT_METADATA: ProjectMetadata[] = [
104
105
  hasLocalComponents: false,
105
106
  },
106
107
  {
107
- name: "with-ai-sdk-v6",
108
- label: "AI SDK v6",
109
- description: "Vercel AI SDK v6",
108
+ name: "with-ai-sdk-v7",
109
+ label: "AI SDK v7",
110
+ description: "Vercel AI SDK v7",
110
111
  category: "example",
111
- path: "examples/with-ai-sdk-v6",
112
+ path: "examples/with-ai-sdk-v7",
112
113
  hasLocalComponents: false,
113
114
  },
114
115
  {
@@ -165,7 +166,7 @@ export const PROJECT_METADATA: ProjectMetadata[] = [
165
166
  description: "Realtime voice with ElevenLabs",
166
167
  category: "example",
167
168
  path: "examples/with-elevenlabs-conversational",
168
- hasLocalComponents: true,
169
+ hasLocalComponents: false,
169
170
  },
170
171
  {
171
172
  name: "with-elevenlabs-scribe",
@@ -181,7 +182,7 @@ export const PROJECT_METADATA: ProjectMetadata[] = [
181
182
  description: "Realtime voice with LiveKit",
182
183
  category: "example",
183
184
  path: "examples/with-livekit",
184
- hasLocalComponents: true,
185
+ hasLocalComponents: false,
185
186
  },
186
187
  {
187
188
  name: "with-expo",
@@ -197,7 +198,7 @@ export const PROJECT_METADATA: ProjectMetadata[] = [
197
198
  description: "AI-driven interactive UI components",
198
199
  category: "example",
199
200
  path: "examples/with-interactables",
200
- hasLocalComponents: true,
201
+ hasLocalComponents: false,
201
202
  },
202
203
  {
203
204
  name: "with-external-store",
@@ -255,6 +256,14 @@ export const PROJECT_METADATA: ProjectMetadata[] = [
255
256
  path: "examples/with-tanstack",
256
257
  hasLocalComponents: false,
257
258
  },
259
+ {
260
+ name: "with-resumable-stream",
261
+ label: "Resumable Stream",
262
+ description: "Resumable LLM stream that survives reload mid-response",
263
+ category: "example",
264
+ path: "examples/with-resumable-stream",
265
+ hasLocalComponents: false,
266
+ },
258
267
  ];
259
268
 
260
269
  // Examples that exist in the monorepo but are intentionally excluded from the CLI:
@@ -601,6 +610,7 @@ export const create = new Command()
601
610
  ? `Copying project from local source: ${localSourceRoot}`
602
611
  : "Downloading project...",
603
612
  );
613
+ let transformResult: TransformResult;
604
614
  try {
605
615
  const source = localSourceRoot
606
616
  ? { kind: "local" as const, rootDir: localSourceRoot }
@@ -624,7 +634,7 @@ export const create = new Command()
624
634
  }
625
635
 
626
636
  // 5. Run transform pipeline
627
- await transformProject(absoluteProjectDir, {
637
+ transformResult = await transformProject(absoluteProjectDir, {
628
638
  hasLocalComponents: project.hasLocalComponents,
629
639
  skipInstall: opts.skipInstall,
630
640
  packageManager: pm,
@@ -649,6 +659,16 @@ export const create = new Command()
649
659
  throw err;
650
660
  }
651
661
 
662
+ if (transformResult.registryInstallFailure) {
663
+ process.removeListener("exit", cleanupOnExit);
664
+ logger.break();
665
+ logger.error("Project created with missing components.");
666
+ logger.info("Retry the component install with:");
667
+ logger.info(` cd ${resolvedProjectDirectory}`);
668
+ logger.info(` ${transformResult.registryInstallFailure.retryCommand}`);
669
+ process.exit(1);
670
+ }
671
+
652
672
  // 6. Apply preset if provided
653
673
  if (scaffoldSelector.preset) {
654
674
  const presetUrl = resolvePresetUrl(scaffoldSelector.preset);
@@ -2,6 +2,8 @@ import { Command } from "commander";
2
2
  import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import chalk from "chalk";
5
+ import { compare, valid } from "semver";
6
+ import { findWorkspaceRoot, resolveRealPath } from "../lib/utils/workspace";
5
7
 
6
8
  const ASSISTANT_UI_PACKAGE_NAMES = new Set([
7
9
  "assistant-stream",
@@ -38,13 +40,7 @@ function processPackageDir(
38
40
  results: DiscoveredPackage[],
39
41
  visited: ProcessedDir,
40
42
  ): void {
41
- const real = (() => {
42
- try {
43
- return fs.realpathSync(pkgDir);
44
- } catch {
45
- return pkgDir;
46
- }
47
- })();
43
+ const real = resolveRealPath(pkgDir);
48
44
  if (visited.set.has(real)) return;
49
45
  visited.set.add(real);
50
46
 
@@ -106,13 +102,99 @@ function walkNodeModulesAt(
106
102
  }
107
103
  }
108
104
 
105
+ // pnpm keeps every real package dir in the `.pnpm` virtual store as
106
+ // `node_modules/.pnpm/<pkg>@<ver>[_<peerhash>]/node_modules/<pkg>` and never
107
+ // nests a package's deps inside its own dir, so the hoisted-layout walk above
108
+ // only ever reaches the project's direct deps. The store dir name encodes the
109
+ // package, so filtering by a tracked prefix keeps this pass O(tracked entries).
110
+ const TRACKED_PNPM_ENTRY_PREFIXES = [
111
+ "@assistant-ui+",
112
+ "assistant-ui@",
113
+ "assistant-stream@",
114
+ "assistant-cloud@",
115
+ ];
116
+
117
+ function isTrackedPnpmEntry(dirName: string): boolean {
118
+ return TRACKED_PNPM_ENTRY_PREFIXES.some((p) => dirName.startsWith(p));
119
+ }
120
+
121
+ function processTrackedPackagesIn(
122
+ nm: string,
123
+ results: DiscoveredPackage[],
124
+ visited: ProcessedDir,
125
+ ): void {
126
+ let entries: fs.Dirent[];
127
+ try {
128
+ entries = fs.readdirSync(nm, { withFileTypes: true });
129
+ } catch {
130
+ return;
131
+ }
132
+
133
+ for (const entry of entries) {
134
+ if (entry.name.startsWith(".")) continue;
135
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
136
+
137
+ if (entry.name.startsWith("@")) {
138
+ const scopeDir = path.join(nm, entry.name);
139
+ let scoped: fs.Dirent[];
140
+ try {
141
+ scoped = fs.readdirSync(scopeDir, { withFileTypes: true });
142
+ } catch {
143
+ continue;
144
+ }
145
+ for (const s of scoped) {
146
+ if (!s.isDirectory() && !s.isSymbolicLink()) continue;
147
+ if (!isTrackedPackage(`${entry.name}/${s.name}`)) continue;
148
+ processPackageDir(path.join(scopeDir, s.name), results, visited);
149
+ }
150
+ } else if (isTrackedPackage(entry.name)) {
151
+ processPackageDir(path.join(nm, entry.name), results, visited);
152
+ }
153
+ }
154
+ }
155
+
156
+ function walkPnpmStore(
157
+ cwd: string,
158
+ results: DiscoveredPackage[],
159
+ visited: ProcessedDir,
160
+ ): void {
161
+ const storeDir = path.join(cwd, "node_modules", ".pnpm");
162
+ let entries: fs.Dirent[];
163
+ try {
164
+ entries = fs.readdirSync(storeDir, { withFileTypes: true });
165
+ } catch {
166
+ return;
167
+ }
168
+
169
+ for (const entry of entries) {
170
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
171
+ if (!isTrackedPnpmEntry(entry.name)) continue;
172
+ processTrackedPackagesIn(
173
+ path.join(storeDir, entry.name, "node_modules"),
174
+ results,
175
+ visited,
176
+ );
177
+ }
178
+ }
179
+
109
180
  // Discover every installation of an assistant-ui-family package reachable
110
- // from `cwd`. Recurses into nested node_modules so transitive copies
111
- // (the real source of duplicate-version bugs) are not missed.
181
+ // from `cwd`. Node resolves packages through ancestor node_modules directories,
182
+ // so inspect each level to include workspace-hoisted installs. Nested installs
183
+ // and pnpm virtual stores are scanned to catch duplicate transitive copies.
112
184
  export function discoverInstalledPackages(cwd: string): DiscoveredPackage[] {
113
185
  const results: DiscoveredPackage[] = [];
114
186
  const visited: ProcessedDir = { set: new Set() };
115
- walkNodeModulesAt(cwd, results, visited);
187
+ let dir = resolveRealPath(cwd);
188
+ const scanRoot = findWorkspaceRoot(dir) ?? dir;
189
+
190
+ while (true) {
191
+ walkNodeModulesAt(dir, results, visited);
192
+ walkPnpmStore(dir, results, visited);
193
+
194
+ if (dir === scanRoot) break;
195
+ dir = path.dirname(dir);
196
+ }
197
+
116
198
  return results;
117
199
  }
118
200
 
@@ -177,40 +259,11 @@ async function fetchAllLatestVersions(
177
259
  return new Map(entries);
178
260
  }
179
261
 
180
- interface SemverParts {
181
- major: number;
182
- minor: number;
183
- patch: number;
184
- prerelease: string;
185
- }
186
-
187
- function parseSemver(v: string): SemverParts | null {
188
- const m = /^(\d+)\.(\d+)\.(\d+)(?:-([^+\s]+))?/.exec(v);
189
- if (!m) return null;
190
- return {
191
- major: parseInt(m[1]!, 10),
192
- minor: parseInt(m[2]!, 10),
193
- patch: parseInt(m[3]!, 10),
194
- prerelease: m[4] ?? "",
195
- };
196
- }
197
-
198
262
  export function compareSemver(a: string, b: string): number {
199
- const pa = parseSemver(a);
200
- const pb = parseSemver(b);
201
- if (!pa || !pb) return a.localeCompare(b);
202
- if (pa.major !== pb.major) return pa.major - pb.major;
203
- if (pa.minor !== pb.minor) return pa.minor - pb.minor;
204
- if (pa.patch !== pb.patch) return pa.patch - pb.patch;
205
-
206
- // Per SemVer §11: a version with a prerelease tag is *less than* the
207
- // same x.y.z without one. We compare tags lexically for a stable
208
- // ordering across prereleases — good enough for doctor's "is X older
209
- // than the npm latest" check.
210
- if (pa.prerelease === pb.prerelease) return 0;
211
- if (!pa.prerelease) return 1;
212
- if (!pb.prerelease) return -1;
213
- return pa.prerelease.localeCompare(pb.prerelease);
263
+ const validA = valid(a);
264
+ const validB = valid(b);
265
+ if (!validA || !validB) return a.localeCompare(b);
266
+ return compare(validA, validB);
214
267
  }
215
268
 
216
269
  export interface OutdatedPackage {
@@ -320,7 +373,7 @@ export const doctor = new Command()
320
373
  )
321
374
  .option("--no-network", "Skip the npm registry check for latest versions.")
322
375
  .action(async (opts: { cwd: string; network: boolean }) => {
323
- const cwd = path.resolve(opts.cwd);
376
+ const cwd = resolveRealPath(opts.cwd);
324
377
  const packageJsonPath = path.join(cwd, "package.json");
325
378
 
326
379
  if (!fs.existsSync(packageJsonPath)) {
@@ -5,6 +5,10 @@ import * as path from "node:path";
5
5
  import { spawnSync } from "node:child_process";
6
6
  import chalk from "chalk";
7
7
  import { detect } from "detect-package-manager";
8
+ import { satisfies } from "semver";
9
+ import { findWorkspaceRoot, resolveRealPath } from "../lib/utils/workspace";
10
+
11
+ export { findWorkspaceRoot };
8
12
 
9
13
  const ASSISTANT_UI_PACKAGES = [
10
14
  // Distribution
@@ -88,25 +92,6 @@ function getInstalledVersion(pkg: string, cwd: string): string | null {
88
92
  return null;
89
93
  }
90
94
 
91
- function findWorkspaceRoot(cwd: string): string | null {
92
- let dir = path.dirname(cwd);
93
- const root = path.parse(dir).root;
94
- while (dir !== root) {
95
- if (fs.existsSync(path.join(dir, "pnpm-workspace.yaml"))) return dir;
96
- const pkgPath = path.join(dir, "package.json");
97
- if (fs.existsSync(pkgPath)) {
98
- try {
99
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
100
- if (pkg.workspaces) return dir;
101
- } catch {
102
- // ignore
103
- }
104
- }
105
- dir = path.dirname(dir);
106
- }
107
- return null;
108
- }
109
-
110
95
  function readProjectDeps(
111
96
  projectPkg: Record<string, unknown>,
112
97
  ): Record<string, string> {
@@ -116,6 +101,16 @@ function readProjectDeps(
116
101
  };
117
102
  }
118
103
 
104
+ function getAssistantUiPackageNames(
105
+ projectPkg: Record<string, unknown>,
106
+ ): string[] {
107
+ const declaredPackages = Object.keys(readProjectDeps(projectPkg))
108
+ .filter((name) => name.startsWith("@assistant-ui/"))
109
+ .sort();
110
+
111
+ return [...new Set([...ASSISTANT_UI_PACKAGES, ...declaredPackages])];
112
+ }
113
+
119
114
  function getSpecifiedRange(
120
115
  pkg: string,
121
116
  projectPkg: Record<string, unknown>,
@@ -186,6 +181,19 @@ function getOsInfo(): string {
186
181
  }
187
182
  }
188
183
 
184
+ function getCliVersion(): string {
185
+ try {
186
+ const packageJson = JSON.parse(
187
+ fs.readFileSync(new URL("../../package.json", import.meta.url), "utf8"),
188
+ ) as { version?: unknown };
189
+ return typeof packageJson.version === "string"
190
+ ? packageJson.version
191
+ : "unknown";
192
+ } catch {
193
+ return "unknown";
194
+ }
195
+ }
196
+
189
197
  async function getPackageManagerInfo(
190
198
  cwd: string,
191
199
  ): Promise<{ name: string; version: string }> {
@@ -198,27 +206,18 @@ async function getPackageManagerInfo(
198
206
  return { name: pm, version };
199
207
  }
200
208
 
201
- function satisfiesRange(version: string, range: string): boolean {
202
- if (range === "*" || range === "any") return true;
203
-
204
- const clean = (v: string) => v.replace(/^[^\d]*/, "");
205
- const major = (v: string) => parseInt(clean(v).split(".")[0]!, 10);
206
-
207
- if (range.includes("||")) {
208
- return range
209
- .split("||")
210
- .some((part) => satisfiesRange(version, part.trim()));
211
- }
212
-
213
- const rangeMajor = major(range);
214
- const versionMajor = major(version);
215
-
216
- if (Number.isNaN(rangeMajor) || Number.isNaN(versionMajor)) return true;
217
-
218
- if (range.startsWith("^")) return versionMajor >= rangeMajor;
219
- if (range.startsWith(">=")) return versionMajor >= rangeMajor;
220
-
221
- return versionMajor >= rangeMajor;
209
+ export function satisfiesRange(version: string, range: string): boolean {
210
+ const normalizedRange = range.startsWith("workspace:")
211
+ ? range.slice("workspace:".length)
212
+ : range;
213
+
214
+ return (
215
+ normalizedRange === "" ||
216
+ normalizedRange === "^" ||
217
+ normalizedRange === "~" ||
218
+ normalizedRange === "any" ||
219
+ satisfies(version, normalizedRange, { includePrerelease: true })
220
+ );
222
221
  }
223
222
 
224
223
  interface PackageInfo {
@@ -228,6 +227,7 @@ interface PackageInfo {
228
227
  }
229
228
 
230
229
  interface InfoData {
230
+ cliVersion: string;
231
231
  os: string;
232
232
  node: string;
233
233
  pm: { name: string; version: string };
@@ -306,11 +306,16 @@ async function collectInfo(
306
306
  projectPkg: Record<string, unknown>,
307
307
  ): Promise<InfoData> {
308
308
  const pm = await getPackageManagerInfo(cwd);
309
- const packages = collectPackages(ASSISTANT_UI_PACKAGES, cwd, projectPkg);
309
+ const packages = collectPackages(
310
+ getAssistantUiPackageNames(projectPkg),
311
+ cwd,
312
+ projectPkg,
313
+ );
310
314
  const ecosystem = collectPackages(ECOSYSTEM_PACKAGES, cwd, projectPkg);
311
315
  const warnings = collectWarnings(packages, cwd, projectPkg);
312
316
 
313
317
  return {
318
+ cliVersion: getCliVersion(),
314
319
  os: getOsInfo(),
315
320
  node: process.version,
316
321
  pm,
@@ -338,6 +343,7 @@ function renderPlain(data: InfoData): string[] {
338
343
  const lines: string[] = [];
339
344
 
340
345
  lines.push("Environment:");
346
+ lines.push(` assistant-ui CLI: ${data.cliVersion}`);
341
347
  lines.push(` OS: ${data.os}`);
342
348
  lines.push(` Node.js: ${data.node}`);
343
349
  lines.push(` Package Manager: ${data.pm.name} ${data.pm.version}`);
@@ -364,6 +370,7 @@ function renderColored(data: InfoData): string[] {
364
370
  const lines: string[] = [];
365
371
 
366
372
  lines.push(chalk.bold("Environment:"));
373
+ lines.push(` assistant-ui CLI: ${data.cliVersion}`);
367
374
  lines.push(` OS: ${data.os}`);
368
375
  lines.push(` Node.js: ${data.node}`);
369
376
  lines.push(` Package Manager: ${data.pm.name} ${data.pm.version}`);
@@ -409,7 +416,7 @@ export const info = new Command()
409
416
  process.cwd(),
410
417
  )
411
418
  .action(async (opts) => {
412
- const cwd = path.resolve(opts.cwd);
419
+ const cwd = resolveRealPath(opts.cwd);
413
420
  const packageJsonPath = path.join(cwd, "package.json");
414
421
 
415
422
  if (!fs.existsSync(packageJsonPath)) {
@@ -8,35 +8,28 @@ import {
8
8
  } from "../lib/create-project";
9
9
  import { runSpawn, SpawnExitError } from "../lib/run-spawn";
10
10
  import { logger } from "../lib/utils/logger";
11
+ import {
12
+ getComponentsJsonStyle,
13
+ resolveQuickStartRegistryUrl,
14
+ } from "../lib/utils/registry";
11
15
  import { create } from "./create";
12
16
 
13
- const DEFAULT_REGISTRY_URL =
14
- "https://r.assistant-ui.com/chat/b/ai-sdk-quick-start/json";
15
-
16
17
  interface ExistingProjectInitPlan {
17
- initArgs: string[] | null;
18
+ initArgs: string[];
18
19
  addArgs: string[];
19
20
  }
20
21
 
21
22
  export function createExistingProjectInitPlan(params: {
22
23
  yes: boolean;
23
24
  overwrite: boolean;
24
- registryUrl: string;
25
25
  }): ExistingProjectInitPlan {
26
- const { yes, overwrite, registryUrl } = params;
27
-
28
- if (!yes) {
29
- const addArgs = [`shadcn@latest`, "add"];
30
- if (overwrite) addArgs.push("--overwrite");
31
- addArgs.push(registryUrl);
32
- return { initArgs: null, addArgs };
33
- }
34
-
35
- const initArgs = [`shadcn@latest`, "init", "--defaults", "--yes"];
36
-
37
- const addArgs = [`shadcn@latest`, "add", "--yes"];
26
+ const { yes, overwrite } = params;
27
+ const initArgs = yes
28
+ ? [`shadcn@latest`, "init", "--defaults", "--yes"]
29
+ : [`shadcn@latest`, "init"];
30
+ const addArgs = [`shadcn@latest`, "add"];
31
+ if (yes) addArgs.push("--yes");
38
32
  if (overwrite) addArgs.push("--overwrite");
39
- addArgs.push(registryUrl);
40
33
 
41
34
  return { initArgs, addArgs };
42
35
  }
@@ -106,7 +99,6 @@ export const init = new Command()
106
99
  return;
107
100
  }
108
101
 
109
- const registryUrl = DEFAULT_REGISTRY_URL;
110
102
  logger.info("Initializing assistant-ui in existing project...");
111
103
  logger.break();
112
104
 
@@ -131,13 +123,13 @@ export const init = new Command()
131
123
  const { initArgs, addArgs } = createExistingProjectInitPlan({
132
124
  yes: opts.yes,
133
125
  overwrite: opts.overwrite,
134
- registryUrl,
135
126
  });
136
127
 
137
- if (initArgs) {
138
- await runSpawn(dlxCmd, [...dlxArgs, ...initArgs], targetDir);
139
- }
140
- await runSpawn(dlxCmd, [...dlxArgs, ...addArgs], targetDir);
128
+ await runSpawn(dlxCmd, [...dlxArgs, ...initArgs], targetDir);
129
+ const registryUrl = resolveQuickStartRegistryUrl(
130
+ getComponentsJsonStyle(targetDir),
131
+ );
132
+ await runSpawn(dlxCmd, [...dlxArgs, ...addArgs, registryUrl], targetDir);
141
133
 
142
134
  logger.break();
143
135
  logger.success("Project initialized successfully!");