promptdock 1.2.1 → 1.2.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.
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Semver precedence for CLI versions (burn-down A2): the CLI's copy of `lib/validation/cli-semver.ts`.
3
+ * The CLI is a standalone build that cannot import `lib/`, so the code below the header is kept
4
+ * BYTE-IDENTICAL to that file, and `test/semver-parity.test.ts` fails when the two drift.
5
+ *
6
+ * A pre-release sorts before its release: `1.2.0 < 1.2.1-rc.15.1 < 1.2.1`. The old inline compare
7
+ * split on "." and read `1.2.1-rc.15.1` as 1.2.0.
8
+ */
9
+ export type ParsedSemver = {
10
+ core: [number, number, number];
11
+ pre: string[];
12
+ };
13
+ /** `1.2.3`, `v1.2.3`, `1.2.3-rc.15.1`, `1.2.3+build.7` → its parts; anything else → null. */
14
+ export declare function parseSemver(version: string): ParsedSemver | null;
15
+ /**
16
+ * Semver 2.0.0 precedence (section 11): negative when `a` sorts first, 0 when equal, positive when
17
+ * `b` does. Build metadata is ignored. Null when either side does not parse.
18
+ */
19
+ export declare function compareSemverStrict(a: string, b: string): number | null;
package/dist/semver.js ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Semver precedence for CLI versions (burn-down A2): the CLI's copy of `lib/validation/cli-semver.ts`.
3
+ * The CLI is a standalone build that cannot import `lib/`, so the code below the header is kept
4
+ * BYTE-IDENTICAL to that file, and `test/semver-parity.test.ts` fails when the two drift.
5
+ *
6
+ * A pre-release sorts before its release: `1.2.0 < 1.2.1-rc.15.1 < 1.2.1`. The old inline compare
7
+ * split on "." and read `1.2.1-rc.15.1` as 1.2.0.
8
+ */
9
+ /** `1.2.3`, `v1.2.3`, `1.2.3-rc.15.1`, `1.2.3+build.7` → its parts; anything else → null. */
10
+ export function parseSemver(version) {
11
+ const m = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(version.trim());
12
+ if (!m)
13
+ return null;
14
+ return { core: [Number(m[1]), Number(m[2]), Number(m[3])], pre: m[4] ? m[4].split(".") : [] };
15
+ }
16
+ /**
17
+ * Semver 2.0.0 precedence (section 11): negative when `a` sorts first, 0 when equal, positive when
18
+ * `b` does. Build metadata is ignored. Null when either side does not parse.
19
+ */
20
+ export function compareSemverStrict(a, b) {
21
+ const pa = parseSemver(a);
22
+ const pb = parseSemver(b);
23
+ if (!pa || !pb)
24
+ return null;
25
+ for (let i = 0; i < 3; i++) {
26
+ if (pa.core[i] !== pb.core[i])
27
+ return pa.core[i] - pb.core[i];
28
+ }
29
+ // A pre-release sorts BEFORE the release it precedes.
30
+ if (pa.pre.length === 0 || pb.pre.length === 0)
31
+ return (pa.pre.length === 0 ? 1 : 0) - (pb.pre.length === 0 ? 1 : 0);
32
+ for (let i = 0; i < Math.max(pa.pre.length, pb.pre.length); i++) {
33
+ const x = pa.pre[i];
34
+ const y = pb.pre[i];
35
+ if (x === undefined)
36
+ return -1;
37
+ if (y === undefined)
38
+ return 1;
39
+ const xNum = /^\d+$/.test(x);
40
+ const yNum = /^\d+$/.test(y);
41
+ if (xNum && yNum) {
42
+ if (Number(x) !== Number(y))
43
+ return Number(x) - Number(y);
44
+ }
45
+ else if (xNum !== yNum) {
46
+ // A numeric identifier sorts before an alphanumeric one.
47
+ return xNum ? -1 : 1;
48
+ }
49
+ else if (x !== y) {
50
+ return x < y ? -1 : 1;
51
+ }
52
+ }
53
+ return 0;
54
+ }
@@ -202,9 +202,10 @@ export declare const SERVER_MESSAGE_MAX = 500;
202
202
  * headroom rather than a round number.
203
203
  *
