ai-agent-session-center 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.
Files changed (41) hide show
  1. package/README.md +618 -0
  2. package/bin/cli.js +20 -0
  3. package/hooks/dashboard-hook-codex.sh +67 -0
  4. package/hooks/dashboard-hook-gemini.sh +102 -0
  5. package/hooks/dashboard-hook.ps1 +147 -0
  6. package/hooks/dashboard-hook.sh +142 -0
  7. package/hooks/dashboard-hooks-backup.json +103 -0
  8. package/hooks/install-hooks.js +543 -0
  9. package/hooks/reset.js +357 -0
  10. package/hooks/setup-wizard.js +156 -0
  11. package/package.json +52 -0
  12. package/public/css/dashboard.css +10200 -0
  13. package/public/index.html +915 -0
  14. package/public/js/analyticsPanel.js +467 -0
  15. package/public/js/app.js +1148 -0
  16. package/public/js/browserDb.js +806 -0
  17. package/public/js/chartUtils.js +383 -0
  18. package/public/js/historyPanel.js +298 -0
  19. package/public/js/movementManager.js +155 -0
  20. package/public/js/navController.js +32 -0
  21. package/public/js/robotManager.js +526 -0
  22. package/public/js/sceneManager.js +7 -0
  23. package/public/js/sessionPanel.js +2477 -0
  24. package/public/js/settingsManager.js +924 -0
  25. package/public/js/soundManager.js +249 -0
  26. package/public/js/statsPanel.js +118 -0
  27. package/public/js/terminalManager.js +391 -0
  28. package/public/js/timelinePanel.js +278 -0
  29. package/public/js/wsClient.js +88 -0
  30. package/server/apiRouter.js +321 -0
  31. package/server/config.js +120 -0
  32. package/server/hookProcessor.js +55 -0
  33. package/server/hookRouter.js +18 -0
  34. package/server/hookStats.js +107 -0
  35. package/server/index.js +314 -0
  36. package/server/logger.js +67 -0
  37. package/server/mqReader.js +218 -0
  38. package/server/serverConfig.js +27 -0
  39. package/server/sessionStore.js +1049 -0
  40. package/server/sshManager.js +339 -0
  41. package/server/wsManager.js +83 -0
