ccperm 1.4.3 → 1.5.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/interactive.js +62 -21
- 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, detailScroll: 0 };
|
|
18
|
+
const state = { view: 'list', cursor: 0, scrollOffset: 0, selectedProject: 0, detailCursor: 0, detailScroll: 0, expanded: new Set() };
|
|
19
19
|
node_readline_1.default.emitKeypressEvents(process.stdin);
|
|
20
20
|
if (process.stdin.isTTY)
|
|
21
21
|
process.stdin.setRawMode(true);
|
|
@@ -60,7 +60,9 @@ function startInteractive(merged, results) {
|
|
|
60
60
|
}
|
|
61
61
|
else if (key.name === 'return') {
|
|
62
62
|
state.selectedProject = state.cursor;
|
|
63
|
+
state.detailCursor = 0;
|
|
63
64
|
state.detailScroll = 0;
|
|
65
|
+
state.expanded = new Set();
|
|
64
66
|
state.view = 'detail';
|
|
65
67
|
}
|
|
66
68
|
}
|
|
@@ -68,13 +70,18 @@ function startInteractive(merged, results) {
|
|
|
68
70
|
// detail view
|
|
69
71
|
if (key.name === 'escape' || key.name === 'backspace') {
|
|
70
72
|
state.view = 'list';
|
|
73
|
+
state.detailCursor = 0;
|
|
71
74
|
state.detailScroll = 0;
|
|
72
75
|
}
|
|
73
76
|
else if (key.name === 'up') {
|
|
74
|
-
state.
|
|
77
|
+
state.detailCursor = Math.max(0, state.detailCursor - 1);
|
|
75
78
|
}
|
|
76
79
|
else if (key.name === 'down') {
|
|
77
|
-
state.
|
|
80
|
+
state.detailCursor++;
|
|
81
|
+
}
|
|
82
|
+
else if (key.name === 'return') {
|
|
83
|
+
// toggle handled in renderDetail via detailRows
|
|
84
|
+
state._toggle = true;
|
|
78
85
|
}
|
|
79
86
|
}
|
|
80
87
|
render();
|
|
@@ -99,7 +106,8 @@ function renderList(state, withPerms, emptyCount) {
|
|
|
99
106
|
if (state.cursor >= state.scrollOffset + visibleRows)
|
|
100
107
|
state.scrollOffset = state.cursor - visibleRows + 1;
|
|
101
108
|
const lines = [];
|
|
102
|
-
|
|
109
|
+
const scrollInfo = withPerms.length > visibleRows ? ` ${colors_js_1.DIM}${state.cursor + 1}/${withPerms.length}${colors_js_1.NC}` : '';
|
|
110
|
+
lines.push(` ${colors_js_1.CYAN}${colors_js_1.BOLD}ccperm${colors_js_1.NC} ${colors_js_1.DIM}interactive${colors_js_1.NC}${scrollInfo}\n`);
|
|
103
111
|
lines.push(` ${colors_js_1.DIM}${pad('PROJECT', nameWidth)} ${catsPresent.map((c) => rpad(c, 5)).join(' ')} TOTAL${colors_js_1.NC}`);
|
|
104
112
|
lines.push(` ${colors_js_1.DIM}${'─'.repeat(nameWidth + catsPresent.length * 7 + 8)}${colors_js_1.NC}`);
|
|
105
113
|
const end = Math.min(state.scrollOffset + visibleRows, withPerms.length);
|
|
@@ -128,6 +136,7 @@ function renderList(state, withPerms, emptyCount) {
|
|
|
128
136
|
}
|
|
129
137
|
function renderDetail(state, withPerms, results) {
|
|
130
138
|
const rows = process.stdout.rows || 24;
|
|
139
|
+
const cols = process.stdout.columns || 80;
|
|
131
140
|
const project = withPerms[state.selectedProject];
|
|
132
141
|
if (!project)
|
|
133
142
|
return;
|
|
@@ -138,37 +147,69 @@ function renderDetail(state, withPerms, results) {
|
|
|
138
147
|
const projDir = projIdx >= 0 ? project.display.slice(0, projIdx) : project.display;
|
|
139
148
|
return dir === projDir;
|
|
140
149
|
});
|
|
141
|
-
// build
|
|
142
|
-
const
|
|
150
|
+
// build navigable rows
|
|
151
|
+
const navRows = [];
|
|
143
152
|
for (const result of projectResults) {
|
|
144
153
|
if (result.totalCount === 0)
|
|
145
154
|
continue;
|
|
146
|
-
// short filename: settings.local.json
|
|
147
155
|
const fileName = result.display.replace(/.*\/\.claude\//, '');
|
|
148
|
-
|
|
156
|
+
navRows.push({ line: ` ${colors_js_1.CYAN}${fileName}${colors_js_1.NC} ${colors_js_1.DIM}(${result.totalCount})${colors_js_1.NC}` });
|
|
149
157
|
for (const group of result.groups) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
158
|
+
const key = `${result.path}:${group.category}`;
|
|
159
|
+
const isOpen = state.expanded.has(key);
|
|
160
|
+
const arrow = isOpen ? '▾' : '▸';
|
|
161
|
+
navRows.push({ line: ` ${colors_js_1.YELLOW}${arrow} ${group.category}${colors_js_1.NC} ${colors_js_1.DIM}(${group.items.length})${colors_js_1.NC}`, key });
|
|
162
|
+
if (isOpen) {
|
|
163
|
+
const maxLen = cols - 10;
|
|
164
|
+
for (const item of group.items) {
|
|
165
|
+
const name = item.name.length > maxLen ? item.name.slice(0, maxLen - 1) + '…' : item.name;
|
|
166
|
+
navRows.push({ line: ` ${colors_js_1.DIM}${name}${colors_js_1.NC}` });
|
|
167
|
+
}
|
|
153
168
|
}
|
|
154
169
|
}
|
|
155
|
-
content.push(` ${colors_js_1.DIM}${'─'.repeat(40)}${colors_js_1.NC}`);
|
|
156
170
|
}
|
|
157
|
-
//
|
|
171
|
+
// handle toggle
|
|
172
|
+
if (state._toggle) {
|
|
173
|
+
delete state._toggle;
|
|
174
|
+
const row = navRows[state.detailCursor];
|
|
175
|
+
if (row?.key) {
|
|
176
|
+
if (state.expanded.has(row.key))
|
|
177
|
+
state.expanded.delete(row.key);
|
|
178
|
+
else
|
|
179
|
+
state.expanded.add(row.key);
|
|
180
|
+
// re-render needed — will happen on next render() call
|
|
181
|
+
renderDetail(state, withPerms, results);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// clamp cursor
|
|
186
|
+
if (state.detailCursor >= navRows.length)
|
|
187
|
+
state.detailCursor = Math.max(0, navRows.length - 1);
|
|
188
|
+
// scroll
|
|
158
189
|
const headerLines = 3;
|
|
159
190
|
const footerLines = 2;
|
|
160
191
|
const visibleRows = Math.max(1, rows - headerLines - footerLines);
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const
|
|
192
|
+
if (state.detailCursor < state.detailScroll)
|
|
193
|
+
state.detailScroll = state.detailCursor;
|
|
194
|
+
if (state.detailCursor >= state.detailScroll + visibleRows)
|
|
195
|
+
state.detailScroll = state.detailCursor - visibleRows + 1;
|
|
196
|
+
const visible = navRows.slice(state.detailScroll, state.detailScroll + visibleRows);
|
|
166
197
|
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}
|
|
198
|
+
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}`);
|
|
168
199
|
lines.push('');
|
|
169
|
-
|
|
200
|
+
for (let i = 0; i < visible.length; i++) {
|
|
201
|
+
const globalIdx = state.detailScroll + i;
|
|
202
|
+
const isCursor = globalIdx === state.detailCursor;
|
|
203
|
+
const row = visible[i];
|
|
204
|
+
if (isCursor) {
|
|
205
|
+
lines.push(row.line.replace(/^ /, `${colors_js_1.CYAN}> `));
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
lines.push(row.line);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
170
211
|
lines.push('');
|
|
171
|
-
lines.push(` ${colors_js_1.DIM}[↑↓]
|
|
212
|
+
lines.push(` ${colors_js_1.DIM}[↑↓] navigate [Enter] expand/collapse [Esc] back [q] quit${colors_js_1.NC}`);
|
|
172
213
|
process.stdout.write(lines.join('\n') + '\n');
|
|
173
214
|
}
|
|
174
215
|
function pad(s, n) {
|