assistant-ui 0.0.105 → 0.0.106

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 (77) 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/event-names-to-camelcase.d.ts.map +1 -1
  7. package/dist/codemods/v0-12/primitive-if-to-aui-if.d.ts.map +1 -1
  8. package/dist/codemods/v0-8/ui-package-split.d.ts.map +1 -1
  9. package/dist/codemods/v0-9/edge-package-split.d.ts.map +1 -1
  10. package/dist/commands/add.d.ts +1 -1
  11. package/dist/commands/add.d.ts.map +1 -1
  12. package/dist/commands/add.js +7 -4
  13. package/dist/commands/add.js.map +1 -1
  14. package/dist/commands/agent.d.ts +0 -1
  15. package/dist/commands/agent.d.ts.map +1 -1
  16. package/dist/commands/create.d.ts +0 -1
  17. package/dist/commands/create.d.ts.map +1 -1
  18. package/dist/commands/create.js +12 -4
  19. package/dist/commands/create.js.map +1 -1
  20. package/dist/commands/doctor.d.ts +0 -1
  21. package/dist/commands/doctor.d.ts.map +1 -1
  22. package/dist/commands/doctor.js +57 -21
  23. package/dist/commands/doctor.js.map +1 -1
  24. package/dist/commands/info.d.ts +3 -2
  25. package/dist/commands/info.d.ts.map +1 -1
  26. package/dist/commands/info.js +12 -15
  27. package/dist/commands/info.js.map +1 -1
  28. package/dist/commands/init.d.ts +1 -3
  29. package/dist/commands/init.d.ts.map +1 -1
  30. package/dist/commands/init.js +14 -24
  31. package/dist/commands/init.js.map +1 -1
  32. package/dist/commands/mcp.d.ts +0 -1
  33. package/dist/commands/mcp.d.ts.map +1 -1
  34. package/dist/commands/mcp.js +14 -4
  35. package/dist/commands/mcp.js.map +1 -1
  36. package/dist/commands/update.d.ts +0 -1
  37. package/dist/commands/update.d.ts.map +1 -1
  38. package/dist/commands/update.js +14 -1
  39. package/dist/commands/update.js.map +1 -1
  40. package/dist/commands/upgrade.d.ts +0 -1
  41. package/dist/commands/upgrade.d.ts.map +1 -1
  42. package/dist/lib/agent-skill.d.ts +0 -1
  43. package/dist/lib/agent-skill.d.ts.map +1 -1
  44. package/dist/lib/create-project.d.ts.map +1 -1
  45. package/dist/lib/create-project.js +22 -8
  46. package/dist/lib/create-project.js.map +1 -1
  47. package/dist/lib/install-ai-sdk-lib.d.ts.map +1 -1
  48. package/dist/lib/install-edge-lib.d.ts.map +1 -1
  49. package/dist/lib/install-ui-lib.d.ts.map +1 -1
  50. package/dist/lib/run-spawn.d.ts.map +1 -1
  51. package/dist/lib/transform-options.d.ts.map +1 -1
  52. package/dist/lib/transform.d.ts +0 -1
  53. package/dist/lib/transform.d.ts.map +1 -1
  54. package/dist/lib/upgrade.d.ts +0 -1
  55. package/dist/lib/upgrade.d.ts.map +1 -1
  56. package/dist/lib/utils/config.d.ts.map +1 -1
  57. package/dist/lib/utils/file-scanner.d.ts.map +1 -1
  58. package/dist/lib/utils/logger.d.ts.map +1 -1
  59. package/dist/lib/utils/package-installer.d.ts.map +1 -1
  60. package/dist/lib/utils/package-manager.d.ts.map +1 -1
  61. package/dist/lib/utils/registry.d.ts +7 -0
  62. package/dist/lib/utils/registry.d.ts.map +1 -0
  63. package/dist/lib/utils/registry.js +24 -0
  64. package/dist/lib/utils/registry.js.map +1 -0
  65. package/dist/program.d.ts +0 -1
  66. package/dist/program.d.ts.map +1 -1
  67. package/package.json +10 -7
  68. package/src/commands/add.ts +8 -3
  69. package/src/commands/create.ts +12 -4
  70. package/src/commands/doctor.ts +83 -34
  71. package/src/commands/info.ts +32 -26
  72. package/src/commands/init.ts +16 -24
  73. package/src/commands/mcp.ts +20 -5
  74. package/src/commands/update.ts +17 -1
  75. package/src/lib/create-project.ts +40 -7
  76. package/src/lib/utils/registry.test.ts +108 -0
  77. package/src/lib/utils/registry.ts +39 -0
