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 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
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepdebug-local-agent",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "type": "module",
5
5
  "main": "src/server.js",
6
6
  "scripts": {