assistant-ui 0.0.112 → 0.0.114

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 (91) hide show
  1. package/dist/codemods/v0-12/assistant-api-to-aui.d.ts.map +1 -1
  2. package/dist/codemods/v0-12/assistant-api-to-aui.js +87 -62
  3. package/dist/codemods/v0-12/assistant-api-to-aui.js.map +1 -1
  4. package/dist/codemods/v0-12/primitive-if-to-aui-if.d.ts.map +1 -1
  5. package/dist/codemods/v0-12/primitive-if-to-aui-if.js +31 -45
  6. package/dist/codemods/v0-12/primitive-if-to-aui-if.js.map +1 -1
  7. package/dist/commands/add.d.ts +1 -1
  8. package/dist/commands/add.d.ts.map +1 -1
  9. package/dist/commands/add.js +4 -2
  10. package/dist/commands/add.js.map +1 -1
  11. package/dist/commands/create.d.ts.map +1 -1
  12. package/dist/commands/create.js +33 -10
  13. package/dist/commands/create.js.map +1 -1
  14. package/dist/commands/info.d.ts.map +1 -1
  15. package/dist/commands/info.js +2 -1
  16. package/dist/commands/info.js.map +1 -1
  17. package/dist/commands/init.d.ts.map +1 -1
  18. package/dist/commands/init.js +4 -2
  19. package/dist/commands/init.js.map +1 -1
  20. package/dist/commands/mcp.d.ts.map +1 -1
  21. package/dist/commands/mcp.js +52 -40
  22. package/dist/commands/mcp.js.map +1 -1
  23. package/dist/commands/update.js +6 -7
  24. package/dist/commands/update.js.map +1 -1
  25. package/dist/commands/upgrade.d.ts.map +1 -1
  26. package/dist/commands/upgrade.js +5 -2
  27. package/dist/commands/upgrade.js.map +1 -1
  28. package/dist/index.js +2 -6
  29. package/dist/index.js.map +1 -1
  30. package/dist/lib/agent-skill.d.ts +1 -1
  31. package/dist/lib/agent-skill.d.ts.map +1 -1
  32. package/dist/lib/agent-skill.js.map +1 -1
  33. package/dist/lib/create-project.d.ts +11 -3
  34. package/dist/lib/create-project.d.ts.map +1 -1
  35. package/dist/lib/create-project.js +127 -43
  36. package/dist/lib/create-project.js.map +1 -1
  37. package/dist/lib/handle-cli-error.d.ts +5 -0
  38. package/dist/lib/handle-cli-error.d.ts.map +1 -0
  39. package/dist/lib/handle-cli-error.js +17 -0
  40. package/dist/lib/handle-cli-error.js.map +1 -0
  41. package/dist/lib/install-ai-sdk-lib.js +8 -1
  42. package/dist/lib/install-ai-sdk-lib.js.map +1 -1
  43. package/dist/lib/install-edge-lib.js +4 -8
  44. package/dist/lib/install-edge-lib.js.map +1 -1
  45. package/dist/lib/run-spawn.d.ts +16 -2
  46. package/dist/lib/run-spawn.d.ts.map +1 -1
  47. package/dist/lib/run-spawn.js +87 -9
  48. package/dist/lib/run-spawn.js.map +1 -1
  49. package/dist/lib/transform.d.ts +1 -1
  50. package/dist/lib/transform.d.ts.map +1 -1
  51. package/dist/lib/transform.js +12 -25
  52. package/dist/lib/transform.js.map +1 -1
  53. package/dist/lib/upgrade.js +20 -16
  54. package/dist/lib/upgrade.js.map +1 -1
  55. package/dist/lib/utils/file-scanner.d.ts +9 -1
  56. package/dist/lib/utils/file-scanner.d.ts.map +1 -1
  57. package/dist/lib/utils/file-scanner.js +22 -20
  58. package/dist/lib/utils/file-scanner.js.map +1 -1
  59. package/dist/lib/utils/package-installer.js +1 -1
  60. package/dist/lib/utils/package-manager.d.ts +3 -1
  61. package/dist/lib/utils/package-manager.d.ts.map +1 -1
  62. package/dist/lib/utils/package-manager.js +22 -10
  63. package/dist/lib/utils/package-manager.js.map +1 -1
  64. package/package.json +7 -7
  65. package/plugin/skills/assistant-ui/SKILL.md +4 -4
  66. package/src/codemods/v0-12/__tests__/assistant-api-to-aui.test.ts +370 -0
  67. package/src/codemods/v0-12/__tests__/primitive-if-to-aui-if.test.ts +100 -0
  68. package/src/codemods/v0-12/assistant-api-to-aui.ts +227 -134
  69. package/src/codemods/v0-12/primitive-if-to-aui-if.ts +44 -72
  70. package/src/commands/add.ts +5 -5
  71. package/src/commands/create.ts +41 -7
  72. package/src/commands/info.ts +1 -0
  73. package/src/commands/init.ts +4 -6
  74. package/src/commands/mcp.ts +57 -41
  75. package/src/commands/update.ts +6 -8
  76. package/src/commands/upgrade.ts +5 -2
  77. package/src/index.ts +2 -7
  78. package/src/lib/agent-skill.ts +2 -1
  79. package/src/lib/create-project.test.ts +175 -0
  80. package/src/lib/create-project.ts +189 -59
  81. package/src/lib/handle-cli-error.test.ts +67 -0
  82. package/src/lib/handle-cli-error.ts +17 -0
  83. package/src/lib/install-ai-sdk-lib.ts +13 -1
  84. package/src/lib/install-edge-lib.ts +7 -8
  85. package/src/lib/run-spawn.test.ts +148 -0
  86. package/src/lib/run-spawn.ts +121 -11
  87. package/src/lib/transform.test.ts +87 -0
  88. package/src/lib/transform.ts +21 -37
  89. package/src/lib/upgrade.ts +23 -19
  90. package/src/lib/utils/file-scanner.ts +26 -20
  91. package/src/lib/utils/package-manager.ts +29 -12
