@gxp-dev/tools 2.0.10 → 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",
@@ -116,6 +79,21 @@ async function initCommand(argv) {
116
79
  dest: "src/DemoPage.vue",
117
80
  desc: "DemoPage.vue (Example component)",
118
81
  },
82
+ {
83
+ src: "default-styling.css",
84
+ dest: "default-styling.css",
85
+ desc: "default-styling.css",
86
+ },
87
+ {
88
+ src: "app-instructions.md",
89
+ dest: "app-instructions.md",
90
+ desc: "app-instructions.md",
91
+ },
92
+ {
93
+ src: "configuration.json",
94
+ dest: "configuration.json",
95
+ desc: "configuration.json",
96
+ },
119
97
  {
120
98
  src: "app-manifest.json",
121
99
  dest: "app-manifest.json",
@@ -128,6 +106,27 @@ async function initCommand(argv) {
128
106
  dest: "README.md",
129
107
  desc: "README.md (Project documentation)",
130
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
+ },
131
130
  ];
132
131
 
133
132
  // Copy template files
@@ -136,12 +135,67 @@ async function initCommand(argv) {
136
135
  const destPath = path.join(projectPath, file.dest);
137
136
  safeCopyFile(srcPath, destPath, file.desc);
138
137
  });
138
+ }
139
139
 
140
- // Create /src/assets/ directory for user assets
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
+ }
160
+
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
141
196
  const assetsDir = path.join(projectPath, "src", "assets");
142
197
  if (!fs.existsSync(assetsDir)) {
143
198
  fs.mkdirSync(assetsDir, { recursive: true });
144
- // Add a .gitkeep to ensure the directory is tracked
145
199
  fs.writeFileSync(path.join(assetsDir, ".gitkeep"), "");
146
200
  console.log("✓ Created src/assets/ directory for project assets");
147
201
  }
@@ -153,153 +207,367 @@ async function initCommand(argv) {
153
207
  fs.copyFileSync(envExamplePath, envPath);
154
208
  console.log("✓ Created .env file from .env.example");
155
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));
156
300
 
157
- // Copy extension management scripts for new projects
158
- if (!hasPackageJson || argv.name) {
159
- const scriptsDir = path.join(projectPath, "scripts");
160
- if (!fs.existsSync(scriptsDir)) {
161
- fs.mkdirSync(scriptsDir, { recursive: true });
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
+ ]);
317
+
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
+ });
162
364
  }
163
365
 
164
- // Copy launch-chrome.js script
165
- const launchChromeSource = path.join(
166
- paths.templateDir,
167
- "../scripts/launch-chrome.js"
168
- );
169
- const launchChromeDest = path.join(scriptsDir, "launch-chrome.js");
170
- if (fs.existsSync(launchChromeSource)) {
171
- safeCopyFile(
172
- launchChromeSource,
173
- launchChromeDest,
174
- "Chrome launcher script"
175
- );
366
+ aiChoice = await arrowSelectPrompt("Choose AI provider for scaffolding", providerOptions);
367
+ if (aiChoice !== "skip") {
368
+ selectedProvider = aiChoice;
176
369
  }
370
+ }
177
371
 
178
- // Copy pack-chrome.js script
179
- const packChromeSource = path.join(
180
- paths.templateDir,
181
- "../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"
182
377
  );
183
- const packChromeDest = path.join(scriptsDir, "pack-chrome.js");
184
- if (fs.existsSync(packChromeSource)) {
185
- safeCopyFile(packChromeSource, packChromeDest, "Chrome packaging script");
378
+
379
+ if (buildPrompt) {
380
+ await runAIScaffolding(projectPath, appName, description, buildPrompt, selectedProvider);
186
381
  }
382
+ }
187
383
 
188
- // Copy socket events directory for simulation (root level for CLI access)
189
- const socketEventsSource = paths.socketEventsDir;
190
- const socketEventsDest = path.join(projectPath, "socket-events");
191
- if (fs.existsSync(socketEventsSource)) {
192
- if (!fs.existsSync(socketEventsDest)) {
193
- fs.mkdirSync(socketEventsDest, { recursive: true });
194
- }
384
+ // 4. SSL Setup
385
+ console.log("");
386
+ console.log("".repeat(50));
387
+ console.log("🔒 SSL Configuration");
388
+ console.log("─".repeat(50));
195
389
 
196
- const eventFiles = fs
197
- .readdirSync(socketEventsSource)
198
- .filter((file) => file.endsWith(".json"));
199
- eventFiles.forEach((file) => {
200
- const srcPath = path.join(socketEventsSource, file);
201
- const destPath = path.join(socketEventsDest, file);
202
- safeCopyFile(srcPath, destPath, `Socket event: ${file}`);
203
- });
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;
204
403
  }
