ccperm 1.2.1 → 1.3.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.
@@ -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, deprecatedCount: 0, groups: new Map() };
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', '--fix', '--update', '--help', '-h', '--version', '-v']);
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')) {
@@ -58,23 +54,18 @@ async function main() {
58
54
  process.exit(1);
59
55
  }
60
56
  const isCwd = args.includes('--cwd');
61
- const isFix = args.includes('--fix');
62
57
  const isVerbose = args.includes('--verbose');
63
58
  const isStatic = args.includes('--static') || !process.stdout.isTTY;
64
59
  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
60
  const searchDir = isCwd ? process.cwd() : node_os_1.default.homedir();
66
61
  console.log(` Scope: ${colors_js_1.YELLOW}${isCwd ? searchDir : '~ (all projects)'}${colors_js_1.NC}`);
67
- const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
68
- let frame = 0;
69
62
  const isTTY = process.stdout.isTTY;
70
- const spinner = isTTY ? setInterval(() => {
71
- process.stdout.write(`\r ${colors_js_1.CYAN}${frames[frame++ % frames.length]}${colors_js_1.NC} Scanning...`);
72
- }, 80) : null;
73
- const files = await (0, scanner_js_1.findSettingsFiles)(searchDir);
74
- if (spinner) {
75
- clearInterval(spinner);
63
+ const onProgress = isTTY ? (count) => {
64
+ process.stdout.write(`\r ${colors_js_1.CYAN}⠹${colors_js_1.NC} Scanning... ${colors_js_1.BOLD}${count}${colors_js_1.NC} found`);
65
+ } : undefined;
66
+ const files = await (0, scanner_js_1.findSettingsFiles)(searchDir, onProgress);
67
+ if (isTTY)
76
68
  process.stdout.write('\r\x1b[K');
77
- }
78
69
  if (files.length === 0) {
79
70
  console.log(` ${colors_js_1.GREEN}✔ No settings files found.${colors_js_1.NC}\n`);
80
71
  return;
@@ -83,7 +74,7 @@ async function main() {
83
74
  const results = files.map(scanner_js_1.scanFile).filter((r) => r !== null);
84
75
  const merged = (0, aggregator_js_1.mergeByProject)(results);
85
76
  const summary = (0, aggregator_js_1.summarize)(results);
86
- if (!isStatic && !isFix) {
77
+ if (!isStatic) {
87
78
  await (0, interactive_js_1.startInteractive)(merged, results);
88
79
  return;
89
80
  }
@@ -94,16 +85,16 @@ async function main() {
94
85
  (0, renderer_js_1.printCompact)(merged, summary);
95
86
  }
96
87
  (0, renderer_js_1.printFooter)(summary);
97
- if (summary.deprecatedTotal === 0) {
98
- return;
99
- }
100
- if (!isFix) {
101
- const verboseHint = isVerbose ? '' : `\n ${colors_js_1.DIM}Use ${colors_js_1.NC}--verbose${colors_js_1.DIM} to see all permissions${colors_js_1.NC}`;
102
- console.log(`\n Run with ${colors_js_1.YELLOW}--fix${colors_js_1.NC} to auto-fix.${verboseHint}\n`);
103
- process.exit(1);
88
+ if (args.includes('--fix')) {
89
+ const affected = (0, scanner_js_1.countDeprecated)(results);
90
+ if (affected.length === 0) {
91
+ console.log(`\n ${colors_js_1.GREEN}✔ Nothing to fix.${colors_js_1.NC}\n`);
92
+ return;
93
+ }
94
+ const { totalPatterns, fixedFiles } = (0, fixer_js_1.fixFiles)(affected);
95
+ console.log(`\n ${colors_js_1.GREEN}✔ Fixed ${totalPatterns} patterns in ${fixedFiles} files.${colors_js_1.NC}`);
96
+ console.log(` ${colors_js_1.DIM}Restart Claude Code for changes to take effect.${colors_js_1.NC}\n`);
104
97
  }
105
- console.log('');
106
- (0, renderer_js_1.printFixResult)((0, fixer_js_1.fixFiles)(summary.affectedFiles));
107
98
  }
108
99
  main();
109
100
  (0, updater_js_1.notifyUpdate)();
@@ -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
- if (item.deprecated) {
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 hasDeprecated = result.deprecatedCount > 0;
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
- if (item.deprecated) {
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.DEPRECATED_PERM_RE = void 0;
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
- exports.DEPRECATED_PERM_RE = /:\*$|:\*\)/;
15
- function findSettingsFiles(searchDir) {
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
- (0, node_child_process_1.execFile)('find', [
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 }, (err, stdout) => {
26
- const out = (stdout || err?.stdout || '').trim();
27
- const lines = out ? out.split('\n').filter(Boolean) : [];
28
- resolve(lines.filter((f) => {
29
- try {
30
- node_fs_1.default.accessSync(f, node_fs_1.default.constants.W_OK);
31
- return true;
32
- }
33
- catch {
34
- return false;
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, deprecatedCount };
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, deprecated: exports.DEPRECATED_PERM_RE.test(p) });
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) }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccperm",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "Audit Claude Code permissions across all your projects",
5
5
  "bin": {
6
6
  "ccperm": "bin/ccperm.js"