ccperm 1.7.0 → 1.8.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.
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.shortPath = shortPath;
4
4
  exports.projectDir = projectDir;
5
- exports.mergeByProject = mergeByProject;
5
+ exports.toFileEntries = toFileEntries;
6
6
  exports.summarize = summarize;
7
7
  function shortPath(display) {
8
8
  const m = display.match(/\/([^/]+)\/\.claude\//);
@@ -12,25 +12,19 @@ function projectDir(display) {
12
12
  const idx = display.indexOf('/.claude/');
13
13
  return idx >= 0 ? display.slice(0, idx) : display;
14
14
  }
15
- function mergeByProject(results) {
16
- const map = new Map();
17
- for (const r of results) {
18
- const dir = projectDir(r.display);
19
- let merged = map.get(dir);
20
- if (!merged) {
21
- const name = r.isGlobal ? 'GLOBAL' : shortPath(r.display);
22
- merged = { display: r.display, shortName: name, totalCount: 0, groups: new Map(), isGlobal: r.isGlobal };
23
- map.set(dir, merged);
24
- }
25
- merged.totalCount += r.totalCount;
15
+ function toFileEntries(results) {
16
+ return results.map((r) => {
17
+ const groups = new Map();
26
18
  for (const g of r.groups) {
27
- merged.groups.set(g.category, (merged.groups.get(g.category) || 0) + g.items.length);
19
+ groups.set(g.category, g.items.length);
28
20
  }
29
- }
30
- return [...map.values()];
21
+ const fileType = r.isGlobal ? 'global' : r.display.includes('settings.local.json') ? 'local' : 'shared';
22
+ const name = r.isGlobal ? 'GLOBAL' : shortPath(r.display);
23
+ return { display: r.display, shortName: name, totalCount: r.totalCount, groups, isGlobal: r.isGlobal, fileType };
24
+ });
31
25
  }
32
26
  function summarize(results) {
33
- const merged = mergeByProject(results);
27
+ const dirs = new Set(results.map((r) => projectDir(r.display)));
34
28
  const categoryTotals = new Map();
35
29
  for (const r of results) {
36
30
  for (const group of r.groups) {
@@ -41,7 +35,7 @@ function summarize(results) {
41
35
  const projectsEmpty = results.filter((r) => r.totalCount === 0).length;
42
36
  const totalPerms = [...categoryTotals.values()].reduce((a, b) => a + b, 0);
43
37
  return {
44
- totalProjects: merged.length,
38
+ totalProjects: dirs.size,
45
39
  projectsWithPerms,
46
40
  projectsEmpty,
47
41
  totalPerms,
package/dist/cli.js CHANGED
@@ -88,17 +88,17 @@ async function main() {
88
88
  }
89
89
  console.log(` ${colors_js_1.GREEN}✔${colors_js_1.NC} Found ${colors_js_1.CYAN}${files.length}${colors_js_1.NC} settings files\n`);
90
90
  const results = files.map(scanner_js_1.scanFile).filter((r) => r !== null);
91
- const merged = (0, aggregator_js_1.mergeByProject)(results);
91
+ const entries = (0, aggregator_js_1.toFileEntries)(results);
92
92
  const summary = (0, aggregator_js_1.summarize)(results);
93
93
  if (!isStatic) {
94
- await (0, interactive_js_1.startInteractive)(merged, results);
94
+ await (0, interactive_js_1.startInteractive)(entries, results);
95
95
  return;
96
96
  }
97
97
  if (isVerbose) {
98
98
  (0, renderer_js_1.printVerbose)(results, summary);
99
99
  }
100
100
  else {
101
- (0, renderer_js_1.printCompact)(merged, summary);
101
+ (0, renderer_js_1.printCompact)(entries, summary);
102
102
  }
103
103
  (0, renderer_js_1.printFooter)(summary);
104
104
  if (args.includes('--fix')) {
@@ -116,7 +116,7 @@ function renderList(state, withPerms, emptyCount) {
116
116
  const cats = ['Bash', 'WebFetch', 'MCP', 'Tools'];
117
117
  const catsPresent = cats.filter((c) => withPerms.some((r) => r.groups.has(c)));
118
118
  const catColWidth = catsPresent.length * 7;
119
- const nameWidths = withPerms.map((r) => r.isGlobal ? r.shortName.length + 2 : r.shortName.length);
119
+ const nameWidths = withPerms.map((r) => r.shortName.length + 2);
120
120
  const nameWidth = Math.min(Math.max(...nameWidths, 7), inner - catColWidth - 8);
121
121
  const hasGlobalSep = withPerms.some((r) => r.isGlobal) && withPerms.some((r) => !r.isGlobal);
122
122
  // box takes: top(1) + header(2) + sep(1) + content + globalSep?(1) + emptyLine?(1) + bottom(1)
@@ -136,7 +136,8 @@ function renderList(state, withPerms, emptyCount) {
136
136
  for (let i = state.scrollOffset; i < end; i++) {
137
137
  const r = withPerms[i];
138
138
  const isCursor = i === state.cursor;
139
- const displayName = r.isGlobal ? `★ ${r.shortName}` : r.shortName;
139
+ const typeLabel = r.isGlobal ? '' : r.fileType === 'local' ? ' ˡ' : ' ˢ';
140
+ const displayName = r.isGlobal ? `★ ${r.shortName}` : `${r.shortName}${typeLabel}`;
140
141
  const truncName = displayName.length > nameWidth ? displayName.slice(0, nameWidth - 1) + '…' : displayName;
141
142
  const marker = isCursor ? `${colors_js_1.CYAN}▸ ` : ' ';
142
143
  const nameStyle = isCursor ? `${colors_js_1.BOLD}` : r.isGlobal ? `${colors_js_1.YELLOW}` : `${colors_js_1.DIM}`;
@@ -167,31 +168,21 @@ function renderDetail(state, withPerms, results) {
167
168
  const project = withPerms[state.selectedProject];
168
169
  if (!project)
169
170
  return;
170
- const projectResults = results.filter((r) => {
171
- const idx = r.display.indexOf('/.claude/');
172
- const dir = idx >= 0 ? r.display.slice(0, idx) : r.display;
173
- const projIdx = project.display.indexOf('/.claude/');
174
- const projDir = projIdx >= 0 ? project.display.slice(0, projIdx) : project.display;
175
- return dir === projDir;
176
- });
171
+ const fileResult = results.find((r) => r.display === project.display);
172
+ if (!fileResult || fileResult.totalCount === 0)
173
+ return;
177
174
  // build navigable rows
178
175
  const navRows = [];
179
- for (const result of projectResults) {
180
- if (result.totalCount === 0)
181
- continue;
182
- const fileName = result.display.replace(/.*\/\.claude\//, '');
183
- navRows.push({ text: `${colors_js_1.CYAN}${fileName}${colors_js_1.NC} ${colors_js_1.DIM}(${result.totalCount})${colors_js_1.NC}` });
184
- for (const group of result.groups) {
185
- const key = `${result.path}:${group.category}`;
186
- const isOpen = state.expanded.has(key);
187
- const arrow = isOpen ? '' : '▸';
188
- 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 });
189
- if (isOpen) {
190
- const maxLen = w - 12;
191
- for (const item of group.items) {
192
- const name = item.name.length > maxLen ? item.name.slice(0, maxLen - 1) + '…' : item.name;
193
- navRows.push({ text: ` ${colors_js_1.DIM}${name}${colors_js_1.NC}` });
194
- }
176
+ for (const group of fileResult.groups) {
177
+ const key = `${fileResult.path}:${group.category}`;
178
+ const isOpen = state.expanded.has(key);
179
+ const arrow = isOpen ? '' : '▸';
180
+ 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
+ if (isOpen) {
182
+ const maxLen = w - 12;
183
+ 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}` });
195
186
  }
196
187
  }
197
188
  }
@@ -219,7 +210,8 @@ function renderDetail(state, withPerms, results) {
219
210
  const visible = navRows.slice(state.detailScroll, state.detailScroll + visibleRows);
220
211
  const scrollInfo = navRows.length > visibleRows ? `${state.detailCursor + 1}/${navRows.length}` : '';
221
212
  const lines = [];
222
- lines.push(boxTop(`${project.shortName} (${project.totalCount})`, scrollInfo, w));
213
+ const typeTag = project.fileType === 'global' ? 'global' : project.fileType;
214
+ lines.push(boxTop(`${project.shortName} (${typeTag}) ${project.totalCount} permissions`, scrollInfo, w));
223
215
  for (let i = 0; i < visible.length; i++) {
224
216
  const globalIdx = state.detailScroll + i;
225
217
  const isCursor = globalIdx === state.detailCursor;
package/dist/renderer.js CHANGED
@@ -11,15 +11,15 @@ function rpad(s, n) {
11
11
  const str = String(s);
12
12
  return str.length >= n ? str : ' '.repeat(n - str.length) + str;
13
13
  }
14
- function printCompact(merged, summary) {
14
+ function printCompact(entries, summary) {
15
15
  const cats = ['Bash', 'WebFetch', 'MCP', 'Tools'];
16
- const catsPresent = cats.filter((c) => merged.some((r) => r.groups.has(c)));
17
- const globals = merged.filter((r) => r.totalCount > 0 && r.isGlobal).sort((a, b) => b.totalCount - a.totalCount);
18
- const projects = merged.filter((r) => r.totalCount > 0 && !r.isGlobal).sort((a, b) => b.totalCount - a.totalCount);
16
+ const catsPresent = cats.filter((c) => entries.some((r) => r.groups.has(c)));
17
+ const globals = entries.filter((r) => r.totalCount > 0 && r.isGlobal).sort((a, b) => b.totalCount - a.totalCount);
18
+ const projects = entries.filter((r) => r.totalCount > 0 && !r.isGlobal).sort((a, b) => b.totalCount - a.totalCount);
19
19
  const withPerms = [...globals, ...projects];
20
- const emptyCount = merged.filter((r) => r.totalCount === 0).length;
20
+ const emptyCount = entries.filter((r) => r.totalCount === 0).length;
21
21
  // header
22
- const nameWidths = withPerms.map((r) => r.isGlobal ? r.shortName.length + 2 : r.shortName.length);
22
+ const nameWidths = withPerms.map((r) => r.shortName.length + 2);
23
23
  const nameWidth = Math.min(Math.max(...nameWidths, 7), 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,7 +27,8 @@ function printCompact(merged, summary) {
27
27
  // rows
28
28
  for (let i = 0; i < withPerms.length; i++) {
29
29
  const result = withPerms[i];
30
- const displayName = result.isGlobal ? `★ ${result.shortName}` : result.shortName;
30
+ const typeLabel = result.isGlobal ? '' : result.fileType === 'local' ? ' ˡ' : ' ˢ';
31
+ const displayName = result.isGlobal ? `★ ${result.shortName}` : `${result.shortName}${typeLabel}`;
31
32
  const truncName = displayName.length > nameWidth ? displayName.slice(0, nameWidth - 1) + '…' : displayName;
32
33
  const nameStyle = result.isGlobal ? `${colors_js_1.YELLOW}` : `${colors_js_1.DIM}`;
33
34
  const nameCol = ` ${nameStyle}${pad(truncName, nameWidth)}${colors_js_1.NC}`;
@@ -68,5 +69,5 @@ function printVerbose(results, summary) {
68
69
  function printFooter(summary) {
69
70
  console.log(`\n ${colors_js_1.DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors_js_1.NC}`);
70
71
  const catSummary = [...summary.categoryTotals.entries()].map(([k, v]) => `${k}: ${colors_js_1.BOLD}${v}${colors_js_1.NC}${colors_js_1.DIM}`).join(' ');
71
- console.log(` ${colors_js_1.BOLD}${summary.projectsWithPerms}${colors_js_1.NC} projects ${colors_js_1.BOLD}${summary.totalPerms}${colors_js_1.NC} permissions ${colors_js_1.DIM}(${catSummary})${colors_js_1.NC}\n`);
72
+ console.log(` ${colors_js_1.BOLD}${summary.totalProjects}${colors_js_1.NC} projects ${colors_js_1.BOLD}${summary.totalPerms}${colors_js_1.NC} permissions ${colors_js_1.DIM}(${catSummary})${colors_js_1.NC}\n`);
72
73
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccperm",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "Audit Claude Code permissions across all your projects",
5
5
  "bin": {
6
6
  "ccperm": "bin/ccperm.js"