@hasna/hooks 0.0.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.
- package/.npmrc.example +2 -0
- package/AGENTS.md +54 -0
- package/CLAUDE.md +70 -0
- package/CONTRIBUTING.md +45 -0
- package/README.md +232 -0
- package/bin/index.js +5171 -0
- package/hooks/hook-agentmessages/CLAUDE.md +79 -0
- package/hooks/hook-agentmessages/LICENSE +21 -0
- package/hooks/hook-agentmessages/README.md +107 -0
- package/hooks/hook-agentmessages/package.json +31 -0
- package/hooks/hook-agentmessages/src/check-messages.ts +151 -0
- package/hooks/hook-agentmessages/src/install.ts +126 -0
- package/hooks/hook-agentmessages/src/session-start.ts +255 -0
- package/hooks/hook-agentmessages/src/uninstall.ts +89 -0
- package/hooks/hook-branchprotect/CLAUDE.md +23 -0
- package/hooks/hook-branchprotect/README.md +25 -0
- package/hooks/hook-branchprotect/package.json +42 -0
- package/hooks/hook-branchprotect/src/cli.ts +126 -0
- package/hooks/hook-branchprotect/src/hook.ts +88 -0
- package/hooks/hook-branchprotect/tsconfig.json +25 -0
- package/hooks/hook-checkbugs/LICENSE +21 -0
- package/hooks/hook-checkbugs/README.md +140 -0
- package/hooks/hook-checkbugs/package.json +58 -0
- package/hooks/hook-checkbugs/src/cli.ts +628 -0
- package/hooks/hook-checkbugs/src/hook.ts +335 -0
- package/hooks/hook-checkbugs/tsconfig.json +15 -0
- package/hooks/hook-checkdocs/README.md +137 -0
- package/hooks/hook-checkdocs/package.json +57 -0
- package/hooks/hook-checkdocs/src/cli.ts +628 -0
- package/hooks/hook-checkdocs/src/hook.ts +310 -0
- package/hooks/hook-checkdocs/tsconfig.json +15 -0
- package/hooks/hook-checkfiles/LICENSE +21 -0
- package/hooks/hook-checkfiles/README.md +141 -0
- package/hooks/hook-checkfiles/package.json +56 -0
- package/hooks/hook-checkfiles/src/cli.ts +545 -0
- package/hooks/hook-checkfiles/src/hook.ts +321 -0
- package/hooks/hook-checkfiles/tsconfig.json +15 -0
- package/hooks/hook-checklint/LICENSE +21 -0
- package/hooks/hook-checklint/README.md +147 -0
- package/hooks/hook-checklint/package.json +57 -0
- package/hooks/hook-checklint/src/cli-patch.ts +32 -0
- package/hooks/hook-checklint/src/cli.ts +667 -0
- package/hooks/hook-checklint/src/hook.ts +473 -0
- package/hooks/hook-checklint/tsconfig.json +15 -0
- package/hooks/hook-checkpoint/CLAUDE.md +23 -0
- package/hooks/hook-checkpoint/README.md +37 -0
- package/hooks/hook-checkpoint/package.json +58 -0
- package/hooks/hook-checkpoint/src/cli.ts +191 -0
- package/hooks/hook-checkpoint/src/hook.ts +207 -0
- package/hooks/hook-checkpoint/tsconfig.json +25 -0
- package/hooks/hook-checksecurity/LICENSE +21 -0
- package/hooks/hook-checksecurity/README.md +158 -0
- package/hooks/hook-checksecurity/package.json +57 -0
- package/hooks/hook-checksecurity/src/cli.ts +601 -0
- package/hooks/hook-checksecurity/src/hook.ts +334 -0
- package/hooks/hook-checksecurity/tsconfig.json +15 -0
- package/hooks/hook-checktasks/README.md +144 -0
- package/hooks/hook-checktasks/package.json +55 -0
- package/hooks/hook-checktasks/src/cli.ts +578 -0
- package/hooks/hook-checktasks/src/hook.ts +308 -0
- package/hooks/hook-checktasks/tsconfig.json +20 -0
- package/hooks/hook-checktests/LICENSE +21 -0
- package/hooks/hook-checktests/README.md +137 -0
- package/hooks/hook-checktests/package.json +57 -0
- package/hooks/hook-checktests/src/cli.ts +627 -0
- package/hooks/hook-checktests/src/hook.ts +334 -0
- package/hooks/hook-checktests/tsconfig.json +15 -0
- package/hooks/hook-contextrefresh/CLAUDE.md +23 -0
- package/hooks/hook-contextrefresh/README.md +42 -0
- package/hooks/hook-contextrefresh/package.json +42 -0
- package/hooks/hook-contextrefresh/src/cli.ts +152 -0
- package/hooks/hook-contextrefresh/src/hook.ts +148 -0
- package/hooks/hook-contextrefresh/tsconfig.json +25 -0
- package/hooks/hook-gitguard/CLAUDE.md +22 -0
- package/hooks/hook-gitguard/README.md +30 -0
- package/hooks/hook-gitguard/package.json +57 -0
- package/hooks/hook-gitguard/src/cli.ts +159 -0
- package/hooks/hook-gitguard/src/hook.ts +129 -0
- package/hooks/hook-gitguard/tsconfig.json +25 -0
- package/hooks/hook-packageage/CLAUDE.md +23 -0
- package/hooks/hook-packageage/README.md +33 -0
- package/hooks/hook-packageage/package.json +42 -0
- package/hooks/hook-packageage/src/cli.ts +165 -0
- package/hooks/hook-packageage/src/hook.ts +177 -0
- package/hooks/hook-packageage/tsconfig.json +25 -0
- package/hooks/hook-phonenotify/CLAUDE.md +25 -0
- package/hooks/hook-phonenotify/README.md +44 -0
- package/hooks/hook-phonenotify/package.json +42 -0
- package/hooks/hook-phonenotify/src/cli.ts +196 -0
- package/hooks/hook-phonenotify/src/hook.ts +139 -0
- package/hooks/hook-phonenotify/tsconfig.json +25 -0
- package/hooks/hook-precompact/CLAUDE.md +23 -0
- package/hooks/hook-precompact/README.md +36 -0
- package/hooks/hook-precompact/package.json +42 -0
- package/hooks/hook-precompact/src/cli.ts +168 -0
- package/hooks/hook-precompact/src/hook.ts +122 -0
- package/hooks/hook-precompact/tsconfig.json +25 -0
- package/package.json +61 -0
- package/src/cli/components/App.tsx +191 -0
- package/src/cli/components/CategorySelect.tsx +37 -0
- package/src/cli/components/DataTable.tsx +133 -0
- package/src/cli/components/Header.tsx +18 -0
- package/src/cli/components/HookSelect.tsx +29 -0
- package/src/cli/components/InstallProgress.tsx +105 -0
- package/src/cli/components/SearchView.tsx +86 -0
- package/src/cli/index.tsx +218 -0
- package/src/index.ts +31 -0
- package/src/lib/installer.ts +288 -0
- package/src/lib/registry.ts +205 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
Instructions for working with hook-agentmessages codebase.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
**hook-agentmessages** is a Claude Code hook package that integrates with service-message for automatic agent registration and message notifications.
|
|
8
|
+
|
|
9
|
+
- **Package:** `@hasnaxyz/hook-agentmessages`
|
|
10
|
+
- **CLI command:** `hook-agentmessages`
|
|
11
|
+
- **Hooks location:** `~/.claude/settings.json`
|
|
12
|
+
|
|
13
|
+
## What it Does
|
|
14
|
+
|
|
15
|
+
### SessionStart Hook (`src/session-start.ts`)
|
|
16
|
+
|
|
17
|
+
Runs when Claude Code session starts:
|
|
18
|
+
1. Auto-registers agent with ID format: `{type}-{shortid}` (claude-abc123, codex-xyz789)
|
|
19
|
+
2. Registers project from `$CLAUDE_PROJECT_DIR`
|
|
20
|
+
3. Creates session linked to Claude session ID
|
|
21
|
+
4. Exports env vars: `SMSG_AGENT_ID`, `SMSG_SESSION_ID`, `SMSG_PROJECT_ID`
|
|
22
|
+
|
|
23
|
+
### Stop Hook (`src/check-messages.ts`)
|
|
24
|
+
|
|
25
|
+
Runs after Claude finishes each response:
|
|
26
|
+
1. Checks for unread messages (efficient, no polling)
|
|
27
|
+
2. Returns notification if unread messages exist
|
|
28
|
+
3. 5s timeout to avoid blocking
|
|
29
|
+
|
|
30
|
+
## Development
|
|
31
|
+
|
|
32
|
+
### Key Files
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
bin/cli.ts # CLI entry point
|
|
36
|
+
src/
|
|
37
|
+
├── session-start.ts # SessionStart hook
|
|
38
|
+
├── check-messages.ts # Stop hook
|
|
39
|
+
├── install.ts # Install hooks to Claude Code
|
|
40
|
+
└── uninstall.ts # Remove hooks
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Hook Input Format
|
|
44
|
+
|
|
45
|
+
Hooks receive JSON via stdin:
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"session_id": "abc123",
|
|
49
|
+
"cwd": "/path/to/project",
|
|
50
|
+
"model": "claude-opus-4-5-20251101",
|
|
51
|
+
"hook_event_name": "SessionStart"
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Hook Output Format
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"decision": "continue",
|
|
60
|
+
"message": "Optional message shown to Claude"
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Testing
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Test session-start hook
|
|
68
|
+
echo '{"session_id":"test","cwd":"/tmp"}' | bun run src/session-start.ts
|
|
69
|
+
|
|
70
|
+
# Test check-messages hook
|
|
71
|
+
SMSG_AGENT_ID=claude-1 bun run src/check-messages.ts
|
|
72
|
+
|
|
73
|
+
# Install/uninstall
|
|
74
|
+
bun run install-hook
|
|
75
|
+
bun run uninstall-hook
|
|
76
|
+
|
|
77
|
+
# Check status
|
|
78
|
+
hook-agentmessages status
|
|
79
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Hasna
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# hook-agentmessages
|
|
2
|
+
|
|
3
|
+
Claude Code hook for service-message integration.
|
|
4
|
+
|
|
5
|
+
Automatically registers projects and sessions when Claude Code starts, and checks for unread messages after each response.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Install globally
|
|
11
|
+
bun install -g @hasnaxyz/hook-agentmessages
|
|
12
|
+
|
|
13
|
+
# Install hooks into Claude Code
|
|
14
|
+
hook-agentmessages install
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Prerequisites
|
|
18
|
+
|
|
19
|
+
Before using this hook, you must initialize an agent with service-message:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
service-message init
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
This hook does NOT auto-generate agents. It only works if an agent is already configured.
|
|
26
|
+
|
|
27
|
+
## What it does
|
|
28
|
+
|
|
29
|
+
### SessionStart Hook
|
|
30
|
+
|
|
31
|
+
When a Claude Code session starts:
|
|
32
|
+
|
|
33
|
+
1. **Reads existing agent** from service-message config
|
|
34
|
+
2. **Registers the project** from `$CLAUDE_PROJECT_DIR` (folder name becomes project ID)
|
|
35
|
+
3. **Starts a session** linked to the Claude session ID
|
|
36
|
+
4. **Exports environment variables** for the session:
|
|
37
|
+
- `SMSG_AGENT_ID`
|
|
38
|
+
- `SMSG_SESSION_ID`
|
|
39
|
+
- `SMSG_PROJECT_ID`
|
|
40
|
+
|
|
41
|
+
### Stop Hook
|
|
42
|
+
|
|
43
|
+
After Claude finishes each response:
|
|
44
|
+
|
|
45
|
+
1. Checks for unread messages (efficient, no polling)
|
|
46
|
+
2. Notifies Claude if there are pending messages
|
|
47
|
+
3. Runs with 5s timeout to avoid blocking
|
|
48
|
+
|
|
49
|
+
## CLI Commands
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
hook-agentmessages install # Install hooks
|
|
53
|
+
hook-agentmessages uninstall # Remove hooks
|
|
54
|
+
hook-agentmessages status # Check installation status
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Configuration
|
|
58
|
+
|
|
59
|
+
Hooks are stored in `~/.claude/settings.json`:
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"hooks": {
|
|
64
|
+
"SessionStart": [
|
|
65
|
+
{
|
|
66
|
+
"hooks": [
|
|
67
|
+
{
|
|
68
|
+
"type": "command",
|
|
69
|
+
"command": "bun /path/to/hook-agentmessages/src/session-start.ts",
|
|
70
|
+
"timeout": 10
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
"Stop": [
|
|
76
|
+
{
|
|
77
|
+
"hooks": [
|
|
78
|
+
{
|
|
79
|
+
"type": "command",
|
|
80
|
+
"command": "bun /path/to/hook-agentmessages/src/check-messages.ts",
|
|
81
|
+
"timeout": 5
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Efficiency
|
|
91
|
+
|
|
92
|
+
The hook is designed to be lightweight:
|
|
93
|
+
|
|
94
|
+
- **SessionStart**: Runs once per session (10s timeout)
|
|
95
|
+
- **Stop**: Only checks for messages after Claude responds (5s timeout)
|
|
96
|
+
- **No polling**: Doesn't continuously monitor, only checks on events
|
|
97
|
+
- **Fast file reads**: Uses Bun's native file APIs
|
|
98
|
+
|
|
99
|
+
## Requirements
|
|
100
|
+
|
|
101
|
+
- Bun runtime
|
|
102
|
+
- service-message (`@hasnaxyz/service-message`) installed and initialized
|
|
103
|
+
- Claude Code with hooks support
|
|
104
|
+
|
|
105
|
+
## License
|
|
106
|
+
|
|
107
|
+
MIT
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hasnaxyz/hook-agentmessages",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Claude Code hook for service-message integration - auto-registers agents, sessions, and checks messages",
|
|
6
|
+
"bin": {
|
|
7
|
+
"hook-agentmessages": "./bin/cli.ts"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/hasnaxyz/hook-agentmessages.git"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/hasnaxyz/hook-agentmessages#readme",
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "restricted",
|
|
16
|
+
"registry": "https://registry.npmjs.org/"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"session-start": "bun run src/session-start.ts",
|
|
20
|
+
"check-messages": "bun run src/check-messages.ts",
|
|
21
|
+
"install-hook": "bun run src/install.ts",
|
|
22
|
+
"uninstall-hook": "bun run src/uninstall.ts"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/bun": "^1.3.8",
|
|
27
|
+
"typescript": "^5.7.2"
|
|
28
|
+
},
|
|
29
|
+
"author": "Hasna",
|
|
30
|
+
"license": "MIT"
|
|
31
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Stop hook for service-message
|
|
4
|
+
*
|
|
5
|
+
* Runs when Claude finishes a response. Checks for unread messages
|
|
6
|
+
* and notifies Claude if there are any pending messages.
|
|
7
|
+
*
|
|
8
|
+
* This is efficient because it only runs after Claude completes a turn,
|
|
9
|
+
* not continuously polling.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { homedir } from 'os';
|
|
13
|
+
import { join } from 'path';
|
|
14
|
+
import { readdir } from 'fs/promises';
|
|
15
|
+
|
|
16
|
+
interface HookInput {
|
|
17
|
+
session_id: string;
|
|
18
|
+
cwd: string;
|
|
19
|
+
hook_event_name: string;
|
|
20
|
+
stop_hook_active?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface Message {
|
|
24
|
+
id: string;
|
|
25
|
+
timestamp: number;
|
|
26
|
+
from: string;
|
|
27
|
+
to: string;
|
|
28
|
+
project: string;
|
|
29
|
+
subject: string;
|
|
30
|
+
body: string;
|
|
31
|
+
read?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const SERVICE_DIR = join(homedir(), '.service', 'service-message');
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Sanitize ID to prevent path traversal attacks
|
|
38
|
+
*/
|
|
39
|
+
function sanitizeId(id: string): string | null {
|
|
40
|
+
if (!id || typeof id !== 'string') return null;
|
|
41
|
+
// Only allow alphanumeric, dash, underscore
|
|
42
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(id)) return null;
|
|
43
|
+
// Reject path traversal attempts
|
|
44
|
+
if (id.includes('..') || id.includes('/') || id.includes('\\')) return null;
|
|
45
|
+
return id;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function readJson<T>(path: string): Promise<T | null> {
|
|
49
|
+
try {
|
|
50
|
+
const file = Bun.file(path);
|
|
51
|
+
if (await file.exists()) {
|
|
52
|
+
return await file.json();
|
|
53
|
+
}
|
|
54
|
+
} catch {}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function getUnreadMessages(agentId: string, projectId?: string): Promise<Message[]> {
|
|
59
|
+
const messages: Message[] = [];
|
|
60
|
+
const messagesDir = join(SERVICE_DIR, 'messages');
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const rawProjects = projectId ? [projectId] : await readdir(messagesDir);
|
|
64
|
+
// Sanitize all project names to prevent path traversal
|
|
65
|
+
const projects = rawProjects.map(p => sanitizeId(p)).filter((p): p is string => p !== null);
|
|
66
|
+
|
|
67
|
+
for (const proj of projects) {
|
|
68
|
+
// Check inbox
|
|
69
|
+
const inboxDir = join(messagesDir, proj, 'inbox', agentId);
|
|
70
|
+
try {
|
|
71
|
+
const files = await readdir(inboxDir);
|
|
72
|
+
for (const file of files) {
|
|
73
|
+
if (!file.endsWith('.json')) continue;
|
|
74
|
+
// Sanitize filename to prevent path traversal
|
|
75
|
+
const safeFile = sanitizeId(file.replace('.json', ''));
|
|
76
|
+
if (!safeFile) continue;
|
|
77
|
+
const msg = await readJson<Message>(join(inboxDir, `${safeFile}.json`));
|
|
78
|
+
if (msg && !msg.read) {
|
|
79
|
+
messages.push(msg);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} catch {}
|
|
83
|
+
|
|
84
|
+
// Check broadcast
|
|
85
|
+
const broadcastDir = join(messagesDir, proj, 'broadcast');
|
|
86
|
+
try {
|
|
87
|
+
const files = await readdir(broadcastDir);
|
|
88
|
+
for (const file of files) {
|
|
89
|
+
if (!file.endsWith('.json')) continue;
|
|
90
|
+
// Sanitize filename to prevent path traversal
|
|
91
|
+
const safeFile = sanitizeId(file.replace('.json', ''));
|
|
92
|
+
if (!safeFile) continue;
|
|
93
|
+
const msg = await readJson<Message>(join(broadcastDir, `${safeFile}.json`));
|
|
94
|
+
if (msg && !msg.read && msg.from !== agentId) {
|
|
95
|
+
messages.push(msg);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} catch {}
|
|
99
|
+
}
|
|
100
|
+
} catch {}
|
|
101
|
+
|
|
102
|
+
return messages.sort((a, b) => b.timestamp - a.timestamp);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function main() {
|
|
106
|
+
// Get agent ID from environment (set by session-start hook)
|
|
107
|
+
const rawAgentId = process.env.SMSG_AGENT_ID;
|
|
108
|
+
const rawProjectId = process.env.SMSG_PROJECT_ID;
|
|
109
|
+
|
|
110
|
+
// Sanitize IDs to prevent path traversal
|
|
111
|
+
const agentId = rawAgentId ? sanitizeId(rawAgentId) : null;
|
|
112
|
+
const projectId = rawProjectId ? sanitizeId(rawProjectId) : undefined;
|
|
113
|
+
|
|
114
|
+
if (!agentId) {
|
|
115
|
+
// Agent not configured or invalid, skip silently
|
|
116
|
+
console.log(JSON.stringify({ continue: true }));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Check for unread messages
|
|
121
|
+
const unreadMessages = await getUnreadMessages(agentId, projectId);
|
|
122
|
+
|
|
123
|
+
if (unreadMessages.length === 0) {
|
|
124
|
+
// No messages, continue normally
|
|
125
|
+
console.log(JSON.stringify({ continue: true }));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Format message summary in a friendly way
|
|
130
|
+
const msgList = unreadMessages.slice(0, 3).map(msg => {
|
|
131
|
+
const time = new Date(msg.timestamp).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
|
|
132
|
+
const preview = msg.body.slice(0, 60).replace(/\n/g, ' ');
|
|
133
|
+
return `📨 **${msg.subject}** from \`${msg.from}\` (${time})\n ${preview}${msg.body.length > 60 ? '...' : ''}`;
|
|
134
|
+
}).join('\n\n');
|
|
135
|
+
|
|
136
|
+
const moreNote = unreadMessages.length > 3
|
|
137
|
+
? `\n\n_...and ${unreadMessages.length - 3} more message(s)_`
|
|
138
|
+
: '';
|
|
139
|
+
|
|
140
|
+
// Inject message in a friendly, readable format
|
|
141
|
+
console.log(JSON.stringify({
|
|
142
|
+
continue: true,
|
|
143
|
+
stopReason: `📬 **You have ${unreadMessages.length} unread message(s):**\n\n${msgList}${moreNote}\n\n💡 Use \`service-message inbox\` to see all messages or \`service-message read <id>\` to read one.`
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
main().catch(() => {
|
|
148
|
+
// Don't block on errors
|
|
149
|
+
console.log(JSON.stringify({ continue: true }));
|
|
150
|
+
process.exit(0);
|
|
151
|
+
});
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Install hook-agentmessages into Claude Code settings
|
|
4
|
+
*
|
|
5
|
+
* Adds hooks to ~/.claude/settings.json:
|
|
6
|
+
* - SessionStart: Auto-register agent, project, session
|
|
7
|
+
* - Stop: Check for unread messages
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { homedir } from 'os';
|
|
11
|
+
import { join } from 'path';
|
|
12
|
+
|
|
13
|
+
const CLAUDE_SETTINGS_DIR = join(homedir(), '.claude');
|
|
14
|
+
const CLAUDE_SETTINGS_FILE = join(CLAUDE_SETTINGS_DIR, 'settings.json');
|
|
15
|
+
const HOOK_DIR = import.meta.dir.replace('/src', '');
|
|
16
|
+
|
|
17
|
+
interface HookConfig {
|
|
18
|
+
type: 'command';
|
|
19
|
+
command: string;
|
|
20
|
+
timeout?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface HookMatcher {
|
|
24
|
+
matcher?: string;
|
|
25
|
+
hooks: HookConfig[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface Settings {
|
|
29
|
+
hooks?: {
|
|
30
|
+
SessionStart?: HookMatcher[];
|
|
31
|
+
Stop?: HookMatcher[];
|
|
32
|
+
[key: string]: HookMatcher[] | undefined;
|
|
33
|
+
};
|
|
34
|
+
[key: string]: unknown;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function readSettings(): Promise<Settings> {
|
|
38
|
+
try {
|
|
39
|
+
const file = Bun.file(CLAUDE_SETTINGS_FILE);
|
|
40
|
+
if (await file.exists()) {
|
|
41
|
+
return await file.json();
|
|
42
|
+
}
|
|
43
|
+
} catch {}
|
|
44
|
+
return {};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function writeSettings(settings: Settings): Promise<void> {
|
|
48
|
+
// Ensure .claude directory exists
|
|
49
|
+
await Bun.write(join(CLAUDE_SETTINGS_DIR, '.gitkeep'), '');
|
|
50
|
+
await Bun.write(CLAUDE_SETTINGS_FILE, JSON.stringify(settings, null, 2));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function findHookIndex(hooks: HookMatcher[], command: string): number {
|
|
54
|
+
return hooks.findIndex(h =>
|
|
55
|
+
h.hooks.some(hook => hook.command.includes('hook-agentmessages'))
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function main() {
|
|
60
|
+
console.log('Installing hook-agentmessages into Claude Code...\n');
|
|
61
|
+
|
|
62
|
+
const settings = await readSettings();
|
|
63
|
+
|
|
64
|
+
// Initialize hooks object if not exists
|
|
65
|
+
if (!settings.hooks) {
|
|
66
|
+
settings.hooks = {};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// SessionStart hook
|
|
70
|
+
const sessionStartHook: HookMatcher = {
|
|
71
|
+
hooks: [
|
|
72
|
+
{
|
|
73
|
+
type: 'command',
|
|
74
|
+
command: `bun ${join(HOOK_DIR, 'src/session-start.ts')}`,
|
|
75
|
+
timeout: 10,
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
if (!settings.hooks.SessionStart) {
|
|
81
|
+
settings.hooks.SessionStart = [];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Remove existing hook-agentmessages hooks
|
|
85
|
+
const existingSessionStartIdx = findHookIndex(settings.hooks.SessionStart, 'hook-agentmessages');
|
|
86
|
+
if (existingSessionStartIdx >= 0) {
|
|
87
|
+
settings.hooks.SessionStart.splice(existingSessionStartIdx, 1);
|
|
88
|
+
}
|
|
89
|
+
settings.hooks.SessionStart.push(sessionStartHook);
|
|
90
|
+
|
|
91
|
+
// Stop hook (check messages after each response)
|
|
92
|
+
const stopHook: HookMatcher = {
|
|
93
|
+
hooks: [
|
|
94
|
+
{
|
|
95
|
+
type: 'command',
|
|
96
|
+
command: `bun ${join(HOOK_DIR, 'src/check-messages.ts')}`,
|
|
97
|
+
timeout: 5,
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
if (!settings.hooks.Stop) {
|
|
103
|
+
settings.hooks.Stop = [];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const existingStopIdx = findHookIndex(settings.hooks.Stop, 'hook-agentmessages');
|
|
107
|
+
if (existingStopIdx >= 0) {
|
|
108
|
+
settings.hooks.Stop.splice(existingStopIdx, 1);
|
|
109
|
+
}
|
|
110
|
+
settings.hooks.Stop.push(stopHook);
|
|
111
|
+
|
|
112
|
+
// Write updated settings
|
|
113
|
+
await writeSettings(settings);
|
|
114
|
+
|
|
115
|
+
console.log('Hooks installed successfully!\n');
|
|
116
|
+
console.log('Installed hooks:');
|
|
117
|
+
console.log(' - SessionStart: Auto-registers agent, project, and session');
|
|
118
|
+
console.log(' - Stop: Checks for unread messages after each response\n');
|
|
119
|
+
console.log(`Settings file: ${CLAUDE_SETTINGS_FILE}`);
|
|
120
|
+
console.log('\nRestart Claude Code for hooks to take effect.');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
main().catch((err) => {
|
|
124
|
+
console.error('Installation failed:', err.message);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
});
|