205
404
  }
206
405
 
207
- // Setup SSL certificates for new projects
208
- if (!hasPackageJson || argv.name) {
209
- // Ask user if they want to set up SSL certificates
210
- const sslChoice = await promptUser(
211
- "Do you want to set up SSL certificates for HTTPS development? (Y/n): "
212
- );
213
- sslSetup =
214
- 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));
411
+
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
+ ];
215
417
 
216
- if (sslSetup) {
217
- console.log("\n🔒 Setting up HTTPS development environment...");
218
- ensureMkcertInstalled();
219
- const certs = generateSSLCertificates(projectPath);
418
+ const startChoice = await arrowSelectPrompt("How would you like to start the development server?", startOptions);
220
419
 
221
- // Update .env file with actual certificate names if SSL setup was successful
222
- if (certs) {
223
- updateEnvWithCertPaths(projectPath, certs);
224
- }
225
- } else {
226
- console.log(
227
- "\n⚠️ Skipping SSL setup. You can set it up later with: npm run setup-ssl"
228
- );
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
229
435
  }
230
- }
231
436
 
232
- console.log("✅ Project setup complete!");
233
- console.log(
234
- "🎨 GX ComponentKit component library included for rapid kiosk development!"
235
- );
236
- console.log("🗃️ GxP Datastore included with Pinia integration!");
237
- if (!hasPackageJson) {
238
- console.log(`📁 Navigate to your project: cd ${projectName}`);
239
- }
240
- 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
+ ]);
241
446
 
242
- if (sslSetup) {
243
- console.log("🔒 Start HTTPS development with Socket.IO: npm run dev");
244
- console.log("🔒 Start HTTPS development only: npm run dev-app");
245
- 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 });
246
454
  } else {
247
- console.log("🌐 Start development: npm run dev-http");
248
- console.log("🔧 Setup SSL certificates: npm run setup-ssl");
249
- console.log("🔒 Then use HTTPS development: npm run dev");
455
+ // Print final instructions
456
+ printFinalInstructions(projectPath, appName, sslSetup);
250
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}`);
251
478
  console.log("");
252
479
  console.log("📖 Project structure:");
253
480
  console.log(" • src/Plugin.vue - Your app entry point (customize this!)");
254
481
  console.log(" • src/DemoPage.vue - Example component");
255
482
  console.log(" • theme-layouts/ - Customizable layout templates");
256
- console.log(
257
- " • main.js - Development entry (loads PortalContainer from toolkit)"
258
- );
259
- 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
+ }
260
515
 
261
- // For new projects, offer to launch TUI
262
- if (projectName) {
516
+ // New project - require a name
517
+ if (!argv.name) {
263
518
  console.log("");
264
- const launchChoice = await promptUser(
265
- "Would you like to open the project in the interactive TUI? (Y/n): "
266
- );
267
- const shouldLaunch =
268
- launchChoice.toLowerCase() !== "n" && launchChoice.toLowerCase() !== "no";
269
-
270
- if (shouldLaunch) {
271
- console.log(`\n🚀 Launching gxdev TUI in ${projectName}...`);
272
- // Change to project directory and launch TUI
273
- process.chdir(projectPath);
274
-
275
- // Try to launch TUI
276
- const tuiPath = path.join(
277
- __dirname,
278
- "..",
279
- "..",
280
- "..",
281
- "dist",
282
- "tui",
283
- "index.js"
284
- );
285
- if (fs.existsSync(tuiPath)) {
286
- try {
287
- const { startTUI } = await import(tuiPath);
288
- startTUI({ autoStart: [], args: {} });
289
- } catch (err) {
290
- console.error("Could not launch TUI:", err.message);
291
- console.log(`\nTo start manually:\n cd ${projectName}\n gxdev`);
292
- }
293
- } else {
294
- console.log(
295
- 'TUI not available. Run "npm run build:tui" in gx-devtools first.'
296
- );
297
- console.log(`\nTo start manually:\n cd ${projectName}\n gxdev`);
298
- }
299
- } else {
300
- 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);
301
526
  }
527
+ } else {
528
+ projectName = argv.name;
302
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;
567
+ }
568
+
569
+ // Run interactive configuration
570
+ await runInteractiveConfig(projectPath, projectName);
303
571
  }
304
572
 
305
573
  module.exports = {