@arach/og 0.1.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/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ import { readFile } from 'node:fs/promises';
3
+ import { resolve } from 'node:path';
4
+ import { generateOG, generateOGBatch } from './generate.js';
5
+ async function main() {
6
+ const args = process.argv.slice(2);
7
+ if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
8
+ console.log(`
9
+ @arach/og - Declarative OG image generation
10
+
11
+ Usage:
12
+ og <config.json> Generate OG images from a config file
13
+ og --help Show this help message
14
+
15
+ Config file format:
16
+ Single image:
17
+ {
18
+ "template": "branded",
19
+ "title": "My App",
20
+ "subtitle": "Do amazing things",
21
+ "accent": "#f07c4f",
22
+ "output": "public/og-image.png"
23
+ }
24
+
25
+ Multiple images:
26
+ [
27
+ { "title": "Home", "output": "public/og-home.png" },
28
+ { "title": "Docs", "template": "docs", "output": "public/og-docs.png" }
29
+ ]
30
+
31
+ Templates: branded, docs, minimal, editor-dark
32
+ `);
33
+ process.exit(0);
34
+ }
35
+ const configPath = resolve(process.cwd(), args[0]);
36
+ try {
37
+ const content = await readFile(configPath, 'utf-8');
38
+ const config = JSON.parse(content);
39
+ if (Array.isArray(config)) {
40
+ await generateOGBatch(config);
41
+ }
42
+ else {
43
+ await generateOG(config);
44
+ }
45
+ console.log('\n✓ Done!');
46
+ }
47
+ catch (error) {
48
+ console.error('Error:', error instanceof Error ? error.message : error);
49
+ process.exit(1);
50
+ }
51
+ }
52
+ main();
53
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAG3D,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IAElC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACxE,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;CAwBf,CAAC,CAAA;QACE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;IAElD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAA;QACnD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAA0B,CAAA;QAE3D,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1B,MAAM,eAAe,CAAC,MAAM,CAAC,CAAA;QAC/B,CAAC;aAAM,CAAC;YACN,MAAM,UAAU,CAAC,MAAM,CAAC,CAAA;QAC1B,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;IAC1B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;QACvE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAA"}
@@ -0,0 +1,10 @@
1
+ import type { OGConfig } from './types.js';
2
+ /**
3
+ * Generate an OG image from config
4
+ */
5
+ export declare function generateOG(config: OGConfig): Promise<void>;
6
+ /**
7
+ * Generate multiple OG images from configs
8
+ */
9
+ export declare function generateOGBatch(configs: OGConfig[]): Promise<void>;
10
+ //# sourceMappingURL=generate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAmB,MAAM,YAAY,CAAA;AAS3D;;GAEG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAqEhE;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAmExE"}
@@ -0,0 +1,108 @@
1
+ import puppeteer from 'puppeteer';
2
+ import { mkdir } from 'node:fs/promises';
3
+ import { dirname } from 'node:path';
4
+ import { templates } from './templates/index.js';
5
+ const DEFAULT_FONTS = ['Fraunces:wght@500;600', 'Space Grotesk:wght@400;500;600'];
6
+ const DEFAULT_ACCENT = '#f07c4f';
7
+ const DEFAULT_ACCENT_SECONDARY = '#1f7a65';
8
+ const DEFAULT_BACKGROUND = '#f7f3ec';
9
+ const DEFAULT_TEXT_COLOR = '#101518';
10
+ /**
11
+ * Generate an OG image from config
12
+ */
13
+ export async function generateOG(config) {
14
+ const { template = 'branded', title, subtitle, accent = DEFAULT_ACCENT, accentSecondary = DEFAULT_ACCENT_SECONDARY, background = DEFAULT_BACKGROUND, textColor = DEFAULT_TEXT_COLOR, output, width = 1200, height = 630, scale = 2, fonts = DEFAULT_FONTS, logo, tag, } = config;
15
+ const templateFn = templates[template];
16
+ if (!templateFn) {
17
+ throw new Error(`Unknown template: ${template}. Available: ${Object.keys(templates).join(', ')}`);
18
+ }
19
+ const context = {
20
+ title,
21
+ subtitle,
22
+ accent,
23
+ accentSecondary,
24
+ background,
25
+ textColor,
26
+ width,
27
+ height,
28
+ fonts,
29
+ logo,
30
+ tag,
31
+ };
32
+ const html = templateFn(context);
33
+ // Ensure output directory exists
34
+ await mkdir(dirname(output), { recursive: true });
35
+ // Launch browser and generate
36
+ const browser = await puppeteer.launch({
37
+ headless: true,
38
+ args: ['--no-sandbox', '--disable-setuid-sandbox'],
39
+ });
40
+ try {
41
+ const page = await browser.newPage();
42
+ await page.setViewport({ width, height, deviceScaleFactor: scale });
43
+ await page.setContent(html, { waitUntil: 'networkidle0' });
44
+ // Wait for fonts to load
45
+ await page.evaluate('document.fonts.ready');
46
+ // Extra buffer for font rendering
47
+ await new Promise((resolve) => setTimeout(resolve, 500));
48
+ await page.screenshot({
49
+ path: output,
50
+ type: output.endsWith('.png') ? 'png' : 'jpeg',
51
+ ...(output.endsWith('.jpeg') || output.endsWith('.jpg') ? { quality: 90 } : {}),
52
+ });
53
+ console.log(`✓ Generated ${output}`);
54
+ }
55
+ finally {
56
+ await browser.close();
57
+ }
58
+ }
59
+ /**
60
+ * Generate multiple OG images from configs
61
+ */
62
+ export async function generateOGBatch(configs) {
63
+ const browser = await puppeteer.launch({
64
+ headless: true,
65
+ args: ['--no-sandbox', '--disable-setuid-sandbox'],
66
+ });
67
+ try {
68
+ for (const config of configs) {
69
+ const { template = 'branded', title, subtitle, accent = DEFAULT_ACCENT, accentSecondary = DEFAULT_ACCENT_SECONDARY, background = DEFAULT_BACKGROUND, textColor = DEFAULT_TEXT_COLOR, output, width = 1200, height = 630, scale = 2, fonts = DEFAULT_FONTS, logo, tag, } = config;
70
+ const templateFn = templates[template];
71
+ if (!templateFn) {
72
+ console.error(`✗ Unknown template: ${template}`);
73
+ continue;
74
+ }
75
+ const context = {
76
+ title,
77
+ subtitle,
78
+ accent,
79
+ accentSecondary,
80
+ background,
81
+ textColor,
82
+ width,
83
+ height,
84
+ fonts,
85
+ logo,
86
+ tag,
87
+ };
88
+ const html = templateFn(context);
89
+ await mkdir(dirname(output), { recursive: true });
90
+ const page = await browser.newPage();
91
+ await page.setViewport({ width, height, deviceScaleFactor: scale });
92
+ await page.setContent(html, { waitUntil: 'networkidle0' });
93
+ await page.evaluate('document.fonts.ready');
94
+ await new Promise((resolve) => setTimeout(resolve, 500));
95
+ await page.screenshot({
96
+ path: output,
97
+ type: output.endsWith('.png') ? 'png' : 'jpeg',
98
+ ...(output.endsWith('.jpeg') || output.endsWith('.jpg') ? { quality: 90 } : {}),
99
+ });
100
+ await page.close();
101
+ console.log(`✓ Generated ${output}`);
102
+ }
103
+ }
104
+ finally {
105
+ await browser.close();
106
+ }
107
+ }
108
+ //# sourceMappingURL=generate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.js","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { generateOG, generateOGBatch } from './generate.js';
2
+ export { templates } from './templates/index.js';
3
+ export type { OGConfig, TemplateId, TemplateContext, TemplateFunction } from './types.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAChD,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { generateOG, generateOGBatch } from './generate.js';
2
+ export { templates } from './templates/index.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA"}
@@ -0,0 +1,6 @@
1
+ import type { TemplateFunction } from '../types.js';
2
+ /**
3
+ * Branded template - full featured with grid overlay, gradient glows, and tag
4
+ */
5
+ export declare const branded: TemplateFunction;
6
+ //# sourceMappingURL=branded.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"branded.d.ts","sourceRoot":"","sources":["../../src/templates/branded.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAEnD;;GAEG;AACH,eAAO,MAAM,OAAO,EAAE,gBAqHrB,CAAA"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Branded template - full featured with grid overlay, gradient glows, and tag
3
+ */
4
+ export const branded = (ctx) => `
5
+ <!DOCTYPE html>
6
+ <html>
7
+ <head>
8
+ <meta charset="UTF-8">
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=${ctx.fonts.map(f => f.replace(/ /g, '+')).join('&family=')}&display=swap" rel="stylesheet">
12
+ <style>
13
+ * {
14
+ margin: 0;
15
+ padding: 0;
16
+ box-sizing: border-box;
17
+ }
18
+ body {
19
+ width: ${ctx.width}px;
20
+ height: ${ctx.height}px;
21
+ background: ${ctx.background};
22
+ font-family: '${ctx.fonts[1] || ctx.fonts[0]}', system-ui, sans-serif;
23
+ color: ${ctx.textColor};
24
+ position: relative;
25
+ overflow: hidden;
26
+ }
27
+ .glow {
28
+ position: absolute;
29
+ inset: -10% -10% auto -10%;
30
+ height: 520px;
31
+ background:
32
+ radial-gradient(circle at 15% 35%, ${ctx.accent}40, transparent 58%),
33
+ radial-gradient(circle at 70% 0%, ${ctx.accentSecondary}38, transparent 65%),
34
+ radial-gradient(circle at 85% 40%, ${ctx.accent}30, transparent 55%);
35
+ pointer-events: none;
36
+ }
37
+ .grid {
38
+ position: absolute;
39
+ inset: 0;
40
+ background-image:
41
+ linear-gradient(${ctx.textColor}08 1px, transparent 1px),
42
+ linear-gradient(90deg, ${ctx.textColor}08 1px, transparent 1px);
43
+ background-size: 40px 40px;
44
+ opacity: 0.5;
45
+ }
46
+ .content {
47
+ position: relative;
48
+ z-index: 1;
49
+ height: 100%;
50
+ padding: 72px 80px;
51
+ display: flex;
52
+ flex-direction: column;
53
+ justify-content: center;
54
+ }
55
+ .tag {
56
+ display: inline-flex;
57
+ align-items: center;
58
+ gap: 8px;
59
+ padding: 10px 18px;
60
+ border-radius: 999px;
61
+ border: 1px solid ${ctx.textColor}20;
62
+ background: ${ctx.background}cc;
63
+ font-size: 14px;
64
+ font-weight: 600;
65
+ text-transform: uppercase;
66
+ letter-spacing: 0.18em;
67
+ color: ${ctx.accentSecondary};
68
+ margin-bottom: 28px;
69
+ width: fit-content;
70
+ }
71
+ .title {
72
+ font-family: '${ctx.fonts[0]}', serif;
73
+ font-size: 72px;
74
+ font-weight: 600;
75
+ line-height: 1.1;
76
+ max-width: 900px;
77
+ margin-bottom: 20px;
78
+ }
79
+ .subtitle {
80
+ font-size: 28px;
81
+ color: ${ctx.textColor}99;
82
+ max-width: 700px;
83
+ line-height: 1.4;
84
+ }
85
+ .brand {
86
+ position: absolute;
87
+ bottom: 72px;
88
+ left: 80px;
89
+ display: flex;
90
+ align-items: center;
91
+ gap: 12px;
92
+ }
93
+ .brand-dot {
94
+ width: 16px;
95
+ height: 16px;
96
+ border-radius: 50%;
97
+ background: ${ctx.accent};
98
+ box-shadow: 0 0 0 6px ${ctx.accent}33;
99
+ }
100
+ .brand-name {
101
+ font-family: '${ctx.fonts[0]}', serif;
102
+ font-size: 24px;
103
+ font-weight: 600;
104
+ }
105
+ </style>
106
+ </head>
107
+ <body>
108
+ <div class="glow"></div>
109
+ <div class="grid"></div>
110
+ <div class="content">
111
+ ${ctx.tag ? `<div class="tag">${ctx.tag}</div>` : ''}
112
+ <h1 class="title">${ctx.title}</h1>
113
+ ${ctx.subtitle ? `<p class="subtitle">${ctx.subtitle}</p>` : ''}
114
+ </div>
115
+ <div class="brand">
116
+ <span class="brand-dot"></span>
117
+ <span class="brand-name">${ctx.title.split('|')[0]?.trim() || ''}</span>
118
+ </div>
119
+ </body>
120
+ </html>
121
+ `;
122
+ //# sourceMappingURL=branded.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"branded.js","sourceRoot":"","sources":["../../src/templates/branded.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,CAAC,MAAM,OAAO,GAAqB,CAAC,GAAG,EAAE,EAAE,CAAC;;;;;;;yDAOO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC;;;;;;;;eAQnG,GAAG,CAAC,KAAK;gBACR,GAAG,CAAC,MAAM;oBACN,GAAG,CAAC,UAAU;sBACZ,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;eACnC,GAAG,CAAC,SAAS;;;;;;;;;6CASiB,GAAG,CAAC,MAAM;4CACX,GAAG,CAAC,eAAe;6CAClB,GAAG,CAAC,MAAM;;;;;;;0BAO7B,GAAG,CAAC,SAAS;iCACN,GAAG,CAAC,SAAS;;;;;;;;;;;;;;;;;;;0BAmBpB,GAAG,CAAC,SAAS;oBACnB,GAAG,CAAC,UAAU;;;;;eAKnB,GAAG,CAAC,eAAe;;;;;sBAKZ,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;;;;;;;;;eASnB,GAAG,CAAC,SAAS;;;;;;;;;;;;;;;;oBAgBR,GAAG,CAAC,MAAM;8BACA,GAAG,CAAC,MAAM;;;sBAGlB,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;;;;;;;;;;MAU5B,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,oBAAoB,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE;wBAChC,GAAG,CAAC,KAAK;MAC3B,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,uBAAuB,GAAG,CAAC,QAAQ,MAAM,CAAC,CAAC,CAAC,EAAE;;;;+BAIpC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE;;;;CAInE,CAAA"}
@@ -0,0 +1,6 @@
1
+ import type { TemplateFunction } from '../types.js';
2
+ /**
3
+ * Docs template - clean layout for documentation pages
4
+ */
5
+ export declare const docs: TemplateFunction;
6
+ //# sourceMappingURL=docs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docs.d.ts","sourceRoot":"","sources":["../../src/templates/docs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAEnD;;GAEG;AACH,eAAO,MAAM,IAAI,EAAE,gBA6HlB,CAAA"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Docs template - clean layout for documentation pages
3
+ */
4
+ export const docs = (ctx) => `
5
+ <!DOCTYPE html>
6
+ <html>
7
+ <head>
8
+ <meta charset="UTF-8">
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=${ctx.fonts.map(f => f.replace(/ /g, '+')).join('&family=')}&display=swap" rel="stylesheet">
12
+ <style>
13
+ * {
14
+ margin: 0;
15
+ padding: 0;
16
+ box-sizing: border-box;
17
+ }
18
+ body {
19
+ width: ${ctx.width}px;
20
+ height: ${ctx.height}px;
21
+ background: ${ctx.background};
22
+ font-family: '${ctx.fonts[1] || ctx.fonts[0]}', system-ui, sans-serif;
23
+ color: ${ctx.textColor};
24
+ position: relative;
25
+ overflow: hidden;
26
+ }
27
+ .accent-bar {
28
+ position: absolute;
29
+ top: 0;
30
+ left: 0;
31
+ right: 0;
32
+ height: 6px;
33
+ background: linear-gradient(90deg, ${ctx.accent}, ${ctx.accentSecondary});
34
+ }
35
+ .grid {
36
+ position: absolute;
37
+ inset: 0;
38
+ background-image:
39
+ linear-gradient(${ctx.textColor}06 1px, transparent 1px),
40
+ linear-gradient(90deg, ${ctx.textColor}06 1px, transparent 1px);
41
+ background-size: 32px 32px;
42
+ }
43
+ .content {
44
+ position: relative;
45
+ z-index: 1;
46
+ height: 100%;
47
+ padding: 80px;
48
+ display: flex;
49
+ flex-direction: column;
50
+ justify-content: center;
51
+ }
52
+ .badge {
53
+ display: inline-flex;
54
+ align-items: center;
55
+ gap: 8px;
56
+ padding: 8px 16px;
57
+ border-radius: 8px;
58
+ background: ${ctx.accent}15;
59
+ border: 1px solid ${ctx.accent}30;
60
+ font-size: 14px;
61
+ font-weight: 600;
62
+ color: ${ctx.accent};
63
+ margin-bottom: 24px;
64
+ width: fit-content;
65
+ }
66
+ .title {
67
+ font-family: '${ctx.fonts[0]}', serif;
68
+ font-size: 64px;
69
+ font-weight: 600;
70
+ line-height: 1.15;
71
+ max-width: 850px;
72
+ margin-bottom: 20px;
73
+ }
74
+ .subtitle {
75
+ font-size: 26px;
76
+ color: ${ctx.textColor}80;
77
+ max-width: 650px;
78
+ line-height: 1.5;
79
+ }
80
+ .footer {
81
+ position: absolute;
82
+ bottom: 60px;
83
+ left: 80px;
84
+ right: 80px;
85
+ display: flex;
86
+ align-items: center;
87
+ justify-content: space-between;
88
+ }
89
+ .brand {
90
+ display: flex;
91
+ align-items: center;
92
+ gap: 10px;
93
+ }
94
+ .brand-dot {
95
+ width: 12px;
96
+ height: 12px;
97
+ border-radius: 50%;
98
+ background: ${ctx.accent};
99
+ }
100
+ .brand-name {
101
+ font-family: '${ctx.fonts[0]}', serif;
102
+ font-size: 20px;
103
+ font-weight: 600;
104
+ }
105
+ .docs-label {
106
+ font-size: 16px;
107
+ color: ${ctx.textColor}60;
108
+ font-weight: 500;
109
+ }
110
+ </style>
111
+ </head>
112
+ <body>
113
+ <div class="accent-bar"></div>
114
+ <div class="grid"></div>
115
+ <div class="content">
116
+ ${ctx.tag ? `<div class="badge">${ctx.tag}</div>` : '<div class="badge">Documentation</div>'}
117
+ <h1 class="title">${ctx.title}</h1>
118
+ ${ctx.subtitle ? `<p class="subtitle">${ctx.subtitle}</p>` : ''}
119
+ </div>
120
+ <div class="footer">
121
+ <div class="brand">
122
+ <span class="brand-dot"></span>
123
+ <span class="brand-name">${ctx.title.split('|')[0]?.trim() || 'Docs'}</span>
124
+ </div>
125
+ <span class="docs-label">docs</span>
126
+ </div>
127
+ </body>
128
+ </html>
129
+ `;
130
+ //# sourceMappingURL=docs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docs.js","sourceRoot":"","sources":["../../src/templates/docs.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,CAAC,MAAM,IAAI,GAAqB,CAAC,GAAG,EAAE,EAAE,CAAC;;;;;;;yDAOU,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC;;;;;;;;eAQnG,GAAG,CAAC,KAAK;gBACR,GAAG,CAAC,MAAM;oBACN,GAAG,CAAC,UAAU;sBACZ,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;eACnC,GAAG,CAAC,SAAS;;;;;;;;;;2CAUe,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC,eAAe;;;;;;0BAMnD,GAAG,CAAC,SAAS;iCACN,GAAG,CAAC,SAAS;;;;;;;;;;;;;;;;;;oBAkB1B,GAAG,CAAC,MAAM;0BACJ,GAAG,CAAC,MAAM;;;eAGrB,GAAG,CAAC,MAAM;;;;;sBAKH,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;;;;;;;;;eASnB,GAAG,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;;oBAsBR,GAAG,CAAC,MAAM;;;sBAGR,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;;;;;;eAMnB,GAAG,CAAC,SAAS;;;;;;;;;MAStB,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,sBAAsB,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,wCAAwC;wBACxE,GAAG,CAAC,KAAK;MAC3B,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,uBAAuB,GAAG,CAAC,QAAQ,MAAM,CAAC,CAAC,CAAC,EAAE;;;;;iCAKlC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,MAAM;;;;;;CAMzE,CAAA"}
@@ -0,0 +1,6 @@
1
+ import type { TemplateFunction } from '../types.js';
2
+ /**
3
+ * Editor Dark template - dark theme for product/editor pages
4
+ */
5
+ export declare const editorDark: TemplateFunction;
6
+ //# sourceMappingURL=editor-dark.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editor-dark.d.ts","sourceRoot":"","sources":["../../src/templates/editor-dark.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAEnD;;GAEG;AACH,eAAO,MAAM,UAAU,EAAE,gBAkIxB,CAAA"}
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Editor Dark template - dark theme for product/editor pages
3
+ */
4
+ export const editorDark = (ctx) => `
5
+ <!DOCTYPE html>
6
+ <html>
7
+ <head>
8
+ <meta charset="UTF-8">
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=${ctx.fonts.map(f => f.replace(/ /g, '+')).join('&family=')}&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
12
+ <style>
13
+ * {
14
+ margin: 0;
15
+ padding: 0;
16
+ box-sizing: border-box;
17
+ }
18
+ body {
19
+ width: ${ctx.width}px;
20
+ height: ${ctx.height}px;
21
+ background: #101518;
22
+ font-family: '${ctx.fonts[1] || ctx.fonts[0]}', system-ui, sans-serif;
23
+ color: #f0f4f7;
24
+ position: relative;
25
+ overflow: hidden;
26
+ }
27
+ .glow {
28
+ position: absolute;
29
+ inset: 0;
30
+ background:
31
+ radial-gradient(circle at 20% 80%, ${ctx.accent}25, transparent 50%),
32
+ radial-gradient(circle at 80% 20%, ${ctx.accentSecondary}20, transparent 50%);
33
+ pointer-events: none;
34
+ }
35
+ .grid {
36
+ position: absolute;
37
+ inset: 0;
38
+ background-image:
39
+ linear-gradient(rgba(255,255,255,0.03) 1px, transparent 1px),
40
+ linear-gradient(90deg, rgba(255,255,255,0.03) 1px, transparent 1px);
41
+ background-size: 24px 24px;
42
+ }
43
+ .content {
44
+ position: relative;
45
+ z-index: 1;
46
+ height: 100%;
47
+ padding: 72px 80px;
48
+ display: flex;
49
+ flex-direction: column;
50
+ justify-content: center;
51
+ }
52
+ .editor-badge {
53
+ display: inline-flex;
54
+ align-items: center;
55
+ gap: 8px;
56
+ padding: 8px 14px;
57
+ border-radius: 6px;
58
+ background: rgba(255,255,255,0.08);
59
+ border: 1px solid rgba(255,255,255,0.12);
60
+ font-family: 'JetBrains Mono', monospace;
61
+ font-size: 13px;
62
+ color: ${ctx.accent};
63
+ margin-bottom: 28px;
64
+ width: fit-content;
65
+ }
66
+ .title {
67
+ font-family: '${ctx.fonts[0]}', serif;
68
+ font-size: 64px;
69
+ font-weight: 600;
70
+ line-height: 1.15;
71
+ max-width: 800px;
72
+ margin-bottom: 20px;
73
+ }
74
+ .subtitle {
75
+ font-size: 26px;
76
+ color: rgba(240, 244, 247, 0.6);
77
+ max-width: 600px;
78
+ line-height: 1.5;
79
+ }
80
+ .window-controls {
81
+ position: absolute;
82
+ top: 40px;
83
+ right: 60px;
84
+ display: flex;
85
+ gap: 8px;
86
+ }
87
+ .window-dot {
88
+ width: 12px;
89
+ height: 12px;
90
+ border-radius: 50%;
91
+ background: rgba(255,255,255,0.15);
92
+ }
93
+ .brand {
94
+ position: absolute;
95
+ bottom: 60px;
96
+ left: 80px;
97
+ display: flex;
98
+ align-items: center;
99
+ gap: 10px;
100
+ }
101
+ .brand-dot {
102
+ width: 14px;
103
+ height: 14px;
104
+ border-radius: 50%;
105
+ background: ${ctx.accent};
106
+ box-shadow: 0 0 20px ${ctx.accent}60;
107
+ }
108
+ .brand-name {
109
+ font-family: '${ctx.fonts[0]}', serif;
110
+ font-size: 22px;
111
+ font-weight: 600;
112
+ }
113
+ </style>
114
+ </head>
115
+ <body>
116
+ <div class="glow"></div>
117
+ <div class="grid"></div>
118
+ <div class="window-controls">
119
+ <span class="window-dot"></span>
120
+ <span class="window-dot"></span>
121
+ <span class="window-dot"></span>
122
+ </div>
123
+ <div class="content">
124
+ <div class="editor-badge">${ctx.tag || 'editor'}</div>
125
+ <h1 class="title">${ctx.title}</h1>
126
+ ${ctx.subtitle ? `<p class="subtitle">${ctx.subtitle}</p>` : ''}
127
+ </div>
128
+ <div class="brand">
129
+ <span class="brand-dot"></span>
130
+ <span class="brand-name">${ctx.title.split('|')[0]?.trim() || ''}</span>
131
+ </div>
132
+ </body>
133
+ </html>
134
+ `;
135
+ //# sourceMappingURL=editor-dark.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editor-dark.js","sourceRoot":"","sources":["../../src/templates/editor-dark.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,CAAC,MAAM,UAAU,GAAqB,CAAC,GAAG,EAAE,EAAE,CAAC;;;;;;;yDAOI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC;;;;;;;;eAQnG,GAAG,CAAC,KAAK;gBACR,GAAG,CAAC,MAAM;;sBAEJ,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;;;;;;;;;6CASL,GAAG,CAAC,MAAM;6CACV,GAAG,CAAC,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eA8BjD,GAAG,CAAC,MAAM;;;;;sBAKH,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBAsCd,GAAG,CAAC,MAAM;6BACD,GAAG,CAAC,MAAM;;;sBAGjB,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;;;;;;;;;;;;;;;gCAeF,GAAG,CAAC,GAAG,IAAI,QAAQ;wBAC3B,GAAG,CAAC,KAAK;MAC3B,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,uBAAuB,GAAG,CAAC,QAAQ,MAAM,CAAC,CAAC,CAAC,EAAE;;;;+BAIpC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE;;;;CAInE,CAAA"}
@@ -0,0 +1,8 @@
1
+ import type { TemplateFunction, TemplateId } from '../types.js';
2
+ import { branded } from './branded.js';
3
+ import { docs } from './docs.js';
4
+ import { minimal } from './minimal.js';
5
+ import { editorDark } from './editor-dark.js';
6
+ export declare const templates: Record<TemplateId, TemplateFunction>;
7
+ export { branded, docs, minimal, editorDark };
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,UAAU,EAAE,gBAAgB,CAK1D,CAAA;AAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,CAAA"}
@@ -0,0 +1,12 @@
1
+ import { branded } from './branded.js';
2
+ import { docs } from './docs.js';
3
+ import { minimal } from './minimal.js';
4
+ import { editorDark } from './editor-dark.js';
5
+ export const templates = {
6
+ branded,
7
+ docs,
8
+ minimal,
9
+ 'editor-dark': editorDark,
10
+ };
11
+ export { branded, docs, minimal, editorDark };
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C,MAAM,CAAC,MAAM,SAAS,GAAyC;IAC7D,OAAO;IACP,IAAI;IACJ,OAAO;IACP,aAAa,EAAE,UAAU;CAC1B,CAAA;AAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,CAAA"}
@@ -0,0 +1,6 @@
1
+ import type { TemplateFunction } from '../types.js';
2
+ /**
3
+ * Minimal template - clean, typography-focused
4
+ */
5
+ export declare const minimal: TemplateFunction;
6
+ //# sourceMappingURL=minimal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"minimal.d.ts","sourceRoot":"","sources":["../../src/templates/minimal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAEnD;;GAEG;AACH,eAAO,MAAM,OAAO,EAAE,gBA0DrB,CAAA"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Minimal template - clean, typography-focused
3
+ */
4
+ export const minimal = (ctx) => `
5
+ <!DOCTYPE html>
6
+ <html>
7
+ <head>
8
+ <meta charset="UTF-8">
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=${ctx.fonts.map(f => f.replace(/ /g, '+')).join('&family=')}&display=swap" rel="stylesheet">
12
+ <style>
13
+ * {
14
+ margin: 0;
15
+ padding: 0;
16
+ box-sizing: border-box;
17
+ }
18
+ body {
19
+ width: ${ctx.width}px;
20
+ height: ${ctx.height}px;
21
+ background: ${ctx.background};
22
+ font-family: '${ctx.fonts[1] || ctx.fonts[0]}', system-ui, sans-serif;
23
+ color: ${ctx.textColor};
24
+ display: flex;
25
+ align-items: center;
26
+ justify-content: center;
27
+ text-align: center;
28
+ }
29
+ .content {
30
+ max-width: 900px;
31
+ padding: 60px;
32
+ }
33
+ .title {
34
+ font-family: '${ctx.fonts[0]}', serif;
35
+ font-size: 68px;
36
+ font-weight: 600;
37
+ line-height: 1.15;
38
+ margin-bottom: 24px;
39
+ }
40
+ .subtitle {
41
+ font-size: 28px;
42
+ color: ${ctx.textColor}70;
43
+ line-height: 1.5;
44
+ }
45
+ .accent-line {
46
+ width: 80px;
47
+ height: 4px;
48
+ background: ${ctx.accent};
49
+ border-radius: 2px;
50
+ margin: 0 auto 32px;
51
+ }
52
+ </style>
53
+ </head>
54
+ <body>
55
+ <div class="content">
56
+ <div class="accent-line"></div>
57
+ <h1 class="title">${ctx.title}</h1>
58
+ ${ctx.subtitle ? `<p class="subtitle">${ctx.subtitle}</p>` : ''}
59
+ </div>
60
+ </body>
61
+ </html>
62
+ `;
63
+ //# sourceMappingURL=minimal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"minimal.js","sourceRoot":"","sources":["../../src/templates/minimal.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,CAAC,MAAM,OAAO,GAAqB,CAAC,GAAG,EAAE,EAAE,CAAC;;;;;;;yDAOO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC;;;;;;;;eAQnG,GAAG,CAAC,KAAK;gBACR,GAAG,CAAC,MAAM;oBACN,GAAG,CAAC,UAAU;sBACZ,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;eACnC,GAAG,CAAC,SAAS;;;;;;;;;;;sBAWN,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;;;;;;;;eAQnB,GAAG,CAAC,SAAS;;;;;;oBAMR,GAAG,CAAC,MAAM;;;;;;;;;wBASN,GAAG,CAAC,KAAK;MAC3B,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,uBAAuB,GAAG,CAAC,QAAQ,MAAM,CAAC,CAAC,CAAC,EAAE;;;;CAIlE,CAAA"}
@@ -0,0 +1,46 @@
1
+ export type TemplateId = 'branded' | 'docs' | 'minimal' | 'editor-dark';
2
+ export interface OGConfig {
3
+ /** Template to use */
4
+ template?: TemplateId;
5
+ /** Primary title */
6
+ title: string;
7
+ /** Subtitle or description */
8
+ subtitle?: string;
9
+ /** Brand/accent color (hex) */
10
+ accent?: string;
11
+ /** Secondary accent color (hex) */
12
+ accentSecondary?: string;
13
+ /** Background color (hex) */
14
+ background?: string;
15
+ /** Text color (hex) */
16
+ textColor?: string;
17
+ /** Output file path */
18
+ output: string;
19
+ /** Width in pixels (default: 1200) */
20
+ width?: number;
21
+ /** Height in pixels (default: 630) */
22
+ height?: number;
23
+ /** Device scale factor for retina (default: 2) */
24
+ scale?: number;
25
+ /** Custom fonts to load from Google Fonts */
26
+ fonts?: string[];
27
+ /** Optional logo URL or base64 */
28
+ logo?: string;
29
+ /** Optional tag/chip text */
30
+ tag?: string;
31
+ }
32
+ export interface TemplateContext {
33
+ title: string;
34
+ subtitle?: string;
35
+ accent: string;
36
+ accentSecondary: string;
37
+ background: string;
38
+ textColor: string;
39
+ width: number;
40
+ height: number;
41
+ fonts: string[];
42
+ logo?: string;
43
+ tag?: string;
44
+ }
45
+ export type TemplateFunction = (ctx: TemplateContext) => string;
46
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,aAAa,CAAA;AAEvE,MAAM,WAAW,QAAQ;IACvB,sBAAsB;IACtB,QAAQ,CAAC,EAAE,UAAU,CAAA;IACrB,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,8BAA8B;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,+BAA+B;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,mCAAmC;IACnC,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,6BAA6B;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,uBAAuB;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,sCAAsC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,6CAA6C;IAC7C,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,kCAAkC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,6BAA6B;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,eAAe,EAAE,MAAM,CAAA;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE,eAAe,KAAK,MAAM,CAAA"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@arach/og",
3
+ "version": "0.1.0",
4
+ "description": "Declarative OG image generation with Puppeteer",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "bin": {
15
+ "og": "./dist/cli.js"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "templates"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc",
23
+ "dev": "tsc --watch",
24
+ "test": "node --test src/**/*.test.ts",
25
+ "prepublishOnly": "pnpm build"
26
+ },
27
+ "keywords": [
28
+ "og",
29
+ "opengraph",
30
+ "social",
31
+ "image",
32
+ "puppeteer",
33
+ "generator"
34
+ ],
35
+ "author": "Arach",
36
+ "license": "MIT",
37
+ "dependencies": {
38
+ "puppeteer": "^24.0.0"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^22.0.0",
42
+ "typescript": "^5.7.0"
43
+ }
44
+ }