assistant-ui 0.0.106 → 0.0.108

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 (59) hide show
  1. package/dist/codemods/v0-12/assistant-api-to-aui.js.map +1 -1
  2. package/dist/codemods/v0-15/aui-accessor-calls-to-properties.d.ts +5 -0
  3. package/dist/codemods/v0-15/aui-accessor-calls-to-properties.d.ts.map +1 -0
  4. package/dist/codemods/v0-15/aui-accessor-calls-to-properties.js +58 -0
  5. package/dist/codemods/v0-15/aui-accessor-calls-to-properties.js.map +1 -0
  6. package/dist/codemods/v0-8/ui-package-split.js.map +1 -1
  7. package/dist/commands/add.js +1 -1
  8. package/dist/commands/add.js.map +1 -1
  9. package/dist/commands/create.d.ts.map +1 -1
  10. package/dist/commands/create.js +14 -4
  11. package/dist/commands/create.js.map +1 -1
  12. package/dist/commands/doctor.d.ts.map +1 -1
  13. package/dist/commands/doctor.js +11 -10
  14. package/dist/commands/doctor.js.map +1 -1
  15. package/dist/commands/info.d.ts +1 -1
  16. package/dist/commands/info.d.ts.map +1 -1
  17. package/dist/commands/info.js +13 -14
  18. package/dist/commands/info.js.map +1 -1
  19. package/dist/commands/upgrade.js +2 -2
  20. package/dist/commands/upgrade.js.map +1 -1
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.js +5 -5
  23. package/dist/index.js.map +1 -1
  24. package/dist/lib/create-project.d.ts +7 -2
  25. package/dist/lib/create-project.d.ts.map +1 -1
  26. package/dist/lib/create-project.js +13 -10
  27. package/dist/lib/create-project.js.map +1 -1
  28. package/dist/lib/run-spawn.d.ts.map +1 -1
  29. package/dist/lib/upgrade.d.ts.map +1 -1
  30. package/dist/lib/upgrade.js +2 -1
  31. package/dist/lib/upgrade.js.map +1 -1
  32. package/dist/lib/utils/logger.d.ts.map +1 -1
  33. package/dist/lib/utils/registry.js +2 -1
  34. package/dist/lib/utils/registry.js.map +1 -1
  35. package/dist/lib/utils/workspace.d.ts +6 -0
  36. package/dist/lib/utils/workspace.d.ts.map +1 -0
  37. package/dist/lib/utils/workspace.js +28 -0
  38. package/dist/lib/utils/workspace.js.map +1 -0
  39. package/dist/run.d.ts +5 -0
  40. package/dist/run.d.ts.map +1 -0
  41. package/dist/run.js +9 -0
  42. package/dist/run.js.map +1 -0
  43. package/package.json +4 -4
  44. package/plugin/skills/assistant-ui/SKILL.md +24 -5
  45. package/src/codemods/v0-15/__tests__/aui-accessor-calls-to-properties.test.ts +109 -0
  46. package/src/codemods/v0-15/aui-accessor-calls-to-properties.ts +83 -0
  47. package/src/commands/add.ts +1 -1
  48. package/src/commands/create.ts +16 -4
  49. package/src/commands/doctor.ts +17 -13
  50. package/src/commands/info.ts +21 -20
  51. package/src/commands/upgrade.ts +2 -2
  52. package/src/index.ts +5 -6
  53. package/src/lib/create-project.ts +25 -14
  54. package/src/lib/upgrade.ts +1 -0
  55. package/src/lib/utils/registry.test.ts +8 -2
  56. package/src/lib/utils/registry.ts +5 -1
  57. package/src/lib/utils/workspace.ts +34 -0
  58. package/src/run.test.ts +27 -0
  59. package/src/run.ts +5 -0
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","names":["path","fs"],"sources":["../../../src/lib/utils/registry.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nconst REGISTRY_BASE_URL = \"https://r.assistant-ui.com\";\n\nexport function getComponentsJsonStyle(cwd: string): string | undefined {\n try {\n const configPath = path.join(cwd, \"components.json\");\n const config = JSON.parse(fs.readFileSync(configPath, \"utf8\")) as {\n style?: unknown;\n };\n\n return typeof config.style === \"string\" ? config.style : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function resolveQuickStartRegistryUrl(style?: string): string {\n // The shadcn CLI appends a literal /json segment to fetched URLs containing\n // /chat/b/ unless they already end with it, so this item must use the direct\n // tree URLs rather than the /styles/ template.\n if (style === undefined || style.startsWith(\"base-\")) {\n return `${REGISTRY_BASE_URL}/base/chat/b/ai-sdk-quick-start/json`;\n }\n\n return `${REGISTRY_BASE_URL}/chat/b/ai-sdk-quick-start/json`;\n}\n\nexport function resolveRegistryItemUrl(\n component: string,\n style?: string,\n): string {\n const registryUrl = style?.startsWith(\"base-\")\n ? `${REGISTRY_BASE_URL}/styles/${encodeURIComponent(style)}`\n : REGISTRY_BASE_URL;\n\n return `${registryUrl}/${encodeURIComponent(component)}.json`;\n}\n"],"mappings":";;;AAGA,MAAM,oBAAoB;AAE1B,SAAgB,uBAAuB,KAAiC;CACtE,IAAI;EACF,MAAM,aAAaA,OAAK,KAAK,KAAK,iBAAiB;EACnD,MAAM,SAAS,KAAK,MAAMC,KAAG,aAAa,YAAY,MAAM,CAAC;EAI7D,OAAO,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ,KAAA;CAC3D,QAAQ;EACN;CACF;AACF;AAEA,SAAgB,6BAA6B,OAAwB;CAInE,IAAI,UAAU,KAAA,KAAa,MAAM,WAAW,OAAO,GACjD,OAAO,GAAG,kBAAkB;CAG9B,OAAO,GAAG,kBAAkB;AAC9B;AAEA,SAAgB,uBACd,WACA,OACQ;CAKR,OAAO,GAJa,OAAO,WAAW,OAAO,IACzC,GAAG,kBAAkB,UAAU,mBAAmB,KAAK,MACvD,kBAEkB,GAAG,mBAAmB,SAAS,EAAE;AACzD"}
1
+ {"version":3,"file":"registry.js","names":["path","fs"],"sources":["../../../src/lib/utils/registry.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nconst REGISTRY_BASE_URL = \"https://r.assistant-ui.com\";\n\nexport function getComponentsJsonStyle(cwd: string): string | undefined {\n try {\n const configPath = path.join(cwd, \"components.json\");\n const config = JSON.parse(fs.readFileSync(configPath, \"utf8\")) as {\n style?: unknown;\n };\n\n return typeof config.style === \"string\" ? config.style : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function resolveQuickStartRegistryUrl(style?: string): string {\n // The shadcn CLI appends a literal /json segment to fetched URLs containing\n // /chat/b/ unless they already end with it, so this item must use the direct\n // tree URLs rather than the /styles/ template.\n if (style === undefined || style.startsWith(\"base-\")) {\n return `${REGISTRY_BASE_URL}/base/chat/b/ai-sdk-quick-start/json`;\n }\n\n return `${REGISTRY_BASE_URL}/chat/b/ai-sdk-quick-start/json`;\n}\n\nexport function resolveRegistryItemUrl(\n component: string,\n style?: string,\n): string {\n if (style === undefined) {\n return `${REGISTRY_BASE_URL}/base/${encodeURIComponent(component)}.json`;\n }\n\n const registryUrl = style.startsWith(\"base-\")\n ? `${REGISTRY_BASE_URL}/styles/${encodeURIComponent(style)}`\n : REGISTRY_BASE_URL;\n\n return `${registryUrl}/${encodeURIComponent(component)}.json`;\n}\n"],"mappings":";;;AAGA,MAAM,oBAAoB;AAE1B,SAAgB,uBAAuB,KAAiC;CACtE,IAAI;EACF,MAAM,aAAaA,OAAK,KAAK,KAAK,iBAAiB;EACnD,MAAM,SAAS,KAAK,MAAMC,KAAG,aAAa,YAAY,MAAM,CAAC;EAI7D,OAAO,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ,KAAA;CAC3D,QAAQ;EACN;CACF;AACF;AAEA,SAAgB,6BAA6B,OAAwB;CAInE,IAAI,UAAU,KAAA,KAAa,MAAM,WAAW,OAAO,GACjD,OAAO,GAAG,kBAAkB;CAG9B,OAAO,GAAG,kBAAkB;AAC9B;AAEA,SAAgB,uBACd,WACA,OACQ;CACR,IAAI,UAAU,KAAA,GACZ,OAAO,GAAG,kBAAkB,QAAQ,mBAAmB,SAAS,EAAE;CAOpE,OAAO,GAJa,MAAM,WAAW,OAAO,IACxC,GAAG,kBAAkB,UAAU,mBAAmB,KAAK,MACvD,kBAEkB,GAAG,mBAAmB,SAAS,EAAE;AACzD"}
@@ -0,0 +1,6 @@
1
+ //#region src/lib/utils/workspace.d.ts
2
+ declare function resolveRealPath(inputPath: string): string;
3
+ declare function findWorkspaceRoot(cwd: string): string | null;
4
+ //#endregion
5
+ export { findWorkspaceRoot, resolveRealPath };
6
+ //# sourceMappingURL=workspace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.d.ts","names":[],"sources":["../../../src/lib/utils/workspace.ts"],"mappings":";iBAGgB,gBAAgB;iBAUhB,kBAAkB"}
@@ -0,0 +1,28 @@
1
+ import * as fs$1 from "node:fs";
2
+ import * as path$1 from "node:path";
3
+ //#region src/lib/utils/workspace.ts
4
+ function resolveRealPath(inputPath) {
5
+ const resolved = path$1.resolve(inputPath);
6
+ try {
7
+ return fs$1.realpathSync(resolved);
8
+ } catch {
9
+ return resolved;
10
+ }
11
+ }
12
+ function findWorkspaceRoot(cwd) {
13
+ let dir = path$1.resolve(cwd);
14
+ const root = path$1.parse(dir).root;
15
+ while (true) {
16
+ if (fs$1.existsSync(path$1.join(dir, "pnpm-workspace.yaml"))) return dir;
17
+ const pkgPath = path$1.join(dir, "package.json");
18
+ if (fs$1.existsSync(pkgPath)) try {
19
+ if (JSON.parse(fs$1.readFileSync(pkgPath, "utf8")).workspaces) return dir;
20
+ } catch {}
21
+ if (dir === root) return null;
22
+ dir = path$1.dirname(dir);
23
+ }
24
+ }
25
+ //#endregion
26
+ export { findWorkspaceRoot, resolveRealPath };
27
+
28
+ //# sourceMappingURL=workspace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.js","names":["path","fs"],"sources":["../../../src/lib/utils/workspace.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nexport function resolveRealPath(inputPath: string): string {\n const resolved = path.resolve(inputPath);\n\n try {\n return fs.realpathSync(resolved);\n } catch {\n return resolved;\n }\n}\n\nexport function findWorkspaceRoot(cwd: string): string | null {\n let dir = path.resolve(cwd);\n const root = path.parse(dir).root;\n\n while (true) {\n if (fs.existsSync(path.join(dir, \"pnpm-workspace.yaml\"))) return dir;\n\n const pkgPath = path.join(dir, \"package.json\");\n if (fs.existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf8\"));\n if (pkg.workspaces) return dir;\n } catch {\n // Malformed package manifests are not workspace markers.\n }\n }\n\n if (dir === root) return null;\n dir = path.dirname(dir);\n }\n}\n"],"mappings":";;;AAGA,SAAgB,gBAAgB,WAA2B;CACzD,MAAM,WAAWA,OAAK,QAAQ,SAAS;CAEvC,IAAI;EACF,OAAOC,KAAG,aAAa,QAAQ;CACjC,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAgB,kBAAkB,KAA4B;CAC5D,IAAI,MAAMD,OAAK,QAAQ,GAAG;CAC1B,MAAM,OAAOA,OAAK,MAAM,GAAG,CAAC,CAAC;CAE7B,OAAO,MAAM;EACX,IAAIC,KAAG,WAAWD,OAAK,KAAK,KAAK,qBAAqB,CAAC,GAAG,OAAO;EAEjE,MAAM,UAAUA,OAAK,KAAK,KAAK,cAAc;EAC7C,IAAIC,KAAG,WAAW,OAAO,GACvB,IAAI;GAEF,IADY,KAAK,MAAMA,KAAG,aAAa,SAAS,MAAM,CAChD,CAAC,CAAC,YAAY,OAAO;EAC7B,QAAQ,CAER;EAGF,IAAI,QAAQ,MAAM,OAAO;EACzB,MAAMD,OAAK,QAAQ,GAAG;CACxB;AACF"}
package/dist/run.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ //#region src/run.d.ts
2
+ declare function runCli(): Promise<void>;
3
+ //#endregion
4
+ export { runCli };
5
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","names":[],"sources":["../src/run.ts"],"mappings":";iBAEsB,UAAU"}
package/dist/run.js ADDED
@@ -0,0 +1,9 @@
1
+ import { buildProgram } from "./program.js";
2
+ //#region src/run.ts
3
+ async function runCli() {
4
+ await buildProgram().parseAsync();
5
+ }
6
+ //#endregion
7
+ export { runCli };
8
+
9
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","names":[],"sources":["../src/run.ts"],"sourcesContent":["import { buildProgram } from \"./program\";\n\nexport async function runCli(): Promise<void> {\n await buildProgram().parseAsync();\n}\n"],"mappings":";;AAEA,eAAsB,SAAwB;CAC5C,MAAM,aAAa,CAAC,CAAC,WAAW;AAClC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assistant-ui",
3
- "version": "0.0.106",
3
+ "version": "0.0.108",
4
4
  "description": "CLI for assistant-ui",
