@gxp-dev/tools 2.0.11 → 2.0.12

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.
@@ -1,85 +1,48 @@
1
1
  /**
2
2
  * Init Command
3
3
  *
4
- * Sets up a new GxP project or updates an existing one.
4
+ * Sets up a new GxP project with interactive configuration.
5
+ *
6
+ * Flow:
7
+ * 1. Create project directory with provided name
8
+ * 2. Copy template files and install dependencies
9
+ * 3. Run interactive configuration:
10
+ * - App name (prepopulated from package.json)
11
+ * - Description (prepopulated from package.json)
12
+ * - AI scaffolding (optional)
13
+ * 4. Prompt to start the app
14
+ * 5. Prompt to launch browser with extension
5
15
  */
6
16
 
7
17
  const path = require("path");
8
18
  const fs = require("fs");
19
+ const { spawn } = require("child_process");
9
20
  const { REQUIRED_DEPENDENCIES } = require("../constants");
10
21
  const {
11
22
  findProjectRoot,
12
23
  resolveGxPaths,
13
24
  promptUser,
25
+ arrowSelectPrompt,
26
+ inputWithDefault,
27
+ multiLinePrompt,
14
28
  safeCopyFile,
15
29
  createPackageJson,
30
+ updateAppManifest,
16
31
  installDependencies,
17
32
  updateExistingProject,
18
33
  ensureMkcertInstalled,
19
34
  generateSSLCertificates,
20
35
  updateEnvWithCertPaths,
36
+ runAIScaffolding,
37
+ getAvailableProviders,
21
38
  } = require("../utils");
22
39
 
23
40
  /**
24
- * Initialize command - sets up a new GxP project or updates existing one
41
+ * Copy template files to project
42
+ * @param {string} projectPath - Target project path
43
+ * @param {object} paths - Resolved GxP paths
25
44
  */
