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,46 @@
|
|
|
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
|
+
const { confirm } = require('@inquirer/prompts');
|
|
7
|
+
const ui = require('../ui');
|
|
8
|
+
|
|
9
|
+
const EULA_TEXT = [
|
|
10
|
+
'Agent Recon\u2122 End User License Agreement',
|
|
11
|
+
'',
|
|
12
|
+
'This software is provided under a dual-license model:',
|
|
13
|
+
' \u2022 Core components: Apache License 2.0 (see LICENSE)',
|
|
14
|
+
' \u2022 LLM analysis, telemetry, installer: Commercial (see LICENSE-COMMERCIAL)',
|
|
15
|
+
'',
|
|
16
|
+
'Community tier (free): real-time feed, token tracking, regex security, 7-day retention.',
|
|
17
|
+
'Personal ($9.99/mo) and Professional ($29/mo) tiers unlock LLM-powered analysis,',
|
|
18
|
+
'extended retention, OTEL export, and encrypted archives.',
|
|
19
|
+
'',
|
|
20
|
+
'You supply your own LLM API key(s). All inference costs are your responsibility.',
|
|
21
|
+
'Full terms: see EULA.md in the installation directory.',
|
|
22
|
+
'',
|
|
23
|
+
'Agent Recon\u2122 is not affiliated with or endorsed by Anthropic PBC.',
|
|
24
|
+
'Claude and Claude Code are trademarks of Anthropic PBC.',
|
|
25
|
+
].join('\n ');
|
|
26
|
+
|
|
27
|
+
async function run(ctx) {
|
|
28
|
+
ui.banner(ctx.version);
|
|
29
|
+
|
|
30
|
+
ui.divider();
|
|
31
|
+
console.log(` ${EULA_TEXT}`);
|
|
32
|
+
ui.divider();
|
|
33
|
+
|
|
34
|
+
const accepted = await confirm({ message: 'Do you accept the license terms?' });
|
|
35
|
+
if (!accepted) {
|
|
36
|
+
return { success: false, error: 'License terms not accepted' };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const telemetryEnabled = await confirm({
|
|
40
|
+
message: 'Help improve Agent Recon™ by sharing anonymous usage statistics?',
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return { success: true, eulaAccepted: true, telemetryEnabled };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = { run };
|
package/installer/ui.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
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 — Terminal UI helpers
|
|
8
|
+
*
|
|
9
|
+
* ANSI color output (respects NO_COLOR), step progress, banner, spinner.
|
|
10
|
+
* Zero dependencies — pure Node stdlib.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// ── Color support ───────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
const useColor = !process.env.NO_COLOR && process.stdout.isTTY;
|
|
16
|
+
|
|
17
|
+
const esc = (code, text) => useColor ? `\x1b[${code}m${text}\x1b[0m` : text;
|
|
18
|
+
|
|
19
|
+
const green = t => esc('32', t);
|
|
20
|
+
const red = t => esc('31', t);
|
|
21
|
+
const yellow = t => esc('33', t);
|
|
22
|
+
const cyan = t => esc('36', t);
|
|
23
|
+
const bold = t => esc('1', t);
|
|
24
|
+
const dim = t => esc('2', t);
|
|
25
|
+
|
|
26
|
+
// ── Output helpers ──────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
function ok(msg) { console.log(` ${green('[OK]')} ${msg}`); }
|
|
29
|
+
function warn(msg) { console.log(` ${yellow('[WARN]')} ${msg}`); }
|
|
30
|
+
function error(msg) { console.log(` ${red('[FAIL]')} ${msg}`); }
|
|
31
|
+
function info(msg) { console.log(` ${cyan('[INFO]')} ${msg}`); }
|
|
32
|
+
|
|
33
|
+
function step(n, total, msg) {
|
|
34
|
+
const label = dim(`[${n}/${total}]`);
|
|
35
|
+
console.log(`\n ${label} ${bold(msg)}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function divider() {
|
|
39
|
+
console.log(dim(' ' + '─'.repeat(68)));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ── Banner ──────────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
function banner(version) {
|
|
45
|
+
const v = version || '1.0.0';
|
|
46
|
+
console.log('');
|
|
47
|
+
console.log(bold(cyan(' ╔══════════════════════════════════════════════╗')));
|
|
48
|
+
console.log(bold(cyan(' ║ ║')));
|
|
49
|
+
console.log(bold(cyan(' ║') + ' Agent Recon' + cyan('™') + ' Installer ' + cyan('║')));
|
|
50
|
+
console.log(bold(cyan(' ║') + dim(` v${v}`)
|
|
51
|
+
+ ' '.repeat(Math.max(0, 40 - v.length - 1)) + cyan('║')));
|
|
52
|
+
console.log(bold(cyan(' ║ ║')));
|
|
53
|
+
console.log(bold(cyan(' ╚══════════════════════════════════════════════╝')));
|
|
54
|
+
console.log('');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ── Environment report ──────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
function printEnvReport(env) {
|
|
60
|
+
divider();
|
|
61
|
+
console.log(bold(' Environment Detection'));
|
|
62
|
+
divider();
|
|
63
|
+
|
|
64
|
+
const row = (label, value, status) => {
|
|
65
|
+
const pad = ' '.repeat(Math.max(0, 24 - label.length));
|
|
66
|
+
const val = value || dim('not found');
|
|
67
|
+
const tag = status === 'ok' ? green('OK')
|
|
68
|
+
: status === 'warn' ? yellow('WARN')
|
|
69
|
+
: status === 'fail' ? red('FAIL')
|
|
70
|
+
: '';
|
|
71
|
+
console.log(` ${label}${pad} ${val} ${tag}`);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
row('OS', env.os, 'ok');
|
|
75
|
+
row('Architecture', env.arch, 'ok');
|
|
76
|
+
row('Node.js', env.nodeVersion ? `${env.nodeVersion} (${env.nodePath})` : null,
|
|
77
|
+
env.nodeVersion ? 'ok' : 'fail');
|
|
78
|
+
row('Python', env.pythonVersion ? `${env.pythonVersion} (${env.pythonPath})` : null,
|
|
79
|
+
env.pythonVersion ? 'ok' : 'fail');
|
|
80
|
+
row('Home directory', env.home, 'ok');
|
|
81
|
+
row('Config directory', env.configDir, 'ok');
|
|
82
|
+
row('Claude settings', env.claudeSettingsExists ? env.claudeSettingsPath : 'not found',
|
|
83
|
+
env.claudeSettingsExists ? 'ok' : 'warn');
|
|
84
|
+
row('Credential store', env.credentialBackend, 'ok');
|
|
85
|
+
|
|
86
|
+
if (env.existingInstall) {
|
|
87
|
+
console.log('');
|
|
88
|
+
row('Existing install', `v${env.existingInstall.version} at ${env.existingInstall.installDir}`, 'ok');
|
|
89
|
+
row('Hook script', env.existingInstall.hookScriptCurrent ? 'up to date' : 'outdated',
|
|
90
|
+
env.existingInstall.hookScriptCurrent ? 'ok' : 'warn');
|
|
91
|
+
row('Hooks registered', `${env.existingInstall.hooksRegistered}/13`,
|
|
92
|
+
env.existingInstall.hooksRegistered === 13 ? 'ok' : 'warn');
|
|
93
|
+
row('Service', env.existingInstall.serviceInstalled ? 'installed' : 'not installed',
|
|
94
|
+
env.existingInstall.serviceInstalled ? 'ok' : 'warn');
|
|
95
|
+
row('Server', env.existingInstall.serverHealthy ? 'running' : 'not running',
|
|
96
|
+
env.existingInstall.serverHealthy ? 'ok' : 'warn');
|
|
97
|
+
} else {
|
|
98
|
+
console.log('');
|
|
99
|
+
row('Existing install', 'none (fresh install)', 'ok');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
divider();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── Spinner ─────────────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
function startSpinner(msg) {
|
|
108
|
+
if (!process.stdout.isTTY) {
|
|
109
|
+
console.log(` ${msg}...`);
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
113
|
+
let i = 0;
|
|
114
|
+
return setInterval(() => {
|
|
115
|
+
process.stdout.write(`\r ${cyan(frames[i++ % frames.length])} ${msg}`);
|
|
116
|
+
}, 80);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function stopSpinner(intervalId, successMsg) {
|
|
120
|
+
if (intervalId) clearInterval(intervalId);
|
|
121
|
+
if (process.stdout.isTTY) process.stdout.write('\r' + ' '.repeat(60) + '\r');
|
|
122
|
+
if (successMsg) ok(successMsg);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── Exports ─────────────────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
module.exports = {
|
|
128
|
+
green, red, yellow, cyan, bold, dim,
|
|
129
|
+
ok, warn, error, info,
|
|
130
|
+
step, divider, banner, printEnvReport,
|
|
131
|
+
startSpinner, stopSpinner,
|
|
132
|
+
_useColor: () => useColor,
|
|
133
|
+
};
|
|
@@ -0,0 +1,233 @@
|
|
|
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 — Uninstall Flow (Task 8.4)
|
|
8
|
+
*
|
|
9
|
+
* Reads manifest → confirm → stop/remove service → remove hooks from settings →
|
|
10
|
+
* delete hook script → optionally delete data → delete manifest.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { execSync } = require('child_process');
|
|
16
|
+
const { confirm } = require('@inquirer/prompts');
|
|
17
|
+
const ui = require('./ui');
|
|
18
|
+
const mnf = require('./manifest');
|
|
19
|
+
const hooks = require('./steps/hooks');
|
|
20
|
+
const platform = require('../server/platform');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Run the uninstall flow.
|
|
24
|
+
* @param {object} env — from detect.detectEnv()
|
|
25
|
+
*/
|
|
26
|
+
async function uninstall(env) {
|
|
27
|
+
ui.banner();
|
|
28
|
+
|
|
29
|
+
ui.step(1, 5, 'Reading installation manifest');
|
|
30
|
+
|
|
31
|
+
const m = mnf.readManifest();
|
|
32
|
+
if (!m) {
|
|
33
|
+
// Try heuristic
|
|
34
|
+
if (env.existingInstall && env.existingInstall.hookScriptPath) {
|
|
35
|
+
ui.warn('No manifest found but hooks were detected heuristically.');
|
|
36
|
+
ui.info('A fresh "install" followed by "uninstall" would produce a clean removal.');
|
|
37
|
+
ui.info('Attempting best-effort removal...');
|
|
38
|
+
await _heuristicUninstall(env);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
ui.error('No Agent Recon installation found.');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
ui.ok(`Found: v${m.version || 'unknown'} at ${m.installDir || 'unknown'}`);
|
|
46
|
+
|
|
47
|
+
// ── Confirm ────────────────────────────────────────────────────────────
|
|
48
|
+
ui.step(2, 5, 'Confirmation');
|
|
49
|
+
const proceed = await confirm({
|
|
50
|
+
message: 'Are you sure you want to uninstall Agent Recon?',
|
|
51
|
+
default: false,
|
|
52
|
+
});
|
|
53
|
+
if (!proceed) {
|
|
54
|
+
ui.info('Uninstall cancelled.');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const preserveData = await confirm({
|
|
59
|
+
message: 'Preserve database and credentials? (Recommended if you plan to reinstall)',
|
|
60
|
+
default: true,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// ── Stop and remove service ────────────────────────────────────────────
|
|
64
|
+
ui.step(3, 5, 'Removing service');
|
|
65
|
+
if (m.service && m.service.type && m.service.type !== 'none') {
|
|
66
|
+
try {
|
|
67
|
+
_stopAndRemoveService(m.service, env.os);
|
|
68
|
+
ui.ok(`Removed ${m.service.type} service`);
|
|
69
|
+
} catch (err) {
|
|
70
|
+
ui.warn(`Service removal failed: ${err.message}`);
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
ui.info('No service registered — skipping');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ── Remove hooks from settings.json ────────────────────────────────────
|
|
77
|
+
ui.step(4, 5, 'Removing hooks');
|
|
78
|
+
const settingsPath = m.claudeSettingsPath || env.claudeSettingsPath;
|
|
79
|
+
if (settingsPath && m.hookCommand) {
|
|
80
|
+
try {
|
|
81
|
+
_removeHooksFromSettings(settingsPath, m.hookCommand);
|
|
82
|
+
ui.ok('Removed hook registrations from Claude settings');
|
|
83
|
+
} catch (err) {
|
|
84
|
+
ui.warn(`Could not clean settings.json: ${err.message}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Delete hook script
|
|
89
|
+
const hookDest = m.hookScript && m.hookScript.destination;
|
|
90
|
+
if (hookDest) {
|
|
91
|
+
try {
|
|
92
|
+
fs.unlinkSync(hookDest);
|
|
93
|
+
ui.ok(`Deleted ${path.basename(hookDest)}`);
|
|
94
|
+
} catch {
|
|
95
|
+
ui.warn(`Could not delete hook script: ${hookDest}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ── Data removal ───────────────────────────────────────────────────────
|
|
100
|
+
ui.step(5, 5, 'Cleaning up');
|
|
101
|
+
const removed = [];
|
|
102
|
+
const preserved = [];
|
|
103
|
+
|
|
104
|
+
if (!preserveData) {
|
|
105
|
+
// Delete database
|
|
106
|
+
if (m.dbPath) {
|
|
107
|
+
for (const suffix of ['', '-wal', '-shm']) {
|
|
108
|
+
const dbFile = m.dbPath + suffix;
|
|
109
|
+
try { fs.unlinkSync(dbFile); removed.push(path.basename(dbFile)); } catch { /* fine */ }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Delete credential directory
|
|
113
|
+
if (m.credentialDir) {
|
|
114
|
+
try {
|
|
115
|
+
fs.rmSync(m.credentialDir, { recursive: true, force: true });
|
|
116
|
+
removed.push('credentials/');
|
|
117
|
+
} catch { /* fine */ }
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
if (m.dbPath) preserved.push('database');
|
|
121
|
+
if (m.credentialDir) preserved.push('credentials');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Delete manifest
|
|
125
|
+
mnf.deleteManifest();
|
|
126
|
+
removed.push('manifest.json');
|
|
127
|
+
|
|
128
|
+
// Try to clean up empty config dir
|
|
129
|
+
const confDir = platform.configDir();
|
|
130
|
+
try {
|
|
131
|
+
const entries = fs.readdirSync(confDir);
|
|
132
|
+
if (entries.length === 0) {
|
|
133
|
+
fs.rmdirSync(confDir);
|
|
134
|
+
removed.push('config directory');
|
|
135
|
+
}
|
|
136
|
+
} catch { /* directory not empty or doesn't exist — fine */ }
|
|
137
|
+
|
|
138
|
+
// ── Summary ────────────────────────────────────────────────────────────
|
|
139
|
+
console.log('');
|
|
140
|
+
ui.divider();
|
|
141
|
+
ui.ok('Agent Recon uninstalled');
|
|
142
|
+
ui.divider();
|
|
143
|
+
if (removed.length) {
|
|
144
|
+
ui.info(`Removed: ${removed.join(', ')}`);
|
|
145
|
+
}
|
|
146
|
+
if (preserved.length) {
|
|
147
|
+
ui.info(`Preserved: ${preserved.join(', ')}`);
|
|
148
|
+
}
|
|
149
|
+
ui.info('The source directory was NOT removed (it may be a git repo).');
|
|
150
|
+
ui.divider();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
function _removeHooksFromSettings(settingsPath, hookCommand) {
|
|
156
|
+
if (!fs.existsSync(settingsPath)) return;
|
|
157
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
158
|
+
const cleaned = hooks._removeHooks(settings, hookCommand);
|
|
159
|
+
const tmpPath = settingsPath + '.tmp';
|
|
160
|
+
fs.writeFileSync(tmpPath, JSON.stringify(cleaned, null, 2) + '\n', 'utf8');
|
|
161
|
+
fs.renameSync(tmpPath, settingsPath);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function _stopAndRemoveService(svc, osType) {
|
|
165
|
+
switch (svc.type) {
|
|
166
|
+
case 'systemd':
|
|
167
|
+
execSync('systemctl --user stop agent-recon 2>/dev/null || true', {
|
|
168
|
+
timeout: 10000, stdio: 'pipe', shell: true,
|
|
169
|
+
});
|
|
170
|
+
execSync('systemctl --user disable agent-recon 2>/dev/null || true', {
|
|
171
|
+
timeout: 10000, stdio: 'pipe', shell: true,
|
|
172
|
+
});
|
|
173
|
+
if (svc.path && fs.existsSync(svc.path)) {
|
|
174
|
+
fs.unlinkSync(svc.path);
|
|
175
|
+
}
|
|
176
|
+
execSync('systemctl --user daemon-reload', { timeout: 10000, stdio: 'pipe' });
|
|
177
|
+
break;
|
|
178
|
+
|
|
179
|
+
case 'launchd':
|
|
180
|
+
if (svc.path && fs.existsSync(svc.path)) {
|
|
181
|
+
try { execSync(`launchctl unload "${svc.path}"`, { timeout: 10000, stdio: 'pipe' }); } catch { /* fine */ }
|
|
182
|
+
fs.unlinkSync(svc.path);
|
|
183
|
+
}
|
|
184
|
+
break;
|
|
185
|
+
|
|
186
|
+
case 'nssm': {
|
|
187
|
+
const ps = osType === 'wsl'
|
|
188
|
+
? '/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe'
|
|
189
|
+
: 'powershell.exe';
|
|
190
|
+
try {
|
|
191
|
+
execSync(`"${ps}" -NoProfile -Command "Stop-Service AgentRecon -ErrorAction SilentlyContinue"`,
|
|
192
|
+
{ timeout: 10000, stdio: 'pipe', shell: true });
|
|
193
|
+
execSync(`nssm remove AgentRecon confirm`, { timeout: 10000, stdio: 'pipe' });
|
|
194
|
+
} catch { /* best effort */ }
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function _heuristicUninstall(env) {
|
|
201
|
+
const existing = env.existingInstall;
|
|
202
|
+
if (!existing) return;
|
|
203
|
+
|
|
204
|
+
// Try to find and remove hook command from settings
|
|
205
|
+
const settingsPath = env.claudeSettingsPath;
|
|
206
|
+
if (settingsPath && fs.existsSync(settingsPath)) {
|
|
207
|
+
try {
|
|
208
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
209
|
+
const { _findHookCommand } = require('./detect');
|
|
210
|
+
const hookCmd = _findHookCommand(settings);
|
|
211
|
+
if (hookCmd) {
|
|
212
|
+
_removeHooksFromSettings(settingsPath, hookCmd);
|
|
213
|
+
ui.ok('Removed hook registrations from settings');
|
|
214
|
+
}
|
|
215
|
+
} catch (err) {
|
|
216
|
+
ui.warn(`Could not clean settings: ${err.message}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Delete hook script
|
|
221
|
+
if (existing.hookScriptPath && fs.existsSync(existing.hookScriptPath)) {
|
|
222
|
+
try {
|
|
223
|
+
fs.unlinkSync(existing.hookScriptPath);
|
|
224
|
+
ui.ok(`Deleted ${path.basename(existing.hookScriptPath)}`);
|
|
225
|
+
} catch {
|
|
226
|
+
ui.warn(`Could not delete: ${existing.hookScriptPath}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
ui.ok('Best-effort removal complete');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
module.exports = uninstall;
|