ccperm 1.8.5 → 1.9.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,112 @@
1
+ "use strict";
2
+ // Pattern-based permission explainer
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.explainPermission = explainPermission;
5
+ const BASH_COMMANDS = {
6
+ // [description, risk: green/yellow/red]
7
+ 'git': ['Git version control commands', 'green'],
8
+ 'npm': ['Node.js package manager — can run scripts', 'yellow'],
9
+ 'npx': ['Run npm packages — can execute arbitrary code', 'yellow'],
10
+ 'node': ['Run Node.js scripts', 'yellow'],
11
+ 'bun': ['Bun runtime — run scripts, install packages', 'yellow'],
12
+ 'deno': ['Deno runtime — run scripts', 'yellow'],
13
+ 'python': ['Run Python scripts', 'yellow'],
14
+ 'python3': ['Run Python scripts', 'yellow'],
15
+ 'pip': ['Python package manager — can run setup scripts', 'yellow'],
16
+ 'pip3': ['Python package manager — can run setup scripts', 'yellow'],
17
+ 'docker': ['Docker container management', 'yellow'],
18
+ 'docker-compose': ['Docker Compose multi-container management', 'yellow'],
19
+ 'curl': ['HTTP requests from command line', 'yellow'],
20
+ 'wget': ['Download files from the web', 'yellow'],
21
+ 'ssh': ['Remote shell access', 'red'],
22
+ 'scp': ['Remote file copy over SSH', 'red'],
23
+ 'rsync': ['File synchronization (local or remote)', 'yellow'],
24
+ 'rm': ['Delete files and directories', 'red'],
25
+ 'chmod': ['Change file permissions', 'yellow'],
26
+ 'chown': ['Change file ownership', 'red'],
27
+ 'kill': ['Terminate processes', 'yellow'],
28
+ 'sudo': ['Run commands as superuser', 'red'],
29
+ 'apt': ['System package manager (Debian/Ubuntu)', 'red'],
30
+ 'apt-get': ['System package manager (Debian/Ubuntu)', 'red'],
31
+ 'brew': ['Homebrew package manager (macOS)', 'yellow'],
32
+ 'make': ['Build automation — runs Makefile targets', 'yellow'],
33
+ 'cargo': ['Rust package manager and build tool', 'yellow'],
34
+ 'go': ['Go build and package tool', 'yellow'],
35
+ 'mvn': ['Maven Java build tool', 'yellow'],
36
+ 'gradle': ['Gradle build tool', 'yellow'],
37
+ 'yarn': ['Yarn package manager — can run scripts', 'yellow'],
38
+ 'pnpm': ['pnpm package manager — can run scripts', 'yellow'],
39
+ 'tsc': ['TypeScript compiler', 'green'],
40
+ 'eslint': ['JavaScript/TypeScript linter', 'green'],
41
+ 'prettier': ['Code formatter', 'green'],
42
+ 'jest': ['JavaScript test runner', 'green'],
43
+ 'vitest': ['Vite-based test runner', 'green'],
44
+ 'cat': ['Read file contents', 'green'],
45
+ 'ls': ['List directory contents', 'green'],
46
+ 'find': ['Search for files', 'green'],
47
+ 'grep': ['Search text patterns in files', 'green'],
48
+ 'sed': ['Stream editor — modify file contents', 'yellow'],
49
+ 'awk': ['Text processing language', 'green'],
50
+ 'wc': ['Count lines/words/bytes', 'green'],
51
+ 'head': ['Show first lines of file', 'green'],
52
+ 'tail': ['Show last lines of file', 'green'],
53
+ 'mkdir': ['Create directories', 'green'],
54
+ 'cp': ['Copy files', 'green'],
55
+ 'mv': ['Move/rename files', 'yellow'],
56
+ 'echo': ['Print text', 'green'],
57
+ 'env': ['Show/set environment variables', 'green'],
58
+ 'which': ['Locate a command', 'green'],
59
+ 'gh': ['GitHub CLI — repos, PRs, issues', 'yellow'],
60
+ 'heroku': ['Heroku platform CLI', 'yellow'],
61
+ 'vercel': ['Vercel deployment CLI', 'yellow'],
62
+ 'aws': ['AWS CLI — cloud infrastructure', 'red'],
63
+ 'gcloud': ['Google Cloud CLI', 'red'],
64
+ 'az': ['Azure CLI', 'red'],
65
+ 'kubectl': ['Kubernetes cluster management', 'red'],
66
+ 'terraform': ['Infrastructure as Code', 'red'],
67
+ };
68
+ const TOOL_DESCRIPTIONS = {
69
+ 'Read': 'Read file contents from disk',
70
+ 'Write': 'Create or overwrite files',
71
+ 'Edit': 'Modify existing files (partial edits)',
72
+ 'Glob': 'Search for files by name pattern',
73
+ 'Grep': 'Search file contents with regex',
74
+ 'WebSearch': 'Search the web via search engine',
75
+ };
76
+ function explainPermission(perm) {
77
+ // Bash permissions
78
+ const bashMatch = perm.match(/^Bash\((.+?)[\s)]/);
79
+ if (bashMatch || perm === 'Bash') {
80
+ const cmd = bashMatch ? bashMatch[1] : '';
81
+ const entry = BASH_COMMANDS[cmd];
82
+ if (entry) {
83
+ return { description: entry[0], risk: entry[1], detail: `Command: ${cmd}` };
84
+ }
85
+ if (cmd) {
86
+ return { description: `Run "${cmd}" command`, risk: 'yellow', detail: `Command: ${cmd}` };
87
+ }
88
+ return { description: 'Run shell commands', risk: 'red' };
89
+ }
90
+ // WebFetch
91
+ const fetchMatch = perm.match(/^WebFetch\(domain:(.+)\)$/);
92
+ if (fetchMatch) {
93
+ const domain = fetchMatch[1];
94
+ return { description: `HTTP requests to ${domain}`, risk: 'yellow', detail: `Domain: ${domain}` };
95
+ }
96
+ if (perm.startsWith('WebFetch')) {
97
+ return { description: 'HTTP requests to external URLs', risk: 'yellow' };
98
+ }
99
+ // MCP tools
100
+ if (perm.startsWith('mcp__') || perm.startsWith('mcp_')) {
101
+ const parts = perm.split('__');
102
+ const server = parts[1] || 'unknown';
103
+ const tool = parts.slice(2).join('__') || 'unknown';
104
+ return { description: `MCP: ${server} → ${tool}`, risk: 'yellow', detail: `Server: ${server}, Tool: ${tool}` };
105
+ }
106
+ // Standard tools
107
+ const toolName = perm.match(/^(Read|Write|Edit|Glob|Grep|WebSearch)/)?.[1];
108
+ if (toolName && TOOL_DESCRIPTIONS[toolName]) {
109
+ return { description: TOOL_DESCRIPTIONS[toolName], risk: 'green' };
110
+ }
111
+ return { description: perm, risk: 'yellow' };
112
+ }
@@ -6,13 +6,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.startInteractive = startInteractive;
7
7
  const node_readline_1 = __importDefault(require("node:readline"));
