assistant-ui 0.0.121 → 0.0.122

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 (55) hide show
  1. package/dist/commands/add.d.ts.map +1 -1
  2. package/dist/commands/add.js +1 -2
  3. package/dist/commands/add.js.map +1 -1
  4. package/dist/commands/create.d.ts.map +1 -1
  5. package/dist/commands/create.js +2 -2
  6. package/dist/commands/create.js.map +1 -1
  7. package/dist/commands/info.js +1 -1
  8. package/dist/commands/init.d.ts.map +1 -1
  9. package/dist/commands/init.js +1 -2
  10. package/dist/commands/init.js.map +1 -1
  11. package/dist/commands/mcp.d.ts.map +1 -1
  12. package/dist/commands/mcp.js +95 -23
  13. package/dist/commands/mcp.js.map +1 -1
  14. package/dist/lib/agent-skill.d.ts.map +1 -1
  15. package/dist/lib/agent-skill.js +9 -27
  16. package/dist/lib/agent-skill.js.map +1 -1
  17. package/dist/lib/create-project.d.ts +0 -9
  18. package/dist/lib/create-project.d.ts.map +1 -1
  19. package/dist/lib/create-project.js +15 -46
  20. package/dist/lib/create-project.js.map +1 -1
  21. package/dist/lib/transform.d.ts +0 -4
  22. package/dist/lib/transform.d.ts.map +1 -1
  23. package/dist/lib/transform.js +7 -16
  24. package/dist/lib/transform.js.map +1 -1
  25. package/dist/lib/utils/download.d.ts +5 -0
  26. package/dist/lib/utils/download.d.ts.map +1 -0
  27. package/dist/lib/utils/download.js +41 -0
  28. package/dist/lib/utils/download.js.map +1 -0
  29. package/dist/lib/utils/file-scanner.d.ts +0 -1
  30. package/dist/lib/utils/file-scanner.d.ts.map +1 -1
  31. package/dist/lib/utils/file-scanner.js +1 -16
  32. package/dist/lib/utils/file-scanner.js.map +1 -1
  33. package/dist/lib/utils/package-manager.d.ts +7 -0
  34. package/dist/lib/utils/package-manager.d.ts.map +1 -1
  35. package/dist/lib/utils/package-manager.js +15 -1
  36. package/dist/lib/utils/package-manager.js.map +1 -1
  37. package/package.json +3 -3
  38. package/src/commands/add.ts +2 -1
  39. package/src/commands/create.ts +5 -3
  40. package/src/commands/init.ts +5 -2
  41. package/src/commands/mcp.ts +223 -56
  42. package/src/lib/agent-skill.ts +17 -43
  43. package/src/lib/create-project.test.ts +0 -17
  44. package/src/lib/create-project.ts +33 -98
  45. package/src/lib/transform-files.test.ts +1 -3
  46. package/src/lib/transform.ts +6 -20
  47. package/src/lib/utils/download.ts +57 -0
  48. package/src/lib/utils/file-scanner.ts +0 -26
  49. package/src/lib/utils/package-manager.ts +26 -0
  50. package/src/lib/utils/registry.test.ts +0 -6
  51. package/templates/cloud-harness/app/api/chat/route.ts +1 -0
  52. package/templates/cloud-harness/components/assistant-ui/elements/collapsible-root.tsx +19 -0
  53. package/templates/cloud-harness/components/assistant-ui/elements/markdown-text.tsx +1 -1
  54. package/templates/cloud-harness/components/assistant-ui/elements/tool-fallback.aui.tsx +198 -30
  55. package/templates/cloud-harness/package.json +12 -12
