@agentguard-run/burn 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/README.md +145 -6
  3. package/dist/src/adapters/codex.d.ts +48 -0
  4. package/dist/src/adapters/codex.js +197 -0
  5. package/dist/src/adapters/cursor.d.ts +35 -0
  6. package/dist/src/adapters/cursor.js +135 -0
  7. package/dist/src/adapters/raw-api.d.ts +76 -0
  8. package/dist/src/adapters/raw-api.js +130 -0
  9. package/dist/src/cli.d.ts +7 -3
  10. package/dist/src/cli.js +141 -17
  11. package/dist/src/conformance.d.ts +26 -0
  12. package/dist/src/conformance.js +261 -0
  13. package/dist/src/defaults.d.ts +11 -0
  14. package/dist/src/defaults.js +16 -1
  15. package/dist/src/detectors/local-compute.d.ts +19 -0
  16. package/dist/src/detectors/local-compute.js +66 -0
  17. package/dist/src/events.d.ts +94 -0
  18. package/dist/src/events.js +47 -0
  19. package/dist/src/gateway.d.ts +141 -0
  20. package/dist/src/gateway.js +536 -0
  21. package/dist/src/hook/pre-tool-use.d.ts +25 -1
  22. package/dist/src/hook/pre-tool-use.js +64 -16
  23. package/dist/src/index.d.ts +19 -4
  24. package/dist/src/index.js +57 -1
  25. package/dist/src/install.d.ts +29 -0
  26. package/dist/src/install.js +145 -0
  27. package/dist/src/override.d.ts +32 -0
  28. package/dist/src/override.js +72 -0
  29. package/dist/src/proxy/server.d.ts +45 -0
  30. package/dist/src/proxy/server.js +169 -0
  31. package/dist/src/proxy/usage-observer.d.ts +40 -0
  32. package/dist/src/proxy/usage-observer.js +128 -0
  33. package/dist/src/receipt.d.ts +61 -0
  34. package/dist/src/receipt.js +98 -0
  35. package/dist/src/replay/render.d.ts +1 -0
  36. package/dist/src/replay/render.js +2 -1
  37. package/dist/src/state/reservations.d.ts +115 -11
  38. package/dist/src/state/reservations.js +293 -59
  39. package/dist/src/state/session.d.ts +6 -0
  40. package/dist/src/state/session.js +17 -0
  41. package/dist/src/status.d.ts +19 -0
  42. package/dist/src/status.js +112 -0
  43. package/dist/src/types.d.ts +14 -1
  44. package/fixtures/codex-0.151.0-pretooluse.json +49 -0
  45. package/package.json +34 -6
@@ -15,7 +15,7 @@
15
15
  * as little as possible.
16
16
  */
17
17
  import { type ReaderCursor } from '../history/claude-transcript';
18
- import type { Policy, SessionState } from '../types';
18
+ import type { BurnReport, Policy, SessionState } from '../types';
19
19
  export interface HookInput {
20
20
  session_id?: string;
21
21
  transcript_path?: string;
@@ -35,12 +35,36 @@ export interface HookOutput {
35
35
  permissionDecisionReason: string;
36
36
  };
37
37
  }
38
+ export interface PersistedSession {
39
+ cursor: {
40
+ offset: number;
41
+ size: number;
42
+ malformedLines: number;
43
+ depthByUuid: [string, number][];
44
+ };
45
+ state: Omit<SessionState, 'tokensByActiveMinute' | 'spawnsByActiveMinute' | 'surfaceReaders'> & {
46
+ tokensByActiveMinute: [number, number][];
47
+ spawnsByActiveMinute: [number, number][];
48
+ surfaceReaders: [string, number[]][];
49
+ };
50
+ /** Signature of the last finding set the user was told about. */
51
+ notified?: string;
52
+ }
53
+ /** Inflate a persisted Claude Code session. Shared with status. */
54
+ export declare function inflateHookSession(raw: PersistedSession): SessionState;
55
+ /**
56
+ * Tell the user once per change, not once per spawn. Eighteen identical
57
+ * "WARN: 30 spawns" banners in a row train the user to stop reading them,
58
+ * and the one that matters is the nineteenth.
59
+ */
60
+ export declare function findingSignature(report: BurnReport): string;
38
61
  export declare function loadPolicy(home: string): Policy;
39
62
  /** Refresh session state from the transcript. Cheap: only new bytes are read. */
40
63
  export declare function refreshSession(home: string, sessionId: string, transcriptPath: string): {
41
64
  cursor: ReaderCursor;
42
65
  state: SessionState;
43
66
  newSpawns: number;
67
+ notified: string;
44
68
  };
45
69
  export declare function handlePreToolUse(input: HookInput, home: string, now?: number): HookOutput;
46
70
  /** The settings.json fragment users paste in. Only PreToolUse can block. */
@@ -16,6 +16,8 @@
16
16
  * as little as possible.
17
17
  */
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.inflateHookSession = inflateHookSession;
20
+ exports.findingSignature = findingSignature;
19
21
  exports.loadPolicy = loadPolicy;
20
22
  exports.refreshSession = refreshSession;
21
23
  exports.handlePreToolUse = handlePreToolUse;
@@ -27,28 +29,32 @@ const claude_transcript_1 = require("../history/claude-transcript");
27
29
  const reservations_1 = require("../state/reservations");
28
30
  const session_1 = require("../state/session");
