@gxp-dev/tools 2.0.5

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.
Files changed (145) hide show
  1. package/.github/workflows/npm-publish.yml +48 -0
  2. package/CLAUDE.md +400 -0
  3. package/README.md +247 -0
  4. package/REFACTOR_PLAN.md +194 -0
  5. package/bin/gx-devtools.js +87 -0
  6. package/bin/lib/cli.js +251 -0
  7. package/bin/lib/commands/assets.js +337 -0
  8. package/bin/lib/commands/build.js +259 -0
  9. package/bin/lib/commands/datastore.js +433 -0
  10. package/bin/lib/commands/dev.js +328 -0
  11. package/bin/lib/commands/extensions.js +298 -0
  12. package/bin/lib/commands/index.js +35 -0
  13. package/bin/lib/commands/init.js +307 -0
  14. package/bin/lib/commands/publish.js +189 -0
  15. package/bin/lib/commands/socket.js +158 -0
  16. package/bin/lib/commands/ssl.js +47 -0
  17. package/bin/lib/constants.js +120 -0
  18. package/bin/lib/tui/App.tsx +600 -0
  19. package/bin/lib/tui/components/CommandInput.tsx +278 -0
  20. package/bin/lib/tui/components/GeminiPanel.tsx +161 -0
  21. package/bin/lib/tui/components/Header.tsx +27 -0
  22. package/bin/lib/tui/components/LogPanel.tsx +122 -0
  23. package/bin/lib/tui/components/TabBar.tsx +56 -0
  24. package/bin/lib/tui/components/WelcomeScreen.tsx +80 -0
  25. package/bin/lib/tui/index.tsx +63 -0
  26. package/bin/lib/tui/services/ExtensionService.ts +122 -0
  27. package/bin/lib/tui/services/GeminiService.ts +395 -0
  28. package/bin/lib/tui/services/ServiceManager.ts +336 -0
  29. package/bin/lib/tui/services/SocketService.ts +204 -0
  30. package/bin/lib/tui/services/ViteService.ts +107 -0
  31. package/bin/lib/tui/services/index.ts +13 -0
  32. package/bin/lib/utils/files.js +180 -0
  33. package/bin/lib/utils/index.js +17 -0
  34. package/bin/lib/utils/paths.js +138 -0
  35. package/bin/lib/utils/prompts.js +71 -0
  36. package/bin/lib/utils/ssl.js +233 -0
  37. package/browser-extensions/README.md +1 -0
  38. package/browser-extensions/chrome/background.js +857 -0
  39. package/browser-extensions/chrome/content.js +51 -0
  40. package/browser-extensions/chrome/devtools.html +9 -0
  41. package/browser-extensions/chrome/devtools.js +23 -0
  42. package/browser-extensions/chrome/icons/gx_off_128.png +0 -0
  43. package/browser-extensions/chrome/icons/gx_off_16.png +0 -0
  44. package/browser-extensions/chrome/icons/gx_off_32.png +0 -0
  45. package/browser-extensions/chrome/icons/gx_off_64.png +0 -0
  46. package/browser-extensions/chrome/icons/gx_on_128.png +0 -0
  47. package/browser-extensions/chrome/icons/gx_on_16.png +0 -0
  48. package/browser-extensions/chrome/icons/gx_on_32.png +0 -0
  49. package/browser-extensions/chrome/icons/gx_on_64.png +0 -0
  50. package/browser-extensions/chrome/inspector.js +1087 -0
  51. package/browser-extensions/chrome/manifest.json +70 -0
  52. package/browser-extensions/chrome/panel.html +638 -0
  53. package/browser-extensions/chrome/panel.js +862 -0
  54. package/browser-extensions/chrome/popup.html +399 -0
  55. package/browser-extensions/chrome/popup.js +515 -0
  56. package/browser-extensions/chrome/rules.json +1 -0
  57. package/browser-extensions/chrome/test-chrome.html +145 -0
  58. package/browser-extensions/chrome/test-mixed-content.html +190 -0
  59. package/browser-extensions/chrome/test-uri-pattern.html +199 -0
  60. package/browser-extensions/firefox/README.md +134 -0
  61. package/browser-extensions/firefox/background.js +804 -0
  62. package/browser-extensions/firefox/content.js +120 -0
  63. package/browser-extensions/firefox/debug-errors.html +229 -0
  64. package/browser-extensions/firefox/debug-https.html +113 -0
  65. package/browser-extensions/firefox/devtools.html +9 -0
  66. package/browser-extensions/firefox/devtools.js +24 -0
  67. package/browser-extensions/firefox/icons/gx_off_128.png +0 -0
  68. package/browser-extensions/firefox/icons/gx_off_16.png +0 -0
  69. package/browser-extensions/firefox/icons/gx_off_32.png +0 -0
  70. package/browser-extensions/firefox/icons/gx_off_64.png +0 -0
  71. package/browser-extensions/firefox/icons/gx_on_128.png +0 -0
  72. package/browser-extensions/firefox/icons/gx_on_16.png +0 -0
  73. package/browser-extensions/firefox/icons/gx_on_32.png +0 -0
  74. package/browser-extensions/firefox/icons/gx_on_64.png +0 -0
  75. package/browser-extensions/firefox/inspector.js +1087 -0
  76. package/browser-extensions/firefox/manifest.json +67 -0
  77. package/browser-extensions/firefox/panel.html +638 -0
  78. package/browser-extensions/firefox/panel.js +862 -0
  79. package/browser-extensions/firefox/popup.html +525 -0
  80. package/browser-extensions/firefox/popup.js +536 -0
  81. package/browser-extensions/firefox/test-gramercy.html +126 -0
  82. package/browser-extensions/firefox/test-imports.html +58 -0
  83. package/browser-extensions/firefox/test-masking.html +147 -0
  84. package/browser-extensions/firefox/test-uri-pattern.html +199 -0
  85. package/docs/DOCUSAURUS_IMPORT.md +378 -0
  86. package/docs/_category_.json +8 -0
  87. package/docs/app-manifest.md +272 -0
  88. package/docs/building-for-platform.md +315 -0
  89. package/docs/dev-tools.md +291 -0
  90. package/docs/getting-started.md +180 -0
  91. package/docs/gxp-store.md +305 -0
  92. package/docs/index.md +44 -0
  93. package/package.json +77 -0
  94. package/runtime/PortalContainer.vue +326 -0
  95. package/runtime/dev-tools/DevToolsModal.vue +217 -0
  96. package/runtime/dev-tools/LayoutSwitcher.vue +221 -0
  97. package/runtime/dev-tools/MockDataEditor.vue +621 -0
  98. package/runtime/dev-tools/SocketSimulator.vue +562 -0
  99. package/runtime/dev-tools/StoreInspector.vue +644 -0
  100. package/runtime/dev-tools/index.js +6 -0
  101. package/runtime/gxpStringsPlugin.js +428 -0
  102. package/runtime/index.html +22 -0
  103. package/runtime/main.js +32 -0
  104. package/runtime/mock-api/auth-middleware.js +97 -0
  105. package/runtime/mock-api/image-generator.js +221 -0
  106. package/runtime/mock-api/index.js +197 -0
  107. package/runtime/mock-api/response-generator.js +394 -0
  108. package/runtime/mock-api/route-generator.js +323 -0
  109. package/runtime/mock-api/socket-triggers.js +371 -0
  110. package/runtime/mock-api/spec-loader.js +300 -0
  111. package/runtime/server.js +180 -0
  112. package/runtime/stores/gxpPortalConfigStore.js +554 -0
  113. package/runtime/stores/index.js +6 -0
  114. package/runtime/vite-inspector-plugin.js +749 -0
  115. package/runtime/vite-source-tracker-plugin.js +232 -0
  116. package/runtime/vite.config.js +402 -0
  117. package/scripts/launch-chrome.js +90 -0
  118. package/scripts/pack-chrome.js +91 -0
  119. package/socket-events/AiSessionMessageCreated.json +18 -0
  120. package/socket-events/SocialStreamPostCreated.json +24 -0
  121. package/socket-events/SocialStreamPostVariantCompleted.json +23 -0
  122. package/template/README.md +332 -0
  123. package/template/app-manifest.json +32 -0
  124. package/template/dev-assets/images/avatar-placeholder.png +0 -0
  125. package/template/dev-assets/images/background-placeholder.jpg +0 -0
  126. package/template/dev-assets/images/banner-placeholder.jpg +0 -0
  127. package/template/dev-assets/images/icon-placeholder.png +0 -0
  128. package/template/dev-assets/images/logo-placeholder.png +0 -0
  129. package/template/dev-assets/images/product-placeholder.jpg +0 -0
  130. package/template/dev-assets/images/thumbnail-placeholder.jpg +0 -0
  131. package/template/env.example +51 -0
  132. package/template/gitignore +53 -0
  133. package/template/index.html +22 -0
  134. package/template/main.js +28 -0
  135. package/template/src/DemoPage.vue +459 -0
  136. package/template/src/Plugin.vue +38 -0
  137. package/template/src/stores/index.js +9 -0
  138. package/template/src/stores/test-data.json +173 -0
  139. package/template/theme-layouts/AdditionalStyling.css +0 -0
  140. package/template/theme-layouts/PrivateLayout.vue +39 -0
  141. package/template/theme-layouts/PublicLayout.vue +39 -0
  142. package/template/theme-layouts/SystemLayout.vue +39 -0
  143. package/template/vite.config.js +333 -0
  144. package/tsconfig.tui.json +21 -0
  145. package/vite.config.js +164 -0
