deepdebug-local-agent 1.0.14 → 1.0.16

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.
@@ -0,0 +1,368 @@
1
+ /**
2
+ * setup-agent-routes.js
3
+ *
4
+ * Local Agent routes for the Setup Agent.
5
+ * These endpoints are called by SetupAgentToolExecutor (Java backend)
6
+ * and execute on the user's local machine.
7
+ *
8
+ * Routes:
9
+ * POST /setup/run-auth-command — Run OAuth CLI command (opens browser)
10
+ * GET /setup/auth-status — Poll for OAuth completion
11
+ * POST /setup/run-command — Run a general setup command
12
+ * POST /setup/install-tool — Install a CLI tool (OS-aware)
13
+ * GET /setup/check-tool — Check if a tool is installed
14
+ *
15
+ * Mount in server.js with:
16
+ * import { setupAgentRouter } from './setup-agent-routes.js';
17
+ * app.use(setupAgentRouter);
18
+ */
19
+
20
+ import express from 'express';
21
+ import { spawn, exec } from 'child_process';
22
+ import { promisify } from 'util';
23
+ import os from 'os';
24
+
25
+ const execAsync = promisify(exec);
26
+ const router = express.Router();
27
+
28
+ // Tracks running OAuth sessions: sessionId -> { provider, process, status, startedAt }
29
+ const oauthSessions = new Map();
30
+
31
+ // ============================================
32
+ // POST /setup/run-auth-command
33
+ // Run an OAuth CLI command asynchronously (opens browser)
34
+ // ============================================
35
+
36
+ router.post('/setup/run-auth-command', async (req, res) => {
37
+ const { command, description, session_id, async: isAsync } = req.body;
38
+
39
+ if (!command) {
40
+ return res.status(400).json({ error: 'command is required' });
41
+ }
42
+
43
+ console.log(`[SetupAgent] Running auth command: ${command}`);
44
+
45
+ try {
46
+ // Split command into parts
47
+ const parts = command.trim().split(/\s+/);
48
+ const cmd = parts[0];
49
+ const args = parts.slice(1);
50
+
51
+ const child = spawn(cmd, args, {
52
+ shell: true,
53
+ detached: false,
54
+ stdio: ['inherit', 'pipe', 'pipe'], // inherit stdin so browser auth works
55
+ });
56
+
57
+ const session = {
58
+ provider: detectProviderFromCommand(command),
59
+ process: child,
60
+ status: 'pending',
61
+ output: [],
62
+ error: [],
63
+ startedAt: Date.now(),
64
+ command,
65
+ };
66
+
67
+ if (session_id) {
68
+ oauthSessions.set(session_id, session);
69
+ }
70
+
71
+ child.stdout.on('data', (data) => {
72
+ const text = data.toString();
73
+ session.output.push(text);
74
+ console.log(`[SetupAgent][auth-stdout] ${text.trim()}`);
75
+ });
76
+
77
+ child.stderr.on('data', (data) => {
78
+ const text = data.toString();
79
+ session.error.push(text);
80
+ console.log(`[SetupAgent][auth-stderr] ${text.trim()}`);
81
+ });
82
+
83
+ child.on('close', (code) => {
84
+ session.status = code === 0 ? 'success' : 'failed';
85
+ session.exitCode = code;
86
+ console.log(`[SetupAgent] Auth command finished: code=${code} session=${session_id}`);
87
+ });
88
+
89
+ child.on('error', (err) => {
90
+ session.status = 'failed';
91
+ session.lastError = err.message;
92
+ console.error(`[SetupAgent] Auth command error: ${err.message}`);
93
+ });
94
+
95
+ // If async, respond immediately — client polls for status
96
+ if (isAsync) {
97
+ return res.json({
98
+ status: 'started',
99
+ session_id,
100
+ message: `${description || command} started — browser should open shortly`,
101
+ pid: child.pid,
102
+ });
103
+ }
104
+
105
+ // Otherwise wait for completion (not recommended for auth flows)
106
+ child.on('close', (code) => {
107
+ res.json({
108
+ status: code === 0 ? 'success' : 'failed',
109
+ exit_code: code,
110
+ output: session.output.join(''),
111
+ error: session.error.join(''),
112
+ });
113
+ });
114
+
115
+ } catch (err) {
116
+ console.error(`[SetupAgent] Failed to start auth command: ${err.message}`);
117
+ res.status(500).json({ error: err.message });
118
+ }
119
+ });
120
+
121
+ // ============================================
122
+ // GET /setup/auth-status
123
+ // Poll for OAuth completion
124
+ // ============================================
125
+
126
+ router.get('/setup/auth-status', (req, res) => {
127
+ const { provider, session_id } = req.query;
128
+
129
+ if (!session_id || !oauthSessions.has(session_id)) {
130
+ return res.json({ status: 'unknown', message: 'Session not found' });
131
+ }
132
+
133
+ const session = oauthSessions.get(session_id);
134
+ const elapsed = Math.round((Date.now() - session.startedAt) / 1000);
135
+
136
+ res.json({
137
+ status: session.status, // pending | success | failed
138
+ provider: session.provider || provider,
139
+ session_id,
140
+ elapsed_seconds: elapsed,
141
+ output_preview: session.output.slice(-3).join('').trim().substring(0, 200),
142
+ message: session.status === 'success'
143
+ ? 'Authentication completed successfully'
144
+ : session.status === 'failed'
145
+ ? `Authentication failed: ${session.lastError || 'unknown error'}`
146
+ : `Waiting for browser authentication... (${elapsed}s)`,
147
+ });
148
+ });
149
+
150
+ // ============================================
151
+ // POST /setup/run-command
152
+ // Run a general setup command and wait for result
153
+ // ============================================
154
+
155
+ router.post('/setup/run-command', async (req, res) => {
156
+ const { command, description, working_directory } = req.body;
157
+
158
+ if (!command) {
159
+ return res.status(400).json({ error: 'command is required' });
160
+ }
161
+
162
+ console.log(`[SetupAgent] Running command: ${command}`);
163
+
164
+ try {
165
+ const options = {
166
+ cwd: working_directory || process.cwd(),
167
+ timeout: 60000, // 60s timeout
168
+ };
169
+
170
+ const { stdout, stderr } = await execAsync(command, options);
171
+
172
+ res.json({
173
+ status: 'success',
174
+ stdout: stdout.trim(),
175
+ stderr: stderr.trim(),
176
+ exit_code: 0,
177
+ command,
178
+ description,
179
+ });
180
+
181
+ } catch (err) {
182
+ console.error(`[SetupAgent] Command failed: ${command} — ${err.message}`);
183
+ res.json({
184
+ status: 'failed',
185
+ stdout: err.stdout ? err.stdout.trim() : '',
186
+ stderr: err.stderr ? err.stderr.trim() : err.message,
187
+ exit_code: err.code || 1,
188
+ command,
189
+ });
190
+ }
191
+ });
192
+
193
+ // ============================================
194
+ // POST /setup/install-tool
195
+ // Install a CLI tool using the appropriate package manager
196
+ // ============================================
197
+
198
+ router.post('/setup/install-tool', async (req, res) => {
199
+ const { tool } = req.body;
200
+
201
+ if (!tool) {
202
+ return res.status(400).json({ error: 'tool is required' });
203
+ }
204
+
205
+ const platform = os.platform(); // 'darwin', 'linux', 'win32'
206
+ const installCommand = getInstallCommand(tool, platform);
207
+
208
+ if (!installCommand) {
209
+ return res.json({
210
+ status: 'manual_required',
211
+ tool,
212
+ platform,
213
+ message: `Cannot auto-install ${tool} on ${platform}. Please install it manually.`,
214
+ install_url: getInstallUrl(tool),
215
+ });
216
+ }
217
+
218
+ console.log(`[SetupAgent] Installing ${tool} on ${platform}: ${installCommand}`);
219
+
220
+ try {
221
+ // For brew/apt installs that may take time, stream output
222
+ const { stdout, stderr } = await execAsync(installCommand, { timeout: 300000 }); // 5 min
223
+
224
+ res.json({
225
+ status: 'installed',
226
+ tool,
227
+ platform,
228
+ command_used: installCommand,
229
+ output: stdout.trim(),
230
+ });
231
+
232
+ } catch (err) {
233
+ res.json({
234
+ status: 'failed',
235
+ tool,
236
+ platform,
237
+ error: err.stderr || err.message,
238
+ install_url: getInstallUrl(tool),
239
+ message: `Auto-install failed. Please install ${tool} manually.`,
240
+ });
241
+ }
242
+ });
243
+
244
+ // ============================================
245
+ // GET /setup/check-tool
246
+ // Check if a CLI tool is installed
247
+ // ============================================
248
+
249
+ router.get('/setup/check-tool', async (req, res) => {
250
+ const { name } = req.query;
251
+
252
+ if (!name) {
253
+ return res.status(400).json({ error: 'name is required' });
254
+ }
255
+
256
+ const checkCommand = getVersionCommand(name);
257
+
258
+ try {
259
+ const { stdout } = await execAsync(checkCommand, { timeout: 5000 });
260
+ const version = stdout.trim().split('\n')[0]; // first line usually has version
261
+
262
+ res.json({
263
+ installed: true,
264
+ tool: name,
265
+ version,
266
+ message: `${name} is installed: ${version}`,
267
+ });
268
+
269
+ } catch (err) {
270
+ res.json({
271
+ installed: false,
272
+ tool: name,
273
+ version: null,
274
+ message: `${name} is not installed`,
275
+ });
276
+ }
277
+ });
278
+
279
+ // ============================================
280
+ // Helpers
281
+ // ============================================
282
+
283
+ function detectProviderFromCommand(command) {
284
+ if (command.includes('gcloud')) return 'gcp';
285
+ if (command.includes('az ')) return 'azure';
286
+ if (command.includes('aws ')) return 'aws_sso';
287
+ if (command.includes('gh ')) return 'github';
288
+ if (command.includes('glab')) return 'gitlab';
289
+ return 'unknown';
290
+ }
291
+
292
+ function getVersionCommand(tool) {
293
+ const commands = {
294
+ gcloud: 'gcloud --version',
295
+ 'azure-cli': 'az --version',
296
+ 'aws-cli': 'aws --version',
297
+ 'github-cli': 'gh --version',
298
+ git: 'git --version',
299
+ node: 'node --version',
300
+ docker: 'docker --version',
301
+ brew: 'brew --version',
302
+ python: 'python3 --version',
303
+ java: 'java -version',
304
+ mvn: 'mvn --version',
305
+ };
306
+ return commands[tool] || `${tool} --version`;
307
+ }
308
+
309
+ function getInstallCommand(tool, platform) {
310
+ const isMac = platform === 'darwin';
311
+ const isLinux = platform === 'linux';
312
+ const isWin = platform === 'win32';
313
+
314
+ const commands = {
315
+ gcloud: {
316
+ darwin: 'brew install --cask google-cloud-sdk',
317
+ linux: 'curl https://sdk.cloud.google.com | bash',
318
+ win32: 'winget install Google.CloudSDK',
319
+ },
320
+ 'azure-cli': {
321
+ darwin: 'brew install azure-cli',
322
+ linux: 'curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash',
323
+ win32: 'winget install Microsoft.AzureCLI',
324
+ },
325
+ 'aws-cli': {
326
+ darwin: 'brew install awscli',
327
+ linux: 'curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o awscliv2.zip && unzip awscliv2.zip && sudo ./aws/install',
328
+ win32: 'winget install Amazon.AWSCLI',
329
+ },
330
+ 'github-cli': {
331
+ darwin: 'brew install gh',
332
+ linux: 'sudo apt install gh -y',
333
+ win32: 'winget install GitHub.cli',
334
+ },
335
+ git: {
336
+ darwin: 'brew install git',
337
+ linux: 'sudo apt install git -y',
338
+ win32: 'winget install Git.Git',
339
+ },
340
+ node: {
341
+ darwin: 'brew install node',
342
+ linux: 'curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - && sudo apt-get install -y nodejs',
343
+ win32: 'winget install OpenJS.NodeJS',
344
+ },
345
+ docker: {
346
+ darwin: 'brew install --cask docker',
347
+ linux: 'curl -fsSL https://get.docker.com | sh',
348
+ win32: 'winget install Docker.DockerDesktop',
349
+ },
350
+ };
351
+
352
+ return commands[tool]?.[platform] || null;
353
+ }
354
+
355
+ function getInstallUrl(tool) {
356
+ const urls = {
357
+ gcloud: 'https://cloud.google.com/sdk/docs/install',
358
+ 'azure-cli': 'https://learn.microsoft.com/en-us/cli/azure/install-azure-cli',
359
+ 'aws-cli': 'https://aws.amazon.com/cli/',
360
+ 'github-cli': 'https://cli.github.com/',
361
+ git: 'https://git-scm.com/downloads',
362
+ node: 'https://nodejs.org/',
363
+ docker: 'https://docs.docker.com/get-docker/',
364
+ };
365
+ return urls[tool] || `https://www.google.com/search?q=install+${tool}`;
366
+ }
367
+
368
+ export { router as setupAgentRouter };