@mnemonik/claude-code-hooks 0.9.21 → 0.9.23
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/dist/hook.js +117 -55
- package/dist/hook.js.map +1 -1
- package/dist/install.d.ts +10 -6
- package/dist/install.js +197 -63
- package/dist/install.js.map +1 -1
- package/dist/lib/behavior.d.ts +4 -5
- package/dist/lib/behavior.js +4 -5
- package/dist/lib/behavior.js.map +1 -1
- package/dist/lib/envelopeDisplay.js +4 -2
- package/dist/lib/envelopeDisplay.js.map +1 -1
- package/dist/lib/http.d.ts +40 -3
- package/dist/lib/http.js +120 -15
- package/dist/lib/http.js.map +1 -1
- package/dist/lib/installerSupport.d.ts +36 -0
- package/dist/lib/installerSupport.js +579 -0
- package/dist/lib/installerSupport.js.map +1 -0
- package/dist/lib/sessionLifecycle.d.ts +30 -0
- package/dist/lib/sessionLifecycle.js +115 -0
- package/dist/lib/sessionLifecycle.js.map +1 -0
- package/dist/lib/types.d.ts +6 -0
- package/dist/lib/types.js.map +1 -1
- package/dist/statusline.d.ts +17 -3
- package/dist/statusline.js +69 -23
- package/dist/statusline.js.map +1 -1
- package/package.json +4 -3
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { link, mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
function errorCode(error) {
|
|
6
|
+
return error && typeof error === 'object' && 'code' in error
|
|
7
|
+
? String(error.code)
|
|
8
|
+
: undefined;
|
|
9
|
+
}
|
|
10
|
+
function lifecyclePath(input) {
|
|
11
|
+
const key = createHash('sha256')
|
|
12
|
+
.update(input.server.replace(/\/$/, ''))
|
|
13
|
+
.update('\0')
|
|
14
|
+
.update(input.cwd)
|
|
15
|
+
.update('\0')
|
|
16
|
+
.update(input.sessionId)
|
|
17
|
+
.digest('hex');
|
|
18
|
+
return join(homedir(), '.mnemonik', 'hooks', 'claude-code-hooks', 'session-incarnations', `${key}.json`);
|
|
19
|
+
}
|
|
20
|
+
export async function storeSessionLifecycleToken(input) {
|
|
21
|
+
const path = lifecyclePath(input);
|
|
22
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
23
|
+
try {
|
|
24
|
+
await mkdir(join(path, '..'), { recursive: true, mode: 0o700 });
|
|
25
|
+
await writeFile(tmp, JSON.stringify({ lifecycleToken: input.lifecycleToken, receivedAt: Date.now() }), { encoding: 'utf8', mode: 0o600 });
|
|
26
|
+
await rename(tmp, path);
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
await unlink(tmp).catch(() => { });
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Read the token that existed when this hook subprocess started. If a newer
|
|
36
|
+
* SessionStart overwrote the file while an older SessionEnd was delayed, the
|
|
37
|
+
* local receive timestamp makes the old process fail closed instead of reading
|
|
38
|
+
* and presenting the new incarnation's capability. Equality also fails closed:
|
|
39
|
+
* Date.now() has millisecond resolution, so a newer write can share the hook's
|
|
40
|
+
* captured start timestamp.
|
|
41
|
+
*/
|
|
42
|
+
export async function readSessionLifecycleToken(input) {
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse(await readFile(lifecyclePath(input), 'utf8'));
|
|
45
|
+
if (typeof parsed.lifecycleToken !== 'string' ||
|
|
46
|
+
parsed.lifecycleToken.length === 0 ||
|
|
47
|
+
typeof parsed.receivedAt !== 'number' ||
|
|
48
|
+
parsed.receivedAt >= input.invocationStartedAt) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
return parsed.lifecycleToken;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Compare-and-remove without a read→unlink TOCTOU. Rename the current record to
|
|
59
|
+
* a unique quarantine path first, then inspect the exact inode we moved. If a
|
|
60
|
+
* resumed SessionStart won the race, restore that newer record only when the
|
|
61
|
+
* canonical path is still absent; hard-link creation is atomic and never
|
|
62
|
+
* overwrites a still-newer token.
|
|
63
|
+
*/
|
|
64
|
+
export async function removeSessionLifecycleToken(input) {
|
|
65
|
+
const path = lifecyclePath(input);
|
|
66
|
+
const quarantine = `${path}.${process.pid}.${randomUUID()}.remove`;
|
|
67
|
+
let moved = false;
|
|
68
|
+
try {
|
|
69
|
+
// Confirm the source is readable before moving it. The content is read
|
|
70
|
+
// again from quarantine because another process may replace the path after
|
|
71
|
+
// this check and before rename.
|
|
72
|
+
await readFile(path, 'utf8');
|
|
73
|
+
await rename(path, quarantine);
|
|
74
|
+
moved = true;
|
|
75
|
+
const raw = await readFile(quarantine, 'utf8');
|
|
76
|
+
let matches = false;
|
|
77
|
+
try {
|
|
78
|
+
matches = JSON.parse(raw).lifecycleToken === input.lifecycleToken;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// Malformed/unknown state is never safe to delete; restore it below.
|
|
82
|
+
}
|
|
83
|
+
if (matches) {
|
|
84
|
+
await unlink(quarantine);
|
|
85
|
+
moved = false;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
await link(quarantine, path);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
if (errorCode(error) !== 'EEXIST')
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
await unlink(quarantine);
|
|
96
|
+
moved = false;
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
if (moved) {
|
|
100
|
+
let safeToRemoveQuarantine = false;
|
|
101
|
+
try {
|
|
102
|
+
await link(quarantine, path);
|
|
103
|
+
safeToRemoveQuarantine = true;
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
safeToRemoveQuarantine = ['EEXIST', 'ENOENT'].includes(errorCode(error) ?? '');
|
|
107
|
+
}
|
|
108
|
+
if (safeToRemoveQuarantine)
|
|
109
|
+
await unlink(quarantine).catch(() => { });
|
|
110
|
+
}
|
|
111
|
+
// Best-effort local cleanup; the server token remains authoritative and a
|
|
112
|
+
// missing capability merely defers to normal inactivity expiry.
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=sessionLifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionLifecycle.js","sourceRoot":"","sources":["../../src/lib/sessionLifecycle.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export type HookEventName = 'PreToolUse' | 'PostToolUse' | 'SessionStart' | 'Sto
|
|
|
9
9
|
export interface HookInput {
|
|
10
10
|
session_id: string;
|
|
11
11
|
transcript_path?: string;
|
|
12
|
+
trigger?: string;
|
|
13
|
+
custom_instructions?: string;
|
|
12
14
|
cwd: string;
|
|
13
15
|
hook_event_name: HookEventName;
|
|
14
16
|
permission_mode?: string;
|
|
@@ -17,6 +19,8 @@ export interface HookInput {
|
|
|
17
19
|
tool_input?: Record<string, unknown>;
|
|
18
20
|
tool_response?: Record<string, unknown>;
|
|
19
21
|
tool_use_id?: string;
|
|
22
|
+
/** Claude SessionEnd reason (for example clear/logout/exit). */
|
|
23
|
+
reason?: string;
|
|
20
24
|
}
|
|
21
25
|
/** PreToolUse decision shape recognised by Claude Code. */
|
|
22
26
|
export type PermissionDecision = 'allow' | 'deny' | 'ask' | 'defer';
|
|
@@ -93,6 +97,8 @@ export interface PolicyReminderResponse {
|
|
|
93
97
|
export interface InjectionsResponse {
|
|
94
98
|
ok: boolean;
|
|
95
99
|
reason?: 'unauthorized' | 'no_project';
|
|
100
|
+
/** SessionStart recovery readback for a lost /hooks/session-start response. */
|
|
101
|
+
lifecycleToken?: string;
|
|
96
102
|
injections: Array<{
|
|
97
103
|
signal: string;
|
|
98
104
|
kind: 'directive' | 'nudge';
|
package/dist/lib/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAyOH,MAAM,CAAC,MAAM,cAAc,GAAG,0BAA0B,CAAC"}
|
package/dist/statusline.d.ts
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* The report is skipped silently when used_percentage is null (no live window
|
|
21
21
|
* yet) or the API key / config is unavailable.
|
|
22
22
|
*/
|
|
23
|
+
import { type StatusDigest } from './lib/http.js';
|
|
23
24
|
interface StatusInput {
|
|
24
25
|
session_id?: string;
|
|
25
26
|
cwd?: string;
|
|
@@ -37,6 +38,19 @@ interface StatusInput {
|
|
|
37
38
|
total_cost_usd?: number | null;
|
|
38
39
|
};
|
|
39
40
|
}
|
|
40
|
-
/**
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Compact Mnemonik-state segment appended to the status line (task 2f57b87d):
|
|
43
|
+
* `mnk[tasks=3|ckpt=21m|scan=ok|policy=15]`. The `ckpt` token is rendered by the
|
|
44
|
+
* SERVER (humanDeltaShort in the developer's zone) and printed VERBATIM — the
|
|
45
|
+
* client does no time math. `null`/degraded (server unreachable, non-ok, or no
|
|
46
|
+
* attempt made) yields the known-degraded marker `mnk[?]`; the caller decides
|
|
47
|
+
* whether to append it.
|
|
48
|
+
*/
|
|
49
|
+
declare function renderDigest(digest: StatusDigest | null): string;
|
|
50
|
+
/**
|
|
51
|
+
* Build the single status line. Pure — never throws on missing fields. When a
|
|
52
|
+
* `digestSegment` is supplied it is appended after the local model/ctx/cost
|
|
53
|
+
* segment (`… | mnk[…]`); omit it entirely on hosts/configs with no digest.
|
|
54
|
+
*/
|
|
55
|
+
declare function renderLine(input: StatusInput, digestSegment?: string): string;
|
|
56
|
+
export { renderLine, renderDigest };
|
package/dist/statusline.js
CHANGED
|
@@ -21,9 +21,10 @@
|
|
|
21
21
|
* yet) or the API key / config is unavailable.
|
|
22
22
|
*/
|
|
23
23
|
import { stdin, stdout, exit } from 'node:process';
|
|
24
|
+
import { pathToFileURL } from 'node:url';
|
|
24
25
|
import { resolveConfig } from './lib/env.js';
|
|
25
26
|
import { findProjectIdentity } from './lib/identity.js';
|
|
26
|
-
import { postContextStats } from './lib/http.js';
|
|
27
|
+
import { postContextStats, fetchStatusDigest } from './lib/http.js';
|
|
27
28
|
const FALLBACK_LINE = 'mnemonik';
|
|
28
29
|
async function readStdin() {
|
|
29
30
|
return new Promise((res) => {
|
|
@@ -47,8 +48,31 @@ async function readStdin() {
|
|
|
47
48
|
setTimeout(finish, 500);
|
|
48
49
|
});
|
|
49
50
|
}
|
|
50
|
-
/**
|
|
51
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Compact Mnemonik-state segment appended to the status line (task 2f57b87d):
|
|
53
|
+
* `mnk[tasks=3|ckpt=21m|scan=ok|policy=15]`. The `ckpt` token is rendered by the
|
|
54
|
+
* SERVER (humanDeltaShort in the developer's zone) and printed VERBATIM — the
|
|
55
|
+
* client does no time math. `null`/degraded (server unreachable, non-ok, or no
|
|
56
|
+
* attempt made) yields the known-degraded marker `mnk[?]`; the caller decides
|
|
57
|
+
* whether to append it.
|
|
58
|
+
*/
|
|
59
|
+
function renderDigest(digest) {
|
|
60
|
+
if (!digest || digest.ok !== true)
|
|
61
|
+
return 'mnk[?]';
|
|
62
|
+
const parts = [
|
|
63
|
+
`tasks=${typeof digest.tasks === 'number' ? digest.tasks : 0}`,
|
|
64
|
+
`ckpt=${digest.ckpt ?? 'none'}`,
|
|
65
|
+
`scan=${digest.scan ?? 'off'}`,
|
|
66
|
+
`policy=${typeof digest.policy === 'number' ? digest.policy : 0}`,
|
|
67
|
+
];
|
|
68
|
+
return `mnk[${parts.join('|')}]`;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Build the single status line. Pure — never throws on missing fields. When a
|
|
72
|
+
* `digestSegment` is supplied it is appended after the local model/ctx/cost
|
|
73
|
+
* segment (`… | mnk[…]`); omit it entirely on hosts/configs with no digest.
|
|
74
|
+
*/
|
|
75
|
+
function renderLine(input, digestSegment) {
|
|
52
76
|
const model = input.model?.display_name || input.model?.id || 'Claude';
|
|
53
77
|
const cw = input.context_window ?? {};
|
|
54
78
|
const pct = typeof cw.used_percentage === 'number' ? Math.round(cw.used_percentage) : null;
|
|
@@ -62,7 +86,8 @@ function renderLine(input) {
|
|
|
62
86
|
: 'ctx —';
|
|
63
87
|
const cost = input.cost?.total_cost_usd;
|
|
64
88
|
const costPart = typeof cost === 'number' && cost > 0 ? ` | $${cost.toFixed(2)}` : '';
|
|
65
|
-
|
|
89
|
+
const digestPart = digestSegment ? ` | ${digestSegment}` : '';
|
|
90
|
+
return `${model} | ${ctx}${costPart}${digestPart}`;
|
|
66
91
|
}
|
|
67
92
|
async function main() {
|
|
68
93
|
let input;
|
|
@@ -73,9 +98,26 @@ async function main() {
|
|
|
73
98
|
stdout.write(`${FALLBACK_LINE}\n`);
|
|
74
99
|
return;
|
|
75
100
|
}
|
|
76
|
-
//
|
|
101
|
+
// Resolve config up front — needed for both the digest GET and the context
|
|
102
|
+
// POST. It's a local env/file read (single-digit ms).
|
|
103
|
+
const config = input.cwd ? await resolveConfig(input.cwd) : null;
|
|
104
|
+
// Statusline digest (task 2f57b87d). Fetch BEFORE printing so it can ride the
|
|
105
|
+
// single first line (Claude Code renders line 1 only). Bounded + fail-open:
|
|
106
|
+
// on any failure we render `mnk[?]` (known-degraded) rather than nothing, and
|
|
107
|
+
// only when we actually attempted a fetch. Configs with no API key append no
|
|
108
|
+
// segment at all — identical to today's local-only line.
|
|
109
|
+
let digestSegment;
|
|
110
|
+
if (config?.apiKey && input.cwd) {
|
|
111
|
+
const digest = await fetchStatusDigest({
|
|
112
|
+
server: config.server,
|
|
113
|
+
apiKey: config.apiKey,
|
|
114
|
+
cwd: input.cwd,
|
|
115
|
+
sessionId: input.session_id,
|
|
116
|
+
});
|
|
117
|
+
digestSegment = renderDigest(digest);
|
|
118
|
+
}
|
|
77
119
|
try {
|
|
78
|
-
stdout.write(`${renderLine(input)}\n`);
|
|
120
|
+
stdout.write(`${renderLine(input, digestSegment)}\n`);
|
|
79
121
|
}
|
|
80
122
|
catch {
|
|
81
123
|
stdout.write(`${FALLBACK_LINE}\n`);
|
|
@@ -87,12 +129,10 @@ async function main() {
|
|
|
87
129
|
if (typeof cw.used_percentage !== 'number' ||
|
|
88
130
|
typeof tokens !== 'number' ||
|
|
89
131
|
!input.session_id ||
|
|
90
|
-
!input.cwd
|
|
132
|
+
!input.cwd ||
|
|
133
|
+
!config?.apiKey) {
|
|
91
134
|
return;
|
|
92
135
|
}
|
|
93
|
-
const config = await resolveConfig(input.cwd);
|
|
94
|
-
if (!config.apiKey)
|
|
95
|
-
return;
|
|
96
136
|
await postContextStats({
|
|
97
137
|
server: config.server,
|
|
98
138
|
apiKey: config.apiKey,
|
|
@@ -102,17 +142,23 @@ async function main() {
|
|
|
102
142
|
contextTokens: Math.max(0, Math.floor(tokens)),
|
|
103
143
|
});
|
|
104
144
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
|
|
145
|
+
// Entry-point guard: only run (and exit-0) when executed as the statusline
|
|
146
|
+
// binary. Importing this module for renderLine/renderDigest (the test suite
|
|
147
|
+
// does) must never read stdin or call exit — pre-guard, the import killed the
|
|
148
|
+
// vitest worker via the exit(0) below.
|
|
149
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
150
|
+
main()
|
|
151
|
+
.then(() => exit(0))
|
|
152
|
+
.catch(() => {
|
|
153
|
+
// Absolute last resort — a status line must never error out.
|
|
154
|
+
try {
|
|
155
|
+
stdout.write(`${FALLBACK_LINE}\n`);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
/* nothing left to do */
|
|
159
|
+
}
|
|
160
|
+
exit(0);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
export { renderLine, renderDigest };
|
|
118
164
|
//# sourceMappingURL=statusline.js.map
|
package/dist/statusline.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statusline.js","sourceRoot":"","sources":["../src/statusline.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"statusline.js","sourceRoot":"","sources":["../src/statusline.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mnemonik/claude-code-hooks",
|
|
3
|
-
"version": "0.9.
|
|
4
|
-
"description": "Claude Code lifecycle contextual hooks for Mnemonik. PreToolUse Edit/Write/NotebookEdit/Bash gates plus SessionStart bootstrap
|
|
3
|
+
"version": "0.9.23",
|
|
4
|
+
"description": "Claude Code lifecycle contextual hooks for Mnemonik. PreToolUse Edit/Write/NotebookEdit/Bash gates plus SessionStart bootstrap warming, clean Stop, PreCompact context_snapshot creation, bounded SessionEnd cleanup, and UserPromptSubmit context injection. This hooks-only package does not install skills, rules, project identity, or MCP config. Versioned independently from the @mnemonik/server release cadence (mirrors @mnemonik/scanner).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"mnemonik-claude-code-hooks": "dist/install.js"
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"build": "tsc",
|
|
15
15
|
"typecheck": "tsc --noEmit",
|
|
16
16
|
"test": "vitest run",
|
|
17
|
-
"test:watch": "vitest"
|
|
17
|
+
"test:watch": "vitest",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
18
19
|
},
|
|
19
20
|
"engines": {
|
|
20
21
|
"node": ">=20"
|