bluedither 1.0.6 → 1.0.8

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.
@@ -22,24 +22,23 @@ export default async function login(args) {
22
22
 
23
23
  return new Promise((resolvePromise, reject) => {
24
24
  const server = createServer(async (req, res) => {
25
- if (req.method === 'POST' && req.url === '/callback') {
26
- let body = '';
27
- req.on('data', chunk => { body += chunk; });
28
- req.on('end', () => {
29
- try {
30
- const creds = JSON.parse(body);
31
- saveCredentials(creds);
32
- res.writeHead(200, { 'Content-Type': 'application/json' });
33
- res.end('{"ok":true}');
34
- console.log('\n ✓ Logged in successfully!\n');
35
- server.close();
36
- resolvePromise();
37
- } catch (err) {
38
- res.writeHead(400);
39
- res.end();
40
- reject(err);
41
- }
42
- });
25
+ const url = new URL(req.url, `http://localhost`);
26
+ if (url.pathname === '/callback') {
27
+ try {
28
+ const access_token = url.searchParams.get('access_token');
29
+ const refresh_token = url.searchParams.get('refresh_token');
30
+ if (!access_token) throw new Error('No token received');
31
+ saveCredentials({ access_token, refresh_token });
32
+ res.writeHead(200, { 'Content-Type': 'text/html' });
33
+ res.end('<html><body style="background:#0a0a0f;color:#e4e4ef;font-family:system-ui;display:flex;align-items:center;justify-content:center;height:100vh;margin:0"><h1>Logged in! You can close this tab.</h1></body></html>');
34
+ console.log('\n ✓ Logged in successfully!\n');
35
+ server.close();
36
+ resolvePromise();
37
+ } catch (err) {
38
+ res.writeHead(400);
39
+ res.end('Login failed');
40
+ reject(err);
41
+ }
43
42
  } else {
44
43
  res.writeHead(404);
45
44
  res.end();
@@ -5,18 +5,22 @@
5
5
  */
6
6
 
7
7
  import { existsSync, readFileSync, createReadStream } from 'fs';
8
- import { resolve } from 'path';
8
+ import { resolve, extname } from 'path';
9
9
  import { execSync } from 'child_process';
10
10
  import { getCredentials } from '../lib/credentials.js';
11
- import { uploadPackage, publishTheme, REGISTRY_URL } from '../lib/registry.js';
11
+ import { uploadPackage, publishTheme, uploadScreenshot, REGISTRY_URL } from '../lib/registry.js';
12
12
 
13
13
  export default async function publish(args) {
14
14
  if (args.includes('--help')) {
15
15
  console.log(`
16
- bluedither publish
16
+ bluedither publish [--screenshot <path>]
17
17
 
18
18
  Validates, builds, and publishes your theme to the BlueDither marketplace.
19
19
  If you're not logged in, a browser window will open for authentication.
20
+
21
+ Options:
22
+ --screenshot <path> Path to a screenshot image (png/jpg/webp)
23
+ Auto-detected: screenshot.png, preview.png, etc.
20
24
  `);
21
25
  return;
22
26
  }
@@ -160,7 +164,35 @@ export default async function publish(args) {
160
164
 
161
165
  const { path: packagePath } = await uploadPackage(zipFile, slug, creds.access_token);
162
166
 
163
- console.log('Step 7: Publishing theme...');
167
+ // Upload screenshot if available
168
+ let screenshotUrl = null;
169
+ const ssIdx = args.indexOf('--screenshot');
170
+ const ssPath = ssIdx !== -1 ? resolve(dir, args[ssIdx + 1]) : null;
171
+
172
+ // Auto-detect screenshot file
173
+ const screenshotFile = ssPath
174
+ || ['screenshot.png', 'screenshot.jpg', 'screenshot.webp', 'preview.png', 'preview.jpg'].map(f => resolve(dir, f)).find(f => existsSync(f));
175
+
176
+ if (screenshotFile && existsSync(screenshotFile)) {
177
+ console.log(`Step 7: Uploading screenshot (${screenshotFile.split(/[\\/]/).pop()})...`);
178
+ try {
179
+ const imgBuffer = readFileSync(screenshotFile);
180
+ const ext = extname(screenshotFile).slice(1) || 'png';
181
+ const mimeType = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', webp: 'image/webp' }[ext] || 'image/png';
182
+ const imgBlob = new Blob([imgBuffer], { type: mimeType });
183
+ const imgFile = new File([imgBlob], `screenshot.${ext}`, { type: mimeType });
184
+ const result = await uploadScreenshot(imgFile, slug, creds.access_token);
185
+ screenshotUrl = result.url;
186
+ console.log(' ✓ Screenshot uploaded');
187
+ } catch (e) {
188
+ console.log(` ⚠ Screenshot upload failed: ${e.message}`);
189
+ }
190
+ } else {
191
+ console.log('Step 7: No screenshot found — skipping.');
192
+ console.log(' Tip: Add screenshot.png to your theme directory, or use --screenshot <path>');
193
+ }
194
+
195
+ console.log('\nStep 8: Publishing theme...');
164
196
  const tokens = JSON.parse(readFileSync(resolve(dir, config.tokens), 'utf-8'));
165
197
 
166
198
  const theme = await publishTheme({
@@ -169,6 +201,7 @@ export default async function publish(args) {
169
201
  description: tokens.content?.subHeadline || '',
170
202
  tokens_json: tokens,
171
203
  package_path: packagePath,
204
+ screenshot_url: screenshotUrl,
172
205
  }, creds.access_token);
173
206
 
174
207
  console.log(`
@@ -39,6 +39,23 @@ export async function uploadPackage(file, slug, accessToken) {
39
39
  return res.json();
40
40
  }
41
41
 
42
+ export async function uploadScreenshot(file, slug, accessToken) {
43
+ const formData = new FormData();
44
+ formData.append('file', file);
45
+ formData.append('slug', slug);
46
+
47
+ const res = await fetch(`${REGISTRY_URL}/api/upload/screenshot`, {
48
+ method: 'POST',
49
+ headers: { Authorization: `Bearer ${accessToken}` },
50
+ body: formData,
51
+ });
52
+ if (!res.ok) {
53
+ const err = await res.json().catch(() => ({}));
54
+ throw new Error(err.error || `Screenshot upload failed: ${res.statusText}`);
55
+ }
56
+ return res.json();
57
+ }
58
+
42
59
  export async function publishTheme(metadata, accessToken) {
43
60
  const res = await fetch(`${REGISTRY_URL}/api/themes`, {
44
61
  method: 'POST',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bluedither",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "A bold, dithered-shader hero theme for Claude Code — skill + fine-tuner",
5
5
  "type": "module",
6
6
  "bin": {