@phnx-labs/agents-cli 1.19.2 → 1.20.3
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/CHANGELOG.md +140 -0
- package/README.md +72 -12
- package/dist/browser.js +0 -0
- package/dist/commands/browser.js +88 -16
- package/dist/commands/cli.d.ts +14 -0
- package/dist/commands/cli.js +244 -0
- package/dist/commands/cloud.js +1 -1
- package/dist/commands/commands.js +27 -10
- package/dist/commands/computer.js +18 -1
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/doctor.js +2 -2
- package/dist/commands/exec.js +38 -18
- package/dist/commands/factory.d.ts +3 -14
- package/dist/commands/factory.js +3 -3
- package/dist/commands/feedback.d.ts +7 -0
- package/dist/commands/feedback.js +89 -0
- package/dist/commands/helper.d.ts +12 -0
- package/dist/commands/helper.js +87 -0
- package/dist/commands/hooks.js +89 -10
- package/dist/commands/mcp.js +166 -10
- package/dist/commands/packages.js +196 -27
- package/dist/commands/permissions.js +21 -6
- package/dist/commands/plugins.js +11 -4
- package/dist/commands/profiles.d.ts +8 -0
- package/dist/commands/profiles.js +118 -5
- package/dist/commands/prune.js +39 -160
- package/dist/commands/pull.js +58 -5
- package/dist/commands/routines.js +107 -14
- package/dist/commands/rules.js +8 -4
- package/dist/commands/secrets-migrate.d.ts +24 -0
- package/dist/commands/secrets-migrate.js +198 -0
- package/dist/commands/secrets-sync.d.ts +11 -0
- package/dist/commands/secrets-sync.js +155 -0
- package/dist/commands/secrets.js +79 -46
- package/dist/commands/sessions.d.ts +28 -0
- package/dist/commands/sessions.js +98 -33
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.js +37 -28
- package/dist/commands/skills.js +25 -8
- package/dist/commands/subagents.js +69 -49
- package/dist/commands/teams.js +61 -10
- package/dist/commands/utils.d.ts +33 -0
- package/dist/commands/utils.js +139 -0
- package/dist/commands/versions.d.ts +4 -3
- package/dist/commands/versions.js +134 -130
- package/dist/commands/view.d.ts +6 -0
- package/dist/commands/view.js +175 -19
- package/dist/commands/workflows.js +29 -6
- package/dist/computer.js +0 -0
- package/dist/index.js +38 -6
- package/dist/lib/acp/client.js +6 -1
- package/dist/lib/acp/harnesses.js +8 -0
- package/dist/lib/agents.d.ts +4 -0
- package/dist/lib/agents.js +125 -34
- package/dist/lib/auto-pull-worker.js +18 -1
- package/dist/lib/browser/cdp.d.ts +8 -1
- package/dist/lib/browser/cdp.js +40 -3
- package/dist/lib/browser/chrome.d.ts +13 -0
- package/dist/lib/browser/chrome.js +46 -3
- package/dist/lib/browser/domain-skills.d.ts +51 -0
- package/dist/lib/browser/domain-skills.js +157 -0
- package/dist/lib/browser/drivers/local.js +45 -4
- package/dist/lib/browser/drivers/ssh.js +2 -2
- package/dist/lib/browser/ipc.d.ts +8 -1
- package/dist/lib/browser/ipc.js +37 -28
- package/dist/lib/browser/profiles.d.ts +16 -3
- package/dist/lib/browser/profiles.js +44 -4
- package/dist/lib/browser/service.d.ts +3 -0
- package/dist/lib/browser/service.js +40 -5
- package/dist/lib/browser/types.d.ts +11 -4
- package/dist/lib/cli-resources.d.ts +137 -0
- package/dist/lib/cli-resources.js +477 -0
- package/dist/lib/cloud/factory.d.ts +1 -1
- package/dist/lib/cloud/factory.js +1 -1
- package/dist/lib/cloud/rush.js +5 -5
- package/dist/lib/command-skills.js +0 -2
- package/dist/lib/computer-rpc.d.ts +3 -0
- package/dist/lib/computer-rpc.js +53 -0
- package/dist/lib/daemon.js +20 -0
- package/dist/lib/events.d.ts +16 -2
- package/dist/lib/events.js +33 -2
- package/dist/lib/exec.d.ts +42 -13
- package/dist/lib/exec.js +127 -33
- package/dist/lib/help.js +11 -5
- package/dist/lib/hooks/cache.d.ts +38 -0
- package/dist/lib/hooks/cache.js +242 -0
- package/dist/lib/hooks/profile.d.ts +33 -0
- package/dist/lib/hooks/profile.js +129 -0
- package/dist/lib/hooks.d.ts +0 -10
- package/dist/lib/hooks.js +246 -11
- package/dist/lib/mcp.d.ts +15 -0
- package/dist/lib/mcp.js +46 -0
- package/dist/lib/migrate.js +1 -1
- package/dist/lib/overdue.d.ts +26 -0
- package/dist/lib/overdue.js +101 -0
- package/dist/lib/permissions.d.ts +13 -0
- package/dist/lib/permissions.js +55 -1
- package/dist/lib/plugin-marketplace.js +1 -1
- package/dist/lib/plugins.js +15 -1
- package/dist/lib/profiles-presets.d.ts +26 -0
- package/dist/lib/profiles-presets.js +216 -0
- package/dist/lib/profiles.d.ts +34 -0
- package/dist/lib/profiles.js +112 -1
- package/dist/lib/resources/mcp.js +37 -0
- package/dist/lib/resources.d.ts +1 -1
- package/dist/lib/rotate.js +10 -4
- package/dist/lib/routines-format.d.ts +47 -0
- package/dist/lib/routines-format.js +194 -0
- package/dist/lib/routines.d.ts +8 -2
- package/dist/lib/routines.js +34 -14
- package/dist/lib/runner.js +83 -15
- package/dist/lib/scheduler.js +8 -1
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -9
- package/dist/lib/secrets/bundles.d.ts +34 -17
- package/dist/lib/secrets/bundles.js +210 -36
- package/dist/lib/secrets/index.d.ts +49 -30
- package/dist/lib/secrets/index.js +126 -115
- package/dist/lib/secrets/install-helper.d.ts +45 -0
- package/dist/lib/secrets/install-helper.js +165 -0
- package/dist/lib/secrets/linux.js +4 -4
- package/dist/lib/secrets/sync.d.ts +56 -0
- package/dist/lib/secrets/sync.js +180 -0
- package/dist/lib/session/active.d.ts +8 -0
- package/dist/lib/session/active.js +3 -2
- package/dist/lib/session/db.d.ts +0 -4
- package/dist/lib/session/db.js +0 -26
- package/dist/lib/session/parse.d.ts +1 -0
- package/dist/lib/session/parse.js +44 -0
- package/dist/lib/session/render.js +4 -4
- package/dist/lib/session/types.d.ts +2 -2
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/shims.d.ts +5 -2
- package/dist/lib/shims.js +70 -38
- package/dist/lib/state.d.ts +14 -2
- package/dist/lib/state.js +51 -20
- package/dist/lib/teams/agents.d.ts +5 -4
- package/dist/lib/teams/agents.js +48 -22
- package/dist/lib/teams/api.d.ts +2 -1
- package/dist/lib/teams/api.js +4 -3
- package/dist/lib/teams/parsers.d.ts +1 -1
- package/dist/lib/teams/parsers.js +153 -3
- package/dist/lib/teams/summarizer.js +18 -2
- package/dist/lib/teams/worktree.js +14 -3
- package/dist/lib/types.d.ts +63 -4
- package/dist/lib/types.js +8 -3
- package/dist/lib/usage.d.ts +27 -2
- package/dist/lib/usage.js +100 -17
- package/dist/lib/versions.d.ts +45 -3
- package/dist/lib/versions.js +455 -60
- package/package.json +15 -14
- package/scripts/install-helper.js +97 -0
- package/scripts/postinstall.js +16 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
- package/npm-shrinkwrap.json +0 -3162
|
@@ -15,6 +15,9 @@ export declare function resolveLogPath(): string;
|
|
|
15
15
|
export declare function resolvePolicyPath(): string;
|
|
16
16
|
export declare function loadComputerAllowList(): string[];
|
|
17
17
|
export declare function writeComputerPolicy(allowedBundleIds: string[]): void;
|
|
18
|
+
export declare function resolvePeersPath(): string;
|
|
19
|
+
export declare function loadDefaultPeers(): string[];
|
|
20
|
+
export declare function writeComputerPeers(allowedExecPaths: string[]): void;
|
|
18
21
|
export declare function resolveHelperExec(): string | null;
|
|
19
22
|
export declare function resolveHelperApp(): string | null;
|
|
20
23
|
export declare function openComputerClient(): ComputerClient;
|
package/dist/lib/computer-rpc.js
CHANGED
|
@@ -110,6 +110,59 @@ export function writeComputerPolicy(allowedBundleIds) {
|
|
|
110
110
|
const policy = { allow: allowedBundleIds };
|
|
111
111
|
fs.writeFileSync(resolvePolicyPath(), JSON.stringify(policy, null, 2), { mode: 0o600 });
|
|
112
112
|
}
|
|
113
|
+
// Peer-auth (F5): the helper reads a list of executable paths it will
|
|
114
|
+
// accept connections from. Anything else — `nc`, `/usr/bin/python3`, a
|
|
115
|
+
// random electron app — gets the socket closed before its first RPC.
|
|
116
|
+
// File mirrors computer-policy.json: JSON, mode 0600, missing/unparseable
|
|
117
|
+
// means deny-everything.
|
|
118
|
+
export function resolvePeersPath() {
|
|
119
|
+
return path.join(getHelpersDir(), 'computer-peers.json');
|
|
120
|
+
}
|
|
121
|
+
// Default peer set: this exact `agents` CLI binary plus Rush.app if it's
|
|
122
|
+
// installed. realpath() the symlink chain so we record the on-disk path
|
|
123
|
+
// the helper will see via proc_pidpath, not the shim path.
|
|
124
|
+
//
|
|
125
|
+
// Why path-based instead of codesign-team-id? The agents CLI is unsigned
|
|
126
|
+
// today (npm distribution), and even if we sign Rush.app the team-id
|
|
127
|
+
// check would need a separate roundtrip. Path is concrete and fast; the
|
|
128
|
+
// daemon already runs as the user so anyone who can swap a binary at
|
|
129
|
+
// these paths can do worse via other means.
|
|
130
|
+
export function loadDefaultPeers() {
|
|
131
|
+
const out = new Set();
|
|
132
|
+
const add = (p) => {
|
|
133
|
+
try {
|
|
134
|
+
out.add(fs.realpathSync(p));
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
out.add(p);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
// The Node executable currently running the CLI. This is what
|
|
141
|
+
// proc_pidpath() will report when the CLI calls into the daemon.
|
|
142
|
+
if (process.execPath)
|
|
143
|
+
add(process.execPath);
|
|
144
|
+
// Rush.app — the consumer Electron client. Both the helper-binary and
|
|
145
|
+
// the main app binary are possible callers depending on how Rush wires
|
|
146
|
+
// the RPC client.
|
|
147
|
+
const rushCandidates = [
|
|
148
|
+
'/Applications/Rush.app/Contents/MacOS/Rush',
|
|
149
|
+
'/Applications/Rush.app/Contents/MacOS/Electron',
|
|
150
|
+
];
|
|
151
|
+
for (const p of rushCandidates) {
|
|
152
|
+
if (fs.existsSync(p))
|
|
153
|
+
add(p);
|
|
154
|
+
}
|
|
155
|
+
return [...out].sort();
|
|
156
|
+
}
|
|
157
|
+
// Write the peer-auth allow list. Same mode 0600 + atomic-ish semantics
|
|
158
|
+
// as the policy file. The daemon picks it up at startup and on SIGHUP.
|
|
159
|
+
export function writeComputerPeers(allowedExecPaths) {
|
|
160
|
+
const dir = getHelpersDir();
|
|
161
|
+
if (!fs.existsSync(dir)) {
|
|
162
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
163
|
+
}
|
|
164
|
+
fs.writeFileSync(resolvePeersPath(), JSON.stringify({ allow: allowedExecPaths }, null, 2), { mode: 0o600 });
|
|
165
|
+
}
|
|
113
166
|
// Resolve the helper executable inside the dist .app bundle. Used by the
|
|
114
167
|
// stdio fallback and by install-helper to find the source bundle.
|
|
115
168
|
export function resolveHelperExec() {
|
package/dist/lib/daemon.js
CHANGED
|
@@ -14,6 +14,7 @@ import { getDaemonDir as getDaemonDirRoot } from './state.js';
|
|
|
14
14
|
import { listJobs as listAllJobs } from './routines.js';
|
|
15
15
|
import { JobScheduler } from './scheduler.js';
|
|
16
16
|
import { executeJobDetached, monitorRunningJobs } from './runner.js';
|
|
17
|
+
import { detectOverdueJobs, notifyOverdue } from './overdue.js';
|
|
17
18
|
import { BrowserService } from './browser/service.js';
|
|
18
19
|
import { BrowserIPCServer } from './browser/ipc.js';
|
|
19
20
|
const PID_FILE = 'daemon.pid';
|
|
@@ -178,6 +179,25 @@ export async function runDaemon() {
|
|
|
178
179
|
for (const job of scheduled) {
|
|
179
180
|
log('INFO', ` ${job.name} -> next: ${job.nextRun?.toISOString() || 'unknown'}`);
|
|
180
181
|
}
|
|
182
|
+
// Backlog detection: any enabled recurring job whose most-recent expected
|
|
183
|
+
// fire is older than its most-recent recorded run is overdue. Happens when
|
|
184
|
+
// the laptop was off or the daemon crashed through a scheduled fire.
|
|
185
|
+
// We log it and pop a native notification — the user can review with
|
|
186
|
+
// `agents routines list` and run them with `agents routines catchup`.
|
|
187
|
+
try {
|
|
188
|
+
const overdue = detectOverdueJobs();
|
|
189
|
+
if (overdue.length > 0) {
|
|
190
|
+
log('WARN', `${overdue.length} routine(s) overdue:`);
|
|
191
|
+
for (const job of overdue) {
|
|
192
|
+
const last = job.lastRanAt ? job.lastRanAt.toISOString() : 'never';
|
|
193
|
+
log('WARN', ` ${job.name} -- expected ${job.expectedAt.toISOString()}, last ran ${last}`);
|
|
194
|
+
}
|
|
195
|
+
notifyOverdue(overdue);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
log('ERROR', `Overdue detection failed: ${err.message}`);
|
|
200
|
+
}
|
|
181
201
|
// Before the BrowserService comes up, reap browser + tunnel processes
|
|
182
202
|
// spawned by previous daemons that are no longer alive. Without this,
|
|
183
203
|
// a daemon hard-crash (SIGKILL, OOM) would leak every browser and SSH
|
package/dist/lib/events.d.ts
CHANGED
|
@@ -32,7 +32,8 @@ export interface EventPayload {
|
|
|
32
32
|
args?: string[];
|
|
33
33
|
input?: string;
|
|
34
34
|
output?: string;
|
|
35
|
-
|
|
35
|
+
prompt_length?: number;
|
|
36
|
+
prompt_sha256?: string;
|
|
36
37
|
durationMs?: number;
|
|
37
38
|
startupMs?: number;
|
|
38
39
|
exitCode?: number;
|
|
@@ -42,6 +43,19 @@ export interface EventPayload {
|
|
|
42
43
|
[key: string]: unknown;
|
|
43
44
|
}
|
|
44
45
|
export type EventRecord = EventMeta & EventPayload;
|
|
46
|
+
/**
|
|
47
|
+
* Replace a prompt string with length + short SHA so we can correlate runs
|
|
48
|
+
* without persisting the raw text. Returns the fields to spread into a payload.
|
|
49
|
+
*/
|
|
50
|
+
export declare function redactPrompt(prompt: string | null | undefined): {
|
|
51
|
+
prompt_length?: number;
|
|
52
|
+
prompt_sha256?: string;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Mask argv entries that look like tokens or secret paths. Preserves structure
|
|
56
|
+
* for debugging but drops the sensitive substring.
|
|
57
|
+
*/
|
|
58
|
+
export declare function redactArgs(args: string[] | undefined): string[] | undefined;
|
|
45
59
|
/**
|
|
46
60
|
* Truncate a string to maxLength, adding ellipsis if truncated.
|
|
47
61
|
* Returns undefined for null/undefined input.
|
|
@@ -124,7 +138,7 @@ export declare function emitError(err: Error | string, payload?: EventPayload):
|
|
|
124
138
|
* Remove log files older than the retention period.
|
|
125
139
|
* Called lazily on emit or explicitly via CLI.
|
|
126
140
|
*
|
|
127
|
-
* @param retentionDays - Number of days to keep (default
|
|
141
|
+
* @param retentionDays - Number of days to keep (default 7, from DEFAULT_RETENTION_DAYS)
|
|
128
142
|
* @returns Number of files removed
|
|
129
143
|
*/
|
|
130
144
|
export declare function rotate(retentionDays?: number): number;
|
package/dist/lib/events.js
CHANGED
|
@@ -14,11 +14,12 @@
|
|
|
14
14
|
import * as fs from 'fs';
|
|
15
15
|
import * as path from 'path';
|
|
16
16
|
import * as os from 'os';
|
|
17
|
+
import { createHash } from 'node:crypto';
|
|
17
18
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
18
19
|
// Logs live under the cache bucket — they're regenerable telemetry.
|
|
19
20
|
const LOGS_DIR = path.join(os.homedir(), '.agents', '.cache', 'logs');
|
|
20
21
|
/** Default retention period in days. */
|
|
21
|
-
const DEFAULT_RETENTION_DAYS =
|
|
22
|
+
const DEFAULT_RETENTION_DAYS = 7;
|
|
22
23
|
/** Default max length for truncated strings. */
|
|
23
24
|
const DEFAULT_TRUNCATE_LENGTH = 500;
|
|
24
25
|
/** Environment variable to disable event logging. */
|
|
@@ -68,6 +69,36 @@ function ensureLogsDir() {
|
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
}
|
|
72
|
+
// ─── Redaction ────────────────────────────────────────────────────────────────
|
|
73
|
+
/**
|
|
74
|
+
* Replace a prompt string with length + short SHA so we can correlate runs
|
|
75
|
+
* without persisting the raw text. Returns the fields to spread into a payload.
|
|
76
|
+
*/
|
|
77
|
+
export function redactPrompt(prompt) {
|
|
78
|
+
if (prompt == null)
|
|
79
|
+
return {};
|
|
80
|
+
return {
|
|
81
|
+
prompt_length: prompt.length,
|
|
82
|
+
prompt_sha256: createHash('sha256').update(prompt).digest('hex').slice(0, 16),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const TOKEN_LIKE = /(sk_(?:live|test)_|pk_(?:live|test)_|ghp_|gho_|ghu_|ghs_|xox[bpars]-|AKIA|ASIA|AIza|Bearer\s+|eyJ[A-Za-z0-9_-]+\.)/i;
|
|
86
|
+
const SECRET_PATH = /\/(secrets|credentials|\.env|user\.yaml)\b/i;
|
|
87
|
+
/**
|
|
88
|
+
* Mask argv entries that look like tokens or secret paths. Preserves structure
|
|
89
|
+
* for debugging but drops the sensitive substring.
|
|
90
|
+
*/
|
|
91
|
+
export function redactArgs(args) {
|
|
92
|
+
if (!args)
|
|
93
|
+
return undefined;
|
|
94
|
+
return args.map(a => {
|
|
95
|
+
if (typeof a !== 'string')
|
|
96
|
+
return a;
|
|
97
|
+
if (TOKEN_LIKE.test(a) || SECRET_PATH.test(a))
|
|
98
|
+
return '[REDACTED]';
|
|
99
|
+
return a;
|
|
100
|
+
});
|
|
101
|
+
}
|
|
71
102
|
// ─── Truncation ───────────────────────────────────────────────────────────────
|
|
72
103
|
/**
|
|
73
104
|
* Truncate a string to maxLength, adding ellipsis if truncated.
|
|
@@ -324,7 +355,7 @@ export function emitError(err, payload = {}) {
|
|
|
324
355
|
* Remove log files older than the retention period.
|
|
325
356
|
* Called lazily on emit or explicitly via CLI.
|
|
326
357
|
*
|
|
327
|
-
* @param retentionDays - Number of days to keep (default
|
|
358
|
+
* @param retentionDays - Number of days to keep (default 7, from DEFAULT_RETENTION_DAYS)
|
|
328
359
|
* @returns Number of files removed
|
|
329
360
|
*/
|
|
330
361
|
export function rotate(retentionDays = DEFAULT_RETENTION_DAYS) {
|
package/dist/lib/exec.d.ts
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
|
-
import type { AgentId } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
|
|
1
|
+
import type { AgentId, Mode } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Agent execution modes. Canonical name `skip` (dangerously skip permissions);
|
|
4
|
+
* `full` is accepted as a permanent silent alias via normalizeMode().
|
|
5
|
+
*/
|
|
6
|
+
export type ExecMode = Mode;
|
|
7
|
+
/**
|
|
8
|
+
* Map a raw mode string (CLI flag, YAML field, env var) to the canonical Mode.
|
|
9
|
+
*
|
|
10
|
+
* Accepts the historical `full` spelling and rewrites it to `skip`. Throws on
|
|
11
|
+
* anything outside the four canonical values so bad input fails loud at the
|
|
12
|
+
* boundary rather than silently picking a wrong code path.
|
|
13
|
+
*/
|
|
14
|
+
export declare function normalizeMode(input: string | null | undefined): Mode;
|
|
15
|
+
/**
|
|
16
|
+
* Resolve a requested mode against an agent's capability table.
|
|
17
|
+
*
|
|
18
|
+
* - `auto` on an agent without auto support silently degrades to `edit`
|
|
19
|
+
* (every agent supports edit-like behavior as its default).
|
|
20
|
+
* - `skip` on an agent without skip support throws with a clear message
|
|
21
|
+
* naming the agent's supported modes. No silent fallback — the user
|
|
22
|
+
* explicitly asked to bypass permissions; pretending we did is unsafe.
|
|
23
|
+
* - `plan` on an agent without plan support throws the same way.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveMode(agent: AgentId, requested: Mode): Mode;
|
|
4
26
|
/** Reasoning effort levels passed to agents that support them. 'auto' defers to the agent's default. */
|
|
5
27
|
export type ExecEffort = 'low' | 'medium' | 'high' | 'xhigh' | 'max' | 'auto';
|
|
6
28
|
/** Options for spawning an agent process. Omitting `prompt` launches the CLI interactively. */
|
|
@@ -27,26 +49,33 @@ export interface ExecOptions {
|
|
|
27
49
|
export declare function parseExecEnv(entries: string[]): Record<string, string> | undefined;
|
|
28
50
|
/**
|
|
29
51
|
* Build the process environment for an agent invocation.
|
|
30
|
-
* Pins CLAUDE_CONFIG_DIR for Claude
|
|
31
|
-
* other
|
|
52
|
+
* Pins CLAUDE_CONFIG_DIR for Claude, CODEX_HOME for Codex, and COPILOT_HOME
|
|
53
|
+
* for GitHub Copilot; strips the other agents' env vars so they don't leak
|
|
54
|
+
* into unrelated invocations.
|
|
32
55
|
*/
|
|
33
56
|
export declare function buildExecEnv(options: ExecOptions): NodeJS.ProcessEnv;
|
|
34
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Describes how to translate ExecOptions into CLI arguments for a specific agent.
|
|
59
|
+
*
|
|
60
|
+
* `modeFlags` only declares modes this agent natively supports. Keys must agree
|
|
61
|
+
* with AGENTS[agent].capabilities.modes — resolveMode() routes a request to a
|
|
62
|
+
* supported mode (or throws), then buildExecCommand looks up the flags here.
|
|
63
|
+
*/
|
|
35
64
|
export interface AgentCommandTemplate {
|
|
36
65
|
base: string[];
|
|
37
66
|
promptFlag: 'positional' | string;
|
|
38
|
-
modeFlags:
|
|
39
|
-
plan: string[];
|
|
40
|
-
edit: string[];
|
|
41
|
-
full: string[];
|
|
42
|
-
auto?: string[];
|
|
43
|
-
};
|
|
67
|
+
modeFlags: Partial<Record<Mode, string[]>>;
|
|
44
68
|
jsonFlags?: string[];
|
|
45
69
|
modelFlag?: string;
|
|
46
70
|
printFlags?: string[];
|
|
47
71
|
verboseFlag?: string;
|
|
48
72
|
}
|
|
49
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* CLI command templates for every supported agent.
|
|
75
|
+
*
|
|
76
|
+
* Each agent's `modeFlags` keys MUST match the modes listed in
|
|
77
|
+
* AGENTS[agent].capabilities.modes. A test in exec.test.ts asserts this.
|
|
78
|
+
*/
|
|
50
79
|
export declare const AGENT_COMMANDS: Record<AgentId, AgentCommandTemplate>;
|
|
51
80
|
/** Assemble the full CLI argument array for an agent invocation. */
|
|
52
81
|
export declare function buildExecCommand(options: ExecOptions): string[];
|
package/dist/lib/exec.js
CHANGED
|
@@ -7,10 +7,52 @@
|
|
|
7
7
|
import { spawn } from 'child_process';
|
|
8
8
|
import { randomUUID } from 'crypto';
|
|
9
9
|
import * as path from 'path';
|
|
10
|
+
import { ALL_MODES } from './types.js';
|
|
11
|
+
import { AGENTS } from './agents.js';
|
|
10
12
|
import { parseTimeout } from './routines.js';
|
|
11
13
|
import { getVersionHomePath, isVersionInstalled, resolveVersion } from './versions.js';
|
|
12
14
|
import { resolveModel, buildReasoningFlags } from './models.js';
|
|
13
|
-
import { maybeRotate, createTimer,
|
|
15
|
+
import { maybeRotate, createTimer, redactPrompt, redactArgs } from './events.js';
|
|
16
|
+
import { sanitizeProcessEnv } from './secrets/bundles.js';
|
|
17
|
+
/**
|
|
18
|
+
* Map a raw mode string (CLI flag, YAML field, env var) to the canonical Mode.
|
|
19
|
+
*
|
|
20
|
+
* Accepts the historical `full` spelling and rewrites it to `skip`. Throws on
|
|
21
|
+
* anything outside the four canonical values so bad input fails loud at the
|
|
22
|
+
* boundary rather than silently picking a wrong code path.
|
|
23
|
+
*/
|
|
24
|
+
export function normalizeMode(input) {
|
|
25
|
+
if (!input) {
|
|
26
|
+
throw new Error(`Mode is required. Use one of: ${ALL_MODES.join(', ')}.`);
|
|
27
|
+
}
|
|
28
|
+
const v = input.trim().toLowerCase();
|
|
29
|
+
if (v === 'full')
|
|
30
|
+
return 'skip';
|
|
31
|
+
if (ALL_MODES.includes(v))
|
|
32
|
+
return v;
|
|
33
|
+
throw new Error(`Invalid mode '${input}'. Use one of: ${ALL_MODES.join(', ')} (or 'full' as a deprecated alias for 'skip').`);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Resolve a requested mode against an agent's capability table.
|
|
37
|
+
*
|
|
38
|
+
* - `auto` on an agent without auto support silently degrades to `edit`
|
|
39
|
+
* (every agent supports edit-like behavior as its default).
|
|
40
|
+
* - `skip` on an agent without skip support throws with a clear message
|
|
41
|
+
* naming the agent's supported modes. No silent fallback — the user
|
|
42
|
+
* explicitly asked to bypass permissions; pretending we did is unsafe.
|
|
43
|
+
* - `plan` on an agent without plan support throws the same way.
|
|
44
|
+
*/
|
|
45
|
+
export function resolveMode(agent, requested) {
|
|
46
|
+
const supported = AGENTS[agent].capabilities.modes;
|
|
47
|
+
if (supported.includes(requested))
|
|
48
|
+
return requested;
|
|
49
|
+
if (requested === 'auto') {
|
|
50
|
+
// Fall back to edit — guaranteed to exist on every agent (every agent has
|
|
51
|
+
// at least 'edit' in its modes table, since that's the default behavior).
|
|
52
|
+
return 'edit';
|
|
53
|
+
}
|
|
54
|
+
throw new Error(`${agent} does not support '${requested}' mode. Supported modes: ${supported.join(', ')}.`);
|
|
55
|
+
}
|
|
14
56
|
/** Pattern for valid environment variable names (C identifier rules). */
|
|
15
57
|
const EXEC_ENV_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
16
58
|
/** Parse a single KEY=VALUE string into a tuple, validating the key name. */
|
|
@@ -35,11 +77,12 @@ export function parseExecEnv(entries) {
|
|
|
35
77
|
}
|
|
36
78
|
/**
|
|
37
79
|
* Build the process environment for an agent invocation.
|
|
38
|
-
* Pins CLAUDE_CONFIG_DIR for Claude
|
|
39
|
-
* other
|
|
80
|
+
* Pins CLAUDE_CONFIG_DIR for Claude, CODEX_HOME for Codex, and COPILOT_HOME
|
|
81
|
+
* for GitHub Copilot; strips the other agents' env vars so they don't leak
|
|
82
|
+
* into unrelated invocations.
|
|
40
83
|
*/
|
|
41
84
|
export function buildExecEnv(options) {
|
|
42
|
-
const result = { ...process.env };
|
|
85
|
+
const result = { ...sanitizeProcessEnv(process.env) };
|
|
43
86
|
// Config-dir env vars are agent-specific. When the caller is running inside
|
|
44
87
|
// an agent-managed shell, process.env already carries one; spreading into a
|
|
45
88
|
// different agent's env would leak a config pointer the target CLI doesn't
|
|
@@ -56,6 +99,7 @@ export function buildExecEnv(options) {
|
|
|
56
99
|
result.CLAUDE_CONFIG_DIR = path.join(getVersionHomePath('claude', version), '.claude');
|
|
57
100
|
}
|
|
58
101
|
delete result.CODEX_HOME;
|
|
102
|
+
delete result.COPILOT_HOME;
|
|
59
103
|
}
|
|
60
104
|
else if (options.agent === 'codex') {
|
|
61
105
|
const cwd = options.cwd || process.cwd();
|
|
@@ -67,17 +111,39 @@ export function buildExecEnv(options) {
|
|
|
67
111
|
result.CODEX_HOME = path.join(getVersionHomePath('codex', version), '.codex');
|
|
68
112
|
}
|
|
69
113
|
delete result.CLAUDE_CONFIG_DIR;
|
|
114
|
+
delete result.COPILOT_HOME;
|
|
115
|
+
}
|
|
116
|
+
else if (options.agent === 'copilot') {
|
|
117
|
+
// Copilot honors COPILOT_HOME (relocates ~/.copilot, including settings,
|
|
118
|
+
// mcp-config.json, sessions, logs). Pin it at the per-version home so
|
|
119
|
+
// version switches isolate MCP servers, auth, and session history.
|
|
120
|
+
const cwd = options.cwd || process.cwd();
|
|
121
|
+
const resolvedVersion = options.version ?? resolveVersion('copilot', cwd);
|
|
122
|
+
const version = options.version
|
|
123
|
+
? resolvedVersion
|
|
124
|
+
: (resolvedVersion && isVersionInstalled('copilot', resolvedVersion) ? resolvedVersion : null);
|
|
125
|
+
if (version) {
|
|
126
|
+
result.COPILOT_HOME = path.join(getVersionHomePath('copilot', version), '.copilot');
|
|
127
|
+
}
|
|
128
|
+
delete result.CLAUDE_CONFIG_DIR;
|
|
129
|
+
delete result.CODEX_HOME;
|
|
70
130
|
}
|
|
71
131
|
else {
|
|
72
132
|
delete result.CLAUDE_CONFIG_DIR;
|
|
73
133
|
delete result.CODEX_HOME;
|
|
134
|
+
delete result.COPILOT_HOME;
|
|
74
135
|
}
|
|
75
136
|
return {
|
|
76
137
|
...result,
|
|
77
138
|
...options.env,
|
|
78
139
|
};
|
|
79
140
|
}
|
|
80
|
-
/**
|
|
141
|
+
/**
|
|
142
|
+
* CLI command templates for every supported agent.
|
|
143
|
+
*
|
|
144
|
+
* Each agent's `modeFlags` keys MUST match the modes listed in
|
|
145
|
+
* AGENTS[agent].capabilities.modes. A test in exec.test.ts asserts this.
|
|
146
|
+
*/
|
|
81
147
|
export const AGENT_COMMANDS = {
|
|
82
148
|
claude: {
|
|
83
149
|
base: ['claude'],
|
|
@@ -85,8 +151,8 @@ export const AGENT_COMMANDS = {
|
|
|
85
151
|
modeFlags: {
|
|
86
152
|
plan: ['--permission-mode', 'plan'],
|
|
87
153
|
edit: ['--permission-mode', 'acceptEdits'],
|
|
88
|
-
full: ['--dangerously-skip-permissions'],
|
|
89
154
|
auto: ['--permission-mode', 'auto'],
|
|
155
|
+
skip: ['--dangerously-skip-permissions'],
|
|
90
156
|
},
|
|
91
157
|
jsonFlags: ['--output-format', 'stream-json', '--verbose'],
|
|
92
158
|
modelFlag: '--model',
|
|
@@ -97,9 +163,13 @@ export const AGENT_COMMANDS = {
|
|
|
97
163
|
base: ['codex', 'exec'],
|
|
98
164
|
promptFlag: 'positional',
|
|
99
165
|
modeFlags: {
|
|
166
|
+
// NOTE: codex has no read-only mode in --sandbox; 'plan' here means
|
|
167
|
+
// "workspace-write but no auto-approval" — closer to plan-as-restraint.
|
|
168
|
+
// True read-only requires --sandbox read-only which we haven't wired.
|
|
100
169
|
plan: ['--sandbox', 'workspace-write'],
|
|
101
170
|
edit: ['--sandbox', 'workspace-write', '--full-auto'],
|
|
102
|
-
|
|
171
|
+
// skip drops the sandbox entirely; --full-auto then approves anything.
|
|
172
|
+
skip: ['--full-auto'],
|
|
103
173
|
},
|
|
104
174
|
jsonFlags: ['--json'],
|
|
105
175
|
modelFlag: '--model',
|
|
@@ -108,9 +178,9 @@ export const AGENT_COMMANDS = {
|
|
|
108
178
|
base: ['gemini'],
|
|
109
179
|
promptFlag: 'positional',
|
|
110
180
|
modeFlags: {
|
|
111
|
-
plan: [],
|
|
112
|
-
edit: ['--
|
|
113
|
-
|
|
181
|
+
plan: ['--approval-mode', 'plan'],
|
|
182
|
+
edit: ['--approval-mode', 'auto_edit'],
|
|
183
|
+
skip: ['--yolo'],
|
|
114
184
|
},
|
|
115
185
|
jsonFlags: ['--output-format', 'stream-json'],
|
|
116
186
|
modelFlag: '--model',
|
|
@@ -119,9 +189,9 @@ export const AGENT_COMMANDS = {
|
|
|
119
189
|
base: ['cursor-agent'],
|
|
120
190
|
promptFlag: '-p',
|
|
121
191
|
modeFlags: {
|
|
122
|
-
|
|
123
|
-
edit: [
|
|
124
|
-
|
|
192
|
+
// cursor-agent has no read-only flag; we only expose edit + skip.
|
|
193
|
+
edit: [],
|
|
194
|
+
skip: ['-f'],
|
|
125
195
|
},
|
|
126
196
|
jsonFlags: ['--output-format', 'stream-json'],
|
|
127
197
|
modelFlag: '--model',
|
|
@@ -132,7 +202,6 @@ export const AGENT_COMMANDS = {
|
|
|
132
202
|
modeFlags: {
|
|
133
203
|
plan: ['--agent', 'plan'],
|
|
134
204
|
edit: ['--agent', 'build'],
|
|
135
|
-
full: ['--agent', 'build'],
|
|
136
205
|
},
|
|
137
206
|
jsonFlags: ['--format', 'json'],
|
|
138
207
|
modelFlag: '--model',
|
|
@@ -143,19 +212,32 @@ export const AGENT_COMMANDS = {
|
|
|
143
212
|
modeFlags: {
|
|
144
213
|
plan: ['--mode', 'plan'],
|
|
145
214
|
edit: ['--mode', 'edit'],
|
|
146
|
-
|
|
215
|
+
skip: ['--mode', 'full'],
|
|
147
216
|
},
|
|
148
217
|
jsonFlags: ['--output-format', 'stream-json'],
|
|
149
218
|
modelFlag: '--model',
|
|
150
219
|
},
|
|
220
|
+
// GitHub Copilot CLI (`@github/copilot`, GA 2026-02-25). Flags verified
|
|
221
|
+
// against `copilot --help` from v0.0.413+:
|
|
222
|
+
// -p, --prompt <text> non-interactive one-shot
|
|
223
|
+
// --mode <interactive|plan|autopilot>
|
|
224
|
+
// --autopilot start in autopilot (smart-classifier) mode
|
|
225
|
+
// --allow-all-tools required for non-interactive tool exec
|
|
226
|
+
// --allow-all (alias --yolo) tools + paths + URLs
|
|
227
|
+
// --output-format <text|json> json => JSONL, one object per line
|
|
228
|
+
// --model <model>
|
|
229
|
+
// Plan mode is read-only so it does not need an allow-tools grant; edit
|
|
230
|
+
// needs --allow-all-tools so headless runs don't stall on prompts.
|
|
151
231
|
copilot: {
|
|
152
232
|
base: ['copilot'],
|
|
153
|
-
promptFlag: '
|
|
233
|
+
promptFlag: '-p',
|
|
154
234
|
modeFlags: {
|
|
155
|
-
plan: [],
|
|
156
|
-
edit: [],
|
|
157
|
-
|
|
235
|
+
plan: ['--mode', 'plan'],
|
|
236
|
+
edit: ['--allow-all-tools'],
|
|
237
|
+
auto: ['--autopilot'],
|
|
238
|
+
skip: ['--allow-all'],
|
|
158
239
|
},
|
|
240
|
+
jsonFlags: ['--output-format', 'json'],
|
|
159
241
|
modelFlag: '--model',
|
|
160
242
|
},
|
|
161
243
|
amp: {
|
|
@@ -164,7 +246,6 @@ export const AGENT_COMMANDS = {
|
|
|
164
246
|
modeFlags: {
|
|
165
247
|
plan: ['--mode', 'plan'],
|
|
166
248
|
edit: ['--mode', 'edit'],
|
|
167
|
-
full: ['--mode', 'edit'],
|
|
168
249
|
},
|
|
169
250
|
modelFlag: '--model',
|
|
170
251
|
},
|
|
@@ -172,9 +253,8 @@ export const AGENT_COMMANDS = {
|
|
|
172
253
|
base: ['kiro-cli'],
|
|
173
254
|
promptFlag: 'positional',
|
|
174
255
|
modeFlags: {
|
|
175
|
-
|
|
256
|
+
// kiro-cli has no permission flags — edit is the default behavior.
|
|
176
257
|
edit: [],
|
|
177
|
-
full: [],
|
|
178
258
|
},
|
|
179
259
|
modelFlag: '--model',
|
|
180
260
|
},
|
|
@@ -182,9 +262,8 @@ export const AGENT_COMMANDS = {
|
|
|
182
262
|
base: ['goose', 'run'],
|
|
183
263
|
promptFlag: 'positional',
|
|
184
264
|
modeFlags: {
|
|
185
|
-
|
|
265
|
+
// goose has no permission flags — edit is the default behavior.
|
|
186
266
|
edit: [],
|
|
187
|
-
full: [],
|
|
188
267
|
},
|
|
189
268
|
},
|
|
190
269
|
roo: {
|
|
@@ -193,27 +272,33 @@ export const AGENT_COMMANDS = {
|
|
|
193
272
|
modeFlags: {
|
|
194
273
|
plan: ['--mode', 'architect'],
|
|
195
274
|
edit: ['--mode', 'code'],
|
|
196
|
-
full: ['--mode', 'code'],
|
|
197
275
|
},
|
|
198
276
|
modelFlag: '--model',
|
|
199
277
|
},
|
|
278
|
+
// TODO: --output-format json is documented but currently broken upstream
|
|
279
|
+
// ("flags provided but not defined: -output-format"). Track resolution at
|
|
280
|
+
// https://github.com/google-antigravity/antigravity-cli/issues/7 before
|
|
281
|
+
// adding `jsonFlags` here.
|
|
200
282
|
antigravity: {
|
|
201
283
|
base: ['agy'],
|
|
202
284
|
promptFlag: 'positional',
|
|
203
285
|
modeFlags: {
|
|
204
|
-
plan
|
|
286
|
+
// agy --help shows no plan/edit flags; default behavior is edit-like
|
|
287
|
+
// (prompts on tool use). Only skip has an explicit flag.
|
|
205
288
|
edit: [],
|
|
206
|
-
|
|
289
|
+
skip: ['--dangerously-skip-permissions'],
|
|
207
290
|
},
|
|
291
|
+
printFlags: ['--print'],
|
|
208
292
|
modelFlag: '--model',
|
|
209
293
|
},
|
|
210
294
|
grok: {
|
|
211
295
|
base: ['grok'],
|
|
212
296
|
promptFlag: '-p',
|
|
213
297
|
modeFlags: {
|
|
214
|
-
plan
|
|
298
|
+
// grok --help lists `--permission-mode plan`; the TUI defaults to ask.
|
|
299
|
+
plan: ['--permission-mode', 'plan'],
|
|
215
300
|
edit: [],
|
|
216
|
-
|
|
301
|
+
skip: ['--always-approve'],
|
|
217
302
|
},
|
|
218
303
|
jsonFlags: ['--output-format', 'streaming-json'],
|
|
219
304
|
modelFlag: '--model',
|
|
@@ -248,8 +333,17 @@ export function buildExecCommand(options) {
|
|
|
248
333
|
}
|
|
249
334
|
}
|
|
250
335
|
}
|
|
251
|
-
//
|
|
252
|
-
|
|
336
|
+
// Resolve the requested mode against the agent's capability table.
|
|
337
|
+
// - `auto` on an agent without auto support → silently degrades to `edit`
|
|
338
|
+
// - `skip`/`plan` on an unsupported agent → throws a clear error
|
|
339
|
+
// After resolveMode, the chosen mode is guaranteed to be in template.modeFlags.
|
|
340
|
+
const resolvedMode = resolveMode(options.agent, normalizeMode(options.mode));
|
|
341
|
+
const modeFlags = template.modeFlags[resolvedMode];
|
|
342
|
+
if (!modeFlags) {
|
|
343
|
+
// Defense in depth: would only fire if AGENTS.capabilities.modes and
|
|
344
|
+
// AGENT_COMMANDS.modeFlags drifted apart. Tests assert they agree.
|
|
345
|
+
throw new Error(`Internal error: ${options.agent} declares '${resolvedMode}' in capabilities.modes but has no entry in AGENT_COMMANDS.modeFlags.${resolvedMode}.`);
|
|
346
|
+
}
|
|
253
347
|
cmd.push(...modeFlags);
|
|
254
348
|
// Add print/headless flags only when a prompt is provided. Without a prompt
|
|
255
349
|
// the caller wants an interactive REPL -- passing --print would immediately
|
|
@@ -335,9 +429,9 @@ async function spawnAgent(options) {
|
|
|
335
429
|
model: options.model,
|
|
336
430
|
interactive,
|
|
337
431
|
sessionId: options.sessionId,
|
|
338
|
-
|
|
432
|
+
...redactPrompt(options.prompt),
|
|
339
433
|
command: executable,
|
|
340
|
-
args: args.slice(0, 10),
|
|
434
|
+
args: redactArgs(args.slice(0, 10)),
|
|
341
435
|
});
|
|
342
436
|
return new Promise((resolve, reject) => {
|
|
343
437
|
// Interactive mode inherits all stdio so the CLI owns the TTY (TUI
|
package/dist/lib/help.js
CHANGED
|
@@ -47,15 +47,21 @@ function formatHelpCommandsFirst(cmd, helper) {
|
|
|
47
47
|
const helpWidth = helper.helpWidth || 80;
|
|
48
48
|
const itemIndentWidth = 2;
|
|
49
49
|
const itemSeparatorWidth = 2;
|
|
50
|
+
// commander v15 dropped `Help.wrap(str, width, indent)` in favor of
|
|
51
|
+
// `boxWrap(str, width)` plus a built-in `formatItem(term, termWidth,
|
|
52
|
+
// description, helper)` that handles the term-pad + continuation-indent
|
|
53
|
+
// math we used to do by hand. Delegate to it so callers get the same
|
|
54
|
+
// continuation-line alignment under the description column.
|
|
50
55
|
function formatItem(term, description) {
|
|
51
56
|
if (description) {
|
|
52
|
-
|
|
53
|
-
return helper.wrap(fullText, helpWidth - itemIndentWidth, termWidth + itemSeparatorWidth);
|
|
57
|
+
return helper.formatItem(term, termWidth, description, helper);
|
|
54
58
|
}
|
|
55
|
-
return term;
|
|
59
|
+
return ' '.repeat(itemIndentWidth) + term;
|
|
56
60
|
}
|
|
57
61
|
function formatList(textArray) {
|
|
58
|
-
|
|
62
|
+
// formatItem already prefixes each item with its 2-space indent, so just
|
|
63
|
+
// join. Single-line items (no description) are indented above.
|
|
64
|
+
return textArray.join('\n');
|
|
59
65
|
}
|
|
60
66
|
// Drop arguments flagged as hidden (deprecation / compat slots) from both
|
|
61
67
|
// the Usage line and the Arguments section. Commander v12's Argument lacks
|
|
@@ -81,7 +87,7 @@ function formatHelpCommandsFirst(cmd, helper) {
|
|
|
81
87
|
let output = [`Usage: ${usageLine}`, ''];
|
|
82
88
|
const commandDescription = helper.commandDescription(cmd);
|
|
83
89
|
if (commandDescription.length > 0) {
|
|
84
|
-
output = output.concat([helper.
|
|
90
|
+
output = output.concat([helper.boxWrap(commandDescription, helpWidth), '']);
|
|
85
91
|
}
|
|
86
92
|
const sections = helpSectionRegistry.get(cmd);
|
|
87
93
|
if (sections?.examples) {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { HookCache, HookCacheConfig } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parse a `cache:` value from hooks.yaml into the canonical config form.
|
|
4
|
+
* Accepts the shorthand string ("5m", "30s-bg") or the full object form.
|
|
5
|
+
* Returns null if the value is missing or unparseable.
|
|
6
|
+
*/
|
|
7
|
+
export declare function parseCacheConfig(raw: HookCache | undefined): HookCacheConfig | null;
|
|
8
|
+
/** Parse "30s" | "5m" | "1h" | plain seconds. Returns seconds, or null on failure. */
|
|
9
|
+
export declare function parseDuration(d: number | string | undefined): number | null;
|
|
10
|
+
/** Absolute path of the generated shim for a hook name. */
|
|
11
|
+
export declare function getHookShimPath(name: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Optional path overrides for tests that need to redirect cache + logs to a
|
|
14
|
+
* temp dir. Production callers omit `paths`; the shim uses real state.ts dirs.
|
|
15
|
+
* (state.ts captures HOME at module load, so mutating process.env.HOME in a
|
|
16
|
+
* test's beforeEach doesn't reach getHookCacheDir() — this is the explicit
|
|
17
|
+
* seam.)
|
|
18
|
+
*/
|
|
19
|
+
export interface HookShimPaths {
|
|
20
|
+
shimsDir?: string;
|
|
21
|
+
cacheDir?: string;
|
|
22
|
+
logsDir?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Generate (or refresh) the shim script for a hook. Idempotent — only writes
|
|
26
|
+
* when the content differs from what's on disk. Returns the absolute shim path.
|
|
27
|
+
*/
|
|
28
|
+
export declare function generateHookShim(args: {
|
|
29
|
+
name: string;
|
|
30
|
+
scriptPath: string;
|
|
31
|
+
cache: HookCacheConfig;
|
|
32
|
+
paths?: HookShimPaths;
|
|
33
|
+
}): string;
|
|
34
|
+
/**
|
|
35
|
+
* Remove a hook's shim. Called by the registrar's garbage collection when a
|
|
36
|
+
* hook is renamed/deleted or has its `cache:` field removed.
|
|
37
|
+
*/
|
|
38
|
+
export declare function removeHookShim(name: string, shimsDir?: string): void;
|