ccperm 1.12.0 → 1.12.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/advisor.js +15 -1
- package/dist/cli.js +13 -12
- package/dist/explain.js +4 -3
- package/dist/interactive.js +34 -10
- package/dist/renderer.js +1 -1
- package/dist/scanner.js +2 -6
- package/package.json +1 -1
package/dist/advisor.js
CHANGED
|
@@ -134,7 +134,21 @@ function analyze(results) {
|
|
|
134
134
|
const topStr = top.map(([p, c]) => `${p} (${c})`).join(', ');
|
|
135
135
|
hints.push(`${heredocTotal} one-time/heredoc permissions found (${topStr}). Safe to remove.`);
|
|
136
136
|
}
|
|
137
|
-
// 3.
|
|
137
|
+
// 3. WebFetch → suggest global wildcard
|
|
138
|
+
let webFetchTotal = 0;
|
|
139
|
+
let webFetchProjects = 0;
|
|
140
|
+
for (const r of results) {
|
|
141
|
+
const wf = r.groups.find((g) => g.category === 'WebFetch');
|
|
142
|
+
if (wf) {
|
|
143
|
+
webFetchTotal += wf.items.length;
|
|
144
|
+
webFetchProjects++;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const hasGlobalWebFetch = results.some((r) => r.isGlobal && r.groups.some((g) => g.category === 'WebFetch'));
|
|
148
|
+
if (webFetchProjects >= 3 && !hasGlobalWebFetch) {
|
|
149
|
+
hints.push(`WebFetch permissions found in ${webFetchProjects} projects (${webFetchTotal} domains total). WebFetch is read-only — add \`"WebFetch(*)"\` to ~/.claude/settings.json to skip per-domain approval globally.`);
|
|
150
|
+
}
|
|
151
|
+
// 4. Global check
|
|
138
152
|
const globalEntries = entries.filter((e) => e.isGlobal);
|
|
139
153
|
const globalPerms = globalEntries.reduce((sum, e) => sum + e.totalCount, 0);
|
|
140
154
|
if (globalPerms === 0 && frequent.length > 0) {
|
package/dist/cli.js
CHANGED
|
@@ -95,12 +95,23 @@ async function main() {
|
|
|
95
95
|
}
|
|
96
96
|
console.log(` ${colors_js_1.GREEN}✔${colors_js_1.NC} Found ${colors_js_1.CYAN}${files.length}${colors_js_1.NC} settings files\n`);
|
|
97
97
|
const results = files.map(scanner_js_1.scanFile).filter((r) => r !== null);
|
|
98
|
-
const entries = (0, aggregator_js_1.toFileEntries)(results);
|
|
99
|
-
const summary = (0, aggregator_js_1.summarize)(results);
|
|
100
98
|
if (args.includes('--hey-claude-witness-me')) {
|
|
101
99
|
console.log((0, advisor_js_1.analyze)(results));
|
|
102
100
|
return;
|
|
103
101
|
}
|
|
102
|
+
const entries = (0, aggregator_js_1.toFileEntries)(results);
|
|
103
|
+
const summary = (0, aggregator_js_1.summarize)(results);
|
|
104
|
+
if (args.includes('--fix')) {
|
|
105
|
+
const affected = (0, scanner_js_1.countDeprecated)(results);
|
|
106
|
+
if (affected.length === 0) {
|
|
107
|
+
console.log(` ${colors_js_1.GREEN}✔ Nothing to fix.${colors_js_1.NC}\n`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const { totalPatterns, fixedFiles } = (0, fixer_js_1.fixFiles)(affected);
|
|
111
|
+
console.log(` ${colors_js_1.GREEN}✔ Fixed ${totalPatterns} patterns in ${fixedFiles} files.${colors_js_1.NC}`);
|
|
112
|
+
console.log(` ${colors_js_1.DIM}Restart Claude Code for changes to take effect.${colors_js_1.NC}\n`);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
104
115
|
if (!isStatic) {
|
|
105
116
|
await (0, interactive_js_1.startInteractive)(entries, results);
|
|
106
117
|
return;
|
|
@@ -112,16 +123,6 @@ async function main() {
|
|
|
112
123
|
(0, renderer_js_1.printCompact)(entries, summary);
|
|
113
124
|
}
|
|
114
125
|
(0, renderer_js_1.printFooter)(summary);
|
|
115
|
-
if (args.includes('--fix')) {
|
|
116
|
-
const affected = (0, scanner_js_1.countDeprecated)(results);
|
|
117
|
-
if (affected.length === 0) {
|
|
118
|
-
console.log(`\n ${colors_js_1.GREEN}✔ Nothing to fix.${colors_js_1.NC}\n`);
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
const { totalPatterns, fixedFiles } = (0, fixer_js_1.fixFiles)(affected);
|
|
122
|
-
console.log(`\n ${colors_js_1.GREEN}✔ Fixed ${totalPatterns} patterns in ${fixedFiles} files.${colors_js_1.NC}`);
|
|
123
|
-
console.log(` ${colors_js_1.DIM}Restart Claude Code for changes to take effect.${colors_js_1.NC}\n`);
|
|
124
|
-
}
|
|
125
126
|
}
|
|
126
127
|
main();
|
|
127
128
|
(0, updater_js_1.notifyUpdate)();
|
package/dist/explain.js
CHANGED
|
@@ -266,11 +266,12 @@ function explainTool(label) {
|
|
|
266
266
|
function explain(category, label) {
|
|
267
267
|
if (category === 'Bash')
|
|
268
268
|
return explainBash(label);
|
|
269
|
-
if (category === 'WebFetch')
|
|
270
|
-
return explainWebFetch(label);
|
|
271
269
|
if (category === 'MCP')
|
|
272
270
|
return explainMcp(label);
|
|
273
|
-
if (category === 'Tools')
|
|
271
|
+
if (category === 'Tools') {
|
|
272
|
+
if (label.startsWith('WebFetch'))
|
|
273
|
+
return explainWebFetch(label);
|
|
274
274
|
return explainTool(label);
|
|
275
|
+
}
|
|
275
276
|
return { description: '', risk: 'medium' };
|
|
276
277
|
}
|
package/dist/interactive.js
CHANGED
|
@@ -27,8 +27,7 @@ function cleanLabel(label) {
|
|
|
27
27
|
// Truncate inline scripts with \n
|
|
28
28
|
if (s.includes('\\n'))
|
|
29
29
|
s = s.replace(/\\n.*$/, '…');
|
|
30
|
-
//
|
|
31
|
-
s = s.replace(/:\*$/, ' *');
|
|
30
|
+
// Show deprecated :* as-is (don't normalize to space)
|
|
32
31
|
return s;
|
|
33
32
|
}
|
|
34
33
|
// strip ANSI escape codes for visible length
|
|
@@ -63,6 +62,7 @@ function startInteractive(merged, results) {
|
|
|
63
62
|
const withPerms = [...globals, ...projects];
|
|
64
63
|
const emptyCount = merged.filter((r) => r.totalCount === 0 && !r.isGlobal).length;
|
|
65
64
|
const riskMap = buildRiskMap(results);
|
|
65
|
+
const depMap = buildDeprecatedMap(results);
|
|
66
66
|
if (withPerms.length === 0) {
|
|
67
67
|
console.log(`\n ${colors_js_1.GREEN}No projects with permissions found.${colors_js_1.NC}\n`);
|
|
68
68
|
resolve();
|
|
@@ -86,7 +86,7 @@ function startInteractive(merged, results) {
|
|
|
86
86
|
const render = () => {
|
|
87
87
|
process.stdout.write('\x1b[2J\x1b[H\x1b[?25l');
|
|
88
88
|
if (state.view === 'list')
|
|
89
|
-
renderList(state, withPerms, emptyCount, riskMap);
|
|
89
|
+
renderList(state, withPerms, emptyCount, riskMap, depMap);
|
|
90
90
|
else
|
|
91
91
|
renderDetail(state, withPerms, results);
|
|
92
92
|
};
|
|
@@ -137,6 +137,19 @@ function startInteractive(merged, results) {
|
|
|
137
137
|
render();
|
|
138
138
|
});
|
|
139
139
|
}
|
|
140
|
+
function buildDeprecatedMap(results) {
|
|
141
|
+
const map = new Map();
|
|
142
|
+
for (const r of results) {
|
|
143
|
+
let count = 0;
|
|
144
|
+
for (const p of r.permissions) {
|
|
145
|
+
if (p.includes(':*'))
|
|
146
|
+
count++;
|
|
147
|
+
}
|
|
148
|
+
if (count > 0)
|
|
149
|
+
map.set(r.display, count);
|
|
150
|
+
}
|
|
151
|
+
return map;
|
|
152
|
+
}
|
|
140
153
|
function buildRiskMap(results) {
|
|
141
154
|
const map = new Map();
|
|
142
155
|
for (const r of results) {
|
|
@@ -154,20 +167,22 @@ function buildRiskMap(results) {
|
|
|
154
167
|
}
|
|
155
168
|
return map;
|
|
156
169
|
}
|
|
157
|
-
function renderList(state, withPerms, emptyCount, riskMap) {
|
|
170
|
+
function renderList(state, withPerms, emptyCount, riskMap, depMap) {
|
|
158
171
|
const rows = process.stdout.rows || 24;
|
|
159
172
|
const cols = process.stdout.columns || 80;
|
|
160
|
-
const cats = ['Bash', '
|
|
173
|
+
const cats = ['Bash', 'MCP', 'Tools'];
|
|
161
174
|
const catsPresent = cats.filter((c) => withPerms.some((r) => r.groups.has(c)));
|
|
162
175
|
const hasRisk = [...riskMap.values()].some((v) => v.critical > 0 || v.high > 0);
|
|
176
|
+
const hasDep = depMap.size > 0;
|
|
163
177
|
const riskColWidth = hasRisk ? 3 : 0;
|
|
178
|
+
const depColWidth = hasDep ? 3 : 0;
|
|
164
179
|
const catColWidth = catsPresent.length * 7;
|
|
165
180
|
const typeColWidth = 7;
|
|
166
181
|
const maxName = Math.max(...withPerms.map((r) => r.shortName.length), 7);
|
|
167
182
|
const nameColWidth = Math.min(maxName + typeColWidth, 35);
|
|
168
183
|
const nameWidth = nameColWidth - typeColWidth;
|
|
169
|
-
// content: marker(2) + nameCol + gap(2) + catCols + gap(2) + total(5) +
|
|
170
|
-
const contentWidth = 2 + nameColWidth + 2 + catColWidth + 2 + 5 + (hasRisk ?
|
|
184
|
+
// content: marker(2) + nameCol + gap(2) + catCols + gap(2) + total(5) + riskCol(3) + depCol(3)
|
|
185
|
+
const contentWidth = 2 + nameColWidth + 2 + catColWidth + 2 + 5 + (hasRisk ? riskColWidth : 0) + (hasDep ? depColWidth : 0);
|
|
171
186
|
const w = Math.min(cols, contentWidth + 4);
|
|
172
187
|
const inner = w - 4;
|
|
173
188
|
const hasGlobalSep = withPerms.some((r) => r.isGlobal) && withPerms.some((r) => !r.isGlobal);
|
|
@@ -181,8 +196,9 @@ function renderList(state, withPerms, emptyCount, riskMap) {
|
|
|
181
196
|
const scrollInfo = withPerms.length > visibleRows ? `${state.cursor + 1}/${withPerms.length}` : '';
|
|
182
197
|
const lines = [];
|
|
183
198
|
lines.push(boxTop('ccperm', scrollInfo, w));
|
|
184
|
-
const riskHeader = hasRisk ? ` ${
|
|
185
|
-
|
|
199
|
+
const riskHeader = hasRisk ? ` ${rpad('!', 2)}` : '';
|
|
200
|
+
const depHeader = hasDep ? ` ${rpad('†', 2)}` : '';
|
|
201
|
+
lines.push(boxLine(`${colors_js_1.DIM} ${pad('PROJECT', nameColWidth)} ${catsPresent.map((c) => rpad(c, 5)).join(' ')} ${rpad('TOTAL', 5)}${riskHeader}${depHeader}${colors_js_1.NC}`, w));
|
|
186
202
|
lines.push(boxSep(w));
|
|
187
203
|
const globalCount = withPerms.filter((r) => r.isGlobal).length;
|
|
188
204
|
const end = Math.min(state.scrollOffset + visibleRows, withPerms.length);
|
|
@@ -211,7 +227,15 @@ function renderList(state, withPerms, emptyCount, riskMap) {
|
|
|
211
227
|
else
|
|
212
228
|
riskCol = ` ${colors_js_1.DIM}${rpad('·', 2)}${colors_js_1.NC}`;
|
|
213
229
|
}
|
|
214
|
-
|
|
230
|
+
let depCol = '';
|
|
231
|
+
if (hasDep) {
|
|
232
|
+
const dep = depMap.get(r.display) || 0;
|
|
233
|
+
if (dep > 0)
|
|
234
|
+
depCol = ` ${colors_js_1.DIM}${rpad(dep, 2)}${colors_js_1.NC}`;
|
|
235
|
+
else
|
|
236
|
+
depCol = ` ${colors_js_1.DIM}${rpad('·', 2)}${colors_js_1.NC}`;
|
|
237
|
+
}
|
|
238
|
+
lines.push(boxLine(`${nameCol} ${catCols} ${totalCol}${riskCol}${depCol}`, w));
|
|
215
239
|
// separator after global section
|
|
216
240
|
if (r.isGlobal && i + 1 < withPerms.length && !withPerms[i + 1].isGlobal) {
|
|
217
241
|
lines.push(boxSep(w));
|
package/dist/renderer.js
CHANGED
|
@@ -12,7 +12,7 @@ function rpad(s, n) {
|
|
|
12
12
|
return str.length >= n ? str : ' '.repeat(n - str.length) + str;
|
|
13
13
|
}
|
|
14
14
|
function printCompact(entries, summary) {
|
|
15
|
-
const cats = ['Bash', '
|
|
15
|
+
const cats = ['Bash', 'MCP', 'Tools'];
|
|
16
16
|
const catsPresent = cats.filter((c) => entries.some((r) => r.groups.has(c)));
|
|
17
17
|
const globals = entries.filter((r) => r.isGlobal);
|
|
18
18
|
const projects = entries.filter((r) => r.totalCount > 0 && !r.isGlobal).sort((a, b) => b.totalCount - a.totalCount);
|
package/dist/scanner.js
CHANGED
|
@@ -111,14 +111,10 @@ function categorize(perm) {
|
|
|
111
111
|
const m = perm.match(/^Bash\((.+)\)$/) || perm.match(/^Bash\((.+)/);
|
|
112
112
|
return { category: 'Bash', label: m ? m[1] : perm };
|
|
113
113
|
}
|
|
114
|
-
if (perm.startsWith('WebFetch')) {
|
|
115
|
-
const m = perm.match(/^WebFetch\(domain:(.+)\)$/);
|
|
116
|
-
return { category: 'WebFetch', label: m ? m[1] : perm };
|
|
117
|
-
}
|
|
118
114
|
if (perm.startsWith('mcp_') || perm.startsWith('mcp__')) {
|
|
119
115
|
return { category: 'MCP', label: perm };
|
|
120
116
|
}
|
|
121
|
-
if (/^(Read|Write|Edit|Glob|Grep|WebSearch)/.test(perm)) {
|
|
117
|
+
if (/^(Read|Write|Edit|Glob|Grep|WebSearch|WebFetch)/.test(perm)) {
|
|
122
118
|
return { category: 'Tools', label: perm };
|
|
123
119
|
}
|
|
124
120
|
return { category: 'Other', label: perm };
|
|
@@ -131,6 +127,6 @@ function groupPermissions(perms) {
|
|
|
131
127
|
map.set(category, []);
|
|
132
128
|
map.get(category).push({ name: label });
|
|
133
129
|
}
|
|
134
|
-
const order = ['Bash', '
|
|
130
|
+
const order = ['Bash', 'MCP', 'Tools', 'Other'];
|
|
135
131
|
return order.filter((c) => map.has(c)).map((c) => ({ category: c, items: map.get(c) }));
|
|
136
132
|
}
|