@buoy-design/cli 0.3.33 → 0.3.35
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/commands/audit.d.ts +3 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +235 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/baseline.d.ts +47 -0
- package/dist/commands/baseline.d.ts.map +1 -0
- package/dist/commands/baseline.js +327 -0
- package/dist/commands/baseline.js.map +1 -0
- package/dist/commands/begin.d.ts +9 -0
- package/dist/commands/begin.d.ts.map +1 -0
- package/dist/commands/begin.js +827 -0
- package/dist/commands/begin.js.map +1 -0
- package/dist/commands/build.d.ts +8 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +26 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/check.d.ts.sync-conflict-20260305-170128-6PCZ3ZU.map +1 -0
- package/dist/commands/check.sync-conflict-20260305-155016-6PCZ3ZU.d.ts +26 -0
- package/dist/commands/check.sync-conflict-20260305-155016-6PCZ3ZU.d.ts.map +1 -0
- package/dist/commands/check.sync-conflict-20260305-155016-6PCZ3ZU.js +438 -0
- package/dist/commands/check.sync-conflict-20260305-155016-6PCZ3ZU.js.map +1 -0
- package/dist/commands/commands.d.ts +8 -0
- package/dist/commands/commands.d.ts.map +1 -0
- package/dist/commands/commands.js +148 -0
- package/dist/commands/commands.js.map +1 -0
- package/dist/commands/components-query.d.ts +13 -0
- package/dist/commands/components-query.d.ts.map +1 -0
- package/dist/commands/components-query.js +263 -0
- package/dist/commands/components-query.js.map +1 -0
- package/dist/commands/components.d.ts +8 -0
- package/dist/commands/components.d.ts.map +1 -0
- package/dist/commands/components.js +14 -0
- package/dist/commands/components.js.map +1 -0
- package/dist/commands/dock.sync-conflict-20260309-191923-6PCZ3ZU.js +1006 -0
- package/dist/commands/history.d.ts +3 -0
- package/dist/commands/history.d.ts.map +1 -0
- package/dist/commands/history.js +352 -0
- package/dist/commands/history.js.map +1 -0
- package/dist/commands/plugins.d.ts +3 -0
- package/dist/commands/plugins.d.ts.map +1 -0
- package/dist/commands/plugins.js +63 -0
- package/dist/commands/plugins.js.map +1 -0
- package/dist/commands/show.d.ts.sync-conflict-20260306-165917-6PCZ3ZU.map +1 -0
- package/dist/commands/show.sync-conflict-20260305-140755-6PCZ3ZU.js +1735 -0
- package/dist/commands/show.sync-conflict-20260309-130326-6PCZ3ZU.d.ts +11 -0
- package/dist/commands/show.sync-conflict-20260309-130326-6PCZ3ZU.d.ts.map +1 -0
- package/dist/commands/show.sync-conflict-20260309-130326-6PCZ3ZU.js +1735 -0
- package/dist/commands/show.sync-conflict-20260309-130326-6PCZ3ZU.js.map +1 -0
- package/dist/config/loader.js +1 -1
- package/dist/config/loader.js.map +1 -1
- package/dist/config/loader.js.sync-conflict-20260309-033512-6PCZ3ZU.map +1 -0
- package/dist/config/loader.sync-conflict-20260307-175208-6PCZ3ZU.d.ts +8 -0
- package/dist/config/loader.sync-conflict-20260307-175208-6PCZ3ZU.d.ts.map +1 -0
- package/dist/config/loader.sync-conflict-20260307-175208-6PCZ3ZU.js +162 -0
- package/dist/config/loader.sync-conflict-20260307-175208-6PCZ3ZU.js.map +1 -0
- package/dist/config/schema.d.ts.sync-conflict-20260309-154654-6PCZ3ZU.map +1 -0
- package/dist/config/schema.sync-conflict-20260309-135703-6PCZ3ZU.js +214 -0
- package/dist/detect/frameworks.js.sync-conflict-20260306-123756-6PCZ3ZU.map +1 -0
- package/dist/detect/monorepo-patterns.js.sync-conflict-20260309-155400-6PCZ3ZU.map +1 -0
- package/dist/hooks/index.d.ts.sync-conflict-20260306-220901-6PCZ3ZU.map +1 -0
- package/dist/output/formatters.js.sync-conflict-20260306-134702-6PCZ3ZU.map +1 -0
- package/dist/output/formatters.sync-conflict-20260306-180804-6PCZ3ZU.js +867 -0
- package/dist/output/formatters.sync-conflict-20260307-131418-5SN2GZG.d.ts +29 -0
- package/dist/output/formatters.sync-conflict-20260307-131418-5SN2GZG.d.ts.map +1 -0
- package/dist/output/formatters.sync-conflict-20260307-131418-5SN2GZG.js +867 -0
- package/dist/output/formatters.sync-conflict-20260307-131418-5SN2GZG.js.map +1 -0
- package/dist/output/formatters.sync-conflict-20260307-143122-5SN2GZG.d.ts +29 -0
- package/dist/output/formatters.sync-conflict-20260307-143122-5SN2GZG.d.ts.map +1 -0
- package/dist/output/formatters.sync-conflict-20260307-143122-5SN2GZG.js +867 -0
- package/dist/output/formatters.sync-conflict-20260307-143122-5SN2GZG.js.map +1 -0
- package/dist/output/index.sync-conflict-20260309-222859-6PCZ3ZU.js +5 -0
- package/dist/output/reporters.d.sync-conflict-20260309-193820-6PCZ3ZU.ts +38 -0
- package/dist/output/reporters.d.ts.sync-conflict-20260306-193811-6PCZ3ZU.map +1 -0
- package/dist/output/reporters.sync-conflict-20260309-030558-6PCZ3ZU.js +182 -0
- package/dist/output/reports.d.ts.sync-conflict-20260307-172149-6PCZ3ZU.map +1 -0
- package/dist/output/reports.js.sync-conflict-20260305-161643-6PCZ3ZU.map +1 -0
- package/dist/output/reports.sync-conflict-20260305-211951-6PCZ3ZU.js +393 -0
- package/dist/output/visuals.d.ts +53 -0
- package/dist/output/visuals.d.ts.map +1 -0
- package/dist/output/visuals.js +194 -0
- package/dist/output/visuals.js.map +1 -0
- package/dist/services/drift-analysis.d.sync-conflict-20260306-151016-6PCZ3ZU.ts +194 -0
- package/dist/services/drift-analysis.d.ts.sync-conflict-20260307-175904-6PCZ3ZU.map +1 -0
- package/dist/services/drift-analysis.sync-conflict-20260309-133811-6PCZ3ZU.d.ts +194 -0
- package/dist/services/drift-analysis.sync-conflict-20260309-133811-6PCZ3ZU.d.ts.map +1 -0
- package/dist/services/drift-analysis.sync-conflict-20260309-133811-6PCZ3ZU.js +1022 -0
- package/dist/services/drift-analysis.sync-conflict-20260309-133811-6PCZ3ZU.js.map +1 -0
- package/dist/services/skill-export.d.ts.sync-conflict-20260309-171021-6PCZ3ZU.map +1 -0
- package/dist/services/skill-export.sync-conflict-20260309-144621-6PCZ3ZU.d.ts +109 -0
- package/dist/services/skill-export.sync-conflict-20260309-144621-6PCZ3ZU.d.ts.map +1 -0
- package/dist/services/skill-export.sync-conflict-20260309-144621-6PCZ3ZU.js +737 -0
- package/dist/services/skill-export.sync-conflict-20260309-144621-6PCZ3ZU.js.map +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,1006 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* buoy dock - Dock tools into your project
|
|
3
|
+
*
|
|
4
|
+
* Smart walkthrough that sets up config, agents, hooks, tokens, and graph.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* buoy dock # Smart walkthrough: config → agents → hooks
|
|
8
|
+
* buoy dock config # Just create .buoy.yaml
|
|
9
|
+
* buoy dock agents # Set up AI agents (skills + context)
|
|
10
|
+
* buoy dock skills # Just create skill files
|
|
11
|
+
* buoy dock context # Just generate CLAUDE.md section
|
|
12
|
+
* buoy dock hooks # Just set up git hooks
|
|
13
|
+
* buoy dock tokens # Generate/export design tokens
|
|
14
|
+
* buoy dock graph # Build design system knowledge graph
|
|
15
|
+
*/
|
|
16
|
+
import { Command } from "commander";
|
|
17
|
+
import { writeFileSync, existsSync, readFileSync, mkdirSync, appendFileSync, readdirSync, copyFileSync, } from "fs";
|
|
18
|
+
import { resolve, dirname, join } from "path";
|
|
19
|
+
import { homedir } from "os";
|
|
20
|
+
import { fileURLToPath } from "url";
|
|
21
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
22
|
+
const __dirname = dirname(__filename);
|
|
23
|
+
import chalk from "chalk";
|
|
24
|
+
import { stringify as stringifyYaml } from "yaml";
|
|
25
|
+
import { createInterface } from "readline";
|
|
26
|
+
import ora from "ora";
|
|
27
|
+
import { success, error, info, warning, setJsonMode, spinner, } from "../output/reporters.js";
|
|
28
|
+
import { loadConfig, getConfigPath } from "../config/loader.js";
|
|
29
|
+
import { buildAutoConfig, findTokenFiles } from "../config/auto-detect.js";
|
|
30
|
+
import { ScanOrchestrator } from "../scan/orchestrator.js";
|
|
31
|
+
import { SkillExportService } from "../services/skill-export.js";
|
|
32
|
+
import { generateContext } from "../services/context-generator.js";
|
|
33
|
+
import { setupHooks, generateStandaloneHook, detectHookSystem, setupClaudeHooks, } from "../hooks/index.js";
|
|
34
|
+
import { ProjectDetector, detectMonorepoConfig, expandPatternsForMonorepo, } from "../detect/index.js";
|
|
35
|
+
import { detectFrameworks, getPluginInstallCommand, BUILTIN_SCANNERS, PLUGIN_INFO, } from "../detect/frameworks.js";
|
|
36
|
+
import { createTokensCommand } from "./tokens.js";
|
|
37
|
+
import { createCompareCommand } from "./compare.js";
|
|
38
|
+
import { createImportCommand } from "./import.js";
|
|
39
|
+
import { createGraphCommand } from "./graph.js";
|
|
40
|
+
import { createLearnCommand } from "./learn.js";
|
|
41
|
+
import { showMenu } from "../wizard/menu.js";
|
|
42
|
+
import { generateAgents, generateCommands } from "../templates/agents.js";
|
|
43
|
+
export function createDockCommand() {
|
|
44
|
+
const cmd = new Command("dock")
|
|
45
|
+
.description("Dock tools into your project")
|
|
46
|
+
.option("-y, --yes", "Auto-accept all defaults")
|
|
47
|
+
.option("--json", "Output results as JSON")
|
|
48
|
+
.option("-f, --force", "Overwrite existing configuration")
|
|
49
|
+
.action(async (options) => {
|
|
50
|
+
await runSmartDock(options);
|
|
51
|
+
});
|
|
52
|
+
// dock config - Just create config
|
|
53
|
+
cmd
|
|
54
|
+
.command("config")
|
|
55
|
+
.description("Create .buoy.yaml configuration file")
|
|
56
|
+
.option("-f, --force", "Overwrite existing configuration")
|
|
57
|
+
.option("-y, --yes", "Auto-install recommended plugins without prompting")
|
|
58
|
+
.option("--skip-detect", "Skip auto-detection and create minimal config")
|
|
59
|
+
.action(async (options) => {
|
|
60
|
+
await runConfigDock(options);
|
|
61
|
+
});
|
|
62
|
+
// dock agents - Full AI agent onboarding
|
|
63
|
+
cmd
|
|
64
|
+
.command("agents")
|
|
65
|
+
.description("Set up AI agents with your design system")
|
|
66
|
+
.option("--dry-run", "Show what would be created without writing files")
|
|
67
|
+
.option("--json", "Output result as JSON")
|
|
68
|
+
.action(async (options) => {
|
|
69
|
+
await runAgentsDock(options);
|
|
70
|
+
});
|
|
71
|
+
// dock skills - Create skill files
|
|
72
|
+
cmd
|
|
73
|
+
.command("skills")
|
|
74
|
+
.description("Create skill files for AI agents")
|
|
75
|
+
.option("-o, --output <path>", "Output directory", ".claude/skills/design-system")
|
|
76
|
+
.option("--global", "Export to global skills directory (~/.claude/skills/)")
|
|
77
|
+
.option("--dry-run", "Show what would be created without writing files")
|
|
78
|
+
.option("--json", "Output result as JSON")
|
|
79
|
+
.action(async (options) => {
|
|
80
|
+
await runSkillsDock(options);
|
|
81
|
+
});
|
|
82
|
+
// dock context - Generate CLAUDE.md section
|
|
83
|
+
cmd
|
|
84
|
+
.command("context")
|
|
85
|
+
.description("Generate design system context for CLAUDE.md")
|
|
86
|
+
.option("-o, --output <path>", "Output file path (default: CLAUDE.md)")
|
|
87
|
+
.option("--stdout", "Output to stdout instead of file")
|
|
88
|
+
.option("-d, --detail <level>", "Detail level: minimal, standard, comprehensive", "standard")
|
|
89
|
+
.option("--json", "Output as JSON with stats")
|
|
90
|
+
.action(async (options) => {
|
|
91
|
+
await runContextDock(options);
|
|
92
|
+
});
|
|
93
|
+
// dock hooks - Set up hooks (interactive by default)
|
|
94
|
+
cmd
|
|
95
|
+
.command("hooks")
|
|
96
|
+
.description("Set up hooks for drift checking")
|
|
97
|
+
.option("--commit", "Set up git pre-commit hooks")
|
|
98
|
+
.option("--claude", "Set up Claude Code hooks (design system in every session)")
|
|
99
|
+
.action(async (options) => {
|
|
100
|
+
await runHooksDock(options);
|
|
101
|
+
});
|
|
102
|
+
// dock commands - Install global Claude Code slash commands
|
|
103
|
+
const commandsCmd = cmd
|
|
104
|
+
.command("commands")
|
|
105
|
+
.description("Manage Claude Code slash commands");
|
|
106
|
+
commandsCmd
|
|
107
|
+
.command("list")
|
|
108
|
+
.description("List available Buoy slash commands")
|
|
109
|
+
.option("--json", "Output as JSON")
|
|
110
|
+
.action((options) => {
|
|
111
|
+
if (options.json)
|
|
112
|
+
setJsonMode(true);
|
|
113
|
+
const available = listAvailableSlashCommands();
|
|
114
|
+
const commandsDir = join(homedir(), ".claude", "commands");
|
|
115
|
+
if (options.json) {
|
|
116
|
+
const result = available.map(name => ({
|
|
117
|
+
name,
|
|
118
|
+
installed: existsSync(join(commandsDir, `${name}.md`)),
|
|
119
|
+
}));
|
|
120
|
+
console.log(JSON.stringify(result, null, 2));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
console.log("");
|
|
124
|
+
console.log(chalk.cyan.bold(" Available Buoy Commands"));
|
|
125
|
+
console.log("");
|
|
126
|
+
if (available.length === 0) {
|
|
127
|
+
console.log(chalk.dim(" No commands available"));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
for (const name of available) {
|
|
131
|
+
const isInstalled = existsSync(join(commandsDir, `${name}.md`));
|
|
132
|
+
const status = isInstalled
|
|
133
|
+
? chalk.green("✓ installed")
|
|
134
|
+
: chalk.dim("not installed");
|
|
135
|
+
console.log(` /${name} ${status}`);
|
|
136
|
+
}
|
|
137
|
+
console.log("");
|
|
138
|
+
console.log(chalk.dim(" Run `buoy dock commands install` to install all commands"));
|
|
139
|
+
console.log("");
|
|
140
|
+
});
|
|
141
|
+
commandsCmd
|
|
142
|
+
.command("install")
|
|
143
|
+
.description("Install Buoy slash commands to ~/.claude/commands/")
|
|
144
|
+
.option("--dry-run", "Show what would be installed")
|
|
145
|
+
.option("--json", "Output as JSON")
|
|
146
|
+
.action((options) => {
|
|
147
|
+
if (options.json)
|
|
148
|
+
setJsonMode(true);
|
|
149
|
+
const result = installGlobalSlashCommands(options.dryRun);
|
|
150
|
+
if (options.json) {
|
|
151
|
+
console.log(JSON.stringify(result, null, 2));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
console.log("");
|
|
155
|
+
if (options.dryRun) {
|
|
156
|
+
console.log(chalk.cyan.bold(" Dry Run - Would install:"));
|
|
157
|
+
console.log("");
|
|
158
|
+
for (const name of result.installed) {
|
|
159
|
+
console.log(` ${chalk.green("+")} /${name}`);
|
|
160
|
+
}
|
|
161
|
+
for (const name of result.alreadyExisted) {
|
|
162
|
+
console.log(` ${chalk.dim("○")} /${name} (already exists)`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
if (result.installed.length > 0) {
|
|
167
|
+
console.log(chalk.green.bold(" ✓ Installed slash commands:"));
|
|
168
|
+
console.log("");
|
|
169
|
+
for (const name of result.installed) {
|
|
170
|
+
console.log(` /${name}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (result.alreadyExisted.length > 0) {
|
|
174
|
+
console.log("");
|
|
175
|
+
console.log(chalk.dim(` Already installed: ${result.alreadyExisted.map(c => `/${c}`).join(", ")}`));
|
|
176
|
+
}
|
|
177
|
+
if (result.installed.length === 0 && result.alreadyExisted.length > 0) {
|
|
178
|
+
console.log(chalk.dim(" All commands already installed"));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
console.log("");
|
|
182
|
+
console.log(chalk.dim(" Commands installed to: ~/.claude/commands/"));
|
|
183
|
+
console.log(chalk.dim(" Restart Claude Code to use them"));
|
|
184
|
+
console.log("");
|
|
185
|
+
});
|
|
186
|
+
// dock plugins - Show and suggest scanners/plugins
|
|
187
|
+
const pluginsCmd = cmd
|
|
188
|
+
.command("plugins")
|
|
189
|
+
.description("Show available scanners and plugins");
|
|
190
|
+
pluginsCmd
|
|
191
|
+
.command("list")
|
|
192
|
+
.description("List available scanners and plugins")
|
|
193
|
+
.action(async () => {
|
|
194
|
+
console.log(chalk.bold('\nBuilt-in Scanners') + chalk.dim(' (always available)'));
|
|
195
|
+
console.log('');
|
|
196
|
+
for (const [_key, scannerInfo] of Object.entries(BUILTIN_SCANNERS)) {
|
|
197
|
+
console.log(` ${chalk.green('✓')} ${chalk.cyan(scannerInfo.description)}`);
|
|
198
|
+
console.log(` ${chalk.dim(`Detects: ${scannerInfo.detects}`)}`);
|
|
199
|
+
console.log();
|
|
200
|
+
}
|
|
201
|
+
console.log(chalk.bold('Optional Plugins'));
|
|
202
|
+
console.log('');
|
|
203
|
+
for (const [_key, pluginInfo] of Object.entries(PLUGIN_INFO)) {
|
|
204
|
+
console.log(` ${chalk.dim('○')} ${chalk.cyan(pluginInfo.name)}`);
|
|
205
|
+
console.log(` ${chalk.dim(pluginInfo.description)}`);
|
|
206
|
+
console.log();
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
pluginsCmd
|
|
210
|
+
.command("suggest")
|
|
211
|
+
.description("Suggest plugins based on detected frameworks")
|
|
212
|
+
.action(async () => {
|
|
213
|
+
try {
|
|
214
|
+
const detected = await detectFrameworks(process.cwd());
|
|
215
|
+
if (detected.length === 0) {
|
|
216
|
+
console.log(chalk.yellow('No frameworks detected.'));
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const builtIn = detected.filter(fw => fw.scanner);
|
|
220
|
+
const needsPlugin = detected.filter(fw => fw.plugin && !fw.scanner);
|
|
221
|
+
if (builtIn.length > 0) {
|
|
222
|
+
console.log(chalk.bold('\nDetected (built-in support):'));
|
|
223
|
+
for (const fw of builtIn) {
|
|
224
|
+
console.log(` ${chalk.green('✓')} ${fw.name} ${chalk.dim(`- ${fw.evidence}`)}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (needsPlugin.length > 0) {
|
|
228
|
+
console.log(chalk.bold('\nDetected (optional plugin available):'));
|
|
229
|
+
for (const fw of needsPlugin) {
|
|
230
|
+
console.log(` ${chalk.yellow('○')} ${fw.name} ${chalk.dim(`- ${fw.evidence}`)}`);
|
|
231
|
+
}
|
|
232
|
+
const pluginNames = needsPlugin
|
|
233
|
+
.map(fw => fw.plugin)
|
|
234
|
+
.filter((p, i, arr) => arr.indexOf(p) === i);
|
|
235
|
+
console.log('\n' + chalk.bold('Install optional plugins:'));
|
|
236
|
+
console.log(` ${chalk.cyan(getPluginInstallCommand(pluginNames))}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
console.error(chalk.red(`Failed to detect frameworks: ${err instanceof Error ? err.message : String(err)}`));
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
// dock tokens (with compare and import as subcommands)
|
|
245
|
+
const tokensCmd = createTokensCommand();
|
|
246
|
+
tokensCmd.addCommand(createCompareCommand());
|
|
247
|
+
tokensCmd.addCommand(createImportCommand());
|
|
248
|
+
cmd.addCommand(tokensCmd);
|
|
249
|
+
// dock graph (with learn as subcommand)
|
|
250
|
+
const graphCmd = createGraphCommand();
|
|
251
|
+
graphCmd.addCommand(createLearnCommand());
|
|
252
|
+
cmd.addCommand(graphCmd);
|
|
253
|
+
return cmd;
|
|
254
|
+
}
|
|
255
|
+
function listAvailableSlashCommands() {
|
|
256
|
+
const assetsDir = resolve(__dirname, "..", "..", "assets", "commands");
|
|
257
|
+
if (!existsSync(assetsDir))
|
|
258
|
+
return [];
|
|
259
|
+
return readdirSync(assetsDir)
|
|
260
|
+
.filter(f => f.endsWith(".md"))
|
|
261
|
+
.map(f => f.replace(".md", ""));
|
|
262
|
+
}
|
|
263
|
+
function installGlobalSlashCommands(dryRun = false) {
|
|
264
|
+
const commandsDir = join(homedir(), ".claude", "commands");
|
|
265
|
+
const assetsDir = resolve(__dirname, "..", "..", "assets", "commands");
|
|
266
|
+
const installed = [];
|
|
267
|
+
const alreadyExisted = [];
|
|
268
|
+
if (!existsSync(assetsDir))
|
|
269
|
+
return { installed, alreadyExisted };
|
|
270
|
+
if (!dryRun && !existsSync(commandsDir)) {
|
|
271
|
+
mkdirSync(commandsDir, { recursive: true });
|
|
272
|
+
}
|
|
273
|
+
const commandFiles = readdirSync(assetsDir).filter(f => f.endsWith(".md"));
|
|
274
|
+
for (const file of commandFiles) {
|
|
275
|
+
const srcPath = join(assetsDir, file);
|
|
276
|
+
const destPath = join(commandsDir, file);
|
|
277
|
+
if (existsSync(destPath)) {
|
|
278
|
+
alreadyExisted.push(file.replace(".md", ""));
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
if (!dryRun) {
|
|
282
|
+
copyFileSync(srcPath, destPath);
|
|
283
|
+
}
|
|
284
|
+
installed.push(file.replace(".md", ""));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return { installed, alreadyExisted };
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Smart dock walkthrough - goes through config → agents → hooks
|
|
291
|
+
*/
|
|
292
|
+
async function runSmartDock(options) {
|
|
293
|
+
const cwd = process.cwd();
|
|
294
|
+
if (options.json) {
|
|
295
|
+
setJsonMode(true);
|
|
296
|
+
}
|
|
297
|
+
console.log("");
|
|
298
|
+
console.log(chalk.cyan.bold(" Docking Buoy..."));
|
|
299
|
+
console.log(chalk.dim(" ─".repeat(25)));
|
|
300
|
+
console.log("");
|
|
301
|
+
const results = {
|
|
302
|
+
configCreated: false,
|
|
303
|
+
agentsSetup: false,
|
|
304
|
+
hooksSetup: false,
|
|
305
|
+
};
|
|
306
|
+
// Step 1: Check/create config
|
|
307
|
+
const configPath = getConfigPath();
|
|
308
|
+
if (!configPath) {
|
|
309
|
+
console.log(chalk.yellow(" ⚠ No .buoy.yaml found"));
|
|
310
|
+
console.log("");
|
|
311
|
+
const shouldCreateConfig = options.yes || (await promptConfirm(" Create config now?", true));
|
|
312
|
+
if (shouldCreateConfig) {
|
|
313
|
+
console.log("");
|
|
314
|
+
await runConfigDock({ yes: options.yes });
|
|
315
|
+
results.configCreated = true;
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
console.log("");
|
|
319
|
+
info("Run `buoy dock config` later to create config.");
|
|
320
|
+
console.log("");
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
results.configCreated = true;
|
|
326
|
+
console.log(` ${chalk.green("✓")} Config exists`);
|
|
327
|
+
}
|
|
328
|
+
// Step 2: Check/setup AI agents
|
|
329
|
+
const skillsDir = resolve(cwd, ".claude/skills/design-system");
|
|
330
|
+
const claudeMdPath = join(cwd, "CLAUDE.md");
|
|
331
|
+
const hasSkills = existsSync(skillsDir);
|
|
332
|
+
const hasClaudeMd = existsSync(claudeMdPath) &&
|
|
333
|
+
readFileSync(claudeMdPath, "utf-8").includes("Design System");
|
|
334
|
+
if (!hasSkills || !hasClaudeMd) {
|
|
335
|
+
console.log("");
|
|
336
|
+
console.log(chalk.yellow(" ⚠ AI agents not configured"));
|
|
337
|
+
if (!hasSkills)
|
|
338
|
+
console.log(chalk.dim(" • Missing skill files"));
|
|
339
|
+
if (!hasClaudeMd)
|
|
340
|
+
console.log(chalk.dim(" • Missing CLAUDE.md section"));
|
|
341
|
+
console.log("");
|
|
342
|
+
const shouldSetupAgents = options.yes || (await promptConfirm(" Set up AI agents?", true));
|
|
343
|
+
if (shouldSetupAgents) {
|
|
344
|
+
console.log("");
|
|
345
|
+
await runAgentsDock({ json: options.json });
|
|
346
|
+
results.agentsSetup = true;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
console.log(` ${chalk.green("✓")} AI agents configured`);
|
|
351
|
+
results.agentsSetup = true;
|
|
352
|
+
}
|
|
353
|
+
// Step 3: Check/setup hooks
|
|
354
|
+
const hasHooks = existsSync(join(cwd, ".husky/pre-commit")) ||
|
|
355
|
+
existsSync(join(cwd, "lefthook.yml")) ||
|
|
356
|
+
existsSync(join(cwd, ".git/hooks/pre-commit"));
|
|
357
|
+
if (!hasHooks) {
|
|
358
|
+
console.log("");
|
|
359
|
+
console.log(chalk.yellow(" ⚠ No pre-commit hooks"));
|
|
360
|
+
console.log("");
|
|
361
|
+
const shouldSetupHooks = options.yes || (await promptConfirm(" Set up hooks?", true));
|
|
362
|
+
if (shouldSetupHooks) {
|
|
363
|
+
await runHooksDock({});
|
|
364
|
+
results.hooksSetup = true;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
console.log(` ${chalk.green("✓")} Hooks configured`);
|
|
369
|
+
results.hooksSetup = true;
|
|
370
|
+
}
|
|
371
|
+
// Summary
|
|
372
|
+
console.log("");
|
|
373
|
+
console.log(chalk.dim(" ─".repeat(25)));
|
|
374
|
+
console.log("");
|
|
375
|
+
if (options.json) {
|
|
376
|
+
console.log(JSON.stringify(results, null, 2));
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
const allDone = results.configCreated && results.agentsSetup && results.hooksSetup;
|
|
380
|
+
if (allDone) {
|
|
381
|
+
console.log(chalk.green.bold(" ✓ Buoy is docked!"));
|
|
382
|
+
console.log("");
|
|
383
|
+
console.log(chalk.dim(" Try:"));
|
|
384
|
+
console.log(` ${chalk.cyan("buoy show all")} # See your design system`);
|
|
385
|
+
console.log(` ${chalk.cyan("buoy show drift")} # Check for drift`);
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
console.log(chalk.yellow(" Partially docked"));
|
|
389
|
+
console.log("");
|
|
390
|
+
if (!results.configCreated)
|
|
391
|
+
console.log(` Run ${chalk.cyan("buoy dock config")}`);
|
|
392
|
+
if (!results.agentsSetup)
|
|
393
|
+
console.log(` Run ${chalk.cyan("buoy dock agents")}`);
|
|
394
|
+
if (!results.hooksSetup)
|
|
395
|
+
console.log(` Run ${chalk.cyan("buoy dock hooks")}`);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
console.log("");
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Config dock - creates .buoy.yaml
|
|
402
|
+
*/
|
|
403
|
+
async function runConfigDock(options) {
|
|
404
|
+
const cwd = process.cwd();
|
|
405
|
+
const configFilePath = resolve(cwd, ".buoy.yaml");
|
|
406
|
+
if (existsSync(configFilePath) && !options.force) {
|
|
407
|
+
warning(`Config already exists at ${configFilePath}`);
|
|
408
|
+
info("Use --force to overwrite");
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
let project;
|
|
412
|
+
if (options.skipDetect) {
|
|
413
|
+
const detector = new ProjectDetector(cwd);
|
|
414
|
+
project = {
|
|
415
|
+
name: (await detector.detect()).name,
|
|
416
|
+
root: cwd,
|
|
417
|
+
frameworks: [],
|
|
418
|
+
primaryFramework: null,
|
|
419
|
+
components: [],
|
|
420
|
+
tokens: [],
|
|
421
|
+
storybook: null,
|
|
422
|
+
designSystem: null,
|
|
423
|
+
monorepo: null,
|
|
424
|
+
designSystemDocs: null,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
const spin = ora("Scanning project...").start();
|
|
429
|
+
try {
|
|
430
|
+
const detector = new ProjectDetector(cwd);
|
|
431
|
+
project = await detector.detect();
|
|
432
|
+
// Also scan for token files using auto-detect patterns
|
|
433
|
+
// This catches files like theme.css, variables.css that ProjectDetector may miss
|
|
434
|
+
const tokenFiles = await findTokenFiles(cwd);
|
|
435
|
+
if (tokenFiles.length > 0) {
|
|
436
|
+
// Get existing token paths for deduplication
|
|
437
|
+
const existingPaths = new Set(project.tokens.map((t) => t.path));
|
|
438
|
+
// Add any new token files not already detected
|
|
439
|
+
for (const tokenPath of tokenFiles) {
|
|
440
|
+
if (!existingPaths.has(tokenPath)) {
|
|
441
|
+
project.tokens.push({
|
|
442
|
+
path: tokenPath,
|
|
443
|
+
type: tokenPath.endsWith(".json") ? "json" : "css",
|
|
444
|
+
name: `Token file: ${tokenPath}`,
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
spin.stop();
|
|
450
|
+
printDetectionResults(project);
|
|
451
|
+
}
|
|
452
|
+
catch (err) {
|
|
453
|
+
spin.fail("Detection failed");
|
|
454
|
+
error(err instanceof Error ? err.message : String(err));
|
|
455
|
+
process.exit(1);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
// Show frameworks and offer plugin install
|
|
459
|
+
const detectedFrameworks = await detectFrameworks(cwd);
|
|
460
|
+
if (detectedFrameworks.length > 0) {
|
|
461
|
+
await showFrameworksAndPlugins(detectedFrameworks, options.yes);
|
|
462
|
+
}
|
|
463
|
+
// Generate and write config
|
|
464
|
+
const content = generateConfig(project);
|
|
465
|
+
try {
|
|
466
|
+
writeFileSync(configFilePath, content, "utf-8");
|
|
467
|
+
success("Created .buoy.yaml");
|
|
468
|
+
}
|
|
469
|
+
catch (err) {
|
|
470
|
+
error(`Failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
471
|
+
process.exit(1);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Agents dock - sets up AI agents (skills + context + commands)
|
|
476
|
+
*/
|
|
477
|
+
async function runAgentsDock(options) {
|
|
478
|
+
const cwd = process.cwd();
|
|
479
|
+
if (options.json)
|
|
480
|
+
setJsonMode(true);
|
|
481
|
+
const spin = spinner("Setting up AI agents...");
|
|
482
|
+
try {
|
|
483
|
+
const { config, projectName } = await loadOrBuildConfig(cwd);
|
|
484
|
+
spin.text = "Scanning...";
|
|
485
|
+
const orchestrator = new ScanOrchestrator(config, cwd);
|
|
486
|
+
const scanResult = await orchestrator.scan({
|
|
487
|
+
onProgress: (msg) => {
|
|
488
|
+
spin.text = msg;
|
|
489
|
+
},
|
|
490
|
+
});
|
|
491
|
+
spin.text = "Analyzing...";
|
|
492
|
+
const { SemanticDiffEngine } = await import("@buoy-design/core/analysis");
|
|
493
|
+
const engine = new SemanticDiffEngine();
|
|
494
|
+
const diffResult = engine.analyzeComponents(scanResult.components, {
|
|
495
|
+
checkDeprecated: true,
|
|
496
|
+
checkNaming: true,
|
|
497
|
+
checkDocumentation: true,
|
|
498
|
+
});
|
|
499
|
+
spin.stop();
|
|
500
|
+
const results = {
|
|
501
|
+
agentsCreated: [],
|
|
502
|
+
commandsCreated: [],
|
|
503
|
+
skillCreated: false,
|
|
504
|
+
contextUpdated: false,
|
|
505
|
+
stats: { tokens: 0, components: 0 },
|
|
506
|
+
};
|
|
507
|
+
// Create skill files
|
|
508
|
+
const skillPath = resolve(cwd, ".claude/skills/design-system");
|
|
509
|
+
if (options.dryRun) {
|
|
510
|
+
console.log(chalk.dim(" Would create: " + skillPath));
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
const exportService = new SkillExportService(projectName);
|
|
514
|
+
const skillResult = await exportService.export({
|
|
515
|
+
tokens: scanResult.tokens,
|
|
516
|
+
components: scanResult.components,
|
|
517
|
+
drifts: diffResult.drifts,
|
|
518
|
+
projectName,
|
|
519
|
+
}, {
|
|
520
|
+
sections: ["tokens", "components", "patterns", "anti-patterns"],
|
|
521
|
+
outputPath: skillPath,
|
|
522
|
+
});
|
|
523
|
+
for (const file of skillResult.files) {
|
|
524
|
+
const dir = dirname(file.path);
|
|
525
|
+
if (!existsSync(dir))
|
|
526
|
+
mkdirSync(dir, { recursive: true });
|
|
527
|
+
writeFileSync(file.path, file.content);
|
|
528
|
+
}
|
|
529
|
+
results.skillCreated = true;
|
|
530
|
+
results.stats.tokens = skillResult.stats.tokens.total;
|
|
531
|
+
results.stats.components = skillResult.stats.components;
|
|
532
|
+
}
|
|
533
|
+
// Create agents
|
|
534
|
+
const agentsDir = resolve(cwd, ".claude/agents");
|
|
535
|
+
const agents = generateAgents(projectName);
|
|
536
|
+
if (options.dryRun) {
|
|
537
|
+
console.log(chalk.dim(" Would create: .claude/agents/"));
|
|
538
|
+
for (const agent of agents) {
|
|
539
|
+
console.log(chalk.dim(` - ${agent.filename}`));
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
if (!existsSync(agentsDir))
|
|
544
|
+
mkdirSync(agentsDir, { recursive: true });
|
|
545
|
+
for (const agent of agents) {
|
|
546
|
+
writeFileSync(join(agentsDir, agent.filename), agent.content);
|
|
547
|
+
results.agentsCreated.push(agent.filename.replace(".md", ""));
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
// Create commands
|
|
551
|
+
const commandsDir = resolve(cwd, ".claude/commands");
|
|
552
|
+
const commands = generateCommands(projectName);
|
|
553
|
+
if (options.dryRun) {
|
|
554
|
+
console.log(chalk.dim(" Would create: .claude/commands/"));
|
|
555
|
+
for (const cmd of commands) {
|
|
556
|
+
console.log(chalk.dim(` - ${cmd.filename}`));
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
if (!existsSync(commandsDir))
|
|
561
|
+
mkdirSync(commandsDir, { recursive: true });
|
|
562
|
+
for (const cmd of commands) {
|
|
563
|
+
writeFileSync(join(commandsDir, cmd.filename), cmd.content);
|
|
564
|
+
results.commandsCreated.push(cmd.filename.replace(".md", ""));
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
// Update CLAUDE.md
|
|
568
|
+
const claudeMdPath = join(cwd, "CLAUDE.md");
|
|
569
|
+
if (options.dryRun) {
|
|
570
|
+
console.log(chalk.dim(" Would update: CLAUDE.md"));
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
const contextResult = generateContext({
|
|
574
|
+
tokens: scanResult.tokens,
|
|
575
|
+
components: scanResult.components,
|
|
576
|
+
drifts: diffResult.drifts,
|
|
577
|
+
projectName,
|
|
578
|
+
}, { detailLevel: "standard" });
|
|
579
|
+
let existingContent = "";
|
|
580
|
+
let hasSection = false;
|
|
581
|
+
if (existsSync(claudeMdPath)) {
|
|
582
|
+
existingContent = readFileSync(claudeMdPath, "utf-8");
|
|
583
|
+
hasSection = /^##?\s*[Dd]esign\s*[Ss]ystem/m.test(existingContent.replace(/```[\s\S]*?```/g, ""));
|
|
584
|
+
}
|
|
585
|
+
if (!hasSection) {
|
|
586
|
+
const section = `\n## Design System\n\n${contextResult.content}\n`;
|
|
587
|
+
if (existingContent) {
|
|
588
|
+
appendFileSync(claudeMdPath, section);
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
591
|
+
writeFileSync(claudeMdPath, `# CLAUDE.md\n${section}`);
|
|
592
|
+
}
|
|
593
|
+
results.contextUpdated = true;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
if (options.json) {
|
|
597
|
+
console.log(JSON.stringify(results, null, 2));
|
|
598
|
+
}
|
|
599
|
+
else if (!options.dryRun) {
|
|
600
|
+
console.log("");
|
|
601
|
+
if (results.agentsCreated.length > 0) {
|
|
602
|
+
console.log(` ${chalk.green("✓")} Created agents: ${results.agentsCreated.join(", ")}`);
|
|
603
|
+
}
|
|
604
|
+
if (results.commandsCreated.length > 0) {
|
|
605
|
+
console.log(` ${chalk.green("✓")} Created commands: ${results.commandsCreated.map((c) => `/${c}`).join(", ")}`);
|
|
606
|
+
}
|
|
607
|
+
if (results.skillCreated)
|
|
608
|
+
console.log(` ${chalk.green("✓")} Created skill files`);
|
|
609
|
+
if (results.contextUpdated)
|
|
610
|
+
console.log(` ${chalk.green("✓")} Updated CLAUDE.md`);
|
|
611
|
+
console.log("");
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
catch (err) {
|
|
615
|
+
spin.stop();
|
|
616
|
+
error(`Failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
617
|
+
process.exit(1);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Skills dock - creates skill files only
|
|
622
|
+
*/
|
|
623
|
+
async function runSkillsDock(options) {
|
|
624
|
+
const cwd = process.cwd();
|
|
625
|
+
if (options.json)
|
|
626
|
+
setJsonMode(true);
|
|
627
|
+
const spin = spinner("Creating skills...");
|
|
628
|
+
try {
|
|
629
|
+
const { config, projectName } = await loadOrBuildConfig(cwd);
|
|
630
|
+
spin.text = "Scanning...";
|
|
631
|
+
const orchestrator = new ScanOrchestrator(config, cwd);
|
|
632
|
+
const scanResult = await orchestrator.scan({
|
|
633
|
+
onProgress: (msg) => {
|
|
634
|
+
spin.text = msg;
|
|
635
|
+
},
|
|
636
|
+
});
|
|
637
|
+
spin.text = "Analyzing...";
|
|
638
|
+
const { SemanticDiffEngine } = await import("@buoy-design/core/analysis");
|
|
639
|
+
const engine = new SemanticDiffEngine();
|
|
640
|
+
const diffResult = engine.analyzeComponents(scanResult.components, {});
|
|
641
|
+
spin.stop();
|
|
642
|
+
let outputPath = options.output || ".claude/skills/design-system";
|
|
643
|
+
if (options.global)
|
|
644
|
+
outputPath = join(homedir(), ".claude", "skills", projectName);
|
|
645
|
+
outputPath = resolve(cwd, outputPath);
|
|
646
|
+
if (options.dryRun) {
|
|
647
|
+
console.log(chalk.dim(`Would create: ${outputPath}`));
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
const exportService = new SkillExportService(projectName);
|
|
651
|
+
const result = await exportService.export({
|
|
652
|
+
tokens: scanResult.tokens,
|
|
653
|
+
components: scanResult.components,
|
|
654
|
+
drifts: diffResult.drifts,
|
|
655
|
+
projectName,
|
|
656
|
+
}, {
|
|
657
|
+
sections: ["tokens", "components", "patterns", "anti-patterns"],
|
|
658
|
+
outputPath,
|
|
659
|
+
});
|
|
660
|
+
for (const file of result.files) {
|
|
661
|
+
const dir = dirname(file.path);
|
|
662
|
+
if (!existsSync(dir))
|
|
663
|
+
mkdirSync(dir, { recursive: true });
|
|
664
|
+
writeFileSync(file.path, file.content);
|
|
665
|
+
}
|
|
666
|
+
if (options.json) {
|
|
667
|
+
console.log(JSON.stringify({ path: outputPath, stats: result.stats }, null, 2));
|
|
668
|
+
}
|
|
669
|
+
else {
|
|
670
|
+
success(`Created skills at ${outputPath}`);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
catch (err) {
|
|
674
|
+
spin.stop();
|
|
675
|
+
error(`Failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
676
|
+
process.exit(1);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Context dock - generates CLAUDE.md section
|
|
681
|
+
*/
|
|
682
|
+
async function runContextDock(options) {
|
|
683
|
+
const cwd = process.cwd();
|
|
684
|
+
if (options.json)
|
|
685
|
+
setJsonMode(true);
|
|
686
|
+
const spin = spinner("Generating context...");
|
|
687
|
+
try {
|
|
688
|
+
const { config, projectName } = await loadOrBuildConfig(cwd);
|
|
689
|
+
spin.text = "Scanning...";
|
|
690
|
+
const orchestrator = new ScanOrchestrator(config, cwd);
|
|
691
|
+
const scanResult = await orchestrator.scan();
|
|
692
|
+
spin.text = "Analyzing...";
|
|
693
|
+
const { SemanticDiffEngine } = await import("@buoy-design/core/analysis");
|
|
694
|
+
const engine = new SemanticDiffEngine();
|
|
695
|
+
const diffResult = engine.analyzeComponents(scanResult.components, {
|
|
696
|
+
availableTokens: scanResult.tokens,
|
|
697
|
+
});
|
|
698
|
+
spin.stop();
|
|
699
|
+
const contextResult = generateContext({
|
|
700
|
+
tokens: scanResult.tokens,
|
|
701
|
+
components: scanResult.components,
|
|
702
|
+
drifts: diffResult.drifts,
|
|
703
|
+
projectName,
|
|
704
|
+
}, {
|
|
705
|
+
detailLevel: options.detail ||
|
|
706
|
+
"standard",
|
|
707
|
+
});
|
|
708
|
+
if (options.json) {
|
|
709
|
+
console.log(JSON.stringify({ content: contextResult.content, stats: contextResult.stats }, null, 2));
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
if (options.stdout) {
|
|
713
|
+
console.log(contextResult.content);
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
const claudeMdPath = options.output || join(cwd, "CLAUDE.md");
|
|
717
|
+
const header = "## Design System\n\n";
|
|
718
|
+
if (existsSync(claudeMdPath)) {
|
|
719
|
+
const existing = readFileSync(claudeMdPath, "utf-8");
|
|
720
|
+
if (!existing.includes("## Design System")) {
|
|
721
|
+
appendFileSync(claudeMdPath, `\n${header}${contextResult.content}\n`);
|
|
722
|
+
success("Appended to CLAUDE.md");
|
|
723
|
+
}
|
|
724
|
+
else {
|
|
725
|
+
warning("CLAUDE.md already has Design System section");
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
writeFileSync(claudeMdPath, `# CLAUDE.md\n\n${header}${contextResult.content}\n`);
|
|
730
|
+
success("Created CLAUDE.md");
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
catch (err) {
|
|
734
|
+
spin.stop();
|
|
735
|
+
error(`Failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
736
|
+
process.exit(1);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Hooks dock - sets up hooks (interactive by default)
|
|
741
|
+
*/
|
|
742
|
+
async function runHooksDock(options) {
|
|
743
|
+
const cwd = process.cwd();
|
|
744
|
+
console.log("");
|
|
745
|
+
// If no flags provided, ask interactively
|
|
746
|
+
let setupCommit = options.commit;
|
|
747
|
+
let setupClaude = options.claude;
|
|
748
|
+
if (!setupCommit && !setupClaude) {
|
|
749
|
+
// Check if we're in an interactive terminal
|
|
750
|
+
if (!process.stdin.isTTY) {
|
|
751
|
+
// Non-interactive: skip hooks setup with a friendly message
|
|
752
|
+
info("Skipping interactive hooks setup (non-interactive terminal)");
|
|
753
|
+
console.log("");
|
|
754
|
+
console.log(chalk.dim(" To set up hooks later, run:"));
|
|
755
|
+
console.log(` ${chalk.cyan("buoy dock hooks --commit")} # Git pre-commit hooks`);
|
|
756
|
+
console.log(` ${chalk.cyan("buoy dock hooks --claude")} # Claude Code hooks`);
|
|
757
|
+
console.log(` ${chalk.cyan("buoy dock hooks")} # Interactive setup`);
|
|
758
|
+
console.log("");
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
try {
|
|
762
|
+
const choice = await showMenu("Which hooks would you like to set up?", [
|
|
763
|
+
{
|
|
764
|
+
label: "🔗 Claude Code hooks",
|
|
765
|
+
value: "claude",
|
|
766
|
+
description: "Inject design system context into every Claude session",
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
label: "📝 Git pre-commit hooks",
|
|
770
|
+
value: "commit",
|
|
771
|
+
description: "Check for drift before each commit",
|
|
772
|
+
},
|
|
773
|
+
{
|
|
774
|
+
label: "✨ Both",
|
|
775
|
+
value: "both",
|
|
776
|
+
description: "Set up Claude hooks and git pre-commit hooks",
|
|
777
|
+
},
|
|
778
|
+
]);
|
|
779
|
+
if (choice === "commit" || choice === "both") {
|
|
780
|
+
setupCommit = true;
|
|
781
|
+
}
|
|
782
|
+
if (choice === "claude" || choice === "both") {
|
|
783
|
+
setupClaude = true;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
catch (err) {
|
|
787
|
+
// Handle user closing the prompt (Ctrl+C, etc.) gracefully
|
|
788
|
+
if (err && typeof err === 'object' && 'name' in err && err.name === 'ExitPromptError') {
|
|
789
|
+
console.log("");
|
|
790
|
+
info("Hooks setup cancelled");
|
|
791
|
+
console.log(chalk.dim(" Run `buoy dock hooks` anytime to set up hooks"));
|
|
792
|
+
console.log("");
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
throw err;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
// Set up git pre-commit hooks
|
|
799
|
+
if (setupCommit) {
|
|
800
|
+
const hookSystem = detectHookSystem(cwd);
|
|
801
|
+
if (hookSystem) {
|
|
802
|
+
info(`Detected: ${hookSystem}`);
|
|
803
|
+
const hookResult = setupHooks(cwd);
|
|
804
|
+
if (hookResult.success) {
|
|
805
|
+
success(hookResult.message);
|
|
806
|
+
}
|
|
807
|
+
else {
|
|
808
|
+
warning(hookResult.message);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
else {
|
|
812
|
+
const standaloneResult = generateStandaloneHook(cwd);
|
|
813
|
+
if (standaloneResult.success) {
|
|
814
|
+
success(standaloneResult.message);
|
|
815
|
+
info("Copy to .git/hooks/pre-commit to activate");
|
|
816
|
+
}
|
|
817
|
+
else {
|
|
818
|
+
warning(standaloneResult.message);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
// Set up Claude Code hooks
|
|
823
|
+
if (setupClaude) {
|
|
824
|
+
if (setupCommit)
|
|
825
|
+
console.log(""); // Add spacing if both
|
|
826
|
+
const { config, projectName } = await loadOrBuildConfig(cwd);
|
|
827
|
+
const orchestrator = new ScanOrchestrator(config, cwd);
|
|
828
|
+
const spin = spinner("Scanning design system for Claude context...");
|
|
829
|
+
const scanResult = await orchestrator.scan();
|
|
830
|
+
spin.stop();
|
|
831
|
+
const { SemanticDiffEngine } = await import("@buoy-design/core/analysis");
|
|
832
|
+
const engine = new SemanticDiffEngine();
|
|
833
|
+
const diffResult = engine.analyzeComponents(scanResult.components, {});
|
|
834
|
+
const exportService = new SkillExportService(projectName);
|
|
835
|
+
const condensedContext = exportService.generateCondensedContext({
|
|
836
|
+
tokens: scanResult.tokens,
|
|
837
|
+
components: scanResult.components,
|
|
838
|
+
drifts: diffResult.drifts,
|
|
839
|
+
projectName,
|
|
840
|
+
});
|
|
841
|
+
const claudeDir = resolve(cwd, ".claude");
|
|
842
|
+
if (!existsSync(claudeDir))
|
|
843
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
844
|
+
writeFileSync(resolve(claudeDir, "buoy-context.md"), condensedContext);
|
|
845
|
+
const claudeResult = setupClaudeHooks(cwd);
|
|
846
|
+
if (claudeResult.success) {
|
|
847
|
+
success("Created Claude hooks");
|
|
848
|
+
info("Your design system will be loaded at the start of each Claude session");
|
|
849
|
+
}
|
|
850
|
+
else {
|
|
851
|
+
warning(`Claude hooks failed: ${claudeResult.message}`);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
console.log("");
|
|
855
|
+
}
|
|
856
|
+
// ============ Helpers ============
|
|
857
|
+
async function loadOrBuildConfig(cwd) {
|
|
858
|
+
const existingConfigPath = getConfigPath();
|
|
859
|
+
if (existingConfigPath) {
|
|
860
|
+
const result = await loadConfig();
|
|
861
|
+
return {
|
|
862
|
+
config: result.config,
|
|
863
|
+
projectName: result.config.project?.name || "design-system",
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
const autoResult = await buildAutoConfig(cwd);
|
|
867
|
+
return {
|
|
868
|
+
config: autoResult.config,
|
|
869
|
+
projectName: autoResult.config.project?.name || "design-system",
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
function generateConfig(project) {
|
|
873
|
+
const monorepoConfig = detectMonorepoConfig(project.root);
|
|
874
|
+
// Build the config object
|
|
875
|
+
const config = {
|
|
876
|
+
project: { name: project.name },
|
|
877
|
+
sources: {},
|
|
878
|
+
};
|
|
879
|
+
const sources = config.sources;
|
|
880
|
+
for (const framework of project.frameworks) {
|
|
881
|
+
const sourceKey = getSourceKey(framework.name);
|
|
882
|
+
if (sourceKey) {
|
|
883
|
+
const extensions = getExtensions(sourceKey, framework.typescript);
|
|
884
|
+
const defaultPatterns = extensions.map((ext) => `src/**/*.${ext}`);
|
|
885
|
+
const patterns = monorepoConfig.type
|
|
886
|
+
? expandPatternsForMonorepo(defaultPatterns, monorepoConfig).allPatterns
|
|
887
|
+
: defaultPatterns;
|
|
888
|
+
sources[sourceKey] = {
|
|
889
|
+
enabled: true,
|
|
890
|
+
include: patterns,
|
|
891
|
+
exclude: ["**/*.test.*", "**/*.spec.*", "**/*.stories.*"],
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
if (project.tokens.length > 0) {
|
|
896
|
+
sources.tokens = {
|
|
897
|
+
enabled: true,
|
|
898
|
+
files: project.tokens.map((t) => t.path),
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
sources.figma = { enabled: false };
|
|
902
|
+
config.output = { format: "table", colors: true };
|
|
903
|
+
// Generate YAML with header comment
|
|
904
|
+
const header = `# Buoy Configuration
|
|
905
|
+
# Auto-generated - customize to match your project
|
|
906
|
+
# Docs: https://buoy.design/docs/configuration
|
|
907
|
+
|
|
908
|
+
`;
|
|
909
|
+
return header + stringifyYaml(config, { indent: 2, lineWidth: 0 });
|
|
910
|
+
}
|
|
911
|
+
function getSourceKey(name) {
|
|
912
|
+
if ([
|
|
913
|
+
"react",
|
|
914
|
+
"nextjs",
|
|
915
|
+
"remix",
|
|
916
|
+
"gatsby",
|
|
917
|
+
"react-native",
|
|
918
|
+
"expo",
|
|
919
|
+
"preact",
|
|
920
|
+
"solid",
|
|
921
|
+
].includes(name))
|
|
922
|
+
return "react";
|
|
923
|
+
if (["vue", "nuxt"].includes(name))
|
|
924
|
+
return "vue";
|
|
925
|
+
if (["svelte", "sveltekit"].includes(name))
|
|
926
|
+
return "svelte";
|
|
927
|
+
if (name === "angular")
|
|
928
|
+
return "angular";
|
|
929
|
+
if (["lit", "stencil"].includes(name))
|
|
930
|
+
return "webcomponent";
|
|
931
|
+
return null;
|
|
932
|
+
}
|
|
933
|
+
function getExtensions(sourceKey, typescript) {
|
|
934
|
+
switch (sourceKey) {
|
|
935
|
+
case "vue":
|
|
936
|
+
return ["vue"];
|
|
937
|
+
case "svelte":
|
|
938
|
+
return ["svelte"];
|
|
939
|
+
case "angular":
|
|
940
|
+
return ["component.ts"];
|
|
941
|
+
case "webcomponent":
|
|
942
|
+
return ["ts"];
|
|
943
|
+
default:
|
|
944
|
+
return typescript ? ["tsx", "jsx"] : ["jsx", "tsx"];
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
function printDetectionResults(project) {
|
|
948
|
+
console.log("");
|
|
949
|
+
console.log(chalk.bold(" Detected:"));
|
|
950
|
+
for (const fw of project.frameworks) {
|
|
951
|
+
console.log(chalk.green(" ✓ ") +
|
|
952
|
+
chalk.bold(fw.name) +
|
|
953
|
+
(fw.typescript ? " + TS" : ""));
|
|
954
|
+
}
|
|
955
|
+
for (const comp of project.components) {
|
|
956
|
+
console.log(chalk.green(" ✓ ") +
|
|
957
|
+
`${comp.fileCount} files in ${chalk.cyan(comp.path)}`);
|
|
958
|
+
}
|
|
959
|
+
for (const token of project.tokens) {
|
|
960
|
+
console.log(chalk.green(" ✓ ") + `${token.name}: ${chalk.cyan(token.path)}`);
|
|
961
|
+
}
|
|
962
|
+
if (project.storybook)
|
|
963
|
+
console.log(chalk.green(" ✓ ") + "Storybook");
|
|
964
|
+
console.log("");
|
|
965
|
+
}
|
|
966
|
+
async function showFrameworksAndPlugins(frameworks, autoInstall) {
|
|
967
|
+
const builtIn = frameworks.filter((fw) => fw.scanner);
|
|
968
|
+
const plugins = frameworks.filter((fw) => fw.plugin && !fw.scanner);
|
|
969
|
+
if (builtIn.length > 0) {
|
|
970
|
+
console.log(chalk.bold(" Built-in Scanners"));
|
|
971
|
+
for (const fw of builtIn)
|
|
972
|
+
console.log(` ${chalk.green("✓")} ${chalk.cyan(fw.name)}`);
|
|
973
|
+
console.log("");
|
|
974
|
+
}
|
|
975
|
+
if (plugins.length > 0) {
|
|
976
|
+
const missing = plugins
|
|
977
|
+
.map((fw) => fw.plugin)
|
|
978
|
+
.filter((p, i, arr) => arr.indexOf(p) === i);
|
|
979
|
+
console.log(chalk.bold(" Optional Plugins"));
|
|
980
|
+
console.log(` ${chalk.dim(getPluginInstallCommand(missing))}`);
|
|
981
|
+
console.log("");
|
|
982
|
+
if (autoInstall) {
|
|
983
|
+
const { execSync } = await import("node:child_process");
|
|
984
|
+
try {
|
|
985
|
+
execSync(getPluginInstallCommand(missing), { stdio: "inherit" });
|
|
986
|
+
success("Plugins installed");
|
|
987
|
+
}
|
|
988
|
+
catch {
|
|
989
|
+
warning("Plugin install failed");
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
async function promptConfirm(message, defaultValue = true) {
|
|
995
|
+
if (!process.stdin.isTTY)
|
|
996
|
+
return defaultValue;
|
|
997
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
998
|
+
return new Promise((resolve) => {
|
|
999
|
+
rl.question(`${message} ${defaultValue ? "[Y/n]" : "[y/N]"} `, (answer) => {
|
|
1000
|
+
rl.close();
|
|
1001
|
+
const t = answer.trim().toLowerCase();
|
|
1002
|
+
resolve(t === "" ? defaultValue : t === "y" || t === "yes");
|
|
1003
|
+
});
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
//# sourceMappingURL=dock.js.map
|