@tanstack/cta-cli 0.27.1 → 0.29.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.js CHANGED
@@ -226,17 +226,79 @@ Remove your node_modules directory and package lock file and re-install.`);
226
226
  return addOns;
227
227
  })
228
228
  .option('--list-add-ons', 'list all available add-ons', false)
229
+ .option('--addon-details <addon-id>', 'show detailed information about a specific add-on')
229
230
  .option('--no-git', 'do not create a git repository')
230
231
  .option('--target-dir <path>', 'the target directory for the application root')
231
232
  .option('--mcp', 'run the MCP server', false)
232
233
  .option('--mcp-sse', 'run the MCP server in SSE mode', false)
233
- .option('--ui', 'Add with the UI');
234
+ .option('--ui', 'Add with the UI')
235
+ .option('--add-on-config <config>', 'JSON string with add-on configuration options');
234
236
  program.action(async (projectName, options) => {
235
237
  if (options.listAddOns) {
236
238
  const addOns = await getAllAddOns(getFrameworkByName(options.framework || defaultFramework || 'React'), defaultMode ||
237
239
  convertTemplateToMode(options.template || defaultTemplate));
240
+ let hasConfigurableAddOns = false;
238
241
  for (const addOn of addOns.filter((a) => !forcedAddOns.includes(a.id))) {
239
- console.log(`${chalk.bold(addOn.id)}: ${addOn.description}`);
242
+ const hasOptions = addOn.options && Object.keys(addOn.options).length > 0;
243
+ const optionMarker = hasOptions ? '*' : ' ';
244
+ if (hasOptions)
245
+ hasConfigurableAddOns = true;
246
+ console.log(`${optionMarker} ${chalk.bold(addOn.id)}: ${addOn.description}`);
247
+ }
248
+ if (hasConfigurableAddOns) {
249
+ console.log('\n* = has configuration options');
250
+ }
251
+ }
252
+ else if (options.addonDetails) {
253
+ const addOns = await getAllAddOns(getFrameworkByName(options.framework || defaultFramework || 'React'), defaultMode ||
254
+ convertTemplateToMode(options.template || defaultTemplate));
255
+ const addOn = addOns.find((a) => a.id === options.addonDetails);
256
+ if (!addOn) {
257
+ console.error(`Add-on '${options.addonDetails}' not found`);
258
+ process.exit(1);
259
+ }
260
+ console.log(`${chalk.bold.cyan('Add-on Details:')} ${chalk.bold(addOn.name)}`);
261
+ console.log(`${chalk.bold('ID:')} ${addOn.id}`);
262
+ console.log(`${chalk.bold('Description:')} ${addOn.description}`);
263
+ console.log(`${chalk.bold('Type:')} ${addOn.type}`);
264
+ console.log(`${chalk.bold('Phase:')} ${addOn.phase}`);
265
+ console.log(`${chalk.bold('Supported Modes:')} ${addOn.modes.join(', ')}`);
266
+ if (addOn.link) {
267
+ console.log(`${chalk.bold('Link:')} ${chalk.blue(addOn.link)}`);
268
+ }
269
+ if (addOn.dependsOn && addOn.dependsOn.length > 0) {
270
+ console.log(`${chalk.bold('Dependencies:')} ${addOn.dependsOn.join(', ')}`);
271
+ }
272
+ if (addOn.options && Object.keys(addOn.options).length > 0) {
273
+ console.log(`\n${chalk.bold.yellow('Configuration Options:')}`);
274
+ for (const [optionName, option] of Object.entries(addOn.options)) {
275
+ if (option && typeof option === 'object' && 'type' in option) {
276
+ const opt = option;
277
+ console.log(` ${chalk.bold(optionName)}:`);
278
+ console.log(` Label: ${opt.label}`);
279
+ if (opt.description) {
280
+ console.log(` Description: ${opt.description}`);
281
+ }
282
+ console.log(` Type: ${opt.type}`);
283
+ console.log(` Default: ${opt.default}`);
284
+ if (opt.type === 'select' && opt.options) {
285
+ console.log(` Available values:`);
286
+ for (const choice of opt.options) {
287
+ console.log(` - ${choice.value}: ${choice.label}`);
288
+ }
289
+ }
290
+ }
291
+ }
292
+ }
293
+ else {
294
+ console.log(`\n${chalk.gray('No configuration options available')}`);
295
+ }
296
+ if (addOn.routes && addOn.routes.length > 0) {
297
+ console.log(`\n${chalk.bold.green('Routes:')}`);
298
+ for (const route of addOn.routes) {
299
+ console.log(` ${chalk.bold(route.url)} (${route.name})`);
300
+ console.log(` File: ${route.path}`);
301
+ }
240
302
  }
241
303
  }
242
304
  else if (options.mcp || options.mcpSse) {
@@ -1,5 +1,5 @@
1
1
  import { resolve } from 'node:path';
2
- import { DEFAULT_PACKAGE_MANAGER, finalizeAddOns, getFrameworkById, getPackageManager, loadStarter, } from '@tanstack/cta-engine';
2
+ import { DEFAULT_PACKAGE_MANAGER, finalizeAddOns, getFrameworkById, getPackageManager, loadStarter, populateAddOnOptionsDefaults, } from '@tanstack/cta-engine';
3
3
  export async function normalizeOptions(cliOptions, forcedMode, forcedAddOns, opts) {
4
4
  const projectName = (cliOptions.projectName ?? '').trim();
5
5
  if (!projectName && !opts?.disableNameCheck) {
@@ -69,6 +69,17 @@ export async function normalizeOptions(cliOptions, forcedMode, forcedAddOns, opt
69
69
  tailwind = true;
70
70
  typescript = true;
71
71
  }
72
+ // Handle add-on configuration option
73
+ let addOnOptionsFromCLI = {};
74
+ if (cliOptions.addOnConfig) {
75
+ try {
76
+ addOnOptionsFromCLI = JSON.parse(cliOptions.addOnConfig);
77
+ }
78
+ catch (error) {
79
+ console.error('Error parsing add-on config:', error);
80
+ process.exit(1);
81
+ }
82
+ }
72
83
  return {
73
84
  projectName: projectName,
74
85
  targetDir: resolve(process.cwd(), projectName),
@@ -81,6 +92,10 @@ export async function normalizeOptions(cliOptions, forcedMode, forcedAddOns, opt
81
92
  DEFAULT_PACKAGE_MANAGER,
82
93
  git: !!cliOptions.git,
83
94
  chosenAddOns,
95
+ addOnOptions: {
96
+ ...populateAddOnOptionsDefaults(chosenAddOns),
97
+ ...addOnOptionsFromCLI
98
+ },
84
99
  starter: starter,
85
100
  };
86
101
  }
package/dist/mcp.js CHANGED
@@ -5,7 +5,7 @@ import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
5
5
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
6
6
  import express from 'express';
7
7
  import { z } from 'zod';
8
- import { createApp, createDefaultEnvironment, finalizeAddOns, getFrameworkByName, getFrameworks, } from '@tanstack/cta-engine';
8
+ import { createApp, createDefaultEnvironment, finalizeAddOns, getFrameworkByName, getFrameworks, populateAddOnOptionsDefaults, } from '@tanstack/cta-engine';
9
9
  function createServer({ appName, forcedAddOns = [], }) {
10
10
  const server = new McpServer({
11
11
  name: `${appName} Application Builder`,
@@ -28,7 +28,10 @@ function createServer({ appName, forcedAddOns = [], }) {
28
28
  .filter((addOn) => addOn.modes.includes('file-router'))
29
29
  .map((addOn) => ({
30
30
  id: addOn.id,
31
+ name: addOn.name,
31
32
  description: addOn.description,
33
+ options: addOn.options,
34
+ dependsOn: addOn.dependsOn,
32
35
  }))),
33
36
  },
34
37
  ],
@@ -42,11 +45,12 @@ function createServer({ appName, forcedAddOns = [], }) {
42
45
  .string()
43
46
  .describe('The package.json module name of the application (will also be the directory name)'),
44
47
  cwd: z.string().describe('The directory to create the application in'),
45
- addOns: z.array(z.string()).describe('The IDs of the add-ons to install'),
48
+ addOns: z.array(z.string()).describe('Array of add-on IDs to install. Use listTanStackAddOns tool to see available add-ons and their configuration options. Example: ["prisma", "shadcn", "tanstack-query"]'),
49
+ addOnOptions: z.record(z.record(z.any())).optional().describe('Configuration options for add-ons. Format: {"addOnId": {"optionName": "value"}}. Use listTanStackAddOns to see available options for each add-on.'),
46
50
  targetDir: z
47
51
  .string()
48
52
  .describe('The directory to create the application in. Use the absolute path of the directory you want the application to be created in'),
49
- }, async ({ framework: frameworkName, projectName, addOns, cwd, targetDir, }) => {
53
+ }, async ({ framework: frameworkName, projectName, addOns, addOnOptions, cwd, targetDir, }) => {
50
54
  const framework = getFrameworkByName(frameworkName);
51
55
  try {
52
56
  process.chdir(cwd);
@@ -64,6 +68,7 @@ function createServer({ appName, forcedAddOns = [], }) {
64
68
  packageManager: 'pnpm',
65
69
  mode: 'file-router',
66
70
  chosenAddOns,
71
+ addOnOptions: addOnOptions || populateAddOnOptionsDefaults(chosenAddOns),
67
72
  git: true,
68
73
  });
69
74
  }
package/dist/options.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { intro } from '@clack/prompts';
2
- import { finalizeAddOns, getFrameworkById, getPackageManager, readConfigFile, } from '@tanstack/cta-engine';
3
- import { getProjectName, selectAddOns, selectGit, selectPackageManager, selectRouterType, selectTailwind, selectToolchain, selectHost, selectTypescript, } from './ui-prompts.js';
2
+ import { finalizeAddOns, getFrameworkById, getPackageManager, populateAddOnOptionsDefaults, readConfigFile, } from '@tanstack/cta-engine';
3
+ import { getProjectName, promptForAddOnOptions, selectAddOns, selectGit, selectPackageManager, selectRouterType, selectTailwind, selectToolchain, selectHost, selectTypescript, } from './ui-prompts.js';
4
4
  export async function promptForCreateOptions(cliOptions, { forcedAddOns = [], forcedMode, }) {
5
5
  const options = {};
6
6
  options.framework = getFrameworkById(cliOptions.framework || 'react-cra');
@@ -76,6 +76,18 @@ export async function promptForCreateOptions(cliOptions, { forcedAddOns = [], fo
76
76
  options.tailwind = true;
77
77
  options.typescript = true;
78
78
  }
79
+ // Prompt for add-on options in interactive mode
80
+ if (Array.isArray(cliOptions.addOns)) {
81
+ // Non-interactive mode: use defaults
82
+ options.addOnOptions = populateAddOnOptionsDefaults(options.chosenAddOns);
83
+ }
84
+ else {
85
+ // Interactive mode: prompt for options
86
+ const userOptions = await promptForAddOnOptions(options.chosenAddOns.map(a => a.id), options.framework);
87
+ const defaultOptions = populateAddOnOptionsDefaults(options.chosenAddOns);
88
+ // Merge user options with defaults
89
+ options.addOnOptions = { ...defaultOptions, ...userOptions };
90
+ }
79
91
  options.git = cliOptions.git || (await selectGit());
80
92
  return options;
81
93
  }
@@ -11,10 +11,12 @@ export interface CliOptions {
11
11
  git?: boolean;
12
12
  addOns?: Array<string> | boolean;
13
13
  listAddOns?: boolean;
14
+ addonDetails?: string;
14
15
  mcp?: boolean;
15
16
  mcpSse?: boolean;
16
17
  starter?: string;
17
18
  targetDir?: string;
18
19
  interactive?: boolean;
19
20
  ui?: boolean;
21
+ addOnConfig?: string;
20
22
  }
@@ -8,4 +8,5 @@ export declare function selectPackageManager(): Promise<PackageManager>;
8
8
  export declare function selectAddOns(framework: Framework, mode: string, type: string, message: string, forcedAddOns?: Array<string>): Promise<Array<string>>;
9
9
  export declare function selectGit(): Promise<boolean>;
10
10
  export declare function selectToolchain(framework: Framework, toolchain?: string): Promise<string | undefined>;
11
+ export declare function promptForAddOnOptions(addOnIds: Array<string>, framework: Framework): Promise<Record<string, Record<string, any>>>;
11
12
  export declare function selectHost(framework: Framework, host?: string): Promise<string | undefined>;
@@ -138,6 +138,37 @@ export async function selectToolchain(framework, toolchain) {
138
138
  }
139
139
  return tc;
140
140
  }
141
+ export async function promptForAddOnOptions(addOnIds, framework) {
142
+ const addOnOptions = {};
143
+ for (const addOnId of addOnIds) {
144
+ const addOn = framework.getAddOns().find((a) => a.id === addOnId);
145
+ if (!addOn || !addOn.options)
146
+ continue;
147
+ addOnOptions[addOnId] = {};
148
+ for (const [optionName, option] of Object.entries(addOn.options)) {
149
+ if (option && typeof option === 'object' && 'type' in option) {
150
+ if (option.type === 'select') {
151
+ const selectOption = option;
152
+ const value = await select({
153
+ message: `${addOn.name}: ${selectOption.label}`,
154
+ options: selectOption.options.map((opt) => ({
155
+ value: opt.value,
156
+ label: opt.label,
157
+ })),
158
+ initialValue: selectOption.default,
159
+ });
160
+ if (isCancel(value)) {
161
+ cancel('Operation cancelled.');
162
+ process.exit(0);
163
+ }
164
+ addOnOptions[addOnId][optionName] = value;
165
+ }
166
+ // Future option types can be added here
167
+ }
168
+ }
169
+ }
170
+ return addOnOptions;
171
+ }
141
172
  export async function selectHost(framework, host) {
142
173
  const hosts = new Set();
143
174
  let initialValue = undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/cta-cli",
3
- "version": "0.27.1",
3
+ "version": "0.29.0",
4
4
  "description": "Tanstack Application Builder CLI",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -30,8 +30,8 @@
30
30
  "express": "^4.21.2",
31
31
  "semver": "^7.7.2",
32
32
  "zod": "^3.24.2",
33
- "@tanstack/cta-engine": "0.27.1",
34
- "@tanstack/cta-ui": "0.27.1"
33
+ "@tanstack/cta-engine": "0.29.0",
34
+ "@tanstack/cta-ui": "0.29.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@tanstack/config": "^0.16.2",
package/src/cli.ts CHANGED
@@ -353,6 +353,7 @@ Remove your node_modules directory and package lock file and re-install.`,
353
353
  },
