@skilly-hand/skilly-hand 0.2.0 → 0.4.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 +31 -0
- package/README.md +16 -1
- package/catalog/README.md +10 -27
- package/catalog/catalog-index.json +1 -0
- package/catalog/skills/figma-mcp-0to1/SKILL.md +69 -0
- package/catalog/skills/figma-mcp-0to1/agents/canvas-creation-playbook.md +54 -0
- package/catalog/skills/figma-mcp-0to1/agents/install-auth.md +54 -0
- package/catalog/skills/figma-mcp-0to1/agents/tool-function-catalog.md +38 -0
- package/catalog/skills/figma-mcp-0to1/agents/troubleshooting-ops.md +54 -0
- package/catalog/skills/figma-mcp-0to1/assets/client-config-snippets.md +76 -0
- package/catalog/skills/figma-mcp-0to1/assets/prompt-recipes.md +34 -0
- package/catalog/skills/figma-mcp-0to1/manifest.json +42 -0
- package/catalog/skills/figma-mcp-0to1/references/official-tools-matrix.md +49 -0
- package/package.json +1 -1
- package/packages/cli/src/bin.js +40 -54
- package/packages/core/src/terminal.js +76 -69
- package/packages/core/src/ui/brand.js +31 -0
- package/packages/core/src/ui/index.js +3 -0
- package/packages/core/src/ui/layout.js +289 -0
- package/packages/core/src/ui/theme.js +120 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
// Layout primitives: banner, section headers, bordered tables, detection grid, health badge.
|
|
2
|
+
|
|
3
|
+
import { getBrand } from "./brand.js";
|
|
4
|
+
|
|
5
|
+
const ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
6
|
+
|
|
7
|
+
function stripAnsi(value) {
|
|
8
|
+
return String(value).replace(ANSI_RE, "");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function visLen(value) {
|
|
12
|
+
return stripAnsi(value).length;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function padEnd(value, width) {
|
|
16
|
+
const len = visLen(value);
|
|
17
|
+
if (len >= width) return value;
|
|
18
|
+
return value + " ".repeat(width - len);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function padStart(value, width) {
|
|
22
|
+
const len = visLen(value);
|
|
23
|
+
if (len >= width) return value;
|
|
24
|
+
return " ".repeat(width - len) + value;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function truncate(value, maxWidth) {
|
|
28
|
+
const clean = stripAnsi(value);
|
|
29
|
+
if (clean.length <= maxWidth) return value;
|
|
30
|
+
return clean.slice(0, maxWidth - 1) + "…";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Box character sets
|
|
34
|
+
const BOX = {
|
|
35
|
+
// double-line outer (for banner)
|
|
36
|
+
d: {
|
|
37
|
+
tl: "╔", tr: "╗", bl: "╚", br: "╝",
|
|
38
|
+
h: "═", v: "║",
|
|
39
|
+
ml: "╠", mr: "╣",
|
|
40
|
+
},
|
|
41
|
+
// single-line inner (for tables and sections)
|
|
42
|
+
s: {
|
|
43
|
+
tl: "┌", tr: "┐", bl: "└", br: "┘",
|
|
44
|
+
h: "─", v: "│",
|
|
45
|
+
ml: "├", mr: "┤",
|
|
46
|
+
mt: "┬", mb: "┴", x: "┼",
|
|
47
|
+
// double-separator row (header separator in tables)
|
|
48
|
+
dh: "═",
|
|
49
|
+
dl: "╞", dr: "╡", dc: "╪",
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// ASCII fallback chars
|
|
54
|
+
const BOX_ASCII = {
|
|
55
|
+
d: { tl: "+", tr: "+", bl: "+", br: "+", h: "=", v: "|", ml: "+", mr: "+" },
|
|
56
|
+
s: {
|
|
57
|
+
tl: "+", tr: "+", bl: "+", br: "+",
|
|
58
|
+
h: "-", v: "|",
|
|
59
|
+
ml: "+", mr: "+", mt: "+", mb: "+", x: "+",
|
|
60
|
+
dh: "=", dl: "+", dr: "+", dc: "+",
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export function createLayout(theme, unicodeEnabled) {
|
|
65
|
+
const box = unicodeEnabled ? BOX : BOX_ASCII;
|
|
66
|
+
const brand = getBrand();
|
|
67
|
+
|
|
68
|
+
// ── Banner ────────────────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
function banner(version) {
|
|
71
|
+
const logo = unicodeEnabled ? brand.logo.unicode : brand.logo.ascii;
|
|
72
|
+
const innerW = 56; // inner content width (excluding border chars)
|
|
73
|
+
|
|
74
|
+
const nameStr = brand.name;
|
|
75
|
+
const versionStr = version ? ` v${version}` : "";
|
|
76
|
+
const taglineStr = brand.tagline;
|
|
77
|
+
const hintStr = brand.hint;
|
|
78
|
+
|
|
79
|
+
// right column content
|
|
80
|
+
const rightLines = [
|
|
81
|
+
theme.bold(theme.accent(nameStr)) + theme.muted(versionStr),
|
|
82
|
+
theme.muted(taglineStr),
|
|
83
|
+
"",
|
|
84
|
+
"",
|
|
85
|
+
"",
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
const logoW = Math.max(...logo.map((l) => stripAnsi(l).length));
|
|
89
|
+
const gap = 3;
|
|
90
|
+
const rightW = innerW - logoW - gap - 2; // 2 for leading spaces
|
|
91
|
+
|
|
92
|
+
const d = box.d;
|
|
93
|
+
const hLine = d.h.repeat(innerW + 2); // +2 for padding spaces inside border
|
|
94
|
+
|
|
95
|
+
const rows = logo.map((logoLine, i) => {
|
|
96
|
+
const logoColored = theme.primary(theme.bold(logoLine));
|
|
97
|
+
const rightLine = rightLines[i] || "";
|
|
98
|
+
const logoLen = logoW;
|
|
99
|
+
const logoPadded = padEnd(logoColored, logoLen + (visLen(logoColored) - stripAnsi(logoColored).length));
|
|
100
|
+
const rightPadded = padEnd(rightLine, rightW);
|
|
101
|
+
const content = " " + logoPadded + " ".repeat(gap) + rightPadded;
|
|
102
|
+
const contentVis = visLen(content);
|
|
103
|
+
const totalW = innerW + 2;
|
|
104
|
+
const paddingNeeded = Math.max(0, totalW - contentVis);
|
|
105
|
+
return d.v + content + " ".repeat(paddingNeeded) + d.v;
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// separator row between logo area and hint
|
|
109
|
+
const sepRow = d.ml + d.h.repeat(innerW + 2) + d.mr;
|
|
110
|
+
|
|
111
|
+
const hintContent = " " + theme.muted(hintStr);
|
|
112
|
+
const hintVis = visLen(hintContent);
|
|
113
|
+
const hintPad = Math.max(0, innerW + 2 - hintVis);
|
|
114
|
+
const hintRow = d.v + hintContent + " ".repeat(hintPad) + d.v;
|
|
115
|
+
|
|
116
|
+
const lines = [
|
|
117
|
+
d.tl + hLine + d.tr,
|
|
118
|
+
...rows,
|
|
119
|
+
sepRow,
|
|
120
|
+
hintRow,
|
|
121
|
+
d.bl + hLine + d.br,
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
return lines.join("\n");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ── Section header ────────────────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
function sectionHeader(title) {
|
|
130
|
+
if (!unicodeEnabled) {
|
|
131
|
+
return theme.bold(title);
|
|
132
|
+
}
|
|
133
|
+
const s = box.s;
|
|
134
|
+
const decorated = theme.primary(s.tl) + theme.primary(s.h.repeat(2)) + " " + theme.bold(title) + " " + theme.primary(s.h.repeat(Math.max(2, 40 - title.length)));
|
|
135
|
+
return decorated;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ── Bordered table ────────────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
function borderedTable(columns, rows, opts = {}) {
|
|
141
|
+
if (!columns || columns.length === 0) return "";
|
|
142
|
+
const maxColW = opts.maxColWidth || 36;
|
|
143
|
+
|
|
144
|
+
const headers = columns.map((c) => String(c.header));
|
|
145
|
+
const matrix = rows.map((row) =>
|
|
146
|
+
columns.map((c) => truncate(String(row[c.key] ?? ""), maxColW))
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
// Compute column widths
|
|
150
|
+
const widths = headers.map((h, i) => {
|
|
151
|
+
const contentMax = matrix.reduce((m, row) => Math.max(m, visLen(row[i])), 0);
|
|
152
|
+
return Math.max(visLen(h), contentMax, 3);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
if (!unicodeEnabled) {
|
|
156
|
+
// Plain fallback — original table style
|
|
157
|
+
const headerLine = headers.map((h, i) => padEnd(h, widths[i])).join(" ");
|
|
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
|
+
}
|
|
164
|
+
|
|
165
|
+
const s = box.s;
|
|
166
|
+
|
|
167
|
+
function makeRow(cells, widths, leftC, midC, rightC, padChar = " ") {
|
|
168
|
+
const inner = cells.map((cell, i) => padChar + padEnd(cell, widths[i]) + padChar).join(midC);
|
|
169
|
+
return leftC + inner + rightC;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function makeDivider(widths, leftC, midC, rightC, fillC) {
|
|
173
|
+
const inner = widths.map((w) => fillC.repeat(w + 2)).join(midC);
|
|
174
|
+
return leftC + inner + rightC;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const topBorder = makeDivider(widths, s.tl, s.mt, s.tr, s.h);
|
|
178
|
+
const headerRow = makeRow(headers.map((h, i) => theme.bold(h)), widths, theme.primary(s.v), theme.primary(s.v), theme.primary(s.v));
|
|
179
|
+
const headerSep = makeDivider(widths, s.dl, s.dc, s.dr, s.dh);
|
|
180
|
+
const bodyRows = matrix.map((row) =>
|
|
181
|
+
makeRow(
|
|
182
|
+
row.map((cell, i) => {
|
|
183
|
+
// color first column (usually ID) with accent
|
|
184
|
+
return i === 0 ? theme.accent(cell) : cell;
|
|
185
|
+
}),
|
|
186
|
+
widths, theme.primary(s.v), theme.primary(s.v), theme.primary(s.v)
|
|
187
|
+
)
|
|
188
|
+
);
|
|
189
|
+
const bottomBorder = makeDivider(widths, s.bl, s.mb, s.br, s.h);
|
|
190
|
+
|
|
191
|
+
// Apply primary color to border chars
|
|
192
|
+
const colorBorder = (line) => {
|
|
193
|
+
// Replace non-letter/space chars with colored versions
|
|
194
|
+
return theme.primary(line);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
return [
|
|
198
|
+
colorBorder(topBorder),
|
|
199
|
+
headerRow,
|
|
200
|
+
colorBorder(headerSep),
|
|
201
|
+
...bodyRows,
|
|
202
|
+
colorBorder(bottomBorder),
|
|
203
|
+
].join("\n");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ── Detection grid ────────────────────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
function detectionGrid(detections) {
|
|
209
|
+
if (!detections || detections.length === 0) {
|
|
210
|
+
return theme.warn("! No technology signals detected.");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const COLS = 3;
|
|
214
|
+
const COL_W = 20;
|
|
215
|
+
|
|
216
|
+
function icon(confidence) {
|
|
217
|
+
if (unicodeEnabled) {
|
|
218
|
+
if (confidence >= 0.85) return theme.success("✔");
|
|
219
|
+
if (confidence >= 0.70) return theme.primary("◆");
|
|
220
|
+
return theme.warn("⚠");
|
|
221
|
+
}
|
|
222
|
+
if (confidence >= 0.85) return theme.success("[+]");
|
|
223
|
+
if (confidence >= 0.70) return theme.primary("[-]");
|
|
224
|
+
return theme.warn("[!]");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function colorTech(tech, confidence) {
|
|
228
|
+
if (confidence >= 0.85) return theme.success(tech);
|
|
229
|
+
if (confidence >= 0.70) return theme.primary(tech);
|
|
230
|
+
return theme.warn(tech);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const cells = detections.map((d) => {
|
|
234
|
+
const pct = Math.round(d.confidence * 100) + "%";
|
|
235
|
+
const ic = icon(d.confidence);
|
|
236
|
+
const name = colorTech(d.technology, d.confidence);
|
|
237
|
+
const pctStr = theme.muted(pct);
|
|
238
|
+
// visual: "✔ React 95%"
|
|
239
|
+
return ic + " " + padEnd(name, COL_W - 5) + " " + padEnd(pctStr, 4);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const rows = [];
|
|
243
|
+
for (let i = 0; i < cells.length; i += COLS) {
|
|
244
|
+
rows.push(cells.slice(i, i + COLS).join(" "));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return rows.join("\n");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ── Health badge ──────────────────────────────────────────────────────────
|
|
251
|
+
|
|
252
|
+
function healthBadge(installed) {
|
|
253
|
+
if (unicodeEnabled) {
|
|
254
|
+
if (installed) {
|
|
255
|
+
const badge = theme.success(theme.bold("██ HEALTHY"));
|
|
256
|
+
const line = theme.primary(" ─────────────────────────────────");
|
|
257
|
+
return badge + line;
|
|
258
|
+
}
|
|
259
|
+
const badge = theme.warn(theme.bold("██ NOT INSTALLED"));
|
|
260
|
+
const line = theme.muted(" ─────────────────────────────────");
|
|
261
|
+
return badge + line;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (installed) {
|
|
265
|
+
return theme.success(theme.bold("[OK] Installation healthy"));
|
|
266
|
+
}
|
|
267
|
+
return theme.warn(theme.bold("[!!] Not installed"));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ── Key-value panel ───────────────────────────────────────────────────────
|
|
271
|
+
|
|
272
|
+
function kvPanel(entries) {
|
|
273
|
+
if (!entries || entries.length === 0) return "";
|
|
274
|
+
const normalized = entries.map(([k, v]) => [String(k), String(v)]);
|
|
275
|
+
const width = normalized.reduce((max, [k]) => Math.max(max, k.length), 0);
|
|
276
|
+
return normalized
|
|
277
|
+
.map(([k, v]) => `${theme.muted(padEnd(k, width))} : ${v}`)
|
|
278
|
+
.join("\n");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
banner,
|
|
283
|
+
sectionHeader,
|
|
284
|
+
borderedTable,
|
|
285
|
+
detectionGrid,
|
|
286
|
+
healthBadge,
|
|
287
|
+
kvPanel,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// Color level detection and palette creation.
|
|
2
|
+
// Level 0: no color Level 1: basic ANSI Level 2: 256-color Level 3: truecolor
|
|
3
|
+
|
|
4
|
+
function normalizeBooleanEnv(value) {
|
|
5
|
+
if (value === undefined || value === null) return null;
|
|
6
|
+
const normalized = String(value).trim().toLowerCase();
|
|
7
|
+
if (normalized === "" || normalized === "0" || normalized === "false" || normalized === "no") return false;
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function detectColorLevel({ env = process.env, stream = process.stdout } = {}) {
|
|
12
|
+
if (normalizeBooleanEnv(env.NO_COLOR)) return 0;
|
|
13
|
+
|
|
14
|
+
const force = env.FORCE_COLOR;
|
|
15
|
+
if (force !== undefined) {
|
|
16
|
+
const level = parseInt(force, 10);
|
|
17
|
+
if (!isNaN(level)) return Math.min(Math.max(level, 0), 3);
|
|
18
|
+
if (normalizeBooleanEnv(force) === true) return 1;
|
|
19
|
+
if (normalizeBooleanEnv(force) === false) return 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!stream?.isTTY) return 0;
|
|
23
|
+
if (env.CI) return 0;
|
|
24
|
+
|
|
25
|
+
const colorterm = (env.COLORTERM || "").toLowerCase();
|
|
26
|
+
if (colorterm === "truecolor" || colorterm === "24bit") return 3;
|
|
27
|
+
|
|
28
|
+
const term = env.TERM || "";
|
|
29
|
+
const termProgram = env.TERM_PROGRAM || "";
|
|
30
|
+
if (term.includes("256color") || termProgram === "iTerm.app" || termProgram === "vscode") return 2;
|
|
31
|
+
|
|
32
|
+
return 1;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ANSI wrap helpers
|
|
36
|
+
function wrapBasic(code) {
|
|
37
|
+
return (value) => `\x1b[${code}m${String(value)}\x1b[0m`;
|
|
38
|
+
}
|
|
39
|
+
function wrap256(code) {
|
|
40
|
+
return (value) => `\x1b[38;5;${code}m${String(value)}\x1b[0m`;
|
|
41
|
+
}
|
|
42
|
+
function wrapRgb(r, g, b) {
|
|
43
|
+
return (value) => `\x1b[38;2;${r};${g};${b}m${String(value)}\x1b[0m`;
|
|
44
|
+
}
|
|
45
|
+
function wrapBg256(code) {
|
|
46
|
+
return (value) => `\x1b[48;5;${code}m${String(value)}\x1b[0m`;
|
|
47
|
+
}
|
|
48
|
+
function wrapBgRgb(r, g, b) {
|
|
49
|
+
return (value) => `\x1b[48;2;${r};${g};${b}m${String(value)}\x1b[0m`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const PALETTE = {
|
|
53
|
+
primary: { l3: [0, 184, 169], l2: 38, l1: 36 },
|
|
54
|
+
accent: { l3: [77, 208, 225], l2: 51, l1: 36 },
|
|
55
|
+
muted: { l3: [0, 150, 136], l2: 30, l1: 2 },
|
|
56
|
+
success: { l3: [80, 200, 120], l2: 40, l1: 32 },
|
|
57
|
+
warn: { l3: [255, 185, 0], l2: 214, l1: 33 },
|
|
58
|
+
error: { l3: [240, 80, 80], l2: 196, l1: 31 },
|
|
59
|
+
info: { l3: [60, 130, 220], l2: 33, l1: 34 },
|
|
60
|
+
magenta: { l3: [200, 100, 200], l2: 141, l1: 35 },
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const PALETTE_BG = {
|
|
64
|
+
primary: { l3: [0, 184, 169], l2: 38 },
|
|
65
|
+
success: { l3: [40, 140, 80], l2: 28 },
|
|
66
|
+
warn: { l3: [160, 100, 0], l2: 136 },
|
|
67
|
+
error: { l3: [160, 40, 40], l2: 124 },
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const identity = (value) => String(value);
|
|
71
|
+
|
|
72
|
+
export function createTheme(level) {
|
|
73
|
+
if (level === 0) {
|
|
74
|
+
const noop = identity;
|
|
75
|
+
return {
|
|
76
|
+
level,
|
|
77
|
+
primary: noop, accent: noop, muted: noop,
|
|
78
|
+
success: noop, warn: noop, error: noop, info: noop, magenta: noop,
|
|
79
|
+
bold: noop, dim: noop, reset: noop, italic: noop,
|
|
80
|
+
bgPrimary: noop, bgSuccess: noop, bgWarn: noop, bgError: noop,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function makeColor(slot) {
|
|
85
|
+
const p = PALETTE[slot];
|
|
86
|
+
if (level === 3) return wrapRgb(...p.l3);
|
|
87
|
+
if (level === 2) return wrap256(p.l2);
|
|
88
|
+
// level 1: muted is dim, rest are basic codes
|
|
89
|
+
if (slot === "muted") return wrapBasic(2);
|
|
90
|
+
return wrapBasic(p.l1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function makeBg(slot) {
|
|
94
|
+
const p = PALETTE_BG[slot];
|
|
95
|
+
if (!p) return identity;
|
|
96
|
+
if (level === 3) return wrapBgRgb(...p.l3);
|
|
97
|
+
if (level === 2) return wrapBg256(p.l2);
|
|
98
|
+
return identity;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
level,
|
|
103
|
+
primary: makeColor("primary"),
|
|
104
|
+
accent: makeColor("accent"),
|
|
105
|
+
muted: makeColor("muted"),
|
|
106
|
+
success: makeColor("success"),
|
|
107
|
+
warn: makeColor("warn"),
|
|
108
|
+
error: makeColor("error"),
|
|
109
|
+
info: makeColor("info"),
|
|
110
|
+
magenta: makeColor("magenta"),
|
|
111
|
+
bold: wrapBasic(1),
|
|
112
|
+
dim: wrapBasic(2),
|
|
113
|
+
italic: wrapBasic(3),
|
|
114
|
+
reset: wrapBasic(0),
|
|
115
|
+
bgPrimary: makeBg("primary"),
|
|
116
|
+
bgSuccess: makeBg("success"),
|
|
117
|
+
bgWarn: makeBg("warn"),
|
|
118
|
+
bgError: makeBg("error"),
|
|
119
|
+
};
|
|
120
|
+
}
|