@@ -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!");
@@ -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
 
@@ -281,6 +286,18 @@ function transformPackageJson(projectDir: string): void {
281
286
  fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
282
287
  }
283
288
 
289
+ function parseTsConfig(content: string): any {
290
+ const errors: ParseError[] = [];
291
+ const tsconfig = parseJsonc(content, errors, { allowTrailingComma: true });
292
+ const error = errors[0];
293
+ if (error) {
294
+ throw new SyntaxError(
295
+ `Invalid tsconfig.json: ${printParseErrorCode(error.error)} at offset ${error.offset}`,
296
+ );
297
+ }
298
+ return tsconfig;
299
+ }
300
+
284
301
  function transformTsConfig(projectDir: string): void {
285
302
  const tsconfigPath = path.join(projectDir, "tsconfig.json");
286
303
 
@@ -289,16 +306,32 @@ function transformTsConfig(projectDir: string): void {
289
306
  }
290
307
 
291
308
  const content = fs.readFileSync(tsconfigPath, "utf-8");
292
- const tsconfig = JSON.parse(content);
309
+ const tsconfig = parseTsConfig(content);
293
310
 
294
311
  // Remove workspace paths
295
312
  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/*"];
313
+ const workspaceKeys = new Set([
314
+ "@/components/assistant-ui/*",
315
+ "@/components/icons/*",
316
+ "@/components/ui/*",
317
+ "@/components/ui/radix/*",
318
+ "@/hooks/*",
319
+ "@/lib/utils",
320
+ "@assistant-ui/ui/*",
321
+ ]);
322
+ for (const [key, targets] of Object.entries(
323
+ tsconfig.compilerOptions.paths as Record<string, unknown>,
324
+ )) {
325
+ const targetsWorkspace =
326
+ Array.isArray(targets) &&
327
+ targets.some(
328
+ (target) =>
329
+ typeof target === "string" && target.includes("packages/ui/"),
330
+ );
331
+ if (workspaceKeys.has(key) || targetsWorkspace) {
332
+ delete tsconfig.compilerOptions.paths[key];
333
+ }
334
+ }
302
335
 
303
336
  if (Object.keys(tsconfig.compilerOptions.paths).length === 0) {
304
337
  delete tsconfig.compilerOptions.paths;
@@ -0,0 +1,108 @@
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 plain URL without a style", () => {
98
+ expect(resolveRegistryItemUrl("thread")).toBe(
99
+ "https://r.assistant-ui.com/thread.json",
100
+ );
101
+ });
102
+
103
+ it("keeps the style inside a single path segment", () => {
104
+ expect(resolveRegistryItemUrl("thread", "base-nova?preview=1")).toBe(
105
+ "https://r.assistant-ui.com/styles/base-nova%3Fpreview%3D1/thread.json",
106
+ );
107
+ });
108
+ });
@@ -0,0 +1,39 @@
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
+ const registryUrl = style?.startsWith("base-")
35
+ ? `${REGISTRY_BASE_URL}/styles/${encodeURIComponent(style)}`
36
+ : REGISTRY_BASE_URL;
37
+
38
+ return `${registryUrl}/${encodeURIComponent(component)}.json`;
39
+ }