claude-code-kanban 4.24.0 → 4.25.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 +3 -1
- package/cli.js +1 -1
- package/install.js +14 -9
- package/lib/claude-dir.js +53 -0
- package/lib/open-editor.js +4 -1
- package/lib/parsers.js +54 -4
- package/package.json +1 -1
- package/plugin/plugins/claude-code-kanban/.claude-plugin/plugin.json +1 -1
- package/plugin/plugins/claude-code-kanban/scripts/agent-spy.sh +2 -2
- package/plugin/plugins/claude-code-kanban/scripts/approval-gate.sh +6 -6
- package/plugin/plugins/claude-code-kanban/scripts/context-status.sh +2 -2
- package/plugin/plugins/claude-code-kanban/scripts/postman.js +2 -1
- package/public/app.js +235 -108
- package/public/index.html +7 -3
- package/public/style.css +62 -3
- package/server.js +14 -23
package/README.md
CHANGED
|
@@ -22,6 +22,8 @@ npx claude-code-kanban --install
|
|
|
22
22
|
|
|
23
23
|
Non-destructive — existing settings in `~/.claude/settings.json` are preserved. Uninstall anytime with `npx claude-code-kanban --uninstall`.
|
|
24
24
|
|
|
25
|
+
Using another Claude config dir? Pass `--dir=<path>` (or set `CLAUDE_CONFIG_DIR`) to both `--install` and `--uninstall`. The plugin, hooks and statusLine land in that dir, and the hooks write their data under it when Claude Code runs with the same `CLAUDE_CONFIG_DIR`.
|
|
26
|
+
|
|
25
27
|
### 2. Start the dashboard
|
|
26
28
|
|
|
27
29
|
```bash
|
|
@@ -84,7 +86,7 @@ The script pipes through, so your existing statusline keeps working.
|
|
|
84
86
|
```bash
|
|
85
87
|
PORT=8080 npx claude-code-kanban # Custom port (falls back if busy)
|
|
86
88
|
npx claude-code-kanban --open # Auto-open browser
|
|
87
|
-
npx claude-code-kanban --dir=~/.claude-work # Custom Claude config dir
|
|
89
|
+
npx claude-code-kanban --dir=~/.claude-work # Custom Claude config dir (or CLAUDE_CONFIG_DIR)
|
|
88
90
|
```
|
|
89
91
|
|
|
90
92
|
Global install: `npm install -g claude-code-kanban`, then `claude-code-kanban --open`.
|
package/cli.js
CHANGED
|
@@ -155,7 +155,7 @@ function printTopHelp() {
|
|
|
155
155
|
console.log(' --version, -v Print version and exit');
|
|
156
156
|
console.log('\nServer mode (no subcommand):');
|
|
157
157
|
console.log(' --port <n> Port to listen on (default 3541)');
|
|
158
|
-
console.log(' --dir <path> Override Claude config dir (default ~/.claude)');
|
|
158
|
+
console.log(' --dir <path> Override Claude config dir (default ~/.claude); also targets --install/--uninstall');
|
|
159
159
|
console.log(' --open Open browser on start');
|
|
160
160
|
console.log(' --install, --uninstall Install or remove the plugin, context spy, and statusline');
|
|
161
161
|
console.log(' --plugin-only With --install: refresh only the plugin, skip context spy and statusline');
|
package/install.js
CHANGED
|
@@ -5,15 +5,19 @@ const path = require('path');
|
|
|
5
5
|
const os = require('os');
|
|
6
6
|
const readline = require('readline');
|
|
7
7
|
const { execSync } = require('child_process');
|
|
8
|
+
const { getClaudeDir, claudeCliEnv, displayPath } = require('./lib/claude-dir');
|
|
8
9
|
|
|
9
|
-
const CLAUDE_DIR =
|
|
10
|
+
const CLAUDE_DIR = getClaudeDir();
|
|
11
|
+
const CLI_ENV = claudeCliEnv(CLAUDE_DIR);
|
|
10
12
|
const CCK_DIR = path.join(CLAUDE_DIR, '.cck');
|
|
11
13
|
const HOOKS_DIR = path.join(CLAUDE_DIR, 'hooks');
|
|
12
14
|
const SETTINGS_PATH = path.join(CLAUDE_DIR, 'settings.json');
|
|
13
15
|
const PLUGIN_SRC = path.join(__dirname, 'plugin');
|
|
14
16
|
const PLUGIN_DEST = path.join(CCK_DIR, 'plugin');
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
+
const CTX_SCRIPT_NAME = 'context-status.sh';
|
|
18
|
+
const CTX_SCRIPT_SRC = path.join(PLUGIN_SRC, 'plugins', 'claude-code-kanban', 'scripts', CTX_SCRIPT_NAME);
|
|
19
|
+
const CTX_SCRIPT_DEST = path.join(HOOKS_DIR, CTX_SCRIPT_NAME);
|
|
20
|
+
const CTX_COMMAND = displayPath(CTX_SCRIPT_DEST);
|
|
17
21
|
|
|
18
22
|
// ANSI helpers
|
|
19
23
|
const green = s => `\x1b[32m${s}\x1b[0m`;
|
|
@@ -34,7 +38,7 @@ function prompt(question) {
|
|
|
34
38
|
|
|
35
39
|
function runCLI(cmd, okPatterns = []) {
|
|
36
40
|
try {
|
|
37
|
-
const out = execSync(cmd, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
41
|
+
const out = execSync(cmd, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], env: CLI_ENV }).trim();
|
|
38
42
|
return { ok: true, output: out };
|
|
39
43
|
} catch (e) {
|
|
40
44
|
const stderr = e.stderr?.trim() || e.message;
|
|
@@ -78,6 +82,7 @@ function copyDirSync(src, dest) {
|
|
|
78
82
|
|
|
79
83
|
async function runInstall({ pluginOnly = false } = {}) {
|
|
80
84
|
console.log(`\n ${bold('claude-code-kanban')} — ${pluginOnly ? 'Plugin installer' : 'Plugin & StatusLine installer'}\n`);
|
|
85
|
+
console.log(` Claude config dir: ${dim(displayPath(CLAUDE_DIR))}\n`);
|
|
81
86
|
let failed = false;
|
|
82
87
|
|
|
83
88
|
// 1. Check prerequisites
|
|
@@ -102,7 +107,7 @@ async function runInstall({ pluginOnly = false } = {}) {
|
|
|
102
107
|
// 2. Copy plugin to stable location & register marketplace
|
|
103
108
|
console.log(`\n Plugin: ${dim(PLUGIN_DEST)}`);
|
|
104
109
|
if (pluginOnly || await prompt(` Install claude-code-kanban plugin? [Y/n] `)) {
|
|
105
|
-
process.stdout.write(
|
|
110
|
+
process.stdout.write(` Copying plugin to ${displayPath(PLUGIN_DEST)}... `);
|
|
106
111
|
try {
|
|
107
112
|
clearFilesRecursive(PLUGIN_DEST);
|
|
108
113
|
copyDirSync(PLUGIN_SRC, PLUGIN_DEST);
|
|
@@ -178,8 +183,7 @@ async function runInstall({ pluginOnly = false } = {}) {
|
|
|
178
183
|
return;
|
|
179
184
|
}
|
|
180
185
|
|
|
181
|
-
const
|
|
182
|
-
const hasCtx = settings.statusLine?.command?.includes('context-status.sh');
|
|
186
|
+
const hasCtx = settings.statusLine?.command?.includes(CTX_SCRIPT_NAME);
|
|
183
187
|
if (hasCtx) {
|
|
184
188
|
console.log(`\n StatusLine: ${green('✓')} Already configured`);
|
|
185
189
|
} else if (!settings.statusLine) {
|
|
@@ -218,6 +222,7 @@ function printSummary(failed = false) {
|
|
|
218
222
|
|
|
219
223
|
async function runUninstall() {
|
|
220
224
|
console.log(`\n ${bold('claude-code-kanban')} — Uninstaller\n`);
|
|
225
|
+
console.log(` Claude config dir: ${dim(displayPath(CLAUDE_DIR))}\n`);
|
|
221
226
|
|
|
222
227
|
// 1. Uninstall plugin via Claude CLI
|
|
223
228
|
process.stdout.write(' Removing plugin... ');
|
|
@@ -260,9 +265,9 @@ async function runUninstall() {
|
|
|
260
265
|
let changed = false;
|
|
261
266
|
|
|
262
267
|
// Strip context-status.sh from statusLine
|
|
263
|
-
if (settings.statusLine?.command?.includes(
|
|
268
|
+
if (settings.statusLine?.command?.includes(CTX_SCRIPT_NAME)) {
|
|
264
269
|
const cmd = settings.statusLine.command;
|
|
265
|
-
const stripped = cmd.replace(
|
|
270
|
+
const stripped = cmd.replace(new RegExp(`\\S*${CTX_SCRIPT_NAME.replace('.', '\\.')}\\s*\\|\\s*`), '').trim();
|
|
266
271
|
if (stripped && stripped !== cmd) {
|
|
267
272
|
settings.statusLine.command = stripped;
|
|
268
273
|
console.log(` StatusLine: ${green('✓')} Restored to "${stripped}"`);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const crypto = require('node:crypto');
|
|
2
|
+
const os = require('node:os');
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
|
|
5
|
+
const DEFAULT_CLAUDE_DIR = path.join(os.homedir(), '.claude');
|
|
6
|
+
|
|
7
|
+
// --name <value> | --name=<value>; null when absent.
|
|
8
|
+
function getArgValue(name) {
|
|
9
|
+
const argv = process.argv;
|
|
10
|
+
const idx = argv.findIndex((arg) => arg === `--${name}` || arg.startsWith(`--${name}=`));
|
|
11
|
+
if (idx === -1) return null;
|
|
12
|
+
const arg = argv[idx];
|
|
13
|
+
if (arg.includes('=')) return arg.split('=').slice(1).join('=');
|
|
14
|
+
return argv[idx + 1] || null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function expandHome(dir) {
|
|
18
|
+
return dir.replace(/^~/, os.homedir());
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// --dir | CLAUDE_CONFIG_DIR | CLAUDE_DIR | ~/.claude
|
|
22
|
+
function getClaudeDir() {
|
|
23
|
+
const dir = getArgValue('dir') || process.env.CLAUDE_CONFIG_DIR || process.env.CLAUDE_DIR;
|
|
24
|
+
return dir ? expandHome(dir) : DEFAULT_CLAUDE_DIR;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isDefaultClaudeDir(dir) {
|
|
28
|
+
return path.resolve(dir) === path.resolve(DEFAULT_CLAUDE_DIR);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// localStorage is per browser origin, so every config dir served from the same port would
|
|
32
|
+
// otherwise share last view, collapsed groups and sidebar layout. The default dir keeps the
|
|
33
|
+
// bare keys; any other dir gets a prefix derived from its resolved path.
|
|
34
|
+
function storageNamespace(dir) {
|
|
35
|
+
if (isDefaultClaudeDir(dir)) return null;
|
|
36
|
+
return `cfg-${crypto.createHash('sha1').update(path.resolve(dir)).digest('hex').slice(0, 8)}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Env for spawning the claude CLI. Claude Code keeps .claude.json beside ~/.claude when
|
|
40
|
+
// CLAUDE_CONFIG_DIR is unset but inside the dir when it is set, so the default dir must
|
|
41
|
+
// stay unset rather than be spelled out.
|
|
42
|
+
function claudeCliEnv(dir) {
|
|
43
|
+
return isDefaultClaudeDir(dir) ? process.env : { ...process.env, CLAUDE_CONFIG_DIR: dir };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Home-relative, forward-slash form for messages and shell commands.
|
|
47
|
+
function displayPath(p) {
|
|
48
|
+
const home = os.homedir();
|
|
49
|
+
const rel = p.startsWith(home) ? `~${p.slice(home.length)}` : p;
|
|
50
|
+
return rel.split(path.sep).join('/');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = { getArgValue, getClaudeDir, claudeCliEnv, displayPath, storageNamespace };
|
package/lib/open-editor.js
CHANGED
|
@@ -221,6 +221,7 @@ function execNoShell(cmd, args, opts = {}) {
|
|
|
221
221
|
let argv = args;
|
|
222
222
|
const spawnOpts = {
|
|
223
223
|
cwd: opts.cwd || undefined,
|
|
224
|
+
env: opts.env,
|
|
224
225
|
timeout: opts.timeout,
|
|
225
226
|
windowsHide: true,
|
|
226
227
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -258,7 +259,9 @@ function execNoShell(cmd, args, opts = {}) {
|
|
|
258
259
|
child.on('error', reject);
|
|
259
260
|
child.on('close', (code, signal) => {
|
|
260
261
|
if (signal) return reject(new Error(`Command terminated by ${signal}: ${stderr.trim()}`));
|
|
261
|
-
|
|
262
|
+
// The claude CLI reports its own failures on stdout, so stderr alone is
|
|
263
|
+
// often empty and the caller would surface only the exit code.
|
|
264
|
+
if (code !== 0) return reject(new Error(stderr.trim() || stdout.trim() || `Command exited with code ${code}`));
|
|
262
265
|
resolve({ stdout, stderr });
|
|
263
266
|
});
|
|
264
267
|
});
|
package/lib/parsers.js
CHANGED
|
@@ -303,14 +303,54 @@ function extractGoalFromLine(data) {
|
|
|
303
303
|
if (data.type === 'attachment' && data.attachment?.type === 'goal_status') {
|
|
304
304
|
return data.attachment.met ? null : { condition: data.attachment.condition || '' };
|
|
305
305
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
306
|
+
// `/goal clear` is a type:system local_command line carrying the command
|
|
307
|
+
// markup in `content`; a user-typed command line carries it in message.content.
|
|
308
|
+
const content = data.content ?? data.message?.content;
|
|
309
|
+
if (typeof content === 'string' && content.includes(GOAL_COMMAND_TAG) && /<command-args>\s*clear/.test(content)) {
|
|
309
310
|
return null;
|
|
310
311
|
}
|
|
311
312
|
return undefined;
|
|
312
313
|
}
|
|
313
314
|
|
|
315
|
+
const GOAL_COMMAND_TAG = '<command-name>/goal</command-name>';
|
|
316
|
+
const GOAL_MARKERS = ['"goal_status"', GOAL_COMMAND_TAG].map((m) => Buffer.from(m));
|
|
317
|
+
const SCAN_CHUNK = 65536;
|
|
318
|
+
const scanBuf = Buffer.allocUnsafe(SCAN_CHUNK);
|
|
319
|
+
|
|
320
|
+
// Streams [from, to) and calls onLine for the complete lines of every chunk
|
|
321
|
+
// that contains a marker. Chunks without one are never decoded, which keeps a
|
|
322
|
+
// full pass close to raw read speed (measured ~4x cheaper than decode+split).
|
|
323
|
+
function scanMarkedLines(fd, from, to, markers, onLine) {
|
|
324
|
+
let carry = Buffer.alloc(0);
|
|
325
|
+
let offset = from;
|
|
326
|
+
while (offset < to) {
|
|
327
|
+
const n = fs.readSync(fd, scanBuf, 0, Math.min(SCAN_CHUNK, to - offset), offset);
|
|
328
|
+
if (n === 0) break;
|
|
329
|
+
offset += n;
|
|
330
|
+
const view = carry.length ? Buffer.concat([carry, scanBuf.subarray(0, n)]) : scanBuf.subarray(0, n);
|
|
331
|
+
const cut = view.lastIndexOf(0x0a) + 1;
|
|
332
|
+
const body = view.subarray(0, cut);
|
|
333
|
+
carry = Buffer.from(view.subarray(cut));
|
|
334
|
+
if (markers.some((m) => body.includes(m))) for (const line of body.toString('utf8').split('\n')) onLine(line);
|
|
335
|
+
}
|
|
336
|
+
if (carry.length && markers.some((m) => carry.includes(m))) onLine(carry.toString('utf8'));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Goal events are rare and can sit anywhere in a long JSONL, outside both the
|
|
340
|
+
// head and tail windows. Returns the last goal update in [from, to), or
|
|
341
|
+
// undefined when there is none.
|
|
342
|
+
function scanGoalInRange(fd, from, to) {
|
|
343
|
+
let latest;
|
|
344
|
+
scanMarkedLines(fd, from, to, GOAL_MARKERS, (line) => {
|
|
345
|
+
if (!line.includes('"goal_status"') && !line.includes(GOAL_COMMAND_TAG)) return;
|
|
346
|
+
try {
|
|
347
|
+
const update = extractGoalFromLine(JSON.parse(line));
|
|
348
|
+
if (update !== undefined) latest = update;
|
|
349
|
+
} catch (_) {}
|
|
350
|
+
});
|
|
351
|
+
return latest;
|
|
352
|
+
}
|
|
353
|
+
|
|
314
354
|
// gitBranch in the JSONL is pinned to the launch-time repo by Claude Code
|
|
315
355
|
// and goes stale once cwd shifts (Bash `cd`, submodule). Callers needing the
|
|
316
356
|
// live branch must resolve it from cwd separately. Cache is reset on inode
|
|
@@ -392,6 +432,12 @@ function readSessionInfoFromJsonl(jsonlPath) {
|
|
|
392
432
|
if (deltaLen > 0) {
|
|
393
433
|
const readLen = Math.min(deltaLen, DELTA_MAX);
|
|
394
434
|
const readStart = stat.size - readLen;
|
|
435
|
+
// The bytes an over-cap delta skips may hold a goal set, met or clear;
|
|
436
|
+
// scanned first so the delta's own lines win by order.
|
|
437
|
+
if (deltaLen > DELTA_MAX) {
|
|
438
|
+
const skipped = scanGoalInRange(fd, cached.scannedUpTo, readStart);
|
|
439
|
+
if (skipped !== undefined) result.goal = skipped;
|
|
440
|
+
}
|
|
395
441
|
const buf = Buffer.alloc(readLen);
|
|
396
442
|
const n = fs.readSync(fd, buf, 0, readLen, readStart);
|
|
397
443
|
if (n > 0) {
|
|
@@ -452,7 +498,11 @@ function readSessionInfoFromJsonl(jsonlPath) {
|
|
|
452
498
|
} catch (e) {}
|
|
453
499
|
}
|
|
454
500
|
if (latestTailCwd) lastCwdSeen = latestTailCwd;
|
|
455
|
-
|
|
501
|
+
// A goal can be set, met or cleared between the head and tail windows.
|
|
502
|
+
// The scan starts at the head's last partial line and runs to EOF, so a
|
|
503
|
+
// tail event is simply seen again in order.
|
|
504
|
+
const goal = tailGoal !== undefined ? tailGoal : scanGoalInRange(fd, offset - Buffer.byteLength(leftover), stat.size);
|
|
505
|
+
if (goal !== undefined) result.goal = goal;
|
|
456
506
|
}
|
|
457
507
|
scannedUpTo = stat.size;
|
|
458
508
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-kanban",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.0",
|
|
4
4
|
"description": "claude-code-kanban dashboard integration: agent activity tracking, context statusline, skills to drive the board from a session and to follow it",
|
|
5
5
|
"experimental": {
|
|
6
6
|
"monitors": "./monitors.json"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# Tracks subagent lifecycle: one append-only JSONL file per agent, grouped by session
|
|
3
|
-
# Layout: ~/.claude
|
|
3
|
+
# Layout: <CLAUDE_CONFIG_DIR or ~/.claude>/.cck/agent-activity/{sessionId}/{agentId}.jsonl
|
|
4
4
|
# Each line is a lifecycle event (start | idle | stop). Server folds last-line-wins.
|
|
5
5
|
|
|
6
6
|
INPUT=$(cat)
|
|
@@ -17,7 +17,7 @@ eval "$(echo "$INPUT" | jq -r '
|
|
|
17
17
|
|
|
18
18
|
[ -z "$SESSION_ID" ] && exit 0
|
|
19
19
|
|
|
20
|
-
CCK_ACTIVITY="$HOME/.claude/.cck/agent-activity"
|
|
20
|
+
CCK_ACTIVITY="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/.cck/agent-activity"
|
|
21
21
|
|
|
22
22
|
# Map session to custom task list on session start
|
|
23
23
|
if [ "$EVENT" = "SessionStart" ]; then
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
# is unchanged when the feature is off), then — only when explicitly enabled and
|
|
5
5
|
# the board's server is alive — waits for a decision file written by the server.
|
|
6
6
|
#
|
|
7
|
-
# Contract (_plans/cck-ui-approvals/decisions.md):
|
|
8
|
-
# marker
|
|
9
|
-
# decision
|
|
10
|
-
# config
|
|
11
|
-
# liveness
|
|
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 approvals.json {enabled, mode, waitSeconds} (D2: fail-open when absent)
|
|
11
|
+
# liveness server.json {port, pid} (D1: a dead board costs nothing)
|
|
12
12
|
#
|
|
13
13
|
# First writer wins (D5): a terminal answer deletes the marker via PostToolUse
|
|
14
14
|
# and this gate exits silently; a decision arriving after the tool already ran
|
|
@@ -37,7 +37,7 @@ KIND="permission"
|
|
|
37
37
|
[ "$TOOL_NAME" = "AskUserQuestion" ] && KIND="question"
|
|
38
38
|
[ "$TOOL_NAME" = "ExitPlanMode" ] && KIND="plan"
|
|
39
39
|
|
|
40
|
-
CCK_DIR="$HOME/.claude/.cck"
|
|
40
|
+
CCK_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/.cck"
|
|
41
41
|
DIR="$CCK_DIR/agent-activity/$SESSION_ID"
|
|
42
42
|
MARKER="$DIR/_waiting.json"
|
|
43
43
|
mkdir -p "$DIR"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# Statusline spy: writes raw context data for kanban dashboard, passes input through
|
|
3
|
-
# Layout: ~/.claude
|
|
3
|
+
# Layout: <CLAUDE_CONFIG_DIR or ~/.claude>/.cck/context-status/{sessionId}.json
|
|
4
4
|
#
|
|
5
5
|
# Usage: pipe before your statusline command:
|
|
6
6
|
# "command": "~/.claude/hooks/context-status.sh | npx -y ccstatusline@latest"
|
|
@@ -9,7 +9,7 @@ INPUT=$(cat)
|
|
|
9
9
|
|
|
10
10
|
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // ""')
|
|
11
11
|
if [ -n "$SESSION_ID" ]; then
|
|
12
|
-
DIR="$HOME/.claude/.cck/context-status"
|
|
12
|
+
DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/.cck/context-status"
|
|
13
13
|
mkdir -p "$DIR"
|
|
14
14
|
echo "$INPUT" > "$DIR/$SESSION_ID.json"
|
|
15
15
|
fi
|
|
@@ -19,7 +19,8 @@ const os = require('os');
|
|
|
19
19
|
const path = require('path');
|
|
20
20
|
|
|
21
21
|
const SESSION_ID = process.env.CLAUDE_CODE_SESSION_ID;
|
|
22
|
-
const
|
|
22
|
+
const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
|
|
23
|
+
const SERVER_INFO = path.join(CLAUDE_DIR, '.cck', 'server.json');
|
|
23
24
|
// The server caps its own wait at 120s. Sitting at the ceiling halves every recurring
|
|
24
25
|
// cost -- handshake, route walk, timer, empty response -- and costs no event latency,
|
|
25
26
|
// because an enqueue wakes the poll immediately.
|