@@ -0,0 +1,41 @@
1
+ import { downloadTemplate } from "giget";
2
+ //#region src/lib/utils/download.ts
3
+ const DOWNLOAD_TIMEOUT_MS = 3e4;
4
+ function resolveGitHubAuthToken() {
5
+ return (process.env.GIGET_AUTH ?? process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN)?.trim() || void 0;
6
+ }
7
+ async function withStagedDownload(source, options, timeoutMessage, cleanup, consumeDownload) {
8
+ const origDebug = process.env.DEBUG;
9
+ delete process.env.DEBUG;
10
+ let download;
11
+ let settled = false;
12
+ try {
13
+ const authToken = resolveGitHubAuthToken();
14
+ download = downloadTemplate(source, {
15
+ ...options,
16
+ ...authToken ? { auth: authToken } : {}
17
+ }).finally(() => {
18
+ settled = true;
19
+ });
20
+ let timer;
21
+ const timeout = new Promise((_, reject) => {
22
+ timer = setTimeout(() => reject(new Error(timeoutMessage)), DOWNLOAD_TIMEOUT_MS);
23
+ });
24
+ try {
25
+ return await consumeDownload(Promise.race([download, timeout]));
26
+ } finally {
27
+ clearTimeout(timer);
28
+ }
29
+ } finally {
30
+ try {
31
+ if (!download || settled) await cleanup();
32
+ else download.then(cleanup, cleanup);
33
+ } finally {
34
+ if (origDebug !== void 0) process.env.DEBUG = origDebug;
35
+ }
36
+ }
37
+ }
38
+ //#endregion
39
+ export { DOWNLOAD_TIMEOUT_MS, resolveGitHubAuthToken, withStagedDownload };
40
+
41
+ //# sourceMappingURL=download.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"download.js","names":[],"sources":["../../../src/lib/utils/download.ts"],"sourcesContent":["import { downloadTemplate } from \"giget\";\n\nexport const DOWNLOAD_TIMEOUT_MS = 30_000;\n\nexport function resolveGitHubAuthToken(): string | undefined {\n const token =\n process.env.GIGET_AUTH ?? process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;\n const trimmed = token?.trim();\n return trimmed || undefined;\n}\n\nexport async function withStagedDownload<T>(\n source: string,\n options: NonNullable<Parameters<typeof downloadTemplate>[1]>,\n timeoutMessage: string,\n cleanup: () => void | Promise<void>,\n consumeDownload: (download: Promise<unknown>) => Promise<T>,\n): Promise<T> {\n // giget logs to console.debug whenever DEBUG is set, which the `debug`\n // package does at module load for an unrelated namespace.\n const origDebug = process.env.DEBUG;\n delete process.env.DEBUG;\n let download: Promise<unknown> | undefined;\n let settled = false;\n try {\n const authToken = resolveGitHubAuthToken();\n download = downloadTemplate(source, {\n ...options,\n ...(authToken ? { auth: authToken } : {}),\n }).finally(() => {\n settled = true;\n });\n\n let timer: ReturnType<typeof setTimeout>;\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(\n () => reject(new Error(timeoutMessage)),\n DOWNLOAD_TIMEOUT_MS,\n );\n });\n try {\n return await consumeDownload(Promise.race([download, timeout]));\n } finally {\n clearTimeout(timer!);\n }\n } finally {\n try {\n if (!download || settled) {\n await cleanup();\n } else {\n void download.then(cleanup, cleanup);\n }\n } finally {\n if (origDebug !== undefined) process.env.DEBUG = origDebug;\n }\n }\n}\n"],"mappings":";;AAEA,MAAa,sBAAsB;AAEnC,SAAgB,yBAA6C;CAI3D,QAFE,QAAQ,IAAI,cAAc,QAAQ,IAAI,gBAAgB,QAAQ,IAAI,SAAA,EAC7C,KAAK,KACV,KAAA;AACpB;AAEA,eAAsB,mBACpB,QACA,SACA,gBACA,SACA,iBACY;CAGZ,MAAM,YAAY,QAAQ,IAAI;CAC9B,OAAO,QAAQ,IAAI;CACnB,IAAI;CACJ,IAAI,UAAU;CACd,IAAI;EACF,MAAM,YAAY,uBAAuB;EACzC,WAAW,iBAAiB,QAAQ;GAClC,GAAG;GACH,GAAI,YAAY,EAAE,MAAM,UAAU,IAAI,CAAC;EACzC,CAAC,CAAC,CAAC,cAAc;GACf,UAAU;EACZ,CAAC;EAED,IAAI;EACJ,MAAM,UAAU,IAAI,SAAgB,GAAG,WAAW;GAChD,QAAQ,iBACA,OAAO,IAAI,MAAM,cAAc,CAAC,GACtC,mBACF;EACF,CAAC;EACD,IAAI;GACF,OAAO,MAAM,gBAAgB,QAAQ,KAAK,CAAC,UAAU,OAAO,CAAC,CAAC;EAChE,UAAU;GACR,aAAa,KAAM;EACrB;CACF,UAAU;EACR,IAAI;GACF,IAAI,CAAC,YAAY,SACf,MAAM,QAAQ;QAEd,SAAc,KAAK,SAAS,OAAO;EAEvC,UAAU;GACR,IAAI,cAAc,KAAA,GAAW,QAAQ,IAAI,QAAQ;EACnD;CACF;AACF"}
@@ -12,5 +12,4 @@ export declare function readProjectFiles(pattern: string, options: {
12
12
  content: string;
13
13
  }>;
14
14
  export declare function scanForImport(importPattern: string | string[], options?: ScanOptions): boolean;
15
- export declare function getFilesContaining(searchString: string, options?: ScanOptions): string[];
16
15
  //# sourceMappingURL=file-scanner.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"file-scanner.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/file-scanner.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,wBAAiB,gBAAgB,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GAC1C,SAAS,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAYhE;AAED,wBAAgB,aAAa,CAC3B,aAAa,EAAE,MAAM,GAAG,MAAM,EAAE,EAChC,OAAO,GAAE,WAAgB,GACxB,OAAO,CAoBT;AAED,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,WAAgB,GACxB,MAAM,EAAE,CAqBV"}
1
+ {"version":3,"file":"file-scanner.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/file-scanner.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,wBAAiB,gBAAgB,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GAC1C,SAAS,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAYhE;AAED,wBAAgB,aAAa,CAC3B,aAAa,EAAE,MAAM,GAAG,MAAM,EAAE,EAChC,OAAO,GAAE,WAAgB,GACxB,OAAO,CAoBT"}
@@ -32,22 +32,7 @@ function scanForImport(importPattern, options = {}) {
32
32
  })) if (patterns.some((p) => content.includes(p))) return true;
33
33
  return false;
34
34
  }
35
- function getFilesContaining(searchString, options = {}) {
36
- const cwd = options.cwd || process.cwd();
37
- const pattern = options.pattern || "**/*.{js,jsx,ts,tsx}";
38
- const ignore = options.ignore || [
39
- "**/node_modules/**",
40
- "**/dist/**",
41
- "**/build/**"
42
- ];
43
- const result = [];
44
- for (const { fullPath, content } of readProjectFiles(pattern, {
45
- cwd,
46
- ignore
47
- })) if (content.includes(searchString)) result.push(fullPath);
48
- return result;
49
- }
50
35
  //#endregion
51
- export { getFilesContaining, readProjectFiles, scanForImport };
36
+ export { readProjectFiles, scanForImport };
52
37
 
53
38
  //# sourceMappingURL=file-scanner.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"file-scanner.js","names":["globSync","path","fs"],"sources":["../../../src/lib/utils/file-scanner.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { sync as globSync } from \"glob\";\n\nexport interface ScanOptions {\n cwd?: string;\n pattern?: string;\n ignore?: string[];\n}\n\nexport function* readProjectFiles(\n pattern: string,\n options: { cwd: string; ignore?: string[] },\n): Generator<{ file: string; fullPath: string; content: string }> {\n const files = globSync(pattern, options);\n\n for (const file of files) {\n const fullPath = path.join(options.cwd, file);\n try {\n const content = fs.readFileSync(fullPath, \"utf8\");\n yield { file, fullPath, content };\n } catch {\n continue;\n }\n }\n}\n\nexport function scanForImport(\n importPattern: string | string[],\n options: ScanOptions = {},\n): boolean {\n const cwd = options.cwd || process.cwd();\n const pattern = options.pattern || \"**/*.{js,jsx,ts,tsx}\";\n const ignore = options.ignore || [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/build/**\",\n ];\n\n const patterns = Array.isArray(importPattern)\n ? importPattern\n : [importPattern];\n\n for (const { content } of readProjectFiles(pattern, { cwd, ignore })) {\n if (patterns.some((p) => content.includes(p))) {\n return true;\n }\n }\n\n return false;\n}\n\nexport function getFilesContaining(\n searchString: string,\n options: ScanOptions = {},\n): string[] {\n const cwd = options.cwd || process.cwd();\n const pattern = options.pattern || \"**/*.{js,jsx,ts,tsx}\";\n const ignore = options.ignore || [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/build/**\",\n ];\n\n const result: string[] = [];\n\n for (const { fullPath, content } of readProjectFiles(pattern, {\n cwd,\n ignore,\n })) {\n if (content.includes(searchString)) {\n result.push(fullPath);\n }\n }\n\n return result;\n}\n"],"mappings":";;;;AAUA,UAAiB,iBACf,SACA,SACgE;CAChE,MAAM,QAAQA,KAAS,SAAS,OAAO;CAEvC,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,WAAWC,OAAK,KAAK,QAAQ,KAAK,IAAI;EAC5C,IAAI;GAEF,MAAM;IAAE;IAAM;IAAU,SADRC,KAAG,aAAa,UAAU,MACZ;GAAE;EAClC,QAAQ;GACN;EACF;CACF;AACF;AAEA,SAAgB,cACd,eACA,UAAuB,CAAC,GACf;CACT,MAAM,MAAM,QAAQ,OAAO,QAAQ,IAAI;CACvC,MAAM,UAAU,QAAQ,WAAW;CACnC,MAAM,SAAS,QAAQ,UAAU;EAC/B;EACA;EACA;CACF;CAEA,MAAM,WAAW,MAAM,QAAQ,aAAa,IACxC,gBACA,CAAC,aAAa;CAElB,KAAK,MAAM,EAAE,aAAa,iBAAiB,SAAS;EAAE;EAAK;CAAO,CAAC,GACjE,IAAI,SAAS,MAAM,MAAM,QAAQ,SAAS,CAAC,CAAC,GAC1C,OAAO;CAIX,OAAO;AACT;AAEA,SAAgB,mBACd,cACA,UAAuB,CAAC,GACd;CACV,MAAM,MAAM,QAAQ,OAAO,QAAQ,IAAI;CACvC,MAAM,UAAU,QAAQ,WAAW;CACnC,MAAM,SAAS,QAAQ,UAAU;EAC/B;EACA;EACA;CACF;CAEA,MAAM,SAAmB,CAAC;CAE1B,KAAK,MAAM,EAAE,UAAU,aAAa,iBAAiB,SAAS;EAC5D;EACA;CACF,CAAC,GACC,IAAI,QAAQ,SAAS,YAAY,GAC/B,OAAO,KAAK,QAAQ;CAIxB,OAAO;AACT"}
