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
@@ -135,6 +135,18 @@ function deepMerge(target: any, source: any): any {
135
135
  return result;
136
136
  }
137
137
 
138
+ class McpConfigParseError extends Error {
139
+ constructor(targetName: string, configPath: string, flag: string) {
140
+ super(
141
+ `Invalid ${targetName} MCP config JSON at ${configPath}. Fix the JSON syntax, then run: assistant-ui mcp ${flag}`,
142
+ );
143
+ this.name = "McpConfigParseError";
144
+ }
145
+ }
146
+
147
+ const getTargetFlag = (target: Exclude<MCPTarget, "claude-code">) =>
148
+ `--${target}`;
149
+
138
150
  async function installForTarget(target: MCPTarget): Promise<void> {
139
151
  if (target === "claude-code") {
140
152
  logger.info("Installing MCP server for Claude Code...");
@@ -204,12 +216,15 @@ async function installForTarget(target: MCPTarget): Promise<void> {
204
216
  const content = fs.readFileSync(configPath, "utf-8");
205
217
  try {
206
218
  existingConfig = JSON.parse(content);
207
- } catch (e) {
208
- logger.error(`Could not parse existing config at ${configPath}`);
209
- logger.error(
210
- "Please fix the JSON syntax error before running this command.",
219
+ } catch {
220
+ const flag = getTargetFlag(target);
221
+ logger.error(`Could not parse ${targetConfig.name} MCP config.`);
222
+ logger.info(`Config path: ${configPath}`);
223
+ logger.info(
224
+ `Fix the JSON syntax in that file, then run: assistant-ui mcp ${flag}`,
211
225
  );
212
- throw e;
226
+ logger.info("No changes were written.");
227
+ throw new McpConfigParseError(targetConfig.name, configPath, flag);
213
228
  }
214
229
  }
215
230
 
@@ -5,6 +5,15 @@ import { sync as spawnSync } from "cross-spawn";
5
5
  import { logger } from "../lib/utils/logger";
6
6
  import { getInstallCommand } from "../lib/utils/package-manager";
7
7
 
8
+ const logPackageJsonParseError = (packageJsonPath: string) => {
9
+ logger.error("Could not parse package.json.");
10
+ console.error(`Package path: ${packageJsonPath}`);
11
+ console.error(
12
+ "Fix the JSON syntax in that file, then run: assistant-ui update",
13
+ );
14
+ console.error("No changes were written.");
15
+ };
16
+
8
17
  export const update = new Command()
9
18
  .name("update")
10
19
  .description(
@@ -23,7 +32,14 @@ export const update = new Command()
23
32
  process.exit(1);
24
33
  }
25
34
 
26
- const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
35
+ const packageJsonContent = fs.readFileSync(packageJsonPath, "utf-8");
36
+ let pkg: Record<string, Record<string, string> | undefined>;
37
+ try {
38
+ pkg = JSON.parse(packageJsonContent);
39
+ } catch {
40
+ logPackageJsonParseError(packageJsonPath);
41
+ process.exit(1);
42
+ }
27
43
  const sections = ["dependencies", "devDependencies"];
28
44
  const targets: string[] = [];
29
45
 
@@ -3,6 +3,11 @@ import * as path from "node:path";
3
3
  import { downloadTemplate } from "giget";
4
4
  import { sync as globSync } from "glob";
5
5
  import { detect } from "detect-package-manager";
6
+ import {
7
+ parse as parseJsonc,
8
+ printParseErrorCode,
9
+ type ParseError,
10
+ } from "jsonc-parser";
6
11
  import { logger } from "./utils/logger";
7
12
  import { runSpawn, SpawnExitError } from "./run-spawn";
8
13
 
@@ -205,22 +210,25 @@ export async function resolvePackageManagerForCwd(
205
210
  }
206
211
  }
207
212
 
213
+ export interface TransformResult {
214
+ registryInstallFailure?: { retryCommand: string };
215
+ }
216
+
208
217
  export async function transformProject(
209
218
  projectDir: string,
210
219
  opts: TransformOptions,
211
- ): Promise<void> {
220
+ ): Promise<TransformResult> {
212
221
  logger.step("Transforming package.json...");
213
222
  transformPackageJson(projectDir);
214
223
 
224
+ logger.step("Transforming project files...");
225
+ transformTsConfig(projectDir);
226
+ transformCssFiles(projectDir);
227
+
215
228
  let assistantUI: string[] | undefined;
216
229
  let shadcnUI: string[] | undefined;
217
230
 
218
231
  if (!opts.hasLocalComponents) {
219
- logger.step("Transforming project files...");
220
-
221
- transformTsConfig(projectDir);
222
- transformCssFiles(projectDir);
223
-
224
232
  const components = scanRequiredComponents(projectDir);
225
233
  assistantUI = components.assistantUI;
226
234
  shadcnUI = components.shadcnUI;
@@ -244,8 +252,15 @@ export async function transformProject(
244
252
  const auiComponents = assistantUI.map((c) => `@assistant-ui/${c}`);
245
253
  const components = [...allShadcn, ...auiComponents];
246
254
  logger.step(`Installing components: ${components.join(", ")}...`);
247
- await installShadcnRegistry(projectDir, components, "components", pm);
255
+ const failure = await installShadcnRegistry(
256
+ projectDir,
257
+ components,
258
+ "components",
259
+ pm,
260
+ );
261
+ if (failure) return { registryInstallFailure: failure };
248
262
  }
263
+ return {};
249
264
  }
250
265
 
251
266
  function transformPackageJson(projectDir: string): void {
@@ -281,6 +296,18 @@ function transformPackageJson(projectDir: string): void {
281
296
  fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
282
297
  }
283
298
 
299
+ function parseTsConfig(content: string): any {
300
+ const errors: ParseError[] = [];
301
+ const tsconfig = parseJsonc(content, errors, { allowTrailingComma: true });
302
+ const error = errors[0];
303
+ if (error) {
304
+ throw new SyntaxError(
305
+ `Invalid tsconfig.json: ${printParseErrorCode(error.error)} at offset ${error.offset}`,
306
+ );
307
+ }
308
+ return tsconfig;
309
+ }
310
+
284
311
  function transformTsConfig(projectDir: string): void {
285
312
  const tsconfigPath = path.join(projectDir, "tsconfig.json");
286
313
 
@@ -289,16 +316,33 @@ function transformTsConfig(projectDir: string): void {
289
316
  }
290
317
 
291
318
  const content = fs.readFileSync(tsconfigPath, "utf-8");
292
- const tsconfig = JSON.parse(content);
319
+ const tsconfig = parseTsConfig(content);
293
320
 
294
321
  // Remove workspace paths
295
322
  if (tsconfig.compilerOptions?.paths) {
296
- delete tsconfig.compilerOptions.paths["@/components/assistant-ui/*"];
297
- delete tsconfig.compilerOptions.paths["@/components/icons/*"];
298
- delete tsconfig.compilerOptions.paths["@/components/ui/*"];
299
- delete tsconfig.compilerOptions.paths["@/hooks/*"];
300
- delete tsconfig.compilerOptions.paths["@/lib/utils"];
301
- delete tsconfig.compilerOptions.paths["@assistant-ui/ui/*"];
323
+ const workspaceKeys = new Set([
324
+ "@/components/assistant-ui/*",
325
+ "@/components/icons/*",
326
+ "@/components/ui/*",
327
+ "@/components/ui/radix/*",
328
+ "@/hooks/*",
329
+ "@/lib/utils",
330
+ "@assistant-ui/ui/*",
331
+ ]);
332
+ for (const [key, targets] of Object.entries(
333
+ tsconfig.compilerOptions.paths as Record<string, unknown>,
334
+ )) {
335
+ const targetsWorkspace =
336
+ Array.isArray(targets) &&
337
+ targets.some(
338
+ (target) =>
339
+ typeof target === "string" &&
340
+ (target.includes("packages/ui/") || target.startsWith("../")),
341
+ );
342
+ if (workspaceKeys.has(key) || targetsWorkspace) {
343
+ delete tsconfig.compilerOptions.paths[key];
344
+ }
345
+ }
302
346
 
303
347
  if (Object.keys(tsconfig.compilerOptions.paths).length === 0) {
304
348
  delete tsconfig.compilerOptions.paths;
@@ -428,20 +472,20 @@ async function installShadcnRegistry(
428
472
  components: string[],
429
473
  label: string,
430
474
  pm: PackageManagerName,
431
- ): Promise<void> {
475
+ ): Promise<{ retryCommand: string } | undefined> {
432
476
  const [cmd, dlxArgs] = dlxCommand(pm);
433
477
  // For npm, dlxArgs may already include `--yes` for npx auto-install.
434
478
  // The trailing `--yes` is for shadcn's own confirmation prompt.
435
- const addArgs = [...dlxArgs, "shadcn@latest", "add", ...components, "--yes"];
479
+ const retryArgs = [...dlxArgs, "shadcn@latest", "add", ...components];
480
+ const addArgs = [...retryArgs, "--yes"];
436
481
 
437
482
  try {
438
483
  await runSpawn(cmd, addArgs, projectDir);
484
+ return undefined;
439
485
  } catch (error) {
440
486
  if (error instanceof SpawnExitError) {
441
- logger.warn(
442
- `shadcn exited with code ${error.code}. Run the following to retry:\n ${cmd} ${addArgs.slice(0, -1).join(" ")}`,
443
- );
444
- return;
487
+ logger.warn(`shadcn exited with code ${error.code}.`);
488
+ return { retryCommand: `${cmd} ${retryArgs.join(" ")}` };
445
489
  }
446
490
 
447
491
  const message = error instanceof Error ? error.message : String(error);
@@ -0,0 +1,114 @@
1
+ import * as fs from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
5
+ import {
6
+ getComponentsJsonStyle,
7
+ resolveQuickStartRegistryUrl,
8
+ resolveRegistryItemUrl,
9
+ } from "./registry";
10
+
11
+ describe("getComponentsJsonStyle", () => {
12
+ let cwd: string;
13
+
14
+ beforeEach(() => {
15
+ cwd = fs.mkdtempSync(path.join(os.tmpdir(), "assistant-ui-cli-"));
16
+ });
17
+
18
+ afterEach(() => {
19
+ fs.rmSync(cwd, { recursive: true, force: true });
20
+ });
21
+
22
+ it("reads the style from components.json", () => {
23
+ fs.writeFileSync(
24
+ path.join(cwd, "components.json"),
25
+ JSON.stringify({ style: "base-nova" }),
26
+ );
27
+
28
+ expect(getComponentsJsonStyle(cwd)).toBe("base-nova");
29
+ });
30
+
31
+ it("ignores a stale assistant-ui.json", () => {
32
+ fs.writeFileSync(
33
+ path.join(cwd, "assistant-ui.json"),
34
+ JSON.stringify({ style: "new-york" }),
35
+ );
36
+ fs.writeFileSync(
37
+ path.join(cwd, "components.json"),
38
+ JSON.stringify({ style: "base-nova" }),
39
+ );
40
+
41
+ expect(getComponentsJsonStyle(cwd)).toBe("base-nova");
42
+ });
43
+
44
+ it("falls back when components.json is missing", () => {
45
+ expect(getComponentsJsonStyle(cwd)).toBeUndefined();
46
+ });
47
+
48
+ it("falls back when components.json cannot be parsed", () => {
49
+ fs.writeFileSync(path.join(cwd, "components.json"), "{");
50
+
51
+ expect(getComponentsJsonStyle(cwd)).toBeUndefined();
52
+ });
53
+
54
+ it("falls back when the style is not a string", () => {
55
+ fs.writeFileSync(
56
+ path.join(cwd, "components.json"),
57
+ JSON.stringify({ style: 7 }),
58
+ );
59
+
60
+ expect(getComponentsJsonStyle(cwd)).toBeUndefined();
61
+ });
62
+ });
63
+
64
+ describe("resolveQuickStartRegistryUrl", () => {
65
+ it("uses the base quick start for base styles", () => {
66
+ expect(resolveQuickStartRegistryUrl("base-nova")).toBe(
67
+ "https://r.assistant-ui.com/base/chat/b/ai-sdk-quick-start/json",
68
+ );
69
+ });
70
+
71
+ it("uses the radix quick start for radix styles", () => {
72
+ expect(resolveQuickStartRegistryUrl("radix-nova")).toBe(
73
+ "https://r.assistant-ui.com/chat/b/ai-sdk-quick-start/json",
74
+ );
75
+ });
76
+
77
+ it("uses the base quick start without a style", () => {
78
+ expect(resolveQuickStartRegistryUrl()).toBe(
79
+ "https://r.assistant-ui.com/base/chat/b/ai-sdk-quick-start/json",
80
+ );
81
+ });
82
+ });
83
+
84
+ describe("resolveRegistryItemUrl", () => {
85
+ it("uses the style scoped URL for base styles", () => {
86
+ expect(resolveRegistryItemUrl("thread", "base-nova")).toBe(
87
+ "https://r.assistant-ui.com/styles/base-nova/thread.json",
88
+ );
89
+ });
90
+
91
+ it("uses the plain URL for non-base styles", () => {
92
+ expect(resolveRegistryItemUrl("thread", "nova")).toBe(
93
+ "https://r.assistant-ui.com/thread.json",
94
+ );
95
+ });
96
+
97
+ it("uses the base URL without a style", () => {
98
+ expect(resolveRegistryItemUrl("thread")).toBe(
99
+ "https://r.assistant-ui.com/base/thread.json",
100
+ );
101
+ });
102
+
103
+ it("uses the base URL for an explicit undefined style", () => {
104
+ expect(resolveRegistryItemUrl("thread", undefined)).toBe(
105
+ "https://r.assistant-ui.com/base/thread.json",
106
+ );
107
+ });
108
+
109
+ it("keeps the style inside a single path segment", () => {
110
+ expect(resolveRegistryItemUrl("thread", "base-nova?preview=1")).toBe(
111
+ "https://r.assistant-ui.com/styles/base-nova%3Fpreview%3D1/thread.json",
112
+ );
113
+ });
114
+ });
@@ -0,0 +1,43 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+
4
+ const REGISTRY_BASE_URL = "https://r.assistant-ui.com";
5
+
6
+ export function getComponentsJsonStyle(cwd: string): string | undefined {
7
+ try {
8
+ const configPath = path.join(cwd, "components.json");
9
+ const config = JSON.parse(fs.readFileSync(configPath, "utf8")) as {
10
+ style?: unknown;
11
+ };
12
+
13
+ return typeof config.style === "string" ? config.style : undefined;
14
+ } catch {
15
+ return undefined;
16
+ }
17
+ }
18
+
19
+ export function resolveQuickStartRegistryUrl(style?: string): string {
20
+ // The shadcn CLI appends a literal /json segment to fetched URLs containing
21
+ // /chat/b/ unless they already end with it, so this item must use the direct
22
+ // tree URLs rather than the /styles/ template.
23
+ if (style === undefined || style.startsWith("base-")) {
24
+ return `${REGISTRY_BASE_URL}/base/chat/b/ai-sdk-quick-start/json`;
25
+ }
26
+
27
+ return `${REGISTRY_BASE_URL}/chat/b/ai-sdk-quick-start/json`;
28
+ }
29
+
30
+ export function resolveRegistryItemUrl(
31
+ component: string,
32
+ style?: string,
33
+ ): string {
34
+ if (style === undefined) {
35
+ return `${REGISTRY_BASE_URL}/base/${encodeURIComponent(component)}.json`;
36
+ }
37
+
38
+ const registryUrl = style.startsWith("base-")
39
+ ? `${REGISTRY_BASE_URL}/styles/${encodeURIComponent(style)}`
40
+ : REGISTRY_BASE_URL;
41
+
42
+ return `${registryUrl}/${encodeURIComponent(component)}.json`;
43
+ }
@@ -0,0 +1,34 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+
4
+ export function resolveRealPath(inputPath: string): string {
5
+ const resolved = path.resolve(inputPath);
6
+
7
+ try {
8
+ return fs.realpathSync(resolved);
9
+ } catch {
10
+ return resolved;
11
+ }
12
+ }
13
+
14
+ export function findWorkspaceRoot(cwd: string): string | null {
15
+ let dir = path.resolve(cwd);
16
+ const root = path.parse(dir).root;
17
+
18
+ while (true) {
19
+ if (fs.existsSync(path.join(dir, "pnpm-workspace.yaml"))) return dir;
20
+
21
+ const pkgPath = path.join(dir, "package.json");
22
+ if (fs.existsSync(pkgPath)) {
23
+ try {
24
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
25
+ if (pkg.workspaces) return dir;
26
+ } catch {
27
+ // Malformed package manifests are not workspace markers.
28
+ }
29
+ }
30
+
31
+ if (dir === root) return null;
32
+ dir = path.dirname(dir);
33
+ }
34
+ }