claude-code-kanban 5.2.0 → 5.4.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 (39) hide show
  1. package/README.md +2 -2
  2. package/cli.js +10 -5
  3. package/install.js +43 -123
  4. package/lib/approvals.js +16 -17
  5. package/lib/claude-dir.js +7 -1
  6. package/lib/git-branch.js +47 -0
  7. package/lib/net-guard.js +41 -14
  8. package/lib/parsers.js +96 -59
  9. package/lib/plugin-status.js +1 -1
  10. package/lib/proc-stats.js +82 -0
  11. package/lib/retention.js +12 -9
  12. package/lib/session-cache.js +1 -1
  13. package/lib/terminal.js +19 -5
  14. package/lib/worktrees.js +105 -0
  15. package/package.json +1 -2
  16. package/plugin/.claude-plugin/marketplace.json +1 -1
  17. package/plugin/plugins/claude-code-kanban/.claude-plugin/plugin.json +2 -2
  18. package/plugin/plugins/claude-code-kanban/hooks/activity.ts +266 -0
  19. package/plugin/plugins/claude-code-kanban/hooks/context.ts +77 -0
  20. package/plugin/plugins/claude-code-kanban/hooks/hooks.json +1 -93
  21. package/plugin/plugins/claude-code-kanban/hooks/index.ts +8 -0
  22. package/plugin/plugins/claude-code-kanban/scripts/postman.js +4 -4
  23. package/plugin/plugins/claude-code-kanban/skills/dispatch/SKILL.md +1 -1
  24. package/plugin/plugins/claude-code-kanban/skills/kanban/SKILL.md +0 -1
  25. package/plugin/plugins/claude-code-kanban/tests/activity.test.ts +300 -0
  26. package/plugin/plugins/claude-code-kanban/tests/context.test.ts +72 -0
  27. package/plugin/plugins/claude-code-kanban/tsconfig.json +3 -0
  28. package/public/app.js +661 -380
  29. package/public/index.html +7 -3
  30. package/public/project-match.js +6 -4
  31. package/public/style.css +258 -79
  32. package/public/terminal-frame.js +341 -0
  33. package/public/terminal.html +27 -0
  34. package/public/vendor/claude-hub-sdk.js +13 -3
  35. package/server.js +163 -145
  36. package/skill-guides/dispatch.md +4 -4
  37. package/plugin/plugins/claude-code-kanban/scripts/agent-spy.sh +0 -135
  38. package/plugin/plugins/claude-code-kanban/scripts/approval-gate.sh +0 -208
  39. package/plugin/plugins/claude-code-kanban/scripts/context-status.sh +0 -18
@@ -2,7 +2,7 @@
2
2
 
3
3
  A dispatch is one Claude Code session that cck starts for a task, in its embedded terminal. It is an ordinary session, not a child: it shows in the sidebar like any other, and the user can open its terminal at any time.
4
4
 
5
- A dispatch is **fire-and-forget** by default: you hand the task off, and the user watches it in the sidebar. Add `--report` only when you need the outcome back: the user asked you to collect it, or your next step depends on it.
5
+ A dispatch **reports back** by default: start it with `--report` and `--peer`, then collect and verify the outcome (With `--report`, below). When the user's request says `--no-report`, it is **fire-and-forget**: start it with neither flag (Fire-and-forget, below). `--no-report` lives only in the user's request; `dispatch start` has no such flag.
6
6
 
7
7
  ## Write the spec
8
8
 
@@ -19,12 +19,12 @@ Dispatch when the task can run on its own. Do the work yourself when it is small
19
19
  ## Start
20
20
 
21
21
  ```bash
22
- claude-code-kanban dispatch start --cwd <dir> --spec-file <spec.md> --name <name> --group <group> --peer <your-peer> [--report] --json
22
+ claude-code-kanban dispatch start --cwd <dir> --spec-file <spec.md> --name <name> --group <group> --peer <your-peer> --report --json
23
23
  ```
