create-claudecraft 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.
Files changed (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +194 -0
  3. package/bin/cli.js +2 -0
  4. package/dist/constants.d.ts +71 -0
  5. package/dist/constants.js +128 -0
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.js +229 -0
  8. package/dist/ink-prompts.d.ts +12 -0
  9. package/dist/ink-prompts.js +363 -0
  10. package/dist/prompts.d.ts +16 -0
  11. package/dist/prompts.js +434 -0
  12. package/dist/scaffold.d.ts +19 -0
  13. package/dist/scaffold.js +303 -0
  14. package/dist/ui.d.ts +27 -0
  15. package/dist/ui.js +254 -0
  16. package/package.json +74 -0
  17. package/templates/app/App.tsx +21 -0
  18. package/templates/base/CLAUDE.md +332 -0
  19. package/templates/base/eslint.config.js +28 -0
  20. package/templates/base/index.html +17 -0
  21. package/templates/base/package.json +43 -0
  22. package/templates/base/postcss.config.js +6 -0
  23. package/templates/base/tailwind.config.js +81 -0
  24. package/templates/base/tsconfig.json +25 -0
  25. package/templates/base/vite.config.ts +16 -0
  26. package/templates/commands/brainstorm.md +6 -0
  27. package/templates/commands/build.md +41 -0
  28. package/templates/commands/execute-plan.md +6 -0
  29. package/templates/commands/lint.md +41 -0
  30. package/templates/commands/ralph.md +113 -0
  31. package/templates/commands/typecheck.md +44 -0
  32. package/templates/commands/write-plan.md +6 -0
  33. package/templates/components/ErrorBoundary.tsx +49 -0
  34. package/templates/components/ui/Button.tsx +60 -0
  35. package/templates/components/ui/CodeBlock.tsx +46 -0
  36. package/templates/components/ui/CopyCommand.tsx +38 -0
  37. package/templates/components/ui/FilePreview.tsx +46 -0
  38. package/templates/components/ui/SkipLink.tsx +7 -0
  39. package/templates/components/ui/ThemeSelector.tsx +41 -0
  40. package/templates/components/ui/UICarousel.tsx +309 -0
  41. package/templates/context/ThemeContext.tsx +61 -0
  42. package/templates/homepage/HomePage.tsx +534 -0
  43. package/templates/homepage/NotFoundPage.tsx +17 -0
  44. package/templates/hooks/README.md +82 -0
  45. package/templates/hooks/check-branch.js +27 -0
  46. package/templates/hooks/typecheck-after-edit.js +51 -0
  47. package/templates/index.css +67 -0
  48. package/templates/lib/utils.ts +9 -0
  49. package/templates/main.tsx +16 -0
  50. package/templates/settings/MCP_SETUP.md +76 -0
  51. package/templates/settings/settings.json +16 -0
  52. package/templates/settings/settings.local.json +37 -0
  53. package/templates/skills/design/a11y-audit/SKILL.md +173 -0
  54. package/templates/skills/design/design-polish/SKILL.md +75 -0
  55. package/templates/skills/design/figma-to-code/SKILL.md +157 -0
  56. package/templates/skills/design/json-ld/SKILL.md +125 -0
  57. package/templates/skills/design/microcopy/SKILL.md +197 -0
  58. package/templates/skills/design/og-image/SKILL.md +157 -0
  59. package/templates/skills/design/ralph-wiggum-loops/SKILL.md +299 -0
  60. package/templates/skills/design/react-best-practices/SKILL.md +106 -0
  61. package/templates/skills/design/react-best-practices/references/react-performance-guidelines.md +143 -0
  62. package/templates/skills/design/seo-review/SKILL.md +96 -0
  63. package/templates/skills/design/sitemap-generator/SKILL.md +66 -0
  64. package/templates/skills/design/testing-patterns/SKILL.md +276 -0
  65. package/templates/skills/design/ui-skills/SKILL.md +85 -0
  66. package/templates/skills/design/visual-iteration/SKILL.md +88 -0
  67. package/templates/skills/workflow/brainstorming/SKILL.md +54 -0
  68. package/templates/skills/workflow/dispatching-parallel-agents/SKILL.md +180 -0
  69. package/templates/skills/workflow/executing-plans/SKILL.md +76 -0
  70. package/templates/skills/workflow/finishing-a-development-branch/SKILL.md +200 -0
  71. package/templates/skills/workflow/receiving-code-review/SKILL.md +213 -0
  72. package/templates/skills/workflow/requesting-code-review/SKILL.md +105 -0
  73. package/templates/skills/workflow/requesting-code-review/code-reviewer.md +146 -0
  74. package/templates/skills/workflow/subagent-driven-development/SKILL.md +240 -0
  75. package/templates/skills/workflow/subagent-driven-development/code-quality-reviewer-prompt.md +20 -0
  76. package/templates/skills/workflow/subagent-driven-development/implementer-prompt.md +78 -0
  77. package/templates/skills/workflow/subagent-driven-development/spec-reviewer-prompt.md +61 -0
  78. package/templates/skills/workflow/systematic-debugging/CREATION-LOG.md +119 -0
  79. package/templates/skills/workflow/systematic-debugging/SKILL.md +296 -0
  80. package/templates/skills/workflow/systematic-debugging/condition-based-waiting-example.ts +158 -0
  81. package/templates/skills/workflow/systematic-debugging/condition-based-waiting.md +115 -0
  82. package/templates/skills/workflow/systematic-debugging/defense-in-depth.md +122 -0
  83. package/templates/skills/workflow/systematic-debugging/find-polluter.sh +63 -0
  84. package/templates/skills/workflow/systematic-debugging/root-cause-tracing.md +169 -0
  85. package/templates/skills/workflow/systematic-debugging/test-academic.md +14 -0
  86. package/templates/skills/workflow/systematic-debugging/test-pressure-1.md +58 -0
  87. package/templates/skills/workflow/systematic-debugging/test-pressure-2.md +68 -0
  88. package/templates/skills/workflow/systematic-debugging/test-pressure-3.md +69 -0
  89. package/templates/skills/workflow/test-driven-development/SKILL.md +371 -0
  90. package/templates/skills/workflow/test-driven-development/testing-anti-patterns.md +299 -0
  91. package/templates/skills/workflow/using-git-worktrees/SKILL.md +217 -0
  92. package/templates/skills/workflow/using-superpowers/SKILL.md +87 -0
  93. package/templates/skills/workflow/verification-before-completion/SKILL.md +139 -0
  94. package/templates/skills/workflow/writing-plans/SKILL.md +116 -0
  95. package/templates/skills/workflow/writing-skills/SKILL.md +655 -0
  96. package/templates/skills/workflow/writing-skills/anthropic-best-practices.md +1150 -0
  97. package/templates/skills/workflow/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  98. package/templates/skills/workflow/writing-skills/graphviz-conventions.dot +172 -0
  99. package/templates/skills/workflow/writing-skills/persuasion-principles.md +187 -0
  100. package/templates/skills/workflow/writing-skills/render-graphs.js +168 -0
  101. package/templates/skills/workflow/writing-skills/testing-skills-with-subagents.md +384 -0
  102. package/templates/types/index.ts +17 -0
  103. package/templates/vite-env.d.ts +1 -0
@@ -0,0 +1,303 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { execSync } from 'child_process';
4
+ import { fileURLToPath } from 'url';
5
+ import { SKILLS } from './constants.js';
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ const TEMPLATES_DIR = path.join(__dirname, '..', 'templates');
8
+ export async function scaffold(choices, onProgress) {
9
+ const startTime = Date.now();
10
+ const targetDir = path.resolve(process.cwd(), choices.projectName);
11
+ // Check if directory exists
12
+ if (await fs.pathExists(targetDir)) {
13
+ throw new Error(`DIR_EXISTS:${choices.projectName}`);
14
+ }
15
+ // Create directory
16
+ await fs.ensureDir(targetDir);
17
+ // Copy base template
18
+ onProgress('scaffolding', 10, 'base files');
19
+ const baseDir = path.join(TEMPLATES_DIR, 'base');
20
+ if (await fs.pathExists(baseDir)) {
21
+ await fs.copy(baseDir, targetDir);
22
+ }
23
+ // Ensure .claude directories exist
24
+ const claudeDir = path.join(targetDir, '.claude');
25
+ const skillsDir = path.join(claudeDir, 'skills');
26
+ const commandsDir = path.join(claudeDir, 'commands');
27
+ await fs.ensureDir(skillsDir);
28
+ await fs.ensureDir(commandsDir);
29
+ // Copy selected skills
30
+ onProgress('scaffolding', 30, 'skills');
31
+ for (const skillName of choices.selectedSkills) {
32
+ const skill = SKILLS.find((s) => s.name === skillName);
33
+ if (!skill)
34
+ continue;
35
+ // Try workflow first, then design
36
+ let sourceDir = path.join(TEMPLATES_DIR, 'skills', 'workflow', skillName);
37
+ if (!(await fs.pathExists(sourceDir))) {
38
+ sourceDir = path.join(TEMPLATES_DIR, 'skills', 'design', skillName);
39
+ }
40
+ if (await fs.pathExists(sourceDir)) {
41
+ await fs.copy(sourceDir, path.join(skillsDir, skillName));
42
+ }
43
+ }
44
+ // Copy commands
45
+ onProgress('scaffolding', 50, 'commands');
46
+ const commandsSrc = path.join(TEMPLATES_DIR, 'commands');
47
+ if (await fs.pathExists(commandsSrc)) {
48
+ await fs.copy(commandsSrc, commandsDir);
49
+ }
50
+ // Copy hooks
51
+ onProgress('scaffolding', 55, 'hooks');
52
+ const hooksSrc = path.join(TEMPLATES_DIR, 'hooks');
53
+ const hooksDir = path.join(claudeDir, 'hooks');
54
+ if (await fs.pathExists(hooksSrc)) {
55
+ await fs.ensureDir(hooksDir);
56
+ await fs.copy(hooksSrc, hooksDir);
57
+ }
58
+ // Copy settings files
59
+ const settingsSrc = path.join(TEMPLATES_DIR, 'settings');
60
+ if (await fs.pathExists(settingsSrc)) {
61
+ const files = await fs.readdir(settingsSrc);
62
+ for (const file of files) {
63
+ await fs.copy(path.join(settingsSrc, file), path.join(claudeDir, file));
64
+ }
65
+ }
66
+ // Copy components
67
+ onProgress('scaffolding', 60, 'components');
68
+ const componentsSrc = path.join(TEMPLATES_DIR, 'components');
69
+ const componentsDir = path.join(targetDir, 'src', 'components');
70
+ if (await fs.pathExists(componentsSrc)) {
71
+ await fs.ensureDir(componentsDir);
72
+ await fs.copy(componentsSrc, componentsDir);
73
+ }
74
+ // Copy context
75
+ onProgress('scaffolding', 65, 'context');
76
+ const contextSrc = path.join(TEMPLATES_DIR, 'context');
77
+ const contextDir = path.join(targetDir, 'src', 'context');
78
+ if (await fs.pathExists(contextSrc)) {
79
+ await fs.ensureDir(contextDir);
80
+ await fs.copy(contextSrc, contextDir);
81
+ }
82
+ // Copy lib
83
+ const libSrc = path.join(TEMPLATES_DIR, 'lib');
84
+ const libDir = path.join(targetDir, 'src', 'lib');
85
+ if (await fs.pathExists(libSrc)) {
86
+ await fs.ensureDir(libDir);
87
+ await fs.copy(libSrc, libDir);
88
+ }
89
+ // Copy types
90
+ const typesSrc = path.join(TEMPLATES_DIR, 'types');
91
+ const typesDir = path.join(targetDir, 'src', 'types');
92
+ if (await fs.pathExists(typesSrc)) {
93
+ await fs.ensureDir(typesDir);
94
+ await fs.copy(typesSrc, typesDir);
95
+ }
96
+ // Copy homepage or create blank App
97
+ onProgress('scaffolding', 70, 'pages');
98
+ const pagesDir = path.join(targetDir, 'src', 'pages');
99
+ await fs.ensureDir(pagesDir);
100
+ if (choices.includeHomepage) {
101
+ const homepageSrc = path.join(TEMPLATES_DIR, 'homepage');
102
+ if (await fs.pathExists(homepageSrc)) {
103
+ await fs.copy(homepageSrc, pagesDir);
104
+ }
105
+ // Copy the full App.tsx that includes routing
106
+ const appSrc = path.join(TEMPLATES_DIR, 'app', 'App.tsx');
107
+ if (await fs.pathExists(appSrc)) {
108
+ await fs.copy(appSrc, path.join(targetDir, 'src', 'App.tsx'));
109
+ }
110
+ }
111
+ else {
112
+ // Create blank App.tsx
113
+ const blankApp = `export default function App() {
114
+ return (
115
+ <div className="min-h-dvh bg-base-100 text-base-content flex items-center justify-center">
116
+ <h1 className="text-2xl font-bold">Your mass of pixels starts here.</h1>
117
+ </div>
118
+ )
119
+ }
120
+ `;
121
+ await fs.writeFile(path.join(targetDir, 'src', 'App.tsx'), blankApp);
122
+ // Create blank NotFoundPage
123
+ const notFound = `import { Link } from 'react-router-dom'
124
+
125
+ export function NotFoundPage() {
126
+ return (
127
+ <div className="min-h-dvh bg-base-100 text-base-content flex items-center justify-center px-6">
128
+ <div className="text-center max-w-md">
129
+ <h1 className="text-6xl font-bold mb-4">404</h1>
130
+ <p className="text-xl text-base-content/70 mb-8">
131
+ This page doesn't exist.
132
+ </p>
133
+ <Link to="/" className="btn btn-primary">
134
+ Back to home
135
+ </Link>
136
+ </div>
137
+ </div>
138
+ )
139
+ }
140
+ `;
141
+ await fs.writeFile(path.join(pagesDir, 'NotFoundPage.tsx'), notFound);
142
+ }
143
+ // Update package.json with project name
144
+ onProgress('scaffolding', 80, 'package.json');
145
+ const pkgPath = path.join(targetDir, 'package.json');
146
+ if (await fs.pathExists(pkgPath)) {
147
+ const pkg = await fs.readJson(pkgPath);
148
+ pkg.name = choices.projectName;
149
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
150
+ }
151
+ // Copy main.tsx
152
+ const mainSrc = path.join(TEMPLATES_DIR, 'main.tsx');
153
+ if (await fs.pathExists(mainSrc)) {
154
+ await fs.copy(mainSrc, path.join(targetDir, 'src', 'main.tsx'));
155
+ }
156
+ // Copy index.css
157
+ const cssSrc = path.join(TEMPLATES_DIR, 'index.css');
158
+ if (await fs.pathExists(cssSrc)) {
159
+ await fs.copy(cssSrc, path.join(targetDir, 'src', 'index.css'));
160
+ }
161
+ // Copy vite-env.d.ts
162
+ const viteSrc = path.join(TEMPLATES_DIR, 'vite-env.d.ts');
163
+ if (await fs.pathExists(viteSrc)) {
164
+ await fs.copy(viteSrc, path.join(targetDir, 'src', 'vite-env.d.ts'));
165
+ }
166
+ onProgress('scaffolding', 100, 'done');
167
+ // Install dependencies
168
+ onProgress('dependencies', 0, 'bun install');
169
+ try {
170
+ execSync('bun install', { cwd: targetDir, stdio: 'pipe' });
171
+ }
172
+ catch {
173
+ throw new Error('INSTALL_FAILED');
174
+ }
175
+ onProgress('dependencies', 100, 'done');
176
+ // Git init
177
+ if (choices.initGit) {
178
+ onProgress('git init', 0, '.git/');
179
+ try {
180
+ execSync('git init', { cwd: targetDir, stdio: 'pipe' });
181
+ execSync('git add .', { cwd: targetDir, stdio: 'pipe' });
182
+ execSync('git commit -m "init: claudecraft scaffolding"', {
183
+ cwd: targetDir,
184
+ stdio: 'pipe',
185
+ });
186
+ }
187
+ catch {
188
+ // Git init failed, not critical
189
+ }
190
+ onProgress('git init', 100, 'done');
191
+ }
192
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
193
+ const files = await countFiles(targetDir);
194
+ const diskUsage = await getDiskUsage(targetDir);
195
+ return {
196
+ files,
197
+ skills: choices.selectedSkills.length,
198
+ commands: 6,
199
+ time: `${elapsed}s`,
200
+ deps: 24,
201
+ disk: diskUsage,
202
+ };
203
+ }
204
+ /**
205
+ * Initialize claudecraft in an existing project
206
+ * Only adds .claude folder with skills, commands, hooks, settings
207
+ */
208
+ export async function initExisting(choices, onProgress) {
209
+ const startTime = Date.now();
210
+ const targetDir = process.cwd();
211
+ // Ensure .claude directories exist
212
+ onProgress('scaffolding', 10, '.claude/');
213
+ const claudeDir = path.join(targetDir, '.claude');
214
+ const skillsDir = path.join(claudeDir, 'skills');
215
+ const commandsDir = path.join(claudeDir, 'commands');
216
+ await fs.ensureDir(skillsDir);
217
+ await fs.ensureDir(commandsDir);
218
+ // Copy selected skills
219
+ onProgress('scaffolding', 30, 'skills');
220
+ for (const skillName of choices.selectedSkills) {
221
+ const skill = SKILLS.find((s) => s.name === skillName);
222
+ if (!skill)
223
+ continue;
224
+ // Try workflow first, then design
225
+ let sourceDir = path.join(TEMPLATES_DIR, 'skills', 'workflow', skillName);
226
+ if (!(await fs.pathExists(sourceDir))) {
227
+ sourceDir = path.join(TEMPLATES_DIR, 'skills', 'design', skillName);
228
+ }
229
+ if (await fs.pathExists(sourceDir)) {
230
+ await fs.copy(sourceDir, path.join(skillsDir, skillName));
231
+ }
232
+ }
233
+ // Copy commands
234
+ onProgress('scaffolding', 50, 'commands');
235
+ const commandsSrc = path.join(TEMPLATES_DIR, 'commands');
236
+ if (await fs.pathExists(commandsSrc)) {
237
+ await fs.copy(commandsSrc, commandsDir);
238
+ }
239
+ // Copy hooks
240
+ onProgress('scaffolding', 70, 'hooks');
241
+ const hooksSrc = path.join(TEMPLATES_DIR, 'hooks');
242
+ const hooksDir = path.join(claudeDir, 'hooks');
243
+ if (await fs.pathExists(hooksSrc)) {
244
+ await fs.ensureDir(hooksDir);
245
+ await fs.copy(hooksSrc, hooksDir);
246
+ }
247
+ // Copy settings files
248
+ onProgress('scaffolding', 85, 'settings');
249
+ const settingsSrc = path.join(TEMPLATES_DIR, 'settings');
250
+ if (await fs.pathExists(settingsSrc)) {
251
+ const files = await fs.readdir(settingsSrc);
252
+ for (const file of files) {
253
+ const destPath = path.join(claudeDir, file);
254
+ // Don't overwrite existing settings
255
+ if (!(await fs.pathExists(destPath))) {
256
+ await fs.copy(path.join(settingsSrc, file), destPath);
257
+ }
258
+ }
259
+ }
260
+ // Create or update CLAUDE.md if it doesn't exist
261
+ const claudeMdPath = path.join(targetDir, 'CLAUDE.md');
262
+ if (!(await fs.pathExists(claudeMdPath))) {
263
+ const claudeMdSrc = path.join(TEMPLATES_DIR, 'base', 'CLAUDE.md');
264
+ if (await fs.pathExists(claudeMdSrc)) {
265
+ await fs.copy(claudeMdSrc, claudeMdPath);
266
+ }
267
+ }
268
+ onProgress('scaffolding', 100, 'done');
269
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
270
+ const claudeFiles = await countFiles(claudeDir);
271
+ return {
272
+ files: claudeFiles,
273
+ skills: choices.selectedSkills.length,
274
+ commands: 7,
275
+ time: `${elapsed}s`,
276
+ deps: 0,
277
+ disk: '~500kb',
278
+ };
279
+ }
280
+ async function countFiles(dir) {
281
+ let count = 0;
282
+ const items = await fs.readdir(dir, { withFileTypes: true });
283
+ for (const item of items) {
284
+ if (item.name === 'node_modules' || item.name === '.git')
285
+ continue;
286
+ if (item.isDirectory()) {
287
+ count += await countFiles(path.join(dir, item.name));
288
+ }
289
+ else {
290
+ count++;
291
+ }
292
+ }
293
+ return count;
294
+ }
295
+ async function getDiskUsage(dir) {
296
+ try {
297
+ const result = execSync(`du -sh "${dir}" | cut -f1`, { encoding: 'utf-8' });
298
+ return result.trim();
299
+ }
300
+ catch {
301
+ return '~4mb';
302
+ }
303
+ }
package/dist/ui.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ export declare function renderHeader(): string;
2
+ export declare function renderManifest(): string;
3
+ export declare function renderMiniHeader(_step?: number, _total?: number): string;
4
+ export declare function renderStepHeader(step: number, total: number, _title: string, _required?: boolean): string;
5
+ export declare function renderStepFooter(): string;
6
+ export declare function renderSpecs(specs: Array<{
7
+ label: string;
8
+ value: string;
9
+ }>): string;
10
+ export declare function renderAnnotation(text: string): string;
11
+ export declare function renderError(title: string, body: string): string;
12
+ export interface ProgressTask {
13
+ name: string;
14
+ status: 'done' | 'active' | 'queued';
15
+ progress?: number;
16
+ time?: string;
17
+ detail?: string;
18
+ }
19
+ export declare function renderProgress(tasks: ProgressTask[]): string;
20
+ export declare function renderProgressFooter(elapsed: string, _eta: string): string;
21
+ export declare function renderComplete(projectName: string, stats: Record<string, string | number>): string;
22
+ export declare function renderInitComplete(stats: {
23
+ files: number;
24
+ skills: number;
25
+ commands: number;
26
+ time: string;
27
+ }): string;
package/dist/ui.js ADDED
@@ -0,0 +1,254 @@
1
+ import figlet from 'figlet';
2
+ import gradient from 'gradient-string';
3
+ import pc from 'picocolors';
4
+ import { TAGLINE, STACK, ASSETS, DEFAULT_THEME, DEFAULT_PORT, VERSION } from './constants.js';
5
+ // Split gradients for two-line logo
6
+ const claudeGradient = gradient(['#00ff9f', '#00b8ff']); // Green → Cyan (fresh)
7
+ const craftGradient = gradient(['#a855f7', '#6d28d9']); // Purple → Violet (deep)
8
+ // Generate ASCII art for each word separately
9
+ const asciiClaude = figlet.textSync('CLAUDE', { font: 'ANSI Shadow', horizontalLayout: 'fitted' });
10
+ const asciiCraft = figlet.textSync('CRAFT', { font: 'ANSI Shadow', horizontalLayout: 'fitted' });
11
+ // Get today's date for the revision block
12
+ const today = new Date().toISOString().split('T')[0].replace(/-/g, '.');
13
+ // ═══════════════════════════════════════════════════════════════════════════
14
+ // ALIGNMENT: Frame width = 76 chars exactly
15
+ // Normal row: " │ │ " (7) + content (66) + "│ │" (3) = 76
16
+ // CAP HT row: " │ │ " (7) + content (58) + "│◀┼─ CAP HT" (11) = 76
17
+ // BASELINE row: " │ │ " (7) + content (56) + "│◀┼─ BASELINE" (13) = 76
18
+ // ASCII art: CLAUDE=49 chars, CRAFT=41 chars
19
+ // ═══════════════════════════════════════════════════════════════════════════
20
+ const FRAME = 76;
21
+ const PREFIX = ' │ │ '; // 7 chars
22
+ const SUFFIX = '│ │'; // 3 chars
23
+ const INNER = 66; // Normal content width
24
+ // Pad content to exact width (strips ANSI codes for length calc)
25
+ const padTo = (line, width) => {
26
+ const visible = line.replace(/\x1b\[[0-9;]*m/g, '').length;
27
+ return line + ' '.repeat(Math.max(0, width - visible));
28
+ };
29
+ // Build a normal row: prefix(7) + content(66) + suffix(3) = 76 chars
30
+ const row = (content) => {
31
+ return `${pc.dim(PREFIX)}${padTo(content, INNER)}${pc.dim(SUFFIX)}`;
32
+ };
33
+ // Build a callout row with specific suffix
34
+ const calloutRow = (content, suffix) => {
35
+ const contentWidth = FRAME - PREFIX.length - suffix.length;
36
+ return `${pc.dim(PREFIX)}${padTo(content, contentWidth)}${pc.dim(suffix)}`;
37
+ };
38
+ export function renderHeader() {
39
+ // Apply gradients and filter empty lines
40
+ const claudeLines = claudeGradient(asciiClaude).split('\n').filter(l => l.trim());
41
+ const craftLines = craftGradient(asciiCraft).split('\n').filter(l => l.trim());
42
+ const lines = [];
43
+ // ═══ TOP FRAME (76 chars) ════════════════════════════════════════════════
44
+ // " ⊕" (3) + "─┬" (2) + dashes (68) + "┬─" (2) + "⊕" (1) = 76
45
+ lines.push(pc.cyan(' ⊕') + pc.dim('─┬' + '─'.repeat(68) + '┬─') + pc.cyan('⊕'));
46
+ // " │ ◀" (7) + dashes (28) + " 72 COLS " (9) + dashes (28) + "▶ │" (4) = 76
47
+ lines.push(pc.dim(' │ ◀' + '─'.repeat(28) + ' 72 COLS ' + '─'.repeat(28) + '▶ │ '));
48
+ // " ┌─┼" (5) + dashes (68) + "┼─┐" (3) = 76
49
+ lines.push(pc.dim(' ┌─┼' + '─'.repeat(68) + '┼─┐'));
50
+ // ═══ CONTENT AREA ════════════════════════════════════════════════════════
51
+ // Ruler
52
+ lines.push(row('┆ · · · · ┆ · · · · ┆ · · · · ┆ · · · · ┆ · · · · ┆ · · · · ┆ ·'));
53
+ // CLAUDE with CAP HT callout on first line
54
+ claudeLines.forEach((line, i) => {
55
+ if (i === 0) {
56
+ lines.push(calloutRow(line, '│◀┼─ CAP HT'));
57
+ }
58
+ else {
59
+ lines.push(row(line));
60
+ }
61
+ });
62
+ // Baseline separator (content = 76 - 7 - 13 = 56, so 53 dashes + 2 for ├┤ = 55)
63
+ lines.push(calloutRow('├' + '─'.repeat(53) + '┤', '│◀┼─ BASELINE'));
64
+ // CRAFT
65
+ craftLines.forEach((line) => {
66
+ lines.push(row(line));
67
+ });
68
+ // Measurement ruler
69
+ lines.push(row('0 10 20 30 40 50 60'));
70
+ // ═══ BOTTOM FRAME (76 chars) ══════════════════════════════════════════════
71
+ // " └─┼" (5) + dashes (68) + "┼─┘" (3) = 76
72
+ lines.push(pc.dim(' └─┼' + '─'.repeat(68) + '┼─┘'));
73
+ // CMYK bar + revision block (76 chars)
74
+ // " │ " (7) + "■ C ■ M ■ Y ■ K" (18) + spaces (11) + rev (37) + " │" (3) = 76
75
+ const cmyk = pc.cyan('■') + pc.dim(' C ') + pc.magenta('■') + pc.dim(' M ') + pc.yellow('■') + pc.dim(' Y ') + pc.white('■') + pc.dim(' K');
76
+ const rev = pc.green(`REV ${VERSION}`) + pc.dim(` │ ${today} │ `) + pc.yellow('WORKS ON MAC');
77
+ lines.push(pc.dim(' │ ') + cmyk + pc.dim(' ') + rev + pc.dim(' │'));
78
+ // Bottom registration marks: " ⊕" (3) + "─┴" (2) + dashes (68) + "┴─" (2) + "⊕" (1) = 76
79
+ lines.push(pc.cyan(' ⊕') + pc.dim('─┴' + '─'.repeat(68) + '┴─') + pc.cyan('⊕'));
80
+ return '\n' + lines.join('\n') + '\n';
81
+ }
82
+ export function renderManifest() {
83
+ // Frame = 76 chars exactly
84
+ // Structure: " │ " (4) + content (70) + " │" (2) = 76
85
+ // 3-col data: col1 (22) + " │ " (3) + col2 (20) + " │ " (3) + col3 (22) = 70 ✓
86
+ const p = (s, len) => s.padEnd(len);
87
+ const D = pc.dim;
88
+ // Column builders: label + value, totaling exact width
89
+ const col1 = (label, value) => `${D(p(label, 11))}${pc.cyan(p(value, 11))}`; // 22 chars
90
+ const col2 = (label, value) => `${D(p(label, 10))}${pc.cyan(p(value, 10))}`; // 20 chars
91
+ const col3 = (label, value) => `${D(p(label, 10))}${pc.cyan(p(value, 12))}`; // 22 chars
92
+ // Header dividers: ─ sections must match column widths + frame chars
93
+ // col1 section: "─── STACK " (10) + 14 dashes = 24 (for 22 + " │" = 25... wait)
94
+ // Actually: between ├ and first ┬ = col1(22) + " " (1) = 23, plus ─'s for label
95
+ // Let me just hardcode the exact strings that work
96
+ const lines = [
97
+ ` ${D('╭' + '─'.repeat(72) + '╮')}`,
98
+ ` ${D('│')} ${D('SPECS ·')} ${p(TAGLINE, 63)}${D('│')}`,
99
+ ` ${D('├─── STACK ────────────────┬─── ASSETS ────────────┬─── DEFAULTS ────────┤')}`,
100
+ ` ${D('│')} ${col1('react', STACK.react)} ${D('│')} ${col2('skills', String(ASSETS.skills))} ${D('│')} ${col3('theme', DEFAULT_THEME)} ${D('│')}`,
101
+ ` ${D('│')} ${col1('typescript', STACK.typescript)} ${D('│')} ${col2('commands', String(ASSETS.commands))} ${D('│')} ${col3('port', String(DEFAULT_PORT))} ${D('│')}`,
102
+ ` ${D('│')} ${col1('vite', STACK.vite)} ${D('│')} ${col2('themes', String(ASSETS.themes))} ${D('│')} ${col3('tests', 'vitest')} ${D('│')}`,
103
+ ` ${D('│')} ${col1('tailwind', STACK.tailwind)} ${D('│')} ${col2('hooks', String(ASSETS.hooks))} ${D('│')} ${col3('pkg', 'bun')} ${D('│')}`,
104
+ ` ${D('│')} ${col1('daisyui', STACK.daisyui)} ${D('│')} ${col2('comps', String(ASSETS.components))} ${D('│')} ${col3('license', 'MIT')} ${D('│')}`,
105
+ ` ${D('├──────────────────────────┴────────────────────────┴────────────────────┤')}`,
106
+ ` ${D('│')} ${D('~48 files · 0 deps ·')} ${pc.cyan('/help')} ${D('for existential guidance')} ${D('│')}`,
107
+ ` ${D('╰' + '─'.repeat(72) + '╯')}`,
108
+ ];
109
+ return '\n' + lines.join('\n');
110
+ }
111
+ export function renderMiniHeader(_step, _total) {
112
+ // Condensed ASCII logo with measurement tick - split gradient
113
+ const logoGradient = gradient(['#00ff9f', '#00b8ff', '#a855f7', '#6d28d9']);
114
+ const logo = logoGradient('▓▒░ CLAUDECRAFT');
115
+ const tick = pc.dim('┆');
116
+ return `
117
+ ${tick} ${logo} ${pc.dim(`v${VERSION}`)} ${tick}
118
+ `;
119
+ }
120
+ export function renderStepHeader(step, total, _title, _required = false) {
121
+ return renderMiniHeader(step, total);
122
+ }
123
+ export function renderStepFooter() {
124
+ return '';
125
+ }
126
+ export function renderSpecs(specs) {
127
+ return specs.map((s) => ` ${pc.dim('│')} ${pc.dim(s.label + ':')} ${s.value}`).join('\n');
128
+ }
129
+ export function renderAnnotation(text) {
130
+ return ` ${pc.dim('│')} ${pc.dim(text)}`;
131
+ }
132
+ export function renderError(title, body) {
133
+ return `
134
+ ${pc.dim('╭─── ERROR ────────────────────────────────────────────────────────────╮')}
135
+ ${pc.dim('│')} ${pc.red('✗')} ${pc.bold(pc.red(title))}
136
+ ${pc.dim('├─────────────────────────────────────────────────────────────────────┤')}
137
+ ${body
138
+ .split('\n')
139
+ .map((line) => ` ${pc.dim('│')} ${line}`)
140
+ .join('\n')}
141
+ ${pc.dim('╰─────────────────────────────────────────────────────────────────────╯')}
142
+ `;
143
+ }
144
+ export function renderProgress(tasks) {
145
+ const lines = tasks.map((t, i) => {
146
+ const width = 24;
147
+ let bar = '';
148
+ let statusText = '';
149
+ const lineNum = pc.dim(`${String(i + 1).padStart(2)}`);
150
+ if (t.status === 'done') {
151
+ bar = pc.green('█'.repeat(width));
152
+ statusText = pc.green('✓ done');
153
+ }
154
+ else if (t.status === 'active') {
155
+ const filled = Math.floor(((t.progress || 0) / 100) * width);
156
+ // Animated-looking bar with gradient feel
157
+ bar = pc.cyan('▓'.repeat(filled)) + pc.dim('░'.repeat(width - filled));
158
+ statusText = pc.yellow(`◐ ${t.progress}%`);
159
+ }
160
+ else {
161
+ bar = pc.dim('·'.repeat(width));
162
+ statusText = pc.dim('○ waiting');
163
+ }
164
+ const detail = t.detail ? pc.dim(` ${t.detail}`) : '';
165
+ return ` ${pc.dim('│')} ${lineNum} ${t.name.padEnd(12)} ${bar} ${statusText.padEnd(10)}${detail}`;
166
+ });
167
+ return `
168
+ ${pc.dim('╭─── PROGRESS ─────────────────────────────────────────────────────────╮')}
169
+ ${lines.join('\n')}
170
+ ${pc.dim('├─────────────────────────────────────────────────────────────────────┤')}`;
171
+ }
172
+ export function renderProgressFooter(elapsed, _eta) {
173
+ return ` ${pc.dim('│')} elapsed: ${elapsed}
174
+ ${pc.dim('╰─────────────────────────────────────────────────────────────────────╯')}
175
+ `;
176
+ }
177
+ export function renderComplete(projectName, stats) {
178
+ // Existential dread for designers
179
+ const dread = [
180
+ "The blank canvas awaits. It judges you silently.",
181
+ "You wanted this. Remember that when it's 3am.",
182
+ "The cursor blinks. It will outlast us all.",
183
+ "Another project. Another chance to mass Cmd+Z.",
184
+ "Ship it before the doubt sets in.",
185
+ "The robots built this. You still have to design it.",
186
+ "Perfection is the enemy. So is that deadline.",
187
+ "It works on localhost. That's something.",
188
+ "Congratulations. You've automated your anxiety.",
189
+ "The PRD was 'make it pop'. Good luck.",
190
+ "Your taste exceeds your velocity. As always.",
191
+ "Claude believes in you. Claude is wrong sometimes.",
192
+ "27 skills. 0 excuses.",
193
+ "The slop awaits your curation.",
194
+ "localhost:6969. Nice.",
195
+ ];
196
+ const randomDread = dread[Math.floor(Math.random() * dread.length)];
197
+ // Stats with units for designer feel
198
+ const filesVal = String(stats.files).padEnd(4);
199
+ const skillsVal = String(stats.skills).padEnd(4);
200
+ const timeVal = String(stats.time).padEnd(6);
201
+ return `
202
+ ${pc.dim('╭─── COMPLETE ─────────────────────────────────────────────────────────╮')}
203
+ ${pc.dim('│')} ${pc.green('✓')} ${pc.bold('Ready')} ${pc.dim('·')} ${pc.dim(randomDread)}
204
+ ${pc.dim('├─── NEXT ─────────────────────────────────────────────────────────────┤')}
205
+ ${pc.dim('│')} ${pc.dim('01')} ${pc.cyan('$')} cd ${projectName}
206
+ ${pc.dim('│')} ${pc.dim('02')} ${pc.cyan('$')} bun dev
207
+ ${pc.dim('│')} ${pc.dim('03')} ${pc.cyan('$')} open http://localhost:${DEFAULT_PORT}
208
+ ${pc.dim('├─── STATS ────────────────────────────────────────────────────────────┤')}
209
+ ${pc.dim('│')} files ${filesVal} ${pc.dim('│')} skills ${skillsVal} ${pc.dim('│')} time ${timeVal} ${pc.dim('│')} deps 0
210
+ ${pc.dim('├─── COMMANDS ─────────────────────────────────────────────────────────┤')}
211
+ ${pc.dim('│')} ${pc.cyan('/build')} ${pc.dim('compile and hope')}
212
+ ${pc.dim('│')} ${pc.cyan('/brainstorm')} ${pc.dim('poke holes in your ideas')}
213
+ ${pc.dim('│')} ${pc.cyan('/ralph')} ${pc.dim('sleep while Claude ships')}
214
+ ${pc.dim('│')} ${pc.cyan('/write-plan')} ${pc.dim('think before you regret')}
215
+ ${pc.dim('├─────────────────────────────────────────────────────────────────────┤')}
216
+ ${pc.dim('│')} ${pc.dim('github.com/raduceuca/claudecraft')} ${pc.dim('·')} ${pc.dim('MIT')} ${pc.dim('·')} ${pc.dim(`v${VERSION}`)}
217
+ ${pc.dim('╰─────────────────────────────────────────────────────────────────────╯')}
218
+ `;
219
+ }
220
+ export function renderInitComplete(stats) {
221
+ // Existential dread for existing projects
222
+ const dread = [
223
+ "Your existing chaos now has structure. Briefly.",
224
+ "The slop has been organized. Alphabetically, even.",
225
+ "Claude will remember this project. Unfortunately.",
226
+ "Skills injected. Side effects may include productivity.",
227
+ ".claude/ added. Your git diff weeps.",
228
+ "Now Claude knows your codebase. Be afraid.",
229
+ "Legacy code meets AI. Pray.",
230
+ "27 guardrails installed. You're still the driver.",
231
+ "Your technical debt now has company.",
232
+ ];
233
+ const randomDread = dread[Math.floor(Math.random() * dread.length)];
234
+ return `
235
+ ${pc.dim('╭─── INIT COMPLETE ────────────────────────────────────────────────────╮')}
236
+ ${pc.dim('│')} ${pc.green('✓')} ${pc.bold('Skills injected')} ${pc.dim('·')} ${pc.dim(randomDread)}
237
+ ${pc.dim('├─── ADDED ────────────────────────────────────────────────────────────┤')}
238
+ ${pc.dim('│')} .claude/skills/ ${pc.dim(`${stats.skills} skills`)}
239
+ ${pc.dim('│')} .claude/commands/ ${pc.dim(`${stats.commands} commands`)}
240
+ ${pc.dim('│')} .claude/hooks/ ${pc.dim('2 hooks')}
241
+ ${pc.dim('│')} .claude/settings/ ${pc.dim('config + MCP guide')}
242
+ ${pc.dim('│')} CLAUDE.md ${pc.dim('project context (if missing)')}
243
+ ${pc.dim('├─── FIGMA INTEGRATION ────────────────────────────────────────────────┤')}
244
+ ${pc.dim('│')} ${pc.cyan('$')} claude mcp add --transport http figma https://mcp.figma.com/mcp
245
+ ${pc.dim('│')} ${pc.dim('Then /mcp → figma → Authenticate')}
246
+ ${pc.dim('├─── STATS ────────────────────────────────────────────────────────────┤')}
247
+ ${pc.dim('│')} files ${String(stats.files).padEnd(4)} ${pc.dim('│')} skills ${String(stats.skills).padEnd(4)} ${pc.dim('│')} time ${stats.time}
248
+ ${pc.dim('├─── NEXT ─────────────────────────────────────────────────────────────┤')}
249
+ ${pc.dim('│')} ${pc.cyan('/brainstorm')} ${pc.dim('start a conversation')}
250
+ ${pc.dim('│')} ${pc.cyan('/write-plan')} ${pc.dim('plan before building')}
251
+ ${pc.dim('│')} ${pc.dim('See .claude/settings/MCP_SETUP.md for more integrations')}
252
+ ${pc.dim('╰─────────────────────────────────────────────────────────────────────╯')}
253
+ `;
254
+ }
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "create-claudecraft",
3
+ "version": "1.0.0",
4
+ "description": "Designer-first boilerplate for Claude Code. 27 skills, 7 commands, 32 themes. The robots are here.",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-claudecraft": "./bin/cli.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "dist",
12
+ "templates",
13
+ "README.md"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "dev": "tsc --watch",
18
+ "start": "node bin/cli.js",
19
+ "prepublishOnly": "npm run build"
20
+ },
21
+ "keywords": [
22
+ "claude",
23
+ "claude-code",
24
+ "ai",
25
+ "boilerplate",
26
+ "react",
27
+ "typescript",
28
+ "tailwind",
29
+ "daisyui",
30
+ "create",
31
+ "cli",
32
+ "designer",
33
+ "vite",
34
+ "bun",
35
+ "anthropic",
36
+ "skills",
37
+ "agents"
38
+ ],
39
+ "author": {
40
+ "name": "Radu Ceuca",
41
+ "url": "https://x.com/raduceuca"
42
+ },
43
+ "license": "MIT",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "git+https://github.com/raduceuca/claudecraft.git",
47
+ "directory": "create-claudecraft"
48
+ },
49
+ "bugs": {
50
+ "url": "https://github.com/raduceuca/claudecraft/issues"
51
+ },
52
+ "homepage": "https://claudecraft.dev",
53
+ "engines": {
54
+ "node": ">=18.0.0"
55
+ },
56
+ "devDependencies": {
57
+ "@types/figlet": "^1.5.8",
58
+ "@types/fs-extra": "^11.0.4",
59
+ "@types/gradient-string": "^1.1.6",
60
+ "@types/node": "^20.11.0",
61
+ "typescript": "^5.3.3"
62
+ },
63
+ "dependencies": {
64
+ "@clack/prompts": "^0.7.0",
65
+ "figlet": "^1.7.0",
66
+ "fs-extra": "^11.2.0",
67
+ "gradient-string": "^2.0.2",
68
+ "ink": "^6.6.0",
69
+ "ink-select-input": "^6.2.0",
70
+ "ink-text-input": "^6.0.0",
71
+ "picocolors": "^1.0.0",
72
+ "react": "^19.2.3"
73
+ }
74
+ }