skillrepo 4.8.1 → 4.8.2
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/package.json +1 -1
- package/src/commands/add.mjs +2 -1
- package/src/lib/http.mjs +13 -9
- package/src/test/commands/add.test.mjs +1 -1
- package/src/test/commands/publish.test.mjs +6 -6
- package/src/test/commands/push.test.mjs +1 -1
- package/src/test/e2e/cli-commands.test.mjs +1 -1
- package/src/test/lib/http.test.mjs +3 -3
package/package.json
CHANGED
package/src/commands/add.mjs
CHANGED
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
* - 404 not-found → clean error, exit 5
|
|
28
28
|
* - 409 self-ownership → clean error, exit 5 ("you can't add your
|
|
29
29
|
* own skill to your own library")
|
|
30
|
-
* - 403 plan-limit → validationError (via http.mjs)
|
|
30
|
+
* - 403 plan-limit → validationError (via http.mjs); the server
|
|
31
|
+
* message carries the per-cap remedy (no CLI-side hint, #2239)
|
|
31
32
|
* - 403 scope → scopeError (via http.mjs) with write-key hint
|
|
32
33
|
*
|
|
33
34
|
* Flags: --global / --agent / --json / --key / --url
|
package/src/lib/http.mjs
CHANGED
|
@@ -339,7 +339,7 @@ async function mapErrorResponse(res, url) {
|
|
|
339
339
|
// `src/app/api/v1/library/route.ts:226-235`. The route comment
|
|
340
340
|
// EXPLICITLY documents that the CLI must distinguish this from
|
|
341
341
|
// scope errors. We map it to `validationError` (exit 5) with a
|
|
342
|
-
//
|
|
342
|
+
// no CLI-side hint — it is not a key-permission issue
|
|
343
343
|
// so neither authError nor scopeError is the right fit, and
|
|
344
344
|
// adding a dedicated planLimitError to the exit code matrix is
|
|
345
345
|
// out of scope for v3.0.0.
|
|
@@ -350,9 +350,13 @@ async function mapErrorResponse(res, url) {
|
|
|
350
350
|
// 3. Generic 403 — suspended account, blocked user, etc. Maps to
|
|
351
351
|
// authError with a "contact support" hint.
|
|
352
352
|
if (typeof code === "string" && code === "plan_limit") {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
353
|
+
// No CLI-side hint: the server's plan_limit message now states the
|
|
354
|
+
// limit AND the correct remedy for the specific cap that fired
|
|
355
|
+
// (skills "Delete one to add another.", library "Remove one to add
|
|
356
|
+
// another.", cap-0 "A personal library isn't included in your
|
|
357
|
+
// plan."). A generic hint here either duplicates it or — for the
|
|
358
|
+
// cap-0 tier — contradicts it (#2239 round-2 review).
|
|
359
|
+
return validationError(message);
|
|
356
360
|
}
|
|
357
361
|
if (typeof code === "string" && code.toLowerCase().includes("scope")) {
|
|
358
362
|
return scopeError(message, {
|
|
@@ -1233,7 +1237,7 @@ async function setSkillVisibility(serverUrl, apiKey, owner, name, target, option
|
|
|
1233
1237
|
// CANNOT fall through to `mapErrorResponse` (which re-reads the
|
|
1234
1238
|
// body and would get an empty object — a `plan_limit` 403 would
|
|
1235
1239
|
// mis-classify as authError exit 2 instead of validationError
|
|
1236
|
-
// exit 5
|
|
1240
|
+
// exit 5, message self-contained). Pre-parse once, then dispatch every
|
|
1237
1241
|
// code path inline mirroring `mapErrorResponse`'s shape so the
|
|
1238
1242
|
// CliError types stay consistent across endpoints.
|
|
1239
1243
|
if (res.status === 403 || res.status === 422) {
|
|
@@ -1253,13 +1257,13 @@ async function setSkillVisibility(serverUrl, apiKey, owner, name, target, option
|
|
|
1253
1257
|
}
|
|
1254
1258
|
// Mirror `mapErrorResponse`'s 403 taxonomy so a 403 here behaves
|
|
1255
1259
|
// identically to a 403 on any other endpoint:
|
|
1256
|
-
// - `plan_limit` → validationError (exit 5)
|
|
1260
|
+
// - `plan_limit` → validationError (exit 5), server message only
|
|
1257
1261
|
// - any `*scope*` code → scopeError (exit 4)
|
|
1258
1262
|
// - everything else → authError (exit 2, "suspended" hint)
|
|
1259
1263
|
if (code === "plan_limit") {
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1264
|
+
// Same as mapErrorResponse: the server message is the complete,
|
|
1265
|
+
// cap-specific remedy — no CLI hint (#2239 round-2 review).
|
|
1266
|
+
throw validationError(message);
|
|
1263
1267
|
}
|
|
1264
1268
|
if (code && code.toLowerCase().includes("scope")) {
|
|
1265
1269
|
throw scopeError(message, {
|
|
@@ -235,7 +235,7 @@ describe("runAdd — error paths", () => {
|
|
|
235
235
|
);
|
|
236
236
|
});
|
|
237
237
|
|
|
238
|
-
it("403 plan-limit maps to validationError
|
|
238
|
+
it("403 plan-limit maps to validationError (server message self-contained, no hint)", async () => {
|
|
239
239
|
server.setAddResponse("alice", "limit-test", {
|
|
240
240
|
status: 403,
|
|
241
241
|
body: {
|
|
@@ -192,11 +192,11 @@ describe("runPublish — error paths", () => {
|
|
|
192
192
|
});
|
|
193
193
|
});
|
|
194
194
|
|
|
195
|
-
it("403 plan_limit → CliError EXIT_VALIDATION (NOT authError),
|
|
195
|
+
it("403 plan_limit → CliError EXIT_VALIDATION (NOT authError), no CLI-side hint", async () => {
|
|
196
196
|
// Real-world regression scenario: an account over its skill
|
|
197
197
|
// quota tries to publish. Server returns 403 with code
|
|
198
198
|
// `plan_limit`. The CLI MUST classify this as exit 5
|
|
199
|
-
// validation with the
|
|
199
|
+
// validation with the self-contained server message, NOT exit 2 authError
|
|
200
200
|
// ("contact support") — a pre-review version of the code
|
|
201
201
|
// consumed the 403 body and let mapErrorResponse re-parse, but
|
|
202
202
|
// the body was empty by then so plan_limit fell through to
|
|
@@ -211,7 +211,7 @@ describe("runPublish — error paths", () => {
|
|
|
211
211
|
await assert.rejects(runPublish(baseArgv(), { stdout }), (err) => {
|
|
212
212
|
assert.ok(err instanceof CliError);
|
|
213
213
|
assert.equal(err.exitCode, EXIT_VALIDATION);
|
|
214
|
-
assert.
|
|
214
|
+
assert.equal(err.hint, undefined); // server plan_limit message is self-contained (#2239)
|
|
215
215
|
return true;
|
|
216
216
|
});
|
|
217
217
|
});
|
|
@@ -514,10 +514,10 @@ describe("runUnpublish — error paths", () => {
|
|
|
514
514
|
);
|
|
515
515
|
});
|
|
516
516
|
|
|
517
|
-
it("403 plan_limit → CliError EXIT_VALIDATION (NOT authError),
|
|
517
|
+
it("403 plan_limit → CliError EXIT_VALIDATION (NOT authError), no CLI-side hint", async () => {
|
|
518
518
|
// Same shared `setSkillVisibility` 403 dispatch as publish: a
|
|
519
519
|
// plan_limit 403 must classify as exit 5 validation with the
|
|
520
|
-
//
|
|
520
|
+
// self-contained server message, never exit 2 authError. Mirrors the publish-side
|
|
521
521
|
// regression guard for the unpublish verb.
|
|
522
522
|
server.setUnpublishResponse("alice", "my-skill", {
|
|
523
523
|
status: 403,
|
|
@@ -529,7 +529,7 @@ describe("runUnpublish — error paths", () => {
|
|
|
529
529
|
await assert.rejects(runUnpublish(baseArgv(), { stdout }), (err) => {
|
|
530
530
|
assert.ok(err instanceof CliError);
|
|
531
531
|
assert.equal(err.exitCode, EXIT_VALIDATION);
|
|
532
|
-
assert.
|
|
532
|
+
assert.equal(err.hint, undefined); // server plan_limit message is self-contained (#2239)
|
|
533
533
|
return true;
|
|
534
534
|
});
|
|
535
535
|
});
|
|
@@ -401,7 +401,7 @@ describe("runPush — server error mapping", () => {
|
|
|
401
401
|
|
|
402
402
|
it("maps server 403 plan_limit to EXIT_VALIDATION (NOT auth)", async () => {
|
|
403
403
|
// mapErrorResponse explicitly disambiguates plan_limit from the
|
|
404
|
-
// generic-403 authError path so the user sees
|
|
404
|
+
// generic-403 authError path so the user sees the plan-limit message, not
|
|
405
405
|
// a "contact support" hint. Mirrors the http.test.mjs assertion.
|
|
406
406
|
file("SKILL.md", VALID_SKILL_MD);
|
|
407
407
|
server.setPushResponse({
|
|
@@ -363,7 +363,7 @@ describe("CLI E2E — read commands", () => {
|
|
|
363
363
|
assert.match(r.stderr, /write-scoped key|scope/);
|
|
364
364
|
});
|
|
365
365
|
|
|
366
|
-
it("add with 403 plan_limit exits 5
|
|
366
|
+
it("add with 403 plan_limit exits 5 (validation, server message shown)", async () => {
|
|
367
367
|
server.setAddResponseForAny({
|
|
368
368
|
status: 403,
|
|
369
369
|
body: { error: "Your free plan allows up to 5 library skills.", code: "plan_limit" },
|
|
@@ -1278,7 +1278,7 @@ describe("pushSkill", () => {
|
|
|
1278
1278
|
it("maps 403 plan_limit through mapErrorResponse to validationError", async () => {
|
|
1279
1279
|
const srv = await makePushServer((req, res) => {
|
|
1280
1280
|
jsonRes(res, 403, {
|
|
1281
|
-
error: "
|
|
1281
|
+
error: "Your developer plan allows up to 40 skills. Delete one to add another.",
|
|
1282
1282
|
code: "plan_limit",
|
|
1283
1283
|
});
|
|
1284
1284
|
});
|
|
@@ -2119,9 +2119,9 @@ describe("publishLibrarySkill", () => {
|
|
|
2119
2119
|
}
|
|
2120
2120
|
});
|
|
2121
2121
|
|
|
2122
|
-
it("throws validationError on 403 plan_limit (
|
|
2122
|
+
it("throws validationError on 403 plan_limit (validation exit, not auth)", async () => {
|
|
2123
2123
|
const srv = await startServer((req, res) =>
|
|
2124
|
-
jsonRes(res, 403, { error: "
|
|
2124
|
+
jsonRes(res, 403, { error: "Your developer plan allows up to 40 skills. Delete one to add another.", code: "plan_limit" }),
|
|
2125
2125
|
);
|
|
2126
2126
|
try {
|
|
2127
2127
|
await assert.rejects(
|