29
31
  const defaults_1 = require("../defaults");
32
+ const override_1 = require("../override");
30
33
  const render_1 = require("../replay/render");
31
34
  const SPAWN_TOOLS = new Set(['Agent', 'Task']);
32
35
  function sessionFile(home, sessionId) {
33
36
  return (0, node_path_1.join)(home, 'sessions', `${sessionId.replace(/[^a-zA-Z0-9_-]/g, '_')}.json`);
34
37
  }
38
+ /** Inflate a persisted Claude Code session. Shared with status. */
39
+ function inflateHookSession(raw) {
40
+ return {
41
+ ...raw.state,
42
+ tokensByActiveMinute: new Map(raw.state.tokensByActiveMinute),
43
+ spawnsByActiveMinute: new Map(raw.state.spawnsByActiveMinute),
44
+ surfaceReaders: new Map(raw.state.surfaceReaders.map(([k, v]) => [k, new Set(v)])),
45
+ };
46
+ }
35
47
  function loadSession(home, sessionId, firstEventAt) {
36
48
  try {
37
49
  const raw = JSON.parse((0, node_fs_1.readFileSync)(sessionFile(home, sessionId), 'utf8'));
38
50
  const cursor = { ...raw.cursor, depthByUuid: new Map(raw.cursor.depthByUuid) };
39
- const state = {
40
- ...raw.state,
41
- tokensByActiveMinute: new Map(raw.state.tokensByActiveMinute),
42
- spawnsByActiveMinute: new Map(raw.state.spawnsByActiveMinute),
43
- surfaceReaders: new Map(raw.state.surfaceReaders.map(([k, v]) => [k, new Set(v)])),
44
- };
45
- return { cursor, state };
51
+ return { cursor, state: inflateHookSession(raw), notified: raw.notified ?? '' };
46
52
  }
47
53
  catch {
48
- return { cursor: (0, claude_transcript_1.newCursor)(), state: (0, session_1.newSessionState)(sessionId, firstEventAt) };
54
+ return { cursor: (0, claude_transcript_1.newCursor)(), state: (0, session_1.newSessionState)(sessionId, firstEventAt), notified: '' };
49
55
  }
50
56
  }