@@ -0,0 +1,102 @@
1
+ #!/bin/bash
2
+ # AI Agent Session Center - Gemini CLI hook relay (macOS / Linux)
3
+ # Receives Gemini event name as $1, reads hook JSON from stdin.
4
+ # Maps Gemini events to dashboard-compatible format, enriches with env info,
5
+ # and delivers to the dashboard server via file-based MQ or HTTP.
6
+ #
7
+ # Key difference from Claude hook:
8
+ # - Gemini hooks are SYNCHRONOUS — must print response to stdout immediately
9
+ # - Event name comes as $1 argument (registered per-event in settings.json)
10
+ # - Session ID from GEMINI_SESSION_ID env var
11
+ # - CWD from GEMINI_CWD env var
12
+
13
+ GEMINI_EVENT="${1:-unknown}"
14
+ SENT_AT=$(date +%s)
15
+ INPUT=$(cat)
16
+
17
+ # Gemini hooks are blocking — respond immediately to allow execution
18
+ echo '{"decision":"allow"}'
19
+
20
+ # --- Everything below runs in background so the hook returns instantly ---
21
+ {
22
+
23
+ # ── Map Gemini events to dashboard-compatible event names ──
24
+ case "$GEMINI_EVENT" in
25
+ SessionStart) MAPPED_EVENT="SessionStart" ;;
26
+ BeforeAgent) MAPPED_EVENT="UserPromptSubmit" ;;
27
+ BeforeTool) MAPPED_EVENT="PreToolUse" ;;
28
+ AfterTool) MAPPED_EVENT="PostToolUse" ;;
29
+ AfterAgent) MAPPED_EVENT="Stop" ;;
30
+ SessionEnd) MAPPED_EVENT="SessionEnd" ;;
31
+ Notification) MAPPED_EVENT="Notification" ;;
32
+ *) MAPPED_EVENT="$GEMINI_EVENT" ;;
33
+ esac
34
+
35
+ # ── Session/CWD from Gemini env vars ──
36
+ SESSION_ID="${GEMINI_SESSION_ID:-}"
37
+ CWD="${GEMINI_CWD:-$(pwd)}"
38
+
39
+ # ── TTY detection (cached per PID) ──
40
+ HOOK_TTY=""
41
+ if [ -n "$PPID" ] && [ "$PPID" != "0" ]; then
42
+ TTY_CACHE="/tmp/gemini-tty-cache"
43
+ TTY_CACHE_FILE="$TTY_CACHE/$PPID"
44
+ if [ -f "$TTY_CACHE_FILE" ]; then
45
+ HOOK_TTY=$(cat "$TTY_CACHE_FILE" 2>/dev/null)
46
+ else
47
+ RAW_TTY=$(ps -o tty= -p "$PPID" 2>/dev/null | tr -d ' ')
48
+ if [ -n "$RAW_TTY" ] && [ "$RAW_TTY" != "??" ] && [ "$RAW_TTY" != "?" ]; then
49
+ HOOK_TTY="/dev/${RAW_TTY}"
50
+ mkdir -p "$TTY_CACHE" 2>/dev/null
51
+ echo "$HOOK_TTY" > "$TTY_CACHE_FILE" 2>/dev/null
52
+ fi
53
+ fi
54
+ fi
55
+
56
+ # ── Single jq pass: build enriched JSON ──
57
+ ENRICHED=$(echo "$INPUT" | jq -c \
58
+ --arg event "$MAPPED_EVENT" \
59
+ --arg session_id "$SESSION_ID" \
60
+ --arg cwd "$CWD" \
61
+ --arg pid "$PPID" \
62
+ --arg tty "$HOOK_TTY" \
63
+ --arg sent_at "$SENT_AT" \
64
+ --arg agent_terminal_id "${AGENT_MANAGER_TERMINAL_ID:-}" \
65
+ --arg gemini_event "$GEMINI_EVENT" \
66
+ '
67
+ {
68
+ hook_event_name: $event,
69
+ session_id: (if $session_id != "" then $session_id else (.session_id // null) end),
70
+ cwd: (if $cwd != "" then $cwd else (.cwd // null) end),
71
+ claude_pid: ($pid | tonumber),
72
+ hook_sent_at: (($sent_at | tonumber) * 1000),
73
+ tty_path: (if $tty != "" then $tty else null end),
74
+ agent_terminal_id: (if $agent_terminal_id != "" then $agent_terminal_id else null end),
75
+ tool_name: (.tool_name // null),
76
+ tool_input: (.tool_input // null),
77
+ prompt: (.prompt // .llm_request // null),
78
+ response: (.response // .llm_response // .prompt_response // null),
79
+ model: (.model // null),
80
+ source: "gemini",
81
+ gemini_event: $gemini_event
82
+ }
83
+ ' 2>/dev/null)
84
+
85
+ [ -z "$ENRICHED" ] && ENRICHED="{\"hook_event_name\":\"$MAPPED_EVENT\",\"session_id\":\"$SESSION_ID\",\"cwd\":\"$CWD\",\"source\":\"gemini\"}"
86
+
87
+ # ── Deliver to dashboard via file-based MQ (primary) or HTTP (fallback) ──
88
+ MQ_DIR="/tmp/claude-session-center"
89
+ MQ_FILE="$MQ_DIR/queue.jsonl"
90
+
91
+ if [ -d "$MQ_DIR" ]; then
92
+ echo "$ENRICHED" >> "$MQ_FILE" 2>/dev/null
93
+ else
94
+ echo "$ENRICHED" | curl -s --connect-timeout 1 -m 3 -X POST \
95
+ -H "Content-Type: application/json" \
96
+ --data-binary @- \
97
+ http://localhost:3333/api/hooks &>/dev/null
98
+ fi
99
+
100
+ } &>/dev/null &
101
+ disown
102
+ exit 0
@@ -0,0 +1,147 @@
1
+ # AI Agent Session Center - Hook relay (Windows)
2
+ # Reads hook JSON from stdin, enriches with process/env info, POSTs to dashboard server
3
+ # Runs in background, fails silently if server is not running
4
+
5
+ $ErrorActionPreference = 'SilentlyContinue'
6
+ $input_json = [Console]::In.ReadToEnd()
7
+
8
+ if (-not $input_json) { exit 0 }
9
+
10
+ # Gather environment info
11
+ $claude_pid = (Get-Process -Id $PID).Parent.Id # Parent of PowerShell = Claude process
12
+ $vscode_pid = $env:VSCODE_PID
13
+ $term_program = $env:TERM_PROGRAM
14
+ $wt_session = $env:WT_SESSION # Windows Terminal session GUID
15
+ $wt_profile = $env:WT_PROFILE_ID # Windows Terminal profile ID
16
+ $conemu_pid = $env:ConEmuPID # ConEmu/Cmder PID
17
+ $term = $env:TERM
18
+
19
+ # Build enrichment object
20
+ $enrich = @{
21
+ claude_pid = if ($claude_pid) { [int]$claude_pid } else { $null }
22
+ term_program = if ($term_program) { $term_program } else { $null }
23
+ vscode_pid = if ($vscode_pid) { [int]$vscode_pid } else { $null }
24
+ term = if ($term) { $term } else { $null }
25
+ tab_id = if ($wt_session) { "wt:$wt_session" } elseif ($conemu_pid) { "conemu:$conemu_pid" } else { $null }
26
+ wt_profile = if ($wt_profile) { $wt_profile } else { $null }
27
+ }
28
+
29
+ # Merge enrichment into the hook JSON
30
+ try {
31
+ $data = $input_json | ConvertFrom-Json
32
+ foreach ($key in $enrich.Keys) {
33
+ if ($null -ne $enrich[$key]) {
34
+ $data | Add-Member -NotePropertyName $key -NotePropertyValue $enrich[$key] -Force
35
+ }
36
+ }
37
+ $enriched = $data | ConvertTo-Json -Compress -Depth 10
38
+ } catch {
39
+ $enriched = $input_json
40
+ }
41
+
42
+ # ---- Tab title management ----
43
+ # Keep the terminal tab/window title set to "Claude: <project>" so the dashboard can find it.
44
+ # On SessionStart: resolve project name and cache to temp file.
45
+ # On every other event: read cache and refresh the title.
46
+ # Uses console title (works on Windows Terminal, ConEmu, cmd, PowerShell, VS Code, JetBrains).
47
+ # Works in all terminals including VS Code and JetBrains integrated terminals.
48
+ try {
49
+ $hookEvent = ($input_json | ConvertFrom-Json).hook_event_name
50
+ $sessionId = ($input_json | ConvertFrom-Json).session_id
51
+ } catch {
52
+ $hookEvent = $null
53
+ $sessionId = $null
54
+ }
55
+
56
+ $cacheDir = "$env:TEMP\claude-tab-titles"
57
+ if ($sessionId) {
58
+ $cacheFile = "$cacheDir\$sessionId"
59
+
60
+ if ($hookEvent -eq 'SessionStart') {
61
+ # Resolve project name from cwd in JSON or from Claude process cwd
62
+ $project = $null
63
+ try {
64
+ $cwd = ($input_json | ConvertFrom-Json).cwd
65
+ if ($cwd) { $project = Split-Path $cwd -Leaf }
66
+ } catch {}
67
+ if (-not $project -and $claude_pid) {
68
+ try {
69
+ $proc = Get-Process -Id $claude_pid -ErrorAction Stop
70
+ $project = Split-Path $proc.Path -Leaf
71
+ # Try to get actual working directory via CIM
72
+ $wmiProc = Get-CimInstance Win32_Process -Filter "ProcessId=$claude_pid" -ErrorAction Stop
73
+ if ($wmiProc.CommandLine -match '([A-Z]:\\[^\s"]+)') {
74
+ $possiblePath = $Matches[1]
75
+ if (Test-Path $possiblePath -PathType Container) {
76
+ $project = Split-Path $possiblePath -Leaf
77
+ }
78
+ }
79
+ } catch {}
80
+ }
81
+ if ($project) {
82
+ if (-not (Test-Path $cacheDir)) { New-Item -ItemType Directory -Path $cacheDir -Force | Out-Null }
83
+ $project | Out-File -FilePath $cacheFile -Encoding utf8 -NoNewline
84
+ }
85
+ } elseif ($hookEvent -eq 'SessionEnd') {
86
+ # Clean up
87
+ if (Test-Path $cacheFile) { Remove-Item $cacheFile -Force }
88
+ } else {
89
+ # Read cached project name
90
+ $project = $null
91
+ if (Test-Path $cacheFile) { $project = Get-Content $cacheFile -Raw -ErrorAction SilentlyContinue }
92
+ }
93
+
94
+ # Set/refresh the console window title on every event (except SessionEnd)
95
+ if ($hookEvent -ne 'SessionEnd' -and $project) {
96
+ $Host.UI.RawUI.WindowTitle = "Claude: $project"
97
+ }
98
+ }
99
+
100
+ # Deliver to dashboard via file-based MQ (primary) or HTTP (fallback)
101
+ $mqDir = "$env:TEMP\claude-session-center"
102
+ $mqFile = "$mqDir\queue.jsonl"
103
+
104
+ if (Test-Path $mqDir -PathType Container) {
105
+ # File-based MQ: atomic append via .NET StreamWriter (no process spawn)
106
+ try {
107
+ $fs = [System.IO.File]::Open($mqFile,
108
+ [System.IO.FileMode]::Append,
109
+ [System.IO.FileAccess]::Write,
110
+ [System.IO.FileShare]::ReadWrite)
111
+ $writer = New-Object System.IO.StreamWriter($fs)
112
+ $writer.WriteLine($enriched)
113
+ $writer.Flush()
114
+ $writer.Close()
115
+ $fs.Close()
116
+ } catch {
117
+ # Fall through to HTTP on file error
118
+ try {
119
+ $job = Start-Job -ScriptBlock {
120
+ param($body)
121
+ try {
122
+ Invoke-RestMethod -Uri 'http://localhost:3333/api/hooks' `
123
+ -Method POST `
124
+ -ContentType 'application/json' `
125
+ -Body $body `
126
+ -TimeoutSec 5 | Out-Null
127
+ } catch {}
128
+ } -ArgumentList $enriched
129
+ } catch {}
130
+ }
131
+ } else {
132
+ # Fallback: HTTP POST when MQ dir doesn't exist (server not started yet)
133
+ try {
134
+ $job = Start-Job -ScriptBlock {
135
+ param($body)
136
+ try {
137
+ Invoke-RestMethod -Uri 'http://localhost:3333/api/hooks' `
138
+ -Method POST `
139
+ -ContentType 'application/json' `
140
+ -Body $body `
141
+ -TimeoutSec 5 | Out-Null
142
+ } catch {}
143
+ } -ArgumentList $enriched
144
+ } catch {}
145
+ }
146
+
147
+ exit 0
@@ -0,0 +1,142 @@
1
+ #!/bin/bash
2
+ # AI Agent Session Center - Hook relay (macOS / Linux)
3
+ # Reads hook JSON from stdin, enriches with process/env info, POSTs to dashboard server
4
+ #
5
+ # Performance notes:
6
+ # - stdin read (cat) is synchronous; everything else runs in a background subshell
7
+ # - Single jq invocation for all JSON parsing + enrichment
8
+ # - TTY lookup cached per PID in /tmp to avoid `ps` on every event
9
+ # - Tab title only refreshed on state-changing events, not rapid tool calls
10
+ # - curl fails fast (1s connect timeout) so dead server doesn't pile up processes
11
+ # - hook_sent_at timestamp lets the server measure delivery latency
12
+
13
+ SENT_AT=$(date +%s)
14
+ INPUT=$(cat)
15
+
16
+ # --- Everything below runs in background so the hook returns instantly ---
17
+ {
18
+
19
+ # ── TTY detection (cached per Claude PID) ──
20
+ HOOK_TTY=""
21
+ if [ -n "$PPID" ] && [ "$PPID" != "0" ]; then
22
+ TTY_CACHE="/tmp/claude-tty-cache"
23
+ TTY_CACHE_FILE="$TTY_CACHE/$PPID"
24
+ if [ -f "$TTY_CACHE_FILE" ]; then
25
+ HOOK_TTY=$(cat "$TTY_CACHE_FILE" 2>/dev/null)
26
+ else
27
+ RAW_TTY=$(ps -o tty= -p "$PPID" 2>/dev/null | tr -d ' ')
28
+ if [ -n "$RAW_TTY" ] && [ "$RAW_TTY" != "??" ] && [ "$RAW_TTY" != "?" ]; then
29
+ HOOK_TTY="/dev/${RAW_TTY}"
30
+ mkdir -p "$TTY_CACHE" 2>/dev/null
31
+ echo "$HOOK_TTY" > "$TTY_CACHE_FILE" 2>/dev/null
32
+ fi
33
+ fi
34
+ fi
35
+
36
+ # ── Single jq pass: enrich JSON + extract event/session_id/cwd ──
37
+ JQ_OUT=$(echo "$INPUT" | jq -c \
38
+ --arg pid "$PPID" \
39
+ --arg tty "$HOOK_TTY" \
40
+ --arg sent_at "$SENT_AT" \
41
+ --arg term_program "${TERM_PROGRAM:-}" \
42
+ --arg term_program_version "${TERM_PROGRAM_VERSION:-}" \
43
+ --arg vscode_pid "${VSCODE_PID:-}" \
44
+ --arg term "${TERM:-}" \
45
+ --arg iterm_session "${ITERM_SESSION_ID:-}" \
46
+ --arg term_session "${TERM_SESSION_ID:-}" \
47
+ --arg kitty_window "${KITTY_WINDOW_ID:-}" \
48
+ --arg kitty_pid "${KITTY_PID:-}" \
49
+ --arg warp_session "${WARP_SESSION_ID:-}" \
50
+ --arg windowid "${WINDOWID:-}" \
51
+ --arg ghostty_resources "${GHOSTTY_RESOURCES_DIR:-}" \
52
+ --arg wezterm_pane "${WEZTERM_PANE:-}" \
53
+ --arg tmux "${TMUX:-}" \
54
+ --arg tmux_pane "${TMUX_PANE:-}" \
55
+ --arg agent_terminal_id "${AGENT_MANAGER_TERMINAL_ID:-}" \
56
+ --arg claude_project_dir "${CLAUDE_PROJECT_DIR:-}" \
57
+ '
58
+ (. + {
59
+ claude_pid: ($pid | tonumber),
60
+ hook_sent_at: (($sent_at | tonumber) * 1000),
61
+ tty_path: (if $tty != "" then $tty else null end),
62
+ term_program: (if $term_program != "" then $term_program else null end),
63
+ term_program_version: (if $term_program_version != "" then $term_program_version else null end),
64
+ vscode_pid: (if $vscode_pid != "" then ($vscode_pid | tonumber) else null end),
65
+ term: (if $term != "" then $term else null end),
66
+ tab_id: (
67
+ if $iterm_session != "" then $iterm_session
68
+ elif $kitty_window != "" then ("kitty:" + $kitty_window)
69
+ elif $warp_session != "" then ("warp:" + $warp_session)
70
+ elif $wezterm_pane != "" then ("wezterm:" + $wezterm_pane)
71
+ elif $term_session != "" then $term_session
72
+ else null end
73
+ ),
74
+ window_id: (if $windowid != "" then ($windowid | tonumber) else null end),
75
+ tmux: (if $tmux != "" then {session: $tmux, pane: $tmux_pane} else null end),
76
+ is_ghostty: (if $ghostty_resources != "" then true else null end),
77
+ kitty_pid: (if $kitty_pid != "" then ($kitty_pid | tonumber) else null end),
78
+ agent_terminal_id: (if $agent_terminal_id != "" then $agent_terminal_id else null end),
79
+ claude_project_dir: (if $claude_project_dir != "" then $claude_project_dir else null end)
80
+ }),
81
+ "\(.hook_event_name // "")",
82
+ "\(.session_id // "")",
83
+ "\(.cwd // "")"
84
+ ' 2>/dev/null)
85
+
86
+ ENRICHED=$(echo "$JQ_OUT" | head -1)
87
+ EVENT=$(echo "$JQ_OUT" | sed -n '2p' | tr -d '"')
88
+ SESSION_ID=$(echo "$JQ_OUT" | sed -n '3p' | tr -d '"')
89
+ CWD=$(echo "$JQ_OUT" | sed -n '4p' | tr -d '"')
90
+
91
+ [ -z "$ENRICHED" ] && ENRICHED="$INPUT"
92
+
93
+ # ── Tab title management ──
94
+ # Only refresh on state-changing events — skip rapid PreToolUse/PostToolUse
95
+ CACHE_DIR="/tmp/claude-tab-titles"
96
+
97
+ if [ -n "$HOOK_TTY" ] && [ -n "$SESSION_ID" ]; then
98
+ CACHE_FILE="$CACHE_DIR/$SESSION_ID"
99
+
100
+ case "$EVENT" in
101
+ SessionStart)
102
+ PROJECT=""
103
+ [ -n "$CWD" ] && PROJECT=$(basename "$CWD" 2>/dev/null)
104
+ if [ -z "$PROJECT" ] && [ -n "$PPID" ]; then
105
+ PROJECT=$(lsof -a -d cwd -p "$PPID" -Fn 2>/dev/null | grep '^n/' | head -1 | sed 's|^n||' | xargs basename 2>/dev/null)
106
+ fi
107
+ if [ -n "$PROJECT" ]; then
108
+ mkdir -p "$CACHE_DIR" 2>/dev/null
109
+ echo "$PROJECT" > "$CACHE_FILE" 2>/dev/null
110
+ printf '\033]0;Claude: %s\007' "$PROJECT" > "$HOOK_TTY" 2>/dev/null
111
+ fi
112
+ ;;
113
+ SessionEnd)
114
+ rm -f "$CACHE_FILE" 2>/dev/null
115
+ ;;
116
+ UserPromptSubmit|PermissionRequest|Stop|Notification)
117
+ PROJECT=""
118
+ [ -f "$CACHE_FILE" ] && PROJECT=$(cat "$CACHE_FILE" 2>/dev/null)
119
+ [ -n "$PROJECT" ] && printf '\033]0;Claude: %s\007' "$PROJECT" > "$HOOK_TTY" 2>/dev/null
120
+ ;;
121
+ esac
122
+ fi
123
+
124
+ # ── Deliver to dashboard via file-based MQ (primary) or HTTP (fallback) ──
125
+ MQ_DIR="/tmp/claude-session-center"
126
+ MQ_FILE="$MQ_DIR/queue.jsonl"
127
+
128
+ if [ -d "$MQ_DIR" ]; then
129
+ # Atomic append: POSIX guarantees atomicity for writes <= PIPE_BUF (4096 bytes).
130
+ # Our enriched JSON is typically 300-800 bytes — no process spawn, ~0.1ms.
131
+ echo "$ENRICHED" >> "$MQ_FILE" 2>/dev/null
132
+ else
133
+ # Fallback: HTTP POST when MQ dir doesn't exist (server not started yet)
134
+ echo "$ENRICHED" | curl -s --connect-timeout 1 -m 3 -X POST \
135
+ -H "Content-Type: application/json" \
136
+ --data-binary @- \
137
+ http://localhost:3333/api/hooks &>/dev/null
138
+ fi
139
+
140
+ } &>/dev/null &
141
+ disown
142
+ exit 0
@@ -0,0 +1,103 @@
1
+ {
2
+ "hooks": {
3
+ "UserPromptSubmit": [
4
+ {
5
+ "hooks": [
6
+ {
7
+ "type": "command",
8
+ "command": "~/.claude/hooks/dashboard-hook.sh",
9
+ "async": true
10
+ }
11
+ ]
12
+ }
13
+ ],
14
+ "Stop": [
15
+ {
16
+ "hooks": [
17
+ {
18
+ "type": "command",
19
+ "command": "~/.claude/hooks/dashboard-hook.sh",
20
+ "async": true
21
+ }
22
+ ]
23
+ }
24
+ ],
25
+ "SessionStart": [
26
+ {
27
+ "hooks": [
28
+ {
29
+ "type": "command",
30
+ "command": "~/.claude/hooks/dashboard-hook.sh",
31
+ "async": true
32
+ }
33
+ ]
34
+ }
35
+ ],
36
+ "PreToolUse": [
37
+ {
38
+ "hooks": [
39
+ {
40
+ "type": "command",
41
+ "command": "~/.claude/hooks/dashboard-hook.sh",
42
+ "async": true
43
+ }
44
+ ]
45
+ }
46
+ ],
47
+ "PostToolUse": [
48
+ {
49
+ "hooks": [
50
+ {
51
+ "type": "command",
52
+ "command": "~/.claude/hooks/dashboard-hook.sh",
53
+ "async": true
54
+ }
55
+ ]
56
+ }
57
+ ],
58
+ "Notification": [
59
+ {
60
+ "hooks": [
61
+ {
62
+ "type": "command",
63
+ "command": "~/.claude/hooks/dashboard-hook.sh",
64
+ "async": true
65
+ }
66
+ ]
67
+ }
68
+ ],
69
+ "SubagentStart": [
70
+ {
71
+ "hooks": [
72
+ {
73
+ "type": "command",
74
+ "command": "~/.claude/hooks/dashboard-hook.sh",
75
+ "async": true
76
+ }
77
+ ]
78
+ }
79
+ ],
80
+ "SubagentStop": [
81
+ {
82
+ "hooks": [
83
+ {
84
+ "type": "command",
85
+ "command": "~/.claude/hooks/dashboard-hook.sh",
86
+ "async": true
87
+ }
88
+ ]
89
+ }
90
+ ],
91
+ "SessionEnd": [
92
+ {
93
+ "hooks": [
94
+ {
95
+ "type": "command",
96
+ "command": "~/.claude/hooks/dashboard-hook.sh",
97
+ "async": true
98
+ }
99
+ ]
100
+ }
101
+ ]
102
+ }
103
+ }