@gxp-dev/tools 2.0.73 → 2.0.75

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,118 +1,90 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * GxToolkit CLI
4
+ * GxToolkit CLI dispatcher.
5
5
  *
6
- * This tool works both as a globally installed npm package and as a local dependency.
7
- * It provides commands for creating new GxP projects and managing development workflows.
6
+ * Default mode is the plain CLI. The interactive Terminal UI (TUI) only
7
+ * launches when the user either runs `gxdev ui` or passes `--ui` alongside
8
+ * another command. Running `gxdev` with no command prints the help menu.
8
9
  *
9
- * Commands:
10
- * (no command) - Launch interactive TUI (Terminal UI)
11
- * init [name] - Initialize a new GxP project or update existing one
12
- * setup-ssl - Setup SSL certificates for HTTPS development
13
- * dev - Start development server (launches TUI with auto-start)
14
- * build - Build plugin for production
15
- * publish [file] - Publish package files to local project
16
- * datastore - Manage GxP datastore
17
- * socket - Simulate socket events (launches TUI with auto-start)
18
- * assets - Manage development assets and placeholders
19
- * ext:firefox - Launch Firefox with browser extension (launches TUI)
20
- * ext:chrome - Launch Chrome with browser extension (launches TUI)
21
- * ext:build - Build browser extensions for distribution
10
+ * Launching into the TUI still supports auto-starting well-known long-running
11
+ * commands (dev, socket, ext:chrome, ext:firefox) so that `gxdev dev --ui`
12
+ * boots the TUI with the dev server already running.
22
13
  */
23
14
 
24
- // Commands that should use the traditional CLI (one-shot commands)
25
- const ONE_SHOT_COMMANDS = [
26
- "init",
27
- "build",
28
- "publish",
29
- "setup-ssl",
30
- "ext:build",
31
- "add-dependency",
32
- "extract-config",
33
- "--help",
34
- "-h",
35
- "--version",
36
- ]
37
-
38
- // Commands that should launch TUI with auto-start
39
- const TUI_AUTO_START_COMMANDS = [
40
- "dev",
41
- "socket",
42
- "ext:firefox",
43
- "ext:chrome",
44
- "datastore",
45
- "assets",
46
- ]
47
-
48
15
  const args = process.argv.slice(2)
49
16
  const command = args[0]
50
17
 
51
- // Check if this is a one-shot command
52
- const isOneShot =
53
- ONE_SHOT_COMMANDS.includes(command) || (command && command.startsWith("-"))
54
-
55
- // Check if we should use TUI with auto-start
56
- const isTuiCommand = TUI_AUTO_START_COMMANDS.includes(command)
18
+ // TUI activation: bare `ui` command OR any invocation with `--ui`.
19
+ const uiFlagPresent = args.includes("--ui")
20
+ const isBareUiCommand = command === "ui"
21
+ const wantsUi = uiFlagPresent || isBareUiCommand
57
22
 
58
- // --cli flag forces non-TUI mode regardless of TTY
59
- const forceCliMode = args.includes("--cli")
23
+ if (!wantsUi) {
24
+ // Default path — delegate straight to yargs-driven CLI.
25
+ require("./lib/cli")
26
+ return
27
+ }
60
28
 
