mcp-app-studio 0.5.1 → 0.6.0

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.
package/dist/cli/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  // src/cli/index.ts
2
- import fs2 from "fs";
2
+ import fs5 from "fs";
3
3
  import { createWriteStream } from "fs";
4
4
  import { Readable } from "stream";
5
5
  import { pipeline } from "stream/promises";
6
- import path3 from "path";
6
+ import path6 from "path";
7
7
  import os from "os";
8
8
  import { fileURLToPath } from "url";
9
9
  import * as p from "@clack/prompts";
@@ -36,15 +36,43 @@ function filterTemplateTarEntry(rootDir, entryPath, entry) {
36
36
  return true;
37
37
  }
38
38
 
39
- // src/cli/utils.ts
39
+ // src/cli/template-validation.ts
40
40
  import fs from "fs";
41
41
  import path2 from "path";
42
+ var REQUIRED_TEMPLATE_PATHS = [
43
+ "package.json",
44
+ "scripts/dev.ts",
45
+ "scripts/export.ts",
46
+ "lib/workbench/component-registry.tsx",
47
+ "lib/workbench/wrappers/index.ts",
48
+ "components/examples/index.ts",
49
+ "lib/workbench/store.ts"
50
+ ];
51
+ function validateTemplateDir(templateDir) {
52
+ const missing = REQUIRED_TEMPLATE_PATHS.filter(
53
+ (p2) => !fs.existsSync(path2.join(templateDir, p2))
54
+ );
55
+ if (missing.length > 0) {
56
+ throw new Error(
57
+ `Template validation failed. Missing expected files:
58
+ ${missing.map((p2) => `- ${p2}`).join(
59
+ "\n"
60
+ )}
61
+
62
+ This may indicate the starter template has changed. Try updating mcp-app-studio or set MCP_APP_STUDIO_TEMPLATE_REPO / MCP_APP_STUDIO_TEMPLATE_REF to a compatible template.`
63
+ );
64
+ }
65
+ }
66
+
67
+ // src/cli/utils.ts
68
+ import fs2 from "fs";
69
+ import path3 from "path";
42
70
  function isValidProjectPath(name) {
43
71
  if (!name || name.trim() === "") {
44
72
  return { valid: false, error: "Project name is required" };
45
73
  }
46
74
  const trimmed = name.trim();
47
- if (path2.isAbsolute(trimmed)) {
75
+ if (path3.isAbsolute(trimmed)) {
48
76
  return {
49
77
  valid: false,
50
78
  error: "Absolute paths are not allowed. Use a relative path or project name."
@@ -57,9 +85,9 @@ function isValidProjectPath(name) {
57
85
  };
58
86
  }
59
87
  const cwd = process.cwd();
60
- const resolved = path2.resolve(cwd, trimmed);
61
- const relative = path2.relative(cwd, resolved);
62
- if (relative.startsWith("..") || path2.isAbsolute(relative)) {
88
+ const resolved = path3.resolve(cwd, trimmed);
89
+ const relative = path3.relative(cwd, resolved);
90
+ if (relative.startsWith("..") || path3.isAbsolute(relative)) {
63
91
  return {
64
92
  valid: false,
65
93
  error: "Project must be created within current directory."
@@ -76,15 +104,15 @@ function toValidPackageName(name) {
76
104
  return name.trim().toLowerCase().replace(/\s+/g, "-").replace(/^[._]/, "").replace(/[^a-z0-9-~]+/g, "-");
77
105
  }
78
106
  function isEmpty(dir) {
79
- if (!fs.existsSync(dir)) return true;
80
- const files = fs.readdirSync(dir);
107
+ if (!fs2.existsSync(dir)) return true;
108
+ const files = fs2.readdirSync(dir);
81
109
  return files.length === 0 || files.length === 1 && files[0] === ".git";
82
110
  }
83
111
  function emptyDir(dir) {
84
- if (!fs.existsSync(dir)) return;
85
- for (const file of fs.readdirSync(dir)) {
112
+ if (!fs2.existsSync(dir)) return;
113
+ for (const file of fs2.readdirSync(dir)) {
86
114
  if (file === ".git") continue;
87
- fs.rmSync(path2.join(dir, file), { recursive: true, force: true });
115
+ fs2.rmSync(path3.join(dir, file), { recursive: true, force: true });
88
116
  }
89
117
  }
90
118
  var DEPENDENCY_OVERRIDES = {
@@ -93,10 +121,10 @@ var DEPENDENCY_OVERRIDES = {
93
121
  "@assistant-ui/react-markdown": "^0.12.1"
94
122
  };
95
123
  function updatePackageJson(dir, name, description, opts) {
96
- const pkgPath = path2.join(dir, "package.json");
97
- if (!fs.existsSync(pkgPath)) return;
124
+ const pkgPath = path3.join(dir, "package.json");
125
+ if (!fs2.existsSync(pkgPath)) return;
98
126
  try {
99
- const content = fs.readFileSync(pkgPath, "utf-8");
127
+ const content = fs2.readFileSync(pkgPath, "utf-8");
100
128
  const pkg = JSON.parse(content);
101
129
  pkg["name"] = name;
102
130
  pkg["version"] = "0.1.0";
@@ -114,7 +142,7 @@ function updatePackageJson(dir, name, description, opts) {
114
142
  }
115
143
  }
116
144
  pkg["dependencies"] = deps;
117
- fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
145
+ fs2.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
118
146
  `);
119
147
  } catch (error) {
120
148
  throw new Error(
@@ -130,8 +158,59 @@ function detectPackageManager() {
130
158
  return "npm";
131
159
  }
132
160
 
161
+ // src/cli/version.ts
162
+ import fs3 from "fs";
163
+ import path4 from "path";
164
+ function getVersionFromCliDir(cliDir) {
165
+ try {
166
+ const candidatePaths = [
167
+ path4.resolve(cliDir, "../../package.json"),
168
+ path4.resolve(cliDir, "../package.json"),
169
+ path4.resolve(cliDir, "../../../package.json")
170
+ ];
171
+ for (const pkgPath of candidatePaths) {
172
+ if (!fs3.existsSync(pkgPath)) continue;
173
+ const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
174
+ if (pkg.version) return pkg.version;
175
+ }
176
+ return "0.0.0";
177
+ } catch {
178
+ return "0.0.0";
179
+ }
180
+ }
181
+
182
+ // src/cli/workbench-index.ts
183
+ import fs4 from "fs";
184
+ import path5 from "path";
185
+ function generateWorkbenchIndexExport(components) {
186
+ const exports = [];
187
+ if (components.includes("welcome")) {
188
+ exports.push("WelcomeCardSDK");
189
+ }
190
+ if (components.includes("poi-map")) {
191
+ exports.push("POIMapSDK");
192
+ }
193
+ return exports.length > 0 ? `export { ${exports.join(", ")} } from "./wrappers";` : "// No SDK exports";
194
+ }
195
+ function updateWorkbenchIndex(targetDir, components) {
196
+ const indexPath = path5.join(targetDir, "lib/workbench/index.ts");
197
+ if (!fs4.existsSync(indexPath)) return;
198
+ let content = fs4.readFileSync(indexPath, "utf-8");
199
+ const wrappersExportRegex = /export \{[^}]*\} from "\.\/wrappers";/;
200
+ const newExport = generateWorkbenchIndexExport(components);
201
+ if (wrappersExportRegex.test(content)) {
202
+ content = content.replace(wrappersExportRegex, newExport);
203
+ } else {
204
+ content = `${content.trimEnd()}
205
+
206
+ ${newExport}
207
+ `;
208
+ }
209
+ fs4.writeFileSync(indexPath, content);
210
+ }
211
+
133
212
  // src/cli/index.ts
134
- var __dirname = path3.dirname(fileURLToPath(import.meta.url));
213
+ var __dirname = path6.dirname(fileURLToPath(import.meta.url));
135
214
  var TEMPLATE_REPO = process.env["MCP_APP_STUDIO_TEMPLATE_REPO"] ?? "assistant-ui/mcp-app-studio-starter";
136
215
  var TEMPLATE_REF = process.env["MCP_APP_STUDIO_TEMPLATE_REF"] ?? process.env["MCP_APP_STUDIO_TEMPLATE_BRANCH"] ?? "main";
137
216
  var DOCS_URL = "https://github.com/assistant-ui/assistant-ui/tree/main/packages/mcp-app-studio";
@@ -164,19 +243,13 @@ function ensureSupportedNodeVersion() {
164
243
  }
165
244
  }
166
245
  function getVersion() {
167
- try {
168
- const pkgPath = path3.resolve(__dirname, "../package.json");
169
- const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
170
- return pkg.version || "0.0.0";
171
- } catch {
172
- return "0.0.0";
173
- }
246
+ return getVersionFromCliDir(__dirname);
174
247
  }
175
248
  function showHelp() {
176
249
  console.log(`
177
250
  mcp-app-studio v${getVersion()}
178
251
 
179
- Create interactive apps for ChatGPT and MCP hosts (like Claude Desktop).
252
+ Create interactive apps for MCP hosts (including ChatGPT and Claude Desktop).
180
253
 
181
254
  ${pc.bold("Requirements:")}
182
255
  Node.js >=${REQUIRED_NODE_VERSION.major}.${REQUIRED_NODE_VERSION.minor}.${REQUIRED_NODE_VERSION.patch}
@@ -236,38 +309,38 @@ function writeStudioConfig(targetDir, template, appName) {
236
309
  name: appName
237
310
  }
238
311
  };
239
- fs2.writeFileSync(
240
- path3.join(targetDir, "mcp-app-studio.config.json"),
312
+ fs5.writeFileSync(
313
+ path6.join(targetDir, "mcp-app-studio.config.json"),
241
314
  `${JSON.stringify(config, null, 2)}
242
315
  `
243
316
  );
244
317
  }
245
318
  function updateServerPackageName(targetDir, projectPackageName) {
246
- const serverPkgPath = path3.join(targetDir, "server", "package.json");
247
- if (!fs2.existsSync(serverPkgPath)) return;
248
- const raw = fs2.readFileSync(serverPkgPath, "utf-8");
319
+ const serverPkgPath = path6.join(targetDir, "server", "package.json");
320
+ if (!fs5.existsSync(serverPkgPath)) return;
321
+ const raw = fs5.readFileSync(serverPkgPath, "utf-8");
249
322
  const pkg = JSON.parse(raw);
250
323
  const baseName = projectPackageName.includes("/") ? projectPackageName.split("/").pop() : projectPackageName;
251
324
  if (!baseName) return;
252
325
  pkg["name"] = `${baseName}-mcp-server`;
253
- fs2.writeFileSync(serverPkgPath, `${JSON.stringify(pkg, null, 2)}
326
+ fs5.writeFileSync(serverPkgPath, `${JSON.stringify(pkg, null, 2)}
254
327
  `, "utf-8");
255
328
  }
256
329
  function ensureServerPostinstall(targetDir) {
257
- const pkgPath = path3.join(targetDir, "package.json");
258
- const serverPkgPath = path3.join(targetDir, "server", "package.json");
259
- if (!fs2.existsSync(pkgPath) || !fs2.existsSync(serverPkgPath)) return;
260
- const raw = fs2.readFileSync(pkgPath, "utf-8");
330
+ const pkgPath = path6.join(targetDir, "package.json");
331
+ const serverPkgPath = path6.join(targetDir, "server", "package.json");
332
+ if (!fs5.existsSync(pkgPath) || !fs5.existsSync(serverPkgPath)) return;
333
+ const raw = fs5.readFileSync(pkgPath, "utf-8");
261
334
  const pkg = JSON.parse(raw);
262
335
  const scripts = pkg["scripts"] ?? {};
263
336
  if (scripts["postinstall"]) return;
264
- const scriptPath = path3.join(
337
+ const scriptPath = path6.join(
265
338
  targetDir,
266
339
  "scripts",
267
340
  "mcp-app-studio-postinstall.cjs"
268
341
  );
269
- fs2.mkdirSync(path3.dirname(scriptPath), { recursive: true });
270
- fs2.writeFileSync(
342
+ fs5.mkdirSync(path6.dirname(scriptPath), { recursive: true });
343
+ fs5.writeFileSync(
271
344
  scriptPath,
272
345
  `/* eslint-disable */
273
346
  const { spawnSync } = require("node:child_process");
@@ -294,7 +367,7 @@ process.exit(result.status == null ? 1 : result.status);
294
367
  );
295
368
  scripts["postinstall"] = "node scripts/mcp-app-studio-postinstall.cjs";
296
369
  pkg["scripts"] = scripts;
297
- fs2.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
370
+ fs5.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
298
371
  `, "utf-8");
299
372
  }
300
373
  function generateComponentRegistry(components) {
@@ -311,7 +384,7 @@ function generateComponentRegistry(components) {
311
384
  defaultProps: {
312
385
  title: "Welcome!",
313
386
  message:
314
- "This is your ChatGPT App. Edit this component to build something amazing.",
387
+ "This is your MCP App. Edit this component to build something amazing.",
315
388
  },
316
389
  exportConfig: {
317
390
  entryPoint: "lib/workbench/wrappers/welcome-card-sdk.tsx",
@@ -422,9 +495,9 @@ function generateExamplesIndex(components) {
422
495
  ` : "// No examples\n";
423
496
  }
424
497
  function updateExportScriptDefaults(targetDir, entryPoint, exportName) {
425
- const exportScriptPath = path3.join(targetDir, "scripts/export.ts");
426
- if (!fs2.existsSync(exportScriptPath)) return;
427
- let content = fs2.readFileSync(exportScriptPath, "utf-8");
498
+ const exportScriptPath = path6.join(targetDir, "scripts/export.ts");
499
+ if (!fs5.existsSync(exportScriptPath)) return;
500
+ let content = fs5.readFileSync(exportScriptPath, "utf-8");
428
501
  if (content.includes("mcp-app-studio.config.json")) return;
429
502
  content = content.replace(
430
503
  /entryPoint: "lib\/workbench\/wrappers\/[^"]+"/,
@@ -443,79 +516,54 @@ function updateExportScriptDefaults(targetDir, entryPoint, exportName) {
443
516
  /Export name from entry file \(default: [^)]+\)/,
444
517
  `Export name from entry file (default: ${exportName})`
445
518
  );
446
- fs2.writeFileSync(exportScriptPath, content);
447
- }
448
- function generateWorkbenchIndexExport(components) {
449
- const exports = [];
450
- if (components.includes("welcome")) {
451
- exports.push("WelcomeCardSDK");
452
- }
453
- if (components.includes("poi-map")) {
454
- exports.push("POIMapSDK");
455
- }
456
- return exports.length > 0 ? `export { ${exports.join(", ")} } from "./wrappers";` : "// No SDK exports";
457
- }
458
- function updateWorkbenchIndex(targetDir, components) {
459
- const indexPath = path3.join(targetDir, "lib/workbench/index.ts");
460
- let content = fs2.readFileSync(indexPath, "utf-8");
461
- const wrappersExportRegex = /export \{[^}]*\} from "\.\/wrappers";/;
462
- const newExport = generateWorkbenchIndexExport(components);
463
- if (wrappersExportRegex.test(content)) {
464
- content = content.replace(wrappersExportRegex, newExport);
465
- } else {
466
- content = `${content.trimEnd()}
467
-
468
- ${newExport}
469
- `;
470
- }
471
- fs2.writeFileSync(indexPath, content);
519
+ fs5.writeFileSync(exportScriptPath, content);
472
520
  }
473
521
  function updateWorkbenchStoreDefault(targetDir, defaultComponent) {
474
- const storePath = path3.join(targetDir, "lib/workbench/store.ts");
475
- let content = fs2.readFileSync(storePath, "utf-8");
522
+ const storePath = path6.join(targetDir, "lib/workbench/store.ts");
523
+ let content = fs5.readFileSync(storePath, "utf-8");
476
524
  content = content.replace(
477
525
  /selectedComponent: "[^"]+"/,
478
526
  `selectedComponent: "${defaultComponent}"`
479
527
  );
480
- fs2.writeFileSync(storePath, content);
528
+ fs5.writeFileSync(storePath, content);
481
529
  }
482
530
  function applyTemplate(targetDir, template) {
483
531
  const components = TEMPLATE_COMPONENTS[template];
484
- const registryPath = path3.join(
532
+ const registryPath = path6.join(
485
533
  targetDir,
486
534
  "lib/workbench/component-registry.tsx"
487
535
  );
488
- fs2.writeFileSync(registryPath, generateComponentRegistry(components));
489
- const wrappersIndexPath = path3.join(
536
+ fs5.writeFileSync(registryPath, generateComponentRegistry(components));
537
+ const wrappersIndexPath = path6.join(
490
538
  targetDir,
491
539
  "lib/workbench/wrappers/index.ts"
492
540
  );
493
- fs2.writeFileSync(wrappersIndexPath, generateWrappersIndex(components));
494
- const examplesIndexPath = path3.join(
541
+ fs5.writeFileSync(wrappersIndexPath, generateWrappersIndex(components));
542
+ const examplesIndexPath = path6.join(
495
543
  targetDir,
496
544
  "components/examples/index.ts"
497
545
  );
498
- fs2.writeFileSync(examplesIndexPath, generateExamplesIndex(components));
546
+ fs5.writeFileSync(examplesIndexPath, generateExamplesIndex(components));
499
547
  updateWorkbenchIndex(targetDir, components);
500
548
  const defaultComponent = TEMPLATE_DEFAULT_COMPONENT[template];
501
549
  updateWorkbenchStoreDefault(targetDir, defaultComponent);
502
- const examplesDir = path3.join(targetDir, "components/examples");
550
+ const examplesDir = path6.join(targetDir, "components/examples");
503
551
  if (!components.includes("welcome")) {
504
- fs2.rmSync(path3.join(examplesDir, "welcome-card"), {
552
+ fs5.rmSync(path6.join(examplesDir, "welcome-card"), {
505
553
  recursive: true,
506
554
  force: true
507
555
  });
508
- fs2.rmSync(
509
- path3.join(targetDir, "lib/workbench/wrappers/welcome-card-sdk.tsx"),
556
+ fs5.rmSync(
557
+ path6.join(targetDir, "lib/workbench/wrappers/welcome-card-sdk.tsx"),
510
558
  { force: true }
511
559
  );
512
560
  }
513
561
  if (!components.includes("poi-map")) {
514
- fs2.rmSync(path3.join(examplesDir, "poi-map"), {
562
+ fs5.rmSync(path6.join(examplesDir, "poi-map"), {
515
563
  recursive: true,
516
564
  force: true
517
565
  });
518
- fs2.rmSync(path3.join(targetDir, "lib/workbench/wrappers/poi-map-sdk.tsx"), {
566
+ fs5.rmSync(path6.join(targetDir, "lib/workbench/wrappers/poi-map-sdk.tsx"), {
519
567
  force: true
520
568
  });
521
569
  }
@@ -526,57 +574,32 @@ function applyTemplate(targetDir, template) {
526
574
  exportConfig.exportName
527
575
  );
528
576
  }
529
- var REQUIRED_TEMPLATE_PATHS = [
530
- "package.json",
531
- "scripts/dev.ts",
532
- "scripts/export.ts",
533
- "lib/workbench/component-registry.tsx",
534
- "lib/workbench/wrappers/index.ts",
535
- "components/examples/index.ts",
536
- "lib/workbench/index.ts",
537
- "lib/workbench/store.ts"
538
- ];
539
- function validateTemplateDir(templateDir) {
540
- const missing = REQUIRED_TEMPLATE_PATHS.filter(
541
- (p2) => !fs2.existsSync(path3.join(templateDir, p2))
542
- );
543
- if (missing.length > 0) {
544
- throw new Error(
545
- `Template validation failed. Missing expected files:
546
- ${missing.map((p2) => `- ${p2}`).join(
547
- "\n"
548
- )}
549
-
550
- This may indicate the starter template has changed. Try updating mcp-app-studio or set MCP_APP_STUDIO_TEMPLATE_REPO / MCP_APP_STUDIO_TEMPLATE_REF to a compatible template.`
551
- );
552
- }
553
- }
554
577
  function copyDirContents(srcDir, destDir) {
555
- fs2.mkdirSync(destDir, { recursive: true });
556
- const entries = fs2.readdirSync(srcDir, { withFileTypes: true });
578
+ fs5.mkdirSync(destDir, { recursive: true });
579
+ const entries = fs5.readdirSync(srcDir, { withFileTypes: true });
557
580
  for (const entry of entries) {
558
581
  if (entry.name === ".git") continue;
559
- const srcPath = path3.join(srcDir, entry.name);
560
- const destPath = path3.join(destDir, entry.name);
582
+ const srcPath = path6.join(srcDir, entry.name);
583
+ const destPath = path6.join(destDir, entry.name);
561
584
  if (entry.isDirectory()) {
562
585
  copyDirContents(srcPath, destPath);
563
586
  continue;
564
587
  }
565
588
  if (entry.isFile()) {
566
- fs2.mkdirSync(path3.dirname(destPath), { recursive: true });
567
- fs2.copyFileSync(srcPath, destPath);
589
+ fs5.mkdirSync(path6.dirname(destPath), { recursive: true });
590
+ fs5.copyFileSync(srcPath, destPath);
568
591
  }
569
592
  }
570
593
  }
571
594
  async function downloadTemplateToTemp() {
572
595
  const tarballUrl = getGithubArchiveTarballUrl(TEMPLATE_REPO, TEMPLATE_REF);
573
- const tempDir = fs2.mkdtempSync(
574
- path3.join(os.tmpdir(), "mcp-app-studio-template-")
596
+ const tempDir = fs5.mkdtempSync(
597
+ path6.join(os.tmpdir(), "mcp-app-studio-template-")
575
598
  );
576
- const tarballPath = path3.join(tempDir, "template.tar.gz");
577
- const extractDir = path3.join(tempDir, "extract");
599
+ const tarballPath = path6.join(tempDir, "template.tar.gz");
600
+ const extractDir = path6.join(tempDir, "extract");
578
601
  try {
579
- fs2.mkdirSync(extractDir, { recursive: true });
602
+ fs5.mkdirSync(extractDir, { recursive: true });
580
603
  const response = await fetch(tarballUrl);
581
604
  if (!response.ok) {
582
605
  throw new Error(
@@ -598,7 +621,7 @@ async function downloadTemplateToTemp() {
598
621
  strip: 0,
599
622
  filter: (entryPath, entry) => filterTemplateTarEntry(extractDir, entryPath, entry)
600
623
  });
601
- const topLevelDirs = fs2.readdirSync(extractDir, { withFileTypes: true }).filter((d) => d.isDirectory());
624
+ const topLevelDirs = fs5.readdirSync(extractDir, { withFileTypes: true }).filter((d) => d.isDirectory());
602
625
  if (topLevelDirs.length !== 1) {
603
626
  throw new Error(
604
627
  `Unexpected template archive layout. Expected a single top-level directory, found ${topLevelDirs.length}.`
@@ -610,12 +633,12 @@ async function downloadTemplateToTemp() {
610
633
  "Unexpected template archive layout. No top-level directory found."
611
634
  );
612
635
  }
613
- const templateDir = path3.join(extractDir, topLevelDir.name);
636
+ const templateDir = path6.join(extractDir, topLevelDir.name);
614
637
  validateTemplateDir(templateDir);
615
638
  return { tempDir, templateDir };
616
639
  } catch (err) {
617
640
  try {
618
- fs2.rmSync(tempDir, { recursive: true, force: true });
641
+ fs5.rmSync(tempDir, { recursive: true, force: true });
619
642
  } catch {
620
643
  }
621
644
  throw err;
@@ -713,7 +736,7 @@ async function main() {
713
736
  } else {
714
737
  description = await p.text({
715
738
  message: "App description:",
716
- placeholder: "A ChatGPT app that helps users...",
739
+ placeholder: "An MCP app that helps users...",
717
740
  initialValue: ""
718
741
  });
719
742
  }
@@ -763,8 +786,8 @@ async function main() {
763
786
  p.cancel("Operation cancelled.");
764
787
  process.exit(0);
765
788
  }
766
- const targetDir = path3.resolve(process.cwd(), projectName);
767
- const packageName = projectName === "." ? toValidPackageName(path3.basename(targetDir)) : toValidPackageName(projectName);
789
+ const targetDir = path6.resolve(process.cwd(), projectName);
790
+ const packageName = projectName === "." ? toValidPackageName(path6.basename(targetDir)) : toValidPackageName(projectName);
768
791
  const config = {
769
792
  name: projectName,
770
793
  packageName,
@@ -811,11 +834,11 @@ async function main() {
811
834
  if (shouldOverwrite) {
812
835
  emptyDir(targetDir);
813
836
  }
814
- fs2.mkdirSync(targetDir, { recursive: true });
837
+ fs5.mkdirSync(targetDir, { recursive: true });
815
838
  copyDirContents(downloaded.templateDir, targetDir);
816
839
  } finally {
817
840
  if (downloaded?.tempDir) {
818
- fs2.rmSync(downloaded.tempDir, { recursive: true, force: true });
841
+ fs5.rmSync(downloaded.tempDir, { recursive: true, force: true });
819
842
  }
820
843
  }
821
844
  updatePackageJson(targetDir, config.packageName, config.description, {
@@ -826,11 +849,11 @@ async function main() {
826
849
  }
827
850
  s.message("Applying template...");
828
851
  applyTemplate(targetDir, config.template);
829
- writeStudioConfig(targetDir, config.template, path3.basename(targetDir));
852
+ writeStudioConfig(targetDir, config.template, path6.basename(targetDir));
830
853
  if (config.includeServer) {
831
854
  updateServerPackageName(targetDir, config.packageName);
832
855
  } else {
833
- fs2.rmSync(path3.join(targetDir, "server"), { recursive: true, force: true });
856
+ fs5.rmSync(path6.join(targetDir, "server"), { recursive: true, force: true });
834
857
  }
835
858
  s.stop("Project created!");
836
859
  const pm = detectPackageManager();
@@ -850,7 +873,7 @@ async function main() {
850
873
  ];
851
874
  if (config.includeServer) {
852
875
  structureGuide.push(
853
- `${pc.cyan("server/")} ${pc.dim("\u2190 MCP server for Claude Desktop")}`
876
+ `${pc.cyan("server/")} ${pc.dim("\u2190 MCP server for Claude Desktop and other MCP hosts")}`
854
877
  );
855
878
  }
856
879
  p.note(structureGuide.join("\n"), "Project structure");
@@ -859,7 +882,7 @@ async function main() {
859
882
  `${pc.cyan(`${runCmd} dev`)} ${pc.dim("Start the development workbench")}`
860
883
  );
861
884
  keyCommands.push(
862
- `${pc.cyan(`${runCmd} export`)} ${pc.dim("Build & export for ChatGPT")}`
885
+ `${pc.cyan(`${runCmd} export`)} ${pc.dim("Build & export app bundle + manifest")}`
863
886
  );
864
887
  if (config.includeServer) {
865
888
  keyCommands.push(
@@ -870,7 +893,7 @@ async function main() {
870
893
  p.log.message("");
871
894
  p.log.step(pc.bold("Building for multiple platforms:"));
872
895
  p.log.message(
873
- ` ${pc.dim("\u2022")} Use ${pc.cyan("useFeature('widgetState')")} to check for ChatGPT features`
896
+ ` ${pc.dim("\u2022")} Use ${pc.cyan("useFeature('widgetState')")} to check for optional ChatGPT extensions`
874
897
  );
875
898
  p.log.message(
876
899
  ` ${pc.dim("\u2022")} Use ${pc.cyan("useFeature('modelContext')")} to check for MCP features`
@@ -886,6 +909,12 @@ async function main() {
886
909
  p.log.message(
887
910
  ` ${pc.dim("\u2022")} MCP Guide: ${pc.cyan("https://modelcontextprotocol.io/quickstart")}`
888
911
  );
912
+ p.log.message(
913
+ ` ${pc.dim("\u2022")} ChatGPT Submission: ${pc.cyan("https://platform.openai.com/apps")}`
914
+ );
915
+ p.log.message(
916
+ ` ${pc.dim("\u2022")} Claude Submission: ${pc.cyan("https://support.claude.com/en/articles/12922490-remote-mcp-server-submission-guide")}`
917
+ );
889
918
  }
890
919
  p.log.message("");
891
920
  p.outro(pc.green("Happy building! \u{1F680}"));
@@ -11,8 +11,7 @@
11
11
  type Theme = "light" | "dark";
12
12
  /**
13
13
  * Platform your app is running on.
14
- * - `"chatgpt"` - Running inside ChatGPT (window.openai API available)
15
- * - `"mcp"` - Running inside an MCP host like Claude Desktop
14
+ * - `"mcp"` - Running inside an MCP Apps host (including ChatGPT)
16
15
  * - `"unknown"` - Platform not detected (development mode or unsupported host)
17
16
  *
18
17
  * Use `usePlatform()` hook or `detectPlatform()` to check at runtime.
@@ -20,12 +19,10 @@ type Theme = "light" | "dark";
20
19
  * @example
21
20
  * ```tsx
22
21
  * const platform = usePlatform();
23
- * if (platform === 'chatgpt') {
24
- * // Use ChatGPT-specific features like widgetState
25
- * }
22
+ * if (platform === 'unknown') return null;
26
23
  * ```
27
24
  */
28
- type Platform = "chatgpt" | "mcp" | "unknown";
25
+ type Platform = "mcp" | "unknown";
29
26
  /**
30
27
  * Display mode for the widget.
31
28
  * - `"inline"` - Widget embedded in conversation flow (default)
@@ -314,7 +311,7 @@ type FeatureKey = "callTool" | "openLink" | "sizeReporting" | "closeWidget" | "s
314
311
  * ```tsx
315
312
  * const capabilities = useCapabilities();
316
313
  * if (capabilities?.widgetState) {
317
- * // ChatGPT-specific feature
314
+ * // ChatGPT extensions (window.openai)
318
315
  * }
319
316
  * ```
320
317
  */
@@ -335,11 +332,11 @@ interface HostCapabilities {
335
332
  sendMessage: boolean;
336
333
  /** Can request modal dialogs */
337
334
  modal: boolean;
338
- /** Can upload files (ChatGPT only) */
335
+ /** Can upload files (ChatGPT extensions only) */
339
336
  fileUpload: boolean;
340
- /** Can download files (ChatGPT only) */
337
+ /** Can download files (ChatGPT extensions only) */
341
338
  fileDownload: boolean;
342
- /** Can persist widget state (ChatGPT only) */
339
+ /** Can persist widget state (ChatGPT extensions only) */
343
340
  widgetState: boolean;
344
341
  /** Can update model context (MCP only) */
345
342
  modelContext: boolean;
@@ -352,16 +349,6 @@ interface HostCapabilities {
352
349
  /** Teardown notification support (MCP only) */
353
350
  teardown: boolean;
354
351
  }
355
- /**
356
- * Capabilities available on ChatGPT.
357
- *
358
- * Notable features:
359
- * - `widgetState`: Persistent state across refreshes
360
- * - `fileUpload`/`fileDownload`: File handling
361
- * - `modal`: Modal dialog support
362
- * - `pip`: Picture-in-picture display mode
363
- */
364
- declare const CHATGPT_CAPABILITIES: HostCapabilities;
365
352
  /**
366
353
  * Capabilities available on MCP hosts (e.g., Claude Desktop).
367
354
  *
@@ -396,7 +383,7 @@ type ToolCancelledCallback = (reason: string) => void;
396
383
  type HostContextChangedCallback = (ctx: Partial<HostContext>) => void;
397
384
  type TeardownCallback = () => Promise<void> | void;
398
385
  interface HostBridge {
399
- readonly platform: "chatgpt" | "mcp";
386
+ readonly platform: "mcp";
400
387
  readonly capabilities: HostCapabilities;
401
388
  connect(): Promise<void>;
402
389
  getHostContext(): HostContext | null;
@@ -431,16 +418,11 @@ interface ExtendedBridge extends HostBridge {
431
418
  }>;
432
419
  requestClose?(): void;
433
420
  requestModal?(options: {
434
- title?: string;
435
421
  params?: Record<string, unknown>;
436
- anchor?: {
437
- x: number;
438
- y: number;
439
- width: number;
440
- height: number;
441
- };
422
+ template?: string;
423
+ [key: string]: unknown;
442
424
  }): Promise<void>;
443
425
  sendLog?(level: "debug" | "info" | "warning" | "error", data: string): void;
444
426
  }
445
427
 
446
- export { type AudioContentBlock, CHATGPT_CAPABILITIES, type ChatMessage, type ContainerDimensions, type ContentBlock, type ContentBlockAnnotations, type ContentBlockIcon, type DisplayMode, type ExtendedBridge, type FeatureKey, type HostBridge, type HostCapabilities, type HostContext, type HostContextChangedCallback, type HostStyles, type ImageContentBlock, MCP_CAPABILITIES, type Platform, type ResourceContentBlock, type ResourceLinkContentBlock, type TeardownCallback, type TextContentBlock, type Theme, type ToolCancelledCallback, type ToolInputCallback, type ToolInputPartialCallback, type ToolResult, type ToolResultCallback, hasFeature, imageBlock, textBlock };
428
+ export { type AudioContentBlock, type ChatMessage, type ContainerDimensions, type ContentBlock, type ContentBlockAnnotations, type ContentBlockIcon, type DisplayMode, type ExtendedBridge, type FeatureKey, type HostBridge, type HostCapabilities, type HostContext, type HostContextChangedCallback, type HostStyles, type ImageContentBlock, MCP_CAPABILITIES, type Platform, type ResourceContentBlock, type ResourceLinkContentBlock, type TeardownCallback, type TextContentBlock, type Theme, type ToolCancelledCallback, type ToolInputCallback, type ToolInputPartialCallback, type ToolResult, type ToolResultCallback, hasFeature, imageBlock, textBlock };
@@ -3,12 +3,10 @@ import {
3
3
  textBlock
4
4
  } from "../chunk-KRCGOYZ5.js";
5
5
  import {
6
- CHATGPT_CAPABILITIES,
7
6
  MCP_CAPABILITIES,
8
7
  hasFeature
9
- } from "../chunk-4LAH4JH6.js";
8
+ } from "../chunk-QNH5NSRH.js";
10
9
  export {
11
- CHATGPT_CAPABILITIES,
12
10
  MCP_CAPABILITIES,
13
11
  hasFeature,
14
12
  imageBlock,