ccperm 1.8.6 → 1.9.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/explain.js +112 -0
- package/dist/interactive.js +25 -9
- package/dist/renderer.js +4 -4
- package/package.json +1 -1
package/dist/explain.js
ADDED
|
@@ -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
|
+
}
|
package/dist/interactive.js
CHANGED
|
@@ -6,6 +6,7 @@ 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;
|
|
@@ -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
|
};
|
|
@@ -116,7 +120,7 @@ function renderList(state, withPerms, emptyCount) {
|
|
|
116
120
|
const cats = ['Bash', 'WebFetch', 'MCP', 'Tools'];
|
|
117
121
|
const catsPresent = cats.filter((c) => withPerms.some((r) => r.groups.has(c)));
|
|
118
122
|
const catColWidth = catsPresent.length * 7;
|
|
119
|
-
const maxName = Math.max(...withPerms.map((r) => r.shortName.length), 7);
|
|
123
|
+
const maxName = Math.max(...withPerms.map((r) => r.isGlobal ? r.shortName.length + 2 : r.shortName.length), 7);
|
|
120
124
|
const nameWidth = Math.min(maxName, inner - catColWidth - 16);
|
|
121
125
|
const hasGlobalSep = withPerms.some((r) => r.isGlobal) && withPerms.some((r) => !r.isGlobal);
|
|
122
126
|
// box takes: top(1) + header(2) + sep(1) + content + globalSep?(1) + emptyLine?(1) + bottom(1)
|
|
@@ -136,12 +140,12 @@ function renderList(state, withPerms, emptyCount) {
|
|
|
136
140
|
for (let i = state.scrollOffset; i < end; i++) {
|
|
137
141
|
const r = withPerms[i];
|
|
138
142
|
const isCursor = i === state.cursor;
|
|
139
|
-
const
|
|
143
|
+
const displayName = r.isGlobal ? `★ ${r.shortName}` : r.shortName;
|
|
144
|
+
const truncName = displayName.length > nameWidth ? displayName.slice(0, nameWidth - 1) + '…' : displayName;
|
|
140
145
|
const typeTag = r.isGlobal ? pad('', 7) : `${colors_js_1.DIM} ${pad(r.fileType, 6)}${colors_js_1.NC}`;
|
|
141
|
-
const prefix = r.isGlobal ? '★ ' : '';
|
|
142
146
|
const marker = isCursor ? `${colors_js_1.CYAN}▸ ` : ' ';
|
|
143
147
|
const nameStyle = isCursor ? `${colors_js_1.BOLD}` : r.isGlobal ? `${colors_js_1.YELLOW}` : '';
|
|
144
|
-
const nameCol = `${marker}${nameStyle}${
|
|
148
|
+
const nameCol = `${marker}${nameStyle}${pad(truncName, nameWidth)}${colors_js_1.NC}${typeTag}`;
|
|
145
149
|
const catCols = catsPresent.map((c) => {
|
|
146
150
|
const count = r.groups.get(c) || 0;
|
|
147
151
|
if (count > 0)
|
|
@@ -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
|
-
|
|
185
|
-
|
|
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
|
-
|
|
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/dist/renderer.js
CHANGED
|
@@ -19,7 +19,7 @@ function printCompact(entries, summary) {
|
|
|
19
19
|
const withPerms = [...globals, ...projects];
|
|
20
20
|
const emptyCount = entries.filter((r) => r.totalCount === 0 && !r.isGlobal).length;
|
|
21
21
|
// header
|
|
22
|
-
const maxName = Math.max(...withPerms.map((r) => r.shortName.length), 7);
|
|
22
|
+
const maxName = Math.max(...withPerms.map((r) => r.isGlobal ? r.shortName.length + 2 : r.shortName.length), 7);
|
|
23
23
|
const nameWidth = Math.min(maxName, 40);
|
|
24
24
|
const header = ` ${colors_js_1.DIM}${pad('PROJECT', nameWidth)} ${catsPresent.map((c) => rpad(c, 5)).join(' ')} TOTAL${colors_js_1.NC}`;
|
|
25
25
|
console.log(header);
|
|
@@ -27,11 +27,11 @@ function printCompact(entries, summary) {
|
|
|
27
27
|
// rows
|
|
28
28
|
for (let i = 0; i < withPerms.length; i++) {
|
|
29
29
|
const result = withPerms[i];
|
|
30
|
-
const
|
|
30
|
+
const displayName = result.isGlobal ? `★ ${result.shortName}` : result.shortName;
|
|
31
|
+
const truncName = displayName.length > nameWidth ? displayName.slice(0, nameWidth - 1) + '…' : displayName;
|
|
31
32
|
const typeTag = result.isGlobal ? pad('', 7) : `${colors_js_1.DIM} ${pad(result.fileType, 6)}${colors_js_1.NC}`;
|
|
32
|
-
const prefix = result.isGlobal ? '★ ' : '';
|
|
33
33
|
const nameStyle = result.isGlobal ? `${colors_js_1.YELLOW}` : '';
|
|
34
|
-
const nameCol = ` ${nameStyle}${
|
|
34
|
+
const nameCol = ` ${nameStyle}${pad(truncName, nameWidth)}${colors_js_1.NC}${typeTag}`;
|
|
35
35
|
const catCols = catsPresent.map((c) => {
|
|
36
36
|
const count = result.groups.get(c) || 0;
|
|
37
37
|
return count > 0 ? rpad(count, 5) : `${colors_js_1.DIM}${rpad('·', 5)}${colors_js_1.NC}`;
|