@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 +21 -0
- package/README.md +58 -0
- package/bin/install.js +174 -0
- package/files/CLAUDE.md +53 -0
- package/files/commands/check-dep.md +52 -0
- package/files/commands/debug.md +26 -0
- package/files/commands/scan-secrets.md +40 -0
- package/files/hooks/caveman-activate.sh +18 -0
- package/files/hooks/check-dep.sh +33 -0
- package/files/hooks/eslint-fix.sh +29 -0
- package/files/hooks/notify-sound.sh +3 -0
- package/files/hooks/scan-secrets.sh +40 -0
- package/files/hooks/stop-verify.sh +50 -0
- package/files/settings.json +149 -0
- package/files/skills/mute.md +7 -0
- package/files/skills/unmute.md +7 -0
- package/files/statusline.sh +94 -0
- package/package.json +24 -0
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();
|
package/files/CLAUDE.md
ADDED
|
@@ -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,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,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
|
+
}
|