204
204
  * ARITHMETIC (server side, lib/cli-auth/protocol.ts `upgradeRemedy`): the prefix
205
- * "Run: npx promptdock@latest " is 27 characters and the echoed args are already
206
- * allowlisted and sliced to 120 there, so a legitimate remedy tops out at 147. 256
207
- * leaves room for the prefix to grow without ever truncating an honest command.
205
+ * "Run: npx promptdock@latest " is 27 characters, the echoed args are already
206
+ * allowlisted and sliced to 120 there, and since the 2026-10-04 sweep (DX-A6) the line
207
+ * ends " (installed globally? npm i -g promptdock@latest)", 49 more, so a legitimate
208
+ * remedy tops out at 196. 256 still leaves room without truncating an honest command.
208
209
  *
209
210
  * WARNING: AND THE CAP IS APPLIED TO THE REMEDY ALONE, BEFORE api.ts joins it to the
210
211
  * message. Applied after the join it would be measured against about 215 characters
@@ -357,9 +357,10 @@ export const SERVER_MESSAGE_MAX = 500;
357
357
  * headroom rather than a round number.
358
358
  *
359
359
  * ARITHMETIC (server side, lib/cli-auth/protocol.ts `upgradeRemedy`): the prefix
360
- * "Run: npx promptdock@latest " is 27 characters and the echoed args are already
361
- * allowlisted and sliced to 120 there, so a legitimate remedy tops out at 147. 256
362
- * leaves room for the prefix to grow without ever truncating an honest command.
360
+ * "Run: npx promptdock@latest " is 27 characters, the echoed args are already
361
+ * allowlisted and sliced to 120 there, and since the 2026-10-04 sweep (DX-A6) the line
362
+ * ends " (installed globally? npm i -g promptdock@latest)", 49 more, so a legitimate
363
+ * remedy tops out at 196. 256 still leaves room without truncating an honest command.
363
364
  *
364
365
  * WARNING: AND THE CAP IS APPLIED TO THE REMEDY ALONE, BEFORE api.ts joins it to the
365
366
  * message. Applied after the join it would be measured against about 215 characters
@@ -3,5 +3,7 @@ import type { ResolveResponse } from "./contract.js";
3
3
  * tells them their plan is too low — the one link most likely to be clicked under
4
4
  * friction, so it must not spend a redirect hop. */
5
5
  export declare const UPGRADE_URL = "https://www.promptdock.ai/pricing";
6
+ /** What a paid plan unlocks for the CLI, stated with no price (see `insufficient_tier`). */
7
+ export declare const PREMIUM_SKILLS_SENTENCE = "Premium skills come with any paid plan.";
6
8
  /** Throws the DX3-copy CliError for a deny verdict; returns for ok/already_entitled. */
7
9
  export declare function assertInstallable(resolve: ResolveResponse, refString: string, cliVersion: string): void;
package/dist/verdicts.js CHANGED
@@ -1,16 +1,27 @@
1
1
  import { CliError, EXIT } from "./errors.js";
2
2
  import { SERVER_LABEL_MAX, terminalLine } from "./terminal-text.js";
3
3
  import { retryHours } from "./ui.js";
4
+ import { compareSemverStrict } from "./semver.js";
4
5
  import { CANONICAL_INSTALL_COMMAND, CANONICAL_ORIGIN, CLI_DOS_MAX_SKILL_FILES, CLI_DOS_MAX_SKILL_TOTAL_BYTES, } from "./generated/constants.js";
5
6
  /** ⚠️ DERIVED from `CANONICAL_ORIGIN`. This is the CTA a user follows the moment the CLI
6
7
  * tells them their plan is too low — the one link most likely to be clicked under
7
8
  * friction, so it must not spend a redirect hop. */
8
9
  export const UPGRADE_URL = `${CANONICAL_ORIGIN}/pricing`;
