@hasna/hooks 0.0.1 → 0.0.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.
Files changed (64) hide show
  1. package/.hooks/index.ts +6 -0
  2. package/bin/index.js +1 -1
  3. package/dist/index.js +366 -0
  4. package/hooks/hook-agentmessages/bin/cli.ts +125 -0
  5. package/package.json +2 -2
  6. package/hooks/hook-agentmessages/src/check-messages.ts +0 -151
  7. package/hooks/hook-agentmessages/src/install.ts +0 -126
  8. package/hooks/hook-agentmessages/src/session-start.ts +0 -255
  9. package/hooks/hook-agentmessages/src/uninstall.ts +0 -89
  10. package/hooks/hook-branchprotect/src/cli.ts +0 -126
  11. package/hooks/hook-branchprotect/src/hook.ts +0 -88
  12. package/hooks/hook-branchprotect/tsconfig.json +0 -25
  13. package/hooks/hook-checkbugs/src/cli.ts +0 -628
  14. package/hooks/hook-checkbugs/src/hook.ts +0 -335
  15. package/hooks/hook-checkbugs/tsconfig.json +0 -15
  16. package/hooks/hook-checkdocs/src/cli.ts +0 -628
  17. package/hooks/hook-checkdocs/src/hook.ts +0 -310
  18. package/hooks/hook-checkdocs/tsconfig.json +0 -15
  19. package/hooks/hook-checkfiles/src/cli.ts +0 -545
  20. package/hooks/hook-checkfiles/src/hook.ts +0 -321
  21. package/hooks/hook-checkfiles/tsconfig.json +0 -15
  22. package/hooks/hook-checklint/src/cli-patch.ts +0 -32
  23. package/hooks/hook-checklint/src/cli.ts +0 -667
  24. package/hooks/hook-checklint/src/hook.ts +0 -473
  25. package/hooks/hook-checklint/tsconfig.json +0 -15
  26. package/hooks/hook-checkpoint/src/cli.ts +0 -191
  27. package/hooks/hook-checkpoint/src/hook.ts +0 -207
  28. package/hooks/hook-checkpoint/tsconfig.json +0 -25
  29. package/hooks/hook-checksecurity/src/cli.ts +0 -601
  30. package/hooks/hook-checksecurity/src/hook.ts +0 -334
  31. package/hooks/hook-checksecurity/tsconfig.json +0 -15
  32. package/hooks/hook-checktasks/src/cli.ts +0 -578
  33. package/hooks/hook-checktasks/src/hook.ts +0 -308
  34. package/hooks/hook-checktasks/tsconfig.json +0 -20
  35. package/hooks/hook-checktests/src/cli.ts +0 -627
  36. package/hooks/hook-checktests/src/hook.ts +0 -334
  37. package/hooks/hook-checktests/tsconfig.json +0 -15
  38. package/hooks/hook-contextrefresh/src/cli.ts +0 -152
  39. package/hooks/hook-contextrefresh/src/hook.ts +0 -148
  40. package/hooks/hook-contextrefresh/tsconfig.json +0 -25
  41. package/hooks/hook-gitguard/src/cli.ts +0 -159
  42. package/hooks/hook-gitguard/src/hook.ts +0 -129
  43. package/hooks/hook-gitguard/tsconfig.json +0 -25
  44. package/hooks/hook-packageage/src/cli.ts +0 -165
  45. package/hooks/hook-packageage/src/hook.ts +0 -177
  46. package/hooks/hook-packageage/tsconfig.json +0 -25
  47. package/hooks/hook-phonenotify/src/cli.ts +0 -196
  48. package/hooks/hook-phonenotify/src/hook.ts +0 -139
  49. package/hooks/hook-phonenotify/tsconfig.json +0 -25
  50. package/hooks/hook-precompact/src/cli.ts +0 -168
  51. package/hooks/hook-precompact/src/hook.ts +0 -122
  52. package/hooks/hook-precompact/tsconfig.json +0 -25
  53. package/src/cli/components/App.tsx +0 -191
  54. package/src/cli/components/CategorySelect.tsx +0 -37
  55. package/src/cli/components/DataTable.tsx +0 -133
  56. package/src/cli/components/Header.tsx +0 -18
  57. package/src/cli/components/HookSelect.tsx +0 -29
  58. package/src/cli/components/InstallProgress.tsx +0 -105
  59. package/src/cli/components/SearchView.tsx +0 -86
  60. package/src/cli/index.tsx +0 -218
  61. package/src/index.ts +0 -31
  62. package/src/lib/installer.ts +0 -288
  63. package/src/lib/registry.ts +0 -205
  64. package/tsconfig.json +0 -17
