ccperm 1.4.0 → 1.4.2
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 +33 -9
- package/dist/scanner.js +6 -5
- package/package.json +1 -1
package/dist/interactive.js
CHANGED
|
@@ -15,7 +15,7 @@ function startInteractive(merged, results) {
|
|
|
15
15
|
resolve();
|
|
16
16
|
return;
|
|
17
17
|
}
|
|
18
|
-
const state = { view: 'list', cursor: 0, scrollOffset: 0, selectedProject: 0 };
|
|
18
|
+
const state = { view: 'list', cursor: 0, scrollOffset: 0, selectedProject: 0, detailScroll: 0 };
|
|
19
19
|
node_readline_1.default.emitKeypressEvents(process.stdin);
|
|
20
20
|
if (process.stdin.isTTY)
|
|
21
21
|
process.stdin.setRawMode(true);
|
|
@@ -60,6 +60,7 @@ function startInteractive(merged, results) {
|
|
|
60
60
|
}
|
|
61
61
|
else if (key.name === 'return') {
|
|
62
62
|
state.selectedProject = state.cursor;
|
|
63
|
+
state.detailScroll = 0;
|
|
63
64
|
state.view = 'detail';
|
|
64
65
|
}
|
|
65
66
|
}
|
|
@@ -67,6 +68,13 @@ function startInteractive(merged, results) {
|
|
|
67
68
|
// detail view
|
|
68
69
|
if (key.name === 'escape' || key.name === 'backspace') {
|
|
69
70
|
state.view = 'list';
|
|
71
|
+
state.detailScroll = 0;
|
|
72
|
+
}
|
|
73
|
+
else if (key.name === 'up') {
|
|
74
|
+
state.detailScroll = Math.max(0, state.detailScroll - 1);
|
|
75
|
+
}
|
|
76
|
+
else if (key.name === 'down') {
|
|
77
|
+
state.detailScroll++;
|
|
70
78
|
}
|
|
71
79
|
}
|
|
72
80
|
render();
|
|
@@ -119,10 +127,10 @@ function renderList(state, withPerms, emptyCount) {
|
|
|
119
127
|
process.stdout.write(lines.join('\n') + '\n');
|
|
120
128
|
}
|
|
121
129
|
function renderDetail(state, withPerms, results) {
|
|
130
|
+
const rows = process.stdout.rows || 24;
|
|
122
131
|
const project = withPerms[state.selectedProject];
|
|
123
132
|
if (!project)
|
|
124
133
|
return;
|
|
125
|
-
// find ScanResults belonging to this project
|
|
126
134
|
const projectResults = results.filter((r) => {
|
|
127
135
|
const idx = r.display.indexOf('/.claude/');
|
|
128
136
|
const dir = idx >= 0 ? r.display.slice(0, idx) : r.display;
|
|
@@ -130,21 +138,37 @@ function renderDetail(state, withPerms, results) {
|
|
|
130
138
|
const projDir = projIdx >= 0 ? project.display.slice(0, projIdx) : project.display;
|
|
131
139
|
return dir === projDir;
|
|
132
140
|
});
|
|
133
|
-
|
|
134
|
-
|
|
141
|
+
// build content lines
|
|
142
|
+
const content = [];
|
|
135
143
|
for (const result of projectResults) {
|
|
136
144
|
if (result.totalCount === 0)
|
|
137
145
|
continue;
|
|
138
|
-
|
|
146
|
+
// short filename: settings.local.json
|
|
147
|
+
const fileName = result.display.replace(/.*\/\.claude\//, '');
|
|
148
|
+
content.push(` ${colors_js_1.CYAN}${fileName}${colors_js_1.NC} ${colors_js_1.DIM}(${result.totalCount})${colors_js_1.NC}`);
|
|
139
149
|
for (const group of result.groups) {
|
|
140
|
-
|
|
150
|
+
content.push(` ${colors_js_1.YELLOW}${group.category}${colors_js_1.NC} ${colors_js_1.DIM}(${group.items.length})${colors_js_1.NC}`);
|
|
141
151
|
for (const item of group.items) {
|
|
142
|
-
|
|
152
|
+
content.push(` ${colors_js_1.DIM}${item.name}${colors_js_1.NC}`);
|
|
143
153
|
}
|
|
144
154
|
}
|
|
145
|
-
|
|
155
|
+
content.push(` ${colors_js_1.DIM}${'─'.repeat(40)}${colors_js_1.NC}`);
|
|
146
156
|
}
|
|
147
|
-
|
|
157
|
+
// header + footer take space
|
|
158
|
+
const headerLines = 3;
|
|
159
|
+
const footerLines = 2;
|
|
160
|
+
const visibleRows = Math.max(1, rows - headerLines - footerLines);
|
|
161
|
+
const maxScroll = Math.max(0, content.length - visibleRows);
|
|
162
|
+
if (state.detailScroll > maxScroll)
|
|
163
|
+
state.detailScroll = maxScroll;
|
|
164
|
+
const visible = content.slice(state.detailScroll, state.detailScroll + visibleRows);
|
|
165
|
+
const scrollPct = content.length <= visibleRows ? '' : ` ${colors_js_1.DIM}${state.detailScroll + visibleRows}/${content.length}${colors_js_1.NC}`;
|
|
166
|
+
const lines = [];
|
|
167
|
+
lines.push(` ${colors_js_1.CYAN}${colors_js_1.BOLD}${project.shortName}${colors_js_1.NC} ${colors_js_1.DIM}(${project.totalCount} permissions)${colors_js_1.NC}${scrollPct}`);
|
|
168
|
+
lines.push('');
|
|
169
|
+
lines.push(...visible);
|
|
170
|
+
lines.push('');
|
|
171
|
+
lines.push(` ${colors_js_1.DIM}[↑↓] scroll [Esc] back [q] quit${colors_js_1.NC}`);
|
|
148
172
|
process.stdout.write(lines.join('\n') + '\n');
|
|
149
173
|
}
|
|
150
174
|
function pad(s, n) {
|
package/dist/scanner.js
CHANGED
|
@@ -44,12 +44,12 @@ async function findSettingsFiles(searchDir, onProgress, debug = false) {
|
|
|
44
44
|
entries = await node_fs_1.default.promises.readdir(dir, { withFileTypes: true });
|
|
45
45
|
}
|
|
46
46
|
catch {
|
|
47
|
-
return;
|
|
47
|
+
return;
|
|
48
48
|
}
|
|
49
|
+
const subdirs = [];
|
|
49
50
|
for (const entry of entries) {
|
|
50
51
|
const name = entry.name;
|
|
51
52
|
if (name === '.claude' && entry.isDirectory()) {
|
|
52
|
-
// found a .claude dir — check for settings files inside
|
|
53
53
|
const claudeDir = node_path_1.default.join(dir, '.claude');
|
|
54
54
|
let inner;
|
|
55
55
|
try {
|
|
@@ -71,16 +71,17 @@ async function findSettingsFiles(searchDir, onProgress, debug = false) {
|
|
|
71
71
|
catch { /* not writable */ }
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
-
continue;
|
|
74
|
+
continue;
|
|
75
75
|
}
|
|
76
76
|
if (!entry.isDirectory())
|
|
77
77
|
continue;
|
|
78
78
|
if (SKIP.has(name))
|
|
79
79
|
continue;
|
|
80
80
|
if (name.startsWith('.') && name !== '.claude')
|
|
81
|
-
continue;
|
|
82
|
-
|
|
81
|
+
continue;
|
|
82
|
+
subdirs.push(walk(node_path_1.default.join(dir, name)));
|
|
83
83
|
}
|
|
84
|
+
await Promise.all(subdirs);
|
|
84
85
|
}
|
|
85
86
|
await walk(searchDir);
|
|
86
87
|
if (debug) {
|