@promptai.credit/cli 0.4.0 → 0.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/README.md +29 -8
- package/claude-plugin/.claude-plugin/plugin.json +11 -0
- package/claude-plugin/README.md +33 -0
- package/claude-plugin/hooks/ads.tsx +557 -0
- package/claude-plugin/hooks/hooks.json +4 -0
- package/claude-plugin/hooks/logo.ts +167 -0
- package/dist/index.js +246 -85
- package/package.json +4 -3
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brand logo → terminal cells for the AbovePrompt card.
|
|
3
|
+
*
|
|
4
|
+
* Works in any terminal (Apple Terminal included): the logo becomes a
|
|
5
|
+
* `Raster` of half-block glyphs, two pixels per cell. Everything here fails
|
|
6
|
+
* open — a missing converter, a slow CDN or an odd format just means the card
|
|
7
|
+
* draws without a logo.
|
|
8
|
+
*
|
|
9
|
+
* Pure image code only: the host pipeline (curl → svg→png → bmp) lives in
|
|
10
|
+
* ads.tsx, because every `$.noun.method()` call has to sit in the hooks module.
|
|
11
|
+
*/
|
|
12
|
+
export interface LogoCells {
|
|
13
|
+
columns: number;
|
|
14
|
+
rows: number;
|
|
15
|
+
/** Raster `cells`: base64 of little-endian u32 [codePoint, fg, bg] triplets. */
|
|
16
|
+
cells: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Working size handed to the converters; downscaled to the cell grid here. */
|
|
20
|
+
export const WORK_PX = 64;
|
|
21
|
+
const DEFAULT_COLOR = 0x01000000;
|
|
22
|
+
const B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
23
|
+
|
|
24
|
+
export function encodeBase64(bytes: Uint8Array): string {
|
|
25
|
+
let out = "";
|
|
26
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
27
|
+
const a = bytes[i];
|
|
28
|
+
const b = i + 1 < bytes.length ? bytes[i + 1] : 0;
|
|
29
|
+
const c = i + 2 < bytes.length ? bytes[i + 2] : 0;
|
|
30
|
+
out += B64[a >> 2] + B64[((a & 3) << 4) | (b >> 4)];
|
|
31
|
+
out += i + 1 < bytes.length ? B64[((b & 15) << 2) | (c >> 6)] : "=";
|
|
32
|
+
out += i + 2 < bytes.length ? B64[c & 63] : "=";
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function decodeBase64(text: string): Uint8Array {
|
|
38
|
+
const bin = atob(text);
|
|
39
|
+
const out = new Uint8Array(bin.length);
|
|
40
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface Bitmap {
|
|
45
|
+
width: number;
|
|
46
|
+
height: number;
|
|
47
|
+
/** RGBA, row-major, top-down. */
|
|
48
|
+
rgba: Uint8Array;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Uncompressed 24/32-bit BMP (what sips and ImageMagick's BMP3 write). */
|
|
52
|
+
export function parseBmp(bytes: Uint8Array): Bitmap | null {
|
|
53
|
+
if (bytes.length < 54 || bytes[0] !== 0x42 || bytes[1] !== 0x4d) return null;
|
|
54
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
55
|
+
const dataOffset = view.getUint32(10, true);
|
|
56
|
+
const width = view.getInt32(18, true);
|
|
57
|
+
const rawHeight = view.getInt32(22, true);
|
|
58
|
+
const bpp = view.getUint16(28, true);
|
|
59
|
+
const compression = view.getUint32(30, true);
|
|
60
|
+
const height = Math.abs(rawHeight);
|
|
61
|
+
const topDown = rawHeight < 0;
|
|
62
|
+
if (width <= 0 || height <= 0 || width > 4096 || height > 4096) return null;
|
|
63
|
+
if ((bpp !== 24 && bpp !== 32) || (compression !== 0 && compression !== 3)) return null;
|
|
64
|
+
|
|
65
|
+
const bytesPerPx = bpp / 8;
|
|
66
|
+
const stride = ((width * bpp + 31) >> 5) << 2;
|
|
67
|
+
if (dataOffset + stride * height > bytes.length) return null;
|
|
68
|
+
|
|
69
|
+
const rgba = new Uint8Array(width * height * 4);
|
|
70
|
+
let anyAlpha = false;
|
|
71
|
+
for (let y = 0; y < height; y++) {
|
|
72
|
+
const srcRow = dataOffset + (topDown ? y : height - 1 - y) * stride;
|
|
73
|
+
for (let x = 0; x < width; x++) {
|
|
74
|
+
const s = srcRow + x * bytesPerPx;
|
|
75
|
+
const d = (y * width + x) * 4;
|
|
76
|
+
rgba[d] = bytes[s + 2];
|
|
77
|
+
rgba[d + 1] = bytes[s + 1];
|
|
78
|
+
rgba[d + 2] = bytes[s];
|
|
79
|
+
const a = bpp === 32 ? bytes[s + 3] : 255;
|
|
80
|
+
rgba[d + 3] = a;
|
|
81
|
+
if (a !== 0) anyAlpha = true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// A 32-bit BMP whose alpha byte is all zero is XRGB padding, not "invisible".
|
|
85
|
+
if (!anyAlpha) for (let i = 3; i < rgba.length; i += 4) rgba[i] = 255;
|
|
86
|
+
return { width, height, rgba };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Fit `bmp` into columns × (rows*2) pixels (aspect kept, centred, area
|
|
91
|
+
* averaged) and pack half-block cells. Transparent pixels stay the terminal's
|
|
92
|
+
* own background, so the logo sits on the card, not on a colored square.
|
|
93
|
+
*/
|
|
94
|
+
export function bitmapToCells(bmp: Bitmap, columns: number, rows: number): LogoCells {
|
|
95
|
+
const W = columns;
|
|
96
|
+
const H = rows * 2;
|
|
97
|
+
const scale = Math.min(W / bmp.width, H / bmp.height);
|
|
98
|
+
const fitW = Math.max(1, Math.round(bmp.width * scale));
|
|
99
|
+
const fitH = Math.max(1, Math.round(bmp.height * scale));
|
|
100
|
+
const offX = Math.floor((W - fitW) / 2);
|
|
101
|
+
const offY = Math.floor((H - fitH) / 2);
|
|
102
|
+
|
|
103
|
+
// One RGB (or null for transparent) per target pixel.
|
|
104
|
+
const px: Array<number | null> = new Array(W * H).fill(null);
|
|
105
|
+
for (let ty = 0; ty < fitH; ty++) {
|
|
106
|
+
const y0 = Math.floor((ty * bmp.height) / fitH);
|
|
107
|
+
const y1 = Math.max(y0 + 1, Math.floor(((ty + 1) * bmp.height) / fitH));
|
|
108
|
+
for (let tx = 0; tx < fitW; tx++) {
|
|
109
|
+
const x0 = Math.floor((tx * bmp.width) / fitW);
|
|
110
|
+
const x1 = Math.max(x0 + 1, Math.floor(((tx + 1) * bmp.width) / fitW));
|
|
111
|
+
let r = 0;
|
|
112
|
+
let g = 0;
|
|
113
|
+
let b = 0;
|
|
114
|
+
let a = 0;
|
|
115
|
+
let n = 0;
|
|
116
|
+
for (let y = y0; y < y1; y++) {
|
|
117
|
+
for (let x = x0; x < x1; x++) {
|
|
118
|
+
const i = (y * bmp.width + x) * 4;
|
|
119
|
+
const alpha = bmp.rgba[i + 3];
|
|
120
|
+
r += bmp.rgba[i] * alpha;
|
|
121
|
+
g += bmp.rgba[i + 1] * alpha;
|
|
122
|
+
b += bmp.rgba[i + 2] * alpha;
|
|
123
|
+
a += alpha;
|
|
124
|
+
n++;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (a / n < 90) continue; // mostly transparent
|
|
128
|
+
const R = Math.round(r / a);
|
|
129
|
+
const G = Math.round(g / a);
|
|
130
|
+
const B = Math.round(b / a);
|
|
131
|
+
px[(offY + ty) * W + (offX + tx)] = (R << 16) | (G << 8) | B;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const words = new Uint32Array(columns * rows * 3);
|
|
136
|
+
for (let row = 0; row < rows; row++) {
|
|
137
|
+
for (let col = 0; col < columns; col++) {
|
|
138
|
+
const top = px[row * 2 * W + col];
|
|
139
|
+
const bottom = px[(row * 2 + 1) * W + col];
|
|
140
|
+
let glyph = 0x20;
|
|
141
|
+
let fg = DEFAULT_COLOR;
|
|
142
|
+
let bg = DEFAULT_COLOR;
|
|
143
|
+
if (top !== null && bottom !== null) {
|
|
144
|
+
glyph = 0x2580; // ▀
|
|
145
|
+
fg = top;
|
|
146
|
+
bg = bottom;
|
|
147
|
+
} else if (top !== null) {
|
|
148
|
+
glyph = 0x2580;
|
|
149
|
+
fg = top;
|
|
150
|
+
} else if (bottom !== null) {
|
|
151
|
+
glyph = 0x2584; // ▄
|
|
152
|
+
fg = bottom;
|
|
153
|
+
}
|
|
154
|
+
const at = (row * columns + col) * 3;
|
|
155
|
+
words[at] = glyph;
|
|
156
|
+
words[at + 1] = fg;
|
|
157
|
+
words[at + 2] = bg;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return { columns, rows, cells: encodeBase64(new Uint8Array(words.buffer)) };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function hashKey(text: string): string {
|
|
164
|
+
let h = 5381;
|
|
165
|
+
for (let i = 0; i < text.length; i++) h = ((h * 33) ^ text.charCodeAt(i)) >>> 0;
|
|
166
|
+
return h.toString(36);
|
|
167
|
+
}
|