354
354
  )
355
355
  .option('--list-add-ons', 'list all available add-ons', false)
356
+ .option('--addon-details <addon-id>', 'show detailed information about a specific add-on')
356
357
  .option('--no-git', 'do not create a git repository')
357
358
  .option(
358
359
  '--target-dir <path>',
@@ -361,6 +362,7 @@ Remove your node_modules directory and package lock file and re-install.`,
361
362
  .option('--mcp', 'run the MCP server', false)
362
363
  .option('--mcp-sse', 'run the MCP server in SSE mode', false)
363
364
  .option('--ui', 'Add with the UI')
365
+ .option('--add-on-config <config>', 'JSON string with add-on configuration options')
364
366
 
365
367
  program.action(async (projectName: string, options: CliOptions) => {
366
368
  if (options.listAddOns) {
@@ -369,8 +371,73 @@ Remove your node_modules directory and package lock file and re-install.`,
369
371
  defaultMode ||
370
372
  convertTemplateToMode(options.template || defaultTemplate),
371
373
  )
374
+ let hasConfigurableAddOns = false
372
375
  for (const addOn of addOns.filter((a) => !forcedAddOns.includes(a.id))) {
373
- console.log(`${chalk.bold(addOn.id)}: ${addOn.description}`)
376
+ const hasOptions = addOn.options && Object.keys(addOn.options).length > 0
377
+ const optionMarker = hasOptions ? '*' : ' '
378
+ if (hasOptions) hasConfigurableAddOns = true
379
+ console.log(`${optionMarker} ${chalk.bold(addOn.id)}: ${addOn.description}`)
380
+ }
381
+ if (hasConfigurableAddOns) {
382
+ console.log('\n* = has configuration options')
383
+ }
384
+ } else if (options.addonDetails) {
385
+ const addOns = await getAllAddOns(
386
+ getFrameworkByName(options.framework || defaultFramework || 'React')!,
387
+ defaultMode ||
388
+ convertTemplateToMode(options.template || defaultTemplate),
389
+ )
390
+ const addOn = addOns.find((a) => a.id === options.addonDetails)
391
+ if (!addOn) {
392
+ console.error(`Add-on '${options.addonDetails}' not found`)
393
+ process.exit(1)
394
+ }
395
+
396
+ console.log(`${chalk.bold.cyan('Add-on Details:')} ${chalk.bold(addOn.name)}`)
397
+ console.log(`${chalk.bold('ID:')} ${addOn.id}`)
398
+ console.log(`${chalk.bold('Description:')} ${addOn.description}`)
399
+ console.log(`${chalk.bold('Type:')} ${addOn.type}`)
400
+ console.log(`${chalk.bold('Phase:')} ${addOn.phase}`)
401
+ console.log(`${chalk.bold('Supported Modes:')} ${addOn.modes.join(', ')}`)
402
+
403
+ if (addOn.link) {
404
+ console.log(`${chalk.bold('Link:')} ${chalk.blue(addOn.link)}`)
405
+ }
406
+
407
+ if (addOn.dependsOn && addOn.dependsOn.length > 0) {
408
+ console.log(`${chalk.bold('Dependencies:')} ${addOn.dependsOn.join(', ')}`)
409
+ }
410
+
411
+ if (addOn.options && Object.keys(addOn.options).length > 0) {
412
+ console.log(`\n${chalk.bold.yellow('Configuration Options:')}`)
413
+ for (const [optionName, option] of Object.entries(addOn.options)) {
414
+ if (option && typeof option === 'object' && 'type' in option) {
415
+ const opt = option as any
416
+ console.log(` ${chalk.bold(optionName)}:`)
417
+ console.log(` Label: ${opt.label}`)
418
+ if (opt.description) {
419
+ console.log(` Description: ${opt.description}`)
420
+ }
421
+ console.log(` Type: ${opt.type}`)
422
+ console.log(` Default: ${opt.default}`)
423
+ if (opt.type === 'select' && opt.options) {
424
+ console.log(` Available values:`)
425
+ for (const choice of opt.options) {
426
+ console.log(` - ${choice.value}: ${choice.label}`)
427
+ }
428
+ }
429
+ }
430
+ }
431
+ } else {
432
+ console.log(`\n${chalk.gray('No configuration options available')}`)
433
+ }
434
+
435
+ if (addOn.routes && addOn.routes.length > 0) {
436
+ console.log(`\n${chalk.bold.green('Routes:')}`)
437
+ for (const route of addOn.routes) {
438
+ console.log(` ${chalk.bold(route.url)} (${route.name})`)
439
+ console.log(` File: ${route.path}`)
440
+ }
374
441
  }
375
442
  } else if (options.mcp || options.mcpSse) {
376
443
  await runMCPServer(!!options.mcpSse, {
@@ -6,6 +6,7 @@ import {
6
6
  getFrameworkById,
7
7
  getPackageManager,
8
8
  loadStarter,
9
+ populateAddOnOptionsDefaults,
9
10
  } from '@tanstack/cta-engine'
10
11
 
11
12
  import type { Options } from '@tanstack/cta-engine'
@@ -110,6 +111,17 @@ export async function normalizeOptions(
110
111
  typescript = true
111
112
  }
112
113
 
114
+ // Handle add-on configuration option
115
+ let addOnOptionsFromCLI = {}
116
+ if (cliOptions.addOnConfig) {
117
+ try {
118
+ addOnOptionsFromCLI = JSON.parse(cliOptions.addOnConfig)
119
+ } catch (error) {
120
+ console.error('Error parsing add-on config:', error)
121
+ process.exit(1)
122
+ }
123
+ }
124
+
113
125
  return {
114
126
  projectName: projectName,
115
127
  targetDir: resolve(process.cwd(), projectName),
@@ -123,6 +135,10 @@ export async function normalizeOptions(
123
135
  DEFAULT_PACKAGE_MANAGER,
124
136
  git: !!cliOptions.git,
125
137
  chosenAddOns,
138
+ addOnOptions: {
139
+ ...populateAddOnOptionsDefaults(chosenAddOns),
140
+ ...addOnOptionsFromCLI
141
+ },
126
142
  starter: starter,
127
143
  }
128
144
  }
package/src/mcp.ts CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  finalizeAddOns,
14
14
  getFrameworkByName,
15
15
  getFrameworks,
16
+ populateAddOnOptionsDefaults,
16
17
  } from '@tanstack/cta-engine'
17
18
 
18
19
  function createServer({
@@ -53,7 +54,10 @@ function createServer({
53
54
  .filter((addOn) => addOn.modes.includes('file-router'))
54
55
  .map((addOn) => ({
55
56
  id: addOn.id,
57
+ name: addOn.name,
56
58
  description: addOn.description,
59
+ options: addOn.options,
60
+ dependsOn: addOn.dependsOn,
57
61
  })),
58
62
  ),
59
63
  },
@@ -77,7 +81,8 @@ function createServer({
77
81
  'The package.json module name of the application (will also be the directory name)',
78
82
  ),
79
83
  cwd: z.string().describe('The directory to create the application in'),
80
- addOns: z.array(z.string()).describe('The IDs of the add-ons to install'),
84
+ addOns: z.array(z.string()).describe('Array of add-on IDs to install. Use listTanStackAddOns tool to see available add-ons and their configuration options. Example: ["prisma", "shadcn", "tanstack-query"]'),
85
+ addOnOptions: z.record(z.record(z.any())).optional().describe('Configuration options for add-ons. Format: {"addOnId": {"optionName": "value"}}. Use listTanStackAddOns to see available options for each add-on.'),
81
86
  targetDir: z
82
87
  .string()
83
88
  .describe(
@@ -88,6 +93,7 @@ function createServer({
88
93
  framework: frameworkName,
89
94
  projectName,
90
95
  addOns,
96
+ addOnOptions,
91
97
  cwd,
92
98
  targetDir,
93
99
  }) => {
@@ -114,6 +120,7 @@ function createServer({
114
120
  packageManager: 'pnpm',
115
121
  mode: 'file-router',
116
122
  chosenAddOns,
123
+ addOnOptions: addOnOptions || populateAddOnOptionsDefaults(chosenAddOns),
117
124
  git: true,
118
125
  })
119
126
  } catch (error) {
package/src/options.ts CHANGED
@@ -4,11 +4,13 @@ import {
4
4
  finalizeAddOns,
5
5
  getFrameworkById,
6
6
  getPackageManager,
7
+ populateAddOnOptionsDefaults,
7
8
  readConfigFile,
8
9
  } from '@tanstack/cta-engine'
9
10
 
10
11
  import {
11
12
  getProjectName,
13
+ promptForAddOnOptions,
12
14
  selectAddOns,
13
15
  selectGit,
14
16
  selectPackageManager,
@@ -137,6 +139,17 @@ export async function promptForCreateOptions(
137
139
  options.typescript = true
138
140
  }
139
141
 
142
+ // Prompt for add-on options in interactive mode
143
+ if (Array.isArray(cliOptions.addOns)) {
144
+ // Non-interactive mode: use defaults
145
+ options.addOnOptions = populateAddOnOptionsDefaults(options.chosenAddOns)
146
+ } else {
147
+ // Interactive mode: prompt for options
148
+ const userOptions = await promptForAddOnOptions(options.chosenAddOns.map(a => a.id), options.framework)
149
+ const defaultOptions = populateAddOnOptionsDefaults(options.chosenAddOns)
150
+ // Merge user options with defaults
151
+ options.addOnOptions = { ...defaultOptions, ...userOptions }
152
+ }
140
153
  options.git = cliOptions.git || (await selectGit())
141
154
 
142
155
  return options
package/src/types.ts CHANGED
@@ -13,10 +13,12 @@ export interface CliOptions {
13
13
  git?: boolean
14
14
  addOns?: Array<string> | boolean
15
15
  listAddOns?: boolean
16
+ addonDetails?: string
16
17
  mcp?: boolean
17
18
  mcpSse?: boolean
18
19
  starter?: string
19
20
  targetDir?: string
20
21
  interactive?: boolean
21
22
  ui?: boolean
23
+ addOnConfig?: string
22
24
  }
package/src/ui-prompts.ts CHANGED
@@ -185,6 +185,53 @@ export async function selectToolchain(
185
185
  return tc
186
186
  }
187
187
 
188
+ export async function promptForAddOnOptions(
189
+ addOnIds: Array<string>,
190
+ framework: Framework,
191
+ ): Promise<Record<string, Record<string, any>>> {
192
+ const addOnOptions: Record<string, Record<string, any>> = {}
193
+
194
+ for (const addOnId of addOnIds) {
195
+ const addOn = framework.getAddOns().find((a) => a.id === addOnId)
196
+ if (!addOn || !addOn.options) continue
197
+
198
+ addOnOptions[addOnId] = {}
199
+
200
+ for (const [optionName, option] of Object.entries(addOn.options)) {
201
+ if (option && typeof option === 'object' && 'type' in option) {
202
+ if (option.type === 'select') {
203
+ const selectOption = option as {
204
+ type: 'select'
205
+ label: string
206
+ description?: string
207
+ default: string
208
+ options: Array<{ value: string; label: string }>
209
+ }
210
+
211
+ const value = await select({
212
+ message: `${addOn.name}: ${selectOption.label}`,
213
+ options: selectOption.options.map((opt) => ({
214
+ value: opt.value,
215
+ label: opt.label,
216
+ })),
217
+ initialValue: selectOption.default,
218
+ })
219
+
220
+ if (isCancel(value)) {
221
+ cancel('Operation cancelled.')
222
+ process.exit(0)
223
+ }
224
+
225
+ addOnOptions[addOnId][optionName] = value
226
+ }
227
+ // Future option types can be added here
228
+ }
229
+ }
230
+ }
231
+
232
+ return addOnOptions
233
+ }
234
+
188
235
  export async function selectHost(
189
236
  framework: Framework,
190
237
  host?: string,