@antonior/claude-code-setup 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Antonio Radosav
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # claude-code-setup
2
+
3
+ One-command installer for my [Claude Code](https://claude.com/claude-code) configuration — hooks, slash commands, skills, statusline, and global rules.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npx @antonior/claude-code-setup
9
+ ```
10
+
11
+ That's it. The installer is idempotent — safe to re-run to pull updates.
12
+
13
+ ## Platform
14
+
15
+ **macOS only.** The hooks and statusline use macOS-native tools (`afplay` for sound, BSD `date -r`, Homebrew for `jq`). On Linux/Windows the installer runs but the sound and statusline-clock features won't work.
16
+
17
+ ## Prerequisites
18
+
19
+ | Tool | Required? | Notes |
20
+ |------|-----------|-------|
21
+ | `jq` | **Yes** | Hooks parse JSON with it. Auto-installed via Homebrew if missing. |
22
+ | `git` | Optional | Commit-scan and verify hooks no-op outside a git repo. |
23
+ | `eslint` / `tsc` | Optional | Auto-fix and verify hooks no-op if not in `node_modules`. |
24
+
25
+ ## What it installs (into `~/.claude/`)
26
+
27
+ - **`CLAUDE.md`** — global rules (trust/integrity, scope discipline, verification). *Skipped if you already have one — merge manually.*
28
+ - **`settings.json`** — permissions, hooks, model/effort/theme. *Smart-merged with any existing file; never overwritten.*
29
+ - **`hooks/`**
30
+ - `caveman-activate.sh` — terse response style on session start
31
+ - `scan-secrets.sh` — blocks `git commit` if staged diff contains secrets/`.env`
32
+ - `check-dep.sh` — prompts to research a dependency before adding it
33
+ - `eslint-fix.sh` — auto `eslint --fix` on edited JS/TS files
34
+ - `stop-verify.sh` — lint + typecheck changed files when a turn ends
35
+ - `notify-sound.sh` — sound on notification/stop
36
+ - **`commands/`** — `/check-dep`, `/debug`, `/scan-secrets` slash commands
37
+ - **`skills/`** — `/mute`, `/unmute`
38
+ - **`statusline.sh`** — git, model, context %, rate-limit statusline
39
+
40
+ ## Optional MCP servers
41
+
42
+ `CLAUDE.md` references two MCP servers that are **not bundled** (they need their own setup). Without them, the related instructions simply don't fire:
43
+
44
+ - **`transcript-search`** — powers the "past conversations are indexed" behaviour
45
+ - **`claude-video-vision`** — video analysis
46
+
47
+ Install these separately if you want the full experience.
48
+
49
+ ## Notes on the shared config
50
+
51
+ Two items from my personal setup are intentionally **stripped** from the published version:
52
+
53
+ - `skipDangerousModePermissionPrompt` — I won't disable a safety prompt on your machine.
54
+ - The video-FPS `UserPromptSubmit` hook — it depends on `python3` and an unbundled MCP server.
55
+
56
+ ## License
57
+
58
+ MIT
package/bin/install.js ADDED
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const { execSync, spawnSync } = require('child_process');
7
+ const os = require('os');
8
+
9
+ const CLAUDE_DIR = path.join(os.homedir(), '.claude');
10
+ const FILES_DIR = path.join(__dirname, '..', 'files');
11
+
12
+ const c = {
13
+ green: (s) => `\x1b[32m${s}\x1b[0m`,
14
+ yellow: (s) => `\x1b[33m${s}\x1b[0m`,
15
+ red: (s) => `\x1b[31m${s}\x1b[0m`,
16
+ bold: (s) => `\x1b[1m${s}\x1b[0m`,
17
+ dim: (s) => `\x1b[2m${s}\x1b[0m`,
18
+ };
19
+
20
+ function log(symbol, msg) { console.log(`${symbol} ${msg}`); }
21
+ function ok(msg) { log(c.green('✓'), msg); }
22
+ function warn(msg) { log(c.yellow('⚠'), msg); }
23
+ function info(msg) { log(c.dim('·'), msg); }
24
+ function fail(msg) { log(c.red('✗'), msg); }
25
+
26
+ function ensureDir(dir) {
27
+ if (!fs.existsSync(dir)) {
28
+ fs.mkdirSync(dir, { recursive: true });
29
+ ok(`Created ${dir}`);
30
+ }
31
+ }
32
+
33
+ function copyFile(src, dest, opts = {}) {
34
+ const exists = fs.existsSync(dest);
35
+ if (exists && opts.skipIfExists) {
36
+ info(`Skipped (exists): ${dest}`);
37
+ return;
38
+ }
39
+ fs.copyFileSync(src, dest);
40
+ ok(`Copied → ${dest}`);
41
+ }
42
+
43
+ function makeExecutable(filePath) {
44
+ fs.chmodSync(filePath, '755');
45
+ }
46
+
47
+ function checkJq() {
48
+ const r = spawnSync('which', ['jq'], { encoding: 'utf8' });
49
+ return r.status === 0;
50
+ }
51
+
52
+ function installJq() {
53
+ const hasBrew = spawnSync('which', ['brew'], { encoding: 'utf8' }).status === 0;
54
+ if (!hasBrew) {
55
+ fail('jq not found and Homebrew not installed.');
56
+ console.log(c.yellow(' Install jq manually: https://stedolan.github.io/jq/download/'));
57
+ return false;
58
+ }
59
+ console.log(c.yellow(' Installing jq via Homebrew...'));
60
+ const r = spawnSync('brew', ['install', 'jq'], { stdio: 'inherit' });
61
+ if (r.status !== 0) {
62
+ fail('brew install jq failed. Install manually.');
63
+ return false;
64
+ }
65
+ ok('jq installed');
66
+ return true;
67
+ }
68
+
69
+ function mergeSettings(existingPath, incomingPath) {
70
+ const existing = JSON.parse(fs.readFileSync(existingPath, 'utf8'));
71
+ const incoming = JSON.parse(fs.readFileSync(incomingPath, 'utf8'));
72
+
73
+ // Deep merge: scalars from incoming win, arrays are union-merged by value
74
+ function mergeArrays(a, b) {
75
+ const seen = new Set(a.map((x) => JSON.stringify(x)));
76
+ const result = [...a];
77
+ for (const item of b) {
78
+ const key = JSON.stringify(item);
79
+ if (!seen.has(key)) {
80
+ seen.add(key);
81
+ result.push(item);
82
+ }
83
+ }
84
+ return result;
85
+ }
86
+
87
+ function deepMerge(base, override) {
88
+ const out = { ...base };
89
+ for (const [k, v] of Object.entries(override)) {
90
+ if (Array.isArray(v) && Array.isArray(base[k])) {
91
+ out[k] = mergeArrays(base[k], v);
92
+ } else if (v && typeof v === 'object' && !Array.isArray(v) && base[k] && typeof base[k] === 'object') {
93
+ out[k] = deepMerge(base[k], v);
94
+ } else {
95
+ out[k] = v;
96
+ }
97
+ }
98
+ return out;
99
+ }
100
+
101
+ return deepMerge(existing, incoming);
102
+ }
103
+
104
+ function main() {
105
+ console.log(c.bold('\nclaude-code-setup — installing Antonio\'s Claude config\n'));
106
+
107
+ // 1. Check jq
108
+ if (!checkJq()) {
109
+ warn('jq not found — required by hooks');
110
+ const ok2 = installJq();
111
+ if (!ok2) process.exit(1);
112
+ } else {
113
+ ok('jq present');
114
+ }
115
+
116
+ // 2. Ensure dirs
117
+ ensureDir(CLAUDE_DIR);
118
+ ensureDir(path.join(CLAUDE_DIR, 'hooks'));
119
+ ensureDir(path.join(CLAUDE_DIR, 'skills'));
120
+ ensureDir(path.join(CLAUDE_DIR, 'commands'));
121
+
122
+ // 3. Hooks
123
+ const hooks = ['caveman-activate.sh', 'check-dep.sh', 'eslint-fix.sh', 'notify-sound.sh', 'scan-secrets.sh', 'stop-verify.sh'];
124
+ for (const h of hooks) {
125
+ const dest = path.join(CLAUDE_DIR, 'hooks', h);
126
+ copyFile(path.join(FILES_DIR, 'hooks', h), dest);
127
+ makeExecutable(dest);
128
+ }
129
+
130
+ // 4. Skills
131
+ for (const s of ['mute.md', 'unmute.md']) {
132
+ copyFile(path.join(FILES_DIR, 'skills', s), path.join(CLAUDE_DIR, 'skills', s), { skipIfExists: true });
133
+ }
134
+
135
+ // 4b. Commands (slash commands referenced by CLAUDE.md)
136
+ for (const cmd of ['check-dep.md', 'debug.md', 'scan-secrets.md']) {
137
+ copyFile(path.join(FILES_DIR, 'commands', cmd), path.join(CLAUDE_DIR, 'commands', cmd), { skipIfExists: true });
138
+ }
139
+
140
+ // 5. Statusline
141
+ const statuslineDest = path.join(CLAUDE_DIR, 'statusline.sh');
142
+ copyFile(path.join(FILES_DIR, 'statusline.sh'), statuslineDest);
143
+ makeExecutable(statuslineDest);
144
+
145
+ // 6. CLAUDE.md
146
+ const claudeMdDest = path.join(CLAUDE_DIR, 'CLAUDE.md');
147
+ if (fs.existsSync(claudeMdDest)) {
148
+ warn('CLAUDE.md already exists — skipped (edit manually if needed)');
149
+ info(` Your file: ${claudeMdDest}`);
150
+ info(` Reference: ${path.join(FILES_DIR, 'CLAUDE.md')}`);
151
+ } else {
152
+ copyFile(path.join(FILES_DIR, 'CLAUDE.md'), claudeMdDest);
153
+ }
154
+
155
+ // 7. settings.json — smart merge
156
+ const settingsDest = path.join(CLAUDE_DIR, 'settings.json');
157
+ if (fs.existsSync(settingsDest)) {
158
+ info('Merging settings.json...');
159
+ try {
160
+ const merged = mergeSettings(settingsDest, path.join(FILES_DIR, 'settings.json'));
161
+ fs.writeFileSync(settingsDest, JSON.stringify(merged, null, 2) + '\n');
162
+ ok('settings.json merged');
163
+ } catch (e) {
164
+ fail(`settings.json merge failed: ${e.message}`);
165
+ warn('Manual merge needed — reference file at: ' + path.join(FILES_DIR, 'settings.json'));
166
+ }
167
+ } else {
168
+ copyFile(path.join(FILES_DIR, 'settings.json'), settingsDest);
169
+ }
170
+
171
+ console.log(c.bold(c.green('\nDone. Restart Claude Code to apply.\n')));
172
+ }
173
+
174
+ main();
@@ -0,0 +1,53 @@
1
+ # Global rules
2
+
3
+ Applies to all projects. The current user and any project CLAUDE.md override this.
4
+ (Communication style is handled by the caveman SessionStart hook — not repeated here.)
5
+
6
+ ## Trust & integrity (non-negotiable)
7
+ - Never claim tests pass without running them. Show the output.
8
+ - Never delete, weaken, or skip tests to go green — no `toBe`→`toBeTruthy`, no `.skip`, no commenting-out.
9
+ - Never suppress instead of fix: no `eslint-disable`, no `@ts-ignore`, no swallowed `catch`.
10
+ - Never claim "done" without self-review and verification. A score like "100/100" needs proof.
11
+ - Be honest about failures. Surface them; never hide them.
12
+
13
+ ## Scope discipline
14
+ - Only change what's requested or clearly necessary. No drive-by refactors, renames, or "improvements".
15
+ - Don't add comments, types, or docstrings to code you didn't change.
16
+ - Spot something else worth doing? Mention it, don't silently do it. Ask before expanding scope.
17
+ - Three plain lines beat a premature abstraction. Don't build for hypothetical futures.
18
+
19
+ ## Best, not easiest
20
+ - My implementation effort is not a valid constraint — recommend the correct solution, not the convenient one.
21
+ - Push back when I'm wrong. Don't fold under pushback unless genuinely convinced, and say so explicitly.
22
+
23
+ ## Ask vs act
24
+ - Reversible (read, edit, run tests): just do it.
25
+ - Irreversible (delete files/branches, force push, reset, drop data): ask first.
26
+ - New patterns/dirs/conventions or new dependencies: propose first (`/check-dep` for deps).
27
+
28
+ ## Verify — grep points, reading proves
29
+ - After a grep, open and read sample matches before trusting the count.
30
+ - Numeric or identifier claims: cite a specific `file:line` you actually read. "Verified against codebase" is not evidence.
31
+ - One pattern finding nothing ≠ the thing doesn't exist (could be a workspace package or different syntax). Try bare-word, declaration, and import patterns.
32
+
33
+ ## Past conversations
34
+ - Don't say "I don't remember" — past sessions are indexed.
35
+ - When the user references prior work ("we talked about", "remember when", "last time"), call `mcp__transcript-search__search` first, then `get_context` on the hit.
36
+
37
+ ## Debugging
38
+ - Never guess or try random fixes. Read the error, trace the data flow, find the root cause, then fix. Use `/debug` when stuck.
39
+
40
+ ## Dates
41
+ - Don't guess today's date or weekday maths. Run `date` when it matters.
42
+
43
+ ## Plan mode
44
+ - Ask clarifying questions until ≥97% confident you fully understand the requirement. Don't proceed until there.
45
+ - Then automatically, in order: (1) deeply understand the problem, (2) evaluate trade-offs, (3) make a decision, (4) explain the reasoning.
46
+ - Never skip or compress these steps unless explicitly told to.
47
+
48
+ ## Context management
49
+ - Run `/compact` proactively when context hits 60%. Don't wait to be asked.
50
+
51
+ ## Misc
52
+ - British English in prose (behaviour, colour, licence) — HL is UK.
53
+ - Skills to use proactively: `/scan-secrets` (auto before commits), `/check-dep` (before adding deps), `/debug` (when stuck).
@@ -0,0 +1,52 @@
1
+ Research a dependency before adding it to the project.
2
+
3
+ The user will provide a package name, or you should use this skill proactively before running `yarn add`, `npm install`, or adding any new dependency.
4
+
5
+ ## Process
6
+
7
+ 1. **Check if it's already installed:**
8
+ ```bash
9
+ grep "package-name" package.json
10
+ ```
11
+
12
+ 2. **Research the package:**
13
+ - Search npm for the package: `npm view <package> description version license homepage`
14
+ - Check bundle size: search bundlephobia.com or bundlejs.com
15
+ - Check download stats and maintenance: last publish date, open issues, weekly downloads
16
+ - Check if it's actively maintained (last commit, release frequency)
17
+
18
+ 3. **Evaluate alternatives:**
19
+ - Are there lighter alternatives?
20
+ - Can this be done with existing dependencies already in the project?
21
+ - Can this be done with a few lines of code instead of a dependency?
22
+ - For React Native: does it require native linking? Does it support both iOS and Android?
23
+
24
+ 4. **Check compatibility:**
25
+ - Does it work with the project's current versions (React, React Native, Node)?
26
+ - Any known issues with the current stack?
27
+ - Does it have TypeScript types (built-in or `@types/`)?
28
+
29
+ 5. **Present findings to the user:**
30
+ ```
31
+ Package: <name>
32
+ Version: <latest>
33
+ Size: <minified + gzipped>
34
+ Last published: <date>
35
+ Weekly downloads: <count>
36
+ Licence: <licence>
37
+ Types: <built-in / @types / none>
38
+ Native linking: <yes/no> (React Native only)
39
+ Alternatives: <list>
40
+ Recommendation: <add / use alternative / write it yourself>
41
+ ```
42
+
43
+ 6. **Only proceed with installation after user approval.**
44
+
45
+ ## Red flags (warn the user)
46
+ - Last published over 12 months ago
47
+ - Fewer than 1,000 weekly downloads
48
+ - No TypeScript support
49
+ - Licence incompatible with the project
50
+ - Large bundle size for what it does
51
+ - Requires native linking for a simple feature
52
+ - Many open issues with no maintainer responses
@@ -0,0 +1,26 @@
1
+ Systematic debugging. Never guess — diagnose. Follow these steps in order; do not jump to a fix before the root cause is understood.
2
+
3
+ ## Steps
4
+
5
+ 1. **Capture the full failure.** Exact error message, full stack trace, the command run, and what changed recently (`git diff`, `git log`, recent edits). Don't paraphrase the error — read it literally.
6
+
7
+ 2. **Read the error.** The answer is often right there. Read the whole message, including the lines you'd usually skip. Note the exact file, line, and value mentioned.
8
+
9
+ 3. **Reproduce reliably.** Find the smallest, most consistent way to trigger the failure. If it's intermittent, identify what varies between pass and fail (state, timing, env, data).
10
+
11
+ 4. **Trace the data flow.** Work backwards from the failure point. Where did the bad value come from? Follow it up the call chain until you find where it first went wrong — that's the cause, not the crash site.
12
+
13
+ 5. **Form a hypothesis.** State a specific, testable cause: "X is null because Y returns undefined when Z." Predict what you'd see if it's true.
14
+
15
+ 6. **Test the hypothesis.** Add a log, a breakpoint, or a tiny experiment that confirms or refutes it. If refuted, return to step 4 — don't patch around it.
16
+
17
+ 7. **Fix with understanding.** Only now change code, and only the root cause. You must be able to explain *why* the fix works. "I don't know why this works" is not acceptable.
18
+
19
+ 8. **Verify.** Reproduce the original trigger and confirm it's resolved. Run the relevant tests. Check you introduced no regressions.
20
+
21
+ ## Red flags (you're guessing, not debugging)
22
+ - Making changes with no clear reason
23
+ - Rapid successive attempts ("try this... no, try this...")
24
+ - A fix you can't explain
25
+ - Reverting versions hoping it helps
26
+ - Suppressing the error (`try/catch` swallow, `@ts-ignore`, `eslint-disable`) instead of fixing it
@@ -0,0 +1,40 @@
1
+ Scan all staged files for secrets, PII, and sensitive data before committing.
2
+
3
+ ## What to scan for
4
+
5
+ ### Secrets
6
+ - API keys, tokens, passwords, licence keys
7
+ - Private SSH keys (`id_rsa`, `id_ed25519`, `*.pem`, `*.key`)
8
+ - Certificates and keystores (`*.p12`, `*.pfx`, `*.keystore`)
9
+ - Database connection strings with credentials
10
+ - Bearer tokens, `sk-` prefixed keys, `pk_` prefixed keys
11
+ - This repo specifically: `GRAPHQL_SCHEMA_REPO_PAT` (Harness PAT), `CONTENTFUL_PERSONALISATION_API_KEY_*`, anything from `.env` (never commit `.env`)
12
+
13
+ ### PII (Personally Identifiable Information)
14
+ - Email addresses (replace with `user@example.com` or placeholders)
15
+ - Phone numbers, physical addresses
16
+ - Corporate tenant IDs, user OIDs, OAuth URLs with personal identifiers
17
+ - Device fingerprints, hardware IDs, telemetry data
18
+ - IP addresses (internal or personal)
19
+ - Client numbers, access tokens (`x-hl-access-token`, `x-hl-client-number`)
20
+
21
+ ## Process
22
+
23
+ 1. Run these checks against staged files:
24
+
25
+ ```bash
26
+ # Check for secrets
27
+ git diff --cached | grep -iE "api.key|secret|token|password|Bearer|sk-|pk_|PRIVATE KEY|PAT" && echo "⚠️ SECRETS FOUND" || echo "✅ No secrets"
28
+
29
+ # Check for PII (emails, IPs)
30
+ git diff --cached | grep -iE "@[a-z]+\.(com|co\.uk|org|net|io)|[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" && echo "⚠️ PII FOUND" || echo "✅ No PII"
31
+
32
+ # Check no .env is staged
33
+ git diff --cached --name-only | grep -E "(^|/)\.env" && echo "⚠️ .env STAGED" || echo "✅ No .env staged"
34
+ ```
35
+
36
+ 2. For each finding, determine if it's a real secret/PII or a false positive (e.g. `example.com` in docs, or a fixture in `__mocks__`/test files is usually fine).
37
+ 3. Report all real findings with file paths and line numbers.
38
+ 4. **Do not proceed with the commit** if real secrets or PII are found. Fix them first.
39
+
40
+ > Note: AI-reference scanning is intentionally omitted — this environment requires commits to end with a `Co-Authored-By: Claude` trailer.
@@ -0,0 +1,18 @@
1
+ #!/bin/sh
2
+ # Caveman mode — inject rules as system context on session start
3
+ cat <<'CAVEMAN'
4
+ Respond terse like smart caveman. All technical substance stay. Only fluff die.
5
+
6
+ ACTIVE EVERY RESPONSE. No revert. Off only: "stop caveman" / "normal mode".
7
+
8
+ Rules — Drop: articles (a/an/the), filler (just/really/basically/actually/simply), pleasantries (sure/certainly/of course/happy to), hedging. Fragments OK. Short synonyms. Technical terms exact. Code blocks unchanged.
9
+
10
+ Pattern: [thing] [action] [reason]. [next step].
11
+
12
+ Not: "Sure! I'd be happy to help you with that. The issue you're experiencing is likely caused by..."
13
+ Yes: "Bug in auth middleware. Token expiry check use < not <=. Fix:"
14
+
15
+ Auto-clarity: revert to normal prose for security warnings, irreversible action confirmations, ambiguous multi-step sequences. Resume caveman after.
16
+
17
+ Code/commits/PRs: write normal.
18
+ CAVEMAN
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env bash
2
+ # PreToolUse(Bash) hook — when a command ADDS a dependency, return permission
3
+ # decision "ask" with a reminder to research it first (/check-dep). Bare installs
4
+ # that just restore existing deps (yarn install, npm ci, npm install) do NOT trigger.
5
+ set -uo pipefail
6
+
7
+ input=$(cat)
8
+ cmd=$(printf '%s' "$input" | jq -r '.tool_input.command // ""' 2>/dev/null)
9
+
10
+ has_pkg() {
11
+ local s="$1" rest tok
12
+ case "$s" in
13
+ *"yarn add "*) rest="${s#*yarn add }" ;;
14
+ *"pnpm add "*) rest="${s#*pnpm add }" ;;
15
+ *"npm install "*) rest="${s#*npm install }" ;;
16
+ *"npm i "*) rest="${s#*npm i }" ;;
17
+ *"npm add "*) rest="${s#*npm add }" ;;
18
+ *) return 1 ;;
19
+ esac
20
+ for tok in $rest; do
21
+ case "$tok" in
22
+ "&&"|";"|"|") break ;; # stop at the next command
23
+ -*) ;; # flag — skip
24
+ *) return 0 ;; # a real package argument
25
+ esac
26
+ done
27
+ return 1
28
+ }
29
+
30
+ if has_pkg "$cmd"; then
31
+ printf '%s\n' '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"Adding a dependency. Run /check-dep first — bundle size, maintenance, RN native-linking, lighter alternatives — before approving."}}'
32
+ fi
33
+ exit 0
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env bash
2
+ # PostToolUse(Write|Edit) — auto eslint --fix the edited JS/TS file, best-effort.
3
+ # Silently no-ops if the file isn't lintable or eslint isn't installed.
4
+ set -uo pipefail
5
+
6
+ input=$(cat)
7
+ f=$(printf '%s' "$input" | jq -r '.tool_input.file_path // .tool_response.filePath // empty' 2>/dev/null)
8
+ [ -z "$f" ] && exit 0
9
+
10
+ case "$f" in
11
+ *.ts|*.tsx|*.js|*.jsx|*.cjs|*.mjs) ;;
12
+ *) exit 0 ;;
13
+ esac
14
+ [ -f "$f" ] || exit 0
15
+
16
+ # Walk up to the nearest node_modules/.bin/eslint
17
+ dir=$(dirname "$f")
18
+ eslint=""
19
+ while [ "$dir" != "/" ] && [ -n "$dir" ]; do
20
+ if [ -x "$dir/node_modules/.bin/eslint" ]; then
21
+ eslint="$dir/node_modules/.bin/eslint"
22
+ break
23
+ fi
24
+ dir=$(dirname "$dir")
25
+ done
26
+ [ -z "$eslint" ] && exit 0 # eslint not installed -> skip silently
27
+
28
+ "$eslint" --fix --no-error-on-unmatched-pattern "$f" >/dev/null 2>&1 || true
29
+ exit 0
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+ [ -f "/tmp/claude-nosound" ] && exit 0
3
+ afplay "${1:-/System/Library/Sounds/Glass.aiff}" >/dev/null 2>&1
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env bash
2
+ # PreToolUse(Bash) hook — block `git commit` when staged changes contain
3
+ # high-confidence secrets or a real .env file. Self-filters: exits 0 (allow)
4
+ # for any command that isn't a git commit, and for clean commits.
5
+ set -uo pipefail
6
+
7
+ input=$(cat)
8
+ cmd=$(printf '%s' "$input" | jq -r '.tool_input.command // ""' 2>/dev/null)
9
+
10
+ # Only act on git commit
11
+ case "$cmd" in
12
+ *"git commit"*) ;;
13
+ *) exit 0 ;;
14
+ esac
15
+
16
+ # Scan only ADDED lines in the staged diff (ignore context/removed lines + the +++ header)
17
+ added=$(git diff --cached --unified=0 2>/dev/null | grep -E '^\+[^+]' || true)
18
+
19
+ findings=""
20
+
21
+ # High-confidence secret formats (real key shapes, not generic words)
22
+ secret_re='-----BEGIN [A-Z ]*PRIVATE KEY-----|sk-[A-Za-z0-9]{16,}|sk_(live|test)_[A-Za-z0-9]{16,}|gh[pousr]_[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16}|xox[baprs]-[A-Za-z0-9-]{10,}|eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{6,}'
23
+ hits=$(printf '%s' "$added" | grep -nEi -- "$secret_re" | head -20 || true)
24
+ if [ -n "$hits" ]; then
25
+ findings="${findings}Secret-like strings in staged additions:\n${hits}\n\n"
26
+ fi
27
+
28
+ # Staged .env files (allow .sample / .template / .example)
29
+ envfiles=$(git diff --cached --name-only --diff-filter=A 2>/dev/null \
30
+ | grep -E '(^|/)\.env([.][A-Za-z0-9_-]+)?$' \
31
+ | grep -vE '\.(sample|template|example|dist)$' || true)
32
+ if [ -n "$envfiles" ]; then
33
+ findings="${findings}Staged .env file(s):\n${envfiles}\n"
34
+ fi
35
+
36
+ if [ -n "$findings" ]; then
37
+ printf 'BLOCKED: possible secrets in staged commit.\n\n%b\nRedact or unstage them before committing. If this is a genuine false positive, run the commit yourself in the terminal.\n' "$findings" >&2
38
+ exit 2
39
+ fi
40
+ exit 0
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env bash
2
+ # Stop hook (async + asyncRewake) — when a turn ends, verify the changed TS files.
3
+ # Runs eslint on changed files and tsc --noEmit, but only fails if a problem
4
+ # touches a file you actually changed. Exit 2 wakes the model to fix it.
5
+ # No-ops fast when: not a git repo, no changed .ts/.tsx files, or tools absent.
6
+ set -uo pipefail
7
+
8
+ cat >/dev/null 2>&1 || true # drain stdin
9
+
10
+ repo=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
11
+ cd "$repo" 2>/dev/null || exit 0
12
+
13
+ files=$( { git diff --name-only --diff-filter=ACM 2>/dev/null; \
14
+ git diff --cached --name-only --diff-filter=ACM 2>/dev/null; \
15
+ git ls-files --others --exclude-standard 2>/dev/null; } \
16
+ | grep -Ei '\.(ts|tsx)$' | sort -u )
17
+ [ -z "$files" ] && exit 0 # nothing changed -> nothing to verify
18
+
19
+ eslint="$repo/node_modules/.bin/eslint"
20
+ tsc="$repo/node_modules/.bin/tsc"
21
+ out=""
22
+ fail=0
23
+
24
+ # 1) ESLint on changed files only (scoped, fast, high signal)
25
+ if [ -x "$eslint" ]; then
26
+ emsg=$(printf '%s\n' "$files" | tr '\n' '\0' \
27
+ | xargs -0 "$eslint" --no-error-on-unmatched-pattern 2>&1) || {
28
+ fail=1
29
+ out="${out}ESLint on changed files:\n${emsg}\n\n"
30
+ }
31
+ fi
32
+
33
+ # 2) tsc --noEmit project-wide, but report only errors in YOUR changed files
34
+ if [ -x "$tsc" ]; then
35
+ tmsg=$("$tsc" --noEmit 2>&1) || true
36
+ pat=$(printf '%s\n' "$files" | sed 's/[.]/\\./g' | paste -sd'|' -)
37
+ if [ -n "$pat" ]; then
38
+ myerr=$(printf '%s\n' "$tmsg" | grep -E "($pat)\(" || true)
39
+ if [ -n "$myerr" ]; then
40
+ fail=1
41
+ out="${out}tsc --noEmit (your files):\n${myerr}\n"
42
+ fi
43
+ fi
44
+ fi
45
+
46
+ if [ "$fail" -eq 1 ]; then
47
+ printf 'Verification failed on files you changed:\n\n%b\nFix these before claiming done.\n' "$out" >&2
48
+ exit 2
49
+ fi
50
+ exit 0
@@ -0,0 +1,149 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(yarn:*)",
5
+ "Bash(pod:*)",
6
+ "Bash(xcrun:*)",
7
+ "Bash(xcodebuild:*)",
8
+ "Bash(jest:*)",
9
+ "Bash(tsc:*)",
10
+ "Bash(adb:*)",
11
+ "Bash(git status:*)",
12
+ "Bash(git diff:*)",
13
+ "Bash(git log:*)",
14
+ "Bash(git show:*)",
15
+ "Bash(git branch:*)",
16
+ "Bash(git rev-parse:*)",
17
+ "Bash(git remote -v)",
18
+ "Bash(git stash list:*)",
19
+ "Bash(git add *)",
20
+ "Bash(git commit *)",
21
+ "Bash(ls:*)",
22
+ "Bash(cat:*)",
23
+ "Bash(head:*)",
24
+ "Bash(tail:*)",
25
+ "Bash(wc:*)",
26
+ "Bash(grep:*)",
27
+ "Bash(rg:*)",
28
+ "Bash(find:*)",
29
+ "Bash(which:*)",
30
+ "Bash(file:*)",
31
+ "Bash(jq:*)",
32
+ "Bash(sw_vers:*)",
33
+ "Bash(date)",
34
+ "Bash(npm view:*)",
35
+ "Bash(npm ls:*)"
36
+ ]
37
+ },
38
+ "model": "sonnet",
39
+ "hooks": {
40
+ "SessionStart": [
41
+ {
42
+ "matcher": "",
43
+ "hooks": [
44
+ {
45
+ "type": "command",
46
+ "command": "~/.claude/hooks/caveman-activate.sh",
47
+ "timeout": 5
48
+ }
49
+ ]
50
+ }
51
+ ],
52
+ "PreToolUse": [
53
+ {
54
+ "matcher": "Bash",
55
+ "hooks": [
56
+ {
57
+ "type": "command",
58
+ "command": "~/.claude/hooks/scan-secrets.sh",
59
+ "if": "Bash(git commit*)",
60
+ "timeout": 15,
61
+ "statusMessage": "Scanning staged changes for secrets"
62
+ },
63
+ {
64
+ "type": "command",
65
+ "command": "~/.claude/hooks/check-dep.sh",
66
+ "if": "Bash(yarn add*)",
67
+ "timeout": 10
68
+ },
69
+ {
70
+ "type": "command",
71
+ "command": "~/.claude/hooks/check-dep.sh",
72
+ "if": "Bash(npm install *)",
73
+ "timeout": 10
74
+ },
75
+ {
76
+ "type": "command",
77
+ "command": "~/.claude/hooks/check-dep.sh",
78
+ "if": "Bash(npm i *)",
79
+ "timeout": 10
80
+ },
81
+ {
82
+ "type": "command",
83
+ "command": "~/.claude/hooks/check-dep.sh",
84
+ "if": "Bash(npm add*)",
85
+ "timeout": 10
86
+ },
87
+ {
88
+ "type": "command",
89
+ "command": "~/.claude/hooks/check-dep.sh",
90
+ "if": "Bash(pnpm add*)",
91
+ "timeout": 10
92
+ }
93
+ ]
94
+ }
95
+ ],
96
+ "PostToolUse": [
97
+ {
98
+ "matcher": "Write|Edit",
99
+ "hooks": [
100
+ {
101
+ "type": "command",
102
+ "command": "~/.claude/hooks/eslint-fix.sh",
103
+ "timeout": 30
104
+ }
105
+ ]
106
+ }
107
+ ],
108
+ "Notification": [
109
+ {
110
+ "matcher": "",
111
+ "hooks": [
112
+ {
113
+ "type": "command",
114
+ "command": "~/.claude/hooks/notify-sound.sh /System/Library/Sounds/Ping.aiff",
115
+ "timeout": 5
116
+ }
117
+ ]
118
+ }
119
+ ],
120
+ "Stop": [
121
+ {
122
+ "hooks": [
123
+ {
124
+ "type": "command",
125
+ "command": "~/.claude/hooks/notify-sound.sh /System/Library/Sounds/Glass.aiff",
126
+ "timeout": 5
127
+ },
128
+ {
129
+ "type": "command",
130
+ "command": "~/.claude/hooks/stop-verify.sh",
131
+ "timeout": 180,
132
+ "statusMessage": "Verifying changed files (lint + types)",
133
+ "async": true,
134
+ "asyncRewake": true,
135
+ "rewakeSummary": "Verification failed"
136
+ }
137
+ ]
138
+ }
139
+ ]
140
+ },
141
+ "statusLine": {
142
+ "type": "command",
143
+ "command": "~/.claude/statusline.sh"
144
+ },
145
+ "effortLevel": "xhigh",
146
+ "theme": "dark-ansi",
147
+ "autoCompactEnabled": true,
148
+ "advisorModel": "opus"
149
+ }
@@ -0,0 +1,7 @@
1
+ ---
2
+ name: mute
3
+ description: Mute Claude sound notifications for this session
4
+ ---
5
+
6
+ Run `touch /tmp/claude-nosound` via Bash.
7
+ Reply exactly: "Sound off. /unmute to restore."
@@ -0,0 +1,7 @@
1
+ ---
2
+ name: unmute
3
+ description: Re-enable Claude sound notifications
4
+ ---
5
+
6
+ Run `rm -f /tmp/claude-nosound` via Bash.
7
+ Reply exactly: "Sound on."
@@ -0,0 +1,94 @@
1
+ #!/bin/sh
2
+ input=$(cat)
3
+
4
+ R=$(printf '\033[0m')
5
+ G=$(printf '\033[1;32m')
6
+ C=$(printf '\033[0;36m')
7
+ B=$(printf '\033[1;34m')
8
+ RE=$(printf '\033[0;31m')
9
+ YE=$(printf '\033[0;33m')
10
+ GR=$(printf '\033[0;32m')
11
+ PU=$(printf '\033[0;35m')
12
+ DIM=$(printf '\033[2m')
13
+ SEP="${R}${DIM} | ${R}"
14
+
15
+ # Dir + git
16
+ dir=$(echo "$input" | jq -r '.workspace.current_dir // .cwd // empty' 2>/dev/null)
17
+ [ -z "$dir" ] && dir="$HOME"
18
+ short_dir=$(basename "$dir")
19
+ branch=$(git --no-optional-locks -C "$dir" branch --show-current 2>/dev/null)
20
+ dirty=$([ -n "$branch" ] && git --no-optional-locks -C "$dir" status --porcelain 2>/dev/null)
21
+
22
+ out="${G}➜${R} ${C}${short_dir}${R}"
23
+ [ -n "$branch" ] && out="$out ${B}git:(${R}${RE}${branch}${R}${B})${R}"
24
+ [ -n "$dirty" ] && out="$out ${YE}✗${R}"
25
+
26
+ # Color by percentage
27
+ pct_color() {
28
+ p=$1
29
+ if [ "$p" -ge 80 ] 2>/dev/null; then printf '%s' "$RE"
30
+ elif [ "$p" -ge 50 ] 2>/dev/null; then printf '%s' "$YE"
31
+ else printf '%s' "$GR"
32
+ fi
33
+ }
34
+
35
+ # Model + effort
36
+ model=$(echo "$input" | jq -r '.model.display_name // empty' 2>/dev/null)
37
+ effort=$(echo "$input" | jq -r '.effort.level // empty' 2>/dev/null)
38
+ if [ -n "$model" ]; then
39
+ label="$model"
40
+ [ -n "$effort" ] && label="$label/$effort"
41
+ out="$out${SEP}${PU}${label}${R}"
42
+ fi
43
+
44
+ # Compact token formatter: raw under 1000, Xk under 1M, X.Xm above
45
+ fmt_tok() {
46
+ n=$1
47
+ if [ -z "$n" ] || [ "$n" = "null" ]; then echo "?"; return; fi
48
+ n=$(printf '%.0f' "$n")
49
+ if [ "$n" -ge 1000000 ] 2>/dev/null; then
50
+ printf '%.1fm' "$(echo "$n 1000000" | awk '{printf "%.1f", $1/$2}')"
51
+ elif [ "$n" -ge 1000 ] 2>/dev/null; then
52
+ printf '%dk' "$(( n / 1000 ))"
53
+ else
54
+ printf '%d' "$n"
55
+ fi
56
+ }
57
+
58
+ # Context window
59
+ ctx_raw=$(echo "$input" | jq -r '.context_window.used_percentage // empty' 2>/dev/null)
60
+ tok_in=$(echo "$input" | jq -r '.context_window.total_input_tokens // empty' 2>/dev/null)
61
+ tok_out=$(echo "$input" | jq -r '.context_window.total_output_tokens // empty' 2>/dev/null)
62
+ if [ -n "$ctx_raw" ]; then
63
+ ctx=$(printf '%.2f' "$ctx_raw")
64
+ col=$(pct_color "$ctx_raw")
65
+ tok_str=""
66
+ if [ -n "$tok_in" ] || [ -n "$tok_out" ]; then
67
+ tok_str=" (in $(fmt_tok "$tok_in") | out $(fmt_tok "$tok_out"))"
68
+ fi
69
+ out="$out${SEP}${col}ctx ${ctx}%${tok_str}${R}"
70
+ fi
71
+
72
+ # 5h limit
73
+ five_raw=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty' 2>/dev/null)
74
+ five_reset=$(echo "$input" | jq -r '.rate_limits.five_hour.resets_at // empty' 2>/dev/null)
75
+ if [ -n "$five_raw" ]; then
76
+ five_pct=$(printf '%.2f' "$five_raw")
77
+ col=$(pct_color "$five_raw")
78
+ rst=""
79
+ [ -n "$five_reset" ] && rst=" →$(date -r "$five_reset" '+%H:%M' 2>/dev/null)"
80
+ out="$out${SEP}${col}5h ${five_pct}%${rst}${R}"
81
+ fi
82
+
83
+ # 7-day limit
84
+ seven_raw=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty' 2>/dev/null)
85
+ seven_reset=$(echo "$input" | jq -r '.rate_limits.seven_day.resets_at // empty' 2>/dev/null)
86
+ if [ -n "$seven_raw" ]; then
87
+ seven_pct=$(printf '%.2f' "$seven_raw")
88
+ col=$(pct_color "$seven_raw")
89
+ rst=""
90
+ [ -n "$seven_reset" ] && rst=" →$(date -r "$seven_reset" '+%a %H:%M' 2>/dev/null)"
91
+ out="$out${SEP}${col}7d ${seven_pct}%${rst}${R}"
92
+ fi
93
+
94
+ printf '%s\n' "$out"
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@antonior/claude-code-setup",
3
+ "version": "1.0.0",
4
+ "description": "Install Antonio's Claude Code config: hooks, skills, statusline, and settings",
5
+ "bin": {
6
+ "claude-code-setup": "bin/install.js"
7
+ },
8
+ "files": [
9
+ "bin/",
10
+ "files/"
11
+ ],
12
+ "keywords": [
13
+ "claude",
14
+ "claude-code",
15
+ "anthropic",
16
+ "config",
17
+ "setup"
18
+ ],
19
+ "author": "Antonio Radosav <antonio.radosav@protonmail.com>",
20
+ "license": "MIT",
21
+ "engines": {
22
+ "node": ">=18"
23
+ }
24
+ }