24
24
 
25
25
  `claude-code-kanban help dispatch start` lists every flag (model, worktree, and the rest). `project list` shows the folders `--cwd` accepts. How to choose the values:
26
26
 
27
- - `--peer` is your own peer name: the first line of `ListAgents` ("This session is `<name>`"). Pass it whenever you have the `ListAgents` tool. cck then tells the started session to ask you with `SendMessage` instead of failing on a question. See [Peer](#peer).
27
+ - `--peer` is your own peer name: the first line of `ListAgents` ("This session is `<name>`"). Pass it when you have the `ListAgents` tool. cck then tells the started session to ask you with `SendMessage` instead of failing on a question. See [Peer](#peer).
28
28
  - `--spec-file` over `--spec` for anything longer than a line: no shell quoting.
29
29
  - `--name` is what the user sees in the sidebar. Kebab-case, saying what the session does: `fix-login-redirect`, not `task-1`.
30
30
  - `--group` names the effort, in kebab-case (`auth-refactor`), and shows the new session under that sidebar group. This session stays where it is. Pass it on your first dispatch; later dispatches join the same group without it. A group goes away when its sessions end, unless the user pins a member or keeps the group.
@@ -32,7 +32,7 @@ claude-code-kanban dispatch start --cwd <dir> --spec-file <spec.md> --name <name
32
32
 
33
33
  ## Fire-and-forget
34
34
 
35
- Tell the user the session name, its group, and the dispatch id, then carry on with your own work or end your turn. The user follows the dispatch in the sidebar. With `--peer`, its questions still reach you as new turns.
35
+ The started session gets the task alone and does not know your session exists. Tell the user the session name, its group, and the dispatch id. Your part ends with that message: the user follows the dispatch in the sidebar, and asks you when they want it checked.
36
36
 
37
37
  ## With `--report`
38
38
 
@@ -1,135 +0,0 @@
1
- #!/bin/bash
2
- # Tracks subagent lifecycle: one append-only JSONL file per agent, grouped by session
3
- # Layout: <CLAUDE_CONFIG_DIR or ~/.claude>/.cck/agent-activity/{sessionId}/{agentId}.jsonl
4
- # Each line is a lifecycle event (start | idle | stop). Server folds last-line-wins.
5
-
6
- INPUT=$(cat)
7
-
8
- # Single jq call to extract all routing fields
9
- eval "$(echo "$INPUT" | jq -r '
10
- @sh "SESSION_ID=\(.session_id // "")",
11
- @sh "AGENT_ID=\(.agent_id // "")",
12
- @sh "EVENT=\(.hook_event_name // "")",
13
- @sh "TOOL_NAME=\(.tool_name // "")",
14
- @sh "AGENT_TYPE_RAW=\(.agent_type // "")",
15
- @sh "TEAMMATE_NAME=\(.teammate_name // "")"
16
- ')"
17
-
18
- [ -z "$SESSION_ID" ] && exit 0
19
-
20
- CCK_ACTIVITY="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/.cck/agent-activity"
21
-
22
- # Map session to custom task list on session start
23
- if [ "$EVENT" = "SessionStart" ]; then
24
- TASK_LIST_ID="${CLAUDE_CODE_TASK_LIST_ID:-}"
25
- if [ -n "$TASK_LIST_ID" ]; then
26
- CWD=$(echo "$INPUT" | jq -r '.cwd // ""')
27
- MAPS_DIR="$CCK_ACTIVITY/_task-maps"
28
- mkdir -p "$MAPS_DIR"
29
- MAP_FILE="$MAPS_DIR/$TASK_LIST_ID.json"
30
- TMP_FILE="$MAP_FILE.$$"
31
- TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
32
- EXISTING="{}"
33
- [ -f "$MAP_FILE" ] && EXISTING=$(cat "$MAP_FILE")
34
- echo "$EXISTING" | jq -c --arg sid "$SESSION_ID" --arg cwd "$CWD" --arg ts "$TS" \
35
- '.[$sid] = {project: $cwd, updatedAt: $ts}' > "$TMP_FILE" && mv "$TMP_FILE" "$MAP_FILE"
36
- fi
37
- exit 0
38
- fi
39
-
40
- # Stop: the session finished its turn. The marker means unread; the board deletes it on open.
41
- if [ "$EVENT" = "Stop" ]; then
42
- DIR="$CCK_ACTIVITY/$SESSION_ID"
43
- [ -d "$DIR" ] || mkdir -p "$DIR"
44
- : > "$DIR/_stop.json"
45
- exit 0
46
- fi
47
-
48
- # PostToolUse / non-waiting PreToolUse: clear waiting state, and unread since the session is working again
49
- if [ "$EVENT" = "PostToolUse" ] || { [ "$EVENT" = "PreToolUse" ] && [ "$TOOL_NAME" != "AskUserQuestion" ] && [ "$TOOL_NAME" != "ExitPlanMode" ]; }; then
50
- rm -f "$CCK_ACTIVITY/$SESSION_ID/_waiting.json" "$CCK_ACTIVITY/$SESSION_ID/_stop.json"
51
- [ "$EVENT" = "PostToolUse" ] && exit 0
52
- fi
53
-
54
- # EnterPlanMode has no waiting semantics — skip
55
- [ "$TOOL_NAME" = "EnterPlanMode" ] && exit 0
56
-
57
- # Legacy-config shim: shipped hooks.json routes PermissionRequest to
58
- # approval-gate.sh (which owns the id-bearing marker) and registers no
59
- # PreToolUse, so the suppression and writer below only run for configs that
60
- # still route those events here. Suppressing question/plan PermissionRequest
61
- # keeps such a config from overwriting the gate's marker with an id-less one.
62
- if [ "$EVENT" = "PermissionRequest" ] && { [ "$TOOL_NAME" = "AskUserQuestion" ] || [ "$TOOL_NAME" = "ExitPlanMode" ]; }; then
63
- exit 0
64
- fi
65
-
66
- # Waiting-for-user events → write _waiting.json marker
67
- if [ "$EVENT" = "PermissionRequest" ] || { [ "$EVENT" = "PreToolUse" ] && { [ "$TOOL_NAME" = "AskUserQuestion" ] || [ "$TOOL_NAME" = "ExitPlanMode" ]; }; }; then
68
- DIR="$CCK_ACTIVITY/$SESSION_ID"
69
- mkdir -p "$DIR"
70
- KIND="permission"
71
- [ "$EVENT" = "PreToolUse" ] && KIND="question"
72
- TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
73
- echo "$INPUT" | jq -c --arg kind "$KIND" --arg ts "$TS" '{
74
- status: "waiting",
75
- kind: $kind,
76
- toolName: (.tool_name // "unknown"),
77
- toolInput: ((.tool_input | tostring) // ""),
78
- timestamp: $ts
79
- }' > "$DIR/_waiting.json"
80
- exit 0
81
- fi
82
-
83
- # TeammateIdle has no agent_id — resolve via name→id mapping file
84
- if [ "$EVENT" = "TeammateIdle" ] && [ -z "$AGENT_ID" ] && [ -n "$TEAMMATE_NAME" ]; then
85
- DIR="$CCK_ACTIVITY/$SESSION_ID"
86
- MAP_FILE="$DIR/_name-${TEAMMATE_NAME}.id"
87
- [ ! -f "$MAP_FILE" ] && exit 0
88
- AGENT_ID=$(cat "$MAP_FILE")
89
- [ -z "$AGENT_ID" ] && exit 0
90
- FILE="$DIR/$AGENT_ID.jsonl"
91
- TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
92
- echo "{\"agentId\":\"$AGENT_ID\",\"type\":\"$TEAMMATE_NAME\",\"event\":\"idle\",\"status\":\"idle\",\"updatedAt\":\"$TS\"}" >> "$FILE"
93
- exit 0
94
- fi
95
-
96
- [ -z "$AGENT_ID" ] && exit 0
97
-
98
- DIR="$CCK_ACTIVITY/$SESSION_ID"
99
- FILE="$DIR/$AGENT_ID.jsonl"
100
-
101
- # On Start: skip if no type (internal agents like AskUserQuestion)
102
- # On Stop/Idle: only skip if no existing file (never tracked)
103
- if [ -z "$AGENT_TYPE_RAW" ]; then
104
- if [ "$EVENT" = "SubagentStart" ]; then
105
- exit 0
106
- elif [ ! -f "$FILE" ]; then
107
- exit 0
108
- fi
109
- fi
110
-
111
- mkdir -p "$DIR"
112
- TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
113
-
114
- if [ "$EVENT" = "SubagentStart" ]; then
115
- echo "{\"agentId\":\"$AGENT_ID\",\"type\":\"$AGENT_TYPE_RAW\",\"event\":\"start\",\"status\":\"active\",\"startedAt\":\"$TS\",\"updatedAt\":\"$TS\"}" >> "$FILE"
116
- # Mapping always points at latest agent of this type (used by TeammateIdle resolution).
117
- if [ -n "$AGENT_TYPE_RAW" ]; then
118
- echo -n "$AGENT_ID" > "$DIR/_name-${AGENT_TYPE_RAW}.id"
119
- fi
120
-
121
- elif [ "$EVENT" = "SubagentStop" ]; then
122
- # Omit empty type: the server folds lines last-key-wins, so an empty type
123
- # here would clobber the type recorded by the start line
124
- echo "$INPUT" | jq -c \
125
- --arg id "$AGENT_ID" --arg type "$AGENT_TYPE_RAW" --arg ts "$TS" \
126
- '{agentId: $id, event: "stop", status: "stopped",
127
- lastMessage: (.last_assistant_message // ""), stoppedAt: $ts, updatedAt: $ts}
128
- + (if $type == "" then {} else {type: $type} end)' \
129
- >> "$FILE"
130
-
131
- elif [ "$EVENT" = "TeammateIdle" ]; then
132
- TYPE_FIELD=""
133
- [ -n "$AGENT_TYPE_RAW" ] && TYPE_FIELD="\"type\":\"$AGENT_TYPE_RAW\","
134
- echo "{\"agentId\":\"$AGENT_ID\",${TYPE_FIELD}\"event\":\"idle\",\"status\":\"idle\",\"updatedAt\":\"$TS\"}" >> "$FILE"
135
- fi
@@ -1,208 +0,0 @@
1
- #!/bin/bash
2
- # Blocking approval gate: lets the cck board answer a permission ask or an
3
- # AskUserQuestion. Always writes the _waiting.json marker first (badge behavior
4
- # is unchanged when the feature is off), then — unless config.json opts out, and
5
- # only while the board's server is alive — waits for a decision file written by the server.
6
- #
7
- # Contract (_plans/cck-ui-approvals/decisions.md), rooted at <CLAUDE_CONFIG_DIR or ~/.claude>/.cck:
8
- # marker agent-activity/<sid>/_waiting.json (D8: + id, cwd, permissionSuggestions)
9
- # decision agent-activity/<sid>/_decision-<id>.json (server writes it, Phase 3)
10
- # config config.json {approvals: {enabled, mode, waitSeconds}} (on by default; absent = defaults)
11
- # liveness server.json {port, pid} (D1: a dead board costs nothing)
12
- #
13
- # First writer wins (D5): a terminal answer deletes the marker (this gate, via
14
- # the session registry, or PostToolUse) and this gate exits silently; a decision arriving after the tool already ran
15
- # is discarded by Claude Code, so a losing write on either side is harmless.
16
-
17
- INPUT=$(cat)
18
-
19
- eval "$(echo "$INPUT" | jq -r '
20
- @sh "SESSION_ID=\(.session_id // "")",
21
- @sh "EVENT=\(.hook_event_name // "")",
22
- @sh "TOOL_NAME=\(.tool_name // "")"
23
- ')"
24
-
25
- [ -z "$SESSION_ID" ] && exit 0
26
-
27
- # AskUserQuestion and ExitPlanMode gate on PermissionRequest, not PreToolUse:
28
- # the TUI question and plan dialogs render ~10 s in while a PermissionRequest
29
- # hook blocks (first writer wins, like permissions), but stay frozen for the
30
- # whole wait during a PreToolUse hook — measured live (#42, #40). Suppress the
31
- # PreToolUse double-fire in case a stale hooks.json still registers it.
32
- if [ "$EVENT" = "PreToolUse" ]; then
33
- exit 0
34
- fi
35
-
36
- KIND="permission"
37
- [ "$TOOL_NAME" = "AskUserQuestion" ] && KIND="question"
38
- [ "$TOOL_NAME" = "ExitPlanMode" ] && KIND="plan"
39
-
40
- CCK_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/.cck"
41
- DIR="$CCK_DIR/agent-activity/$SESSION_ID"
42
- MARKER="$DIR/_waiting.json"
43
- mkdir -p "$DIR"
44
-
45
- # uuidgen is missing on some Git Bash installs; uniqueness only has to hold
46
- # across the asks of one session, so a timestamp compound is enough
47
- REQ_ID=$(uuidgen 2>/dev/null) || REQ_ID="$(date +%s%N)-$$-$RANDOM"
48
-
49
- TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
50
- echo "$INPUT" | jq -c --arg kind "$KIND" --arg ts "$TS" --arg id "$REQ_ID" '{
51
- status: "waiting",
52
- kind: $kind,
53
- id: $id,
54
- toolName: (.tool_name // "unknown"),
55
- toolInput: ((.tool_input | tostring) // ""),
56
- cwd: (.cwd // ""),
57
- permissionSuggestions: (.permission_suggestions // []),
58
- timestamp: $ts
59
- }' > "$MARKER"
60
-
61
- # Every exit below leaves the marker in place for the badge; agent-spy.sh's
62
- # PostToolUse (or the server's TTL) retires it, exactly as before this feature.
63
-
64
- # Defaults mirror lib/approvals.js — keep in sync. A missing or unparseable
65
- # config.json means defaults, i.e. the gate is on.
66
- ENABLED="true"
67
- MODE="permission+question"
68
- WAIT_SECONDS=""
69
- CONFIG="$CCK_DIR/config.json"
70
- if [ -f "$CONFIG" ]; then
71
- eval "$(jq -r '
72
- @sh "ENABLED=\(if .approvals.enabled == false then "false" else "true" end)",
73
- @sh "MODE=\(.approvals.mode // "permission+question")",
74
- @sh "WAIT_SECONDS=\(.approvals.waitSeconds // "")"
75
- ' < "$CONFIG" 2>/dev/null)"
76
- fi
77
- [ "$ENABLED" = "false" ] && exit 0
78
-
79
- if [ "$KIND" = "question" ] && [ "$MODE" != "permission+question" ]; then
80
- exit 0
81
- fi
82
-
83
- case "$WAIT_SECONDS" in *[!0-9]* | "") WAIT_SECONDS=1800 ;; esac
84
- # PERMISSION_TTL_MS hides the card at 30 min — waiting longer than the UI can
85
- # show the ask is strictly worse than giving up (D11)
86
- [ "$WAIT_SECONDS" -gt 1800 ] && WAIT_SECONDS=1800
87
-
88
- SERVER_INFO="$CCK_DIR/server.json"
89
-
90
- # A TCP connect beats a pid probe: it proves the board is actually serving, and
91
- # it works in the stripped environment Claude Code spawns hooks into, where
92
- # kill -0 cannot see native Windows pids and ps may be missing from PATH. The
93
- # port is re-read on every probe, not cached: under the hub every sub-app binds
94
- # an ephemeral port, so a board that restarts mid-wait comes back on a new one.
95
- # $(<file) and =~ keep the probe free of jq and cat spawns (~280 ms each here).
96
- board_alive() {
97
- local raw port
98
- raw=$(<"$SERVER_INFO") 2>/dev/null || return 1
99
- [[ $raw =~ \"port\"[[:space:]]*:[[:space:]]*([0-9]+) ]] || return 1
100
- port=${BASH_REMATCH[1]}
101
- (: < "/dev/tcp/127.0.0.1/$port") 2>/dev/null
102
- }
103
-
104
- DECISION="$DIR/_decision-$REQ_ID.json"
105
- # EPOCHSECONDS (bash 5) keeps the poll loop free of `date` spawns
106
- DEADLINE=$((EPOCHSECONDS + WAIT_SECONDS))
107
- # Probing once up front made one unreachable moment disarm the whole ask, so a
108
- # board restarting on a new port (every hub-spawned sub-app binds an ephemeral
109
- # one) took the ask with it. Probe on a cadence instead and only give up once
110
- # the board has been gone for the whole grace. The terminal prompt stays live
111
- # throughout either way.
112
- BOARD_PROBE_SECONDS=5
113
- BOARD_GRACE_SECONDS=15
114
- NEXT_PROBE=0
115
- UNREACHABLE_SINCE=0
116
-
117
- # Claude Code's live-session registry flips from "waiting" the moment the
118
- # terminal prompt is answered, while PostToolUse waits for the tool to finish
119
- # and never fires on a deny. Undocumented, so a missing file or field leaves
120
- # PostToolUse in charge, as before.
121
- SESSIONS_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/sessions"
122
- REGISTRY=""
123
- SEEN_WAITING=0
124
- find_registry() {
125
- local f raw
126
- for f in "$SESSIONS_DIR"/*.json; do
127
- [ -f "$f" ] || continue
128
- raw=""
129
- IFS= read -r raw 2>/dev/null < "$f"
130
- case "$raw" in *"\"sessionId\":\"$SESSION_ID\""*) REGISTRY=$f; return 0 ;; esac
131
- done
132
- return 1
133
- }
134
-
135
- while :; do
136
- if [ -f "$DECISION" ]; then
137
- PAYLOAD=$(cat "$DECISION" 2>/dev/null)
138
- rm -f "$DECISION" "$MARKER"
139
- [ -n "$PAYLOAD" ] || exit 0
140
- if [ "$KIND" = "plan" ] && [ "$(echo "$PAYLOAD" | jq -r '.behavior // "deny"' 2>/dev/null)" = "allow" ]; then
141
- # A plan allow must echo tool_input as updatedInput — Claude Code
142
- # >= 2.1.199 silently drops an ExitPlanMode allow without it and falls
143
- # back to the built-in dialog (measured; plannotator does the same).
144
- echo "$INPUT" | jq -c --argjson p "$PAYLOAD" \
145
- '{hookSpecificOutput: {hookEventName: "PermissionRequest",
146
- decision: ({behavior: "allow", updatedInput: (.tool_input // {})}
147
- + (if $p.updatedPermissions then {updatedPermissions: $p.updatedPermissions} else {} end))}}' 2>/dev/null
148
- elif [ "$KIND" != "question" ]; then
149
- # Permission asks and plan denies share this shaping — the server sends
150
- # only behavior+message for a plan deny. PermissionRequest decisions must
151
- # ride hookSpecificOutput — a top-level {decision} is the approve/block
152
- # string channel and an object there throws
153
- echo "$PAYLOAD" | jq -c '{hookSpecificOutput: {hookEventName: "PermissionRequest",
154
- decision: ({behavior: (.behavior // "deny")}
155
- + (if .message then {message: .message} else {} end)
156
- + (if .updatedInput then {updatedInput: .updatedInput} else {} end)
157
- + (if .updatedPermissions then {updatedPermissions: .updatedPermissions} else {} end))}}' 2>/dev/null
158
- else
159
- # updatedInput replaces the whole input object, so echo every field and
160
- # add the answers Claude never fills in itself (D6). Questions ride the
161
- # PermissionRequest channel now (#42) — allow with the answers filled in.
162
- ANSWERS=$(echo "$PAYLOAD" | jq -c '.answers // empty' 2>/dev/null)
163
- [ -n "$ANSWERS" ] || exit 0
164
- echo "$INPUT" | jq -c --argjson answers "$ANSWERS" \
165
- '{hookSpecificOutput: {hookEventName: "PermissionRequest",
166
- decision: {behavior: "allow", updatedInput: ((.tool_input // {}) + {answers: $answers})}}}' 2>/dev/null
167
- fi
168
- exit 0
169
- fi
170
-
171
- # Marker gone = answered in the terminal (PostToolUse fires ~23 ms after — D5);
172
- # id changed = displaced by a newer ask (D8). Either way this gate is over.
173
- # Builtin read + substring match instead of jq: a spawn costs ~280 ms on
174
- # Windows (O2), and the marker is single-line jq -c output with a known id.
175
- IFS= read -r CUR_MARKER < "$MARKER" 2>/dev/null || exit 0
176
- case "$CUR_MARKER" in *"\"id\":\"$REQ_ID\""*) ;; *) exit 0 ;; esac
177
-
178
- if [ -n "$REGISTRY" ]; then
179
- REG_RAW=""
180
- IFS= read -r REG_RAW 2>/dev/null < "$REGISTRY"
181
- if [[ $REG_RAW =~ \"status\":\"([a-z]+)\" ]]; then
182
- if [ "${BASH_REMATCH[1]}" = "waiting" ]; then
183
- SEEN_WAITING=1
184
- elif [ "$SEEN_WAITING" -eq 1 ]; then
185
- rm -f "$MARKER"
186
- exit 0
187
- fi
188
- fi
189
- fi
190
-
191
- if [ "$EPOCHSECONDS" -ge "$NEXT_PROBE" ]; then
192
- NEXT_PROBE=$((EPOCHSECONDS + BOARD_PROBE_SECONDS))
193
- [ -n "$REGISTRY" ] || find_registry
194
- # No beacon at all = no board on this config dir, now or a moment ago: give
195
- # up at once, exactly as before. A beacon whose port is closed is the
196
- # restart window instead, so that one gets the grace.
197
- [ -f "$SERVER_INFO" ] || exit 0
198
- if board_alive; then
199
- UNREACHABLE_SINCE=0
200
- else
201
- [ "$UNREACHABLE_SINCE" -eq 0 ] && UNREACHABLE_SINCE=$EPOCHSECONDS
202
- [ $((EPOCHSECONDS - UNREACHABLE_SINCE)) -ge "$BOARD_GRACE_SECONDS" ] && exit 0
203
- fi
204
- fi
205
-
206
- [ "$EPOCHSECONDS" -ge "$DEADLINE" ] && exit 0
207
- sleep 0.5
208
- done
@@ -1,18 +0,0 @@
1
- #!/bin/bash
2
- # Statusline spy: writes raw context data for kanban dashboard, passes input through
3
- # Layout: <CLAUDE_CONFIG_DIR or ~/.claude>/.cck/context-status/{sessionId}.json
4
- #
5
- # Usage: pipe before your statusline command:
6
- # "command": "~/.claude/hooks/context-status.sh | npx -y ccstatusline@latest"
7
-
8
- INPUT=$(cat)
9
-
10
- SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // ""')
11
- if [ -n "$SESSION_ID" ]; then
12
- DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/.cck/context-status"
13
- mkdir -p "$DIR"
14
- echo "$INPUT" > "$DIR/$SESSION_ID.json"
15
- fi
16
-
17
- # Pass through original input for downstream statusline
18
- echo "$INPUT"