ccperm 1.9.7 → 1.10.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.
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.analyze = analyze;
4
+ const aggregator_js_1 = require("./aggregator.js");
5
+ const RISKY = new Set(['rm', 'sudo', 'chmod', 'chown', 'kill', 'dd', 'ssh', 'scp', 'aws', 'gcloud', 'az', 'kubectl', 'terraform']);
6
+ function extractCmd(label) {
7
+ return label.replace(/__NEW_LINE_[a-f0-9]+__\s*/, '').replace(/[:]\*.*$/, '').replace(/\s\*.*$/, '').split(/[\s(]/)[0];
8
+ }
9
+ function analyze(results) {
10
+ const entries = (0, aggregator_js_1.toFileEntries)(results);
11
+ const withPerms = entries.filter((e) => e.totalCount > 0);
12
+ const hints = [];
13
+ // 1. Find frequently repeated bash commands → suggest global
14
+ const bashCmdProjects = new Map();
15
+ for (const r of results) {
16
+ const dir = (0, aggregator_js_1.projectDir)(r.display);
17
+ for (const g of r.groups) {
18
+ if (g.category !== 'Bash')
19
+ continue;
20
+ for (const item of g.items) {
21
+ const cmd = extractCmd(item.name);
22
+ if (!cmd)
23
+ continue;
24
+ if (!bashCmdProjects.has(cmd))
25
+ bashCmdProjects.set(cmd, new Set());
26
+ bashCmdProjects.get(cmd).add(dir);
27
+ }
28
+ }
29
+ }
30
+ const frequent = [...bashCmdProjects.entries()]
31
+ .filter(([, dirs]) => dirs.size >= 5)
32
+ .sort((a, b) => b[1].size - a[1].size)
33
+ .slice(0, 5);
34
+ if (frequent.length > 0) {
35
+ const cmds = frequent.map(([cmd, dirs]) => `${cmd} (${dirs.size} projects)`).join(', ');
36
+ hints.push({
37
+ type: 'consolidate',
38
+ message: `Common commands found across many projects: ${cmds}. Consider adding these to ~/.claude/settings.json to allow globally instead of per-project.`,
39
+ });
40
+ }
41
+ // 2. Find risky permissions
42
+ const riskyFound = [];
43
+ for (const r of results) {
44
+ for (const g of r.groups) {
45
+ if (g.category !== 'Bash')
46
+ continue;
47
+ for (const item of g.items) {
48
+ const cmd = extractCmd(item.name);
49
+ if (RISKY.has(cmd)) {
50
+ const dir = (0, aggregator_js_1.projectDir)(r.display);
51
+ const shortDir = dir.replace(/.*\//, '');
52
+ const file = r.display.includes('settings.local.json') ? 'local' : 'shared';
53
+ riskyFound.push({ cmd, project: shortDir, file });
54
+ }
55
+ }
56
+ }
57
+ }
58
+ if (riskyFound.length > 0) {
59
+ const items = riskyFound.slice(0, 5).map((r) => `${r.cmd} in ${r.project} (${r.file})`).join(', ');
60
+ hints.push({
61
+ type: 'risk',
62
+ message: `High-risk commands found: ${items}. Review if these are still needed.`,
63
+ });
64
+ }
65
+ // 3. Find heredoc/one-time permissions
66
+ const heredocProjects = new Map();
67
+ for (const r of results) {
68
+ let count = 0;
69
+ for (const g of r.groups) {
70
+ if (g.category !== 'Bash')
71
+ continue;
72
+ for (const item of g.items) {
73
+ if (item.name.includes('__NEW_LINE_') || item.name.includes('<<') || item.name.includes('\\n'))
74
+ count++;
75
+ }
76
+ }
77
+ if (count > 0) {
78
+ const dir = (0, aggregator_js_1.projectDir)(r.display).replace(/.*\//, '');
79
+ heredocProjects.set(dir, (heredocProjects.get(dir) || 0) + count);
80
+ }
81
+ }
82
+ if (heredocProjects.size > 0) {
83
+ const total = [...heredocProjects.values()].reduce((a, b) => a + b, 0);
84
+ const top = [...heredocProjects.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
85
+ const topStr = top.map(([p, c]) => `${p} (${c})`).join(', ');
86
+ hints.push({
87
+ type: 'cleanup',
88
+ message: `${total} one-time/heredoc permissions found (${topStr}). These were likely auto-allowed for single tasks and are safe to remove.`,
89
+ });
90
+ }
91
+ // 4. Global permissions check
92
+ const globalEntries = entries.filter((e) => e.isGlobal);
93
+ const globalPerms = globalEntries.reduce((sum, e) => sum + e.totalCount, 0);
94
+ if (globalPerms === 0 && frequent.length > 0) {
95
+ hints.push({
96
+ type: 'info',
97
+ message: `~/.claude/settings.json has no permissions. Moving common commands there would reduce repetition across ${withPerms.length} projects.`,
98
+ });
99
+ }
100
+ // Build output
101
+ const lines = [];
102
+ const dirs = new Set(results.map((r) => (0, aggregator_js_1.projectDir)(r.display)));
103
+ const totalPerms = results.reduce((sum, r) => sum + r.totalCount, 0);
104
+ lines.push(`# ccperm: Permission Audit`);
105
+ lines.push(``);
106
+ lines.push(`Scanned ${results.length} settings files across ${dirs.size} projects. Found ${totalPerms} total permissions.`);
107
+ lines.push(``);
108
+ // Top projects
109
+ const sorted = [...withPerms].sort((a, b) => b.totalCount - a.totalCount).slice(0, 10);
110
+ lines.push(`## Top projects by permission count:`);
111
+ for (const e of sorted) {
112
+ lines.push(`- ${e.shortName} (${e.fileType}): ${e.totalCount} permissions`);
113
+ }
114
+ lines.push(``);
115
+ if (hints.length > 0) {
116
+ lines.push(`## Recommendations:`);
117
+ for (let i = 0; i < hints.length; i++) {
118
+ lines.push(`${i + 1}. ${hints[i].message}`);
119
+ }
120
+ lines.push(``);
121
+ }
122
+ lines.push(`## How to act:`);
123
+ lines.push(`- Global settings: ~/.claude/settings.json`);
124
+ lines.push(`- Project shared: <project>/.claude/settings.json (git tracked)`);
125
+ lines.push(`- Project local: <project>/.claude/settings.local.json (gitignored)`);
126
+ lines.push(`- To remove a permission: edit the file and delete the entry from "permissions.allow" array`);
127
+ return lines.join('\n');
128
+ }
package/dist/cli.js CHANGED
@@ -12,7 +12,8 @@ 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', '--update', '--fix', '--debug', '--help', '-h', '--version', '-v']);
15
+ const advisor_js_1 = require("./advisor.js");
16
+ const KNOWN_FLAGS = new Set(['--cwd', '--verbose', '--static', '--update', '--fix', '--agent', '--debug', '--help', '-h', '--version', '-v']);
16
17
  const HELP = `${colors_js_1.CYAN}ccperm${colors_js_1.NC} — Audit Claude Code permissions across projects
17
18
 
18
19
  ${colors_js_1.YELLOW}Usage:${colors_js_1.NC}
@@ -24,6 +25,7 @@ ${colors_js_1.YELLOW}Options:${colors_js_1.NC}
24
25
  --verbose Show all permissions per project (static)
25
26
  --static Force static output (default in non-TTY)
26
27
  --update Update ccperm to latest version
28
+ --agent Briefing for your AI overlord 🤖
27
29
  --help, -h Show this help
28
30
  --version, -v Show version`;
29
31
  async function main() {
@@ -90,6 +92,10 @@ async function main() {
90
92
  const results = files.map(scanner_js_1.scanFile).filter((r) => r !== null);
91
93
  const entries = (0, aggregator_js_1.toFileEntries)(results);
92
94
  const summary = (0, aggregator_js_1.summarize)(results);
95
+ if (args.includes('--agent')) {
96
+ console.log((0, advisor_js_1.analyze)(results));
97
+ return;
98
+ }
93
99
  if (!isStatic) {
94
100
  await (0, interactive_js_1.startInteractive)(entries, results);
95
101
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccperm",
3
- "version": "1.9.7",
3
+ "version": "1.10.0",
4
4
  "description": "Audit Claude Code permissions across all your projects",
5
5
  "bin": {
6
6
  "ccperm": "bin/ccperm.js"