51
- function saveSession(home, cursor, state) {
57
+ function saveSession(home, cursor, state, notified) {
52
58
  (0, node_fs_1.mkdirSync)((0, node_path_1.join)(home, 'sessions'), { recursive: true, mode: 0o700 });
53
59
  const persisted = {
54
60
  cursor: { offset: cursor.offset, size: cursor.size, malformedLines: cursor.malformedLines, depthByUuid: [...cursor.depthByUuid] },
@@ -58,12 +64,24 @@ function saveSession(home, cursor, state) {
58
64
  spawnsByActiveMinute: [...state.spawnsByActiveMinute],
59
65
  surfaceReaders: [...state.surfaceReaders].map(([k, v]) => [k, [...v]]),
60
66
  },
67
+ notified,
61
68
  };
62
69
  const file = sessionFile(home, state.sessionId);
63
70
  (0, node_fs_1.writeFileSync)(`${file}.tmp`, JSON.stringify(persisted), { mode: 0o600 });
64
71
  // rename is atomic; a concurrent reader never sees a half-written file.
65
72
  require('node:fs').renameSync(`${file}.tmp`, file);
66
73
  }
74
+ /**
75
+ * Tell the user once per change, not once per spawn. Eighteen identical
76
+ * "WARN: 30 spawns" banners in a row train the user to stop reading them,
77
+ * and the one that matters is the nineteenth.
78
+ */
79
+ function findingSignature(report) {
80
+ return report.findings
81
+ .map((f) => `${f.detector}:${f.verdict}`)
82
+ .sort()
83
+ .join('|');
84
+ }
67
85
  function loadPolicy(home) {
68
86
  try {
69
87
  const parsed = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(home, 'burn-policy.json'), 'utf8'));
@@ -81,15 +99,27 @@ function recordDecision(home, entry) {
81
99
  }
82
100
  /** Refresh session state from the transcript. Cheap: only new bytes are read. */
83
101
  function refreshSession(home, sessionId, transcriptPath) {
84
- const { cursor, state } = loadSession(home, sessionId, Date.now());
102
+ const { cursor, state, notified } = loadSession(home, sessionId, Date.now());
85
103
  const before = state.spawnCount;
86
104
  const events = (0, claude_transcript_1.readIncremental)(transcriptPath, cursor);
87
105
  for (const event of events)
88
106
  (0, session_1.applyEvent)(state, event);
89
107
  if (events.length > 0 && state.startedAt > events[0].at)
90
108
  state.startedAt = events[0].at;
91
- saveSession(home, cursor, state);
92
- return { cursor, state, newSpawns: state.spawnCount - before };
109
+ saveSession(home, cursor, state, notified);
110
+ return { cursor, state, newSpawns: state.spawnCount - before, notified };
111
+ }
112
+ function rememberNotified(home, sessionId, signature) {
113
+ try {
114
+ const raw = JSON.parse((0, node_fs_1.readFileSync)(sessionFile(home, sessionId), 'utf8'));
115
+ raw.notified = signature;
116
+ const file = sessionFile(home, sessionId);
117
+ (0, node_fs_1.writeFileSync)(`${file}.tmp`, JSON.stringify(raw), { mode: 0o600 });
118
+ require('node:fs').renameSync(`${file}.tmp`, file);
119
+ }
120
+ catch {
121
+ /* best effort; the worst case is one repeated banner */
122
+ }
93
123
  }
94
124
  function handlePreToolUse(input, home, now = Date.now()) {
95
125
  const toolName = input.tool_name ?? '';
@@ -100,8 +130,11 @@ function handlePreToolUse(input, home, now = Date.now()) {
100
130
  const store = new reservations_1.ReservationStore(home);
101
131
  let report;
102
132
  let reservation;
133
+ let notified = '';
103
134
  try {
104
- const { state, newSpawns } = refreshSession(home, input.session_id, input.transcript_path);
135
+ const refreshed = refreshSession(home, input.session_id, input.transcript_path);
136
+ const { state, newSpawns } = refreshed;
137
+ notified = refreshed.notified;
105
138
  if (newSpawns > 0)
106
139
  store.reconcile(input.session_id, state.spawnCount, state.spawnCount - newSpawns);
107
140
  // Depth of the proposed child: the issuing agent's depth plus one. A hook
@@ -124,26 +157,41 @@ function handlePreToolUse(input, home, now = Date.now()) {
124
157
  }
125
158
  const shouldDeny = report.verdict === 'STOP' || !reservation.allowed;
126
159
  const reason = shouldDeny ? buildDenyReason(report, reservation) : '';
160
+ // The audited override: only consulted when a block is about to happen.
161
+ const override = shouldDeny && policy.mode === 'enforce' ? (0, override_1.consumeOverride)(home, now) : null;
127
162
  recordDecision(home, {
128
163
  at: now,
129
164
  sessionId: input.session_id,
130
165
  toolUseId: input.tool_use_id ?? null,
131
166
  verdict: report.verdict,
132
167
  wouldDeny: shouldDeny,
133
- enforced: policy.mode === 'enforce' && shouldDeny,
168
+ enforced: policy.mode === 'enforce' && shouldDeny && !override,
169
+ overridden: override ? { once: override.once, reason: override.reason } : undefined,
134
170
  mode: policy.mode,
135
171
  findings: report.findings.map((f) => ({ detector: f.detector, verdict: f.verdict, observed: f.observed, threshold: f.threshold })),
136
172
  effectiveSpawns: reservation.effectiveSpawns,
137
173
  totals: report.totals,
138
174
  });
139
- if (shouldDeny && policy.mode === 'enforce')
140
- return deny(reason);
175
+ if (shouldDeny && policy.mode === 'enforce') {
176
+ if (!override)
177
+ return deny(reason);
178
+ return {
179
+ continue: true,
180
+ systemMessage: `AgentGuard STOP overridden${override.once ? ' once' : ''} ("${override.reason}"): ${report.findings[0]?.summary ?? ''}`,
181
+ };
182
+ }
141
183
  if (report.verdict !== 'OK') {
184
+ const signature = findingSignature(report);
185
+ if (signature === notified)
186
+ return { continue: true, suppressOutput: true };
187
+ rememberNotified(home, input.session_id, signature);
142
188
  return {
143
189
  continue: true,
144
190
  systemMessage: `AgentGuard ${report.verdict}${policy.mode === 'shadow' && shouldDeny ? ' (shadow: would have blocked)' : ''}: ${report.findings[0]?.summary ?? ''}`,
145
191
  };
146
192
  }
193
+ if (notified)
194
+ rememberNotified(home, input.session_id, '');
147
195
  return { continue: true, suppressOutput: true };
148
196
  }
149
197
  function buildDenyReason(report, _reservation) {
@@ -1,10 +1,25 @@
1
1
  export * from './types';
2
- export { DEFAULT_POLICY, DEFAULT_THRESHOLDS } from './defaults';
2
+ export { DEFAULT_POLICY, DEFAULT_THRESHOLDS, DEFAULT_LOCAL_COMPUTE, CALL_RESERVATION_TTL_MS } from './defaults';
3
3
  export { evaluate, fmt } from './detectors/evaluate';
4
+ export { evaluateLocalCompute } from './detectors/local-compute';
4
5
  export { readIncremental, readAll, newCursor, normaliseLine, type ReaderCursor } from './history/claude-transcript';
5
- export { applyEvent, newSessionState, windowSum, medianCompletedMinute, cacheReadRatio } from './state/session';
6
- export { ReservationStore, RESERVATION_TTL_MS } from './state/reservations';
6
+ export { applyEvent, applyCorrection, newSessionState, windowSum, medianCompletedMinute, cacheReadRatio } from './state/session';
7
+ export { ReservationStore, Transaction, RESERVATION_TTL_MS, type Reservation, type CallReservation, type ComputeSnapshot } from './state/reservations';
7
8
  export { replayAll, replaySession, replayEvents, discoverTranscripts, type ReplaySummary, type SessionReplay } from './replay/simulate';
8
- export { renderReplay } from './replay/render';
9
+ export { renderReplay, renderStop } from './replay/render';
9
10
  export { calibrate, type CalibrationResult } from './calibrate';
10
11
  export { handlePreToolUse, refreshSession, loadPolicy, settingsSnippet, type HookInput, type HookOutput } from './hook/pre-tool-use';
12
+ export * from './events';
13
+ export { Gateway, mergeCapabilities, type Decision, type GatewaySessionView, type UsageCoverage, type GatewayOptions } from './gateway';
14
+ export { ReceiptSigner, verifyReceipt, receiptDigest, canonical, sha256, type ReceiptPayload, type SignedReceipt } from './receipt';
15
+ export { createRawApiGuard, BurnStopError, type RawApiGuard, type RawGuardOptions } from './adapters/raw-api';
16
+ export { handleCursorHook, cursorHooksSnippet, type CursorHookOutput } from './adapters/cursor';
17
+ export { handleCodexHook, codexHooksSnippet, parseCodexTranscript, readCodexTranscriptUsage, CODEX_FORBIDDEN_FIELDS, type CodexHookOutput } from './adapters/codex';
18
+ export { startProxy, profileFor, type ProxyOptions, type ProxyHost, type RunningProxy } from './proxy/server';
19
+ export { UsageObserver, type ProxyProfile, type ObservedUsage } from './proxy/usage-observer';
20
+ export { renderMachineStatus } from './status';
21
+ export { runConformance, type ConformanceResult } from './conformance';
22
+ export { writeOverride, readOverride, consumeOverride, clearOverride, OVERRIDE_WINDOW_MS, type Override } from './override';
23
+ export { install, health, configPath, type InstallHost, type InstallResult, type HostHealth } from './install';
24
+ export { readHookSessions, renderHostHealth } from './status';
25
+ export { findingSignature, inflateHookSession, type PersistedSession } from './hook/pre-tool-use';
package/dist/src/index.js CHANGED
@@ -14,14 +14,19 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.settingsSnippet = exports.loadPolicy = exports.refreshSession = exports.handlePreToolUse = exports.calibrate = exports.renderReplay = exports.discoverTranscripts = exports.replayEvents = exports.replaySession = exports.replayAll = exports.RESERVATION_TTL_MS = exports.ReservationStore = exports.cacheReadRatio = exports.medianCompletedMinute = exports.windowSum = exports.newSessionState = exports.applyEvent = exports.normaliseLine = exports.newCursor = exports.readAll = exports.readIncremental = exports.fmt = exports.evaluate = exports.DEFAULT_THRESHOLDS = exports.DEFAULT_POLICY = void 0;
17
+ exports.UsageObserver = exports.profileFor = exports.startProxy = exports.CODEX_FORBIDDEN_FIELDS = exports.readCodexTranscriptUsage = exports.parseCodexTranscript = exports.codexHooksSnippet = exports.handleCodexHook = exports.cursorHooksSnippet = exports.handleCursorHook = exports.BurnStopError = exports.createRawApiGuard = exports.sha256 = exports.canonical = exports.receiptDigest = exports.verifyReceipt = exports.ReceiptSigner = exports.mergeCapabilities = exports.Gateway = exports.settingsSnippet = exports.loadPolicy = exports.refreshSession = exports.handlePreToolUse = exports.calibrate = exports.renderStop = exports.renderReplay = exports.discoverTranscripts = exports.replayEvents = exports.replaySession = exports.replayAll = exports.RESERVATION_TTL_MS = exports.Transaction = exports.ReservationStore = exports.cacheReadRatio = exports.medianCompletedMinute = exports.windowSum = exports.newSessionState = exports.applyCorrection = exports.applyEvent = exports.normaliseLine = exports.newCursor = exports.readAll = exports.readIncremental = exports.evaluateLocalCompute = exports.fmt = exports.evaluate = exports.CALL_RESERVATION_TTL_MS = exports.DEFAULT_LOCAL_COMPUTE = exports.DEFAULT_THRESHOLDS = exports.DEFAULT_POLICY = void 0;
18
+ exports.inflateHookSession = exports.findingSignature = exports.renderHostHealth = exports.readHookSessions = exports.configPath = exports.health = exports.install = exports.OVERRIDE_WINDOW_MS = exports.clearOverride = exports.consumeOverride = exports.readOverride = exports.writeOverride = exports.runConformance = exports.renderMachineStatus = void 0;
18
19
  __exportStar(require("./types"), exports);
19
20
  var defaults_1 = require("./defaults");
20
21
  Object.defineProperty(exports, "DEFAULT_POLICY", { enumerable: true, get: function () { return defaults_1.DEFAULT_POLICY; } });
21
22
  Object.defineProperty(exports, "DEFAULT_THRESHOLDS", { enumerable: true, get: function () { return defaults_1.DEFAULT_THRESHOLDS; } });
23
+ Object.defineProperty(exports, "DEFAULT_LOCAL_COMPUTE", { enumerable: true, get: function () { return defaults_1.DEFAULT_LOCAL_COMPUTE; } });
24
+ Object.defineProperty(exports, "CALL_RESERVATION_TTL_MS", { enumerable: true, get: function () { return defaults_1.CALL_RESERVATION_TTL_MS; } });
22
25
  var evaluate_1 = require("./detectors/evaluate");
23
26
  Object.defineProperty(exports, "evaluate", { enumerable: true, get: function () { return evaluate_1.evaluate; } });
24
27
  Object.defineProperty(exports, "fmt", { enumerable: true, get: function () { return evaluate_1.fmt; } });
28
+ var local_compute_1 = require("./detectors/local-compute");
29
+ Object.defineProperty(exports, "evaluateLocalCompute", { enumerable: true, get: function () { return local_compute_1.evaluateLocalCompute; } });
25
30
  var claude_transcript_1 = require("./history/claude-transcript");
26
31
  Object.defineProperty(exports, "readIncremental", { enumerable: true, get: function () { return claude_transcript_1.readIncremental; } });
27
32
  Object.defineProperty(exports, "readAll", { enumerable: true, get: function () { return claude_transcript_1.readAll; } });
@@ -29,12 +34,14 @@ Object.defineProperty(exports, "newCursor", { enumerable: true, get: function ()
29
34
  Object.defineProperty(exports, "normaliseLine", { enumerable: true, get: function () { return claude_transcript_1.normaliseLine; } });
30
35
  var session_1 = require("./state/session");
31
36
  Object.defineProperty(exports, "applyEvent", { enumerable: true, get: function () { return session_1.applyEvent; } });
37
+ Object.defineProperty(exports, "applyCorrection", { enumerable: true, get: function () { return session_1.applyCorrection; } });
32
38
  Object.defineProperty(exports, "newSessionState", { enumerable: true, get: function () { return session_1.newSessionState; } });
33
39
  Object.defineProperty(exports, "windowSum", { enumerable: true, get: function () { return session_1.windowSum; } });
34
40
  Object.defineProperty(exports, "medianCompletedMinute", { enumerable: true, get: function () { return session_1.medianCompletedMinute; } });
35
41
  Object.defineProperty(exports, "cacheReadRatio", { enumerable: true, get: function () { return session_1.cacheReadRatio; } });
36
42
  var reservations_1 = require("./state/reservations");
37
43
  Object.defineProperty(exports, "ReservationStore", { enumerable: true, get: function () { return reservations_1.ReservationStore; } });
44
+ Object.defineProperty(exports, "Transaction", { enumerable: true, get: function () { return reservations_1.Transaction; } });
38
45
  Object.defineProperty(exports, "RESERVATION_TTL_MS", { enumerable: true, get: function () { return reservations_1.RESERVATION_TTL_MS; } });
39
46
  var simulate_1 = require("./replay/simulate");
40
47
  Object.defineProperty(exports, "replayAll", { enumerable: true, get: function () { return simulate_1.replayAll; } });
@@ -43,6 +50,7 @@ Object.defineProperty(exports, "replayEvents", { enumerable: true, get: function
43
50
  Object.defineProperty(exports, "discoverTranscripts", { enumerable: true, get: function () { return simulate_1.discoverTranscripts; } });
44
51
  var render_1 = require("./replay/render");
45
52
  Object.defineProperty(exports, "renderReplay", { enumerable: true, get: function () { return render_1.renderReplay; } });
53
+ Object.defineProperty(exports, "renderStop", { enumerable: true, get: function () { return render_1.renderStop; } });
46
54
  var calibrate_1 = require("./calibrate");
47
55
  Object.defineProperty(exports, "calibrate", { enumerable: true, get: function () { return calibrate_1.calibrate; } });
48
56
  var pre_tool_use_1 = require("./hook/pre-tool-use");
@@ -50,3 +58,51 @@ Object.defineProperty(exports, "handlePreToolUse", { enumerable: true, get: func
50
58
  Object.defineProperty(exports, "refreshSession", { enumerable: true, get: function () { return pre_tool_use_1.refreshSession; } });
51
59
  Object.defineProperty(exports, "loadPolicy", { enumerable: true, get: function () { return pre_tool_use_1.loadPolicy; } });
52
60
  Object.defineProperty(exports, "settingsSnippet", { enumerable: true, get: function () { return pre_tool_use_1.settingsSnippet; } });
61
+ // Cross-tool layer (0.2.0)
62
+ __exportStar(require("./events"), exports);
63
+ var gateway_1 = require("./gateway");
64
+ Object.defineProperty(exports, "Gateway", { enumerable: true, get: function () { return gateway_1.Gateway; } });
65
+ Object.defineProperty(exports, "mergeCapabilities", { enumerable: true, get: function () { return gateway_1.mergeCapabilities; } });
66
+ var receipt_1 = require("./receipt");
67
+ Object.defineProperty(exports, "ReceiptSigner", { enumerable: true, get: function () { return receipt_1.ReceiptSigner; } });
68
+ Object.defineProperty(exports, "verifyReceipt", { enumerable: true, get: function () { return receipt_1.verifyReceipt; } });
69
+ Object.defineProperty(exports, "receiptDigest", { enumerable: true, get: function () { return receipt_1.receiptDigest; } });
70
+ Object.defineProperty(exports, "canonical", { enumerable: true, get: function () { return receipt_1.canonical; } });
71
+ Object.defineProperty(exports, "sha256", { enumerable: true, get: function () { return receipt_1.sha256; } });
72
+ var raw_api_1 = require("./adapters/raw-api");
73
+ Object.defineProperty(exports, "createRawApiGuard", { enumerable: true, get: function () { return raw_api_1.createRawApiGuard; } });
74
+ Object.defineProperty(exports, "BurnStopError", { enumerable: true, get: function () { return raw_api_1.BurnStopError; } });
75
+ var cursor_1 = require("./adapters/cursor");
76
+ Object.defineProperty(exports, "handleCursorHook", { enumerable: true, get: function () { return cursor_1.handleCursorHook; } });
77
+ Object.defineProperty(exports, "cursorHooksSnippet", { enumerable: true, get: function () { return cursor_1.cursorHooksSnippet; } });
78
+ var codex_1 = require("./adapters/codex");
79
+ Object.defineProperty(exports, "handleCodexHook", { enumerable: true, get: function () { return codex_1.handleCodexHook; } });
80
+ Object.defineProperty(exports, "codexHooksSnippet", { enumerable: true, get: function () { return codex_1.codexHooksSnippet; } });
81
+ Object.defineProperty(exports, "parseCodexTranscript", { enumerable: true, get: function () { return codex_1.parseCodexTranscript; } });
82
+ Object.defineProperty(exports, "readCodexTranscriptUsage", { enumerable: true, get: function () { return codex_1.readCodexTranscriptUsage; } });
83
+ Object.defineProperty(exports, "CODEX_FORBIDDEN_FIELDS", { enumerable: true, get: function () { return codex_1.CODEX_FORBIDDEN_FIELDS; } });
84
+ var server_1 = require("./proxy/server");
85
+ Object.defineProperty(exports, "startProxy", { enumerable: true, get: function () { return server_1.startProxy; } });
86
+ Object.defineProperty(exports, "profileFor", { enumerable: true, get: function () { return server_1.profileFor; } });
87
+ var usage_observer_1 = require("./proxy/usage-observer");
88
+ Object.defineProperty(exports, "UsageObserver", { enumerable: true, get: function () { return usage_observer_1.UsageObserver; } });
89
+ var status_1 = require("./status");
90
+ Object.defineProperty(exports, "renderMachineStatus", { enumerable: true, get: function () { return status_1.renderMachineStatus; } });
91
+ var conformance_1 = require("./conformance");
92
+ Object.defineProperty(exports, "runConformance", { enumerable: true, get: function () { return conformance_1.runConformance; } });
93
+ var override_1 = require("./override");
94
+ Object.defineProperty(exports, "writeOverride", { enumerable: true, get: function () { return override_1.writeOverride; } });
95
+ Object.defineProperty(exports, "readOverride", { enumerable: true, get: function () { return override_1.readOverride; } });
96
+ Object.defineProperty(exports, "consumeOverride", { enumerable: true, get: function () { return override_1.consumeOverride; } });
97
+ Object.defineProperty(exports, "clearOverride", { enumerable: true, get: function () { return override_1.clearOverride; } });
98
+ Object.defineProperty(exports, "OVERRIDE_WINDOW_MS", { enumerable: true, get: function () { return override_1.OVERRIDE_WINDOW_MS; } });
99
+ var install_1 = require("./install");
100
+ Object.defineProperty(exports, "install", { enumerable: true, get: function () { return install_1.install; } });
101
+ Object.defineProperty(exports, "health", { enumerable: true, get: function () { return install_1.health; } });
102
+ Object.defineProperty(exports, "configPath", { enumerable: true, get: function () { return install_1.configPath; } });
103
+ var status_2 = require("./status");
104
+ Object.defineProperty(exports, "readHookSessions", { enumerable: true, get: function () { return status_2.readHookSessions; } });
105
+ Object.defineProperty(exports, "renderHostHealth", { enumerable: true, get: function () { return status_2.renderHostHealth; } });
106
+ var pre_tool_use_2 = require("./hook/pre-tool-use");
107
+ Object.defineProperty(exports, "findingSignature", { enumerable: true, get: function () { return pre_tool_use_2.findingSignature; } });
108
+ Object.defineProperty(exports, "inflateHookSession", { enumerable: true, get: function () { return pre_tool_use_2.inflateHookSession; } });
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Installing the hook into each host's config, and checking it is there.
3
+ *
4
+ * Merging into a config file the user also edits by hand is the one place
5
+ * this tool can do damage, so: always a timestamped backup first, always a
6
+ * parse-before-write, always an atomic rename, and the AgentGuard entry is
7
+ * found by its command (any command mentioning agentguard-burn), so a
8
+ * re-run upgrades the path in place instead of stacking duplicates.
9
+ */
10
+ export type InstallHost = 'claude' | 'cursor' | 'codex';
11
+ export interface InstallResult {
12
+ file: string;
13
+ backup: string | null;
14
+ changed: boolean;
15
+ command: string;
16
+ }
17
+ export interface HostHealth {
18
+ host: InstallHost;
19
+ file: string;
20
+ installed: boolean;
21
+ command: string | null;
22
+ /** The script the command points at exists on disk. */
23
+ commandExists: boolean;
24
+ /** The host itself is present (its config dir or binary). */
25
+ hostPresent: boolean;
26
+ }
27
+ export declare function configPath(host: InstallHost, home?: string): string;
28
+ export declare function install(host: InstallHost, cliPath: string, home?: string): InstallResult;
29
+ export declare function health(host: InstallHost, home?: string): HostHealth;
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ /**
3
+ * Installing the hook into each host's config, and checking it is there.
4
+ *
5
+ * Merging into a config file the user also edits by hand is the one place
6
+ * this tool can do damage, so: always a timestamped backup first, always a
7
+ * parse-before-write, always an atomic rename, and the AgentGuard entry is
8
+ * found by its command (any command mentioning agentguard-burn), so a
9
+ * re-run upgrades the path in place instead of stacking duplicates.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.configPath = configPath;
13
+ exports.install = install;
14
+ exports.health = health;
15
+ const node_fs_1 = require("node:fs");
16
+ const node_os_1 = require("node:os");
17
+ const node_path_1 = require("node:path");
18
+ const MARK = /agentguard-burn|agentguard-run\/burn/;
19
+ function configPath(host, home = (0, node_os_1.homedir)()) {
20
+ return host === 'claude' ? (0, node_path_1.join)(home, '.claude', 'settings.json') : host === 'cursor' ? (0, node_path_1.join)(home, '.cursor', 'hooks.json') : (0, node_path_1.join)(home, '.codex', 'hooks.json');
21
+ }
22
+ function readJson(file) {
23
+ if (!(0, node_fs_1.existsSync)(file))
24
+ return {};
25
+ const text = (0, node_fs_1.readFileSync)(file, 'utf8');
26
+ if (!text.trim())
27
+ return {};
28
+ const parsed = JSON.parse(text); // throws on a corrupt file: we refuse to overwrite what we cannot read
29
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
30
+ throw new Error(`${file} is not a JSON object`);
31
+ return parsed;
32
+ }
33
+ function writeJson(file, value) {
34
+ (0, node_fs_1.mkdirSync)((0, node_path_1.join)(file, '..'), { recursive: true });
35
+ let backup = null;
36
+ if ((0, node_fs_1.existsSync)(file)) {
37
+ backup = `${file}.bak-${new Date().toISOString().replace(/[:.]/g, '').slice(0, 15)}`;
38
+ (0, node_fs_1.copyFileSync)(file, backup);
39
+ }
40
+ const tmp = `${file}.agentguard.tmp`;
41
+ (0, node_fs_1.writeFileSync)(tmp, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
42
+ (0, node_fs_1.renameSync)(tmp, file);
43
+ return backup;
44
+ }
45
+ /** Claude Code and Codex share the { hooks: { Event: [ { matcher, hooks: [ {command} ] } ] } } shape. */
46
+ function mergeMatcherStyle(cfg, event, matcher, command, timeout) {
47
+ const hooks = (cfg.hooks ??= {});
48
+ const list = (hooks[event] ??= []);
49
+ let changed = false;
50
+ let found = false;
51
+ for (const m of list) {
52
+ for (const h of m.hooks ?? []) {
53
+ if (typeof h.command === 'string' && MARK.test(h.command)) {
54
+ found = true;
55
+ if (h.command !== command || m.matcher !== matcher || h.timeout !== timeout) {
56
+ h.command = command;
57
+ h.timeout = timeout;
58
+ m.matcher = matcher;
59
+ changed = true;
60
+ }
61
+ }
62
+ }
63
+ }
64
+ if (!found) {
65
+ list.push({ matcher, hooks: [{ type: 'command', command, timeout }] });
66
+ changed = true;
67
+ }
68
+ return changed;
69
+ }
70
+ /** Cursor: { version: 1, hooks: { subagentStart: [ {command, timeout, failClosed} ] } } */
71
+ function mergeCursor(cfg, command) {
72
+ let changed = false;
73
+ if (cfg.version !== 1) {
74
+ cfg.version = 1;
75
+ changed = true;
76
+ }
77
+ const hooks = (cfg.hooks ??= {});
78
+ const want = {
79
+ subagentStart: { command, timeout: 5, failClosed: true },
80
+ subagentStop: { command, timeout: 5 },
81
+ sessionEnd: { command, timeout: 3 },
82
+ };
83
+ for (const [event, entry] of Object.entries(want)) {
84
+ const list = (hooks[event] ??= []);
85
+ const mine = list.find((h) => typeof h.command === 'string' && MARK.test(h.command));
86
+ if (!mine) {
87
+ list.push(entry);
88
+ changed = true;
89
+ }
90
+ else if (JSON.stringify(mine) !== JSON.stringify({ ...mine, ...entry })) {
91
+ Object.assign(mine, entry);
92
+ changed = true;
93
+ }
94
+ }
95
+ return changed;
96
+ }
97
+ function install(host, cliPath, home = (0, node_os_1.homedir)()) {
98
+ const file = configPath(host, home);
99
+ const cfg = readJson(file);
100
+ let command;
101
+ let changed;
102
+ if (host === 'claude') {
103
+ command = `node ${cliPath} hook`;
104
+ changed = mergeMatcherStyle(cfg, 'PreToolUse', '^(Agent|Task)$', command, 5);
105
+ }
106
+ else if (host === 'codex') {
107
+ command = `node ${cliPath} codex-hook`;
108
+ changed = mergeMatcherStyle(cfg, 'PreToolUse', '^(Agent|spawn_agent)$', command, 5);
109
+ }
110
+ else {
111
+ command = `node ${cliPath} cursor-hook`;
112
+ changed = mergeCursor(cfg, command);
113
+ }
114
+ const backup = changed ? writeJson(file, cfg) : null;
115
+ return { file, backup, changed, command };
116
+ }
117
+ function findCommand(cfg) {
118
+ const hooks = cfg.hooks;
119
+ if (!hooks || typeof hooks !== 'object')
120
+ return null;
121
+ for (const list of Object.values(hooks)) {
122
+ if (!Array.isArray(list))
123
+ continue;
124
+ for (const item of list) {
125
+ const entries = Array.isArray(item.hooks) ? item.hooks : [item];
126
+ for (const h of entries)
127
+ if (typeof h.command === 'string' && MARK.test(h.command))
128
+ return h.command;
129
+ }
130
+ }
131
+ return null;
132
+ }
133
+ function health(host, home = (0, node_os_1.homedir)()) {
134
+ const file = configPath(host, home);
135
+ let command = null;
136
+ try {
137
+ command = findCommand(readJson(file));
138
+ }
139
+ catch {
140
+ command = null;
141
+ }
142
+ const script = command?.match(/^node (\S+)/)?.[1] ?? null;
143
+ const hostPresent = host === 'claude' ? (0, node_fs_1.existsSync)((0, node_path_1.join)(home, '.claude')) : host === 'cursor' ? (0, node_fs_1.existsSync)((0, node_path_1.join)(home, '.cursor')) : (0, node_fs_1.existsSync)((0, node_path_1.join)(home, '.codex'));
144
+ return { host, file, installed: command !== null, command, commandExists: script !== null && (0, node_fs_1.existsSync)(script), hostPresent };
145
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The audited override.
3
+ *
4
+ * The STOP box says "override once: agentguard-burn resume --once". That
5
+ * has to be real, and it has to be single-use across every host on the
6
+ * machine: two hooks that both see the file must not both let a spawn
7
+ * through. Consumption is a rename, which is atomic; the loser of the race
8
+ * finds nothing to rename and blocks.
9
+ *
10
+ * Without --once the override is a window, 15 minutes by default, for the
11
+ * case where the user has decided the session is fine and does not want to
12
+ * type the command forty times. Every overridden STOP is written to the
13
+ * decisions ledger with the reason, so "why did this get through" always
14
+ * has an answer.
15
+ */
16
+ export interface Override {
17
+ at: number;
18
+ once: boolean;
19
+ reason: string;
20
+ /** Absent for --once. */
21
+ until?: number;
22
+ }
23
+ export declare const OVERRIDE_WINDOW_MS: number;
24
+ export declare function writeOverride(home: string, o: Override): void;
25
+ export declare function readOverride(home: string, now?: number): Override | null;
26
+ /**
27
+ * Called only when a STOP is about to be enforced. Returns the override to
28
+ * apply, or null. A --once override is consumed here and cannot be used
29
+ * twice, however many processes ask at the same moment.
30
+ */
31
+ export declare function consumeOverride(home: string, now?: number): Override | null;
32
+ export declare function clearOverride(home: string): void;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ /**
3
+ * The audited override.
4
+ *
5
+ * The STOP box says "override once: agentguard-burn resume --once". That
6
+ * has to be real, and it has to be single-use across every host on the
7
+ * machine: two hooks that both see the file must not both let a spawn
8
+ * through. Consumption is a rename, which is atomic; the loser of the race
9
+ * finds nothing to rename and blocks.
10
+ *
11
+ * Without --once the override is a window, 15 minutes by default, for the
12
+ * case where the user has decided the session is fine and does not want to
13
+ * type the command forty times. Every overridden STOP is written to the
14
+ * decisions ledger with the reason, so "why did this get through" always
15
+ * has an answer.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.OVERRIDE_WINDOW_MS = void 0;
19
+ exports.writeOverride = writeOverride;
20
+ exports.readOverride = readOverride;
21
+ exports.consumeOverride = consumeOverride;
22
+ exports.clearOverride = clearOverride;
23
+ const node_fs_1 = require("node:fs");
24
+ const node_path_1 = require("node:path");
25
+ exports.OVERRIDE_WINDOW_MS = 15 * 60 * 1000;
26
+ function file(home) {
27
+ return (0, node_path_1.join)(home, 'override.json');
28
+ }
29
+ function writeOverride(home, o) {
30
+ (0, node_fs_1.mkdirSync)(home, { recursive: true, mode: 0o700 });
31
+ (0, node_fs_1.writeFileSync)(file(home), JSON.stringify(o), { mode: 0o600 });
32
+ }
33
+ function readOverride(home, now = Date.now()) {
34
+ try {
35
+ const o = JSON.parse((0, node_fs_1.readFileSync)(file(home), 'utf8'));
36
+ if (typeof o.reason !== 'string' || typeof o.at !== 'number')
37
+ return null;
38
+ if (!o.once && (o.until === undefined || o.until < now))
39
+ return null;
40
+ return o;
41
+ }
42
+ catch {
43
+ return null;
44
+ }
45
+ }
46
+ /**
47
+ * Called only when a STOP is about to be enforced. Returns the override to
48
+ * apply, or null. A --once override is consumed here and cannot be used
49
+ * twice, however many processes ask at the same moment.
50
+ */
51
+ function consumeOverride(home, now = Date.now()) {
52
+ const o = readOverride(home, now);
53
+ if (!o) {
54
+ if ((0, node_fs_1.existsSync)(file(home)))
55
+ (0, node_fs_1.rmSync)(file(home), { force: true }); // expired or malformed
56
+ return null;
57
+ }
58
+ if (!o.once)
59
+ return o;
60
+ const consumed = `${file(home)}.used.${process.pid}.${now.toString(36)}`;
61
+ try {
62
+ (0, node_fs_1.renameSync)(file(home), consumed);
63
+ }
64
+ catch {
65
+ return null; // a sibling consumed it first
66
+ }
67
+ (0, node_fs_1.rmSync)(consumed, { force: true });
68
+ return o;
69
+ }
70
+ function clearOverride(home) {
71
+ (0, node_fs_1.rmSync)(file(home), { force: true });
72
+ }