1
+ {"version":3,"file":"file-scanner.js","names":["globSync","path","fs"],"sources":["../../../src/lib/utils/file-scanner.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { sync as globSync } from \"glob\";\n\nexport interface ScanOptions {\n cwd?: string;\n pattern?: string;\n ignore?: string[];\n}\n\nexport function* readProjectFiles(\n pattern: string,\n options: { cwd: string; ignore?: string[] },\n): Generator<{ file: string; fullPath: string; content: string }> {\n const files = globSync(pattern, options);\n\n for (const file of files) {\n const fullPath = path.join(options.cwd, file);\n try {\n const content = fs.readFileSync(fullPath, \"utf8\");\n yield { file, fullPath, content };\n } catch {\n continue;\n }\n }\n}\n\nexport function scanForImport(\n importPattern: string | string[],\n options: ScanOptions = {},\n): boolean {\n const cwd = options.cwd || process.cwd();\n const pattern = options.pattern || \"**/*.{js,jsx,ts,tsx}\";\n const ignore = options.ignore || [\n \"**/node_modules/**\",\n \"**/dist/**\",\n \"**/build/**\",\n ];\n\n const patterns = Array.isArray(importPattern)\n ? importPattern\n : [importPattern];\n\n for (const { content } of readProjectFiles(pattern, { cwd, ignore })) {\n if (patterns.some((p) => content.includes(p))) {\n return true;\n }\n }\n\n return false;\n}\n"],"mappings":";;;;AAUA,UAAiB,iBACf,SACA,SACgE;CAChE,MAAM,QAAQA,KAAS,SAAS,OAAO;CAEvC,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,WAAWC,OAAK,KAAK,QAAQ,KAAK,IAAI;EAC5C,IAAI;GAEF,MAAM;IAAE;IAAM;IAAU,SADRC,KAAG,aAAa,UAAU,MACZ;GAAE;EAClC,QAAQ;GACN;EACF;CACF;AACF;AAEA,SAAgB,cACd,eACA,UAAuB,CAAC,GACf;CACT,MAAM,MAAM,QAAQ,OAAO,QAAQ,IAAI;CACvC,MAAM,UAAU,QAAQ,WAAW;CACnC,MAAM,SAAS,QAAQ,UAAU;EAC/B;EACA;EACA;CACF;CAEA,MAAM,WAAW,MAAM,QAAQ,aAAa,IACxC,gBACA,CAAC,aAAa;CAElB,KAAK,MAAM,EAAE,aAAa,iBAAiB,SAAS;EAAE;EAAK;CAAO,CAAC,GACjE,IAAI,SAAS,MAAM,MAAM,QAAQ,SAAS,CAAC,CAAC,GAC1C,OAAO;CAIX,OAAO;AACT"}
@@ -1,4 +1,11 @@
1
1
  export type PackageManagerName = "npm" | "pnpm" | "yarn" | "bun";
2
+ export declare function dlxCommand(pm: PackageManagerName): [string, string[]];
3
+ export declare function resolvePackageManager(opts: {
4
+ useNpm?: boolean;
5
+ usePnpm?: boolean;
6
+ useYarn?: boolean;
7
+ useBun?: boolean;
8
+ }): PackageManagerName | undefined;
2
9
  export declare function askQuestion(query: string): Promise<string>;
3
10
  export declare function isPackageInstalled(pkg: string, cwd?: string): boolean;
