agent-recon 1.0.1
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/.claude/hooks/send-event-wsl.py +339 -0
- package/.claude/hooks/send-event.py +334 -0
- package/CHANGELOG.md +66 -0
- package/CONTRIBUTING.md +70 -0
- package/EULA.md +223 -0
- package/INSTALL.md +193 -0
- package/LICENSE +287 -0
- package/LICENSE-COMMERCIAL +241 -0
- package/PRIVACY.md +115 -0
- package/README.md +182 -0
- package/SECURITY.md +63 -0
- package/TERMS.md +233 -0
- package/install-service.ps1 +302 -0
- package/installer/cli.js +177 -0
- package/installer/detect.js +355 -0
- package/installer/install.js +195 -0
- package/installer/manifest.js +140 -0
- package/installer/package.json +12 -0
- package/installer/steps/api-keys.js +59 -0
- package/installer/steps/directory.js +41 -0
- package/installer/steps/env-report.js +48 -0
- package/installer/steps/hooks.js +149 -0
- package/installer/steps/service.js +159 -0
- package/installer/steps/tls.js +104 -0
- package/installer/steps/verify.js +117 -0
- package/installer/steps/welcome.js +46 -0
- package/installer/ui.js +133 -0
- package/installer/uninstall.js +233 -0
- package/installer/upgrade.js +289 -0
- package/package.json +58 -0
- package/public/index.html +13953 -0
- package/server/fixtures/allowlist-profiles.json +185 -0
- package/server/package.json +34 -0
- package/server/platform.js +270 -0
- package/server/rules/gitleaks.toml +3214 -0
- package/server/rules/security.yara +579 -0
- package/server/start.js +178 -0
- package/service/agent-recon.service +30 -0
- package/service/com.agent-recon.server.plist +56 -0
- package/setup-linux.sh +259 -0
- package/setup-macos.sh +264 -0
- package/setup-wsl.sh +248 -0
- package/setup.ps1 +171 -0
- package/start-agent-recon.bat +4 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
// Copyright 2026 PNW Great Loop LLC. All rights reserved.
|
|
2
|
+
// Licensed under the Agent Recon™ Commercial License — see LICENSE-COMMERCIAL.
|
|
3
|
+
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Agent Recon Installer — Upgrade Flow (Task 8.3)
|
|
8
|
+
*
|
|
9
|
+
* Reads manifest → backup → stop service → update hooks → re-merge settings →
|
|
10
|
+
* update service → restart → verify → update manifest.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { execSync } = require('child_process');
|
|
16
|
+
const ui = require('./ui');
|
|
17
|
+
const manifest = require('./manifest');
|
|
18
|
+
const hooks = require('./steps/hooks');
|
|
19
|
+
const verify = require('./steps/verify');
|
|
20
|
+
const platform = require('../server/platform');
|
|
21
|
+
const crypto = require('crypto');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Run the upgrade flow.
|
|
25
|
+
* @param {object} env — from detect.detectEnv()
|
|
26
|
+
* @param {object} opts — {version, force}
|
|
27
|
+
*/
|
|
28
|
+
async function upgrade(env, opts) {
|
|
29
|
+
const newVersion = opts.version || '1.0.0';
|
|
30
|
+
const force = opts.force || false;
|
|
31
|
+
|
|
32
|
+
ui.banner(newVersion);
|
|
33
|
+
ui.step(1, 6, 'Reading existing installation');
|
|
34
|
+
|
|
35
|
+
// ── Read manifest ──────────────────────────────────────────────────────
|
|
36
|
+
const m = manifest.readManifest();
|
|
37
|
+
if (!m) {
|
|
38
|
+
if (env.existingInstall && !env.existingInstall.manifestPath) {
|
|
39
|
+
ui.warn('Existing installation detected but no manifest found.');
|
|
40
|
+
ui.info('Run "node cli.js install" for a fresh install that creates a manifest.');
|
|
41
|
+
} else {
|
|
42
|
+
ui.error('No existing Agent Recon installation found.');
|
|
43
|
+
ui.info('Run "node cli.js install" for a fresh installation.');
|
|
44
|
+
}
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const currentVersion = m.version || 'unknown';
|
|
49
|
+
ui.ok(`Found installation: v${currentVersion} at ${m.installDir || 'unknown'}`);
|
|
50
|
+
|
|
51
|
+
if (currentVersion === newVersion && !force) {
|
|
52
|
+
ui.ok(`Already at version ${newVersion}. Use --force to re-apply.`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
ui.info(`Upgrading: v${currentVersion} → v${newVersion}`);
|
|
57
|
+
|
|
58
|
+
// ── Backup ─────────────────────────────────────────────────────────────
|
|
59
|
+
ui.step(2, 6, 'Creating backups');
|
|
60
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
61
|
+
const backups = [];
|
|
62
|
+
|
|
63
|
+
// Backup settings.json
|
|
64
|
+
const settingsPath = m.claudeSettingsPath || env.claudeSettingsPath;
|
|
65
|
+
if (settingsPath && fs.existsSync(settingsPath)) {
|
|
66
|
+
const backupPath = settingsPath + `.bak_${timestamp}`;
|
|
67
|
+
fs.copyFileSync(settingsPath, backupPath);
|
|
68
|
+
backups.push(backupPath);
|
|
69
|
+
ui.ok(`Backed up settings.json → ${path.basename(backupPath)}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Backup hook script
|
|
73
|
+
const hookDest = m.hookScript && m.hookScript.destination;
|
|
74
|
+
if (hookDest && fs.existsSync(hookDest)) {
|
|
75
|
+
const backupPath = hookDest + `.bak_${timestamp}`;
|
|
76
|
+
fs.copyFileSync(hookDest, backupPath);
|
|
77
|
+
backups.push(backupPath);
|
|
78
|
+
ui.ok(`Backed up hook script → ${path.basename(backupPath)}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ── Stop service ───────────────────────────────────────────────────────
|
|
82
|
+
ui.step(3, 6, 'Stopping service');
|
|
83
|
+
if (m.service && m.service.type && m.service.type !== 'none') {
|
|
84
|
+
try {
|
|
85
|
+
_stopService(m.service, env.os);
|
|
86
|
+
ui.ok(`Stopped ${m.service.type} service`);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
ui.warn(`Could not stop service: ${err.message}`);
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
ui.info('No service registered — skipping');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ── Update hooks ───────────────────────────────────────────────────────
|
|
95
|
+
ui.step(4, 6, 'Updating hooks');
|
|
96
|
+
|
|
97
|
+
// Determine install directory: use manifest value or detect from current repo
|
|
98
|
+
const installDir = m.installDir || path.resolve(__dirname, '..');
|
|
99
|
+
|
|
100
|
+
const ctx = {
|
|
101
|
+
envReport: env,
|
|
102
|
+
installDir,
|
|
103
|
+
version: newVersion,
|
|
104
|
+
results: {},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const hooksResult = await hooks.run(ctx);
|
|
109
|
+
if (!hooksResult.success) {
|
|
110
|
+
ui.error(`Hook update failed: ${hooksResult.error}`);
|
|
111
|
+
ui.warn(`Backups preserved at: ${backups.join(', ')}`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
ctx.results.hooks = hooksResult;
|
|
115
|
+
ui.ok('Hooks updated');
|
|
116
|
+
} catch (err) {
|
|
117
|
+
ui.error(`Hook update failed: ${err.message}`);
|
|
118
|
+
ui.warn(`Backups preserved at: ${backups.join(', ')}`);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── Update service template ────────────────────────────────────────────
|
|
123
|
+
ui.step(5, 6, 'Updating service');
|
|
124
|
+
if (m.service && m.service.type && m.service.type !== 'none') {
|
|
125
|
+
try {
|
|
126
|
+
_updateService(m.service, installDir, env);
|
|
127
|
+
ui.ok(`Updated ${m.service.type} service configuration`);
|
|
128
|
+
} catch (err) {
|
|
129
|
+
ui.warn(`Service update failed: ${err.message}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Restart service
|
|
133
|
+
try {
|
|
134
|
+
_startService(m.service, env.os);
|
|
135
|
+
ui.ok(`Restarted ${m.service.type} service`);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
ui.warn(`Could not restart service: ${err.message}`);
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
ui.info('No service registered — skipping');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── TLS (if --tls flag) ────────────────────────────────────────────────
|
|
144
|
+
if (opts.tls) {
|
|
145
|
+
ui.info('Configuring mkcert TLS...');
|
|
146
|
+
try {
|
|
147
|
+
const r = await fetch('http://localhost:3131/api/settings', {
|
|
148
|
+
method: 'POST',
|
|
149
|
+
headers: { 'Content-Type': 'application/json' },
|
|
150
|
+
body: JSON.stringify({ key: 'tls_enabled', value: 'true' }),
|
|
151
|
+
});
|
|
152
|
+
if (r.ok) {
|
|
153
|
+
await fetch('http://localhost:3131/api/settings', {
|
|
154
|
+
method: 'POST',
|
|
155
|
+
headers: { 'Content-Type': 'application/json' },
|
|
156
|
+
body: JSON.stringify({ key: 'tls_mode', value: 'mkcert' }),
|
|
157
|
+
});
|
|
158
|
+
ui.ok('TLS enabled with mkcert mode. Restart the server to activate HTTPS.');
|
|
159
|
+
} else {
|
|
160
|
+
ui.warn('Could not configure TLS — server returned ' + r.status);
|
|
161
|
+
}
|
|
162
|
+
} catch (err) {
|
|
163
|
+
ui.warn(`Could not configure TLS: ${err.message}. Configure manually in Settings.`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Update manifest TLS block
|
|
167
|
+
m.tls = { enabled: true, mode: 'mkcert', certPath: null, keyPath: null };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── Verify ─────────────────────────────────────────────────────────────
|
|
171
|
+
ui.step(6, 6, 'Verification');
|
|
172
|
+
try {
|
|
173
|
+
const verifyResult = await verify.run(ctx);
|
|
174
|
+
if (verifyResult.success) {
|
|
175
|
+
ui.ok('Server verified and running');
|
|
176
|
+
} else {
|
|
177
|
+
ui.warn('Server verification did not complete');
|
|
178
|
+
}
|
|
179
|
+
} catch (err) {
|
|
180
|
+
ui.warn(`Verification failed: ${err.message}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ── Update manifest ────────────────────────────────────────────────────
|
|
184
|
+
let hookSha256 = null;
|
|
185
|
+
if (ctx.results.hooks && ctx.results.hooks.hookScriptDest) {
|
|
186
|
+
try {
|
|
187
|
+
const buf = fs.readFileSync(ctx.results.hooks.hookScriptDest);
|
|
188
|
+
hookSha256 = crypto.createHash('sha256').update(buf).digest('hex');
|
|
189
|
+
} catch { /* best effort */ }
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Ensure installation_id exists (generate if upgrading from pre-v2 manifest)
|
|
193
|
+
if (!m.installation_id) {
|
|
194
|
+
m.installation_id = crypto.randomUUID();
|
|
195
|
+
ui.ok(`Generated install ID: ${m.installation_id}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
m.version = newVersion;
|
|
199
|
+
m.updatedAt = new Date().toISOString();
|
|
200
|
+
m.installDir = installDir;
|
|
201
|
+
if (ctx.results.hooks) {
|
|
202
|
+
m.hookScript = {
|
|
203
|
+
source: m.hookScript ? m.hookScript.source : null,
|
|
204
|
+
destination: ctx.results.hooks.hookScriptDest,
|
|
205
|
+
sha256: hookSha256,
|
|
206
|
+
};
|
|
207
|
+
m.hookCommand = ctx.results.hooks.hookCommand;
|
|
208
|
+
m.hooksRegistered = ctx.results.hooks.hooksAdded;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
manifest.writeManifest(m);
|
|
212
|
+
ui.ok('Manifest updated');
|
|
213
|
+
|
|
214
|
+
// ── Summary ────────────────────────────────────────────────────────────
|
|
215
|
+
console.log('');
|
|
216
|
+
ui.divider();
|
|
217
|
+
ui.ok(`Upgraded to Agent Recon v${newVersion}`);
|
|
218
|
+
ui.divider();
|
|
219
|
+
if (backups.length) {
|
|
220
|
+
ui.info(`Backups: ${backups.map(b => path.basename(b)).join(', ')}`);
|
|
221
|
+
}
|
|
222
|
+
ui.divider();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ── Service helpers ─────────────────────────────────────────────────────────
|
|
226
|
+
|
|
227
|
+
function _stopService(svc, osType) {
|
|
228
|
+
switch (svc.type) {
|
|
229
|
+
case 'systemd':
|
|
230
|
+
execSync('systemctl --user stop agent-recon', { timeout: 10000, stdio: 'pipe' });
|
|
231
|
+
break;
|
|
232
|
+
case 'launchd':
|
|
233
|
+
if (svc.path && fs.existsSync(svc.path)) {
|
|
234
|
+
execSync(`launchctl unload "${svc.path}"`, { timeout: 10000, stdio: 'pipe' });
|
|
235
|
+
}
|
|
236
|
+
break;
|
|
237
|
+
case 'nssm': {
|
|
238
|
+
const ps = osType === 'wsl'
|
|
239
|
+
? '/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe'
|
|
240
|
+
: 'powershell.exe';
|
|
241
|
+
execSync(`"${ps}" -NoProfile -Command "Stop-Service AgentRecon -ErrorAction SilentlyContinue"`,
|
|
242
|
+
{ timeout: 10000, stdio: 'pipe', shell: true });
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function _startService(svc, osType) {
|
|
249
|
+
switch (svc.type) {
|
|
250
|
+
case 'systemd':
|
|
251
|
+
execSync('systemctl --user start agent-recon', { timeout: 10000, stdio: 'pipe' });
|
|
252
|
+
break;
|
|
253
|
+
case 'launchd':
|
|
254
|
+
if (svc.path && fs.existsSync(svc.path)) {
|
|
255
|
+
execSync(`launchctl load "${svc.path}"`, { timeout: 10000, stdio: 'pipe' });
|
|
256
|
+
}
|
|
257
|
+
break;
|
|
258
|
+
case 'nssm': {
|
|
259
|
+
const ps = osType === 'wsl'
|
|
260
|
+
? '/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe'
|
|
261
|
+
: 'powershell.exe';
|
|
262
|
+
execSync(`"${ps}" -NoProfile -Command "Start-Service AgentRecon -ErrorAction SilentlyContinue"`,
|
|
263
|
+
{ timeout: 10000, stdio: 'pipe', shell: true });
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function _updateService(svc, installDir, env) {
|
|
270
|
+
if (svc.type === 'systemd' && svc.path) {
|
|
271
|
+
const templatePath = path.join(installDir, 'service', 'agent-recon.service');
|
|
272
|
+
if (fs.existsSync(templatePath)) {
|
|
273
|
+
let content = fs.readFileSync(templatePath, 'utf8');
|
|
274
|
+
content = content.replace(/\{\{INSTALL_DIR\}\}/g, installDir);
|
|
275
|
+
fs.writeFileSync(svc.path, content, 'utf8');
|
|
276
|
+
execSync('systemctl --user daemon-reload', { timeout: 10000, stdio: 'pipe' });
|
|
277
|
+
}
|
|
278
|
+
} else if (svc.type === 'launchd' && svc.path) {
|
|
279
|
+
const templatePath = path.join(installDir, 'service', 'com.agent-recon.server.plist');
|
|
280
|
+
if (fs.existsSync(templatePath)) {
|
|
281
|
+
let content = fs.readFileSync(templatePath, 'utf8');
|
|
282
|
+
content = content.replace(/\{\{INSTALL_DIR\}\}/g, installDir);
|
|
283
|
+
content = content.replace(/\{\{HOME\}\}/g, env.home);
|
|
284
|
+
fs.writeFileSync(svc.path, content, 'utf8');
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
module.exports = upgrade;
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agent-recon",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Agent Recon™ — real-time observability dashboard for AI coding agents",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
6
|
+
"bin": {
|
|
7
|
+
"agent-recon": "./installer/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "server/start.js",
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=22.0.0"
|
|
12
|
+
},
|
|
13
|
+
"os": [
|
|
14
|
+
"darwin",
|
|
15
|
+
"linux",
|
|
16
|
+
"win32"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "node build.js",
|
|
20
|
+
"start": "node server/start.js",
|
|
21
|
+
"test": "cd server && npm test",
|
|
22
|
+
"test:unit": "cd server && npm run test:unit",
|
|
23
|
+
"test:fe": "cd server && npm run test:fe",
|
|
24
|
+
"prepare": "node scripts/install-hooks.js",
|
|
25
|
+
"prepublishOnly": "bash scripts/release-build.sh",
|
|
26
|
+
"postinstall": "node -e \"try{require('child_process').execSync('cd server && npm rebuild better-sqlite3',{stdio:'ignore'})}catch(e){}\""
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@inquirer/prompts": "^7.0.0",
|
|
30
|
+
"@litko/yara-x": "^0.5.0",
|
|
31
|
+
"better-sqlite3": "^12.8.0",
|
|
32
|
+
"express": "^5.2.1",
|
|
33
|
+
"ws": "^8.16.0"
|
|
34
|
+
},
|
|
35
|
+
"optionalDependencies": {
|
|
36
|
+
"@litko/yara-x-darwin-arm64": "0.5.0",
|
|
37
|
+
"@litko/yara-x-darwin-x64": "0.5.0",
|
|
38
|
+
"@litko/yara-x-linux-x64-gnu": "0.5.0",
|
|
39
|
+
"@litko/yara-x-win32-x64-msvc": "0.5.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"c8": "^11.0.0",
|
|
43
|
+
"esbuild": "^0.25.12",
|
|
44
|
+
"terser": "^5.46.1"
|
|
45
|
+
},
|
|
46
|
+
"keywords": [
|
|
47
|
+
"observability",
|
|
48
|
+
"dashboard",
|
|
49
|
+
"claude-code",
|
|
50
|
+
"agent",
|
|
51
|
+
"hooks",
|
|
52
|
+
"security",
|
|
53
|
+
"monitoring"
|
|
54
|
+
],
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"access": "public"
|
|
57
|
+
}
|
|
58
|
+
}
|