@reposkein/mcp 0.4.0 → 0.6.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 (78) hide show
  1. package/README.md +6 -1
  2. package/binary-digests.json +4 -4
  3. package/dist/ads/auditLog.d.ts +23 -0
  4. package/dist/ads/auditLog.js +33 -0
  5. package/dist/ads/auditLog.js.map +1 -0
  6. package/dist/ads/config.d.ts +83 -0
  7. package/dist/ads/config.js +107 -0
  8. package/dist/ads/config.js.map +1 -0
  9. package/dist/ads/luluSource.d.ts +32 -0
  10. package/dist/ads/luluSource.js +60 -0
  11. package/dist/ads/luluSource.js.map +1 -0
  12. package/dist/ads/sanitize.d.ts +16 -0
  13. package/dist/ads/sanitize.js +187 -0
  14. package/dist/ads/sanitize.js.map +1 -0
  15. package/dist/ads/slot.d.ts +63 -0
  16. package/dist/ads/slot.js +159 -0
  17. package/dist/ads/slot.js.map +1 -0
  18. package/dist/ads/supporter.d.ts +73 -0
  19. package/dist/ads/supporter.js +119 -0
  20. package/dist/ads/supporter.js.map +1 -0
  21. package/dist/ads/supporterKey.d.ts +45 -0
  22. package/dist/ads/supporterKey.js +51 -0
  23. package/dist/ads/supporterKey.js.map +1 -0
  24. package/dist/ads/supporterStore.d.ts +61 -0
  25. package/dist/ads/supporterStore.js +113 -0
  26. package/dist/ads/supporterStore.js.map +1 -0
  27. package/dist/ads/supporterToken.d.ts +133 -0
  28. package/dist/ads/supporterToken.js +233 -0
  29. package/dist/ads/supporterToken.js.map +1 -0
  30. package/dist/ads/types.d.ts +53 -0
  31. package/dist/ads/types.js +15 -0
  32. package/dist/ads/types.js.map +1 -0
  33. package/dist/cli/support.d.ts +46 -0
  34. package/dist/cli/support.js +276 -0
  35. package/dist/cli/support.js.map +1 -0
  36. package/dist/cli/view.js +27 -6
  37. package/dist/cli/view.js.map +1 -1
  38. package/dist/index.d.ts +8 -56
  39. package/dist/index.js +45 -402
  40. package/dist/index.js.map +1 -1
  41. package/dist/indexer/indexLock.d.ts +13 -0
  42. package/dist/indexer/indexLock.js +66 -0
  43. package/dist/indexer/indexLock.js.map +1 -0
  44. package/dist/indexer/runIndexer.d.ts +8 -0
  45. package/dist/indexer/runIndexer.js +12 -0
  46. package/dist/indexer/runIndexer.js.map +1 -1
  47. package/dist/serve/serve.d.ts +95 -0
  48. package/dist/serve/serve.js +581 -0
  49. package/dist/serve/serve.js.map +1 -0
  50. package/dist/serve/tokens.d.ts +49 -0
  51. package/dist/serve/tokens.js +108 -0
  52. package/dist/serve/tokens.js.map +1 -0
  53. package/dist/serve/watch.d.ts +73 -0
  54. package/dist/serve/watch.js +135 -0
  55. package/dist/serve/watch.js.map +1 -0
  56. package/dist/server/createMcpServer.d.ts +134 -0
  57. package/dist/server/createMcpServer.js +473 -0
  58. package/dist/server/createMcpServer.js.map +1 -0
  59. package/dist/store/JsonlGraphStore.d.ts +5 -1
  60. package/dist/store/JsonlGraphStore.js +6 -2
  61. package/dist/store/JsonlGraphStore.js.map +1 -1
  62. package/dist/store/teamConfig.d.ts +20 -6
  63. package/dist/store/teamConfig.js +70 -17
  64. package/dist/store/teamConfig.js.map +1 -1
  65. package/dist/tools/recordDecision.d.ts +3 -0
  66. package/dist/tools/recordDecision.js +1 -1
  67. package/dist/tools/recordDecision.js.map +1 -1
  68. package/dist/tools/writeSemanticSummary.d.ts +4 -1
  69. package/dist/tools/writeSemanticSummary.js +5 -2
  70. package/dist/tools/writeSemanticSummary.js.map +1 -1
  71. package/dist/viz/assets/index-BnqIVYFE.css +1 -0
  72. package/dist/viz/assets/index-IhtszTp0.js +5 -0
  73. package/dist/viz/assets/{r3f-BZY3QR6_.js → r3f-Bn9htB92.js} +1 -1
  74. package/dist/viz/assets/{tanstack-CjXr-L-s.js → tanstack-CSzx6NDA.js} +7 -7
  75. package/dist/viz/index.html +4 -4
  76. package/package.json +3 -2
  77. package/dist/viz/assets/index-CxqeGuRS.css +0 -1
  78. package/dist/viz/assets/index-DiNZyrXx.js +0 -9
