@structured-world/gitlab-mcp 6.14.0 → 6.16.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.
- package/dist/src/cli/list-tools.js +556 -3
- package/dist/src/cli/list-tools.js.map +1 -1
- package/dist/src/cli-utils.d.ts +8 -0
- package/dist/src/cli-utils.js +125 -0
- package/dist/src/cli-utils.js.map +1 -0
- package/dist/src/entities/core/schema-readonly.d.ts +4 -4
- package/dist/src/entities/mrs/schema-readonly.d.ts +1 -1
- package/dist/src/entities/pipelines/schema-readonly.d.ts +3 -3
- package/dist/src/main.js +31 -23
- package/dist/src/main.js.map +1 -1
- package/dist/src/profiles/index.d.ts +3 -1
- package/dist/src/profiles/index.js +17 -1
- package/dist/src/profiles/index.js.map +1 -1
- package/dist/src/profiles/project-loader.d.ts +12 -0
- package/dist/src/profiles/project-loader.js +214 -0
- package/dist/src/profiles/project-loader.js.map +1 -0
- package/dist/src/profiles/scope-enforcer.d.ts +24 -0
- package/dist/src/profiles/scope-enforcer.js +128 -0
- package/dist/src/profiles/scope-enforcer.js.map +1 -0
- package/dist/src/profiles/types.d.ts +55 -0
- package/dist/src/profiles/types.js +41 -1
- package/dist/src/profiles/types.js.map +1 -1
- package/dist/structured-world-gitlab-mcp-6.16.0.tgz +0 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/dist/structured-world-gitlab-mcp-6.14.0.tgz +0 -0
|
@@ -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
|
-
|
|
148
|
-
|
|
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();
|