claudehq 1.0.0
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/README.md +88 -0
- package/bin/cli.js +56 -0
- package/hooks/settings.example.json +58 -0
- package/hooks/tasks-board-hook.sh +166 -0
- package/lib/server.js +8670 -0
- package/package.json +42 -0
- package/scripts/setup.js +366 -0
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Claude HQ
|
|
2
|
+
|
|
3
|
+
A real-time command center for all your Claude Code sessions.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Session Discovery**: Automatically discovers all running Claude Code sessions
|
|
8
|
+
- **Real-Time Activity**: Live stream of tool usage, prompts, and responses
|
|
9
|
+
- **Task Management**: View tasks organized by status (Pending, In Progress, Completed)
|
|
10
|
+
- **Linear-Inspired UI**: Clean, modern interface with dark/light themes
|
|
11
|
+
- **Zero Config**: One command setup with automatic hook installation
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Install hooks (one time)
|
|
17
|
+
npx claudehq setup
|
|
18
|
+
|
|
19
|
+
# Start the dashboard
|
|
20
|
+
npx claudehq
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Open **http://localhost:3456** - done!
|
|
24
|
+
|
|
25
|
+
## Requirements
|
|
26
|
+
|
|
27
|
+
- **Node.js** 18+
|
|
28
|
+
- **jq** - `brew install jq` (macOS) or `apt install jq` (Linux)
|
|
29
|
+
|
|
30
|
+
## Commands
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx claudehq # Start server
|
|
34
|
+
npx claudehq setup # Install Claude Code hooks
|
|
35
|
+
npx claudehq status # Check installation
|
|
36
|
+
npx claudehq uninstall # Remove hooks
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Or install globally:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm i -g claudehq
|
|
43
|
+
chq setup
|
|
44
|
+
chq
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## How It Works
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
Claude Code ──▶ Hook Script ──▶ Events File
|
|
51
|
+
│ │
|
|
52
|
+
▼ ▼
|
|
53
|
+
┌─────────────────────────┐
|
|
54
|
+
│ Claude HQ Server │
|
|
55
|
+
└───────────┬─────────────┘
|
|
56
|
+
│ SSE
|
|
57
|
+
▼
|
|
58
|
+
┌─────────────────────────┐
|
|
59
|
+
│ Web Dashboard │
|
|
60
|
+
│ • Sessions & Status │
|
|
61
|
+
│ • Task Board │
|
|
62
|
+
│ • Live Activity Feed │
|
|
63
|
+
└─────────────────────────┘
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Configuration
|
|
67
|
+
|
|
68
|
+
| Variable | Default | Description |
|
|
69
|
+
|----------|---------|-------------|
|
|
70
|
+
| `PORT` | `3456` | Server port |
|
|
71
|
+
| `TASKS_BOARD_DATA_DIR` | `~/.claude/tasks-board` | Data directory |
|
|
72
|
+
|
|
73
|
+
## Troubleshooting
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# Check status
|
|
77
|
+
npx claudehq status
|
|
78
|
+
|
|
79
|
+
# Test hook manually
|
|
80
|
+
echo '{"hook_event_name":"PreToolUse","session_id":"test"}' | ~/.claude/hooks/tasks-board-hook.sh
|
|
81
|
+
|
|
82
|
+
# View events
|
|
83
|
+
tail ~/.claude/tasks-board/events.jsonl
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { program } = require('commander');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { version } = require('../package.json');
|
|
6
|
+
|
|
7
|
+
program
|
|
8
|
+
.name('claudehq')
|
|
9
|
+
.description('Claude HQ - A beautiful command center for Claude Code')
|
|
10
|
+
.version(version);
|
|
11
|
+
|
|
12
|
+
program
|
|
13
|
+
.command('start')
|
|
14
|
+
.description('Start the tasks board server')
|
|
15
|
+
.option('-p, --port <port>', 'Port to run on', '3456')
|
|
16
|
+
.option('--no-open', 'Don\'t open browser automatically')
|
|
17
|
+
.action((options) => {
|
|
18
|
+
process.env.PORT = options.port;
|
|
19
|
+
process.env.NO_OPEN = options.open ? '' : '1';
|
|
20
|
+
require('../lib/server.js');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
program
|
|
24
|
+
.command('setup')
|
|
25
|
+
.description('Install Claude Code hooks (run this first!)')
|
|
26
|
+
.option('--force', 'Overwrite existing hook script')
|
|
27
|
+
.action(async (options) => {
|
|
28
|
+
const setup = require('../scripts/setup.js');
|
|
29
|
+
await setup.install(options);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
program
|
|
33
|
+
.command('uninstall')
|
|
34
|
+
.description('Remove Claude Code hooks')
|
|
35
|
+
.action(async () => {
|
|
36
|
+
const setup = require('../scripts/setup.js');
|
|
37
|
+
await setup.uninstall();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
program
|
|
41
|
+
.command('status')
|
|
42
|
+
.description('Check if hooks are installed and server is running')
|
|
43
|
+
.action(async () => {
|
|
44
|
+
const setup = require('../scripts/setup.js');
|
|
45
|
+
await setup.status();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Default command is start
|
|
49
|
+
program
|
|
50
|
+
.action(() => {
|
|
51
|
+
// If no command specified, run start
|
|
52
|
+
process.env.PORT = process.env.PORT || '3456';
|
|
53
|
+
require('../lib/server.js');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
program.parse();
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PreToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "~/.claude/hooks/tasks-board-hook.sh"
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"PostToolUse": [
|
|
15
|
+
{
|
|
16
|
+
"matcher": "",
|
|
17
|
+
"hooks": [
|
|
18
|
+
{
|
|
19
|
+
"type": "command",
|
|
20
|
+
"command": "~/.claude/hooks/tasks-board-hook.sh"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"Stop": [
|
|
26
|
+
{
|
|
27
|
+
"matcher": "",
|
|
28
|
+
"hooks": [
|
|
29
|
+
{
|
|
30
|
+
"type": "command",
|
|
31
|
+
"command": "~/.claude/hooks/tasks-board-hook.sh"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"UserPromptSubmit": [
|
|
37
|
+
{
|
|
38
|
+
"hooks": [
|
|
39
|
+
{
|
|
40
|
+
"type": "command",
|
|
41
|
+
"command": "~/.claude/hooks/tasks-board-hook.sh"
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"SubagentStop": [
|
|
47
|
+
{
|
|
48
|
+
"matcher": "",
|
|
49
|
+
"hooks": [
|
|
50
|
+
{
|
|
51
|
+
"type": "command",
|
|
52
|
+
"command": "~/.claude/hooks/tasks-board-hook.sh"
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# tasks-board-hook.sh - Captures Claude Code events for Tasks Board
|
|
3
|
+
#
|
|
4
|
+
# Installation:
|
|
5
|
+
# 1. Copy this file to ~/.claude/hooks/tasks-board-hook.sh
|
|
6
|
+
# 2. Make it executable: chmod +x ~/.claude/hooks/tasks-board-hook.sh
|
|
7
|
+
# 3. Add hooks to ~/.claude/settings.json (see README)
|
|
8
|
+
#
|
|
9
|
+
# Environment variables:
|
|
10
|
+
# - TASKS_BOARD_DATA_DIR: Directory for events.jsonl (default: ~/.claude/tasks-board)
|
|
11
|
+
# - TASKS_BOARD_SERVER: Server URL for instant delivery (default: http://localhost:3456)
|
|
12
|
+
# - TASKS_BOARD_NOTIFY: Enable HTTP notification (default: true)
|
|
13
|
+
|
|
14
|
+
set -e
|
|
15
|
+
|
|
16
|
+
# Configuration
|
|
17
|
+
DATA_DIR="${TASKS_BOARD_DATA_DIR:-$HOME/.claude/tasks-board}"
|
|
18
|
+
EVENTS_FILE="${DATA_DIR}/events.jsonl"
|
|
19
|
+
SERVER_URL="${TASKS_BOARD_SERVER:-http://localhost:3456}"
|
|
20
|
+
ENABLE_NOTIFY="${TASKS_BOARD_NOTIFY:-true}"
|
|
21
|
+
|
|
22
|
+
# Ensure data directory exists
|
|
23
|
+
mkdir -p "$(dirname "$EVENTS_FILE")"
|
|
24
|
+
|
|
25
|
+
# PATH setup for tools (jq, curl)
|
|
26
|
+
KNOWN_PATHS=("/opt/homebrew/bin" "/usr/local/bin" "$HOME/.local/bin" "/usr/bin" "/bin")
|
|
27
|
+
for dir in "${KNOWN_PATHS[@]}"; do
|
|
28
|
+
[ -d "$dir" ] && export PATH="$dir:$PATH"
|
|
29
|
+
done
|
|
30
|
+
|
|
31
|
+
# Find jq (required)
|
|
32
|
+
JQ=$(command -v jq 2>/dev/null) || {
|
|
33
|
+
echo "tasks-board-hook: ERROR - jq not found" >&2
|
|
34
|
+
exit 1
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# Find curl (optional)
|
|
38
|
+
CURL=$(command -v curl 2>/dev/null) || CURL=""
|
|
39
|
+
|
|
40
|
+
# Read input from stdin
|
|
41
|
+
input=$(cat)
|
|
42
|
+
|
|
43
|
+
# Extract common fields
|
|
44
|
+
hook_event_name=$("$JQ" -r '.hook_event_name // "unknown"' <<< "$input")
|
|
45
|
+
session_id=$("$JQ" -r '.session_id // "unknown"' <<< "$input")
|
|
46
|
+
cwd=$("$JQ" -r '.cwd // ""' <<< "$input")
|
|
47
|
+
|
|
48
|
+
# Generate timestamp (cross-platform)
|
|
49
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
50
|
+
timestamp=$(perl -MTime::HiRes=time -e 'printf "%.0f", time * 1000' 2>/dev/null || echo $(($(date +%s) * 1000)))
|
|
51
|
+
else
|
|
52
|
+
timestamp=$(($(date +%s) * 1000 + 10#$(date +%N | cut -c1-3)))
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
event_id="${session_id}-${timestamp}-${RANDOM}"
|
|
56
|
+
|
|
57
|
+
# Map event names to types
|
|
58
|
+
case "$hook_event_name" in
|
|
59
|
+
PreToolUse) event_type="pre_tool_use" ;;
|
|
60
|
+
PostToolUse) event_type="post_tool_use" ;;
|
|
61
|
+
Stop) event_type="stop" ;;
|
|
62
|
+
SubagentStop) event_type="subagent_stop" ;;
|
|
63
|
+
SessionStart) event_type="session_start" ;;
|
|
64
|
+
SessionEnd) event_type="session_end" ;;
|
|
65
|
+
UserPromptSubmit) event_type="user_prompt_submit" ;;
|
|
66
|
+
Notification) event_type="notification" ;;
|
|
67
|
+
PreCompact) event_type="pre_compact" ;;
|
|
68
|
+
*) event_type="unknown" ;;
|
|
69
|
+
esac
|
|
70
|
+
|
|
71
|
+
# Build event JSON based on type
|
|
72
|
+
case "$event_type" in
|
|
73
|
+
pre_tool_use)
|
|
74
|
+
tool_name=$("$JQ" -r '.tool_name // ""' <<< "$input")
|
|
75
|
+
tool_input=$("$JQ" -c '.tool_input // {}' <<< "$input")
|
|
76
|
+
tool_use_id=$("$JQ" -r '.tool_use_id // ""' <<< "$input")
|
|
77
|
+
event=$("$JQ" -n -c \
|
|
78
|
+
--arg id "$event_id" \
|
|
79
|
+
--argjson ts "$timestamp" \
|
|
80
|
+
--arg type "$event_type" \
|
|
81
|
+
--arg sid "$session_id" \
|
|
82
|
+
--arg cwd "$cwd" \
|
|
83
|
+
--arg tool "$tool_name" \
|
|
84
|
+
--argjson input "$tool_input" \
|
|
85
|
+
--arg tuid "$tool_use_id" \
|
|
86
|
+
'{id:$id,timestamp:$ts,type:$type,sessionId:$sid,cwd:$cwd,tool:$tool,toolInput:$input,toolUseId:$tuid}')
|
|
87
|
+
;;
|
|
88
|
+
post_tool_use)
|
|
89
|
+
tool_name=$("$JQ" -r '.tool_name // ""' <<< "$input")
|
|
90
|
+
tool_input=$("$JQ" -c '.tool_input // {}' <<< "$input")
|
|
91
|
+
tool_use_id=$("$JQ" -r '.tool_use_id // ""' <<< "$input")
|
|
92
|
+
# Truncate tool response to avoid huge events
|
|
93
|
+
tool_response=$("$JQ" -c 'if .tool_response | type == "string" and (. | length) > 500 then .tool_response[:500] + "..." else .tool_response // null end' <<< "$input")
|
|
94
|
+
event=$("$JQ" -n -c \
|
|
95
|
+
--arg id "$event_id" \
|
|
96
|
+
--argjson ts "$timestamp" \
|
|
97
|
+
--arg type "$event_type" \
|
|
98
|
+
--arg sid "$session_id" \
|
|
99
|
+
--arg cwd "$cwd" \
|
|
100
|
+
--arg tool "$tool_name" \
|
|
101
|
+
--argjson input "$tool_input" \
|
|
102
|
+
--argjson response "$tool_response" \
|
|
103
|
+
--arg tuid "$tool_use_id" \
|
|
104
|
+
--argjson success true \
|
|
105
|
+
'{id:$id,timestamp:$ts,type:$type,sessionId:$sid,cwd:$cwd,tool:$tool,toolInput:$input,toolResponse:$response,toolUseId:$tuid,success:$success}')
|
|
106
|
+
;;
|
|
107
|
+
stop)
|
|
108
|
+
# Truncate response text
|
|
109
|
+
response=$("$JQ" -r 'if .response | type == "string" and (. | length) > 1000 then .response[:1000] + "..." else .response // "" end' <<< "$input")
|
|
110
|
+
event=$("$JQ" -n -c \
|
|
111
|
+
--arg id "$event_id" \
|
|
112
|
+
--argjson ts "$timestamp" \
|
|
113
|
+
--arg type "$event_type" \
|
|
114
|
+
--arg sid "$session_id" \
|
|
115
|
+
--arg cwd "$cwd" \
|
|
116
|
+
--arg response "$response" \
|
|
117
|
+
'{id:$id,timestamp:$ts,type:$type,sessionId:$sid,cwd:$cwd,response:$response}')
|
|
118
|
+
;;
|
|
119
|
+
user_prompt_submit)
|
|
120
|
+
# Truncate prompt text
|
|
121
|
+
prompt=$("$JQ" -r 'if .prompt | type == "string" and (. | length) > 500 then .prompt[:500] + "..." else .prompt // "" end' <<< "$input")
|
|
122
|
+
event=$("$JQ" -n -c \
|
|
123
|
+
--arg id "$event_id" \
|
|
124
|
+
--argjson ts "$timestamp" \
|
|
125
|
+
--arg type "$event_type" \
|
|
126
|
+
--arg sid "$session_id" \
|
|
127
|
+
--arg cwd "$cwd" \
|
|
128
|
+
--arg prompt "$prompt" \
|
|
129
|
+
'{id:$id,timestamp:$ts,type:$type,sessionId:$sid,cwd:$cwd,prompt:$prompt}')
|
|
130
|
+
;;
|
|
131
|
+
subagent_stop)
|
|
132
|
+
agent_id=$("$JQ" -r '.agent_id // ""' <<< "$input")
|
|
133
|
+
event=$("$JQ" -n -c \
|
|
134
|
+
--arg id "$event_id" \
|
|
135
|
+
--argjson ts "$timestamp" \
|
|
136
|
+
--arg type "$event_type" \
|
|
137
|
+
--arg sid "$session_id" \
|
|
138
|
+
--arg cwd "$cwd" \
|
|
139
|
+
--arg aid "$agent_id" \
|
|
140
|
+
'{id:$id,timestamp:$ts,type:$type,sessionId:$sid,cwd:$cwd,agentId:$aid}')
|
|
141
|
+
;;
|
|
142
|
+
*)
|
|
143
|
+
event=$("$JQ" -n -c \
|
|
144
|
+
--arg id "$event_id" \
|
|
145
|
+
--argjson ts "$timestamp" \
|
|
146
|
+
--arg type "$event_type" \
|
|
147
|
+
--arg sid "$session_id" \
|
|
148
|
+
--arg cwd "$cwd" \
|
|
149
|
+
'{id:$id,timestamp:$ts,type:$type,sessionId:$sid,cwd:$cwd}')
|
|
150
|
+
;;
|
|
151
|
+
esac
|
|
152
|
+
|
|
153
|
+
# Append to JSONL file
|
|
154
|
+
echo "$event" >> "$EVENTS_FILE"
|
|
155
|
+
|
|
156
|
+
# Notify server (fire and forget)
|
|
157
|
+
if [ "$ENABLE_NOTIFY" = "true" ] && [ -n "$CURL" ]; then
|
|
158
|
+
"$CURL" -s -X POST "${SERVER_URL}/api/claude-events" \
|
|
159
|
+
-H "Content-Type: application/json" \
|
|
160
|
+
-d "$event" \
|
|
161
|
+
--connect-timeout 1 \
|
|
162
|
+
--max-time 2 \
|
|
163
|
+
>/dev/null 2>&1 &
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
exit 0
|