@daghis/teamcity-mcp 2.12.5 → 2.12.7
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/CHANGELOG.md +14 -0
- package/dist/index.js +203 -28
- package/package.json +1 -1
- package/server.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.12.7](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v2.12.6...teamcity-mcp-v2.12.7) (2026-10-03)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **build-log:** protect errors and bound stream snapshots ([#538](https://github.com/Daghis/teamcity-mcp/issues/538)) ([36a300c](https://github.com/Daghis/teamcity-mcp/commit/36a300c932fadeea6136effde9c93d82ad716780))
|
|
9
|
+
|
|
10
|
+
## [2.12.6](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v2.12.5...teamcity-mcp-v2.12.6) (2026-10-03)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **build-log:** repair streamed downloads and error logging ([#526](https://github.com/Daghis/teamcity-mcp/issues/526)) ([aca2eea](https://github.com/Daghis/teamcity-mcp/commit/aca2eea6b996668e75ed69318c8846cdfeee624d))
|
|
16
|
+
|
|
3
17
|
## [2.12.5](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v2.12.4...teamcity-mcp-v2.12.5) (2026-10-03)
|
|
4
18
|
|
|
5
19
|
|
package/dist/index.js
CHANGED
|
@@ -42,6 +42,71 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
42
42
|
mod
|
|
43
43
|
));
|
|
44
44
|
|
|
45
|
+
// src/teamcity/utils/stream.ts
|
|
46
|
+
async function* sliceLines(source, startLine, endLine) {
|
|
47
|
+
if (endLine !== void 0 && endLine <= startLine) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
let line = 0;
|
|
51
|
+
for await (const raw of source) {
|
|
52
|
+
const chunk = toBuffer(raw);
|
|
53
|
+
let pos = 0;
|
|
54
|
+
while (pos < chunk.length) {
|
|
55
|
+
const newline = chunk.indexOf(10, pos);
|
|
56
|
+
const stop = newline === -1 ? chunk.length : newline + 1;
|
|
57
|
+
if (line >= startLine) {
|
|
58
|
+
yield chunk.subarray(pos, stop);
|
|
59
|
+
}
|
|
60
|
+
if (newline === -1) {
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
line += 1;
|
|
64
|
+
pos = stop;
|
|
65
|
+
if (endLine !== void 0 && line >= endLine) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
var import_stream, isReadableStream, destroyStream, discardStreamBody, toBuffer, sliceStreamLines;
|
|
72
|
+
var init_stream = __esm({
|
|
73
|
+
"src/teamcity/utils/stream.ts"() {
|
|
74
|
+
"use strict";
|
|
75
|
+
import_stream = require("stream");
|
|
76
|
+
isReadableStream = (value) => typeof value === "object" && value !== null && typeof value.pipe === "function" && typeof value.on === "function";
|
|
77
|
+
destroyStream = (stream) => {
|
|
78
|
+
stream.on("error", () => void 0);
|
|
79
|
+
const destroyable = stream;
|
|
80
|
+
if (typeof destroyable.destroy === "function") {
|
|
81
|
+
destroyable.destroy();
|
|
82
|
+
} else {
|
|
83
|
+
stream.resume();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
discardStreamBody = (value) => {
|
|
87
|
+
if (isReadableStream(value)) {
|
|
88
|
+
destroyStream(value);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
toBuffer = (chunk) => {
|
|
92
|
+
if (Buffer.isBuffer(chunk)) {
|
|
93
|
+
return chunk;
|
|
94
|
+
}
|
|
95
|
+
return chunk instanceof Uint8Array ? Buffer.from(chunk) : Buffer.from(String(chunk));
|
|
96
|
+
};
|
|
97
|
+
sliceStreamLines = (source, startLine, lineCount) => {
|
|
98
|
+
const endLine = lineCount === void 0 ? void 0 : startLine + lineCount;
|
|
99
|
+
const slice = import_stream.Readable.from(sliceLines(source, startLine, endLine), { objectMode: false });
|
|
100
|
+
const destroySlice = slice._destroy.bind(slice);
|
|
101
|
+
slice._destroy = (error3, callback) => {
|
|
102
|
+
destroyStream(source);
|
|
103
|
+
destroySlice(error3, callback);
|
|
104
|
+
};
|
|
105
|
+
return slice;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
45
110
|
// src/teamcity/errors.ts
|
|
46
111
|
function isRetryableError(error3) {
|
|
47
112
|
if (error3 instanceof TeamCityAPIError) {
|
|
@@ -63,9 +128,11 @@ function isRetryableError(error3) {
|
|
|
63
128
|
}
|
|
64
129
|
return false;
|
|
65
130
|
}
|
|
66
|
-
var TeamCityAPIError, TeamCityAuthorizationError, TeamCityNotFoundError, TeamCityRateLimitError, TeamCityServerError, TeamCityNetworkError, TeamCityRequestError, TeamCityTimeoutError, BuildNotFoundError, BuildAccessDeniedError;
|
|
131
|
+
var import_node_util2, TeamCityAPIError, TeamCityAuthorizationError, TeamCityNotFoundError, TeamCityRateLimitError, TeamCityServerError, TeamCityNetworkError, TeamCityRequestError, TeamCityTimeoutError, BuildNotFoundError, BuildAccessDeniedError;
|
|
67
132
|
var init_errors = __esm({
|
|
68
133
|
"src/teamcity/errors.ts"() {
|
|
134
|
+
import_node_util2 = require("node:util");
|
|
135
|
+
init_stream();
|
|
69
136
|
TeamCityAPIError = class _TeamCityAPIError extends Error {
|
|
70
137
|
code;
|
|
71
138
|
statusCode;
|
|
@@ -89,7 +156,7 @@ var init_errors = __esm({
|
|
|
89
156
|
*/
|
|
90
157
|
static fromAxiosError(error3, requestId) {
|
|
91
158
|
if (error3.response) {
|
|
92
|
-
const data = error3.response.data;
|
|
159
|
+
const data = isReadableStream(error3.response.data) ? void 0 : error3.response.data;
|
|
93
160
|
return new _TeamCityAPIError(
|
|
94
161
|
(typeof data?.["message"] === "string" ? data["message"] : null) ?? error3.message,
|
|
95
162
|
(typeof data?.["code"] === "string" ? data["code"] : null) ?? `HTTP_${error3.response.status}`,
|
|
@@ -115,6 +182,9 @@ var init_errors = __esm({
|
|
|
115
182
|
stack: this.stack
|
|
116
183
|
};
|
|
117
184
|
}
|
|
185
|
+
[import_node_util2.inspect.custom]() {
|
|
186
|
+
return this.toJSON();
|
|
187
|
+
}
|
|
118
188
|
};
|
|
119
189
|
TeamCityAuthorizationError = class extends TeamCityAPIError {
|
|
120
190
|
constructor(message = "Authorization failed", requestId, originalError) {
|
|
@@ -956,7 +1026,21 @@ var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
|
956
1026
|
|
|
957
1027
|
// src/utils/logger/index.ts
|
|
958
1028
|
var import_node_fs = require("node:fs");
|
|
1029
|
+
var import_node_util = require("node:util");
|
|
959
1030
|
var import_winston = __toESM(require("winston"));
|
|
1031
|
+
function safeStringify(value) {
|
|
1032
|
+
try {
|
|
1033
|
+
return (0, import_node_util.inspect)(value, {
|
|
1034
|
+
depth: 4,
|
|
1035
|
+
maxArrayLength: 50,
|
|
1036
|
+
maxStringLength: 1e3,
|
|
1037
|
+
breakLength: Infinity,
|
|
1038
|
+
compact: true
|
|
1039
|
+
});
|
|
1040
|
+
} catch {
|
|
1041
|
+
return "[Uninspectable value]";
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
960
1044
|
var TeamCityLogger = class _TeamCityLogger {
|
|
961
1045
|
winston;
|
|
962
1046
|
config;
|
|
@@ -1052,7 +1136,8 @@ var TeamCityLogger = class _TeamCityLogger {
|
|
|
1052
1136
|
if (projectId) contextParts.push(`project=${projectId}`);
|
|
1053
1137
|
if (duration !== void 0) contextParts.push(`${duration}ms`);
|
|
1054
1138
|
const contextString = contextParts.length > 0 ? ` [${contextParts.join(" ")}]` : "";
|
|
1055
|
-
const
|
|
1139
|
+
const metaEntries = Object.entries(otherMeta);
|
|
1140
|
+
const metaString = metaEntries.length > 0 ? ` ${safeStringify(Object.fromEntries(metaEntries))}` : "";
|
|
1056
1141
|
return `${baseLog}${contextString}${metaString}`;
|
|
1057
1142
|
}
|
|
1058
1143
|
/**
|
|
@@ -1225,7 +1310,7 @@ function debug2(message, meta) {
|
|
|
1225
1310
|
// package.json
|
|
1226
1311
|
var package_default = {
|
|
1227
1312
|
name: "@daghis/teamcity-mcp",
|
|
1228
|
-
version: "2.12.
|
|
1313
|
+
version: "2.12.7",
|
|
1229
1314
|
description: "Model Control Protocol server for TeamCity CI/CD integration with AI coding assistants",
|
|
1230
1315
|
mcpName: "io.github.Daghis/teamcity",
|
|
1231
1316
|
main: "dist/index.js",
|
|
@@ -4845,8 +4930,10 @@ axiosRetry.isRetryableError = isRetryableError2;
|
|
|
4845
4930
|
var esm_default = axiosRetry;
|
|
4846
4931
|
|
|
4847
4932
|
// src/teamcity/auth.ts
|
|
4933
|
+
var import_node_string_decoder = require("node:string_decoder");
|
|
4848
4934
|
var import_crypto = require("crypto");
|
|
4849
4935
|
init_errors();
|
|
4936
|
+
init_stream();
|
|
4850
4937
|
var asTimingMetaContainer = (value) => {
|
|
4851
4938
|
if (typeof value === "object" && value !== null) {
|
|
4852
4939
|
return value;
|
|
@@ -4891,10 +4978,54 @@ function logResponse(response) {
|
|
|
4891
4978
|
});
|
|
4892
4979
|
return response;
|
|
4893
4980
|
}
|
|
4894
|
-
|
|
4981
|
+
var STREAM_BODY_TIMEOUT_MS = 5e3;
|
|
4982
|
+
var streamToString = async (stream, maxBytes = 64 * 1024) => {
|
|
4983
|
+
const chunks = [];
|
|
4984
|
+
let total = 0;
|
|
4985
|
+
let truncated = false;
|
|
4986
|
+
for await (const chunk of stream) {
|
|
4987
|
+
const buf = toBuffer(chunk);
|
|
4988
|
+
const retained = buf.subarray(0, maxBytes - total);
|
|
4989
|
+
chunks.push(retained);
|
|
4990
|
+
total += retained.length;
|
|
4991
|
+
if (total >= maxBytes) {
|
|
4992
|
+
truncated = true;
|
|
4993
|
+
break;
|
|
4994
|
+
}
|
|
4995
|
+
}
|
|
4996
|
+
const decoder = new import_node_string_decoder.StringDecoder("utf8");
|
|
4997
|
+
const text = decoder.write(Buffer.concat(chunks, total)) + (truncated ? "" : decoder.end());
|
|
4998
|
+
if (Buffer.byteLength(text) <= maxBytes) {
|
|
4999
|
+
return text;
|
|
5000
|
+
}
|
|
5001
|
+
return new import_node_string_decoder.StringDecoder("utf8").write(Buffer.from(text).subarray(0, maxBytes));
|
|
5002
|
+
};
|
|
5003
|
+
var snapshotStreamBody = async (stream, timeoutMs) => {
|
|
5004
|
+
let timer;
|
|
5005
|
+
const deadline = new Promise((_resolve, reject) => {
|
|
5006
|
+
timer = setTimeout(() => reject(new Error("Timed out reading streamed error body")), timeoutMs);
|
|
5007
|
+
});
|
|
5008
|
+
try {
|
|
5009
|
+
return await Promise.race([streamToString(stream), deadline]);
|
|
5010
|
+
} catch {
|
|
5011
|
+
destroyStream(stream);
|
|
5012
|
+
return void 0;
|
|
5013
|
+
} finally {
|
|
5014
|
+
clearTimeout(timer);
|
|
5015
|
+
}
|
|
5016
|
+
};
|
|
5017
|
+
async function logAndTransformError(error3) {
|
|
4895
5018
|
if (error3 instanceof TeamCityAPIError) {
|
|
4896
5019
|
return Promise.reject(error3);
|
|
4897
5020
|
}
|
|
5021
|
+
const response = error3.response;
|
|
5022
|
+
if (response && isReadableStream(response.data)) {
|
|
5023
|
+
const requestTimeout = error3.config?.timeout ?? 0;
|
|
5024
|
+
response.data = await snapshotStreamBody(
|
|
5025
|
+
response.data,
|
|
5026
|
+
requestTimeout > 0 ? Math.min(requestTimeout, STREAM_BODY_TIMEOUT_MS) : STREAM_BODY_TIMEOUT_MS
|
|
5027
|
+
);
|
|
5028
|
+
}
|
|
4898
5029
|
const requestId = error3.config?.requestId;
|
|
4899
5030
|
const tcError = TeamCityAPIError.fromAxiosError(error3, requestId);
|
|
4900
5031
|
const meta = asTimingMetaContainer(error3.config)?._tcMeta;
|
|
@@ -4950,6 +5081,7 @@ function validateConfiguration(baseUrl, token) {
|
|
|
4950
5081
|
|
|
4951
5082
|
// src/api-client.ts
|
|
4952
5083
|
init_errors();
|
|
5084
|
+
init_stream();
|
|
4953
5085
|
|
|
4954
5086
|
// src/teamcity-client/api/agent-api.ts
|
|
4955
5087
|
var import_axios5 = __toESM(require("axios"));
|
|
@@ -38427,6 +38559,24 @@ var extractRetryAfterMilliseconds = (value) => {
|
|
|
38427
38559
|
}
|
|
38428
38560
|
return void 0;
|
|
38429
38561
|
};
|
|
38562
|
+
var toOptionalLineNumber = (value) => {
|
|
38563
|
+
if (value === void 0 || value === null || value === "") {
|
|
38564
|
+
return void 0;
|
|
38565
|
+
}
|
|
38566
|
+
const parsed = Number(value);
|
|
38567
|
+
return Number.isInteger(parsed) && parsed >= 0 ? parsed : void 0;
|
|
38568
|
+
};
|
|
38569
|
+
var sliceLogData = (data, startLine, lineCount) => {
|
|
38570
|
+
if (isReadableStream(data)) {
|
|
38571
|
+
return sliceStreamLines(data, startLine, lineCount);
|
|
38572
|
+
}
|
|
38573
|
+
if (typeof data === "string") {
|
|
38574
|
+
const lines = data.split("\n");
|
|
38575
|
+
const end = lineCount === void 0 ? void 0 : startLine + lineCount;
|
|
38576
|
+
return lines.slice(startLine, end).join("\n");
|
|
38577
|
+
}
|
|
38578
|
+
return data;
|
|
38579
|
+
};
|
|
38430
38580
|
var TeamCityAPI = class _TeamCityAPI {
|
|
38431
38581
|
static instance;
|
|
38432
38582
|
static instanceConfig;
|
|
@@ -38491,6 +38641,7 @@ var TeamCityAPI = class _TeamCityAPI {
|
|
|
38491
38641
|
this.http = this.axiosInstance;
|
|
38492
38642
|
esm_default(this.axiosInstance, {
|
|
38493
38643
|
retries: 3,
|
|
38644
|
+
onRetry: (_retryCount, error3) => discardStreamBody(error3.response?.data),
|
|
38494
38645
|
retryDelay: (retryCount, error3) => {
|
|
38495
38646
|
const reqId = error3?.config?.requestId;
|
|
38496
38647
|
const tcError = TeamCityAPIError.fromAxiosError(error3, reqId);
|
|
@@ -38748,27 +38899,51 @@ var TeamCityAPI = class _TeamCityAPI {
|
|
|
38748
38899
|
);
|
|
38749
38900
|
}
|
|
38750
38901
|
async downloadBuildLog(buildId, options) {
|
|
38902
|
+
const responseType = options?.responseType ?? "text";
|
|
38903
|
+
const rawHeaders = options?.headers ?? void 0;
|
|
38904
|
+
const headers = { Accept: "text/plain", ...rawHeaders ?? {} };
|
|
38905
|
+
const transformResponse = options?.transformResponse ?? [(data) => data];
|
|
38751
38906
|
const rawParams = options?.params ?? void 0;
|
|
38752
|
-
const
|
|
38753
|
-
|
|
38754
|
-
|
|
38907
|
+
const { start, count, ...primaryParams } = rawParams ?? {};
|
|
38908
|
+
const startLine = toOptionalLineNumber(start);
|
|
38909
|
+
const lineCount = toOptionalLineNumber(count);
|
|
38910
|
+
try {
|
|
38911
|
+
const response = await this.axiosInstance.get(`/downloadBuildLog.html`, {
|
|
38912
|
+
...options,
|
|
38913
|
+
params: { ...primaryParams, buildId },
|
|
38914
|
+
headers,
|
|
38915
|
+
responseType,
|
|
38916
|
+
transformResponse
|
|
38917
|
+
});
|
|
38918
|
+
if (startLine === void 0 && lineCount === void 0) {
|
|
38919
|
+
return response;
|
|
38920
|
+
}
|
|
38921
|
+
return { ...response, data: sliceLogData(response.data, startLine ?? 0, lineCount) };
|
|
38922
|
+
} catch (primaryError) {
|
|
38923
|
+
const status = primaryError instanceof TeamCityAPIError ? primaryError.statusCode : void 0;
|
|
38924
|
+
if (status === void 0 || [401, 403, 429, 503].includes(status)) {
|
|
38925
|
+
throw primaryError;
|
|
38926
|
+
}
|
|
38927
|
+
const params = rawParams ? { ...rawParams } : {};
|
|
38928
|
+
if (!Object.prototype.hasOwnProperty.call(params, "plain")) {
|
|
38929
|
+
params["plain"] = true;
|
|
38930
|
+
}
|
|
38931
|
+
try {
|
|
38932
|
+
return await this.axiosInstance.get(`/app/rest/builds/${toBuildLocator(buildId)}/log`, {
|
|
38933
|
+
...options,
|
|
38934
|
+
params,
|
|
38935
|
+
headers,
|
|
38936
|
+
responseType,
|
|
38937
|
+
transformResponse
|
|
38938
|
+
});
|
|
38939
|
+
} catch (fallbackError) {
|
|
38940
|
+
const fallbackStatus = fallbackError instanceof TeamCityAPIError ? fallbackError.statusCode : void 0;
|
|
38941
|
+
if (fallbackStatus === 404 || fallbackStatus === 405) {
|
|
38942
|
+
throw primaryError;
|
|
38943
|
+
}
|
|
38944
|
+
throw fallbackError;
|
|
38945
|
+
}
|
|
38755
38946
|
}
|
|
38756
|
-
const rawHeaders = options?.headers ?? void 0;
|
|
38757
|
-
const headers = rawHeaders ? { ...rawHeaders } : {};
|
|
38758
|
-
const requestOptions = {
|
|
38759
|
-
...options,
|
|
38760
|
-
params,
|
|
38761
|
-
headers: {
|
|
38762
|
-
Accept: "text/plain",
|
|
38763
|
-
...headers
|
|
38764
|
-
},
|
|
38765
|
-
responseType: options?.responseType ?? "text",
|
|
38766
|
-
transformResponse: options?.transformResponse ?? [(data) => data]
|
|
38767
|
-
};
|
|
38768
|
-
return this.axiosInstance.get(
|
|
38769
|
-
`/app/rest/builds/${toBuildLocator(buildId)}/log`,
|
|
38770
|
-
requestOptions
|
|
38771
|
-
);
|
|
38772
38947
|
}
|
|
38773
38948
|
async getBuildStatistics(buildId, fields) {
|
|
38774
38949
|
return this.builds.getBuildStatisticValues(toBuildLocator(buildId), fields);
|
|
@@ -38857,7 +39032,7 @@ var extraHeadersEqual = (a, b) => {
|
|
|
38857
39032
|
};
|
|
38858
39033
|
|
|
38859
39034
|
// src/tools.ts
|
|
38860
|
-
var
|
|
39035
|
+
var isReadableStream2 = (value) => typeof value === "object" && value !== null && typeof value.pipe === "function";
|
|
38861
39036
|
var isAxios4042 = (error3) => (0, import_axios36.isAxiosError)(error3) && error3.response?.status === 404;
|
|
38862
39037
|
var buildIdentifierSchema = import_zod4.z.object({
|
|
38863
39038
|
buildId: import_zod4.z.string().min(1).optional(),
|
|
@@ -38982,7 +39157,7 @@ var writeArtifactStreamToDisk = async (artifact, stream, options) => {
|
|
|
38982
39157
|
var buildArtifactPayload = async (artifact, encoding, options) => {
|
|
38983
39158
|
if (encoding === "stream") {
|
|
38984
39159
|
const contentStream = artifact.content;
|
|
38985
|
-
if (!
|
|
39160
|
+
if (!isReadableStream2(contentStream)) {
|
|
38986
39161
|
throw new Error("Streaming download did not return a readable stream");
|
|
38987
39162
|
}
|
|
38988
39163
|
const { outputPath, bytesWritten } = await writeArtifactStreamToDisk(
|
|
@@ -39066,7 +39241,7 @@ var downloadArtifactByUrl = async (adapter, request, encoding, options) => {
|
|
|
39066
39241
|
}
|
|
39067
39242
|
if (encoding === "stream") {
|
|
39068
39243
|
const stream = response.data;
|
|
39069
|
-
if (!
|
|
39244
|
+
if (!isReadableStream2(stream)) {
|
|
39070
39245
|
throw new Error("Streaming download did not return a readable stream");
|
|
39071
39246
|
}
|
|
39072
39247
|
const artifact2 = {
|
|
@@ -40237,7 +40412,7 @@ var DEV_TOOLS = [
|
|
|
40237
40412
|
responseType: "stream"
|
|
40238
40413
|
});
|
|
40239
40414
|
const stream = response.data;
|
|
40240
|
-
if (!
|
|
40415
|
+
if (!isReadableStream2(stream)) {
|
|
40241
40416
|
throw new Error("Streaming log download did not return a readable stream");
|
|
40242
40417
|
}
|
|
40243
40418
|
const safeBuildId = effectiveBuildId.replace(/[^a-zA-Z0-9._-]/g, "_") || "build";
|
package/package.json
CHANGED
package/server.json
CHANGED
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
9
|
"websiteUrl": "https://github.com/Daghis/teamcity-mcp",
|
|
10
|
-
"version": "2.12.
|
|
10
|
+
"version": "2.12.7",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
15
15
|
"identifier": "@daghis/teamcity-mcp",
|
|
16
|
-
"version": "2.12.
|
|
16
|
+
"version": "2.12.7",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"runtimeArguments": [
|
|
19
19
|
{
|