bluedither 1.0.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.
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "bluedither",
3
+ "version": "1.0.0",
4
+ "tokens": "theme/tokens.json",
5
+ "defaults": "theme/tokens.defaults.json",
6
+ "schema": "theme/tokens.schema.json",
7
+ "structure": "theme/structure.json",
8
+ "rules": "theme/rules.md",
9
+ "skill": "skill.md",
10
+ "shaders": ["dist/bluedither-shader.js"],
11
+ "shaderSources": ["theme/shaders/bluedither-shader.js", "theme/shaders/paper-shaders-bundle.js"],
12
+ "tuner": "dist/bluedither-tuner.js",
13
+ "generators": "theme/generators/"
14
+ }
package/cli/bin.js ADDED
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * BlueDither CLI
5
+ *
6
+ * Usage:
7
+ * bluedither install [target-dir] — Install theme into a project
8
+ * bluedither tune — Launch fine-tuner dev server
9
+ * bluedither extract [dir] [opts] — Validate/extract theme package
10
+ * bluedither publish — Validate and prepare for publishing
11
+ */
12
+
13
+ import { resolve } from 'path';
14
+
15
+ const [,, command, ...args] = process.argv;
16
+
17
+ const HELP = `
18
+ BlueDither CLI
19
+
20
+ Usage:
21
+ bluedither install [target-dir] Install theme into a project
22
+ bluedither tune [--port N] Launch fine-tuner dev server
23
+ bluedither extract [dir] [--validate] Validate theme package completeness
24
+ bluedither publish Validate and prepare for publishing
25
+
26
+ Options:
27
+ --help Show help for a command
28
+ --version Show version
29
+ `;
30
+
31
+ if (!command || command === '--help' || command === '-h') {
32
+ console.log(HELP);
33
+ process.exit(0);
34
+ }
35
+
36
+ if (command === '--version' || command === '-v') {
37
+ const { readFileSync } = await import('fs');
38
+ const { dirname } = await import('path');
39
+ const { fileURLToPath } = await import('url');
40
+ const __dirname = dirname(fileURLToPath(import.meta.url));
41
+ const pkg = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf-8'));
42
+ console.log(pkg.version);
43
+ process.exit(0);
44
+ }
45
+
46
+ const commands = {
47
+ install: () => import('./commands/install.js'),
48
+ tune: () => import('./commands/tune.js'),
49
+ extract: () => import('./commands/extract.js'),
50
+ publish: () => import('./commands/publish.js'),
51
+ };
52
+
53
+ if (!commands[command]) {
54
+ console.error(`Unknown command: ${command}\n`);
55
+ console.log(HELP);
56
+ process.exit(1);
57
+ }
58
+
59
+ const mod = await commands[command]();
60
+ await mod.default(args);
@@ -0,0 +1,103 @@
1
+ /**
2
+ * bluedither extract [dir] [--validate]
3
+ *
4
+ * Validates theme directory completeness.
5
+ * Full Paper extraction is planned for Phase 5.
6
+ */
7
+
8
+ 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));
13
+
14
+ export default async function extract(args) {
15
+ if (args.includes('--help')) {
16
+ console.log(`
17
+ bluedither extract [dir] [--validate]
18
+
19
+ Validates theme package completeness in the specified directory.
20
+ Checks that all required files exist and tokens validate against schema.
21
+
22
+ Options:
23
+ --validate Run schema validation on tokens.json
24
+ `);
25
+ return;
26
+ }
27
+
28
+ const doValidate = args.includes('--validate');
29
+ const dir = resolve(args.find(a => !a.startsWith('--')) || '.');
30
+
31
+ console.log(`Checking theme package in: ${dir}\n`);
32
+
33
+ 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' },
43
+ { path: 'bluedither.config.json', label: 'Package manifest' },
44
+ ];
45
+
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
+ ];
54
+
55
+ let allPresent = true;
56
+
57
+ console.log('Required files:');
58
+ for (const { path, label } of required) {
59
+ const exists = existsSync(resolve(dir, path));
60
+ console.log(` ${exists ? '✓' : '✗'} ${label} (${path})`);
61
+ if (!exists) allPresent = false;
62
+ }
63
+
64
+ console.log('\nOptional files:');
65
+ for (const { path, label } of optional) {
66
+ const exists = existsSync(resolve(dir, path));
67
+ console.log(` ${exists ? '✓' : '–'} ${label} (${path})`);
68
+ }
69
+
70
+ // Schema validation
71
+ if (doValidate) {
72
+ console.log('\nSchema validation:');
73
+ const schemaPath = resolve(dir, 'theme', 'tokens.schema.json');
74
+ const tokensPath = resolve(dir, 'theme', 'tokens.json');
75
+
76
+ if (!existsSync(schemaPath) || !existsSync(tokensPath)) {
77
+ console.log(' ✗ Cannot validate: schema or tokens file missing');
78
+ } else {
79
+ try {
80
+ const Ajv2020 = (await import('ajv/dist/2020.js')).default;
81
+ const schema = JSON.parse(readFileSync(schemaPath, 'utf-8'));
82
+ const tokens = JSON.parse(readFileSync(tokensPath, 'utf-8'));
83
+ const ajv = new Ajv2020({ allErrors: true });
84
+ const validate = ajv.compile(schema);
85
+ if (validate(tokens)) {
86
+ console.log(' ✓ tokens.json passes schema validation');
87
+ } else {
88
+ console.log(' ✗ tokens.json failed validation:');
89
+ for (const err of validate.errors) {
90
+ console.log(` - ${err.instancePath || '/'}: ${err.message}`);
91
+ }
92
+ allPresent = false;
93
+ }
94
+ } catch (e) {
95
+ console.log(` ✗ Validation error: ${e.message}`);
96
+ allPresent = false;
97
+ }
98
+ }
99
+ }
100
+
101
+ console.log(`\n${allPresent ? '✓ Package is complete.' : '✗ Package is incomplete — missing required files.'}`);
102
+ process.exit(allPresent ? 0 : 1);
103
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * bluedither install [target-dir]
3
+ *
4
+ * Installs the BlueDither theme into a target project:
5
+ * - Detects framework from package.json
6
+ * - Copies tokens, structure, rules, shader, tuner assets
7
+ * - Generates a Claude Code command file for invoking the skill
8
+ */
9
+
10
+ import { readFileSync, writeFileSync, copyFileSync, mkdirSync, existsSync } from 'fs';
11
+ import { resolve, dirname, basename } from 'path';
12
+ import { fileURLToPath } from 'url';
13
+
14
+ const __dirname = dirname(fileURLToPath(import.meta.url));
15
+ const BLUEDITHER_ROOT = resolve(__dirname, '..', '..');
16
+
17
+ function detectFramework(targetDir) {
18
+ const pkgPath = resolve(targetDir, 'package.json');
19
+ if (!existsSync(pkgPath)) return { framework: 'vanilla', typescript: false };
20
+
21
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
22
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
23
+
24
+ let framework = 'vanilla';
25
+ if (deps.next || deps.react || deps['react-dom']) framework = 'react';
26
+ else if (deps.nuxt || deps.vue) framework = 'vue';
27
+ else if (deps['@sveltejs/kit'] || deps.svelte) framework = 'svelte';
28
+
29
+ const typescript = !!(deps.typescript || existsSync(resolve(targetDir, 'tsconfig.json')));
30
+
31
+ return { framework, typescript };
32
+ }
33
+
34
+ export default async function install(args) {
35
+ if (args.includes('--help')) {
36
+ console.log(`
37
+ bluedither install [target-dir]
38
+
39
+ Installs BlueDither theme assets into the target project directory.
40
+ Defaults to current directory if no target specified.
41
+
42
+ Detects the project's framework and copies appropriate files.
43
+ `);
44
+ return;
45
+ }
46
+
47
+ const targetDir = resolve(args[0] || '.');
48
+
49
+ if (!existsSync(targetDir)) {
50
+ console.error(`Target directory does not exist: ${targetDir}`);
51
+ process.exit(1);
52
+ }
53
+
54
+ const { framework, typescript } = detectFramework(targetDir);
55
+ console.log(`Detected framework: ${framework}${typescript ? ' + TypeScript' : ''}`);
56
+
57
+ // Create bluedither directory in target
58
+ const bdDir = resolve(targetDir, 'bluedither');
59
+ mkdirSync(bdDir, { recursive: true });
60
+ mkdirSync(resolve(bdDir, 'shaders'), { recursive: true });
61
+
62
+ // Copy core theme files
63
+ const filesToCopy = [
64
+ ['theme/tokens.json', 'tokens.json'],
65
+ ['theme/tokens.defaults.json', 'tokens.defaults.json'],
66
+ ['theme/tokens.schema.json', 'tokens.schema.json'],
67
+ ['theme/structure.json', 'structure.json'],
68
+ ['theme/rules.md', 'rules.md'],
69
+ ['theme/shaders/bluedither-shader.js', 'shaders/bluedither-shader.js'],
70
+ ['theme/shaders/paper-shaders-bundle.js', 'shaders/paper-shaders-bundle.js'],
71
+ ['skill.md', 'skill.md'],
72
+ ['bluedither.config.json', 'bluedither.config.json'],
73
+ ];
74
+
75
+ // Copy generator file for detected framework
76
+ filesToCopy.push([`theme/generators/${framework}.md`, `generators/${framework}.md`]);
77
+ mkdirSync(resolve(bdDir, 'generators'), { recursive: true });
78
+
79
+ // Copy bundled tuner if it exists
80
+ const tunerBundlePath = resolve(BLUEDITHER_ROOT, 'dist', 'bluedither-tuner.js');
81
+ if (existsSync(tunerBundlePath)) {
82
+ filesToCopy.push(['dist/bluedither-tuner.js', 'bluedither-tuner.js']);
83
+ }
84
+
85
+ let copied = 0;
86
+ for (const [src, dest] of filesToCopy) {
87
+ const srcPath = resolve(BLUEDITHER_ROOT, src);
88
+ const destPath = resolve(bdDir, dest);
89
+ if (existsSync(srcPath)) {
90
+ mkdirSync(dirname(destPath), { recursive: true });
91
+ copyFileSync(srcPath, destPath);
92
+ copied++;
93
+ }
94
+ }
95
+
96
+ console.log(`Copied ${copied} files to ${bdDir}`);
97
+
98
+ // Generate Claude Code command file
99
+ const claudeDir = resolve(targetDir, '.claude', 'commands');
100
+ mkdirSync(claudeDir, { recursive: true });
101
+
102
+ const commandContent = `---
103
+ description: Apply BlueDither theme to this project
104
+ ---
105
+
106
+ Read the skill instructions at bluedither/skill.md and follow them to generate the BlueDither theme for this project.
107
+
108
+ The theme files are in the bluedither/ directory. The target framework is ${framework}${typescript ? ' with TypeScript' : ''}.
109
+
110
+ $ARGUMENTS
111
+ `;
112
+
113
+ writeFileSync(resolve(claudeDir, 'apply-theme.md'), commandContent);
114
+ console.log(`Created .claude/commands/apply-theme.md`);
115
+
116
+ console.log(`
117
+ BlueDither installed successfully!
118
+
119
+ To apply the theme, use Claude Code:
120
+ /apply-theme [description of your site]
121
+
122
+ To customize visually:
123
+ Include <script src="bluedither/bluedither-tuner.js"></script> in your page
124
+ Or run: npx bluedither tune
125
+ `);
126
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * bluedither publish
3
+ *
4
+ * Validates the theme package and prepares for publishing.
5
+ * Full marketplace upload will be added in Phase 6.
6
+ */
7
+
8
+ import { existsSync, readFileSync } from 'fs';
9
+ import { resolve } from 'path';
10
+ import { execSync } from 'child_process';
11
+
12
+ export default async function publish(args) {
13
+ if (args.includes('--help')) {
14
+ console.log(`
15
+ bluedither publish
16
+
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.
21
+ `);
22
+ return;
23
+ }
24
+
25
+ const dir = process.cwd();
26
+ const configPath = resolve(dir, 'bluedither.config.json');
27
+
28
+ if (!existsSync(configPath)) {
29
+ console.error('No bluedither.config.json found in current directory.');
30
+ console.error('Run this command from the root of a BlueDither theme project.');
31
+ process.exit(1);
32
+ }
33
+
34
+ const config = JSON.parse(readFileSync(configPath, 'utf-8'));
35
+ console.log(`Publishing: ${config.name}@${config.version}\n`);
36
+
37
+ // Step 1: Build
38
+ 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);
44
+ }
45
+
46
+ // Step 2: Validate package completeness
47
+ console.log('\nStep 2: Validating package...');
48
+ const { default: extract } = await import('./extract.js');
49
+ try {
50
+ await extract(['--validate']);
51
+ } catch {
52
+ // extract calls process.exit on failure
53
+ }
54
+
55
+ // Step 3: Check all config paths point to existing files
56
+ console.log('\nStep 3: Checking config paths...');
57
+ const pathFields = ['tokens', 'defaults', 'schema', 'structure', 'rules', 'skill', 'tuner'];
58
+ let configValid = true;
59
+ for (const field of pathFields) {
60
+ if (config[field]) {
61
+ const exists = existsSync(resolve(dir, config[field]));
62
+ console.log(` ${exists ? '✓' : '✗'} ${field}: ${config[field]}`);
63
+ if (!exists) configValid = false;
64
+ }
65
+ }
66
+ if (config.shaders) {
67
+ for (const s of config.shaders) {
68
+ const exists = existsSync(resolve(dir, s));
69
+ console.log(` ${exists ? '✓' : '✗'} shader: ${s}`);
70
+ if (!exists) configValid = false;
71
+ }
72
+ }
73
+
74
+ if (!configValid) {
75
+ console.error('\n✗ Config paths point to missing files.');
76
+ process.exit(1);
77
+ }
78
+
79
+ console.log(`
80
+ ✓ Package ready for publishing.
81
+
82
+ Marketplace upload is not yet available.
83
+ To publish as an npm package, run: npm publish
84
+ `);
85
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * bluedither tune [--port N]
3
+ *
4
+ * Launches the fine-tuner dev server.
5
+ * Finds the nearest bluedither.config.json or theme/ directory.
6
+ */
7
+
8
+ import { existsSync } from 'fs';
9
+ import { resolve, dirname } from 'path';
10
+ import { fileURLToPath } from 'url';
11
+ import { spawn } from 'child_process';
12
+
13
+ const __dirname = dirname(fileURLToPath(import.meta.url));
14
+
15
+ function findThemeRoot(startDir) {
16
+ let dir = startDir;
17
+ while (dir !== dirname(dir)) {
18
+ if (existsSync(resolve(dir, 'bluedither.config.json'))) return dir;
19
+ if (existsSync(resolve(dir, 'theme', 'tokens.json'))) return dir;
20
+ dir = dirname(dir);
21
+ }
22
+ return null;
23
+ }
24
+
25
+ export default async function tune(args) {
26
+ if (args.includes('--help')) {
27
+ console.log(`
28
+ bluedither tune [--port N]
29
+
30
+ Launches the fine-tuner dev server for real-time token editing.
31
+ Searches upward for bluedither.config.json or theme/tokens.json.
32
+
33
+ Options:
34
+ --port N Port number (default: 3333)
35
+ `);
36
+ return;
37
+ }
38
+
39
+ const themeRoot = findThemeRoot(process.cwd());
40
+
41
+ if (!themeRoot) {
42
+ console.error('Could not find a BlueDither theme directory.');
43
+ console.error('Run this from a directory containing bluedither.config.json or theme/tokens.json.');
44
+ process.exit(1);
45
+ }
46
+
47
+ const serverPath = resolve(themeRoot, 'fine-tuner', 'server.js');
48
+
49
+ if (!existsSync(serverPath)) {
50
+ // If we're in an installed theme (not the source repo), use the source repo's server
51
+ const sourceServer = resolve(__dirname, '..', '..', 'fine-tuner', 'server.js');
52
+ if (!existsSync(sourceServer)) {
53
+ console.error('Fine-tuner server not found.');
54
+ process.exit(1);
55
+ }
56
+ const child = spawn('node', [sourceServer, ...args], { cwd: themeRoot, stdio: 'inherit' });
57
+ child.on('exit', (code) => process.exit(code || 0));
58
+ return;
59
+ }
60
+
61
+ const child = spawn('node', [serverPath, ...args], { cwd: themeRoot, stdio: 'inherit' });
62
+ child.on('exit', (code) => process.exit(code || 0));
63
+ }