@percher/shared 0.1.8 → 1.0.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.
Files changed (53) hide show
  1. package/LICENSE +132 -0
  2. package/dist/brand.d.ts +64 -0
  3. package/dist/brand.d.ts.map +1 -0
  4. package/dist/brand.js +67 -0
  5. package/dist/brand.js.map +1 -0
  6. package/dist/client-ip-attestation.d.ts +67 -0
  7. package/dist/client-ip-attestation.d.ts.map +1 -0
  8. package/dist/client-ip-attestation.js +74 -0
  9. package/dist/client-ip-attestation.js.map +1 -0
  10. package/dist/index.d.ts +10 -3
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +10 -3
  13. package/dist/index.js.map +1 -1
  14. package/dist/legal-changes.d.ts +118 -0
  15. package/dist/legal-changes.d.ts.map +1 -0
  16. package/dist/legal-changes.js +135 -0
  17. package/dist/legal-changes.js.map +1 -0
  18. package/dist/legal.d.ts +58 -0
  19. package/dist/legal.d.ts.map +1 -0
  20. package/dist/legal.js +54 -0
  21. package/dist/legal.js.map +1 -0
  22. package/dist/limits.d.ts +8 -0
  23. package/dist/limits.d.ts.map +1 -1
  24. package/dist/limits.js +8 -0
  25. package/dist/limits.js.map +1 -1
  26. package/dist/oauth-client-name.d.ts +22 -0
  27. package/dist/oauth-client-name.d.ts.map +1 -0
  28. package/dist/oauth-client-name.js +37 -0
  29. package/dist/oauth-client-name.js.map +1 -0
  30. package/dist/oauth-scopes.d.ts +19 -0
  31. package/dist/oauth-scopes.d.ts.map +1 -0
  32. package/dist/oauth-scopes.js +31 -0
  33. package/dist/oauth-scopes.js.map +1 -0
  34. package/dist/platform-env.d.ts.map +1 -1
  35. package/dist/platform-env.js +6 -0
  36. package/dist/platform-env.js.map +1 -1
  37. package/dist/platform.d.ts +18 -1
  38. package/dist/platform.d.ts.map +1 -1
  39. package/dist/platform.js +18 -1
  40. package/dist/platform.js.map +1 -1
  41. package/dist/signup-invite-hash.d.ts +8 -0
  42. package/dist/signup-invite-hash.d.ts.map +1 -0
  43. package/dist/signup-invite-hash.js +11 -0
  44. package/dist/signup-invite-hash.js.map +1 -0
  45. package/dist/signup-invite.d.ts +23 -0
  46. package/dist/signup-invite.d.ts.map +1 -0
  47. package/dist/signup-invite.js +29 -0
  48. package/dist/signup-invite.js.map +1 -0
  49. package/dist/timeouts.d.ts +85 -0
  50. package/dist/timeouts.d.ts.map +1 -1
  51. package/dist/timeouts.js +84 -0
  52. package/dist/timeouts.js.map +1 -1
  53. package/package.json +12 -2
