@buoy-design/cli 0.3.5 → 0.3.6
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/LICENSE +21 -0
- package/dist/commands/context.js +1 -1
- package/dist/commands/context.js.map +1 -1
- package/dist/commands/dock.d.ts +4 -2
- package/dist/commands/dock.d.ts.map +1 -1
- package/dist/commands/dock.js +19 -3
- package/dist/commands/dock.js.map +1 -1
- package/dist/commands/fix.js +2 -2
- package/dist/commands/fix.js.map +1 -1
- package/dist/commands/index.d.ts +0 -2
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +0 -2
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/show.d.ts.map +1 -1
- package/dist/commands/show.js +574 -2
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/skill.js +2 -2
- package/dist/commands/skill.js.map +1 -1
- package/dist/commands/tokens-lookup.d.ts +4 -4
- package/dist/commands/tokens-lookup.js +7 -7
- package/dist/commands/tokens-lookup.js.map +1 -1
- package/dist/commands/tokens.js +2 -2
- package/dist/commands/tokens.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -10
- package/dist/index.js.map +1 -1
- package/dist/insights/project-insights.js +1 -1
- package/dist/insights/project-insights.js.map +1 -1
- package/dist/integrations/github-formatter.js +1 -1
- package/dist/integrations/github-formatter.js.map +1 -1
- package/package.json +14 -14
package/dist/commands/show.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
-
import { existsSync } from "fs";
|
|
3
|
+
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
4
4
|
import { writeFileSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import { homedir } from "os";
|
|
5
7
|
import { loadConfig, getConfigPath } from "../config/loader.js";
|
|
6
8
|
import { buildAutoConfig } from "../config/auto-detect.js";
|
|
7
9
|
import { spinner, error, info, success, header, keyValue, newline, setJsonMode, } from "../output/reporters.js";
|
|
@@ -15,6 +17,8 @@ import { extractStyles, extractCssFileStyles } from "@buoy-design/scanners";
|
|
|
15
17
|
import { parseCssValues } from "@buoy-design/core";
|
|
16
18
|
import { glob } from "glob";
|
|
17
19
|
import { readFile } from "fs/promises";
|
|
20
|
+
import { detectHookSystem } from "../hooks/index.js";
|
|
21
|
+
import { detectFrameworks, BUILTIN_SCANNERS, PLUGIN_INFO, } from "../detect/frameworks.js";
|
|
18
22
|
export function createShowCommand() {
|
|
19
23
|
const cmd = new Command("show")
|
|
20
24
|
.description("Show design system information")
|
|
@@ -351,7 +355,7 @@ export function createShowCommand() {
|
|
|
351
355
|
if (!hasTokens && !hasFigma && !hasStorybook && !hasDesignTokensFile) {
|
|
352
356
|
newline();
|
|
353
357
|
info("No reference source configured.");
|
|
354
|
-
info("Run " + chalk.cyan("buoy
|
|
358
|
+
info("Run " + chalk.cyan("buoy dock tokens") + " to extract design tokens.");
|
|
355
359
|
}
|
|
356
360
|
}
|
|
357
361
|
}
|
|
@@ -739,6 +743,492 @@ export function createShowCommand() {
|
|
|
739
743
|
process.exit(1);
|
|
740
744
|
}
|
|
741
745
|
});
|
|
746
|
+
// show config
|
|
747
|
+
cmd
|
|
748
|
+
.command("config")
|
|
749
|
+
.description("Show current .buoy.yaml configuration")
|
|
750
|
+
.option("--json", "Output as JSON")
|
|
751
|
+
.action(async (options, command) => {
|
|
752
|
+
const parentOpts = command.parent?.opts() || {};
|
|
753
|
+
const json = options.json || parentOpts.json !== false;
|
|
754
|
+
if (json)
|
|
755
|
+
setJsonMode(true);
|
|
756
|
+
const configPath = getConfigPath();
|
|
757
|
+
if (!configPath) {
|
|
758
|
+
if (json) {
|
|
759
|
+
console.log(JSON.stringify({ exists: false, path: null }, null, 2));
|
|
760
|
+
}
|
|
761
|
+
else {
|
|
762
|
+
info("No config found. Run " + chalk.cyan("buoy dock config") + " to create .buoy.yaml.");
|
|
763
|
+
}
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
try {
|
|
767
|
+
const { config } = await loadConfig();
|
|
768
|
+
if (json) {
|
|
769
|
+
console.log(JSON.stringify({ exists: true, path: configPath, config }, null, 2));
|
|
770
|
+
}
|
|
771
|
+
else {
|
|
772
|
+
header("Configuration");
|
|
773
|
+
newline();
|
|
774
|
+
keyValue("Path", configPath);
|
|
775
|
+
if (config.project?.name)
|
|
776
|
+
keyValue("Project", config.project.name);
|
|
777
|
+
newline();
|
|
778
|
+
// Show enabled sources
|
|
779
|
+
if (config.sources) {
|
|
780
|
+
header("Sources");
|
|
781
|
+
for (const [key, source] of Object.entries(config.sources)) {
|
|
782
|
+
if (source && typeof source === "object" && "enabled" in source) {
|
|
783
|
+
const enabled = source.enabled;
|
|
784
|
+
const status = enabled ? chalk.green("enabled") : chalk.dim("disabled");
|
|
785
|
+
keyValue(` ${key}`, status);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
newline();
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
catch (err) {
|
|
793
|
+
error(err instanceof Error ? err.message : String(err));
|
|
794
|
+
process.exit(1);
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
// show skills
|
|
798
|
+
cmd
|
|
799
|
+
.command("skills")
|
|
800
|
+
.description("Show AI agent skill files")
|
|
801
|
+
.option("--json", "Output as JSON")
|
|
802
|
+
.action(async (options, command) => {
|
|
803
|
+
const parentOpts = command.parent?.opts() || {};
|
|
804
|
+
const json = options.json || parentOpts.json !== false;
|
|
805
|
+
if (json)
|
|
806
|
+
setJsonMode(true);
|
|
807
|
+
const cwd = process.cwd();
|
|
808
|
+
const skillsDir = join(cwd, ".claude", "skills", "design-system");
|
|
809
|
+
if (!existsSync(skillsDir)) {
|
|
810
|
+
if (json) {
|
|
811
|
+
console.log(JSON.stringify({ exists: false, path: skillsDir, files: [] }, null, 2));
|
|
812
|
+
}
|
|
813
|
+
else {
|
|
814
|
+
info("No skills found. Run " + chalk.cyan("buoy dock skills") + " to create AI agent skills.");
|
|
815
|
+
}
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
try {
|
|
819
|
+
const files = walkDir(skillsDir);
|
|
820
|
+
if (json) {
|
|
821
|
+
console.log(JSON.stringify({
|
|
822
|
+
exists: true,
|
|
823
|
+
path: skillsDir,
|
|
824
|
+
fileCount: files.length,
|
|
825
|
+
files: files.map(f => f.replace(cwd + "/", "")),
|
|
826
|
+
}, null, 2));
|
|
827
|
+
}
|
|
828
|
+
else {
|
|
829
|
+
header("Design System Skills");
|
|
830
|
+
newline();
|
|
831
|
+
keyValue("Path", skillsDir.replace(cwd + "/", ""));
|
|
832
|
+
keyValue("Files", String(files.length));
|
|
833
|
+
newline();
|
|
834
|
+
for (const file of files) {
|
|
835
|
+
console.log(` ${chalk.green("•")} ${file.replace(cwd + "/", "")}`);
|
|
836
|
+
}
|
|
837
|
+
newline();
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
catch (err) {
|
|
841
|
+
error(err instanceof Error ? err.message : String(err));
|
|
842
|
+
process.exit(1);
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
// show agents
|
|
846
|
+
cmd
|
|
847
|
+
.command("agents")
|
|
848
|
+
.description("Show configured AI agents and commands")
|
|
849
|
+
.option("--json", "Output as JSON")
|
|
850
|
+
.action(async (options, command) => {
|
|
851
|
+
const parentOpts = command.parent?.opts() || {};
|
|
852
|
+
const json = options.json || parentOpts.json !== false;
|
|
853
|
+
if (json)
|
|
854
|
+
setJsonMode(true);
|
|
855
|
+
const cwd = process.cwd();
|
|
856
|
+
const agentsDir = join(cwd, ".claude", "agents");
|
|
857
|
+
const commandsDir = join(cwd, ".claude", "commands");
|
|
858
|
+
const hasAgents = existsSync(agentsDir) && readdirSync(agentsDir).some(f => f.endsWith(".md"));
|
|
859
|
+
const hasCommands = existsSync(commandsDir) && readdirSync(commandsDir).some(f => f.endsWith(".md"));
|
|
860
|
+
if (!hasAgents && !hasCommands) {
|
|
861
|
+
if (json) {
|
|
862
|
+
console.log(JSON.stringify({ exists: false, agents: [], commands: [] }, null, 2));
|
|
863
|
+
}
|
|
864
|
+
else {
|
|
865
|
+
info("No agents configured. Run " + chalk.cyan("buoy dock agents") + " to set up AI agents.");
|
|
866
|
+
}
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
const agents = hasAgents
|
|
870
|
+
? readdirSync(agentsDir).filter(f => f.endsWith(".md")).map(f => ({
|
|
871
|
+
name: f.replace(".md", ""),
|
|
872
|
+
path: join(".claude", "agents", f),
|
|
873
|
+
}))
|
|
874
|
+
: [];
|
|
875
|
+
const commands = hasCommands
|
|
876
|
+
? readdirSync(commandsDir).filter(f => f.endsWith(".md")).map(f => ({
|
|
877
|
+
name: f.replace(".md", ""),
|
|
878
|
+
path: join(".claude", "commands", f),
|
|
879
|
+
}))
|
|
880
|
+
: [];
|
|
881
|
+
if (json) {
|
|
882
|
+
console.log(JSON.stringify({ exists: true, agents, commands }, null, 2));
|
|
883
|
+
}
|
|
884
|
+
else {
|
|
885
|
+
if (agents.length > 0) {
|
|
886
|
+
header("Agents");
|
|
887
|
+
newline();
|
|
888
|
+
for (const agent of agents) {
|
|
889
|
+
console.log(` ${chalk.green("•")} ${agent.name} ${chalk.dim(agent.path)}`);
|
|
890
|
+
}
|
|
891
|
+
newline();
|
|
892
|
+
}
|
|
893
|
+
if (commands.length > 0) {
|
|
894
|
+
header("Commands");
|
|
895
|
+
newline();
|
|
896
|
+
for (const cmd of commands) {
|
|
897
|
+
console.log(` ${chalk.green("•")} /${cmd.name} ${chalk.dim(cmd.path)}`);
|
|
898
|
+
}
|
|
899
|
+
newline();
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
});
|
|
903
|
+
// show context
|
|
904
|
+
cmd
|
|
905
|
+
.command("context")
|
|
906
|
+
.description("Show design system context in CLAUDE.md")
|
|
907
|
+
.option("--json", "Output as JSON")
|
|
908
|
+
.action(async (options, command) => {
|
|
909
|
+
const parentOpts = command.parent?.opts() || {};
|
|
910
|
+
const json = options.json || parentOpts.json !== false;
|
|
911
|
+
if (json)
|
|
912
|
+
setJsonMode(true);
|
|
913
|
+
const cwd = process.cwd();
|
|
914
|
+
const claudeMdPath = join(cwd, "CLAUDE.md");
|
|
915
|
+
if (!existsSync(claudeMdPath)) {
|
|
916
|
+
if (json) {
|
|
917
|
+
console.log(JSON.stringify({ exists: false, path: claudeMdPath }, null, 2));
|
|
918
|
+
}
|
|
919
|
+
else {
|
|
920
|
+
info("No design system context in CLAUDE.md. Run " + chalk.cyan("buoy dock context") + " to generate it.");
|
|
921
|
+
}
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
const content = readFileSync(claudeMdPath, "utf-8");
|
|
925
|
+
const sectionMatch = content.match(/^##?\s*Design\s*System\b.*$/m);
|
|
926
|
+
if (!sectionMatch) {
|
|
927
|
+
if (json) {
|
|
928
|
+
console.log(JSON.stringify({ exists: false, path: claudeMdPath, hasSection: false }, null, 2));
|
|
929
|
+
}
|
|
930
|
+
else {
|
|
931
|
+
info("No design system context in CLAUDE.md. Run " + chalk.cyan("buoy dock context") + " to generate it.");
|
|
932
|
+
}
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
// Extract section content between ## Design System and next ## header
|
|
936
|
+
const sectionStart = content.indexOf(sectionMatch[0]);
|
|
937
|
+
const afterHeader = content.slice(sectionStart + sectionMatch[0].length);
|
|
938
|
+
const nextHeaderMatch = afterHeader.match(/\n##?\s+[^\n]/);
|
|
939
|
+
const sectionContent = nextHeaderMatch
|
|
940
|
+
? afterHeader.slice(0, nextHeaderMatch.index)
|
|
941
|
+
: afterHeader;
|
|
942
|
+
const sectionLines = sectionContent.trim().split("\n").length;
|
|
943
|
+
const sectionWords = sectionContent.trim().split(/\s+/).length;
|
|
944
|
+
if (json) {
|
|
945
|
+
console.log(JSON.stringify({
|
|
946
|
+
exists: true,
|
|
947
|
+
path: claudeMdPath,
|
|
948
|
+
hasSection: true,
|
|
949
|
+
sectionLines,
|
|
950
|
+
sectionWords,
|
|
951
|
+
}, null, 2));
|
|
952
|
+
}
|
|
953
|
+
else {
|
|
954
|
+
header("Design System Context");
|
|
955
|
+
newline();
|
|
956
|
+
keyValue("Path", "CLAUDE.md");
|
|
957
|
+
keyValue("Lines", String(sectionLines));
|
|
958
|
+
keyValue("Words", String(sectionWords));
|
|
959
|
+
newline();
|
|
960
|
+
// Show preview (first 5 lines)
|
|
961
|
+
const preview = sectionContent.trim().split("\n").slice(0, 5);
|
|
962
|
+
for (const line of preview) {
|
|
963
|
+
console.log(chalk.dim(` ${line}`));
|
|
964
|
+
}
|
|
965
|
+
if (sectionLines > 5) {
|
|
966
|
+
console.log(chalk.dim(` ... ${sectionLines - 5} more lines`));
|
|
967
|
+
}
|
|
968
|
+
newline();
|
|
969
|
+
}
|
|
970
|
+
});
|
|
971
|
+
// show hooks
|
|
972
|
+
cmd
|
|
973
|
+
.command("hooks")
|
|
974
|
+
.description("Show configured hooks for drift checking")
|
|
975
|
+
.option("--json", "Output as JSON")
|
|
976
|
+
.action(async (options, command) => {
|
|
977
|
+
const parentOpts = command.parent?.opts() || {};
|
|
978
|
+
const json = options.json || parentOpts.json !== false;
|
|
979
|
+
if (json)
|
|
980
|
+
setJsonMode(true);
|
|
981
|
+
const cwd = process.cwd();
|
|
982
|
+
// Detect git hook system
|
|
983
|
+
const hookSystem = detectHookSystem(cwd);
|
|
984
|
+
// Check for buoy in the hook
|
|
985
|
+
let gitHookInstalled = false;
|
|
986
|
+
let gitHookPath = null;
|
|
987
|
+
if (hookSystem === "husky") {
|
|
988
|
+
gitHookPath = join(cwd, ".husky", "pre-commit");
|
|
989
|
+
}
|
|
990
|
+
else if (hookSystem === "pre-commit") {
|
|
991
|
+
gitHookPath = join(cwd, ".pre-commit-config.yaml");
|
|
992
|
+
}
|
|
993
|
+
else if (hookSystem === "git") {
|
|
994
|
+
gitHookPath = join(cwd, ".git", "hooks", "pre-commit");
|
|
995
|
+
}
|
|
996
|
+
if (gitHookPath && existsSync(gitHookPath)) {
|
|
997
|
+
try {
|
|
998
|
+
const hookContent = readFileSync(gitHookPath, "utf-8");
|
|
999
|
+
gitHookInstalled = hookContent.includes("buoy");
|
|
1000
|
+
}
|
|
1001
|
+
catch {
|
|
1002
|
+
// Ignore read errors
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
// Check for Claude hooks
|
|
1006
|
+
const claudeSettingsPath = join(cwd, ".claude", "settings.local.json");
|
|
1007
|
+
let claudeHooksEnabled = false;
|
|
1008
|
+
let claudeEvents = [];
|
|
1009
|
+
if (existsSync(claudeSettingsPath)) {
|
|
1010
|
+
try {
|
|
1011
|
+
const settings = JSON.parse(readFileSync(claudeSettingsPath, "utf-8"));
|
|
1012
|
+
const hooks = settings.hooks;
|
|
1013
|
+
if (hooks) {
|
|
1014
|
+
if (hooks.SessionStart?.some((h) => h.hooks?.some(hk => hk.command?.includes("buoy") || hk.command?.includes("Design system")))) {
|
|
1015
|
+
claudeHooksEnabled = true;
|
|
1016
|
+
claudeEvents.push("SessionStart");
|
|
1017
|
+
}
|
|
1018
|
+
if (hooks.PostToolUse?.some((h) => h.hooks?.some(hk => hk.command?.includes("buoy")))) {
|
|
1019
|
+
claudeHooksEnabled = true;
|
|
1020
|
+
claudeEvents.push("PostToolUse");
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
catch {
|
|
1025
|
+
// Ignore parse errors
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
if (!gitHookInstalled && !claudeHooksEnabled) {
|
|
1029
|
+
if (json) {
|
|
1030
|
+
console.log(JSON.stringify({
|
|
1031
|
+
gitHooks: { type: hookSystem, installed: false },
|
|
1032
|
+
claudeHooks: { enabled: false, events: [] },
|
|
1033
|
+
}, null, 2));
|
|
1034
|
+
}
|
|
1035
|
+
else {
|
|
1036
|
+
info("No hooks configured. Run " + chalk.cyan("buoy dock hooks") + " to set up drift checking.");
|
|
1037
|
+
}
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
if (json) {
|
|
1041
|
+
console.log(JSON.stringify({
|
|
1042
|
+
gitHooks: { type: hookSystem, path: gitHookPath, installed: gitHookInstalled },
|
|
1043
|
+
claudeHooks: { enabled: claudeHooksEnabled, events: claudeEvents },
|
|
1044
|
+
}, null, 2));
|
|
1045
|
+
}
|
|
1046
|
+
else {
|
|
1047
|
+
header("Hooks");
|
|
1048
|
+
newline();
|
|
1049
|
+
if (gitHookInstalled) {
|
|
1050
|
+
console.log(` ${chalk.green("✓")} Git pre-commit hook (${hookSystem})`);
|
|
1051
|
+
if (gitHookPath)
|
|
1052
|
+
keyValue(" Path", gitHookPath.replace(cwd + "/", ""));
|
|
1053
|
+
}
|
|
1054
|
+
else {
|
|
1055
|
+
console.log(` ${chalk.dim("○")} Git pre-commit hook not installed`);
|
|
1056
|
+
}
|
|
1057
|
+
if (claudeHooksEnabled) {
|
|
1058
|
+
console.log(` ${chalk.green("✓")} Claude Code hooks`);
|
|
1059
|
+
keyValue(" Events", claudeEvents.join(", "));
|
|
1060
|
+
}
|
|
1061
|
+
else {
|
|
1062
|
+
console.log(` ${chalk.dim("○")} Claude Code hooks not configured`);
|
|
1063
|
+
}
|
|
1064
|
+
newline();
|
|
1065
|
+
}
|
|
1066
|
+
});
|
|
1067
|
+
// show commands
|
|
1068
|
+
cmd
|
|
1069
|
+
.command("commands")
|
|
1070
|
+
.description("Show installed slash commands")
|
|
1071
|
+
.option("--json", "Output as JSON")
|
|
1072
|
+
.action(async (options, command) => {
|
|
1073
|
+
const parentOpts = command.parent?.opts() || {};
|
|
1074
|
+
const json = options.json || parentOpts.json !== false;
|
|
1075
|
+
if (json)
|
|
1076
|
+
setJsonMode(true);
|
|
1077
|
+
const commandsDir = join(homedir(), ".claude", "commands");
|
|
1078
|
+
if (!existsSync(commandsDir)) {
|
|
1079
|
+
if (json) {
|
|
1080
|
+
console.log(JSON.stringify({ commands: [] }, null, 2));
|
|
1081
|
+
}
|
|
1082
|
+
else {
|
|
1083
|
+
info("No slash commands installed. Run " + chalk.cyan("buoy dock commands install") + " to set them up.");
|
|
1084
|
+
}
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
const buoyCommands = readdirSync(commandsDir)
|
|
1088
|
+
.filter(f => f.endsWith(".md"))
|
|
1089
|
+
.map(f => ({
|
|
1090
|
+
name: f.replace(".md", ""),
|
|
1091
|
+
installed: true,
|
|
1092
|
+
}));
|
|
1093
|
+
if (buoyCommands.length === 0) {
|
|
1094
|
+
if (json) {
|
|
1095
|
+
console.log(JSON.stringify({ commands: [] }, null, 2));
|
|
1096
|
+
}
|
|
1097
|
+
else {
|
|
1098
|
+
info("No slash commands installed. Run " + chalk.cyan("buoy dock commands install") + " to set them up.");
|
|
1099
|
+
}
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
if (json) {
|
|
1103
|
+
console.log(JSON.stringify({ commands: buoyCommands }, null, 2));
|
|
1104
|
+
}
|
|
1105
|
+
else {
|
|
1106
|
+
header("Slash Commands");
|
|
1107
|
+
newline();
|
|
1108
|
+
for (const cmd of buoyCommands) {
|
|
1109
|
+
console.log(` ${chalk.green("✓")} /${cmd.name}`);
|
|
1110
|
+
}
|
|
1111
|
+
newline();
|
|
1112
|
+
}
|
|
1113
|
+
});
|
|
1114
|
+
// show graph
|
|
1115
|
+
cmd
|
|
1116
|
+
.command("graph")
|
|
1117
|
+
.description("Show knowledge graph statistics")
|
|
1118
|
+
.option("--json", "Output as JSON")
|
|
1119
|
+
.action(async (options, command) => {
|
|
1120
|
+
const parentOpts = command.parent?.opts() || {};
|
|
1121
|
+
const json = options.json || parentOpts.json !== false;
|
|
1122
|
+
if (json)
|
|
1123
|
+
setJsonMode(true);
|
|
1124
|
+
const cwd = process.cwd();
|
|
1125
|
+
const graphPath = join(cwd, ".buoy", "graph.json");
|
|
1126
|
+
if (!existsSync(graphPath)) {
|
|
1127
|
+
if (json) {
|
|
1128
|
+
console.log(JSON.stringify({ exists: false }, null, 2));
|
|
1129
|
+
}
|
|
1130
|
+
else {
|
|
1131
|
+
info("No knowledge graph built. Run " + chalk.cyan("buoy dock graph") + " to build it.");
|
|
1132
|
+
}
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
try {
|
|
1136
|
+
const { importFromJSON, getGraphStats } = await import("@buoy-design/core");
|
|
1137
|
+
const graphData = JSON.parse(readFileSync(graphPath, "utf-8"));
|
|
1138
|
+
const graph = importFromJSON(graphData);
|
|
1139
|
+
const stats = getGraphStats(graph);
|
|
1140
|
+
if (json) {
|
|
1141
|
+
console.log(JSON.stringify({
|
|
1142
|
+
exists: true,
|
|
1143
|
+
path: graphPath,
|
|
1144
|
+
nodes: stats.nodeCount,
|
|
1145
|
+
edges: stats.edgeCount,
|
|
1146
|
+
nodesByType: stats.nodesByType,
|
|
1147
|
+
edgesByType: stats.edgesByType,
|
|
1148
|
+
}, null, 2));
|
|
1149
|
+
}
|
|
1150
|
+
else {
|
|
1151
|
+
header("Knowledge Graph");
|
|
1152
|
+
newline();
|
|
1153
|
+
keyValue("Path", ".buoy/graph.json");
|
|
1154
|
+
keyValue("Nodes", String(stats.nodeCount));
|
|
1155
|
+
keyValue("Edges", String(stats.edgeCount));
|
|
1156
|
+
newline();
|
|
1157
|
+
if (Object.keys(stats.nodesByType).length > 0) {
|
|
1158
|
+
header("Nodes by Type");
|
|
1159
|
+
for (const [type, count] of Object.entries(stats.nodesByType)) {
|
|
1160
|
+
keyValue(` ${type}`, String(count));
|
|
1161
|
+
}
|
|
1162
|
+
newline();
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
catch (err) {
|
|
1167
|
+
error(err instanceof Error ? err.message : String(err));
|
|
1168
|
+
process.exit(1);
|
|
1169
|
+
}
|
|
1170
|
+
});
|
|
1171
|
+
// show plugins
|
|
1172
|
+
cmd
|
|
1173
|
+
.command("plugins")
|
|
1174
|
+
.description("Show available scanners and plugins")
|
|
1175
|
+
.option("--json", "Output as JSON")
|
|
1176
|
+
.action(async (options, command) => {
|
|
1177
|
+
const parentOpts = command.parent?.opts() || {};
|
|
1178
|
+
const json = options.json || parentOpts.json !== false;
|
|
1179
|
+
if (json)
|
|
1180
|
+
setJsonMode(true);
|
|
1181
|
+
const cwd = process.cwd();
|
|
1182
|
+
const detected = await detectFrameworks(cwd);
|
|
1183
|
+
const builtIn = Object.entries(BUILTIN_SCANNERS).map(([key, info]) => ({
|
|
1184
|
+
key,
|
|
1185
|
+
description: info.description,
|
|
1186
|
+
detects: info.detects,
|
|
1187
|
+
}));
|
|
1188
|
+
const optional = Object.entries(PLUGIN_INFO).map(([key, info]) => ({
|
|
1189
|
+
key,
|
|
1190
|
+
name: info.name,
|
|
1191
|
+
description: info.description,
|
|
1192
|
+
}));
|
|
1193
|
+
if (json) {
|
|
1194
|
+
console.log(JSON.stringify({
|
|
1195
|
+
builtIn,
|
|
1196
|
+
detected: detected.map(fw => ({
|
|
1197
|
+
name: fw.name,
|
|
1198
|
+
scanner: fw.scanner,
|
|
1199
|
+
plugin: fw.plugin,
|
|
1200
|
+
confidence: fw.confidence,
|
|
1201
|
+
})),
|
|
1202
|
+
optional,
|
|
1203
|
+
}, null, 2));
|
|
1204
|
+
}
|
|
1205
|
+
else {
|
|
1206
|
+
header("Built-in Scanners");
|
|
1207
|
+
newline();
|
|
1208
|
+
for (const scanner of builtIn) {
|
|
1209
|
+
console.log(` ${chalk.green("✓")} ${chalk.cyan(scanner.description)}`);
|
|
1210
|
+
console.log(` ${chalk.dim(`Detects: ${scanner.detects}`)}`);
|
|
1211
|
+
}
|
|
1212
|
+
newline();
|
|
1213
|
+
if (detected.length > 0) {
|
|
1214
|
+
header("Detected Frameworks");
|
|
1215
|
+
newline();
|
|
1216
|
+
for (const fw of detected) {
|
|
1217
|
+
console.log(` ${chalk.green("•")} ${fw.name} ${chalk.dim(`(${fw.confidence})`)}`);
|
|
1218
|
+
}
|
|
1219
|
+
newline();
|
|
1220
|
+
}
|
|
1221
|
+
if (optional.length > 0) {
|
|
1222
|
+
header("Optional Plugins");
|
|
1223
|
+
newline();
|
|
1224
|
+
for (const plugin of optional) {
|
|
1225
|
+
console.log(` ${chalk.dim("○")} ${chalk.cyan(plugin.name)}`);
|
|
1226
|
+
console.log(` ${chalk.dim(plugin.description)}`);
|
|
1227
|
+
}
|
|
1228
|
+
newline();
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
});
|
|
742
1232
|
// show all
|
|
743
1233
|
cmd
|
|
744
1234
|
.command("all")
|
|
@@ -785,6 +1275,9 @@ export function createShowCommand() {
|
|
|
785
1275
|
pathPatterns: aggregationConfig.pathPatterns,
|
|
786
1276
|
});
|
|
787
1277
|
const aggregated = aggregator.aggregate(driftResult.drifts);
|
|
1278
|
+
// Gather setup status
|
|
1279
|
+
const cwd = process.cwd();
|
|
1280
|
+
const setup = getSetupStatus(cwd);
|
|
788
1281
|
const output = {
|
|
789
1282
|
components: scanResult.components,
|
|
790
1283
|
tokens: scanResult.tokens,
|
|
@@ -814,6 +1307,7 @@ export function createShowCommand() {
|
|
|
814
1307
|
categories: healthReport.categories,
|
|
815
1308
|
worstFiles: healthReport.worstFiles.slice(0, 5),
|
|
816
1309
|
},
|
|
1310
|
+
setup,
|
|
817
1311
|
};
|
|
818
1312
|
console.log(JSON.stringify(output, null, 2));
|
|
819
1313
|
}
|
|
@@ -835,6 +1329,69 @@ async function getOrBuildConfig() {
|
|
|
835
1329
|
const autoResult = await buildAutoConfig(process.cwd());
|
|
836
1330
|
return autoResult.config;
|
|
837
1331
|
}
|
|
1332
|
+
// Helper: Get setup status for all dock tools
|
|
1333
|
+
function getSetupStatus(cwd) {
|
|
1334
|
+
const hasConfig = !!getConfigPath();
|
|
1335
|
+
const hasSkills = existsSync(join(cwd, ".claude", "skills", "design-system"));
|
|
1336
|
+
const agentsDir = join(cwd, ".claude", "agents");
|
|
1337
|
+
const hasAgents = existsSync(agentsDir) && readdirSync(agentsDir).some(f => f.endsWith(".md"));
|
|
1338
|
+
// Check CLAUDE.md for Design System section
|
|
1339
|
+
const claudeMdPath = join(cwd, "CLAUDE.md");
|
|
1340
|
+
let hasContext = false;
|
|
1341
|
+
if (existsSync(claudeMdPath)) {
|
|
1342
|
+
try {
|
|
1343
|
+
const content = readFileSync(claudeMdPath, "utf-8");
|
|
1344
|
+
hasContext = /^##?\s*Design\s*System/m.test(content);
|
|
1345
|
+
}
|
|
1346
|
+
catch {
|
|
1347
|
+
// Ignore read errors
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
// Check hooks
|
|
1351
|
+
const hookSystem = detectHookSystem(cwd);
|
|
1352
|
+
let gitHookInstalled = false;
|
|
1353
|
+
if (hookSystem === "husky") {
|
|
1354
|
+
const p = join(cwd, ".husky", "pre-commit");
|
|
1355
|
+
if (existsSync(p)) {
|
|
1356
|
+
try {
|
|
1357
|
+
gitHookInstalled = readFileSync(p, "utf-8").includes("buoy");
|
|
1358
|
+
}
|
|
1359
|
+
catch { /* skip */ }
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
else if (hookSystem === "git") {
|
|
1363
|
+
const p = join(cwd, ".git", "hooks", "pre-commit");
|
|
1364
|
+
if (existsSync(p)) {
|
|
1365
|
+
try {
|
|
1366
|
+
gitHookInstalled = readFileSync(p, "utf-8").includes("buoy");
|
|
1367
|
+
}
|
|
1368
|
+
catch { /* skip */ }
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
let claudeHooksEnabled = false;
|
|
1372
|
+
const claudeSettingsPath = join(cwd, ".claude", "settings.local.json");
|
|
1373
|
+
if (existsSync(claudeSettingsPath)) {
|
|
1374
|
+
try {
|
|
1375
|
+
const settings = JSON.parse(readFileSync(claudeSettingsPath, "utf-8"));
|
|
1376
|
+
claudeHooksEnabled = !!settings.hooks?.SessionStart?.some((h) => h.hooks?.some(hk => hk.command?.includes("buoy") || hk.command?.includes("Design system")));
|
|
1377
|
+
}
|
|
1378
|
+
catch { /* skip */ }
|
|
1379
|
+
}
|
|
1380
|
+
// Check commands
|
|
1381
|
+
const commandsDir = join(homedir(), ".claude", "commands");
|
|
1382
|
+
const hasCommands = existsSync(commandsDir) && readdirSync(commandsDir).some(f => f.endsWith(".md"));
|
|
1383
|
+
// Check graph
|
|
1384
|
+
const hasGraph = existsSync(join(cwd, ".buoy", "graph.json"));
|
|
1385
|
+
return {
|
|
1386
|
+
config: hasConfig,
|
|
1387
|
+
skills: hasSkills,
|
|
1388
|
+
agents: hasAgents,
|
|
1389
|
+
context: hasContext,
|
|
1390
|
+
hooks: { git: gitHookInstalled, claude: claudeHooksEnabled },
|
|
1391
|
+
commands: hasCommands,
|
|
1392
|
+
graph: hasGraph,
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
838
1395
|
// Helper: Extract all hardcoded values for health audit
|
|
839
1396
|
async function extractAllValues(spin) {
|
|
840
1397
|
const cwd = process.cwd();
|
|
@@ -1020,6 +1577,21 @@ function fuzzyScore(query, target) {
|
|
|
1020
1577
|
}
|
|
1021
1578
|
return 0;
|
|
1022
1579
|
}
|
|
1580
|
+
function walkDir(dir) {
|
|
1581
|
+
const files = [];
|
|
1582
|
+
if (!existsSync(dir))
|
|
1583
|
+
return files;
|
|
1584
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
1585
|
+
const fullPath = join(dir, entry.name);
|
|
1586
|
+
if (entry.isDirectory()) {
|
|
1587
|
+
files.push(...walkDir(fullPath));
|
|
1588
|
+
}
|
|
1589
|
+
else {
|
|
1590
|
+
files.push(fullPath);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
return files;
|
|
1594
|
+
}
|
|
1023
1595
|
function formatRelativeDate(date) {
|
|
1024
1596
|
const now = new Date();
|
|
1025
1597
|
const diff = now.getTime() - date.getTime();
|