@@ -0,0 +1,63 @@
1
+ import { type SponsoredSlot, type SponsoredSource } from "./types.js";
2
+ /** Minimal shape the hook needs from a tool result — structurally compatible
3
+ * with `tools/readCypher.ts`'s `ToolResult` without importing it (the hook
4
+ * must stay usable around any handler). */
5
+ export interface AdResultLike {
6
+ isError?: boolean;
7
+ _meta?: Record<string, unknown>;
8
+ structuredContent?: unknown;
9
+ }
10
+ export interface AdsHookOptions {
11
+ /** The active repo root for `[ads] enabled`, resolved lazily so a disabled
12
+ * integration costs nothing. */
13
+ resolveRepoPath: () => string | undefined;
14
+ env?: NodeJS.ProcessEnv;
15
+ /** Injected in tests. Omitted in production: the real source is built (and
16
+ * the `lulu-ads` package first imported) only after the gating chain
17
+ * passes. Pass `null` to guarantee no source can ever be built. */
18
+ source?: SponsoredSource | null;
19
+ isSupporter?: () => boolean;
20
+ timeoutMs?: number;
21
+ /** Test seam: `[ads] enabled` lookup. */
22
+ readOptIn?: (repoPath: string) => boolean | null;
23
+ /** Test seams for the local request audit (`.reposkein/local/ads-requests.jsonl`).
24
+ * Defaults: the real appender, deferred with `setImmediate`. */
25
+ audit?: (repoPath: string, record: {
26
+ ts: string;
27
+ tool: string;
28
+ }) => void;
29
+ schedule?: (fn: () => void) => void;
30
+ }
31
+ export interface AdsHook {
32
+ /** Wraps a tool handler so its RESPONSE ENVELOPE may carry a sponsored slot.
33
+ * Wrap OUTSIDE `withLog` (`withAds(name, withLog(name, handler))`): the
34
+ * slot must not exist yet when the session logger captures the result, or
35
+ * sponsored bytes would show up in `.reposkein/local/sessions` byte counts.
36
+ * Attachment is copy-on-write for the same reason — the object the logger
37
+ * holds by reference is never mutated. */
38
+ withAds<Args, R extends AdResultLike>(tool: string, cb: (args: Args) => Promise<R>): (args: Args) => Promise<R>;
39
+ }
40
+ /** Builds the sponsorship hook for ONE server connection (mirrors
41
+ * `createToolLogger`: per-connection state, no module globals).
42
+ *
43
+ * Everything about this is fail-open. A disabled integration, absent
44
+ * credentials, a rejecting source, a hanging source, a hostile payload and an
45
+ * outright bug all land in the same place: the tool's own result, returned
46
+ * unchanged. There is no code path here that can make a tool call fail, and
47
+ * none that can slow it by more than `timeoutMs`. */
48
+ export declare function createAdsHook(opts: AdsHookOptions): AdsHook;
49
+ /** Attaches the slot to the response ENVELOPE, copy-on-write.
50
+ *
51
+ * `_meta` under a namespaced key — not `content`, and never a
52
+ * `structuredContent` object invented for the purpose. Rationale: `content`
53
+ * is the tool's answer and the one surface an agent reads as prose, so
54
+ * sponsored data must never appear there in any form (concatenated or
55
+ * appended); and materializing a `structuredContent` that only holds an ad
56
+ * would let a host that prefers structured output over text render the ad
57
+ * INSTEAD of the answer. When a tool already returns structuredContent, the
58
+ * slot is mirrored there as a clearly separate `sponsored` key so hosts that
59
+ * render structured fields can disclose it.
60
+ *
61
+ * The `sponsored` label travels inside the slot object and the envelope key
62
+ * itself is fixed: nothing here can rename, hide, or restyle the disclosure. */
63
+ export declare function attachSponsored<R extends AdResultLike>(result: R, slot: SponsoredSlot): R;
@@ -0,0 +1,159 @@
1
+ import { AD_ELIGIBLE_TOOLS, SLOT_TIMEOUT_MS, resolveAdsVerdict, } from "./config.js";
2
+ import { luluAdsSource } from "./luluSource.js";
3
+ import { sanitizeSponsored } from "./sanitize.js";
4
+ import { isSupporter as defaultIsSupporter } from "./supporter.js";
5
+ import { readConfigBool } from "../store/teamConfig.js";
6
+ import { appendAdsRequest } from "./auditLog.js";
7
+ import { SPONSORED_META_KEY } from "./types.js";
8
+ /** Builds the sponsorship hook for ONE server connection (mirrors
9
+ * `createToolLogger`: per-connection state, no module globals).
10
+ *
11
+ * Everything about this is fail-open. A disabled integration, absent
12
+ * credentials, a rejecting source, a hanging source, a hostile payload and an
13
+ * outright bug all land in the same place: the tool's own result, returned
14
+ * unchanged. There is no code path here that can make a tool call fail, and
15
+ * none that can slow it by more than `timeoutMs`. */
16
+ export function createAdsHook(opts) {
17
+ const env = opts.env ?? process.env;
18
+ const timeoutMs = opts.timeoutMs ?? SLOT_TIMEOUT_MS;
19
+ // Bound to THIS hook's env so an injected environment reaches the
20
+ // entitlement-file lookup too (`REPOSKEIN_SUPPORTER_FILE`), rather than the
21
+ // supporter check silently reading the real `process.env` while everything
22
+ // else in the chain reads the injected one.
23
+ const isSupporter = opts.isSupporter ?? (() => defaultIsSupporter(env));
24
+ const audit = opts.audit ?? appendAdsRequest;
25
+ const schedule = opts.schedule ?? setImmediate;
26
+ // The config.toml opt-in is memoized for the connection's lifetime: one
27
+ // filesystem read per repo, never one per tool call. The env kill switch and
28
+ // the credential/supporter checks are re-evaluated on every call (they are
29
+ // pure reads of already-loaded state), so `REPOSKEIN_ADS=off` takes effect
30
+ // immediately while a config.toml edit needs a reconnect.
31
+ const optInMemo = new Map();
32
+ const readOptIn = (repoPath) => {
33
+ if (!optInMemo.has(repoPath)) {
34
+ const read = opts.readOptIn;
35
+ try {
36
+ optInMemo.set(repoPath, read ? read(repoPath) : resolveOptInFromDisk(repoPath));
37
+ }
38
+ catch {
39
+ optInMemo.set(repoPath, null);
40
+ }
41
+ }
42
+ return optInMemo.get(repoPath) ?? null;
43
+ };
44
+ let source = opts.source;
45
+ function sourceFor(verdict) {
46
+ if (source !== undefined)
47
+ return source;
48
+ source = luluAdsSource({
49
+ publisherId: verdict.publisherId,
50
+ apiKey: verdict.apiKey,
51
+ baseUrl: verdict.baseUrl,
52
+ });
53
+ return source;
54
+ }
55
+ /** One bounded slot request, or null. Never throws, never exceeds the
56
+ * budget: an `AbortController` is signalled at the deadline and the race
57
+ * resolves null regardless of what the source does afterwards, so even a
58
+ * source that ignores the signal entirely cannot delay a tool result. */
59
+ async function requestSlot(src, ctx, clickHosts) {
60
+ const controller = new AbortController();
61
+ let timer;
62
+ const deadline = new Promise((resolve) => {
63
+ timer = setTimeout(() => {
64
+ controller.abort();
65
+ resolve(null);
66
+ }, timeoutMs);
67
+ timer.unref?.();
68
+ });
69
+ try {
70
+ const raw = await Promise.race([
71
+ src.requestSlot(ctx, { timeoutMs, signal: controller.signal }).catch(() => null),
72
+ deadline,
73
+ ]);
74
+ if (raw === null || raw === undefined)
75
+ return null;
76
+ return sanitizeSponsored(raw, { clickHosts });
77
+ }
78
+ catch {
79
+ return null;
80
+ }
81
+ finally {
82
+ if (timer)
83
+ clearTimeout(timer);
84
+ }
85
+ }
86
+ async function maybeSlot(tool) {
87
+ if (!AD_ELIGIBLE_TOOLS.includes(tool))
88
+ return null;
89
+ const repoPath = opts.resolveRepoPath();
90
+ const verdict = resolveAdsVerdict({
91
+ repoPath,
92
+ env,
93
+ isSupporter,
94
+ readOptIn,
95
+ });
96
+ if (!verdict.enabled)
97
+ return null;
98
+ const src = sourceFor(verdict);
99
+ if (!src)
100
+ return null;
101
+ // Audit BEFORE the call, deferred off the hot path (the instrumentTool
102
+ // pattern): the line records that a request left this machine, so it must
103
+ // be written even if the request then hangs, fails, or is aborted at the
104
+ // deadline. Never blocks and never throws.
105
+ if (repoPath) {
106
+ const ts = new Date().toISOString();
107
+ schedule(() => audit(repoPath, { ts, tool }));
108
+ }
109
+ return requestSlot(src, { tool }, verdict.clickHosts);
110
+ }
111
+ function withAds(tool, cb) {
112
+ return async (args) => {
113
+ const result = await cb(args);
114
+ try {
115
+ // Error results are off-limits: a failure is never a sales surface,
116
+ // and a caller debugging one should see nothing but the failure.
117
+ if (!result || typeof result !== "object" || result.isError === true)
118
+ return result;
119
+ const slot = await maybeSlot(tool);
120
+ if (!slot)
121
+ return result;
122
+ return attachSponsored(result, slot);
123
+ }
124
+ catch {
125
+ // A sponsorship bug must be invisible to the caller.
126
+ return result;
127
+ }
128
+ };
129
+ }
130
+ return { withAds };
131
+ }
132
+ /** `[ads] enabled` from `.reposkein/config.toml`. */
133
+ function resolveOptInFromDisk(repoPath) {
134
+ return readConfigBool(repoPath, "ads", "enabled");
135
+ }
136
+ /** Attaches the slot to the response ENVELOPE, copy-on-write.
137
+ *
138
+ * `_meta` under a namespaced key — not `content`, and never a
139
+ * `structuredContent` object invented for the purpose. Rationale: `content`
140
+ * is the tool's answer and the one surface an agent reads as prose, so
141
+ * sponsored data must never appear there in any form (concatenated or
142
+ * appended); and materializing a `structuredContent` that only holds an ad
143
+ * would let a host that prefers structured output over text render the ad
144
+ * INSTEAD of the answer. When a tool already returns structuredContent, the
145
+ * slot is mirrored there as a clearly separate `sponsored` key so hosts that
146
+ * render structured fields can disclose it.
147
+ *
148
+ * The `sponsored` label travels inside the slot object and the envelope key
149
+ * itself is fixed: nothing here can rename, hide, or restyle the disclosure. */
150
+ export function attachSponsored(result, slot) {
151
+ const meta = { ...(result._meta ?? {}), [SPONSORED_META_KEY]: slot };
152
+ const out = { ...result, _meta: meta };
153
+ const structured = result.structuredContent;
154
+ if (structured && typeof structured === "object" && !Array.isArray(structured)) {
155
+ out.structuredContent = { ...structured, sponsored: slot };
156
+ }
157
+ return out;
158
+ }
159
+ //# sourceMappingURL=slot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slot.js","sourceRoot":"","sources":["../../src/ads/slot.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,iBAAiB,GAElB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,WAAW,IAAI,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAA8D,MAAM,YAAY,CAAC;AA2C5G;;;;;;;sDAOsD;AACtD,MAAM,UAAU,aAAa,CAAC,IAAoB;IAChD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACpC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,eAAe,CAAC;IACpD,kEAAkE;IAClE,4EAA4E;IAC5E,2EAA2E;IAC3E,4CAA4C;IAC5C,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,CAAC,GAAG,EAAE,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC;IACxE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,gBAAgB,CAAC;IAC7C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,YAAY,CAAC;IAE/C,wEAAwE;IACxE,6EAA6E;IAC7E,2EAA2E;IAC3E,2EAA2E;IAC3E,0DAA0D;IAC1D,MAAM,SAAS,GAAG,IAAI,GAAG,EAA0B,CAAC;IACpD,MAAM,SAAS,GAAG,CAAC,QAAgB,EAAkB,EAAE;QACrD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;YAC5B,IAAI,CAAC;gBACH,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC,CAAC;YAClF,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YAChC,CAAC;QACH,CAAC;QACD,OAAO,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC;IACzC,CAAC,CAAC;IAEF,IAAI,MAAM,GAAuC,IAAI,CAAC,MAAM,CAAC;IAE7D,SAAS,SAAS,CAAC,OAA+C;QAChE,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,MAAM,CAAC;QACxC,MAAM,GAAG,aAAa,CAAC;YACrB,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,OAAO,EAAE,OAAO,CAAC,OAAO;SACzB,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;;8EAG0E;IAC1E,KAAK,UAAU,WAAW,CACxB,GAAoB,EACpB,GAAgB,EAChB,UAAoB;QAEpB,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,IAAI,KAAgD,CAAC;QACrD,MAAM,QAAQ,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YAC7C,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBACtB,UAAU,CAAC,KAAK,EAAE,CAAC;gBACnB,OAAO,CAAC,IAAI,CAAC,CAAC;YAChB,CAAC,EAAE,SAAS,CAAC,CAAC;YACd,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;QAClB,CAAC,CAAC,CAAC;QACH,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;gBAC7B,GAAG,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC;gBAChF,QAAQ;aACT,CAAC,CAAC;YACH,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS;gBAAE,OAAO,IAAI,CAAC;YACnD,OAAO,iBAAiB,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC;QAChD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;gBAAS,CAAC;YACT,IAAI,KAAK;gBAAE,YAAY,CAAC,KAAK,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED,KAAK,UAAU,SAAS,CAAC,IAAY;QACnC,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;QACnD,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QACxC,MAAM,OAAO,GAAG,iBAAiB,CAAC;YAChC,QAAQ;YACR,GAAG;YACH,WAAW;YACX,SAAS;SACV,CAAC,CAAC;QACH,IAAI,CAAC,OAAO,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAClC,MAAM,GAAG,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;QAC/B,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QACtB,uEAAuE;QACvE,0EAA0E;QAC1E,yEAAyE;QACzE,2CAA2C;QAC3C,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YACpC,QAAQ,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAChD,CAAC;QACD,OAAO,WAAW,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IACxD,CAAC;IAED,SAAS,OAAO,CACd,IAAY,EACZ,EAA8B;QAE9B,OAAO,KAAK,EAAE,IAAU,EAAc,EAAE;YACtC,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC;YAC9B,IAAI,CAAC;gBACH,oEAAoE;gBACpE,iEAAiE;gBACjE,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,OAAO,KAAK,IAAI;oBAAE,OAAO,MAAM,CAAC;gBACpF,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,CAAC;gBACnC,IAAI,CAAC,IAAI;oBAAE,OAAO,MAAM,CAAC;gBACzB,OAAO,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YACvC,CAAC;YAAC,MAAM,CAAC;gBACP,qDAAqD;gBACrD,OAAO,MAAM,CAAC;YAChB,CAAC;QACH,CAAC,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,CAAC;AACrB,CAAC;AAED,qDAAqD;AACrD,SAAS,oBAAoB,CAAC,QAAgB;IAC5C,OAAO,cAAc,CAAC,QAAQ,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;AACpD,CAAC;AAED;;;;;;;;;;;;;iFAaiF;AACjF,MAAM,UAAU,eAAe,CAAyB,MAAS,EAAE,IAAmB;IACpF,MAAM,IAAI,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC,kBAAkB,CAAC,EAAE,IAAI,EAAE,CAAC;IACrE,MAAM,GAAG,GAAM,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAC1C,MAAM,UAAU,GAAG,MAAM,CAAC,iBAAiB,CAAC;IAC5C,IAAI,UAAU,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/E,GAAG,CAAC,iBAAiB,GAAG,EAAE,GAAI,UAAsC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IAC1F,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,73 @@
1
+ /** Supporter check — the "you already paid, you see no ads" gate.
2
+ *
3
+ * REP-28 wired a stub of this into `resolveAdsVerdict` so the sponsorship
4
+ * path could never ship without it. REP-29 filled it in. The signature is
5
+ * unchanged: still synchronous, still consulted before any slot is
6
+ * requested, still returning a plain boolean.
7
+ *
8
+ * ## Entirely local
9
+ *
10
+ * Verification is a signature check against a public key compiled into this
11
+ * package (`supporterKey.ts`) over a file the user placed at
12
+ * `~/.config/reposkein/supporter.jwt`. There is no licence server, no
13
+ * activation, no revocation fetch, no telemetry — and the import graph
14
+ * reachable from this module contains nothing that can open a socket, which
15
+ * a test asserts statically rather than trusting this comment. Being a
16
+ * supporter is therefore something you can be on a plane, behind a
17
+ * corporate proxy, or on a machine that has never resolved DNS.
18
+ *
19
+ * ## Staying off the hot path
20
+ *
21
+ * This runs inside a tool call's gating chain, so the expensive parts are
22
+ * cached for the process:
23
+ *
24
+ * - The signature verification result is cached against the entitlement
25
+ * file's (mtime, size). Editing or replacing the file invalidates it.
26
+ * - The `statSync` that detects such an edit is THROTTLED: at most one every
27
+ * `RECHECK_INTERVAL_MS`. Between probes the cached claims are reused, so a
28
+ * steady stream of tool calls costs one integer comparison each, not one
29
+ * syscall each.
30
+ * - Expiry is re-evaluated on every call regardless, since it depends on the
31
+ * clock rather than on the file. A token that lapses mid-session stops
32
+ * entitling immediately, without waiting for a stat.
33
+ *
34
+ * The bound this buys: a token added, renewed, or deleted mid-session takes
35
+ * effect within `RECHECK_INTERVAL_MS` (five seconds) rather than instantly.
36
+ * For a gate whose only job is to suppress an ad, that is the right trade. */
37
+ import { type SupporterClaims, type SupporterRejection, type SupporterVerdict } from "./supporterToken.js";
38
+ /** How often the entitlement file may be re-probed on the hot path. */
39
+ export declare const RECHECK_INTERVAL_MS = 5000;
40
+ /** What `support --status` (and `doctor`, later) needs to say something
41
+ * useful. `state: "none"` means no entitlement file at all — the state of
42
+ * every install that has not run `reposkein-mcp support`. */
43
+ export type SupporterStatus = {
44
+ state: "none";
45
+ path: string;
46
+ } | {
47
+ state: "valid" | "grace" | "expired";
48
+ path: string;
49
+ claims: SupporterClaims;
50
+ mode: number | null;
51
+ } | {
52
+ state: "invalid";
53
+ path: string;
54
+ reason: SupporterRejection;
55
+ mode: number | null;
56
+ };
57
+ /** Drops the memoized verification. Tests call it; nothing in production
58
+ * does — the mtime/size probe is what keeps the cache honest at runtime. */
59
+ export declare function resetSupporterCache(): void;
60
+ /** Reads + verifies with no caching and no clock-independent shortcuts. The
61
+ * CLI path: correctness and detail matter, a syscall does not. */
62
+ export declare function readSupporterStatus(env?: NodeJS.ProcessEnv, now?: number): SupporterStatus;
63
+ /** The cached verdict used by the gating chain. Exported for tests and for
64
+ * anything that wants the reason as well as the boolean. */
65
+ export declare function supporterVerdict(env?: NodeJS.ProcessEnv, now?: number): SupporterVerdict;
66
+ /** The gate itself. True only for a signed, untampered, correctly-tiered
67
+ * token that is inside its validity window or its grace period.
68
+ *
69
+ * Fail-CLOSED with respect to entitlement (any doubt → "not a supporter",
70
+ * which merely means the normal, already-opt-in-gated ad path applies) and
71
+ * fail-OPEN with respect to errors: nothing here can throw into a tool
72
+ * call. */
73
+ export declare function isSupporter(env?: NodeJS.ProcessEnv, now?: number): boolean;
@@ -0,0 +1,119 @@
1
+ /** Supporter check — the "you already paid, you see no ads" gate.
2
+ *
3
+ * REP-28 wired a stub of this into `resolveAdsVerdict` so the sponsorship
4
+ * path could never ship without it. REP-29 filled it in. The signature is
5
+ * unchanged: still synchronous, still consulted before any slot is
6
+ * requested, still returning a plain boolean.
7
+ *
8
+ * ## Entirely local
9
+ *
10
+ * Verification is a signature check against a public key compiled into this
11
+ * package (`supporterKey.ts`) over a file the user placed at
12
+ * `~/.config/reposkein/supporter.jwt`. There is no licence server, no
13
+ * activation, no revocation fetch, no telemetry — and the import graph
14
+ * reachable from this module contains nothing that can open a socket, which
15
+ * a test asserts statically rather than trusting this comment. Being a
16
+ * supporter is therefore something you can be on a plane, behind a
17
+ * corporate proxy, or on a machine that has never resolved DNS.
18
+ *
19
+ * ## Staying off the hot path
20
+ *
21
+ * This runs inside a tool call's gating chain, so the expensive parts are
22
+ * cached for the process:
23
+ *
24
+ * - The signature verification result is cached against the entitlement
25
+ * file's (mtime, size). Editing or replacing the file invalidates it.
26
+ * - The `statSync` that detects such an edit is THROTTLED: at most one every
27
+ * `RECHECK_INTERVAL_MS`. Between probes the cached claims are reused, so a
28
+ * steady stream of tool calls costs one integer comparison each, not one
29
+ * syscall each.
30
+ * - Expiry is re-evaluated on every call regardless, since it depends on the
31
+ * clock rather than on the file. A token that lapses mid-session stops
32
+ * entitling immediately, without waiting for a stat.
33
+ *
34
+ * The bound this buys: a token added, renewed, or deleted mid-session takes
35
+ * effect within `RECHECK_INTERVAL_MS` (five seconds) rather than instantly.
36
+ * For a gate whose only job is to suppress an ad, that is the right trade. */
37
+ import { classifyClaims, isEntitled, verifySupporterTokenClaims, } from "./supporterToken.js";
38
+ import { readSupporterTokenFile, statSupporterTokenFile, supporterTokenPath } from "./supporterStore.js";
39
+ /** How often the entitlement file may be re-probed on the hot path. */
40
+ export const RECHECK_INTERVAL_MS = 5_000;
41
+ /** Module-level and deliberately so: the entitlement is a property of the
42
+ * machine, not of a server connection, and re-verifying an Ed25519 signature
43
+ * per connection would be pure waste. Reset in tests via
44
+ * `resetSupporterCache()`. */
45
+ let cache = null;
46
+ /** Drops the memoized verification. Tests call it; nothing in production
47
+ * does — the mtime/size probe is what keeps the cache honest at runtime. */
48
+ export function resetSupporterCache() {
49
+ cache = null;
50
+ }
51
+ /** Reads + verifies with no caching and no clock-independent shortcuts. The
52
+ * CLI path: correctness and detail matter, a syscall does not. */
53
+ export function readSupporterStatus(env = process.env, now = Date.now()) {
54
+ const path = supporterTokenPath(env);
55
+ const st = statSupporterTokenFile(env);
56
+ if (!st)
57
+ return { state: "none", path };
58
+ const text = readSupporterTokenFile(env);
59
+ if (text === null)
60
+ return { state: "invalid", path, reason: "empty", mode: st.mode };
61
+ const parsed = verifySupporterTokenClaims(text);
62
+ if (!parsed.ok)
63
+ return { state: "invalid", path, reason: parsed.reason, mode: st.mode };
64
+ const verdict = classifyClaims(parsed.claims, now);
65
+ if (verdict.state === "invalid")
66
+ return { state: "invalid", path, reason: verdict.reason, mode: st.mode };
67
+ return { state: verdict.state, path, claims: verdict.claims, mode: st.mode };
68
+ }
69
+ /** The cached verdict used by the gating chain. Exported for tests and for
70
+ * anything that wants the reason as well as the boolean. */
71
+ export function supporterVerdict(env = process.env, now = Date.now()) {
72
+ const path = supporterTokenPath(env);
73
+ // Reuse the cached claims without touching the filesystem while the
74
+ // throttle window is open and the path has not changed under us.
75
+ if (cache && cache.path === path && now - cache.probedAt < RECHECK_INTERVAL_MS) {
76
+ return fromCache(cache, now);
77
+ }
78
+ const st = statSupporterTokenFile(env);
79
+ if (!st) {
80
+ cache = { mtimeMs: -1, size: -1, path, claims: null, reason: "empty", probedAt: now };
81
+ return { state: "invalid", reason: "empty" };
82
+ }
83
+ if (cache && cache.path === path && cache.mtimeMs === st.mtimeMs && cache.size === st.size) {
84
+ cache.probedAt = now;
85
+ return fromCache(cache, now);
86
+ }
87
+ const text = readSupporterTokenFile(env);
88
+ const parsed = text === null ? { ok: false, reason: "empty" } : verifySupporterTokenClaims(text);
89
+ cache = {
90
+ mtimeMs: st.mtimeMs,
91
+ size: st.size,
92
+ path,
93
+ claims: parsed.ok ? parsed.claims : null,
94
+ reason: parsed.ok ? null : parsed.reason,
95
+ probedAt: now,
96
+ };
97
+ return fromCache(cache, now);
98
+ }
99
+ function fromCache(entry, now) {
100
+ if (!entry.claims)
101
+ return { state: "invalid", reason: entry.reason ?? "empty" };
102
+ return classifyClaims(entry.claims, now);
103
+ }
104
+ /** The gate itself. True only for a signed, untampered, correctly-tiered
105
+ * token that is inside its validity window or its grace period.
106
+ *
107
+ * Fail-CLOSED with respect to entitlement (any doubt → "not a supporter",
108
+ * which merely means the normal, already-opt-in-gated ad path applies) and
109
+ * fail-OPEN with respect to errors: nothing here can throw into a tool
110
+ * call. */
111
+ export function isSupporter(env = process.env, now = Date.now()) {
112
+ try {
113
+ return isEntitled(supporterVerdict(env, now));
114
+ }
115
+ catch {
116
+ return false;
117
+ }
118
+ }
119
+ //# sourceMappingURL=supporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supporter.js","sourceRoot":"","sources":["../../src/ads/supporter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /** The public half of the supporter-entitlement signing key (REP-29).
2
+ *
3
+ * Baked into the published package on purpose. Verification of a supporter
4
+ * token is a purely local operation — signature, tier, expiry — and this
5
+ * constant is the whole trust root for it. Nothing about entitlement needs a
6
+ * server, so nothing about entitlement contacts one: no licence check, no
7
+ * activation call, no heartbeat, no way for us to learn that you ran the
8
+ * tool. That property is only achievable if the verifier ships with the key.
9
+ *
10
+ * ## Provenance
11
+ *
12
+ * - Algorithm: Ed25519 (RFC 8032), SPKI/PEM encoding.
13
+ * - Generated 2026-08-21 with `node:crypto` `generateKeyPairSync("ed25519")`
14
+ * on the maintainer's machine, during the REP-29 implementation.
15
+ * - The PRIVATE half was never committed and never left that machine except
16
+ * into a secret store. Its only consumer is the Ko-fi fulfilment worker
17
+ * (`workers/kofi-fulfillment/`), which holds it as the Cloudflare secret
18
+ * `SUPPORTER_SIGNING_KEY`. No developer workflow, no test, and no part of
19
+ * the published package needs it: tests that must sign generate their own
20
+ * throwaway keypair.
21
+ *
22
+ * ## What this key can and cannot do for us
23
+ *
24
+ * Publishing the public half means anyone can *verify* a token; only the
25
+ * holder of the private half can *mint* one. It also means a supporter can
26
+ * read their own token's payload — which is deliberate, and why the payload
27
+ * contains nothing but an opaque subject hash, a tier name, and two
28
+ * timestamps (see `supporterToken.ts`). There is nothing in it to hide,
29
+ * because there is nothing in it about you.
30
+ *
31
+ * ## Rotation
32
+ *
33
+ * Tokens carry a `kid`. To rotate: add the new public key here alongside the
34
+ * old one, keep verifying both until every issued token has expired, then
35
+ * drop the old entry. Removing a key invalidates every token signed with it,
36
+ * so a rotation that is not additive-then-subtractive will strand paying
37
+ * supporters. The verifier already selects by `kid`, so the only change a
38
+ * rotation needs is to this map. */
39
+ /** Key id → SPKI PEM. Every entry is a key tokens may legitimately be signed
40
+ * with; a token naming a `kid` absent from this map is rejected before any
41
+ * signature check happens. */
42
+ export declare const SUPPORTER_PUBLIC_KEYS: Readonly<Record<string, string>>;
43
+ /** The key the minter should be signing with today. Only the fulfilment
44
+ * worker cares; the verifier accepts anything in `SUPPORTER_PUBLIC_KEYS`. */
45
+ export declare const CURRENT_SUPPORTER_KID = "skein-2026-08";
@@ -0,0 +1,51 @@
1
+ /** The public half of the supporter-entitlement signing key (REP-29).
2
+ *
3
+ * Baked into the published package on purpose. Verification of a supporter
4
+ * token is a purely local operation — signature, tier, expiry — and this
5
+ * constant is the whole trust root for it. Nothing about entitlement needs a
6
+ * server, so nothing about entitlement contacts one: no licence check, no
7
+ * activation call, no heartbeat, no way for us to learn that you ran the
8
+ * tool. That property is only achievable if the verifier ships with the key.
9
+ *
10
+ * ## Provenance
11
+ *
12
+ * - Algorithm: Ed25519 (RFC 8032), SPKI/PEM encoding.
13
+ * - Generated 2026-08-21 with `node:crypto` `generateKeyPairSync("ed25519")`
14
+ * on the maintainer's machine, during the REP-29 implementation.
15
+ * - The PRIVATE half was never committed and never left that machine except
16
+ * into a secret store. Its only consumer is the Ko-fi fulfilment worker
17
+ * (`workers/kofi-fulfillment/`), which holds it as the Cloudflare secret
18
+ * `SUPPORTER_SIGNING_KEY`. No developer workflow, no test, and no part of
19
+ * the published package needs it: tests that must sign generate their own
20
+ * throwaway keypair.
21
+ *
22
+ * ## What this key can and cannot do for us
23
+ *
24
+ * Publishing the public half means anyone can *verify* a token; only the
25
+ * holder of the private half can *mint* one. It also means a supporter can
26
+ * read their own token's payload — which is deliberate, and why the payload
27
+ * contains nothing but an opaque subject hash, a tier name, and two
28
+ * timestamps (see `supporterToken.ts`). There is nothing in it to hide,
29
+ * because there is nothing in it about you.
30
+ *
31
+ * ## Rotation
32
+ *
33
+ * Tokens carry a `kid`. To rotate: add the new public key here alongside the
34
+ * old one, keep verifying both until every issued token has expired, then
35
+ * drop the old entry. Removing a key invalidates every token signed with it,
36
+ * so a rotation that is not additive-then-subtractive will strand paying
37
+ * supporters. The verifier already selects by `kid`, so the only change a
38
+ * rotation needs is to this map. */
39
+ /** Key id → SPKI PEM. Every entry is a key tokens may legitimately be signed
40
+ * with; a token naming a `kid` absent from this map is rejected before any
41
+ * signature check happens. */
42
+ export const SUPPORTER_PUBLIC_KEYS = Object.freeze({
43
+ "skein-2026-08": `-----BEGIN PUBLIC KEY-----
44
+ MCowBQYDK2VwAyEAV7vHvsTIlioxm9pWOJ83/sEZW/ps8ymd2I+LZeucnOk=
45
+ -----END PUBLIC KEY-----
46
+ `,
47
+ });
48
+ /** The key the minter should be signing with today. Only the fulfilment
49
+ * worker cares; the verifier accepts anything in `SUPPORTER_PUBLIC_KEYS`. */
50
+ export const CURRENT_SUPPORTER_KID = "skein-2026-08";
51
+ //# sourceMappingURL=supporterKey.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supporterKey.js","sourceRoot":"","sources":["../../src/ads/supporterKey.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qCAqCqC;AAErC;;+BAE+B;AAC/B,MAAM,CAAC,MAAM,qBAAqB,GAAqC,MAAM,CAAC,MAAM,CAAC;IACnF,eAAe,EAAE;;;CAGlB;CACA,CAAC,CAAC;AAEH;8EAC8E;AAC9E,MAAM,CAAC,MAAM,qBAAqB,GAAG,eAAe,CAAC"}
@@ -0,0 +1,61 @@
1
+ /** Where a supporter token lives on disk, and the only code that puts it
2
+ * there.
3
+ *
4
+ * ## User-level, never repo-level
5
+ *
6
+ * Entitlement belongs to a PERSON, not to a checkout. Storing it under
7
+ * `.reposkein/` would be wrong three times over: it would ride into git (or
8
+ * need yet another ignore rule to stop it), it would be shared with everyone
9
+ * who clones the repo — handing them a token they did not pay for — and it
10
+ * would have to be re-supplied in every working copy by the one person who
11
+ * did. So it lives under the user's config directory and nothing in this
12
+ * package ever writes an entitlement byte inside a repository. There is a
13
+ * test that asserts exactly that by byte-comparing a repo tree across a
14
+ * `support` invocation.
15
+ *
16
+ * ## Permissions
17
+ *
18
+ * Directory 0700, file 0600, and the file mode is re-applied with an
19
+ * explicit `chmodSync` after every write: `writeFileSync`'s `mode` option is
20
+ * honoured only when the file is CREATED, so overwriting an existing
21
+ * world-readable token would otherwise silently leave it world-readable.
22
+ * The token is not a password — the worst a thief gets is no ads — but a
23
+ * paid entitlement is still the holder's to keep, and 0600 costs nothing.
24
+ *
25
+ * On Windows these modes are approximated by the runtime; `--status` reports
26
+ * the mode it actually observes rather than asserting a POSIX guarantee the
27
+ * platform does not offer. */
28
+ /** Overrides the entitlement file location. Exists for tests and for anyone
29
+ * running with an unusual HOME; it is NOT a bypass — the file it points at
30
+ * still has to contain a validly signed token, so redirecting it cannot
31
+ * manufacture entitlement. */
32
+ export declare const SUPPORTER_FILE_ENV = "REPOSKEIN_SUPPORTER_FILE";
33
+ /** Resolves the entitlement file path, in order:
34
+ *
35
+ * 1. `REPOSKEIN_SUPPORTER_FILE`, if absolute.
36
+ * 2. `$XDG_CONFIG_HOME/reposkein/supporter.jwt`, if absolute.
37
+ * 3. `~/.config/reposkein/supporter.jwt` — the documented location. */
38
+ export declare function supporterTokenPath(env?: NodeJS.ProcessEnv): string;
39
+ export interface SupporterFileStat {
40
+ mtimeMs: number;
41
+ size: number;
42
+ /** POSIX permission bits (`mode & 0o777`). */
43
+ mode: number;
44
+ }
45
+ /** Cheap existence + identity probe. Returns null for "no file" and for any
46
+ * error reading it — an unreadable entitlement file is indistinguishable
47
+ * from an absent one as far as entitlement goes, and this runs on a tool
48
+ * call's hot path, so it must never throw. */
49
+ export declare function statSupporterTokenFile(env?: NodeJS.ProcessEnv): SupporterFileStat | null;
50
+ /** Reads the token text, or null if there is nothing readable there. Never
51
+ * throws. Size is bounded by the caller's own token-length limit; a file
52
+ * larger than that fails verification rather than being read into memory
53
+ * blindly, so the read is capped here too. */
54
+ export declare function readSupporterTokenFile(env?: NodeJS.ProcessEnv, maxBytes?: number): string | null;
55
+ /** Writes the token, creating `~/.config/reposkein/` as 0700 if needed, and
56
+ * returns the path written. Throws on failure — this one is called from the
57
+ * CLI, where a failure must be reported, not swallowed. */
58
+ export declare function writeSupporterTokenFile(token: string, env?: NodeJS.ProcessEnv): string;
59
+ /** Deletes the entitlement file. Returns true if a file was removed, false if
60
+ * there was nothing to remove. */
61
+ export declare function removeSupporterTokenFile(env?: NodeJS.ProcessEnv): boolean;