@gxp-dev/tools 2.0.62 → 2.0.64
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/README.md +32 -31
- package/bin/gx-devtools.js +74 -54
- package/bin/lib/cli.js +23 -21
- package/bin/lib/commands/add-dependency.js +366 -325
- package/bin/lib/commands/assets.js +137 -139
- package/bin/lib/commands/build.js +169 -174
- package/bin/lib/commands/datastore.js +181 -183
- package/bin/lib/commands/dev.js +127 -131
- package/bin/lib/commands/extensions.js +147 -149
- package/bin/lib/commands/extract-config.js +73 -67
- package/bin/lib/commands/index.js +12 -12
- package/bin/lib/commands/init.js +342 -240
- package/bin/lib/commands/publish.js +69 -75
- package/bin/lib/commands/socket.js +69 -69
- package/bin/lib/commands/ssl.js +14 -14
- package/bin/lib/constants.js +10 -24
- package/bin/lib/tui/App.tsx +761 -705
- package/bin/lib/tui/components/AIPanel.tsx +191 -171
- package/bin/lib/tui/components/CommandInput.tsx +394 -343
- package/bin/lib/tui/components/GeminiPanel.tsx +175 -151
- package/bin/lib/tui/components/Header.tsx +23 -21
- package/bin/lib/tui/components/LogPanel.tsx +244 -220
- package/bin/lib/tui/components/TabBar.tsx +50 -48
- package/bin/lib/tui/components/WelcomeScreen.tsx +126 -71
- package/bin/lib/tui/index.tsx +37 -39
- package/bin/lib/tui/services/AIService.ts +518 -462
- package/bin/lib/tui/services/ExtensionService.ts +140 -129
- package/bin/lib/tui/services/GeminiService.ts +367 -337
- package/bin/lib/tui/services/ServiceManager.ts +344 -322
- package/bin/lib/tui/services/SocketService.ts +168 -168
- package/bin/lib/tui/services/ViteService.ts +88 -88
- package/bin/lib/tui/services/index.ts +47 -22
- package/bin/lib/utils/ai-scaffold.js +291 -280
- package/bin/lib/utils/extract-config.js +157 -140
- package/bin/lib/utils/files.js +82 -86
- package/bin/lib/utils/index.js +7 -7
- package/bin/lib/utils/paths.js +34 -34
- package/bin/lib/utils/prompts.js +194 -169
- package/bin/lib/utils/ssl.js +79 -81
- package/browser-extensions/README.md +0 -1
- package/browser-extensions/chrome/background.js +244 -237
- package/browser-extensions/chrome/content.js +32 -29
- package/browser-extensions/chrome/devtools.html +7 -7
- package/browser-extensions/chrome/devtools.js +19 -19
- package/browser-extensions/chrome/inspector.js +802 -767
- package/browser-extensions/chrome/manifest.json +71 -63
- package/browser-extensions/chrome/panel.html +674 -636
- package/browser-extensions/chrome/panel.js +722 -712
- package/browser-extensions/chrome/popup.html +586 -543
- package/browser-extensions/chrome/popup.js +282 -244
- package/browser-extensions/chrome/rules.json +1 -1
- package/browser-extensions/chrome/test-chrome.html +216 -136
- package/browser-extensions/chrome/test-mixed-content.html +284 -189
- package/browser-extensions/chrome/test-uri-pattern.html +221 -198
- package/browser-extensions/firefox/README.md +9 -6
- package/browser-extensions/firefox/background.js +221 -218
- package/browser-extensions/firefox/content.js +55 -52
- package/browser-extensions/firefox/debug-errors.html +386 -228
- package/browser-extensions/firefox/debug-https.html +153 -105
- package/browser-extensions/firefox/devtools.html +7 -7
- package/browser-extensions/firefox/devtools.js +23 -20
- package/browser-extensions/firefox/inspector.js +802 -767
- package/browser-extensions/firefox/manifest.json +68 -68
- package/browser-extensions/firefox/panel.html +674 -636
- package/browser-extensions/firefox/panel.js +722 -712
- package/browser-extensions/firefox/popup.html +572 -535
- package/browser-extensions/firefox/popup.js +281 -236
- package/browser-extensions/firefox/test-gramercy.html +170 -125
- package/browser-extensions/firefox/test-imports.html +59 -55
- package/browser-extensions/firefox/test-masking.html +231 -140
- package/browser-extensions/firefox/test-uri-pattern.html +221 -198
- package/dist/tui/App.d.ts +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +154 -150
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/components/AIPanel.d.ts.map +1 -1
- package/dist/tui/components/AIPanel.js +42 -35
- package/dist/tui/components/AIPanel.js.map +1 -1
- package/dist/tui/components/CommandInput.d.ts +1 -1
- package/dist/tui/components/CommandInput.d.ts.map +1 -1
- package/dist/tui/components/CommandInput.js +92 -62
- package/dist/tui/components/CommandInput.js.map +1 -1
- package/dist/tui/components/GeminiPanel.d.ts.map +1 -1
- package/dist/tui/components/GeminiPanel.js +37 -30
- package/dist/tui/components/GeminiPanel.js.map +1 -1
- package/dist/tui/components/Header.d.ts.map +1 -1
- package/dist/tui/components/Header.js +1 -1
- package/dist/tui/components/Header.js.map +1 -1
- package/dist/tui/components/LogPanel.d.ts +1 -1
- package/dist/tui/components/LogPanel.d.ts.map +1 -1
- package/dist/tui/components/LogPanel.js +26 -24
- package/dist/tui/components/LogPanel.js.map +1 -1
- package/dist/tui/components/TabBar.d.ts +2 -2
- package/dist/tui/components/TabBar.d.ts.map +1 -1
- package/dist/tui/components/TabBar.js +11 -11
- package/dist/tui/components/TabBar.js.map +1 -1
- package/dist/tui/components/WelcomeScreen.d.ts.map +1 -1
- package/dist/tui/components/WelcomeScreen.js +6 -6
- package/dist/tui/components/WelcomeScreen.js.map +1 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +8 -8
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/services/AIService.d.ts +2 -2
- package/dist/tui/services/AIService.d.ts.map +1 -1
- package/dist/tui/services/AIService.js +165 -125
- package/dist/tui/services/AIService.js.map +1 -1
- package/dist/tui/services/ExtensionService.d.ts +1 -1
- package/dist/tui/services/ExtensionService.d.ts.map +1 -1
- package/dist/tui/services/ExtensionService.js +33 -26
- package/dist/tui/services/ExtensionService.js.map +1 -1
- package/dist/tui/services/GeminiService.d.ts +1 -1
- package/dist/tui/services/GeminiService.d.ts.map +1 -1
- package/dist/tui/services/GeminiService.js +87 -76
- package/dist/tui/services/GeminiService.js.map +1 -1
- package/dist/tui/services/ServiceManager.d.ts +3 -3
- package/dist/tui/services/ServiceManager.d.ts.map +1 -1
- package/dist/tui/services/ServiceManager.js +72 -58
- package/dist/tui/services/ServiceManager.js.map +1 -1
- package/dist/tui/services/SocketService.d.ts.map +1 -1
- package/dist/tui/services/SocketService.js +32 -32
- package/dist/tui/services/SocketService.js.map +1 -1
- package/dist/tui/services/ViteService.d.ts.map +1 -1
- package/dist/tui/services/ViteService.js +26 -28
- package/dist/tui/services/ViteService.js.map +1 -1
- package/dist/tui/services/index.d.ts +6 -6
- package/dist/tui/services/index.d.ts.map +1 -1
- package/dist/tui/services/index.js +6 -6
- package/dist/tui/services/index.js.map +1 -1
- package/mcp/gxp-api-server.js +83 -81
- package/package.json +109 -93
- package/runtime/PortalContainer.vue +258 -234
- package/runtime/dev-tools/DevToolsModal.vue +153 -155
- package/runtime/dev-tools/LayoutSwitcher.vue +144 -140
- package/runtime/dev-tools/MockDataEditor.vue +456 -433
- package/runtime/dev-tools/SocketSimulator.vue +379 -371
- package/runtime/dev-tools/StoreInspector.vue +517 -455
- package/runtime/dev-tools/index.js +5 -5
- package/runtime/fallback-layouts/PrivateLayout.vue +2 -2
- package/runtime/fallback-layouts/PublicLayout.vue +2 -2
- package/runtime/fallback-layouts/SystemLayout.vue +2 -2
- package/runtime/gxpStringsPlugin.js +159 -134
- package/runtime/index.html +17 -19
- package/runtime/main.js +24 -22
- package/runtime/mock-api/auth-middleware.js +15 -15
- package/runtime/mock-api/image-generator.js +46 -46
- package/runtime/mock-api/index.js +55 -55
- package/runtime/mock-api/response-generator.js +116 -105
- package/runtime/mock-api/route-generator.js +107 -84
- package/runtime/mock-api/socket-triggers.js +94 -93
- package/runtime/mock-api/spec-loader.js +79 -80
- package/runtime/package.json +3 -0
- package/runtime/server.js +68 -68
- package/runtime/stores/gxpPortalConfigStore.js +204 -186
- package/runtime/stores/index.js +2 -2
- package/runtime/vite-inspector-plugin.js +858 -707
- package/runtime/vite-source-tracker-plugin.js +132 -113
- package/runtime/vite.config.js +207 -132
- package/scripts/launch-chrome.js +41 -41
- package/scripts/pack-chrome.js +38 -39
- package/socket-events/AiSessionMessageCreated.json +17 -17
- package/socket-events/SocialStreamPostCreated.json +23 -23
- package/socket-events/SocialStreamPostVariantCompleted.json +22 -22
- package/template/.claude/agents/gxp-developer.md +100 -99
- package/template/.claude/settings.json +7 -7
- package/template/AGENTS.md +30 -23
- package/template/GEMINI.md +20 -20
- package/template/README.md +70 -53
- package/template/app-manifest.json +2 -4
- package/template/configuration.json +10 -10
- package/template/default-styling.css +1 -1
- package/template/index.html +18 -20
- package/template/main.js +24 -22
- package/template/src/DemoPage.vue +415 -362
- package/template/src/Plugin.vue +76 -85
- package/template/src/stores/index.js +3 -3
- package/template/src/stores/test-data.json +164 -172
- package/template/theme-layouts/AdditionalStyling.css +50 -50
- package/template/theme-layouts/PrivateLayout.vue +8 -12
- package/template/theme-layouts/PublicLayout.vue +8 -12
- package/template/theme-layouts/SystemLayout.vue +8 -12
- package/template/vite.extend.js +45 -0
- package/template/vite.config.js +0 -409
|
@@ -1,354 +1,405 @@
|
|
|
1
|
-
import React, { useState, useMemo, useEffect, useCallback, useRef } from
|
|
2
|
-
import { Box, Text, useInput } from
|
|
3
|
-
import TextInput from
|
|
1
|
+
import React, { useState, useMemo, useEffect, useCallback, useRef } from "react"
|
|
2
|
+
import { Box, Text, useInput } from "ink"
|
|
3
|
+
import TextInput from "ink-text-input"
|
|
4
4
|
|
|
5
5
|
// Command definitions with descriptions - comprehensive list
|
|
6
6
|
const COMMANDS = [
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
7
|
+
// Dev server commands
|
|
8
|
+
{ cmd: "/dev", args: "", desc: "Start Vite + Socket.IO servers" },
|
|
9
|
+
{ cmd: "/dev", args: "--with-mock", desc: "Start with Mock API enabled" },
|
|
10
|
+
{ cmd: "/dev", args: "--no-https", desc: "Start without SSL" },
|
|
11
|
+
{ cmd: "/dev", args: "--no-socket", desc: "Start without Socket.IO" },
|
|
12
|
+
{ cmd: "/dev", args: "--chrome", desc: "Start + launch Chrome extension" },
|
|
13
|
+
{ cmd: "/dev", args: "--firefox", desc: "Start + launch Firefox extension" },
|
|
14
|
+
|
|
15
|
+
// Socket commands
|
|
16
|
+
{ cmd: "/socket", args: "", desc: "Start Socket.IO server" },
|
|
17
|
+
{ cmd: "/socket", args: "--with-mock", desc: "Start Socket.IO + Mock API" },
|
|
18
|
+
{ cmd: "/socket", args: "send <event>", desc: "Send a socket event" },
|
|
19
|
+
{ cmd: "/socket", args: "list", desc: "List available socket events" },
|
|
20
|
+
{ cmd: "/mock", args: "", desc: "Start Socket.IO + Mock API (shorthand)" },
|
|
21
|
+
|
|
22
|
+
// Browser extension commands
|
|
23
|
+
{ cmd: "/ext", args: "chrome", desc: "Launch Chrome with extension" },
|
|
24
|
+
{ cmd: "/ext", args: "firefox", desc: "Launch Firefox with extension" },
|
|
25
|
+
|
|
26
|
+
// Service management
|
|
27
|
+
{
|
|
28
|
+
cmd: "/stop",
|
|
29
|
+
args: "[service]",
|
|
30
|
+
desc: "Stop current or specified service",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
cmd: "/restart",
|
|
34
|
+
args: "[service]",
|
|
35
|
+
desc: "Restart current or specified service",
|
|
36
|
+
},
|
|
37
|
+
{ cmd: "/clear", args: "", desc: "Clear current log panel" },
|
|
38
|
+
|
|
39
|
+
// Config extraction
|
|
40
|
+
{ cmd: "/extract-config", args: "", desc: "Extract GxP config from source" },
|
|
41
|
+
{
|
|
42
|
+
cmd: "/extract-config",
|
|
43
|
+
args: "--dry-run",
|
|
44
|
+
desc: "Preview config extraction",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
cmd: "/extract-config",
|
|
48
|
+
args: "--overwrite",
|
|
49
|
+
desc: "Overwrite existing config values",
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// Dependency management
|
|
53
|
+
{
|
|
54
|
+
cmd: "/add-dependency",
|
|
55
|
+
args: "",
|
|
56
|
+
desc: "Add API dependency wizard (develop)",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
cmd: "/add-dependency",
|
|
60
|
+
args: "--env local",
|
|
61
|
+
desc: "Add dependency from local API",
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
// AI commands
|
|
65
|
+
{ cmd: "/ai", args: "", desc: "Open AI chat with current provider" },
|
|
66
|
+
{ cmd: "/ai", args: "model", desc: "Show available AI providers" },
|
|
67
|
+
{ cmd: "/ai", args: "model claude", desc: "Switch to Claude AI" },
|
|
68
|
+
{ cmd: "/ai", args: "model codex", desc: "Switch to Codex AI" },
|
|
69
|
+
{ cmd: "/ai", args: "model gemini", desc: "Switch to Gemini AI" },
|
|
70
|
+
{ cmd: "/ai", args: "ask <query>", desc: "Quick AI question" },
|
|
71
|
+
{ cmd: "/ai", args: "status", desc: "Check provider availability" },
|
|
72
|
+
{ cmd: "/ai", args: "clear", desc: "Clear conversation history" },
|
|
73
|
+
|
|
74
|
+
// General
|
|
75
|
+
{ cmd: "/help", args: "", desc: "Show all commands" },
|
|
76
|
+
{ cmd: "/quit", args: "", desc: "Exit application" },
|
|
77
|
+
{ cmd: "/exit", args: "", desc: "Exit application (alias)" },
|
|
78
|
+
]
|
|
55
79
|
|
|
56
80
|
interface ActiveService {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
81
|
+
id: string
|
|
82
|
+
name: string
|
|
83
|
+
status: string
|
|
60
84
|
}
|
|
61
85
|
|
|
62
86
|
interface CommandInputProps {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
87
|
+
onSubmit: (command: string) => void
|
|
88
|
+
activeService?: ActiveService | null
|
|
89
|
+
onSuggestionsChange?: (count: number) => void
|
|
66
90
|
}
|
|
67
91
|
|
|
68
|
-
export default function CommandInput({
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
92
|
+
export default function CommandInput({
|
|
93
|
+
onSubmit,
|
|
94
|
+
activeService,
|
|
95
|
+
onSuggestionsChange,
|
|
96
|
+
}: CommandInputProps) {
|
|
97
|
+
const [value, setValue] = useState("")
|
|
98
|
+
const [filterValue, setFilterValue] = useState("") // What we filter suggestions by (doesn't change when arrowing)
|
|
99
|
+
const [history, setHistory] = useState<string[]>([])
|
|
100
|
+
const [historyIndex, setHistoryIndex] = useState(-1)
|
|
101
|
+
const [selectedSuggestion, setSelectedSuggestion] = useState(0)
|
|
102
|
+
const [isNavigating, setIsNavigating] = useState(false) // True when user is arrowing through suggestions
|
|
103
|
+
|
|
104
|
+
// Track if suggestions are currently shown to avoid flicker
|
|
105
|
+
const prevShowSuggestions = useRef(false)
|
|
106
|
+
|
|
107
|
+
// Maximum visible suggestions in the dropdown
|
|
108
|
+
const MAX_VISIBLE = 8
|
|
109
|
+
|
|
110
|
+
// Filter commands based on filterValue (stays stable while navigating with arrows)
|
|
111
|
+
const suggestions = useMemo(() => {
|
|
112
|
+
if (!filterValue.startsWith("/")) return []
|
|
113
|
+
|
|
114
|
+
const search = filterValue.toLowerCase()
|
|
115
|
+
return COMMANDS.filter((c) => {
|
|
116
|
+
const fullCmd = c.args ? `${c.cmd} ${c.args}` : c.cmd
|
|
117
|
+
return (
|
|
118
|
+
fullCmd.toLowerCase().includes(search) ||
|
|
119
|
+
c.cmd.toLowerCase().startsWith(search)
|
|
120
|
+
)
|
|
121
|
+
})
|
|
122
|
+
}, [filterValue])
|
|
123
|
+
|
|
124
|
+
// Calculate visible window of suggestions (scrolls to keep selection visible)
|
|
125
|
+
const { visibleSuggestions, startIndex } = useMemo(() => {
|
|
126
|
+
if (suggestions.length <= MAX_VISIBLE) {
|
|
127
|
+
return { visibleSuggestions: suggestions, startIndex: 0 }
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Calculate window to keep selected item visible
|
|
131
|
+
let start = 0
|
|
132
|
+
if (selectedSuggestion >= MAX_VISIBLE) {
|
|
133
|
+
// Selected item is beyond initial window, scroll down
|
|
134
|
+
start = selectedSuggestion - MAX_VISIBLE + 1
|
|
135
|
+
}
|
|
136
|
+
// Ensure we don't go past the end
|
|
137
|
+
start = Math.min(start, suggestions.length - MAX_VISIBLE)
|
|
138
|
+
start = Math.max(0, start)
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
visibleSuggestions: suggestions.slice(start, start + MAX_VISIBLE),
|
|
142
|
+
startIndex: start,
|
|
143
|
+
}
|
|
144
|
+
}, [suggestions, selectedSuggestion])
|
|
145
|
+
|
|
146
|
+
const showSuggestions =
|
|
147
|
+
filterValue.startsWith("/") &&
|
|
148
|
+
filterValue.length >= 1 &&
|
|
149
|
+
suggestions.length > 0
|
|
150
|
+
|
|
151
|
+
// Helper to build full command string from suggestion
|
|
152
|
+
const buildFullCommand = useCallback(
|
|
153
|
+
(suggestion: (typeof COMMANDS)[0]): string => {
|
|
154
|
+
if (suggestion.args) {
|
|
155
|
+
const hasPlaceholder =
|
|
156
|
+
suggestion.args.includes("<") || suggestion.args.includes("[")
|
|
157
|
+
if (!hasPlaceholder) {
|
|
158
|
+
return `${suggestion.cmd} ${suggestion.args}`
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return suggestion.cmd
|
|
162
|
+
},
|
|
163
|
+
[],
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
// Notify parent when suggestions visibility changes (not on every count change)
|
|
167
|
+
// This reduces flicker by only updating when suggestions appear/disappear
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
if (showSuggestions !== prevShowSuggestions.current) {
|
|
170
|
+
prevShowSuggestions.current = showSuggestions
|
|
171
|
+
const visibleCount = Math.min(suggestions.length, MAX_VISIBLE)
|
|
172
|
+
const count = showSuggestions ? visibleCount + 4 : 0 // +4 for borders, scroll indicators, hint line
|
|
173
|
+
onSuggestionsChange?.(count)
|
|
174
|
+
}
|
|
175
|
+
}, [showSuggestions, suggestions.length, onSuggestionsChange])
|
|
176
|
+
|
|
177
|
+
// Reset selected suggestion when suggestions change
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
if (selectedSuggestion >= suggestions.length) {
|
|
180
|
+
setSelectedSuggestion(Math.max(0, suggestions.length - 1))
|
|
181
|
+
}
|
|
182
|
+
}, [suggestions.length, selectedSuggestion])
|
|
183
|
+
|
|
184
|
+
useInput((input, key) => {
|
|
185
|
+
// Tab to autocomplete selected suggestion and commit it
|
|
186
|
+
if (key.tab && showSuggestions && suggestions[selectedSuggestion]) {
|
|
187
|
+
const suggestion = suggestions[selectedSuggestion]
|
|
188
|
+
const fullCmd = buildFullCommand(suggestion)
|
|
189
|
+
setValue(fullCmd)
|
|
190
|
+
setFilterValue(fullCmd) // Commit the selection to filter
|
|
191
|
+
setSelectedSuggestion(0)
|
|
192
|
+
setIsNavigating(false)
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Up/Down to navigate suggestions when showing (circular navigation)
|
|
197
|
+
if (showSuggestions) {
|
|
198
|
+
if (key.upArrow) {
|
|
199
|
+
setIsNavigating(true)
|
|
200
|
+
setSelectedSuggestion((prev) => {
|
|
201
|
+
const newIndex = prev <= 0 ? suggestions.length - 1 : prev - 1
|
|
202
|
+
// Update display value to show selected command
|
|
203
|
+
const suggestion = suggestions[newIndex]
|
|
204
|
+
if (suggestion) {
|
|
205
|
+
setValue(buildFullCommand(suggestion))
|
|
206
|
+
}
|
|
207
|
+
return newIndex
|
|
208
|
+
})
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
if (key.downArrow) {
|
|
212
|
+
setIsNavigating(true)
|
|
213
|
+
setSelectedSuggestion((prev) => {
|
|
214
|
+
const newIndex = prev >= suggestions.length - 1 ? 0 : prev + 1
|
|
215
|
+
// Update display value to show selected command
|
|
216
|
+
const suggestion = suggestions[newIndex]
|
|
217
|
+
if (suggestion) {
|
|
218
|
+
setValue(buildFullCommand(suggestion))
|
|
219
|
+
}
|
|
220
|
+
return newIndex
|
|
221
|
+
})
|
|
222
|
+
return
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
// History navigation when not showing suggestions
|
|
226
|
+
if (key.upArrow && history.length > 0) {
|
|
227
|
+
const newIndex = Math.min(historyIndex + 1, history.length - 1)
|
|
228
|
+
setHistoryIndex(newIndex)
|
|
229
|
+
const historyValue = history[history.length - 1 - newIndex] || ""
|
|
230
|
+
setValue(historyValue)
|
|
231
|
+
setFilterValue(historyValue)
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (key.downArrow) {
|
|
236
|
+
const newIndex = Math.max(historyIndex - 1, -1)
|
|
237
|
+
setHistoryIndex(newIndex)
|
|
238
|
+
if (newIndex < 0) {
|
|
239
|
+
setValue("")
|
|
240
|
+
setFilterValue("")
|
|
241
|
+
} else {
|
|
242
|
+
const historyValue = history[history.length - 1 - newIndex] || ""
|
|
243
|
+
setValue(historyValue)
|
|
244
|
+
setFilterValue(historyValue)
|
|
245
|
+
}
|
|
246
|
+
return
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Escape to clear input or close suggestions
|
|
251
|
+
if (key.escape) {
|
|
252
|
+
setValue("")
|
|
253
|
+
setFilterValue("")
|
|
254
|
+
setSelectedSuggestion(0)
|
|
255
|
+
setHistoryIndex(-1)
|
|
256
|
+
setIsNavigating(false)
|
|
257
|
+
return
|
|
258
|
+
}
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
const handleSubmit = useCallback(
|
|
262
|
+
(input: string) => {
|
|
263
|
+
if (!input.trim()) return
|
|
264
|
+
|
|
265
|
+
// Add to history (avoid duplicates)
|
|
266
|
+
setHistory((prev) => [
|
|
267
|
+
...prev.filter((h) => h !== input.trim()),
|
|
268
|
+
input.trim(),
|
|
269
|
+
])
|
|
270
|
+
|
|
271
|
+
// Reset state
|
|
272
|
+
setValue("")
|
|
273
|
+
setFilterValue("")
|
|
274
|
+
setHistoryIndex(-1)
|
|
275
|
+
setSelectedSuggestion(0)
|
|
276
|
+
setIsNavigating(false)
|
|
277
|
+
|
|
278
|
+
// Call handler
|
|
279
|
+
onSubmit(input)
|
|
280
|
+
},
|
|
281
|
+
[onSubmit],
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
// Handle text input changes - updates both value and filter
|
|
285
|
+
const handleChange = useCallback((v: string) => {
|
|
286
|
+
setValue(v)
|
|
287
|
+
setFilterValue(v) // When user types, update filter to match
|
|
288
|
+
setSelectedSuggestion(0)
|
|
289
|
+
setIsNavigating(false)
|
|
290
|
+
}, [])
|
|
291
|
+
|
|
292
|
+
// Get context-specific hints for current tab
|
|
293
|
+
const hints = useMemo((): string[] => {
|
|
294
|
+
const h: string[] = []
|
|
295
|
+
|
|
296
|
+
if (activeService) {
|
|
297
|
+
const isRunning =
|
|
298
|
+
activeService.status === "running" ||
|
|
299
|
+
activeService.status === "starting"
|
|
300
|
+
if (isRunning && activeService.id !== "system") {
|
|
301
|
+
h.push(`Ctrl+K stop`)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
h.push("Ctrl+L clear")
|
|
306
|
+
h.push("←/→ tabs")
|
|
307
|
+
h.push("Esc cancel")
|
|
308
|
+
|
|
309
|
+
return h
|
|
310
|
+
}, [activeService])
|
|
311
|
+
|
|
312
|
+
return (
|
|
313
|
+
<Box flexDirection="column">
|
|
314
|
+
{/* Suggestions dropdown (above input) */}
|
|
315
|
+
{showSuggestions && (
|
|
316
|
+
<Box
|
|
317
|
+
flexDirection="column"
|
|
318
|
+
borderStyle="round"
|
|
319
|
+
borderColor="gray"
|
|
320
|
+
marginBottom={0}
|
|
321
|
+
>
|
|
322
|
+
{/* Scroll up indicator */}
|
|
323
|
+
{startIndex > 0 && (
|
|
324
|
+
<Box paddingX={1}>
|
|
325
|
+
<Text color="gray">↑ {startIndex} more above</Text>
|
|
326
|
+
</Box>
|
|
327
|
+
)}
|
|
328
|
+
{visibleSuggestions.map((suggestion, visibleIndex) => {
|
|
329
|
+
const actualIndex = startIndex + visibleIndex
|
|
330
|
+
const isSelected = actualIndex === selectedSuggestion
|
|
331
|
+
return (
|
|
332
|
+
<Box
|
|
333
|
+
key={`${suggestion.cmd}-${suggestion.args}-${actualIndex}`}
|
|
334
|
+
paddingX={1}
|
|
335
|
+
>
|
|
336
|
+
<Text
|
|
337
|
+
backgroundColor={isSelected ? "blue" : undefined}
|
|
338
|
+
color={isSelected ? "white" : "cyan"}
|
|
339
|
+
bold={isSelected}
|
|
340
|
+
>
|
|
341
|
+
{suggestion.cmd}
|
|
342
|
+
</Text>
|
|
343
|
+
{suggestion.args && (
|
|
344
|
+
<Text
|
|
345
|
+
color={isSelected ? "white" : "gray"}
|
|
346
|
+
backgroundColor={isSelected ? "blue" : undefined}
|
|
347
|
+
>
|
|
348
|
+
{" "}
|
|
349
|
+
{suggestion.args}
|
|
350
|
+
</Text>
|
|
351
|
+
)}
|
|
352
|
+
<Text color="gray"> - </Text>
|
|
353
|
+
<Text
|
|
354
|
+
color={isSelected ? "white" : "gray"}
|
|
355
|
+
dimColor={!isSelected}
|
|
356
|
+
>
|
|
357
|
+
{suggestion.desc}
|
|
358
|
+
</Text>
|
|
359
|
+
</Box>
|
|
360
|
+
)
|
|
361
|
+
})}
|
|
362
|
+
{/* Scroll down indicator */}
|
|
363
|
+
{startIndex + MAX_VISIBLE < suggestions.length && (
|
|
364
|
+
<Box paddingX={1}>
|
|
365
|
+
<Text color="gray">
|
|
366
|
+
↓ {suggestions.length - startIndex - MAX_VISIBLE} more below
|
|
367
|
+
</Text>
|
|
368
|
+
</Box>
|
|
369
|
+
)}
|
|
370
|
+
<Box paddingX={1}>
|
|
371
|
+
<Text dimColor>Tab complete · ↑↓ select · Esc cancel</Text>
|
|
372
|
+
</Box>
|
|
373
|
+
</Box>
|
|
374
|
+
)}
|
|
375
|
+
|
|
376
|
+
{/* Input box */}
|
|
377
|
+
<Box borderStyle="single" borderColor="cyan" paddingX={1}>
|
|
378
|
+
<Text color="cyan" bold>
|
|
379
|
+
>
|
|
380
|
+
</Text>
|
|
381
|
+
<Text> </Text>
|
|
382
|
+
<TextInput
|
|
383
|
+
value={value}
|
|
384
|
+
onChange={handleChange}
|
|
385
|
+
onSubmit={() =>
|
|
386
|
+
handleSubmit(value.startsWith("/") ? value : "/" + value)
|
|
387
|
+
}
|
|
388
|
+
placeholder="Type / to run a command..."
|
|
389
|
+
/>
|
|
390
|
+
</Box>
|
|
391
|
+
|
|
392
|
+
{/* Hints bar (below input) */}
|
|
393
|
+
<Box paddingX={1} justifyContent="space-between">
|
|
394
|
+
<Box>
|
|
395
|
+
{hints.map((hint, index) => (
|
|
396
|
+
<React.Fragment key={hint}>
|
|
397
|
+
{index > 0 && <Text color="gray"> · </Text>}
|
|
398
|
+
<Text dimColor>{hint}</Text>
|
|
399
|
+
</React.Fragment>
|
|
400
|
+
))}
|
|
401
|
+
</Box>
|
|
402
|
+
</Box>
|
|
403
|
+
</Box>
|
|
404
|
+
)
|
|
354
405
|
}
|