litmus-cli 1.4.35 → 1.4.37
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 +28 -2
- package/dist/commands/connect.d.ts +59 -4
- package/dist/commands/connect.d.ts.map +1 -1
- package/dist/commands/connect.js +137 -12
- package/dist/commands/connect.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +3 -2
- package/dist/commands/init.js.map +1 -1
- package/dist/lib/ai-tracking.d.ts +5 -0
- package/dist/lib/ai-tracking.d.ts.map +1 -1
- package/dist/lib/ai-tracking.js +24 -9
- package/dist/lib/ai-tracking.js.map +1 -1
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +6 -5
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/backfill-tools.d.ts +37 -1
- package/dist/lib/backfill-tools.d.ts.map +1 -1
- package/dist/lib/backfill-tools.js +63 -12
- package/dist/lib/backfill-tools.js.map +1 -1
- package/dist/lib/capture-selftest.d.ts +7 -2
- package/dist/lib/capture-selftest.d.ts.map +1 -1
- package/dist/lib/capture-selftest.js +4 -4
- package/dist/lib/capture-selftest.js.map +1 -1
- package/dist/lib/cursor-restart.js +1 -1
- package/dist/lib/cursor-restart.js.map +1 -1
- package/dist/lib/detection.d.ts +75 -11
- package/dist/lib/detection.d.ts.map +1 -1
- package/dist/lib/detection.js +259 -33
- package/dist/lib/detection.js.map +1 -1
- package/dist/lib/hook-logger.cjs +57 -60
- package/dist/lib/platform.d.ts +19 -6
- package/dist/lib/platform.d.ts.map +1 -1
- package/dist/lib/platform.js +40 -9
- package/dist/lib/platform.js.map +1 -1
- package/dist/lib/ssh-client.d.ts +64 -0
- package/dist/lib/ssh-client.d.ts.map +1 -0
- package/dist/lib/ssh-client.js +128 -0
- package/dist/lib/ssh-client.js.map +1 -0
- package/dist/lib/watcher.js +4 -41
- package/dist/lib/watcher.js.map +1 -1
- package/dist/lib/wsl.d.ts +49 -0
- package/dist/lib/wsl.d.ts.map +1 -0
- package/dist/lib/wsl.js +99 -0
- package/dist/lib/wsl.js.map +1 -0
- package/package.json +1 -1
package/dist/lib/detection.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Claude surface
|
|
3
|
-
* is a spawned script, so logic living there can't
|
|
2
|
+
* Editor, AI tool and Claude surface detection — pure functions,
|
|
3
|
+
* unit-testable (watcher.ts is a spawned script, so logic living there can't
|
|
4
|
+
* be imported by tests).
|
|
4
5
|
*
|
|
5
6
|
* "claude" on a candidate machine is at least three different products, and
|
|
6
7
|
* the old bare-word pattern labeled them all `claude` — which report readers
|
|
@@ -10,37 +11,6 @@
|
|
|
10
11
|
* claude_web — claude.ai in a browser tab; no hook can exist
|
|
11
12
|
* claude — legacy/unattributable, kept so old readers keep working
|
|
12
13
|
*/
|
|
13
|
-
/**
|
|
14
|
-
* Classifies every process-list line containing "claude" into surfaces.
|
|
15
|
-
* `ps` is the lowercased `ps aux` / `tasklist` output from getProcessList().
|
|
16
|
-
* A line matching no surface pattern stays legacy `claude` (unattributable —
|
|
17
|
-
* notably Windows tasklist, where Desktop and Claude Code are both bare
|
|
18
|
-
* `claude.exe` image names, and bystander strings like the VS Code
|
|
19
|
-
* extension's install path in some process's args).
|
|
20
|
-
*/
|
|
21
|
-
export function classifyClaudeProcesses(ps) {
|
|
22
|
-
const found = new Set();
|
|
23
|
-
for (const line of ps.split("\n")) {
|
|
24
|
-
if (!/\bclaude\b/.test(line))
|
|
25
|
-
continue;
|
|
26
|
-
if (/claude\.app|claude helper/.test(line)) {
|
|
27
|
-
// macOS Desktop app bundle and its renderer/GPU helpers
|
|
28
|
-
found.add("claude_desktop");
|
|
29
|
-
}
|
|
30
|
-
else if (/claude-code[/\\]cli\.js|[/\\]claude(?:\s|$)/.test(line)) {
|
|
31
|
-
// The npm entrypoint, or the `claude` binary itself as the executed
|
|
32
|
-
// path. Path-anchored so the word inside a directory name (the VS Code
|
|
33
|
-
// extension's install path, a .claude config dir) doesn't count as the
|
|
34
|
-
// CLI running. Desktop's binary also ends in /claude but its lines
|
|
35
|
-
// match the app-bundle branch above first.
|
|
36
|
-
found.add("claude_code");
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
found.add("claude");
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return [...found];
|
|
43
|
-
}
|
|
44
14
|
// Owners that mean "a browser tab". First-token matching: lsof reports
|
|
45
15
|
// multi-word app names ("Google Chrome Helper", "Brave Browser") but the
|
|
46
16
|
// leading token identifies the browser; Safari's network traffic comes from
|
|
@@ -77,4 +47,260 @@ export function refineClaudeTool(tool, owner) {
|
|
|
77
47
|
// drops the surface claim; api traffic keeps its API label.
|
|
78
48
|
return tool === "claude_web" ? "claude" : tool;
|
|
79
49
|
}
|
|
50
|
+
// USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND — the same eleven
|
|
51
|
+
// columns on macOS and procps, none of the first ten containing a space.
|
|
52
|
+
const PS_AUX_ROW = /^(\S+)\s+(\d+)\s+(?:\S+\s+){8}(.*)$/;
|
|
53
|
+
// `tasklist /fo csv /nh`: "Image Name","PID","Session Name","Session#","Mem
|
|
54
|
+
// Usage". CSV, because the default table cuts image names at 25 characters.
|
|
55
|
+
const TASKLIST_ROW = /^"([^"]+)","(\d+)","([^"]*)","(\d+)",/;
|
|
56
|
+
/**
|
|
57
|
+
* Parses getProcessList() output into rows. Header, separator and
|
|
58
|
+
* unrecognised lines are dropped — a line that cannot be attributed to a
|
|
59
|
+
* program is not evidence of one.
|
|
60
|
+
*/
|
|
61
|
+
export function parseProcessRows(ps, platform) {
|
|
62
|
+
const rows = [];
|
|
63
|
+
for (const line of ps.split("\n")) {
|
|
64
|
+
if (platform === "win32") {
|
|
65
|
+
const m = line.trim().match(TASKLIST_ROW);
|
|
66
|
+
if (m)
|
|
67
|
+
rows.push({ owner: m[4], pid: Number(m[2]), command: m[1], imageOnly: true });
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
const m = line.trim().match(PS_AUX_ROW);
|
|
71
|
+
if (m)
|
|
72
|
+
rows.push({ owner: m[1], pid: Number(m[2]), command: m[3], imageOnly: false });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return rows;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Keeps the rows owned by whoever owns `selfPid` — the watcher itself, which
|
|
79
|
+
* runs as the candidate. Its own row carries the owner exactly as this
|
|
80
|
+
* output spells it (procps truncates long user names), so no lookup of the
|
|
81
|
+
* account name is needed. Without that row ownership cannot be established,
|
|
82
|
+
* so nothing counts rather than everyone's processes.
|
|
83
|
+
*
|
|
84
|
+
* On Windows the owner is the Session#, which another account can share
|
|
85
|
+
* (a `runas` process in the candidate's session); getProcessList() therefore
|
|
86
|
+
* asks tasklist for the candidate's own processes only, and this keeps the
|
|
87
|
+
* session filter on top.
|
|
88
|
+
*/
|
|
89
|
+
export function ownRows(rows, selfPid) {
|
|
90
|
+
const self = rows.find((r) => r.pid === selfPid);
|
|
91
|
+
return self ? rows.filter((r) => r.owner === self.owner) : [];
|
|
92
|
+
}
|
|
93
|
+
export const EDITOR_SIGNATURES = {
|
|
94
|
+
vscode: {
|
|
95
|
+
names: ["code", "code-insiders", "code - insiders", "code-oss"],
|
|
96
|
+
bundles: ["visual studio code", "visual studio code - insiders"],
|
|
97
|
+
paths: ["/.vscode-server/", "/opt/vscode-server-cache/"],
|
|
98
|
+
},
|
|
99
|
+
cursor: {
|
|
100
|
+
names: ["cursor", "cursor-agent"],
|
|
101
|
+
bundles: ["cursor"],
|
|
102
|
+
paths: ["/.cursor-server/", "/opt/cursor-server-cache/"],
|
|
103
|
+
},
|
|
104
|
+
vim: { names: ["vim", "gvim", "nvim", "mvim"], bundles: ["macvim"] },
|
|
105
|
+
intellij: { names: ["idea", "idea64"], bundles: ["intellij idea", "intellij idea ce", "intellij idea ultimate"] },
|
|
106
|
+
webstorm: { names: ["webstorm", "webstorm64"], bundles: ["webstorm"] },
|
|
107
|
+
sublime: { names: ["subl", "sublime_text"], bundles: ["sublime text"] },
|
|
108
|
+
emacs: { names: ["emacs", "runemacs"], bundles: ["emacs"] },
|
|
109
|
+
zed: { names: ["zed", "zed-editor"], bundles: ["zed", "zed preview"] },
|
|
110
|
+
};
|
|
111
|
+
export const AI_TOOL_SIGNATURES = {
|
|
112
|
+
aider: { names: ["aider"], modules: ["aider"] },
|
|
113
|
+
codeium: { names: ["codeium"], paths: ["/codeium.codeium-", "/.codeium/"] },
|
|
114
|
+
codex: { names: ["codex"], bundles: ["codex"], paths: ["/@openai/codex/"] },
|
|
115
|
+
copilot: { names: ["copilot", "copilot-language-server"], paths: ["/@github/copilot/", "/github.copilot-"] },
|
|
116
|
+
gemini: { names: ["gemini"], paths: ["/@google/gemini-cli/"] },
|
|
117
|
+
windsurf: { names: ["windsurf"], bundles: ["windsurf"] },
|
|
118
|
+
ollama: { names: ["ollama", "ollama app"], bundles: ["ollama"] },
|
|
119
|
+
tabnine: { names: ["tabnine", "tabnine-deep-local", "tabnine-deep-cloud"], paths: ["/.tabnine/"] },
|
|
120
|
+
supermaven: { names: ["supermaven", "sm-agent"], paths: ["/.supermaven/"] },
|
|
121
|
+
cline: { names: ["cline"] },
|
|
122
|
+
cody: { names: ["cody-agent", "sourcegraph-cody"], paths: ["/@sourcegraph/cody/"] },
|
|
123
|
+
"amazon-q": { names: ["amazon-q", "qchat"], bundles: ["amazon q"] },
|
|
124
|
+
"roo-code": { names: ["roo-code", "roo_code"] },
|
|
125
|
+
pearai: { names: ["pearai"], bundles: ["pearai"] },
|
|
126
|
+
ghostwriter: { names: ["ghostwriter"], bundles: ["ghostwriter"] },
|
|
127
|
+
};
|
|
128
|
+
// Programs that run somebody else's program: the tool is the script they
|
|
129
|
+
// were handed (`node /opt/homebrew/bin/codex`, `python3 -m aider`), never
|
|
130
|
+
// the interpreter.
|
|
131
|
+
const INTERPRETER = /^(?:node|nodejs|bun|deno|python[\d.]*|pythonw|sh|bash|dash|zsh)$/;
|
|
132
|
+
const PYTHON = /^python/;
|
|
133
|
+
const SHELL = /^(?:sh|bash|dash|zsh)$/;
|
|
134
|
+
// Interpreter options whose value is the NEXT argument, so it is not the
|
|
135
|
+
// script (`python3 -W ignore -m aider`, `node -r ./hook.js bin/codex`).
|
|
136
|
+
// Lowercase, like the command line they are matched against.
|
|
137
|
+
const VALUE_FLAGS = {
|
|
138
|
+
python: new Set(["-w", "-x", "--check-hash-based-pycs"]),
|
|
139
|
+
node: new Set(["-r", "--require", "--import", "--loader", "--experimental-loader", "--conditions", "--inspect-port", "--title", "--env-file"]),
|
|
140
|
+
shell: new Set(["-o", "+o"]),
|
|
141
|
+
};
|
|
142
|
+
// Options after which the rest is inline code, not a script.
|
|
143
|
+
const CODE_FLAGS = {
|
|
144
|
+
python: new Set(["-c"]),
|
|
145
|
+
node: new Set(["-e", "--eval", "-p", "--print"]),
|
|
146
|
+
shell: new Set(),
|
|
147
|
+
};
|
|
148
|
+
// A Linux editor binary run as node (ELECTRON_RUN_AS_NODE): only its .js
|
|
149
|
+
// script is a program; any other argument is a file or folder it opens.
|
|
150
|
+
const ELECTRON_NODE = /^(?:code|code-insiders|code-oss|cursor)$/;
|
|
151
|
+
// What Electron runs as node is an absolute script path. Anything else among
|
|
152
|
+
// its arguments is a file it opens or a piece of a flag value split at a
|
|
153
|
+
// space (`--user-data-dir=/users/c/library/application support/cursor`).
|
|
154
|
+
const ELECTRON_SCRIPT = /^\/.*\.[cm]?js$/;
|
|
155
|
+
// Only executable suffixes are stripped. A script keeps its source extension,
|
|
156
|
+
// so a candidate's own gemini.py is not the gemini CLI; packaged entrypoints
|
|
157
|
+
// are recognised by their install-path markers instead.
|
|
158
|
+
const EXECUTABLE_EXT = /\.(?:exe|cmd|bat)$/;
|
|
159
|
+
// Outermost bundle of an absolute macOS program path. Only the bundle's own
|
|
160
|
+
// name may hold spaces ("visual studio code - insiders.app"); the directories
|
|
161
|
+
// before it may not, so a `.app/contents/` path in an argument (`codesign
|
|
162
|
+
// --verify /applications/cursor.app/...`, `vim build/cursor.app/...`) is
|
|
163
|
+
// never the program's bundle. The one spaced directory allowed is the macOS
|
|
164
|
+
// per-user install parent "application support" — a deliberate allow-list of
|
|
165
|
+
// known install parents, not a general space allowance, which would let an
|
|
166
|
+
// argument path become the bundle again.
|
|
167
|
+
const BUNDLE = /^\/(?:(?:[^/\s]+|application support)\/)*?([^/]+)\.app\/contents\//;
|
|
168
|
+
// An Electron helper run as node (VS Code / Cursor extension processes):
|
|
169
|
+
// `<...>/x.app/contents/frameworks/x helper (plugin).app/contents/macos/x
|
|
170
|
+
// helper (plugin) <script>`.
|
|
171
|
+
const ELECTRON_HELPER = /^\/(?:(?:[^/\s]+|application support)\/)*?[^/]+\.app\/contents\/frameworks\/([^/]+ helper[^/]*)\.app\/contents\/macos\/\1(?=\s|$)(.*)$/;
|
|
172
|
+
function basename(path) {
|
|
173
|
+
return path.slice(Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\")) + 1);
|
|
174
|
+
}
|
|
175
|
+
function programName(p) {
|
|
176
|
+
return p.kind === "exe" ? basename(p.path).replace(EXECUTABLE_EXT, "") : basename(p.path);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* What an interpreter was handed: the first argument that is neither an
|
|
180
|
+
* option nor an option's value; the module after python's `-m`; the command
|
|
181
|
+
* after a shell's `-c`. Inline code (`python -c`, `node -e`) is no program.
|
|
182
|
+
*/
|
|
183
|
+
function scriptArg(interpreter, args) {
|
|
184
|
+
const family = PYTHON.test(interpreter) ? "python" : SHELL.test(interpreter) ? "shell" : "node";
|
|
185
|
+
for (let i = 0; i < args.length; i++) {
|
|
186
|
+
const a = args[i];
|
|
187
|
+
if (family === "python" && a === "-m")
|
|
188
|
+
return args[i + 1] ? { path: args[i + 1], kind: "module" } : undefined;
|
|
189
|
+
if (family === "shell" && a === "-c")
|
|
190
|
+
return args[i + 1] ? { path: args[i + 1], kind: "exe" } : undefined;
|
|
191
|
+
if (CODE_FLAGS[family].has(a))
|
|
192
|
+
return undefined;
|
|
193
|
+
if (VALUE_FLAGS[family].has(a)) {
|
|
194
|
+
i++;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
// `bun run x`, `deno run -A x`: the subcommand is not the script.
|
|
198
|
+
if (a === "run" && (interpreter === "bun" || interpreter === "deno"))
|
|
199
|
+
continue;
|
|
200
|
+
if (!a.startsWith("-") && !a.startsWith("+"))
|
|
201
|
+
return { path: a, kind: "script" };
|
|
202
|
+
}
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* What a row runs: the macOS bundle name (if any) and the programs — argv0,
|
|
207
|
+
* plus the script when argv0 is an interpreter or an Electron binary.
|
|
208
|
+
*/
|
|
209
|
+
export function rowPrograms(row) {
|
|
210
|
+
const command = row.command.trim().toLowerCase();
|
|
211
|
+
if (row.imageOnly)
|
|
212
|
+
return { programs: [{ path: command, kind: "exe" }] };
|
|
213
|
+
const bundle = command.match(BUNDLE)?.[1];
|
|
214
|
+
const helper = command.match(ELECTRON_HELPER);
|
|
215
|
+
if (helper) {
|
|
216
|
+
const script = scriptArg("node", helper[2].split(/\s+/).filter(Boolean));
|
|
217
|
+
return { bundle, programs: script && ELECTRON_SCRIPT.test(script.path) ? [script] : [] };
|
|
218
|
+
}
|
|
219
|
+
const words = command.split(/\s+/);
|
|
220
|
+
// macOS per-user installs live under "Library/Application Support".
|
|
221
|
+
if (words[0].endsWith("/library/application") && words[1]?.startsWith("support/")) {
|
|
222
|
+
words.splice(0, 2, `${words[0]} ${words[1]}`);
|
|
223
|
+
}
|
|
224
|
+
const [argv0 = "", ...args] = words;
|
|
225
|
+
const programs = [{ path: argv0, kind: "exe" }];
|
|
226
|
+
const name = basename(argv0).replace(EXECUTABLE_EXT, "");
|
|
227
|
+
if (INTERPRETER.test(name)) {
|
|
228
|
+
const script = scriptArg(name, args);
|
|
229
|
+
if (script)
|
|
230
|
+
programs.push(script);
|
|
231
|
+
}
|
|
232
|
+
else if (ELECTRON_NODE.test(name)) {
|
|
233
|
+
const script = scriptArg("node", args);
|
|
234
|
+
if (script && ELECTRON_SCRIPT.test(script.path))
|
|
235
|
+
programs.push(script);
|
|
236
|
+
}
|
|
237
|
+
return { bundle, programs };
|
|
238
|
+
}
|
|
239
|
+
function matchesSignature(sig, bundle, programs) {
|
|
240
|
+
if (bundle && sig.bundles?.includes(bundle))
|
|
241
|
+
return true;
|
|
242
|
+
return programs.some((p) => {
|
|
243
|
+
// A module name is only a tool where that tool really runs as a module;
|
|
244
|
+
// a candidate's own `python3 -m codex` is their code, not the Codex CLI.
|
|
245
|
+
if (p.kind === "module")
|
|
246
|
+
return (sig.modules ?? []).includes(p.path);
|
|
247
|
+
return sig.names.includes(programName(p)) || (sig.paths ?? []).some((marker) => p.path.includes(marker));
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
const CLAUDE_CODE_PATH = "/@anthropic-ai/claude-code/";
|
|
251
|
+
/**
|
|
252
|
+
* Which Claude surface a row runs, from its program only (see the header):
|
|
253
|
+
* claude_desktop — the Claude.app bundle and its helpers. Desktop's own
|
|
254
|
+
* binary is also named `claude`, so the bundle wins.
|
|
255
|
+
* claude_code — an executable named `claude` (native install, npm bin,
|
|
256
|
+
* a shell's bare `claude`), or the npm entrypoint.
|
|
257
|
+
* claude — the Windows image `claude.exe`, which Desktop and
|
|
258
|
+
* Claude Code share, so the surface is unattributable.
|
|
259
|
+
* The word elsewhere in a row (a .claude config dir, an argument, the VS
|
|
260
|
+
* Code extension's install path) is no program.
|
|
261
|
+
*/
|
|
262
|
+
function claudeSurface(row) {
|
|
263
|
+
const { bundle, programs } = rowPrograms(row);
|
|
264
|
+
if (bundle === "claude" || /^claude helper\b/.test(row.command.trim().toLowerCase()))
|
|
265
|
+
return "claude_desktop";
|
|
266
|
+
const runsClaude = programs.some((p) => p.kind !== "module" && (programName(p) === "claude" || p.path.includes(CLAUDE_CODE_PATH)));
|
|
267
|
+
if (!runsClaude)
|
|
268
|
+
return undefined;
|
|
269
|
+
return row.imageOnly ? "claude" : "claude_code";
|
|
270
|
+
}
|
|
271
|
+
function claudeSurfaces(rows) {
|
|
272
|
+
const found = new Set();
|
|
273
|
+
for (const row of rows) {
|
|
274
|
+
const surface = claudeSurface(row);
|
|
275
|
+
if (surface)
|
|
276
|
+
found.add(surface);
|
|
277
|
+
}
|
|
278
|
+
return [...found];
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Claude surfaces running in getProcessList() output, any user's. `ps` is
|
|
282
|
+
* lowercased `ps aux` / `tasklist /fo csv` text, parsed per row like
|
|
283
|
+
* detectTools; `platform` is the OS that produced it.
|
|
284
|
+
*/
|
|
285
|
+
export function classifyClaudeProcesses(ps, platform) {
|
|
286
|
+
return claudeSurfaces(parseProcessRows(ps, platform));
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Editors and AI tools running as the watcher's own user. `ps` is
|
|
290
|
+
* getProcessList() output; `selfPid` is the watcher's pid (its own row
|
|
291
|
+
* names the owner to keep). Cursor counts as both an editor and an AI tool.
|
|
292
|
+
*/
|
|
293
|
+
export function detectTools(ps, platform, selfPid) {
|
|
294
|
+
const rows = ownRows(parseProcessRows(ps, platform), selfPid);
|
|
295
|
+
const programs = rows.map(rowPrograms);
|
|
296
|
+
const running = (sigs) => Object.entries(sigs)
|
|
297
|
+
.filter(([, sig]) => programs.some((p) => matchesSignature(sig, p.bundle, p.programs)))
|
|
298
|
+
.map(([name]) => name);
|
|
299
|
+
const editors = running(EDITOR_SIGNATURES);
|
|
300
|
+
const aiTools = running(AI_TOOL_SIGNATURES);
|
|
301
|
+
aiTools.push(...claudeSurfaces(rows));
|
|
302
|
+
if (editors.includes("cursor") && !aiTools.includes("cursor"))
|
|
303
|
+
aiTools.push("cursor");
|
|
304
|
+
return { editors, aiTools };
|
|
305
|
+
}
|
|
80
306
|
//# sourceMappingURL=detection.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detection.js","sourceRoot":"","sources":["../../src/lib/detection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB,CAAC,EAAU;IAChD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAA;IAC/B,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAClC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAQ;QACtC,IAAI,2BAA2B,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3C,wDAAwD;YACxD,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAA;QAC7B,CAAC;aAAM,IAAI,6CAA6C,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACpE,oEAAoE;YACpE,uEAAuE;YACvE,uEAAuE;YACvE,mEAAmE;YACnE,2CAA2C;YAC3C,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;QAC1B,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;QACrB,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,CAAC,CAAA;AACnB,CAAC;AAED,uEAAuE;AACvE,yEAAyE;AACzE,4EAA4E;AAC5E,+BAA+B;AAC/B,MAAM,gBAAgB,GAAG,4GAA4G,CAAA;AAErI;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,KAAa;IAC1D,IAAI,IAAI,KAAK,YAAY,IAAI,IAAI,KAAK,YAAY;QAAE,OAAO,IAAI,CAAA;IAC/D,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAA,CAAC,+CAA+C;IACvE,4EAA4E;IAC5E,gCAAgC;IAChC,IAAI,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAA;IACjD,2EAA2E;IAC3E,4EAA4E;IAC5E,IAAI,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,aAAa,CAAA;IACxD,IAAI,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,gBAAgB,CAAA;IACpD,IAAI,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IAC7C,2EAA2E;IAC3E,0EAA0E;IAC1E,4DAA4D;IAC5D,OAAO,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAA;AAChD,CAAC"}
|
|
1
|
+
{"version":3,"file":"detection.js","sourceRoot":"","sources":["../../src/lib/detection.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/hook-logger.cjs
CHANGED
|
@@ -123,9 +123,7 @@ function isAtOrUnder(child, parent) {
|
|
|
123
123
|
|
|
124
124
|
/**
|
|
125
125
|
* Does an explicitly named assessment agree with where the session reports it
|
|
126
|
-
* is? Agreement means
|
|
127
|
-
* that assessment, or the assessment sits under the session's directory (the
|
|
128
|
-
* editor-opened-at-a-parent shape, ENG-771).
|
|
126
|
+
* is? Agreement means the session sits inside that assessment.
|
|
129
127
|
*
|
|
130
128
|
* `sessionDirs` are locations the TOOL reported (workspace roots, payload cwd)
|
|
131
129
|
* — deliberately not this process's cwd. A hook runner's cwd is not the
|
|
@@ -135,21 +133,13 @@ function isAtOrUnder(child, parent) {
|
|
|
135
133
|
* the silent Copilot Chat loss ENG-1292 fixed. When the tool reports nothing —
|
|
136
134
|
* exactly the Copilot Chat case the argument exists for — the argument stands.
|
|
137
135
|
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* copied into a sibling project under that
|
|
141
|
-
* original assessment.
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
* parent of the assessment" is ENG-771, the reason resolution looks
|
|
146
|
-
* downward at all; when the session reports only the parent, nothing in the
|
|
147
|
-
* payload says which child the prompt came from.
|
|
148
|
-
* - Rejecting here changes nothing. Verified: with the assessment registered
|
|
149
|
-
* (which `litmus init` always does), dropping the explicit path just falls
|
|
150
|
-
* through to the registry, which returns that same assessment. The only
|
|
151
|
-
* candidates it would affect are project-scope ones, who have no registry
|
|
152
|
-
* entry — so tightening this trades zero privacy gain for real capture loss.
|
|
136
|
+
* A session reporting a PARENT of the named assessment does not agree
|
|
137
|
+
* (ENG-953). It used to, as the "editor opened at a parent" shape (ENG-771),
|
|
138
|
+
* which also let a config copied into a sibling project under that parent file
|
|
139
|
+
* its prompts against the original assessment. Nothing in such a payload says
|
|
140
|
+
* the prompt came from inside the assessment, and the registry fallback no
|
|
141
|
+
* longer claims parent-rooted sessions either, so neither route records work
|
|
142
|
+
* from outside the assessment folder.
|
|
153
143
|
*/
|
|
154
144
|
function explicitAgreesWithSession(assessmentDir, sessionDirs) {
|
|
155
145
|
const target = realpathOrNull(assessmentDir)
|
|
@@ -161,7 +151,7 @@ function explicitAgreesWithSession(assessmentDir, sessionDirs) {
|
|
|
161
151
|
if (real && !roots.includes(real)) roots.push(real)
|
|
162
152
|
}
|
|
163
153
|
if (roots.length === 0) return true // tool reported no location — the blind case
|
|
164
|
-
return roots.some(root => isAtOrUnder(root, target)
|
|
154
|
+
return roots.some(root => isAtOrUnder(root, target))
|
|
165
155
|
}
|
|
166
156
|
|
|
167
157
|
// A directory only counts as an assessment if it has a config.json — the
|
|
@@ -229,21 +219,29 @@ function walkUpForLitmusDir(startDir) {
|
|
|
229
219
|
}
|
|
230
220
|
|
|
231
221
|
/**
|
|
232
|
-
*
|
|
222
|
+
* The registered assessment whose tree holds `location`, as a project dir, or
|
|
223
|
+
* null. When registered assessments nest, the innermost one wins: it is the
|
|
224
|
+
* assessment the session is actually in.
|
|
233
225
|
*
|
|
234
226
|
* Reuses ENG-1311's `realpathOrNull` + `isAtOrUnder` rather than carrying a
|
|
235
227
|
* second path comparison: realpath is what makes this work at all, since the
|
|
236
228
|
* registry stores the string `litmus init` was given while payloads report the
|
|
237
229
|
* tool's own cwd, and on macOS the same directory routinely differs in form
|
|
238
|
-
* (/tmp vs /private/tmp, symlinked homes).
|
|
239
|
-
* "inside itself" for the purpose of picking a registry entry — an exact match
|
|
240
|
-
* is already handled by the walk-up above.
|
|
230
|
+
* (/tmp vs /private/tmp, symlinked homes).
|
|
241
231
|
*/
|
|
242
|
-
function
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
232
|
+
function registeredAssessmentHolding(location, registered) {
|
|
233
|
+
const loc = realpathOrNull(location)
|
|
234
|
+
if (!loc) return null
|
|
235
|
+
let best = null
|
|
236
|
+
let bestReal = ""
|
|
237
|
+
for (const p of registered) {
|
|
238
|
+
const real = realpathOrNull(p)
|
|
239
|
+
if (real && isAtOrUnder(loc, real) && real.length > bestReal.length) {
|
|
240
|
+
best = p
|
|
241
|
+
bestReal = real
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return best
|
|
247
245
|
}
|
|
248
246
|
|
|
249
247
|
// Find the .litmus directory — walk up from any workspace roots the tool told
|
|
@@ -252,24 +250,29 @@ function isStrictlyInside(parent, child) {
|
|
|
252
250
|
// every tool runs the hook from the workspace (Cursor invokes hooks with
|
|
253
251
|
// cwd = its own config dir and passes the workspace in `workspace_roots`).
|
|
254
252
|
//
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
//
|
|
258
|
-
//
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
//
|
|
253
|
+
// The walk-ups are never gated: a session placed inside an assessment is
|
|
254
|
+
// captured whatever the registry says.
|
|
255
|
+
//
|
|
256
|
+
// The registry fallback is SCOPED (ENG-953). It answers only for a session
|
|
257
|
+
// that is inside a registered assessment's tree, and names that assessment.
|
|
258
|
+
// That is the case walk-up misses while the assessment is live: the in-repo
|
|
259
|
+
// config was deleted mid-session and the shadow mirror carries identity
|
|
260
|
+
// (ENG-1453), or the session sits deeper than the walk-up's ten levels.
|
|
261
|
+
//
|
|
262
|
+
// It used to answer for anything. With no match it returned the first
|
|
263
|
+
// registered assessment, so a prompt typed in an unrelated project on the same
|
|
264
|
+
// machine was written into the assessment and uploaded, and a later version
|
|
265
|
+
// also claimed any session rooted at a PARENT of an assessment ($HOME
|
|
266
|
+
// included). Both captured work outside the assessment folder, which the
|
|
267
|
+
// candidate disclosure says we do not record. A session placed nowhere now
|
|
268
|
+
// captures nothing.
|
|
264
269
|
//
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
for (const hint of hintDirs) {
|
|
272
|
-
if (typeof hint !== "string" || !hint) continue
|
|
270
|
+
// Where the session is: the tool's reported locations (`hintDirs`), or this
|
|
271
|
+
// process's cwd when the tool reported none, since a hook runner's cwd is only
|
|
272
|
+
// a stand-in for the session's.
|
|
273
|
+
function findLitmusDir(hintDirs = []) {
|
|
274
|
+
const reported = hintDirs.filter(d => typeof d === "string" && d)
|
|
275
|
+
for (const hint of reported) {
|
|
273
276
|
const found = walkUpForLitmusDir(hint)
|
|
274
277
|
if (found) return found
|
|
275
278
|
}
|
|
@@ -282,7 +285,7 @@ function findLitmusDir(hintDirs = [], sessionDirs = []) {
|
|
|
282
285
|
const list = JSON.parse(fs.readFileSync(registry, "utf8"))
|
|
283
286
|
if (!Array.isArray(list)) return null
|
|
284
287
|
const valid = list.filter(
|
|
285
|
-
p => typeof p === "string" && (
|
|
288
|
+
p => typeof p === "string" && p && (
|
|
286
289
|
isAssessmentLitmusDir(path.join(p, ".litmus"))
|
|
287
290
|
// ENG-1453: config wiped but the assessment is alive — the dir still
|
|
288
291
|
// exists and its shadow mirror supplies identity + credentials.
|
|
@@ -291,15 +294,11 @@ function findLitmusDir(hintDirs = [], sessionDirs = []) {
|
|
|
291
294
|
)
|
|
292
295
|
if (!valid.length) return null
|
|
293
296
|
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
if (contained.length) return path.join(contained[contained.length - 1], ".litmus")
|
|
297
|
+
const locations = reported.length ? reported : [process.cwd()]
|
|
298
|
+
for (const location of locations) {
|
|
299
|
+
const holder = registeredAssessmentHolding(location, valid)
|
|
300
|
+
if (holder) return path.join(holder, ".litmus")
|
|
299
301
|
}
|
|
300
|
-
// Nothing placeable — keep the long-standing behavior rather than dropping
|
|
301
|
-
// the event, so a tool that reports no location at all still captures.
|
|
302
|
-
return path.join(valid[0], ".litmus")
|
|
303
302
|
} catch { /* no registry — give up silently */ }
|
|
304
303
|
return null
|
|
305
304
|
}
|
|
@@ -1676,8 +1675,8 @@ function claimPrompt(generationId) {
|
|
|
1676
1675
|
* Where the TOOL says this session is: workspace roots (Cursor) or the
|
|
1677
1676
|
* payload's own cwd (Claude Code, Codex, Copilot CLI). VS Code Copilot Chat
|
|
1678
1677
|
* reports neither. Two uses: checking the explicit path (ENG-1311), and
|
|
1679
|
-
*
|
|
1680
|
-
* findLitmusDir).
|
|
1678
|
+
* deciding which registered assessment, if any, holds the session when
|
|
1679
|
+
* walk-up finds nothing (ENG-953, see findLitmusDir).
|
|
1681
1680
|
*
|
|
1682
1681
|
* Explicit hint first: it is correct by construction — but only once it
|
|
1683
1682
|
* agrees with the session's own reported location, since the hook config
|
|
@@ -1687,9 +1686,7 @@ function claimPrompt(generationId) {
|
|
|
1687
1686
|
* Those merits include every location the tool reported, cwd included. The
|
|
1688
1687
|
* reported cwd beats this process's cwd (a hook runner's need not be the
|
|
1689
1688
|
* session's) and is the only pointer left once a copied explicit path has
|
|
1690
|
-
* been rejected
|
|
1691
|
-
* workspace roots, would fall through to the registry and be filed against
|
|
1692
|
-
* an unrelated assessment.
|
|
1689
|
+
* been rejected.
|
|
1693
1690
|
*/
|
|
1694
1691
|
function resolveAssessmentDir(parsed, workspaceRoots) {
|
|
1695
1692
|
const sessionDirs = [
|
|
@@ -1701,7 +1698,7 @@ function resolveAssessmentDir(parsed, workspaceRoots) {
|
|
|
1701
1698
|
const hintDirs = trustExplicit
|
|
1702
1699
|
? [explicitAssessmentDir, ...sessionDirs]
|
|
1703
1700
|
: sessionDirs
|
|
1704
|
-
return findLitmusDir(hintDirs
|
|
1701
|
+
return findLitmusDir(hintDirs)
|
|
1705
1702
|
}
|
|
1706
1703
|
|
|
1707
1704
|
async function main() {
|
package/dist/lib/platform.d.ts
CHANGED
|
@@ -10,15 +10,28 @@
|
|
|
10
10
|
* a no-op on Unix; tests/unit/windows-hide.test.ts enforces it.
|
|
11
11
|
*/
|
|
12
12
|
/**
|
|
13
|
-
* Returns lowercase text listing
|
|
14
|
-
*
|
|
13
|
+
* Returns lowercase text listing running processes, one per line.
|
|
14
|
+
* Tool detection parses it into rows and keeps the watcher's own user's
|
|
15
|
+
* (detection.ts detectTools); never match a word against the whole text.
|
|
15
16
|
*
|
|
16
|
-
* - macOS/Linux: `ps aux` (full command lines)
|
|
17
|
-
* - Windows: `tasklist` (image names like Code.exe,
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* - macOS/Linux: `ps aux` (full command lines, every user's)
|
|
18
|
+
* - Windows: `tasklist /fo csv /nh` (full image names like Code.exe,
|
|
19
|
+
* Cursor.exe; the default table truncates them at 25 characters), asked for
|
|
20
|
+
* the current user's processes only. tasklist rows carry a Session#, not
|
|
21
|
+
* an account, and another account can run in the candidate's session
|
|
22
|
+
* (`runas`), so the user filter has to happen here, and it never widens:
|
|
23
|
+
* see ownTasklist.
|
|
20
24
|
*/
|
|
21
25
|
export declare function getProcessList(): string;
|
|
26
|
+
/**
|
|
27
|
+
* The first user-filtered tasklist that contains this very process (`pid`),
|
|
28
|
+
* trying each spelling of the account in turn (DOMAIN\user, then user).
|
|
29
|
+
* When none does, the filter did not match how this machine names the
|
|
30
|
+
* account, and ownership cannot be established: the result is empty, so
|
|
31
|
+
* nothing counts. It never falls back to an unfiltered or session-scoped
|
|
32
|
+
* list, which would badge another account's processes.
|
|
33
|
+
*/
|
|
34
|
+
export declare function ownTasklist(run: (account: string) => string, accounts: string[], pid: number): string;
|
|
22
35
|
/**
|
|
23
36
|
* Lists active network connections asynchronously.
|
|
24
37
|
* Calls cb(err, stdout) with raw connection output.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../../src/lib/platform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;
|
|
1
|
+
{"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../../src/lib/platform.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAavC;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAQrG;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,KAAK,GAAG,IAAI,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAarF;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,6EAA6E;IAC7E,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAqBtE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAkBrF"}
|
package/dist/lib/platform.js
CHANGED
|
@@ -9,21 +9,52 @@
|
|
|
9
9
|
* see terminal windows flash on every detection cycle (ENG-2010). The flag is
|
|
10
10
|
* a no-op on Unix; tests/unit/windows-hide.test.ts enforces it.
|
|
11
11
|
*/
|
|
12
|
-
import { execSync, execFile } from "child_process";
|
|
12
|
+
import { execSync, execFile, execFileSync } from "child_process";
|
|
13
|
+
import { userInfo } from "os";
|
|
13
14
|
const isWin = process.platform === "win32";
|
|
14
15
|
const isLinux = process.platform === "linux";
|
|
15
16
|
/**
|
|
16
|
-
* Returns lowercase text listing
|
|
17
|
-
*
|
|
17
|
+
* Returns lowercase text listing running processes, one per line.
|
|
18
|
+
* Tool detection parses it into rows and keeps the watcher's own user's
|
|
19
|
+
* (detection.ts detectTools); never match a word against the whole text.
|
|
18
20
|
*
|
|
19
|
-
* - macOS/Linux: `ps aux` (full command lines)
|
|
20
|
-
* - Windows: `tasklist` (image names like Code.exe,
|
|
21
|
-
*
|
|
22
|
-
*
|
|
21
|
+
* - macOS/Linux: `ps aux` (full command lines, every user's)
|
|
22
|
+
* - Windows: `tasklist /fo csv /nh` (full image names like Code.exe,
|
|
23
|
+
* Cursor.exe; the default table truncates them at 25 characters), asked for
|
|
24
|
+
* the current user's processes only. tasklist rows carry a Session#, not
|
|
25
|
+
* an account, and another account can run in the candidate's session
|
|
26
|
+
* (`runas`), so the user filter has to happen here, and it never widens:
|
|
27
|
+
* see ownTasklist.
|
|
23
28
|
*/
|
|
24
29
|
export function getProcessList() {
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
if (!isWin)
|
|
31
|
+
return execSync("ps aux", { timeout: 5000, encoding: "utf8", windowsHide: true }).toLowerCase();
|
|
32
|
+
const user = userInfo().username;
|
|
33
|
+
const domain = process.env.USERDOMAIN;
|
|
34
|
+
return ownTasklist((account) => execFileSync("tasklist", ["/fo", "csv", "/nh", "/fi", `USERNAME eq ${account}`], {
|
|
35
|
+
timeout: 5000,
|
|
36
|
+
encoding: "utf8",
|
|
37
|
+
windowsHide: true,
|
|
38
|
+
}), domain ? [`${domain}\\${user}`, user] : [user], process.pid);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The first user-filtered tasklist that contains this very process (`pid`),
|
|
42
|
+
* trying each spelling of the account in turn (DOMAIN\user, then user).
|
|
43
|
+
* When none does, the filter did not match how this machine names the
|
|
44
|
+
* account, and ownership cannot be established: the result is empty, so
|
|
45
|
+
* nothing counts. It never falls back to an unfiltered or session-scoped
|
|
46
|
+
* list, which would badge another account's processes.
|
|
47
|
+
*/
|
|
48
|
+
export function ownTasklist(run, accounts, pid) {
|
|
49
|
+
for (const account of accounts) {
|
|
50
|
+
try {
|
|
51
|
+
const out = run(account).toLowerCase();
|
|
52
|
+
if (out.includes(`","${pid}","`))
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
catch { /* try the next spelling */ }
|
|
56
|
+
}
|
|
57
|
+
return "";
|
|
27
58
|
}
|
|
28
59
|
/**
|
|
29
60
|
* Lists active network connections asynchronously.
|