bluedither 1.0.1 → 1.0.3

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/cli/bin.js CHANGED
@@ -21,7 +21,9 @@ const HELP = `
21
21
  bluedither install [target-dir] Install theme into a project
22
22
  bluedither tune [--port N] Launch fine-tuner dev server
23
23
  bluedither extract [dir] [--validate] Validate theme package completeness
24
- bluedither publish Validate and prepare for publishing
24
+ bluedither publish Validate and publish to marketplace
25
+ bluedither login Authenticate with marketplace
26
+ bluedither search <query> Search marketplace for themes
25
27
 
26
28
  Options:
27
29
  --help Show help for a command
@@ -48,6 +50,8 @@ const commands = {
48
50
  tune: () => import('./commands/tune.js'),
49
51
  extract: () => import('./commands/extract.js'),
50
52
  publish: () => import('./commands/publish.js'),
53
+ login: () => import('./commands/login.js'),
54
+ search: () => import('./commands/search.js'),
51
55
  };
52
56
 
53
57
  if (!commands[command]) {
@@ -1,15 +1,11 @@
1
1
  /**
2
2
  * bluedither extract [dir] [--validate]
3
3
  *
4
- * Validates theme directory completeness.
5
- * Full Paper extraction is planned for Phase 5.
4
+ * Validates theme directory completeness using bluedither.config.json paths.
6
5
  */
7
6
 
8
7
  import { readFileSync, existsSync } from 'fs';
9
- import { resolve, dirname } from 'path';
10
- import { fileURLToPath } from 'url';
11
-
12
- const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ import { resolve } from 'path';
13
9
 
14
10
  export default async function extract(args) {
15
11
  if (args.includes('--help')) {
@@ -30,27 +26,52 @@ export default async function extract(args) {
30
26
 
31
27
  console.log(`Checking theme package in: ${dir}\n`);
32
28
 
29
+ // Read config to get actual paths
30
+ const configPath = resolve(dir, 'bluedither.config.json');
31
+ if (!existsSync(configPath)) {
32
+ console.error('✗ No bluedither.config.json found.');
33
+ process.exit(1);
34
+ }
35
+
36
+ const config = JSON.parse(readFileSync(configPath, 'utf-8'));
37
+
33
38
  const required = [
34
- { path: 'theme/tokens.json', label: 'Tokens' },
35
- { path: 'theme/tokens.defaults.json', label: 'Default tokens' },
36
- { path: 'theme/tokens.schema.json', label: 'Token schema' },
37
- { path: 'theme/structure.json', label: 'Structure contract' },
38
- { path: 'theme/rules.md', label: 'Design rules' },
39
- { path: 'theme/template/index.html', label: 'HTML template' },
40
- { path: 'theme/shaders/bluedither-shader.js', label: 'Shader module' },
41
- { path: 'theme/shaders/paper-shaders-bundle.js', label: 'Shader bundle' },
42
- { path: 'skill.md', label: 'Skill instructions' },
39
+ { path: config.tokens, label: 'Tokens' },
40
+ { path: config.defaults, label: 'Default tokens' },
41
+ { path: config.schema, label: 'Token schema' },
42
+ { path: config.structure, label: 'Structure contract' },
43
+ { path: config.rules, label: 'Design rules' },
44
+ { path: config.skill, label: 'Skill instructions' },
43
45
  { path: 'bluedither.config.json', label: 'Package manifest' },
44
46
  ];
45
47
 
46
- const optional = [
47
- { path: 'dist/bluedither-tuner.js', label: 'Tuner bundle' },
48
- { path: 'dist/bluedither-shader.js', label: 'Shader bundle (dist)' },
49
- { path: 'theme/generators/vanilla.md', label: 'Vanilla generator' },
50
- { path: 'theme/generators/react.md', label: 'React generator' },
51
- { path: 'theme/generators/vue.md', label: 'Vue generator' },
52
- { path: 'theme/generators/svelte.md', label: 'Svelte generator' },
53
- ];
48
+ // Add shader sources if present
49
+ if (config.shaderSources) {
50
+ for (const s of config.shaderSources) {
51
+ required.push({ path: s, label: `Shader source (${s})` });
52
+ }
53
+ }
54
+
55
+ // Template directory
56
+ for (const tmplPath of ['theme/template/index.html', 'template/index.html']) {
57
+ if (existsSync(resolve(dir, tmplPath))) {
58
+ required.push({ path: tmplPath, label: 'HTML template' });
59
+ break;
60
+ }
61
+ }
62
+
63
+ const optional = [];
64
+ if (config.tuner) optional.push({ path: config.tuner, label: 'Tuner bundle' });
65
+ if (config.shaders) {
66
+ for (const s of config.shaders) {
67
+ optional.push({ path: s, label: `Shader bundle (${s})` });
68
+ }
69
+ }
70
+ if (config.generators) {
71
+ for (const gen of ['vanilla.md', 'react.md', 'vue.md', 'svelte.md']) {
72
+ optional.push({ path: `${config.generators}${gen}`, label: `${gen.replace('.md', '')} generator` });
73
+ }
74
+ }
54
75
 
55
76
  let allPresent = true;
56
77
 
@@ -70,8 +91,8 @@ export default async function extract(args) {
70
91
  // Schema validation
71
92
  if (doValidate) {
72
93
  console.log('\nSchema validation:');
73
- const schemaPath = resolve(dir, 'theme', 'tokens.schema.json');
74
- const tokensPath = resolve(dir, 'theme', 'tokens.json');
94
+ const schemaPath = resolve(dir, config.schema);
95
+ const tokensPath = resolve(dir, config.tokens);
75
96
 
76
97
  if (!existsSync(schemaPath) || !existsSync(tokensPath)) {
77
98
  console.log(' ✗ Cannot validate: schema or tokens file missing');
@@ -8,8 +8,9 @@
8
8
  */
9
9
 
10
10
  import { readFileSync, writeFileSync, copyFileSync, mkdirSync, existsSync } from 'fs';
11
- import { resolve, dirname, basename } from 'path';
11
+ import { resolve, dirname, basename, join } from 'path';
12
12
  import { fileURLToPath } from 'url';
13
+ import { getDownloadUrl } from '../lib/registry.js';
13
14
 
14
15
  const __dirname = dirname(fileURLToPath(import.meta.url));
15
16
  const BLUEDITHER_ROOT = resolve(__dirname, '..', '..');
@@ -44,7 +45,18 @@ export default async function install(args) {
44
45
  return;
45
46
  }
46
47
 
47
- const targetDir = resolve(args[0] || '.');
48
+ // Check if first arg is a registry reference (@designer/theme-name)
49
+ const firstArg = args[0] || '.';
50
+ const registryMatch = firstArg.match(/^@([^/]+)\/(.+)$/);
51
+
52
+ if (registryMatch) {
53
+ const slug = registryMatch[2];
54
+ const targetDir = resolve(args[1] || '.');
55
+ await installFromRegistry(slug, targetDir);
56
+ return;
57
+ }
58
+
59
+ const targetDir = resolve(firstArg);
48
60
 
49
61
  if (!existsSync(targetDir)) {
50
62
  console.error(`Target directory does not exist: ${targetDir}`);
@@ -125,3 +137,80 @@ $ARGUMENTS
125
137
  Or run: npx bluedither tune
126
138
  `);