@@ -1,196 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- /**
4
- * CLI for hook-phonenotify
5
- */
6
-
7
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
8
- import { join } from "path";
9
- import { homedir } from "os";
10
-
11
- const HOOK_NAME = "hook-phonenotify";
12
- const SETTINGS_PATH = join(homedir(), ".claude", "settings.json");
13
-
14
- interface ClaudeSettings {
15
- hooks?: {
16
- Stop?: Array<{ matcher?: string; hooks: Array<{ type: "command"; command: string }> }>;
17
- Notification?: Array<{ matcher?: string; hooks: Array<{ type: "command"; command: string }> }>;
18
- };
19
- phoneNotifyConfig?: {
20
- enabled?: boolean;
21
- topic?: string;
22
- server?: string;
23
- priority?: number;
24
- };
25
- [key: string]: unknown;
26
- }
27
-
28
- function readSettings(): ClaudeSettings {
29
- try {
30
- if (existsSync(SETTINGS_PATH)) {
31
- return JSON.parse(readFileSync(SETTINGS_PATH, "utf-8"));
32
- }
33
- } catch {}
34
- return {};
35
- }
36
-
37
- function writeSettings(settings: ClaudeSettings): void {
38
- const dir = join(homedir(), ".claude");
39
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
40
- writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
41
- }
42
-
43
- function install(topic?: string): void {
44
- const settings = readSettings();
45
- if (!settings.hooks) settings.hooks = {};
46
- if (!settings.hooks.Stop) settings.hooks.Stop = [];
47
- if (!settings.hooks.Notification) settings.hooks.Notification = [];
48
-
49
- const hookCommand = `bunx @hasnaxyz/${HOOK_NAME}`;
50
-
51
- const existing = settings.hooks.Stop.find((h) =>
52
- h.hooks.some((hook) => hook.command.includes(HOOK_NAME))
53
- );
54
-
55
- if (existing) {
56
- console.log(`${HOOK_NAME} is already installed`);
57
- return;
58
- }
59
-
60
- settings.hooks.Stop.push({
61
- hooks: [{ type: "command", command: hookCommand }],
62
- });
63
- settings.hooks.Notification.push({
64
- hooks: [{ type: "command", command: hookCommand }],
65
- });
66
-
67
- if (!settings.phoneNotifyConfig) {
68
- settings.phoneNotifyConfig = {
69
- enabled: true,
70
- topic: topic || "claude-code",
71
- server: "https://ntfy.sh",
72
- priority: 3,
73
- };
74
- }
75
-
76
- writeSettings(settings);
77
- console.log(`${HOOK_NAME} installed successfully`);
78
- console.log(`Topic: ${settings.phoneNotifyConfig.topic}`);
79
- console.log(`\nTo receive notifications, subscribe to your topic in the ntfy app:`);
80
- console.log(` https://ntfy.sh/${settings.phoneNotifyConfig.topic}`);
81
- }
82
-
83
- function uninstall(): void {
84
- const settings = readSettings();
85
- let removed = false;
86
-
87
- if (settings.hooks?.Stop) {
88
- const before = settings.hooks.Stop.length;
89
- settings.hooks.Stop = settings.hooks.Stop.filter(
90
- (h) => !h.hooks.some((hook) => hook.command.includes(HOOK_NAME))
91
- );
92
- if (before !== settings.hooks.Stop.length) removed = true;
93
- }
94
-
95
- if (settings.hooks?.Notification) {
96
- const before = settings.hooks.Notification.length;
97
- settings.hooks.Notification = settings.hooks.Notification.filter(
98
- (h) => !h.hooks.some((hook) => hook.command.includes(HOOK_NAME))
99
- );
100
- if (before !== settings.hooks.Notification.length) removed = true;
101
- }
102
-
103
- if (!removed) {
104
- console.log(`${HOOK_NAME} is not installed`);
105
- return;
106
- }
107
-
108
- writeSettings(settings);
109
- console.log(`${HOOK_NAME} uninstalled successfully`);
110
- }
111
-
112
- function status(): void {
113
- const settings = readSettings();
114
- const installed = settings.hooks?.Stop?.some((h) =>
115
- h.hooks.some((hook) => hook.command.includes(HOOK_NAME))
116
- );
117
- console.log(`${HOOK_NAME} is ${installed ? "installed" : "not installed"}`);
118
-
119
- if (settings.phoneNotifyConfig) {
120
- console.log(`\nConfig:`);
121
- console.log(` Enabled: ${settings.phoneNotifyConfig.enabled !== false}`);
122
- console.log(` Topic: ${settings.phoneNotifyConfig.topic || "(not set)"}`);
123
- console.log(` Server: ${settings.phoneNotifyConfig.server || "https://ntfy.sh"}`);
124
- console.log(` Priority: ${settings.phoneNotifyConfig.priority || 3}`);
125
- }
126
- }
127
-
128
- async function test(): Promise<void> {
129
- console.log("Sending test notification...");
130
- const settings = readSettings();
131
- const config = settings.phoneNotifyConfig;
132
-
133
- if (!config?.topic) {
134
- console.error("No topic configured. Run: hook-phonenotify install <topic>");
135
- process.exit(1);
136
- }
137
-
138
- const server = config.server || "https://ntfy.sh";
139
- const url = `${server}/${config.topic}`;
140
-
141
- const response = await fetch(url, {
142
- method: "POST",
143
- headers: {
144
- Title: "Claude Code - Test",
145
- Priority: String(config.priority || 3),
146
- Tags: "robot,test_tube",
147
- },
148
- body: "This is a test notification from hook-phonenotify.",
149
- });
150
-
151
- if (response.ok) {
152
- console.log("Test notification sent!");
153
- } else {
154
- console.error(`Failed: ${response.status} ${response.statusText}`);
155
- }
156
- }
157
-
158
- function help(): void {
159
- console.log(`
160
- ${HOOK_NAME} - Push notifications to phone via ntfy.sh
161
-
162
- Usage: ${HOOK_NAME} <command>
163
-
164
- Commands:
165
- install [topic] Install hook (optional: set ntfy topic)
166
- uninstall Remove hook from Claude Code settings
167
- status Check if hook is installed and show config
168
- test Send a test notification
169
- help Show this help message
170
-
171
- Setup:
172
- 1. Install the ntfy app on your phone (iOS/Android)
173
- 2. Subscribe to your chosen topic
174
- 3. Run: ${HOOK_NAME} install my-secret-topic
175
- `);
176
- }
177
-
178
- const command = process.argv[2];
179
-
180
- switch (command) {
181
- case "install": install(process.argv[3]); break;
182
- case "uninstall": uninstall(); break;
183
- case "status": status(); break;
184
- case "test": test(); break;
185
- case "help":
186
- case "--help":
187
- case "-h": help(); break;
188
- default:
189
- if (!command) {
190
- import("./hook.ts").then((m) => m.run());
191
- } else {
192
- console.error(`Unknown command: ${command}`);
193
- help();
194
- process.exit(1);
195
- }
196
- }
@@ -1,139 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- /**
4
- * Claude Code Hook: phonenotify
5
- *
6
- * Stop/Notification hook that sends push notifications to your phone
7
- * via ntfy.sh when Claude finishes a task or needs your attention.
8
- *
9
- * Configure via ~/.claude/settings.json:
10
- * {
11
- * "phoneNotifyConfig": {
12
- * "enabled": true,
13
- * "topic": "claude-code-YOUR_SECRET",
14
- * "server": "https://ntfy.sh",
15
- * "priority": 3
16
- * }
17
- * }
18
- */
19
-
20
- import { readFileSync, existsSync } from "fs";
21
- import { join } from "path";
22
- import { homedir } from "os";
23
-
24
- interface HookInput {
25
- session_id: string;
26
- cwd: string;
27
- hook_event_name: string;
28
- notification_type?: string;
29
- message?: string;
30
- }
31
-
32
- interface HookOutput {
33
- continue?: boolean;
34
- }
35
-
36
- interface PhoneNotifyConfig {
37
- enabled?: boolean;
38
- topic?: string;
39
- server?: string;
40
- priority?: number;
41
- }
42
-
43
- const CONFIG_KEY = "phoneNotifyConfig";
44
-
45
- function readStdinJson(): HookInput | null {
46
- try {
47
- const input = readFileSync(0, "utf-8").trim();
48
- if (!input) return null;
49
- return JSON.parse(input);
50
- } catch {
51
- return null;
52
- }
53
- }
54
-
55
- function getConfig(): PhoneNotifyConfig {
56
- const settingsPath = join(homedir(), ".claude", "settings.json");
57
- try {
58
- if (existsSync(settingsPath)) {
59
- const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
60
- return settings[CONFIG_KEY] || {};
61
- }
62
- } catch {}
63
- return {};
64
- }
65
-
66
- async function sendNotification(
67
- config: PhoneNotifyConfig,
68
- title: string,
69
- message: string
70
- ): Promise<void> {
71
- const server = config.server || "https://ntfy.sh";
72
- const topic = config.topic;
73
-
74
- if (!topic) {
75
- console.error("[hook-phonenotify] No topic configured. Set phoneNotifyConfig.topic in settings.");
76
- return;
77
- }
78
-
79
- const url = `${server}/${topic}`;
80
-
81
- try {
82
- const response = await fetch(url, {
83
- method: "POST",
84
- headers: {
85
- Title: title,
86
- Priority: String(config.priority || 3),
87
- Tags: "robot",
88
- },
89
- body: message,
90
- });
91
-
92
- if (!response.ok) {
93
- console.error(`[hook-phonenotify] Failed to send: ${response.status}`);
94
- }
95
- } catch (error) {
96
- console.error(`[hook-phonenotify] Send failed: ${error}`);
97
- }
98
- }
99
-
100
- function respond(output: HookOutput): void {
101
- console.log(JSON.stringify(output));
102
- }
103
-
104
- export async function run(): Promise<void> {
105
- const input = readStdinJson();
106
-
107
- if (!input) {
108
- respond({ continue: true });
109
- return;
110
- }
111
-
112
- const config = getConfig();
113
-
114
- if (!config.enabled) {
115
- respond({ continue: true });
116
- return;
117
- }
118
-
119
- let title = "Claude Code";
120
- let message = "";
121
-
122
- if (input.hook_event_name === "Stop") {
123
- title = "Claude Code - Done";
124
- message = "Claude has finished and is waiting for your response.";
125
- } else if (input.hook_event_name === "Notification") {
126
- title = "Claude Code - Attention";
127
- message = input.message || "Claude Code requires your attention.";
128
- } else {
129
- respond({ continue: true });
130
- return;
131
- }
132
-
133
- await sendNotification(config, title, message);
134
- respond({ continue: true });
135
- }
136
-
137
- if (import.meta.main) {
138
- run();
139
- }
@@ -1,25 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ESNext",
4
- "module": "ESNext",
5
- "lib": ["ESNext"],
6
- "moduleResolution": "bundler",
7
- "allowImportingTsExtensions": true,
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "skipLibCheck": true,
11
- "forceConsistentCasingInFileNames": true,
12
- "resolveJsonModule": true,
13
- "isolatedModules": true,
14
- "noEmit": true,
15
- "noUnusedLocals": true,
16
- "noUnusedParameters": true,
17
- "declaration": true,
18
- "declarationMap": true,
19
- "outDir": "./dist",
20
- "rootDir": "./src",
21
- "types": ["bun-types"]
22
- },
23
- "include": ["src/**/*"],
24
- "exclude": ["node_modules", "dist"]
25
- }
@@ -1,168 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- /**
4
- * CLI for hook-precompact
5
- */
6
-
7
- import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from "fs";
8
- import { join } from "path";
9
- import { homedir } from "os";
10
-
11
- const HOOK_NAME = "hook-precompact";
12
- const SETTINGS_PATH = join(homedir(), ".claude", "settings.json");
13
- const HANDOFF_DIR = ".claude-handoffs";
14
-
15
- interface ClaudeSettings {
16
- hooks?: {
17
- PreCompact?: Array<{
18
- matcher?: string;
19
- hooks: Array<{ type: "command"; command: string }>;
20
- }>;
21
- };
22
- [key: string]: unknown;
23
- }
24
-
25
- function readSettings(): ClaudeSettings {
26
- try {
27
- if (existsSync(SETTINGS_PATH)) {
28
- return JSON.parse(readFileSync(SETTINGS_PATH, "utf-8"));
29
- }
30
- } catch {}
31
- return {};
32
- }
33
-
34
- function writeSettings(settings: ClaudeSettings): void {
35
- const dir = join(homedir(), ".claude");
36
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
37
- writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
38
- }
39
-
40
- function install(): void {
41
- const settings = readSettings();
42
- if (!settings.hooks) settings.hooks = {};
43
- if (!settings.hooks.PreCompact) settings.hooks.PreCompact = [];
44
-
45
- const existing = settings.hooks.PreCompact.find((h) =>
46
- h.hooks.some((hook) => hook.command.includes(HOOK_NAME))
47
- );
48
-
49
- if (existing) {
50
- console.log(`${HOOK_NAME} is already installed`);
51
- return;
52
- }
53
-
54
- settings.hooks.PreCompact.push({
55
- hooks: [{ type: "command", command: `bunx @hasnaxyz/${HOOK_NAME}` }],
56
- });
57
-
58
- writeSettings(settings);
59
- console.log(`${HOOK_NAME} installed successfully`);
60
- console.log("Hook will save session state before context compaction");
61
- }
62
-
63
- function uninstall(): void {
64
- const settings = readSettings();
65
- if (!settings.hooks?.PreCompact) {
66
- console.log(`${HOOK_NAME} is not installed`);
67
- return;
68
- }
69
-
70
- const before = settings.hooks.PreCompact.length;
71
- settings.hooks.PreCompact = settings.hooks.PreCompact.filter(
72
- (h) => !h.hooks.some((hook) => hook.command.includes(HOOK_NAME))
73
- );
74
-
75
- if (before === settings.hooks.PreCompact.length) {
76
- console.log(`${HOOK_NAME} is not installed`);
77
- return;
78
- }
79
-
80
- writeSettings(settings);
81
- console.log(`${HOOK_NAME} uninstalled successfully`);
82
- }
83
-
84
- function status(): void {
85
- const settings = readSettings();
86
- const installed = settings.hooks?.PreCompact?.some((h) =>
87
- h.hooks.some((hook) => hook.command.includes(HOOK_NAME))
88
- );
89
- console.log(`${HOOK_NAME} is ${installed ? "installed" : "not installed"}`);
90
- }
91
-
92
- function list(): void {
93
- const handoffDir = join(process.cwd(), HANDOFF_DIR);
94
- if (!existsSync(handoffDir)) {
95
- console.log("No handoffs found in current directory");
96
- return;
97
- }
98
-
99
- const files = readdirSync(handoffDir)
100
- .filter((f) => f.startsWith("handoff-") && f.endsWith(".json"))
101
- .sort()
102
- .reverse();
103
-
104
- if (files.length === 0) {
105
- console.log("No handoffs found");
106
- return;
107
- }
108
-
109
- console.log(`Recent handoffs (${files.length} total):\n`);
110
- for (const file of files.slice(0, 10)) {
111
- try {
112
- const data = JSON.parse(readFileSync(join(handoffDir, file), "utf-8"));
113
- console.log(` ${data.timestamp} | session: ${data.session_id} | branch: ${data.git?.branch || "?"}`);
114
- } catch {
115
- console.log(` ${file} (unreadable)`);
116
- }
117
- }
118
- }
119
-
120
- function latest(): void {
121
- const latestPath = join(process.cwd(), HANDOFF_DIR, "latest.json");
122
- if (!existsSync(latestPath)) {
123
- console.log("No handoffs found");
124
- return;
125
- }
126
-
127
- const data = JSON.parse(readFileSync(latestPath, "utf-8"));
128
- console.log(JSON.stringify(data, null, 2));
129
- }
130
-
131
- function help(): void {
132
- console.log(`
133
- ${HOOK_NAME} - Save session state before context compaction
134
-
135
- Usage: ${HOOK_NAME} <command>
136
-
137
- Commands:
138
- install Install hook to Claude Code settings
139
- uninstall Remove hook from Claude Code settings
140
- status Check if hook is installed
141
- list Show recent handoffs in current directory
142
- latest Show the latest handoff data
143
- help Show this help message
144
-
145
- Handoff files are saved in .claude-handoffs/ (gitignored).
146
- `);
147
- }
148
-
149
- const command = process.argv[2];
150
-
151
- switch (command) {
152
- case "install": install(); break;
153
- case "uninstall": uninstall(); break;
154
- case "status": status(); break;
155
- case "list": list(); break;
156
- case "latest": latest(); break;
157
- case "help":
158
- case "--help":
159
- case "-h": help(); break;
160
- default:
161
- if (!command) {
162
- import("./hook.ts").then((m) => m.run());
163
- } else {
164
- console.error(`Unknown command: ${command}`);
165
- help();
166
- process.exit(1);
167
- }
168
- }
@@ -1,122 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- /**
4
- * Claude Code Hook: precompact
5
- *
6
- * PreCompact hook that saves session state/handoff data before
7
- * context compaction to prevent information loss in long sessions.
8
- *
9
- * Creates timestamped handoff files in .claude-handoffs/ with:
10
- * - Session ID and timestamp
11
- * - Current working directory
12
- * - Git branch and recent changes
13
- * - Task list state (if any)
14
- * - Environment context
15
- */
16
-
17
- import { readFileSync, existsSync, mkdirSync, writeFileSync, appendFileSync } from "fs";
18
- import { execSync } from "child_process";
19
- import { join } from "path";
20
-
21
- interface HookInput {
22
- session_id: string;
23
- cwd: string;
24
- hook_event_name: string;
25
- transcript_summary?: string;
26
- }
27
-
28
- interface HookOutput {
29
- continue?: boolean;
30
- }
31
-
32
- const HANDOFF_DIR = ".claude-handoffs";
33
-
34
- function readStdinJson(): HookInput | null {
35
- try {
36
- const input = readFileSync(0, "utf-8").trim();
37
- if (!input) return null;
38
- return JSON.parse(input);
39
- } catch {
40
- return null;
41
- }
42
- }
43
-
44
- function safeExec(cmd: string, cwd: string): string {
45
- try {
46
- return execSync(cmd, { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
47
- } catch {
48
- return "";
49
- }
50
- }
51
-
52
- function getGitContext(cwd: string): Record<string, string> {
53
- const branch = safeExec("git rev-parse --abbrev-ref HEAD", cwd);
54
- const lastCommit = safeExec("git log -1 --oneline", cwd);
55
- const status = safeExec("git status --short", cwd);
56
- const recentCommits = safeExec("git log -5 --oneline", cwd);
57
-
58
- return { branch, lastCommit, status, recentCommits };
59
- }
60
-
61
- function createHandoff(input: HookInput): void {
62
- const handoffDir = join(input.cwd, HANDOFF_DIR);
63
- mkdirSync(handoffDir, { recursive: true });
64
-
65
- // Ensure it's gitignored
66
- const gitignorePath = join(input.cwd, ".gitignore");
67
- if (existsSync(gitignorePath)) {
68
- const content = readFileSync(gitignorePath, "utf-8");
69
- if (!content.includes(HANDOFF_DIR)) {
70
- appendFileSync(gitignorePath, `\n${HANDOFF_DIR}/\n`);
71
- }
72
- }
73
-
74
- const timestamp = new Date().toISOString();
75
- const gitContext = getGitContext(input.cwd);
76
-
77
- const handoff = {
78
- session_id: input.session_id,
79
- timestamp,
80
- cwd: input.cwd,
81
- event: "PreCompact",
82
- git: gitContext,
83
- summary: input.transcript_summary || null,
84
- };
85
-
86
- // Write timestamped handoff file
87
- const filename = `handoff-${timestamp.replace(/[:.]/g, "-")}.json`;
88
- writeFileSync(join(handoffDir, filename), JSON.stringify(handoff, null, 2));
89
-
90
- // Also write a "latest" file for easy access
91
- writeFileSync(join(handoffDir, "latest.json"), JSON.stringify(handoff, null, 2));
92
-
93
- // Append to log
94
- const logEntry = `[${timestamp}] PreCompact handoff saved (session: ${input.session_id})\n`;
95
- appendFileSync(join(handoffDir, "handoff.log"), logEntry);
96
- }
97
-
98
- function respond(output: HookOutput): void {
99
- console.log(JSON.stringify(output));
100
- }
101
-
102
- export function run(): void {
103
- const input = readStdinJson();
104
-
105
- if (!input) {
106
- respond({ continue: true });
107
- return;
108
- }
109
-
110
- try {
111
- createHandoff(input);
112
- } catch (error) {
113
- const errMsg = error instanceof Error ? error.message : String(error);
114
- console.error(`[hook-precompact] Warning: handoff save failed: ${errMsg}`);
115
- }
116
-
117
- respond({ continue: true });
118
- }
119
-
120
- if (import.meta.main) {
121
- run();
122
- }
@@ -1,25 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ESNext",
4
- "module": "ESNext",
5
- "lib": ["ESNext"],
6
- "moduleResolution": "bundler",
7
- "allowImportingTsExtensions": true,
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "skipLibCheck": true,
11
- "forceConsistentCasingInFileNames": true,
12
- "resolveJsonModule": true,
13
- "isolatedModules": true,
14
- "noEmit": true,
15
- "noUnusedLocals": true,
16
- "noUnusedParameters": true,
17
- "declaration": true,
18
- "declarationMap": true,
19
- "outDir": "./dist",
20
- "rootDir": "./src",
21
- "types": ["bun-types"]
22
- },
23
- "include": ["src/**/*"],
24
- "exclude": ["node_modules", "dist"]
25
- }