deepdebug-local-agent 1.0.3 → 1.0.4

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 (2) hide show
  1. package/bin/install.js +491 -209
  2. package/package.json +1 -1
package/bin/install.js CHANGED
@@ -1,282 +1,564 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * deepdebug-local-agent CLI
4
+ * DeepDebug Local Agent - Interactive Installer with Authorization Flow
5
+ *
6
+ * Supports: Windows, macOS, Linux
5
7
  *
6
8
  * Usage:
7
- * npx deepdebug-local-agent install — installs and starts the agent as a service
8
- * npx deepdebug-local-agent start — starts the agent
9
- * npx deepdebug-local-agent stop — stops the agent
10
- * npx deepdebug-local-agent status — shows agent status
11
- * npx deepdebug-local-agent run — runs the agent in the foreground (dev mode)
9
+ * npx deepdebug-local-agent install
10
+ * npx deepdebug-local-agent install --api-key=YOUR_KEY
11
+ * npx deepdebug-local-agent start
12
+ * npx deepdebug-local-agent stop
13
+ * npx deepdebug-local-agent status
12
14
  */
13
15
 
14
- import { execSync, spawn } from 'child_process';
15
- import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
16
- import { join, dirname } from 'path';
17
- import { fileURLToPath } from 'url';
16
+ import fs from 'fs';
17
+ import path from 'path';
18
18
  import os from 'os';
19
+ import crypto from 'crypto';
20
+ import { spawn, execSync } from 'child_process';
19
21
  import readline from 'readline';
20
22
 