127
139
  }
140
+
141
+ async function installFromRegistry(slug, targetDir) {
142
+ console.log(`\n Installing theme "${slug}" from marketplace...\n`);
143
+
144
+ if (!existsSync(targetDir)) {
145
+ console.error(`Target directory does not exist: ${targetDir}`);
146
+ process.exit(1);
147
+ }
148
+
149
+ // Get download URL from marketplace
150
+ let downloadUrl;
151
+ try {
152
+ downloadUrl = await getDownloadUrl(slug);
153
+ } catch (err) {
154
+ console.error(` ✗ ${err.message}`);
155
+ process.exit(1);
156
+ }
157
+
158
+ // Download ZIP
159
+ console.log(' Downloading theme package...');
160
+ const response = await fetch(downloadUrl);
161
+ if (!response.ok) {
162
+ console.error(' ✗ Download failed.');
163
+ process.exit(1);
164
+ }
165
+ const buffer = Buffer.from(await response.arrayBuffer());
166
+
167
+ // Extract ZIP
168
+ console.log(' Extracting...');
169
+ const JSZip = (await import('jszip')).default;
170
+ const zip = await JSZip.loadAsync(buffer);
171
+
172
+ const bdDir = resolve(targetDir, 'bluedither');
173
+ mkdirSync(bdDir, { recursive: true });
174
+
175
+ let extracted = 0;
176
+ for (const [path, entry] of Object.entries(zip.files)) {
177
+ if (entry.dir) {
178
+ mkdirSync(resolve(bdDir, path), { recursive: true });
179
+ continue;
180
+ }
181
+ const content = await entry.async('nodebuffer');
182
+ const destPath = resolve(bdDir, path);
183
+ mkdirSync(dirname(destPath), { recursive: true });
184
+ writeFileSync(destPath, content);
185
+ extracted++;
186
+ }
187
+
188
+ console.log(` Extracted ${extracted} files to ${bdDir}`);
189
+
190
+ // Detect framework and create Claude Code command
191
+ const { framework, typescript } = detectFramework(targetDir);
192
+ console.log(` Detected framework: ${framework}${typescript ? ' + TypeScript' : ''}`);
193
+
194
+ const claudeDir = resolve(targetDir, '.claude', 'commands');
195
+ mkdirSync(claudeDir, { recursive: true });
196
+
197
+ const commandContent = `---
198
+ description: Apply BlueDither theme to this project
199
+ ---
200
+
201
+ Read the skill instructions at bluedither/skill.md and follow them to generate the BlueDither theme for this project.
202
+
203
+ The theme files are in the bluedither/ directory. The target framework is ${framework}${typescript ? ' with TypeScript' : ''}.
204
+
205
+ $ARGUMENTS
206
+ `;
207
+
208
+ writeFileSync(resolve(claudeDir, 'apply-theme.md'), commandContent);
209
+
210
+ console.log(`
211
+ ✓ Theme "${slug}" installed!
212
+
213
+ To apply the theme, use Claude Code:
214
+ /apply-theme [description of your site]
215
+ `);
216
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * bluedither login
3
+ *
4
+ * Authenticates via the BlueDither marketplace.
5
+ * Opens browser for OAuth, receives token via local callback server.
6
+ */
7
+
8
+ import { createServer } from 'http';
9
+ import { saveCredentials } from '../lib/credentials.js';
10
+ import { REGISTRY_URL } from '../lib/registry.js';
11
+
12
+ export default async function login(args) {
13
+ if (args.includes('--help')) {
14
+ console.log(`
15
+ bluedither login
16
+
17
+ Authenticate with the BlueDither marketplace.
18
+ Opens your browser for GitHub sign-in and saves credentials locally.
19
+ `);
20
+ return;
21
+ }
22
+
23
+ return new Promise((resolvePromise, reject) => {
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
+ });
43
+ } else {
44
+ res.writeHead(404);
45
+ res.end();
46
+ }
47
+ });
48
+
49
+ server.listen(0, () => {
50
+ const port = server.address().port;
51
+ const loginUrl = `${REGISTRY_URL}/login?cli_port=${port}`;
52
+
53
+ console.log(`\n Opening browser for authentication...`);
54
+ console.log(` If it doesn't open, visit: ${loginUrl}\n`);
55
+
56
+ // Open browser
57
+ import('child_process').then(({ exec }) => {
58
+ const cmd = process.platform === 'win32' ? 'start' :
59
+ process.platform === 'darwin' ? 'open' : 'xdg-open';
60
+ exec(`${cmd} "${loginUrl}"`);
61
+ });
62
+
63
+ // Timeout after 5 minutes
64
+ setTimeout(() => {
65
+ console.error('\n ✗ Login timed out.\n');
66
+ server.close();
67
+ reject(new Error('Login timed out'));
68
+ }, 300_000);
69
+ });
70
+ });
71
+ }
@@ -1,23 +1,22 @@
1
1
  /**
2
2
  * bluedither publish
3
3
  *
4
- * Validates the theme package and prepares for publishing.
5
- * Full marketplace upload will be added in Phase 6.
4
+ * Validates the theme package and publishes to the BlueDither marketplace.
6
5
  */
