@haposoft/cafekit 0.16.7 → 0.18.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/README.md +14 -22
- package/bin/__tests__/codex-hooks-ownership.test.js +2 -2
- package/bin/__tests__/codex-hooks.test.js +169 -272
- package/bin/__tests__/codex-native.test.js +100 -202
- package/bin/__tests__/develop-contract.test.js +122 -1413
- package/bin/__tests__/docs-sync-on-demand.test.js +93 -0
- package/bin/__tests__/grok-envelope.test.js +0 -56
- package/bin/__tests__/hook-payload.test.js +4 -6
- package/bin/__tests__/legacy-authority-decoupled.test.js +154 -0
- package/bin/__tests__/legacy-hooks-retired.test.js +221 -0
- package/bin/__tests__/omp-bridge.test.js +20 -17
- package/bin/__tests__/omp-hooks.test.js +0 -19
- package/bin/__tests__/optional-skill-inventory.test.js +112 -4
- package/bin/__tests__/orca-session.test.js +0 -2
- package/bin/__tests__/package-inventory.test.js +90 -925
- package/bin/__tests__/runtime-dir.test.js +0 -3
- package/bin/__tests__/runtime-schema.test.js +3 -2
- package/bin/__tests__/skill-routing-source.test.js +27 -15
- package/bin/__tests__/spec-narrowing.test.js +30 -161
- package/bin/__tests__/specs-v2-execution-closeout.test.js +0 -437
- package/bin/__tests__/test-runtime-dependency-closure.cjs +1 -2
- package/bin/__tests__/ui-ux-skill.test.js +101 -0
- package/bin/__tests__/usage-hook-retired.test.js +114 -0
- package/bin/__tests__/validator-grounding.test.js +18 -2949
- package/bin/lib/managed-writer.js +2 -3
- package/bin/phases/codex-runtime.js +27 -0
- package/bin/phases/copy-payload.js +13 -28
- package/bin/phases/omp-runtime.js +25 -0
- package/package.json +2 -3
- package/src/claude/CLAUDE.md +3 -10
- package/src/claude/agents/brainstormer.md +7 -3
- package/src/claude/agents/code-auditor.md +44 -56
- package/src/claude/agents/debugger.md +8 -88
- package/src/claude/agents/deployer.md +3 -5
- package/src/claude/agents/docs-keeper.md +6 -14
- package/src/claude/agents/git-ops.md +2 -4
- package/src/claude/agents/implementer.md +12 -16
- package/src/claude/agents/inspector.md +4 -3
- package/src/claude/agents/project-manager.md +9 -17
- package/src/claude/agents/researcher.md +9 -3
- package/src/claude/agents/spec-maker.md +15 -15
- package/src/claude/agents/test-runner.md +8 -11
- package/src/claude/agents/ui-ux-designer.md +0 -1
- package/src/claude/hooks/__tests__/fixtures/statusline-default.golden +1 -1
- package/src/claude/hooks/__tests__/spec-gate.test.js +244 -333
- package/src/claude/hooks/__tests__/spec-state-touched.test.js +160 -0
- package/src/claude/hooks/__tests__/state.test.js +68 -0
- package/src/claude/hooks/__tests__/statusline.test.js +140 -11
- package/src/claude/hooks/agent.cjs +2 -1
- package/src/claude/hooks/lib/hook-payload.cjs +2 -2
- package/src/claude/hooks/rules.cjs +2 -1
- package/src/claude/hooks/session.cjs +0 -1
- package/src/claude/hooks/spec-gate.cjs +17 -83
- package/src/claude/hooks/spec-state.cjs +142 -39
- package/src/claude/hooks/state.cjs +21 -2
- package/src/claude/migration-manifest.json +42 -22
- package/src/claude/output-styles/coding-level-0-eli5.md +9 -9
- package/src/claude/output-styles/coding-level-1-junior.md +3 -3
- package/src/claude/output-styles/coding-level-2-mid.md +3 -3
- package/src/claude/output-styles/coding-level-3-senior.md +2 -2
- package/src/claude/output-styles/coding-level-4-lead.md +5 -5
- package/src/claude/output-styles/coding-level-5-god.md +6 -6
- package/src/claude/rules/ai-dev-rules.md +2 -3
- package/src/claude/rules/hook-protocols.md +8 -15
- package/src/claude/rules/manage-docs.md +9 -23
- package/src/claude/rules/orchestrator.md +3 -4
- package/src/claude/rules/review-audit-self-decision.md +3 -3
- package/src/claude/rules/skill-domain-routing.md +1 -3
- package/src/claude/rules/skill-workflow-routing.md +4 -5
- package/src/claude/rules/state-sync.md +3 -12
- package/src/claude/rules/workflow.md +11 -19
- package/src/claude/runtime.schema.json +5 -3
- package/src/claude/scripts/generate-skill-catalog.cjs +7 -6
- package/src/claude/scripts/provenance.cjs +23 -5
- package/src/claude/scripts/spec-receipt.cjs +24 -121
- package/src/claude/scripts/spec-resolver.cjs +41 -458
- package/src/claude/scripts/workflow-policy.cjs +3 -1801
- package/src/claude/settings/settings.json +1 -32
- package/src/claude/skills/agent-browser/SKILL.md +1 -1
- package/src/claude/skills/{question → ask}/SKILL.md +9 -75
- package/src/claude/skills/brainstorm/SKILL.md +43 -131
- package/src/claude/skills/chrome-devtools/SKILL.md +14 -31
- package/src/claude/skills/code-review/SKILL.md +29 -81
- package/src/claude/skills/code-review/references/spec-compliance-review.md +4 -8
- package/src/claude/skills/debug/SKILL.md +29 -149
- package/src/claude/skills/develop/SKILL.md +26 -83
- package/src/claude/skills/develop/references/parallel-waves.md +0 -8
- package/src/claude/skills/develop/references/quality-gate.md +15 -13
- package/src/claude/skills/develop/references/subagent-patterns.md +0 -6
- package/src/claude/skills/docs/SKILL.md +2 -2
- package/src/claude/skills/docs/references/update-workflow.md +1 -1
- package/src/claude/skills/fix/SKILL.md +153 -0
- package/src/claude/skills/{hotfix → fix}/references/diagnosis-protocol.md +2 -2
- package/src/claude/skills/{hotfix → fix}/references/parallel-patterns.md +2 -2
- package/src/claude/skills/{hotfix → fix}/references/prevention-gate.md +3 -3
- package/src/claude/skills/{hotfix → fix}/references/review-cycle.md +1 -1
- package/src/claude/skills/git/SKILL.md +48 -9
- package/src/claude/skills/git/references/commit-protocols.md +35 -16
- package/src/claude/skills/git/references/worktree-blueprint.md +42 -6
- package/src/claude/skills/orca/SKILL.md +9 -8
- package/src/claude/skills/research/SKILL.md +17 -46
- package/src/claude/skills/scout/SKILL.md +61 -0
- package/src/claude/skills/{inspect → scout}/references/internal-inspection.md +26 -10
- package/src/claude/skills/specs/SKILL.md +22 -29
- package/src/claude/skills/specs/references/review.md +8 -6
- package/src/claude/skills/specs/references/templates.md +34 -41
- package/src/claude/skills/sync/SKILL.md +16 -45
- package/src/claude/skills/sync/references/rebind-and-audit.md +59 -0
- package/src/claude/skills/sync/references/sync-protocols.md +4 -13
- package/src/claude/skills/test/SKILL.md +39 -104
- package/src/claude/skills/test/references/execution-strategy.md +93 -21
- package/src/claude/skills/test/references/failure-triage.md +1 -1
- package/src/claude/skills/ui-ux/LICENSE +21 -0
- package/src/claude/skills/ui-ux/SKILL.md +497 -0
- package/src/claude/skills/ui-ux/references/brand-tokens.md +90 -0
- package/src/claude/skills/ui-ux/references/budgets.md +113 -0
- package/src/claude/skills/ui-ux/references/checklist.md +271 -0
- package/src/claude/skills/ui-ux/references/components/accordion.md +246 -0
- package/src/claude/skills/ui-ux/references/components/avatar.md +153 -0
- package/src/claude/skills/ui-ux/references/components/banner.md +43 -0
- package/src/claude/skills/ui-ux/references/components/breadcrumb.md +201 -0
- package/src/claude/skills/ui-ux/references/components/button.md +142 -0
- package/src/claude/skills/ui-ux/references/components/card.md +113 -0
- package/src/claude/skills/ui-ux/references/components/charts.md +376 -0
- package/src/claude/skills/ui-ux/references/components/chat.md +132 -0
- package/src/claude/skills/ui-ux/references/components/choice-controls.md +369 -0
- package/src/claude/skills/ui-ux/references/components/comment-thread.md +17 -0
- package/src/claude/skills/ui-ux/references/components/description-list.md +116 -0
- package/src/claude/skills/ui-ux/references/components/empty-state.md +98 -0
- package/src/claude/skills/ui-ux/references/components/file-upload.md +107 -0
- package/src/claude/skills/ui-ux/references/components/inline-edit.md +128 -0
- package/src/claude/skills/ui-ux/references/components/input.md +161 -0
- package/src/claude/skills/ui-ux/references/components/list-row.md +56 -0
- package/src/claude/skills/ui-ux/references/components/loading.md +198 -0
- package/src/claude/skills/ui-ux/references/components/logo.md +147 -0
- package/src/claude/skills/ui-ux/references/components/otp-input.md +43 -0
- package/src/claude/skills/ui-ux/references/components/quantity-input.md +100 -0
- package/src/claude/skills/ui-ux/references/components/range-slider.md +16 -0
- package/src/claude/skills/ui-ux/references/components/small-controls.md +340 -0
- package/src/claude/skills/ui-ux/references/components/sortable-header.md +86 -0
- package/src/claude/skills/ui-ux/references/components/tag-input.md +14 -0
- package/src/claude/skills/ui-ux/references/components/timeline.md +38 -0
- package/src/claude/skills/ui-ux/references/components/tree.md +30 -0
- package/src/claude/skills/ui-ux/references/design-process.md +650 -0
- package/src/claude/skills/ui-ux/references/layouts/app-kanban.html +303 -0
- package/src/claude/skills/ui-ux/references/layouts/app.md +1369 -0
- package/src/claude/skills/ui-ux/references/layouts/form.md +430 -0
- package/src/claude/skills/ui-ux/references/layouts/overlay.md +552 -0
- package/src/claude/skills/ui-ux/references/layouts/pricing.md +204 -0
- package/src/claude/skills/ui-ux/references/locked-rules.md +35 -0
- package/src/claude/skills/ui-ux/references/principles.md +408 -0
- package/src/claude/skills/ui-ux/references/refactor.md +245 -0
- package/src/claude/skills/ui-ux/references/responsive.md +195 -0
- package/src/claude/skills/ui-ux/references/review.md +528 -0
- package/src/claude/skills/ui-ux/references/rules-color.md +623 -0
- package/src/claude/skills/ui-ux/references/rules-form.md +235 -0
- package/src/claude/skills/ui-ux/references/rules-state.md +680 -0
- package/src/claude/skills/ui-ux/references/rules-type.md +369 -0
- package/src/claude/skills/ui-ux/references/styles.md +311 -0
- package/src/claude/skills/ui-ux/references/system.md +219 -0
- package/src/claude/skills/ui-ux/references/tailwind-v4-traps.md +237 -0
- package/src/claude/skills/ui-ux/references/tokens.css +362 -0
- package/src/claude/skills/ui-ux/scripts/lint-skill.mjs +216 -0
- package/src/claude/skills/ui-ux/scripts/package.json +9 -0
- package/src/claude/skills/ui-ux/scripts/probe.mjs +4447 -0
- package/src/claude/skills/ui-ux-pro-max/SKILL.md +25 -80
- package/src/claude/skills/web-testing/SKILL.md +2 -20
- package/src/claude/status.cjs +149 -144
- package/src/codex/AGENTS.md +8 -13
- package/src/codex/hooks/agent.cjs +2 -1
- package/src/codex/hooks/lib/spec-receipt.cjs +4 -32
- package/src/codex/hooks/lib/spec-utils.cjs +6 -52
- package/src/codex/hooks/rules.cjs +2 -1
- package/src/codex/hooks/session.cjs +0 -2
- package/src/codex/hooks/spec-gate.cjs +13 -81
- package/src/codex/hooks/spec-state.cjs +37 -41
- package/src/codex/hooks.json +0 -20
- package/src/codex/rules/state-sync.md +3 -12
- package/src/common/AGENTS.md +6 -12
- package/src/omp/extensions/cafekit-bridge.mjs +6 -6
- package/bin/__tests__/change-firewall.semantic-firewall.test.js +0 -2133
- package/bin/__tests__/finalizer-pass-gate.semantic-firewall.test.js +0 -342
- package/bin/__tests__/release-preflight.semantic-firewall.test.js +0 -586
- package/bin/__tests__/review-receipt-c2.semantic-firewall.test.js +0 -378
- package/bin/__tests__/specs-v2-policy-and-scaffold.test.js +0 -665
- package/bin/__tests__/specs-v2-validator-grounder-contract.test.js +0 -1132
- package/src/claude/hooks/__tests__/completion-authority.test.js +0 -990
- package/src/claude/hooks/__tests__/semantic-review-authority.test.js +0 -259
- package/src/claude/hooks/completion-authority-check.cjs +0 -56
- package/src/claude/hooks/completion-authority-state.cjs +0 -527
- package/src/claude/hooks/completion-authority.cjs +0 -163
- package/src/claude/hooks/lib/context.cjs +0 -616
- package/src/claude/hooks/lib/detect.cjs +0 -474
- package/src/claude/hooks/semantic-review-authority.cjs +0 -154
- package/src/claude/hooks/task-scaffold-guard.cjs +0 -114
- package/src/claude/hooks/usage.cjs +0 -197
- package/src/claude/scripts/change-firewall.cjs +0 -1748
- package/src/claude/scripts/spec-authoring-digest.cjs +0 -151
- package/src/claude/scripts/spec-authoring-validation.cjs +0 -126
- package/src/claude/scripts/spec-final-state.cjs +0 -290
- package/src/claude/scripts/spec-ground.cjs +0 -650
- package/src/claude/scripts/spec-readiness.cjs +0 -425
- package/src/claude/scripts/spec-scaffold.cjs +0 -2317
- package/src/claude/scripts/spec-semantic-model.cjs +0 -396
- package/src/claude/scripts/validate-spec-output.cjs +0 -3941
- package/src/claude/skills/code-review/references/verification-gate.md +0 -46
- package/src/claude/skills/delegate/SKILL.md +0 -124
- package/src/claude/skills/delegate/references/codex-delegation.md +0 -71
- package/src/claude/skills/delegate/references/grok-delegation.md +0 -84
- package/src/claude/skills/hotfix/SKILL.md +0 -289
- package/src/claude/skills/inspect/SKILL.md +0 -142
- package/src/claude/skills/loop/SKILL.md +0 -114
- package/src/claude/skills/loop/references/bounded-loop-protocol.md +0 -104
- package/src/claude/skills/loop/references/metric-and-guard-contract.md +0 -76
- package/src/claude/skills/route/SKILL.md +0 -94
- package/src/claude/skills/route/references/agent-timing.md +0 -46
- package/src/claude/skills/route/references/chaining-patterns.md +0 -53
- package/src/claude/skills/route/references/task-taxonomy.md +0 -48
- package/src/claude/skills/specs/templates/design.md +0 -82
- package/src/claude/skills/specs/templates/requirements-init.md +0 -8
- package/src/claude/skills/specs/templates/requirements.md +0 -42
- package/src/claude/skills/specs/templates/spec-state.json +0 -56
- package/src/claude/skills/specs/templates/task.md +0 -47
- package/src/codex/hooks/completion-authority-check.cjs +0 -67
- package/src/codex/hooks/completion-authority-state.cjs +0 -527
- package/src/codex/hooks/completion-authority.cjs +0 -109
- package/src/codex/hooks/semantic-review-authority.cjs +0 -130
- package/src/codex/hooks/task-scaffold-guard.cjs +0 -63
- /package/src/claude/skills/{question → ask}/templates/question.md +0 -0
- /package/src/claude/skills/{hotfix → fix}/references/escalation-tactics.md +0 -0
- /package/src/claude/skills/{hotfix → fix}/references/workflow-specialized.md +0 -0
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Copyright (c) 2026 Haposoft. MIT License.
|
|
4
|
-
*
|
|
5
|
-
* PreToolUse Hook — task-scaffold-guard.cjs
|
|
6
|
-
* Implements: https://docs.anthropic.com/en/docs/claude-code/hooks
|
|
7
|
-
*
|
|
8
|
-
* Forces spec task files to be GENERATED via spec-scaffold.cjs instead of
|
|
9
|
-
* hand-written. Field tests showed the model dodges the (opt-in) scaffold step
|
|
10
|
-
* and raw-`Write`s every task file — the single biggest output-token cost of a
|
|
11
|
-
* spec run. This hook blocks `Write` to `specs/<feature>/tasks/task-*.md`, so
|
|
12
|
-
* the only path to a task file is: scaffold (creates the stub) -> Edit (fills it).
|
|
13
|
-
*
|
|
14
|
-
* Scope is deliberately narrow — it only ever blocks the `Write` tool on a task
|
|
15
|
-
* file path. `Edit`/`MultiEdit` (filling a stub), and `Write` to any other file
|
|
16
|
-
* (requirements.md, design.md, source code) are never touched. The scaffold
|
|
17
|
-
* script itself writes via Node fs through a Bash call, not the `Write` tool, so
|
|
18
|
-
* it is never blocked.
|
|
19
|
-
*
|
|
20
|
-
* Safety valves:
|
|
21
|
-
* 1. fail-open when spec-scaffold.cjs is absent — you cannot force a tool that
|
|
22
|
-
* is not installed, so a hook shipped without its script must not deadlock.
|
|
23
|
-
* 2. actionable block message — prints the exact scaffold command to run.
|
|
24
|
-
* 3. escape hatch — "spec": { "scaffold_guard": false } in <runtime>/runtime.json.
|
|
25
|
-
*
|
|
26
|
-
* Exit: 0 = allow, 2 = block.
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
const fs = require('fs');
|
|
31
|
-
const path = require('path');
|
|
32
|
-
const { runtimeDirName, runtimeDir, runtimePath } = require('./lib/runtime-dir.cjs');
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Deny the call with a reason every host can read.
|
|
36
|
-
*
|
|
37
|
-
* The reason is the JSON `permissionDecisionReason`, which Claude Code reads natively and
|
|
38
|
-
* grok honours "regardless of exit code"; the same text also goes to stderr, because on
|
|
39
|
-
* an exit-2 denial that is the channel Claude Code feeds back, and grok reads only the
|
|
40
|
-
* first stderr line. Exit 2 stays so a host that reads neither still blocks.
|
|
41
|
-
*/
|
|
42
|
-
function denyWithReason(reason) {
|
|
43
|
-
process.stdout.write(JSON.stringify({
|
|
44
|
-
hookSpecificOutput: {
|
|
45
|
-
hookEventName: 'PreToolUse',
|
|
46
|
-
permissionDecision: 'deny',
|
|
47
|
-
permissionDecisionReason: reason
|
|
48
|
-
}
|
|
49
|
-
}) + '\n');
|
|
50
|
-
process.stderr.write(reason + '\n');
|
|
51
|
-
process.exit(2);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const stdin = fs.readFileSync(0, 'utf8').trim();
|
|
55
|
-
if (!stdin) process.exit(0);
|
|
56
|
-
|
|
57
|
-
const { normalizeHookPayload } = require('./lib/hook-payload.cjs');
|
|
58
|
-
const data = normalizeHookPayload(JSON.parse(stdin));
|
|
59
|
-
const toolName = data.tool_name || '';
|
|
60
|
-
const toolInput = data.tool_input || {};
|
|
61
|
-
const cwd = data.cwd || process.cwd();
|
|
62
|
-
|
|
63
|
-
// Only `Write` can create a file from scratch. Edit/MultiEdit require an
|
|
64
|
-
// existing file, so filling a scaffolded stub is always allowed.
|
|
65
|
-
if (toolName !== 'Write') process.exit(0);
|
|
66
|
-
|
|
67
|
-
const filePath = toolInput.file_path || toolInput.path || '';
|
|
68
|
-
if (!filePath) process.exit(0);
|
|
69
|
-
|
|
70
|
-
// Match a spec task file: .../specs/<feature>/tasks/task-<...>.md
|
|
71
|
-
const norm = filePath.replace(/\\/g, '/');
|
|
72
|
-
const isTaskFile = /(^|\/)specs\/[^/]+\/tasks\/task-[^/]+\.md$/.test(norm);
|
|
73
|
-
if (!isTaskFile) process.exit(0);
|
|
74
|
-
|
|
75
|
-
// Valve 3: explicit escape hatch via <runtime>/runtime.json (fail-closed: a
|
|
76
|
-
// missing/broken runtime.json keeps the guard ON).
|
|
77
|
-
let runtime = {};
|
|
78
|
-
try {
|
|
79
|
-
const rp = runtimePath(cwd, 'runtime.json');
|
|
80
|
-
if (fs.existsSync(rp)) runtime = JSON.parse(fs.readFileSync(rp, 'utf8'));
|
|
81
|
-
} catch { /* malformed runtime.json -> keep guard on */ }
|
|
82
|
-
if (runtime.spec && runtime.spec.scaffold_guard === false) process.exit(0);
|
|
83
|
-
|
|
84
|
-
// Valve 1: fail-open if the scaffold script is not installed next to this hook
|
|
85
|
-
// (hook lives in <runtime>/hooks/, script in <runtime>/scripts/). Forcing a tool
|
|
86
|
-
// that does not exist would deadlock task creation.
|
|
87
|
-
const scaffold = path.join(__dirname, '..', 'scripts', 'spec-scaffold.cjs');
|
|
88
|
-
if (!fs.existsSync(scaffold)) process.exit(0);
|
|
89
|
-
|
|
90
|
-
// Valve 2: block with an actionable message carrying the exact command.
|
|
91
|
-
// NOTE: deliberately does NOT mention the runtime.json escape hatch — an
|
|
92
|
-
// Smoke testing proved the model reads the advertised override and
|
|
93
|
-
// simply disables the guard itself. The hatch stays functional for humans.
|
|
94
|
-
const m = norm.match(/(^|\/)specs\/([^/]+)\/tasks\//);
|
|
95
|
-
const feature = m ? m[2] : '<feature>';
|
|
96
|
-
denyWithReason(
|
|
97
|
-
`TASK SCAFFOLD REQUIRED: task files must be generated, not hand-written.\n` +
|
|
98
|
-
`Blocked Write: ${filePath}\n\n` +
|
|
99
|
-
`Generate the stub(s), then Edit-fill the {{...}} placeholders:\n` +
|
|
100
|
-
` node ${runtimeDirName()}/scripts/spec-scaffold.cjs ${feature} --tasks "R0-01-slug,R1-01-slug,..." --tasks-only\n` +
|
|
101
|
-
`Then use Edit (not Write) on each tasks/task-*.md stub.`
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
} catch (e) {
|
|
105
|
-
// Never let a hook crash block the user — log and fail-open.
|
|
106
|
-
try {
|
|
107
|
-
const fs = require('fs'), p = require('path');
|
|
108
|
-
const d = require('./lib/hook-state-dir.cjs').hookStateDir();
|
|
109
|
-
if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
|
|
110
|
-
fs.appendFileSync(p.join(d, 'hook-log.jsonl'),
|
|
111
|
-
JSON.stringify({ ts: new Date().toISOString(), hook: 'task-scaffold-guard', status: 'crash', error: e.message }) + '\n');
|
|
112
|
-
} catch (_) {}
|
|
113
|
-
process.exit(0);
|
|
114
|
-
}
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Copyright (c) 2026 Haposoft. MIT License.
|
|
4
|
-
*
|
|
5
|
-
* UserPromptSubmit & PostToolUse Hook — usage.cjs
|
|
6
|
-
* Implements: https://docs.anthropic.com/en/docs/claude-code/hooks
|
|
7
|
-
*
|
|
8
|
-
* Fetches Claude Code usage limits from Anthropic OAuth API and writes to cache.
|
|
9
|
-
* Cache is read by status.cjs for statusline display.
|
|
10
|
-
*
|
|
11
|
-
* ⚠ EXPERIMENTAL / UNDOCUMENTED API: api.anthropic.com/api/oauth/usage with the
|
|
12
|
-
* "oauth-2025-04-20" beta header is not a public contract and may break or
|
|
13
|
-
* change shape without notice. Every failure path degrades to
|
|
14
|
-
* status:"unavailable" (statusline hides the segment) — never block on it.
|
|
15
|
-
* Disable entirely with "usage": { "enabled": false } if it misbehaves.
|
|
16
|
-
*
|
|
17
|
-
* Features:
|
|
18
|
-
* - Cross-platform credential retrieval (macOS Keychain, file-based)
|
|
19
|
-
* - Throttled API calls: 1 min (prompt) / 5 min (tool use)
|
|
20
|
-
* - Disable: set "usage": { "enabled": false } in <runtime>/runtime.json
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
// Crash wrapper
|
|
24
|
-
try {
|
|
25
|
-
const fs = require("fs");
|
|
26
|
-
const path = require("path");
|
|
27
|
-
const os = require("os");
|
|
28
|
-
const { execSync } = require("child_process");
|
|
29
|
-
|
|
30
|
-
const { runtimeDirName, runtimeDir, runtimePath } = require('./lib/runtime-dir.cjs');
|
|
31
|
-
function readRuntime(cwd) {
|
|
32
|
-
try {
|
|
33
|
-
const rp = runtimePath(cwd, 'runtime.json');
|
|
34
|
-
if (fs.existsSync(rp)) {
|
|
35
|
-
return JSON.parse(fs.readFileSync(rp, 'utf-8'));
|
|
36
|
-
}
|
|
37
|
-
} catch { /* fail-open */ }
|
|
38
|
-
return {};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Cache configuration
|
|
42
|
-
const USAGE_CACHE_FILE = path.join(os.tmpdir(), "ck-usage-limits-cache.json");
|
|
43
|
-
const CACHE_TTL_MS = 60000; // 60 seconds
|
|
44
|
-
const FETCH_INTERVAL_MS = 300000; // 5 minutes for PostToolUse
|
|
45
|
-
const FETCH_INTERVAL_PROMPT_MS = 60000; // 1 minute for UserPromptSubmit
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Get Claude OAuth credentials (cross-platform)
|
|
49
|
-
*/
|
|
50
|
-
function getClaudeCredentials() {
|
|
51
|
-
// macOS: Try Keychain first
|
|
52
|
-
if (os.platform() === "darwin") {
|
|
53
|
-
try {
|
|
54
|
-
const result = execSync('security find-generic-password -s "Claude Code-credentials" -w', {
|
|
55
|
-
timeout: 5000,
|
|
56
|
-
encoding: "utf-8",
|
|
57
|
-
stdio: ["pipe", "pipe", "ignore"],
|
|
58
|
-
}).trim();
|
|
59
|
-
const parsed = JSON.parse(result);
|
|
60
|
-
if (parsed.claudeAiOauth?.accessToken) {
|
|
61
|
-
return parsed.claudeAiOauth.accessToken;
|
|
62
|
-
}
|
|
63
|
-
} catch {
|
|
64
|
-
// Fallback to file-based
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// File-based credentials (Linux/Windows, or macOS fallback)
|
|
69
|
-
const credPath = path.join(os.homedir(), ".claude", ".credentials.json");
|
|
70
|
-
try {
|
|
71
|
-
const content = fs.readFileSync(credPath, "utf-8");
|
|
72
|
-
const parsed = JSON.parse(content);
|
|
73
|
-
return parsed.claudeAiOauth?.accessToken || null;
|
|
74
|
-
} catch {
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Check if we should fetch (throttled)
|
|
81
|
-
*/
|
|
82
|
-
function shouldFetch(isUserPrompt = false) {
|
|
83
|
-
const interval = isUserPrompt ? FETCH_INTERVAL_PROMPT_MS : FETCH_INTERVAL_MS;
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
if (fs.existsSync(USAGE_CACHE_FILE)) {
|
|
87
|
-
const cache = JSON.parse(fs.readFileSync(USAGE_CACHE_FILE, "utf-8"));
|
|
88
|
-
if (Date.now() - cache.timestamp < interval) {
|
|
89
|
-
return false;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
} catch {}
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Write cache with status (available or unavailable)
|
|
98
|
-
*/
|
|
99
|
-
function writeCache(status, data = null) {
|
|
100
|
-
fs.writeFileSync(
|
|
101
|
-
USAGE_CACHE_FILE,
|
|
102
|
-
JSON.stringify({
|
|
103
|
-
timestamp: Date.now(),
|
|
104
|
-
status,
|
|
105
|
-
data,
|
|
106
|
-
})
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Fetch usage limits from Anthropic OAuth API and write to cache
|
|
112
|
-
* Always writes status to cache (available or unavailable) for statusline fallback
|
|
113
|
-
*/
|
|
114
|
-
async function fetchAndCacheUsageLimits() {
|
|
115
|
-
const token = getClaudeCredentials();
|
|
116
|
-
if (!token) {
|
|
117
|
-
writeCache("unavailable");
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
try {
|
|
122
|
-
const response = await fetch("https://api.anthropic.com/api/oauth/usage", {
|
|
123
|
-
method: "GET",
|
|
124
|
-
headers: {
|
|
125
|
-
Accept: "application/json",
|
|
126
|
-
"Content-Type": "application/json",
|
|
127
|
-
Authorization: `Bearer ${token}`,
|
|
128
|
-
"anthropic-beta": "oauth-2025-04-20",
|
|
129
|
-
"User-Agent": "cafekit/1.0",
|
|
130
|
-
},
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
if (!response.ok) {
|
|
134
|
-
writeCache("unavailable");
|
|
135
|
-
return false;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const data = await response.json();
|
|
139
|
-
writeCache("available", data);
|
|
140
|
-
return true;
|
|
141
|
-
} catch {
|
|
142
|
-
writeCache("unavailable");
|
|
143
|
-
return false;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Main hook execution - just fetch and cache, no injection
|
|
149
|
-
*/
|
|
150
|
-
async function main() {
|
|
151
|
-
// Always allow operation to continue
|
|
152
|
-
const result = { continue: true };
|
|
153
|
-
|
|
154
|
-
try {
|
|
155
|
-
// Read hook input
|
|
156
|
-
let inputStr = "";
|
|
157
|
-
try {
|
|
158
|
-
inputStr = fs.readFileSync(0, "utf-8");
|
|
159
|
-
} catch {}
|
|
160
|
-
|
|
161
|
-
const input = JSON.parse(inputStr || "{}");
|
|
162
|
-
const cwd = input.cwd || process.cwd();
|
|
163
|
-
const runtime = readRuntime(cwd);
|
|
164
|
-
if (runtime.usage?.enabled === false) {
|
|
165
|
-
console.log(JSON.stringify(result));
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Detect hook type
|
|
170
|
-
const isUserPrompt = typeof input.prompt === "string";
|
|
171
|
-
|
|
172
|
-
// Check if we should fetch (throttled)
|
|
173
|
-
if (shouldFetch(isUserPrompt)) {
|
|
174
|
-
await fetchAndCacheUsageLimits();
|
|
175
|
-
}
|
|
176
|
-
} catch {}
|
|
177
|
-
|
|
178
|
-
// Output result (no injection, just continue)
|
|
179
|
-
console.log(JSON.stringify(result));
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
main().catch(() => {
|
|
183
|
-
console.log(JSON.stringify({ continue: true }));
|
|
184
|
-
process.exit(0);
|
|
185
|
-
});
|
|
186
|
-
} catch (e) {
|
|
187
|
-
// Minimal crash logging (zero deps — only Node builtins)
|
|
188
|
-
try {
|
|
189
|
-
const fs = require('fs');
|
|
190
|
-
const p = require('path');
|
|
191
|
-
const logDir = require('./lib/hook-state-dir.cjs').hookStateDir();
|
|
192
|
-
if (!fs.existsSync(logDir)) fs.mkdirSync(logDir, { recursive: true });
|
|
193
|
-
fs.appendFileSync(p.join(logDir, 'hook-log.jsonl'),
|
|
194
|
-
JSON.stringify({ ts: new Date().toISOString(), hook: p.basename(__filename, '.cjs'), status: 'crash', error: e.message }) + '\n');
|
|
195
|
-
} catch (_) {}
|
|
196
|
-
process.exit(0); // fail-open
|
|
197
|
-
}
|