@buffbirb/unclaude 1.0.0 β†’ 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,7 +30,6 @@ If the cloud agent doesn't support pre-session scripts, run locally with `.gitig
30
30
  |---|---|
31
31
  | **Stop Telemetry** | Exports `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1` in `~/.airc` |
32
32
  | **Agent config for git commits** | Sets `attribution: { commit: '', pr: '' }` in Claude Code `settings.json` |
33
- | **Git pre-commit hook for git commits** | Installs a `commit-msg` git hook that strips `πŸ€– Generated with Claude Code`, `Co-Authored-By: Claude`, session URLs, and lone `---` separators |
34
33
  | **Agent SessionStart hook for git commits** | Registers a Claude Code `SessionStart` hook that installs a bash `commit-msg` hook in `.git/hooks` at the start of every session |
35
34
  | **Agent SessionStart hook for git branches** | Registers a Claude Code `SessionStart` hook that renames `claude/*` or `claude-*` branches to a custom prefix (or removes the prefix) |
36
35
  | **GitHub Action for PR body** | Writes a workflow that strips Claude attribution from PR bodies server-side on `opened`/`edited` |
@@ -45,16 +44,15 @@ If the cloud agent doesn't support pre-session scripts, run locally with `.gitig
45
44
  |---|---|---|
46
45
  | Stop Telemetry | Claude Code | Global |
47
46
  | Agent config for git commits | Claude Code | Global, Project |
48
- | Git pre-commit hook for git commitsΒΉ | Claude Code, OpenCode | Project |
49
- | Agent SessionStart hook for git commits | Claude Code | Project |
50
- | Agent SessionStart hook for git branches | Claude Code | Project |
47
+ | Agent SessionStart hook for git commitsΒΉ | Claude Code | Project |
48
+ | Agent SessionStart hook for git branchesΒΉ | Claude Code | Project |
51
49
  | GitHub Action for PR body | Claude Code, OpenCode | Project |
52
50
  | Agent md file for all git ops | Claude Code, OpenCode | Global, Project |
53
51
  | LSP | Claude Code, OpenCode | Global |
54
52
  | Headroom | Claude Code, OpenCode | Global |
55
53
  | OpenSpec | Claude Code, OpenCode | Global (install), Project (init) |
56
54
 
57
- ΒΉ Git pre-commit hook can be set globally, but it's intentionally restricted to project-scope in this installer.
55
+ ΒΉ This can be set globally, but it's intentionally restricted to project-scope in this installer.
58
56
 
59
57
  ### Personalization
60
58
 
@@ -90,7 +88,7 @@ Each list option takes a comma-separated value. Omitting a list option defaults
90
88
  |---|---|
91
89
  | `--agents` | `claudeCode`, `openCode` |
92
90
  | `--scopes` | `global`, `project` |
93
- | `--features` | `stopTelemetry`, `stopAttributionConfig`, `stopAttributionHook`, `stripCommitAttribution`, `renameClaudeBranch`, `stripPrAttribution`, `agentMdFile`, `lsp`, `headroom`, `openspec` |
91
+ | `--features` | `stopTelemetry`, `stripCommitJSON`, `stripCommitHook`, `renameClaudeBranch`, `stripPrWorkflow`, `agentMdFile`, `lsp`, `headroom`, `openspec` |
94
92
  | `--no-gitignore` | Skip adding generated project-scope files to `.gitignore` (default: adds them) |
95
93
  | `--git-user` | Your `user.name` β€” embedded in hooks and agent md files |
96
94
  | `--git-email` | Your `user.email` β€” embedded in hooks and agent md files |
package/dist/common.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { execFile, execFileSync, spawn } from 'child_process';
2
2
  import { promisify } from 'util';
3
- import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync, chmodSync, accessSync, constants, } from 'fs';
3
+ import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync, chmodSync, accessSync, constants, readdirSync, statSync, } from 'fs';
4
4
  import { dirname, join } from 'path';
5
5
  import { homedir } from 'os';
6
6
  // ── Paths ────────────────────────────────────────────────────────────────────
