frameshot-mcp 0.2.0 → 0.6.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/README.md +93 -128
- package/action.yml +130 -0
- package/dist/chunk-47YJG5HR.js +690 -0
- package/dist/index.js +1091 -214
- package/dist/renderer.d.ts +241 -0
- package/dist/renderer.js +28 -0
- package/package.json +28 -5
- package/scripts/render-changed.mjs +90 -0
- package/scripts/setup-labels.sh +48 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { Page } from 'playwright';
|
|
2
|
+
|
|
3
|
+
type Engine = "chromium" | "firefox" | "webkit";
|
|
4
|
+
type Framework = "html" | "react" | "vue" | "svelte";
|
|
5
|
+
type Theme = "light" | "dark";
|
|
6
|
+
interface Viewport {
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
}
|
|
10
|
+
interface RenderOptions {
|
|
11
|
+
viewport: Viewport;
|
|
12
|
+
deviceScaleFactor?: number;
|
|
13
|
+
engines: Engine[];
|
|
14
|
+
fullPage: boolean;
|
|
15
|
+
darkMode: boolean;
|
|
16
|
+
css: string;
|
|
17
|
+
tailwindVersion: "3" | "4";
|
|
18
|
+
waitFor: number;
|
|
19
|
+
}
|
|
20
|
+
interface ScreenshotResult {
|
|
21
|
+
engine: Engine;
|
|
22
|
+
image: string;
|
|
23
|
+
width: number;
|
|
24
|
+
height: number;
|
|
25
|
+
consoleErrors: string[];
|
|
26
|
+
}
|
|
27
|
+
interface DiffResult {
|
|
28
|
+
before: string;
|
|
29
|
+
after: string;
|
|
30
|
+
diff: string;
|
|
31
|
+
diffPixels: number;
|
|
32
|
+
totalPixels: number;
|
|
33
|
+
diffPercentage: number;
|
|
34
|
+
}
|
|
35
|
+
interface ReferenceDiffResult {
|
|
36
|
+
rendered: string;
|
|
37
|
+
diff: string;
|
|
38
|
+
diffPixels: number;
|
|
39
|
+
totalPixels: number;
|
|
40
|
+
diffPercentage: number;
|
|
41
|
+
passed: boolean;
|
|
42
|
+
}
|
|
43
|
+
interface A11yViolation {
|
|
44
|
+
id: string;
|
|
45
|
+
impact: string;
|
|
46
|
+
description: string;
|
|
47
|
+
helpUrl: string;
|
|
48
|
+
nodes: {
|
|
49
|
+
html: string;
|
|
50
|
+
target: string[];
|
|
51
|
+
}[];
|
|
52
|
+
}
|
|
53
|
+
interface A11yResult {
|
|
54
|
+
violations: A11yViolation[];
|
|
55
|
+
passes: number;
|
|
56
|
+
incomplete: number;
|
|
57
|
+
}
|
|
58
|
+
interface PerfMetrics {
|
|
59
|
+
renderTimeMs: number;
|
|
60
|
+
domElements: number;
|
|
61
|
+
domDepth: number;
|
|
62
|
+
scriptCount: number;
|
|
63
|
+
styleSheetCount: number;
|
|
64
|
+
imageCount: number;
|
|
65
|
+
totalDomSize: number;
|
|
66
|
+
}
|
|
67
|
+
interface AnimationFrame {
|
|
68
|
+
timestamp: number;
|
|
69
|
+
image: string;
|
|
70
|
+
}
|
|
71
|
+
interface Interaction {
|
|
72
|
+
action: "click" | "hover" | "focus" | "type" | "wait";
|
|
73
|
+
selector?: string;
|
|
74
|
+
value?: string;
|
|
75
|
+
ms?: number;
|
|
76
|
+
}
|
|
77
|
+
interface GridCell {
|
|
78
|
+
label: string;
|
|
79
|
+
code: string;
|
|
80
|
+
}
|
|
81
|
+
interface Snapshot {
|
|
82
|
+
image: string;
|
|
83
|
+
timestamp: number;
|
|
84
|
+
}
|
|
85
|
+
interface CatalogEntry {
|
|
86
|
+
path: string;
|
|
87
|
+
framework: Framework;
|
|
88
|
+
image: string;
|
|
89
|
+
width: number;
|
|
90
|
+
height: number;
|
|
91
|
+
consoleErrors: string[];
|
|
92
|
+
}
|
|
93
|
+
declare const DEVICE_PRESETS: Record<string, Viewport>;
|
|
94
|
+
declare const EXT_TO_FRAMEWORK: Record<string, Framework>;
|
|
95
|
+
|
|
96
|
+
declare class BrowserPool {
|
|
97
|
+
private pool;
|
|
98
|
+
warmup(engines: Engine[]): Promise<void>;
|
|
99
|
+
getPage(engine: Engine): Promise<Page>;
|
|
100
|
+
setViewport(engine: Engine, viewport: Viewport): Promise<void>;
|
|
101
|
+
shutdown(): Promise<void>;
|
|
102
|
+
private getSlot;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
declare class HtmlBuilder {
|
|
106
|
+
build(code: string, framework: Framework, options: {
|
|
107
|
+
darkMode: boolean;
|
|
108
|
+
css: string;
|
|
109
|
+
tailwindVersion: "3" | "4";
|
|
110
|
+
}): string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
declare class ImageComparator {
|
|
114
|
+
diff(imageA: string, imageB: string, threshold?: number): {
|
|
115
|
+
diff: string;
|
|
116
|
+
diffPixels: number;
|
|
117
|
+
totalPixels: number;
|
|
118
|
+
diffPercentage: number;
|
|
119
|
+
};
|
|
120
|
+
composite(images: Buffer[], columns: number, labelHeight?: number): {
|
|
121
|
+
image: string;
|
|
122
|
+
width: number;
|
|
123
|
+
height: number;
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
declare class SnapshotStore {
|
|
128
|
+
private store;
|
|
129
|
+
save(key: string, image: string): void;
|
|
130
|
+
get(key: string): Snapshot | undefined;
|
|
131
|
+
list(): {
|
|
132
|
+
key: string;
|
|
133
|
+
timestamp: number;
|
|
134
|
+
}[];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
declare class AuditUseCase {
|
|
138
|
+
private readonly pool;
|
|
139
|
+
private readonly htmlBuilder;
|
|
140
|
+
constructor(pool: BrowserPool, htmlBuilder: HtmlBuilder);
|
|
141
|
+
auditA11y(code: string, framework: Framework, options?: Partial<RenderOptions>): Promise<A11yResult>;
|
|
142
|
+
perfAudit(code: string, framework: Framework, options?: Partial<RenderOptions>): Promise<PerfMetrics>;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
declare class RenderUseCase {
|
|
146
|
+
private readonly pool;
|
|
147
|
+
private readonly htmlBuilder;
|
|
148
|
+
private readonly imageComparator;
|
|
149
|
+
constructor(pool: BrowserPool, htmlBuilder: HtmlBuilder, imageComparator: ImageComparator);
|
|
150
|
+
render(code: string, framework: Framework, options?: Partial<RenderOptions>): Promise<ScreenshotResult[]>;
|
|
151
|
+
renderInteraction(code: string, framework: Framework, interactions: Interaction[], options?: Partial<RenderOptions>): Promise<{
|
|
152
|
+
image: string;
|
|
153
|
+
width: number;
|
|
154
|
+
height: number;
|
|
155
|
+
}>;
|
|
156
|
+
captureAnimation(code: string, framework: Framework, options?: Partial<RenderOptions> & {
|
|
157
|
+
frames?: number;
|
|
158
|
+
duration?: number;
|
|
159
|
+
}): Promise<AnimationFrame[]>;
|
|
160
|
+
renderGrid(cells: GridCell[], framework: Framework, options?: Partial<RenderOptions> & {
|
|
161
|
+
columns?: number;
|
|
162
|
+
}): Promise<{
|
|
163
|
+
image: string;
|
|
164
|
+
width: number;
|
|
165
|
+
height: number;
|
|
166
|
+
cells: number;
|
|
167
|
+
}>;
|
|
168
|
+
renderMatrix(code: string, framework: Framework, viewports: {
|
|
169
|
+
label: string;
|
|
170
|
+
width: number;
|
|
171
|
+
height: number;
|
|
172
|
+
}[], themes: Theme[], options?: Partial<Omit<RenderOptions, "viewport" | "darkMode">>): Promise<{
|
|
173
|
+
viewport: string;
|
|
174
|
+
theme: Theme;
|
|
175
|
+
image: string;
|
|
176
|
+
width: number;
|
|
177
|
+
height: number;
|
|
178
|
+
consoleErrors: string[];
|
|
179
|
+
}[]>;
|
|
180
|
+
private renderHtml;
|
|
181
|
+
private resolveOptions;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
declare class CatalogUseCase {
|
|
185
|
+
private readonly renderUseCase;
|
|
186
|
+
constructor(renderUseCase: RenderUseCase);
|
|
187
|
+
renderCatalog(directory: string, options?: Partial<RenderOptions> & {
|
|
188
|
+
recursive?: boolean;
|
|
189
|
+
}): Promise<CatalogEntry[]>;
|
|
190
|
+
private scanDirectory;
|
|
191
|
+
private detectFramework;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
declare class DiffUseCase {
|
|
195
|
+
private readonly renderUseCase;
|
|
196
|
+
private readonly imageComparator;
|
|
197
|
+
constructor(renderUseCase: RenderUseCase, imageComparator: ImageComparator);
|
|
198
|
+
diffComponent(before: string, after: string, framework: Framework, options?: Partial<RenderOptions>): Promise<DiffResult>;
|
|
199
|
+
diffFromReference(code: string, framework: Framework, referenceImage: string, options?: Partial<RenderOptions> & {
|
|
200
|
+
threshold?: number;
|
|
201
|
+
}): Promise<ReferenceDiffResult>;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
interface ScreenshotUrlOptions {
|
|
205
|
+
viewport?: {
|
|
206
|
+
width: number;
|
|
207
|
+
height: number;
|
|
208
|
+
};
|
|
209
|
+
engines?: Engine[];
|
|
210
|
+
fullPage?: boolean;
|
|
211
|
+
waitFor?: number;
|
|
212
|
+
waitForSelector?: string;
|
|
213
|
+
waitForNetworkIdle?: boolean;
|
|
214
|
+
}
|
|
215
|
+
declare class ScreenshotUseCase {
|
|
216
|
+
private readonly pool;
|
|
217
|
+
constructor(pool: BrowserPool);
|
|
218
|
+
screenshotUrl(url: string, options?: ScreenshotUrlOptions): Promise<ScreenshotResult[]>;
|
|
219
|
+
screenshotUrlWithRetry(url: string, options?: ScreenshotUrlOptions & {
|
|
220
|
+
retryCount?: number;
|
|
221
|
+
}): Promise<ScreenshotResult[]>;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
declare class SnapshotUseCase {
|
|
225
|
+
private readonly store;
|
|
226
|
+
private readonly renderUseCase;
|
|
227
|
+
private readonly diffUseCase;
|
|
228
|
+
constructor(store: SnapshotStore, renderUseCase: RenderUseCase, diffUseCase: DiffUseCase);
|
|
229
|
+
save(key: string, code: string, framework: Framework, options?: Partial<RenderOptions>): Promise<{
|
|
230
|
+
image: string;
|
|
231
|
+
width: number;
|
|
232
|
+
height: number;
|
|
233
|
+
}>;
|
|
234
|
+
check(key: string, code: string, framework: Framework, options?: Partial<RenderOptions>): Promise<ReferenceDiffResult | null>;
|
|
235
|
+
list(): {
|
|
236
|
+
key: string;
|
|
237
|
+
timestamp: number;
|
|
238
|
+
}[];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export { type A11yResult, type A11yViolation, type AnimationFrame, AuditUseCase, BrowserPool, type CatalogEntry, CatalogUseCase, DEVICE_PRESETS, type DiffResult, DiffUseCase, EXT_TO_FRAMEWORK, type Engine, type Framework, type GridCell, HtmlBuilder, ImageComparator, type Interaction, type PerfMetrics, type ReferenceDiffResult, type RenderOptions, RenderUseCase, type ScreenshotResult, ScreenshotUseCase, type Snapshot, SnapshotStore, SnapshotUseCase, type Theme, type Viewport };
|
package/dist/renderer.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AuditUseCase,
|
|
3
|
+
BrowserPool,
|
|
4
|
+
CatalogUseCase,
|
|
5
|
+
DEVICE_PRESETS,
|
|
6
|
+
DiffUseCase,
|
|
7
|
+
EXT_TO_FRAMEWORK,
|
|
8
|
+
HtmlBuilder,
|
|
9
|
+
ImageComparator,
|
|
10
|
+
RenderUseCase,
|
|
11
|
+
ScreenshotUseCase,
|
|
12
|
+
SnapshotStore,
|
|
13
|
+
SnapshotUseCase
|
|
14
|
+
} from "./chunk-47YJG5HR.js";
|
|
15
|
+
export {
|
|
16
|
+
AuditUseCase,
|
|
17
|
+
BrowserPool,
|
|
18
|
+
CatalogUseCase,
|
|
19
|
+
DEVICE_PRESETS,
|
|
20
|
+
DiffUseCase,
|
|
21
|
+
EXT_TO_FRAMEWORK,
|
|
22
|
+
HtmlBuilder,
|
|
23
|
+
ImageComparator,
|
|
24
|
+
RenderUseCase,
|
|
25
|
+
ScreenshotUseCase,
|
|
26
|
+
SnapshotStore,
|
|
27
|
+
SnapshotUseCase
|
|
28
|
+
};
|
package/package.json
CHANGED
|
@@ -1,22 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frameshot-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Instant cross-browser component preview for AI agents. One MCP call, one screenshot — no dev server, no Storybook, no ceremony.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts"
|
|
11
|
+
},
|
|
12
|
+
"./renderer": {
|
|
13
|
+
"import": "./dist/renderer.js",
|
|
14
|
+
"types": "./dist/renderer.d.ts"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
7
17
|
"bin": {
|
|
8
18
|
"frameshot-mcp": "dist/index.js"
|
|
9
19
|
},
|
|
10
20
|
"files": [
|
|
11
21
|
"dist",
|
|
22
|
+
"scripts",
|
|
23
|
+
"action.yml",
|
|
12
24
|
"README.md",
|
|
13
25
|
"LICENSE"
|
|
14
26
|
],
|
|
15
27
|
"scripts": {
|
|
16
|
-
"build": "tsup src/index.ts --format esm --dts",
|
|
17
|
-
"dev": "tsup src/index.ts --format esm --watch",
|
|
28
|
+
"build": "tsup src/index.ts src/renderer.ts --format esm --dts",
|
|
29
|
+
"dev": "tsup src/index.ts src/renderer.ts --format esm --watch",
|
|
18
30
|
"start": "node dist/index.js",
|
|
19
31
|
"typecheck": "tsc --noEmit",
|
|
32
|
+
"lint": "biome check src/",
|
|
33
|
+
"lint:fix": "biome check --write src/",
|
|
34
|
+
"format": "biome format --write src/",
|
|
35
|
+
"test": "vitest run",
|
|
20
36
|
"prepublishOnly": "npm run build"
|
|
21
37
|
},
|
|
22
38
|
"keywords": [
|
|
@@ -49,13 +65,20 @@
|
|
|
49
65
|
},
|
|
50
66
|
"dependencies": {
|
|
51
67
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
52
|
-
"
|
|
68
|
+
"axe-core": "^4.12.1",
|
|
69
|
+
"pixelmatch": "^7.2.0",
|
|
70
|
+
"playwright": "^1.52.0",
|
|
71
|
+
"pngjs": "^7.0.0"
|
|
53
72
|
},
|
|
54
73
|
"devDependencies": {
|
|
74
|
+
"@biomejs/biome": "^2.5.0",
|
|
55
75
|
"@types/node": "^22.0.0",
|
|
76
|
+
"@types/pngjs": "^6.0.5",
|
|
56
77
|
"tsup": "^8.0.0",
|
|
57
|
-
"typescript": "^5.7.0"
|
|
78
|
+
"typescript": "^5.7.0",
|
|
79
|
+
"vitest": "^4.1.9"
|
|
58
80
|
},
|
|
81
|
+
"mcpName": "io.github.kamegoro/frameshot-mcp",
|
|
59
82
|
"engines": {
|
|
60
83
|
"node": ">=20.0.0"
|
|
61
84
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { basename, extname, join } from "node:path";
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
INPUT_PATHS = "",
|
|
9
|
+
INPUT_FRAMEWORK = "react",
|
|
10
|
+
INPUT_WIDTH = "1280",
|
|
11
|
+
INPUT_HEIGHT = "800",
|
|
12
|
+
INPUT_DARK_MODE = "false",
|
|
13
|
+
INPUT_TAILWIND_VERSION = "3",
|
|
14
|
+
GITHUB_WORKSPACE = process.cwd(),
|
|
15
|
+
} = process.env;
|
|
16
|
+
|
|
17
|
+
const outputDir = join(GITHUB_WORKSPACE, ".frameshot-screenshots");
|
|
18
|
+
mkdirSync(outputDir, { recursive: true });
|
|
19
|
+
|
|
20
|
+
const patterns = INPUT_PATHS.split(/[,\n]/)
|
|
21
|
+
.map((p) => p.trim())
|
|
22
|
+
.filter(Boolean);
|
|
23
|
+
|
|
24
|
+
const EXT_TO_FRAMEWORK = {
|
|
25
|
+
".jsx": "react",
|
|
26
|
+
".tsx": "react",
|
|
27
|
+
".vue": "vue",
|
|
28
|
+
".svelte": "svelte",
|
|
29
|
+
".html": "html",
|
|
30
|
+
".htm": "html",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
async function main() {
|
|
34
|
+
const { render, warmup, shutdown } = await import("frameshot-mcp/renderer");
|
|
35
|
+
|
|
36
|
+
await warmup(["chromium"]);
|
|
37
|
+
|
|
38
|
+
const allFiles = [];
|
|
39
|
+
for (const pattern of patterns) {
|
|
40
|
+
try {
|
|
41
|
+
const result = execSync(
|
|
42
|
+
`find ${GITHUB_WORKSPACE} -path '${pattern}' -type f 2>/dev/null`,
|
|
43
|
+
{ encoding: "utf-8" },
|
|
44
|
+
);
|
|
45
|
+
allFiles.push(...result.split("\n").filter(Boolean));
|
|
46
|
+
} catch {
|
|
47
|
+
// Pattern didn't match
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (allFiles.length === 0) {
|
|
52
|
+
console.log("No component files found matching the specified patterns.");
|
|
53
|
+
await shutdown();
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
console.log(`Found ${allFiles.length} component file(s) to render...`);
|
|
58
|
+
|
|
59
|
+
for (const filePath of allFiles) {
|
|
60
|
+
const ext = extname(filePath).toLowerCase();
|
|
61
|
+
const framework = EXT_TO_FRAMEWORK[ext] || INPUT_FRAMEWORK;
|
|
62
|
+
const name = basename(filePath, extname(filePath));
|
|
63
|
+
const safeName = name.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
console.log(` Rendering: ${filePath}`);
|
|
67
|
+
const code = readFileSync(filePath, "utf-8");
|
|
68
|
+
const [result] = await render(code, framework, {
|
|
69
|
+
width: parseInt(INPUT_WIDTH),
|
|
70
|
+
height: parseInt(INPUT_HEIGHT),
|
|
71
|
+
darkMode: INPUT_DARK_MODE === "true",
|
|
72
|
+
tailwindVersion: INPUT_TAILWIND_VERSION,
|
|
73
|
+
engines: ["chromium"],
|
|
74
|
+
});
|
|
75
|
+
const outPath = join(outputDir, `${safeName}.png`);
|
|
76
|
+
writeFileSync(outPath, Buffer.from(result.image, "base64"));
|
|
77
|
+
console.log(` ✓ ${safeName}.png (${result.width}x${result.height})`);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error(` ✗ Failed: ${filePath} — ${error.message}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
await shutdown();
|
|
84
|
+
console.log(`\nDone. Screenshots saved to ${outputDir}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
main().catch((e) => {
|
|
88
|
+
console.error(e);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Setup GitHub labels for the frameshot repository.
|
|
3
|
+
# Usage: ./scripts/setup-labels.sh [owner/repo]
|
|
4
|
+
# Requires: gh CLI authenticated
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
REPO="${1:-kamegoro/frameshot}"
|
|
9
|
+
|
|
10
|
+
# Delete default labels that add noise
|
|
11
|
+
for label in "invalid" "wontfix" "help wanted" "question"; do
|
|
12
|
+
gh label delete "$label" --repo "$REPO" --yes 2>/dev/null || true
|
|
13
|
+
done
|
|
14
|
+
|
|
15
|
+
# --- Status labels (S-) ---
|
|
16
|
+
gh label create "S-needs triage" --color "D93F0B" --description "Awaiting maintainer review" --repo "$REPO" --force
|
|
17
|
+
gh label create "S-accepted" --color "0E8A16" --description "Confirmed and ready for work" --repo "$REPO" --force
|
|
18
|
+
gh label create "S-in progress" --color "1D76DB" --description "Currently being worked on" --repo "$REPO" --force
|
|
19
|
+
gh label create "S-blocked" --color "B60205" --description "Waiting on external dependency" --repo "$REPO" --force
|
|
20
|
+
|
|
21
|
+
# --- Priority labels (P-) ---
|
|
22
|
+
gh label create "P0-critical" --color "B60205" --description "Production broken, fix immediately" --repo "$REPO" --force
|
|
23
|
+
gh label create "P1-high" --color "D93F0B" --description "Important, fix this week" --repo "$REPO" --force
|
|
24
|
+
gh label create "P2-medium" --color "FBCA04" --description "Normal priority" --repo "$REPO" --force
|
|
25
|
+
gh label create "P3-low" --color "0075CA" --description "Nice to have, no urgency" --repo "$REPO" --force
|
|
26
|
+
|
|
27
|
+
# --- Type labels ---
|
|
28
|
+
gh label create "bug" --color "D73A4A" --description "Something isn't working" --repo "$REPO" --force
|
|
29
|
+
gh label create "enhancement" --color "A2EEEF" --description "New feature or improvement" --repo "$REPO" --force
|
|
30
|
+
gh label create "documentation" --color "0075CA" --description "Documentation improvements" --repo "$REPO" --force
|
|
31
|
+
gh label create "performance" --color "F9D0C4" --description "Performance improvement" --repo "$REPO" --force
|
|
32
|
+
gh label create "breaking change" --color "B60205" --description "Introduces a breaking change" --repo "$REPO" --force
|
|
33
|
+
|
|
34
|
+
# --- Area labels (A-) ---
|
|
35
|
+
gh label create "A-rendering" --color "C5DEF5" --description "Component rendering pipeline" --repo "$REPO" --force
|
|
36
|
+
gh label create "A-screenshot" --color "C5DEF5" --description "URL screenshot capture" --repo "$REPO" --force
|
|
37
|
+
gh label create "A-diff" --color "C5DEF5" --description "Visual regression diffing" --repo "$REPO" --force
|
|
38
|
+
gh label create "A-audit" --color "C5DEF5" --description "Accessibility & performance audits" --repo "$REPO" --force
|
|
39
|
+
gh label create "A-mcp-protocol" --color "C5DEF5" --description "MCP transport & tool definitions" --repo "$REPO" --force
|
|
40
|
+
gh label create "A-github-action" --color "C5DEF5" --description "GitHub Action integration" --repo "$REPO" --force
|
|
41
|
+
gh label create "A-config" --color "C5DEF5" --description "Configuration & setup" --repo "$REPO" --force
|
|
42
|
+
|
|
43
|
+
# --- Community labels ---
|
|
44
|
+
gh label create "good first issue" --color "7057FF" --description "Good for newcomers" --repo "$REPO" --force
|
|
45
|
+
gh label create "help wanted" --color "008672" --description "Extra attention is needed" --repo "$REPO" --force
|
|
46
|
+
gh label create "duplicate" --color "CFD3D7" --description "This issue already exists" --repo "$REPO" --force
|
|
47
|
+
|
|
48
|
+
echo "Labels setup complete for $REPO"
|