@cookiemunch/cli 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -1,11 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  run
4
- } from "./chunk-R5WPMHRO.js";
4
+ } from "./chunk-Z3WVYYES.js";
5
5
 
6
6
  // src/bin.ts
7
7
  import { readFile, writeFile } from "fs/promises";
8
8
  import { spawn } from "child_process";
9
+ import { createInterface } from "readline/promises";
10
+ async function confirm(question) {
11
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
12
+ try {
13
+ return /^y(es)?$/i.test((await rl.question(question)).trim());
14
+ } finally {
15
+ rl.close();
16
+ }
17
+ }
9
18
  function openUrl(url) {
10
19
  const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
11
20
  return new Promise((resolve) => {
@@ -30,7 +39,8 @@ run(process.argv.slice(2), {
30
39
  stderr: (t) => process.stderr.write(t),
31
40
  readFile: (p) => readFile(p, "utf8"),
32
41
  writeFile: (p, d) => writeFile(p, d, "utf8"),
33
- openUrl
42
+ openUrl,
43
+ ...process.stdin.isTTY && process.stderr.isTTY ? { confirm } : {}
34
44
  }).then(
35
45
  (code) => {
36
46
  process.exitCode = code;
package/dist/bin.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\n/** The `cookiemunch` executable: real I/O around run() (see cli.ts). */\n\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { spawn } from 'node:child_process';\nimport { run } from './cli.js';\n\nfunction openUrl(url: string): Promise<void> {\n const [cmd, args] =\n process.platform === 'darwin'\n ? ['open', [url]]\n : process.platform === 'win32'\n ? ['cmd', ['/c', 'start', '', url]]\n : ['xdg-open', [url]];\n return new Promise((resolve) => {\n try {\n const child = spawn(cmd as string, args as string[], { stdio: 'ignore', detached: true });\n child.on('error', () => {\n process.stderr.write(`Could not open a browser; open the link above yourself.\\n`);\n resolve();\n });\n child.unref();\n resolve();\n } catch {\n resolve();\n }\n });\n}\n\nrun(process.argv.slice(2), {\n env: process.env,\n fetch: globalThis.fetch,\n stdout: (t) => process.stdout.write(t),\n stderr: (t) => process.stderr.write(t),\n readFile: (p) => readFile(p, 'utf8'),\n writeFile: (p, d) => writeFile(p, d, 'utf8'),\n openUrl,\n}).then(\n (code) => {\n process.exitCode = code;\n },\n (err) => {\n process.stderr.write(`cookiemunch: ${(err as Error).message ?? String(err)}\\n`);\n process.exitCode = 1;\n },\n);\n"],"mappings":";;;;;;AAGA,SAAS,UAAU,iBAAiB;AACpC,SAAS,aAAa;AAGtB,SAAS,QAAQ,KAA4B;AAC3C,QAAM,CAAC,KAAK,IAAI,IACd,QAAQ,aAAa,WACjB,CAAC,QAAQ,CAAC,GAAG,CAAC,IACd,QAAQ,aAAa,UACnB,CAAC,OAAO,CAAC,MAAM,SAAS,IAAI,GAAG,CAAC,IAChC,CAAC,YAAY,CAAC,GAAG,CAAC;AAC1B,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,QAAI;AACF,YAAM,QAAQ,MAAM,KAAe,MAAkB,EAAE,OAAO,UAAU,UAAU,KAAK,CAAC;AACxF,YAAM,GAAG,SAAS,MAAM;AACtB,gBAAQ,OAAO,MAAM;AAAA,CAA2D;AAChF,gBAAQ;AAAA,MACV,CAAC;AACD,YAAM,MAAM;AACZ,cAAQ;AAAA,IACV,QAAQ;AACN,cAAQ;AAAA,IACV;AAAA,EACF,CAAC;AACH;AAEA,IAAI,QAAQ,KAAK,MAAM,CAAC,GAAG;AAAA,EACzB,KAAK,QAAQ;AAAA,EACb,OAAO,WAAW;AAAA,EAClB,QAAQ,CAAC,MAAM,QAAQ,OAAO,MAAM,CAAC;AAAA,EACrC,QAAQ,CAAC,MAAM,QAAQ,OAAO,MAAM,CAAC;AAAA,EACrC,UAAU,CAAC,MAAM,SAAS,GAAG,MAAM;AAAA,EACnC,WAAW,CAAC,GAAG,MAAM,UAAU,GAAG,GAAG,MAAM;AAAA,EAC3C;AACF,CAAC,EAAE;AAAA,EACD,CAAC,SAAS;AACR,YAAQ,WAAW;AAAA,EACrB;AAAA,EACA,CAAC,QAAQ;AACP,YAAQ,OAAO,MAAM,gBAAiB,IAAc,WAAW,OAAO,GAAG,CAAC;AAAA,CAAI;AAC9E,YAAQ,WAAW;AAAA,EACrB;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\n/** The `cookiemunch` executable: real I/O around run() (see cli.ts). */\n\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { spawn } from 'node:child_process';\nimport { createInterface } from 'node:readline/promises';\nimport { run } from './cli.js';\n\n/** A y/N question on the terminal; only offered when both ends are a TTY. */\nasync function confirm(question: string): Promise<boolean> {\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n try {\n return /^y(es)?$/i.test((await rl.question(question)).trim());\n } finally {\n rl.close();\n }\n}\n\nfunction openUrl(url: string): Promise<void> {\n const [cmd, args] =\n process.platform === 'darwin'\n ? ['open', [url]]\n : process.platform === 'win32'\n ? ['cmd', ['/c', 'start', '', url]]\n : ['xdg-open', [url]];\n return new Promise((resolve) => {\n try {\n const child = spawn(cmd as string, args as string[], { stdio: 'ignore', detached: true });\n child.on('error', () => {\n process.stderr.write(`Could not open a browser; open the link above yourself.\\n`);\n resolve();\n });\n child.unref();\n resolve();\n } catch {\n resolve();\n }\n });\n}\n\nrun(process.argv.slice(2), {\n env: process.env,\n fetch: globalThis.fetch,\n stdout: (t) => process.stdout.write(t),\n stderr: (t) => process.stderr.write(t),\n readFile: (p) => readFile(p, 'utf8'),\n writeFile: (p, d) => writeFile(p, d, 'utf8'),\n openUrl,\n ...(process.stdin.isTTY && process.stderr.isTTY ? { confirm } : {}),\n}).then(\n (code) => {\n process.exitCode = code;\n },\n (err) => {\n process.stderr.write(`cookiemunch: ${(err as Error).message ?? String(err)}\\n`);\n process.exitCode = 1;\n },\n);\n"],"mappings":";;;;;;AAGA,SAAS,UAAU,iBAAiB;AACpC,SAAS,aAAa;AACtB,SAAS,uBAAuB;AAIhC,eAAe,QAAQ,UAAoC;AACzD,QAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC3E,MAAI;AACF,WAAO,YAAY,MAAM,MAAM,GAAG,SAAS,QAAQ,GAAG,KAAK,CAAC;AAAA,EAC9D,UAAE;AACA,OAAG,MAAM;AAAA,EACX;AACF;AAEA,SAAS,QAAQ,KAA4B;AAC3C,QAAM,CAAC,KAAK,IAAI,IACd,QAAQ,aAAa,WACjB,CAAC,QAAQ,CAAC,GAAG,CAAC,IACd,QAAQ,aAAa,UACnB,CAAC,OAAO,CAAC,MAAM,SAAS,IAAI,GAAG,CAAC,IAChC,CAAC,YAAY,CAAC,GAAG,CAAC;AAC1B,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,QAAI;AACF,YAAM,QAAQ,MAAM,KAAe,MAAkB,EAAE,OAAO,UAAU,UAAU,KAAK,CAAC;AACxF,YAAM,GAAG,SAAS,MAAM;AACtB,gBAAQ,OAAO,MAAM;AAAA,CAA2D;AAChF,gBAAQ;AAAA,MACV,CAAC;AACD,YAAM,MAAM;AACZ,cAAQ;AAAA,IACV,QAAQ;AACN,cAAQ;AAAA,IACV;AAAA,EACF,CAAC;AACH;AAEA,IAAI,QAAQ,KAAK,MAAM,CAAC,GAAG;AAAA,EACzB,KAAK,QAAQ;AAAA,EACb,OAAO,WAAW;AAAA,EAClB,QAAQ,CAAC,MAAM,QAAQ,OAAO,MAAM,CAAC;AAAA,EACrC,QAAQ,CAAC,MAAM,QAAQ,OAAO,MAAM,CAAC;AAAA,EACrC,UAAU,CAAC,MAAM,SAAS,GAAG,MAAM;AAAA,EACnC,WAAW,CAAC,GAAG,MAAM,UAAU,GAAG,GAAG,MAAM;AAAA,EAC3C;AAAA,EACA,GAAI,QAAQ,MAAM,SAAS,QAAQ,OAAO,QAAQ,EAAE,QAAQ,IAAI,CAAC;AACnE,CAAC,EAAE;AAAA,EACD,CAAC,SAAS;AACR,YAAQ,WAAW;AAAA,EACrB;AAAA,EACA,CAAC,QAAQ;AACP,YAAQ,OAAO,MAAM,gBAAiB,IAAc,WAAW,OAAO,GAAG,CAAC;AAAA,CAAI;AAC9E,YAAQ,WAAW;AAAA,EACrB;AACF;","names":[]}
@@ -0,0 +1,527 @@
1
+ // src/diff.ts
2
+ function isPlainObject(v) {
3
+ return typeof v === "object" && v !== null && !Array.isArray(v);
4
+ }
5
+ function mergeLikeServer(base, patch) {
6
+ const out = { ...base };
7
+ for (const [k, v] of Object.entries(patch)) {
8
+ const b = base[k];
9
+ if (v === null) delete out[k];
10
+ else out[k] = isPlainObject(v) ? mergeLikeServer(isPlainObject(b) ? b : {}, v) : v;
11
+ }
12
+ return out;
13
+ }
14
+ function join(path, key) {
15
+ return path ? `${path}.${key}` : key;
16
+ }
17
+ function diffJson(before, after, path = "") {
18
+ if (isPlainObject(before) && isPlainObject(after)) {
19
+ const keys = [.../* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])].sort();
20
+ return keys.flatMap((k) => {
21
+ const p = join(path, k);
22
+ if (!(k in after)) return [{ path: p, kind: "removed", from: before[k] }];
23
+ if (!(k in before)) return [{ path: p, kind: "added", to: after[k] }];
24
+ return diffJson(before[k], after[k], p);
25
+ });
26
+ }
27
+ if (Array.isArray(before) && Array.isArray(after)) {
28
+ const out = [];
29
+ const n = Math.max(before.length, after.length);
30
+ for (let i = 0; i < n; i++) {
31
+ const p = `${path}[${i}]`;
32
+ if (i >= after.length) out.push({ path: p, kind: "removed", from: before[i] });
33
+ else if (i >= before.length) out.push({ path: p, kind: "added", to: after[i] });
34
+ else out.push(...diffJson(before[i], after[i], p));
35
+ }
36
+ return out;
37
+ }
38
+ if (JSON.stringify(before) === JSON.stringify(after)) return [];
39
+ return [{ path: path || "(root)", kind: "changed", from: before, to: after }];
40
+ }
41
+ function show(v) {
42
+ const s = JSON.stringify(v);
43
+ return s === void 0 ? "undefined" : s.length > 120 ? `${s.slice(0, 117)}...` : s;
44
+ }
45
+ function formatChanges(changes) {
46
+ return changes.map(
47
+ (c) => c.kind === "added" ? `+ ${c.path}: ${show(c.to)}` : c.kind === "removed" ? `- ${c.path}: ${show(c.from)}` : `~ ${c.path}: ${show(c.from)} -> ${show(c.to)}`
48
+ ).join("\n");
49
+ }
50
+
51
+ // src/rights-page.ts
52
+ import { CookieMunchApiError } from "@cookiemunch/sdk";
53
+ var isConflict = (e) => e instanceof CookieMunchApiError && e.status === 409;
54
+ function baseFrom(flag, read, h) {
55
+ if (flag === void 0) return read ?? null;
56
+ if (typeof flag === "string" && /^\d+$/.test(flag)) return Number(flag);
57
+ if (typeof flag === "string" && /^\d{4}-\d{2}-\d{2}T/.test(flag) && Number.isFinite(Date.parse(flag))) return flag;
58
+ throw h.usage("--base must be the draft\u2019s updatedAt (epoch ms, as pull prints it, or an ISO date)");
59
+ }
60
+ function issuesOf(e) {
61
+ if (!(e instanceof CookieMunchApiError) || e.status !== 422) return null;
62
+ const body = e.body;
63
+ return body?.issues ?? body?.problems ?? [];
64
+ }
65
+ async function rightsPageCommand(sub, p, io, client, h) {
66
+ const cbid = h.requireSite();
67
+ const report = (list) => io.stderr(`${list.map((i) => ` ${i.path || "(document)"}: ${i.message}`).join("\n")}
68
+ `);
69
+ if (sub === "pull") {
70
+ const { draft, updatedAt } = await client.rightsPage.get(cbid);
71
+ const text = `${JSON.stringify(draft, null, 2)}
72
+ `;
73
+ const out = typeof p.flags.out === "string" ? p.flags.out : void 0;
74
+ if (out) {
75
+ await io.writeFile(out, text);
76
+ io.stderr(`Wrote ${cbid}'s rights page draft to ${out}
77
+ `);
78
+ } else io.stdout(text);
79
+ if (updatedAt != null) io.stderr(`To refuse a push if anyone saves the draft after this pull, push with: --base ${updatedAt}
80
+ `);
81
+ return 0;
82
+ }
83
+ if (sub === "diff" || sub === "push") {
84
+ const file = await h.readJson(p.positional[2]);
85
+ const read = await client.rightsPage.get(cbid);
86
+ const current = read.draft;
87
+ const base = baseFrom(p.flags.base, read.updatedAt, h);
88
+ let proposed2;
89
+ try {
90
+ proposed2 = (await client.rightsPage.dryRun(cbid, file, { mode: "put" })).draft;
91
+ } catch (e) {
92
+ const list = issuesOf(e);
93
+ if (!list) throw e;
94
+ io.stderr(`${p.positional[2]} is not a valid rights page:
95
+ `);
96
+ report(list);
97
+ return 1;
98
+ }
99
+ const changes = diffJson(current, proposed2);
100
+ if (!changes.length) {
101
+ io.stderr(`No changes: ${cbid}'s rights page draft already matches.
102
+ `);
103
+ return 0;
104
+ }
105
+ io.stdout(`${formatChanges(changes)}
106
+ `);
107
+ if (sub === "diff") {
108
+ io.stderr(`${changes.length} change${changes.length === 1 ? "" : "s"} (nothing saved). Apply with: cookiemunch rights-page push --site ${cbid} ${p.positional[2]}
109
+ `);
110
+ return p.flags["exit-code"] === true ? 1 : 0;
111
+ }
112
+ let saved;
113
+ try {
114
+ saved = await client.rightsPage.put(cbid, file, { baseUpdatedAt: base });
115
+ } catch (e) {
116
+ if (!isConflict(e)) throw e;
117
+ io.stderr(`Not saved: ${cbid}'s rights page draft changed since ${p.flags.base !== void 0 ? "the --base you gave" : "this diff"} (someone else saved it).
118
+ See what changed with: cookiemunch rights-page diff --site ${cbid} ${p.positional[2]}
119
+ then push again.
120
+ `);
121
+ return 1;
122
+ }
123
+ io.stderr(`Saved the draft (${changes.length} change${changes.length === 1 ? "" : "s"}). Nothing is live until: cookiemunch rights-page publish --site ${cbid}
124
+ `);
125
+ if (saved.warnings.length) {
126
+ io.stderr("It cannot be published yet:\n");
127
+ report(saved.warnings);
128
+ }
129
+ return 0;
130
+ }
131
+ if (sub === "publish") {
132
+ const base = p.flags.base !== void 0 ? baseFrom(p.flags.base, null, h) : (await client.rightsPage.get(cbid)).updatedAt ?? null;
133
+ if (p.flags.yes !== true) {
134
+ if (!io.confirm) throw h.usage("rights-page publish puts the draft live and there is no terminal to confirm in. Pass --yes to publish without a prompt.");
135
+ if (!await io.confirm(`Publish ${cbid}'s rights page draft? It goes live at once. [y/N] `)) {
136
+ io.stderr("Not published.\n");
137
+ return 1;
138
+ }
139
+ }
140
+ try {
141
+ const r = await client.rightsPage.publish(cbid, { baseUpdatedAt: base });
142
+ io.stderr(`Published version ${r.version} of ${cbid}'s rights page.
143
+ `);
144
+ return 0;
145
+ } catch (e) {
146
+ if (isConflict(e)) {
147
+ io.stderr(`Nothing was published: ${cbid}'s rights page draft changed since it was read (someone else saved it).
148
+ Review it with: cookiemunch rights-page pull --site ${cbid}
149
+ then publish again.
150
+ `);
151
+ return 1;
152
+ }
153
+ const list = issuesOf(e);
154
+ if (!list) throw e;
155
+ io.stderr("The rights page cannot be published:\n");
156
+ report(list);
157
+ return 1;
158
+ }
159
+ }
160
+ throw h.usage(`unknown rights-page command: ${sub ?? "(none)"} \u2014 use pull, diff, push or publish`);
161
+ }
162
+
163
+ // src/cli.ts
164
+ import { createCookieMunch, CookieMunchApiError as CookieMunchApiError2 } from "@cookiemunch/sdk";
165
+
166
+ // src/design-warnings.ts
167
+ function where(f) {
168
+ const w = f.where ?? {};
169
+ return [
170
+ f.layer === "banner" ? "first layer" : `${f.layer} layer`,
171
+ w.view ? `view "${w.view}"` : "",
172
+ w.variant && w.variant !== "base" ? `variant ${w.variant}` : "",
173
+ w.theme === "dark" ? "dark theme" : "",
174
+ w.breakpoint === "mobile" ? "mobile" : "",
175
+ w.language ? `language ${w.language}` : ""
176
+ ].filter(Boolean).join(", ");
177
+ }
178
+ function formatDesignWarnings(findings) {
179
+ return findings.map((f) => `${f.severity === "warning" ? "warning" : "notice "} ${f.code} ${where(f)}
180
+ ${f.detail}
181
+ fix: ${f.fix}`).join("\n");
182
+ }
183
+ function countBySeverity(findings) {
184
+ const warnings = findings.filter((f) => f.severity === "warning").length;
185
+ return { warnings, notices: findings.length - warnings };
186
+ }
187
+ function summaryLine(findings) {
188
+ const { warnings, notices } = countBySeverity(findings);
189
+ if (warnings + notices === 0) return "No design warnings: Accept all and Reject all have equal weight.";
190
+ if (warnings === 0) {
191
+ return `Design: 0 warnings, ${notices} notice${notices === 1 ? "" : "s"} \u2014 worth a look, not a finding that Accept is favoured. See https://cookiemunch.net/docs/fair-consent-buttons`;
192
+ }
193
+ return `Design: ${warnings} warning${warnings === 1 ? "" : "s"}, ${notices} notice${notices === 1 ? "" : "s"} \u2014 Accept is pushed harder than Reject. Regulators treat that as a dark pattern; make both buttons the same style. See https://cookiemunch.net/docs/fair-consent-buttons`;
194
+ }
195
+
196
+ // src/cli.ts
197
+ var DEFAULT_BASE_URL = "https://api.cookiemunch.net";
198
+ var USAGE = `cookiemunch \u2014 Cookie Munch from the command line
199
+
200
+ Usage:
201
+ cookiemunch config pull --site <cbid> [--out <file>]
202
+ Write the site's config as JSON (to stdout, or to <file>).
203
+ cookiemunch config diff --site <cbid> <file> [--replace] [--exit-code]
204
+ Show what pushing <file> would change. Nothing is sent.
205
+ cookiemunch config push --site <cbid> <file> [--replace] [--strict]
206
+ Apply <file>. Default: merged over the stored config (PATCH) \u2014 fields the file
207
+ leaves out keep their value, and a field set to null is removed (it takes its
208
+ default). --replace sends it whole (PUT): a top-level field the file leaves out
209
+ reverts to its default. Prints any design warnings the server found. --strict
210
+ checks the file first and does not push it (exit 1) when Accept all is pushed
211
+ harder than Reject all anywhere \u2014 for CI.
212
+ cookiemunch config validate --site <cbid> <file> [--replace] [--strict]
213
+ Check <file> for consent-button parity on the server, without writing: is Accept
214
+ all more prominent than Reject all (colour, size, placement, copy, custom CSS)?
215
+ Merged over the stored config like push; --replace checks it as sent. --strict
216
+ exits 1 when there is any warning (notices never fail).
217
+ cookiemunch preview --site <cbid> [--url <page>] [--region <eu|de|us-ca\u2026>] [--lang <de\u2026>]
218
+ [--ttl <seconds>] [--open]
219
+ Print (and with --open, open) a link showing the banner as a visitor in that
220
+ region and language sees it. Works in headless browsers; nothing is recorded.
221
+ cookiemunch executors test <id> --email <email> [--operation <locate|export|erase>]
222
+ Try a connected system against one person without filing a request.
223
+ --operation defaults to locate (lookup is accepted as another name for it).
224
+ cookiemunch executors webhook-test <id> --headers-file <json> --body-file <file>
225
+ Would this webhook delivery verify? Sends the headers (a JSON object) and the
226
+ body file byte for byte to the receiver's check; nothing is acted on. Prints
227
+ { verified, stage, reason } and exits 1 when it does not verify.
228
+ cookiemunch executors deliveries <id>
229
+ The last 50 deliveries to that connection's webhook, each with its reason.
230
+ cookiemunch rights-page pull --site <cbid> [--out <file>]
231
+ Write the site's rights page draft as JSON, and print its updatedAt for --base.
232
+ cookiemunch rights-page diff --site <cbid> <file> [--exit-code]
233
+ Validate <file> on the server (dry run) and show what pushing it would change.
234
+ cookiemunch rights-page push --site <cbid> <file> [--base <updatedAt>]
235
+ Replace the draft with <file>. Nothing is live until publish. Refused (exit 1)
236
+ if the draft changed since the diff, or since --base (the updatedAt pull printed).
237
+ cookiemunch rights-page publish --site <cbid> [--yes] [--base <updatedAt>]
238
+ Publish the draft (it goes live at once). Asks first; --yes skips the question
239
+ and is required when there is no terminal to ask in. Prints why when the page
240
+ cannot go live. Refused (exit 1) if the draft changed after it was read (or
241
+ since --base); --yes never overrides that.
242
+
243
+ Environment:
244
+ COOKIEMUNCH_API_KEY an API key (fck_\u2026). Required.
245
+ COOKIEMUNCH_BASE_URL the API origin. Default ${DEFAULT_BASE_URL}.
246
+ COOKIEMUNCH_SITE default for --site.
247
+ `;
248
+ var UsageError = class extends Error {
249
+ };
250
+ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["replace", "exit-code", "open", "help", "json", "yes", "strict"]);
251
+ function parseArgs(argv) {
252
+ const positional = [];
253
+ const flags = {};
254
+ for (let i = 0; i < argv.length; i++) {
255
+ const a = argv[i];
256
+ if (a === "-h") {
257
+ flags.help = true;
258
+ } else if (a.startsWith("--")) {
259
+ const eq = a.indexOf("=");
260
+ const name = eq > 0 ? a.slice(2, eq) : a.slice(2);
261
+ if (eq > 0) flags[name] = a.slice(eq + 1);
262
+ else if (BOOLEAN_FLAGS.has(name)) flags[name] = true;
263
+ else {
264
+ const v = argv[i + 1];
265
+ if (v === void 0 || v.startsWith("--")) throw new UsageError(`--${name} needs a value`);
266
+ flags[name] = v;
267
+ i++;
268
+ }
269
+ } else {
270
+ positional.push(a);
271
+ }
272
+ }
273
+ return { positional, flags };
274
+ }
275
+ function str(flags, name) {
276
+ const v = flags[name];
277
+ return typeof v === "string" ? v : void 0;
278
+ }
279
+ function requireSite(p, io) {
280
+ const site = str(p.flags, "site") ?? io.env.COOKIEMUNCH_SITE;
281
+ if (!site) throw new UsageError("--site <cbid> is required (or set COOKIEMUNCH_SITE)");
282
+ return site;
283
+ }
284
+ async function readConfigFile(io, path) {
285
+ if (!path) throw new UsageError("a config file is required");
286
+ let text;
287
+ try {
288
+ text = await io.readFile(path);
289
+ } catch (e) {
290
+ throw new UsageError(`cannot read ${path}: ${e.message}`);
291
+ }
292
+ let parsed;
293
+ try {
294
+ parsed = JSON.parse(text);
295
+ } catch (e) {
296
+ throw new UsageError(`${path} is not valid JSON: ${e.message}`);
297
+ }
298
+ if (!isPlainObject(parsed)) throw new UsageError(`${path} must hold a JSON object`);
299
+ return parsed;
300
+ }
301
+ function proposed(current, file, replace, cbid) {
302
+ return replace ? { ...file, cbid } : mergeLikeServer(current, file);
303
+ }
304
+ async function rawRequest(io, base, key, method, path, body) {
305
+ const res = await io.fetch(`${base.replace(/\/+$/, "")}/v1${path}`, {
306
+ method,
307
+ headers: { Authorization: `Bearer ${key}`, ...body !== void 0 ? { "Content-Type": "application/json" } : {} },
308
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {}
309
+ });
310
+ const text = await res.text();
311
+ let data = text;
312
+ try {
313
+ data = text ? JSON.parse(text) : void 0;
314
+ } catch {
315
+ }
316
+ if (!res.ok) {
317
+ const err = isPlainObject(data) ? data : {};
318
+ throw new CookieMunchApiError2(
319
+ res.status,
320
+ typeof err.error === "string" ? err.error : `request failed with status ${res.status}`,
321
+ typeof err.code === "string" ? err.code : void 0
322
+ );
323
+ }
324
+ return data;
325
+ }
326
+ async function configCommand(sub, p, io, client) {
327
+ const cbid = requireSite(p, io);
328
+ if (sub === "pull") {
329
+ const cfg = await client.sites.getConfig(cbid);
330
+ const text = `${JSON.stringify(cfg, null, 2)}
331
+ `;
332
+ const out = str(p.flags, "out");
333
+ if (out) {
334
+ await io.writeFile(out, text);
335
+ io.stderr(`Wrote ${cbid}'s config to ${out}
336
+ `);
337
+ } else {
338
+ io.stdout(text);
339
+ }
340
+ return 0;
341
+ }
342
+ if (sub === "validate") {
343
+ const file = await readConfigFile(io, p.positional[2]);
344
+ const r = await client.sites.lintConfig(cbid, file, p.flags.replace === true ? { replace: true } : {});
345
+ if (r.designWarnings.length) io.stdout(`${formatDesignWarnings(r.designWarnings)}
346
+ `);
347
+ io.stderr(`${summaryLine(r.designWarnings)}
348
+ `);
349
+ return p.flags.strict === true && countBySeverity(r.designWarnings).warnings > 0 ? 1 : 0;
350
+ }
351
+ if (sub === "diff" || sub === "push") {
352
+ const file = await readConfigFile(io, p.positional[2]);
353
+ const replace = p.flags.replace === true;
354
+ if (sub === "push" && p.flags.strict === true) {
355
+ const lint = await client.sites.lintConfig(cbid, file, replace ? { replace: true } : {});
356
+ if (countBySeverity(lint.designWarnings).warnings > 0) {
357
+ io.stderr(`${formatDesignWarnings(lint.designWarnings)}
358
+ ${summaryLine(lint.designWarnings)}
359
+ not pushed (--strict).
360
+ `);
361
+ return 1;
362
+ }
363
+ }
364
+ const current = await client.sites.getConfig(cbid);
365
+ const changes = diffJson(current, proposed(current, file, replace, cbid));
366
+ if (changes.length === 0) {
367
+ io.stderr(`No changes: ${cbid}'s config already matches.
368
+ `);
369
+ return 0;
370
+ }
371
+ io.stdout(`${formatChanges(changes)}
372
+ `);
373
+ if (sub === "diff") {
374
+ io.stderr(`${changes.length} change${changes.length === 1 ? "" : "s"} (nothing sent). Apply with: cookiemunch config push --site ${cbid} ${p.positional[2]}${replace ? " --replace" : ""}
375
+ `);
376
+ return p.flags["exit-code"] === true ? 1 : 0;
377
+ }
378
+ const saved = replace ? await client.sites.putConfig(cbid, { ...file, cbid }) : await client.sites.patchConfig(cbid, file);
379
+ io.stderr(`Applied ${changes.length} change${changes.length === 1 ? "" : "s"} to ${cbid}.
380
+ `);
381
+ const found = Array.isArray(saved?.designWarnings) ? saved.designWarnings : [];
382
+ if (found.length) io.stderr(`${formatDesignWarnings(found)}
383
+ ${summaryLine(found)}
384
+ `);
385
+ return 0;
386
+ }
387
+ throw new UsageError(`unknown config command: ${sub ?? "(none)"} \u2014 use pull, diff, push or validate`);
388
+ }
389
+ async function previewCommand(p, io, client) {
390
+ const cbid = requireSite(p, io);
391
+ const ttl = str(p.flags, "ttl");
392
+ const ttlSeconds = ttl === void 0 ? void 0 : Number(ttl);
393
+ if (ttlSeconds !== void 0 && (!Number.isInteger(ttlSeconds) || ttlSeconds < 1 || ttlSeconds > 3600)) {
394
+ throw new UsageError("--ttl must be a whole number of seconds from 1 to 3600");
395
+ }
396
+ const r = await client.sites.previewToken(cbid, {
397
+ ...str(p.flags, "region") ? { region: str(p.flags, "region") } : {},
398
+ ...str(p.flags, "lang") ? { lang: str(p.flags, "lang") } : {},
399
+ ...str(p.flags, "url") ? { url: str(p.flags, "url") } : {},
400
+ ...ttlSeconds !== void 0 ? { ttlSeconds } : {}
401
+ });
402
+ if (p.flags.json === true) {
403
+ io.stdout(`${JSON.stringify(r, null, 2)}
404
+ `);
405
+ } else if (r.previewUrl) {
406
+ io.stdout(`${r.previewUrl}
407
+ `);
408
+ } else {
409
+ io.stdout(`${r.token}
410
+ `);
411
+ io.stderr("Add ?cm_preview=<token>&cm_region=..&cm_lang=.. to any page of the site.\n");
412
+ }
413
+ io.stderr(`Valid until ${new Date(r.expiresAt).toISOString()}.
414
+ `);
415
+ if (p.flags.open === true && r.previewUrl) await io.openUrl(r.previewUrl);
416
+ return 0;
417
+ }
418
+ async function executorsCommand(sub, p, io, base, key, client) {
419
+ if (sub === "webhook-test") {
420
+ const id2 = p.positional[2];
421
+ if (!id2) throw new UsageError("executors webhook-test <id> needs the connection id");
422
+ const headersFile = str(p.flags, "headers-file");
423
+ const bodyFile = str(p.flags, "body-file");
424
+ if (!headersFile || !bodyFile) throw new UsageError("--headers-file and --body-file are both required");
425
+ let headers;
426
+ try {
427
+ headers = JSON.parse(await io.readFile(headersFile));
428
+ } catch {
429
+ throw new UsageError(`${headersFile} is not a JSON object of header name to value`);
430
+ }
431
+ if (!isPlainObject(headers)) throw new UsageError(`${headersFile} is not a JSON object of header name to value`);
432
+ const body = await io.readFile(bodyFile);
433
+ const r = await client.fulfillment.testExecutorWebhook(id2, { headers, body });
434
+ io.stdout(`${JSON.stringify(r, null, 2)}
435
+ `);
436
+ return r.verified ? 0 : 1;
437
+ }
438
+ if (sub === "deliveries") {
439
+ const id2 = p.positional[2];
440
+ if (!id2) throw new UsageError("executors deliveries <id> needs the connection id");
441
+ io.stdout(`${JSON.stringify(await client.fulfillment.executorWebhookDeliveries(id2), null, 2)}
442
+ `);
443
+ return 0;
444
+ }
445
+ if (sub !== "test") throw new UsageError(`unknown executors command: ${sub ?? "(none)"} \u2014 use test, webhook-test or deliveries`);
446
+ const id = p.positional[2];
447
+ if (!id) throw new UsageError("executors test <id> needs the connection id");
448
+ const email = str(p.flags, "email");
449
+ if (!email) throw new UsageError("--email is required");
450
+ const asked = str(p.flags, "operation") ?? "locate";
451
+ const operation = asked === "lookup" ? "locate" : asked;
452
+ try {
453
+ const r = await rawRequest(io, base, key, "POST", `/dsar/executors/${encodeURIComponent(id)}/test`, { email, operation });
454
+ io.stdout(`${JSON.stringify(r, null, 2)}
455
+ `);
456
+ return 0;
457
+ } catch (e) {
458
+ if (e instanceof CookieMunchApiError2 && e.status === 404 && e.message === "not found") {
459
+ io.stderr("This server does not offer connection tests yet (POST /v1/dsar/executors/{id}/test).\n");
460
+ return 1;
461
+ }
462
+ throw e;
463
+ }
464
+ }
465
+ async function run(argv, io) {
466
+ let p;
467
+ try {
468
+ p = parseArgs(argv);
469
+ } catch (e) {
470
+ io.stderr(`${e.message}
471
+
472
+ ${USAGE}`);
473
+ return 2;
474
+ }
475
+ const [cmd, sub] = p.positional;
476
+ if (!cmd || p.flags.help === true || cmd === "help") {
477
+ io.stdout(USAGE);
478
+ return cmd || p.flags.help === true ? 0 : 2;
479
+ }
480
+ const key = io.env.COOKIEMUNCH_API_KEY;
481
+ if (!key) {
482
+ io.stderr("COOKIEMUNCH_API_KEY is not set. Create a key under Developers in the dashboard.\n");
483
+ return 2;
484
+ }
485
+ const base = io.env.COOKIEMUNCH_BASE_URL || DEFAULT_BASE_URL;
486
+ const client = createCookieMunch({ apiKey: key, baseUrl: base, fetch: io.fetch });
487
+ try {
488
+ if (cmd === "config") return await configCommand(sub, p, io, client);
489
+ if (cmd === "preview") return await previewCommand(p, io, client);
490
+ if (cmd === "executors") return await executorsCommand(sub, p, io, base, key, client);
491
+ if (cmd === "rights-page") {
492
+ return await rightsPageCommand(sub, p, io, client, {
493
+ requireSite: () => requireSite(p, io),
494
+ readJson: (path) => readConfigFile(io, path),
495
+ usage: (m) => new UsageError(m)
496
+ });
497
+ }
498
+ throw new UsageError(`unknown command: ${cmd}`);
499
+ } catch (e) {
500
+ if (e instanceof UsageError) {
501
+ io.stderr(`${e.message}
502
+
503
+ ${USAGE}`);
504
+ return 2;
505
+ }
506
+ if (e instanceof CookieMunchApiError2) {
507
+ io.stderr(`API error ${e.status}${e.code ? ` (${e.code})` : ""}: ${e.message}
508
+ `);
509
+ return 1;
510
+ }
511
+ io.stderr(`${e.message ?? String(e)}
512
+ `);
513
+ return 1;
514
+ }
515
+ }
516
+
517
+ export {
518
+ mergeLikeServer,
519
+ diffJson,
520
+ formatChanges,
521
+ rightsPageCommand,
522
+ DEFAULT_BASE_URL,
523
+ USAGE,
524
+ parseArgs,
525
+ run
526
+ };
527
+ //# sourceMappingURL=chunk-Z3WVYYES.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/diff.ts","../src/rights-page.ts","../src/cli.ts","../src/design-warnings.ts"],"sourcesContent":["/**\n * What a config push would change, computed on this side of the wire.\n *\n * `mergeLikeServer` mirrors the server's `PATCH /v1/sites/{cbid}/config` semantics exactly\n * (JSON Merge Patch, RFC 7386 — packages/server/src/site-config-write.ts mergePatch): the\n * patch wins, nested plain objects merge, arrays and primitives replace wholesale, and a\n * `null` removes the key. A diff computed against any other merge would describe a change\n * the server is not going to make.\n */\n\nexport type Json = null | boolean | number | string | Json[] | { [k: string]: Json };\n\nexport interface Change {\n /** Dotted path; array indices in brackets: `banner.content.title`, `geoRules[0].mode`. */\n path: string;\n kind: 'added' | 'removed' | 'changed';\n from?: Json;\n to?: Json;\n}\n\nexport function isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\nexport function mergeLikeServer(base: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = { ...base };\n for (const [k, v] of Object.entries(patch)) {\n const b = base[k];\n if (v === null) delete out[k];\n else out[k] = isPlainObject(v) ? mergeLikeServer(isPlainObject(b) ? b : {}, v) : v;\n }\n return out;\n}\n\nfunction join(path: string, key: string): string {\n return path ? `${path}.${key}` : key;\n}\n\n/** Every leaf that differs between `before` and `after`, in a stable order. */\nexport function diffJson(before: unknown, after: unknown, path = ''): Change[] {\n if (isPlainObject(before) && isPlainObject(after)) {\n const keys = [...new Set([...Object.keys(before), ...Object.keys(after)])].sort();\n return keys.flatMap((k) => {\n const p = join(path, k);\n if (!(k in after)) return [{ path: p, kind: 'removed' as const, from: before[k] as Json }];\n if (!(k in before)) return [{ path: p, kind: 'added' as const, to: after[k] as Json }];\n return diffJson(before[k], after[k], p);\n });\n }\n if (Array.isArray(before) && Array.isArray(after)) {\n const out: Change[] = [];\n const n = Math.max(before.length, after.length);\n for (let i = 0; i < n; i++) {\n const p = `${path}[${i}]`;\n if (i >= after.length) out.push({ path: p, kind: 'removed', from: before[i] as Json });\n else if (i >= before.length) out.push({ path: p, kind: 'added', to: after[i] as Json });\n else out.push(...diffJson(before[i], after[i], p));\n }\n return out;\n }\n if (JSON.stringify(before) === JSON.stringify(after)) return [];\n return [{ path: path || '(root)', kind: 'changed', from: before as Json, to: after as Json }];\n}\n\nfunction show(v: Json | undefined): string {\n const s = JSON.stringify(v);\n return s === undefined ? 'undefined' : s.length > 120 ? `${s.slice(0, 117)}...` : s;\n}\n\n/** A unified-diff-ish rendering: one line per change. */\nexport function formatChanges(changes: Change[]): string {\n return changes\n .map((c) =>\n c.kind === 'added'\n ? `+ ${c.path}: ${show(c.to)}`\n : c.kind === 'removed'\n ? `- ${c.path}: ${show(c.from)}`\n : `~ ${c.path}: ${show(c.from)} -> ${show(c.to)}`,\n )\n .join('\\n');\n}\n","/**\n * `cookiemunch rights-page pull|diff|push|publish` — the hosted rights page as code.\n *\n * Unlike `config diff`, `diff` asks the server: the rights page has a real dry run, so the\n * diff is against what the server would store (normalised and sanitised), and an invalid\n * file fails with the server's issues before anything is written.\n */\nimport { CookieMunchApiError, type CookieMunchClient } from '@cookiemunch/sdk';\nimport type { CliIo } from './cli.js';\nimport { diffJson, formatChanges } from './diff.js';\n\ninterface Helpers { requireSite(): string; readJson(path: string | undefined): Promise<Record<string, unknown>>; usage(msg: string): Error }\ntype Issue = { path: string; message: string };\n\nconst isConflict = (e: unknown) => e instanceof CookieMunchApiError && e.status === 409;\n\n/** `--base <updatedAt>` (epoch ms or an ISO date), else the updatedAt this command just read. */\nfunction baseFrom(flag: string | true | undefined, read: number | null | undefined, h: Helpers): number | string | null {\n if (flag === undefined) return read ?? null;\n if (typeof flag === 'string' && /^\\d+$/.test(flag)) return Number(flag);\n if (typeof flag === 'string' && /^\\d{4}-\\d{2}-\\d{2}T/.test(flag) && Number.isFinite(Date.parse(flag))) return flag;\n throw h.usage('--base must be the draft’s updatedAt (epoch ms, as pull prints it, or an ISO date)');\n}\n\nfunction issuesOf(e: unknown): Issue[] | null {\n if (!(e instanceof CookieMunchApiError) || e.status !== 422) return null;\n const body = e.body as { issues?: Issue[]; problems?: Issue[] } | undefined;\n return body?.issues ?? body?.problems ?? [];\n}\n\nexport async function rightsPageCommand(\n sub: string | undefined,\n p: { positional: string[]; flags: Record<string, string | true> },\n io: CliIo,\n client: CookieMunchClient,\n h: Helpers,\n): Promise<number> {\n const cbid = h.requireSite();\n const report = (list: Issue[]) => io.stderr(`${list.map((i) => ` ${i.path || '(document)'}: ${i.message}`).join('\\n')}\\n`);\n if (sub === 'pull') {\n const { draft, updatedAt } = await client.rightsPage.get(cbid);\n const text = `${JSON.stringify(draft, null, 2)}\\n`;\n const out = typeof p.flags.out === 'string' ? p.flags.out : undefined;\n if (out) {\n await io.writeFile(out, text);\n io.stderr(`Wrote ${cbid}'s rights page draft to ${out}\\n`);\n } else io.stdout(text);\n if (updatedAt != null) io.stderr(`To refuse a push if anyone saves the draft after this pull, push with: --base ${updatedAt}\\n`);\n return 0;\n }\n if (sub === 'diff' || sub === 'push') {\n const file = await h.readJson(p.positional[2]);\n const read = await client.rightsPage.get(cbid);\n const current = read.draft;\n // The base the diff is against (or the one pull printed): a save after it is a conflict, never lost.\n const base = baseFrom(p.flags.base, read.updatedAt, h);\n let proposed: unknown;\n try {\n proposed = (await client.rightsPage.dryRun(cbid, file, { mode: 'put' })).draft;\n } catch (e) {\n const list = issuesOf(e);\n if (!list) throw e;\n io.stderr(`${p.positional[2]} is not a valid rights page:\\n`);\n report(list);\n return 1;\n }\n const changes = diffJson(current, proposed);\n if (!changes.length) { io.stderr(`No changes: ${cbid}'s rights page draft already matches.\\n`); return 0; }\n io.stdout(`${formatChanges(changes)}\\n`);\n if (sub === 'diff') {\n io.stderr(`${changes.length} change${changes.length === 1 ? '' : 's'} (nothing saved). Apply with: cookiemunch rights-page push --site ${cbid} ${p.positional[2]}\\n`);\n return p.flags['exit-code'] === true ? 1 : 0;\n }\n let saved;\n try {\n saved = await client.rightsPage.put(cbid, file as never, { baseUpdatedAt: base });\n } catch (e) {\n if (!isConflict(e)) throw e;\n io.stderr(`Not saved: ${cbid}'s rights page draft changed since ${p.flags.base !== undefined ? 'the --base you gave' : 'this diff'} (someone else saved it).\\n`\n + `See what changed with: cookiemunch rights-page diff --site ${cbid} ${p.positional[2]}\\nthen push again.\\n`);\n return 1;\n }\n io.stderr(`Saved the draft (${changes.length} change${changes.length === 1 ? '' : 's'}). Nothing is live until: cookiemunch rights-page publish --site ${cbid}\\n`);\n if (saved.warnings.length) { io.stderr('It cannot be published yet:\\n'); report(saved.warnings); }\n return 0;\n }\n if (sub === 'publish') {\n // Read before asking, so the draft that goes live is the one that existed when you said yes.\n const base = p.flags.base !== undefined ? baseFrom(p.flags.base, null, h) : (await client.rightsPage.get(cbid)).updatedAt ?? null;\n if (p.flags.yes !== true) {\n if (!io.confirm) throw h.usage('rights-page publish puts the draft live and there is no terminal to confirm in. Pass --yes to publish without a prompt.');\n if (!(await io.confirm(`Publish ${cbid}'s rights page draft? It goes live at once. [y/N] `))) {\n io.stderr('Not published.\\n');\n return 1;\n }\n }\n try {\n const r = await client.rightsPage.publish(cbid, { baseUpdatedAt: base });\n io.stderr(`Published version ${r.version} of ${cbid}'s rights page.\\n`);\n return 0;\n } catch (e) {\n if (isConflict(e)) {\n io.stderr(`Nothing was published: ${cbid}'s rights page draft changed since it was read (someone else saved it).\\n`\n + `Review it with: cookiemunch rights-page pull --site ${cbid}\\nthen publish again.\\n`);\n return 1;\n }\n const list = issuesOf(e);\n if (!list) throw e;\n io.stderr('The rights page cannot be published:\\n');\n report(list);\n return 1;\n }\n }\n throw h.usage(`unknown rights-page command: ${sub ?? '(none)'} — use pull, diff, push or publish`);\n}\n","/**\n * `cookiemunch` — the Developer API from a terminal or a CI job.\n *\n * cookiemunch config pull --site <cbid> [--out file]\n * cookiemunch config diff --site <cbid> <file> [--replace] [--exit-code]\n * cookiemunch config push --site <cbid> <file> [--replace] [--strict]\n * cookiemunch config validate --site <cbid> <file> [--replace] [--strict]\n * cookiemunch preview --site <cbid> [--url <url>] [--region eu] [--lang de] [--ttl 900] [--open]\n * cookiemunch executors test <id> --email <email> [--operation locate]\n * cookiemunch executors webhook-test <id> --headers-file <json> --body-file <file>\n * cookiemunch executors deliveries <id>\n * cookiemunch rights-page pull|diff|push|publish --site <cbid> [<file>] [--base <updatedAt>] [--yes]\n *\n * Everything goes through the typed SDK (@cookiemunch/sdk) except where the SDK has no\n * method yet; those calls go through `rawRequest` with the same key and base URL. All I/O\n * is injected, so the whole CLI is testable with a mocked fetch.\n */\n\nimport { createCookieMunch, CookieMunchApiError, type CookieMunchClient } from '@cookiemunch/sdk';\nimport { diffJson, formatChanges, isPlainObject, mergeLikeServer } from './diff.js';\nimport { rightsPageCommand } from './rights-page.js';\nimport { countBySeverity, formatDesignWarnings, summaryLine } from './design-warnings.js';\n\nexport const DEFAULT_BASE_URL = 'https://api.cookiemunch.net';\n\nexport interface CliIo {\n env: Record<string, string | undefined>;\n fetch: typeof fetch;\n stdout: (text: string) => void;\n stderr: (text: string) => void;\n readFile: (path: string) => Promise<string>;\n writeFile: (path: string, data: string) => Promise<void>;\n /** Open a URL in the user's browser. */\n openUrl: (url: string) => Promise<void>;\n /**\n * Ask a yes/no question. Present only when a person can answer (an interactive terminal);\n * absent, a command that needs confirmation refuses unless given --yes.\n */\n confirm?: (question: string) => Promise<boolean>;\n}\n\nexport const USAGE = `cookiemunch — Cookie Munch from the command line\n\nUsage:\n cookiemunch config pull --site <cbid> [--out <file>]\n Write the site's config as JSON (to stdout, or to <file>).\n cookiemunch config diff --site <cbid> <file> [--replace] [--exit-code]\n Show what pushing <file> would change. Nothing is sent.\n cookiemunch config push --site <cbid> <file> [--replace] [--strict]\n Apply <file>. Default: merged over the stored config (PATCH) — fields the file\n leaves out keep their value, and a field set to null is removed (it takes its\n default). --replace sends it whole (PUT): a top-level field the file leaves out\n reverts to its default. Prints any design warnings the server found. --strict\n checks the file first and does not push it (exit 1) when Accept all is pushed\n harder than Reject all anywhere — for CI.\n cookiemunch config validate --site <cbid> <file> [--replace] [--strict]\n Check <file> for consent-button parity on the server, without writing: is Accept\n all more prominent than Reject all (colour, size, placement, copy, custom CSS)?\n Merged over the stored config like push; --replace checks it as sent. --strict\n exits 1 when there is any warning (notices never fail).\n cookiemunch preview --site <cbid> [--url <page>] [--region <eu|de|us-ca…>] [--lang <de…>]\n [--ttl <seconds>] [--open]\n Print (and with --open, open) a link showing the banner as a visitor in that\n region and language sees it. Works in headless browsers; nothing is recorded.\n cookiemunch executors test <id> --email <email> [--operation <locate|export|erase>]\n Try a connected system against one person without filing a request.\n --operation defaults to locate (lookup is accepted as another name for it).\n cookiemunch executors webhook-test <id> --headers-file <json> --body-file <file>\n Would this webhook delivery verify? Sends the headers (a JSON object) and the\n body file byte for byte to the receiver's check; nothing is acted on. Prints\n { verified, stage, reason } and exits 1 when it does not verify.\n cookiemunch executors deliveries <id>\n The last 50 deliveries to that connection's webhook, each with its reason.\n cookiemunch rights-page pull --site <cbid> [--out <file>]\n Write the site's rights page draft as JSON, and print its updatedAt for --base.\n cookiemunch rights-page diff --site <cbid> <file> [--exit-code]\n Validate <file> on the server (dry run) and show what pushing it would change.\n cookiemunch rights-page push --site <cbid> <file> [--base <updatedAt>]\n Replace the draft with <file>. Nothing is live until publish. Refused (exit 1)\n if the draft changed since the diff, or since --base (the updatedAt pull printed).\n cookiemunch rights-page publish --site <cbid> [--yes] [--base <updatedAt>]\n Publish the draft (it goes live at once). Asks first; --yes skips the question\n and is required when there is no terminal to ask in. Prints why when the page\n cannot go live. Refused (exit 1) if the draft changed after it was read (or\n since --base); --yes never overrides that.\n\nEnvironment:\n COOKIEMUNCH_API_KEY an API key (fck_…). Required.\n COOKIEMUNCH_BASE_URL the API origin. Default ${DEFAULT_BASE_URL}.\n COOKIEMUNCH_SITE default for --site.\n`;\n\n/** A usage mistake: exit code 2. */\nclass UsageError extends Error {}\n\ninterface Parsed {\n positional: string[];\n flags: Record<string, string | true>;\n}\n\nconst BOOLEAN_FLAGS = new Set(['replace', 'exit-code', 'open', 'help', 'json', 'yes', 'strict']);\n\nexport function parseArgs(argv: string[]): Parsed {\n const positional: string[] = [];\n const flags: Record<string, string | true> = {};\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i]!;\n if (a === '-h') {\n flags.help = true;\n } else if (a.startsWith('--')) {\n const eq = a.indexOf('=');\n const name = eq > 0 ? a.slice(2, eq) : a.slice(2);\n if (eq > 0) flags[name] = a.slice(eq + 1);\n else if (BOOLEAN_FLAGS.has(name)) flags[name] = true;\n else {\n const v = argv[i + 1];\n if (v === undefined || v.startsWith('--')) throw new UsageError(`--${name} needs a value`);\n flags[name] = v;\n i++;\n }\n } else {\n positional.push(a);\n }\n }\n return { positional, flags };\n}\n\nfunction str(flags: Parsed['flags'], name: string): string | undefined {\n const v = flags[name];\n return typeof v === 'string' ? v : undefined;\n}\n\nfunction requireSite(p: Parsed, io: CliIo): string {\n const site = str(p.flags, 'site') ?? io.env.COOKIEMUNCH_SITE;\n if (!site) throw new UsageError('--site <cbid> is required (or set COOKIEMUNCH_SITE)');\n return site;\n}\n\nasync function readConfigFile(io: CliIo, path: string | undefined): Promise<Record<string, unknown>> {\n if (!path) throw new UsageError('a config file is required');\n let text: string;\n try {\n text = await io.readFile(path);\n } catch (e) {\n throw new UsageError(`cannot read ${path}: ${(e as Error).message}`);\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch (e) {\n throw new UsageError(`${path} is not valid JSON: ${(e as Error).message}`);\n }\n if (!isPlainObject(parsed)) throw new UsageError(`${path} must hold a JSON object`);\n return parsed;\n}\n\n/** What the stored config would become, per push mode. */\nfunction proposed(current: Record<string, unknown>, file: Record<string, unknown>, replace: boolean, cbid: string) {\n return replace ? { ...file, cbid } : mergeLikeServer(current, file);\n}\n\nasync function rawRequest(io: CliIo, base: string, key: string, method: string, path: string, body?: unknown): Promise<unknown> {\n const res = await io.fetch(`${base.replace(/\\/+$/, '')}/v1${path}`, {\n method,\n headers: { Authorization: `Bearer ${key}`, ...(body !== undefined ? { 'Content-Type': 'application/json' } : {}) },\n ...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n });\n const text = await res.text();\n let data: unknown = text;\n try {\n data = text ? JSON.parse(text) : undefined;\n } catch {\n /* keep the text */\n }\n if (!res.ok) {\n const err = isPlainObject(data) ? data : {};\n throw new CookieMunchApiError(\n res.status,\n typeof err.error === 'string' ? err.error : `request failed with status ${res.status}`,\n typeof err.code === 'string' ? err.code : undefined,\n );\n }\n return data;\n}\n\nasync function configCommand(sub: string | undefined, p: Parsed, io: CliIo, client: CookieMunchClient): Promise<number> {\n const cbid = requireSite(p, io);\n if (sub === 'pull') {\n const cfg = await client.sites.getConfig(cbid);\n const text = `${JSON.stringify(cfg, null, 2)}\\n`;\n const out = str(p.flags, 'out');\n if (out) {\n await io.writeFile(out, text);\n io.stderr(`Wrote ${cbid}'s config to ${out}\\n`);\n } else {\n io.stdout(text);\n }\n return 0;\n }\n if (sub === 'validate') {\n const file = await readConfigFile(io, p.positional[2]);\n const r = await client.sites.lintConfig(cbid, file as never, p.flags.replace === true ? { replace: true } : {});\n if (r.designWarnings.length) io.stdout(`${formatDesignWarnings(r.designWarnings)}\\n`);\n io.stderr(`${summaryLine(r.designWarnings)}\\n`);\n return p.flags.strict === true && countBySeverity(r.designWarnings).warnings > 0 ? 1 : 0;\n }\n if (sub === 'diff' || sub === 'push') {\n const file = await readConfigFile(io, p.positional[2]);\n const replace = p.flags.replace === true;\n // --strict (push): check first, and do not write a config that fails it.\n if (sub === 'push' && p.flags.strict === true) {\n const lint = await client.sites.lintConfig(cbid, file as never, replace ? { replace: true } : {});\n if (countBySeverity(lint.designWarnings).warnings > 0) {\n io.stderr(`${formatDesignWarnings(lint.designWarnings)}\\n${summaryLine(lint.designWarnings)}\\nnot pushed (--strict).\\n`);\n return 1;\n }\n }\n const current = (await client.sites.getConfig(cbid)) as unknown as Record<string, unknown>;\n // Computed here: the server has no dry-run for config writes yet, and the only safe\n // way to ask \"what would this change\" of an API that might ignore a dryRun flag is\n // not to send the write at all.\n const changes = diffJson(current, proposed(current, file, replace, cbid));\n if (changes.length === 0) {\n io.stderr(`No changes: ${cbid}'s config already matches.\\n`);\n return 0;\n }\n io.stdout(`${formatChanges(changes)}\\n`);\n if (sub === 'diff') {\n io.stderr(`${changes.length} change${changes.length === 1 ? '' : 's'} (nothing sent). Apply with: cookiemunch config push --site ${cbid} ${p.positional[2]}${replace ? ' --replace' : ''}\\n`);\n return p.flags['exit-code'] === true ? 1 : 0;\n }\n const saved = (replace\n ? await client.sites.putConfig(cbid, { ...file, cbid } as never)\n : await client.sites.patchConfig(cbid, file as never)) as { designWarnings?: unknown };\n io.stderr(`Applied ${changes.length} change${changes.length === 1 ? '' : 's'} to ${cbid}.\\n`);\n const found = Array.isArray(saved?.designWarnings) ? (saved.designWarnings as Parameters<typeof formatDesignWarnings>[0]) : [];\n if (found.length) io.stderr(`${formatDesignWarnings(found)}\\n${summaryLine(found)}\\n`);\n return 0;\n }\n throw new UsageError(`unknown config command: ${sub ?? '(none)'} — use pull, diff, push or validate`);\n}\n\nasync function previewCommand(p: Parsed, io: CliIo, client: CookieMunchClient): Promise<number> {\n const cbid = requireSite(p, io);\n const ttl = str(p.flags, 'ttl');\n const ttlSeconds = ttl === undefined ? undefined : Number(ttl);\n if (ttlSeconds !== undefined && (!Number.isInteger(ttlSeconds) || ttlSeconds < 1 || ttlSeconds > 3600)) {\n throw new UsageError('--ttl must be a whole number of seconds from 1 to 3600');\n }\n const r = await client.sites.previewToken(cbid, {\n ...(str(p.flags, 'region') ? { region: str(p.flags, 'region')! } : {}),\n ...(str(p.flags, 'lang') ? { lang: str(p.flags, 'lang')! } : {}),\n ...(str(p.flags, 'url') ? { url: str(p.flags, 'url')! } : {}),\n ...(ttlSeconds !== undefined ? { ttlSeconds } : {}),\n });\n if (p.flags.json === true) {\n io.stdout(`${JSON.stringify(r, null, 2)}\\n`);\n } else if (r.previewUrl) {\n io.stdout(`${r.previewUrl}\\n`);\n } else {\n // No page to put it on (the site has no domain and no --url): hand over the token.\n io.stdout(`${r.token}\\n`);\n io.stderr('Add ?cm_preview=<token>&cm_region=..&cm_lang=.. to any page of the site.\\n');\n }\n io.stderr(`Valid until ${new Date(r.expiresAt).toISOString()}.\\n`);\n if (p.flags.open === true && r.previewUrl) await io.openUrl(r.previewUrl);\n return 0;\n}\n\nasync function executorsCommand(\n sub: string | undefined,\n p: Parsed,\n io: CliIo,\n base: string,\n key: string,\n client: CookieMunchClient,\n): Promise<number> {\n if (sub === 'webhook-test') {\n const id = p.positional[2];\n if (!id) throw new UsageError('executors webhook-test <id> needs the connection id');\n const headersFile = str(p.flags, 'headers-file');\n const bodyFile = str(p.flags, 'body-file');\n if (!headersFile || !bodyFile) throw new UsageError('--headers-file and --body-file are both required');\n let headers: unknown;\n try {\n headers = JSON.parse(await io.readFile(headersFile));\n } catch {\n throw new UsageError(`${headersFile} is not a JSON object of header name to value`);\n }\n if (!isPlainObject(headers)) throw new UsageError(`${headersFile} is not a JSON object of header name to value`);\n // The body is sent exactly as read: the signature is over these bytes, so no trimming.\n const body = await io.readFile(bodyFile);\n const r = await client.fulfillment.testExecutorWebhook(id, { headers: headers as Record<string, string>, body });\n io.stdout(`${JSON.stringify(r, null, 2)}\\n`);\n return r.verified ? 0 : 1;\n }\n if (sub === 'deliveries') {\n const id = p.positional[2];\n if (!id) throw new UsageError('executors deliveries <id> needs the connection id');\n io.stdout(`${JSON.stringify(await client.fulfillment.executorWebhookDeliveries(id), null, 2)}\\n`);\n return 0;\n }\n if (sub !== 'test') throw new UsageError(`unknown executors command: ${sub ?? '(none)'} — use test, webhook-test or deliveries`);\n const id = p.positional[2];\n if (!id) throw new UsageError('executors test <id> needs the connection id');\n const email = str(p.flags, 'email');\n if (!email) throw new UsageError('--email is required');\n // The API's operations are locate, export and erase. `lookup` was this CLI's documented\n // default and the API refused it (400), so it stays accepted as another name for locate.\n const asked = str(p.flags, 'operation') ?? 'locate';\n const operation = asked === 'lookup' ? 'locate' : asked;\n // The typed SDK has no method for this yet, so it is a direct call with the same key.\n try {\n const r = await rawRequest(io, base, key, 'POST', `/dsar/executors/${encodeURIComponent(id)}/test`, { email, operation });\n io.stdout(`${JSON.stringify(r, null, 2)}\\n`);\n return 0;\n } catch (e) {\n // The API's generic route miss is a bare `not found`; an unknown connection says so.\n if (e instanceof CookieMunchApiError && e.status === 404 && e.message === 'not found') {\n io.stderr('This server does not offer connection tests yet (POST /v1/dsar/executors/{id}/test).\\n');\n return 1;\n }\n throw e;\n }\n}\n\n/** Run the CLI. Returns the process exit code: 0 ok, 1 failed, 2 usage. */\nexport async function run(argv: string[], io: CliIo): Promise<number> {\n let p: Parsed;\n try {\n p = parseArgs(argv);\n } catch (e) {\n io.stderr(`${(e as Error).message}\\n\\n${USAGE}`);\n return 2;\n }\n const [cmd, sub] = p.positional;\n if (!cmd || p.flags.help === true || cmd === 'help') {\n io.stdout(USAGE);\n return cmd || p.flags.help === true ? 0 : 2;\n }\n const key = io.env.COOKIEMUNCH_API_KEY;\n if (!key) {\n io.stderr('COOKIEMUNCH_API_KEY is not set. Create a key under Developers in the dashboard.\\n');\n return 2;\n }\n const base = io.env.COOKIEMUNCH_BASE_URL || DEFAULT_BASE_URL;\n const client = createCookieMunch({ apiKey: key, baseUrl: base, fetch: io.fetch });\n try {\n if (cmd === 'config') return await configCommand(sub, p, io, client);\n if (cmd === 'preview') return await previewCommand(p, io, client);\n if (cmd === 'executors') return await executorsCommand(sub, p, io, base, key, client);\n if (cmd === 'rights-page') {\n return await rightsPageCommand(sub, p, io, client, {\n requireSite: () => requireSite(p, io),\n readJson: (path) => readConfigFile(io, path),\n usage: (m) => new UsageError(m),\n });\n }\n throw new UsageError(`unknown command: ${cmd}`);\n } catch (e) {\n if (e instanceof UsageError) {\n io.stderr(`${e.message}\\n\\n${USAGE}`);\n return 2;\n }\n if (e instanceof CookieMunchApiError) {\n io.stderr(`API error ${e.status}${e.code ? ` (${e.code})` : ''}: ${e.message}\\n`);\n return 1;\n }\n io.stderr(`${(e as Error).message ?? String(e)}\\n`);\n return 1;\n }\n}\n","/**\n * Consent-button parity findings, as terminal text: one line per finding (severity, code,\n * where), then its explanation and fix, indented. See /docs/fair-consent-buttons.\n */\nimport type { ButtonParityFinding } from '@cookiemunch/sdk';\n\nfunction where(f: ButtonParityFinding): string {\n const w = f.where ?? {};\n return [\n f.layer === 'banner' ? 'first layer' : `${f.layer} layer`,\n w.view ? `view \"${w.view}\"` : '',\n w.variant && w.variant !== 'base' ? `variant ${w.variant}` : '',\n w.theme === 'dark' ? 'dark theme' : '',\n w.breakpoint === 'mobile' ? 'mobile' : '',\n w.language ? `language ${w.language}` : '',\n ]\n .filter(Boolean)\n .join(', ');\n}\n\nexport function formatDesignWarnings(findings: ButtonParityFinding[]): string {\n return findings\n .map((f) => `${f.severity === 'warning' ? 'warning' : 'notice '} ${f.code} ${where(f)}\\n ${f.detail}\\n fix: ${f.fix}`)\n .join('\\n');\n}\n\nexport function countBySeverity(findings: ButtonParityFinding[]): { warnings: number; notices: number } {\n const warnings = findings.filter((f) => f.severity === 'warning').length;\n return { warnings, notices: findings.length - warnings };\n}\n\nexport function summaryLine(findings: ButtonParityFinding[]): string {\n const { warnings, notices } = countBySeverity(findings);\n if (warnings + notices === 0) return 'No design warnings: Accept all and Reject all have equal weight.';\n // Notices are hints (wording, custom CSS a live scan must confirm), not proof of a dark pattern.\n if (warnings === 0) {\n return (\n `Design: 0 warnings, ${notices} notice${notices === 1 ? '' : 's'} — worth a look, not a finding that Accept is favoured. ` +\n 'See https://cookiemunch.net/docs/fair-consent-buttons'\n );\n }\n return (\n `Design: ${warnings} warning${warnings === 1 ? '' : 's'}, ${notices} notice${notices === 1 ? '' : 's'} — ` +\n 'Accept is pushed harder than Reject. Regulators treat that as a dark pattern; make both buttons the same style. ' +\n 'See https://cookiemunch.net/docs/fair-consent-buttons'\n );\n}\n"],"mappings":"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","names":["proposed","CookieMunchApiError","CookieMunchApiError","id"]}
package/dist/index.d.ts CHANGED
@@ -1,11 +1,17 @@
1
+ import { CookieMunchClient } from '@cookiemunch/sdk';
2
+
1
3
  /**
2
4
  * `cookiemunch` — the Developer API from a terminal or a CI job.
3
5
  *
4
6
  * cookiemunch config pull --site <cbid> [--out file]
5
7
  * cookiemunch config diff --site <cbid> <file> [--replace] [--exit-code]
6
- * cookiemunch config push --site <cbid> <file> [--replace]
8
+ * cookiemunch config push --site <cbid> <file> [--replace] [--strict]
9
+ * cookiemunch config validate --site <cbid> <file> [--replace] [--strict]
7
10
  * cookiemunch preview --site <cbid> [--url <url>] [--region eu] [--lang de] [--ttl 900] [--open]
8
11
  * cookiemunch executors test <id> --email <email> [--operation locate]
12
+ * cookiemunch executors webhook-test <id> --headers-file <json> --body-file <file>
13
+ * cookiemunch executors deliveries <id>
14
+ * cookiemunch rights-page pull|diff|push|publish --site <cbid> [<file>] [--base <updatedAt>] [--yes]
9
15
  *
10
16
  * Everything goes through the typed SDK (@cookiemunch/sdk) except where the SDK has no
11
17
  * method yet; those calls go through `rawRequest` with the same key and base URL. All I/O
@@ -21,8 +27,13 @@ interface CliIo {
21
27
  writeFile: (path: string, data: string) => Promise<void>;
22
28
  /** Open a URL in the user's browser. */
23
29
  openUrl: (url: string) => Promise<void>;
30
+ /**
31
+ * Ask a yes/no question. Present only when a person can answer (an interactive terminal);
32
+ * absent, a command that needs confirmation refuses unless given --yes.
33
+ */
34
+ confirm?: (question: string) => Promise<boolean>;
24
35
  }
25
- declare const USAGE = "cookiemunch \u2014 Cookie Munch from the command line\n\nUsage:\n cookiemunch config pull --site <cbid> [--out <file>]\n Write the site's config as JSON (to stdout, or to <file>).\n cookiemunch config diff --site <cbid> <file> [--replace] [--exit-code]\n Show what pushing <file> would change. Nothing is sent.\n cookiemunch config push --site <cbid> <file> [--replace]\n Apply <file>. Default: merged over the stored config (PATCH) \u2014 fields the file\n leaves out keep their value, and a field set to null is removed (it takes its\n default). --replace sends it whole (PUT): a top-level field the file leaves out\n reverts to its default.\n cookiemunch preview --site <cbid> [--url <page>] [--region <eu|de|us-ca\u2026>] [--lang <de\u2026>]\n [--ttl <seconds>] [--open]\n Print (and with --open, open) a link showing the banner as a visitor in that\n region and language sees it. Works in headless browsers; nothing is recorded.\n cookiemunch executors test <id> --email <email> [--operation <locate|export|erase>]\n Try a connected system against one person without filing a request.\n --operation defaults to locate (lookup is accepted as another name for it).\n\nEnvironment:\n COOKIEMUNCH_API_KEY an API key (fck_\u2026). Required.\n COOKIEMUNCH_BASE_URL the API origin. Default https://api.cookiemunch.net.\n COOKIEMUNCH_SITE default for --site.\n";
36
+ declare const USAGE = "cookiemunch \u2014 Cookie Munch from the command line\n\nUsage:\n cookiemunch config pull --site <cbid> [--out <file>]\n Write the site's config as JSON (to stdout, or to <file>).\n cookiemunch config diff --site <cbid> <file> [--replace] [--exit-code]\n Show what pushing <file> would change. Nothing is sent.\n cookiemunch config push --site <cbid> <file> [--replace] [--strict]\n Apply <file>. Default: merged over the stored config (PATCH) \u2014 fields the file\n leaves out keep their value, and a field set to null is removed (it takes its\n default). --replace sends it whole (PUT): a top-level field the file leaves out\n reverts to its default. Prints any design warnings the server found. --strict\n checks the file first and does not push it (exit 1) when Accept all is pushed\n harder than Reject all anywhere \u2014 for CI.\n cookiemunch config validate --site <cbid> <file> [--replace] [--strict]\n Check <file> for consent-button parity on the server, without writing: is Accept\n all more prominent than Reject all (colour, size, placement, copy, custom CSS)?\n Merged over the stored config like push; --replace checks it as sent. --strict\n exits 1 when there is any warning (notices never fail).\n cookiemunch preview --site <cbid> [--url <page>] [--region <eu|de|us-ca\u2026>] [--lang <de\u2026>]\n [--ttl <seconds>] [--open]\n Print (and with --open, open) a link showing the banner as a visitor in that\n region and language sees it. Works in headless browsers; nothing is recorded.\n cookiemunch executors test <id> --email <email> [--operation <locate|export|erase>]\n Try a connected system against one person without filing a request.\n --operation defaults to locate (lookup is accepted as another name for it).\n cookiemunch executors webhook-test <id> --headers-file <json> --body-file <file>\n Would this webhook delivery verify? Sends the headers (a JSON object) and the\n body file byte for byte to the receiver's check; nothing is acted on. Prints\n { verified, stage, reason } and exits 1 when it does not verify.\n cookiemunch executors deliveries <id>\n The last 50 deliveries to that connection's webhook, each with its reason.\n cookiemunch rights-page pull --site <cbid> [--out <file>]\n Write the site's rights page draft as JSON, and print its updatedAt for --base.\n cookiemunch rights-page diff --site <cbid> <file> [--exit-code]\n Validate <file> on the server (dry run) and show what pushing it would change.\n cookiemunch rights-page push --site <cbid> <file> [--base <updatedAt>]\n Replace the draft with <file>. Nothing is live until publish. Refused (exit 1)\n if the draft changed since the diff, or since --base (the updatedAt pull printed).\n cookiemunch rights-page publish --site <cbid> [--yes] [--base <updatedAt>]\n Publish the draft (it goes live at once). Asks first; --yes skips the question\n and is required when there is no terminal to ask in. Prints why when the page\n cannot go live. Refused (exit 1) if the draft changed after it was read (or\n since --base); --yes never overrides that.\n\nEnvironment:\n COOKIEMUNCH_API_KEY an API key (fck_\u2026). Required.\n COOKIEMUNCH_BASE_URL the API origin. Default https://api.cookiemunch.net.\n COOKIEMUNCH_SITE default for --site.\n";
26
37
  interface Parsed {
27
38
  positional: string[];
28
39
  flags: Record<string, string | true>;
@@ -56,4 +67,22 @@ declare function diffJson(before: unknown, after: unknown, path?: string): Chang
56
67
  /** A unified-diff-ish rendering: one line per change. */
57
68
  declare function formatChanges(changes: Change[]): string;
58
69
 
59
- export { type Change, type CliIo, DEFAULT_BASE_URL, USAGE, diffJson, formatChanges, mergeLikeServer, parseArgs, run };
70
+ /**
71
+ * `cookiemunch rights-page pull|diff|push|publish` — the hosted rights page as code.
72
+ *
73
+ * Unlike `config diff`, `diff` asks the server: the rights page has a real dry run, so the
74
+ * diff is against what the server would store (normalised and sanitised), and an invalid
75
+ * file fails with the server's issues before anything is written.
76
+ */
77
+
78
+ interface Helpers {
79
+ requireSite(): string;
80
+ readJson(path: string | undefined): Promise<Record<string, unknown>>;
81
+ usage(msg: string): Error;
82
+ }
83
+ declare function rightsPageCommand(sub: string | undefined, p: {
84
+ positional: string[];
85
+ flags: Record<string, string | true>;
86
+ }, io: CliIo, client: CookieMunchClient, h: Helpers): Promise<number>;
87
+
88
+ export { type Change, type CliIo, DEFAULT_BASE_URL, USAGE, diffJson, formatChanges, mergeLikeServer, parseArgs, rightsPageCommand, run };
package/dist/index.js CHANGED
@@ -5,8 +5,9 @@ import {
5
5
  formatChanges,
6
6
  mergeLikeServer,
7
7
  parseArgs,
8
+ rightsPageCommand,
8
9
  run
9
- } from "./chunk-R5WPMHRO.js";
10
+ } from "./chunk-Z3WVYYES.js";
10
11
  export {
11
12
  DEFAULT_BASE_URL,
12
13
  USAGE,
@@ -14,6 +15,7 @@ export {
14
15
  formatChanges,
15
16
  mergeLikeServer,
16
17
  parseArgs,
18
+ rightsPageCommand,
17
19
  run
18
20
  };
19
21
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cookiemunch/cli",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "cookiemunch — config as code, banner previews and connection tests from a terminal or CI, over the Cookie Munch Developer API",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -29,7 +29,7 @@
29
29
  "access": "public"
30
30
  },
31
31
  "dependencies": {
32
- "@cookiemunch/sdk": "0.1.1"
32
+ "@cookiemunch/sdk": "0.2.0"
33
33
  },
34
34
  "homepage": "https://cookiemunch.net/docs",
35
35
  "bugs": {
@@ -1,302 +0,0 @@
1
- // src/diff.ts
2
- function isPlainObject(v) {
3
- return typeof v === "object" && v !== null && !Array.isArray(v);
4
- }
5
- function mergeLikeServer(base, patch) {
6
- const out = { ...base };
7
- for (const [k, v] of Object.entries(patch)) {
8
- const b = base[k];
9
- if (v === null) delete out[k];
10
- else out[k] = isPlainObject(v) ? mergeLikeServer(isPlainObject(b) ? b : {}, v) : v;
11
- }
12
- return out;
13
- }
14
- function join(path, key) {
15
- return path ? `${path}.${key}` : key;
16
- }
17
- function diffJson(before, after, path = "") {
18
- if (isPlainObject(before) && isPlainObject(after)) {
19
- const keys = [.../* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])].sort();
20
- return keys.flatMap((k) => {
21
- const p = join(path, k);
22
- if (!(k in after)) return [{ path: p, kind: "removed", from: before[k] }];
23
- if (!(k in before)) return [{ path: p, kind: "added", to: after[k] }];
24
- return diffJson(before[k], after[k], p);
25
- });
26
- }
27
- if (Array.isArray(before) && Array.isArray(after)) {
28
- const out = [];
29
- const n = Math.max(before.length, after.length);
30
- for (let i = 0; i < n; i++) {
31
- const p = `${path}[${i}]`;
32
- if (i >= after.length) out.push({ path: p, kind: "removed", from: before[i] });
33
- else if (i >= before.length) out.push({ path: p, kind: "added", to: after[i] });
34
- else out.push(...diffJson(before[i], after[i], p));
35
- }
36
- return out;
37
- }
38
- if (JSON.stringify(before) === JSON.stringify(after)) return [];
39
- return [{ path: path || "(root)", kind: "changed", from: before, to: after }];
40
- }
41
- function show(v) {
42
- const s = JSON.stringify(v);
43
- return s === void 0 ? "undefined" : s.length > 120 ? `${s.slice(0, 117)}...` : s;
44
- }
45
- function formatChanges(changes) {
46
- return changes.map(
47
- (c) => c.kind === "added" ? `+ ${c.path}: ${show(c.to)}` : c.kind === "removed" ? `- ${c.path}: ${show(c.from)}` : `~ ${c.path}: ${show(c.from)} -> ${show(c.to)}`
48
- ).join("\n");
49
- }
50
-
51
- // src/cli.ts
52
- import { createCookieMunch, CookieMunchApiError } from "@cookiemunch/sdk";
53
- var DEFAULT_BASE_URL = "https://api.cookiemunch.net";
54
- var USAGE = `cookiemunch \u2014 Cookie Munch from the command line
55
-
56
- Usage:
57
- cookiemunch config pull --site <cbid> [--out <file>]
58
- Write the site's config as JSON (to stdout, or to <file>).
59
- cookiemunch config diff --site <cbid> <file> [--replace] [--exit-code]
60
- Show what pushing <file> would change. Nothing is sent.
61
- cookiemunch config push --site <cbid> <file> [--replace]
62
- Apply <file>. Default: merged over the stored config (PATCH) \u2014 fields the file
63
- leaves out keep their value, and a field set to null is removed (it takes its
64
- default). --replace sends it whole (PUT): a top-level field the file leaves out
65
- reverts to its default.
66
- cookiemunch preview --site <cbid> [--url <page>] [--region <eu|de|us-ca\u2026>] [--lang <de\u2026>]
67
- [--ttl <seconds>] [--open]
68
- Print (and with --open, open) a link showing the banner as a visitor in that
69
- region and language sees it. Works in headless browsers; nothing is recorded.
70
- cookiemunch executors test <id> --email <email> [--operation <locate|export|erase>]
71
- Try a connected system against one person without filing a request.
72
- --operation defaults to locate (lookup is accepted as another name for it).
73
-
74
- Environment:
75
- COOKIEMUNCH_API_KEY an API key (fck_\u2026). Required.
76
- COOKIEMUNCH_BASE_URL the API origin. Default ${DEFAULT_BASE_URL}.
77
- COOKIEMUNCH_SITE default for --site.
78
- `;
79
- var UsageError = class extends Error {
80
- };
81
- var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["replace", "exit-code", "open", "help", "json"]);
82
- function parseArgs(argv) {
83
- const positional = [];
84
- const flags = {};
85
- for (let i = 0; i < argv.length; i++) {
86
- const a = argv[i];
87
- if (a === "-h") {
88
- flags.help = true;
89
- } else if (a.startsWith("--")) {
90
- const eq = a.indexOf("=");
91
- const name = eq > 0 ? a.slice(2, eq) : a.slice(2);
92
- if (eq > 0) flags[name] = a.slice(eq + 1);
93
- else if (BOOLEAN_FLAGS.has(name)) flags[name] = true;
94
- else {
95
- const v = argv[i + 1];
96
- if (v === void 0 || v.startsWith("--")) throw new UsageError(`--${name} needs a value`);
97
- flags[name] = v;
98
- i++;
99
- }
100
- } else {
101
- positional.push(a);
102
- }
103
- }
104
- return { positional, flags };
105
- }
106
- function str(flags, name) {
107
- const v = flags[name];
108
- return typeof v === "string" ? v : void 0;
109
- }
110
- function requireSite(p, io) {
111
- const site = str(p.flags, "site") ?? io.env.COOKIEMUNCH_SITE;
112
- if (!site) throw new UsageError("--site <cbid> is required (or set COOKIEMUNCH_SITE)");
113
- return site;
114
- }
115
- async function readConfigFile(io, path) {
116
- if (!path) throw new UsageError("a config file is required");
117
- let text;
118
- try {
119
- text = await io.readFile(path);
120
- } catch (e) {
121
- throw new UsageError(`cannot read ${path}: ${e.message}`);
122
- }
123
- let parsed;
124
- try {
125
- parsed = JSON.parse(text);
126
- } catch (e) {
127
- throw new UsageError(`${path} is not valid JSON: ${e.message}`);
128
- }
129
- if (!isPlainObject(parsed)) throw new UsageError(`${path} must hold a JSON object`);
130
- return parsed;
131
- }
132
- function proposed(current, file, replace, cbid) {
133
- return replace ? { ...file, cbid } : mergeLikeServer(current, file);
134
- }
135
- async function rawRequest(io, base, key, method, path, body) {
136
- const res = await io.fetch(`${base.replace(/\/+$/, "")}/v1${path}`, {
137
- method,
138
- headers: { Authorization: `Bearer ${key}`, ...body !== void 0 ? { "Content-Type": "application/json" } : {} },
139
- ...body !== void 0 ? { body: JSON.stringify(body) } : {}
140
- });
141
- const text = await res.text();
142
- let data = text;
143
- try {
144
- data = text ? JSON.parse(text) : void 0;
145
- } catch {
146
- }
147
- if (!res.ok) {
148
- const err = isPlainObject(data) ? data : {};
149
- throw new CookieMunchApiError(
150
- res.status,
151
- typeof err.error === "string" ? err.error : `request failed with status ${res.status}`,
152
- typeof err.code === "string" ? err.code : void 0
153
- );
154
- }
155
- return data;
156
- }
157
- async function configCommand(sub, p, io, client) {
158
- const cbid = requireSite(p, io);
159
- if (sub === "pull") {
160
- const cfg = await client.sites.getConfig(cbid);
161
- const text = `${JSON.stringify(cfg, null, 2)}
162
- `;
163
- const out = str(p.flags, "out");
164
- if (out) {
165
- await io.writeFile(out, text);
166
- io.stderr(`Wrote ${cbid}'s config to ${out}
167
- `);
168
- } else {
169
- io.stdout(text);
170
- }
171
- return 0;
172
- }
173
- if (sub === "diff" || sub === "push") {
174
- const file = await readConfigFile(io, p.positional[2]);
175
- const replace = p.flags.replace === true;
176
- const current = await client.sites.getConfig(cbid);
177
- const changes = diffJson(current, proposed(current, file, replace, cbid));
178
- if (changes.length === 0) {
179
- io.stderr(`No changes: ${cbid}'s config already matches.
180
- `);
181
- return 0;
182
- }
183
- io.stdout(`${formatChanges(changes)}
184
- `);
185
- if (sub === "diff") {
186
- io.stderr(`${changes.length} change${changes.length === 1 ? "" : "s"} (nothing sent). Apply with: cookiemunch config push --site ${cbid} ${p.positional[2]}${replace ? " --replace" : ""}
187
- `);
188
- return p.flags["exit-code"] === true ? 1 : 0;
189
- }
190
- if (replace) await client.sites.putConfig(cbid, { ...file, cbid });
191
- else await client.sites.patchConfig(cbid, file);
192
- io.stderr(`Applied ${changes.length} change${changes.length === 1 ? "" : "s"} to ${cbid}.
193
- `);
194
- return 0;
195
- }
196
- throw new UsageError(`unknown config command: ${sub ?? "(none)"} \u2014 use pull, diff or push`);
197
- }
198
- async function previewCommand(p, io, client) {
199
- const cbid = requireSite(p, io);
200
- const ttl = str(p.flags, "ttl");
201
- const ttlSeconds = ttl === void 0 ? void 0 : Number(ttl);
202
- if (ttlSeconds !== void 0 && (!Number.isInteger(ttlSeconds) || ttlSeconds < 1 || ttlSeconds > 3600)) {
203
- throw new UsageError("--ttl must be a whole number of seconds from 1 to 3600");
204
- }
205
- const r = await client.sites.previewToken(cbid, {
206
- ...str(p.flags, "region") ? { region: str(p.flags, "region") } : {},
207
- ...str(p.flags, "lang") ? { lang: str(p.flags, "lang") } : {},
208
- ...str(p.flags, "url") ? { url: str(p.flags, "url") } : {},
209
- ...ttlSeconds !== void 0 ? { ttlSeconds } : {}
210
- });
211
- if (p.flags.json === true) {
212
- io.stdout(`${JSON.stringify(r, null, 2)}
213
- `);
214
- } else if (r.previewUrl) {
215
- io.stdout(`${r.previewUrl}
216
- `);
217
- } else {
218
- io.stdout(`${r.token}
219
- `);
220
- io.stderr("Add ?cm_preview=<token>&cm_region=..&cm_lang=.. to any page of the site.\n");
221
- }
222
- io.stderr(`Valid until ${new Date(r.expiresAt).toISOString()}.
223
- `);
224
- if (p.flags.open === true && r.previewUrl) await io.openUrl(r.previewUrl);
225
- return 0;
226
- }
227
- async function executorsCommand(sub, p, io, base, key) {
228
- if (sub !== "test") throw new UsageError(`unknown executors command: ${sub ?? "(none)"} \u2014 use test`);
229
- const id = p.positional[2];
230
- if (!id) throw new UsageError("executors test <id> needs the connection id");
231
- const email = str(p.flags, "email");
232
- if (!email) throw new UsageError("--email is required");
233
- const asked = str(p.flags, "operation") ?? "locate";
234
- const operation = asked === "lookup" ? "locate" : asked;
235
- try {
236
- const r = await rawRequest(io, base, key, "POST", `/dsar/executors/${encodeURIComponent(id)}/test`, { email, operation });
237
- io.stdout(`${JSON.stringify(r, null, 2)}
238
- `);
239
- return 0;
240
- } catch (e) {
241
- if (e instanceof CookieMunchApiError && e.status === 404 && e.message === "not found") {
242
- io.stderr("This server does not offer connection tests yet (POST /v1/dsar/executors/{id}/test).\n");
243
- return 1;
244
- }
245
- throw e;
246
- }
247
- }
248
- async function run(argv, io) {
249
- let p;
250
- try {
251
- p = parseArgs(argv);
252
- } catch (e) {
253
- io.stderr(`${e.message}
254
-
255
- ${USAGE}`);
256
- return 2;
257
- }
258
- const [cmd, sub] = p.positional;
259
- if (!cmd || p.flags.help === true || cmd === "help") {
260
- io.stdout(USAGE);
261
- return cmd || p.flags.help === true ? 0 : 2;
262
- }
263
- const key = io.env.COOKIEMUNCH_API_KEY;
264
- if (!key) {
265
- io.stderr("COOKIEMUNCH_API_KEY is not set. Create a key under Developers in the dashboard.\n");
266
- return 2;
267
- }
268
- const base = io.env.COOKIEMUNCH_BASE_URL || DEFAULT_BASE_URL;
269
- const client = createCookieMunch({ apiKey: key, baseUrl: base, fetch: io.fetch });
270
- try {
271
- if (cmd === "config") return await configCommand(sub, p, io, client);
272
- if (cmd === "preview") return await previewCommand(p, io, client);
273
- if (cmd === "executors") return await executorsCommand(sub, p, io, base, key);
274
- throw new UsageError(`unknown command: ${cmd}`);
275
- } catch (e) {
276
- if (e instanceof UsageError) {
277
- io.stderr(`${e.message}
278
-
279
- ${USAGE}`);
280
- return 2;
281
- }
282
- if (e instanceof CookieMunchApiError) {
283
- io.stderr(`API error ${e.status}${e.code ? ` (${e.code})` : ""}: ${e.message}
284
- `);
285
- return 1;
286
- }
287
- io.stderr(`${e.message ?? String(e)}
288
- `);
289
- return 1;
290
- }
291
- }
292
-
293
- export {
294
- mergeLikeServer,
295
- diffJson,
296
- formatChanges,
297
- DEFAULT_BASE_URL,
298
- USAGE,
299
- parseArgs,
300
- run
301
- };
302
- //# sourceMappingURL=chunk-R5WPMHRO.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/diff.ts","../src/cli.ts"],"sourcesContent":["/**\n * What a config push would change, computed on this side of the wire.\n *\n * `mergeLikeServer` mirrors the server's `PATCH /v1/sites/{cbid}/config` semantics exactly\n * (JSON Merge Patch, RFC 7386 — packages/server/src/site-config-write.ts mergePatch): the\n * patch wins, nested plain objects merge, arrays and primitives replace wholesale, and a\n * `null` removes the key. A diff computed against any other merge would describe a change\n * the server is not going to make.\n */\n\nexport type Json = null | boolean | number | string | Json[] | { [k: string]: Json };\n\nexport interface Change {\n /** Dotted path; array indices in brackets: `banner.content.title`, `geoRules[0].mode`. */\n path: string;\n kind: 'added' | 'removed' | 'changed';\n from?: Json;\n to?: Json;\n}\n\nexport function isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\nexport function mergeLikeServer(base: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = { ...base };\n for (const [k, v] of Object.entries(patch)) {\n const b = base[k];\n if (v === null) delete out[k];\n else out[k] = isPlainObject(v) ? mergeLikeServer(isPlainObject(b) ? b : {}, v) : v;\n }\n return out;\n}\n\nfunction join(path: string, key: string): string {\n return path ? `${path}.${key}` : key;\n}\n\n/** Every leaf that differs between `before` and `after`, in a stable order. */\nexport function diffJson(before: unknown, after: unknown, path = ''): Change[] {\n if (isPlainObject(before) && isPlainObject(after)) {\n const keys = [...new Set([...Object.keys(before), ...Object.keys(after)])].sort();\n return keys.flatMap((k) => {\n const p = join(path, k);\n if (!(k in after)) return [{ path: p, kind: 'removed' as const, from: before[k] as Json }];\n if (!(k in before)) return [{ path: p, kind: 'added' as const, to: after[k] as Json }];\n return diffJson(before[k], after[k], p);\n });\n }\n if (Array.isArray(before) && Array.isArray(after)) {\n const out: Change[] = [];\n const n = Math.max(before.length, after.length);\n for (let i = 0; i < n; i++) {\n const p = `${path}[${i}]`;\n if (i >= after.length) out.push({ path: p, kind: 'removed', from: before[i] as Json });\n else if (i >= before.length) out.push({ path: p, kind: 'added', to: after[i] as Json });\n else out.push(...diffJson(before[i], after[i], p));\n }\n return out;\n }\n if (JSON.stringify(before) === JSON.stringify(after)) return [];\n return [{ path: path || '(root)', kind: 'changed', from: before as Json, to: after as Json }];\n}\n\nfunction show(v: Json | undefined): string {\n const s = JSON.stringify(v);\n return s === undefined ? 'undefined' : s.length > 120 ? `${s.slice(0, 117)}...` : s;\n}\n\n/** A unified-diff-ish rendering: one line per change. */\nexport function formatChanges(changes: Change[]): string {\n return changes\n .map((c) =>\n c.kind === 'added'\n ? `+ ${c.path}: ${show(c.to)}`\n : c.kind === 'removed'\n ? `- ${c.path}: ${show(c.from)}`\n : `~ ${c.path}: ${show(c.from)} -> ${show(c.to)}`,\n )\n .join('\\n');\n}\n","/**\n * `cookiemunch` — the Developer API from a terminal or a CI job.\n *\n * cookiemunch config pull --site <cbid> [--out file]\n * cookiemunch config diff --site <cbid> <file> [--replace] [--exit-code]\n * cookiemunch config push --site <cbid> <file> [--replace]\n * cookiemunch preview --site <cbid> [--url <url>] [--region eu] [--lang de] [--ttl 900] [--open]\n * cookiemunch executors test <id> --email <email> [--operation locate]\n *\n * Everything goes through the typed SDK (@cookiemunch/sdk) except where the SDK has no\n * method yet; those calls go through `rawRequest` with the same key and base URL. All I/O\n * is injected, so the whole CLI is testable with a mocked fetch.\n */\n\nimport { createCookieMunch, CookieMunchApiError, type CookieMunchClient } from '@cookiemunch/sdk';\nimport { diffJson, formatChanges, isPlainObject, mergeLikeServer } from './diff.js';\n\nexport const DEFAULT_BASE_URL = 'https://api.cookiemunch.net';\n\nexport interface CliIo {\n env: Record<string, string | undefined>;\n fetch: typeof fetch;\n stdout: (text: string) => void;\n stderr: (text: string) => void;\n readFile: (path: string) => Promise<string>;\n writeFile: (path: string, data: string) => Promise<void>;\n /** Open a URL in the user's browser. */\n openUrl: (url: string) => Promise<void>;\n}\n\nexport const USAGE = `cookiemunch — Cookie Munch from the command line\n\nUsage:\n cookiemunch config pull --site <cbid> [--out <file>]\n Write the site's config as JSON (to stdout, or to <file>).\n cookiemunch config diff --site <cbid> <file> [--replace] [--exit-code]\n Show what pushing <file> would change. Nothing is sent.\n cookiemunch config push --site <cbid> <file> [--replace]\n Apply <file>. Default: merged over the stored config (PATCH) — fields the file\n leaves out keep their value, and a field set to null is removed (it takes its\n default). --replace sends it whole (PUT): a top-level field the file leaves out\n reverts to its default.\n cookiemunch preview --site <cbid> [--url <page>] [--region <eu|de|us-ca…>] [--lang <de…>]\n [--ttl <seconds>] [--open]\n Print (and with --open, open) a link showing the banner as a visitor in that\n region and language sees it. Works in headless browsers; nothing is recorded.\n cookiemunch executors test <id> --email <email> [--operation <locate|export|erase>]\n Try a connected system against one person without filing a request.\n --operation defaults to locate (lookup is accepted as another name for it).\n\nEnvironment:\n COOKIEMUNCH_API_KEY an API key (fck_…). Required.\n COOKIEMUNCH_BASE_URL the API origin. Default ${DEFAULT_BASE_URL}.\n COOKIEMUNCH_SITE default for --site.\n`;\n\n/** A usage mistake: exit code 2. */\nclass UsageError extends Error {}\n\ninterface Parsed {\n positional: string[];\n flags: Record<string, string | true>;\n}\n\nconst BOOLEAN_FLAGS = new Set(['replace', 'exit-code', 'open', 'help', 'json']);\n\nexport function parseArgs(argv: string[]): Parsed {\n const positional: string[] = [];\n const flags: Record<string, string | true> = {};\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i]!;\n if (a === '-h') {\n flags.help = true;\n } else if (a.startsWith('--')) {\n const eq = a.indexOf('=');\n const name = eq > 0 ? a.slice(2, eq) : a.slice(2);\n if (eq > 0) flags[name] = a.slice(eq + 1);\n else if (BOOLEAN_FLAGS.has(name)) flags[name] = true;\n else {\n const v = argv[i + 1];\n if (v === undefined || v.startsWith('--')) throw new UsageError(`--${name} needs a value`);\n flags[name] = v;\n i++;\n }\n } else {\n positional.push(a);\n }\n }\n return { positional, flags };\n}\n\nfunction str(flags: Parsed['flags'], name: string): string | undefined {\n const v = flags[name];\n return typeof v === 'string' ? v : undefined;\n}\n\nfunction requireSite(p: Parsed, io: CliIo): string {\n const site = str(p.flags, 'site') ?? io.env.COOKIEMUNCH_SITE;\n if (!site) throw new UsageError('--site <cbid> is required (or set COOKIEMUNCH_SITE)');\n return site;\n}\n\nasync function readConfigFile(io: CliIo, path: string | undefined): Promise<Record<string, unknown>> {\n if (!path) throw new UsageError('a config file is required');\n let text: string;\n try {\n text = await io.readFile(path);\n } catch (e) {\n throw new UsageError(`cannot read ${path}: ${(e as Error).message}`);\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch (e) {\n throw new UsageError(`${path} is not valid JSON: ${(e as Error).message}`);\n }\n if (!isPlainObject(parsed)) throw new UsageError(`${path} must hold a JSON object`);\n return parsed;\n}\n\n/** What the stored config would become, per push mode. */\nfunction proposed(current: Record<string, unknown>, file: Record<string, unknown>, replace: boolean, cbid: string) {\n return replace ? { ...file, cbid } : mergeLikeServer(current, file);\n}\n\nasync function rawRequest(io: CliIo, base: string, key: string, method: string, path: string, body?: unknown): Promise<unknown> {\n const res = await io.fetch(`${base.replace(/\\/+$/, '')}/v1${path}`, {\n method,\n headers: { Authorization: `Bearer ${key}`, ...(body !== undefined ? { 'Content-Type': 'application/json' } : {}) },\n ...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n });\n const text = await res.text();\n let data: unknown = text;\n try {\n data = text ? JSON.parse(text) : undefined;\n } catch {\n /* keep the text */\n }\n if (!res.ok) {\n const err = isPlainObject(data) ? data : {};\n throw new CookieMunchApiError(\n res.status,\n typeof err.error === 'string' ? err.error : `request failed with status ${res.status}`,\n typeof err.code === 'string' ? err.code : undefined,\n );\n }\n return data;\n}\n\nasync function configCommand(sub: string | undefined, p: Parsed, io: CliIo, client: CookieMunchClient): Promise<number> {\n const cbid = requireSite(p, io);\n if (sub === 'pull') {\n const cfg = await client.sites.getConfig(cbid);\n const text = `${JSON.stringify(cfg, null, 2)}\\n`;\n const out = str(p.flags, 'out');\n if (out) {\n await io.writeFile(out, text);\n io.stderr(`Wrote ${cbid}'s config to ${out}\\n`);\n } else {\n io.stdout(text);\n }\n return 0;\n }\n if (sub === 'diff' || sub === 'push') {\n const file = await readConfigFile(io, p.positional[2]);\n const replace = p.flags.replace === true;\n const current = (await client.sites.getConfig(cbid)) as unknown as Record<string, unknown>;\n // Computed here: the server has no dry-run for config writes yet, and the only safe\n // way to ask \"what would this change\" of an API that might ignore a dryRun flag is\n // not to send the write at all.\n const changes = diffJson(current, proposed(current, file, replace, cbid));\n if (changes.length === 0) {\n io.stderr(`No changes: ${cbid}'s config already matches.\\n`);\n return 0;\n }\n io.stdout(`${formatChanges(changes)}\\n`);\n if (sub === 'diff') {\n io.stderr(`${changes.length} change${changes.length === 1 ? '' : 's'} (nothing sent). Apply with: cookiemunch config push --site ${cbid} ${p.positional[2]}${replace ? ' --replace' : ''}\\n`);\n return p.flags['exit-code'] === true ? 1 : 0;\n }\n if (replace) await client.sites.putConfig(cbid, { ...file, cbid } as never);\n else await client.sites.patchConfig(cbid, file as never);\n io.stderr(`Applied ${changes.length} change${changes.length === 1 ? '' : 's'} to ${cbid}.\\n`);\n return 0;\n }\n throw new UsageError(`unknown config command: ${sub ?? '(none)'} — use pull, diff or push`);\n}\n\nasync function previewCommand(p: Parsed, io: CliIo, client: CookieMunchClient): Promise<number> {\n const cbid = requireSite(p, io);\n const ttl = str(p.flags, 'ttl');\n const ttlSeconds = ttl === undefined ? undefined : Number(ttl);\n if (ttlSeconds !== undefined && (!Number.isInteger(ttlSeconds) || ttlSeconds < 1 || ttlSeconds > 3600)) {\n throw new UsageError('--ttl must be a whole number of seconds from 1 to 3600');\n }\n const r = await client.sites.previewToken(cbid, {\n ...(str(p.flags, 'region') ? { region: str(p.flags, 'region')! } : {}),\n ...(str(p.flags, 'lang') ? { lang: str(p.flags, 'lang')! } : {}),\n ...(str(p.flags, 'url') ? { url: str(p.flags, 'url')! } : {}),\n ...(ttlSeconds !== undefined ? { ttlSeconds } : {}),\n });\n if (p.flags.json === true) {\n io.stdout(`${JSON.stringify(r, null, 2)}\\n`);\n } else if (r.previewUrl) {\n io.stdout(`${r.previewUrl}\\n`);\n } else {\n // No page to put it on (the site has no domain and no --url): hand over the token.\n io.stdout(`${r.token}\\n`);\n io.stderr('Add ?cm_preview=<token>&cm_region=..&cm_lang=.. to any page of the site.\\n');\n }\n io.stderr(`Valid until ${new Date(r.expiresAt).toISOString()}.\\n`);\n if (p.flags.open === true && r.previewUrl) await io.openUrl(r.previewUrl);\n return 0;\n}\n\nasync function executorsCommand(sub: string | undefined, p: Parsed, io: CliIo, base: string, key: string): Promise<number> {\n if (sub !== 'test') throw new UsageError(`unknown executors command: ${sub ?? '(none)'} — use test`);\n const id = p.positional[2];\n if (!id) throw new UsageError('executors test <id> needs the connection id');\n const email = str(p.flags, 'email');\n if (!email) throw new UsageError('--email is required');\n // The API's operations are locate, export and erase. `lookup` was this CLI's documented\n // default and the API refused it (400), so it stays accepted as another name for locate.\n const asked = str(p.flags, 'operation') ?? 'locate';\n const operation = asked === 'lookup' ? 'locate' : asked;\n // The typed SDK has no method for this yet, so it is a direct call with the same key.\n try {\n const r = await rawRequest(io, base, key, 'POST', `/dsar/executors/${encodeURIComponent(id)}/test`, { email, operation });\n io.stdout(`${JSON.stringify(r, null, 2)}\\n`);\n return 0;\n } catch (e) {\n // The API's generic route miss is a bare `not found`; an unknown connection says so.\n if (e instanceof CookieMunchApiError && e.status === 404 && e.message === 'not found') {\n io.stderr('This server does not offer connection tests yet (POST /v1/dsar/executors/{id}/test).\\n');\n return 1;\n }\n throw e;\n }\n}\n\n/** Run the CLI. Returns the process exit code: 0 ok, 1 failed, 2 usage. */\nexport async function run(argv: string[], io: CliIo): Promise<number> {\n let p: Parsed;\n try {\n p = parseArgs(argv);\n } catch (e) {\n io.stderr(`${(e as Error).message}\\n\\n${USAGE}`);\n return 2;\n }\n const [cmd, sub] = p.positional;\n if (!cmd || p.flags.help === true || cmd === 'help') {\n io.stdout(USAGE);\n return cmd || p.flags.help === true ? 0 : 2;\n }\n const key = io.env.COOKIEMUNCH_API_KEY;\n if (!key) {\n io.stderr('COOKIEMUNCH_API_KEY is not set. Create a key under Developers in the dashboard.\\n');\n return 2;\n }\n const base = io.env.COOKIEMUNCH_BASE_URL || DEFAULT_BASE_URL;\n const client = createCookieMunch({ apiKey: key, baseUrl: base, fetch: io.fetch });\n try {\n if (cmd === 'config') return await configCommand(sub, p, io, client);\n if (cmd === 'preview') return await previewCommand(p, io, client);\n if (cmd === 'executors') return await executorsCommand(sub, p, io, base, key);\n throw new UsageError(`unknown command: ${cmd}`);\n } catch (e) {\n if (e instanceof UsageError) {\n io.stderr(`${e.message}\\n\\n${USAGE}`);\n return 2;\n }\n if (e instanceof CookieMunchApiError) {\n io.stderr(`API error ${e.status}${e.code ? ` (${e.code})` : ''}: ${e.message}\\n`);\n return 1;\n }\n io.stderr(`${(e as Error).message ?? String(e)}\\n`);\n return 1;\n }\n}\n"],"mappings":";AAoBO,SAAS,cAAc,GAA0C;AACtE,SAAO,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,QAAQ,CAAC;AAChE;AAEO,SAAS,gBAAgB,MAA+B,OAAyD;AACtH,QAAM,MAA+B,EAAE,GAAG,KAAK;AAC/C,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC1C,UAAM,IAAI,KAAK,CAAC;AAChB,QAAI,MAAM,KAAM,QAAO,IAAI,CAAC;AAAA,QACvB,KAAI,CAAC,IAAI,cAAc,CAAC,IAAI,gBAAgB,cAAc,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI;AAAA,EACnF;AACA,SAAO;AACT;AAEA,SAAS,KAAK,MAAc,KAAqB;AAC/C,SAAO,OAAO,GAAG,IAAI,IAAI,GAAG,KAAK;AACnC;AAGO,SAAS,SAAS,QAAiB,OAAgB,OAAO,IAAc;AAC7E,MAAI,cAAc,MAAM,KAAK,cAAc,KAAK,GAAG;AACjD,UAAM,OAAO,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,OAAO,KAAK,MAAM,GAAG,GAAG,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK;AAChF,WAAO,KAAK,QAAQ,CAAC,MAAM;AACzB,YAAM,IAAI,KAAK,MAAM,CAAC;AACtB,UAAI,EAAE,KAAK,OAAQ,QAAO,CAAC,EAAE,MAAM,GAAG,MAAM,WAAoB,MAAM,OAAO,CAAC,EAAU,CAAC;AACzF,UAAI,EAAE,KAAK,QAAS,QAAO,CAAC,EAAE,MAAM,GAAG,MAAM,SAAkB,IAAI,MAAM,CAAC,EAAU,CAAC;AACrF,aAAO,SAAS,OAAO,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC;AAAA,IACxC,CAAC;AAAA,EACH;AACA,MAAI,MAAM,QAAQ,MAAM,KAAK,MAAM,QAAQ,KAAK,GAAG;AACjD,UAAM,MAAgB,CAAC;AACvB,UAAM,IAAI,KAAK,IAAI,OAAO,QAAQ,MAAM,MAAM;AAC9C,aAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,YAAM,IAAI,GAAG,IAAI,IAAI,CAAC;AACtB,UAAI,KAAK,MAAM,OAAQ,KAAI,KAAK,EAAE,MAAM,GAAG,MAAM,WAAW,MAAM,OAAO,CAAC,EAAU,CAAC;AAAA,eAC5E,KAAK,OAAO,OAAQ,KAAI,KAAK,EAAE,MAAM,GAAG,MAAM,SAAS,IAAI,MAAM,CAAC,EAAU,CAAC;AAAA,UACjF,KAAI,KAAK,GAAG,SAAS,OAAO,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;AAAA,IACnD;AACA,WAAO;AAAA,EACT;AACA,MAAI,KAAK,UAAU,MAAM,MAAM,KAAK,UAAU,KAAK,EAAG,QAAO,CAAC;AAC9D,SAAO,CAAC,EAAE,MAAM,QAAQ,UAAU,MAAM,WAAW,MAAM,QAAgB,IAAI,MAAc,CAAC;AAC9F;AAEA,SAAS,KAAK,GAA6B;AACzC,QAAM,IAAI,KAAK,UAAU,CAAC;AAC1B,SAAO,MAAM,SAAY,cAAc,EAAE,SAAS,MAAM,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,QAAQ;AACpF;AAGO,SAAS,cAAc,SAA2B;AACvD,SAAO,QACJ;AAAA,IAAI,CAAC,MACJ,EAAE,SAAS,UACP,KAAK,EAAE,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC,KAC1B,EAAE,SAAS,YACT,KAAK,EAAE,IAAI,KAAK,KAAK,EAAE,IAAI,CAAC,KAC5B,KAAK,EAAE,IAAI,KAAK,KAAK,EAAE,IAAI,CAAC,OAAO,KAAK,EAAE,EAAE,CAAC;AAAA,EACrD,EACC,KAAK,IAAI;AACd;;;AClEA,SAAS,mBAAmB,2BAAmD;AAGxE,IAAM,mBAAmB;AAazB,IAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kDAsB6B,gBAAgB;AAAA;AAAA;AAKlE,IAAM,aAAN,cAAyB,MAAM;AAAC;AAOhC,IAAM,gBAAgB,oBAAI,IAAI,CAAC,WAAW,aAAa,QAAQ,QAAQ,MAAM,CAAC;AAEvE,SAAS,UAAU,MAAwB;AAChD,QAAM,aAAuB,CAAC;AAC9B,QAAM,QAAuC,CAAC;AAC9C,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,IAAI,KAAK,CAAC;AAChB,QAAI,MAAM,MAAM;AACd,YAAM,OAAO;AAAA,IACf,WAAW,EAAE,WAAW,IAAI,GAAG;AAC7B,YAAM,KAAK,EAAE,QAAQ,GAAG;AACxB,YAAM,OAAO,KAAK,IAAI,EAAE,MAAM,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC;AAChD,UAAI,KAAK,EAAG,OAAM,IAAI,IAAI,EAAE,MAAM,KAAK,CAAC;AAAA,eAC/B,cAAc,IAAI,IAAI,EAAG,OAAM,IAAI,IAAI;AAAA,WAC3C;AACH,cAAM,IAAI,KAAK,IAAI,CAAC;AACpB,YAAI,MAAM,UAAa,EAAE,WAAW,IAAI,EAAG,OAAM,IAAI,WAAW,KAAK,IAAI,gBAAgB;AACzF,cAAM,IAAI,IAAI;AACd;AAAA,MACF;AAAA,IACF,OAAO;AACL,iBAAW,KAAK,CAAC;AAAA,IACnB;AAAA,EACF;AACA,SAAO,EAAE,YAAY,MAAM;AAC7B;AAEA,SAAS,IAAI,OAAwB,MAAkC;AACrE,QAAM,IAAI,MAAM,IAAI;AACpB,SAAO,OAAO,MAAM,WAAW,IAAI;AACrC;AAEA,SAAS,YAAY,GAAW,IAAmB;AACjD,QAAM,OAAO,IAAI,EAAE,OAAO,MAAM,KAAK,GAAG,IAAI;AAC5C,MAAI,CAAC,KAAM,OAAM,IAAI,WAAW,qDAAqD;AACrF,SAAO;AACT;AAEA,eAAe,eAAe,IAAW,MAA4D;AACnG,MAAI,CAAC,KAAM,OAAM,IAAI,WAAW,2BAA2B;AAC3D,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,GAAG,SAAS,IAAI;AAAA,EAC/B,SAAS,GAAG;AACV,UAAM,IAAI,WAAW,eAAe,IAAI,KAAM,EAAY,OAAO,EAAE;AAAA,EACrE;AACA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,IAAI;AAAA,EAC1B,SAAS,GAAG;AACV,UAAM,IAAI,WAAW,GAAG,IAAI,uBAAwB,EAAY,OAAO,EAAE;AAAA,EAC3E;AACA,MAAI,CAAC,cAAc,MAAM,EAAG,OAAM,IAAI,WAAW,GAAG,IAAI,0BAA0B;AAClF,SAAO;AACT;AAGA,SAAS,SAAS,SAAkC,MAA+B,SAAkB,MAAc;AACjH,SAAO,UAAU,EAAE,GAAG,MAAM,KAAK,IAAI,gBAAgB,SAAS,IAAI;AACpE;AAEA,eAAe,WAAW,IAAW,MAAc,KAAa,QAAgB,MAAc,MAAkC;AAC9H,QAAM,MAAM,MAAM,GAAG,MAAM,GAAG,KAAK,QAAQ,QAAQ,EAAE,CAAC,MAAM,IAAI,IAAI;AAAA,IAClE;AAAA,IACA,SAAS,EAAE,eAAe,UAAU,GAAG,IAAI,GAAI,SAAS,SAAY,EAAE,gBAAgB,mBAAmB,IAAI,CAAC,EAAG;AAAA,IACjH,GAAI,SAAS,SAAY,EAAE,MAAM,KAAK,UAAU,IAAI,EAAE,IAAI,CAAC;AAAA,EAC7D,CAAC;AACD,QAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,MAAI,OAAgB;AACpB,MAAI;AACF,WAAO,OAAO,KAAK,MAAM,IAAI,IAAI;AAAA,EACnC,QAAQ;AAAA,EAER;AACA,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,MAAM,cAAc,IAAI,IAAI,OAAO,CAAC;AAC1C,UAAM,IAAI;AAAA,MACR,IAAI;AAAA,MACJ,OAAO,IAAI,UAAU,WAAW,IAAI,QAAQ,8BAA8B,IAAI,MAAM;AAAA,MACpF,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO;AAAA,IAC5C;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAe,cAAc,KAAyB,GAAW,IAAW,QAA4C;AACtH,QAAM,OAAO,YAAY,GAAG,EAAE;AAC9B,MAAI,QAAQ,QAAQ;AAClB,UAAM,MAAM,MAAM,OAAO,MAAM,UAAU,IAAI;AAC7C,UAAM,OAAO,GAAG,KAAK,UAAU,KAAK,MAAM,CAAC,CAAC;AAAA;AAC5C,UAAM,MAAM,IAAI,EAAE,OAAO,KAAK;AAC9B,QAAI,KAAK;AACP,YAAM,GAAG,UAAU,KAAK,IAAI;AAC5B,SAAG,OAAO,SAAS,IAAI,gBAAgB,GAAG;AAAA,CAAI;AAAA,IAChD,OAAO;AACL,SAAG,OAAO,IAAI;AAAA,IAChB;AACA,WAAO;AAAA,EACT;AACA,MAAI,QAAQ,UAAU,QAAQ,QAAQ;AACpC,UAAM,OAAO,MAAM,eAAe,IAAI,EAAE,WAAW,CAAC,CAAC;AACrD,UAAM,UAAU,EAAE,MAAM,YAAY;AACpC,UAAM,UAAW,MAAM,OAAO,MAAM,UAAU,IAAI;AAIlD,UAAM,UAAU,SAAS,SAAS,SAAS,SAAS,MAAM,SAAS,IAAI,CAAC;AACxE,QAAI,QAAQ,WAAW,GAAG;AACxB,SAAG,OAAO,eAAe,IAAI;AAAA,CAA8B;AAC3D,aAAO;AAAA,IACT;AACA,OAAG,OAAO,GAAG,cAAc,OAAO,CAAC;AAAA,CAAI;AACvC,QAAI,QAAQ,QAAQ;AAClB,SAAG,OAAO,GAAG,QAAQ,MAAM,UAAU,QAAQ,WAAW,IAAI,KAAK,GAAG,+DAA+D,IAAI,IAAI,EAAE,WAAW,CAAC,CAAC,GAAG,UAAU,eAAe,EAAE;AAAA,CAAI;AAC5L,aAAO,EAAE,MAAM,WAAW,MAAM,OAAO,IAAI;AAAA,IAC7C;AACA,QAAI,QAAS,OAAM,OAAO,MAAM,UAAU,MAAM,EAAE,GAAG,MAAM,KAAK,CAAU;AAAA,QACrE,OAAM,OAAO,MAAM,YAAY,MAAM,IAAa;AACvD,OAAG,OAAO,WAAW,QAAQ,MAAM,UAAU,QAAQ,WAAW,IAAI,KAAK,GAAG,OAAO,IAAI;AAAA,CAAK;AAC5F,WAAO;AAAA,EACT;AACA,QAAM,IAAI,WAAW,2BAA2B,OAAO,QAAQ,gCAA2B;AAC5F;AAEA,eAAe,eAAe,GAAW,IAAW,QAA4C;AAC9F,QAAM,OAAO,YAAY,GAAG,EAAE;AAC9B,QAAM,MAAM,IAAI,EAAE,OAAO,KAAK;AAC9B,QAAM,aAAa,QAAQ,SAAY,SAAY,OAAO,GAAG;AAC7D,MAAI,eAAe,WAAc,CAAC,OAAO,UAAU,UAAU,KAAK,aAAa,KAAK,aAAa,OAAO;AACtG,UAAM,IAAI,WAAW,wDAAwD;AAAA,EAC/E;AACA,QAAM,IAAI,MAAM,OAAO,MAAM,aAAa,MAAM;AAAA,IAC9C,GAAI,IAAI,EAAE,OAAO,QAAQ,IAAI,EAAE,QAAQ,IAAI,EAAE,OAAO,QAAQ,EAAG,IAAI,CAAC;AAAA,IACpE,GAAI,IAAI,EAAE,OAAO,MAAM,IAAI,EAAE,MAAM,IAAI,EAAE,OAAO,MAAM,EAAG,IAAI,CAAC;AAAA,IAC9D,GAAI,IAAI,EAAE,OAAO,KAAK,IAAI,EAAE,KAAK,IAAI,EAAE,OAAO,KAAK,EAAG,IAAI,CAAC;AAAA,IAC3D,GAAI,eAAe,SAAY,EAAE,WAAW,IAAI,CAAC;AAAA,EACnD,CAAC;AACD,MAAI,EAAE,MAAM,SAAS,MAAM;AACzB,OAAG,OAAO,GAAG,KAAK,UAAU,GAAG,MAAM,CAAC,CAAC;AAAA,CAAI;AAAA,EAC7C,WAAW,EAAE,YAAY;AACvB,OAAG,OAAO,GAAG,EAAE,UAAU;AAAA,CAAI;AAAA,EAC/B,OAAO;AAEL,OAAG,OAAO,GAAG,EAAE,KAAK;AAAA,CAAI;AACxB,OAAG,OAAO,4EAA4E;AAAA,EACxF;AACA,KAAG,OAAO,eAAe,IAAI,KAAK,EAAE,SAAS,EAAE,YAAY,CAAC;AAAA,CAAK;AACjE,MAAI,EAAE,MAAM,SAAS,QAAQ,EAAE,WAAY,OAAM,GAAG,QAAQ,EAAE,UAAU;AACxE,SAAO;AACT;AAEA,eAAe,iBAAiB,KAAyB,GAAW,IAAW,MAAc,KAA8B;AACzH,MAAI,QAAQ,OAAQ,OAAM,IAAI,WAAW,8BAA8B,OAAO,QAAQ,kBAAa;AACnG,QAAM,KAAK,EAAE,WAAW,CAAC;AACzB,MAAI,CAAC,GAAI,OAAM,IAAI,WAAW,6CAA6C;AAC3E,QAAM,QAAQ,IAAI,EAAE,OAAO,OAAO;AAClC,MAAI,CAAC,MAAO,OAAM,IAAI,WAAW,qBAAqB;AAGtD,QAAM,QAAQ,IAAI,EAAE,OAAO,WAAW,KAAK;AAC3C,QAAM,YAAY,UAAU,WAAW,WAAW;AAElD,MAAI;AACF,UAAM,IAAI,MAAM,WAAW,IAAI,MAAM,KAAK,QAAQ,mBAAmB,mBAAmB,EAAE,CAAC,SAAS,EAAE,OAAO,UAAU,CAAC;AACxH,OAAG,OAAO,GAAG,KAAK,UAAU,GAAG,MAAM,CAAC,CAAC;AAAA,CAAI;AAC3C,WAAO;AAAA,EACT,SAAS,GAAG;AAEV,QAAI,aAAa,uBAAuB,EAAE,WAAW,OAAO,EAAE,YAAY,aAAa;AACrF,SAAG,OAAO,wFAAwF;AAClG,aAAO;AAAA,IACT;AACA,UAAM;AAAA,EACR;AACF;AAGA,eAAsB,IAAI,MAAgB,IAA4B;AACpE,MAAI;AACJ,MAAI;AACF,QAAI,UAAU,IAAI;AAAA,EACpB,SAAS,GAAG;AACV,OAAG,OAAO,GAAI,EAAY,OAAO;AAAA;AAAA,EAAO,KAAK,EAAE;AAC/C,WAAO;AAAA,EACT;AACA,QAAM,CAAC,KAAK,GAAG,IAAI,EAAE;AACrB,MAAI,CAAC,OAAO,EAAE,MAAM,SAAS,QAAQ,QAAQ,QAAQ;AACnD,OAAG,OAAO,KAAK;AACf,WAAO,OAAO,EAAE,MAAM,SAAS,OAAO,IAAI;AAAA,EAC5C;AACA,QAAM,MAAM,GAAG,IAAI;AACnB,MAAI,CAAC,KAAK;AACR,OAAG,OAAO,mFAAmF;AAC7F,WAAO;AAAA,EACT;AACA,QAAM,OAAO,GAAG,IAAI,wBAAwB;AAC5C,QAAM,SAAS,kBAAkB,EAAE,QAAQ,KAAK,SAAS,MAAM,OAAO,GAAG,MAAM,CAAC;AAChF,MAAI;AACF,QAAI,QAAQ,SAAU,QAAO,MAAM,cAAc,KAAK,GAAG,IAAI,MAAM;AACnE,QAAI,QAAQ,UAAW,QAAO,MAAM,eAAe,GAAG,IAAI,MAAM;AAChE,QAAI,QAAQ,YAAa,QAAO,MAAM,iBAAiB,KAAK,GAAG,IAAI,MAAM,GAAG;AAC5E,UAAM,IAAI,WAAW,oBAAoB,GAAG,EAAE;AAAA,EAChD,SAAS,GAAG;AACV,QAAI,aAAa,YAAY;AAC3B,SAAG,OAAO,GAAG,EAAE,OAAO;AAAA;AAAA,EAAO,KAAK,EAAE;AACpC,aAAO;AAAA,IACT;AACA,QAAI,aAAa,qBAAqB;AACpC,SAAG,OAAO,aAAa,EAAE,MAAM,GAAG,EAAE,OAAO,KAAK,EAAE,IAAI,MAAM,EAAE,KAAK,EAAE,OAAO;AAAA,CAAI;AAChF,aAAO;AAAA,IACT;AACA,OAAG,OAAO,GAAI,EAAY,WAAW,OAAO,CAAC,CAAC;AAAA,CAAI;AAClD,WAAO;AAAA,EACT;AACF;","names":[]}