ccperm 1.2.1 → 1.3.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/dist/aggregator.js +1 -13
- package/dist/cli.js +26 -27
- package/dist/interactive.js +1 -6
- package/dist/renderer.js +3 -23
- package/dist/scanner.js +49 -20
- package/package.json +1 -1
package/dist/aggregator.js
CHANGED
|
@@ -18,11 +18,10 @@ function mergeByProject(results) {
|
|
|
18
18
|
const dir = projectDir(r.display);
|
|
19
19
|
let merged = map.get(dir);
|
|
20
20
|
if (!merged) {
|
|
21
|
-
merged = { display: r.display, shortName: shortPath(r.display), totalCount: 0,
|
|
21
|
+
merged = { display: r.display, shortName: shortPath(r.display), totalCount: 0, groups: new Map() };
|
|
22
22
|
map.set(dir, merged);
|
|
23
23
|
}
|
|
24
24
|
merged.totalCount += r.totalCount;
|
|
25
|
-
merged.deprecatedCount += r.deprecatedCount;
|
|
26
25
|
for (const g of r.groups) {
|
|
27
26
|
merged.groups.set(g.category, (merged.groups.get(g.category) || 0) + g.items.length);
|
|
28
27
|
}
|
|
@@ -32,15 +31,7 @@ function mergeByProject(results) {
|
|
|
32
31
|
function summarize(results) {
|
|
33
32
|
const merged = mergeByProject(results);
|
|
34
33
|
const categoryTotals = new Map();
|
|
35
|
-
let deprecatedTotal = 0;
|
|
36
|
-
let deprecatedFiles = 0;
|
|
37
|
-
const affectedFiles = [];
|
|
38
34
|
for (const r of results) {
|
|
39
|
-
if (r.deprecatedCount > 0) {
|
|
40
|
-
deprecatedTotal += r.deprecatedCount;
|
|
41
|
-
deprecatedFiles++;
|
|
42
|
-
affectedFiles.push({ path: r.path, count: r.deprecatedCount });
|
|
43
|
-
}
|
|
44
35
|
for (const group of r.groups) {
|
|
45
36
|
categoryTotals.set(group.category, (categoryTotals.get(group.category) || 0) + group.items.length);
|
|
46
37
|
}
|
|
@@ -53,9 +44,6 @@ function summarize(results) {
|
|
|
53
44
|
projectsWithPerms,
|
|
54
45
|
projectsEmpty,
|
|
55
46
|
totalPerms,
|
|
56
|
-
deprecatedTotal,
|
|
57
|
-
deprecatedFiles,
|
|
58
47
|
categoryTotals,
|
|
59
|
-
affectedFiles,
|
|
60
48
|
};
|
|
61
49
|
}
|
package/dist/cli.js
CHANGED
|
@@ -12,7 +12,7 @@ const updater_js_1 = require("./updater.js");
|
|
|
12
12
|
const aggregator_js_1 = require("./aggregator.js");
|
|
13
13
|
const renderer_js_1 = require("./renderer.js");
|
|
14
14
|
const interactive_js_1 = require("./interactive.js");
|
|
15
|
-
const KNOWN_FLAGS = new Set(['--cwd', '--verbose', '--static', '--
|
|
15
|
+
const KNOWN_FLAGS = new Set(['--cwd', '--verbose', '--static', '--update', '--fix', '--help', '-h', '--version', '-v']);
|
|
16
16
|
const HELP = `${colors_js_1.CYAN}ccperm${colors_js_1.NC} — Audit Claude Code permissions across projects
|
|
17
17
|
|
|
18
18
|
${colors_js_1.YELLOW}Usage:${colors_js_1.NC}
|
|
@@ -25,11 +25,7 @@ ${colors_js_1.YELLOW}Options:${colors_js_1.NC}
|
|
|
25
25
|
--static Force static output (default in non-TTY)
|
|
26
26
|
--update Update ccperm to latest version
|
|
27
27
|
--help, -h Show this help
|
|
28
|
-
--version, -v Show version
|
|
29
|
-
|
|
30
|
-
${colors_js_1.YELLOW}Legacy:${colors_js_1.NC}
|
|
31
|
-
--fix Auto-fix deprecated :* patterns (will be removed once
|
|
32
|
-
Claude Code patches this upstream)`;
|
|
28
|
+
--version, -v Show version`;
|
|
33
29
|
async function main() {
|
|
34
30
|
const args = process.argv.slice(2);
|
|
35
31
|
if (args.includes('--help') || args.includes('-h')) {
|
|
@@ -41,9 +37,17 @@ async function main() {
|
|
|
41
37
|
return;
|
|
42
38
|
}
|
|
43
39
|
if (args.includes('--update')) {
|
|
44
|
-
|
|
40
|
+
const before = (0, updater_js_1.getVersion)();
|
|
41
|
+
console.log(` Updating ccperm from ${colors_js_1.DIM}v${before}${colors_js_1.NC}...\n`);
|
|
45
42
|
try {
|
|
46
43
|
(0, node_child_process_1.execFileSync)('npm', ['install', '-g', 'ccperm@latest'], { stdio: 'inherit' });
|
|
44
|
+
const after = (0, node_child_process_1.execFileSync)('ccperm', ['-v'], { encoding: 'utf8' }).trim();
|
|
45
|
+
if (after === before) {
|
|
46
|
+
console.log(`\n ${colors_js_1.GREEN}✔ Already up to date. ${colors_js_1.BOLD}v${after}${colors_js_1.NC}`);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
console.log(`\n ${colors_js_1.GREEN}✔ Updated! ${colors_js_1.DIM}v${before}${colors_js_1.NC} → ${colors_js_1.GREEN}${colors_js_1.BOLD}v${after}${colors_js_1.NC}`);
|
|
50
|
+
}
|
|
47
51
|
}
|
|
48
52
|
catch {
|
|
49
53
|
console.error(`\n ${colors_js_1.RED}Update failed. Try manually: npm install -g ccperm@latest${colors_js_1.NC}\n`);
|
|
@@ -58,23 +62,18 @@ async function main() {
|
|
|
58
62
|
process.exit(1);
|
|
59
63
|
}
|
|
60
64
|
const isCwd = args.includes('--cwd');
|
|
61
|
-
const isFix = args.includes('--fix');
|
|
62
65
|
const isVerbose = args.includes('--verbose');
|
|
63
66
|
const isStatic = args.includes('--static') || !process.stdout.isTTY;
|
|
64
67
|
console.log(`\n ${colors_js_1.CYAN}${colors_js_1.BOLD}ccperm${colors_js_1.NC} ${colors_js_1.DIM}v${(0, updater_js_1.getVersion)()}${colors_js_1.NC} — Claude Code Permission Audit\n`);
|
|
65
68
|
const searchDir = isCwd ? process.cwd() : node_os_1.default.homedir();
|
|
66
69
|
console.log(` Scope: ${colors_js_1.YELLOW}${isCwd ? searchDir : '~ (all projects)'}${colors_js_1.NC}`);
|
|
67
|
-
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
68
|
-
let frame = 0;
|
|
69
70
|
const isTTY = process.stdout.isTTY;
|
|
70
|
-
const
|
|
71
|
-
process.stdout.write(`\r ${colors_js_1.CYAN}
|
|
72
|
-
}
|
|
73
|
-
const files = await (0, scanner_js_1.findSettingsFiles)(searchDir);
|
|
74
|
-
if (
|
|
75
|
-
clearInterval(spinner);
|
|
71
|
+
const onProgress = isTTY ? (count) => {
|
|
72
|
+
process.stdout.write(`\r ${colors_js_1.CYAN}⠹${colors_js_1.NC} Scanning... ${colors_js_1.BOLD}${count}${colors_js_1.NC} found`);
|
|
73
|
+
} : undefined;
|
|
74
|
+
const files = await (0, scanner_js_1.findSettingsFiles)(searchDir, onProgress);
|
|
75
|
+
if (isTTY)
|
|
76
76
|
process.stdout.write('\r\x1b[K');
|
|
77
|
-
}
|
|
78
77
|
if (files.length === 0) {
|
|
79
78
|
console.log(` ${colors_js_1.GREEN}✔ No settings files found.${colors_js_1.NC}\n`);
|
|
80
79
|
return;
|
|
@@ -83,7 +82,7 @@ async function main() {
|
|
|
83
82
|
const results = files.map(scanner_js_1.scanFile).filter((r) => r !== null);
|
|
84
83
|
const merged = (0, aggregator_js_1.mergeByProject)(results);
|
|
85
84
|
const summary = (0, aggregator_js_1.summarize)(results);
|
|
86
|
-
if (!isStatic
|
|
85
|
+
if (!isStatic) {
|
|
87
86
|
await (0, interactive_js_1.startInteractive)(merged, results);
|
|
88
87
|
return;
|
|
89
88
|
}
|
|
@@ -94,16 +93,16 @@ async function main() {
|
|
|
94
93
|
(0, renderer_js_1.printCompact)(merged, summary);
|
|
95
94
|
}
|
|
96
95
|
(0, renderer_js_1.printFooter)(summary);
|
|
97
|
-
if (
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
96
|
+
if (args.includes('--fix')) {
|
|
97
|
+
const affected = (0, scanner_js_1.countDeprecated)(results);
|
|
98
|
+
if (affected.length === 0) {
|
|
99
|
+
console.log(`\n ${colors_js_1.GREEN}✔ Nothing to fix.${colors_js_1.NC}\n`);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const { totalPatterns, fixedFiles } = (0, fixer_js_1.fixFiles)(affected);
|
|
103
|
+
console.log(`\n ${colors_js_1.GREEN}✔ Fixed ${totalPatterns} patterns in ${fixedFiles} files.${colors_js_1.NC}`);
|
|
104
|
+
console.log(` ${colors_js_1.DIM}Restart Claude Code for changes to take effect.${colors_js_1.NC}\n`);
|
|
104
105
|
}
|
|
105
|
-
console.log('');
|
|
106
|
-
(0, renderer_js_1.printFixResult)((0, fixer_js_1.fixFiles)(summary.affectedFiles));
|
|
107
106
|
}
|
|
108
107
|
main();
|
|
109
108
|
(0, updater_js_1.notifyUpdate)();
|
package/dist/interactive.js
CHANGED
|
@@ -139,12 +139,7 @@ function renderDetail(state, withPerms, results) {
|
|
|
139
139
|
for (const group of result.groups) {
|
|
140
140
|
lines.push(` ${colors_js_1.YELLOW}${group.category}${colors_js_1.NC} ${colors_js_1.DIM}(${group.items.length})${colors_js_1.NC}`);
|
|
141
141
|
for (const item of group.items) {
|
|
142
|
-
|
|
143
|
-
lines.push(` ${colors_js_1.RED}✖ ${item.name}${colors_js_1.NC} ${colors_js_1.RED}← deprecated${colors_js_1.NC}`);
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
lines.push(` ${colors_js_1.DIM}${item.name}${colors_js_1.NC}`);
|
|
147
|
-
}
|
|
142
|
+
lines.push(` ${colors_js_1.DIM}${item.name}${colors_js_1.NC}`);
|
|
148
143
|
}
|
|
149
144
|
}
|
|
150
145
|
lines.push('');
|
package/dist/renderer.js
CHANGED
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.printCompact = printCompact;
|
|
4
4
|
exports.printVerbose = printVerbose;
|
|
5
5
|
exports.printFooter = printFooter;
|
|
6
|
-
exports.printFixResult = printFixResult;
|
|
7
6
|
const colors_js_1 = require("./colors.js");
|
|
8
7
|
function pad(s, n) {
|
|
9
8
|
return s.length >= n ? s : s + ' '.repeat(n - s.length);
|
|
@@ -25,11 +24,7 @@ function printCompact(merged, summary) {
|
|
|
25
24
|
// rows
|
|
26
25
|
for (const result of withPerms) {
|
|
27
26
|
const truncName = result.shortName.length > nameWidth ? result.shortName.slice(0, nameWidth - 1) + '…' : result.shortName;
|
|
28
|
-
const
|
|
29
|
-
const marker = hasDeprecated ? `${colors_js_1.RED}✖ ` : ' ';
|
|
30
|
-
const nameCol = hasDeprecated
|
|
31
|
-
? `${marker}${pad(truncName, nameWidth)}${colors_js_1.NC}`
|
|
32
|
-
: ` ${colors_js_1.DIM}${pad(truncName, nameWidth)}${colors_js_1.NC}`;
|
|
27
|
+
const nameCol = ` ${colors_js_1.DIM}${pad(truncName, nameWidth)}${colors_js_1.NC}`;
|
|
33
28
|
const catCols = catsPresent.map((c) => {
|
|
34
29
|
const count = result.groups.get(c) || 0;
|
|
35
30
|
return count > 0 ? rpad(count, 5) : `${colors_js_1.DIM}${rpad('·', 5)}${colors_js_1.NC}`;
|
|
@@ -51,12 +46,7 @@ function printVerbose(results, summary) {
|
|
|
51
46
|
for (const group of result.groups) {
|
|
52
47
|
console.log(` ${colors_js_1.YELLOW}${group.category}${colors_js_1.NC} ${colors_js_1.DIM}(${group.items.length})${colors_js_1.NC}`);
|
|
53
48
|
for (const item of group.items) {
|
|
54
|
-
|
|
55
|
-
console.log(` ${colors_js_1.RED}✖ ${item.name}${colors_js_1.NC} ${colors_js_1.RED}← deprecated${colors_js_1.NC}`);
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
console.log(` ${colors_js_1.DIM}${item.name}${colors_js_1.NC}`);
|
|
59
|
-
}
|
|
49
|
+
console.log(` ${colors_js_1.DIM}${item.name}${colors_js_1.NC}`);
|
|
60
50
|
}
|
|
61
51
|
}
|
|
62
52
|
console.log('');
|
|
@@ -68,15 +58,5 @@ function printVerbose(results, summary) {
|
|
|
68
58
|
function printFooter(summary) {
|
|
69
59
|
console.log(`\n ${colors_js_1.DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors_js_1.NC}`);
|
|
70
60
|
const catSummary = [...summary.categoryTotals.entries()].map(([k, v]) => `${k}: ${colors_js_1.BOLD}${v}${colors_js_1.NC}${colors_js_1.DIM}`).join(' ');
|
|
71
|
-
console.log(` ${colors_js_1.BOLD}${summary.projectsWithPerms}${colors_js_1.NC} projects ${colors_js_1.BOLD}${summary.totalPerms}${colors_js_1.NC} permissions ${colors_js_1.DIM}(${catSummary})${colors_js_1.NC}`);
|
|
72
|
-
if (summary.deprecatedTotal === 0) {
|
|
73
|
-
console.log(` ${colors_js_1.GREEN}✔ All clean — no deprecated patterns${colors_js_1.NC}\n`);
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
console.log(` ${colors_js_1.RED}✖ ${summary.deprecatedTotal} deprecated patterns in ${summary.deprecatedFiles} files${colors_js_1.NC}`);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
function printFixResult(result) {
|
|
80
|
-
console.log(` ${colors_js_1.GREEN}✔ Fixed ${result.totalPatterns} patterns in ${result.fixedFiles} files.${colors_js_1.NC}`);
|
|
81
|
-
console.log(` ${colors_js_1.DIM}Start a new session for changes to take effect.${colors_js_1.NC}\n`);
|
|
61
|
+
console.log(` ${colors_js_1.BOLD}${summary.projectsWithPerms}${colors_js_1.NC} projects ${colors_js_1.BOLD}${summary.totalPerms}${colors_js_1.NC} permissions ${colors_js_1.DIM}(${catSummary})${colors_js_1.NC}\n`);
|
|
82
62
|
}
|
package/dist/scanner.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
6
|
+
exports.countDeprecated = countDeprecated;
|
|
7
7
|
exports.findSettingsFiles = findSettingsFiles;
|
|
8
8
|
exports.scanFile = scanFile;
|
|
9
9
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
@@ -11,30 +11,61 @@ const node_os_1 = __importDefault(require("node:os"));
|
|
|
11
11
|
const node_child_process_1 = require("node:child_process");
|
|
12
12
|
const PERM_RE = /"(Bash|Write|Edit|Read|Glob|Grep|WebSearch|WebFetch|mcp_)[^"]*"/g;
|
|
13
13
|
const DEPRECATED_RE = /:\*\)|:\*"/g;
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
function countDeprecated(results) {
|
|
15
|
+
const out = [];
|
|
16
|
+
for (const r of results) {
|
|
17
|
+
let content;
|
|
18
|
+
try {
|
|
19
|
+
content = node_fs_1.default.readFileSync(r.path, 'utf8');
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const m = content.match(DEPRECATED_RE);
|
|
25
|
+
if (m)
|
|
26
|
+
out.push({ path: r.path, count: m.length });
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
function isWritable(f) {
|
|
31
|
+
try {
|
|
32
|
+
node_fs_1.default.accessSync(f, node_fs_1.default.constants.W_OK);
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function findSettingsFiles(searchDir, onProgress) {
|
|
16
40
|
const prune = ['node_modules', '.git', '.cache', '.local', '.npm', '.nvm', '.bun',
|
|
17
41
|
'snap', '.vscode', '.docker', '.cargo', '.rustup', 'go', '.gradle', '.m2',
|
|
18
42
|
'Library', '.Trash', 'Pictures', 'Music', 'Videos', 'Downloads'];
|
|
19
43
|
const pruneArgs = prune.flatMap((d) => ['-name', d, '-o']).slice(0, -1);
|
|
20
44
|
return new Promise((resolve) => {
|
|
21
|
-
|
|
45
|
+
const results = [];
|
|
46
|
+
let buf = '';
|
|
47
|
+
const child = (0, node_child_process_1.execFile)('find', [
|
|
22
48
|
searchDir,
|
|
23
49
|
'(', ...pruneArgs, ')', '-prune',
|
|
24
50
|
'-o', '-path', '*/.claude/settings*.json', '-type', 'f', '-print',
|
|
25
|
-
], { encoding: 'utf8', timeout: 15000 }
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
51
|
+
], { encoding: 'utf8', timeout: 15000 });
|
|
52
|
+
child.stdout?.on('data', (chunk) => {
|
|
53
|
+
buf += chunk;
|
|
54
|
+
const lines = buf.split('\n');
|
|
55
|
+
buf = lines.pop() || '';
|
|
56
|
+
for (const line of lines) {
|
|
57
|
+
if (line && isWritable(line))
|
|
58
|
+
results.push(line);
|
|
59
|
+
}
|
|
60
|
+
onProgress?.(results.length);
|
|
61
|
+
});
|
|
62
|
+
child.on('close', () => {
|
|
63
|
+
if (buf && isWritable(buf))
|
|
64
|
+
results.push(buf);
|
|
65
|
+
onProgress?.(results.length);
|
|
66
|
+
resolve(results);
|
|
37
67
|
});
|
|
68
|
+
child.on('error', () => resolve(results));
|
|
38
69
|
});
|
|
39
70
|
}
|
|
40
71
|
function scanFile(filePath) {
|
|
@@ -48,11 +79,9 @@ function scanFile(filePath) {
|
|
|
48
79
|
return null;
|
|
49
80
|
}
|
|
50
81
|
const perms = [...new Set((content.match(PERM_RE) || []).map((s) => s.slice(1, -1)))].sort();
|
|
51
|
-
const matches = content.match(DEPRECATED_RE);
|
|
52
|
-
const deprecatedCount = matches ? matches.length : 0;
|
|
53
82
|
const groups = groupPermissions(perms);
|
|
54
83
|
const totalCount = perms.length;
|
|
55
|
-
return { path: filePath, display, permissions: perms, groups, totalCount
|
|
84
|
+
return { path: filePath, display, permissions: perms, groups, totalCount };
|
|
56
85
|
}
|
|
57
86
|
function categorize(perm) {
|
|
58
87
|
if (perm.startsWith('Bash')) {
|
|
@@ -77,7 +106,7 @@ function groupPermissions(perms) {
|
|
|
77
106
|
const { category, label } = categorize(p);
|
|
78
107
|
if (!map.has(category))
|
|
79
108
|
map.set(category, []);
|
|
80
|
-
map.get(category).push({ name: label
|
|
109
|
+
map.get(category).push({ name: label });
|
|
81
110
|
}
|
|
82
111
|
const order = ['Bash', 'WebFetch', 'MCP', 'Tools', 'Other'];
|
|
83
112
|
return order.filter((c) => map.has(c)).map((c) => ({ category: c, items: map.get(c) }));
|