61
- // If no command or TUI command, try to launch TUI
62
- // Fall back to traditional CLI if TUI dependencies are not available
63
- if (!isOneShot) {
64
- const fs = require("fs")
65
- const path = require("path")
66
- // TUI output is in project root's dist/tui, not bin/dist/tui
67
- const tuiPath = path.join(__dirname, "..", "dist", "tui", "index.js")
29
+ // TUI path locate the compiled TUI bundle and hand off.
30
+ const fs = require("fs")
31
+ const path = require("path")
32
+ const tuiPath = path.join(__dirname, "..", "dist", "tui", "index.js")
68
33
 
69
- // Check if we're in an interactive terminal (TTY); fall back to CLI if not
70
- const isTTY = process.stdout.isTTY && process.stdin.isTTY
34
+ if (!fs.existsSync(tuiPath)) {
35
+ console.error("TUI bundle not found.")
36
+ console.error('Run "npm run build:tui" in gx-devtools to compile it.')
37
+ process.exit(1)
38
+ }
71
39
 
72
- if (fs.existsSync(tuiPath) && isTTY && !forceCliMode) {
73
- // Use dynamic import() for ESM modules (ink v5 is ESM-only)
74
- ;(async () => {
75
- try {
76
- const { startTUI } = await import(tuiPath)
40
+ const isTTY = process.stdout.isTTY && process.stdin.isTTY
41
+ if (!isTTY) {
42
+ console.error(
43
+ "The GxP TUI requires an interactive terminal (TTY). Run the plain CLI form without --ui.",
44
+ )
45
+ process.exit(1)
46
+ }
77
47
 
78
- // Determine auto-start commands
79
- const autoStart = []
80
- const tuiArgs = {}
48
+ // Work out which TUI services to auto-start from the command that was paired
49
+ // with --ui. `gxdev ui` on its own launches the TUI idle.
50
+ const autoStart = []
51
+ const tuiArgs = {}
81
52
 
82
- if (command === "dev") {
83
- autoStart.push("dev")
84
- if (args.includes("--with-socket") || args.includes("-s")) {
85
- autoStart.push("socket")
86
- }
87
- tuiArgs.noHttps = args.includes("--no-https")
88
- } else if (command === "socket") {
89
- autoStart.push("socket")
90
- } else if (command === "ext:firefox") {
91
- autoStart.push("ext firefox")
92
- } else if (command === "ext:chrome") {
93
- autoStart.push("ext chrome")
94
- }
53
+ switch (isBareUiCommand ? null : command) {
54
+ case "dev":
55
+ autoStart.push("dev")
56
+ if (args.includes("--with-socket") || args.includes("-s")) {
57
+ autoStart.push("socket")
58
+ }
59
+ tuiArgs.noHttps = args.includes("--no-https")
60
+ break
61
+ case "socket":
62
+ autoStart.push("socket")
63
+ break
64
+ case "ext:firefox":
65
+ autoStart.push("ext firefox")
66
+ break
67
+ case "ext:chrome":
68
+ autoStart.push("ext chrome")
69
+ break
70
+ default:
71
+ // No known auto-start for this command (or no command) — launch idle.
72
+ break
73
+ }
95
74
 
96
- startTUI({ autoStart, args: tuiArgs })
97
- } catch (err) {
98
- // TUI not available or no TTY — fall back to traditional CLI
99
- if (err.message !== "NO_TTY") {
100
- console.error("TUI error:", err.message)
101
- }
102
- require("./lib/cli")
103
- }
104
- })()
105
- } else if (!isTTY || forceCliMode) {
106
- // Non-interactive shell or --cli flag — skip TUI and run directly
107
- require("./lib/cli")
108
- } else {
109
- // TUI not compiled yet, use traditional CLI
110
- console.log(
111
- 'Note: TUI not yet available. Run "npm run build:tui" to enable interactive mode.',
75
+ ;(async () => {
76
+ try {
77
+ const { startTUI } = await import(tuiPath)
78
+ startTUI({ autoStart, args: tuiArgs })
79
+ } catch (err) {
80
+ if (err && err.message === "NO_TTY") {
81
+ console.error("The GxP TUI requires an interactive terminal (TTY).")
82
+ process.exit(1)
83
+ }
84
+ console.error(
85
+ "Failed to start TUI:",
86
+ err && err.message ? err.message : err,
112
87
  )
113
- require("./lib/cli")
88
+ process.exit(1)
114
89
  }
115
- } else {
116
- // One-shot command, use traditional CLI
117
- require("./lib/cli")
118
- }
90
+ })()
package/bin/lib/cli.js CHANGED
@@ -31,12 +31,42 @@ const {
31
31
  const globalConfig = loadGlobalConfig()
32
32
 
33
33
  // Set up yargs CLI
34
- yargs
35
- .usage("$0 <command>")
34
+ const cli = yargs
35
+ .scriptName("gxdev")
36
+ .usage(
37
+ [
38
+ "Usage: gxdev <command> [options]",
39
+ "",
40
+ "By default every command runs in the plain CLI. Pass --ui to any",
41
+ "command to launch it inside the interactive Terminal UI, or run",
42
+ "`gxdev ui` to open the TUI without starting anything.",
43
+ ].join("\n"),
44
+ )
36
45
  .config(globalConfig)
46
+ .option("ui", {
47
+ describe:
48
+ "Launch the interactive Terminal UI (auto-starts dev/socket/ext when paired with those commands)",
49
+ type: "boolean",
50
+ default: false,
51
+ global: true,
52
+ })
53
+ .command(
54
+ "ui",
55
+ "Open the interactive Terminal UI without auto-starting anything",
56
+ {},
57
+ () => {
58
+ // The dispatcher in bin/gx-devtools.js intercepts `ui` before yargs
59
+ // ever runs. Reaching this handler means the TUI bundle couldn't be
60
+ // launched (e.g. not built); bubble a helpful hint.
61
+ console.error(
62
+ 'TUI is not available. Run "npm run build:tui" inside gx-devtools and try again.',
63
+ )
64
+ process.exit(1)
65
+ },
66
+ )
37
67
  .command(
38
68
  "init [name]",
39
- "Initialize a new GxP project or update existing one",
69
+ "Initialize a new GxP project or update an existing one in the current directory",
40
70
  {
41
71
  name: {
42
72
  describe: "Project name (for new projects)",
@@ -49,12 +79,13 @@ yargs
49
79
  },
50
80
  build: {
51
81
  describe:
52
- "AI build prompt - describe what to build for auto-scaffolding",
82
+ "Non-interactive AI scaffold: describe what to build and apply it directly",
53
83
  type: "string",
54
84
  alias: "b",
55
85
  },
56
86
  provider: {
57
- describe: "AI provider for scaffolding (claude, codex, gemini)",
87
+ describe:
88
+ "AI provider for --build scaffolding (interactive mode picks from available CLIs)",
58
89
  type: "string",
59
90
  alias: "p",
60
91
  choices: ["claude", "codex", "gemini"],
@@ -333,7 +364,31 @@ yargs
333
364
  },
334
365
  addDependencyCommand,
335
366
  )
336
- .demandCommand(1, "Please provide a valid command")
367
+ .command("$0", false, {}, () => {
368
+ cli.showHelp()
369
+ })
370
+ .example("gxdev init my-plugin", "Scaffold a new plugin called my-plugin")
371
+ .example(
372
+ "gxdev init",
373
+ "Add the toolkit to the current project (detects existing package.json)",
374
+ )
375
+ .example("gxdev dev", "Start Vite + Socket.IO in plain CLI mode")
376
+ .example("gxdev dev --ui", "Start the dev server inside the interactive TUI")
377
+ .example("gxdev ui", "Open the TUI without auto-starting anything")
378
+ .example("gxdev lint --all", "Lint configuration.json and app-manifest.json")
379
+ .example(
380
+ "gxdev extract-config",
381
+ "Sync app-manifest.json from gxp-string / gxp-src / store usage in src/",
382
+ )
383
+ .epilog(
384
+ [
385
+ "Docs: https://docs.gxp.dev",
386
+ "AI/MCP: the gxp-api MCP server (bin: gxp-api-server) exposes 29 tools",
387
+ " across API specs, config editing, docs search, and test helpers.",
388
+ ].join("\n"),
389
+ )
337
390
  .help("h")
338
391
  .alias("h", "help")
339
- .parse()
392
+ .strict(false)
393
+
394
+ cli.parse()
@@ -506,14 +506,19 @@ async function runInteractiveConfig(projectPath, initialName, isLocal = false) {
506
506
 
507
507
  // If an AI agent was chosen, it becomes the last step — skip starting the
508
508
  // dev server. The agent is responsible for driving the rest of the session.
509
+ //
510
+ // Print the project-ready summary BEFORE we hand stdin/stdout to the agent
511
+ // so the user sees "setup complete, here's how to start the dev server"
512
+ // up front. Once the agent owns the terminal the init script is done — it
513
+ // won't print anything else on the way out.
509
514
  if (selectedProvider) {
515
+ printFinalInstructions(projectPath, appName, sslSetup, isLocal)
510
516
  await launchInteractiveAISession(
511
517
  projectPath,
512
518
  appName,
513
519
  description,
514
520
  selectedProvider,
515
521
  )
516
- printFinalInstructions(projectPath, appName, sslSetup, isLocal)
517
522
  return null
518
523
  }
519
524
 
@@ -1069,10 +1069,13 @@ function launchInteractiveAISession(
1069
1069
  console.log(" implement. Exit the agent when you're done.")
1070
1070
  console.log("")
1071
1071
 
1072
+ // Do NOT pass shell: true here. The initial prompt contains backticks,
1073
+ // parentheses, and quotes; going through /bin/sh -c would require
1074
+ // shell-escaping every special character. Without shell: true, spawn
1075
+ // execs the binary directly and the argv is delivered as-is.
1072
1076
  const child = spawn(command, args, {
1073
1077
  cwd: projectPath,
1074
1078
  stdio: "inherit",
1075
- shell: true,
1076
1079
  })
1077
1080
 
1078
1081
  child.on("close", (code) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gxp-dev/tools",
3
- "version": "2.0.73",
3
+ "version": "2.0.75",
4
4
  "description": "Dev tools to create platform plugins",
5
5
  "type": "commonjs",
6
6
  "publishConfig": {