@@ -120,12 +120,18 @@ const fixedConditionMap: Record<string, Record<string, string>> = {
120
120
  },
121
121
  };
122
122
 
123
+ // A prop value the maps cannot faithfully express as a static condition
124
+ // (dynamic expressions, `{undefined}`); elements carrying one are skipped
125
+ // so runtime behavior is never silently changed.
126
+ const UNSUPPORTED_VALUE: unique symbol = Symbol("unsupported");
127
+
123
128
  /**
124
129
  * Extract the value of a JSX attribute.
125
130
  * - Boolean prop (no value): `<X.If user>` → `true`
126
131
  * - `{true}` / `{false}`: → `true` / `false`
127
132
  * - `{"positive"}`: → `"positive"`
128
133
  * - `{null}`: → `null`
134
+ * - anything else (dynamic expressions): → UNSUPPORTED_VALUE
129
135
  */
130
136
  const getAttrValue = (j: any, attr: any): unknown => {
131
137
  // Boolean attribute (no value), e.g. `<X.If user>`
@@ -137,12 +143,14 @@ const getAttrValue = (j: any, attr: any): unknown => {
137
143
  if (j.JSXExpressionContainer.check(attr.value)) {
138
144
  const expr = attr.value.expression;
139
145
  if (j.BooleanLiteral.check(expr)) return expr.value;
146
+ // NullLiteral bases Literal in ast-types but carries no `value` field,
147
+ // so it must be recognized before the generic Literal branch.
148
+ if (j.NullLiteral.check(expr)) return null;
140
149
  if (j.Literal.check(expr)) {
141
150
  if (expr.value === null) return null;
142
151
  return expr.value;
143
152
  }
144
- if (j.NullLiteral.check(expr)) return null;
145
- if (j.Identifier.check(expr) && expr.name === "undefined") return undefined;
153
+ return UNSUPPORTED_VALUE;
146
154
  }
147
155
 
148
156
  // String literal
@@ -150,7 +158,7 @@ const getAttrValue = (j: any, attr: any): unknown => {
150
158
  return attr.value.value;
151
159
  }
152
160
 
153
- return undefined;
161
+ return UNSUPPORTED_VALUE;
154
162
  };
155
163
 
156
164
  const buildConditionString = (fragments: ConditionFragment[]): string => {
@@ -185,61 +193,52 @@ const migratePrimitiveIfToAuiIf = createTransformer(
185
193
 
186
194
  if (importedPrimitives.size === 0) return;
187
195
 
188
- // Process fixed-condition components: <ThreadPrimitive.Empty> → <AuiIf condition={...}>
189
- root.find(j.JSXOpeningElement).forEach((path: any) => {
190
- const name = path.value.name;
191
- if (!j.JSXMemberExpression.check(name)) return;
192
- if (!j.JSXIdentifier.check(name.object)) return;
193
- if (!j.JSXIdentifier.check(name.property)) return;
194
-
195
- const primitiveName = name.object.name as string;
196
- const propertyName = name.property.name as string;
197
- const fixedMap = fixedConditionMap[primitiveName];
198
- if (!fixedMap) return;
199
- const conditionBody = fixedMap[propertyName];
200
- if (!conditionBody) return;
201
- if (!importedPrimitives.has(primitiveName)) return;
202
-
203
- // Only transform if there are no props (other than children, which are implicit)
204
- const attrs: any[] = path.value.attributes || [];
205
- if (attrs.length > 0) return;
206
-
196
+ // Opening and closing tags are rewritten together per element, so a
197
+ // skipped element can never be left with a mismatched closing tag.
198
+ const convertElementToAuiIf = (elementPath: any, conditionBody: string) => {
207
199
  const arrowFnAst = j(`(s) => ${conditionBody}`)
208
200
  .find(j.ArrowFunctionExpression)
209
201
  .paths()[0]!.value;
210
202
 
211
- path.value.name = j.jsxIdentifier("AuiIf");
212
- path.value.attributes = [
203
+ const opening = elementPath.value.openingElement;
204
+ opening.name = j.jsxIdentifier("AuiIf");
205
+ opening.attributes = [
213
206
  j.jsxAttribute(
214
207
  j.jsxIdentifier("condition"),
215
208
  j.jsxExpressionContainer(arrowFnAst),
216
209
  ),
217
210
  ];
211
+ if (elementPath.value.closingElement) {
212
+ elementPath.value.closingElement.name = j.jsxIdentifier("AuiIf");
213
+ }
218
214
 
219
215
  needsAuiIfImport = true;
220
216
  markAsChanged();
221
- });
217
+ };
222
218
 
223
- // Update closing elements for fixed-condition components
224
- root.find(j.JSXClosingElement).forEach((path: any) => {
225
- const name = path.value.name;
219
+ // Process fixed-condition components: <ThreadPrimitive.Empty> → <AuiIf condition={...}>
220
+ root.find(j.JSXElement).forEach((path: any) => {
221
+ const name = path.value.openingElement.name;
226
222
  if (!j.JSXMemberExpression.check(name)) return;
227
223
  if (!j.JSXIdentifier.check(name.object)) return;
228
224
  if (!j.JSXIdentifier.check(name.property)) return;
229
225
 
230
226
  const primitiveName = name.object.name as string;
231
227
  const propertyName = name.property.name as string;
232
- const fixedMap = fixedConditionMap[primitiveName];
233
- if (!fixedMap?.[propertyName]) return;
228
+ const conditionBody = fixedConditionMap[primitiveName]?.[propertyName];
229
+ if (!conditionBody) return;
234
230
  if (!importedPrimitives.has(primitiveName)) return;
235
231
 
236
- path.value.name = j.jsxIdentifier("AuiIf");
237
- markAsChanged();
232
+ // Only transform if there are no props (other than children, which are implicit)
233
+ const attrs: any[] = path.value.openingElement.attributes || [];
234
+ if (attrs.length > 0) return;
235
+
236
+ convertElementToAuiIf(path, conditionBody);
238
237
  });
239
238
 
240
239
  // Process JSX elements: <ThreadPrimitive.If ...> → <AuiIf condition={...}>
241
- root.find(j.JSXOpeningElement).forEach((path: any) => {
242
- const name = path.value.name;
240
+ root.find(j.JSXElement).forEach((path: any) => {
241
+ const name = path.value.openingElement.name;
243
242
 
244
243
  // Check for `<XPrimitive.If ...>`
245
244
  if (!j.JSXMemberExpression.check(name)) return;
@@ -253,7 +252,7 @@ const migratePrimitiveIfToAuiIf = createTransformer(
253
252
  if (!importedPrimitives.has(primitiveName)) return;
254
253
 
255
254
  // Extract props
256
- const attrs: any[] = path.value.attributes || [];
255
+ const attrs: any[] = path.value.openingElement.attributes || [];
257
256
  const fragments: ConditionFragment[] = [];
258
257
  let hasUnknownProp = false;
259
258
 
@@ -265,7 +264,11 @@ const migratePrimitiveIfToAuiIf = createTransformer(
265
264
  }
266
265
  const propName =
267
266
  typeof attr.name.name === "string" ? attr.name.name : null;
268
- if (!propName) continue;
267
+ if (!propName) {
268
+ // e.g. JSXNamespacedName — not expressible as a condition
269
+ hasUnknownProp = true;
270
+ continue;
271
+ }
269
272
 
270
273
  const mapper = propMap[propName];
271
274
  if (!mapper) {
@@ -274,6 +277,10 @@ const migratePrimitiveIfToAuiIf = createTransformer(
274
277
  }
275
278
 
276
279
  const value = getAttrValue(j, attr);
280
+ if (value === UNSUPPORTED_VALUE) {
281
+ hasUnknownProp = true;
282
+ continue;
283
+ }
277
284
  const fragment = mapper(value);
278
285
  if (fragment) {
279
286
  fragments.push(fragment);
@@ -283,42 +290,7 @@ const migratePrimitiveIfToAuiIf = createTransformer(
283
290
  // If we couldn't map all props, skip this element
284
291
  if (hasUnknownProp || fragments.length === 0) return;
285
292
 
286
- const conditionBody = buildConditionString(fragments);
287
-
288
- // Parse the arrow function as an expression to get a proper AST node
289
- const arrowFnAst = j(`(s) => ${conditionBody}`)
290
- .find(j.ArrowFunctionExpression)
291
- .paths()[0]!.value;
292
-
293
- // Replace <XPrimitive.If ...> with <AuiIf condition={...}>
294
- path.value.name = j.jsxIdentifier("AuiIf");
295
-
296
- // Replace all attributes with a single condition prop
297
- path.value.attributes = [
298
- j.jsxAttribute(
299
- j.jsxIdentifier("condition"),
300
- j.jsxExpressionContainer(arrowFnAst),
301
- ),
302
- ];
303
-
304
- needsAuiIfImport = true;
305
- markAsChanged();
306
- });
307
-
308
- // Update closing elements to match
309
- root.find(j.JSXClosingElement).forEach((path: any) => {
310
- const name = path.value.name;
311
- if (!j.JSXMemberExpression.check(name)) return;
312
- if (!j.JSXIdentifier.check(name.object)) return;
313
- if (!j.JSXIdentifier.check(name.property)) return;
314
- if (name.property.name !== "If") return;
315
-
316
- const primitiveName = name.object.name;
317
- if (!primitiveMap[primitiveName]) return;
318
- if (!importedPrimitives.has(primitiveName)) return;
319
-
320
- path.value.name = j.jsxIdentifier("AuiIf");
321
- markAsChanged();
293
+ convertElementToAuiIf(path, buildConditionString(fragments));
322
294
  });
323
295
 
324
296
  // Add AuiIf import if needed
@@ -5,13 +5,12 @@ import {
5
5
  getComponentsJsonStyle,
6
6
  resolveRegistryItemUrl,
7
7
  } from "../lib/utils/registry";
8
+ import { dlxCommand, resolvePackageManager } from "../lib/create-project";
9
+ import { runSpawn, SpawnExitError, SpawnSignalError } from "../lib/run-spawn";
8
10
  import {
9
- dlxCommand,
10
- resolvePackageManager,
11
- resolvePackageManagerForCwd,
12
11
  type PackageManagerName,
13
- } from "../lib/create-project";
14
- import { runSpawn, SpawnExitError } from "../lib/run-spawn";
12
+ resolvePackageManagerForCwd,
13
+ } from "../lib/utils/package-manager";
15
14
 
16
15
  export interface AddComponentsPlan {
17
16
  command: string;
@@ -92,6 +91,7 @@ export const add = new Command()
92
91
  try {
93
92
  await runSpawn(command, args, opts.cwd);
94
93
  } catch (error) {
94
+ if (error instanceof SpawnSignalError) throw error;
95
95
  if (error instanceof SpawnExitError) {
96
96
  logger.error(`Process exited with code ${error.code}`);
97
97
  process.exit(error.code);
@@ -9,12 +9,17 @@ import {
9
9
  downloadProject,
10
10
  resolveLatestReleaseRef,
11
11
  resolvePackageManager,
12
- resolvePackageManagerForCwd,
13
12
  scaffoldProject,
14
13
  transformProject,
15
14
  type TransformResult,
16
15
  } from "../lib/create-project";
17
- import { runSpawn, SpawnExitError } from "../lib/run-spawn";
16
+ import {
17
+ hasActiveSpawn,
18
+ runSpawn,
19
+ SpawnExitError,
20
+ SpawnSignalError,
21
+ } from "../lib/run-spawn";
22
+ import { resolvePackageManagerForCwd } from "../lib/utils/package-manager";
18
23
  import {
19
24
  buildSkillsAddCommand,
20
25
  resolveSkillsInstall,
@@ -597,10 +602,31 @@ export const create = new Command()
597
602
  );
598
603
 
599
604
  // Clean up partial project directory on unexpected exit (e.g. Ctrl+C)
605
+ let cleanupArmed = true;
600
606
  const cleanupOnExit = () => {
607
+ if (!cleanupArmed) return;
608
+ cleanupArmed = false;
601
609
  fs.rmSync(absoluteProjectDir, { recursive: true, force: true });
602
610
  };
611
+ const disarmCleanup = () => {
612
+ cleanupArmed = false;
613
+ process.removeListener("exit", cleanupOnExit);
614
+ process.removeListener("SIGINT", cleanupOnSignal);
615
+ process.removeListener("SIGTERM", cleanupOnSignal);
616
+ };
617
+ // Node emits no "exit" when a signal kills the process. An in-flight
618
+ // runSpawn forwards the signal itself, so the directory is removed on the
619
+ // error path once the child is reaped rather than while it is still writing.
620
+ const cleanupOnSignal = (signal: NodeJS.Signals) => {
621
+ if (hasActiveSpawn()) return;
622
+ cleanupOnExit();
623
+ disarmCleanup();
624
+ process.kill(process.pid, signal);
625
+ };
626
+
603
627
  process.once("exit", cleanupOnExit);
628
+ process.on("SIGINT", cleanupOnSignal);
629
+ process.on("SIGTERM", cleanupOnSignal);
604
630
 
605
631
  try {
606
632
  // 3. Resolve latest release ref (started before prompts)
@@ -655,7 +681,8 @@ export const create = new Command()
655
681
  });
656
682
  try {
657
683
  await runSpawn(skillsCmd, skillsArgs, absoluteProjectDir);
658
- } catch {
684
+ } catch (error) {
685
+ if (error instanceof SpawnSignalError) throw error;
659
686
  logger.warn(
660
687
  `Could not add assistant-ui agent skills. You can add them later with:\n ${skillsCmd} ${skillsArgs.join(" ")}`,
661
688
  );
@@ -663,12 +690,13 @@ export const create = new Command()
663
690
  }
664
691
  } catch (err) {
665
692
  // Clean up partially created project directory
666
- fs.rmSync(absoluteProjectDir, { recursive: true, force: true });
693
+ cleanupOnExit();
694
+ disarmCleanup();
667
695
  throw err;
668
696
  }
669
697
 
670
698
  if (transformResult.registryInstallFailure) {
671
- process.removeListener("exit", cleanupOnExit);
699
+ disarmCleanup();
672
700
  logger.break();
673
701
  logger.error("Project created with missing components.");
674
702
  logger.info("Retry the component install with:");
@@ -696,14 +724,15 @@ export const create = new Command()
696
724
  ],
697
725
  absoluteProjectDir,
698
726
  );
699
- } catch {
727
+ } catch (error) {
728
+ if (error instanceof SpawnSignalError) throw error;
700
729
  logger.warn(
701
730
  `Preset application failed. You can retry manually with:\n ${dlxCmd} ${[...dlxArgs, "shadcn@latest", "add", presetUrl].join(" ")}`,
702
731
  );
703
732
  }
704
733
  }
705
734
 
706
- process.removeListener("exit", cleanupOnExit);
735
+ disarmCleanup();
707
736
 
708
737
  logger.break();
709
738
  logger.success("Project created successfully!");
@@ -736,6 +765,11 @@ export const create = new Command()
736
765
  logger.info(` # Set up your environment variables in ${envFile}`);
737
766
  logger.info(` ${runCmd} ${devScript}`);
738
767
  } catch (error) {
768
+ if (error instanceof SpawnSignalError) {
769
+ cleanupOnExit();
770
+ disarmCleanup();
771
+ throw error;
772
+ }
739
773
  if (error instanceof SpawnExitError) {
740
774
  logger.error(`Project creation failed with code ${error.code}`);
741
775
  process.exit(error.code);
@@ -26,6 +26,7 @@ const ASSISTANT_UI_PACKAGES = [
26
26
  "@assistant-ui/cloud-ai-sdk",
27
27
  // Adapters
28
28
  "@assistant-ui/eve",
29
+ "@assistant-ui/ai-sdk",
29
30
  "@assistant-ui/react-ai-sdk",
30
31
  "@assistant-ui/react-langgraph",
31
32
  "@assistant-ui/react-ag-ui",
@@ -1,13 +1,10 @@
1
1
  import { Command, Option } from "commander";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
- import {
5
- dlxCommand,
6
- resolvePackageManager,
7
- resolvePackageManagerForCwd,
8
- } from "../lib/create-project";
9
- import { runSpawn, SpawnExitError } from "../lib/run-spawn";
4
+ import { dlxCommand, resolvePackageManager } from "../lib/create-project";
5
+ import { runSpawn, SpawnExitError, SpawnSignalError } from "../lib/run-spawn";
10
6
  import { logger } from "../lib/utils/logger";
7
+ import { resolvePackageManagerForCwd } from "../lib/utils/package-manager";
11
8
  import {
12
9
  getComponentsJsonStyle,
13
10
  resolveQuickStartRegistryUrl,
@@ -135,6 +132,7 @@ export const init = new Command()
135
132
  logger.success("Project initialized successfully!");
136
133
  logger.info("You can now add more components with 'assistant-ui add'");
137
134
  } catch (error) {
135
+ if (error instanceof SpawnSignalError) throw error;
138
136
  if (error instanceof SpawnExitError) {
139
137
  logger.error(`Initialization failed with code ${error.code}`);
140
138
  process.exit(error.code);
@@ -2,8 +2,8 @@ import { Command } from "commander";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import os from "node:os";
5
- import { spawn } from "cross-spawn";
6
5
  import { logger } from "../lib/utils/logger";
6
+ import { runSpawn, SpawnExitError, SpawnSignalError } from "../lib/run-spawn";
7
7
  import * as p from "@clack/prompts";
8
8
 
9
9
  type MCPTarget =
@@ -14,12 +14,15 @@ type MCPTarget =
14
14
  | "claude-code"
15
15
  | "claude-desktop";
16
16
 
17
+ const HOSTED_MCP_URL = "https://www.assistant-ui.com/mcp";
18
+
17
19
  const MCP_CONFIGS: Record<
18
20
  Exclude<MCPTarget, "claude-code">,
19
21
  {
20
22
  name: string;
21
23
  getPath: () => string;
22
24
  config: object;
25
+ replaceServerKey?: string;
23
26
  postInstall?: string;
24
27
  }
25
28
  > = {
@@ -29,11 +32,11 @@ const MCP_CONFIGS: Record<
29
32
  config: {
30
33
  mcpServers: {
31
34
  "assistant-ui": {
32
- command: "npx",
33
- args: ["-y", "@assistant-ui/mcp-docs-server"],
35
+ url: HOSTED_MCP_URL,
34
36
  },
35
37
  },
36
38
  },
39
+ replaceServerKey: "mcpServers",
37
40
  postInstall:
38
41
  "Open Cursor Settings → MCP → find 'assistant-ui' and click enable.",
39
42
  },
@@ -44,11 +47,11 @@ const MCP_CONFIGS: Record<
44
47
  config: {
45
48
  mcpServers: {
46
49
  "assistant-ui": {
47
- command: "npx",
48
- args: ["-y", "@assistant-ui/mcp-docs-server"],
50
+ serverUrl: HOSTED_MCP_URL,
49
51
  },
50
52
  },
51
53
  },
54
+ replaceServerKey: "mcpServers",
52
55
  postInstall: "Fully quit and re-open Windsurf to activate.",
53
56
  },
54
57
  vscode: {
@@ -57,12 +60,12 @@ const MCP_CONFIGS: Record<
57
60
  config: {
58
61
  servers: {
59
62
  "assistant-ui": {
60
- command: "npx",
61
- args: ["-y", "@assistant-ui/mcp-docs-server"],
62
- type: "stdio",
63
+ type: "http",
64
+ url: HOSTED_MCP_URL,
63
65
  },
64
66
  },
65
67
  },
68
+ replaceServerKey: "servers",
66
69
  postInstall:
67
70
  "Enable MCP in Settings → search 'MCP' → enable 'Chat > MCP'. Use Copilot Chat in Agent mode.",
68
71
  },
@@ -152,45 +155,42 @@ async function installForTarget(target: MCPTarget): Promise<void> {
152
155
  logger.info("Installing MCP server for Claude Code...");
153
156
  logger.break();
154
157
 
155
- const child = spawn(
156
- "claude",
157
- [
158
+ try {
159
+ await runSpawn("claude", ["mcp", "remove", "assistant-ui"]).catch(
160
+ (error: unknown) => {
161
+ if (error instanceof SpawnSignalError) throw error;
162
+ },
163
+ );
164
+ await runSpawn("claude", [
158
165
  "mcp",
159
166
  "add",
167
+ "--transport",
168
+ "http",
160
169
  "assistant-ui",
161
- "--",
162
- "npx",
163
- "-y",
164
- "@assistant-ui/mcp-docs-server",
165
- ],
166
- {
167
- stdio: "inherit",
168
- },
169
- );
170
-
171
- return new Promise((resolve, reject) => {
172
- child.on("error", (error) => {
170
+ HOSTED_MCP_URL,
171
+ ]);
172
+ } catch (error) {
173
+ if (error instanceof SpawnSignalError) throw error;
174
+ if (error instanceof SpawnExitError) {
175
+ logger.error(`Installation failed with code ${error.code}`);
176
+ } else if (error instanceof Error) {
173
177
  logger.error(`Failed to install: ${error.message}`);
174
178
  logger.info(
175
179
  "Make sure Claude Code CLI is installed: https://docs.anthropic.com/en/docs/claude-code",
176
180
  );
177
- reject(error);
178
- });
181
+ }
182
+ throw error;
183
+ }
179
184
 
180
- child.on("close", (code) => {
181
- if (code !== 0) {
182
- logger.error(`Installation failed with code ${code}`);
183
- reject(new Error(`Exit code ${code}`));
184
- } else {
185
- logger.break();
186
- logger.success("MCP server installed for Claude Code!");
187
- logger.info(
188
- "The server starts automatically. Try asking about assistant-ui!",
189
- );
190
- resolve();
191
- }
192
- });
193
- });
185
+ logger.break();
186
+ logger.success("MCP server installed for Claude Code!");
187
+ logger.info(
188
+ `Connects to the hosted assistant-ui MCP server at ${HOSTED_MCP_URL}.`,
189
+ );
190
+ logger.info(
191
+ "The server starts automatically. Try asking about assistant-ui!",
192
+ );
193
+ return;
194
194
  }
195
195
 
196
196
  if (target === "claude-desktop" && process.platform === "linux") {
@@ -230,12 +230,27 @@ async function installForTarget(target: MCPTarget): Promise<void> {
230
230
 
231
231
  const newConfig = deepMerge(existingConfig, targetConfig.config);
232
232
 
233
+ if (targetConfig.replaceServerKey) {
234
+ const key = targetConfig.replaceServerKey;
235
+ newConfig[key] = {
236
+ ...newConfig[key],
237
+ "assistant-ui": (targetConfig.config as any)[key]["assistant-ui"],
238
+ };
239
+ }
240
+
233
241
  fs.writeFileSync(configPath, `${JSON.stringify(newConfig, null, 2)}\n`);
234
242
 
235
243
  logger.break();
236
244
  logger.success(`MCP server installed for ${targetConfig.name}!`);
237
245
  logger.info(`Config written to: ${configPath}`);
238
246
 
247
+ if (targetConfig.replaceServerKey) {
248
+ logger.break();
249
+ logger.info(
250
+ `Connects to the hosted assistant-ui MCP server at ${HOSTED_MCP_URL}.`,
251
+ );
252
+ }
253
+
239
254
  if (targetConfig.postInstall) {
240
255
  logger.break();
241
256
  logger.info(targetConfig.postInstall);
@@ -244,7 +259,7 @@ async function installForTarget(target: MCPTarget): Promise<void> {
244
259
 
245
260
  export const mcp = new Command()
246
261
  .name("mcp")
247
- .description("install assistant-ui MCP docs server for your IDE")
262
+ .description("connect your IDE to the assistant-ui MCP server")
248
263
  .option("--cursor", "install for Cursor")
249
264
  .option("--windsurf", "install for Windsurf")
250
265
  .option("--vscode", "install for VSCode")
@@ -288,7 +303,8 @@ export const mcp = new Command()
288
303
  for (const target of targets) {
289
304
  try {
290
305
  await installForTarget(target);
291
- } catch {
306
+ } catch (error) {
307
+ if (error instanceof SpawnSignalError) throw error;
292
308
  process.exit(1);
293
309
  }
294
310
  }
@@ -1,10 +1,10 @@
1
1
  import { Command } from "commander";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
- import { sync as spawnSync } from "cross-spawn";
5
4
  import { logger } from "../lib/utils/logger";
6
5
  import { logPackageJsonParseError } from "../lib/utils/package-json";
7
6
  import { getInstallCommand } from "../lib/utils/package-manager";
7
+ import { runSpawn, SpawnExitError, SpawnSignalError } from "../lib/run-spawn";
8
8
 
9
9
  export const update = new Command()
10
10
  .name("update")
@@ -73,14 +73,12 @@ export const update = new Command()
73
73
  }
74
74
 
75
75
  logger.step("Updating packages...");
76
- const result = spawnSync(installCmd.command, installCmd.args, {
77
- stdio: "inherit",
78
- cwd: opts.cwd,
79
- });
80
-
81
- if (result.status !== 0) {
76
+ try {
77
+ await runSpawn(installCmd.command, installCmd.args, opts.cwd);
78
+ } catch (error) {
79
+ if (error instanceof SpawnSignalError) throw error;
82
80
  logger.error("Package manager update failed.");
83
- process.exit(result.status || 1);
81
+ process.exit(error instanceof SpawnExitError ? error.code : 1);
84
82
  }
85
83
 
86
84
  logger.break();
@@ -1,6 +1,7 @@
1
1
  import { Command } from "commander";
2
2
  import { transform } from "../lib/transform";
3
3
  import { upgrade } from "../lib/upgrade";
4
+ import { SpawnSignalError } from "../lib/run-spawn";
4
5
  import debug from "debug";
5
6
 
6
7
  export interface TransformOptions {
@@ -30,10 +31,11 @@ export const codemodCommand = addTransformOptions(
30
31
  .description("CLI tool for running codemods")
31
32
  .argument("<codemod>", "Codemod to run (e.g., rewrite-framework-imports)")
32
33
  .argument("<source>", "Path to source files or directory to transform"),
33
- ).action((codemod, source, options: TransformOptions) => {
34
+ ).action(async (codemod, source, options: TransformOptions) => {
34
35
  try {
35
- transform(codemod, source, options);
36
+ await transform(codemod, source, options);
36
37
  } catch (err) {
38
+ if (err instanceof SpawnSignalError) throw err;
37
39
  const errorMessage = err instanceof Error ? err.message : String(err);
38
40
  const errorStack = err instanceof Error ? err.stack : undefined;
39
41
  error(`Error transforming: ${errorMessage}`);
@@ -52,6 +54,7 @@ export const upgradeCommand = addTransformOptions(
52
54
  try {
53
55
  await upgrade(options);
54
56
  } catch (err) {
57
+ if (err instanceof SpawnSignalError) throw err;
55
58
  const errorMessage = err instanceof Error ? err.message : String(err);
56
59
  const errorStack = err instanceof Error ? err.stack : undefined;
57
60
  error(`Error upgrading: ${errorMessage}`);
package/src/index.ts CHANGED
@@ -1,11 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import { handleCliError } from "./lib/handle-cli-error";
3
4
  import { runCli } from "./run";
4
5
 
5
- process.on("SIGINT", () => process.exit(0));
6
- process.on("SIGTERM", () => process.exit(0));
7
-
8
- void runCli().catch((error: unknown) => {
9
- console.error(error);
10
- process.exitCode = 1;
11
- });
6
+ void runCli().catch(handleCliError);
@@ -1,4 +1,5 @@
1
- import { dlxCommand, type PackageManagerName } from "./create-project";
1
+ import { dlxCommand } from "./create-project";
2
+ import { type PackageManagerName } from "./utils/package-manager";
2
3
 
3
4
  export const SKILLS_PACKAGE = "assistant-ui/skills";
4
5