@mattersec/matt 0.1.17

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/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # @mattersec/overwatch
2
+
3
+ AI agent telemetry collector for developer machines. Monitors Claude Code, Cursor, and Codex hooks, captures events, forwards to the Mattersec security intelligence dashboard.
4
+
5
+ ## Install
6
+
7
+ ```
8
+ npm install -g @mattersec/overwatch
9
+ ```
10
+
11
+ ### Installation notes
12
+
13
+ On POSIX platforms the postinstall step replaces the JS shim with a direct symlink to the native binary so `overwatch` invocations skip Node cold-start entirely. If you install with `npm install -g --ignore-scripts` (common in locked-down corporate npm environments), the optimize step is skipped and every invocation pays ~30 ms of Node startup overhead. Functionally correct, just slower. To apply the optimization after an `--ignore-scripts` install:
14
+
15
+ ```
16
+ node "$(npm root -g)/@mattersec/overwatch/install.js"
17
+ ```
18
+
19
+ ## Quickstart
20
+
21
+ **Easiest path:** visit [overwatch.mattersec.com](https://overwatch.mattersec.com), sign up, and follow the install wizard. It hands you a one-time `npx @mattersec/overwatch link --token=…` command that authenticates this machine, installs your AI tool hooks, and starts the daemon — all in one paste.
22
+
23
+ If you'd rather drive each step manually:
24
+
25
+ ```
26
+ overwatch auth login # device-code flow
27
+ overwatch install-hooks claude-code # install hooks
28
+ overwatch start # register background service + start daemon
29
+ overwatch status
30
+ ```
31
+
32
+ ## Uninstall
33
+
34
+ Because npm has no `preuninstall` hook, run the service cleanup explicitly before removing the package:
35
+
36
+ ```
37
+ overwatch uninstall-service
38
+ npm uninstall -g @mattersec/overwatch
39
+ ```
40
+
41
+ ## Commands
42
+
43
+ | Command | Purpose |
44
+ |---|---|
45
+ | `overwatch start` | Register service + start daemon |
46
+ | `overwatch stop` | Stop daemon (service registration stays) |
47
+ | `overwatch status` | Print daemon / auth / events status |
48
+ | `overwatch auth login/logout/status/whoami` | Auth subcommands |
49
+ | `overwatch install-hooks <tool>` | Install hooks for `claude-code`, `cursor`, or `codex` |
50
+ | `overwatch uninstall-hooks <tool>` | Remove hooks |
51
+ | `overwatch check` | Hook installation status |
52
+ | `overwatch buffer list/flush/clear` | Manage the encrypted event buffer |
53
+ | `overwatch dashboard` | Open Mattersec dashboard in browser |
54
+ | `overwatch update` | Update via npm registry |
55
+ | `overwatch uninstall-service` | Remove service registration |
56
+
57
+ ## License
58
+
59
+ Proprietary — © Mattersec Labs.
package/bin/matt ADDED
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+ // JS shim for `@mattersec/matt`. On first install (and during `npx`
3
+ // runs that don't get to run install.js before invoking us), this file is
4
+ // what gets symlinked into PATH. We resolve the platform-specific
5
+ // optionalDependency at runtime and exec the native binary.
6
+ //
7
+ // `install.js` (postinstall) replaces this file with a direct symlink to the
8
+ // native binary so subsequent invocations skip the Node startup cost (~30 ms).
9
+ // If install.js fails for any reason, this shim keeps working — slow, but
10
+ // correct.
11
+ 'use strict';
12
+
13
+ const { spawn } = require('child_process');
14
+
15
+ const { platform, arch } = process;
16
+
17
+ function detectLibc() {
18
+ if (platform !== 'linux') return null;
19
+ try {
20
+ const report = process.report && process.report.getReport();
21
+ if (report && report.header && report.header.glibcVersionRuntime) {
22
+ return 'gnu';
23
+ }
24
+ return 'musl';
25
+ } catch (_) {
26
+ return 'gnu';
27
+ }
28
+ }
29
+
30
+ const libc = detectLibc();
31
+ const triple = libc ? `${platform}-${arch}-${libc}` : `${platform}-${arch}`;
32
+ const pkgName = `@mattersec/matt-${triple}`;
33
+ const binExt = platform === 'win32' ? '.exe' : '';
34
+
35
+ let binPath;
36
+ try {
37
+ binPath = require.resolve(`${pkgName}/matt${binExt}`);
38
+ } catch (_) {
39
+ process.stderr.write(
40
+ `matt: platform '${triple}' is not supported, or '${pkgName}' was ` +
41
+ `not installed. If your platform is supported, this usually means npm ` +
42
+ `skipped the optionalDependency for your OS/arch. Please open an issue at:\n` +
43
+ ` https://github.com/mattersec-labs/overwatch-cli/issues\n`
44
+ );
45
+ process.exit(1);
46
+ }
47
+
48
+ // Use spawn (not spawnSync) so we forward stdin properly for interactive
49
+ // commands like `matt link` that may prompt for OTP / browser approval.
50
+ const child = spawn(binPath, process.argv.slice(2), {
51
+ stdio: 'inherit',
52
+ // On Windows, `windowsHide: true` prevents a phantom console flash when
53
+ // the launched binary in turn spawns helper processes.
54
+ windowsHide: true,
55
+ });
56
+
57
+ child.on('exit', (code, signal) => {
58
+ if (signal) {
59
+ // Re-raise the signal in our own process so parent shells see the right
60
+ // termination semantics (Ctrl-C → 130, etc.). spawn returns null code on
61
+ // signal-termination.
62
+ process.kill(process.pid, signal);
63
+ return;
64
+ }
65
+ process.exit(code === null ? 1 : code);
66
+ });
67
+
68
+ child.on('error', (err) => {
69
+ process.stderr.write(`matt: failed to spawn native binary: ${err.message}\n`);
70
+ process.exit(1);
71
+ });
package/install.js ADDED
@@ -0,0 +1,170 @@
1
+ // Post-install optimize: replace the JS shim at bin/matt with a direct
2
+ // symlink (POSIX) or the Rust launcher .exe (Windows) to the platform binary.
3
+ // Skipping Node from the CLI invocation hot path.
4
+ //
5
+ // Modeled on esbuild's `maybeOptimizePackage` — see
6
+ // https://github.com/evanw/esbuild/blob/main/CHANGELOG-2021.md
7
+ //
8
+ // If anything fails, the JS shim remains in place and the CLI still works
9
+ // (just slower by ~30 ms due to Node startup).
10
+ 'use strict';
11
+
12
+ const fs = require('fs');
13
+ const os = require('os');
14
+ const path = require('path');
15
+ const { execFileSync } = require('child_process');
16
+
17
+ const { platform, arch } = process;
18
+
19
+ function detectLibc() {
20
+ if (platform !== 'linux') return null;
21
+ try {
22
+ const report = process.report && process.report.getReport();
23
+ if (report && report.header && report.header.glibcVersionRuntime) {
24
+ return 'gnu';
25
+ }
26
+ return 'musl';
27
+ } catch (_) {
28
+ return 'gnu';
29
+ }
30
+ }
31
+
32
+ function resolvePlatformBinary() {
33
+ const libc = detectLibc();
34
+ const triple = libc ? `${platform}-${arch}-${libc}` : `${platform}-${arch}`;
35
+ const pkgName = `@mattersec/matt-${triple}`;
36
+ const binExt = platform === 'win32' ? '.exe' : '';
37
+ try {
38
+ return {
39
+ pkgName,
40
+ path: require.resolve(`${pkgName}/matt${binExt}`),
41
+ };
42
+ } catch (e) {
43
+ return { pkgName, path: null, error: e.message };
44
+ }
45
+ }
46
+
47
+ // Persist node + npm absolute paths so the daemon (which inherits a minimal
48
+ // launchd/systemd PATH and won't see nvm/fnm/asdf installs) can spawn the
49
+ // right `npm install -g` at auto-update time. Best-effort: never throw.
50
+ // See `src/update/install_meta.rs` for the consumer side.
51
+ function writeInstallMeta() {
52
+ try {
53
+ const home = os.homedir();
54
+ if (!home) return;
55
+ const dataDir = path.join(home, '.matt');
56
+ if (!fs.existsSync(dataDir)) {
57
+ fs.mkdirSync(dataDir, { recursive: true, mode: 0o700 });
58
+ }
59
+
60
+ let globalPrefix = process.env.npm_config_prefix || null;
61
+ if (!globalPrefix) {
62
+ try {
63
+ globalPrefix = execFileSync('npm', ['prefix', '-g'], {
64
+ encoding: 'utf8',
65
+ stdio: ['ignore', 'pipe', 'ignore'],
66
+ timeout: 5000,
67
+ }).trim() || null;
68
+ } catch (_) {
69
+ globalPrefix = null;
70
+ }
71
+ }
72
+
73
+ let pkgVersion = null;
74
+ try {
75
+ pkgVersion = require('./package.json').version || null;
76
+ } catch (_) { /* ignore */ }
77
+
78
+ const meta = {
79
+ node: process.execPath || null,
80
+ npm: process.env.npm_execpath || null,
81
+ global_prefix: globalPrefix,
82
+ package_version: pkgVersion,
83
+ captured_at: new Date().toISOString(),
84
+ };
85
+
86
+ const metaPath = path.join(dataDir, 'install_meta.json');
87
+ fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2), { mode: 0o600 });
88
+ if (platform !== 'win32') {
89
+ try { fs.chmodSync(metaPath, 0o600); } catch (_) { /* ignore */ }
90
+ }
91
+ } catch (e) {
92
+ // Never block install on this — daemon falls back to bare `npm` on PATH.
93
+ console.error(`matt: install_meta capture skipped (${e.message})`);
94
+ }
95
+ }
96
+
97
+ function optimizePosix(shimPath, platformBin) {
98
+ // Replace the JS shim with a symlink (fast) or a copy (fallback).
99
+ try {
100
+ fs.unlinkSync(shimPath);
101
+ } catch (_) {
102
+ /* ignore; file may not exist */
103
+ }
104
+ try {
105
+ fs.symlinkSync(platformBin, shimPath);
106
+ fs.chmodSync(shimPath, 0o755);
107
+ return;
108
+ } catch (e) {
109
+ // Symlinks failed (uncommon on POSIX) — fall back to copy.
110
+ try {
111
+ fs.copyFileSync(platformBin, shimPath);
112
+ fs.chmodSync(shimPath, 0o755);
113
+ return;
114
+ } catch (copyErr) {
115
+ console.error(
116
+ `matt: postinstall optimize failed (${copyErr.message}); ` +
117
+ `falling back to Node-shim invocation (~30 ms slower per call).`
118
+ );
119
+ }
120
+ }
121
+ }
122
+
123
+ function optimizeWindows(platformBin) {
124
+ // Copy the Rust launcher from the platform package's bin/ into the meta
125
+ // package's bin/ — this way npm's .cmd wrapper invokes the .exe directly
126
+ // (no Node). The launcher in turn CreateProcess's the platform binary.
127
+ const metaExePath = path.join(__dirname, 'bin', 'matt.exe');
128
+ // Look for a launcher sibling of the platform binary.
129
+ const platformDir = path.dirname(platformBin);
130
+ const launcherCandidate = path.join(platformDir, 'matt-launcher.exe');
131
+ try {
132
+ if (fs.existsSync(launcherCandidate)) {
133
+ fs.copyFileSync(launcherCandidate, metaExePath);
134
+ return;
135
+ }
136
+ // Fallback: copy the platform binary itself.
137
+ fs.copyFileSync(platformBin, metaExePath);
138
+ } catch (e) {
139
+ console.error(
140
+ `matt: postinstall optimize failed on Windows (${e.message}); ` +
141
+ `falling back to Node shim.`
142
+ );
143
+ }
144
+ }
145
+
146
+ (function main() {
147
+ writeInstallMeta();
148
+
149
+ const resolved = resolvePlatformBinary();
150
+ if (!resolved.path) {
151
+ // Platform not supported or package not installed — leave the JS shim in
152
+ // place. The shim itself will report the missing platform at invocation time.
153
+ return;
154
+ }
155
+
156
+ // Defensive: refuse to symlink outside node_modules (symlink-escape guard).
157
+ if (!path.resolve(resolved.path).includes(`${path.sep}node_modules${path.sep}`)) {
158
+ console.error(
159
+ `matt: refused to optimize bin — resolved path is outside node_modules: ${resolved.path}`
160
+ );
161
+ return;
162
+ }
163
+
164
+ if (platform === 'win32') {
165
+ optimizeWindows(resolved.path);
166
+ } else {
167
+ const shimPath = path.join(__dirname, 'bin', 'matt');
168
+ optimizePosix(shimPath, resolved.path);
169
+ }
170
+ })();
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@mattersec/matt",
3
+ "version": "0.1.17",
4
+ "description": "matt — AI agent telemetry for developer machines",
5
+ "bin": {
6
+ "matt": "bin/matt"
7
+ },
8
+ "scripts": {
9
+ "postinstall": "node install.js",
10
+ "preuninstall": "node preuninstall.js"
11
+ },
12
+ "optionalDependencies": {
13
+ "@mattersec/matt-darwin-arm64": "=0.1.17",
14
+ "@mattersec/matt-darwin-x64": "=0.1.17",
15
+ "@mattersec/matt-linux-x64-gnu": "=0.1.17",
16
+ "@mattersec/matt-linux-arm64-gnu": "=0.1.17",
17
+ "@mattersec/matt-linux-x64-musl": "=0.1.17",
18
+ "@mattersec/matt-linux-arm64-musl": "=0.1.17",
19
+ "@mattersec/matt-win32-x64": "=0.1.17",
20
+ "@mattersec/matt-win32-arm64": "=0.1.17"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "engines": {
26
+ "node": ">=18"
27
+ },
28
+ "homepage": "https://mattersec.com",
29
+ "license": "SEE LICENSE IN LICENSE",
30
+ "files": [
31
+ "bin/",
32
+ "install.js",
33
+ "preuninstall.js",
34
+ "README.md"
35
+ ],
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/mattersec-labs/overwatch-cli.git",
39
+ "directory": "npm/matt"
40
+ },
41
+ "bugs": {
42
+ "url": "https://github.com/mattersec-labs/overwatch-cli/issues"
43
+ }
44
+ }
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+ // Stop the daemon before npm deletes its binary.
3
+ // On Windows, a running process holds an exclusive lock on its .exe file —
4
+ // npm cannot delete it and leaves behind a broken partial uninstall.
5
+ // Reading the PID file and terminating directly is the reliable fallback when
6
+ // IPC is unavailable (e.g. npm uninstall run without matt stop first).
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const os = require('os');
10
+
11
+ const pidFile = path.join(os.homedir(), '.matt', 'matt.pid');
12
+
13
+ try {
14
+ const pid = parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10);
15
+ if (!isNaN(pid) && pid > 0) {
16
+ try {
17
+ process.kill(pid, 0); // throws ESRCH if not running — nothing to do
18
+ process.kill(pid); // SIGTERM on POSIX, TerminateProcess on Windows
19
+ // Poll up to 2 s for the process to exit and release file handles.
20
+ // Atomics.wait is synchronous sleep available in Node.js (not browsers).
21
+ const deadline = Date.now() + 2000;
22
+ while (Date.now() < deadline) {
23
+ try {
24
+ process.kill(pid, 0);
25
+ } catch (_) {
26
+ break; // process gone — safe for npm to delete files
27
+ }
28
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
29
+ }
30
+ } catch (_) {
31
+ // ESRCH: process already gone — nothing to do
32
+ }
33
+ }
34
+ } catch (_) {
35
+ // No PID file or unreadable — daemon not running
36
+ }