@zereight/mcp-gitlab 2.1.67 → 2.1.69
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/README.ko.md +8 -4
- package/README.md +14 -5
- package/README.pt-BR.md +589 -0
- package/README.zh-CN.md +8 -4
- package/build/cli/args.js +298 -0
- package/build/cli/compact-mcp-result.js +87 -0
- package/build/cli/curated.js +243 -0
- package/build/cli/exposure.js +72 -0
- package/build/cli/help.js +108 -0
- package/build/cli/output.js +65 -0
- package/build/cli/replay-command.js +53 -0
- package/build/cli/router.js +158 -0
- package/build/cli/tables.js +117 -0
- package/build/cli-boolean-flags.js +38 -0
- package/build/cli-command.js +14 -4
- package/build/config.js +30 -7
- package/build/index.js +329 -44
- package/build/oauth.js +93 -10
- package/build/schemas.js +187 -0
- package/build/scripts/generate-tool-docs.js +7 -1
- package/build/scripts/tool-coverage/coverage.js +2 -0
- package/build/test/cli/args.test.js +175 -0
- package/build/test/cli/boolean-flags.test.js +106 -0
- package/build/test/cli/compact-mcp-result.test.js +93 -0
- package/build/test/cli/exposure.test.js +104 -0
- package/build/test/cli/load-config-snapshot.js +5 -0
- package/build/test/cli/output.test.js +74 -0
- package/build/test/cli/replay-command.test.js +37 -0
- package/build/test/cli/router.test.js +183 -0
- package/build/test/oauth-device-flow-tests.js +10 -0
- package/build/test/oauth-startup.test.js +204 -0
- package/build/test/test-dynamic-project-scope.js +162 -0
- package/build/test/test-geteffectiveprojectid.js +4 -2
- package/build/test/test-permission-mode.js +87 -0
- package/build/test/test-snippets.js +735 -0
- package/build/test/test-toolset-filtering.js +3 -0
- package/build/tools/denied-regex.js +38 -0
- package/build/tools/registry.js +42 -1
- package/package.json +1 -1
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { BOOLEAN_CLI_FLAG_NAMES, isBooleanFlagLiteral } from "../cli-boolean-flags.js";
|
|
2
|
+
export class CliUsageError extends Error {
|
|
3
|
+
exitCode = 2;
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "CliUsageError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
const SCRIPT_PATH_PATTERN = /\.(cjs|mjs|js|cts|mts|ts)$/;
|
|
10
|
+
const FLAGS_WITHOUT_VALUE = new Set(["help", "yes", ...BOOLEAN_CLI_FLAG_NAMES]);
|
|
11
|
+
const GLOBAL_FLAG_NAMES = new Set([
|
|
12
|
+
"help",
|
|
13
|
+
"yes",
|
|
14
|
+
"output",
|
|
15
|
+
"args-json",
|
|
16
|
+
"client-id",
|
|
17
|
+
"token-path",
|
|
18
|
+
"token",
|
|
19
|
+
"job-token",
|
|
20
|
+
"cookie-path",
|
|
21
|
+
"use-oauth",
|
|
22
|
+
"is-old",
|
|
23
|
+
"read-only",
|
|
24
|
+
"masking-enabled",
|
|
25
|
+
"masking-config",
|
|
26
|
+
"masking-policy-file",
|
|
27
|
+
"masking-workspace-dir",
|
|
28
|
+
"permission-mode",
|
|
29
|
+
"use-wiki",
|
|
30
|
+
"use-milestone",
|
|
31
|
+
"use-pipeline",
|
|
32
|
+
"disable-version-check",
|
|
33
|
+
"toolsets",
|
|
34
|
+
"tools",
|
|
35
|
+
"tool-policy-approve",
|
|
36
|
+
"tool-policy-hidden",
|
|
37
|
+
"sse",
|
|
38
|
+
"streamable-http",
|
|
39
|
+
"remote-auth",
|
|
40
|
+
"mcp-oauth",
|
|
41
|
+
"allow-unauthenticated-tool-discovery",
|
|
42
|
+
"mcp-trust-proxy",
|
|
43
|
+
"mcp-server-url",
|
|
44
|
+
"oauth-app-id",
|
|
45
|
+
"oauth-scopes",
|
|
46
|
+
"oauth-callback-proxy",
|
|
47
|
+
"allowed-groups",
|
|
48
|
+
"oauth-allowed-groups",
|
|
49
|
+
"enable-dynamic-api-url",
|
|
50
|
+
"enable-dynamic-project-scope",
|
|
51
|
+
"enable-strict-project-scope",
|
|
52
|
+
"oauth-stateless-mode",
|
|
53
|
+
"oauth-stateless-client-ttl",
|
|
54
|
+
"oauth-stateless-pending-ttl",
|
|
55
|
+
"oauth-stateless-stored-ttl",
|
|
56
|
+
"session-timeout",
|
|
57
|
+
"oauth-stateless-session-ttl",
|
|
58
|
+
"host",
|
|
59
|
+
"port",
|
|
60
|
+
"http-proxy",
|
|
61
|
+
"https-proxy",
|
|
62
|
+
"no-proxy",
|
|
63
|
+
"tls-reject-unauthorized",
|
|
64
|
+
"ca-cert-path",
|
|
65
|
+
"pool-max-size",
|
|
66
|
+
"api-url",
|
|
67
|
+
"denied-tools-regex",
|
|
68
|
+
"compact-results",
|
|
69
|
+
"compact-result-chars",
|
|
70
|
+
]);
|
|
71
|
+
export function kebabToSnake(value) {
|
|
72
|
+
return value.replace(/-/g, "_");
|
|
73
|
+
}
|
|
74
|
+
export function snakeToKebab(value) {
|
|
75
|
+
return value.replace(/_/g, "-");
|
|
76
|
+
}
|
|
77
|
+
export function argvHasHelp(argv) {
|
|
78
|
+
return argv.includes("--help") || argv.includes("-h");
|
|
79
|
+
}
|
|
80
|
+
export function parseArgv(argv) {
|
|
81
|
+
const userArgs = userArgv(argv);
|
|
82
|
+
const positionals = [];
|
|
83
|
+
const toolFlags = {};
|
|
84
|
+
let help = false;
|
|
85
|
+
let yes = false;
|
|
86
|
+
let output;
|
|
87
|
+
let argsJson;
|
|
88
|
+
for (let i = 0; i < userArgs.length; i += 1) {
|
|
89
|
+
const current = userArgs[i];
|
|
90
|
+
const flag = readFlag(current);
|
|
91
|
+
if (flag === undefined) {
|
|
92
|
+
if (current.startsWith("-")) {
|
|
93
|
+
throw new CliUsageError(`Unknown flag: ${current}`);
|
|
94
|
+
}
|
|
95
|
+
positionals.push(current);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (flag.name === "help") {
|
|
99
|
+
help = true;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (flag.name === "yes") {
|
|
103
|
+
if (flag.inline !== undefined) {
|
|
104
|
+
throw new CliUsageError("--yes does not accept a value");
|
|
105
|
+
}
|
|
106
|
+
yes = true;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (BOOLEAN_CLI_FLAG_NAMES.has(flag.name) && flag.inline === undefined) {
|
|
110
|
+
if (isBooleanFlagLiteral(userArgs[i + 1])) {
|
|
111
|
+
i += 1;
|
|
112
|
+
}
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const value = flag.inline ?? takeFlagValue(userArgs, i, flag.name);
|
|
116
|
+
if (flag.inline === undefined && !FLAGS_WITHOUT_VALUE.has(flag.name)) {
|
|
117
|
+
i += 1;
|
|
118
|
+
}
|
|
119
|
+
if (flag.name === "output") {
|
|
120
|
+
output = parseOutputMode(value);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (flag.name === "args-json") {
|
|
124
|
+
argsJson = parseArgsJson(value);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (GLOBAL_FLAG_NAMES.has(flag.name)) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
toolFlags[kebabToSnake(flag.name)] = value;
|
|
131
|
+
}
|
|
132
|
+
return { positionals, toolFlags, help, yes, output, argsJson };
|
|
133
|
+
}
|
|
134
|
+
export function applyFlagAliases(flags, aliases) {
|
|
135
|
+
if (aliases === undefined) {
|
|
136
|
+
return { ...flags };
|
|
137
|
+
}
|
|
138
|
+
const mapped = {};
|
|
139
|
+
for (const [key, value] of Object.entries(flags)) {
|
|
140
|
+
const target = aliases[key] ?? key;
|
|
141
|
+
mapped[target] = value;
|
|
142
|
+
}
|
|
143
|
+
return mapped;
|
|
144
|
+
}
|
|
145
|
+
export function parseToolArgs(input) {
|
|
146
|
+
const schema = readObjectSchema(input.schema);
|
|
147
|
+
const propertyNames = schema.properties === undefined ? new Set() : new Set(Object.keys(schema.properties));
|
|
148
|
+
const unknownFlags = Object.keys(input.flags).filter(name => !propertyNames.has(name));
|
|
149
|
+
if (unknownFlags.length > 0) {
|
|
150
|
+
throw new CliUsageError(`Unknown flag: --${snakeToKebab(unknownFlags[0] ?? "")}`);
|
|
151
|
+
}
|
|
152
|
+
const args = {};
|
|
153
|
+
assignOwn(args, input.argsJson);
|
|
154
|
+
for (const [key, value] of Object.entries(input.flags)) {
|
|
155
|
+
args[key] = coerceFlagValue(key, value, schema.properties?.[key]);
|
|
156
|
+
}
|
|
157
|
+
assignOwn(args, input.extraArgs);
|
|
158
|
+
const required = schema.required ?? [];
|
|
159
|
+
const missing = required.filter(name => args[name] === undefined);
|
|
160
|
+
if (missing.length === 1) {
|
|
161
|
+
throw new CliUsageError(`Missing required flag: --${snakeToKebab(missing[0] ?? "")}`);
|
|
162
|
+
}
|
|
163
|
+
if (missing.length > 1) {
|
|
164
|
+
throw new CliUsageError(`Missing required flags: ${missing.map(name => `--${snakeToKebab(name)}`).join(", ")}`);
|
|
165
|
+
}
|
|
166
|
+
return args;
|
|
167
|
+
}
|
|
168
|
+
function scalarFlagType(property) {
|
|
169
|
+
const rawTypes = Array.isArray(property?.type) ? property.type : [property?.type];
|
|
170
|
+
const types = rawTypes.filter(type => type !== "null");
|
|
171
|
+
if (types.length !== 1) {
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
const [type] = types;
|
|
175
|
+
if (type === "number" || type === "integer" || type === "boolean") {
|
|
176
|
+
return type;
|
|
177
|
+
}
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
function coerceFlagValue(name, value, property) {
|
|
181
|
+
const type = scalarFlagType(property);
|
|
182
|
+
if (type === undefined) {
|
|
183
|
+
return value;
|
|
184
|
+
}
|
|
185
|
+
const flag = `--${snakeToKebab(name)}`;
|
|
186
|
+
if (type === "boolean") {
|
|
187
|
+
const normalized = value.toLowerCase();
|
|
188
|
+
if (normalized === "true" || normalized === "1") {
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
if (normalized === "false" || normalized === "0") {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
throw new CliUsageError(`${flag} must be true or false`);
|
|
195
|
+
}
|
|
196
|
+
const parsed = value.trim() === "" ? Number.NaN : Number(value);
|
|
197
|
+
if (!Number.isFinite(parsed)) {
|
|
198
|
+
throw new CliUsageError(`${flag} must be a number`);
|
|
199
|
+
}
|
|
200
|
+
if (type === "integer" && !Number.isInteger(parsed)) {
|
|
201
|
+
throw new CliUsageError(`${flag} must be an integer`);
|
|
202
|
+
}
|
|
203
|
+
return parsed;
|
|
204
|
+
}
|
|
205
|
+
export function readObjectSchema(schema) {
|
|
206
|
+
if (!isObject(schema)) {
|
|
207
|
+
return {};
|
|
208
|
+
}
|
|
209
|
+
const propertiesValue = schema.properties;
|
|
210
|
+
const requiredValue = schema.required;
|
|
211
|
+
const properties = isObject(propertiesValue)
|
|
212
|
+
? Object.fromEntries(Object.entries(propertiesValue).flatMap(([key, value]) => isObject(value) ? [[key, value]] : []))
|
|
213
|
+
: undefined;
|
|
214
|
+
const required = Array.isArray(requiredValue)
|
|
215
|
+
? requiredValue.filter((item) => typeof item === "string")
|
|
216
|
+
: undefined;
|
|
217
|
+
return {
|
|
218
|
+
type: schema.type,
|
|
219
|
+
properties,
|
|
220
|
+
required,
|
|
221
|
+
description: schema.description,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
export function isCliOutputMode(value) {
|
|
225
|
+
return value === "json" || value === "table";
|
|
226
|
+
}
|
|
227
|
+
function userArgv(argv) {
|
|
228
|
+
const args = argv.slice(2);
|
|
229
|
+
const start = args.findIndex(arg => !arg.startsWith("-") && SCRIPT_PATH_PATTERN.test(arg));
|
|
230
|
+
if (start >= 0) {
|
|
231
|
+
return args.slice(start + 1);
|
|
232
|
+
}
|
|
233
|
+
return args;
|
|
234
|
+
}
|
|
235
|
+
function readFlag(arg) {
|
|
236
|
+
if (arg === "-h") {
|
|
237
|
+
return { name: "help", inline: undefined };
|
|
238
|
+
}
|
|
239
|
+
if (!arg.startsWith("--") || arg === "--") {
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
const body = arg.slice(2);
|
|
243
|
+
const separator = body.indexOf("=");
|
|
244
|
+
if (separator === -1) {
|
|
245
|
+
return { name: body, inline: undefined };
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
name: body.slice(0, separator),
|
|
249
|
+
inline: body.slice(separator + 1),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function takeFlagValue(args, index, flagName) {
|
|
253
|
+
if (FLAGS_WITHOUT_VALUE.has(flagName)) {
|
|
254
|
+
return "true";
|
|
255
|
+
}
|
|
256
|
+
const next = args[index + 1];
|
|
257
|
+
if (next === undefined || next.startsWith("-")) {
|
|
258
|
+
throw new CliUsageError(`Missing value for --${flagName}`);
|
|
259
|
+
}
|
|
260
|
+
return next;
|
|
261
|
+
}
|
|
262
|
+
function parseOutputMode(value) {
|
|
263
|
+
if (isCliOutputMode(value)) {
|
|
264
|
+
return value;
|
|
265
|
+
}
|
|
266
|
+
throw new CliUsageError("--output must be table or json");
|
|
267
|
+
}
|
|
268
|
+
function parseArgsJson(raw) {
|
|
269
|
+
try {
|
|
270
|
+
const parsed = JSON.parse(raw);
|
|
271
|
+
if (!isObject(parsed)) {
|
|
272
|
+
throw new CliUsageError("--args-json must be a JSON object");
|
|
273
|
+
}
|
|
274
|
+
const copy = {};
|
|
275
|
+
assignOwn(copy, parsed);
|
|
276
|
+
return copy;
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
if (error instanceof CliUsageError) {
|
|
280
|
+
throw error;
|
|
281
|
+
}
|
|
282
|
+
throw new CliUsageError("Invalid --args-json");
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function assignOwn(target, source) {
|
|
286
|
+
if (source === undefined) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
for (const key of Object.keys(source)) {
|
|
290
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
target[key] = Reflect.get(source, key);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
function isObject(value) {
|
|
297
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
298
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { buildToolReplayCommand } from "./replay-command.js";
|
|
2
|
+
const PREVIEW_ARRAY_ITEMS = 5;
|
|
3
|
+
const PREVIEW_TEXT_CHARS = 1500;
|
|
4
|
+
const PREVIEW_JSON_CHARS = 2000;
|
|
5
|
+
export function readOwnRecord(value) {
|
|
6
|
+
if (!isRecord(value)) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
const copy = {};
|
|
10
|
+
for (const key of Object.keys(value)) {
|
|
11
|
+
copy[key] = Reflect.get(value, key);
|
|
12
|
+
}
|
|
13
|
+
return copy;
|
|
14
|
+
}
|
|
15
|
+
export function compactMcpToolResult(input) {
|
|
16
|
+
if (!input.enabled || input.toolName === "discover_tools") {
|
|
17
|
+
return toolResultRecord(input.result);
|
|
18
|
+
}
|
|
19
|
+
if (!isToolResult(input.result) || input.result.isError === true) {
|
|
20
|
+
return toolResultRecord(input.result);
|
|
21
|
+
}
|
|
22
|
+
const text = unwrapToolText(input.result);
|
|
23
|
+
if (text.length <= input.maxChars) {
|
|
24
|
+
return toolResultRecord(input.result);
|
|
25
|
+
}
|
|
26
|
+
const compact = {
|
|
27
|
+
compact: true,
|
|
28
|
+
tool: input.toolName,
|
|
29
|
+
original_chars: text.length,
|
|
30
|
+
...buildPreview(text),
|
|
31
|
+
cli: buildToolReplayCommand(input.toolName, input.args),
|
|
32
|
+
};
|
|
33
|
+
return {
|
|
34
|
+
content: [{ type: "text", text: JSON.stringify(compact) }],
|
|
35
|
+
isError: false,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function toolResultRecord(value) {
|
|
39
|
+
if (isRecord(value)) {
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
return { content: [{ type: "text", text: stringifyUnknown(value) }] };
|
|
43
|
+
}
|
|
44
|
+
function buildPreview(text) {
|
|
45
|
+
const parsed = parseJsonValue(text);
|
|
46
|
+
if (parsed === undefined) {
|
|
47
|
+
return { preview: text.slice(0, PREVIEW_TEXT_CHARS) };
|
|
48
|
+
}
|
|
49
|
+
if (Array.isArray(parsed)) {
|
|
50
|
+
return { preview: parsed.slice(0, PREVIEW_ARRAY_ITEMS), items_total: parsed.length };
|
|
51
|
+
}
|
|
52
|
+
if (typeof parsed === "string") {
|
|
53
|
+
return { preview: parsed.slice(0, PREVIEW_TEXT_CHARS) };
|
|
54
|
+
}
|
|
55
|
+
const encoded = JSON.stringify(parsed);
|
|
56
|
+
if (encoded.length <= PREVIEW_JSON_CHARS) {
|
|
57
|
+
return { preview: parsed };
|
|
58
|
+
}
|
|
59
|
+
return { preview: encoded.slice(0, PREVIEW_JSON_CHARS) };
|
|
60
|
+
}
|
|
61
|
+
function unwrapToolText(result) {
|
|
62
|
+
const textBlock = result.content.find(item => isRecord(item) && typeof item.text === "string");
|
|
63
|
+
if (!isRecord(textBlock) || typeof textBlock.text !== "string") {
|
|
64
|
+
return stringifyUnknown(result);
|
|
65
|
+
}
|
|
66
|
+
return textBlock.text;
|
|
67
|
+
}
|
|
68
|
+
function isToolResult(value) {
|
|
69
|
+
return isRecord(value) && Array.isArray(value.content);
|
|
70
|
+
}
|
|
71
|
+
function parseJsonValue(text) {
|
|
72
|
+
try {
|
|
73
|
+
return JSON.parse(text);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function stringifyUnknown(value) {
|
|
80
|
+
if (typeof value === "string") {
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
return JSON.stringify(value);
|
|
84
|
+
}
|
|
85
|
+
function isRecord(value) {
|
|
86
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
87
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { CliUsageError } from "./args.js";
|
|
2
|
+
const MR_IID = { mr_iid: "merge_request_iid", iid: "merge_request_iid" };
|
|
3
|
+
const ISSUE_IID = { issue_iid: "issue_iid", iid: "issue_iid" };
|
|
4
|
+
const BRANCH = { branch: "branch_name" };
|
|
5
|
+
const SOURCE_TARGET = { source: "source_branch", target: "target_branch" };
|
|
6
|
+
const LIST_MR = { kind: "list", columns: ["iid", "state", "title"] };
|
|
7
|
+
const KV_MR = { kind: "kv", columns: ["iid", "state", "title", "source_branch", "target_branch", "web_url"] };
|
|
8
|
+
const LIST_ISSUE = { kind: "list", columns: ["iid", "state", "title"] };
|
|
9
|
+
const KV_ISSUE = { kind: "kv", columns: ["iid", "state", "title", "web_url"] };
|
|
10
|
+
const LIST_PIPELINE = { kind: "list", columns: ["id", "status", "ref"] };
|
|
11
|
+
const KV_PIPELINE = { kind: "kv", columns: ["id", "status", "ref", "web_url"] };
|
|
12
|
+
const LIST_JOB = { kind: "list", columns: ["id", "name", "status"] };
|
|
13
|
+
const LIST_PROJECT = { kind: "list", columns: ["id", "path_with_namespace"] };
|
|
14
|
+
const KV_PROJECT = { kind: "kv", columns: ["id", "name", "path_with_namespace", "web_url"] };
|
|
15
|
+
const LIST_BRANCH = { kind: "list", columns: ["name", "merged", "protected"] };
|
|
16
|
+
const KV_BRANCH = { kind: "kv", columns: ["name", "merged", "protected", "default"] };
|
|
17
|
+
const LIST_COMMIT = { kind: "list", columns: ["short_id", "title", "author_name"] };
|
|
18
|
+
const KV_COMMIT = { kind: "kv", columns: ["id", "title", "author_name", "web_url"] };
|
|
19
|
+
const KV_USER = { kind: "kv", columns: ["id", "username", "name", "web_url"] };
|
|
20
|
+
const LIST_LABEL = { kind: "list", columns: ["id", "name", "color"] };
|
|
21
|
+
const LIST_RELEASE = { kind: "list", columns: ["tag_name", "name"] };
|
|
22
|
+
const LIST_TAG = { kind: "list", columns: ["name", "message"] };
|
|
23
|
+
const LIST_VAR = { kind: "list", columns: ["key", "protected", "masked"] };
|
|
24
|
+
const LIST_MILESTONE = { kind: "list", columns: ["iid", "title", "state"] };
|
|
25
|
+
const LIST_WIKI = { kind: "list", columns: ["slug", "title"] };
|
|
26
|
+
const LIST_WEBHOOK = { kind: "list", columns: ["id", "url"] };
|
|
27
|
+
const LIST_WORKITEM = { kind: "list", columns: ["iid", "title", "work_item_type"] };
|
|
28
|
+
const TEXT = { kind: "text" };
|
|
29
|
+
const GROUP_ALIASES = {
|
|
30
|
+
mr: "mr",
|
|
31
|
+
"merge-requests": "mr",
|
|
32
|
+
issue: "issue",
|
|
33
|
+
issues: "issue",
|
|
34
|
+
pipeline: "pipeline",
|
|
35
|
+
pipelines: "pipeline",
|
|
36
|
+
project: "project",
|
|
37
|
+
projects: "project",
|
|
38
|
+
branch: "branch",
|
|
39
|
+
branches: "branch",
|
|
40
|
+
commit: "commit",
|
|
41
|
+
commits: "commit",
|
|
42
|
+
user: "user",
|
|
43
|
+
users: "user",
|
|
44
|
+
repo: "repo",
|
|
45
|
+
repository: "repo",
|
|
46
|
+
repositories: "repo",
|
|
47
|
+
label: "label",
|
|
48
|
+
labels: "label",
|
|
49
|
+
release: "release",
|
|
50
|
+
releases: "release",
|
|
51
|
+
tag: "tag",
|
|
52
|
+
tags: "tag",
|
|
53
|
+
variable: "variable",
|
|
54
|
+
variables: "variable",
|
|
55
|
+
milestone: "milestone",
|
|
56
|
+
milestones: "milestone",
|
|
57
|
+
wiki: "wiki",
|
|
58
|
+
group: "group",
|
|
59
|
+
groups: "group",
|
|
60
|
+
ci: "ci",
|
|
61
|
+
webhook: "webhook",
|
|
62
|
+
webhooks: "webhook",
|
|
63
|
+
workitem: "workitem",
|
|
64
|
+
workitems: "workitem",
|
|
65
|
+
search: "search",
|
|
66
|
+
vulnerability: "vulnerability",
|
|
67
|
+
vulnerabilities: "vulnerability",
|
|
68
|
+
"dependency-proxy": "dependency-proxy",
|
|
69
|
+
orbit: "orbit",
|
|
70
|
+
};
|
|
71
|
+
export const CURATED_COMMANDS = [
|
|
72
|
+
// Phase 1 reads
|
|
73
|
+
{ group: "mr", action: "list", tool: "list_merge_requests", scope: { projectTool: "list_merge_requests", groupTool: "list_group_merge_requests" }, table: LIST_MR },
|
|
74
|
+
{ group: "mr", action: "view", tool: "get_merge_request", flagAliases: MR_IID, table: KV_MR },
|
|
75
|
+
{ group: "mr", action: "diff", tool: "get_merge_request_diffs", flagAliases: MR_IID, table: TEXT },
|
|
76
|
+
{ group: "mr", action: "files", tool: "list_merge_request_changed_files", flagAliases: MR_IID, table: { kind: "list", columns: ["new_path", "old_path"] } },
|
|
77
|
+
{ group: "mr", action: "discussions", tool: "mr_discussions", flagAliases: MR_IID, table: TEXT },
|
|
78
|
+
{ group: "mr", action: "pipelines", tool: "list_merge_request_pipelines", flagAliases: MR_IID, table: LIST_PIPELINE },
|
|
79
|
+
{ group: "mr", action: "approvals", tool: "get_merge_request_approval_state", flagAliases: MR_IID, table: TEXT },
|
|
80
|
+
{ group: "issue", action: "list", tool: "list_issues", table: LIST_ISSUE },
|
|
81
|
+
{ group: "issue", action: "mine", tool: "my_issues", table: LIST_ISSUE },
|
|
82
|
+
{ group: "issue", action: "view", tool: "get_issue", flagAliases: ISSUE_IID, table: KV_ISSUE },
|
|
83
|
+
{ group: "issue", action: "discussions", tool: "list_issue_discussions", flagAliases: ISSUE_IID, table: TEXT },
|
|
84
|
+
{ group: "pipeline", action: "list", tool: "list_pipelines", table: LIST_PIPELINE },
|
|
85
|
+
{ group: "pipeline", action: "view", tool: "get_pipeline", table: KV_PIPELINE },
|
|
86
|
+
{ group: "pipeline", action: "jobs", tool: "list_pipeline_jobs", table: LIST_JOB },
|
|
87
|
+
{ group: "pipeline", action: "log", tool: "get_pipeline_job_output", table: TEXT },
|
|
88
|
+
{ group: "project", action: "list", tool: "list_projects", table: LIST_PROJECT },
|
|
89
|
+
{ group: "project", action: "view", tool: "get_project", table: KV_PROJECT },
|
|
90
|
+
{ group: "branch", action: "list", tool: "list_branches", table: LIST_BRANCH },
|
|
91
|
+
{ group: "branch", action: "view", tool: "get_branch", flagAliases: BRANCH, table: KV_BRANCH },
|
|
92
|
+
{ group: "commit", action: "list", tool: "list_commits", table: LIST_COMMIT },
|
|
93
|
+
{ group: "commit", action: "view", tool: "get_commit", table: KV_COMMIT },
|
|
94
|
+
{ group: "user", action: "whoami", tool: "whoami", table: KV_USER },
|
|
95
|
+
// Phase 2 writes + extra daily commands
|
|
96
|
+
{ group: "mr", action: "create", tool: "create_merge_request", flagAliases: SOURCE_TARGET, table: KV_MR },
|
|
97
|
+
{ group: "mr", action: "approve", tool: "approve_merge_request", flagAliases: MR_IID, table: KV_MR },
|
|
98
|
+
{ group: "mr", action: "unapprove", tool: "unapprove_merge_request", flagAliases: MR_IID, table: KV_MR },
|
|
99
|
+
{ group: "mr", action: "merge", tool: "merge_merge_request", flagAliases: MR_IID, table: KV_MR },
|
|
100
|
+
{ group: "mr", action: "note", tool: "create_merge_request_note", flagAliases: MR_IID, table: { kind: "kv", columns: ["id", "body", "created_at"] } },
|
|
101
|
+
{ group: "mr", action: "update", tool: "update_merge_request", flagAliases: MR_IID, table: KV_MR },
|
|
102
|
+
{ group: "issue", action: "create", tool: "create_issue", table: KV_ISSUE },
|
|
103
|
+
{ group: "issue", action: "close", tool: "update_issue", extraArgs: { state_event: "close" }, flagAliases: ISSUE_IID, table: KV_ISSUE },
|
|
104
|
+
{ group: "issue", action: "reopen", tool: "update_issue", extraArgs: { state_event: "reopen" }, flagAliases: ISSUE_IID, table: KV_ISSUE },
|
|
105
|
+
{ group: "issue", action: "update", tool: "update_issue", flagAliases: ISSUE_IID, table: KV_ISSUE },
|
|
106
|
+
{ group: "issue", action: "note", tool: "create_issue_note", flagAliases: ISSUE_IID, table: { kind: "kv", columns: ["id", "body"] } },
|
|
107
|
+
{ group: "issue", action: "delete", tool: "delete_issue", flagAliases: ISSUE_IID, table: TEXT },
|
|
108
|
+
{ group: "pipeline", action: "retry", tool: "retry_pipeline", table: KV_PIPELINE },
|
|
109
|
+
{ group: "pipeline", action: "cancel", tool: "cancel_pipeline", table: KV_PIPELINE },
|
|
110
|
+
{ group: "pipeline", action: "create", tool: "create_pipeline", table: KV_PIPELINE },
|
|
111
|
+
{ group: "pipeline", action: "delete", tool: "delete_pipeline", table: TEXT },
|
|
112
|
+
{ group: "pipeline", action: "job-retry", tool: "retry_pipeline_job", table: LIST_JOB },
|
|
113
|
+
{ group: "pipeline", action: "job-cancel", tool: "cancel_pipeline_job", table: LIST_JOB },
|
|
114
|
+
{ group: "pipeline", action: "job-play", tool: "play_pipeline_job", table: LIST_JOB },
|
|
115
|
+
{ group: "project", action: "members", tool: "list_project_members", table: { kind: "list", columns: ["id", "username", "access_level"] } },
|
|
116
|
+
{ group: "project", action: "update", tool: "update_project", table: KV_PROJECT },
|
|
117
|
+
{ group: "branch", action: "create", tool: "create_branch", table: KV_BRANCH },
|
|
118
|
+
{ group: "branch", action: "delete", tool: "delete_branch", flagAliases: BRANCH, table: TEXT },
|
|
119
|
+
{ group: "commit", action: "diff", tool: "get_commit_diff", table: TEXT },
|
|
120
|
+
{ group: "user", action: "list", tool: "get_users", table: { kind: "list", columns: ["id", "username", "name"] } },
|
|
121
|
+
{ group: "user", action: "view", tool: "get_user", table: KV_USER },
|
|
122
|
+
{ group: "user", action: "events", tool: "list_events", table: { kind: "list", columns: ["action_name", "target_type", "created_at"] } },
|
|
123
|
+
{ group: "repo", action: "search", tool: "search_repositories", table: LIST_PROJECT },
|
|
124
|
+
{ group: "repo", action: "tree", tool: "get_repository_tree", table: { kind: "list", columns: ["path", "type"] } },
|
|
125
|
+
{ group: "repo", action: "file", tool: "get_file_contents", table: TEXT },
|
|
126
|
+
{ group: "repo", action: "create", tool: "create_repository", table: KV_PROJECT },
|
|
127
|
+
{ group: "repo", action: "fork", tool: "fork_repository", table: KV_PROJECT },
|
|
128
|
+
{ group: "repo", action: "push", tool: "push_files", table: TEXT },
|
|
129
|
+
{ group: "label", action: "list", tool: "list_labels", table: LIST_LABEL },
|
|
130
|
+
{ group: "label", action: "view", tool: "get_label", table: { kind: "kv", columns: ["id", "name", "color", "description"] } },
|
|
131
|
+
{ group: "label", action: "create", tool: "create_label", table: LIST_LABEL },
|
|
132
|
+
{ group: "label", action: "update", tool: "update_label", table: LIST_LABEL },
|
|
133
|
+
{ group: "label", action: "delete", tool: "delete_label", table: TEXT },
|
|
134
|
+
{ group: "release", action: "list", tool: "list_releases", table: LIST_RELEASE },
|
|
135
|
+
{ group: "release", action: "view", tool: "get_release", table: { kind: "kv", columns: ["tag_name", "name", "description"] } },
|
|
136
|
+
{ group: "release", action: "create", tool: "create_release", table: LIST_RELEASE },
|
|
137
|
+
{ group: "release", action: "update", tool: "update_release", table: LIST_RELEASE },
|
|
138
|
+
{ group: "release", action: "delete", tool: "delete_release", table: TEXT },
|
|
139
|
+
{ group: "release", action: "download", tool: "download_release_asset", table: TEXT },
|
|
140
|
+
{ group: "tag", action: "list", tool: "list_tags", table: LIST_TAG },
|
|
141
|
+
{ group: "tag", action: "view", tool: "get_tag", table: { kind: "kv", columns: ["name", "message", "target"] } },
|
|
142
|
+
{ group: "tag", action: "create", tool: "create_tag", table: LIST_TAG },
|
|
143
|
+
{ group: "tag", action: "delete", tool: "delete_tag", table: TEXT },
|
|
144
|
+
{ group: "tag", action: "signature", tool: "get_tag_signature", table: TEXT },
|
|
145
|
+
{ group: "variable", action: "list", tool: "list_project_variables", scope: { projectTool: "list_project_variables", groupTool: "list_group_variables" }, table: LIST_VAR },
|
|
146
|
+
{ group: "variable", action: "view", tool: "get_project_variable", scope: { projectTool: "get_project_variable", groupTool: "get_group_variable" }, table: { kind: "kv", columns: ["key", "variable_type", "protected", "masked"] } },
|
|
147
|
+
{ group: "variable", action: "create", tool: "create_project_variable", scope: { projectTool: "create_project_variable", groupTool: "create_group_variable" }, table: LIST_VAR },
|
|
148
|
+
{ group: "variable", action: "update", tool: "update_project_variable", scope: { projectTool: "update_project_variable", groupTool: "update_group_variable" }, table: LIST_VAR },
|
|
149
|
+
{ group: "variable", action: "delete", tool: "delete_project_variable", scope: { projectTool: "delete_project_variable", groupTool: "delete_group_variable" }, table: TEXT },
|
|
150
|
+
// Phase 3 remaining groups
|
|
151
|
+
{ group: "milestone", action: "list", tool: "list_milestones", scope: { projectTool: "list_milestones", groupTool: "list_group_milestones" }, table: LIST_MILESTONE },
|
|
152
|
+
{ group: "milestone", action: "view", tool: "get_milestone", scope: { projectTool: "get_milestone", groupTool: "get_group_milestone" }, table: { kind: "kv", columns: ["iid", "title", "state", "web_url"] } },
|
|
153
|
+
{ group: "milestone", action: "create", tool: "create_milestone", scope: { projectTool: "create_milestone", groupTool: "create_group_milestone" }, table: LIST_MILESTONE },
|
|
154
|
+
{ group: "milestone", action: "edit", tool: "edit_milestone", scope: { projectTool: "edit_milestone", groupTool: "edit_group_milestone" }, table: LIST_MILESTONE },
|
|
155
|
+
{ group: "milestone", action: "delete", tool: "delete_milestone", scope: { projectTool: "delete_milestone", groupTool: "delete_group_milestone" }, table: TEXT },
|
|
156
|
+
{ group: "milestone", action: "issues", tool: "get_milestone_issue", scope: { projectTool: "get_milestone_issue", groupTool: "get_group_milestone_issue" }, table: LIST_ISSUE },
|
|
157
|
+
{ group: "milestone", action: "mrs", tool: "get_milestone_merge_requests", scope: { projectTool: "get_milestone_merge_requests", groupTool: "get_group_milestone_merge_requests" }, table: LIST_MR },
|
|
158
|
+
{ group: "wiki", action: "list", tool: "list_wiki_pages", scope: { projectTool: "list_wiki_pages", groupTool: "list_group_wiki_pages" }, table: LIST_WIKI },
|
|
159
|
+
{ group: "wiki", action: "view", tool: "get_wiki_page", scope: { projectTool: "get_wiki_page", groupTool: "get_group_wiki_page" }, table: { kind: "kv", columns: ["slug", "title", "content"] } },
|
|
160
|
+
{ group: "wiki", action: "create", tool: "create_wiki_page", scope: { projectTool: "create_wiki_page", groupTool: "create_group_wiki_page" }, table: LIST_WIKI },
|
|
161
|
+
{ group: "wiki", action: "update", tool: "update_wiki_page", scope: { projectTool: "update_wiki_page", groupTool: "update_group_wiki_page" }, table: LIST_WIKI },
|
|
162
|
+
{ group: "wiki", action: "delete", tool: "delete_wiki_page", scope: { projectTool: "delete_wiki_page", groupTool: "delete_group_wiki_page" }, table: TEXT },
|
|
163
|
+
{ group: "group", action: "create", tool: "create_group", table: { kind: "kv", columns: ["id", "name", "full_path", "web_url"] } },
|
|
164
|
+
{ group: "ci", action: "lint", tool: "validate_ci_lint", table: TEXT },
|
|
165
|
+
{ group: "ci", action: "project-lint", tool: "validate_project_ci_lint", table: TEXT },
|
|
166
|
+
{ group: "ci", action: "catalog", tool: "list_ci_catalog_resources", table: { kind: "list", columns: ["id", "name"] } },
|
|
167
|
+
{ group: "ci", action: "catalog-view", tool: "get_ci_catalog_resource", table: TEXT },
|
|
168
|
+
{ group: "webhook", action: "list", tool: "list_webhooks", table: LIST_WEBHOOK },
|
|
169
|
+
{ group: "webhook", action: "create", tool: "create_webhook", table: LIST_WEBHOOK },
|
|
170
|
+
{ group: "webhook", action: "update", tool: "update_webhook", table: LIST_WEBHOOK },
|
|
171
|
+
{ group: "webhook", action: "delete", tool: "delete_webhook", table: TEXT },
|
|
172
|
+
{ group: "webhook", action: "events", tool: "list_webhook_events", table: TEXT },
|
|
173
|
+
{ group: "webhook", action: "event", tool: "get_webhook_event", table: TEXT },
|
|
174
|
+
{ group: "workitem", action: "list", tool: "list_work_items", table: LIST_WORKITEM },
|
|
175
|
+
{ group: "workitem", action: "view", tool: "get_work_item", table: { kind: "kv", columns: ["iid", "title", "web_url"] } },
|
|
176
|
+
{ group: "workitem", action: "create", tool: "create_work_item", table: LIST_WORKITEM },
|
|
177
|
+
{ group: "workitem", action: "update", tool: "update_work_item", table: LIST_WORKITEM },
|
|
178
|
+
{ group: "workitem", action: "convert", tool: "convert_work_item_type", table: LIST_WORKITEM },
|
|
179
|
+
{ group: "workitem", action: "move", tool: "move_work_item", table: LIST_WORKITEM },
|
|
180
|
+
{ group: "workitem", action: "notes", tool: "list_work_item_notes", table: TEXT },
|
|
181
|
+
{ group: "workitem", action: "note", tool: "create_work_item_note", table: TEXT },
|
|
182
|
+
{ group: "search", action: "code", tool: "search_code", table: TEXT },
|
|
183
|
+
{ group: "search", action: "project", tool: "search_project_code", table: TEXT },
|
|
184
|
+
{ group: "search", action: "group", tool: "search_group_code", table: TEXT },
|
|
185
|
+
{ group: "vulnerability", action: "list", tool: "list_project_vulnerabilities", table: TEXT },
|
|
186
|
+
{ group: "vulnerability", action: "view", tool: "get_vulnerability", table: TEXT },
|
|
187
|
+
{ group: "vulnerability", action: "dismiss", tool: "dismiss_vulnerability", table: TEXT },
|
|
188
|
+
{ group: "vulnerability", action: "confirm", tool: "confirm_vulnerability", table: TEXT },
|
|
189
|
+
{ group: "dependency-proxy", action: "settings", tool: "get_dependency_proxy_settings", table: TEXT },
|
|
190
|
+
{ group: "dependency-proxy", action: "update-settings", tool: "update_dependency_proxy_settings", table: TEXT },
|
|
191
|
+
{ group: "dependency-proxy", action: "blobs", tool: "list_dependency_proxy_blobs", table: TEXT },
|
|
192
|
+
{ group: "dependency-proxy", action: "purge", tool: "purge_dependency_proxy_cache", table: TEXT },
|
|
193
|
+
{ group: "orbit", action: "query", tool: "orbit_query", table: TEXT },
|
|
194
|
+
{ group: "orbit", action: "schema", tool: "orbit_get_schema", table: TEXT },
|
|
195
|
+
{ group: "orbit", action: "status", tool: "orbit_get_status", table: TEXT },
|
|
196
|
+
{ group: "orbit", action: "tools", tool: "orbit_list_tools", table: TEXT },
|
|
197
|
+
];
|
|
198
|
+
export function canonicalGroup(name) {
|
|
199
|
+
return GROUP_ALIASES[name];
|
|
200
|
+
}
|
|
201
|
+
export function isKnownCliCommand(name) {
|
|
202
|
+
return name === "auth" || name === "tool" || canonicalGroup(name) !== undefined;
|
|
203
|
+
}
|
|
204
|
+
export function findCuratedCommand(group, action) {
|
|
205
|
+
const canonical = canonicalGroup(group);
|
|
206
|
+
if (canonical === undefined) {
|
|
207
|
+
return undefined;
|
|
208
|
+
}
|
|
209
|
+
return CURATED_COMMANDS.find(command => command.group === canonical && command.action === action);
|
|
210
|
+
}
|
|
211
|
+
export function listGroupActions(group) {
|
|
212
|
+
const canonical = canonicalGroup(group);
|
|
213
|
+
if (canonical === undefined) {
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
216
|
+
return CURATED_COMMANDS.filter(command => command.group === canonical);
|
|
217
|
+
}
|
|
218
|
+
export function listGroups() {
|
|
219
|
+
const seen = new Set();
|
|
220
|
+
const groups = [];
|
|
221
|
+
for (const command of CURATED_COMMANDS) {
|
|
222
|
+
if (!seen.has(command.group)) {
|
|
223
|
+
seen.add(command.group);
|
|
224
|
+
groups.push(command.group);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return groups;
|
|
228
|
+
}
|
|
229
|
+
export function resolveScopedTool(command, flags) {
|
|
230
|
+
const scope = command.scope;
|
|
231
|
+
if (scope === undefined) {
|
|
232
|
+
return command.tool;
|
|
233
|
+
}
|
|
234
|
+
const hasGroup = flags.group_id !== undefined;
|
|
235
|
+
const hasProject = flags.project_id !== undefined;
|
|
236
|
+
if (hasGroup && hasProject) {
|
|
237
|
+
throw new CliUsageError("use --project-id or --group-id, not both");
|
|
238
|
+
}
|
|
239
|
+
if (hasGroup) {
|
|
240
|
+
return scope.groupTool;
|
|
241
|
+
}
|
|
242
|
+
return scope.projectTool;
|
|
243
|
+
}
|