4
11
  export interface InstallCommand {
@@ -1 +1 @@
1
- {"version":3,"file":"package-manager.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/package-manager.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,kBAAkB,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;AAEjE,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAkC1D;AAED,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EACX,GAAG,GAAE,MAAsB,GAC1B,OAAO,CAgBT;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,EAC9B,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,cAAc,CAAC,CAazB;AAYD,wBAAsB,2BAA2B,CAC/C,GAAG,EAAE,MAAM,EACX,cAAc,CAAC,EAAE,kBAAkB,GAClC,OAAO,CAAC,kBAAkB,CAAC,CAS7B;AAED,wBAAsB,cAAc,CAClC,WAAW,EAAE,MAAM,EACnB,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,OAAO,CAAC,CAUlB"}
1
+ {"version":3,"file":"package-manager.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/package-manager.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,kBAAkB,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;AAEjE,wBAAgB,UAAU,CAAC,EAAE,EAAE,kBAAkB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAWrE;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,GAAG,kBAAkB,GAAG,SAAS,CAMjC;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAkC1D;AAED,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EACX,GAAG,GAAE,MAAsB,GAC1B,OAAO,CAgBT;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,EAC9B,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,cAAc,CAAC,CAazB;AAYD,wBAAsB,2BAA2B,CAC/C,GAAG,EAAE,MAAM,EACX,cAAc,CAAC,EAAE,kBAAkB,GAClC,OAAO,CAAC,kBAAkB,CAAC,CAS7B;AAED,wBAAsB,cAAc,CAClC,WAAW,EAAE,MAAM,EACnB,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,OAAO,CAAC,CAUlB"}
@@ -5,6 +5,20 @@ import * as path$1 from "node:path";
5
5
  import { detect } from "detect-package-manager";
6
6
  import * as readline from "node:readline";
7
7
  //#region src/lib/utils/package-manager.ts
8
+ function dlxCommand(pm) {
9
+ switch (pm) {
10
+ case "pnpm": return ["pnpm", ["dlx"]];
11
+ case "yarn": return ["yarn", ["dlx"]];
12
+ case "bun": return ["bunx", []];
13
+ case "npm": return ["npx", ["--yes"]];
14
+ }
15
+ }
16
+ function resolvePackageManager(opts) {
17
+ if (opts.useNpm) return "npm";
18
+ if (opts.usePnpm) return "pnpm";
19
+ if (opts.useYarn) return "yarn";
20
+ if (opts.useBun) return "bun";
21
+ }
8
22
  function askQuestion(query) {
9
23
  return new Promise((resolve) => {
10
24
  if (process.stdin.readableEnded) {
@@ -93,6 +107,6 @@ async function installPackage(packageName, cwd) {
93
107
  }
94
108
  }
95
109
  //#endregion
96
- export { askQuestion, getInstallCommand, installPackage, isPackageInstalled, resolvePackageManagerForCwd };
110
+ export { askQuestion, dlxCommand, getInstallCommand, installPackage, isPackageInstalled, resolvePackageManager, resolvePackageManagerForCwd };
97
111
 
98
112
  //# sourceMappingURL=package-manager.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"package-manager.js","names":["path","fs"],"sources":["../../../src/lib/utils/package-manager.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { detect } from \"detect-package-manager\";\nimport * as readline from \"node:readline\";\nimport { logger } from \"./logger\";\nimport { runSpawn, SpawnSignalError } from \"../run-spawn\";\n\nexport type PackageManagerName = \"npm\" | \"pnpm\" | \"yarn\" | \"bun\";\n\nexport function askQuestion(query: string): Promise<string> {\n return new Promise((resolve) => {\n // A stream only reaches EOF once, so a run that already consumed stdin gets\n // no further `end` and an interface built over it would wait on a `close`\n // that cannot arrive. An upgrade asks up to three questions.\n if (process.stdin.readableEnded) {\n resolve(\"\");\n return;\n }\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n // stdin at EOF (a piped or CI run) emits `close` without a `line`, so the\n // question callback alone would leave this pending forever and the process\n // would exit 0 with the remaining work silently skipped. An empty answer is\n // what pressing Enter sends, so EOF lands on the prompt's own default.\n // A final answer with no trailing newline arrives as `line` instead of\n // through the callback, and has to win over that default.\n // Ctrl-C in raw mode is delivered as this event rather than as a signal,\n // and without a listener readline answers it by closing — which the EOF\n // default would then read as approval. A cancelled prompt declines.\n let cancelled = false;\n rl.on(\"SIGINT\", () => {\n cancelled = true;\n rl.close();\n });\n rl.on(\"line\", resolve);\n rl.on(\"close\", () => resolve(cancelled ? \"n\" : \"\"));\n rl.question(query, (answer) => {\n resolve(answer);\n rl.close();\n });\n });\n}\n\nexport function isPackageInstalled(\n pkg: string,\n cwd: string = process.cwd(),\n): boolean {\n try {\n const pkgJsonPath = path.join(cwd, \"package.json\");\n if (fs.existsSync(pkgJsonPath)) {\n const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, \"utf8\"));\n const deps = pkgJson.dependencies || {};\n const devDeps = pkgJson.devDependencies || {};\n if (deps[pkg] || devDeps[pkg]) {\n return true;\n }\n }\n } catch {\n // Fall back to node_modules check below.\n }\n const modulePath = path.join(cwd, \"node_modules\", ...pkg.split(\"/\"));\n return fs.existsSync(modulePath);\n}\n\nexport interface InstallCommand {\n command: string;\n args: string[];\n}\n\nexport async function getInstallCommand(\n packageName: string | string[],\n cwd?: string,\n): Promise<InstallCommand> {\n const pm = await detect({ cwd });\n const packages = Array.isArray(packageName) ? packageName : [packageName];\n switch (pm) {\n case \"yarn\":\n return { command: \"yarn\", args: [\"add\", ...packages] };\n case \"pnpm\":\n return { command: \"pnpm\", args: [\"add\", ...packages] };\n case \"bun\":\n return { command: \"bun\", args: [\"add\", ...packages] };\n default:\n return { command: \"npm\", args: [\"install\", ...packages] };\n }\n}\n\nfunction detectFromUserAgent(): PackageManagerName | undefined {\n const ua = process.env.npm_config_user_agent;\n if (!ua) return undefined;\n if (ua.startsWith(\"bun/\")) return \"bun\";\n if (ua.startsWith(\"pnpm/\")) return \"pnpm\";\n if (ua.startsWith(\"yarn/\")) return \"yarn\";\n if (ua.startsWith(\"npm/\")) return \"npm\";\n return undefined;\n}\n\nexport async function resolvePackageManagerForCwd(\n cwd: string,\n packageManager?: PackageManagerName,\n): Promise<PackageManagerName> {\n if (packageManager) return packageManager;\n const fromAgent = detectFromUserAgent();\n if (fromAgent) return fromAgent;\n try {\n return await detect({ cwd });\n } catch {\n return \"npm\";\n }\n}\n\nexport async function installPackage(\n packageName: string,\n cwd?: string,\n): Promise<boolean> {\n try {\n const { command, args } = await getInstallCommand(packageName, cwd);\n await runSpawn(command, args, cwd);\n return true;\n } catch (e) {\n if (e instanceof SpawnSignalError) throw e;\n logger.error(`Installation failed: ${String(e)}`);\n return false;\n }\n}\n"],"mappings":";;;;;;;AASA,SAAgB,YAAY,OAAgC;CAC1D,OAAO,IAAI,SAAS,YAAY;EAI9B,IAAI,QAAQ,MAAM,eAAe;GAC/B,QAAQ,EAAE;GACV;EACF;EACA,MAAM,KAAK,SAAS,gBAAgB;GAClC,OAAO,QAAQ;GACf,QAAQ,QAAQ;EAClB,CAAC;EAUD,IAAI,YAAY;EAChB,GAAG,GAAG,gBAAgB;GACpB,YAAY;GACZ,GAAG,MAAM;EACX,CAAC;EACD,GAAG,GAAG,QAAQ,OAAO;EACrB,GAAG,GAAG,eAAe,QAAQ,YAAY,MAAM,EAAE,CAAC;EAClD,GAAG,SAAS,QAAQ,WAAW;GAC7B,QAAQ,MAAM;GACd,GAAG,MAAM;EACX,CAAC;CACH,CAAC;AACH;AAEA,SAAgB,mBACd,KACA,MAAc,QAAQ,IAAI,GACjB;CACT,IAAI;EACF,MAAM,cAAcA,OAAK,KAAK,KAAK,cAAc;EACjD,IAAIC,KAAG,WAAW,WAAW,GAAG;GAC9B,MAAM,UAAU,KAAK,MAAMA,KAAG,aAAa,aAAa,MAAM,CAAC;GAC/D,MAAM,OAAO,QAAQ,gBAAgB,CAAC;GACtC,MAAM,UAAU,QAAQ,mBAAmB,CAAC;GAC5C,IAAI,KAAK,QAAQ,QAAQ,MACvB,OAAO;EAEX;CACF,QAAQ,CAER;CACA,MAAM,aAAaD,OAAK,KAAK,KAAK,gBAAgB,GAAG,IAAI,MAAM,GAAG,CAAC;CACnE,OAAOC,KAAG,WAAW,UAAU;AACjC;AAOA,eAAsB,kBACpB,aACA,KACyB;CACzB,MAAM,KAAK,MAAM,OAAO,EAAE,IAAI,CAAC;CAC/B,MAAM,WAAW,MAAM,QAAQ,WAAW,IAAI,cAAc,CAAC,WAAW;CACxE,QAAQ,IAAR;EACE,KAAK,QACH,OAAO;GAAE,SAAS;GAAQ,MAAM,CAAC,OAAO,GAAG,QAAQ;EAAE;EACvD,KAAK,QACH,OAAO;GAAE,SAAS;GAAQ,MAAM,CAAC,OAAO,GAAG,QAAQ;EAAE;EACvD,KAAK,OACH,OAAO;GAAE,SAAS;GAAO,MAAM,CAAC,OAAO,GAAG,QAAQ;EAAE;EACtD,SACE,OAAO;GAAE,SAAS;GAAO,MAAM,CAAC,WAAW,GAAG,QAAQ;EAAE;CAC5D;AACF;AAEA,SAAS,sBAAsD;CAC7D,MAAM,KAAK,QAAQ,IAAI;CACvB,IAAI,CAAC,IAAI,OAAO,KAAA;CAChB,IAAI,GAAG,WAAW,MAAM,GAAG,OAAO;CAClC,IAAI,GAAG,WAAW,OAAO,GAAG,OAAO;CACnC,IAAI,GAAG,WAAW,OAAO,GAAG,OAAO;CACnC,IAAI,GAAG,WAAW,MAAM,GAAG,OAAO;AAEpC;AAEA,eAAsB,4BACpB,KACA,gBAC6B;CAC7B,IAAI,gBAAgB,OAAO;CAC3B,MAAM,YAAY,oBAAoB;CACtC,IAAI,WAAW,OAAO;CACtB,IAAI;EACF,OAAO,MAAM,OAAO,EAAE,IAAI,CAAC;CAC7B,QAAQ;EACN,OAAO;CACT;AACF;AAEA,eAAsB,eACpB,aACA,KACkB;CAClB,IAAI;EACF,MAAM,EAAE,SAAS,SAAS,MAAM,kBAAkB,aAAa,GAAG;EAClE,MAAM,SAAS,SAAS,MAAM,GAAG;EACjC,OAAO;CACT,SAAS,GAAG;EACV,IAAI,aAAa,kBAAkB,MAAM;EACzC,OAAO,MAAM,wBAAwB,OAAO,CAAC,GAAG;EAChD,OAAO;CACT;AACF"}
1
+ {"version":3,"file":"package-manager.js","names":["path","fs"],"sources":["../../../src/lib/utils/package-manager.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { detect } from \"detect-package-manager\";\nimport * as readline from \"node:readline\";\nimport { logger } from \"./logger\";\nimport { runSpawn, SpawnSignalError } from \"../run-spawn\";\n\nexport type PackageManagerName = \"npm\" | \"pnpm\" | \"yarn\" | \"bun\";\n\nexport function dlxCommand(pm: PackageManagerName): [string, string[]] {\n switch (pm) {\n case \"pnpm\":\n return [\"pnpm\", [\"dlx\"]];\n case \"yarn\":\n return [\"yarn\", [\"dlx\"]];\n case \"bun\":\n return [\"bunx\", []];\n case \"npm\":\n return [\"npx\", [\"--yes\"]];\n }\n}\n\nexport function resolvePackageManager(opts: {\n useNpm?: boolean;\n usePnpm?: boolean;\n useYarn?: boolean;\n useBun?: boolean;\n}): PackageManagerName | undefined {\n if (opts.useNpm) return \"npm\";\n if (opts.usePnpm) return \"pnpm\";\n if (opts.useYarn) return \"yarn\";\n if (opts.useBun) return \"bun\";\n return undefined;\n}\n\nexport function askQuestion(query: string): Promise<string> {\n return new Promise((resolve) => {\n // A stream only reaches EOF once, so a run that already consumed stdin gets\n // no further `end` and an interface built over it would wait on a `close`\n // that cannot arrive. An upgrade asks up to three questions.\n if (process.stdin.readableEnded) {\n resolve(\"\");\n return;\n }\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n // stdin at EOF (a piped or CI run) emits `close` without a `line`, so the\n // question callback alone would leave this pending forever and the process\n // would exit 0 with the remaining work silently skipped. An empty answer is\n // what pressing Enter sends, so EOF lands on the prompt's own default.\n // A final answer with no trailing newline arrives as `line` instead of\n // through the callback, and has to win over that default.\n // Ctrl-C in raw mode is delivered as this event rather than as a signal,\n // and without a listener readline answers it by closing — which the EOF\n // default would then read as approval. A cancelled prompt declines.\n let cancelled = false;\n rl.on(\"SIGINT\", () => {\n cancelled = true;\n rl.close();\n });\n rl.on(\"line\", resolve);\n rl.on(\"close\", () => resolve(cancelled ? \"n\" : \"\"));\n rl.question(query, (answer) => {\n resolve(answer);\n rl.close();\n });\n });\n}\n\nexport function isPackageInstalled(\n pkg: string,\n cwd: string = process.cwd(),\n): boolean {\n try {\n const pkgJsonPath = path.join(cwd, \"package.json\");\n if (fs.existsSync(pkgJsonPath)) {\n const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, \"utf8\"));\n const deps = pkgJson.dependencies || {};\n const devDeps = pkgJson.devDependencies || {};\n if (deps[pkg] || devDeps[pkg]) {\n return true;\n }\n }\n } catch {\n // Fall back to node_modules check below.\n }\n const modulePath = path.join(cwd, \"node_modules\", ...pkg.split(\"/\"));\n return fs.existsSync(modulePath);\n}\n\nexport interface InstallCommand {\n command: string;\n args: string[];\n}\n\nexport async function getInstallCommand(\n packageName: string | string[],\n cwd?: string,\n): Promise<InstallCommand> {\n const pm = await detect({ cwd });\n const packages = Array.isArray(packageName) ? packageName : [packageName];\n switch (pm) {\n case \"yarn\":\n return { command: \"yarn\", args: [\"add\", ...packages] };\n case \"pnpm\":\n return { command: \"pnpm\", args: [\"add\", ...packages] };\n case \"bun\":\n return { command: \"bun\", args: [\"add\", ...packages] };\n default:\n return { command: \"npm\", args: [\"install\", ...packages] };\n }\n}\n\nfunction detectFromUserAgent(): PackageManagerName | undefined {\n const ua = process.env.npm_config_user_agent;\n if (!ua) return undefined;\n if (ua.startsWith(\"bun/\")) return \"bun\";\n if (ua.startsWith(\"pnpm/\")) return \"pnpm\";\n if (ua.startsWith(\"yarn/\")) return \"yarn\";\n if (ua.startsWith(\"npm/\")) return \"npm\";\n return undefined;\n}\n\nexport async function resolvePackageManagerForCwd(\n cwd: string,\n packageManager?: PackageManagerName,\n): Promise<PackageManagerName> {\n if (packageManager) return packageManager;\n const fromAgent = detectFromUserAgent();\n if (fromAgent) return fromAgent;\n try {\n return await detect({ cwd });\n } catch {\n return \"npm\";\n }\n}\n\nexport async function installPackage(\n packageName: string,\n cwd?: string,\n): Promise<boolean> {\n try {\n const { command, args } = await getInstallCommand(packageName, cwd);\n await runSpawn(command, args, cwd);\n return true;\n } catch (e) {\n if (e instanceof SpawnSignalError) throw e;\n logger.error(`Installation failed: ${String(e)}`);\n return false;\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assistant-ui",
3
- "version": "0.0.121",
3
+ "version": "0.0.122",
4
4
  "description": "CLI for assistant-ui",
5
5
  "keywords": [
6
6
  "cli",
@@ -49,7 +49,7 @@
49
49
  "@types/cross-spawn": "^6.0.6",
50
50
  "@types/debug": "^4.1.13",
51
51
  "@types/jscodeshift": "^17.3.0",
52
- "@types/node": "^26.6.3",
52
+ "@types/node": "^26.6.4",
53
53
  "@types/semver": "^7.8.0",
54
54
  "@vitest/coverage-v8": "^5.0.3",
55
55
  "vitest": "^5.0.3"
@@ -70,7 +70,7 @@
70
70
  "scripts": {
71
71
  "build": "aui-build && node scripts/copy-cloud-harness-template.mjs",
72
72
  "test": "vitest run && node --test scripts/copy-cloud-harness-template.test.mjs",
73
- "test:coverage": "vitest run --coverage --coverage.reporter=text --coverage.reporter=html --coverage.reporter=json-summary",
73
+ "test:coverage": "vitest run --coverage --coverage.reporter=text --coverage.reporter=html --coverage.reporter=json-summary && node --test scripts/copy-cloud-harness-template.test.mjs",
74
74
  "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json",
75
75
  "test:watch": "vitest"
76
76
  }
@@ -6,9 +6,10 @@ import {
6
6
  getComponentsJsonStyle,
7
7
  resolveRegistryItemUrl,
8
8
  } from "../lib/utils/registry";
9
- import { dlxCommand, resolvePackageManager } from "../lib/create-project";
10
9
  import { runSpawn, SpawnExitError, SpawnSignalError } from "../lib/run-spawn";
11
10
  import {
11
+ dlxCommand,
12
+ resolvePackageManager,
12
13
  type PackageManagerName,
13
14
  resolvePackageManagerForCwd,
14
15
  } from "../lib/utils/package-manager";
@@ -6,10 +6,8 @@ import * as p from "@clack/prompts";
6
6
  import { logger } from "../lib/utils/logger";
7
7
  import {
8
8
  cleanupPendingProjectDownloads,
9
- dlxCommand,
10
9
  downloadProject,
11
10
  resolveLatestReleaseRef,
12
- resolvePackageManager,
13
11
  scaffoldProject,
14
12
  transformProject,
15
13
  type TransformResult,
@@ -20,7 +18,11 @@ import {
20
18
  SpawnExitError,
21
19
  SpawnSignalError,
22
20
  } from "../lib/run-spawn";
23
- import { resolvePackageManagerForCwd } from "../lib/utils/package-manager";
21
+ import {
22
+ dlxCommand,
23
+ resolvePackageManager,
24
+ resolvePackageManagerForCwd,
25
+ } from "../lib/utils/package-manager";
24
26
  import {
25
27
  buildSkillsAddCommand,
26
28
  resolveSkillsInstall,
@@ -1,10 +1,13 @@
1
1
  import { Command, Option } from "commander";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
- import { dlxCommand, resolvePackageManager } from "../lib/create-project";
5
4
  import { runSpawn, SpawnExitError, SpawnSignalError } from "../lib/run-spawn";
6
5
  import { logger } from "../lib/utils/logger";
7
- import { resolvePackageManagerForCwd } from "../lib/utils/package-manager";
6
+ import {
7
+ dlxCommand,
8
+ resolvePackageManager,
9
+ resolvePackageManagerForCwd,
10
+ } from "../lib/utils/package-manager";
8
11
  import {
9
12
  getComponentsJsonStyle,
10
13
  resolveQuickStartRegistryUrl,
@@ -2,6 +2,14 @@ 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 {
6
+ applyEdits,
7
+ parse as parseJsonc,
8
+ parseTree,
9
+ stripComments,
10
+ type Node,
11
+ type ParseError,
12
+ } from "jsonc-parser";
5
13
  import { logger } from "../lib/utils/logger";
6
14
  import { runSpawn, SpawnExitError, SpawnSignalError } from "../lib/run-spawn";
7
15
  import * as p from "@clack/prompts";
@@ -21,22 +29,17 @@ const MCP_CONFIGS: Record<
21
29
  {
22
30
  name: string;
23
31
  getPath: () => string;
24
- config: object;
25
- replaceServerKey?: string;
32
+ serverKey: string;
33
+ server: object;
34
+ jsonc?: boolean;
26
35
  postInstall?: string;
27
36
  }
28
37
  > = {
29
38
  cursor: {
30
39
  name: "Cursor",
31
40
  getPath: () => path.join(process.cwd(), ".cursor", "mcp.json"),
32
- config: {
33
- mcpServers: {
34
- "assistant-ui": {
35
- url: HOSTED_MCP_URL,
36
- },
37
- },
38
- },
39
- replaceServerKey: "mcpServers",
41
+ serverKey: "mcpServers",
42
+ server: { url: HOSTED_MCP_URL },
40
43
  postInstall:
41
44
  "Open Cursor Settings → MCP → find 'assistant-ui' and click enable.",
42
45
  },
@@ -44,47 +47,33 @@ const MCP_CONFIGS: Record<
44
47
  name: "Windsurf",
45
48
  getPath: () =>
46
49
  path.join(os.homedir(), ".codeium", "windsurf", "mcp_config.json"),
47
- config: {
48
- mcpServers: {
49
- "assistant-ui": {
50
- serverUrl: HOSTED_MCP_URL,
51
- },
52
- },
53
- },
54
- replaceServerKey: "mcpServers",
50
+ serverKey: "mcpServers",
51
+ server: { serverUrl: HOSTED_MCP_URL },
55
52
  postInstall: "Fully quit and re-open Windsurf to activate.",
56
53
  },
57
54
  vscode: {
58
55
  name: "VSCode",
59
56
  getPath: () => path.join(process.cwd(), ".vscode", "mcp.json"),
60
- config: {
61
- servers: {
62
- "assistant-ui": {
63
- type: "http",
64
- url: HOSTED_MCP_URL,
65
- },
66
- },
67
- },
68
- replaceServerKey: "servers",
57
+ serverKey: "servers",
58
+ server: { type: "http", url: HOSTED_MCP_URL },
59
+ jsonc: true,
69
60
  postInstall:
70
61
  "Enable MCP in Settings → search 'MCP' → enable 'Chat > MCP'. Use Copilot Chat in Agent mode.",
71
62
  },
72
63
  zed: {
73
64
  name: "Zed",
65
+ serverKey: "context_servers",
66
+ jsonc: true,
74
67
  getPath: () => {
75
68
  if (process.platform === "win32") {
76
69
  return path.join(process.env.APPDATA || "", "Zed", "settings.json");
77
70
  }
78
71
  return path.join(os.homedir(), ".config", "zed", "settings.json");
79
72
  },
80
- config: {
81
- context_servers: {
82
- "assistant-ui": {
83
- command: {
84
- path: "npx",
85
- args: ["-y", "@assistant-ui/mcp-docs-server"],
86
- },
87
- },
73
+ server: {
74
+ command: {
75
+ path: "npx",
76
+ args: ["-y", "@assistant-ui/mcp-docs-server"],
88
77
  },
89
78
  },
90
79
  postInstall: "The server starts automatically with the Assistant Panel.",
@@ -107,13 +96,10 @@ const MCP_CONFIGS: Record<
107
96
  "claude_desktop_config.json",
108
97
  );
109
98
  },
110
- config: {
111
- mcpServers: {
112
- "assistant-ui": {
113
- command: "npx",
114
- args: ["-y", "@assistant-ui/mcp-docs-server"],
115
- },
116
- },
99
+ serverKey: "mcpServers",
100
+ server: {
101
+ command: "npx",
102
+ args: ["-y", "@assistant-ui/mcp-docs-server"],
117
103
  },
118
104
  postInstall: "Restart Claude Desktop to activate.",
119
105
  },
@@ -147,6 +133,162 @@ class McpConfigParseError extends Error {
147
133
  const getTargetFlag = (target: Exclude<MCPTarget, "claude-code">) =>
148
134
  `--${target}`;
149
135
 
136
+ const lastPropertyValue = (node: Node, key: string) =>
137
+ node.children?.findLast((property) => property.children?.[0]?.value === key)
138
+ ?.children?.[1];
139
+
140
+ const lineStartAt = (content: string, offset: number) =>
141
+ content.lastIndexOf("\n", offset - 1) + 1;
142
+
143
+ const lineIndentAt = (content: string, offset: number) =>
144
+ content.slice(lineStartAt(content, offset), offset).match(/^[\t ]*/)?.[0] ??
145
+ "";
146
+
147
+ const getIndentUnit = (content: string, object: Node) => {
148
+ const objectLine = lineStartAt(content, object.offset);
149
+ const objectIndent = lineIndentAt(content, object.offset);
150
+ const multilineChild = object.children?.find(
151
+ (child) => lineStartAt(content, child.offset) > objectLine,
152
+ );
153
+ if (multilineChild) {
154
+ const childIndent = lineIndentAt(content, multilineChild.offset);
155
+ if (
156
+ childIndent.startsWith(objectIndent) &&
157
+ childIndent.length > objectIndent.length
158
+ ) {
159
+ return childIndent.slice(objectIndent.length);
160
+ }
161
+ }
162
+
163
+ const documentIndent = content.match(/(?:^|\r?\n)([\t ]+)(?=")/)?.[1];
164
+ if (documentIndent?.includes("\t")) return "\t";
165
+ return documentIndent || " ";
166
+ };
167
+
168
+ const stringifyValue = (
169
+ value: object,
170
+ baseIndent: string,
171
+ indentUnit: string,
172
+ eol: string,
173
+ ) =>
174
+ JSON.stringify(value, null, indentUnit).replaceAll(
175
+ "\n",
176
+ `${eol}${baseIndent}`,
177
+ );
178
+
179
+ const appendObjectProperty = (
180
+ content: string,
181
+ object: Node,
182
+ key: string,
183
+ value: object,
184
+ eol: string,
185
+ ) => {
186
+ const objectIndent = lineIndentAt(content, object.offset);
187
+ const indentUnit = getIndentUnit(content, object);
188
+ const propertyIndent = objectIndent + indentUnit;
189
+ const property = `${propertyIndent}${JSON.stringify(key)}: ${stringifyValue(
190
+ value,
191
+ propertyIndent,
192
+ indentUnit,
193
+ eol,
194
+ )}`;
195
+ const closeOffset = object.offset + object.length - 1;
196
+ const closeLineStart = lineStartAt(content, closeOffset);
197
+ const closeOnOwnLine = /^[\t ]*$/.test(
198
+ content.slice(closeLineStart, closeOffset),
199
+ );
200
+ const edits = [
201
+ {
202
+ offset: closeOnOwnLine ? closeLineStart : closeOffset,
203
+ length: 0,
204
+ content: closeOnOwnLine
205
+ ? `${property}${eol}`
206
+ : `${eol}${property}${eol}${objectIndent}`,
207
+ },
208
+ ];
209
+
210
+ const lastProperty = object.children?.at(-1);
211
+ if (lastProperty) {
212
+ const propertyEnd = lastProperty.offset + lastProperty.length;
213
+ const trailingContent = stripComments(
214
+ content.slice(propertyEnd, closeOffset),
215
+ ).trimStart();
216
+ if (!trailingContent.startsWith(",")) {
217
+ edits.unshift({ offset: propertyEnd, length: 0, content: "," });
218
+ }
219
+ }
220
+
221
+ return applyEdits(content, edits);
222
+ };
223
+
224
+ const replaceNodeValue = (
225
+ content: string,
226
+ node: Node,
227
+ container: Node,
228
+ value: object,
229
+ eol: string,
230
+ ) =>
231
+ applyEdits(content, [
232
+ {
233
+ offset: node.offset,
234
+ length: node.length,
235
+ content: stringifyValue(
236
+ value,
237
+ lineIndentAt(content, node.offset),
238
+ getIndentUnit(content, container),
239
+ eol,
240
+ ),
241
+ },
242
+ ]);
243
+
244
+ function updateJsoncConfig(
245
+ content: string,
246
+ serverKey: string,
247
+ server: object,
248
+ ): string {
249
+ const eol = content.includes("\r\n") ? "\r\n" : "\n";
250
+ const root = parseTree(content, [], {
251
+ allowTrailingComma: true,
252
+ allowEmptyContent: true,
253
+ });
254
+ if (!root) {
255
+ const initialized = JSON.stringify(
256
+ { [serverKey]: { "assistant-ui": server } },
257
+ null,
258
+ 2,
259
+ ).replaceAll("\n", eol);
260
+ if (!content.trim()) return `${initialized}${eol}`;
261
+ return `${content}${content.endsWith(eol) ? "" : eol}${initialized}${eol}`;
262
+ }
263
+
264
+ // JSONC resolves duplicate keys to the last value.
265
+ const servers = lastPropertyValue(root, serverKey);
266
+ if (!servers) {
267
+ return appendObjectProperty(
268
+ content,
269
+ root,
270
+ serverKey,
271
+ { "assistant-ui": server },
272
+ eol,
273
+ );
274
+ }
275
+
276
+ if (servers.type !== "object") {
277
+ return replaceNodeValue(
278
+ content,
279
+ servers,
280
+ root,
281
+ { "assistant-ui": server },
282
+ eol,
283
+ );
284
+ }
285
+
286
+ const existing = lastPropertyValue(servers, "assistant-ui");
287
+ return existing
288
+ ? replaceNodeValue(content, existing, servers, server, eol)
289
+ : appendObjectProperty(content, servers, "assistant-ui", server, eol);
290
+ }
291
+
150
292
  async function installForTarget(target: MCPTarget): Promise<void> {
151
293
  if (target === "claude-code") {
152
294
  logger.info("Installing MCP server for Claude Code...");
@@ -209,10 +351,35 @@ async function installForTarget(target: MCPTarget): Promise<void> {
209
351
  }
210
352
 
211
353
  let existingConfig: any = {};
354
+ let content = "{}\n";
212
355
  if (fs.existsSync(configPath)) {
213
- const content = fs.readFileSync(configPath, "utf-8");
356
+ content = fs.readFileSync(configPath, "utf-8");
214
357
  try {
215
- existingConfig = JSON.parse(content);
358
+ if (targetConfig.jsonc) {
359
+ const errors: ParseError[] = [];
360
+ existingConfig = parseJsonc(content, errors, {
361
+ allowTrailingComma: true,
362
+ allowEmptyContent: true,
363
+ });
364
+ if (existingConfig === undefined) existingConfig = {};
365
+ if (
366
+ errors.length > 0 ||
367
+ existingConfig === null ||
368
+ typeof existingConfig !== "object" ||
369
+ Array.isArray(existingConfig)
370
+ ) {
371
+ throw new SyntaxError("Invalid MCP configuration");
372
+ }
373
+ } else {
374
+ existingConfig = JSON.parse(content);
375
+ if (
376
+ existingConfig === null ||
377
+ typeof existingConfig !== "object" ||
378
+ Array.isArray(existingConfig)
379
+ ) {
380
+ throw new SyntaxError("Invalid MCP configuration");
381
+ }
382
+ }
216
383
  } catch {
217
384
  const flag = getTargetFlag(target);
218
385
  logger.error(`Could not parse ${targetConfig.name} MCP config.`);
@@ -225,23 +392,23 @@ async function installForTarget(target: MCPTarget): Promise<void> {
225
392
  }
226
393
  }
227
394
 
228
- const newConfig = deepMerge(existingConfig, targetConfig.config);
229
-
230
- if (targetConfig.replaceServerKey) {
231
- const key = targetConfig.replaceServerKey;
232
- newConfig[key] = {
233
- ...newConfig[key],
234
- "assistant-ui": (targetConfig.config as any)[key]["assistant-ui"],
235
- };
236
- }
237
-
238
- fs.writeFileSync(configPath, `${JSON.stringify(newConfig, null, 2)}\n`);
395
+ const updatedContent = updateJsoncConfig(
396
+ content,
397
+ targetConfig.serverKey,
398
+ target === "zed" || target === "claude-desktop"
399
+ ? deepMerge(
400
+ existingConfig[targetConfig.serverKey]?.["assistant-ui"] ?? {},
401
+ targetConfig.server,
402
+ )
403
+ : targetConfig.server,
404
+ );
405
+ fs.writeFileSync(configPath, updatedContent);
239
406
 
240
407
  logger.break();
241
408
  logger.success(`MCP server installed for ${targetConfig.name}!`);
242
409
  logger.info(`Config written to: ${configPath}`);
243
410
 
244
- if (targetConfig.replaceServerKey) {
411
+ if (target === "cursor" || target === "windsurf" || target === "vscode") {
245
412
  logger.break();
246
413
  logger.info(
247
414
  `Connects to the hosted assistant-ui MCP server at ${HOSTED_MCP_URL}.`,