@@ -28,27 +28,25 @@ export const GIT_SETTING_LABELS = {
28
28
  export const FEATURE_LABELS = {
29
29
  headroom: 'Headroom',
30
30
  stopTelemetry: 'Stop Telemetry',
31
- stopAttributionConfig: 'Agent config for git commits',
32
- stopAttributionHook: 'Git pre-commit hook for git commits',
33
- stripCommitAttribution: 'Agent SessionStart hook for git commits',
34
- stripPrAttribution: 'GitHub Action for PR body',
31
+ stripCommitJSON: 'Agent config for git commits',
32
+ stripCommitHook: 'Agent SessionStart hook for git commits',
33
+ stripPrWorkflow: 'GitHub Action for PR body',
35
34
  renameClaudeBranch: 'Agent SessionStart hook for git branches',
36
35
  agentMdFile: 'Agent md file for all git ops',
37
36
  lsp: 'LSP',
38
37
  openspec: 'OpenSpec',
39
38
  };
40
39
  export const FEATURE_ORDER = [
41
- 'stopTelemetry', 'stopAttributionConfig', 'stopAttributionHook',
42
- 'stripCommitAttribution', 'renameClaudeBranch', 'stripPrAttribution',
40
+ 'stopTelemetry', 'stripCommitJSON',
41
+ 'stripCommitHook', 'renameClaudeBranch', 'stripPrWorkflow',
43
42
  'agentMdFile', 'lsp', 'headroom', 'openspec',
44
43
  ];
45
44
  export const FEATURE_SCOPES = {
46
45
  headroom: ['global'],
47
46
  stopTelemetry: ['global'],
48
- stopAttributionConfig: ['global', 'project'],
49
- stopAttributionHook: ['project'],
50
- stripCommitAttribution: ['project'],
51
- stripPrAttribution: ['project'],
47
+ stripCommitJSON: ['global', 'project'],
48
+ stripCommitHook: ['project'],
49
+ stripPrWorkflow: ['project'],
52
50
  renameClaudeBranch: ['project'],
53
51
  agentMdFile: ['global', 'project'],
54
52
  lsp: ['global'],
@@ -56,19 +54,18 @@ export const FEATURE_SCOPES = {
56
54
  };
57
55
  export const FEATURE_AGENTS = {
58
56
  stopTelemetry: ['claudeCode'],
59
- stopAttributionConfig: ['claudeCode'],
60
- stopAttributionHook: ['claudeCode', 'openCode'],
61
- stripCommitAttribution: ['claudeCode'],
57
+ stripCommitJSON: ['claudeCode'],
58
+ stripCommitHook: ['claudeCode'],
62
59
  renameClaudeBranch: ['claudeCode'],
63
- stripPrAttribution: ['claudeCode', 'openCode'],
60
+ stripPrWorkflow: ['claudeCode', 'openCode'],
64
61
  agentMdFile: ['claudeCode', 'openCode'],
65
62
  lsp: ['claudeCode', 'openCode'],
66
63
  headroom: ['claudeCode', 'openCode'],
67
64
  openspec: ['claudeCode', 'openCode'],
68
65
  };
69
66
  export const DEFAULT_FEATURES = [
70
- 'stopTelemetry', 'stopAttributionConfig', 'stopAttributionHook',
71
- 'stripCommitAttribution', 'renameClaudeBranch', 'stripPrAttribution',
67
+ 'stopTelemetry', 'stripCommitJSON',
68
+ 'stripCommitHook', 'renameClaudeBranch', 'stripPrWorkflow',
72
69
  'agentMdFile',
73
70
  ];
74
71
  export function defaultSelection() {
@@ -97,11 +94,10 @@ export function getFeatureItems(agents, scopes) {
97
94
  items.push({ id: 'stopTelemetry', label: FEATURE_LABELS.stopTelemetry, indent: false });
98
95
  }
99
96
  const stopAttributionChildren = [
100
- { id: 'stopAttribution.hook', feature: 'stopAttributionHook', label: FEATURE_LABELS.stopAttributionHook },
101
- { id: 'stopAttribution.config', feature: 'stopAttributionConfig', label: FEATURE_LABELS.stopAttributionConfig },
102
- { id: 'stopAttribution.stripCommit', feature: 'stripCommitAttribution', label: FEATURE_LABELS.stripCommitAttribution },
97
+ { id: 'stopAttribution.config', feature: 'stripCommitJSON', label: FEATURE_LABELS.stripCommitJSON },
98
+ { id: 'stopAttribution.stripCommit', feature: 'stripCommitHook', label: FEATURE_LABELS.stripCommitHook },
103
99
  { id: 'stopAttribution.renameBranch', feature: 'renameClaudeBranch', label: FEATURE_LABELS.renameClaudeBranch },
104
- { id: 'stopAttribution.stripPr', feature: 'stripPrAttribution', label: FEATURE_LABELS.stripPrAttribution },
100
+ { id: 'stopAttribution.stripPr', feature: 'stripPrWorkflow', label: FEATURE_LABELS.stripPrWorkflow },
105
101
  { id: 'stopAttribution.mdFile', feature: 'agentMdFile', label: FEATURE_LABELS.agentMdFile },
106
102
  ].filter(c => supported(c.feature));
107
103
  if (stopAttributionChildren.length > 0) {
@@ -185,29 +181,23 @@ export function toggleSelection(panel, cursor, sel, featureItems) {
185
181
  else
186
182
  next.features.delete('lsp');
187
183
  }
188
- else if (item.id === 'stopAttribution.hook') {
189
- if (next.features.has('stopAttributionHook'))
190
- next.features.delete('stopAttributionHook');
191
- else
192
- next.features.add('stopAttributionHook');
193
- }
194
184
  else if (item.id === 'stopAttribution.config') {
195
- if (next.features.has('stopAttributionConfig'))
196
- next.features.delete('stopAttributionConfig');
185
+ if (next.features.has('stripCommitJSON'))
186
+ next.features.delete('stripCommitJSON');
197
187
  else
198
- next.features.add('stopAttributionConfig');
188
+ next.features.add('stripCommitJSON');
199
189
  }
200
190
  else if (item.id === 'stopAttribution.stripCommit') {
201
- if (next.features.has('stripCommitAttribution'))
202
- next.features.delete('stripCommitAttribution');
191
+ if (next.features.has('stripCommitHook'))
192
+ next.features.delete('stripCommitHook');
203
193
  else
204
- next.features.add('stripCommitAttribution');
194
+ next.features.add('stripCommitHook');
205
195
  }
206
196
  else if (item.id === 'stopAttribution.stripPr') {
207
- if (next.features.has('stripPrAttribution'))
208
- next.features.delete('stripPrAttribution');
197
+ if (next.features.has('stripPrWorkflow'))
198
+ next.features.delete('stripPrWorkflow');
209
199
  else
210
- next.features.add('stripPrAttribution');
200
+ next.features.add('stripPrWorkflow');
211
201
  }
212
202
  else if (item.id === 'stopAttribution.renameBranch') {
213
203
  if (next.features.has('renameClaudeBranch'))
@@ -223,16 +213,14 @@ export function toggleSelection(panel, cursor, sel, featureItems) {
223
213
  }
224
214
  else if (item.id === 'stopAttribution') {
225
215
  const visibleSubs = [];
226
- if (featureItems.some(fi => fi.id === 'stopAttribution.hook'))
227
- visibleSubs.push('stopAttributionHook');
228
216
  if (featureItems.some(fi => fi.id === 'stopAttribution.config'))
229
- visibleSubs.push('stopAttributionConfig');
217
+ visibleSubs.push('stripCommitJSON');
230
218
  if (featureItems.some(fi => fi.id === 'stopAttribution.stripCommit'))
231
- visibleSubs.push('stripCommitAttribution');
219
+ visibleSubs.push('stripCommitHook');
232
220
  if (featureItems.some(fi => fi.id === 'stopAttribution.renameBranch'))
233
221
  visibleSubs.push('renameClaudeBranch');
234
222
  if (featureItems.some(fi => fi.id === 'stopAttribution.stripPr'))
235
- visibleSubs.push('stripPrAttribution');
223
+ visibleSubs.push('stripPrWorkflow');
236
224
  if (featureItems.some(fi => fi.id === 'stopAttribution.mdFile'))
237
225
  visibleSubs.push('agentMdFile');
238
226
  if (visibleSubs.length === 0)
@@ -299,6 +287,41 @@ export function isGitRepo() {
299
287
  return false;
300
288
  }
301
289
  }
290
+ export function findRecentGitRepo(maxAgeMs = 120_000) {
291
+ const cwd = process.cwd();
292
+ const now = Date.now();
293
+ let best = null;
294
+ try {
295
+ for (const entry of readdirSync(cwd, { withFileTypes: true })) {
296
+ if (!entry.isDirectory())
297
+ continue;
298
+ const gitDir = join(cwd, entry.name, '.git');
299
+ try {
300
+ const st = statSync(gitDir);
301
+ if (now - st.mtimeMs <= maxAgeMs) {
302
+ if (!best || st.mtimeMs > best.mtime) {
303
+ best = { path: join(cwd, entry.name), mtime: st.mtimeMs };
304
+ }
305
+ }
306
+ }
307
+ catch { }
308
+ }
309
+ }
310
+ catch { }
311
+ return best?.path ?? null;
312
+ }
313
+ export function detectProjectRoot() {
314
+ const recent = findRecentGitRepo();
315
+ if (recent)
316
+ return recent;
317
+ if (isGitRepo()) {
318
+ try {
319
+ return execFileSync('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf8' }).trim();
320
+ }
321
+ catch { }
322
+ }
323
+ return null;
324
+ }
302
325
  export function canWrite(path) {
303
326
  try {
304
327
  accessSync(path, constants.W_OK);
@@ -383,13 +406,13 @@ OPENAI_BASE_URL=http://localhost:8787/v1 opencode "$@"
383
406
  `;
384
407
  // Bash commit-msg hook used by the SessionStart strip-commit-attribution script.
385
408
  export const BASH_COMMIT_MSG_HOOK = `#!/bin/bash
386
- sed -i '' \\
409
+ sed -i.bak \\
387
410
  -e '/claude\\.ai\\/code\\/session/d' \\
388
411
  -e '/Co-[Aa]uthored-[Bb]y:.*[Cc]laude/d' \\
389
412
  -e '/[Gg]enerated [bw][yi]t*h*.*[Cc]laude/d' \\
390
413
  -e '/[[:space:]]*πŸ€– [Gg]enerated/d' \\
391
414
  -e '/^---[[:space:]]*$/d' \\
392
- "$1"
415
+ "$1" && rm -f "$1.bak"
393
416
  awk 'BEGIN{blank=0} /^$/{blank++; next} {while(blank--)print ""; blank=0; print}' "$1" > "$1.tmp" && mv "$1.tmp" "$1"
394
417
  `;
395
418
  // GitHub Actions workflow that strips Claude attribution from PR bodies server-side.
@@ -432,10 +455,10 @@ jobs:
432
455
  // ── Agent markdown file content ──────────────────────────────────────────────
433
456
  export const AGENT_MD_HEADER = '# UNCLAUDE';
434
457
  const FEATURE_PROJECT_FILES = {
435
- stopAttributionConfig: ['.claude/settings.json'],
436
- stripCommitAttribution: ['.claude/settings.json', '.claude/hooks/strip-commit-attribution.sh'],
458
+ stripCommitJSON: ['.claude/settings.json'],
459
+ stripCommitHook: ['.claude/settings.json', '.claude/hooks/strip-commit-attribution.sh'],
437
460
  renameClaudeBranch: ['.claude/settings.json', '.claude/hooks/strip-claude-branch.sh'],
438
- stripPrAttribution: ['.github/workflows/strip-pr-attribution.yml'],
461
+ stripPrWorkflow: ['.github/workflows/strip-pr-attribution.yml'],
439
462
  agentMdFile: ['CLAUDE.md', 'AGENTS.md'],
440
463
  };
441
464
  export function addGitignoreEntries(features) {
package/dist/install.js CHANGED
@@ -3,7 +3,7 @@ import { useState, useEffect, useCallback, useMemo } from 'react';
3
3
  import { Box, Text, useInput, useApp } from 'ink';
4
4
  import { existsSync, mkdirSync, writeFileSync, chmodSync, readFileSync, appendFileSync } from 'fs';
5
5
  import { join } from 'path';
6
- import { HOME, AIRC, ZSHRC, CLAUDE_SETTINGS, OPENCODE_CONFIG, HCLAUDE, HOPENCODE, AGENT_IDS, SCOPE_IDS, GIT_SETTING_IDS, FEATURE_ORDER, AGENT_LABELS, SCOPE_LABELS, GIT_SETTING_LABELS, FEATURE_LABELS, defaultSelection, getFeatureItems, toggleSelection, addGitignoreEntries, removeGitignoreEntries, run, stream, commandExists, isGitRepo, nvmShell, canWrite, patchJson, ensureFile, appendIfMissing, writeExecutable, HEADROOM_START_FUNC, HCLAUDE_CONTENT, HOPENCODE_CONTENT, BASH_COMMIT_MSG_HOOK, STRIP_PR_WORKFLOW, addSessionStartHook, FEATURE_SCOPES, AGENT_MD_HEADER, removeSectionByHeader, } from './common.js';
6
+ import { HOME, AIRC, ZSHRC, CLAUDE_SETTINGS, OPENCODE_CONFIG, HCLAUDE, HOPENCODE, AGENT_IDS, SCOPE_IDS, GIT_SETTING_IDS, FEATURE_ORDER, AGENT_LABELS, SCOPE_LABELS, GIT_SETTING_LABELS, FEATURE_LABELS, defaultSelection, getFeatureItems, toggleSelection, addGitignoreEntries, removeGitignoreEntries, run, stream, commandExists, detectProjectRoot, nvmShell, canWrite, patchJson, ensureFile, appendIfMissing, writeExecutable, HEADROOM_START_FUNC, HCLAUDE_CONTENT, HOPENCODE_CONTENT, BASH_COMMIT_MSG_HOOK, STRIP_PR_WORKFLOW, addSessionStartHook, FEATURE_SCOPES, AGENT_MD_HEADER, removeSectionByHeader, } from './common.js';
7
7
  import { runUninstall } from './uninstall.js';
8
8
  const FORM_WIDTH = 55;
9
9
  // ── Install functions ────────────────────────────────────────────────────────
@@ -59,7 +59,7 @@ async function installStopTelemetry(onLine) {
59
59
  appendIfMissing(ZSHRC, '[ -f ~/.airc ]', '\n[ -f ~/.airc ] && source ~/.airc\n');
60
60
  }
61
61
  }
62
- async function installStopAttributionConfig(scopes, onLine) {
62
+ async function installStripCommitJSON(scopes, onLine) {
63
63
  if (scopes.has('global')) {
64
64
  patchJson(CLAUDE_SETTINGS, d => { d['attribution'] = { commit: '', pr: '' }; });
65
65
  onLine('attribution config set in ~/.claude/settings.json');
@@ -69,19 +69,7 @@ async function installStopAttributionConfig(scopes, onLine) {
69
69
  onLine('attribution config set in .claude/settings.json');
70
70
  }
71
71
  }
72
- async function installStopAttributionHook(onLine) {
73
- const writeHook = (hookPath) => {
74
- mkdirSync(join(hookPath, '..'), { recursive: true });
75
- writeFileSync(hookPath, BASH_COMMIT_MSG_HOOK);
76
- chmodSync(hookPath, 0o755);
77
- };
78
- const gitDir = await run('git', ['rev-parse', '--git-dir']);
79
- const hooksDir = join(gitDir, 'hooks');
80
- mkdirSync(hooksDir, { recursive: true });
81
- writeHook(join(hooksDir, 'commit-msg'));
82
- onLine('Project git commit-msg hook installed');
83
- }
84
- async function installStripCommitAttribution(gitUser, gitEmail, personalize, onLine) {
72
+ async function installStripCommitHook(gitUser, gitEmail, personalize, onLine) {
85
73
  const name = personalize ? gitUser : '';
86
74
  const email = personalize ? gitEmail : '';
87
75
  const hookScript = [
@@ -100,7 +88,7 @@ async function installStripCommitAttribution(gitUser, gitEmail, personalize, onL
100
88
  addSessionStartHook('.claude/settings.json', 'bash .claude/hooks/strip-commit-attribution.sh');
101
89
  onLine('SessionStart hook registered in .claude/settings.json');
102
90
  }
103
- async function installStripPrAttribution(onLine) {
91
+ async function installStripPrWorkflow(onLine) {
104
92
  mkdirSync('.github/workflows', { recursive: true });
105
93
  writeFileSync('.github/workflows/strip-pr-attribution.yml', STRIP_PR_WORKFLOW);
106
94
  onLine('.github/workflows/strip-pr-attribution.yml written');
@@ -252,20 +240,26 @@ async function installOpenspec(scopes, onLine) {
252
240
  export async function runInstall(selection, formState, updateStep) {
253
241
  const { features, scopes, agents, lspLanguages } = selection;
254
242
  const { personalize, gitUser, gitEmail, branchPrefix } = formState;
243
+ if (scopes.has('project')) {
244
+ if (PROJECT_ROOT) {
245
+ process.chdir(PROJECT_ROOT);
246
+ }
247
+ else {
248
+ scopes.delete('project');
249
+ }
250
+ }
255
251
  const scopeOk = (feat) => scopes.size === 0 || FEATURE_SCOPES[feat].some(s => scopes.has(s));
256
252
  const tasks = [];
257
253
  if (features.has('headroom') && scopeOk('headroom'))
258
254
  tasks.push(['headroom', () => installHeadroom(l => updateStep('headroom', { line: l }))]);
259
255
  if (features.has('stopTelemetry') && scopeOk('stopTelemetry'))
260
256
  tasks.push(['stopTelemetry', () => installStopTelemetry(l => updateStep('stopTelemetry', { line: l }))]);
261
- if (features.has('stopAttributionConfig') && scopeOk('stopAttributionConfig'))
262
- tasks.push(['stopAttributionConfig', () => installStopAttributionConfig(scopes, l => updateStep('stopAttributionConfig', { line: l }))]);
263
- if (features.has('stopAttributionHook') && scopeOk('stopAttributionHook'))
264
- tasks.push(['stopAttributionHook', () => installStopAttributionHook(l => updateStep('stopAttributionHook', { line: l }))]);
265
- if (features.has('stripCommitAttribution') && scopeOk('stripCommitAttribution'))
266
- tasks.push(['stripCommitAttribution', () => installStripCommitAttribution(gitUser, gitEmail, personalize, l => updateStep('stripCommitAttribution', { line: l }))]);
267
- if (features.has('stripPrAttribution') && scopeOk('stripPrAttribution'))
268
- tasks.push(['stripPrAttribution', () => installStripPrAttribution(l => updateStep('stripPrAttribution', { line: l }))]);
257
+ if (features.has('stripCommitJSON') && scopeOk('stripCommitJSON'))
258
+ tasks.push(['stripCommitJSON', () => installStripCommitJSON(scopes, l => updateStep('stripCommitJSON', { line: l }))]);
259
+ if (features.has('stripCommitHook') && scopeOk('stripCommitHook'))
260
+ tasks.push(['stripCommitHook', () => installStripCommitHook(gitUser, gitEmail, personalize, l => updateStep('stripCommitHook', { line: l }))]);
261
+ if (features.has('stripPrWorkflow') && scopeOk('stripPrWorkflow'))
262
+ tasks.push(['stripPrWorkflow', () => installStripPrWorkflow(l => updateStep('stripPrWorkflow', { line: l }))]);
269
263
  if (features.has('renameClaudeBranch') && scopeOk('renameClaudeBranch'))
270
264
  tasks.push(['renameClaudeBranch', () => installRenameClaudeBranch(branchPrefix, l => updateStep('renameClaudeBranch', { line: l }))]);
271
265
  if (features.has('agentMdFile') && scopeOk('agentMdFile'))
@@ -297,14 +291,15 @@ function buildSteps(sel) {
297
291
  return steps;
298
292
  }
299
293
  function validate(sel) {
300
- const needsScope = ['stopAttributionConfig', 'stopAttributionHook', 'agentMdFile', 'openspec'];
294
+ const needsScope = ['stripCommitJSON', 'agentMdFile', 'openspec'];
301
295
  const hasScoped = needsScope.some(f => sel.features.has(f));
302
296
  if (hasScoped && sel.scopes.size === 0)
303
297
  return 'Attribution and OpenSpec require a scope β€” check Global or Project';
304
298
  return null;
305
299
  }
306
300
  // ── UI components ────────────────────────────────────────────────────────────
307
- const IN_GIT_REPO = isGitRepo();
301
+ const PROJECT_ROOT = detectProjectRoot();
302
+ const IN_GIT_REPO = PROJECT_ROOT !== null;
308
303
  function isDisabledAt(p, idx) {
309
304
  if (p !== 'scope')
310
305
  return false;
@@ -471,30 +466,26 @@ const SelectScreen = ({ onConfirm }) => {
471
466
  return 'partial';
472
467
  return 'unchecked';
473
468
  }
474
- if (item.id === 'stopAttribution.hook')
475
- return sel.features.has('stopAttributionHook') ? 'checked' : 'unchecked';
476
469
  if (item.id === 'stopAttribution.config')
477
- return sel.features.has('stopAttributionConfig') ? 'checked' : 'unchecked';
470
+ return sel.features.has('stripCommitJSON') ? 'checked' : 'unchecked';
478
471
  if (item.id === 'stopAttribution.stripCommit')
479
- return sel.features.has('stripCommitAttribution') ? 'checked' : 'unchecked';
472
+ return sel.features.has('stripCommitHook') ? 'checked' : 'unchecked';
480
473
  if (item.id === 'stopAttribution.stripPr')
481
- return sel.features.has('stripPrAttribution') ? 'checked' : 'unchecked';
474
+ return sel.features.has('stripPrWorkflow') ? 'checked' : 'unchecked';
482
475
  if (item.id === 'stopAttribution.renameBranch')
483
476
  return sel.features.has('renameClaudeBranch') ? 'checked' : 'unchecked';
484
477
  if (item.id === 'stopAttribution.mdFile')
485
478
  return sel.features.has('agentMdFile') ? 'checked' : 'unchecked';
486
479
  if (item.id === 'stopAttribution') {
487
480
  const subs = [];
488
- if (featureItems.some(fi => fi.id === 'stopAttribution.hook'))
489
- subs.push('stopAttributionHook');
490
481
  if (featureItems.some(fi => fi.id === 'stopAttribution.config'))
491
- subs.push('stopAttributionConfig');
482
+ subs.push('stripCommitJSON');
492
483
  if (featureItems.some(fi => fi.id === 'stopAttribution.stripCommit'))
493
- subs.push('stripCommitAttribution');
484
+ subs.push('stripCommitHook');
494
485
  if (featureItems.some(fi => fi.id === 'stopAttribution.renameBranch'))
495
486
  subs.push('renameClaudeBranch');
496
487
  if (featureItems.some(fi => fi.id === 'stopAttribution.stripPr'))
497
- subs.push('stripPrAttribution');
488
+ subs.push('stripPrWorkflow');
498
489
  if (featureItems.some(fi => fi.id === 'stopAttribution.mdFile'))
499
490
  subs.push('agentMdFile');
500
491
  const count = subs.filter(f => sel.features.has(f)).length;
@@ -662,7 +653,7 @@ export const App = () => {
662
653
  const [screen, setScreen] = useState('select');
663
654
  const [selection, setSelection] = useState(defaultSelection());
664
655
  const [formState, setFormState] = useState({ personalize: true, gitUser: '', gitEmail: '', branchPrefix: '' });
665
- const needsForm = (sel) => ['stripCommitAttribution', 'agentMdFile', 'renameClaudeBranch'].some(f => sel.features.has(f));
656
+ const needsForm = (sel) => ['stripCommitHook', 'agentMdFile', 'renameClaudeBranch'].some(f => sel.features.has(f));
666
657
  if (screen === 'select') {
667
658
  return (_jsx(SelectScreen, { onConfirm: (sel, mode) => {
668
659
  setSelection(sel);
package/dist/uninstall.js CHANGED
@@ -2,7 +2,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState, useEffect, useCallback, useMemo } from 'react';
3
3
  import { Box, Text, useInput, useApp } from 'ink';
4
4
  import { existsSync, readFileSync, writeFileSync, unlinkSync, rmSync, readdirSync } from 'fs';
5
- import { execFileSync } from 'child_process';
6
5
  import { join } from 'path';
7
6
  import { HOME, AIRC, CLAUDE_SETTINGS, OPENCODE_CONFIG, HCLAUDE, HOPENCODE, AGENT_IDS, SCOPE_IDS, GIT_SETTING_IDS, FEATURE_ORDER, AGENT_LABELS, SCOPE_LABELS, GIT_SETTING_LABELS, FEATURE_LABELS, getFeatureItems, toggleSelection, removeGitignoreEntries, run, stream, commandExists, nvmShell, readJson, patchJson, HEADROOM_START_FUNC, removeSessionStartHook, AGENT_MD_HEADER, removeSectionByHeader, FEATURE_SCOPES, } from './common.js';
8
7
  // ── Detection ─────────────────────────────────────────────────────────────────
@@ -29,35 +28,23 @@ function detectInstalled() {
29
28
  }
30
29
  const attrGlobal = globalSettings['attribution'];
31
30
  if (attrGlobal?.['commit'] === '') {
32
- sel.features.add('stopAttributionConfig');
31
+ sel.features.add('stripCommitJSON');
33
32
  sel.scopes.add('global');
34
33
  }
35
34
  if (existsSync('.claude/settings.json')) {
36
35
  const pSettings = readJson('.claude/settings.json');
37
36
  const attrProject = pSettings['attribution'];
38
37
  if (attrProject?.['commit'] === '') {
39
- sel.features.add('stopAttributionConfig');
38
+ sel.features.add('stripCommitJSON');
40
39
  sel.scopes.add('project');
41
40
  }
42
41
  }
43
42
  if (existsSync('.claude/hooks/strip-commit-attribution.sh'))
44
- sel.features.add('stripCommitAttribution');
43
+ sel.features.add('stripCommitHook');
45
44
  if (existsSync('.github/workflows/strip-pr-attribution.yml'))
46
- sel.features.add('stripPrAttribution');
45
+ sel.features.add('stripPrWorkflow');
47
46
  if (existsSync('.claude/hooks/strip-claude-branch.sh'))
48
47
  sel.features.add('renameClaudeBranch');
49
- if (existsSync(join(HOME, '.config', 'git', 'hooks', 'commit-msg'))) {
50
- sel.features.add('stopAttributionHook');
51
- sel.scopes.add('global');
52
- }
53
- try {
54
- const gitDir = execFileSync('git', ['rev-parse', '--git-dir'], { encoding: 'utf8' }).trim();
55
- if (existsSync(join(gitDir, 'hooks', 'commit-msg'))) {
56
- sel.features.add('stopAttributionHook');
57
- sel.scopes.add('project');
58
- }
59
- }
60
- catch { /* not in a git repo */ }
61
48
  const envSettings = globalSettings['env'];
62
49
  if (envSettings?.['ENABLE_LSP_TOOL'] === '1') {
63
50
  sel.features.add('lsp');
@@ -148,7 +135,7 @@ async function uninstallStopTelemetry(onLine) {
148
135
  onLine('CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC not found in ~/.airc');
149
136
  }
150
137
  }
151
- async function uninstallStopAttributionConfig(scopes, onLine) {
138
+ async function uninstallStripCommitJSON(scopes, onLine) {
152
139
  if (scopes.has('global')) {
153
140
  patchJson(CLAUDE_SETTINGS, d => { delete d['attribution']; });
154
141
  onLine('attribution removed from ~/.claude/settings.json');
@@ -158,20 +145,7 @@ async function uninstallStopAttributionConfig(scopes, onLine) {
158
145
  onLine('attribution removed from .claude/settings.json');
159
146
  }
160
147
  }
161
- async function uninstallStopAttributionHook(onLine) {
162
- try {
163
- const gitDir = await run('git', ['rev-parse', '--git-dir']);
164
- const hookPath = join(gitDir, 'hooks', 'commit-msg');
165
- if (existsSync(hookPath)) {
166
- unlinkSync(hookPath);
167
- onLine('Project commit-msg hook removed');
168
- }
169
- }
170
- catch {
171
- onLine('Not in a git repo β€” skipping project hook removal');
172
- }
173
- }
174
- async function uninstallStripCommitAttribution(onLine) {
148
+ async function uninstallStripCommitHook(onLine) {
175
149
  const hookPath = '.claude/hooks/strip-commit-attribution.sh';
176
150
  if (existsSync(hookPath)) {
177
151
  unlinkSync(hookPath);
@@ -194,7 +168,7 @@ async function uninstallStripCommitAttribution(onLine) {
194
168
  }
195
169
  catch { /* not in a git repo */ }
196
170
  }
197
- async function uninstallStripPrAttribution(onLine) {
171
+ async function uninstallStripPrWorkflow(onLine) {
198
172
  const wfPath = '.github/workflows/strip-pr-attribution.yml';
199
173
  if (existsSync(wfPath)) {
200
174
  unlinkSync(wfPath);
@@ -318,14 +292,12 @@ export async function runUninstall(selection, updateStep) {
318
292
  tasks.push(['headroom', () => uninstallHeadroom(l => updateStep('headroom', { line: l }))]);
319
293
  if (features.has('stopTelemetry') && scopeOk('stopTelemetry'))
320
294
  tasks.push(['stopTelemetry', () => uninstallStopTelemetry(l => updateStep('stopTelemetry', { line: l }))]);
321
- if (features.has('stopAttributionConfig') && scopeOk('stopAttributionConfig'))
322
- tasks.push(['stopAttributionConfig', () => uninstallStopAttributionConfig(scopes, l => updateStep('stopAttributionConfig', { line: l }))]);
323
- if (features.has('stopAttributionHook') && scopeOk('stopAttributionHook'))
324
- tasks.push(['stopAttributionHook', () => uninstallStopAttributionHook(l => updateStep('stopAttributionHook', { line: l }))]);
325
- if (features.has('stripCommitAttribution') && scopeOk('stripCommitAttribution'))
326
- tasks.push(['stripCommitAttribution', () => uninstallStripCommitAttribution(l => updateStep('stripCommitAttribution', { line: l }))]);
327
- if (features.has('stripPrAttribution') && scopeOk('stripPrAttribution'))
328
- tasks.push(['stripPrAttribution', () => uninstallStripPrAttribution(l => updateStep('stripPrAttribution', { line: l }))]);
295
+ if (features.has('stripCommitJSON') && scopeOk('stripCommitJSON'))
296
+ tasks.push(['stripCommitJSON', () => uninstallStripCommitJSON(scopes, l => updateStep('stripCommitJSON', { line: l }))]);
297
+ if (features.has('stripCommitHook') && scopeOk('stripCommitHook'))
298
+ tasks.push(['stripCommitHook', () => uninstallStripCommitHook(l => updateStep('stripCommitHook', { line: l }))]);
299
+ if (features.has('stripPrWorkflow') && scopeOk('stripPrWorkflow'))
300
+ tasks.push(['stripPrWorkflow', () => uninstallStripPrWorkflow(l => updateStep('stripPrWorkflow', { line: l }))]);
329
301
  if (features.has('renameClaudeBranch') && scopeOk('renameClaudeBranch'))
330
302
  tasks.push(['renameClaudeBranch', () => uninstallRenameClaudeBranch(l => updateStep('renameClaudeBranch', { line: l }))]);
331
303
  if (features.has('agentMdFile') && scopeOk('agentMdFile'))
@@ -359,7 +331,7 @@ function buildSteps(sel) {
359
331
  function validate(sel) {
360
332
  if (sel.features.size === 0)
361
333
  return 'Nothing selected to uninstall';
362
- const needsScope = ['stopAttributionConfig', 'stopAttributionHook', 'agentMdFile'];
334
+ const needsScope = ['stripCommitJSON', 'agentMdFile'];
363
335
  const hasScoped = needsScope.some(f => sel.features.has(f));
364
336
  if (hasScoped && sel.scopes.size === 0)
365
337
  return 'Attribution features require a scope β€” check Global or Project';
@@ -497,30 +469,26 @@ const SelectScreen = ({ initialSel, onConfirm }) => {
497
469
  return 'partial';
498
470
  return 'unchecked';
499
471
  }
500
- if (item.id === 'stopAttribution.hook')
501
- return sel.features.has('stopAttributionHook') ? 'checked' : 'unchecked';
502
472
  if (item.id === 'stopAttribution.config')
503
- return sel.features.has('stopAttributionConfig') ? 'checked' : 'unchecked';
473
+ return sel.features.has('stripCommitJSON') ? 'checked' : 'unchecked';
504
474
  if (item.id === 'stopAttribution.stripCommit')
505
- return sel.features.has('stripCommitAttribution') ? 'checked' : 'unchecked';
475
+ return sel.features.has('stripCommitHook') ? 'checked' : 'unchecked';
506
476
  if (item.id === 'stopAttribution.stripPr')
507
- return sel.features.has('stripPrAttribution') ? 'checked' : 'unchecked';
477
+ return sel.features.has('stripPrWorkflow') ? 'checked' : 'unchecked';
508
478
  if (item.id === 'stopAttribution.renameBranch')
509
479
  return sel.features.has('renameClaudeBranch') ? 'checked' : 'unchecked';
510
480
  if (item.id === 'stopAttribution.mdFile')
511
481
  return sel.features.has('agentMdFile') ? 'checked' : 'unchecked';
512
482
  if (item.id === 'stopAttribution') {
513
483
  const subs = [];
514
- if (featureItems.some(fi => fi.id === 'stopAttribution.hook'))
515
- subs.push('stopAttributionHook');
516
484
  if (featureItems.some(fi => fi.id === 'stopAttribution.config'))
517
- subs.push('stopAttributionConfig');
485
+ subs.push('stripCommitJSON');
518
486
  if (featureItems.some(fi => fi.id === 'stopAttribution.stripCommit'))
519
- subs.push('stripCommitAttribution');
487
+ subs.push('stripCommitHook');
520
488
  if (featureItems.some(fi => fi.id === 'stopAttribution.renameBranch'))
521
489
  subs.push('renameClaudeBranch');
522
490
  if (featureItems.some(fi => fi.id === 'stopAttribution.stripPr'))
523
- subs.push('stripPrAttribution');
491
+ subs.push('stripPrWorkflow');
524
492
  if (featureItems.some(fi => fi.id === 'stopAttribution.mdFile'))
525
493
  subs.push('agentMdFile');
526
494
  const count = subs.filter(f => sel.features.has(f)).length;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buffbirb/unclaude",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "An opinionated AI dev tool setup script with a terminal UI. Configure privacy, code intelligence, and tool wrappers for Claude Code and OpenCode.",
5
5
  "type": "module",
6
6
  "keywords": [