26
- async function initCommand(argv) {
27
- const currentDir = process.cwd();
28
- const hasPackageJson = fs.existsSync(path.join(currentDir, "package.json"));
29
- let projectPath = currentDir;
30
- let projectName;
31
- let sslSetup = false;
32
-
33
- if (!hasPackageJson && !argv.name) {
34
- // New project - prompt for name
35
- projectName = await promptUser("Enter project name: ");
36
- if (!projectName) {
37
- console.error("Project name is required!");
38
- process.exit(1);
39
- }
40
-
41
- // Create project directory
42
- projectPath = path.join(currentDir, projectName);
43
- if (fs.existsSync(projectPath)) {
44
- console.error(`Directory ${projectName} already exists!`);
45
- process.exit(1);
46
- }
47
-
48
- console.log(`Creating new project: ${projectName}`);
49
- fs.mkdirSync(projectPath, { recursive: true });
50
-
51
- // Create package.json
52
- createPackageJson(projectPath, projectName);
53
-
54
- // Install dependencies
55
- installDependencies(projectPath);
56
- } else if (hasPackageJson) {
57
- // Existing project - update it
58
- console.log("Updating existing project...");
59
- updateExistingProject(projectPath);
60
- } else if (argv.name) {
61
- // New project with provided name
62
- projectName = argv.name;
63
- projectPath = path.join(currentDir, projectName);
64
-
65
- if (fs.existsSync(projectPath)) {
66
- console.error(`Directory ${projectName} already exists!`);
67
- process.exit(1);
68
- }
69
-
70
- console.log(`Creating new project: ${projectName}`);
71
- fs.mkdirSync(projectPath, { recursive: true });
72
- createPackageJson(projectPath, projectName);
73
- installDependencies(projectPath);
74
- }
75
-
76
- // Copy template files
77
- // Note: PortalContainer.vue (formerly App.vue) is now in runtime/ and accessed via @gx-runtime alias
78
- // Users don't need a copy - it's immutable and loaded from node_modules
79
- const paths = resolveGxPaths();
80
- // Note: main.js, index.html, and vite.config.js are NOT copied by default.
81
- // They are served from the runtime directory. Users can publish them
82
- // for customization using: gxdev publish main.js / index.html / vite.config.js
45
+ function copyTemplateFiles(projectPath, paths) {
83
46
  const filesToCopy = [
84
47
  {
85
48
  src: "theme-layouts/SystemLayout.vue",
@@ -143,6 +106,27 @@ async function initCommand(argv) {
143
106
  dest: "README.md",
144
107
  desc: "README.md (Project documentation)",
145
108
  },
109
+ // AI Agent configuration files
110
+ {
111
+ src: "AGENTS.md",
112
+ dest: "AGENTS.md",
113
+ desc: "AGENTS.md (Codex/AI agent instructions)",
114
+ },
115
+ {
116
+ src: "GEMINI.md",
117
+ dest: "GEMINI.md",
118
+ desc: "GEMINI.md (Gemini Code Assist instructions)",
119
+ },
120
+ {
121
+ src: ".claude/agents/gxp-developer.md",
122
+ dest: ".claude/agents/gxp-developer.md",
123
+ desc: "Claude Code subagent (GxP developer)",
124
+ },
125
+ {
126
+ src: ".claude/settings.json",
127
+ dest: ".claude/settings.json",
128
+ desc: "Claude Code MCP settings (GxP API server)",
129
+ },
146
130
  ];
147
131
 
148
132
  // Copy template files
@@ -151,12 +135,67 @@ async function initCommand(argv) {
151
135
  const destPath = path.join(projectPath, file.dest);
152
136
  safeCopyFile(srcPath, destPath, file.desc);
153
137
  });
138
+ }
139
+
140
+ /**
141
+ * Copy extension scripts to project
142
+ * @param {string} projectPath - Target project path
143
+ * @param {object} paths - Resolved GxP paths
144
+ */
145
+ function copyExtensionScripts(projectPath, paths) {
146
+ const scriptsDir = path.join(projectPath, "scripts");
147
+ if (!fs.existsSync(scriptsDir)) {
148
+ fs.mkdirSync(scriptsDir, { recursive: true });
149
+ }
150
+
151
+ // Copy launch-chrome.js script
152
+ const launchChromeSource = path.join(
153
+ paths.templateDir,
154
+ "../scripts/launch-chrome.js"
155
+ );
156
+ const launchChromeDest = path.join(scriptsDir, "launch-chrome.js");
157
+ if (fs.existsSync(launchChromeSource)) {
158
+ safeCopyFile(launchChromeSource, launchChromeDest, "Chrome launcher script");
159
+ }
154
160
 
155
- // Create /src/assets/ directory for user assets
161
+ // Copy pack-chrome.js script
162
+ const packChromeSource = path.join(
163
+ paths.templateDir,
164
+ "../scripts/pack-chrome.js"
165
+ );
166
+ const packChromeDest = path.join(scriptsDir, "pack-chrome.js");
167
+ if (fs.existsSync(packChromeSource)) {
168
+ safeCopyFile(packChromeSource, packChromeDest, "Chrome packaging script");
169
+ }
170
+
171
+ // Copy socket events directory
172
+ const socketEventsSource = paths.socketEventsDir;
173
+ const socketEventsDest = path.join(projectPath, "socket-events");
174
+ if (fs.existsSync(socketEventsSource)) {
175
+ if (!fs.existsSync(socketEventsDest)) {
176
+ fs.mkdirSync(socketEventsDest, { recursive: true });
177
+ }
178
+
179
+ const eventFiles = fs
180
+ .readdirSync(socketEventsSource)
181
+ .filter((file) => file.endsWith(".json"));
182
+ eventFiles.forEach((file) => {
183
+ const srcPath = path.join(socketEventsSource, file);
184
+ const destPath = path.join(socketEventsDest, file);
185
+ safeCopyFile(srcPath, destPath, `Socket event: ${file}`);
186
+ });
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Create supporting directories and files
192
+ * @param {string} projectPath - Target project path
193
+ */
194
+ function createSupportingFiles(projectPath) {
195
+ // Create /src/assets/ directory
156
196
  const assetsDir = path.join(projectPath, "src", "assets");
157
197
  if (!fs.existsSync(assetsDir)) {
158
198
  fs.mkdirSync(assetsDir, { recursive: true });
159
- // Add a .gitkeep to ensure the directory is tracked
160
199
  fs.writeFileSync(path.join(assetsDir, ".gitkeep"), "");
161
200
  console.log("✓ Created src/assets/ directory for project assets");
162
201
  }
@@ -168,153 +207,367 @@ async function initCommand(argv) {
168
207
  fs.copyFileSync(envExamplePath, envPath);
169
208
  console.log("✓ Created .env file from .env.example");
170
209
  }
210
+ }
211
+
212
+ /**
213
+ * Read project info from package.json
214
+ * @param {string} projectPath - Project path
215
+ * @returns {object} - { name, description }
216
+ */
217
+ function readProjectInfo(projectPath) {
218
+ const packageJsonPath = path.join(projectPath, "package.json");
219
+ if (fs.existsSync(packageJsonPath)) {
220
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
221
+ return {
222
+ name: pkg.name || "",
223
+ description: pkg.description || "",
224
+ };
225
+ }
226
+ return { name: "", description: "" };
227
+ }
228
+
229
+ /**
230
+ * Update package.json with new name and description
231
+ * @param {string} projectPath - Project path
232
+ * @param {string} name - New name
233
+ * @param {string} description - New description
234
+ */
235
+ function updatePackageJson(projectPath, name, description) {
236
+ const packageJsonPath = path.join(projectPath, "package.json");
237
+ if (fs.existsSync(packageJsonPath)) {
238
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
239
+ pkg.name = name;
240
+ pkg.description = description;
241
+ fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, "\t"));
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Launch the development server
247
+ * @param {string} projectPath - Project path
248
+ * @param {object} options - { withMock: boolean, noHttps: boolean }
249
+ * @returns {ChildProcess} - The spawned process
250
+ */
251
+ function launchDevServer(projectPath, options = {}) {
252
+ const args = ["run"];
253
+
254
+ if (options.withMock) {
255
+ args.push("dev");
256
+ // The dev script with mock runs through TUI
257
+ } else {
258
+ args.push(options.noHttps ? "dev-http" : "dev");
259
+ }
260
+
261
+ console.log(`\n🚀 Starting development server...`);
262
+
263
+ const child = spawn("npm", args, {
264
+ cwd: projectPath,
265
+ stdio: "inherit",
266
+ shell: true,
267
+ });
268
+
269
+ return child;
270
+ }
271
+
272
+ /**
273
+ * Launch browser with extension
274
+ * @param {string} projectPath - Project path
275
+ * @param {string} browser - 'chrome' or 'firefox'
276
+ */
277
+ function launchBrowserExtension(projectPath, browser) {
278
+ console.log(`\n🌐 Launching ${browser} with GxP extension...`);
279
+
280
+ const gxdevPath = path.join(__dirname, "..", "..", "gx-devtools.js");
281
+
282
+ spawn("node", [gxdevPath, `ext:${browser}`], {
283
+ cwd: projectPath,
284
+ stdio: "inherit",
285
+ shell: true,
286
+ detached: true,
287
+ }).unref();
288
+ }
289
+
290
+ /**
291
+ * Run interactive configuration after project creation
292
+ * @param {string} projectPath - Path to project directory
293
+ * @param {string} initialName - Initial project name from CLI
294
+ */
295
+ async function runInteractiveConfig(projectPath, initialName) {
296
+ console.log("");
297
+ console.log("─".repeat(50));
298
+ console.log("📝 Configure Your Plugin");
299
+ console.log("─".repeat(50));
300
+
301
+ // Read current values from package.json
302
+ const projectInfo = readProjectInfo(projectPath);
303
+ const defaultName = projectInfo.name || initialName || path.basename(projectPath);
304
+ const defaultDescription = projectInfo.description || "A GxP kiosk plugin";
305
+
306
+ // 1. App Name - with prepopulated value and custom option
307
+ const appName = await arrowSelectPrompt("App name", [
308
+ { label: defaultName, value: defaultName, description: "From package.json" },
309
+ { label: "Enter custom name", value: "__custom__", isCustomInput: true, defaultValue: defaultName },
310
+ ]);
311
+
312
+ // 2. Description - with prepopulated value and custom option
313
+ const description = await arrowSelectPrompt("Description", [
314
+ { label: defaultDescription, value: defaultDescription, description: "From package.json" },
315
+ { label: "Enter custom description", value: "__custom__", isCustomInput: true, defaultValue: "" },
316
+ ]);
171
317
 
172
- // Copy extension management scripts for new projects
173
- if (!hasPackageJson || argv.name) {
174
- const scriptsDir = path.join(projectPath, "scripts");
175
- if (!fs.existsSync(scriptsDir)) {
176
- fs.mkdirSync(scriptsDir, { recursive: true });
318
+ // Update package.json and app-manifest with new values
319
+ updatePackageJson(projectPath, appName, description);
320
+ updateAppManifest(projectPath, appName, description);
321
+
322
+ // 3. AI Scaffolding
323
+ console.log("");
324
+ console.log("─".repeat(50));
325
+ console.log("🤖 AI-Powered Scaffolding");
326
+ console.log("─".repeat(50));
327
+ console.log(" Describe what you want to build and AI will generate");
328
+ console.log(" starter components, views, and manifest configuration.");
329
+ console.log("");
330
+
331
+ // Check available AI providers
332
+ const providers = await getAvailableProviders();
333
+ const availableProviders = providers.filter((p) => p.available);
334
+
335
+ let aiChoice = "skip";
336
+ let selectedProvider = null;
337
+
338
+ if (availableProviders.length === 0) {
339
+ console.log(" ⚠️ No AI providers available.");
340
+ console.log(" To enable AI scaffolding, set up one of:");
341
+ console.log(" • Claude CLI: npm install -g @anthropic-ai/claude-code && claude login");
342
+ console.log(" • Codex CLI: npm install -g @openai/codex && codex auth");
343
+ console.log(" • Gemini: export GEMINI_API_KEY=your_key or gcloud auth login");
344
+ console.log("");
345
+ aiChoice = "skip";
346
+ } else {
347
+ // Build provider options
348
+ const providerOptions = [
349
+ { label: "Skip AI scaffolding", value: "skip" },
350
+ ];
351
+
352
+ for (const provider of availableProviders) {
353
+ let authInfo = "";
354
+ if (provider.id === "gemini") {
355
+ authInfo = provider.method === "api_key" ? "via API key" : "via gcloud";
356
+ } else {
357
+ authInfo = "logged in";
358
+ }
359
+ providerOptions.push({
360
+ label: `${provider.name}`,
361
+ value: provider.id,
362
+ description: `${authInfo}`,
363
+ });
177
364
  }
178
365
 
179
- // Copy launch-chrome.js script
180
- const launchChromeSource = path.join(
181
- paths.templateDir,
182
- "../scripts/launch-chrome.js"
183
- );
184
- const launchChromeDest = path.join(scriptsDir, "launch-chrome.js");
185
- if (fs.existsSync(launchChromeSource)) {
186
- safeCopyFile(
187
- launchChromeSource,
188
- launchChromeDest,
189
- "Chrome launcher script"
190
- );
366
+ aiChoice = await arrowSelectPrompt("Choose AI provider for scaffolding", providerOptions);
367
+ if (aiChoice !== "skip") {
368
+ selectedProvider = aiChoice;
191
369
  }
370
+ }
192
371
 
193
- // Copy pack-chrome.js script
194
- const packChromeSource = path.join(
195
- paths.templateDir,
196
- "../scripts/pack-chrome.js"
372
+ let buildPrompt = "";
373
+ if (selectedProvider) {
374
+ buildPrompt = await multiLinePrompt(
375
+ "📝 Describe your plugin (what it does, key features, UI elements):",
376
+ "Press Enter twice when done"
197
377
  );
198
- const packChromeDest = path.join(scriptsDir, "pack-chrome.js");
199
- if (fs.existsSync(packChromeSource)) {
200
- safeCopyFile(packChromeSource, packChromeDest, "Chrome packaging script");
378
+
379
+ if (buildPrompt) {
380
+ await runAIScaffolding(projectPath, appName, description, buildPrompt, selectedProvider);
201
381
  }
382
+ }
202
383
 
203
- // Copy socket events directory for simulation (root level for CLI access)
204
- const socketEventsSource = paths.socketEventsDir;
205
- const socketEventsDest = path.join(projectPath, "socket-events");
206
- if (fs.existsSync(socketEventsSource)) {
207
- if (!fs.existsSync(socketEventsDest)) {
208
- fs.mkdirSync(socketEventsDest, { recursive: true });
209
- }
384
+ // 4. SSL Setup
385
+ console.log("");
386
+ console.log("".repeat(50));
387
+ console.log("🔒 SSL Configuration");
388
+ console.log("─".repeat(50));
210
389
 
211
- const eventFiles = fs
212
- .readdirSync(socketEventsSource)
213
- .filter((file) => file.endsWith(".json"));
214
- eventFiles.forEach((file) => {
215
- const srcPath = path.join(socketEventsSource, file);
216
- const destPath = path.join(socketEventsDest, file);
217
- safeCopyFile(srcPath, destPath, `Socket event: ${file}`);
218
- });
390
+ const sslChoice = await arrowSelectPrompt("Set up SSL certificates for HTTPS development?", [
391
+ { label: "Yes, set up SSL", value: "yes", description: "Recommended for full feature access" },
392
+ { label: "Skip SSL setup", value: "no", description: "Can be set up later with npm run setup-ssl" },
393
+ ]);
394
+
395
+ let sslSetup = false;
396
+ if (sslChoice === "yes") {
397
+ console.log("\n🔒 Setting up HTTPS development environment...");
398
+ ensureMkcertInstalled();
399
+ const certs = generateSSLCertificates(projectPath);
400
+ if (certs) {
401
+ updateEnvWithCertPaths(projectPath, certs);
402
+ sslSetup = true;
219
403
  }
220
404
  }
221
405
 
222
- // Setup SSL certificates for new projects
223
- if (!hasPackageJson || argv.name) {
224
- // Ask user if they want to set up SSL certificates
225
- const sslChoice = await promptUser(
226
- "Do you want to set up SSL certificates for HTTPS development? (Y/n): "
227
- );
228
- sslSetup =
229
- sslChoice.toLowerCase() !== "n" && sslChoice.toLowerCase() !== "no";
406
+ // 5. Start App
407
+ console.log("");
408
+ console.log("─".repeat(50));
409
+ console.log("🚀 Start Development");
410
+ console.log("─".repeat(50));
230
411
 
231
- if (sslSetup) {
232
- console.log("\n🔒 Setting up HTTPS development environment...");
233
- ensureMkcertInstalled();
234
- const certs = generateSSLCertificates(projectPath);
412
+ const startOptions = [
413
+ { label: "Start app", value: "start", description: sslSetup ? "HTTPS dev server" : "HTTP dev server" },
414
+ { label: "Start app with Mock API", value: "start-mock", description: "Dev server + Socket.IO + Mock API" },
415
+ { label: "Skip", value: "skip" },
416
+ ];
235
417
 
236
- // Update .env file with actual certificate names if SSL setup was successful
237
- if (certs) {
238
- updateEnvWithCertPaths(projectPath, certs);
239
- }
240
- } else {
241
- console.log(
242
- "\n⚠️ Skipping SSL setup. You can set it up later with: npm run setup-ssl"
243
- );
418
+ const startChoice = await arrowSelectPrompt("How would you like to start the development server?", startOptions);
419
+
420
+ let devProcess = null;
421
+ if (startChoice === "start") {
422
+ // 6. Browser Extension (only if starting)
423
+ console.log("");
424
+ const browserChoice = await arrowSelectPrompt("Launch browser with GxP extension?", [
425
+ { label: "Chrome", value: "chrome", description: "Launch Chrome with DevTools panel" },
426
+ { label: "Firefox", value: "firefox", description: "Launch Firefox with DevTools panel" },
427
+ { label: "Skip", value: "skip" },
428
+ ]);
429
+
430
+ if (browserChoice !== "skip") {
431
+ // Launch browser first (it will connect when server starts)
432
+ setTimeout(() => {
433
+ launchBrowserExtension(projectPath, browserChoice);
434
+ }, 3000); // Wait a bit for server to start
244
435
  }
245
- }
246
436
 
247
- console.log("✅ Project setup complete!");
248
- console.log(
249
- "🎨 GX ComponentKit component library included for rapid kiosk development!"
250
- );
251
- console.log("🗃️ GxP Datastore included with Pinia integration!");
252
- if (!hasPackageJson) {
253
- console.log(`📁 Navigate to your project: cd ${projectName}`);
254
- }
255
- console.log("⚙️ Environment file (.env) ready - customize as needed");
437
+ devProcess = launchDevServer(projectPath, { noHttps: !sslSetup });
438
+ } else if (startChoice === "start-mock") {
439
+ // 6. Browser Extension (only if starting)
440
+ console.log("");
441
+ const browserChoice = await arrowSelectPrompt("Launch browser with GxP extension?", [
442
+ { label: "Chrome", value: "chrome", description: "Launch Chrome with DevTools panel" },
443
+ { label: "Firefox", value: "firefox", description: "Launch Firefox with DevTools panel" },
444
+ { label: "Skip", value: "skip" },
445
+ ]);
256
446
 
257
- if (sslSetup) {
258
- console.log("🔒 Start HTTPS development with Socket.IO: npm run dev");
259
- console.log("🔒 Start HTTPS development only: npm run dev-app");
260
- console.log("🌐 Start HTTP development: npm run dev-http");
447
+ if (browserChoice !== "skip") {
448
+ setTimeout(() => {
449
+ launchBrowserExtension(projectPath, browserChoice);
450
+ }, 3000);
451
+ }
452
+
453
+ devProcess = launchDevServer(projectPath, { withMock: true, noHttps: !sslSetup });
261
454
  } else {
262
- console.log("🌐 Start development: npm run dev-http");
263
- console.log("🔧 Setup SSL certificates: npm run setup-ssl");
264
- console.log("🔒 Then use HTTPS development: npm run dev");
455
+ // Print final instructions
456
+ printFinalInstructions(projectPath, appName, sslSetup);
265
457
  }
458
+
459
+ return devProcess;
460
+ }
461
+
462
+ /**
463
+ * Print final setup instructions
464
+ * @param {string} projectPath - Project path
465
+ * @param {string} projectName - Project name
466
+ * @param {boolean} sslSetup - Whether SSL was set up
467
+ */
468
+ function printFinalInstructions(projectPath, projectName, sslSetup) {
469
+ console.log("");
470
+ console.log("─".repeat(50));
471
+ console.log("✅ Project setup complete!");
472
+ console.log("─".repeat(50));
473
+ console.log("");
474
+ console.log("🎨 GX ComponentKit component library included!");
475
+ console.log("🗃️ GxP Datastore included with Pinia integration!");
476
+ console.log("");
477
+ console.log(`📁 Project location: ${projectPath}`);
266
478
  console.log("");
267
479
  console.log("📖 Project structure:");
268
480
  console.log(" • src/Plugin.vue - Your app entry point (customize this!)");
269
481
  console.log(" • src/DemoPage.vue - Example component");
270
482
  console.log(" • theme-layouts/ - Customizable layout templates");
271
- console.log(
272
- " • main.js - Development entry (loads PortalContainer from toolkit)"
273
- );
274
- console.log("📚 Check README.md for detailed usage instructions");
483
+ console.log(" • app-manifest.json - Plugin configuration");
484
+ console.log("");
485
+ console.log("🚀 To start development:");
486
+ console.log(` cd ${projectName}`);
487
+ if (sslSetup) {
488
+ console.log(" npm run dev # HTTPS with TUI");
489
+ console.log(" npm run dev-http # HTTP only");
490
+ } else {
491
+ console.log(" npm run dev-http # HTTP dev server");
492
+ console.log(" npm run setup-ssl # Then npm run dev for HTTPS");
493
+ }
494
+ console.log("");
495
+ console.log("📚 Documentation: https://docs.gramercytech.com/gxp-toolkit");
496
+ console.log("");
497
+ }
498
+
499
+ /**
500
+ * Initialize command - sets up a new GxP project or updates existing one
501
+ */
502
+ async function initCommand(argv) {
503
+ const currentDir = process.cwd();
504
+ const hasPackageJson = fs.existsSync(path.join(currentDir, "package.json"));
505
+ let projectPath = currentDir;
506
+ let projectName;
507
+
508
+ // Handle existing project update
509
+ if (hasPackageJson && !argv.name) {
510
+ console.log("Updating existing project...");
511
+ updateExistingProject(projectPath);
512
+ console.log("✅ Project updated!");
513
+ return;
514
+ }
275
515
 
276
- // For new projects, offer to launch TUI
277
- if (projectName) {
516
+ // New project - require a name
517
+ if (!argv.name) {
278
518
  console.log("");
279
- const launchChoice = await promptUser(
280
- "Would you like to open the project in the interactive TUI? (Y/n): "
281
- );
282
- const shouldLaunch =
283
- launchChoice.toLowerCase() !== "n" && launchChoice.toLowerCase() !== "no";
284
-
285
- if (shouldLaunch) {
286
- console.log(`\n🚀 Launching gxdev TUI in ${projectName}...`);
287
- // Change to project directory and launch TUI
288
- process.chdir(projectPath);
289
-
290
- // Try to launch TUI
291
- const tuiPath = path.join(
292
- __dirname,
293
- "..",
294
- "..",
295
- "..",
296
- "dist",
297
- "tui",
298
- "index.js"
299
- );
300
- if (fs.existsSync(tuiPath)) {
301
- try {
302
- const { startTUI } = await import(tuiPath);
303
- startTUI({ autoStart: [], args: {} });
304
- } catch (err) {
305
- console.error("Could not launch TUI:", err.message);
306
- console.log(`\nTo start manually:\n cd ${projectName}\n gxdev`);
307
- }
308
- } else {
309
- console.log(
310
- 'TUI not available. Run "npm run build:tui" in gx-devtools first.'
311
- );
312
- console.log(`\nTo start manually:\n cd ${projectName}\n gxdev`);
313
- }
314
- } else {
315
- console.log(`\nTo get started:\n cd ${projectName}\n gxdev`);
519
+ console.log("🚀 GxP Plugin Creator");
520
+ console.log("─".repeat(40));
521
+ console.log("");
522
+ projectName = await promptUser("📝 Project name: ");
523
+ if (!projectName) {
524
+ console.error("❌ Project name is required!");
525
+ process.exit(1);
316
526
  }
527
+ } else {
528
+ projectName = argv.name;
529
+ }
530
+
531
+ // Create project directory
532
+ projectPath = path.join(currentDir, projectName);
533
+ if (fs.existsSync(projectPath)) {
534
+ console.error(`\n❌ Directory ${projectName} already exists!`);
535
+ process.exit(1);
536
+ }
537
+
538
+ console.log("");
539
+ console.log(`📁 Creating project: ${projectName}`);
540
+ console.log("─".repeat(40));
541
+ fs.mkdirSync(projectPath, { recursive: true });
542
+
543
+ // Create package.json
544
+ const initialDescription = argv.description || "A GxP kiosk plugin";
545
+ createPackageJson(projectPath, projectName, initialDescription);
546
+
547
+ // Copy template files
548
+ const paths = resolveGxPaths();
549
+ copyTemplateFiles(projectPath, paths);
550
+ copyExtensionScripts(projectPath, paths);
551
+ createSupportingFiles(projectPath);
552
+
553
+ // Install dependencies
554
+ console.log("");
555
+ installDependencies(projectPath);
556
+
557
+ // Change to project directory
558
+ process.chdir(projectPath);
559
+
560
+ // If CLI provided build prompt, skip interactive and just run AI
561
+ if (argv.build) {
562
+ updateAppManifest(projectPath, projectName, initialDescription);
563
+ const provider = argv.provider || "gemini"; // Default to gemini for backward compatibility
564
+ await runAIScaffolding(projectPath, projectName, initialDescription, argv.build, provider);
565
+ printFinalInstructions(projectPath, projectName, false);
566
+ return;
317
567
  }
568
+
569
+ // Run interactive configuration
570
+ await runInteractiveConfig(projectPath, projectName);
318
571
  }
319
572
 
320
573
  module.exports = {