@@ -0,0 +1,135 @@
1
+ import { LEGAL_DOCUMENT_VERSIONS } from "./legal.js";
2
+ /**
3
+ * Newest last. Empty because version 1.0 is the first publication under
4
+ * Kaizensu AB: there is no earlier version to have changed from, so the chain
5
+ * starts here rather than being backfilled with a notice nobody was owed.
6
+ */
7
+ export const LEGAL_CHANGES = [];
8
+ /**
9
+ * Header carrying the change ids the signup form actually rendered.
10
+ *
11
+ * Same channel as the terms-acceptance header, for the same reason: the signup
12
+ * flow returns no session when email verification is required, so a follow-up
13
+ * authenticated POST would be refused and the evidence lost. Comma-separated,
14
+ * and never trusted on its own — the server intersects it with what it can
15
+ * independently see was disclosable.
16
+ */
17
+ export const SIGNUP_DISCLOSURE_HEADER = "x-percher-disclosed-changes";
18
+ /** The notice period the documents promise, in milliseconds. */
19
+ export const MATERIAL_CHANGE_NOTICE_MS = 30 * 24 * 60 * 60 * 1000;
20
+ /** `id` shape: the date half is parsed, so this is a gate and not decoration. */
21
+ export const LEGAL_CHANGE_ID_PATTERN = /^\d{4}-\d{2}-\d{2}-[a-z0-9]+(?:-[a-z0-9]+)*$/;
22
+ /** Versions are `1.0` or `1.0.1`; an editorial change may only move the last component. */
23
+ export const LEGAL_VERSION_PATTERN = /^\d+\.\d+(?:\.\d+)?$/;
24
+ /**
25
+ * The canonical JSON of an entry, for the hash that freezes it after the first
26
+ * send. Keys are emitted in a fixed order rather than insertion order so that
27
+ * reformatting the source cannot invalidate a change already announced.
28
+ */
29
+ export function canonicalLegalChangeJson(change) {
30
+ return JSON.stringify({
31
+ announceAt: change.announceAt,
32
+ documents: change.documents.map((document) => ({
33
+ from: document.from,
34
+ key: document.key,
35
+ to: document.to,
36
+ })),
37
+ effectiveAt: change.effectiveAt,
38
+ enforcement: change.enforcement,
39
+ id: change.id,
40
+ materiality: change.materiality,
41
+ modifiesService: change.modifiesService === true,
42
+ reason: change.reason,
43
+ summary: { en: change.summary.en, sv: change.summary.sv },
44
+ });
45
+ }
46
+ /** Entries whose notice may go out now: material, announced, newest last. */
47
+ export function openMaterialChanges(now, changes = LEGAL_CHANGES) {
48
+ return changes.filter((change) => change.materiality === "material" && Date.parse(change.announceAt) <= now);
49
+ }
50
+ /**
51
+ * The latest declared version of a document, or its current published version
52
+ * when no change has ever named it.
53
+ */
54
+ export function declaredVersion(key, changes = LEGAL_CHANGES) {
55
+ for (let index = changes.length - 1; index >= 0; index -= 1) {
56
+ const document = changes[index]?.documents.find((entry) => entry.key === key);
57
+ if (document)
58
+ return document.to;
59
+ }
60
+ return LEGAL_DOCUMENT_VERSIONS[key];
61
+ }
62
+ /**
63
+ * Order two `1.0` / `1.0.1` strings. Unparseable input sorts as older, so a
64
+ * version we cannot read never suppresses a notice.
65
+ */
66
+ export function compareVersions(left, right) {
67
+ // Components are compared as digit strings, never parsed to numbers. The
68
+ // pattern accepts a component of any length, and Number.parseInt collapses
69
+ // anything past the safe-integer range onto the same value: two different
70
+ // versions would then read as equal, and pendingAcknowledgement would clear a
71
+ // banner for a change nobody had acknowledged. Leading zeros are stripped
72
+ // first, so `01` and `1` stay the same version.
73
+ const parse = (value) => {
74
+ if (!LEGAL_VERSION_PATTERN.test(value))
75
+ return null;
76
+ const parts = value.split(".");
77
+ const normalize = (part) => (part ?? "0").replace(/^0+(?=\d)/, "");
78
+ return [normalize(parts[0]), normalize(parts[1]), normalize(parts[2])];
79
+ };
80
+ // Longer digit string wins; equal length falls back to lexicographic order,
81
+ // which is numeric order once both are the same width.
82
+ const order = (a, b) => {
83
+ if (a.length !== b.length)
84
+ return a.length > b.length ? 1 : -1;
85
+ if (a === b)
86
+ return 0;
87
+ return a > b ? 1 : -1;
88
+ };
89
+ const a = parse(left);
90
+ const b = parse(right);
91
+ if (!a)
92
+ return b ? -1 : 0;
93
+ if (!b)
94
+ return 1;
95
+ for (let index = 0; index < 3; index += 1) {
96
+ const difference = order(a[index] ?? "0", b[index] ?? "0");
97
+ if (difference !== 0)
98
+ return difference;
99
+ }
100
+ return 0;
101
+ }
102
+ /**
103
+ * The change a user has not yet acknowledged, if any.
104
+ *
105
+ * Read by the dashboard banner and by the operator dry-run. Deliberately NOT
106
+ * read by the API's auth middleware: a stale accepted version is never an
107
+ * authorization failure. Extending the 403 to a version comparison would break
108
+ * every customer's deploy pipeline and every agent integration on a chosen
109
+ * date, with the remedy being a browser click a CI runner cannot perform — and
110
+ * it would be stricter than the Terms promise, which make continued use the
111
+ * acceptance. A test in the API asserts that absence.
112
+ */
113
+ export function pendingAcknowledgement(acceptedTermsVersion, now, changes = LEGAL_CHANGES) {
114
+ if (!acceptedTermsVersion)
115
+ return null;
116
+ for (let index = changes.length - 1; index >= 0; index -= 1) {
117
+ const change = changes[index];
118
+ if (change?.materiality !== "material")
119
+ continue;
120
+ if (Date.parse(change.effectiveAt) > now)
121
+ continue;
122
+ const terms = change.documents.find((entry) => entry.key === "terms");
123
+ if (!terms)
124
+ continue;
125
+ // Accepting a LATER version acknowledges this one. Exact equality looked
126
+ // right and was not: a material change to 1.1 followed by an editorial
127
+ // correction to 1.1.1 leaves someone who accepted 1.1.1 staring at a
128
+ // banner for a change they have demonstrably already agreed to, with no
129
+ // way to clear it, because the click re-stamps the newest version and the
130
+ // comparison keeps failing.
131
+ return compareVersions(acceptedTermsVersion, terms.to) >= 0 ? null : change;
132
+ }
133
+ return null;
134
+ }
135
+ //# sourceMappingURL=legal-changes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legal-changes.js","sourceRoot":"","sources":["../src/legal-changes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Published legal-document versions, shared by the dashboard and the API.
3
+ *
4
+ * Keep these in lock-step with the `**Version:**` headers of the canonical
5
+ * files under legal/ — `scripts/check-legal-versions.mjs` fails review when
6
+ * they drift. The numbering restarts at 1.0 because Kaizensu AB now owns and
7
+ * operates the platform, so these are the company's first published versions;
8
+ * the earlier 2.x sequence belongs to the sole-trader era and does not carry
9
+ * over. The acceptance version is selected by trusted server code, never by a
10
+ * browser header. Changing a value records document evidence; nothing compares
11
+ * or orders these strings, so it neither publishes new terms nor forces
12
+ * re-acceptance.
13
+ */
14
+ export declare const LEGAL_DOCUMENT_VERSIONS: {
15
+ readonly terms: "1.0";
16
+ readonly aup: "1.0";
17
+ readonly privacy: "1.0";
18
+ readonly cookies: "1.0";
19
+ readonly dpa: "1.0";
20
+ readonly subprocessors: "1.0";
21
+ readonly dataPortability: "1.0";
22
+ };
23
+ /**
24
+ * What each published document said at the version above.
25
+ *
26
+ * sha256 over the English then the Swedish file, newline-normalised and
27
+ * NUL-separated, so a checkout's line-ending setting cannot move the value.
28
+ *
29
+ * Without this the version header was the only anchor, and it is one a person
30
+ * has to remember to move. Editing the body of a document and leaving the
31
+ * header alone passed every check: the sync step copied the new bytes to the
32
+ * served copy, no version moved, so no change had to be declared and nobody
33
+ * was notified. That is the whole mechanism defeated by an omission rather
34
+ * than a decision. Now the bytes themselves are the anchor: change a document
35
+ * and the check fails until the change is declared.
36
+ *
37
+ * `bun scripts/check-legal-versions.mjs --print-hashes` prints the current
38
+ * values; never paste one in without the version move and the change entry it
39
+ * belongs to.
40
+ */
41
+ export declare const LEGAL_DOCUMENT_HASHES: {
42
+ readonly terms: "d412c9e85d7fefa5e3595b47aaea67a5d9f495955e458b2d0b4f43c9215317ce";
43
+ readonly aup: "138ce3bdd9d244a4f31c98955b3c9d59920f32c056608f1135dc8393769a761f";
44
+ readonly privacy: "186910f4f4825d3ac3bbc3340e7bf4babccea33f8b4166a10c892a7012cff175";
45
+ readonly cookies: "9c8250a4ea2fbf701d04cfb145d466ff8ba410e6da92433950f119952ba16bf0";
46
+ readonly dpa: "4c97fd36c886a113477f3eb2c2c0a9b5e03820ec624439906c938bbd40f7a0fb";
47
+ readonly subprocessors: "e7380f2d604814e1f06f44d8c4afcb34143c1c7994dfecdcec4cf1bbc8810363";
48
+ readonly dataPortability: "27a7d3ec0175af8f3f1c67db1a65bf01f74d5139b44d9134caef5dd8a924d775";
49
+ };
50
+ /** Version of the canonical English Terms presented during account acceptance. */
51
+ export declare const CURRENT_TERMS_VERSION: "1.0";
52
+ /** The "Last updated" date shared by the current revision of the documents. */
53
+ export declare const LEGAL_DOCUMENT_LAST_UPDATED = "10 September 2026";
54
+ export interface TermsAcceptanceEvidence {
55
+ version?: string;
56
+ language?: "en" | "sv";
57
+ }
58
+ //# sourceMappingURL=legal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legal.d.ts","sourceRoot":"","sources":["../src/legal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB;aAClC,KAAK,EAAE,KAAK;aACZ,GAAG,EAAE,KAAK;aACV,OAAO,EAAE,KAAK;aACd,OAAO,EAAE,KAAK;aACd,GAAG,EAAE,KAAK;aACV,aAAa,EAAE,KAAK;aACpB,eAAe,EAAE,KAAK;CACd,CAAC;AAEX;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,qBAAqB;aAChC,KAAK,EAAE,kEAAkE;aACzE,GAAG,EAAE,kEAAkE;aACvE,OAAO,EAAE,kEAAkE;aAC3E,OAAO,EAAE,kEAAkE;aAC3E,GAAG,EAAE,kEAAkE;aACvE,aAAa,EAAE,kEAAkE;aACjF,eAAe,EAAE,kEAAkE;CAC3E,CAAC;AAEX,kFAAkF;AAClF,eAAO,MAAM,qBAAqB,OAAgC,CAAC;AAEnE,+EAA+E;AAC/E,eAAO,MAAM,2BAA2B,sBAAsB,CAAC;AAE/D,MAAM,WAAW,uBAAuB;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;CACxB"}
package/dist/legal.js ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Published legal-document versions, shared by the dashboard and the API.
3
+ *
4
+ * Keep these in lock-step with the `**Version:**` headers of the canonical
5
+ * files under legal/ — `scripts/check-legal-versions.mjs` fails review when
6
+ * they drift. The numbering restarts at 1.0 because Kaizensu AB now owns and
7
+ * operates the platform, so these are the company's first published versions;
8
+ * the earlier 2.x sequence belongs to the sole-trader era and does not carry
9
+ * over. The acceptance version is selected by trusted server code, never by a
10
+ * browser header. Changing a value records document evidence; nothing compares
11
+ * or orders these strings, so it neither publishes new terms nor forces
12
+ * re-acceptance.
13
+ */
14
+ export const LEGAL_DOCUMENT_VERSIONS = {
15
+ terms: "1.0",
16
+ aup: "1.0",
17
+ privacy: "1.0",
18
+ cookies: "1.0",
19
+ dpa: "1.0",
20
+ subprocessors: "1.0",
21
+ dataPortability: "1.0",
22
+ };
23
+ /**
24
+ * What each published document said at the version above.
25
+ *
26
+ * sha256 over the English then the Swedish file, newline-normalised and
27
+ * NUL-separated, so a checkout's line-ending setting cannot move the value.
28
+ *
29
+ * Without this the version header was the only anchor, and it is one a person
30
+ * has to remember to move. Editing the body of a document and leaving the
31
+ * header alone passed every check: the sync step copied the new bytes to the
32
+ * served copy, no version moved, so no change had to be declared and nobody
33
+ * was notified. That is the whole mechanism defeated by an omission rather
34
+ * than a decision. Now the bytes themselves are the anchor: change a document
35
+ * and the check fails until the change is declared.
36
+ *
37
+ * `bun scripts/check-legal-versions.mjs --print-hashes` prints the current
38
+ * values; never paste one in without the version move and the change entry it
39
+ * belongs to.
40
+ */
41
+ export const LEGAL_DOCUMENT_HASHES = {
42
+ terms: "d412c9e85d7fefa5e3595b47aaea67a5d9f495955e458b2d0b4f43c9215317ce",
43
+ aup: "138ce3bdd9d244a4f31c98955b3c9d59920f32c056608f1135dc8393769a761f",
44
+ privacy: "186910f4f4825d3ac3bbc3340e7bf4babccea33f8b4166a10c892a7012cff175",
45
+ cookies: "9c8250a4ea2fbf701d04cfb145d466ff8ba410e6da92433950f119952ba16bf0",
46
+ dpa: "4c97fd36c886a113477f3eb2c2c0a9b5e03820ec624439906c938bbd40f7a0fb",
47
+ subprocessors: "e7380f2d604814e1f06f44d8c4afcb34143c1c7994dfecdcec4cf1bbc8810363",
48
+ dataPortability: "27a7d3ec0175af8f3f1c67db1a65bf01f74d5139b44d9134caef5dd8a924d775",
49
+ };
50
+ /** Version of the canonical English Terms presented during account acceptance. */
51
+ export const CURRENT_TERMS_VERSION = LEGAL_DOCUMENT_VERSIONS.terms;
52
+ /** The "Last updated" date shared by the current revision of the documents. */
53
+ export const LEGAL_DOCUMENT_LAST_UPDATED = "10 September 2026";
54
+ //# sourceMappingURL=legal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legal.js","sourceRoot":"","sources":["../src/legal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,KAAK,EAAE,KAAK;IACZ,GAAG,EAAE,KAAK;IACV,OAAO,EAAE,KAAK;IACd,OAAO,EAAE,KAAK;IACd,GAAG,EAAE,KAAK;IACV,aAAa,EAAE,KAAK;IACpB,eAAe,EAAE,KAAK;CACd,CAAC;AAEX;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,KAAK,EAAE,kEAAkE;IACzE,GAAG,EAAE,kEAAkE;IACvE,OAAO,EAAE,kEAAkE;IAC3E,OAAO,EAAE,kEAAkE;IAC3E,GAAG,EAAE,kEAAkE;IACvE,aAAa,EAAE,kEAAkE;IACjF,eAAe,EAAE,kEAAkE;CAC3E,CAAC;AAEX,kFAAkF;AAClF,MAAM,CAAC,MAAM,qBAAqB,GAAG,uBAAuB,CAAC,KAAK,CAAC;AAEnE,+EAA+E;AAC/E,MAAM,CAAC,MAAM,2BAA2B,GAAG,mBAAmB,CAAC"}
package/dist/limits.d.ts CHANGED
@@ -6,6 +6,14 @@
6
6
  * extractor, which guards against zip bombs.
