@structured-world/gitlab-mcp 6.13.0 → 6.15.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.
@@ -39,6 +39,7 @@ const fs = __importStar(require("fs"));
39
39
  const path = __importStar(require("path"));
40
40
  const registry_manager_1 = require("../registry-manager");
41
41
  const ToolAvailability_1 = require("../services/ToolAvailability");
42
+ const profiles_1 = require("../profiles");
42
43
  function parseArgs() {
43
44
  const args = process.argv.slice(2);
44
45
  const options = {
@@ -49,6 +50,9 @@ function parseArgs() {
49
50
  detail: false,
50
51
  noExamples: false,
51
52
  toc: false,
53
+ showPresets: false,
54
+ showProfiles: false,
55
+ validate: false,
52
56
  };
53
57
  for (let i = 0; i < args.length; i++) {
54
58
  const arg = args[i];
@@ -97,6 +101,39 @@ function parseArgs() {
97
101
  case "--toc":
98
102
  options.toc = true;
99
103
  break;
104
+ case "--presets":
105
+ options.showPresets = true;
106
+ break;
107
+ case "--profiles":
108
+ options.showProfiles = true;
109
+ break;
110
+ case "--preset":
111
+ if (i + 1 >= args.length) {
112
+ console.error("Error: --preset flag requires a value.");
113
+ console.error("Usage: yarn list-tools --preset <preset_name>");
114
+ process.exit(1);
115
+ }
116
+ options.preset = args[++i];
117
+ break;
118
+ case "--profile":
119
+ if (i + 1 >= args.length) {
120
+ console.error("Error: --profile flag requires a value.");
121
+ console.error("Usage: yarn list-tools --profile <profile_name>");
122
+ process.exit(1);
123
+ }
124
+ options.profile = args[++i];
125
+ break;
126
+ case "--validate":
127
+ options.validate = true;
128
+ break;
129
+ case "--compare":
130
+ if (i + 1 >= args.length) {
131
+ console.error("Error: --compare flag requires a value.");
132
+ console.error("Usage: yarn list-tools --preset <name> --compare <other_name>");
133
+ process.exit(1);
134
+ }
135
+ options.compare = args[++i];
136
+ break;
100
137
  case "--help":
101
138
  case "-h":
102
139
  printHelp();
@@ -119,7 +156,7 @@ GitLab MCP Tool Lister
119
156
 
120
157
  Usage: yarn list-tools [options]
121
158
 
122
- Options:
159
+ Tool Options:
123
160
  --json Output in JSON format
124
161
  --simple Simple list of tool names
125
162
  --export Generate complete TOOLS.md documentation
@@ -131,6 +168,16 @@ Options:
131
168
  --detail Show all tools with their input schemas
132
169
  --no-examples Skip example JSON blocks (for --export)
133
170
  --toc Include table of contents (for --export)
171
+
172
+ Profile/Preset Options:
173
+ --presets List all available presets (built-in and user)
174
+ --profiles List user-defined profiles
175
+ --preset <name> Inspect a specific preset
176
+ --profile <name> Inspect a specific profile
177
+ --validate Validate configuration (use with --preset or --profile)
178
+ --compare <name> Compare two presets (use with --preset)
179
+
180
+ General:
134
181
  --help, -h Show this help
135
182
 
136
183
  Examples:
@@ -144,8 +191,14 @@ Examples:
144
191
  yarn list-tools --env-gates --json # JSON output of gates
145
192
  yarn list-tools --entity workitems # Only work items tools
146
193
  yarn list-tools --tool list_work_items # Specific tool details
147
- GITLAB_READONLY=true yarn list-tools # Show read-only tools
148
- GITLAB_DENIED_TOOLS_REGEX="^create" yarn list-tools # Test filtering
194
+
195
+ Profile/Preset Examples:
196
+ yarn list-tools --presets # List all presets
197
+ yarn list-tools --profiles # List user profiles
198
+ yarn list-tools --preset junior-dev # Inspect preset details
199
+ yarn list-tools --preset junior-dev --validate # Validate preset
200
+ yarn list-tools --preset junior-dev --compare senior-dev # Compare presets
201
+ yarn list-tools --presets --json # JSON output of presets
149
202
 
150
203
  Environment Variables:
151
204
  GITLAB_READONLY Show only read-only tools
@@ -715,8 +768,480 @@ function printEnvGatesMarkdown(gates, ungatedTools, format) {
715
768
  console.log("USE_PIPELINE=false");
716
769
  console.log("```");
717
770
  }
771
+ const FEATURE_TO_TOOLS = {
772
+ wiki: ["browse_wiki", "manage_wiki"],
773
+ milestones: ["browse_milestones", "manage_milestone"],
774
+ pipelines: ["browse_pipelines", "manage_pipeline", "manage_pipeline_job"],
775
+ labels: ["browse_labels", "manage_label"],
776
+ mrs: [
777
+ "browse_merge_requests",
778
+ "browse_mr_discussions",
779
+ "manage_merge_request",
780
+ "manage_mr_discussion",
781
+ "manage_draft_notes",
782
+ ],
783
+ files: ["browse_files", "manage_files"],
784
+ variables: ["browse_variables", "manage_variable"],
785
+ workitems: ["browse_work_items", "manage_work_item"],
786
+ webhooks: ["list_webhooks", "manage_webhook"],
787
+ snippets: ["browse_snippets", "manage_snippet"],
788
+ integrations: ["list_integrations", "manage_integration"],
789
+ };
790
+ const FEATURE_NAMES = Object.keys(FEATURE_TO_TOOLS);
791
+ function countToolsForPreset(preset, allToolNames) {
792
+ let enabledTools = allToolNames;
793
+ if (preset.read_only) {
794
+ enabledTools = enabledTools.filter(name => !name.startsWith("manage_"));
795
+ }
796
+ if (preset.denied_tools_regex) {
797
+ try {
798
+ const regex = new RegExp(preset.denied_tools_regex);
799
+ enabledTools = enabledTools.filter(name => !regex.test(name));
800
+ }
801
+ catch (error) {
802
+ console.warn(`Warning: invalid denied_tools_regex "${preset.denied_tools_regex}": ${error instanceof Error ? error.message : "unknown error"}`);
803
+ }
804
+ }
805
+ if (preset.allowed_tools && preset.allowed_tools.length > 0) {
806
+ const allowedSet = new Set(preset.allowed_tools);
807
+ enabledTools = enabledTools.filter(name => allowedSet.has(name));
808
+ }
809
+ if (preset.features) {
810
+ for (const [feature, tools] of Object.entries(FEATURE_TO_TOOLS)) {
811
+ const featureKey = feature;
812
+ if (preset.features[featureKey] === false) {
813
+ const toolSet = new Set(tools);
814
+ enabledTools = enabledTools.filter(name => !toolSet.has(name));
815
+ }
816
+ }
817
+ }
818
+ return enabledTools.length;
819
+ }
820
+ function getToolsForPreset(preset, allToolNames) {
821
+ let enabledTools = [...allToolNames];
822
+ const disabledTools = [];
823
+ if (preset.read_only) {
824
+ const manageTools = enabledTools.filter(name => name.startsWith("manage_"));
825
+ disabledTools.push(...manageTools);
826
+ enabledTools = enabledTools.filter(name => !name.startsWith("manage_"));
827
+ }
828
+ if (preset.denied_tools_regex) {
829
+ try {
830
+ const regex = new RegExp(preset.denied_tools_regex);
831
+ const denied = enabledTools.filter(name => regex.test(name));
832
+ disabledTools.push(...denied);
833
+ enabledTools = enabledTools.filter(name => !regex.test(name));
834
+ }
835
+ catch (error) {
836
+ console.warn(`Warning: invalid denied_tools_regex "${preset.denied_tools_regex}": ${error instanceof Error ? error.message : "unknown error"}`);
837
+ }
838
+ }
839
+ if (preset.allowed_tools && preset.allowed_tools.length > 0) {
840
+ const allowedSet = new Set(preset.allowed_tools);
841
+ const notAllowed = enabledTools.filter(name => !allowedSet.has(name));
842
+ disabledTools.push(...notAllowed);
843
+ enabledTools = enabledTools.filter(name => allowedSet.has(name));
844
+ }
845
+ if (preset.features) {
846
+ for (const [feature, tools] of Object.entries(FEATURE_TO_TOOLS)) {
847
+ const featureKey = feature;
848
+ if (preset.features[featureKey] === false) {
849
+ const toolSet = new Set(tools);
850
+ const disabled = enabledTools.filter(name => toolSet.has(name));
851
+ disabledTools.push(...disabled);
852
+ enabledTools = enabledTools.filter(name => !toolSet.has(name));
853
+ }
854
+ }
855
+ }
856
+ return {
857
+ enabled: enabledTools.sort(),
858
+ disabled: [...new Set(disabledTools)].sort(),
859
+ };
860
+ }
861
+ async function printPresetsList(loader, allToolNames, format) {
862
+ const profiles = await loader.listProfiles();
863
+ const presets = profiles.filter(p => p.isPreset);
864
+ const userProfiles = profiles.filter(p => !p.isPreset);
865
+ if (format === "json") {
866
+ const output = {
867
+ builtIn: await Promise.all(presets.map(async (p) => {
868
+ const preset = await loader.loadPreset(p.name);
869
+ return {
870
+ name: p.name,
871
+ description: p.description ?? "",
872
+ readOnly: p.readOnly,
873
+ toolCount: countToolsForPreset(preset, allToolNames),
874
+ };
875
+ })),
876
+ userPresets: userProfiles.length,
877
+ totalTools: allToolNames.length,
878
+ };
879
+ console.log(JSON.stringify(output, null, 2));
880
+ return;
881
+ }
882
+ console.log("# Available Presets\n");
883
+ console.log(`Total tools available: ${allToolNames.length}\n`);
884
+ console.log("## Built-in Presets\n");
885
+ console.log("| Preset | Tools | Read-Only | Description |");
886
+ console.log("|--------|-------|-----------|-------------|");
887
+ for (const p of presets) {
888
+ const preset = await loader.loadPreset(p.name);
889
+ const toolCount = countToolsForPreset(preset, allToolNames);
890
+ const ro = p.readOnly ? "Yes" : "No";
891
+ const desc = p.description ?? "-";
892
+ console.log(`| \`${p.name}\` | ${toolCount} | ${ro} | ${desc} |`);
893
+ }
894
+ if (userProfiles.length > 0) {
895
+ console.log("\n## User Profiles\n");
896
+ console.log(`${userProfiles.length} user profile(s) defined. Use \`--profiles\` to list them.`);
897
+ }
898
+ console.log("\nUse `yarn list-tools --preset <name>` for details.");
899
+ }
900
+ async function printProfilesList(loader, format) {
901
+ const profiles = await loader.listProfiles();
902
+ const userProfiles = profiles.filter(p => !p.isPreset);
903
+ if (format === "json") {
904
+ const output = userProfiles.map(p => ({
905
+ name: p.name,
906
+ host: p.host,
907
+ authType: p.authType,
908
+ readOnly: p.readOnly,
909
+ }));
910
+ console.log(JSON.stringify(output, null, 2));
911
+ return;
912
+ }
913
+ console.log("# User Profiles\n");
914
+ if (userProfiles.length === 0) {
915
+ console.log("No user profiles defined.\n");
916
+ console.log("Create profiles in: `~/.config/gitlab-mcp/profiles.yaml`\n");
917
+ console.log("Example:\n");
918
+ console.log("```yaml");
919
+ console.log("profiles:");
920
+ console.log(" work:");
921
+ console.log(" host: gitlab.company.com");
922
+ console.log(" auth:");
923
+ console.log(" type: pat");
924
+ console.log(" token_env: GITLAB_WORK_TOKEN");
925
+ console.log("```");
926
+ return;
927
+ }
928
+ console.log("| Profile | Host | Auth | Read-Only |");
929
+ console.log("|---------|------|------|-----------|");
930
+ for (const p of userProfiles) {
931
+ const ro = p.readOnly ? "Yes" : "No";
932
+ console.log(`| \`${p.name}\` | ${p.host ?? "-"} | ${p.authType ?? "-"} | ${ro} |`);
933
+ }
934
+ console.log("\nUse `yarn list-tools --profile <name>` for details.");
935
+ }
936
+ async function printPresetDetails(loader, presetName, allToolNames, format, validate) {
937
+ let preset;
938
+ try {
939
+ preset = await loader.loadPreset(presetName);
940
+ }
941
+ catch {
942
+ console.error(`Error: Preset '${presetName}' not found`);
943
+ process.exit(1);
944
+ return;
945
+ }
946
+ const { enabled, disabled } = getToolsForPreset(preset, allToolNames);
947
+ if (format === "json") {
948
+ const output = {
949
+ name: presetName,
950
+ type: "builtin",
951
+ description: preset.description ?? null,
952
+ readOnly: preset.read_only ?? false,
953
+ toolsEnabled: enabled.length,
954
+ toolsDisabled: disabled.length,
955
+ features: preset.features ?? {},
956
+ deniedToolsRegex: preset.denied_tools_regex ?? null,
957
+ allowedTools: preset.allowed_tools ?? null,
958
+ deniedActions: preset.denied_actions ?? null,
959
+ enabledTools: enabled,
960
+ disabledTools: disabled,
961
+ };
962
+ if (validate) {
963
+ const validation = await loader.validatePreset(preset);
964
+ output.validation = validation;
965
+ }
966
+ console.log(JSON.stringify(output, null, 2));
967
+ return;
968
+ }
969
+ console.log(`# Preset: ${presetName}\n`);
970
+ console.log(`**Type:** Built-in`);
971
+ console.log(`**Description:** ${preset.description ?? "-"}`);
972
+ console.log(`**Tools Enabled:** ${enabled.length} (of ${allToolNames.length} available)`);
973
+ console.log(`**Read-Only:** ${preset.read_only ? "Yes" : "No"}\n`);
974
+ if (preset.features) {
975
+ console.log("## Features\n");
976
+ console.log("| Feature | Status |");
977
+ console.log("|---------|--------|");
978
+ for (const f of FEATURE_NAMES) {
979
+ const featureKey = f;
980
+ const status = preset.features[featureKey] === true
981
+ ? "Enabled"
982
+ : preset.features[featureKey] === false
983
+ ? "Disabled"
984
+ : "-";
985
+ console.log(`| ${f} | ${status} |`);
986
+ }
987
+ console.log();
988
+ }
989
+ console.log("## Tool Restrictions\n");
990
+ if (preset.denied_tools_regex) {
991
+ console.log(`**Denied tools regex:** \`${preset.denied_tools_regex}\`\n`);
992
+ }
993
+ if (preset.allowed_tools && preset.allowed_tools.length > 0) {
994
+ console.log(`**Allowed tools (whitelist):** ${preset.allowed_tools.length} tools\n`);
995
+ }
996
+ if (preset.denied_actions && preset.denied_actions.length > 0) {
997
+ console.log(`**Denied actions:** ${preset.denied_actions.join(", ")}\n`);
998
+ }
999
+ if (!preset.denied_tools_regex &&
1000
+ !preset.allowed_tools?.length &&
1001
+ !preset.denied_actions?.length) {
1002
+ console.log("No explicit tool restrictions.\n");
1003
+ }
1004
+ console.log("## Enabled Tools\n");
1005
+ for (const tool of enabled) {
1006
+ console.log(`- ${tool}`);
1007
+ }
1008
+ console.log();
1009
+ if (disabled.length > 0) {
1010
+ console.log("## Disabled Tools\n");
1011
+ for (const tool of disabled) {
1012
+ console.log(`- ${tool}`);
1013
+ }
1014
+ console.log();
1015
+ }
1016
+ if (validate) {
1017
+ console.log("## Validation\n");
1018
+ const validation = await loader.validatePreset(preset);
1019
+ if (validation.valid && validation.warnings.length === 0) {
1020
+ console.log("**Status: VALID**\n");
1021
+ }
1022
+ else if (validation.valid) {
1023
+ console.log(`**Status: VALID** (${validation.warnings.length} warning(s))\n`);
1024
+ console.log("### Warnings\n");
1025
+ for (const w of validation.warnings) {
1026
+ console.log(`- ${w}`);
1027
+ }
1028
+ }
1029
+ else {
1030
+ console.log(`**Status: INVALID** (${validation.errors.length} error(s))\n`);
1031
+ console.log("### Errors\n");
1032
+ for (const e of validation.errors) {
1033
+ console.log(`- ${e}`);
1034
+ }
1035
+ if (validation.warnings.length > 0) {
1036
+ console.log("\n### Warnings\n");
1037
+ for (const w of validation.warnings) {
1038
+ console.log(`- ${w}`);
1039
+ }
1040
+ }
1041
+ }
1042
+ }
1043
+ }
1044
+ async function printProfileDetails(loader, profileName, format, validate) {
1045
+ let profile;
1046
+ try {
1047
+ profile = await loader.loadProfile(profileName);
1048
+ }
1049
+ catch {
1050
+ console.error(`Error: Profile '${profileName}' not found`);
1051
+ process.exit(1);
1052
+ return;
1053
+ }
1054
+ if (format === "json") {
1055
+ const output = {
1056
+ name: profileName,
1057
+ type: "user",
1058
+ host: profile.host,
1059
+ authType: profile.auth.type,
1060
+ readOnly: profile.read_only ?? false,
1061
+ features: profile.features ?? {},
1062
+ deniedToolsRegex: profile.denied_tools_regex ?? null,
1063
+ allowedTools: profile.allowed_tools ?? null,
1064
+ deniedActions: profile.denied_actions ?? null,
1065
+ allowedProjects: profile.allowed_projects ?? null,
1066
+ allowedGroups: profile.allowed_groups ?? null,
1067
+ defaultProject: profile.default_project ?? null,
1068
+ defaultNamespace: profile.default_namespace ?? null,
1069
+ timeoutMs: profile.timeout_ms ?? null,
1070
+ skipTlsVerify: profile.skip_tls_verify ?? false,
1071
+ };
1072
+ if (validate) {
1073
+ const validation = await loader.validateProfile(profile);
1074
+ output.validation = validation;
1075
+ }
1076
+ console.log(JSON.stringify(output, null, 2));
1077
+ return;
1078
+ }
1079
+ console.log(`# Profile: ${profileName}\n`);
1080
+ console.log(`**Type:** User-defined`);
1081
+ console.log(`**Host:** ${profile.host}`);
1082
+ console.log(`**Auth:** ${profile.auth.type}`);
1083
+ console.log(`**Read-Only:** ${profile.read_only ? "Yes" : "No"}\n`);
1084
+ console.log("## Settings\n");
1085
+ console.log("| Setting | Value |");
1086
+ console.log("|---------|-------|");
1087
+ console.log(`| Timeout | ${profile.timeout_ms ?? "default"}ms |`);
1088
+ console.log(`| TLS Verify | ${profile.skip_tls_verify ? "No" : "Yes"} |`);
1089
+ if (profile.default_project) {
1090
+ console.log(`| Default Project | ${profile.default_project} |`);
1091
+ }
1092
+ if (profile.default_namespace) {
1093
+ console.log(`| Default Namespace | ${profile.default_namespace} |`);
1094
+ }
1095
+ console.log();
1096
+ if (profile.allowed_projects?.length || profile.allowed_groups?.length) {
1097
+ console.log("## Access Restrictions\n");
1098
+ if (profile.allowed_projects?.length) {
1099
+ console.log(`**Allowed Projects:** ${profile.allowed_projects.join(", ")}\n`);
1100
+ }
1101
+ if (profile.allowed_groups?.length) {
1102
+ console.log(`**Allowed Groups:** ${profile.allowed_groups.join(", ")}\n`);
1103
+ }
1104
+ }
1105
+ if (profile.denied_tools_regex ||
1106
+ profile.allowed_tools?.length ||
1107
+ profile.denied_actions?.length) {
1108
+ console.log("## Tool Restrictions\n");
1109
+ if (profile.denied_tools_regex) {
1110
+ console.log(`**Denied tools regex:** \`${profile.denied_tools_regex}\`\n`);
1111
+ }
1112
+ if (profile.allowed_tools?.length) {
1113
+ console.log(`**Allowed tools (whitelist):** ${profile.allowed_tools.length} tools\n`);
1114
+ }
1115
+ if (profile.denied_actions?.length) {
1116
+ console.log(`**Denied actions:** ${profile.denied_actions.join(", ")}\n`);
1117
+ }
1118
+ }
1119
+ if (validate) {
1120
+ console.log("## Validation\n");
1121
+ const validation = await loader.validateProfile(profile);
1122
+ if (validation.valid && validation.warnings.length === 0) {
1123
+ console.log("**Status: VALID**\n");
1124
+ }
1125
+ else if (validation.valid) {
1126
+ console.log(`**Status: VALID** (${validation.warnings.length} warning(s))\n`);
1127
+ console.log("### Warnings\n");
1128
+ for (const w of validation.warnings) {
1129
+ console.log(`- ${w}`);
1130
+ }
1131
+ }
1132
+ else {
1133
+ console.log(`**Status: INVALID** (${validation.errors.length} error(s))\n`);
1134
+ console.log("### Errors\n");
1135
+ for (const e of validation.errors) {
1136
+ console.log(`- ${e}`);
1137
+ }
1138
+ }
1139
+ }
1140
+ }
1141
+ async function comparePresets(loader, presetA, presetB, allToolNames, format) {
1142
+ let a, b;
1143
+ try {
1144
+ a = await loader.loadPreset(presetA);
1145
+ }
1146
+ catch {
1147
+ console.error(`Error: Preset '${presetA}' not found`);
1148
+ process.exit(1);
1149
+ return;
1150
+ }
1151
+ try {
1152
+ b = await loader.loadPreset(presetB);
1153
+ }
1154
+ catch {
1155
+ console.error(`Error: Preset '${presetB}' not found`);
1156
+ process.exit(1);
1157
+ return;
1158
+ }
1159
+ const toolsA = getToolsForPreset(a, allToolNames);
1160
+ const toolsB = getToolsForPreset(b, allToolNames);
1161
+ const enabledSetA = new Set(toolsA.enabled);
1162
+ const enabledSetB = new Set(toolsB.enabled);
1163
+ const onlyInA = toolsA.enabled.filter(t => !enabledSetB.has(t));
1164
+ const onlyInB = toolsB.enabled.filter(t => !enabledSetA.has(t));
1165
+ const common = toolsA.enabled.filter(t => enabledSetB.has(t));
1166
+ if (format === "json") {
1167
+ const output = {
1168
+ presetA: {
1169
+ name: presetA,
1170
+ description: a.description ?? null,
1171
+ toolCount: toolsA.enabled.length,
1172
+ readOnly: a.read_only ?? false,
1173
+ },
1174
+ presetB: {
1175
+ name: presetB,
1176
+ description: b.description ?? null,
1177
+ toolCount: toolsB.enabled.length,
1178
+ readOnly: b.read_only ?? false,
1179
+ },
1180
+ comparison: {
1181
+ commonTools: common.length,
1182
+ onlyInA: onlyInA.length,
1183
+ onlyInB: onlyInB.length,
1184
+ onlyInAList: onlyInA,
1185
+ onlyInBList: onlyInB,
1186
+ },
1187
+ };
1188
+ console.log(JSON.stringify(output, null, 2));
1189
+ return;
1190
+ }
1191
+ console.log(`# Comparison: ${presetA} vs ${presetB}\n`);
1192
+ console.log("## Summary\n");
1193
+ console.log("| | " + presetA + " | " + presetB + " |");
1194
+ console.log("|---|---|---|");
1195
+ console.log(`| Tools | ${toolsA.enabled.length} | ${toolsB.enabled.length} |`);
1196
+ console.log(`| Read-Only | ${a.read_only ? "Yes" : "No"} | ${b.read_only ? "Yes" : "No"} |`);
1197
+ console.log();
1198
+ console.log(`**Common tools:** ${common.length}\n`);
1199
+ if (onlyInA.length > 0) {
1200
+ console.log(`## Only in ${presetA} (${onlyInA.length})\n`);
1201
+ for (const t of onlyInA) {
1202
+ console.log(`- ${t}`);
1203
+ }
1204
+ console.log();
1205
+ }
1206
+ if (onlyInB.length > 0) {
1207
+ console.log(`## Only in ${presetB} (${onlyInB.length})\n`);
1208
+ for (const t of onlyInB) {
1209
+ console.log(`- ${t}`);
1210
+ }
1211
+ console.log();
1212
+ }
1213
+ if (a.features || b.features) {
1214
+ console.log("## Feature Comparison\n");
1215
+ console.log("| Feature | " + presetA + " | " + presetB + " |");
1216
+ console.log("|---------|---|---|");
1217
+ for (const f of FEATURE_NAMES) {
1218
+ const featureKey = f;
1219
+ const statusA = a.features?.[featureKey] === true ? "Yes" : a.features?.[featureKey] === false ? "No" : "-";
1220
+ const statusB = b.features?.[featureKey] === true ? "Yes" : b.features?.[featureKey] === false ? "No" : "-";
1221
+ if (statusA !== statusB) {
1222
+ console.log(`| **${f}** | ${statusA} | ${statusB} |`);
1223
+ }
1224
+ else {
1225
+ console.log(`| ${f} | ${statusA} | ${statusB} |`);
1226
+ }
1227
+ }
1228
+ }
1229
+ }
718
1230
  async function main() {
719
1231
  const options = parseArgs();
1232
+ if (options.compare && !options.preset) {
1233
+ console.error("Error: --compare flag must be used with --preset.");
1234
+ console.error("Usage: yarn list-tools --preset <name> --compare <other_name>");
1235
+ process.exit(1);
1236
+ return;
1237
+ }
1238
+ if (options.validate && !options.preset && !options.profile) {
1239
+ console.error("Error: --validate flag must be used with --preset or --profile.");
1240
+ console.error("Usage: yarn list-tools --preset <name> --validate");
1241
+ console.error(" or: yarn list-tools --profile <name> --validate");
1242
+ process.exit(1);
1243
+ return;
1244
+ }
720
1245
  if (options.showEnv) {
721
1246
  printEnvironmentInfo();
722
1247
  }
@@ -728,6 +1253,34 @@ async function main() {
728
1253
  printEnvGatesMarkdown(gates, ungated, options.format === "json" ? "json" : "markdown");
729
1254
  return;
730
1255
  }
1256
+ const needsProfileLoader = Boolean(options.showPresets) ||
1257
+ Boolean(options.showProfiles) ||
1258
+ Boolean(options.preset) ||
1259
+ Boolean(options.profile);
1260
+ if (needsProfileLoader) {
1261
+ const loader = new profiles_1.ProfileLoader();
1262
+ const allToolNames = registryManager.getAllToolDefinitionsUnfiltered().map(t => t.name);
1263
+ if (options.showPresets) {
1264
+ await printPresetsList(loader, allToolNames, options.format === "json" ? "json" : "markdown");
1265
+ return;
1266
+ }
1267
+ if (options.showProfiles) {
1268
+ await printProfilesList(loader, options.format === "json" ? "json" : "markdown");
1269
+ return;
1270
+ }
1271
+ if (options.preset && options.compare) {
1272
+ await comparePresets(loader, options.preset, options.compare, allToolNames, options.format === "json" ? "json" : "markdown");
1273
+ return;
1274
+ }
1275
+ if (options.preset) {
1276
+ await printPresetDetails(loader, options.preset, allToolNames, options.format === "json" ? "json" : "markdown", options.validate ?? false);
1277
+ return;
1278
+ }
1279
+ if (options.profile) {
1280
+ await printProfileDetails(loader, options.profile, options.format === "json" ? "json" : "markdown", options.validate ?? false);
1281
+ return;
1282
+ }
1283
+ }
731
1284
  const toolDefinitions = options.format === "export"
732
1285
  ? registryManager.getAllToolDefinitionsUnfiltered()
733
1286
  : registryManager.getAllToolDefinitionsTierless();