@@ -0,0 +1,336 @@
1
+ import { spawn, ChildProcess, execSync } from 'child_process';
2
+ import { EventEmitter } from 'events';
3
+
4
+ export type ServiceStatus = 'stopped' | 'starting' | 'running' | 'error';
5
+
6
+ export interface ServiceConfig {
7
+ id: string;
8
+ name: string;
9
+ command: string;
10
+ args: string[];
11
+ cwd: string;
12
+ env?: Record<string, string>;
13
+ }
14
+
15
+ // Store configs for restart capability
16
+ const serviceConfigs: Map<string, ServiceConfig> = new Map();
17
+
18
+ export interface ServiceState {
19
+ id: string;
20
+ name: string;
21
+ status: ServiceStatus;
22
+ logs: string[];
23
+ process?: ChildProcess;
24
+ pid?: number;
25
+ error?: string;
26
+ }
27
+
28
+ export class ServiceManager extends EventEmitter {
29
+ private services: Map<string, ServiceState> = new Map();
30
+ private maxLogLines = 1000;
31
+ private cleanupRegistered = false;
32
+
33
+ constructor() {
34
+ super();
35
+ this.registerCleanupHandlers();
36
+ }
37
+
38
+ // Register cleanup handlers to kill all services on exit
39
+ private registerCleanupHandlers(): void {
40
+ if (this.cleanupRegistered) return;
41
+ this.cleanupRegistered = true;
42
+
43
+ const cleanup = () => {
44
+ this.forceStopAll();
45
+ };
46
+
47
+ // Handle various exit scenarios
48
+ process.on('exit', cleanup);
49
+ process.on('SIGINT', cleanup);
50
+ process.on('SIGTERM', cleanup);
51
+ process.on('SIGHUP', cleanup);
52
+ process.on('beforeExit', cleanup);
53
+
54
+ // Handle uncaught exceptions
55
+ process.on('uncaughtException', (err) => {
56
+ console.error('Uncaught exception:', err);
57
+ cleanup();
58
+ process.exit(1);
59
+ });
60
+ }
61
+
62
+ getService(id: string): ServiceState | undefined {
63
+ return this.services.get(id);
64
+ }
65
+
66
+ getAllServices(): ServiceState[] {
67
+ return Array.from(this.services.values());
68
+ }
69
+
70
+ isRunning(id: string): boolean {
71
+ const service = this.services.get(id);
72
+ return service?.status === 'running' || service?.status === 'starting';
73
+ }
74
+
75
+ start(config: ServiceConfig): ServiceState {
76
+ // Store config for restart capability
77
+ serviceConfigs.set(config.id, config);
78
+
79
+ // Check if already running
80
+ const existing = this.services.get(config.id);
81
+ if (existing && (existing.status === 'running' || existing.status === 'starting')) {
82
+ this.addLog(config.id, `[${config.name}] Already running`);
83
+ return existing;
84
+ }
85
+
86
+ // Create or reset service state
87
+ const state: ServiceState = {
88
+ id: config.id,
89
+ name: config.name,
90
+ status: 'starting',
91
+ logs: existing?.logs || [],
92
+ };
93
+ this.services.set(config.id, state);
94
+
95
+ this.addLog(config.id, `[${config.name}] Starting...`);
96
+ this.emit('statusChange', config.id, 'starting');
97
+
98
+ try {
99
+ // Spawn the process with environment variables to prevent stdin access
100
+ // CI=true disables interactive prompts in many tools
101
+ // These prevent child processes from trying to use raw stdin mode
102
+ // detached: true creates a new process group that doesn't share the parent's tty
103
+ const proc = spawn(config.command, config.args, {
104
+ cwd: config.cwd,
105
+ env: {
106
+ ...process.env,
107
+ ...config.env,
108
+ FORCE_COLOR: '1',
109
+ CI: 'true',
110
+ },
111
+ shell: true,
112
+ stdio: ['ignore', 'pipe', 'pipe'],
113
+ detached: true,
114
+ });
115
+
116
+ state.process = proc;
117
+ state.pid = proc.pid;
118
+
119
+ // Handle stdout
120
+ proc.stdout?.on('data', (data: Buffer) => {
121
+ const lines = data.toString().split('\n').filter(line => line.trim());
122
+ lines.forEach(line => {
123
+ this.addLog(config.id, line);
124
+ });
125
+
126
+ // Detect when service is ready
127
+ const output = data.toString();
128
+ if (state.status === 'starting') {
129
+ // Vite ready indicators
130
+ if (output.includes('ready in') || output.includes('Local:') || output.includes('VITE')) {
131
+ state.status = 'running';
132
+ this.emit('statusChange', config.id, 'running');
133
+ }
134
+ // Socket.IO ready indicator
135
+ if (output.includes('Socket.IO server') || output.includes('listening on port')) {
136
+ state.status = 'running';
137
+ this.emit('statusChange', config.id, 'running');
138
+ }
139
+ }
140
+ });
141
+
142
+ // Handle stderr
143
+ proc.stderr?.on('data', (data: Buffer) => {
144
+ const lines = data.toString().split('\n').filter(line => line.trim());
145
+ lines.forEach(line => {
146
+ this.addLog(config.id, `[stderr] ${line}`);
147
+ });
148
+ });
149
+
150
+ // Handle process exit
151
+ proc.on('close', (code) => {
152
+ state.process = undefined;
153
+ if (code === 0 || code === null) {
154
+ state.status = 'stopped';
155
+ this.addLog(config.id, `[${config.name}] Stopped`);
156
+ } else {
157
+ state.status = 'error';
158
+ state.error = `Process exited with code ${code}`;
159
+ this.addLog(config.id, `[${config.name}] Error: exited with code ${code}`);
160
+ }
161
+ this.emit('statusChange', config.id, state.status);
162
+ });
163
+
164
+ // Handle spawn errors
165
+ proc.on('error', (err) => {
166
+ state.status = 'error';
167
+ state.error = err.message;
168
+ state.process = undefined;
169
+ this.addLog(config.id, `[${config.name}] Error: ${err.message}`);
170
+ this.emit('statusChange', config.id, 'error');
171
+ });
172
+
173
+ // Set running after a short delay if no ready message detected
174
+ setTimeout(() => {
175
+ if (state.status === 'starting' && state.process) {
176
+ state.status = 'running';
177
+ this.emit('statusChange', config.id, 'running');
178
+ }
179
+ }, 3000);
180
+
181
+ } catch (err) {
182
+ state.status = 'error';
183
+ state.error = err instanceof Error ? err.message : 'Unknown error';
184
+ this.addLog(config.id, `[${config.name}] Failed to start: ${state.error}`);
185
+ this.emit('statusChange', config.id, 'error');
186
+ }
187
+
188
+ return state;
189
+ }
190
+
191
+ stop(id: string): boolean {
192
+ const service = this.services.get(id);
193
+ if (!service || !service.process) {
194
+ return false;
195
+ }
196
+
197
+ this.addLog(id, `[${service.name}] Stopping...`);
198
+
199
+ // Kill the process tree
200
+ try {
201
+ process.kill(-service.process.pid!, 'SIGTERM');
202
+ } catch {
203
+ // Process group kill failed, try direct kill
204
+ service.process.kill('SIGTERM');
205
+ }
206
+
207
+ // Force kill after timeout
208
+ setTimeout(() => {
209
+ if (service.process && !service.process.killed) {
210
+ try {
211
+ process.kill(-service.process.pid!, 'SIGKILL');
212
+ } catch {
213
+ service.process.kill('SIGKILL');
214
+ }
215
+ }
216
+ }, 2000);
217
+
218
+ return true;
219
+ }
220
+
221
+ stopAll(): void {
222
+ for (const [id] of this.services) {
223
+ this.stop(id);
224
+ }
225
+ }
226
+
227
+ // Force stop all services synchronously - used during process exit
228
+ forceStopAll(): void {
229
+ for (const [id, service] of this.services) {
230
+ if (service.pid) {
231
+ try {
232
+ // Try to kill the process group first
233
+ process.kill(-service.pid, 'SIGKILL');
234
+ } catch {
235
+ // Process group kill failed, try direct kill
236
+ try {
237
+ process.kill(service.pid, 'SIGKILL');
238
+ } catch {
239
+ // Process may already be dead
240
+ }
241
+ }
242
+ }
243
+ if (service.process && !service.process.killed) {
244
+ try {
245
+ service.process.kill('SIGKILL');
246
+ } catch {
247
+ // Ignore errors
248
+ }
249
+ }
250
+ }
251
+
252
+ // Also try to kill any orphaned vite/nodemon processes using lsof on common ports
253
+ try {
254
+ // Kill processes on typical dev ports synchronously
255
+ execSync('lsof -ti :3060 | xargs kill -9 2>/dev/null || true', { stdio: 'ignore' });
256
+ execSync('lsof -ti :3069 | xargs kill -9 2>/dev/null || true', { stdio: 'ignore' });
257
+ } catch {
258
+ // Ignore errors - best effort cleanup
259
+ }
260
+ }
261
+
262
+ restart(id: string): boolean {
263
+ const config = serviceConfigs.get(id);
264
+ if (!config) {
265
+ return false;
266
+ }
267
+
268
+ const service = this.services.get(id);
269
+ if (service?.process) {
270
+ // Stop first, then restart after process exits
271
+ this.addLog(id, `[${config.name}] Restarting...`);
272
+
273
+ const onExit = () => {
274
+ // Small delay to ensure cleanup
275
+ setTimeout(() => {
276
+ this.start(config);
277
+ }, 500);
278
+ };
279
+
280
+ // Listen for process exit once
281
+ service.process.once('close', onExit);
282
+
283
+ // Kill the process
284
+ try {
285
+ process.kill(-service.process.pid!, 'SIGTERM');
286
+ } catch {
287
+ service.process.kill('SIGTERM');
288
+ }
289
+
290
+ // Force kill after timeout
291
+ setTimeout(() => {
292
+ if (service.process && !service.process.killed) {
293
+ try {
294
+ process.kill(-service.process.pid!, 'SIGKILL');
295
+ } catch {
296
+ service.process.kill('SIGKILL');
297
+ }
298
+ }
299
+ }, 2000);
300
+ } else {
301
+ // Not running, just start
302
+ this.start(config);
303
+ }
304
+
305
+ return true;
306
+ }
307
+
308
+ getConfig(id: string): ServiceConfig | undefined {
309
+ return serviceConfigs.get(id);
310
+ }
311
+
312
+ clearLogs(id: string): void {
313
+ const service = this.services.get(id);
314
+ if (service) {
315
+ service.logs = [];
316
+ this.emit('logsCleared', id);
317
+ }
318
+ }
319
+
320
+ private addLog(id: string, message: string): void {
321
+ const service = this.services.get(id);
322
+ if (!service) return;
323
+
324
+ service.logs.push(message);
325
+
326
+ // Trim logs if too many
327
+ if (service.logs.length > this.maxLogLines) {
328
+ service.logs = service.logs.slice(-this.maxLogLines);
329
+ }
330
+
331
+ this.emit('log', id, message);
332
+ }
333
+ }
334
+
335
+ // Singleton instance
336
+ export const serviceManager = new ServiceManager();
@@ -0,0 +1,204 @@
1
+ import { serviceManager, ServiceConfig } from './ServiceManager.js';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ import { fileURLToPath } from 'url';
5
+ import https from 'https';
6
+ import http from 'http';
7
+
8
+ export interface SocketOptions {
9
+ cwd?: string;
10
+ withMock?: boolean;
11
+ }
12
+
13
+ export interface SocketEvent {
14
+ name: string;
15
+ event: string;
16
+ channel: string;
17
+ data: Record<string, unknown>;
18
+ }
19
+
20
+ // Get toolkit root
21
+ function getToolkitRoot(): string {
22
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
23
+ // From dist/tui/services/ go up 3 levels to toolkit root
24
+ return path.resolve(__dirname, '..', '..', '..');
25
+ }
26
+
27
+ // Get the path to the runtime server.js
28
+ function getServerPath(): string {
29
+ const toolkitRoot = getToolkitRoot();
30
+ return path.join(toolkitRoot, 'runtime', 'server.js');
31
+ }
32
+
33
+ // Get the socket events directory
34
+ function getSocketEventsDir(cwd: string): string | null {
35
+ // Check local project first
36
+ const localDir = path.join(cwd, 'socket-events');
37
+ if (fs.existsSync(localDir)) {
38
+ return localDir;
39
+ }
40
+
41
+ // Fall back to toolkit's socket-events
42
+ const toolkitDir = path.join(getToolkitRoot(), 'socket-events');
43
+ if (fs.existsSync(toolkitDir)) {
44
+ return toolkitDir;
45
+ }
46
+
47
+ return null;
48
+ }
49
+
50
+ export function startSocket(options: SocketOptions = {}): void {
51
+ const cwd = options.cwd || process.cwd();
52
+ const withMock = options.withMock || false;
53
+
54
+ const env: Record<string, string> = {
55
+ FORCE_COLOR: '1',
56
+ };
57
+
58
+ // Enable mock API if requested
59
+ if (withMock) {
60
+ env.MOCK_API_ENABLED = 'true';
61
+ }
62
+
63
+ const config: ServiceConfig = {
64
+ id: 'socket',
65
+ name: withMock ? 'Socket.IO + Mock API' : 'Socket.IO',
66
+ command: 'node',
67
+ args: [getServerPath()],
68
+ cwd,
69
+ env,
70
+ };
71
+
72
+ serviceManager.start(config);
73
+ }
74
+
75
+ export function stopSocket(): boolean {
76
+ return serviceManager.stop('socket');
77
+ }
78
+
79
+ export function isSocketRunning(): boolean {
80
+ return serviceManager.isRunning('socket');
81
+ }
82
+
83
+ // List available socket events
84
+ export function listSocketEvents(cwd?: string): SocketEvent[] {
85
+ const eventsDir = getSocketEventsDir(cwd || process.cwd());
86
+ if (!eventsDir) {
87
+ return [];
88
+ }
89
+
90
+ const events: SocketEvent[] = [];
91
+ const files = fs.readdirSync(eventsDir).filter(f => f.endsWith('.json'));
92
+
93
+ for (const file of files) {
94
+ try {
95
+ const content = fs.readFileSync(path.join(eventsDir, file), 'utf-8');
96
+ const data = JSON.parse(content);
97
+ events.push({
98
+ name: path.basename(file, '.json'),
99
+ event: data.event,
100
+ channel: data.channel,
101
+ data: data.data,
102
+ });
103
+ } catch {
104
+ // Skip invalid files
105
+ }
106
+ }
107
+
108
+ return events;
109
+ }
110
+
111
+ // Send a socket event
112
+ export async function sendSocketEvent(
113
+ eventName: string,
114
+ identifier?: string,
115
+ cwd?: string
116
+ ): Promise<{ success: boolean; message: string }> {
117
+ const eventsDir = getSocketEventsDir(cwd || process.cwd());
118
+ if (!eventsDir) {
119
+ return { success: false, message: 'Socket events directory not found' };
120
+ }
121
+
122
+ const eventPath = path.join(eventsDir, `${eventName}.json`);
123
+ if (!fs.existsSync(eventPath)) {
124
+ return { success: false, message: `Event "${eventName}" not found` };
125
+ }
126
+
127
+ try {
128
+ const content = fs.readFileSync(eventPath, 'utf-8');
129
+ const eventData = JSON.parse(content);
130
+
131
+ // Update channel if identifier provided
132
+ if (identifier) {
133
+ const channelParts = eventData.channel.split('.');
134
+ if (channelParts.length >= 2) {
135
+ const model = channelParts[1];
136
+ eventData.channel = `private.${model}.${identifier}`;
137
+ }
138
+ }
139
+
140
+ const socketPort = process.env.SOCKET_IO_PORT || 3069;
141
+ const payload = JSON.stringify({
142
+ event: eventData.event,
143
+ channel: eventData.channel,
144
+ data: eventData.data,
145
+ });
146
+
147
+ return new Promise((resolve) => {
148
+ // Try HTTPS first, then HTTP
149
+ const tryRequest = (useHttps: boolean) => {
150
+ const protocol = useHttps ? https : http;
151
+ const url = `${useHttps ? 'https' : 'http'}://localhost:${socketPort}/emit`;
152
+
153
+ const req = protocol.request(
154
+ url,
155
+ {
156
+ method: 'POST',
157
+ headers: {
158
+ 'Content-Type': 'application/json',
159
+ 'Content-Length': Buffer.byteLength(payload),
160
+ },
161
+ rejectUnauthorized: false, // Allow self-signed certs
162
+ },
163
+ (res) => {
164
+ if (res.statusCode === 200) {
165
+ resolve({
166
+ success: true,
167
+ message: `Sent "${eventData.event}" to channel "${eventData.channel}"`,
168
+ });
169
+ } else {
170
+ resolve({
171
+ success: false,
172
+ message: `Server returned status ${res.statusCode}`,
173
+ });
174
+ }
175
+ }
176
+ );
177
+
178
+ req.on('error', (err) => {
179
+ if (useHttps && err.message.includes('ECONNREFUSED')) {
180
+ // Try HTTP if HTTPS fails
181
+ tryRequest(false);
182
+ } else {
183
+ resolve({
184
+ success: false,
185
+ message: err.message.includes('ECONNREFUSED')
186
+ ? 'Socket.IO server not running. Start it with /socket'
187
+ : `Error: ${err.message}`,
188
+ });
189
+ }
190
+ });
191
+
192
+ req.write(payload);
193
+ req.end();
194
+ };
195
+
196
+ tryRequest(true);
197
+ });
198
+ } catch (err) {
199
+ return {
200
+ success: false,
201
+ message: `Error reading event file: ${err instanceof Error ? err.message : 'Unknown error'}`,
202
+ };
203
+ }
204
+ }
@@ -0,0 +1,107 @@
1
+ import { serviceManager, ServiceConfig } from './ServiceManager.js';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ import { fileURLToPath } from 'url';
5
+ import dotenv from 'dotenv';
6
+
7
+ export interface ViteOptions {
8
+ noHttps?: boolean;
9
+ cwd?: string;
10
+ }
11
+
12
+ // Get the toolkit root directory
13
+ function getToolkitRoot(): string {
14
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
+ // Navigate from dist/tui/services to project root (3 levels up)
16
+ return path.resolve(__dirname, '..', '..', '..');
17
+ }
18
+
19
+ // Find existing SSL certificates in a directory
20
+ function findExistingCertificates(certsDir: string): { certPath: string; keyPath: string } | null {
21
+ if (!fs.existsSync(certsDir)) return null;
22
+
23
+ const files = fs.readdirSync(certsDir);
24
+ const certFile = files.find(f => f.endsWith('.pem') && !f.includes('key'));
25
+ const keyFile = files.find(f => f.includes('key') && f.endsWith('.pem'));
26
+
27
+ if (certFile && keyFile) {
28
+ return {
29
+ certPath: path.join(certsDir, certFile),
30
+ keyPath: path.join(certsDir, keyFile),
31
+ };
32
+ }
33
+ return null;
34
+ }
35
+
36
+ export function startVite(options: ViteOptions = {}): void {
37
+ const cwd = options.cwd || process.cwd();
38
+ const toolkitRoot = getToolkitRoot();
39
+
40
+ // Load .env file if it exists
41
+ const envPath = path.join(cwd, '.env');
42
+ let envVars: Record<string, string> = {};
43
+
44
+ if (fs.existsSync(envPath)) {
45
+ const envResult = dotenv.config({ path: envPath });
46
+ if (envResult.parsed) {
47
+ envVars = { ...envResult.parsed };
48
+ }
49
+ }
50
+
51
+ // Determine HTTPS settings
52
+ let useHttps = !options.noHttps;
53
+ let certPath = '';
54
+ let keyPath = '';
55
+
56
+ if (useHttps) {
57
+ const certsDir = path.join(cwd, '.certs');
58
+ const existingCerts = findExistingCertificates(certsDir);
59
+ if (existingCerts) {
60
+ certPath = existingCerts.certPath;
61
+ keyPath = existingCerts.keyPath;
62
+ } else {
63
+ useHttps = false; // No certs found, fall back to HTTP
64
+ }
65
+ }
66
+
67
+ // Determine the port (priority: .env > default)
68
+ const port = envVars.NODE_PORT || '3060';
69
+
70
+ // Find vite.config.js (local or toolkit runtime)
71
+ const localViteConfig = path.join(cwd, 'vite.config.js');
72
+ // The correct vite.config.js is in the runtime directory, not the root
73
+ const toolkitViteConfig = path.join(toolkitRoot, 'runtime', 'vite.config.js');
74
+ const viteConfigPath = fs.existsSync(localViteConfig) ? localViteConfig : toolkitViteConfig;
75
+
76
+ // Build final environment variables
77
+ const env: Record<string, string> = {
78
+ ...envVars,
79
+ FORCE_COLOR: '1',
80
+ NODE_PORT: port,
81
+ USE_HTTPS: useHttps ? 'true' : 'false',
82
+ CERT_PATH: certPath,
83
+ KEY_PATH: keyPath,
84
+ COMPONENT_PATH: envVars.COMPONENT_PATH || './src/Plugin.vue',
85
+ NODE_LOG_LEVEL: envVars.NODE_LOG_LEVEL || 'info',
86
+ };
87
+
88
+ // Run vite directly with the correct config
89
+ const config: ServiceConfig = {
90
+ id: 'vite',
91
+ name: 'Vite',
92
+ command: 'npx',
93
+ args: ['vite', 'dev', '--config', viteConfigPath],
94
+ cwd,
95
+ env,
96
+ };
97
+
98
+ serviceManager.start(config);
99
+ }
100
+
101
+ export function stopVite(): boolean {
102
+ return serviceManager.stop('vite');
103
+ }
104
+
105
+ export function isViteRunning(): boolean {
106
+ return serviceManager.isRunning('vite');
107
+ }
@@ -0,0 +1,13 @@
1
+ export { serviceManager, ServiceManager, ServiceStatus, ServiceConfig, ServiceState } from './ServiceManager.js';
2
+ export { startVite, stopVite, isViteRunning, ViteOptions } from './ViteService.js';
3
+ export { startSocket, stopSocket, isSocketRunning, listSocketEvents, sendSocketEvent, SocketOptions, SocketEvent } from './SocketService.js';
4
+ export { startExtension, stopExtension, isExtensionRunning, BrowserType, ExtensionOptions } from './ExtensionService.js';
5
+ export {
6
+ geminiService,
7
+ GeminiService,
8
+ GeminiConfig,
9
+ isAuthenticated,
10
+ loadGeminiConfig,
11
+ saveGeminiConfig,
12
+ clearAuthTokens
13
+ } from './GeminiService.js';