deepdebug-local-agent 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/install.js +419 -0
- package/package.json +1 -1
- package/src/git/base-git-provider.js +169 -18
- package/src/git/bitbucket-provider.js +723 -0
- package/src/git/git-provider-registry.js +12 -11
- package/src/git/github-provider.js +299 -6
- package/src/server.js +587 -42
package/bin/install.js
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Insptech AI Local Agent — Installer
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx @insptech/agent install
|
|
7
|
+
*
|
|
8
|
+
* What it does:
|
|
9
|
+
* 1. Asks for API Key (ia_live_...)
|
|
10
|
+
* 2. Validates the key against the platform
|
|
11
|
+
* 3. Installs the agent as a background service
|
|
12
|
+
* 4. Registers the machine on the platform
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { createInterface } from 'readline';
|
|
16
|
+
import { execSync, spawn } from 'child_process';
|
|
17
|
+
import { writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
18
|
+
import { homedir, platform, hostname } from 'os';
|
|
19
|
+
import { join } from 'path';
|
|
20
|
+
|
|
21
|
+
const PLATFORM_URL = process.env.INSPTECH_URL || 'https://gateway.insptech.pt';
|
|
22
|
+
const AGENT_PORT = process.env.AGENT_PORT || '5055';
|
|
23
|
+
const VERSION = '1.0.0';
|
|
24
|
+
|
|
25
|
+
// ============================================
|
|
26
|
+
// COLOURS
|
|
27
|
+
// ============================================
|
|
28
|
+
const c = {
|
|
29
|
+
reset: '\x1b[0m',
|
|
30
|
+
bold: '\x1b[1m',
|
|
31
|
+
green: '\x1b[32m',
|
|
32
|
+
yellow: '\x1b[33m',
|
|
33
|
+
blue: '\x1b[34m',
|
|
34
|
+
red: '\x1b[31m',
|
|
35
|
+
cyan: '\x1b[36m',
|
|
36
|
+
grey: '\x1b[90m',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function log(msg) { console.log(msg); }
|
|
40
|
+
function ok(msg) { console.log(`${c.green}✅ ${msg}${c.reset}`); }
|
|
41
|
+
function warn(msg) { console.log(`${c.yellow}⚠️ ${msg}${c.reset}`); }
|
|
42
|
+
function error(msg) { console.log(`${c.red}❌ ${msg}${c.reset}`); }
|
|
43
|
+
function info(msg) { console.log(`${c.cyan}ℹ️ ${msg}${c.reset}`); }
|
|
44
|
+
function step(n, msg){ console.log(`\n${c.bold}${c.blue}[${n}]${c.reset} ${c.bold}${msg}${c.reset}`); }
|
|
45
|
+
|
|
46
|
+
// ============================================
|
|
47
|
+
// PROMPT HELPER
|
|
48
|
+
// ============================================
|
|
49
|
+
function prompt(question, hidden = false) {
|
|
50
|
+
return new Promise(resolve => {
|
|
51
|
+
const rl = createInterface({
|
|
52
|
+
input: process.stdin,
|
|
53
|
+
output: process.stdout,
|
|
54
|
+
terminal: true
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (hidden) {
|
|
58
|
+
process.stdout.write(question);
|
|
59
|
+
process.stdin.setRawMode?.(true);
|
|
60
|
+
let input = '';
|
|
61
|
+
process.stdin.once('data', function handler(chunk) {
|
|
62
|
+
const char = chunk.toString();
|
|
63
|
+
if (char === '\r' || char === '\n') {
|
|
64
|
+
process.stdin.setRawMode?.(false);
|
|
65
|
+
process.stdout.write('\n');
|
|
66
|
+
rl.close();
|
|
67
|
+
resolve(input);
|
|
68
|
+
} else if (char === '\u0003') {
|
|
69
|
+
process.exit();
|
|
70
|
+
} else if (char === '\u007f') {
|
|
71
|
+
if (input.length > 0) {
|
|
72
|
+
input = input.slice(0, -1);
|
|
73
|
+
process.stdout.write('\b \b');
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
input += char;
|
|
77
|
+
process.stdout.write('*');
|
|
78
|
+
}
|
|
79
|
+
if (char !== '\r' && char !== '\n') {
|
|
80
|
+
process.stdin.once('data', handler);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
} else {
|
|
84
|
+
rl.question(question, answer => {
|
|
85
|
+
rl.close();
|
|
86
|
+
resolve(answer.trim());
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ============================================
|
|
93
|
+
// VALIDATE API KEY FORMAT
|
|
94
|
+
// ============================================
|
|
95
|
+
function isValidKeyFormat(key) {
|
|
96
|
+
return /^ia_live_[a-zA-Z0-9]{24,}$/.test(key);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ============================================
|
|
100
|
+
// VALIDATE API KEY AGAINST PLATFORM
|
|
101
|
+
// ============================================
|
|
102
|
+
async function validateApiKey(apiKey) {
|
|
103
|
+
try {
|
|
104
|
+
const response = await fetch(`${PLATFORM_URL}/api/v1/agent/validate-key`, {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
headers: {
|
|
107
|
+
'Content-Type': 'application/json',
|
|
108
|
+
'X-Agent-Key': apiKey
|
|
109
|
+
},
|
|
110
|
+
body: JSON.stringify({
|
|
111
|
+
hostname: hostname(),
|
|
112
|
+
platform: platform(),
|
|
113
|
+
version: VERSION
|
|
114
|
+
}),
|
|
115
|
+
signal: AbortSignal.timeout(10000)
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (response.ok) {
|
|
119
|
+
const data = await response.json();
|
|
120
|
+
return { valid: true, tenantName: data.tenantName, workspaceName: data.workspaceName };
|
|
121
|
+
} else if (response.status === 401) {
|
|
122
|
+
return { valid: false, reason: 'Invalid API key' };
|
|
123
|
+
} else {
|
|
124
|
+
return { valid: false, reason: `Server returned ${response.status}` };
|
|
125
|
+
}
|
|
126
|
+
} catch (err) {
|
|
127
|
+
// Network error — allow offline install with warning
|
|
128
|
+
warn(`Could not reach platform (${err.message}). Continuing with offline install.`);
|
|
129
|
+
return { valid: true, offline: true };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ============================================
|
|
134
|
+
// SAVE CONFIG
|
|
135
|
+
// ============================================
|
|
136
|
+
function saveConfig(apiKey, port) {
|
|
137
|
+
const configDir = join(homedir(), '.insptech');
|
|
138
|
+
const configFile = join(configDir, 'agent.json');
|
|
139
|
+
|
|
140
|
+
if (!existsSync(configDir)) mkdirSync(configDir, { recursive: true });
|
|
141
|
+
|
|
142
|
+
const config = {
|
|
143
|
+
apiKey,
|
|
144
|
+
port,
|
|
145
|
+
platformUrl: PLATFORM_URL,
|
|
146
|
+
hostname: hostname(),
|
|
147
|
+
installedAt: new Date().toISOString(),
|
|
148
|
+
version: VERSION
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
writeFileSync(configFile, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
152
|
+
return configFile;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ============================================
|
|
156
|
+
// INSTALL AS SERVICE
|
|
157
|
+
// ============================================
|
|
158
|
+
async function installService(apiKey, port) {
|
|
159
|
+
const os = platform();
|
|
160
|
+
|
|
161
|
+
if (os === 'darwin') {
|
|
162
|
+
return installLaunchd(apiKey, port);
|
|
163
|
+
} else if (os === 'linux') {
|
|
164
|
+
return installSystemd(apiKey, port);
|
|
165
|
+
} else if (os === 'win32') {
|
|
166
|
+
return installWindowsService(apiKey, port);
|
|
167
|
+
} else {
|
|
168
|
+
warn(`Unsupported OS (${os}). Agent will not be installed as a service.`);
|
|
169
|
+
info(`To start manually: npx @insptech/agent start --api-key ${apiKey}`);
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function installLaunchd(apiKey, port) {
|
|
175
|
+
try {
|
|
176
|
+
const plistPath = join(homedir(), 'Library/LaunchAgents/pt.insptech.agent.plist');
|
|
177
|
+
const nodePath = execSync('which node').toString().trim();
|
|
178
|
+
const agentPath = new URL('../src/server.js', import.meta.url).pathname;
|
|
179
|
+
|
|
180
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
181
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
182
|
+
<plist version="1.0">
|
|
183
|
+
<dict>
|
|
184
|
+
<key>Label</key>
|
|
185
|
+
<string>pt.insptech.agent</string>
|
|
186
|
+
<key>ProgramArguments</key>
|
|
187
|
+
<array>
|
|
188
|
+
<string>${nodePath}</string>
|
|
189
|
+
<string>${agentPath}</string>
|
|
190
|
+
</array>
|
|
191
|
+
<key>EnvironmentVariables</key>
|
|
192
|
+
<dict>
|
|
193
|
+
<key>INSPTECH_API_KEY</key>
|
|
194
|
+
<string>${apiKey}</string>
|
|
195
|
+
<key>AGENT_PORT</key>
|
|
196
|
+
<string>${port}</string>
|
|
197
|
+
<key>GATEWAY_URL</key>
|
|
198
|
+
<string>${PLATFORM_URL}</string>
|
|
199
|
+
</dict>
|
|
200
|
+
<key>RunAtLoad</key>
|
|
201
|
+
<true/>
|
|
202
|
+
<key>KeepAlive</key>
|
|
203
|
+
<true/>
|
|
204
|
+
<key>StandardOutPath</key>
|
|
205
|
+
<string>${homedir()}/.insptech/agent.log</string>
|
|
206
|
+
<key>StandardErrorPath</key>
|
|
207
|
+
<string>${homedir()}/.insptech/agent-error.log</string>
|
|
208
|
+
</dict>
|
|
209
|
+
</plist>`;
|
|
210
|
+
|
|
211
|
+
writeFileSync(plistPath, plist);
|
|
212
|
+
execSync(`launchctl load ${plistPath}`);
|
|
213
|
+
ok(`macOS LaunchAgent installed: ${plistPath}`);
|
|
214
|
+
return true;
|
|
215
|
+
} catch (err) {
|
|
216
|
+
warn(`Could not install as service: ${err.message}`);
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function installSystemd(apiKey, port) {
|
|
222
|
+
try {
|
|
223
|
+
const nodePath = execSync('which node').toString().trim();
|
|
224
|
+
const agentPath = new URL('../src/server.js', import.meta.url).pathname;
|
|
225
|
+
const serviceFile = `/etc/systemd/system/insptech-agent.service`;
|
|
226
|
+
|
|
227
|
+
const service = `[Unit]
|
|
228
|
+
Description=Insptech AI Local Agent
|
|
229
|
+
After=network.target
|
|
230
|
+
|
|
231
|
+
[Service]
|
|
232
|
+
Type=simple
|
|
233
|
+
ExecStart=${nodePath} ${agentPath}
|
|
234
|
+
Environment=INSPTECH_API_KEY=${apiKey}
|
|
235
|
+
Environment=AGENT_PORT=${port}
|
|
236
|
+
Environment=GATEWAY_URL=${PLATFORM_URL}
|
|
237
|
+
Restart=on-failure
|
|
238
|
+
RestartSec=10
|
|
239
|
+
StandardOutput=journal
|
|
240
|
+
StandardError=journal
|
|
241
|
+
|
|
242
|
+
[Install]
|
|
243
|
+
WantedBy=multi-user.target
|
|
244
|
+
`;
|
|
245
|
+
writeFileSync(serviceFile, service);
|
|
246
|
+
execSync('systemctl daemon-reload');
|
|
247
|
+
execSync('systemctl enable insptech-agent');
|
|
248
|
+
execSync('systemctl start insptech-agent');
|
|
249
|
+
ok('systemd service installed and started: insptech-agent');
|
|
250
|
+
return true;
|
|
251
|
+
} catch (err) {
|
|
252
|
+
warn(`Could not install systemd service: ${err.message}`);
|
|
253
|
+
info('Try running with sudo for system-wide service installation.');
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function installWindowsService(apiKey, port) {
|
|
259
|
+
try {
|
|
260
|
+
const nodePath = execSync('where node').toString().trim().split('\n')[0];
|
|
261
|
+
const agentPath = new URL('../src/server.js', import.meta.url).pathname.replace(/^\//, '');
|
|
262
|
+
|
|
263
|
+
// Use sc.exe to create Windows Service
|
|
264
|
+
const cmd = `sc create "Insptech AI Agent" binPath= "${nodePath} ${agentPath}" start= auto DisplayName= "Insptech AI Agent"`;
|
|
265
|
+
execSync(cmd, { shell: 'cmd.exe' });
|
|
266
|
+
execSync('sc start "Insptech AI Agent"', { shell: 'cmd.exe' });
|
|
267
|
+
ok('Windows Service installed: "Insptech AI Agent" (visible in services.msc)');
|
|
268
|
+
return true;
|
|
269
|
+
} catch (err) {
|
|
270
|
+
warn(`Could not install Windows Service: ${err.message}`);
|
|
271
|
+
info('Try running PowerShell as Administrator.');
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ============================================
|
|
277
|
+
// MAIN
|
|
278
|
+
// ============================================
|
|
279
|
+
async function main() {
|
|
280
|
+
const args = process.argv.slice(2);
|
|
281
|
+
const command = args[0] || 'install';
|
|
282
|
+
|
|
283
|
+
if (command === 'start') {
|
|
284
|
+
// Direct start (used by service managers)
|
|
285
|
+
const { default: startServer } = await import('../src/server.js');
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (command !== 'install') {
|
|
290
|
+
log(`\nUsage: npx @insptech/agent install\n`);
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ============================================
|
|
295
|
+
// BANNER
|
|
296
|
+
// ============================================
|
|
297
|
+
log('');
|
|
298
|
+
log(`${c.bold}${c.blue}╔════════════════════════════════════════╗${c.reset}`);
|
|
299
|
+
log(`${c.bold}${c.blue}║ Insptech AI — Local Agent ║${c.reset}`);
|
|
300
|
+
log(`${c.bold}${c.blue}║ Installation Wizard ║${c.reset}`);
|
|
301
|
+
log(`${c.bold}${c.blue}╚════════════════════════════════════════╝${c.reset}`);
|
|
302
|
+
log(`${c.grey} version ${VERSION} · insptech.pt${c.reset}`);
|
|
303
|
+
log('');
|
|
304
|
+
|
|
305
|
+
// ============================================
|
|
306
|
+
// STEP 1 — CHECK NODE VERSION
|
|
307
|
+
// ============================================
|
|
308
|
+
step(1, 'Checking requirements');
|
|
309
|
+
const nodeVersion = process.versions.node;
|
|
310
|
+
const major = parseInt(nodeVersion.split('.')[0]);
|
|
311
|
+
if (major < 18) {
|
|
312
|
+
error(`Node.js 18+ required. Current: ${nodeVersion}`);
|
|
313
|
+
info('Download from: https://nodejs.org');
|
|
314
|
+
process.exit(1);
|
|
315
|
+
}
|
|
316
|
+
ok(`Node.js ${nodeVersion}`);
|
|
317
|
+
|
|
318
|
+
// ============================================
|
|
319
|
+
// STEP 2 — API KEY
|
|
320
|
+
// ============================================
|
|
321
|
+
step(2, 'API Key');
|
|
322
|
+
log(`${c.grey} Get your key: app.insptech.pt → Settings → Agent → Generate API Key${c.reset}`);
|
|
323
|
+
log('');
|
|
324
|
+
|
|
325
|
+
let apiKey = '';
|
|
326
|
+
let attempts = 0;
|
|
327
|
+
|
|
328
|
+
while (attempts < 3) {
|
|
329
|
+
apiKey = await prompt(' Enter your API Key: ', true);
|
|
330
|
+
|
|
331
|
+
if (!apiKey) {
|
|
332
|
+
error('API Key cannot be empty.');
|
|
333
|
+
attempts++;
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (!isValidKeyFormat(apiKey)) {
|
|
338
|
+
error(`Invalid format. Key must start with "ia_live_" followed by 24+ characters.`);
|
|
339
|
+
attempts++;
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (!apiKey || !isValidKeyFormat(apiKey)) {
|
|
347
|
+
error('Installation cancelled: invalid API Key.');
|
|
348
|
+
process.exit(1);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ============================================
|
|
352
|
+
// STEP 3 — VALIDATE KEY
|
|
353
|
+
// ============================================
|
|
354
|
+
step(3, 'Validating API Key');
|
|
355
|
+
process.stdout.write(' Connecting to Insptech AI platform...');
|
|
356
|
+
|
|
357
|
+
const validation = await validateApiKey(apiKey);
|
|
358
|
+
|
|
359
|
+
if (!validation.valid) {
|
|
360
|
+
log('');
|
|
361
|
+
error(`API Key validation failed: ${validation.reason}`);
|
|
362
|
+
info('Generate a new key at: app.insptech.pt → Settings → Agent');
|
|
363
|
+
process.exit(1);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
log(` ${c.green}OK${c.reset}`);
|
|
367
|
+
|
|
368
|
+
if (!validation.offline) {
|
|
369
|
+
if (validation.tenantName) ok(`Authenticated as: ${validation.tenantName}`);
|
|
370
|
+
if (validation.workspaceName) info(`Workspace: ${validation.workspaceName}`);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ============================================
|
|
374
|
+
// STEP 4 — PORT
|
|
375
|
+
// ============================================
|
|
376
|
+
step(4, 'Configuration');
|
|
377
|
+
const portAnswer = await prompt(` Agent port [${AGENT_PORT}]: `);
|
|
378
|
+
const port = portAnswer || AGENT_PORT;
|
|
379
|
+
ok(`Port: ${port}`);
|
|
380
|
+
|
|
381
|
+
// ============================================
|
|
382
|
+
// STEP 5 — SAVE CONFIG
|
|
383
|
+
// ============================================
|
|
384
|
+
step(5, 'Saving configuration');
|
|
385
|
+
const configFile = saveConfig(apiKey, port);
|
|
386
|
+
ok(`Config saved: ${configFile}`);
|
|
387
|
+
|
|
388
|
+
// ============================================
|
|
389
|
+
// STEP 6 — INSTALL SERVICE
|
|
390
|
+
// ============================================
|
|
391
|
+
step(6, 'Installing service');
|
|
392
|
+
const serviceInstalled = await installService(apiKey, port);
|
|
393
|
+
|
|
394
|
+
// ============================================
|
|
395
|
+
// DONE
|
|
396
|
+
// ============================================
|
|
397
|
+
log('');
|
|
398
|
+
log(`${c.bold}${c.green}╔════════════════════════════════════════╗${c.reset}`);
|
|
399
|
+
log(`${c.bold}${c.green}║ Installation Complete! 🎉 ║${c.reset}`);
|
|
400
|
+
log(`${c.bold}${c.green}╚════════════════════════════════════════╝${c.reset}`);
|
|
401
|
+
log('');
|
|
402
|
+
|
|
403
|
+
if (serviceInstalled) {
|
|
404
|
+
ok(`Agent is running on port ${port}`);
|
|
405
|
+
info(`Logs: ${homedir()}/.insptech/agent.log`);
|
|
406
|
+
} else {
|
|
407
|
+
info(`To start manually: node src/server.js`);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
log('');
|
|
411
|
+
log(`${c.grey} Platform: ${PLATFORM_URL}${c.reset}`);
|
|
412
|
+
log(`${c.grey} Docs: https://docs.insptech.pt/agent${c.reset}`);
|
|
413
|
+
log('');
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
main().catch(err => {
|
|
417
|
+
error(`Unexpected error: ${err.message}`);
|
|
418
|
+
process.exit(1);
|
|
419
|
+
});
|