@gxp-dev/tools 2.0.72 → 2.0.74
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/bin/gx-devtools.js +71 -99
- package/bin/lib/cli.js +62 -7
- package/bin/lib/commands/init.js +81 -82
- package/bin/lib/utils/ai-scaffold.js +137 -0
- package/mcp/gxp-api-server.js +31 -2
- package/mcp/lib/api-tools.js +87 -0
- package/package.json +1 -1
- package/runtime/stores/gxpPortalConfigStore.js +88 -87
- package/template/.claude/agents/gxp-developer.md +377 -50
- package/template/AGENTS.md +265 -21
- package/template/GEMINI.md +181 -19
package/bin/gx-devtools.js
CHANGED
|
@@ -1,118 +1,90 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* GxToolkit CLI
|
|
4
|
+
* GxToolkit CLI dispatcher.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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
|
-
//
|
|
52
|
-
const
|
|
53
|
-
|
|
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
|
-
|
|
59
|
-
|
|
23
|
+
if (!wantsUi) {
|
|
24
|
+
// Default path — delegate straight to yargs-driven CLI.
|
|
25
|
+
require("./lib/cli")
|
|
26
|
+
return
|
|
27
|
+
}
|
|
60
28
|
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
70
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
88
|
+
process.exit(1)
|
|
114
89
|
}
|
|
115
|
-
}
|
|
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
|
-
.
|
|
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
|
|
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:
|
|
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
|
-
.
|
|
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
|
-
.
|
|
392
|
+
.strict(false)
|
|
393
|
+
|
|
394
|
+
cli.parse()
|
package/bin/lib/commands/init.js
CHANGED
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
* 3. Run interactive configuration:
|
|
10
10
|
* - App name (prepopulated from package.json)
|
|
11
11
|
* - Description (prepopulated from package.json)
|
|
12
|
-
* -
|
|
13
|
-
*
|
|
12
|
+
* - SSL setup
|
|
13
|
+
* - Launch AI agent (2nd-to-last — replaces the start-server step when chosen)
|
|
14
|
+
* 4. Start Development (skipped if an AI agent was launched)
|
|
14
15
|
* 5. Prompt to launch browser with extension
|
|
15
16
|
*/
|
|
16
17
|
|
|
@@ -34,6 +35,7 @@ const {
|
|
|
34
35
|
generateSSLCertificates,
|
|
35
36
|
updateEnvWithCertPaths,
|
|
36
37
|
runAIScaffolding,
|
|
38
|
+
launchInteractiveAISession,
|
|
37
39
|
getAvailableProviders,
|
|
38
40
|
} = require("../utils")
|
|
39
41
|
|
|
@@ -401,65 +403,100 @@ async function runInteractiveConfig(projectPath, initialName, isLocal = false) {
|
|
|
401
403
|
updatePackageJson(projectPath, appName, description)
|
|
402
404
|
updateAppManifest(projectPath, appName, description)
|
|
403
405
|
|
|
404
|
-
// 3.
|
|
406
|
+
// 3. SSL Setup
|
|
405
407
|
console.log("")
|
|
406
408
|
console.log("─".repeat(50))
|
|
407
|
-
console.log("
|
|
409
|
+
console.log("🔒 SSL Configuration")
|
|
408
410
|
console.log("─".repeat(50))
|
|
409
|
-
|
|
410
|
-
|
|
411
|
+
|
|
412
|
+
const sslChoice = await arrowSelectPrompt(
|
|
413
|
+
"Set up SSL certificates for HTTPS development?",
|
|
414
|
+
[
|
|
415
|
+
{
|
|
416
|
+
label: "Yes, set up SSL",
|
|
417
|
+
value: "yes",
|
|
418
|
+
description: "Recommended for full feature access",
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
label: "Skip SSL setup",
|
|
422
|
+
value: "no",
|
|
423
|
+
description: "Can be set up later with npm run setup-ssl",
|
|
424
|
+
},
|
|
425
|
+
],
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
let sslSetup = false
|
|
429
|
+
if (sslChoice === "yes") {
|
|
430
|
+
console.log("\n🔒 Setting up HTTPS development environment...")
|
|
431
|
+
ensureMkcertInstalled()
|
|
432
|
+
const certs = generateSSLCertificates(projectPath)
|
|
433
|
+
if (certs) {
|
|
434
|
+
updateEnvWithCertPaths(projectPath, certs)
|
|
435
|
+
sslSetup = true
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// 4. Build with an AI agent (2nd-to-last step)
|
|
440
|
+
//
|
|
441
|
+
// Launches the selected CLI in interactive mode with an initial prompt
|
|
442
|
+
// that points the agent at AGENTS.md / GEMINI.md in the scaffolded project
|
|
443
|
+
// and instructs it to greet the user, ask clarifying questions until it
|
|
444
|
+
// has enough detail, then plan and implement. When an agent is launched
|
|
445
|
+
// we skip the "Start Development" step — the agent session replaces it.
|
|
446
|
+
console.log("")
|
|
447
|
+
console.log("─".repeat(50))
|
|
448
|
+
console.log("🤖 Build with an AI Agent")
|
|
449
|
+
console.log("─".repeat(50))
|
|
450
|
+
console.log(
|
|
451
|
+
" Launch an AI coding agent that already knows the GxP toolkit,",
|
|
452
|
+
)
|
|
453
|
+
console.log(
|
|
454
|
+
" MCP tools, and workflow. The agent will ask you what you want",
|
|
455
|
+
)
|
|
456
|
+
console.log(" to build and then plan and implement it with you.")
|
|
411
457
|
console.log("")
|
|
412
458
|
|
|
413
459
|
// Check available AI providers
|
|
414
460
|
const providers = await getAvailableProviders()
|
|
415
|
-
const
|
|
461
|
+
const interactiveProviders = providers.filter(
|
|
462
|
+
(p) => p.available && (p.id !== "gemini" || p.method === "cli"),
|
|
463
|
+
)
|
|
416
464
|
|
|
417
465
|
let aiChoice = "skip"
|
|
418
466
|
let selectedProvider = null
|
|
419
467
|
|
|
420
|
-
if (
|
|
421
|
-
console.log(" ⚠️ No AI
|
|
422
|
-
console.log(
|
|
468
|
+
if (interactiveProviders.length === 0) {
|
|
469
|
+
console.log(" ⚠️ No AI CLIs detected.")
|
|
470
|
+
console.log(
|
|
471
|
+
" To use an AI agent here, install one of the following and retry:",
|
|
472
|
+
)
|
|
423
473
|
console.log(
|
|
424
474
|
" • Claude CLI: npm install -g @anthropic-ai/claude-code && claude login",
|
|
425
475
|
)
|
|
426
476
|
console.log(" • Codex CLI: npm install -g @openai/codex && codex auth")
|
|
427
477
|
console.log(" • Gemini CLI: npm install -g @google/gemini-cli && gemini")
|
|
428
|
-
console.log(" • Gemini API: export GEMINI_API_KEY=your_key")
|
|
429
478
|
console.log("")
|
|
430
479
|
aiChoice = "skip"
|
|
431
480
|
} else {
|
|
432
481
|
// Build provider options
|
|
433
|
-
const providerOptions = [
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
case "api_key":
|
|
443
|
-
authInfo = "via API key"
|
|
444
|
-
break
|
|
445
|
-
case "gcloud":
|
|
446
|
-
authInfo = "via gcloud"
|
|
447
|
-
break
|
|
448
|
-
default:
|
|
449
|
-
authInfo = ""
|
|
450
|
-
}
|
|
451
|
-
} else {
|
|
452
|
-
authInfo = "logged in"
|
|
453
|
-
}
|
|
482
|
+
const providerOptions = [
|
|
483
|
+
{
|
|
484
|
+
label: "Skip — I'll build it myself",
|
|
485
|
+
value: "skip",
|
|
486
|
+
description: "You can launch an AI agent later from the project root",
|
|
487
|
+
},
|
|
488
|
+
]
|
|
489
|
+
|
|
490
|
+
for (const provider of interactiveProviders) {
|
|
454
491
|
providerOptions.push({
|
|
455
|
-
label:
|
|
492
|
+
label: provider.name,
|
|
456
493
|
value: provider.id,
|
|
457
|
-
description:
|
|
494
|
+
description: "logged in",
|
|
458
495
|
})
|
|
459
496
|
}
|
|
460
497
|
|
|
461
498
|
aiChoice = await arrowSelectPrompt(
|
|
462
|
-
"
|
|
499
|
+
"Launch an AI agent to build your plugin?",
|
|
463
500
|
providerOptions,
|
|
464
501
|
)
|
|
465
502
|
if (aiChoice !== "skip") {
|
|
@@ -467,55 +504,17 @@ async function runInteractiveConfig(projectPath, initialName, isLocal = false) {
|
|
|
467
504
|
}
|
|
468
505
|
}
|
|
469
506
|
|
|
470
|
-
|
|
507
|
+
// If an AI agent was chosen, it becomes the last step — skip starting the
|
|
508
|
+
// dev server. The agent is responsible for driving the rest of the session.
|
|
471
509
|
if (selectedProvider) {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
510
|
+
await launchInteractiveAISession(
|
|
511
|
+
projectPath,
|
|
512
|
+
appName,
|
|
513
|
+
description,
|
|
514
|
+
selectedProvider,
|
|
475
515
|
)
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
await runAIScaffolding(
|
|
479
|
-
projectPath,
|
|
480
|
-
appName,
|
|
481
|
-
description,
|
|
482
|
-
buildPrompt,
|
|
483
|
-
selectedProvider,
|
|
484
|
-
)
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// 4. SSL Setup
|
|
489
|
-
console.log("")
|
|
490
|
-
console.log("─".repeat(50))
|
|
491
|
-
console.log("🔒 SSL Configuration")
|
|
492
|
-
console.log("─".repeat(50))
|
|
493
|
-
|
|
494
|
-
const sslChoice = await arrowSelectPrompt(
|
|
495
|
-
"Set up SSL certificates for HTTPS development?",
|
|
496
|
-
[
|
|
497
|
-
{
|
|
498
|
-
label: "Yes, set up SSL",
|
|
499
|
-
value: "yes",
|
|
500
|
-
description: "Recommended for full feature access",
|
|
501
|
-
},
|
|
502
|
-
{
|
|
503
|
-
label: "Skip SSL setup",
|
|
504
|
-
value: "no",
|
|
505
|
-
description: "Can be set up later with npm run setup-ssl",
|
|
506
|
-
},
|
|
507
|
-
],
|
|
508
|
-
)
|
|
509
|
-
|
|
510
|
-
let sslSetup = false
|
|
511
|
-
if (sslChoice === "yes") {
|
|
512
|
-
console.log("\n🔒 Setting up HTTPS development environment...")
|
|
513
|
-
ensureMkcertInstalled()
|
|
514
|
-
const certs = generateSSLCertificates(projectPath)
|
|
515
|
-
if (certs) {
|
|
516
|
-
updateEnvWithCertPaths(projectPath, certs)
|
|
517
|
-
sslSetup = true
|
|
518
|
-
}
|
|
516
|
+
printFinalInstructions(projectPath, appName, sslSetup, isLocal)
|
|
517
|
+
return null
|
|
519
518
|
}
|
|
520
519
|
|
|
521
520
|
// 5. Start App
|
|
@@ -953,6 +953,141 @@ async function runAIScaffolding(
|
|
|
953
953
|
return result.errors.length === 0
|
|
954
954
|
}
|
|
955
955
|
|
|
956
|
+
/**
|
|
957
|
+
* Build the initial prompt sent to an interactive AI CLI session.
|
|
958
|
+
* The prompt anchors the agent in the scaffolded project, points it at
|
|
959
|
+
* the project's instruction files (which describe the full tool set),
|
|
960
|
+
* and tells it to keep asking questions until it has enough detail.
|
|
961
|
+
*
|
|
962
|
+
* @param {string} projectName - Plugin name
|
|
963
|
+
* @param {string} description - Plugin description
|
|
964
|
+
* @param {string} provider - Selected AI provider (claude, codex, gemini)
|
|
965
|
+
* @returns {string} Prompt text
|
|
966
|
+
*/
|
|
967
|
+
function buildInteractiveInitialPrompt(projectName, description, provider) {
|
|
968
|
+
const instructionFile = provider === "gemini" ? "GEMINI.md" : "AGENTS.md"
|
|
969
|
+
const claudeAgentHint =
|
|
970
|
+
provider === "claude"
|
|
971
|
+
? " You can also delegate to the `gxp-developer` subagent defined in `.claude/agents/gxp-developer.md`."
|
|
972
|
+
: ""
|
|
973
|
+
|
|
974
|
+
return [
|
|
975
|
+
`I just ran \`gxdev init\` to scaffold a new GxP plugin called "${projectName}"${
|
|
976
|
+
description ? ` (${description})` : ""
|
|
977
|
+
} in this directory.`,
|
|
978
|
+
"",
|
|
979
|
+
`Start a new GxP plugin development session. First read \`${instructionFile}\` and \`app-instructions.md\` in this project — they describe the workflow, conventions, and the full set of tools available to you.${claudeAgentHint}`,
|
|
980
|
+
"",
|
|
981
|
+
"You have the `gxp-api` MCP server available with 29 tools across five families:",
|
|
982
|
+
"- **API spec discovery** — `search_api_endpoints`, `api_list_operation_ids`, `api_get_operation_parameters`, `api_find_endpoints_by_schema`, `api_generate_dependency`, `get_endpoint_details`.",
|
|
983
|
+
"- **WebSocket events** — `api_find_events_for_operation` (maps an operationId to the AsyncAPI events it triggers), `api_list_events`, `search_websocket_events`.",
|
|
984
|
+
"- **Config editing** — `config_add_card`, `config_add_field`, `config_list_field_types`, `config_get_field_schema`, `config_extract_strings`, `config_validate`, etc. Every mutation is linter-guarded against the schemas in `bin/lib/lint/schemas/`.",
|
|
985
|
+
"- **Docs search** — `docs_search`, `docs_get_page`, `docs_list_pages` (full-text search across docs.gxp.dev).",
|
|
986
|
+
"- **Test helpers** — `test_scaffold_component_test`, `test_api_route`.",
|
|
987
|
+
"",
|
|
988
|
+
"Follow the full workflow from the instructions: (1) understand the feature, (2) discover data sources via MCP, (3) plan including the admin configuration form, (4) implement, (5) **sync the manifest and build the admin form**, (6) test with real broadcasts, (7) final `gxdev lint --all`.",
|
|
989
|
+
"",
|
|
990
|
+
"Step 5 is not optional. Every time you add or change a `store.callApi`, `store.listen`, `gxp-string`, or `gxp-src`, close the loop:",
|
|
991
|
+
'- Call `config_extract_strings` with `writeTo: "app-manifest.json"` — it scans `src/` and merges every directive, store getter, and dependency identifier into the manifest (same logic as `gxdev extract-config`, linter-guarded).',
|
|
992
|
+
"- For every entry now in the manifest, add a matching field in `configuration.json` using the MCP `config_*` tools. Default mapping: `strings.default.*` → `text`/`textarea`, `assets.*` → `selectAsset`, each declared `dependencies[]` identifier → `asyncSelect` bound to the resource's list endpoint, colors → `colorPicker`, numbers → `number`, toggles → `boolean`. Field `name` must match the manifest key exactly.",
|
|
993
|
+
"- Run `gxdev lint --all` before moving on.",
|
|
994
|
+
"",
|
|
995
|
+
"Do NOT start implementing until you have enough detail. Keep asking me clarifying questions until you know:",
|
|
996
|
+
"- The user-facing outcome and who uses it (attendee, staff, admin)",
|
|
997
|
+
"- What real-world data it reads/writes — identify the concrete platform operationIds via the MCP, never invent them",
|
|
998
|
+
"- Which real-time events matter (use `api_find_events_for_operation` for each planned operationId)",
|
|
999
|
+
"- Every piece of admin-editable content: strings, assets, colors/thresholds/settings, feature toggles",
|
|
1000
|
+
"",
|
|
1001
|
+
"Then propose a plan — screens/components, data flow, admin configuration form, and the exact keys you'll add to `app-manifest.json` — and get my confirmation before implementing.",
|
|
1002
|
+
"",
|
|
1003
|
+
"Begin now by greeting me briefly and asking what I want to build. Ask one focused question at a time rather than dumping a full questionnaire.",
|
|
1004
|
+
].join("\n")
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
/**
|
|
1008
|
+
* Launch the selected AI CLI in interactive mode with an initial prompt.
|
|
1009
|
+
* The user talks to the agent directly in the terminal; the agent is
|
|
1010
|
+
* responsible for eliciting the feature spec and building it.
|
|
1011
|
+
*
|
|
1012
|
+
* @param {string} projectPath - Project directory (cwd for the spawned CLI)
|
|
1013
|
+
* @param {string} projectName - Plugin name
|
|
1014
|
+
* @param {string} description - Plugin description
|
|
1015
|
+
* @param {string} provider - claude | codex | gemini
|
|
1016
|
+
* @returns {Promise<boolean>} True if the CLI exited successfully
|
|
1017
|
+
*/
|
|
1018
|
+
function launchInteractiveAISession(
|
|
1019
|
+
projectPath,
|
|
1020
|
+
projectName,
|
|
1021
|
+
description,
|
|
1022
|
+
provider,
|
|
1023
|
+
) {
|
|
1024
|
+
return new Promise((resolve) => {
|
|
1025
|
+
const initialPrompt = buildInteractiveInitialPrompt(
|
|
1026
|
+
projectName,
|
|
1027
|
+
description,
|
|
1028
|
+
provider,
|
|
1029
|
+
)
|
|
1030
|
+
|
|
1031
|
+
let command
|
|
1032
|
+
let args
|
|
1033
|
+
|
|
1034
|
+
switch (provider) {
|
|
1035
|
+
case "claude":
|
|
1036
|
+
command = "claude"
|
|
1037
|
+
args = [initialPrompt]
|
|
1038
|
+
break
|
|
1039
|
+
case "codex":
|
|
1040
|
+
command = "codex"
|
|
1041
|
+
args = [initialPrompt]
|
|
1042
|
+
break
|
|
1043
|
+
case "gemini":
|
|
1044
|
+
command = "gemini"
|
|
1045
|
+
args = ["-i", initialPrompt]
|
|
1046
|
+
break
|
|
1047
|
+
default:
|
|
1048
|
+
console.error(
|
|
1049
|
+
`❌ Interactive AI sessions are not supported for provider: ${provider}`,
|
|
1050
|
+
)
|
|
1051
|
+
resolve(false)
|
|
1052
|
+
return
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
console.log("")
|
|
1056
|
+
console.log("─".repeat(50))
|
|
1057
|
+
console.log(`🚀 Launching ${provider} in interactive mode...`)
|
|
1058
|
+
console.log("─".repeat(50))
|
|
1059
|
+
console.log("")
|
|
1060
|
+
console.log(
|
|
1061
|
+
" The agent will read this project's instruction files, greet you,",
|
|
1062
|
+
)
|
|
1063
|
+
console.log(
|
|
1064
|
+
" and ask what you want to build. Answer its questions — it will",
|
|
1065
|
+
)
|
|
1066
|
+
console.log(
|
|
1067
|
+
" keep asking until it has enough detail, then plan, confirm, and",
|
|
1068
|
+
)
|
|
1069
|
+
console.log(" implement. Exit the agent when you're done.")
|
|
1070
|
+
console.log("")
|
|
1071
|
+
|
|
1072
|
+
const child = spawn(command, args, {
|
|
1073
|
+
cwd: projectPath,
|
|
1074
|
+
stdio: "inherit",
|
|
1075
|
+
shell: true,
|
|
1076
|
+
})
|
|
1077
|
+
|
|
1078
|
+
child.on("close", (code) => {
|
|
1079
|
+
console.log("")
|
|
1080
|
+
console.log(`✅ ${provider} session ended.`)
|
|
1081
|
+
resolve(code === 0)
|
|
1082
|
+
})
|
|
1083
|
+
|
|
1084
|
+
child.on("error", (err) => {
|
|
1085
|
+
console.error(`❌ Failed to launch ${provider}: ${err.message}`)
|
|
1086
|
+
resolve(false)
|
|
1087
|
+
})
|
|
1088
|
+
})
|
|
1089
|
+
}
|
|
1090
|
+
|
|
956
1091
|
module.exports = {
|
|
957
1092
|
SCAFFOLD_SYSTEM_PROMPT,
|
|
958
1093
|
AI_PROVIDERS,
|
|
@@ -961,4 +1096,6 @@ module.exports = {
|
|
|
961
1096
|
applyScaffold,
|
|
962
1097
|
generateScaffold,
|
|
963
1098
|
runAIScaffolding,
|
|
1099
|
+
buildInteractiveInitialPrompt,
|
|
1100
|
+
launchInteractiveAISession,
|
|
964
1101
|
}
|