21
- const __filename = fileURLToPath(import.meta.url);
22
- const __dirname = dirname(__filename);
23
- const ROOT_DIR = join(__dirname, '..');
24
-
25
- const CONFIG_DIR = join(os.homedir(), '.deepdebug');
26
- const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
27
- const DEFAULT_PORT = 5055;
28
- const DEFAULT_GATEWAY = 'https://gateway.insptech.pt';
29
- const AGENT_NAME = 'deepdebug-agent';
30
-
31
- const BLUE = '\x1b[34m';
32
- const GREEN = '\x1b[32m';
33
- const YELLOW = '\x1b[33m';
34
- const RED = '\x1b[31m';
35
- const BOLD = '\x1b[1m';
36
- const RESET = '\x1b[0m';
37
-
38
- const log = (msg) => console.log(`${BLUE}[DeepDebug]${RESET} ${msg}`);
39
- const ok = (msg) => console.log(`${GREEN}✓${RESET} ${msg}`);
40
- const warn = (msg) => console.log(`${YELLOW}⚠${RESET} ${msg}`);
41
- const err = (msg) => console.log(`${RED}✗${RESET} ${msg}`);
42
- const bold = (msg) => `${BOLD}${msg}${RESET}`;
43
-
44
- function printBanner() {
45
- console.log('');
46
- console.log(`${BLUE}${BOLD}╔═══════════════════════════════════════╗${RESET}`);
47
- console.log(`${BLUE}${BOLD}║ Insptech AI — DeepDebug Agent ║${RESET}`);
48
- console.log(`${BLUE}${BOLD}║ deepdebug-local-agent ║${RESET}`);
49
- console.log(`${BLUE}${BOLD}╚═══════════════════════════════════════╝${RESET}`);
50
- console.log('');
23
+ // ============================================
24
+ // Configuration
25
+ // ============================================
26
+ const CONFIG_DIR = path.join(os.homedir(), '.deepdebug');
27
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
28
+ const MACHINE_ID_FILE = path.join(CONFIG_DIR, 'machine-id');
29
+ const API_BASE_URL = process.env.DEEPDEBUG_API_URL || 'https://deepdebug-gateway-lszyf7c4ia-uc.a.run.app';
30
+ const DOCKER_IMAGE = 'williambella/deepdebug-local-agent:latest';
31
+
32
+ // ============================================
33
+ // Cross-platform utilities
34
+ // ============================================
35
+ const isWindows = os.platform() === 'win32';
36
+ const isMac = os.platform() === 'darwin';
37
+ const isLinux = os.platform() === 'linux';
38
+
39
+ function getOsType() {
40
+ if (isWindows) return 'WINDOWS';
41
+ if (isMac) return 'MACOS';
42
+ if (isLinux) return 'LINUX';
43
+ return 'UNKNOWN';
51
44
  }
52
45
 
53
- function ask(question) {
54
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
55
- return new Promise(resolve => rl.question(question, ans => { rl.close(); resolve(ans.trim()); }));
46
+ function getOsVersion() {
47
+ return `${os.type()} ${os.release()}`;
56
48
  }
57
49
 
58
- function loadConfig() {
59
- try {
60
- if (existsSync(CONFIG_FILE)) {
61
- return JSON.parse(readFileSync(CONFIG_FILE, 'utf8'));
62
- }
63
- } catch {}
64
- return {};
50
+ // ============================================
51
+ // Console colors (cross-platform)
52
+ // ============================================
53
+ const colors = {
54
+ reset: '\x1b[0m',
55
+ bright: '\x1b[1m',
56
+ green: '\x1b[32m',
57
+ yellow: '\x1b[33m',
58
+ red: '\x1b[31m',
59
+ cyan: '\x1b[36m',
60
+ magenta: '\x1b[35m',
61
+ blue: '\x1b[34m'
62
+ };
63
+
64
+ function log(message, color = 'reset') {
65
+ console.log(`${colors[color]}${message}${colors.reset}`);
65
66
  }
66
67
 
67
- function saveConfig(cfg) {
68
- if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
69
- writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2), 'utf8');
68
+ function logSuccess(message) {
69
+ console.log(`${colors.green}✅ ${message}${colors.reset}`);
70
70
  }
71
71
 
72
- function checkNodeVersion() {
73
- const major = parseInt(process.version.slice(1).split('.')[0]);
74
- if (major < 18) {
75
- err(`Node.js ${process.version} detected. Version 18 or higher is required.`);
76
- err('Download: https://nodejs.org');
77
- process.exit(1);
78
- }
79
- ok(`Node.js ${process.version} detected`);
72
+ function logError(message) {
73
+ console.log(`${colors.red}❌ ${message}${colors.reset}`);
80
74
  }
81
75
 
82
- function getServerScript() {
83
- // Try common locations
84
- const candidates = [
85
- join(ROOT_DIR, 'server.js'),
86
- join(ROOT_DIR, 'src', 'server.js'),
87
- ];
88
- for (const c of candidates) {
89
- if (existsSync(c)) return c;
90
- }
91
- // Fallback: server.js next to bin/
92
- return join(ROOT_DIR, 'server.js');
76
+ function logInfo(message) {
77
+ console.log(`${colors.cyan}ℹ️ ${message}${colors.reset}`);
93
78
  }
94
79
 
95
- // ─── Platform detection ───────────────────────────────────────────────────────
80
+ function logWarning(message) {
81
+ console.log(`${colors.yellow}⚠️ ${message}${colors.reset}`);
82
+ }
83
+
84
+ // ============================================
85
+ // Input utilities
86
+ // ============================================
87
+ async function prompt(question) {
88
+ const rl = readline.createInterface({
89
+ input: process.stdin,
90
+ output: process.stdout
91
+ });
96
92
 
97
- const platform = os.platform(); // 'win32' | 'darwin' | 'linux'
93
+ return new Promise((resolve) => {
94
+ rl.question(`${colors.cyan}${question}${colors.reset}`, (answer) => {
95
+ rl.close();
96
+ resolve(answer.trim());
97
+ });
98
+ });
99
+ }
98
100
 
99
- function getPidFile() {
100
- return join(CONFIG_DIR, 'agent.pid');
101
+ async function promptYesNo(question, defaultYes = true) {
102
+ const hint = defaultYes ? '(Y/n)' : '(y/N)';
103
+ const answer = await prompt(`${question} ${hint}: `);
104
+ if (answer === '') return defaultYes;
105
+ return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
101
106
  }
102
107
 
103
- function readPid() {
108
+ // ============================================
109
+ // Machine ID generation (unique per machine)
110
+ // ============================================
111
+ function getMachineId() {
112
+ if (fs.existsSync(MACHINE_ID_FILE)) {
113
+ return fs.readFileSync(MACHINE_ID_FILE, 'utf-8').trim();
114
+ }
115
+
116
+ let machineId;
104
117
  try {
105
- const pid = parseInt(readFileSync(getPidFile(), 'utf8').trim());
106
- return isNaN(pid) ? null : pid;
107
- } catch { return null; }
118
+ if (isWindows) {
119
+ try {
120
+ const result = execSync('wmic bios get serialnumber', { encoding: 'utf-8' });
121
+ const serial = result.split('\n')[1]?.trim();
122
+ if (serial && serial !== 'To be filled by O.E.M.') {
123
+ machineId = crypto.createHash('sha256').update(serial).digest('hex').substring(0, 32);
124
+ }
125
+ } catch {}
126
+ } else if (isMac) {
127
+ try {
128
+ const result = execSync('ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID', { encoding: 'utf-8' });
129
+ const match = result.match(/"([A-F0-9-]+)"/);
130
+ if (match) {
131
+ machineId = crypto.createHash('sha256').update(match[1]).digest('hex').substring(0, 32);
132
+ }
133
+ } catch {}
134
+ } else if (isLinux) {
135
+ try {
136
+ if (fs.existsSync('/etc/machine-id')) {
137
+ const mid = fs.readFileSync('/etc/machine-id', 'utf-8').trim();
138
+ machineId = crypto.createHash('sha256').update(mid).digest('hex').substring(0, 32);
139
+ }
140
+ } catch {}
141
+ }
142
+ } catch (e) {}
143
+
144
+ if (!machineId) {
145
+ const seed = `${os.hostname()}-${os.userInfo().username}-${Date.now()}-${crypto.randomBytes(8).toString('hex')}`;
146
+ machineId = crypto.createHash('sha256').update(seed).digest('hex').substring(0, 32);
147
+ }
148
+
149
+ ensureConfigDir();
150
+ fs.writeFileSync(MACHINE_ID_FILE, machineId, { mode: 0o600 });
151
+ return machineId;
108
152
  }
109
153
 
110
- function isRunning(pid) {
111
- try { process.kill(pid, 0); return true; } catch { return false; }
154
+ function getMachineName() {
155
+ return os.hostname();
112
156
  }
113
157
 
114
- // ─── Commands ─────────────────────────────────────────────────────────────────
158
+ // ============================================
159
+ // Config management
160
+ // ============================================
161
+ function ensureConfigDir() {
162
+ if (!fs.existsSync(CONFIG_DIR)) {
163
+ fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
164
+ }
165
+ }
115
166
 
116
- async function cmdInstall() {
117
- printBanner();
118
- log('Starting installation...');
119
- console.log('');
167
+ function saveConfig(config) {
168
+ ensureConfigDir();
169
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 });
170
+ logSuccess(`Configuration saved to: ${CONFIG_FILE}`);
171
+ }
120
172
 
121
- checkNodeVersion();
173
+ function loadConfig() {
174
+ if (fs.existsSync(CONFIG_FILE)) {
175
+ try {
176
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
177
+ } catch (error) {
178
+ return null;
179
+ }
180
+ }
181
+ return null;
182
+ }
122
183
 
123
- // Read or create config
124
- let cfg = loadConfig();
184
+ function isConfigured() {
185
+ const config = loadConfig();
186
+ return config && config.authorizationToken && config.tenantId;
187
+ }
125
188
 
126
- // Prompt for API Key if not set
127
- if (!cfg.apiKey) {
128
- console.log(`${YELLOW}You need an API Key from the Insptech AI platform.${RESET}`);
129
- console.log(`Open ${bold('https://app.insptech.pt')} Settings → Agent → Generate API Key`);
130
- console.log('');
131
- const key = await ask(' Enter your API Key: ');
132
- if (!key || key.length < 10) {
133
- err('Invalid API Key. Installation aborted.');
134
- process.exit(1);
135
- }
136
- cfg.apiKey = key;
137
- } else {
138
- ok('API Key already configured');
189
+ // ============================================
190
+ // Permission definitions
191
+ // ============================================
192
+ const PERMISSIONS = [
193
+ { id: 'read_files', name: 'Read Files', description: 'Read source code files in your workspaces', required: true },
194
+ { id: 'write_config', name: 'Write Configuration', description: 'Create configuration files in ~/.deepdebug', required: true },
195
+ { id: 'send_snippets', name: 'Send Code Snippets', description: 'Send code snippets to DeepDebug for AI analysis (encrypted)', required: true },
196
+ { id: 'execute_build', name: 'Execute Build Commands', description: 'Run build commands (maven, npm, gradle)', required: false },
197
+ { id: 'apply_patches', name: 'Apply Code Patches', description: 'Apply AI-suggested code fixes to your files', required: false },
198
+ { id: 'git_operations', name: 'Git Operations', description: 'Perform git operations (commit, branch)', required: false },
199
+ { id: 'create_pr', name: 'Create Pull Requests', description: 'Create pull requests with suggested fixes', required: false }
200
+ ];
201
+
202
+ // ============================================
203
+ // Authorization display
204
+ // ============================================
205
+ function displayAuthorizationTerms() {
206
+ console.log();
207
+ log('╔══════════════════════════════════════════════════════════════════╗', 'yellow');
208
+ log('║ AUTHORIZATION REQUEST ║', 'yellow');
209
+ log('╚══════════════════════════════════════════════════════════════════╝', 'yellow');
210
+ console.log();
211
+ log('The DeepDebug Local Agent needs your permission to:', 'bright');
212
+ console.log();
213
+ log('Required permissions:', 'cyan');
214
+ PERMISSIONS.filter(p => p.required).forEach(p => {
215
+ log(` ✓ ${p.name}`, 'green');
216
+ log(` ${p.description}`, 'reset');
217
+ });
218
+ console.log();
219
+ log('Optional permissions (you can enable/disable later):', 'cyan');
220
+ PERMISSIONS.filter(p => !p.required).forEach(p => {
221
+ log(` ○ ${p.name}`, 'yellow');
222
+ log(` ${p.description}`, 'reset');
223
+ });
224
+ console.log();
225
+ log('Security notes:', 'magenta');
226
+ log(' • Your code is only analyzed when errors occur', 'reset');
227
+ log(' • Code snippets are encrypted in transit (TLS 1.3)', 'reset');
228
+ log(' • You can revoke this authorization at any time', 'reset');
229
+ log(' • Full audit trail is maintained in your dashboard', 'reset');
230
+ console.log();
231
+ log('Privacy Policy: https://deepdebug.ai/privacy', 'blue');
232
+ log('Terms of Service: https://deepdebug.ai/terms', 'blue');
233
+ console.log();
234
+ }
235
+
236
+ async function selectPermissions() {
237
+ const granted = PERMISSIONS.filter(p => p.required).map(p => p.id);
238
+ console.log();
239
+ log('Select optional permissions:', 'cyan');
240
+ console.log();
241
+ for (const perm of PERMISSIONS.filter(p => !p.required)) {
242
+ const enable = await promptYesNo(` Enable "${perm.name}"?`, false);
243
+ if (enable) granted.push(perm.id);
139
244
  }
245
+ return granted;
246
+ }
140
247
 
141
- if (!cfg.gatewayUrl) cfg.gatewayUrl = DEFAULT_GATEWAY;
142
- if (!cfg.port) cfg.port = DEFAULT_PORT;
248
+ // ============================================
249
+ // API functions
250
+ // ============================================
251
+ async function authorizeWithServer(apiKey, permissions) {
252
+ const machineId = getMachineId();
253
+ const machineName = getMachineName();
254
+ const osType = getOsType();
255
+ const osVersion = getOsVersion();
256
+ const agentVersion = getAgentVersion();
143
257
 
144
- saveConfig(cfg);
145
- ok(`Configuration saved to ${CONFIG_FILE}`);
258
+ logInfo('Requesting authorization from DeepDebug server...');
146
259
 
147
- // Install npm dependencies if needed
148
- const nodeModules = join(ROOT_DIR, 'node_modules');
149
- if (!existsSync(nodeModules)) {
150
- log('Installing dependencies...');
151
- try {
152
- execSync('npm install --production', { cwd: ROOT_DIR, stdio: 'inherit' });
153
- ok('Dependencies installed');
154
- } catch (e) {
155
- warn('Could not install dependencies automatically. Run: npm install');
260
+ try {
261
+ const response = await fetch(`${API_BASE_URL}/api/v1/agent/authorize`, {
262
+ method: 'POST',
263
+ headers: { 'Content-Type': 'application/json' },
264
+ body: JSON.stringify({
265
+ apiKey,
266
+ machineId,
267
+ machineName,
268
+ osType,
269
+ osVersion,
270
+ permissions,
271
+ workspacePaths: [], // Empty - workspaces configured in dashboard
272
+ agentVersion
273
+ })
274
+ });
275
+ const data = await response.json();
276
+ if (data.success) {
277
+ return { success: true, ...data };
156
278
  }
157
- } else {
158
- ok('Dependencies already installed');
279
+ return { success: false, error: data.message || 'Authorization failed' };
280
+ } catch (error) {
281
+ return { success: false, error: `Failed to connect: ${error.message}` };
159
282
  }
283
+ }
284
+
285
+ async function validateAuthorization(token, machineId) {
286
+ try {
287
+ const response = await fetch(`${API_BASE_URL}/api/v1/agent/validate`, {
288
+ method: 'POST',
289
+ headers: { 'Content-Type': 'application/json' },
290
+ body: JSON.stringify({ authorizationToken: token, machineId })
291
+ });
292
+ return await response.json();
293
+ } catch (error) {
294
+ return { valid: false, message: error.message };
295
+ }
296
+ }
160
297
 
161
- console.log('');
298
+ function getAgentVersion() {
299
+ try {
300
+ const pkgPath = path.join(path.dirname(new URL(import.meta.url).pathname), '..', 'package.json');
301
+ return JSON.parse(fs.readFileSync(pkgPath, 'utf-8')).version || '1.0.0';
302
+ } catch { return '1.0.0'; }
303
+ }
162
304
 
163
- // Start the agent
164
- await cmdStart(cfg);
305
+ // ============================================
306
+ // Docker functions
307
+ // ============================================
308
+ function isDockerInstalled() {
309
+ try { execSync('docker --version', { stdio: 'ignore' }); return true; } catch { return false; }
310
+ }
165
311
 
166
- console.log('');
167
- ok(bold('Agent registered successfully'));
168
- console.log('');
169
- log(`Health check: ${bold(`http://localhost:${cfg.port}/health`)}`);
170
- log(`Platform: ${bold('https://app.insptech.pt')}`);
171
- console.log('');
312
+ function isDockerRunning() {
313
+ try { execSync('docker info', { stdio: 'ignore' }); return true; } catch { return false; }
172
314
  }
173
315
 
174
- async function cmdStart(cfg) {
175
- cfg = cfg || loadConfig();
316
+ function isAgentContainerRunning() {
317
+ try {
318
+ const result = execSync('docker ps --filter "name=deepdebug-agent" --format "{{.Names}}"', { encoding: 'utf-8' });
319
+ return result.trim() === 'deepdebug-agent';
320
+ } catch { return false; }
321
+ }
176
322
 
177
- const pid = readPid();
178
- if (pid && isRunning(pid)) {
179
- ok(`Agent is already running (PID ${pid})`);
180
- return;
323
+ function startDockerAgent(config) {
324
+ try {
325
+ try { execSync('docker stop deepdebug-agent', { stdio: 'ignore' }); execSync('docker rm deepdebug-agent', { stdio: 'ignore' }); } catch {}
326
+ logInfo('Pulling latest DeepDebug Local Agent image...');
327
+ execSync(`docker pull ${DOCKER_IMAGE}`, { stdio: 'inherit' });
328
+
329
+ let configMount = CONFIG_DIR;
330
+ if (isWindows) {
331
+ configMount = configMount.replace(/\\/g, '/').replace(/^([A-Za-z]):/, '/$1');
332
+ }
333
+
334
+ const dockerArgs = [
335
+ 'run', '-d', '--name', 'deepdebug-agent', '-p', '5055:5055',
336
+ '-v', `${configMount}:/home/deepdebug/.deepdebug:ro`,
337
+ '-e', `DEEPDEBUG_GATEWAY_URL=${config.gatewayUrl}`,
338
+ '-e', `DEEPDEBUG_TENANT_ID=${config.tenantId}`,
339
+ '-e', `DEEPDEBUG_AUTH_TOKEN=${config.authorizationToken}`,
340
+ '--restart', 'unless-stopped', DOCKER_IMAGE
341
+ ];
342
+ execSync(`docker ${dockerArgs.join(' ')}`, { stdio: 'inherit' });
343
+ logSuccess('DeepDebug Local Agent started successfully!');
344
+ return true;
345
+ } catch (error) {
346
+ logError(`Failed to start Docker container: ${error.message}`);
347
+ return false;
181
348
  }
349
+ }
182
350
 
183
- const serverScript = getServerScript();
184
- if (!existsSync(serverScript)) {
185
- err(`Server script not found: ${serverScript}`);
186
- process.exit(1);
351
+ // ============================================
352
+ // Commands
353
+ // ============================================
354
+ async function install(options = {}) {
355
+ console.log();
356
+ log('╔══════════════════════════════════════════════════════════════════╗', 'magenta');
357
+ log('║ DeepDebug Local Agent - Installation ║', 'magenta');
358
+ log('╚══════════════════════════════════════════════════════════════════╝', 'magenta');
359
+ console.log();
360
+ logInfo(`Detected OS: ${getOsType()} (${getOsVersion()})`);
361
+ logInfo(`Machine: ${getMachineName()}`);
362
+ console.log();
363
+
364
+ if (isConfigured() && !options.force) {
365
+ const config = loadConfig();
366
+ logInfo(`Already configured for tenant: ${config.tenantName || config.tenantId}`);
367
+ if (!await promptYesNo('Do you want to reconfigure?', false)) {
368
+ logInfo('Run "npx deepdebug-local-agent start" to start the agent.');
369
+ return;
370
+ }
187
371
  }
188
372
 
189
- log(`Starting agent on port ${cfg.port || DEFAULT_PORT}...`);
373
+ let apiKey = options.apiKey;
374
+ if (!apiKey) {
375
+ console.log();
376
+ log('┌────────────────────────────────────────────────────────────────┐', 'cyan');
377
+ log('│ Find your API Key: DeepDebug Dashboard → Settings → Agent │', 'cyan');
378
+ log('└────────────────────────────────────────────────────────────────┘', 'cyan');
379
+ console.log();
380
+ apiKey = await prompt('Enter your API Key: ');
381
+ }
382
+ if (!apiKey) { logError('API Key is required'); process.exit(1); }
190
383
 
191
- const env = {
192
- ...process.env,
193
- NODE_ENV: 'production',
194
- PORT: String(cfg.port || DEFAULT_PORT),
195
- DEEPDEBUG_API_KEY: cfg.apiKey || '',
196
- GATEWAY_URL: cfg.gatewayUrl || DEFAULT_GATEWAY,
384
+ displayAuthorizationTerms();
385
+ if (!await promptYesNo('Do you authorize the DeepDebug Local Agent with these permissions?', false)) {
386
+ logWarning('Authorization declined. Installation cancelled.');
387
+ process.exit(0);
388
+ }
389
+
390
+ const permissions = await selectPermissions();
391
+ logInfo(`Selected permissions: ${permissions.join(', ')}`);
392
+
393
+ console.log();
394
+ const authResult = await authorizeWithServer(apiKey, permissions);
395
+ if (!authResult.success) { logError(`Authorization failed: ${authResult.error}`); process.exit(1); }
396
+ logSuccess(`Authorized for tenant: ${authResult.tenantName}`);
397
+
398
+ const config = {
399
+ authorizationToken: authResult.authorizationToken,
400
+ authorizationId: authResult.authorizationId,
401
+ tenantId: authResult.tenantId,
402
+ tenantName: authResult.tenantName,
403
+ gatewayUrl: authResult.gatewayUrl,
404
+ websocketUrl: authResult.websocketUrl,
405
+ permissions,
406
+ machineId: getMachineId(),
407
+ machineName: getMachineName(),
408
+ osType: getOsType(),
409
+ installedAt: new Date().toISOString(),
410
+ expiresAt: authResult.expiresAt
197
411
  };
412
+ saveConfig(config);
413
+
414
+ console.log();
415
+ logInfo('Checking Docker installation...');
416
+ if (!isDockerInstalled()) {
417
+ logWarning('Docker is not installed.');
418
+ log(`Please install Docker Desktop: https://www.docker.com/products/docker-desktop/`, 'blue');
419
+ logInfo('After installing, run: npx deepdebug-local-agent start');
420
+ return;
421
+ }
422
+ if (!isDockerRunning()) {
423
+ logWarning('Docker is not running. Please start Docker Desktop.');
424
+ logInfo('Then run: npx deepdebug-local-agent start');
425
+ return;
426
+ }
427
+ logSuccess('Docker is installed and running');
198
428
 
199
- const child = spawn(process.execPath, [serverScript], {
200
- env,
201
- detached: true,
202
- stdio: 'ignore',
203
- cwd: ROOT_DIR,
204
- });
429
+ console.log();
430
+ if (await promptYesNo('Start DeepDebug Local Agent now?', true)) {
431
+ await start();
432
+ } else {
433
+ logInfo('To start later: npx deepdebug-local-agent start');
434
+ }
205
435
 
206
- child.unref();
436
+ console.log();
437
+ log('╔══════════════════════════════════════════════════════════════════╗', 'green');
438
+ log('║ Installation Complete! 🎉 ║', 'green');
439
+ log('╚══════════════════════════════════════════════════════════════════╝', 'green');
440
+ console.log();
441
+ logInfo('Workspaces can be configured in the DeepDebug dashboard.');
442
+ log('Dashboard: https://deepdebug.ai/settings/agent', 'blue');
443
+ console.log();
444
+ }
207
445
 
208
- // Save PID
209
- if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
210
- writeFileSync(getPidFile(), String(child.pid), 'utf8');
446
+ async function start() {
447
+ console.log();
448
+ log('🚀 Starting DeepDebug Local Agent...', 'cyan');
449
+ const config = loadConfig();
450
+ if (!config?.authorizationToken) { logError('Not configured. Run "npx deepdebug-local-agent install" first.'); process.exit(1); }
211
451
 
212
- // Wait a moment and verify
213
- await new Promise(r => setTimeout(r, 1500));
452
+ logInfo('Validating authorization...');
453
+ const validation = await validateAuthorization(config.authorizationToken, config.machineId);
454
+ if (!validation.valid) { logError(`Authorization invalid: ${validation.message}`); process.exit(1); }
455
+ logSuccess('Authorization valid');
214
456
 
215
- if (isRunning(child.pid)) {
216
- ok(`Agent started (PID ${child.pid})`);
217
- } else {
218
- warn('Agent may have failed to start. Check logs or run in foreground: npx deepdebug-local-agent run');
457
+ if (!isDockerInstalled()) { logError('Docker is not installed.'); process.exit(1); }
458
+ if (!isDockerRunning()) { logError('Docker is not running.'); process.exit(1); }
459
+
460
+ if (isAgentContainerRunning()) {
461
+ logWarning('Agent is already running.');
462
+ if (!await promptYesNo('Restart it?', false)) return;
219
463
  }
220
- }
221
464
 
222
- function cmdStop() {
223
- const pid = readPid();
224
- if (!pid) { warn('No PID file found. Agent may not be running.'); return; }
225
- if (!isRunning(pid)) { warn(`No process with PID ${pid}. Agent is not running.`); return; }
226
- try {
227
- process.kill(pid, 'SIGTERM');
228
- ok(`Agent stopped (PID ${pid})`);
229
- } catch (e) {
230
- err(`Failed to stop agent: ${e.message}`);
465
+ if (startDockerAgent(config)) {
466
+ console.log();
467
+ logSuccess('Agent is running!');
468
+ logInfo('Dashboard: https://deepdebug.ai/monitoring');
469
+ logInfo('Local API: http://localhost:5055/health');
470
+ logInfo('Logs: docker logs -f deepdebug-agent');
231
471
  }
232
472
  }
233
473
 
234
- function cmdStatus() {
235
- const cfg = loadConfig();
236
- const pid = readPid();
237
- const port = cfg.port || DEFAULT_PORT;
238
- console.log('');
239
- console.log(`${BOLD}DeepDebug Local Agent — Status${RESET}`);
240
- console.log('─'.repeat(40));
241
- if (pid && isRunning(pid)) {
242
- console.log(` Process : ${GREEN}Running${RESET} (PID ${pid})`);
474
+ async function stop() {
475
+ log('🛑 Stopping DeepDebug Local Agent...', 'yellow');
476
+ try { execSync('docker stop deepdebug-agent', { stdio: 'inherit' }); logSuccess('Agent stopped.'); }
477
+ catch { logWarning('Agent is not running.'); }
478
+ }
479
+
480
+ async function status() {
481
+ console.log();
482
+ log('╔══════════════════════════════════════════════════════════════════╗', 'cyan');
483
+ log('║ DeepDebug Local Agent - Status ║', 'cyan');
484
+ log('╚══════════════════════════════════════════════════════════════════╝', 'cyan');
485
+ console.log();
486
+ logInfo(`OS: ${getOsType()} | Machine: ${getMachineName()}`);
487
+
488
+ const config = loadConfig();
489
+ if (config) {
490
+ logSuccess(`Configured for: ${config.tenantName || config.tenantId}`);
491
+ logInfo(`Permissions: ${config.permissions?.join(', ') || 'N/A'}`);
492
+ } else {
493
+ logWarning('Not configured. Run: npx deepdebug-local-agent install');
494
+ }
495
+
496
+ if (isDockerInstalled() && isDockerRunning() && isAgentContainerRunning()) {
497
+ logSuccess('Agent: Running');
498
+ try {
499
+ const resp = await fetch('http://localhost:5055/health');
500
+ logSuccess(resp.ok ? 'Health: OK' : 'Health: Degraded');
501
+ } catch { logWarning('Health: Cannot connect'); }
243
502
  } else {
244
- console.log(` Process : ${RED}Not running${RESET}`);
503
+ logWarning('Agent: Not running');
245
504
  }
246
- console.log(` Port : ${port}`);
247
- console.log(` Config : ${CONFIG_FILE}`);
248
- console.log(` Gateway : ${cfg.gatewayUrl || DEFAULT_GATEWAY}`);
249
- console.log(` Health : http://localhost:${port}/health`);
250
- console.log('');
505
+ console.log();
251
506
  }
252
507
 
253
- function cmdRun() {
254
- // Foreground mode useful for debugging
255
- const cfg = loadConfig();
256
- const serverScript = getServerScript();
257
- const env = {
258
- ...process.env,
259
- NODE_ENV: 'development',
260
- PORT: String(cfg.port || DEFAULT_PORT),
261
- DEEPDEBUG_API_KEY: cfg.apiKey || '',
262
- GATEWAY_URL: cfg.gatewayUrl || DEFAULT_GATEWAY,
263
- };
264
- log(`Running in foreground on port ${cfg.port || DEFAULT_PORT} (Ctrl+C to stop)...`);
265
- const child = spawn(process.execPath, [serverScript], { env, stdio: 'inherit', cwd: ROOT_DIR });
266
- child.on('exit', code => process.exit(code || 0));
508
+ async function logs() {
509
+ spawn('docker', ['logs', '-f', 'deepdebug-agent'], { stdio: 'inherit' });
510
+ }
511
+
512
+ async function uninstall() {
513
+ if (!await promptYesNo('Uninstall DeepDebug Local Agent?', false)) return;
514
+ try { execSync('docker stop deepdebug-agent', { stdio: 'ignore' }); execSync('docker rm deepdebug-agent', { stdio: 'ignore' }); } catch {}
515
+ if (fs.existsSync(CONFIG_FILE)) fs.unlinkSync(CONFIG_FILE);
516
+ if (fs.existsSync(MACHINE_ID_FILE)) fs.unlinkSync(MACHINE_ID_FILE);
517
+ try { fs.rmdirSync(CONFIG_DIR); } catch {}
518
+ logSuccess('DeepDebug Local Agent uninstalled.');
267
519
  }
268
520
 
269
- // ─── Entry point ──────────────────────────────────────────────────────────────
521
+ function showHelp() {
522
+ console.log(`
523
+ DeepDebug Local Agent - AI-powered debugging assistant
524
+
525
+ Usage: npx deepdebug-local-agent <command>
526
+
527
+ Commands:
528
+ install Configure and authorize the agent
529
+ start Start the agent
530
+ stop Stop the agent
531
+ status Show agent status
532
+ logs Show agent logs
533
+ uninstall Remove the agent
534
+
535
+ Options:
536
+ --api-key=KEY Provide API key
537
+ --force Force reconfiguration
538
+ --help Show this help
539
+
540
+ Documentation: https://docs.deepdebug.ai
541
+ `);
542
+ }
270
543
 
271
- const command = process.argv[2] || 'install';
544
+ // ============================================
545
+ // Main
546
+ // ============================================
547
+ const args = process.argv.slice(2);
548
+ const command = args[0];
549
+ const options = {};
550
+ args.forEach(arg => {
551
+ if (arg.startsWith('--api-key=')) options.apiKey = arg.split('=')[1];
552
+ if (arg === '--force') options.force = true;
553
+ });
272
554
 
273
555
  switch (command) {
274
- case 'install': cmdInstall().catch(e => { err(e.message); process.exit(1); }); break;
275
- case 'start': cmdStart().catch(e => { err(e.message); process.exit(1); }); break;
276
- case 'stop': cmdStop(); break;
277
- case 'status': cmdStatus(); break;
278
- case 'run': cmdRun(); break;
279
- default:
280
- console.log(`Usage: npx deepdebug-local-agent [install|start|stop|status|run]`);
281
- process.exit(1);
556
+ case 'install': install(options); break;
557
+ case 'start': start(); break;
558
+ case 'stop': stop(); break;
559
+ case 'status': status(); break;
560
+ case 'logs': logs(); break;
561
+ case 'uninstall': uninstall(); break;
562
+ case 'help': case '--help': case '-h': showHelp(); break;
563
+ default: if (command) logError(`Unknown command: ${command}`); showHelp();
282
564
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepdebug-local-agent",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "DeepDebug Local Agent - AI-powered code debugging assistant",
5
5
  "private": false,
6
6
  "type": "module",