9
- /** Numeric semver compare; mirrors compareCliVersions in lib/validation/skills.ts. */
10
- function cmpVersion(a, b) {
11
- const pa = a.split(".").map((n) => Number(n) || 0);
12
- const pb = b.split(".").map((n) => Number(n) || 0);
13
- return (pa[0] - pb[0]) || (pa[1] - pb[1]) || (pa[2] - pb[2]);
10
+ /** What a paid plan unlocks for the CLI, stated with no price (see `insufficient_tier`). */
11
+ export const PREMIUM_SKILLS_SENTENCE = "Premium skills come with any paid plan.";
12
+ /**
13
+ * True when this CLI (`cliVersion`) is too old for `min`, by semver precedence (./semver.ts, the
14
+ * byte-identical twin of lib/validation/cli-semver.ts): a pre-release sorts before its release, so
15
+ * 1.2.1-rc.15.1 is below 1.2.1 (the old split-on-"." compare read it as 1.2.0).
16
+ *
17
+ * ⚠️ An UNREADABLE `min` counts as too old. This is the opposite of the server's floor check,
18
+ * deliberately: the server must never brick a client over a header it cannot parse, but here the
19
+ * SERVER is stating a requirement this CLI cannot evaluate, and installing anyway would skip the
20
+ * protections a newer CLI may carry. Real servers only ever send a valid version.
21
+ */
22
+ function belowRequirement(cliVersion, min) {
23
+ const cmp = compareSemverStrict(cliVersion, min);
24
+ return cmp === null || cmp < 0;
14
25
  }
15
26
  /**
16
27
  * PACKAGE-SCOPED compatibility, checked BEFORE the metered install POST.
@@ -40,7 +51,7 @@ function assertPackageCompatible(resolve, refString, cliVersion) {
40
51
  const safeTitle = terminalLine(resolve.title, SERVER_LABEL_MAX);
41
52
  const title = safeTitle ? `"${safeTitle}"` : "This skill";
42
53
  const min = resolve.min_cli_version;
43
- if (typeof min === "string" && min.length > 0 && cmpVersion(cliVersion, min) < 0) {
54
+ if (typeof min === "string" && min.length > 0 && belowRequirement(cliVersion, min)) {
44
55
  throw new CliError(`${title} needs promptdock CLI ${terminalLine(min, SERVER_LABEL_MAX)} or newer — you are on ${cliVersion}.`, EXIT.DENIED, { footer: "upgrade_required", hint: upgrade });
45
56
  }
46
57
  // Belt and braces for a server too old to send min_cli_version: compare the shape
@@ -67,10 +78,14 @@ export function assertInstallable(resolve, refString, cliVersion) {
67
78
  case "insufficient_tier": {
68
79
  const safe = terminalLine(resolve.title, SERVER_LABEL_MAX);
69
80
  const title = safe ? `"${safe}"` : "This skill";
70
- throw new CliError(`${title} is a premium skill. Premium skills need the Explorer plan ($10/mo) or higher` +
81
+ // ⚠️ NO PRICE, EVER: a hand-typed "$10/mo" was wrong for every account billed in
82
+ // euros or rupees, and this binary cannot know the reader's currency. "Any paid plan"
83
+ // is true in every currency; /pricing shows the amounts.
84
+ throw new CliError(`${title} is a premium skill` +
71
85
  (resolve.current
72
- ? ` — your account is on the ${terminalLine(resolve.current, SERVER_LABEL_MAX)} tier.`
73
- : "."), EXIT.DENIED, { footer: "insufficient_tier", hint: `Upgrade: ${UPGRADE_URL}` });
86
+ ? ` and your account is on the ${terminalLine(resolve.current, SERVER_LABEL_MAX)} tier`
87
+ : "") +
88
+ `. ${PREMIUM_SKILLS_SENTENCE} See ${UPGRADE_URL}.`, EXIT.DENIED, { footer: "insufficient_tier", hint: `Upgrade: ${UPGRADE_URL}` });
74
89
  }
75
90
  case "cap_hit": {
76
91
  const wait = retryHours(Number(resolve.retry_after_secs) || 3600);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "promptdock",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
4
4
  "description": "Install AI agent skills from PromptDock — npx promptdock@latest install <handle>/<slug>",
5
5
  "keywords": [
6
6
  "promptdock",
@@ -43,5 +43,5 @@
43
43
  "typescript": "^5",
44
44
  "vitest": "^4.1.8"
45
45
  },
46
- "gitHead": "46e6b02fe045e9b803f748b32b9443995a40b1ac"
46
+ "gitHead": "63be7ca7ef9c9a0e64b4ebc98c4a9c0d25e6cbd9"
47
47
  }