@antonior/claude-code-setup 1.2.0 โ†’ 2.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/README.md CHANGED
@@ -18,7 +18,7 @@ Stock Claude Code is powerful but neutral. This config turns it into a careful,
18
18
  - **๐Ÿ’ธ Cheap on tokens.** Caveman mode keeps full technical accuracy while cutting chatter ~75%; video defaults to audio-only transcription; verification hooks touch only changed files, not the whole repo. See [Optimised for low token usage](#optimised-for-low-token-usage).
19
19
  - **๐ŸŽฌ Understands video.** The `claude-video-vision` MCP lets Claude watch and reason about video, not just text.
20
20
  - **๐Ÿ“Š Knows where you stand.** A rich statusline shows git branch + dirty state, model and effort level, context-window %, and your 5-hour / 7-day rate-limit usage at a glance.
21
- - **โšก Zero-friction, safe install.** One command installs everything, auto-installs its dependencies, registers the MCP servers, and **smart-merges** into any existing config (it never clobbers your `settings.json` or `CLAUDE.md`). Idempotent โ€” re-run any time to pull updates.
21
+ - **โšก One-command, exact mirror.** Installs everything, auto-installs its dependencies, and registers the MCP servers. It reproduces my config **1:1** โ€” existing files are overwritten so you get an identical setup, with any file it replaces backed up to `<file>.bak` first. It never deletes files it doesn't manage. Idempotent โ€” re-run any time to pull the latest.
22
22
 
23
23
  ## Install
24
24
 
@@ -45,8 +45,8 @@ The installer installs these for you if missing:
45
45
 
46
46
  ## What it installs (into `~/.claude/`)
47
47
 
48
- - **`CLAUDE.md`** โ€” global rules (trust/integrity, scope discipline, verification). *Skipped if you already have one โ€” merge manually.*
49
- - **`settings.json`** โ€” permissions, hooks, model/effort/theme. *Smart-merged with any existing file; never overwritten.*
48
+ - **`CLAUDE.md`** โ€” global rules (trust/integrity, scope discipline, verification). *Overwritten to match; any existing one is saved to `CLAUDE.md.bak`.*
49
+ - **`settings.json`** + **`settings.local.json`** โ€” permissions, hooks, effort/theme. *Overwritten to match; any existing ones are saved to `.bak`.*
50
50
  - **`hooks/`**
51
51
  - `caveman-activate.sh` โ€” terse response style on session start
52
52
  - `scan-secrets.sh` โ€” blocks `git commit` if staged diff contains secrets/`.env`
@@ -54,7 +54,7 @@ The installer installs these for you if missing:
54
54
  - `eslint-fix.sh` โ€” auto `eslint --fix` on edited JS/TS files
55
55
  - `stop-verify.sh` โ€” lint + typecheck changed files when a turn ends
56
56
  - `notify-sound.sh` โ€” sound on notification/stop
57
- - **`commands/`** โ€” `/check-dep`, `/debug`, `/scan-secrets` slash commands
57
+ - **`commands/`** โ€” `/check-dep`, `/debug`, `/scan-secrets`, `/updateClaudeNpm` slash commands
58
58
  - **`skills/`** โ€” `/mute`, `/unmute`, and `/caveman` (with intensity levels)
59
59
  - **`agents/`** โ€” custom subagents: `Explore` (fast read-only code search), `Plan` (architect/implementation plans), `statusline-setup` (statusline config)
60
60
  - **`statusline.sh`** โ€” git, model, context %, rate-limit statusline
@@ -85,6 +85,13 @@ The installer reproduces **100% of the configuration and behaviour**. Two things
85
85
  1. **Log into Claude Code** (your account โ€” you'd do this on any new machine anyway).
86
86
  2. **Give `claude-video-vision` your own API key** if you use video analysis.
87
87
 
88
+ ## Maintaining (for me)
89
+
90
+ The package is an exact mirror of my own `~/.claude`. To refresh it from my live config and publish:
91
+
92
+ - `scripts/sync-from-local.sh` rebuilds `files/` from an allowlist of my config (`CLAUDE.md`, `settings*.json`, `statusline.sh`, `hooks/`, `commands/`, `agents/`, `skills/`, the caveman skill, the transcript-search engine). It never copies private or runtime data (history, transcripts, memory, the search index, sessions, caches, marketplace clones).
93
+ - The `/updateClaudeNpm` slash command runs that sync, scans for secrets, bumps the version, pushes to `main`, and publishes to npm โ€” so a fresh `npx @antonior/claude-code-setup` on another machine reproduces my setup 1:1.
94
+
88
95
  ## License
89
96
 
90
97
  [MIT](./LICENSE) ยฉ Antonio Radosav
package/bin/install.js CHANGED
@@ -3,12 +3,16 @@
3
3
 
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
- const { execSync, spawnSync } = require('child_process');
6
+ const { spawnSync } = require('child_process');
7
7
  const os = require('os');
8
8
 
9
9
  const CLAUDE_DIR = path.join(os.homedir(), '.claude');
10
10
  const FILES_DIR = path.join(__dirname, '..', 'files');
11
11
 
12
+ // --config-only: just mirror the config files; skip Homebrew deps + MCP
13
+ // registration (useful for a quick re-sync on an already-set-up machine).
14
+ const CONFIG_ONLY = process.argv.includes('--config-only');
15
+
12
16
  const c = {
13
17
  green: (s) => `\x1b[32m${s}\x1b[0m`,
14
18
  yellow: (s) => `\x1b[33m${s}\x1b[0m`,
@@ -24,24 +28,7 @@ function info(msg) { log(c.dim('ยท'), msg); }
24
28
  function fail(msg) { log(c.red('โœ—'), msg); }
25
29
 
26
30
  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');
31
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
45
32
  }
46
33
 
47
34
  // Is a command resolvable on PATH? (uses `command -v` via a shell so it sees
@@ -76,6 +63,43 @@ function brewInstall(pkg, { required = false } = {}) {
76
63
  return true;
77
64
  }
78
65
 
66
+ // Recursively list files under `dir`, returned as paths relative to it.
67
+ function walk(dir, base = dir) {
68
+ const out = [];
69
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
70
+ const full = path.join(dir, entry.name);
71
+ if (entry.isDirectory()) out.push(...walk(full, base));
72
+ else out.push(path.relative(base, full));
73
+ }
74
+ return out;
75
+ }
76
+
77
+ function sameContent(a, b) {
78
+ try { return fs.readFileSync(a).equals(fs.readFileSync(b)); } catch (_) { return false; }
79
+ }
80
+
81
+ // Lay one shipped file down at the mirrored path under ~/.claude. Overwrites to
82
+ // guarantee parity, but backs up any differing existing file to <file>.bak
83
+ // first (reversible). Unchanged files are left alone. *.sh become executable.
84
+ function installFile(rel) {
85
+ const src = path.join(FILES_DIR, rel);
86
+ const dest = path.join(CLAUDE_DIR, rel);
87
+ ensureDir(path.dirname(dest));
88
+ if (fs.existsSync(dest)) {
89
+ if (sameContent(src, dest)) {
90
+ info(`Unchanged: ${rel}`);
91
+ } else {
92
+ fs.copyFileSync(dest, dest + '.bak');
93
+ fs.copyFileSync(src, dest);
94
+ ok(`Updated (backup โ†’ ${rel}.bak): ${rel}`);
95
+ }
96
+ } else {
97
+ fs.copyFileSync(src, dest);
98
+ ok(`Installed: ${rel}`);
99
+ }
100
+ if (rel.endsWith('.sh')) fs.chmodSync(dest, '755');
101
+ }
102
+
79
103
  // Register the MCP servers via the official `claude` CLI (writes to user scope,
80
104
  // the same place they already live). Idempotent: skip any server already
81
105
  // registered. SECURITY: env is always {} โ€” never serialize the user's real
@@ -117,41 +141,6 @@ function registerMcpServers() {
117
141
  }
118
142
  }