8
8
  const colors_js_1 = require("./colors.js");
9
+ const explain_js_1 = require("./explain.js");
9
10
  // strip ANSI escape codes for visible length
10
11
  function visLen(s) {
11
12
  return s.replace(/\x1b\[[0-9;]*m/g, '').length;
12
13
  }
13
14
  function boxLine(text, width) {
14
15
  const vis = visLen(text);
15
- const padRight = Math.max(0, width - vis - 1);
16
+ const padRight = Math.max(0, width - vis - 3);
16
17
  return `${colors_js_1.DIM}│${colors_js_1.NC} ${text}${' '.repeat(padRight)}${colors_js_1.DIM}│${colors_js_1.NC}`;
17
18
  }
18
19
  function boxTop(title, info, width) {
@@ -42,7 +43,7 @@ function startInteractive(merged, results) {
42
43
  resolve();
43
44
  return;
44
45
  }
45
- const state = { view: 'list', cursor: 0, scrollOffset: 0, selectedProject: 0, detailCursor: 0, detailScroll: 0, expanded: new Set() };
46
+ const state = { view: 'list', cursor: 0, scrollOffset: 0, selectedProject: 0, detailCursor: 0, detailScroll: 0, expanded: new Set(), showInfo: false };
46
47
  node_readline_1.default.emitKeypressEvents(process.stdin);
47
48
  if (process.stdin.isTTY)
48
49
  process.stdin.setRawMode(true);
@@ -101,6 +102,9 @@ function startInteractive(merged, results) {
101
102
  else if (key.name === 'return') {
102
103
  state._toggle = true;
103
104
  }
105
+ else if (key.name === 'i') {
106
+ state.showInfo = !state.showInfo;
107
+ }
104
108
  }
105
109
  render();
106
110
  };
@@ -179,10 +183,21 @@ function renderDetail(state, withPerms, results) {
179
183
  const arrow = isOpen ? '▾' : '▸';
180
184
  navRows.push({ text: `${colors_js_1.YELLOW}${arrow} ${group.category}${colors_js_1.NC} ${colors_js_1.DIM}(${group.items.length})${colors_js_1.NC}`, key });
181
185
  if (isOpen) {
182
- const maxLen = w - 12;
183
186
  for (const item of group.items) {
184
- const name = item.name.length > maxLen ? item.name.slice(0, maxLen - 1) + '…' : item.name;
185
- navRows.push({ text: ` ${colors_js_1.DIM}${name}${colors_js_1.NC}` });
187
+ if (state.showInfo) {
188
+ const info = (0, explain_js_1.explainPermission)(item.name);
189
+ const riskColor = info.risk === 'red' ? colors_js_1.RED : info.risk === 'yellow' ? colors_js_1.YELLOW : colors_js_1.GREEN;
190
+ const dot = `${riskColor}●${colors_js_1.NC}`;
191
+ const maxLen = w - 16;
192
+ const name = item.name.length > maxLen ? item.name.slice(0, maxLen - 1) + '…' : item.name;
193
+ navRows.push({ text: ` ${dot} ${colors_js_1.DIM}${name}${colors_js_1.NC}`, perm: item.name });
194
+ navRows.push({ text: ` ${colors_js_1.DIM}${info.description}${colors_js_1.NC}` });
195
+ }
196
+ else {
197
+ const maxLen = w - 12;
198
+ const name = item.name.length > maxLen ? item.name.slice(0, maxLen - 1) + '…' : item.name;
199
+ navRows.push({ text: ` ${colors_js_1.DIM}${name}${colors_js_1.NC}`, perm: item.name });
200
+ }
186
201
  }
187
202
  }
188
203
  }
@@ -219,7 +234,8 @@ function renderDetail(state, withPerms, results) {
219
234
  const prefix = isCursor ? `${colors_js_1.CYAN}▸ ` : ' ';
220
235
  lines.push(boxLine(`${prefix}${row.text}`, w));
221
236
  }
222
- lines.push(boxBottom('[↑↓] navigate [Enter] expand [Esc] back [q] quit', w));
237
+ const infoHint = state.showInfo ? '[i] hide info' : '[i] info';
238
+ lines.push(boxBottom(`[↑↓] navigate [Enter] expand ${infoHint} [Esc] back [q] quit`, w));
223
239
  process.stdout.write(lines.join('\n') + '\n');
224
240
  }
225
241
  function pad(s, n) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccperm",
3
- "version": "1.8.5",
3
+ "version": "1.9.0",
4
4
  "description": "Audit Claude Code permissions across all your projects",
5
5
  "bin": {
6
6
  "ccperm": "bin/ccperm.js"