5
5
  "keywords": [
6
6
  "cli",
@@ -33,9 +33,9 @@
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",
38
+ "jscodeshift": "^17.4.0",
39
39
  "jsonc-parser": "^3.3.1",
40
40
  "semver": "^7.8.5",
41
41
  "@assistant-ui/agent-launcher": "0.1.9"
@@ -49,7 +49,7 @@
49
49
  "@types/semver": "^7.7.1",
50
50
  "@vitest/coverage-v8": "^4.1.10",
51
51
  "vitest": "^4.1.10",
52
- "@assistant-ui/x-buildutils": "0.0.18"
52
+ "@assistant-ui/x-buildutils": "0.0.20"
53
53
  },
54
54
  "publishConfig": {
55
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
@@ -0,0 +1,109 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import jscodeshift, { type API } from "jscodeshift";
3
+ import transform from "../aui-accessor-calls-to-properties";
4
+
5
+ const j = jscodeshift.withParser("tsx");
6
+
7
+ function applyTransform(source: string): string | null {
8
+ const fileInfo = {
9
+ path: "test.tsx",
10
+ source,
11
+ };
12
+
13
+ const api: API = {
14
+ jscodeshift: j,
15
+ j,
16
+ stats: () => {},
17
+ report: () => {},
18
+ };
19
+
20
+ return transform(fileInfo, api, {});
21
+ }
22
+
23
+ describe("aui-accessor-calls-to-properties", () => {
24
+ it("rewrites nullary accessor calls on a useAui variable", () => {
25
+ const input = `
26
+ const client = useAui();
27
+ client.thread().cancelRun();
28
+ const state = client.composer().getState();
29
+ `;
30
+ const expected = `
31
+ const client = useAui();
32
+ client.thread.cancelRun();
33
+ const state = client.composer.getState();
34
+ `;
35
+ expect(applyTransform(input)?.trim()).toBe(expected.trim());
36
+ });
37
+
38
+ it("rewrites only the accessor call in chained expressions", () => {
39
+ const input = `
40
+ const aui = useAui();
41
+ const part = aui.message().part({ index: 0 });
42
+ aui.message().composer().send();
43
+ `;
44
+ const expected = `
45
+ const aui = useAui();
46
+ const part = aui.message.part({ index: 0 });
47
+ aui.message.composer().send();
48
+ `;
49
+ expect(applyTransform(input)?.trim()).toBe(expected.trim());
50
+ });
51
+
52
+ it("rewrites identifiers named aui without a declaration", () => {
53
+ const input = `
54
+ const Derived = {
55
+ get: (aui) => aui.thread().message({ index: 0 }),
56
+ };
57
+ `;
58
+ const expected = `
59
+ const Derived = {
60
+ get: (aui) => aui.thread.message({ index: 0 }),
61
+ };
62
+ `;
63
+ expect(applyTransform(input)?.trim()).toBe(expected.trim());
64
+ });
65
+
66
+ it("rewrites parameters typed AssistantClient", () => {
67
+ const input = `
68
+ const getItem = (client: AssistantClient) => client.threadListItem().getState();
69
+ `;
70
+ const expected = `
71
+ const getItem = (client: AssistantClient) => client.threadListItem.getState();
72
+ `;
73
+ expect(applyTransform(input)?.trim()).toBe(expected.trim());
74
+ });
75
+
76
+ it("leaves calls with arguments untouched", () => {
77
+ const input = `
78
+ const aui = useAui();
79
+ const t = aui.threads().thread({ id: "t1" });
80
+ `;
81
+ const expected = `
82
+ const aui = useAui();
83
+ const t = aui.threads.thread({ id: "t1" });
84
+ `;
85
+ expect(applyTransform(input)?.trim()).toBe(expected.trim());
86
+ });
87
+
88
+ it("does not rewrite unknown receivers", () => {
89
+ const input = `
90
+ toolkit.tools();
91
+ message.composer().send();
92
+ ref.current.thread().getState();
93
+ `;
94
+ expect(applyTransform(input)).toBeNull();
95
+ });
96
+
97
+ it("does not rewrite non-scope member calls on aui", () => {
98
+ const input = `
99
+ const aui = useAui();
100
+ aui.subscribe(() => {});
101
+ aui.on("thread.updated", () => {});
102
+ `;
103
+ expect(applyTransform(input)).toBeNull();
104
+ });
105
+
106
+ it("returns null when nothing changes", () => {
107
+ expect(applyTransform(`const x = 1;`)).toBeNull();
108
+ });
109
+ });
@@ -0,0 +1,83 @@
1
+ import { createTransformer } from "../utils/createTransformer";
2
+
3
+ // Nullary scope accessors that became properties in v0.15. Parameterized
4
+ // lookups (e.g. `aui.thread.message({ id })`) stay as real calls.
5
+ const NULLARY_SCOPES = new Set([
6
+ "threads",
7
+ "threadListItem",
8
+ "thread",
9
+ "message",
10
+ "part",
11
+ "composer",
12
+ "attachment",
13
+ "modelContext",
14
+ "suggestions",
15
+ "suggestion",
16
+ "chainOfThought",
17
+ "queueItem",
18
+ "tools",
19
+ "dataRenderers",
20
+ "interactables",
21
+ "unstable_interactables",
22
+ "mcp",
23
+ "mcpServer",
24
+ "span",
25
+ ]);
26
+
27
+ const AUI_HOOKS = new Set(["useAui", "useAssistantApi"]);
28
+
29
+ const auiAccessorCallsToProperties = createTransformer(
30
+ ({ j, root, markAsChanged }) => {
31
+ const auiNames = new Set(["aui"]);
32
+
33
+ root.find(j.VariableDeclarator).forEach((path: any) => {
34
+ const { id, init } = path.value;
35
+ if (
36
+ j.Identifier.check(id) &&
37
+ init &&
38
+ j.CallExpression.check(init) &&
39
+ j.Identifier.check(init.callee) &&
40
+ AUI_HOOKS.has(init.callee.name)
41
+ ) {
42
+ auiNames.add(id.name);
43
+ }
44
+ });
45
+
46
+ const collectParam = (param: any) => {
47
+ const annotation = param?.typeAnnotation?.typeAnnotation;
48
+ if (
49
+ j.Identifier.check(param) &&
50
+ annotation &&
51
+ j.TSTypeReference.check(annotation) &&
52
+ j.Identifier.check(annotation.typeName) &&
53
+ annotation.typeName.name === "AssistantClient"
54
+ ) {
55
+ auiNames.add(param.name);
56
+ }
57
+ };
58
+ for (const fnType of [
59
+ j.FunctionDeclaration,
60
+ j.FunctionExpression,
61
+ j.ArrowFunctionExpression,
62
+ ] as const) {
63
+ root.find(fnType as typeof j.FunctionDeclaration).forEach((path: any) => {
64
+ path.value.params.forEach(collectParam);
65
+ });
66
+ }
67
+
68
+ root.find(j.CallExpression).forEach((path: any) => {
69
+ const node = path.value;
70
+ if (node.arguments.length !== 0) return;
71
+ const callee = node.callee;
72
+ if (!j.MemberExpression.check(callee) || callee.computed) return;
73
+ if (!j.Identifier.check(callee.property)) return;
74
+ if (!NULLARY_SCOPES.has(callee.property.name)) return;
75
+ if (!j.Identifier.check(callee.object)) return;
76
+ if (!auiNames.has(callee.object.name)) return;
77
+ j(path).replaceWith(callee);
78
+ markAsChanged();
79
+ });
80
+ },
81
+ );
82
+
83
+ export default auiAccessorCallsToProperties;
@@ -67,7 +67,7 @@ export const add = new Command()
67
67
  // Check if project is initialized
68
68
  if (!hasConfig(opts.cwd)) {
69
69
  logger.warn(
70
- "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.",
71
71
  );
72
72
  logger.break();
73
73
  }
@@ -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 {
@@ -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",
@@ -609,6 +610,7 @@ export const create = new Command()
609
610
  ? `Copying project from local source: ${localSourceRoot}`
610
611
  : "Downloading project...",
611
612
  );
613
+ let transformResult: TransformResult;
612
614
  try {
613
615
  const source = localSourceRoot
614
616
  ? { kind: "local" as const, rootDir: localSourceRoot }
@@ -632,7 +634,7 @@ export const create = new Command()
632
634
  }
633
635
 
634
636
  // 5. Run transform pipeline
635
- await transformProject(absoluteProjectDir, {
637
+ transformResult = await transformProject(absoluteProjectDir, {
636
638
  hasLocalComponents: project.hasLocalComponents,
637
639
  skipInstall: opts.skipInstall,
638
640
  packageManager: pm,
@@ -657,6 +659,16 @@ export const create = new Command()
657
659
  throw err;
658
660
  }
659
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
+
660
672
  // 6. Apply preset if provided
661
673
  if (scaffoldSelector.preset) {
662
674
  const presetUrl = resolvePresetUrl(scaffoldSelector.preset);
@@ -3,6 +3,7 @@ import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import chalk from "chalk";
5
5
  import { compare, valid } from "semver";
6
+ import { findWorkspaceRoot, resolveRealPath } from "../lib/utils/workspace";
6
7
 
7
8
  const ASSISTANT_UI_PACKAGE_NAMES = new Set([
8
9
  "assistant-stream",
@@ -39,13 +40,7 @@ function processPackageDir(
39
40
  results: DiscoveredPackage[],
40
41
  visited: ProcessedDir,
41
42
  ): void {
42
- const real = (() => {
43
- try {
44
- return fs.realpathSync(pkgDir);
45
- } catch {
46
- return pkgDir;
47
- }
48
- })();
43
+ const real = resolveRealPath(pkgDir);
49
44
  if (visited.set.has(real)) return;
50
45
  visited.set.add(real);
51
46
 
@@ -183,14 +178,23 @@ function walkPnpmStore(
183
178
  }
184
179
 
185
180
  // Discover every installation of an assistant-ui-family package reachable
186
- // from `cwd`. Recurses into nested node_modules so transitive copies
187
- // (the real source of duplicate-version bugs) are not missed, and scans the
188
- // pnpm `.pnpm` virtual store, which the hoisted walk cannot reach.
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.
189
184
  export function discoverInstalledPackages(cwd: string): DiscoveredPackage[] {
190
185
  const results: DiscoveredPackage[] = [];
191
186
  const visited: ProcessedDir = { set: new Set() };
192
- walkNodeModulesAt(cwd, results, visited);
193
- walkPnpmStore(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
+
194
198
  return results;
195
199
  }
196
200
 
@@ -369,7 +373,7 @@ export const doctor = new Command()
369
373
  )
370
374
  .option("--no-network", "Skip the npm registry check for latest versions.")
371
375
  .action(async (opts: { cwd: string; network: boolean }) => {
372
- const cwd = path.resolve(opts.cwd);
376
+ const cwd = resolveRealPath(opts.cwd);
373
377
  const packageJsonPath = path.join(cwd, "package.json");
374
378
 
375
379
  if (!fs.existsSync(packageJsonPath)) {
@@ -6,6 +6,9 @@ import { spawnSync } from "node:child_process";
6
6
  import chalk from "chalk";
7
7
  import { detect } from "detect-package-manager";
8
8
  import { satisfies } from "semver";
9
+ import { findWorkspaceRoot, resolveRealPath } from "../lib/utils/workspace";
10
+
11
+ export { findWorkspaceRoot };
9
12
 
10
13
  const ASSISTANT_UI_PACKAGES = [
11
14
  // Distribution
@@ -89,25 +92,6 @@ function getInstalledVersion(pkg: string, cwd: string): string | null {
89
92
  return null;
90
93
  }
91
94
 
92
- export function findWorkspaceRoot(cwd: string): string | null {
93
- let dir = cwd;
94
- const root = path.parse(dir).root;
95
- while (true) {
96
- if (fs.existsSync(path.join(dir, "pnpm-workspace.yaml"))) return dir;
97
- const pkgPath = path.join(dir, "package.json");
98
- if (fs.existsSync(pkgPath)) {
99
- try {
100
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
101
- if (pkg.workspaces) return dir;
102
- } catch {
103
- // ignore
104
- }
105
- }
106
- if (dir === root) return null;
107
- dir = path.dirname(dir);
108
- }
109
- }
110
-
111
95
  function readProjectDeps(
112
96
  projectPkg: Record<string, unknown>,
113
97
  ): Record<string, string> {
@@ -197,6 +181,19 @@ function getOsInfo(): string {
197
181
  }
198
182
  }
199
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
+
200
197
  async function getPackageManagerInfo(
201
198
  cwd: string,
202
199
  ): Promise<{ name: string; version: string }> {
@@ -230,6 +227,7 @@ interface PackageInfo {
230
227
  }
231
228
 
232
229
  interface InfoData {
230
+ cliVersion: string;
233
231
  os: string;
234
232
  node: string;
235
233
  pm: { name: string; version: string };
@@ -317,6 +315,7 @@ async function collectInfo(
317
315
  const warnings = collectWarnings(packages, cwd, projectPkg);
318
316
 
319
317
  return {
318
+ cliVersion: getCliVersion(),
320
319
  os: getOsInfo(),
321
320
  node: process.version,
322
321
  pm,
@@ -344,6 +343,7 @@ function renderPlain(data: InfoData): string[] {
344
343
  const lines: string[] = [];
345
344
 
346
345
  lines.push("Environment:");
346
+ lines.push(` assistant-ui CLI: ${data.cliVersion}`);
347
347
  lines.push(` OS: ${data.os}`);
348
348
  lines.push(` Node.js: ${data.node}`);
349
349
  lines.push(` Package Manager: ${data.pm.name} ${data.pm.version}`);
@@ -370,6 +370,7 @@ function renderColored(data: InfoData): string[] {
370
370
  const lines: string[] = [];
371
371
 
372
372
  lines.push(chalk.bold("Environment:"));
373
+ lines.push(` assistant-ui CLI: ${data.cliVersion}`);
373
374
  lines.push(` OS: ${data.os}`);
374
375
  lines.push(` Node.js: ${data.node}`);
375
376
  lines.push(` Package Manager: ${data.pm.name} ${data.pm.version}`);
@@ -415,7 +416,7 @@ export const info = new Command()
415
416
  process.cwd(),
416
417
  )
417
418
  .action(async (opts) => {
418
- const cwd = path.resolve(opts.cwd);
419
+ const cwd = resolveRealPath(opts.cwd);
419
420
  const packageJsonPath = path.join(cwd, "package.json");
420
421
 
421
422
  if (!fs.existsSync(packageJsonPath)) {
@@ -48,9 +48,9 @@ export const upgradeCommand = addTransformOptions(
48
48
  new Command()
49
49
  .command("upgrade")
50
50
  .description("Upgrade ai package dependencies and apply codemods"),
51
- ).action((options: TransformOptions) => {
51
+ ).action(async (options: TransformOptions) => {
52
52
  try {
53
- upgrade(options);
53
+ await upgrade(options);
54
54
  } catch (err) {
55
55
  const errorMessage = err instanceof Error ? err.message : String(err);
56
56
  const errorStack = err instanceof Error ? err.stack : undefined;
package/src/index.ts CHANGED
@@ -1,12 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { buildProgram } from "./program";
3
+ import { runCli } from "./run";
4
4
 
5
5
  process.on("SIGINT", () => process.exit(0));
6
6
  process.on("SIGTERM", () => process.exit(0));
7
7
 
8
- function main() {
9
- buildProgram().parse();
10
- }
11
-
12
- main();
8
+ void runCli().catch((error: unknown) => {
9
+ console.error(error);
10
+ process.exitCode = 1;
11
+ });