7
7
  */
8
8
  export declare const MAX_TARBALL_BYTES: number;
9
+ /**
10
+ * Max accepted tarball for an ANONYMOUS deploy (25 MiB). The anonymous lane
11
+ * is a demo sandbox with no account behind it: the whole lane is capped at a
12
+ * handful of live apps, each one costs disk that nobody has agreed to pay for,
13
+ * and no legitimate demo needs 500 MB. Enforced on the streaming save, so a
14
+ * chunked or forged Content-Length cannot walk past it.
15
+ */
16
+ export declare const MAX_ANON_TARBALL_BYTES: number;
9
17
  /**
10
18
  * Max accepted inline-source publish size in bytes (25 MB) — the sum of
11
19
  * the decoded `content` bytes an assistant passes to `percher_publish` /
@@ -1 +1 @@
1
- {"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../src/limits.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,QAAoB,CAAC;AAEnD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,wBAAwB,QAAmB,CAAC;AAEzD;;;;;;GAMG;AAEH;;8BAE8B;AAC9B,eAAO,MAAM,sBAAsB,QAAY,CAAC;AAEhD;;;;;iEAKiE;AACjE,eAAO,MAAM,sBAAsB,QAAY,CAAC;AAEhD;;;;qDAIqD;AACrD,eAAO,MAAM,qBAAqB,QAAqB,CAAC;AAExD;;4BAE4B;AAC5B,eAAO,MAAM,8BAA8B,QAAsB,CAAC;AAElE;yEACyE;AACzE,eAAO,MAAM,iCAAiC,IAAI,CAAC;AAEnD,8DAA8D;AAC9D,eAAO,MAAM,wBAAwB,MAAM,CAAC"}
1
+ {"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../src/limits.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,QAAoB,CAAC;AAEnD;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,QAAmB,CAAC;AAEvD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,wBAAwB,QAAmB,CAAC;AAEzD;;;;;;GAMG;AAEH;;8BAE8B;AAC9B,eAAO,MAAM,sBAAsB,QAAY,CAAC;AAEhD;;;;;iEAKiE;AACjE,eAAO,MAAM,sBAAsB,QAAY,CAAC;AAEhD;;;;qDAIqD;AACrD,eAAO,MAAM,qBAAqB,QAAqB,CAAC;AAExD;;4BAE4B;AAC5B,eAAO,MAAM,8BAA8B,QAAsB,CAAC;AAElE;yEACyE;AACzE,eAAO,MAAM,iCAAiC,IAAI,CAAC;AAEnD,8DAA8D;AAC9D,eAAO,MAAM,wBAAwB,MAAM,CAAC"}
package/dist/limits.js CHANGED
@@ -6,6 +6,14 @@
6
6
  * extractor, which guards against zip bombs.
7
7
  */
