@skilly-hand/skilly-hand 0.18.0 → 0.19.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/CHANGELOG.md +19 -0
- package/README.md +9 -3
- package/catalog/skills/project-security/assets/generic-ci-security-gate.sh +1 -28
- package/catalog/skills/project-security/assets/github-actions-security-gate.yml +38 -0
- package/catalog/skills/project-security/assets/pre-commit.sample.sh +1 -1
- package/catalog/skills/project-security/assets/pre-push.sample.sh +1 -30
- package/catalog/skills/project-security/assets/run-security-check.shared.sh +33 -0
- package/package.json +5 -3
- package/packages/catalog/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/cli/src/bin.js +126 -161
- package/packages/cli/src/ink-ui.js +692 -0
- package/packages/core/package.json +1 -1
- package/packages/core/src/terminal.js +16 -5
- package/packages/core/src/ui/layout.js +193 -42
- package/packages/detectors/package.json +1 -1
|
@@ -24,6 +24,19 @@ function normalizeBooleanEnv(value) {
|
|
|
24
24
|
return true;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
function detectViewportWidth({ stdout = process.stdout, env = process.env } = {}) {
|
|
28
|
+
if (stdout?.isTTY && Number.isInteger(stdout.columns) && stdout.columns > 0) {
|
|
29
|
+
return stdout.columns;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const envColumns = Number.parseInt(String(env?.COLUMNS ?? ""), 10);
|
|
33
|
+
if (Number.isInteger(envColumns) && envColumns > 0) {
|
|
34
|
+
return envColumns;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return 80;
|
|
38
|
+
}
|
|
39
|
+
|
|
27
40
|
// Kept as exported API (tests import these directly)
|
|
28
41
|
export function detectColorSupport({ env = process.env, stream = process.stdout } = {}) {
|
|
29
42
|
const noColor = normalizeBooleanEnv(env.NO_COLOR);
|
|
@@ -78,6 +91,7 @@ export function createTerminalRenderer({
|
|
|
78
91
|
const colorLevel = detectColorLevel({ env, stream: stdout });
|
|
79
92
|
const colorEnabled = colorLevel > 0;
|
|
80
93
|
const unicodeEnabled = detectUnicodeSupport({ env, stream: stdout, platform });
|
|
94
|
+
const viewportWidth = detectViewportWidth({ stdout, env });
|
|
81
95
|
|
|
82
96
|
const theme = createTheme(colorLevel);
|
|
83
97
|
const layout = createLayout(theme, unicodeEnabled);
|
|
@@ -109,6 +123,7 @@ export function createTerminalRenderer({
|
|
|
109
123
|
colorEnabled,
|
|
110
124
|
colorLevel,
|
|
111
125
|
unicodeEnabled,
|
|
126
|
+
viewportWidth,
|
|
112
127
|
symbols,
|
|
113
128
|
style,
|
|
114
129
|
theme,
|
|
@@ -134,11 +149,7 @@ export function createTerminalRenderer({
|
|
|
134
149
|
},
|
|
135
150
|
|
|
136
151
|
table(columns, rows) {
|
|
137
|
-
|
|
138
|
-
if (unicodeEnabled) {
|
|
139
|
-
return layout.borderedTable(columns, rows);
|
|
140
|
-
}
|
|
141
|
-
return renderPlainTable(columns, rows);
|
|
152
|
+
return layout.borderedTable(columns, rows, { viewportWidth });
|
|
142
153
|
},
|
|
143
154
|
|
|
144
155
|
status(level, message, detail = "") {
|
|
@@ -30,6 +30,68 @@ function truncate(value, maxWidth) {
|
|
|
30
30
|
return clean.slice(0, maxWidth - 1) + "…";
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
function summarizeDenseValue(value) {
|
|
34
|
+
const raw = String(value);
|
|
35
|
+
const parts = raw.split(",").map((part) => part.trim()).filter(Boolean);
|
|
36
|
+
if (parts.length < 4) return { display: raw, detail: null };
|
|
37
|
+
return {
|
|
38
|
+
display: `${parts.slice(0, 3).join(", ")} +${parts.length - 3} more`,
|
|
39
|
+
detail: raw
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function fitWidths(preferred, minimums, target) {
|
|
44
|
+
const widths = [...preferred];
|
|
45
|
+
let current = widths.reduce((sum, width) => sum + width, 0);
|
|
46
|
+
if (current <= target) return widths;
|
|
47
|
+
|
|
48
|
+
while (current > target) {
|
|
49
|
+
let idx = -1;
|
|
50
|
+
let widest = 0;
|
|
51
|
+
|
|
52
|
+
for (let i = 0; i < widths.length; i += 1) {
|
|
53
|
+
if (widths[i] > minimums[i] && widths[i] > widest) {
|
|
54
|
+
widest = widths[i];
|
|
55
|
+
idx = i;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (idx === -1) return null;
|
|
60
|
+
widths[idx] -= 1;
|
|
61
|
+
current -= 1;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return widths;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function nextWrapChunk(text, width) {
|
|
68
|
+
if (text.length <= width) return [text, ""];
|
|
69
|
+
const split = text.lastIndexOf(" ", width);
|
|
70
|
+
if (split <= 0) return [text.slice(0, width), text.slice(width)];
|
|
71
|
+
return [text.slice(0, split), text.slice(split + 1)];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function wrapPrefixedText(text, maxWidth, firstPrefix, continuationPrefix) {
|
|
75
|
+
const lines = [];
|
|
76
|
+
let remaining = String(text).trim();
|
|
77
|
+
let first = true;
|
|
78
|
+
|
|
79
|
+
while (remaining.length > 0) {
|
|
80
|
+
const prefix = first ? firstPrefix : continuationPrefix;
|
|
81
|
+
const available = Math.max(8, maxWidth - prefix.length);
|
|
82
|
+
const [chunk, rest] = nextWrapChunk(remaining, available);
|
|
83
|
+
lines.push(prefix + chunk);
|
|
84
|
+
remaining = rest.trimStart();
|
|
85
|
+
first = false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (lines.length === 0) {
|
|
89
|
+
return [firstPrefix.trimEnd()];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return lines;
|
|
93
|
+
}
|
|
94
|
+
|
|
33
95
|
// Box character sets
|
|
34
96
|
const BOX = {
|
|
35
97
|
// double-line outer (for banner)
|
|
@@ -140,27 +202,29 @@ export function createLayout(theme, unicodeEnabled) {
|
|
|
140
202
|
function borderedTable(columns, rows, opts = {}) {
|
|
141
203
|
if (!columns || columns.length === 0) return "";
|
|
142
204
|
const maxColW = opts.maxColWidth || 36;
|
|
143
|
-
|
|
205
|
+
const viewportWidth = Math.max(40, Number(opts.viewportWidth) || 80);
|
|
144
206
|
const headers = columns.map((c) => String(c.header));
|
|
207
|
+
const detailMarker = unicodeEnabled ? "↳ " : "-> ";
|
|
208
|
+
const minWidths = headers.map((header) => Math.max(6, Math.min(visLen(header), 14)));
|
|
209
|
+
|
|
145
210
|
const matrix = rows.map((row) =>
|
|
146
|
-
columns.map((c) =>
|
|
211
|
+
columns.map((c) => {
|
|
212
|
+
const summarized = summarizeDenseValue(String(row[c.key] ?? ""));
|
|
213
|
+
return {
|
|
214
|
+
display: summarized.display,
|
|
215
|
+
detail: summarized.detail
|
|
216
|
+
};
|
|
217
|
+
})
|
|
147
218
|
);
|
|
148
219
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return Math.max(visLen(h), contentMax, 3);
|
|
220
|
+
const preferredWidths = headers.map((h, i) => {
|
|
221
|
+
const contentMax = matrix.reduce((max, row) => Math.max(max, visLen(row[i].display)), 0);
|
|
222
|
+
return Math.min(Math.max(visLen(h), contentMax, 3), maxColW);
|
|
153
223
|
});
|
|
154
224
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const sepLine = widths.map((w) => "-".repeat(w)).join(" ");
|
|
159
|
-
const bodyLines = matrix.map((row) =>
|
|
160
|
-
row.map((cell, i) => padEnd(cell, widths[i])).join(" ")
|
|
161
|
-
);
|
|
162
|
-
return [headerLine, sepLine, ...bodyLines].join("\n");
|
|
163
|
-
}
|
|
225
|
+
const borderedOverhead = (3 * headers.length) + 1;
|
|
226
|
+
const compactOverhead = 3 * Math.max(headers.length - 1, 0);
|
|
227
|
+
const preferredSum = preferredWidths.reduce((sum, width) => sum + width, 0);
|
|
164
228
|
|
|
165
229
|
const s = box.s;
|
|
166
230
|
|
|
@@ -174,33 +238,120 @@ export function createLayout(theme, unicodeEnabled) {
|
|
|
174
238
|
return leftC + inner + rightC;
|
|
175
239
|
}
|
|
176
240
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
241
|
+
function buildCompactTable(widths) {
|
|
242
|
+
const compactRows = [];
|
|
243
|
+
const formattedHeaders = headers.map((header, idx) => truncate(header, widths[idx]));
|
|
244
|
+
const headerLine = formattedHeaders.map((header, idx) => padEnd(header, widths[idx])).join(" | ");
|
|
245
|
+
const sepLine = widths.map((width) => "-".repeat(width)).join("-+-");
|
|
246
|
+
|
|
247
|
+
compactRows.push(headerLine);
|
|
248
|
+
compactRows.push(sepLine);
|
|
249
|
+
|
|
250
|
+
for (let rowIdx = 0; rowIdx < matrix.length; rowIdx += 1) {
|
|
251
|
+
const row = matrix[rowIdx];
|
|
252
|
+
const cells = row.map((cell, colIdx) => truncate(cell.display, widths[colIdx]));
|
|
253
|
+
compactRows.push(cells.map((cell, colIdx) => padEnd(cell, widths[colIdx])).join(" | "));
|
|
254
|
+
|
|
255
|
+
const details = [];
|
|
256
|
+
for (let colIdx = 0; colIdx < row.length; colIdx += 1) {
|
|
257
|
+
if (!row[colIdx].detail) continue;
|
|
258
|
+
details.push(...wrapPrefixedText(
|
|
259
|
+
`${headers[colIdx]}: ${row[colIdx].detail}`,
|
|
260
|
+
viewportWidth,
|
|
261
|
+
`${detailMarker}`,
|
|
262
|
+
" "
|
|
263
|
+
));
|
|
264
|
+
}
|
|
265
|
+
compactRows.push(...details.map((line) => theme.muted(line)));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return compactRows.join("\n");
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function buildCardList() {
|
|
272
|
+
const cardRows = [];
|
|
273
|
+
for (let rowIdx = 0; rowIdx < matrix.length; rowIdx += 1) {
|
|
274
|
+
const row = matrix[rowIdx];
|
|
275
|
+
for (let colIdx = 0; colIdx < row.length; colIdx += 1) {
|
|
276
|
+
const header = headers[colIdx];
|
|
277
|
+
const value = row[colIdx].display;
|
|
278
|
+
const isFirst = colIdx === 0;
|
|
279
|
+
const prefix = isFirst ? `${detailMarker}${header}: ` : ` ${header}: `;
|
|
280
|
+
const continuation = isFirst ? " " : " ";
|
|
281
|
+
const lineSet = wrapPrefixedText(value, viewportWidth, prefix, continuation);
|
|
282
|
+
if (isFirst) {
|
|
283
|
+
cardRows.push(...lineSet.map((line, idx) => idx === 0 ? theme.accent(line) : line));
|
|
284
|
+
} else {
|
|
285
|
+
cardRows.push(...lineSet);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (row[colIdx].detail) {
|
|
289
|
+
cardRows.push(...wrapPrefixedText(
|
|
290
|
+
row[colIdx].detail,
|
|
291
|
+
viewportWidth,
|
|
292
|
+
` ${header} (full): `,
|
|
293
|
+
" "
|
|
294
|
+
).map((line) => theme.muted(line)));
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (rowIdx < matrix.length - 1) {
|
|
299
|
+
cardRows.push(theme.muted("-".repeat(Math.min(32, viewportWidth))));
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return cardRows.join("\n");
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const canUseBordered = unicodeEnabled && (preferredSum + borderedOverhead <= viewportWidth);
|
|
306
|
+
if (canUseBordered) {
|
|
307
|
+
const widths = preferredWidths;
|
|
308
|
+
const topBorder = makeDivider(widths, s.tl, s.mt, s.tr, s.h);
|
|
309
|
+
const headerRow = makeRow(headers.map((h) => theme.bold(h)), widths, theme.primary(s.v), theme.primary(s.v), theme.primary(s.v));
|
|
310
|
+
const headerSep = makeDivider(widths, s.dl, s.dc, s.dr, s.dh);
|
|
311
|
+
const tableWidth = visLen(topBorder);
|
|
312
|
+
const detailContentWidth = Math.max(8, tableWidth - 4);
|
|
313
|
+
|
|
314
|
+
const bodyRows = [];
|
|
315
|
+
for (const row of matrix) {
|
|
316
|
+
bodyRows.push(makeRow(
|
|
317
|
+
row.map((cell, i) => i === 0 ? theme.accent(truncate(cell.display, widths[i])) : truncate(cell.display, widths[i])),
|
|
318
|
+
widths,
|
|
319
|
+
theme.primary(s.v),
|
|
320
|
+
theme.primary(s.v),
|
|
321
|
+
theme.primary(s.v)
|
|
322
|
+
));
|
|
323
|
+
|
|
324
|
+
for (let colIdx = 0; colIdx < row.length; colIdx += 1) {
|
|
325
|
+
if (!row[colIdx].detail) continue;
|
|
326
|
+
const wrapped = wrapPrefixedText(
|
|
327
|
+
`${headers[colIdx]}: ${row[colIdx].detail}`,
|
|
328
|
+
detailContentWidth,
|
|
329
|
+
detailMarker,
|
|
330
|
+
" "
|
|
331
|
+
);
|
|
332
|
+
for (const line of wrapped) {
|
|
333
|
+
bodyRows.push(
|
|
334
|
+
theme.primary(s.v) +
|
|
335
|
+
" " +
|
|
336
|
+
padEnd(theme.muted(line), detailContentWidth) +
|
|
337
|
+
" " +
|
|
338
|
+
theme.primary(s.v)
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const bottomBorder = makeDivider(widths, s.bl, s.mb, s.br, s.h);
|
|
345
|
+
return [theme.primary(topBorder), headerRow, theme.primary(headerSep), ...bodyRows, theme.primary(bottomBorder)].join("\n");
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const compactWidthTarget = viewportWidth - compactOverhead;
|
|
349
|
+
const fittedCompactWidths = fitWidths(preferredWidths, minWidths, compactWidthTarget);
|
|
350
|
+
if (fittedCompactWidths) {
|
|
351
|
+
return buildCompactTable(fittedCompactWidths);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return buildCardList();
|
|
204
355
|
}
|
|
205
356
|
|
|
206
357
|
// ── Detection grid ────────────────────────────────────────────────────────
|