119
143
 
120
- function mergeSettings(existingPath, incomingPath) {
121
- const existing = JSON.parse(fs.readFileSync(existingPath, 'utf8'));
122
- const incoming = JSON.parse(fs.readFileSync(incomingPath, 'utf8'));
123
-
124
- // Deep merge: scalars from incoming win, arrays are union-merged by value
125
- function mergeArrays(a, b) {
126
- const seen = new Set(a.map((x) => JSON.stringify(x)));
127
- const result = [...a];
128
- for (const item of b) {
129
- const key = JSON.stringify(item);
130
- if (!seen.has(key)) {
131
- seen.add(key);
132
- result.push(item);
133
- }
134
- }
135
- return result;
136
- }
137
-
138
- function deepMerge(base, override) {
139
- const out = { ...base };
140
- for (const [k, v] of Object.entries(override)) {
141
- if (Array.isArray(v) && Array.isArray(base[k])) {
142
- out[k] = mergeArrays(base[k], v);
143
- } else if (v && typeof v === 'object' && !Array.isArray(v) && base[k] && typeof base[k] === 'object') {
144
- out[k] = deepMerge(base[k], v);
145
- } else {
146
- out[k] = v;
147
- }
148
- }
149
- return out;
150
- }
151
-
152
- return deepMerge(existing, incoming);
153
- }
154
-
155
144
  function main() {
156
145
  console.log(c.bold('\nclaude-code-setup โ€” installing Antonio\'s Claude config\n'));
157
146
 
@@ -159,88 +148,33 @@ function main() {
159
148
  // jq is required (hooks parse JSON with it). python3 powers the
160
149
  // transcript-search MCP server; ffmpeg powers claude-video-vision โ€”
161
150
  // both best-effort (feature stays dormant if absent).
162
- console.log(c.bold('Dependencies:'));
163
- if (!brewInstall('jq', { required: true })) process.exit(1);
164
- brewInstall('python3');
165
- brewInstall('ffmpeg');
166
-
167
- // 2. Ensure dirs
168
- ensureDir(CLAUDE_DIR);
169
- ensureDir(path.join(CLAUDE_DIR, 'hooks'));
170
- ensureDir(path.join(CLAUDE_DIR, 'skills'));
171
- ensureDir(path.join(CLAUDE_DIR, 'skills', 'caveman'));
172
- ensureDir(path.join(CLAUDE_DIR, 'commands'));
173
- ensureDir(path.join(CLAUDE_DIR, 'agents'));
174
- ensureDir(path.join(CLAUDE_DIR, 'transcript-search'));
175
-
176
- // 3. Hooks
177
- const hooks = ['caveman-activate.sh', 'check-dep.sh', 'eslint-fix.sh', 'notify-sound.sh', 'scan-secrets.sh', 'stop-verify.sh'];
178
- for (const h of hooks) {
179
- const dest = path.join(CLAUDE_DIR, 'hooks', h);
180
- copyFile(path.join(FILES_DIR, 'hooks', h), dest);
181
- makeExecutable(dest);
182
- }
183
-
184
- // 4. Skills (mute/unmute + the caveman /caveman skill with intensity levels)
185
- for (const s of ['mute.md', 'unmute.md']) {
186
- copyFile(path.join(FILES_DIR, 'skills', s), path.join(CLAUDE_DIR, 'skills', s), { skipIfExists: true });
187
- }
188
- copyFile(path.join(FILES_DIR, 'skills', 'caveman', 'SKILL.md'), path.join(CLAUDE_DIR, 'skills', 'caveman', 'SKILL.md'), { skipIfExists: true });
189
-
190
- // 4b. Commands (slash commands referenced by CLAUDE.md)
191
- for (const cmd of ['check-dep.md', 'debug.md', 'scan-secrets.md']) {
192
- copyFile(path.join(FILES_DIR, 'commands', cmd), path.join(CLAUDE_DIR, 'commands', cmd), { skipIfExists: true });
151
+ if (!CONFIG_ONLY) {
152
+ console.log(c.bold('Dependencies:'));
153
+ if (!brewInstall('jq', { required: true })) process.exit(1);
154
+ brewInstall('python3');
155
+ brewInstall('ffmpeg');
193
156
  }
194
157
 
195
- // 4c. transcript-search engine (the MCP server script โ€” index.db is built
196
- // per-user from their own ~/.claude/projects and is never shipped)
197
- copyFile(path.join(FILES_DIR, 'transcript-search', 'rag_lite.py'), path.join(CLAUDE_DIR, 'transcript-search', 'rag_lite.py'));
198
-
199
- // 4d. Agents (custom subagent definitions: Explore, Plan, statusline-setup).
200
- // Overwritten on re-run so updates propagate, same as hooks/statusline.
201
- for (const a of ['Explore.md', 'Plan.md', 'statusline-setup.md']) {
202
- copyFile(path.join(FILES_DIR, 'agents', a), path.join(CLAUDE_DIR, 'agents', a));
203
- }
204
-
205
- // 5. Statusline
206
- const statuslineDest = path.join(CLAUDE_DIR, 'statusline.sh');
207
- copyFile(path.join(FILES_DIR, 'statusline.sh'), statuslineDest);
208
- makeExecutable(statuslineDest);
209
-
210
- // 6. CLAUDE.md
211
- const claudeMdDest = path.join(CLAUDE_DIR, 'CLAUDE.md');
212
- if (fs.existsSync(claudeMdDest)) {
213
- warn('CLAUDE.md already exists โ€” skipped (edit manually if needed)');
214
- info(` Your file: ${claudeMdDest}`);
215
- info(` Reference: ${path.join(FILES_DIR, 'CLAUDE.md')}`);
216
- } else {
217
- copyFile(path.join(FILES_DIR, 'CLAUDE.md'), claudeMdDest);
218
- }
158
+ // 2. Mirror every shipped config file into ~/.claude at the same relative
159
+ // path. The package's files/ tree IS the source of truth โ€” whatever is in
160
+ // it lands, so new hooks/commands/agents/skills need no code changes here.
161
+ ensureDir(CLAUDE_DIR);
162
+ console.log(c.bold('\nMirroring config into ~/.claude:'));
163
+ for (const rel of walk(FILES_DIR)) installFile(rel);
219
164
 
220
- // 7. settings.json โ€” smart merge
221
- const settingsDest = path.join(CLAUDE_DIR, 'settings.json');
222
- if (fs.existsSync(settingsDest)) {
223
- info('Merging settings.json...');
224
- try {
225
- const merged = mergeSettings(settingsDest, path.join(FILES_DIR, 'settings.json'));
226
- fs.writeFileSync(settingsDest, JSON.stringify(merged, null, 2) + '\n');
227
- ok('settings.json merged');
228
- } catch (e) {
229
- fail(`settings.json merge failed: ${e.message}`);
230
- warn('Manual merge needed โ€” reference file at: ' + path.join(FILES_DIR, 'settings.json'));
231
- }
232
- } else {
233
- copyFile(path.join(FILES_DIR, 'settings.json'), settingsDest);
165
+ // 3. MCP servers (transcript-search + claude-video-vision)
166
+ if (!CONFIG_ONLY) {
167
+ console.log(c.bold('\nMCP servers:'));
168
+ registerMcpServers();
234
169
  }
235
170
 
236
- // 8. MCP servers (transcript-search + claude-video-vision)
237
- console.log(c.bold('\nMCP servers:'));
238
- registerMcpServers();
239
-
240
171
  console.log(c.bold(c.green('\nDone. Restart Claude Code to apply.')));
241
- console.log(c.dim('Two one-time steps on a new machine (identity, not config โ€” they can\'t ship):'));
242
- console.log(c.dim(' 1. Log into Claude Code.'));
243
- console.log(c.dim(' 2. For video analysis, give claude-video-vision your own API key.\n'));
172
+ console.log(c.dim('Overwrites are backed up to <file>.bak. Unmanaged files already in ~/.claude are left untouched.'));
173
+ if (!CONFIG_ONLY) {
174
+ console.log(c.dim('Two one-time steps on a new machine (identity, not config โ€” they can\'t ship):'));
175
+ console.log(c.dim(' 1. Log into Claude Code.'));
176
+ console.log(c.dim(' 2. For video analysis, give claude-video-vision your own API key.\n'));
177
+ }
244
178
  }
245
179
 
246
180
  main();
@@ -0,0 +1,36 @@
1
+ ---
2
+ description: Mirror 100% of my local ~/.claude config to the @antonior/claude-code-setup npm package + GitHub and publish, so another machine reproduces it exactly.
3
+ ---
4
+
5
+ # /updateClaudeNpm
6
+
7
+ Publish my current local `~/.claude` config to npm + GitHub so another machine reproduces it 1:1.
8
+
9
+ - Package: `@antonior/claude-code-setup`
10
+ - Repo: https://github.com/RadosavAntonio/claude-code-setup
11
+
12
+ ## Scope โ€” what "100%" means
13
+
14
+ Ship 100% of CONFIG and TOOLS. NEVER ship private or machine-runtime data to the public registry.
15
+
16
+ - **SHIP:** `CLAUDE.md`, `settings.json`, `settings.local.json`, `statusline.sh`, `hooks/`, `commands/`, `agents/`, `skills/*.md`, the caveman skill (`plugins/caveman/skills/caveman/SKILL.md`), `transcript-search/rag_lite.py`.
17
+ - **NEVER SHIP:** `history.jsonl`, `projects/` (transcripts + memory), the transcript-search index (`index.db*`), `sessions/`, `session-env/`, `shell-snapshots/`, `paste-cache/`, `file-history/`, `backups/`, `cache/`, `daemon*`, `ide/`, `jobs/`, `plans/`, `telemetry/`, `plugins/marketplaces/`, `known_marketplaces.json`, `.DS_Store`.
18
+
19
+ The allowlist in `scripts/sync-from-local.sh` already encodes this. Do NOT widen it to private data even if asked for "everything" โ€” this is a public npm package.
20
+
21
+ ## Procedure
22
+
23
+ 1. **Preconditions.** Run `npm whoami` (must print `antonior`) and `gh auth status` (must be logged in). If either fails, STOP and tell me.
24
+ 2. **Clone fresh** into a temp dir: `gh repo clone RadosavAntonio/claude-code-setup <tmp>` (or `git pull` if already cloned). Work there.
25
+ 3. **Sync.** Run `bash scripts/sync-from-local.sh`. It rebuilds `files/` as an exact mirror of my current allowlisted config.
26
+ 4. **No-op guard.** `git add -A`, then `git diff --cached --quiet`. If it reports no changes, STOP and tell me "already in sync, nothing to publish." Do not bump or publish.
27
+ 5. **Secret gate.** Run `/scan-secrets` over the staged files. If it flags anything, STOP and show me โ€” do not publish.
28
+ 6. **Bump.** `npm version patch --no-git-tag-version`.
29
+ 7. **Commit + push.** Commit with a normal-English message summarising the diff (end with the `Co-Authored-By: Claude ...` line), push to `main`.
30
+ 8. **Publish.** `npm publish --access public`.
31
+ 9. **Verify + report.** Confirm `npm view @antonior/claude-code-setup version` equals the new version. Report what changed, the new version, and the install command for the other machine: `npx @antonior/claude-code-setup`.
32
+
33
+ ## Notes
34
+
35
+ - The installer **overwrites** `CLAUDE.md` + `settings.json` (and everything else shipped) on the target, backing up any existing file to `<file>.bak` first, so the other machine ends up byte-identical. It does NOT delete unmanaged files already present on the target.
36
+ - `npx @antonior/claude-code-setup --config-only` lays down config without re-checking Homebrew deps / re-registering MCP servers โ€” useful for a quick re-sync.
@@ -32,10 +32,7 @@
32
32
  "Bash(sw_vers:*)",
33
33
  "Bash(date)",
34
34
  "Bash(npm view:*)",
35
- "Bash(npm ls:*)",
36
- "Bash(claude update *)",
37
- "Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(json.dumps\\({k: d.get\\(k\\) for k in ['dependencies','devDependencies']}, indent=2\\)\\)\")",
38
- "Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(json.dumps\\(d.get\\('mcpServers', {}\\), indent=2\\)\\)\")"
35
+ "Bash(npm ls:*)"
39
36
  ]
40
37
  },
41
38
  "hooks": {
@@ -0,0 +1,9 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(claude update *)",
5
+ "Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(json.dumps\\({k: d.get\\(k\\) for k in ['dependencies','devDependencies']}, indent=2\\)\\)\")",
6
+ "Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(json.dumps\\(d.get\\('mcpServers', {}\\), indent=2\\)\\)\")"
7
+ ]
8
+ }
9
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antonior/claude-code-setup",
3
- "version": "1.2.0",
3
+ "version": "2.0.0",
4
4
  "description": "Install Antonio's full Claude Code setup: hooks, slash commands, skills, statusline, settings, and MCP servers (transcript-search + video-vision)",
5
5
  "bin": {
6
6
  "claude-code-setup": "bin/install.js"