@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 +4 -6
- package/dist/common.js +69 -46
- package/dist/install.js +28 -37
- package/dist/uninstall.js +20 -52
- package/package.json +1 -1
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
|
-
|
|
|
49
|
-
| Agent SessionStart hook for git
|
|
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
|
-
ΒΉ
|
|
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`, `
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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', '
|
|
42
|
-
'
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
stripCommitAttribution: ['claudeCode'],
|
|
57
|
+
stripCommitJSON: ['claudeCode'],
|
|
58
|
+
stripCommitHook: ['claudeCode'],
|
|
62
59
|
renameClaudeBranch: ['claudeCode'],
|
|
63
|
-
|
|
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', '
|
|
71
|
-
'
|
|
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.
|
|
101
|
-
{ id: 'stopAttribution.
|
|
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: '
|
|
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('
|
|
196
|
-
next.features.delete('
|
|
185
|
+
if (next.features.has('stripCommitJSON'))
|
|
186
|
+
next.features.delete('stripCommitJSON');
|
|
197
187
|
else
|
|
198
|
-
next.features.add('
|
|
188
|
+
next.features.add('stripCommitJSON');
|
|
199
189
|
}
|
|
200
190
|
else if (item.id === 'stopAttribution.stripCommit') {
|
|
201
|
-
if (next.features.has('
|
|
202
|
-
next.features.delete('
|
|
191
|
+
if (next.features.has('stripCommitHook'))
|
|
192
|
+
next.features.delete('stripCommitHook');
|
|
203
193
|
else
|
|
204
|
-
next.features.add('
|
|
194
|
+
next.features.add('stripCommitHook');
|
|
205
195
|
}
|
|
206
196
|
else if (item.id === 'stopAttribution.stripPr') {
|
|
207
|
-
if (next.features.has('
|
|
208
|
-
next.features.delete('
|
|
197
|
+
if (next.features.has('stripPrWorkflow'))
|
|
198
|
+
next.features.delete('stripPrWorkflow');
|
|
209
199
|
else
|
|
210
|
-
next.features.add('
|
|
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('
|
|
217
|
+
visibleSubs.push('stripCommitJSON');
|
|
230
218
|
if (featureItems.some(fi => fi.id === 'stopAttribution.stripCommit'))
|
|
231
|
-
visibleSubs.push('
|
|
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('
|
|
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
|
-
|
|
436
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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('
|
|
262
|
-
tasks.push(['
|
|
263
|
-
if (features.has('
|
|
264
|
-
tasks.push(['
|
|
265
|
-
if (features.has('
|
|
266
|
-
tasks.push(['
|
|
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 = ['
|
|
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
|
|
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('
|
|
470
|
+
return sel.features.has('stripCommitJSON') ? 'checked' : 'unchecked';
|
|
478
471
|
if (item.id === 'stopAttribution.stripCommit')
|
|
479
|
-
return sel.features.has('
|
|
472
|
+
return sel.features.has('stripCommitHook') ? 'checked' : 'unchecked';
|
|
480
473
|
if (item.id === 'stopAttribution.stripPr')
|
|
481
|
-
return sel.features.has('
|
|
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('
|
|
482
|
+
subs.push('stripCommitJSON');
|
|
492
483
|
if (featureItems.some(fi => fi.id === 'stopAttribution.stripCommit'))
|
|
493
|
-
subs.push('
|
|
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('
|
|
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) => ['
|
|
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('
|
|
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('
|
|
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('
|
|
43
|
+
sel.features.add('stripCommitHook');
|
|
45
44
|
if (existsSync('.github/workflows/strip-pr-attribution.yml'))
|
|
46
|
-
sel.features.add('
|
|
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
|
|
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
|
|
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
|
|
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('
|
|
322
|
-
tasks.push(['
|
|
323
|
-
if (features.has('
|
|
324
|
-
tasks.push(['
|
|
325
|
-
if (features.has('
|
|
326
|
-
tasks.push(['
|
|
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 = ['
|
|
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('
|
|
473
|
+
return sel.features.has('stripCommitJSON') ? 'checked' : 'unchecked';
|
|
504
474
|
if (item.id === 'stopAttribution.stripCommit')
|
|
505
|
-
return sel.features.has('
|
|
475
|
+
return sel.features.has('stripCommitHook') ? 'checked' : 'unchecked';
|
|
506
476
|
if (item.id === 'stopAttribution.stripPr')
|
|
507
|
-
return sel.features.has('
|
|
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('
|
|
485
|
+
subs.push('stripCommitJSON');
|
|
518
486
|
if (featureItems.some(fi => fi.id === 'stopAttribution.stripCommit'))
|
|
519
|
-
subs.push('
|
|
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('
|
|
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.
|
|
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": [
|