8
8
  export const MAX_TARBALL_BYTES = 500 * 1024 * 1024;
9
+ /**
10
+ * Max accepted tarball for an ANONYMOUS deploy (25 MiB). The anonymous lane
11
+ * is a demo sandbox with no account behind it: the whole lane is capped at a
12
+ * handful of live apps, each one costs disk that nobody has agreed to pay for,
13
+ * and no legitimate demo needs 500 MB. Enforced on the streaming save, so a
14
+ * chunked or forged Content-Length cannot walk past it.
15
+ */
16
+ export const MAX_ANON_TARBALL_BYTES = 25 * 1024 * 1024;
9
17
  /**
10
18
  * Max accepted inline-source publish size in bytes (25 MB) — the sum of
11
19
  * the decoded `content` bytes an assistant passes to `percher_publish` /
@@ -1 +1 @@
1
- {"version":3,"file":"limits.js","sourceRoot":"","sources":["../src/limits.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AAEnD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAEzD;;;;;;GAMG;AAEH;;8BAE8B;AAC9B,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,GAAG,IAAI,CAAC;AAEhD;;;;;iEAKiE;AACjE,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,GAAG,IAAI,CAAC;AAEhD;;;;qDAIqD;AACrD,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAExD;;4BAE4B;AAC5B,MAAM,CAAC,MAAM,8BAA8B,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAElE;yEACyE;AACzE,MAAM,CAAC,MAAM,iCAAiC,GAAG,CAAC,CAAC;AAEnD,8DAA8D;AAC9D,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,CAAC"}
1
+ {"version":3,"file":"limits.js","sourceRoot":"","sources":["../src/limits.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AAEnD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAEvD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAEzD;;;;;;GAMG;AAEH;;8BAE8B;AAC9B,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,GAAG,IAAI,CAAC;AAEhD;;;;;iEAKiE;AACjE,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,GAAG,IAAI,CAAC;AAEhD;;;;qDAIqD;AACrD,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAExD;;4BAE4B;AAC5B,MAAM,CAAC,MAAM,8BAA8B,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAElE;yEACyE;AACzE,MAAM,CAAC,MAAM,iCAAiC,GAAG,CAAC,CAAC;AAEnD,8DAA8D;AAC9D,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,CAAC"}
@@ -0,0 +1,22 @@
1
+ /** Maximum number of Unicode code points shown from an open-DCR client name. */
2
+ export declare const MAX_OAUTH_CLIENT_NAME_DISPLAY_CODE_POINTS = 80;
3
+ /**
4
+ * Normalise an attacker-controlled OAuth Dynamic Client Registration name for
5
+ * display. The stored registration metadata remains verbatim; every UI/email
6
+ * surface calls this at its trust boundary.
7
+ *
8
+ * Unicode `Cf` includes bidi overrides/isolates, Arabic Letter Mark, soft
9
+ * hyphen, zero-width space, deprecated directional controls and tag
10
+ * characters. Those can reorder or invisibly split surrounding trusted copy.
11
+ * Replace them with a space rather than deleting them, so fragments cannot be
12
+ * joined into a new visible word. ZWNJ and ZWJ are the narrow exceptions:
13
+ * they are required for legitimate Persian/Indic text and emoji sequences and
14
+ * do not reorder the rest of the line. C0/C1 controls and lone surrogates are
15
+ * treated the same way.
16
+ *
17
+ * This deliberately does not apply NFKC or an ASCII allowlist. The label is
18
+ * explicitly unverified, and international product names should survive
19
+ * unchanged when they contain no display-control characters.
20
+ */
21
+ export declare function normaliseOAuthClientName(raw: string | null | undefined): string | null;
22
+ //# sourceMappingURL=oauth-client-name.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-client-name.d.ts","sourceRoot":"","sources":["../src/oauth-client-name.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,eAAO,MAAM,yCAAyC,KAAK,CAAC;AAK5D;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAetF"}
@@ -0,0 +1,37 @@
1
+ /** Maximum number of Unicode code points shown from an open-DCR client name. */
2
+ export const MAX_OAUTH_CLIENT_NAME_DISPLAY_CODE_POINTS = 80;
3
+ const ZERO_WIDTH_NON_JOINER = "\u200c";
4
+ const ZERO_WIDTH_JOINER = "\u200d";
5
+ /**
6
+ * Normalise an attacker-controlled OAuth Dynamic Client Registration name for
7
+ * display. The stored registration metadata remains verbatim; every UI/email
8
+ * surface calls this at its trust boundary.
9
+ *
10
+ * Unicode `Cf` includes bidi overrides/isolates, Arabic Letter Mark, soft
11
+ * hyphen, zero-width space, deprecated directional controls and tag
12
+ * characters. Those can reorder or invisibly split surrounding trusted copy.
13
+ * Replace them with a space rather than deleting them, so fragments cannot be
14
+ * joined into a new visible word. ZWNJ and ZWJ are the narrow exceptions:
15
+ * they are required for legitimate Persian/Indic text and emoji sequences and
16
+ * do not reorder the rest of the line. C0/C1 controls and lone surrogates are
17
+ * treated the same way.
18
+ *
19
+ * This deliberately does not apply NFKC or an ASCII allowlist. The label is
20
+ * explicitly unverified, and international product names should survive
21
+ * unchanged when they contain no display-control characters.
22
+ */
23
+ export function normaliseOAuthClientName(raw) {
24
+ if (!raw)
25
+ return null;
26
+ const cleaned = raw
27
+ .replace(/[\p{Cc}\p{Cf}\p{Cs}]/gu, (character) => character === ZERO_WIDTH_NON_JOINER || character === ZERO_WIDTH_JOINER ? character : " ")
28
+ .replace(/\s+/gu, " ")
29
+ .trim();
30
+ if (cleaned.length === 0)
31
+ return null;
32
+ const codePoints = [...cleaned];
33
+ return codePoints.length > MAX_OAUTH_CLIENT_NAME_DISPLAY_CODE_POINTS
34
+ ? `${codePoints.slice(0, MAX_OAUTH_CLIENT_NAME_DISPLAY_CODE_POINTS).join("")}...`
35
+ : cleaned;
36
+ }
37
+ //# sourceMappingURL=oauth-client-name.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-client-name.js","sourceRoot":"","sources":["../src/oauth-client-name.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,MAAM,CAAC,MAAM,yCAAyC,GAAG,EAAE,CAAC;AAE5D,MAAM,qBAAqB,GAAG,QAAQ,CAAC;AACvC,MAAM,iBAAiB,GAAG,QAAQ,CAAC;AAEnC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,wBAAwB,CAAC,GAA8B;IACrE,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAEtB,MAAM,OAAO,GAAG,GAAG;SAChB,OAAO,CAAC,wBAAwB,EAAE,CAAC,SAAS,EAAE,EAAE,CAC/C,SAAS,KAAK,qBAAqB,IAAI,SAAS,KAAK,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CACzF;SACA,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC;SACrB,IAAI,EAAE,CAAC;IACV,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEtC,MAAM,UAAU,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC;IAChC,OAAO,UAAU,CAAC,MAAM,GAAG,yCAAyC;QAClE,CAAC,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,yCAAyC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK;QACjF,CAAC,CAAC,OAAO,CAAC;AACd,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Hosted-MCP OAuth scope copy — the permission text a user reads on the
3
+ * consent screen, and again in the email telling them a client was connected.
4
+ *
5
+ * It lives here because those two surfaces must not drift: the email has to
6
+ * describe the access in the words the user actually consented to, and they
7
+ * are rendered by different processes (Next.js dashboard, API mailer). Two
8
+ * copies of security-relevant permission text is exactly the kind of drift
9
+ * that ends with an email under-describing what a grant can do.
10
+ *
11
+ * An unknown scope falls through to its raw token — nothing is ever silently
12
+ * hidden from the reader.
13
+ */
14
+ export declare const OAUTH_SCOPE_LABELS: Readonly<Record<string, string>>;
15
+ /** Map a raw scope token to its human label, or the token itself if unknown. */
16
+ export declare function describeOAuthScope(scope: string): string;
17
+ /** Split a space-delimited OAuth scope string into individual tokens. */
18
+ export declare function parseOAuthScopes(scope: string): string[];
19
+ //# sourceMappingURL=oauth-scopes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-scopes.d.ts","sourceRoot":"","sources":["../src/oauth-scopes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,eAAO,MAAM,kBAAkB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAK/D,CAAC;AAEF,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,yEAAyE;AACzE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAKxD"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Hosted-MCP OAuth scope copy — the permission text a user reads on the
3
+ * consent screen, and again in the email telling them a client was connected.
4
+ *
5
+ * It lives here because those two surfaces must not drift: the email has to
6
+ * describe the access in the words the user actually consented to, and they
7
+ * are rendered by different processes (Next.js dashboard, API mailer). Two
8
+ * copies of security-relevant permission text is exactly the kind of drift
9
+ * that ends with an email under-describing what a grant can do.
10
+ *
11
+ * An unknown scope falls through to its raw token — nothing is ever silently
12
+ * hidden from the reader.
13
+ */
14
+ export const OAUTH_SCOPE_LABELS = {
15
+ "mcp:apps": "View and operate your apps (deploy, env, domains, logs)",
16
+ "mcp:apps.read": "View your apps and their status",
17
+ "mcp:account": "View your account",
18
+ mcp: "Use the hosted Percher MCP tools",
19
+ };
20
+ /** Map a raw scope token to its human label, or the token itself if unknown. */
21
+ export function describeOAuthScope(scope) {
22
+ return OAUTH_SCOPE_LABELS[scope] ?? scope;
23
+ }
24
+ /** Split a space-delimited OAuth scope string into individual tokens. */
25
+ export function parseOAuthScopes(scope) {
26
+ return scope
27
+ .split(/\s+/)
28
+ .map((s) => s.trim())
29
+ .filter((s) => s.length > 0);
30
+ }
31
+ //# sourceMappingURL=oauth-scopes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-scopes.js","sourceRoot":"","sources":["../src/oauth-scopes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,CAAC,MAAM,kBAAkB,GAAqC;IAClE,UAAU,EAAE,yDAAyD;IACrE,eAAe,EAAE,iCAAiC;IAClD,aAAa,EAAE,mBAAmB;IAClC,GAAG,EAAE,kCAAkC;CACxC,CAAC;AAEF,gFAAgF;AAChF,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,OAAO,kBAAkB,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC;AAC5C,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,OAAO,KAAK;SACT,KAAK,CAAC,KAAK,CAAC;SACZ,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACjC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"platform-env.d.ts","sourceRoot":"","sources":["../src/platform-env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,yBAAyB,EAAE,WAAW,CAAC,MAAM,CAqBxD,CAAC;AAcH;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAItD;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGxD"}
1
+ {"version":3,"file":"platform-env.d.ts","sourceRoot":"","sources":["../src/platform-env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,yBAAyB,EAAE,WAAW,CAAC,MAAM,CA2BxD,CAAC;AAcH;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAItD;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGxD"}
@@ -32,6 +32,12 @@ export const PERCHER_INJECTED_ENV_KEYS = new Set([
32
32
  // App identity
33
33
  "PERCHER_APP_NAME",
34
34
  "PERCHER_APP_URL",
35
+ // App id + the identity-assertion verification key, injected into gated
36
+ // (private/password) apps so they can verify `X-Percher-Assertion` (see
37
+ // packages/api/src/site-identity-assertion.ts). Platform-owned like the
38
+ // rest — a user must never set them.
39
+ "PERCHER_APP_ID",
40
+ "PERCHER_IDENTITY_SECRET",
35
41
  // Egress proxy (Percher injects these so app code can reach the
36
42
  // public internet through the forward proxy — see docs/CLAUDE.md
37
43
  // "Outbound HTTPS")
@@ -1 +1 @@
1
- {"version":3,"file":"platform-env.js","sourceRoot":"","sources":["../src/platform-env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAwB,IAAI,GAAG,CAAS;IAC5E,yBAAyB;IACzB,UAAU;IACV,MAAM;IACN,sEAAsE;IACtE,kEAAkE;IAClE,8CAA8C;IAC9C,cAAc;IACd,gBAAgB;IAChB,wBAAwB;IACxB,2BAA2B;IAC3B,eAAe;IACf,kBAAkB;IAClB,iBAAiB;IACjB,gEAAgE;IAChE,iEAAiE;IACjE,oBAAoB;IACpB,YAAY;IACZ,aAAa;IACb,UAAU;IACV,oBAAoB;CACrB,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,uBAAuB,GAAwB,IAAI,GAAG,CAAS;IACnE,YAAY;IACZ,aAAa;IACb,UAAU;CACX,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,IAAI,yBAAyB,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACpD,IAAI,uBAAuB,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAClD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAW;IAC9C,IAAI,uBAAuB,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,CAAC,WAAW,EAAE,CAAC;IAC/D,OAAO,GAAG,CAAC;AACb,CAAC"}
1
+ {"version":3,"file":"platform-env.js","sourceRoot":"","sources":["../src/platform-env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAwB,IAAI,GAAG,CAAS;IAC5E,yBAAyB;IACzB,UAAU;IACV,MAAM;IACN,sEAAsE;IACtE,kEAAkE;IAClE,8CAA8C;IAC9C,cAAc;IACd,gBAAgB;IAChB,wBAAwB;IACxB,2BAA2B;IAC3B,eAAe;IACf,kBAAkB;IAClB,iBAAiB;IACjB,wEAAwE;IACxE,wEAAwE;IACxE,wEAAwE;IACxE,qCAAqC;IACrC,gBAAgB;IAChB,yBAAyB;IACzB,gEAAgE;IAChE,iEAAiE;IACjE,oBAAoB;IACpB,YAAY;IACZ,aAAa;IACb,UAAU;IACV,oBAAoB;CACrB,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,uBAAuB,GAAwB,IAAI,GAAG,CAAS;IACnE,YAAY;IACZ,aAAa;IACb,UAAU;CACX,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,IAAI,yBAAyB,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACpD,IAAI,uBAAuB,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAClD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAW;IAC9C,IAAI,uBAAuB,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,CAAC,WAAW,EAAE,CAAC;IAC/D,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -6,9 +6,26 @@
6
6
  * without depending on each other.
7
7
  */
8
8
  /** Default PocketBase sidecar image when PERCHER_POCKETBASE_IMAGE is unset. */
9
- export declare const DEFAULT_POCKETBASE_IMAGE = "percher/pocketbase:0.39.9";
9
+ export declare const DEFAULT_POCKETBASE_IMAGE = "percher/pocketbase:0.40.3-hardened.1";
10
10
  /** Default app domain suffix when PERCHER_DOMAIN_SUFFIX is unset. */
11
11
  export declare const DEFAULT_DOMAIN_SUFFIX = "percher.run";
12
12
  /** Default hosted-MCP HTTP port when PERCHER_MCP_HTTP_PORT is unset. */
13
13
  export declare const DEFAULT_MCP_HTTP_PORT = 8787;
14
+ /**
15
+ * Number of tools the LOCAL stdio MCP server registers, and the number the
16
+ * HOSTED connector exposes (a deny-by-default allowlist — a strict subset in
17
+ * spirit, though it also carries a few hosted-only names).
18
+ *
19
+ * These live here so public copy can state a figure without importing
20
+ * `@percher/mcp` (the dashboard doesn't depend on it). They are NOT the source
21
+ * of truth: `packages/mcp/test/tools.test.ts` asserts the real registry and
22
+ * allowlist match these constants, so bumping the tool set fails the build
23
+ * until the number is updated in exactly one place.
24
+ *
25
+ * The previous arrangement hardcoded the count in six unrelated files; they
26
+ * drifted to three different values (51/52/53), and one shipped artifact
27
+ * disagreed with itself on two lines.
28
+ */
29
+ export declare const MCP_TOOL_COUNT_LOCAL = 53;
30
+ export declare const MCP_TOOL_COUNT_HOSTED = 22;
14
31
  //# sourceMappingURL=platform.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,+EAA+E;AAC/E,eAAO,MAAM,wBAAwB,8BAA8B,CAAC;AAEpE,qEAAqE;AACrE,eAAO,MAAM,qBAAqB,gBAAgB,CAAC;AAEnD,wEAAwE;AACxE,eAAO,MAAM,qBAAqB,OAAO,CAAC"}
1
+ {"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,+EAA+E;AAC/E,eAAO,MAAM,wBAAwB,yCAAyC,CAAC;AAE/E,qEAAqE;AACrE,eAAO,MAAM,qBAAqB,gBAAgB,CAAC;AAEnD,wEAAwE;AACxE,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAE1C;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,oBAAoB,KAAK,CAAC;AACvC,eAAO,MAAM,qBAAqB,KAAK,CAAC"}
package/dist/platform.js CHANGED
@@ -6,9 +6,26 @@
6
6
  * without depending on each other.
7
7
  */
8
8
  /** Default PocketBase sidecar image when PERCHER_POCKETBASE_IMAGE is unset. */
9
- export const DEFAULT_POCKETBASE_IMAGE = "percher/pocketbase:0.39.9";
9
+ export const DEFAULT_POCKETBASE_IMAGE = "percher/pocketbase:0.40.3-hardened.1";
10
10
  /** Default app domain suffix when PERCHER_DOMAIN_SUFFIX is unset. */
11
11
  export const DEFAULT_DOMAIN_SUFFIX = "percher.run";
12
12
  /** Default hosted-MCP HTTP port when PERCHER_MCP_HTTP_PORT is unset. */
13
13
  export const DEFAULT_MCP_HTTP_PORT = 8787;
14
+ /**
15
+ * Number of tools the LOCAL stdio MCP server registers, and the number the
16
+ * HOSTED connector exposes (a deny-by-default allowlist — a strict subset in
17
+ * spirit, though it also carries a few hosted-only names).
18
+ *
19
+ * These live here so public copy can state a figure without importing
20
+ * `@percher/mcp` (the dashboard doesn't depend on it). They are NOT the source
21
+ * of truth: `packages/mcp/test/tools.test.ts` asserts the real registry and
22
+ * allowlist match these constants, so bumping the tool set fails the build
23
+ * until the number is updated in exactly one place.
24
+ *
25
+ * The previous arrangement hardcoded the count in six unrelated files; they
26
+ * drifted to three different values (51/52/53), and one shipped artifact
27
+ * disagreed with itself on two lines.
28
+ */
29
+ export const MCP_TOOL_COUNT_LOCAL = 53;
30
+ export const MCP_TOOL_COUNT_HOSTED = 22;
14
31
  //# sourceMappingURL=platform.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,+EAA+E;AAC/E,MAAM,CAAC,MAAM,wBAAwB,GAAG,2BAA2B,CAAC;AAEpE,qEAAqE;AACrE,MAAM,CAAC,MAAM,qBAAqB,GAAG,aAAa,CAAC;AAEnD,wEAAwE;AACxE,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC"}
1
+ {"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,+EAA+E;AAC/E,MAAM,CAAC,MAAM,wBAAwB,GAAG,sCAAsC,CAAC;AAE/E,qEAAqE;AACrE,MAAM,CAAC,MAAM,qBAAqB,GAAG,aAAa,CAAC;AAEnD,wEAAwE;AACxE,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAE1C;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AACvC,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * sha256 hex of a normalized invite code — the value stored in
3
+ * `signup_invites.code_hash`. Server-only, on its own subpath like `./pow`:
4
+ * the signup form imports the header name and code shape from the package
5
+ * index, and `node:crypto` must not reach that client bundle.
6
+ */
7
+ export declare function hashSignupInviteCode(code: string): string;
8
+ //# sourceMappingURL=signup-invite-hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signup-invite-hash.d.ts","sourceRoot":"","sources":["../src/signup-invite-hash.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzD"}
@@ -0,0 +1,11 @@
1
+ import { createHash } from "node:crypto";
2
+ /**
3
+ * sha256 hex of a normalized invite code — the value stored in
4
+ * `signup_invites.code_hash`. Server-only, on its own subpath like `./pow`:
5
+ * the signup form imports the header name and code shape from the package
6
+ * index, and `node:crypto` must not reach that client bundle.
7
+ */
8
+ export function hashSignupInviteCode(code) {
9
+ return createHash("sha256").update(code, "utf8").digest("hex");
10
+ }
11
+ //# sourceMappingURL=signup-invite-hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signup-invite-hash.js","sourceRoot":"","sources":["../src/signup-invite-hash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACjE,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Beta signup invites. Two processes act on one code and must not drift: the
3
+ * API mints and stores the hash, and the dashboard validates what the form
4
+ * sent. The API consumes the use atomically with account provisioning.
5
+ *
6
+ * The plaintext code is shown to the operator exactly once at mint time and
7
+ * never stored, so the hash is the only thing either side can compare. The
8
+ * hash itself lives in `./signup-invite-hash` (server-only, needs
9
+ * `node:crypto`); this module is safe for the signup form's client bundle.
10
+ */
11
+ /** Request header the signup form uses to carry the code through BetterAuth. */
12
+ export declare const SIGNUP_INVITE_HEADER = "x-percher-invite-code";
13
+ export declare const SIGNUP_INVITE_CODE_PREFIX = "beta-";
14
+ /** Random part is lowercase base36, so a code survives being typed or spoken. */
15
+ export declare const SIGNUP_INVITE_CODE_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
16
+ export declare const SIGNUP_INVITE_CODE_LENGTH = 24;
17
+ /**
18
+ * Canonical form of a code as a user might have typed it: whitespace trimmed,
19
+ * case folded. Returns null for anything that is not shaped like a code, so
20
+ * callers never hash (or log) arbitrary input.
21
+ */
22
+ export declare function normalizeSignupInviteCode(raw: string | null | undefined): string | null;
23
+ //# sourceMappingURL=signup-invite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signup-invite.d.ts","sourceRoot":"","sources":["../src/signup-invite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,gFAAgF;AAChF,eAAO,MAAM,oBAAoB,0BAA0B,CAAC;AAE5D,eAAO,MAAM,yBAAyB,UAAU,CAAC;AAEjD,iFAAiF;AACjF,eAAO,MAAM,2BAA2B,yCAAyC,CAAC;AAClF,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAI5C;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAIvF"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Beta signup invites. Two processes act on one code and must not drift: the
3
+ * API mints and stores the hash, and the dashboard validates what the form
4
+ * sent. The API consumes the use atomically with account provisioning.
5
+ *
6
+ * The plaintext code is shown to the operator exactly once at mint time and
7
+ * never stored, so the hash is the only thing either side can compare. The
8
+ * hash itself lives in `./signup-invite-hash` (server-only, needs
9
+ * `node:crypto`); this module is safe for the signup form's client bundle.
10
+ */
11
+ /** Request header the signup form uses to carry the code through BetterAuth. */
12
+ export const SIGNUP_INVITE_HEADER = "x-percher-invite-code";
13
+ export const SIGNUP_INVITE_CODE_PREFIX = "beta-";
14
+ /** Random part is lowercase base36, so a code survives being typed or spoken. */
15
+ export const SIGNUP_INVITE_CODE_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
16
+ export const SIGNUP_INVITE_CODE_LENGTH = 24;
17
+ const CODE_RE = /^beta-[a-z0-9]{24}$/;
18
+ /**
19
+ * Canonical form of a code as a user might have typed it: whitespace trimmed,
20
+ * case folded. Returns null for anything that is not shaped like a code, so
21
+ * callers never hash (or log) arbitrary input.
22
+ */
23
+ export function normalizeSignupInviteCode(raw) {
24
+ if (typeof raw !== "string")
25
+ return null;
26
+ const code = raw.trim().toLowerCase();
27
+ return CODE_RE.test(code) ? code : null;
28
+ }
29
+ //# sourceMappingURL=signup-invite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signup-invite.js","sourceRoot":"","sources":["../src/signup-invite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,gFAAgF;AAChF,MAAM,CAAC,MAAM,oBAAoB,GAAG,uBAAuB,CAAC;AAE5D,MAAM,CAAC,MAAM,yBAAyB,GAAG,OAAO,CAAC;AAEjD,iFAAiF;AACjF,MAAM,CAAC,MAAM,2BAA2B,GAAG,sCAAsC,CAAC;AAClF,MAAM,CAAC,MAAM,yBAAyB,GAAG,EAAE,CAAC;AAE5C,MAAM,OAAO,GAAG,qBAAqB,CAAC;AAEtC;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CAAC,GAA8B;IACtE,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACzC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACtC,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AAC1C,CAAC"}