@k1e1n04/mav 0.1.30 → 0.1.32
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 +3 -1
- package/dist/package.json +1 -1
- package/dist/src/agent.d.ts +1 -0
- package/dist/src/agent.js +23 -2
- package/dist/src/agent.js.map +1 -1
- package/dist/src/hook-injector.js +2 -83
- package/dist/src/hook-injector.js.map +1 -1
- package/dist/src/process-cwd.d.ts +1 -0
- package/dist/src/process-cwd.js +105 -1
- package/dist/src/process-cwd.js.map +1 -1
- package/package.json +1 -1
- package/src/agent.ts +20 -2
- package/src/hook-injector.ts +2 -101
- package/src/process-cwd.ts +117 -1
package/README.md
CHANGED
|
@@ -120,13 +120,15 @@ mav tracks each agent's working directory. When an agent changes directory (e.g.
|
|
|
120
120
|
|
|
121
121
|
| Agent | CWD Tracking | Hook Auto-injection |
|
|
122
122
|
|---|---|---|
|
|
123
|
-
| `claude-code` |
|
|
123
|
+
| `claude-code` | child PID polling + OSC 7 | ❌ No injection needed |
|
|
124
124
|
| `gemini-cli` | IPC + AfterTool hook | ✅ Auto-injected via `.gemini/settings.local.json` |
|
|
125
125
|
| `codex` | IPC + PostToolUse hook | ✅ Auto-injected via `--profile-v2` |
|
|
126
126
|
| `copilot` | IPC + PostToolUse hook | ✅ Auto-injected via `.github/hooks/*.json` |
|
|
127
127
|
| `cursor` | lsof polling only | ❌ Not supported |
|
|
128
128
|
| Other | lsof polling only | ❌ Not supported |
|
|
129
129
|
|
|
130
|
+
`claude-code` CWD tracking works by finding the Claude Code process (direct child of the shell PTY) and polling its own working directory. This correctly captures `process.chdir()` calls made by Claude Code when switching git worktrees, without any hook injection.
|
|
131
|
+
|
|
130
132
|
> **Cursor** and unlisted agents use process polling to track the working directory.
|
|
131
133
|
> Directory changes made in child processes (e.g., git worktrees) may not be detected.
|
|
132
134
|
>
|
package/dist/package.json
CHANGED
package/dist/src/agent.d.ts
CHANGED
package/dist/src/agent.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
2
|
import * as pty from 'node-pty';
|
|
3
|
-
import { getProcessCwd } from './process-cwd.js';
|
|
3
|
+
import { getProcessCwd, getClaudeChildPid } from './process-cwd.js';
|
|
4
4
|
import { cleanupHookFiles } from './hook-injector.js';
|
|
5
5
|
const counters = {};
|
|
6
6
|
export class AgentSession extends EventEmitter {
|
|
@@ -22,6 +22,7 @@ export class AgentSession extends EventEmitter {
|
|
|
22
22
|
exited = false;
|
|
23
23
|
idleTimer = null;
|
|
24
24
|
cwdPollTimer = null;
|
|
25
|
+
claudeChildPid = null;
|
|
25
26
|
displayNameLocked = false;
|
|
26
27
|
initialInputBuffer = '';
|
|
27
28
|
hookFiles = [];
|
|
@@ -41,6 +42,11 @@ export class AgentSession extends EventEmitter {
|
|
|
41
42
|
env.MAV_SESSION_ID = this.id;
|
|
42
43
|
this.hookFiles = ipcContext.hookFiles;
|
|
43
44
|
}
|
|
45
|
+
else {
|
|
46
|
+
// Prevent child processes from inheriting the parent mav session's socket
|
|
47
|
+
delete env.MAV_SOCKET;
|
|
48
|
+
delete env.MAV_SESSION_ID;
|
|
49
|
+
}
|
|
44
50
|
proc = pty.spawn(config.cmd, config.args, {
|
|
45
51
|
name: 'xterm-256color',
|
|
46
52
|
cols,
|
|
@@ -153,7 +159,22 @@ export class AgentSession extends EventEmitter {
|
|
|
153
159
|
if (this.exited || !this.ptyProcess) {
|
|
154
160
|
return;
|
|
155
161
|
}
|
|
156
|
-
|
|
162
|
+
if (this.type === 'claude-code') {
|
|
163
|
+
if (this.claudeChildPid === null) {
|
|
164
|
+
this.claudeChildPid = getClaudeChildPid(this.ptyProcess.pid);
|
|
165
|
+
}
|
|
166
|
+
const targetPid = this.claudeChildPid ?? this.ptyProcess.pid;
|
|
167
|
+
const cwd = getProcessCwd(targetPid);
|
|
168
|
+
if (cwd === null && this.claudeChildPid !== null) {
|
|
169
|
+
this.claudeChildPid = null;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
this.updateCwd(cwd);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
this.updateCwd(getProcessCwd(this.ptyProcess.pid));
|
|
177
|
+
}
|
|
157
178
|
}, AgentSession.CWD_POLL_INTERVAL_MS);
|
|
158
179
|
}
|
|
159
180
|
clearCwdPollTimer() {
|
package/dist/src/agent.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,KAAK,GAAG,MAAM,UAAU,CAAA;AAE/B,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/agent.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { writeFileSync, readFileSync, existsSync, unlinkSync, mkdirSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { homedir
|
|
4
|
-
import { randomUUID
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
5
|
/**
|
|
6
6
|
* Injects a hook into the startup args for the given agent type and returns the result.
|
|
7
7
|
* Generated temp files are listed in hookFiles.
|
|
@@ -9,8 +9,6 @@ import { randomUUID, createHash } from 'node:crypto';
|
|
|
9
9
|
*/
|
|
10
10
|
export function buildHookArgs(agentType, baseArgs, hookCommand, options = {}) {
|
|
11
11
|
switch (agentType) {
|
|
12
|
-
case 'claude-code':
|
|
13
|
-
return buildClaudeCodeHook(baseArgs, hookCommand, options.cmd, options.settingsFile);
|
|
14
12
|
case 'gemini-cli':
|
|
15
13
|
return buildGeminiHook(baseArgs, hookCommand, options.cwd);
|
|
16
14
|
case 'copilot':
|
|
@@ -21,85 +19,6 @@ export function buildHookArgs(agentType, baseArgs, hookCommand, options = {}) {
|
|
|
21
19
|
return { args: baseArgs, hookFiles: [] };
|
|
22
20
|
}
|
|
23
21
|
}
|
|
24
|
-
function buildClaudeCodeHook(baseArgs, hookCommand, cmd, settingsFile) {
|
|
25
|
-
// If a custom wrapper is used (e.g. claude-launcher), skip --settings <json> injection.
|
|
26
|
-
// Wrappers detect --settings in their args and skip their own overlay (including
|
|
27
|
-
// apiKeyHelper), which breaks authentication.
|
|
28
|
-
if (cmd != null && cmd !== 'claude') {
|
|
29
|
-
if (settingsFile) {
|
|
30
|
-
return buildClaudeCodeHookViaFile(baseArgs, hookCommand, settingsFile);
|
|
31
|
-
}
|
|
32
|
-
return { args: baseArgs, hookFiles: [] };
|
|
33
|
-
}
|
|
34
|
-
const claudeSettingsPath = join(homedir(), '.claude', 'settings.json');
|
|
35
|
-
let existing = {};
|
|
36
|
-
if (existsSync(claudeSettingsPath)) {
|
|
37
|
-
try {
|
|
38
|
-
existing = JSON.parse(readFileSync(claudeSettingsPath, 'utf-8'));
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
// Unreadable/invalid JSON — start from empty
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
const existingHooks = (existing.hooks ?? {});
|
|
45
|
-
const existingPostToolUse = (existingHooks.PostToolUse ?? []);
|
|
46
|
-
const merged = {
|
|
47
|
-
...existing,
|
|
48
|
-
hooks: {
|
|
49
|
-
...existingHooks,
|
|
50
|
-
PostToolUse: [
|
|
51
|
-
...existingPostToolUse,
|
|
52
|
-
{
|
|
53
|
-
matcher: '',
|
|
54
|
-
hooks: [{ type: 'command', command: hookCommand }],
|
|
55
|
-
},
|
|
56
|
-
],
|
|
57
|
-
},
|
|
58
|
-
};
|
|
59
|
-
return {
|
|
60
|
-
args: [...baseArgs, '--settings', JSON.stringify(merged)],
|
|
61
|
-
hookFiles: [],
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
function buildClaudeCodeHookViaFile(baseArgs, hookCommand, settingsFile) {
|
|
65
|
-
const expandedPath = settingsFile.replace(/^~/, homedir());
|
|
66
|
-
// Read the overlay file BEFORE the wrapper starts (and potentially overwrites it).
|
|
67
|
-
// The wrapper's overlay content (apiKeyHelper etc.) is preserved in the temp file,
|
|
68
|
-
// so even though the wrapper skips its own overlay injection, auth still works.
|
|
69
|
-
let existing = {};
|
|
70
|
-
if (existsSync(expandedPath)) {
|
|
71
|
-
try {
|
|
72
|
-
existing = JSON.parse(readFileSync(expandedPath, 'utf-8'));
|
|
73
|
-
}
|
|
74
|
-
catch {
|
|
75
|
-
// Treat as empty
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
const existingHooks = (existing.hooks ?? {});
|
|
79
|
-
const existingPostToolUse = (existingHooks.PostToolUse ?? []);
|
|
80
|
-
const merged = {
|
|
81
|
-
...existing,
|
|
82
|
-
hooks: {
|
|
83
|
-
...existingHooks,
|
|
84
|
-
PostToolUse: [
|
|
85
|
-
...existingPostToolUse,
|
|
86
|
-
{
|
|
87
|
-
matcher: '',
|
|
88
|
-
hooks: [{ type: 'command', command: hookCommand }],
|
|
89
|
-
},
|
|
90
|
-
],
|
|
91
|
-
},
|
|
92
|
-
};
|
|
93
|
-
// Use a deterministic name based on the settingsFile path so that at most one
|
|
94
|
-
// temp file exists per overlay path — crash leftovers get overwritten next run.
|
|
95
|
-
const pathHash = createHash('sha256').update(expandedPath).digest('hex').slice(0, 8);
|
|
96
|
-
const tempPath = join(tmpdir(), `mav-settings-${pathHash}.json`);
|
|
97
|
-
writeFileSync(tempPath, JSON.stringify(merged, null, 2));
|
|
98
|
-
return {
|
|
99
|
-
args: [...baseArgs, '--settings', tempPath],
|
|
100
|
-
hookFiles: [tempPath],
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
22
|
const MAV_MARKER = '_mavGenerated';
|
|
104
23
|
function buildGeminiHook(baseArgs, hookCommand, cwd) {
|
|
105
24
|
const projectDir = cwd ?? process.cwd();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hook-injector.js","sourceRoot":"","sources":["../../src/hook-injector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AACxF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"hook-injector.js","sourceRoot":"","sources":["../../src/hook-injector.ts"],"names":[],"mappings":"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"}
|
package/dist/src/process-cwd.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
|
-
import { readlinkSync } from 'node:fs';
|
|
2
|
+
import { readlinkSync, readFileSync } from 'node:fs';
|
|
3
3
|
export function getProcessCwd(pid, platform = process.platform) {
|
|
4
4
|
if (!Number.isInteger(pid) || pid <= 0) {
|
|
5
5
|
return null;
|
|
@@ -21,4 +21,108 @@ export function getProcessCwd(pid, platform = process.platform) {
|
|
|
21
21
|
}
|
|
22
22
|
return null;
|
|
23
23
|
}
|
|
24
|
+
const CLAUDE_CHILD_MAX_DEPTH = 5;
|
|
25
|
+
export function getClaudeChildPid(shellPid, platform = process.platform) {
|
|
26
|
+
if (!Number.isInteger(shellPid) || shellPid <= 0) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
if (platform === 'darwin') {
|
|
30
|
+
return findClaudeChildDarwin(shellPid);
|
|
31
|
+
}
|
|
32
|
+
if (platform === 'linux') {
|
|
33
|
+
return findClaudeChildLinux(shellPid);
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
function findClaudeChildDarwin(shellPid) {
|
|
38
|
+
let initialChildren;
|
|
39
|
+
try {
|
|
40
|
+
initialChildren = execFileSync('pgrep', ['-P', String(shellPid)], { encoding: 'utf8' })
|
|
41
|
+
.split('\n')
|
|
42
|
+
.map((s) => s.trim())
|
|
43
|
+
.filter(Boolean);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const queue = initialChildren.map((p) => ({ pidStr: p, depth: 0 }));
|
|
49
|
+
while (queue.length > 0) {
|
|
50
|
+
const item = queue.shift();
|
|
51
|
+
if (item.depth >= CLAUDE_CHILD_MAX_DEPTH)
|
|
52
|
+
continue;
|
|
53
|
+
try {
|
|
54
|
+
const line = execFileSync('ps', ['-o', 'pid=,args=', '-p', item.pidStr], { encoding: 'utf8' }).trim();
|
|
55
|
+
if (line) {
|
|
56
|
+
const spaceIdx = line.search(/\s/);
|
|
57
|
+
if (spaceIdx !== -1) {
|
|
58
|
+
const args = line.slice(spaceIdx).trim();
|
|
59
|
+
const binary = args.split(' ')[0] ?? '';
|
|
60
|
+
if ((binary === 'node' || binary.endsWith('/node')) && args.includes('claude')) {
|
|
61
|
+
return parseInt(item.pidStr, 10);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
// Not claude — enqueue children for next level
|
|
70
|
+
try {
|
|
71
|
+
const children = execFileSync('pgrep', ['-P', item.pidStr], { encoding: 'utf8' })
|
|
72
|
+
.split('\n')
|
|
73
|
+
.map((s) => s.trim())
|
|
74
|
+
.filter(Boolean);
|
|
75
|
+
for (const child of children) {
|
|
76
|
+
queue.push({ pidStr: child, depth: item.depth + 1 });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// No children — continue
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
function findClaudeChildLinux(shellPid) {
|
|
86
|
+
let initialChildren;
|
|
87
|
+
try {
|
|
88
|
+
initialChildren = readFileSync(`/proc/${shellPid}/task/${shellPid}/children`, 'utf8')
|
|
89
|
+
.split(' ')
|
|
90
|
+
.map((s) => s.trim())
|
|
91
|
+
.filter(Boolean);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const queue = initialChildren.map((p) => ({ pidStr: p, depth: 0 }));
|
|
97
|
+
while (queue.length > 0) {
|
|
98
|
+
const item = queue.shift();
|
|
99
|
+
if (item.depth >= CLAUDE_CHILD_MAX_DEPTH)
|
|
100
|
+
continue;
|
|
101
|
+
try {
|
|
102
|
+
const cmdline = readFileSync(`/proc/${item.pidStr}/cmdline`, 'utf8');
|
|
103
|
+
const parts = cmdline.split('\0').filter(Boolean);
|
|
104
|
+
const binary = parts[0] ?? '';
|
|
105
|
+
if ((binary === 'node' || binary.endsWith('/node')) && cmdline.includes('claude')) {
|
|
106
|
+
return parseInt(item.pidStr, 10);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
// Not claude — enqueue children for next level
|
|
113
|
+
try {
|
|
114
|
+
const children = readFileSync(`/proc/${item.pidStr}/task/${item.pidStr}/children`, 'utf8')
|
|
115
|
+
.split(' ')
|
|
116
|
+
.map((s) => s.trim())
|
|
117
|
+
.filter(Boolean);
|
|
118
|
+
for (const child of children) {
|
|
119
|
+
queue.push({ pidStr: child, depth: item.depth + 1 });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// No children — continue
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
24
128
|
//# sourceMappingURL=process-cwd.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-cwd.js","sourceRoot":"","sources":["../../src/process-cwd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"process-cwd.js","sourceRoot":"","sources":["../../src/process-cwd.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/agent.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events'
|
|
2
2
|
import * as pty from 'node-pty'
|
|
3
3
|
import type { AgentConfig } from './config.js'
|
|
4
|
-
import { getProcessCwd } from './process-cwd.js'
|
|
4
|
+
import { getProcessCwd, getClaudeChildPid } from './process-cwd.js'
|
|
5
5
|
import { cleanupHookFiles } from './hook-injector.js'
|
|
6
6
|
|
|
7
7
|
export type SessionStatus = 'running' | 'idle' | 'done' | 'error'
|
|
@@ -34,6 +34,7 @@ export class AgentSession extends EventEmitter {
|
|
|
34
34
|
private exited = false
|
|
35
35
|
private idleTimer: ReturnType<typeof setTimeout> | null = null
|
|
36
36
|
private cwdPollTimer: ReturnType<typeof setInterval> | null = null
|
|
37
|
+
private claudeChildPid: number | null = null
|
|
37
38
|
private displayNameLocked = false
|
|
38
39
|
private initialInputBuffer = ''
|
|
39
40
|
private hookFiles: string[] = []
|
|
@@ -54,6 +55,10 @@ export class AgentSession extends EventEmitter {
|
|
|
54
55
|
env.MAV_SOCKET = ipcContext.socketPath
|
|
55
56
|
env.MAV_SESSION_ID = this.id
|
|
56
57
|
this.hookFiles = ipcContext.hookFiles
|
|
58
|
+
} else {
|
|
59
|
+
// Prevent child processes from inheriting the parent mav session's socket
|
|
60
|
+
delete env.MAV_SOCKET
|
|
61
|
+
delete env.MAV_SESSION_ID
|
|
57
62
|
}
|
|
58
63
|
proc = pty.spawn(config.cmd, config.args, {
|
|
59
64
|
name: 'xterm-256color',
|
|
@@ -179,7 +184,20 @@ export class AgentSession extends EventEmitter {
|
|
|
179
184
|
return
|
|
180
185
|
}
|
|
181
186
|
|
|
182
|
-
|
|
187
|
+
if (this.type === 'claude-code') {
|
|
188
|
+
if (this.claudeChildPid === null) {
|
|
189
|
+
this.claudeChildPid = getClaudeChildPid(this.ptyProcess.pid)
|
|
190
|
+
}
|
|
191
|
+
const targetPid = this.claudeChildPid ?? this.ptyProcess.pid
|
|
192
|
+
const cwd = getProcessCwd(targetPid)
|
|
193
|
+
if (cwd === null && this.claudeChildPid !== null) {
|
|
194
|
+
this.claudeChildPid = null
|
|
195
|
+
} else {
|
|
196
|
+
this.updateCwd(cwd)
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
this.updateCwd(getProcessCwd(this.ptyProcess.pid))
|
|
200
|
+
}
|
|
183
201
|
}, AgentSession.CWD_POLL_INTERVAL_MS)
|
|
184
202
|
}
|
|
185
203
|
|
package/src/hook-injector.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { writeFileSync, readFileSync, existsSync, unlinkSync, mkdirSync } from 'node:fs'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
|
-
import { homedir
|
|
4
|
-
import { randomUUID
|
|
3
|
+
import { homedir } from 'node:os'
|
|
4
|
+
import { randomUUID } from 'node:crypto'
|
|
5
5
|
|
|
6
6
|
export interface HookInjectionResult {
|
|
7
7
|
/** Injected startup args */
|
|
@@ -37,8 +37,6 @@ export function buildHookArgs(
|
|
|
37
37
|
options: BuildHookArgsOptions = {},
|
|
38
38
|
): HookInjectionResult {
|
|
39
39
|
switch (agentType) {
|
|
40
|
-
case 'claude-code':
|
|
41
|
-
return buildClaudeCodeHook(baseArgs, hookCommand, options.cmd, options.settingsFile)
|
|
42
40
|
case 'gemini-cli':
|
|
43
41
|
return buildGeminiHook(baseArgs, hookCommand, options.cwd)
|
|
44
42
|
case 'copilot':
|
|
@@ -50,103 +48,6 @@ export function buildHookArgs(
|
|
|
50
48
|
}
|
|
51
49
|
}
|
|
52
50
|
|
|
53
|
-
function buildClaudeCodeHook(
|
|
54
|
-
baseArgs: string[],
|
|
55
|
-
hookCommand: string,
|
|
56
|
-
cmd?: string,
|
|
57
|
-
settingsFile?: string,
|
|
58
|
-
): HookInjectionResult {
|
|
59
|
-
// If a custom wrapper is used (e.g. claude-launcher), skip --settings <json> injection.
|
|
60
|
-
// Wrappers detect --settings in their args and skip their own overlay (including
|
|
61
|
-
// apiKeyHelper), which breaks authentication.
|
|
62
|
-
if (cmd != null && cmd !== 'claude') {
|
|
63
|
-
if (settingsFile) {
|
|
64
|
-
return buildClaudeCodeHookViaFile(baseArgs, hookCommand, settingsFile)
|
|
65
|
-
}
|
|
66
|
-
return { args: baseArgs, hookFiles: [] }
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const claudeSettingsPath = join(homedir(), '.claude', 'settings.json')
|
|
70
|
-
let existing: Record<string, unknown> = {}
|
|
71
|
-
if (existsSync(claudeSettingsPath)) {
|
|
72
|
-
try {
|
|
73
|
-
existing = JSON.parse(readFileSync(claudeSettingsPath, 'utf-8')) as Record<string, unknown>
|
|
74
|
-
} catch {
|
|
75
|
-
// Unreadable/invalid JSON — start from empty
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const existingHooks = (existing.hooks ?? {}) as Record<string, unknown[]>
|
|
80
|
-
const existingPostToolUse = (existingHooks.PostToolUse ?? []) as unknown[]
|
|
81
|
-
|
|
82
|
-
const merged = {
|
|
83
|
-
...existing,
|
|
84
|
-
hooks: {
|
|
85
|
-
...existingHooks,
|
|
86
|
-
PostToolUse: [
|
|
87
|
-
...existingPostToolUse,
|
|
88
|
-
{
|
|
89
|
-
matcher: '',
|
|
90
|
-
hooks: [{ type: 'command', command: hookCommand }],
|
|
91
|
-
},
|
|
92
|
-
],
|
|
93
|
-
},
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
args: [...baseArgs, '--settings', JSON.stringify(merged)],
|
|
98
|
-
hookFiles: [],
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function buildClaudeCodeHookViaFile(
|
|
103
|
-
baseArgs: string[],
|
|
104
|
-
hookCommand: string,
|
|
105
|
-
settingsFile: string,
|
|
106
|
-
): HookInjectionResult {
|
|
107
|
-
const expandedPath = settingsFile.replace(/^~/, homedir())
|
|
108
|
-
|
|
109
|
-
// Read the overlay file BEFORE the wrapper starts (and potentially overwrites it).
|
|
110
|
-
// The wrapper's overlay content (apiKeyHelper etc.) is preserved in the temp file,
|
|
111
|
-
// so even though the wrapper skips its own overlay injection, auth still works.
|
|
112
|
-
let existing: Record<string, unknown> = {}
|
|
113
|
-
if (existsSync(expandedPath)) {
|
|
114
|
-
try {
|
|
115
|
-
existing = JSON.parse(readFileSync(expandedPath, 'utf-8')) as Record<string, unknown>
|
|
116
|
-
} catch {
|
|
117
|
-
// Treat as empty
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const existingHooks = (existing.hooks ?? {}) as Record<string, unknown[]>
|
|
122
|
-
const existingPostToolUse = (existingHooks.PostToolUse ?? []) as unknown[]
|
|
123
|
-
|
|
124
|
-
const merged = {
|
|
125
|
-
...existing,
|
|
126
|
-
hooks: {
|
|
127
|
-
...existingHooks,
|
|
128
|
-
PostToolUse: [
|
|
129
|
-
...existingPostToolUse,
|
|
130
|
-
{
|
|
131
|
-
matcher: '',
|
|
132
|
-
hooks: [{ type: 'command', command: hookCommand }],
|
|
133
|
-
},
|
|
134
|
-
],
|
|
135
|
-
},
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Use a deterministic name based on the settingsFile path so that at most one
|
|
139
|
-
// temp file exists per overlay path — crash leftovers get overwritten next run.
|
|
140
|
-
const pathHash = createHash('sha256').update(expandedPath).digest('hex').slice(0, 8)
|
|
141
|
-
const tempPath = join(tmpdir(), `mav-settings-${pathHash}.json`)
|
|
142
|
-
writeFileSync(tempPath, JSON.stringify(merged, null, 2))
|
|
143
|
-
|
|
144
|
-
return {
|
|
145
|
-
args: [...baseArgs, '--settings', tempPath],
|
|
146
|
-
hookFiles: [tempPath],
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
51
|
const MAV_MARKER = '_mavGenerated'
|
|
151
52
|
|
|
152
53
|
function buildGeminiHook(
|
package/src/process-cwd.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process'
|
|
2
|
-
import { readlinkSync } from 'node:fs'
|
|
2
|
+
import { readlinkSync, readFileSync } from 'node:fs'
|
|
3
3
|
|
|
4
4
|
export function getProcessCwd(pid: number, platform: NodeJS.Platform = process.platform): string | null {
|
|
5
5
|
if (!Number.isInteger(pid) || pid <= 0) {
|
|
@@ -29,3 +29,119 @@ export function getProcessCwd(pid: number, platform: NodeJS.Platform = process.p
|
|
|
29
29
|
|
|
30
30
|
return null
|
|
31
31
|
}
|
|
32
|
+
|
|
33
|
+
const CLAUDE_CHILD_MAX_DEPTH = 5
|
|
34
|
+
|
|
35
|
+
export function getClaudeChildPid(
|
|
36
|
+
shellPid: number,
|
|
37
|
+
platform: NodeJS.Platform = process.platform,
|
|
38
|
+
): number | null {
|
|
39
|
+
if (!Number.isInteger(shellPid) || shellPid <= 0) {
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (platform === 'darwin') {
|
|
44
|
+
return findClaudeChildDarwin(shellPid)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (platform === 'linux') {
|
|
48
|
+
return findClaudeChildLinux(shellPid)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function findClaudeChildDarwin(shellPid: number): number | null {
|
|
55
|
+
let initialChildren: string[]
|
|
56
|
+
try {
|
|
57
|
+
initialChildren = execFileSync('pgrep', ['-P', String(shellPid)], { encoding: 'utf8' })
|
|
58
|
+
.split('\n')
|
|
59
|
+
.map((s) => s.trim())
|
|
60
|
+
.filter(Boolean)
|
|
61
|
+
} catch {
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const queue: Array<{ pidStr: string; depth: number }> = initialChildren.map((p) => ({ pidStr: p, depth: 0 }))
|
|
66
|
+
|
|
67
|
+
while (queue.length > 0) {
|
|
68
|
+
const item = queue.shift()!
|
|
69
|
+
if (item.depth >= CLAUDE_CHILD_MAX_DEPTH) continue
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const line = execFileSync('ps', ['-o', 'pid=,args=', '-p', item.pidStr], { encoding: 'utf8' }).trim()
|
|
73
|
+
if (line) {
|
|
74
|
+
const spaceIdx = line.search(/\s/)
|
|
75
|
+
if (spaceIdx !== -1) {
|
|
76
|
+
const args = line.slice(spaceIdx).trim()
|
|
77
|
+
const binary = args.split(' ')[0] ?? ''
|
|
78
|
+
if ((binary === 'node' || binary.endsWith('/node')) && args.includes('claude')) {
|
|
79
|
+
return parseInt(item.pidStr, 10)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
continue
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Not claude — enqueue children for next level
|
|
88
|
+
try {
|
|
89
|
+
const children = execFileSync('pgrep', ['-P', item.pidStr], { encoding: 'utf8' })
|
|
90
|
+
.split('\n')
|
|
91
|
+
.map((s) => s.trim())
|
|
92
|
+
.filter(Boolean)
|
|
93
|
+
for (const child of children) {
|
|
94
|
+
queue.push({ pidStr: child, depth: item.depth + 1 })
|
|
95
|
+
}
|
|
96
|
+
} catch {
|
|
97
|
+
// No children — continue
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return null
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function findClaudeChildLinux(shellPid: number): number | null {
|
|
105
|
+
let initialChildren: string[]
|
|
106
|
+
try {
|
|
107
|
+
initialChildren = readFileSync(`/proc/${shellPid}/task/${shellPid}/children`, 'utf8')
|
|
108
|
+
.split(' ')
|
|
109
|
+
.map((s) => s.trim())
|
|
110
|
+
.filter(Boolean)
|
|
111
|
+
} catch {
|
|
112
|
+
return null
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const queue: Array<{ pidStr: string; depth: number }> = initialChildren.map((p) => ({ pidStr: p, depth: 0 }))
|
|
116
|
+
|
|
117
|
+
while (queue.length > 0) {
|
|
118
|
+
const item = queue.shift()!
|
|
119
|
+
if (item.depth >= CLAUDE_CHILD_MAX_DEPTH) continue
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const cmdline = readFileSync(`/proc/${item.pidStr}/cmdline`, 'utf8')
|
|
123
|
+
const parts = cmdline.split('\0').filter(Boolean)
|
|
124
|
+
const binary = parts[0] ?? ''
|
|
125
|
+
if ((binary === 'node' || binary.endsWith('/node')) && cmdline.includes('claude')) {
|
|
126
|
+
return parseInt(item.pidStr, 10)
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
continue
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Not claude — enqueue children for next level
|
|
133
|
+
try {
|
|
134
|
+
const children = readFileSync(`/proc/${item.pidStr}/task/${item.pidStr}/children`, 'utf8')
|
|
135
|
+
.split(' ')
|
|
136
|
+
.map((s) => s.trim())
|
|
137
|
+
.filter(Boolean)
|
|
138
|
+
for (const child of children) {
|
|
139
|
+
queue.push({ pidStr: child, depth: item.depth + 1 })
|
|
140
|
+
}
|
|
141
|
+
} catch {
|
|
142
|
+
// No children — continue
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return null
|
|
147
|
+
}
|