ccperm 1.11.3 → 1.12.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.
- package/dist/interactive.js +40 -9
- package/package.json +1 -1
package/dist/interactive.js
CHANGED
|
@@ -62,6 +62,7 @@ function startInteractive(merged, results) {
|
|
|
62
62
|
const projects = merged.filter((r) => r.totalCount > 0 && !r.isGlobal).sort((a, b) => b.totalCount - a.totalCount);
|
|
63
63
|
const withPerms = [...globals, ...projects];
|
|
64
64
|
const emptyCount = merged.filter((r) => r.totalCount === 0 && !r.isGlobal).length;
|
|
65
|
+
const riskMap = buildRiskMap(results);
|
|
65
66
|
if (withPerms.length === 0) {
|
|
66
67
|
console.log(`\n ${colors_js_1.GREEN}No projects with permissions found.${colors_js_1.NC}\n`);
|
|
67
68
|
resolve();
|
|
@@ -85,7 +86,7 @@ function startInteractive(merged, results) {
|
|
|
85
86
|
const render = () => {
|
|
86
87
|
process.stdout.write('\x1b[2J\x1b[H\x1b[?25l');
|
|
87
88
|
if (state.view === 'list')
|
|
88
|
-
renderList(state, withPerms, emptyCount);
|
|
89
|
+
renderList(state, withPerms, emptyCount, riskMap);
|
|
89
90
|
else
|
|
90
91
|
renderDetail(state, withPerms, results);
|
|
91
92
|
};
|
|
@@ -136,19 +137,38 @@ function startInteractive(merged, results) {
|
|
|
136
137
|
render();
|
|
137
138
|
});
|
|
138
139
|
}
|
|
139
|
-
function
|
|
140
|
+
function buildRiskMap(results) {
|
|
141
|
+
const map = new Map();
|
|
142
|
+
for (const r of results) {
|
|
143
|
+
let crit = 0, hi = 0;
|
|
144
|
+
for (const g of r.groups) {
|
|
145
|
+
for (const item of g.items) {
|
|
146
|
+
const info = (0, explain_js_1.explain)(g.category, item.name);
|
|
147
|
+
if (info.risk === 'critical')
|
|
148
|
+
crit++;
|
|
149
|
+
else if (info.risk === 'high')
|
|
150
|
+
hi++;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
map.set(r.display, { critical: crit, high: hi });
|
|
154
|
+
}
|
|
155
|
+
return map;
|
|
156
|
+
}
|
|
157
|
+
function renderList(state, withPerms, emptyCount, riskMap) {
|
|
140
158
|
const rows = process.stdout.rows || 24;
|
|
141
159
|
const cols = process.stdout.columns || 80;
|
|
142
160
|
const cats = ['Bash', 'WebFetch', 'MCP', 'Tools'];
|
|
143
161
|
const catsPresent = cats.filter((c) => withPerms.some((r) => r.groups.has(c)));
|
|
162
|
+
const hasRisk = [...riskMap.values()].some((v) => v.critical > 0 || v.high > 0);
|
|
163
|
+
const riskColWidth = hasRisk ? 3 : 0;
|
|
144
164
|
const catColWidth = catsPresent.length * 7;
|
|
145
|
-
const typeColWidth = 7;
|
|
165
|
+
const typeColWidth = 7;
|
|
146
166
|
const maxName = Math.max(...withPerms.map((r) => r.shortName.length), 7);
|
|
147
|
-
const nameColWidth = Math.min(maxName + typeColWidth, 35);
|
|
167
|
+
const nameColWidth = Math.min(maxName + typeColWidth, 35);
|
|
148
168
|
const nameWidth = nameColWidth - typeColWidth;
|
|
149
|
-
// content: marker(2) + nameCol
|
|
150
|
-
const contentWidth = 2 + nameColWidth + 2 + catColWidth + 2 + 5;
|
|
151
|
-
const w = Math.min(cols, contentWidth + 4);
|
|
169
|
+
// content: marker(2) + nameCol + gap(2) + catCols + gap(2) + total(5) + gap(1) + riskCol
|
|
170
|
+
const contentWidth = 2 + nameColWidth + 2 + catColWidth + 2 + 5 + (hasRisk ? 1 + riskColWidth : 0);
|
|
171
|
+
const w = Math.min(cols, contentWidth + 4);
|
|
152
172
|
const inner = w - 4;
|
|
153
173
|
const hasGlobalSep = withPerms.some((r) => r.isGlobal) && withPerms.some((r) => !r.isGlobal);
|
|
154
174
|
// box takes: top(1) + header(2) + sep(1) + content + globalSep?(1) + emptyLine?(1) + bottom(1)
|
|
@@ -161,7 +181,8 @@ function renderList(state, withPerms, emptyCount) {
|
|
|
161
181
|
const scrollInfo = withPerms.length > visibleRows ? `${state.cursor + 1}/${withPerms.length}` : '';
|
|
162
182
|
const lines = [];
|
|
163
183
|
lines.push(boxTop('ccperm', scrollInfo, w));
|
|
164
|
-
|
|
184
|
+
const riskHeader = hasRisk ? ` ${colors_js_1.DIM}⚠${colors_js_1.NC}` : '';
|
|
185
|
+
lines.push(boxLine(`${colors_js_1.DIM}${pad('PROJECT', nameColWidth)} ${catsPresent.map((c) => rpad(c, 5)).join(' ')} TOTAL${colors_js_1.NC}${riskHeader}`, w));
|
|
165
186
|
lines.push(boxSep(w));
|
|
166
187
|
const globalCount = withPerms.filter((r) => r.isGlobal).length;
|
|
167
188
|
const end = Math.min(state.scrollOffset + visibleRows, withPerms.length);
|
|
@@ -180,7 +201,17 @@ function renderList(state, withPerms, emptyCount) {
|
|
|
180
201
|
return `${colors_js_1.DIM}${rpad('·', 5)}${colors_js_1.NC}`;
|
|
181
202
|
}).join(' ');
|
|
182
203
|
const totalCol = isCursor ? `${colors_js_1.BOLD}${rpad(r.totalCount, 5)}${colors_js_1.NC}` : rpad(r.totalCount, 5);
|
|
183
|
-
|
|
204
|
+
let riskCol = '';
|
|
205
|
+
if (hasRisk) {
|
|
206
|
+
const risk = riskMap.get(r.display) || { critical: 0, high: 0 };
|
|
207
|
+
if (risk.critical > 0)
|
|
208
|
+
riskCol = ` ${colors_js_1.RED}${rpad(risk.critical, 2)}${colors_js_1.NC}`;
|
|
209
|
+
else if (risk.high > 0)
|
|
210
|
+
riskCol = ` ${colors_js_1.YELLOW}${rpad(risk.high, 2)}${colors_js_1.NC}`;
|
|
211
|
+
else
|
|
212
|
+
riskCol = ` ${colors_js_1.DIM}${rpad('·', 2)}${colors_js_1.NC}`;
|
|
213
|
+
}
|
|
214
|
+
lines.push(boxLine(`${nameCol} ${catCols} ${totalCol}${riskCol}`, w));
|
|
184
215
|
// separator after global section
|
|
185
216
|
if (r.isGlobal && i + 1 < withPerms.length && !withPerms[i + 1].isGlobal) {
|
|
186
217
|
lines.push(boxSep(w));
|