7
6
 
8
- import { existsSync, readFileSync } from 'fs';
7
+ import { existsSync, readFileSync, createReadStream } from 'fs';
9
8
  import { resolve } from 'path';
10
9
  import { execSync } from 'child_process';
10
+ import { getCredentials } from '../lib/credentials.js';
11
+ import { uploadPackage, publishTheme, REGISTRY_URL } from '../lib/registry.js';
11
12
 
12
13
  export default async function publish(args) {
13
14
  if (args.includes('--help')) {
14
15
  console.log(`
15
16
  bluedither publish
16
17
 
17
- Validates theme package completeness, runs the build, and prepares
18
- the package for publishing.
19
-
20
- Marketplace upload will be available in a future version.
18
+ Validates, builds, and publishes your theme to the BlueDither marketplace.
19
+ If you're not logged in, a browser window will open for authentication.
21
20
  `);
22
21
  return;
23
22
  }
@@ -34,13 +33,18 @@ export default async function publish(args) {
34
33
  const config = JSON.parse(readFileSync(configPath, 'utf-8'));
35
34
  console.log(`Publishing: ${config.name}@${config.version}\n`);
36
35
 
37
- // Step 1: Build
36
+ // Step 1: Build BlueDither assets (shader + tuner bundles)
38
37
  console.log('Step 1: Building...');
39
- try {
40
- execSync('npm run build', { cwd: dir, stdio: 'inherit' });
41
- } catch {
42
- console.error('\n✗ Build failed. Fix errors before publishing.');
43
- process.exit(1);
38
+ const buildScript = resolve(dir, 'scripts/build.js');
39
+ if (existsSync(buildScript)) {
40
+ try {
41
+ execSync(`node "${buildScript}"`, { cwd: dir, stdio: 'inherit' });
42
+ } catch {
43
+ console.error('\n✗ Build failed. Fix errors before publishing.');
44
+ process.exit(1);
45
+ }
46
+ } else {
47
+ console.log(' No build script found — skipping build step.');
44
48
  }
45
49
 
46
50
  // Step 2: Validate package completeness
@@ -76,10 +80,99 @@ export default async function publish(args) {
76
80
  process.exit(1);
77
81
  }
78
82
 
83
+ console.log('\n✓ Validation passed.\n');
84
+
85
+ // Step 4: Authenticate
86
+ let creds = getCredentials();
87
+ if (!creds?.access_token) {
88
+ console.log('\nStep 4: Authenticating...');
89
+ const { default: login } = await import('./login.js');
90
+ await login([]);
91
+ creds = getCredentials();
92
+ if (!creds?.access_token) {
93
+ console.error('\n✗ Login failed. Cannot publish without authentication.');
94
+ process.exit(1);
95
+ }
96
+ }
97
+
98
+ console.log('\nStep 5: Creating package ZIP...');
99
+ const JSZip = (await import('jszip')).default;
100
+ const { readdirSync, statSync } = await import('fs');
101
+ const zip = new JSZip();
102
+
103
+ // Collect files to include
104
+ const includeFiles = [
105
+ config.tokens, config.defaults, config.schema,
106
+ config.structure, config.rules, config.skill,
107
+ 'bluedither.config.json',
108
+ ];
109
+ if (config.tuner && existsSync(resolve(dir, config.tuner))) includeFiles.push(config.tuner);
110
+ if (config.shaders) includeFiles.push(...config.shaders);
111
+ if (config.shaderSources) includeFiles.push(...config.shaderSources);
112
+
113
+ function addDirToZip(dirPath, zipPath) {
114
+ const entries = readdirSync(dirPath, { withFileTypes: true });
115
+ for (const entry of entries) {
116
+ const full = resolve(dirPath, entry.name);
117
+ const zp = zipPath ? `${zipPath}/${entry.name}` : entry.name;
118
+ if (entry.isDirectory()) {
119
+ addDirToZip(full, zp);
120
+ } else {
121
+ zip.file(zp, readFileSync(full));
122
+ }
123
+ }
124
+ }
125
+
126
+ for (const f of includeFiles) {
127
+ const full = resolve(dir, f);
128
+ if (!existsSync(full)) continue;
129
+ if (statSync(full).isDirectory()) {
130
+ addDirToZip(full, f);
131
+ } else {
132
+ zip.file(f, readFileSync(full));
133
+ }
134
+ }
135
+
136
+ // Include generators directory
137
+ if (config.generators && existsSync(resolve(dir, config.generators))) {
138
+ addDirToZip(resolve(dir, config.generators), config.generators);
139
+ }
140
+
141
+ // Include template directory (check both source and installed layouts)
142
+ for (const tmplPath of ['theme/template', 'template']) {
143
+ const templateDir = resolve(dir, tmplPath);
144
+ if (existsSync(templateDir)) {
145
+ addDirToZip(templateDir, tmplPath);
146
+ break;
147
+ }
148
+ }
149
+
150
+ const zipBuffer = await zip.generateAsync({ type: 'nodebuffer', compression: 'DEFLATE' });
151
+ const slug = config.name.toLowerCase().replace(/[^a-z0-9]+/g, '-');
152
+
153
+ console.log(` ZIP size: ${(zipBuffer.length / 1024).toFixed(1)} KB`);
154
+
155
+ console.log('\nStep 6: Uploading to marketplace...');
156
+ const zipBlob = new Blob([zipBuffer], { type: 'application/zip' });
157
+ const zipFile = new File([zipBlob], `${slug}.zip`, { type: 'application/zip' });
158
+
159
+ const { path: packagePath } = await uploadPackage(zipFile, slug, creds.access_token);
160
+
161
+ console.log('Step 7: Publishing theme...');
162
+ const tokens = JSON.parse(readFileSync(resolve(dir, config.tokens), 'utf-8'));
163
+
164
+ const theme = await publishTheme({
165
+ name: config.name,
166
+ slug,
167
+ description: tokens.content?.subHeadline || '',
168
+ tokens_json: tokens,
169
+ package_path: packagePath,
170
+ }, creds.access_token);
171
+
79
172
  console.log(`
80
- Package ready for publishing.
173
+ Published to marketplace!
81
174
 
82
- Marketplace upload is not yet available.
83
- To publish as an npm package, run: npm publish
175
+ View: ${REGISTRY_URL}/themes/${slug}
176
+ Install: npx bluedither install @${slug}
84
177
  `);
85
178
  }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * bluedither search <query>
3
+ *
4
+ * Searches the BlueDither marketplace for themes.
5
+ */
6
+
7
+ import { searchThemes } from '../lib/registry.js';
8
+
9
+ export default async function search(args) {
10
+ if (args.includes('--help') || args.length === 0) {
11
+ console.log(`
12
+ bluedither search <query>
13
+
14
+ Search the BlueDither marketplace for themes.
15
+
16
+ Example:
17
+ bluedither search "dark minimal"
18
+ `);
19
+ if (args.length === 0 && !args.includes('--help')) {
20
+ process.exit(1);
21
+ }
22
+ return;
23
+ }
24
+
25
+ const query = args.join(' ');
26
+ console.log(`\n Searching for "${query}"...\n`);
27
+
28
+ try {
29
+ const themes = await searchThemes(query);
30
+
31
+ if (themes.length === 0) {
32
+ console.log(' No themes found.\n');
33
+ return;
34
+ }
35
+
36
+ // Table header
37
+ const nameW = 24, designerW = 18, dlW = 10;
38
+ console.log(
39
+ ' ' +
40
+ 'Name'.padEnd(nameW) +
41
+ 'Designer'.padEnd(designerW) +
42
+ 'Downloads'.padEnd(dlW) +
43
+ 'Install command'
44
+ );
45
+ console.log(' ' + '-'.repeat(nameW + designerW + dlW + 30));
46
+
47
+ for (const t of themes) {
48
+ const designer = t.profiles?.display_name || 'unknown';
49
+ const slug = t.slug;
50
+ const handle = designer.toLowerCase().replace(/\s+/g, '-');
51
+ console.log(
52
+ ' ' +
53
+ t.name.slice(0, nameW - 2).padEnd(nameW) +
54
+ designer.slice(0, designerW - 2).padEnd(designerW) +
55
+ String(t.download_count || 0).padEnd(dlW) +
56
+ `npx bluedither install @${handle}/${slug}`
57
+ );
58
+ }
59
+ console.log();
60
+ } catch (err) {
61
+ console.error(` ✗ ${err.message}\n`);
62
+ process.exit(1);
63
+ }
64
+ }
@@ -0,0 +1,26 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
2
+ import { resolve } from 'path';
3
+ import { homedir } from 'os';
4
+
5
+ const CRED_DIR = resolve(homedir(), '.bluedither');
6
+ const CRED_FILE = resolve(CRED_DIR, 'credentials.json');
7
+
8
+ export function getCredentials() {
9
+ if (!existsSync(CRED_FILE)) return null;
10
+ try {
11
+ return JSON.parse(readFileSync(CRED_FILE, 'utf-8'));
12
+ } catch {
13
+ return null;
14
+ }
15
+ }
16
+
17
+ export function saveCredentials(creds) {
18
+ mkdirSync(CRED_DIR, { recursive: true });
19
+ writeFileSync(CRED_FILE, JSON.stringify(creds, null, 2));
20
+ }
21
+
22
+ export function clearCredentials() {
23
+ if (existsSync(CRED_FILE)) {
24
+ writeFileSync(CRED_FILE, '{}');
25
+ }
26
+ }
@@ -0,0 +1,58 @@
1
+ const REGISTRY_URL = process.env.BLUEDITHER_REGISTRY_URL || 'https://marketplace-five-flame.vercel.app';
2
+
3
+ export async function searchThemes(query) {
4
+ const url = `${REGISTRY_URL}/api/themes?q=${encodeURIComponent(query)}`;
5
+ const res = await fetch(url);
6
+ if (!res.ok) throw new Error(`Search failed: ${res.statusText}`);
7
+ return res.json();
8
+ }
9
+
10
+ export async function getTheme(slug) {
11
+ const url = `${REGISTRY_URL}/api/themes/${encodeURIComponent(slug)}`;
12
+ const res = await fetch(url);
13
+ if (!res.ok) throw new Error(`Theme not found: ${slug}`);
14
+ return res.json();
15
+ }
16
+
17
+ export async function getDownloadUrl(slug) {
18
+ const url = `${REGISTRY_URL}/api/themes/${encodeURIComponent(slug)}/download`;
19
+ const res = await fetch(url);
20
+ if (!res.ok) throw new Error(`Download failed: ${res.statusText}`);
21
+ const data = await res.json();
22
+ return data.url;
23
+ }
24
+
25
+ export async function uploadPackage(file, slug, accessToken) {
26
+ const formData = new FormData();
27
+ formData.append('package', file);
28
+ formData.append('slug', slug);
29
+
30
+ const res = await fetch(`${REGISTRY_URL}/api/upload`, {
31
+ method: 'POST',
32
+ headers: { Authorization: `Bearer ${accessToken}` },
33
+ body: formData,
34
+ });
35
+ if (!res.ok) {
36
+ const err = await res.json().catch(() => ({}));
37
+ throw new Error(err.error || `Upload failed: ${res.statusText}`);
38
+ }
39
+ return res.json();
40
+ }
41
+
42
+ export async function publishTheme(metadata, accessToken) {
43
+ const res = await fetch(`${REGISTRY_URL}/api/themes`, {
44
+ method: 'POST',
45
+ headers: {
46
+ 'Content-Type': 'application/json',
47
+ Authorization: `Bearer ${accessToken}`,
48
+ },
49
+ body: JSON.stringify(metadata),
50
+ });
51
+ if (!res.ok) {
52
+ const err = await res.json().catch(() => ({}));
53
+ throw new Error(err.error || `Publish failed: ${res.statusText}`);
54
+ }
55
+ return res.json();
56
+ }
57
+
58
+ export { REGISTRY_URL };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bluedither",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "A bold, dithered-shader hero theme for Claude Code — skill + fine-tuner",
5
5
  "type": "module",
6
6
  "bin": {
@@ -31,7 +31,9 @@
31
31
  ],
32
32
  "license": "MIT",
33
33
  "dependencies": {
34
- "@paper-design/shaders": "^0.0.71"
34
+ "@paper-design/shaders": "^0.0.71",
35
+ "archiver": "^7.0.1",
36
+ "jszip": "^3.10.1"
35
37
  },
36
38
  "devDependencies": {
37
39
  "ajv": "^8.18.0",
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "$schema": "./tokens.schema.json",
3
3
  "meta": {
4
- "name": "BlueDither",
4
+ "name": "Understanding Moon",
5
5
  "version": "1.0.0",
6
- "description": "A bold, dithered-shader hero theme with striking typography"
6
+ "description": "A deep navy theme with teal dithered ripple shader evoking lunar surfaces"
7
7
  },
8
8
  "layout": {
9
9
  "designWidth": 1364
10
10
  },
11
11
  "colors": {
12
12
  "background": "#040037",
13
- "primary": "#3300FF",
13
+ "primary": "#005A6A",
14
14
  "text": "#FFFFFF",
15
15
  "ctaBackground": "#FFFFFF",
16
16
  "ctaText": "#14161C",
17
- "shaderFront": "#3300FF",
17
+ "shaderFront": "#005A6A",
18
18
  "shaderBack": "#00000000"
19
19
  },
20
20
  "typography": {
@@ -57,7 +57,7 @@
57
57
  },
58
58
  "shader": {
59
59
  "speed": 0.41,
60
- "shape": "warp",
60
+ "shape": "ripple",
61
61
  "type": "2x2",
62
62
  "size": 4.2,
63
63
  "scale": 1,
package/theme/tokens.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "$schema": "./tokens.schema.json",
3
3
  "meta": {
4
- "name": "BlueDither",
4
+ "name": "Understanding Moon",
5
5
  "version": "1.0.0",
6
- "description": "A bold, dithered-shader hero theme with striking typography"
6
+ "description": "A deep navy theme with teal dithered ripple shader evoking lunar surfaces"
7
7
  },
8
8
  "layout": {
9
9
  "designWidth": 1364
10
10
  },
11
11
  "colors": {
12
12
  "background": "#040037",
13
- "primary": "#ff006f",
13
+ "primary": "#005A6A",
14
14
  "text": "#FFFFFF",
15
15
  "ctaBackground": "#FFFFFF",
16
16
  "ctaText": "#14161C",
17
- "shaderFront": "#ffdd00",
17
+ "shaderFront": "#005A6A",
18
18
  "shaderBack": "#00000000"
19
19
  },
20
20
  "typography": {
@@ -57,7 +57,7 @@
57
57
  },
58
58
  "shader": {
59
59
  "speed": 0.41,
60
- "shape": "warp",
60
+ "shape": "ripple",
61
61
  "type": "2x2",
62
62
  "size": 4.2,
63
63
  "scale": 1,