@yawlabs/npmjs-mcp 0.9.0 → 0.10.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/index.js +229 -85
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -21011,12 +21011,39 @@ var StdioServerTransport = class {
|
|
|
21011
21011
|
};
|
|
21012
21012
|
|
|
21013
21013
|
// src/api.ts
|
|
21014
|
-
var
|
|
21014
|
+
var DEFAULT_REGISTRY_URL = "https://registry.npmjs.org";
|
|
21015
21015
|
var DOWNLOADS_URL = "https://api.npmjs.org";
|
|
21016
21016
|
var REPLICATE_URL = "https://replicate.npmjs.com";
|
|
21017
|
-
var
|
|
21017
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
21018
|
+
var DEFAULT_BACKOFF_MS = 500;
|
|
21019
|
+
var MAX_RETRIES = 2;
|
|
21020
|
+
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503, 504]);
|
|
21021
|
+
function getRegistryUrl() {
|
|
21022
|
+
return (process.env.NPM_REGISTRY || DEFAULT_REGISTRY_URL).replace(/\/+$/, "");
|
|
21023
|
+
}
|
|
21024
|
+
function getTimeoutMs() {
|
|
21025
|
+
const raw = process.env.NPM_REQUEST_TIMEOUT_MS;
|
|
21026
|
+
if (!raw) return DEFAULT_TIMEOUT_MS;
|
|
21027
|
+
const parsed = Number(raw);
|
|
21028
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_TIMEOUT_MS;
|
|
21029
|
+
}
|
|
21030
|
+
function getBackoffMs(attempt) {
|
|
21031
|
+
const raw = process.env.NPM_RETRY_BACKOFF_MS;
|
|
21032
|
+
const parsed = raw !== void 0 ? Number(raw) : Number.NaN;
|
|
21033
|
+
const base = Number.isFinite(parsed) && parsed >= 0 ? parsed : DEFAULT_BACKOFF_MS;
|
|
21034
|
+
return base * 2 ** attempt;
|
|
21035
|
+
}
|
|
21036
|
+
function debugEnabled() {
|
|
21037
|
+
const v = process.env.DEBUG;
|
|
21038
|
+
return v === "npmjs-mcp" || v === "*";
|
|
21039
|
+
}
|
|
21040
|
+
function debug(msg) {
|
|
21041
|
+
if (debugEnabled()) console.error(`[npmjs-mcp] ${msg}`);
|
|
21042
|
+
}
|
|
21018
21043
|
var PACKAGE_NAME_MAX_LENGTH = 214;
|
|
21019
21044
|
var PACKAGE_NAME_PATTERN = /^(?:@[a-zA-Z0-9][a-zA-Z0-9\-_.]*\/)?[a-zA-Z0-9][a-zA-Z0-9\-_.]*$/;
|
|
21045
|
+
var IDENT_MAX_LENGTH = 214;
|
|
21046
|
+
var IDENT_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9\-_.]*$/;
|
|
21020
21047
|
function validatePackageName(name) {
|
|
21021
21048
|
if (typeof name !== "string" || name.length === 0) return "Package name is empty";
|
|
21022
21049
|
if (name.length > PACKAGE_NAME_MAX_LENGTH) {
|
|
@@ -21027,11 +21054,43 @@ function validatePackageName(name) {
|
|
|
21027
21054
|
}
|
|
21028
21055
|
return null;
|
|
21029
21056
|
}
|
|
21057
|
+
function validateIdent(value, label) {
|
|
21058
|
+
if (typeof value !== "string" || value.length === 0) return `${label} is empty`;
|
|
21059
|
+
if (value.length > IDENT_MAX_LENGTH) return `${label} exceeds ${IDENT_MAX_LENGTH} characters`;
|
|
21060
|
+
if (!IDENT_PATTERN.test(value)) {
|
|
21061
|
+
return `Invalid ${label.toLowerCase()} '${value}'. Must start alphanumeric and contain only [a-zA-Z0-9-_.].`;
|
|
21062
|
+
}
|
|
21063
|
+
return null;
|
|
21064
|
+
}
|
|
21065
|
+
function validateScope(scope) {
|
|
21066
|
+
return validateIdent(scope.replace(/^@/, ""), "Scope");
|
|
21067
|
+
}
|
|
21068
|
+
function validateUsername(username) {
|
|
21069
|
+
return validateIdent(username.replace(/^@/, ""), "Username");
|
|
21070
|
+
}
|
|
21071
|
+
function validateTeam(team) {
|
|
21072
|
+
return validateIdent(team, "Team name");
|
|
21073
|
+
}
|
|
21030
21074
|
function encPkg(name) {
|
|
21031
21075
|
const err = validatePackageName(name);
|
|
21032
21076
|
if (err) throw new Error(err);
|
|
21033
21077
|
return name.startsWith("@") ? `@${encodeURIComponent(name.slice(1))}` : encodeURIComponent(name);
|
|
21034
21078
|
}
|
|
21079
|
+
function encScope(scope) {
|
|
21080
|
+
const err = validateScope(scope);
|
|
21081
|
+
if (err) throw new Error(err);
|
|
21082
|
+
return encodeURIComponent(scope.replace(/^@/, ""));
|
|
21083
|
+
}
|
|
21084
|
+
function encUser(username) {
|
|
21085
|
+
const err = validateUsername(username);
|
|
21086
|
+
if (err) throw new Error(err);
|
|
21087
|
+
return encodeURIComponent(username.replace(/^@/, ""));
|
|
21088
|
+
}
|
|
21089
|
+
function encTeam(team) {
|
|
21090
|
+
const err = validateTeam(team);
|
|
21091
|
+
if (err) throw new Error(err);
|
|
21092
|
+
return encodeURIComponent(team);
|
|
21093
|
+
}
|
|
21035
21094
|
function isAuthenticated() {
|
|
21036
21095
|
return !!process.env.NPM_TOKEN;
|
|
21037
21096
|
}
|
|
@@ -21047,6 +21106,17 @@ function authHeaders() {
|
|
|
21047
21106
|
const token = process.env.NPM_TOKEN;
|
|
21048
21107
|
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
21049
21108
|
}
|
|
21109
|
+
function parseRetryAfter(header) {
|
|
21110
|
+
if (!header) return null;
|
|
21111
|
+
const seconds = Number(header);
|
|
21112
|
+
if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
|
|
21113
|
+
const date3 = Date.parse(header);
|
|
21114
|
+
if (!Number.isNaN(date3)) return Math.max(0, date3 - Date.now());
|
|
21115
|
+
return null;
|
|
21116
|
+
}
|
|
21117
|
+
function sleep(ms) {
|
|
21118
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
21119
|
+
}
|
|
21050
21120
|
async function request(baseUrl, path, options) {
|
|
21051
21121
|
const method = options?.method ?? "GET";
|
|
21052
21122
|
const headers = {
|
|
@@ -21059,49 +21129,75 @@ async function request(baseUrl, path, options) {
|
|
|
21059
21129
|
fetchBody = JSON.stringify(options.body);
|
|
21060
21130
|
}
|
|
21061
21131
|
const url = `${baseUrl}${path}`;
|
|
21062
|
-
|
|
21063
|
-
const
|
|
21064
|
-
|
|
21065
|
-
|
|
21066
|
-
|
|
21067
|
-
|
|
21068
|
-
|
|
21069
|
-
|
|
21070
|
-
|
|
21071
|
-
|
|
21072
|
-
|
|
21073
|
-
|
|
21074
|
-
|
|
21132
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
21133
|
+
const started = Date.now();
|
|
21134
|
+
try {
|
|
21135
|
+
const res = await fetch(url, {
|
|
21136
|
+
method,
|
|
21137
|
+
headers,
|
|
21138
|
+
body: fetchBody,
|
|
21139
|
+
signal: AbortSignal.timeout(getTimeoutMs())
|
|
21140
|
+
});
|
|
21141
|
+
if (RETRYABLE_STATUSES.has(res.status) && attempt < MAX_RETRIES) {
|
|
21142
|
+
const retryAfter = parseRetryAfter(res.headers.get("Retry-After"));
|
|
21143
|
+
const waitMs = retryAfter ?? getBackoffMs(attempt);
|
|
21144
|
+
debug(`${method} ${url} -> ${res.status} retry in ${waitMs}ms (attempt ${attempt + 1}/${MAX_RETRIES + 1})`);
|
|
21145
|
+
try {
|
|
21146
|
+
await res.text();
|
|
21147
|
+
} catch {
|
|
21148
|
+
}
|
|
21149
|
+
await sleep(waitMs);
|
|
21150
|
+
continue;
|
|
21151
|
+
}
|
|
21152
|
+
const elapsed = Date.now() - started;
|
|
21153
|
+
if (!res.ok) {
|
|
21154
|
+
const errorBody = await res.text();
|
|
21155
|
+
debug(`${method} ${url} -> ${res.status} ${elapsed}ms`);
|
|
21156
|
+
return { ok: false, status: res.status, error: errorBody };
|
|
21157
|
+
}
|
|
21158
|
+
if (res.status === 204 || res.headers.get("content-length") === "0") {
|
|
21159
|
+
debug(`${method} ${url} -> ${res.status} ${elapsed}ms`);
|
|
21160
|
+
return { ok: true, status: res.status };
|
|
21161
|
+
}
|
|
21162
|
+
const data = await res.json();
|
|
21163
|
+
debug(`${method} ${url} -> ${res.status} ${elapsed}ms`);
|
|
21164
|
+
return { ok: true, status: res.status, data };
|
|
21165
|
+
} catch (err) {
|
|
21166
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
21167
|
+
if (attempt < MAX_RETRIES) {
|
|
21168
|
+
const waitMs = getBackoffMs(attempt);
|
|
21169
|
+
debug(`${method} ${url} -> network error retry in ${waitMs}ms (${message})`);
|
|
21170
|
+
await sleep(waitMs);
|
|
21171
|
+
continue;
|
|
21172
|
+
}
|
|
21173
|
+
debug(`${method} ${url} -> network error: ${message}`);
|
|
21174
|
+
return { ok: false, status: 0, error: message };
|
|
21075
21175
|
}
|
|
21076
|
-
const data = await res.json();
|
|
21077
|
-
return { ok: true, status: res.status, data };
|
|
21078
|
-
} catch (err) {
|
|
21079
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
21080
|
-
return { ok: false, status: 0, error: message };
|
|
21081
21176
|
}
|
|
21177
|
+
return { ok: false, status: 0, error: "unreachable" };
|
|
21082
21178
|
}
|
|
21083
21179
|
function registryGet(path) {
|
|
21084
|
-
return request(
|
|
21180
|
+
return request(getRegistryUrl(), path);
|
|
21085
21181
|
}
|
|
21086
21182
|
function registryGetAbbreviated(path) {
|
|
21087
|
-
return request(
|
|
21183
|
+
return request(getRegistryUrl(), path, {
|
|
21088
21184
|
headers: { Accept: "application/vnd.npm.install-v1+json" }
|
|
21089
21185
|
});
|
|
21090
21186
|
}
|
|
21091
21187
|
function registryPost(path, body) {
|
|
21092
|
-
return request(
|
|
21188
|
+
return request(getRegistryUrl(), path, { method: "POST", body });
|
|
21093
21189
|
}
|
|
21094
21190
|
function registryGetAuth(path) {
|
|
21095
|
-
return request(
|
|
21191
|
+
return request(getRegistryUrl(), path, { headers: authHeaders() });
|
|
21096
21192
|
}
|
|
21097
21193
|
function registryPostAuth(path, body) {
|
|
21098
|
-
return request(
|
|
21194
|
+
return request(getRegistryUrl(), path, { method: "POST", body, headers: authHeaders() });
|
|
21099
21195
|
}
|
|
21100
21196
|
function registryPutAuth(path, body) {
|
|
21101
|
-
return request(
|
|
21197
|
+
return request(getRegistryUrl(), path, { method: "PUT", body, headers: authHeaders() });
|
|
21102
21198
|
}
|
|
21103
21199
|
function registryDeleteAuth(path, body) {
|
|
21104
|
-
return request(
|
|
21200
|
+
return request(getRegistryUrl(), path, { method: "DELETE", body, headers: authHeaders() });
|
|
21105
21201
|
}
|
|
21106
21202
|
function downloadsGet(path) {
|
|
21107
21203
|
return request(DOWNLOADS_URL, path);
|
|
@@ -21269,12 +21365,12 @@ function translateError(res, context) {
|
|
|
21269
21365
|
case 422:
|
|
21270
21366
|
return {
|
|
21271
21367
|
...res,
|
|
21272
|
-
error: `Registry rejected the request payload${pkgPart}${opPart} (422 Unprocessable Entity). Most common causes: (1)
|
|
21368
|
+
error: `Registry rejected the request payload${pkgPart}${opPart} (422 Unprocessable Entity). Most common causes: (1) semver range matches no published versions \u2014 validate with npm_versions first; (2) deprecation message exceeds 1024 characters; (3) account-level 2FA policy requires an interactive CLI session. If #3, CLI fallback: \`npm login --auth-type=web\` followed by the equivalent npm CLI command. Raw: ${res.error}`
|
|
21273
21369
|
};
|
|
21274
21370
|
case 429:
|
|
21275
21371
|
return {
|
|
21276
21372
|
...res,
|
|
21277
|
-
error: `Rate limited${opPart}.
|
|
21373
|
+
error: `Rate limited${opPart}. Retried automatically and still failed \u2014 wait longer and retry, or contact npm support if this persists. Raw: ${res.error}`
|
|
21278
21374
|
};
|
|
21279
21375
|
case 0:
|
|
21280
21376
|
return {
|
|
@@ -21289,10 +21385,6 @@ function validateDeprecationMessage(msg) {
|
|
|
21289
21385
|
if (msg.length > 1024) {
|
|
21290
21386
|
return "Deprecation message exceeds 1024 characters (registry limit).";
|
|
21291
21387
|
}
|
|
21292
|
-
if (msg.length === 0) return null;
|
|
21293
|
-
if (/\.\s+[A-Z]/.test(msg)) {
|
|
21294
|
-
return `Deprecation message contains the "period + space + capital letter" pattern that has triggered 422 Unprocessable Entity on at least one scoped package. The working form uses em-dash and lowercase continuation: e.g. "Renamed to @yawlabs/spend \u2014 install that instead". Pass force: true to bypass this validation.`;
|
|
21295
|
-
}
|
|
21296
21388
|
return null;
|
|
21297
21389
|
}
|
|
21298
21390
|
function versionsMatchingRange(versions, range, maxSatisfying2) {
|
|
@@ -22030,10 +22122,24 @@ function classifyHookTarget(target) {
|
|
|
22030
22122
|
if (/^@[^/]+$/.test(target)) return { type: "scope", name: target };
|
|
22031
22123
|
return { type: "package", name: target };
|
|
22032
22124
|
}
|
|
22125
|
+
function stripSecrets(data) {
|
|
22126
|
+
if (Array.isArray(data)) {
|
|
22127
|
+
return data.map((item) => stripSecrets(item));
|
|
22128
|
+
}
|
|
22129
|
+
if (data !== null && typeof data === "object") {
|
|
22130
|
+
const out = {};
|
|
22131
|
+
for (const [k, v] of Object.entries(data)) {
|
|
22132
|
+
if (k === "secret") continue;
|
|
22133
|
+
out[k] = stripSecrets(v);
|
|
22134
|
+
}
|
|
22135
|
+
return out;
|
|
22136
|
+
}
|
|
22137
|
+
return data;
|
|
22138
|
+
}
|
|
22033
22139
|
var hookTools = [
|
|
22034
22140
|
{
|
|
22035
22141
|
name: "npm_hook_add",
|
|
22036
|
-
description: "Create a registry webhook. Target is 'pkg' or '@scope/pkg' for a package, '@scope' for a scope, or '~user' for a user's packages. Endpoint is the HTTPS URL to POST events to; secret is used to HMAC-sign payloads.",
|
|
22142
|
+
description: "Create a registry webhook. Target is 'pkg' or '@scope/pkg' for a package, '@scope' for a scope, or '~user' for a user's packages. Endpoint is the HTTPS URL to POST events to; secret is used to HMAC-sign payloads. The secret is never echoed back in tool responses.",
|
|
22037
22143
|
annotations: {
|
|
22038
22144
|
title: "Add webhook",
|
|
22039
22145
|
readOnlyHint: false,
|
|
@@ -22057,12 +22163,12 @@ var hookTools = [
|
|
|
22057
22163
|
secret: input.secret
|
|
22058
22164
|
});
|
|
22059
22165
|
if (!res.ok) return translateError(res, { op: `hook_add ${input.target}` });
|
|
22060
|
-
return { ok: true, status: 200, data: res.data };
|
|
22166
|
+
return { ok: true, status: 200, data: stripSecrets(res.data) };
|
|
22061
22167
|
}
|
|
22062
22168
|
},
|
|
22063
22169
|
{
|
|
22064
22170
|
name: "npm_hook_list",
|
|
22065
|
-
description: "List webhooks. Optionally filter by package name.",
|
|
22171
|
+
description: "List webhooks. Optionally filter by package name. Secrets are redacted from responses.",
|
|
22066
22172
|
annotations: {
|
|
22067
22173
|
title: "List webhooks",
|
|
22068
22174
|
readOnlyHint: true,
|
|
@@ -22085,12 +22191,12 @@ var hookTools = [
|
|
|
22085
22191
|
const q = qs.toString();
|
|
22086
22192
|
const res = await registryGetAuth(`/-/npm/v1/hooks${q ? `?${q}` : ""}`);
|
|
22087
22193
|
if (!res.ok) return translateError(res, { op: "hook_list" });
|
|
22088
|
-
return { ok: true, status: 200, data: res.data };
|
|
22194
|
+
return { ok: true, status: 200, data: stripSecrets(res.data) };
|
|
22089
22195
|
}
|
|
22090
22196
|
},
|
|
22091
22197
|
{
|
|
22092
22198
|
name: "npm_hook_get",
|
|
22093
|
-
description: "Get a single webhook by its ID.",
|
|
22199
|
+
description: "Get a single webhook by its ID. The stored secret is redacted from the response.",
|
|
22094
22200
|
annotations: {
|
|
22095
22201
|
title: "Get webhook",
|
|
22096
22202
|
readOnlyHint: true,
|
|
@@ -22106,12 +22212,12 @@ var hookTools = [
|
|
|
22106
22212
|
if (authErr) return authErr;
|
|
22107
22213
|
const res = await registryGetAuth(`/-/npm/v1/hooks/hook/${encodeURIComponent(input.id)}`);
|
|
22108
22214
|
if (!res.ok) return translateError(res, { op: `hook_get ${input.id}` });
|
|
22109
|
-
return { ok: true, status: 200, data: res.data };
|
|
22215
|
+
return { ok: true, status: 200, data: stripSecrets(res.data) };
|
|
22110
22216
|
}
|
|
22111
22217
|
},
|
|
22112
22218
|
{
|
|
22113
22219
|
name: "npm_hook_update",
|
|
22114
|
-
description: "Update a webhook's endpoint and/or secret.",
|
|
22220
|
+
description: "Update a webhook's endpoint and/or secret. The returned hook object has the secret redacted.",
|
|
22115
22221
|
annotations: {
|
|
22116
22222
|
title: "Update webhook",
|
|
22117
22223
|
readOnlyHint: false,
|
|
@@ -22132,7 +22238,7 @@ var hookTools = [
|
|
|
22132
22238
|
secret: input.secret
|
|
22133
22239
|
});
|
|
22134
22240
|
if (!res.ok) return translateError(res, { op: `hook_update ${input.id}` });
|
|
22135
|
-
return { ok: true, status: 200, data: res.data };
|
|
22241
|
+
return { ok: true, status: 200, data: stripSecrets(res.data) };
|
|
22136
22242
|
}
|
|
22137
22243
|
},
|
|
22138
22244
|
{
|
|
@@ -23151,7 +23257,16 @@ async function fetchPackument(pkg) {
|
|
|
23151
23257
|
}
|
|
23152
23258
|
function parseTeamTarget(target) {
|
|
23153
23259
|
const m = target.match(/^@?([^:]+):(.+)$/);
|
|
23154
|
-
|
|
23260
|
+
if (!m) {
|
|
23261
|
+
return { error: `Team must be in the form '@scope:team' (got '${target}').` };
|
|
23262
|
+
}
|
|
23263
|
+
const scope = m[1];
|
|
23264
|
+
const team = m[2];
|
|
23265
|
+
const scopeErr = validateScope(scope);
|
|
23266
|
+
if (scopeErr) return { error: scopeErr };
|
|
23267
|
+
const teamErr = validateTeam(team);
|
|
23268
|
+
if (teamErr) return { error: teamErr };
|
|
23269
|
+
return { scope, team };
|
|
23155
23270
|
}
|
|
23156
23271
|
function highestVersion(versions) {
|
|
23157
23272
|
const parsed = [];
|
|
@@ -23169,7 +23284,7 @@ var writeTools = [
|
|
|
23169
23284
|
// ───────────────────────────────────────────────────────
|
|
23170
23285
|
{
|
|
23171
23286
|
name: "npm_deprecate",
|
|
23172
|
-
description: "Deprecate a package or specific versions. Shows a warning message on install. Uses the HTTP API with NPM_TOKEN, bypassing the CLI auth friction that causes 422 errors on accounts with 2FA.
|
|
23287
|
+
description: "Deprecate a package or specific versions. Shows a warning message on install. Uses the HTTP API with NPM_TOKEN, bypassing the CLI auth friction that causes 422 errors on accounts with 2FA. Registry hard limit: deprecation messages must be <= 1024 characters. If the registry 422s, first verify the semver range matches at least one published version (npm_versions) \u2014 range/version mismatches are the most common cause, not message format.",
|
|
23173
23288
|
annotations: {
|
|
23174
23289
|
title: "Deprecate package",
|
|
23175
23290
|
readOnlyHint: false,
|
|
@@ -23180,16 +23295,13 @@ var writeTools = [
|
|
|
23180
23295
|
inputSchema: external_exports.object({
|
|
23181
23296
|
name: external_exports.string().describe("Package name (e.g. '@yawlabs/spend')"),
|
|
23182
23297
|
message: external_exports.string().describe("Deprecation message. Empty string to clear deprecation (use npm_undeprecate instead)."),
|
|
23183
|
-
versionRange: external_exports.string().optional().describe("Semver range. Omit to deprecate ALL versions. Example: '<1.0.0' or '0.3.x'.")
|
|
23184
|
-
force: external_exports.boolean().optional().describe("Bypass message format validation (default: false).")
|
|
23298
|
+
versionRange: external_exports.string().optional().describe("Semver range. Omit to deprecate ALL versions. Example: '<1.0.0' or '0.3.x'.")
|
|
23185
23299
|
}),
|
|
23186
23300
|
handler: async (input) => {
|
|
23187
23301
|
const authErr = requireAuth();
|
|
23188
23302
|
if (authErr) return authErr;
|
|
23189
|
-
|
|
23190
|
-
|
|
23191
|
-
if (problem) return { ok: false, status: 400, error: problem };
|
|
23192
|
-
}
|
|
23303
|
+
const problem = validateDeprecationMessage(input.message);
|
|
23304
|
+
if (problem) return { ok: false, status: 400, error: problem };
|
|
23193
23305
|
const pRes = await fetchPackument(input.name);
|
|
23194
23306
|
if (!pRes.ok) return translateError(pRes, { pkg: input.name, op: "deprecate (fetch)" });
|
|
23195
23307
|
const packument = pRes.data;
|
|
@@ -23518,8 +23630,10 @@ var writeTools = [
|
|
|
23518
23630
|
handler: async (input) => {
|
|
23519
23631
|
const authErr = requireAuth();
|
|
23520
23632
|
if (authErr) return authErr;
|
|
23633
|
+
const userErr = validateUsername(input.username);
|
|
23634
|
+
if (userErr) return { ok: false, status: 400, error: userErr };
|
|
23521
23635
|
const uRes = await registryGetAuth(
|
|
23522
|
-
`/-/user/org.couchdb.user:${
|
|
23636
|
+
`/-/user/org.couchdb.user:${encUser(input.username)}`
|
|
23523
23637
|
);
|
|
23524
23638
|
if (!uRes.ok) return translateError(uRes, { pkg: input.name, op: `owner_add (resolve user ${input.username})` });
|
|
23525
23639
|
const userRecord = { name: uRes.data.name, email: uRes.data.email ?? "" };
|
|
@@ -23584,6 +23698,8 @@ var writeTools = [
|
|
|
23584
23698
|
handler: async (input) => {
|
|
23585
23699
|
const authErr = requireAuth();
|
|
23586
23700
|
if (authErr) return authErr;
|
|
23701
|
+
const userErr = validateUsername(input.username);
|
|
23702
|
+
if (userErr) return { ok: false, status: 400, error: userErr };
|
|
23587
23703
|
const pRes = await fetchPackument(input.name);
|
|
23588
23704
|
if (!pRes.ok) return translateError(pRes, { pkg: input.name, op: "owner_remove (fetch)" });
|
|
23589
23705
|
const packument = pRes.data;
|
|
@@ -23715,15 +23831,11 @@ var writeTools = [
|
|
|
23715
23831
|
const authErr = requireAuth();
|
|
23716
23832
|
if (authErr) return authErr;
|
|
23717
23833
|
const parsed = parseTeamTarget(input.team);
|
|
23718
|
-
if (
|
|
23719
|
-
return {
|
|
23720
|
-
ok: false,
|
|
23721
|
-
status: 400,
|
|
23722
|
-
error: `Team must be in the form '@scope:team' (got '${input.team}').`
|
|
23723
|
-
};
|
|
23834
|
+
if ("error" in parsed) {
|
|
23835
|
+
return { ok: false, status: 400, error: parsed.error };
|
|
23724
23836
|
}
|
|
23725
23837
|
const { scope, team } = parsed;
|
|
23726
|
-
const res = await registryPutAuth(`/-/team/${
|
|
23838
|
+
const res = await registryPutAuth(`/-/team/${encScope(scope)}/${encTeam(team)}/package`, {
|
|
23727
23839
|
package: input.package,
|
|
23728
23840
|
permissions: input.permissions
|
|
23729
23841
|
});
|
|
@@ -23756,15 +23868,11 @@ var writeTools = [
|
|
|
23756
23868
|
const authErr = requireAuth();
|
|
23757
23869
|
if (authErr) return authErr;
|
|
23758
23870
|
const parsed = parseTeamTarget(input.team);
|
|
23759
|
-
if (
|
|
23760
|
-
return {
|
|
23761
|
-
ok: false,
|
|
23762
|
-
status: 400,
|
|
23763
|
-
error: `Team must be in the form '@scope:team' (got '${input.team}').`
|
|
23764
|
-
};
|
|
23871
|
+
if ("error" in parsed) {
|
|
23872
|
+
return { ok: false, status: 400, error: parsed.error };
|
|
23765
23873
|
}
|
|
23766
23874
|
const { scope, team } = parsed;
|
|
23767
|
-
const res = await registryDeleteAuth(`/-/team/${
|
|
23875
|
+
const res = await registryDeleteAuth(`/-/team/${encScope(scope)}/${encTeam(team)}/package`, {
|
|
23768
23876
|
package: input.package
|
|
23769
23877
|
});
|
|
23770
23878
|
if (!res.ok) return translateError(res, { pkg: input.package, op: `team_revoke ${input.team}` });
|
|
@@ -23796,11 +23904,11 @@ var writeTools = [
|
|
|
23796
23904
|
const authErr = requireAuth();
|
|
23797
23905
|
if (authErr) return authErr;
|
|
23798
23906
|
const parsed = parseTeamTarget(input.team);
|
|
23799
|
-
if (
|
|
23800
|
-
return { ok: false, status: 400, error:
|
|
23907
|
+
if ("error" in parsed) {
|
|
23908
|
+
return { ok: false, status: 400, error: parsed.error };
|
|
23801
23909
|
}
|
|
23802
23910
|
const { scope, team } = parsed;
|
|
23803
|
-
const res = await registryPutAuth(`/-/org/${
|
|
23911
|
+
const res = await registryPutAuth(`/-/org/${encScope(scope)}/team`, {
|
|
23804
23912
|
name: team,
|
|
23805
23913
|
description: input.description
|
|
23806
23914
|
});
|
|
@@ -23813,7 +23921,7 @@ var writeTools = [
|
|
|
23813
23921
|
// ───────────────────────────────────────────────────────
|
|
23814
23922
|
{
|
|
23815
23923
|
name: "npm_team_delete",
|
|
23816
|
-
description: "Delete a team. Team is passed as '@scope:team'. Revokes all package permissions that team held.",
|
|
23924
|
+
description: "Delete a team. Team is passed as '@scope:team'. Revokes all package permissions that team held. Requires confirm: true \u2014 this removes the team and all its package grants in one call.",
|
|
23817
23925
|
annotations: {
|
|
23818
23926
|
title: "Delete team",
|
|
23819
23927
|
readOnlyHint: false,
|
|
@@ -23822,17 +23930,25 @@ var writeTools = [
|
|
|
23822
23930
|
openWorldHint: true
|
|
23823
23931
|
},
|
|
23824
23932
|
inputSchema: external_exports.object({
|
|
23825
|
-
team: external_exports.string().describe("Team in the form '@scope:team'")
|
|
23933
|
+
team: external_exports.string().describe("Team in the form '@scope:team'"),
|
|
23934
|
+
confirm: external_exports.literal(true).describe("Must be literally true. Guards against accidental team deletion.")
|
|
23826
23935
|
}),
|
|
23827
23936
|
handler: async (input) => {
|
|
23828
23937
|
const authErr = requireAuth();
|
|
23829
23938
|
if (authErr) return authErr;
|
|
23939
|
+
if (input.confirm !== true) {
|
|
23940
|
+
return {
|
|
23941
|
+
ok: false,
|
|
23942
|
+
status: 400,
|
|
23943
|
+
error: "team_delete requires confirm: true. This removes the team and all its package grants."
|
|
23944
|
+
};
|
|
23945
|
+
}
|
|
23830
23946
|
const parsed = parseTeamTarget(input.team);
|
|
23831
|
-
if (
|
|
23832
|
-
return { ok: false, status: 400, error:
|
|
23947
|
+
if ("error" in parsed) {
|
|
23948
|
+
return { ok: false, status: 400, error: parsed.error };
|
|
23833
23949
|
}
|
|
23834
23950
|
const { scope, team } = parsed;
|
|
23835
|
-
const res = await registryDeleteAuth(`/-/team/${
|
|
23951
|
+
const res = await registryDeleteAuth(`/-/team/${encScope(scope)}/${encTeam(team)}`);
|
|
23836
23952
|
if (!res.ok) return translateError(res, { op: `team_delete ${input.team}` });
|
|
23837
23953
|
return { ok: true, status: 200, data: { team: `@${scope}:${team}`, deleted: true } };
|
|
23838
23954
|
}
|
|
@@ -23857,12 +23973,14 @@ var writeTools = [
|
|
|
23857
23973
|
handler: async (input) => {
|
|
23858
23974
|
const authErr = requireAuth();
|
|
23859
23975
|
if (authErr) return authErr;
|
|
23976
|
+
const userErr = validateUsername(input.user);
|
|
23977
|
+
if (userErr) return { ok: false, status: 400, error: userErr };
|
|
23860
23978
|
const parsed = parseTeamTarget(input.team);
|
|
23861
|
-
if (
|
|
23862
|
-
return { ok: false, status: 400, error:
|
|
23979
|
+
if ("error" in parsed) {
|
|
23980
|
+
return { ok: false, status: 400, error: parsed.error };
|
|
23863
23981
|
}
|
|
23864
23982
|
const { scope, team } = parsed;
|
|
23865
|
-
const res = await registryPutAuth(`/-/team/${
|
|
23983
|
+
const res = await registryPutAuth(`/-/team/${encScope(scope)}/${encTeam(team)}/user`, {
|
|
23866
23984
|
user: input.user
|
|
23867
23985
|
});
|
|
23868
23986
|
if (!res.ok) return translateError(res, { op: `team_member_add ${input.team}` });
|
|
@@ -23889,12 +24007,14 @@ var writeTools = [
|
|
|
23889
24007
|
handler: async (input) => {
|
|
23890
24008
|
const authErr = requireAuth();
|
|
23891
24009
|
if (authErr) return authErr;
|
|
24010
|
+
const userErr = validateUsername(input.user);
|
|
24011
|
+
if (userErr) return { ok: false, status: 400, error: userErr };
|
|
23892
24012
|
const parsed = parseTeamTarget(input.team);
|
|
23893
|
-
if (
|
|
23894
|
-
return { ok: false, status: 400, error:
|
|
24013
|
+
if ("error" in parsed) {
|
|
24014
|
+
return { ok: false, status: 400, error: parsed.error };
|
|
23895
24015
|
}
|
|
23896
24016
|
const { scope, team } = parsed;
|
|
23897
|
-
const res = await registryDeleteAuth(`/-/team/${
|
|
24017
|
+
const res = await registryDeleteAuth(`/-/team/${encScope(scope)}/${encTeam(team)}/user`, {
|
|
23898
24018
|
user: input.user
|
|
23899
24019
|
});
|
|
23900
24020
|
if (!res.ok) return translateError(res, { op: `team_member_remove ${input.team}` });
|
|
@@ -23922,11 +24042,15 @@ var writeTools = [
|
|
|
23922
24042
|
handler: async (input) => {
|
|
23923
24043
|
const authErr = requireAuth();
|
|
23924
24044
|
if (authErr) return authErr;
|
|
24045
|
+
const orgErr = validateScope(input.org);
|
|
24046
|
+
if (orgErr) return { ok: false, status: 400, error: orgErr };
|
|
24047
|
+
const userErr = validateUsername(input.user);
|
|
24048
|
+
if (userErr) return { ok: false, status: 400, error: userErr };
|
|
23925
24049
|
const org = input.org.replace(/^@/, "");
|
|
23926
24050
|
const user = input.user.replace(/^@/, "");
|
|
23927
24051
|
const body = { user };
|
|
23928
24052
|
if (input.role) body.role = input.role;
|
|
23929
|
-
const res = await registryPutAuth(`/-/org/${
|
|
24053
|
+
const res = await registryPutAuth(`/-/org/${encScope(org)}/user`, body);
|
|
23930
24054
|
if (!res.ok) return translateError(res, { op: `org_member_set ${org}/${user}` });
|
|
23931
24055
|
return { ok: true, status: 200, data: { org, user, role: input.role } };
|
|
23932
24056
|
}
|
|
@@ -23936,7 +24060,7 @@ var writeTools = [
|
|
|
23936
24060
|
// ───────────────────────────────────────────────────────
|
|
23937
24061
|
{
|
|
23938
24062
|
name: "npm_org_member_remove",
|
|
23939
|
-
description: "Remove a user from an org. Their team memberships in that org are also removed.",
|
|
24063
|
+
description: "Remove a user from an org. Their team memberships in that org are also removed. Requires confirm: true \u2014 team memberships cascade and cannot be selectively preserved.",
|
|
23940
24064
|
annotations: {
|
|
23941
24065
|
title: "Remove org member",
|
|
23942
24066
|
readOnlyHint: false,
|
|
@@ -23946,14 +24070,26 @@ var writeTools = [
|
|
|
23946
24070
|
},
|
|
23947
24071
|
inputSchema: external_exports.object({
|
|
23948
24072
|
org: external_exports.string().describe("Organization name"),
|
|
23949
|
-
user: external_exports.string().describe("npm username")
|
|
24073
|
+
user: external_exports.string().describe("npm username"),
|
|
24074
|
+
confirm: external_exports.literal(true).describe("Must be literally true. Guards against accidental member removal.")
|
|
23950
24075
|
}),
|
|
23951
24076
|
handler: async (input) => {
|
|
23952
24077
|
const authErr = requireAuth();
|
|
23953
24078
|
if (authErr) return authErr;
|
|
24079
|
+
if (input.confirm !== true) {
|
|
24080
|
+
return {
|
|
24081
|
+
ok: false,
|
|
24082
|
+
status: 400,
|
|
24083
|
+
error: "org_member_remove requires confirm: true. Team memberships in the org are also removed."
|
|
24084
|
+
};
|
|
24085
|
+
}
|
|
24086
|
+
const orgErr = validateScope(input.org);
|
|
24087
|
+
if (orgErr) return { ok: false, status: 400, error: orgErr };
|
|
24088
|
+
const userErr = validateUsername(input.user);
|
|
24089
|
+
if (userErr) return { ok: false, status: 400, error: userErr };
|
|
23954
24090
|
const org = input.org.replace(/^@/, "");
|
|
23955
24091
|
const user = input.user.replace(/^@/, "");
|
|
23956
|
-
const res = await registryDeleteAuth(`/-/org/${
|
|
24092
|
+
const res = await registryDeleteAuth(`/-/org/${encScope(org)}/user`, { user });
|
|
23957
24093
|
if (!res.ok) return translateError(res, { op: `org_member_remove ${org}/${user}` });
|
|
23958
24094
|
return { ok: true, status: 200, data: { org, removedUser: user } };
|
|
23959
24095
|
}
|
|
@@ -23965,7 +24101,7 @@ var writeTools = [
|
|
|
23965
24101
|
// performed via NPM_TOKEN alone — we intentionally don't expose npm_token_create.
|
|
23966
24102
|
{
|
|
23967
24103
|
name: "npm_token_revoke",
|
|
23968
|
-
description: "Revoke an access token by its key (UUID from npm_tokens). Creating tokens is NOT exposed because the endpoint requires the user password \u2014 create via https://www.npmjs.com/settings/~/tokens instead.",
|
|
24104
|
+
description: "Revoke an access token by its key (UUID from npm_tokens). Requires confirm: true. Revoking the token currently in use by NPM_TOKEN will break the next call. Creating tokens is NOT exposed because the endpoint requires the user password \u2014 create via https://www.npmjs.com/settings/~/tokens instead.",
|
|
23969
24105
|
annotations: {
|
|
23970
24106
|
title: "Revoke access token",
|
|
23971
24107
|
readOnlyHint: false,
|
|
@@ -23974,11 +24110,19 @@ var writeTools = [
|
|
|
23974
24110
|
openWorldHint: true
|
|
23975
24111
|
},
|
|
23976
24112
|
inputSchema: external_exports.object({
|
|
23977
|
-
tokenKey: external_exports.string().describe("Token key (UUID shown by npm_tokens)")
|
|
24113
|
+
tokenKey: external_exports.string().describe("Token key (UUID shown by npm_tokens)"),
|
|
24114
|
+
confirm: external_exports.literal(true).describe("Must be literally true. Guards against revoking the token you're authenticating with.")
|
|
23978
24115
|
}),
|
|
23979
24116
|
handler: async (input) => {
|
|
23980
24117
|
const authErr = requireAuth();
|
|
23981
24118
|
if (authErr) return authErr;
|
|
24119
|
+
if (input.confirm !== true) {
|
|
24120
|
+
return {
|
|
24121
|
+
ok: false,
|
|
24122
|
+
status: 400,
|
|
24123
|
+
error: "token_revoke requires confirm: true. Revoking the token in NPM_TOKEN will break the next call."
|
|
24124
|
+
};
|
|
24125
|
+
}
|
|
23982
24126
|
const res = await registryDeleteAuth(`/-/npm/v1/tokens/token/${encodeURIComponent(input.tokenKey)}`);
|
|
23983
24127
|
if (!res.ok) return translateError(res, { op: "token_revoke" });
|
|
23984
24128
|
return { ok: true, status: 200, data: { tokenKey: input.tokenKey, revoked: true } };
|
|
@@ -23987,7 +24131,7 @@ var writeTools = [
|
|
|
23987
24131
|
];
|
|
23988
24132
|
|
|
23989
24133
|
// src/index.ts
|
|
23990
|
-
var version2 = true ? "0.
|
|
24134
|
+
var version2 = true ? "0.10.0" : (await null).createRequire(import.meta.url)("../package.json").version;
|
|
23991
24135
|
var subcommand = process.argv[2];
|
|
23992
24136
|
if (subcommand === "version" || subcommand === "--version") {
|
|
23993
24137
|
console.log(version2);
|
package/package.json
CHANGED