@guidelytech/flags 0.0.0-stage → 0.8.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/cli.cjs ADDED
@@ -0,0 +1,2010 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
6
+
7
+ // src/cli-main.ts
8
+ var import_node_path2 = require("path");
9
+
10
+ // src/hash.ts
11
+ function utf8(input) {
12
+ const out = [];
13
+ for (let i = 0; i < input.length; i++) {
14
+ let c = input.charCodeAt(i);
15
+ if (c >= 55296 && c <= 56319 && i + 1 < input.length) {
16
+ const d = input.charCodeAt(i + 1);
17
+ if (d >= 56320 && d <= 57343) {
18
+ c = 65536 + (c - 55296 << 10) + (d - 56320);
19
+ i++;
20
+ }
21
+ }
22
+ if (c >= 55296 && c <= 57343) c = 65533;
23
+ if (c < 128) out.push(c);
24
+ else if (c < 2048) out.push(192 | c >> 6, 128 | c & 63);
25
+ else if (c < 65536) out.push(224 | c >> 12, 128 | c >> 6 & 63, 128 | c & 63);
26
+ else out.push(240 | c >> 18, 128 | c >> 12 & 63, 128 | c >> 6 & 63, 128 | c & 63);
27
+ }
28
+ return Uint8Array.from(out);
29
+ }
30
+ function decodeUtf8(bytes) {
31
+ if (typeof TextDecoder !== "undefined") return new TextDecoder().decode(bytes);
32
+ let s = "";
33
+ for (let i = 0; i < bytes.length; ) {
34
+ const b = bytes[i++];
35
+ let c;
36
+ if (b < 128) c = b;
37
+ else if (b < 224) c = (b & 31) << 6 | bytes[i++] & 63;
38
+ else if (b < 240) c = (b & 15) << 12 | (bytes[i++] & 63) << 6 | bytes[i++] & 63;
39
+ else c = (b & 7) << 18 | (bytes[i++] & 63) << 12 | (bytes[i++] & 63) << 6 | bytes[i++] & 63;
40
+ s += String.fromCodePoint(c);
41
+ }
42
+ return s;
43
+ }
44
+ function fnv1a32(bytes) {
45
+ let h = 2166136261;
46
+ for (let i = 0; i < bytes.length; i++) {
47
+ h ^= bytes[i];
48
+ h = Math.imul(h, 16777619) >>> 0;
49
+ }
50
+ return h >>> 0;
51
+ }
52
+ function hashBucket(seed, value) {
53
+ const inner = fnv1a32(utf8(seed + value));
54
+ return fnv1a32(utf8(String(inner))) % 1e4 / 1e4;
55
+ }
56
+ var K = new Uint32Array([
57
+ 1116352408,
58
+ 1899447441,
59
+ 3049323471,
60
+ 3921009573,
61
+ 961987163,
62
+ 1508970993,
63
+ 2453635748,
64
+ 2870763221,
65
+ 3624381080,
66
+ 310598401,
67
+ 607225278,
68
+ 1426881987,
69
+ 1925078388,
70
+ 2162078206,
71
+ 2614888103,
72
+ 3248222580,
73
+ 3835390401,
74
+ 4022224774,
75
+ 264347078,
76
+ 604807628,
77
+ 770255983,
78
+ 1249150122,
79
+ 1555081692,
80
+ 1996064986,
81
+ 2554220882,
82
+ 2821834349,
83
+ 2952996808,
84
+ 3210313671,
85
+ 3336571891,
86
+ 3584528711,
87
+ 113926993,
88
+ 338241895,
89
+ 666307205,
90
+ 773529912,
91
+ 1294757372,
92
+ 1396182291,
93
+ 1695183700,
94
+ 1986661051,
95
+ 2177026350,
96
+ 2456956037,
97
+ 2730485921,
98
+ 2820302411,
99
+ 3259730800,
100
+ 3345764771,
101
+ 3516065817,
102
+ 3600352804,
103
+ 4094571909,
104
+ 275423344,
105
+ 430227734,
106
+ 506948616,
107
+ 659060556,
108
+ 883997877,
109
+ 958139571,
110
+ 1322822218,
111
+ 1537002063,
112
+ 1747873779,
113
+ 1955562222,
114
+ 2024104815,
115
+ 2227730452,
116
+ 2361852424,
117
+ 2428436474,
118
+ 2756734187,
119
+ 3204031479,
120
+ 3329325298
121
+ ]);
122
+ function sha256Hex(input) {
123
+ const msg = typeof input === "string" ? utf8(input) : input;
124
+ const bitLen = msg.length * 8;
125
+ const padded = new Uint8Array(msg.length + 9 + 63 >> 6 << 6);
126
+ padded.set(msg);
127
+ padded[msg.length] = 128;
128
+ const view = new DataView(padded.buffer);
129
+ view.setUint32(padded.length - 8, Math.floor(bitLen / 4294967296));
130
+ view.setUint32(padded.length - 4, bitLen >>> 0);
131
+ const h = new Uint32Array([1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225]);
132
+ const w = new Uint32Array(64);
133
+ for (let off = 0; off < padded.length; off += 64) {
134
+ for (let i = 0; i < 16; i++) w[i] = view.getUint32(off + i * 4);
135
+ for (let i = 16; i < 64; i++) {
136
+ const a2 = w[i - 15], b2 = w[i - 2];
137
+ const s0 = (a2 >>> 7 | a2 << 25) ^ (a2 >>> 18 | a2 << 14) ^ a2 >>> 3;
138
+ const s1 = (b2 >>> 17 | b2 << 15) ^ (b2 >>> 19 | b2 << 13) ^ b2 >>> 10;
139
+ w[i] = w[i - 16] + s0 + w[i - 7] + s1 >>> 0;
140
+ }
141
+ let a = h[0], b = h[1], c = h[2], d = h[3], e = h[4], f = h[5], g = h[6], hh = h[7];
142
+ for (let i = 0; i < 64; i++) {
143
+ const S1 = (e >>> 6 | e << 26) ^ (e >>> 11 | e << 21) ^ (e >>> 25 | e << 7);
144
+ const ch = e & f ^ ~e & g;
145
+ const t1 = hh + S1 + ch + K[i] + w[i] >>> 0;
146
+ const S0 = (a >>> 2 | a << 30) ^ (a >>> 13 | a << 19) ^ (a >>> 22 | a << 10);
147
+ const maj = a & b ^ a & c ^ b & c;
148
+ const t2 = S0 + maj >>> 0;
149
+ hh = g;
150
+ g = f;
151
+ f = e;
152
+ e = d + t1 >>> 0;
153
+ d = c;
154
+ c = b;
155
+ b = a;
156
+ a = t1 + t2 >>> 0;
157
+ }
158
+ h[0] = h[0] + a >>> 0;
159
+ h[1] = h[1] + b >>> 0;
160
+ h[2] = h[2] + c >>> 0;
161
+ h[3] = h[3] + d >>> 0;
162
+ h[4] = h[4] + e >>> 0;
163
+ h[5] = h[5] + f >>> 0;
164
+ h[6] = h[6] + g >>> 0;
165
+ h[7] = h[7] + hh >>> 0;
166
+ }
167
+ let hex = "";
168
+ for (let i = 0; i < 8; i++) hex += h[i].toString(16).padStart(8, "0");
169
+ return hex;
170
+ }
171
+ function hashConfidential(salt, value) {
172
+ if (typeof value === "string") return sha256Hex(salt + value);
173
+ const s = utf8(salt);
174
+ const joined = new Uint8Array(s.length + value.length);
175
+ joined.set(s);
176
+ joined.set(value, s.length);
177
+ return sha256Hex(joined);
178
+ }
179
+
180
+ // src/crypto.ts
181
+ var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
182
+ function base64ToBytes(input) {
183
+ const s = input.trim().replace(/-/g, "+").replace(/_/g, "/").replace(/=+$/, "");
184
+ const out = new Uint8Array(Math.floor(s.length * 3 / 4));
185
+ let buffer = 0;
186
+ let bits = 0;
187
+ let n = 0;
188
+ for (let i = 0; i < s.length; i++) {
189
+ const v = ALPHABET.indexOf(s[i]);
190
+ if (v < 0) throw new Error("invalid base64");
191
+ buffer = buffer << 6 | v;
192
+ bits += 6;
193
+ if (bits >= 8) {
194
+ bits -= 8;
195
+ out[n++] = buffer >> bits & 255;
196
+ }
197
+ }
198
+ return out.subarray(0, n);
199
+ }
200
+ async function decryptPayloadString(encrypted, decryptionKey) {
201
+ const subtle = globalThis.crypto?.subtle;
202
+ if (!subtle) throw new Error("WebCrypto (crypto.subtle) is not available in this runtime; encrypted payloads need it (Node \u2265 19, browsers, edge runtimes, or a polyfill)");
203
+ const [ivText, dataText] = encrypted.split(".");
204
+ if (!ivText || !dataText) throw new Error("malformed encrypted payload");
205
+ const key = base64ToBytes(decryptionKey);
206
+ if (key.length !== 32) throw new Error("decryptionKey must be base64 of 32 bytes");
207
+ const cryptoKey = await subtle.importKey("raw", key, { name: "AES-GCM" }, false, ["decrypt"]);
208
+ const plain = await subtle.decrypt({ name: "AES-GCM", iv: base64ToBytes(ivText) }, cryptoKey, base64ToBytes(dataText));
209
+ return decodeUtf8(new Uint8Array(plain));
210
+ }
211
+
212
+ // src/util.ts
213
+ function stableStringify(value) {
214
+ if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
215
+ if (Array.isArray(value)) return `[${value.map((v) => v === void 0 ? "null" : stableStringify(v)).join(",")}]`;
216
+ if (value instanceof Date) return JSON.stringify(value.toISOString());
217
+ const obj = value;
218
+ return `{${Object.keys(obj).filter((k) => obj[k] !== void 0).sort().map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(",")}}`;
219
+ }
220
+ function isEncryptedPayload(x) {
221
+ return !!x && typeof x === "object" && typeof x.encryptedPayload === "string";
222
+ }
223
+ function isRemoteResults(x) {
224
+ return !!x && typeof x === "object" && !("schemaVersion" in x) && !!x.flags && typeof x.flags === "object";
225
+ }
226
+ function isFlagsPayload(x) {
227
+ return !!x && typeof x === "object" && "schemaVersion" in x && !!x.flags && typeof x.flags === "object";
228
+ }
229
+ function kindOf(v) {
230
+ if (v === null || v === void 0) return "null";
231
+ if (Array.isArray(v)) return "array";
232
+ return typeof v;
233
+ }
234
+ function compact(attrs) {
235
+ const out = {};
236
+ for (const [k, v] of Object.entries(attrs)) if (v !== void 0) out[k] = v;
237
+ return out;
238
+ }
239
+ function parseUrlOverrides(search, param = "guidely_flags") {
240
+ let raw = null;
241
+ try {
242
+ raw = new URLSearchParams(search.startsWith("?") ? search.slice(1) : search).get(param);
243
+ } catch {
244
+ return {};
245
+ }
246
+ return raw && parseOverrides(raw) || {};
247
+ }
248
+ function parseOverrides(text) {
249
+ const raw = text.trim();
250
+ if (raw.startsWith("{")) {
251
+ try {
252
+ const obj = JSON.parse(raw);
253
+ return obj && typeof obj === "object" && !Array.isArray(obj) ? obj : null;
254
+ } catch {
255
+ return null;
256
+ }
257
+ }
258
+ const out = {};
259
+ const items = [];
260
+ let depth = 0;
261
+ let quoted = false;
262
+ let start = 0;
263
+ for (let i = 0; i < raw.length; i++) {
264
+ const c = raw[i];
265
+ if (quoted) {
266
+ if (c === "\\") i++;
267
+ else if (c === '"') quoted = false;
268
+ } else if (c === '"') quoted = true;
269
+ else if (c === "[" || c === "{") depth++;
270
+ else if (c === "]" || c === "}") depth = Math.max(0, depth - 1);
271
+ else if ((c === "," || c === "\n") && depth === 0) {
272
+ items.push(raw.slice(start, i));
273
+ start = i + 1;
274
+ }
275
+ }
276
+ items.push(raw.slice(start));
277
+ for (const item of items) {
278
+ const sep2 = item.indexOf(":");
279
+ if (sep2 <= 0) continue;
280
+ const key = item.slice(0, sep2).trim();
281
+ const text2 = item.slice(sep2 + 1).trim();
282
+ if (!key) continue;
283
+ try {
284
+ out[key] = JSON.parse(text2);
285
+ } catch {
286
+ out[key] = text2;
287
+ }
288
+ }
289
+ return out;
290
+ }
291
+ function isDev() {
292
+ try {
293
+ const p = globalThis.process;
294
+ return !!p?.env && p.env.NODE_ENV !== "production";
295
+ } catch {
296
+ return false;
297
+ }
298
+ }
299
+ function timer(fn, ms) {
300
+ const t = setTimeout(fn, ms);
301
+ t.unref?.();
302
+ return t;
303
+ }
304
+
305
+ // src/dev-overrides.ts
306
+ var OVERRIDES_ENV = "GUIDELY_FLAG_OVERRIDES";
307
+ var OVERRIDES_FILE_ENV = "GUIDELY_FLAG_OVERRIDES_FILE";
308
+ var OVERRIDE_BEHAVIOUR_ENV = "GUIDELY_FLAG_OVERRIDE_BEHAVIOUR";
309
+ var OVERRIDES_GLOBAL = "__GUIDELY_FLAG_OVERRIDES__";
310
+ var OVERRIDE_BEHAVIOUR_GLOBAL = "__GUIDELY_FLAG_OVERRIDE_BEHAVIOUR__";
311
+ var BEHAVIOURS = ["localOnly", "localOverRemote", "remoteOverLocal"];
312
+ var runtime = () => globalThis;
313
+ function env(name) {
314
+ const g = runtime();
315
+ try {
316
+ const v = g.process?.env?.[name];
317
+ if (v !== void 0) return v;
318
+ } catch {
319
+ }
320
+ try {
321
+ return g.Deno?.env?.get(name);
322
+ } catch {
323
+ return void 0;
324
+ }
325
+ }
326
+ function readFile(path) {
327
+ const g = runtime();
328
+ const fs = g.process?.getBuiltinModule?.("node:fs");
329
+ if (fs) return fs.readFileSync(path, "utf8");
330
+ if (g.Deno?.readTextFileSync) return g.Deno.readTextFileSync(path);
331
+ throw new Error("this runtime cannot read files");
332
+ }
333
+ function behaviourOf(raw, source, out) {
334
+ if (typeof raw !== "string" || !raw.trim()) return;
335
+ const b = raw.trim();
336
+ if (BEHAVIOURS.includes(b)) {
337
+ out.behaviour = b;
338
+ out.sources.push(source);
339
+ } else out.errors.push(`${source} must be localOnly, localOverRemote or remoteOverLocal (got "${raw}"); ignoring it.`);
340
+ }
341
+ function merge(text, source, out) {
342
+ if (text === void 0 || !text.trim()) return;
343
+ const values = parseOverrides(text);
344
+ if (!values) return void out.errors.push(`${source} is not a JSON object or a key:value list; ignoring it.`);
345
+ if (!Object.keys(values).length) return;
346
+ Object.assign(out.values, values);
347
+ out.sources.push(source);
348
+ }
349
+ function readDevOverrides() {
350
+ const out = { values: {}, sources: [], errors: [] };
351
+ const file = env(OVERRIDES_FILE_ENV);
352
+ if (file?.trim()) {
353
+ try {
354
+ merge(readFile(file.trim()), `${OVERRIDES_FILE_ENV} (${file.trim()})`, out);
355
+ } catch (err) {
356
+ out.errors.push(`Could not read ${OVERRIDES_FILE_ENV} (${file.trim()}): ${err instanceof Error ? err.message : String(err)}; ignoring it.`);
357
+ }
358
+ }
359
+ merge(env(OVERRIDES_ENV), OVERRIDES_ENV, out);
360
+ const g = runtime()[OVERRIDES_GLOBAL];
361
+ if (g && typeof g === "object" && !Array.isArray(g)) {
362
+ if (Object.keys(g).length) {
363
+ Object.assign(out.values, g);
364
+ out.sources.push(OVERRIDES_GLOBAL);
365
+ }
366
+ } else if (typeof g === "string") merge(g, OVERRIDES_GLOBAL, out);
367
+ else if (g !== void 0 && g !== null) out.errors.push(`${OVERRIDES_GLOBAL} must be an object or overrides text; ignoring it.`);
368
+ behaviourOf(env(OVERRIDE_BEHAVIOUR_ENV), OVERRIDE_BEHAVIOUR_ENV, out);
369
+ behaviourOf(runtime()[OVERRIDE_BEHAVIOUR_GLOBAL], OVERRIDE_BEHAVIOUR_GLOBAL, out);
370
+ return out;
371
+ }
372
+
373
+ // src/values.ts
374
+ function textOf(v) {
375
+ if (typeof v === "string") return v;
376
+ if (typeof v === "boolean") return v ? "true" : "false";
377
+ if (typeof v === "number") {
378
+ if (!Number.isFinite(v)) return void 0;
379
+ if (Number.isInteger(v) && Math.abs(v) < 1e21) return v.toFixed(0);
380
+ return String(v);
381
+ }
382
+ if (typeof v === "bigint") return v.toString();
383
+ return void 0;
384
+ }
385
+ var NUMERIC = /^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/;
386
+ function numberOf(v) {
387
+ if (typeof v === "number") return Number.isFinite(v) ? v : void 0;
388
+ if (typeof v === "string") {
389
+ const s = v.trim();
390
+ if (!NUMERIC.test(s)) return void 0;
391
+ const n = Number(s);
392
+ return Number.isFinite(n) ? n : void 0;
393
+ }
394
+ return void 0;
395
+ }
396
+ function arrayOf(v) {
397
+ if (Array.isArray(v)) return v;
398
+ if (typeof v === "string") {
399
+ const s = v.trim();
400
+ if (!s.startsWith("[")) return void 0;
401
+ try {
402
+ const parsed = JSON.parse(s);
403
+ return Array.isArray(parsed) ? parsed : void 0;
404
+ } catch {
405
+ return void 0;
406
+ }
407
+ }
408
+ return void 0;
409
+ }
410
+ var SEMVER = /^[vV]?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
411
+ function parseSemver(v) {
412
+ const s = typeof v === "number" ? textOf(v) : typeof v === "string" ? v.trim() : void 0;
413
+ if (s === void 0) return void 0;
414
+ const m = SEMVER.exec(s);
415
+ if (!m) return void 0;
416
+ return { major: Number(m[1]), minor: Number(m[2] ?? 0), patch: Number(m[3] ?? 0), pre: m[4] ? m[4].split(".") : [] };
417
+ }
418
+ function cmp(a, b) {
419
+ return a < b ? -1 : a > b ? 1 : 0;
420
+ }
421
+ function compareSemver(a, b) {
422
+ const c = cmp(a.major, b.major) || cmp(a.minor, b.minor) || cmp(a.patch, b.patch);
423
+ if (c) return c;
424
+ if (!a.pre.length || !b.pre.length) return a.pre.length ? -1 : b.pre.length ? 1 : 0;
425
+ for (let i = 0; i < Math.max(a.pre.length, b.pre.length); i++) {
426
+ const x = a.pre[i], y = b.pre[i];
427
+ if (x === void 0) return -1;
428
+ if (y === void 0) return 1;
429
+ const xn = /^\d+$/.test(x), yn = /^\d+$/.test(y);
430
+ if (xn && yn) {
431
+ const d = cmp(Number(x), Number(y));
432
+ if (d) return d;
433
+ } else if (xn) return -1;
434
+ else if (yn) return 1;
435
+ else if (x !== y) return x < y ? -1 : 1;
436
+ }
437
+ return 0;
438
+ }
439
+ function daysInMonth(year, month) {
440
+ if (month === 2) return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0 ? 29 : 28;
441
+ return [4, 6, 9, 11].includes(month) ? 30 : 31;
442
+ }
443
+ function daysFromCivil(year, month, day) {
444
+ const y = month <= 2 ? year - 1 : year;
445
+ const era = Math.floor(y / 400);
446
+ const yoe = y - era * 400;
447
+ const doy = Math.floor((153 * (month + (month > 2 ? -3 : 9)) + 2) / 5) + day - 1;
448
+ const doe = yoe * 365 + Math.floor(yoe / 4) - Math.floor(yoe / 100) + doy;
449
+ return era * 146097 + doe - 719468;
450
+ }
451
+ var ISO = /^(\d{4})-(\d{2})-(\d{2})(?:[Tt ](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?(Z|z|[+-]\d{2}:\d{2})?)?$/;
452
+ function instantOf(v) {
453
+ if (v instanceof Date) return Number.isNaN(v.getTime()) ? void 0 : v.getTime();
454
+ if (typeof v === "number") return Number.isFinite(v) ? v * 1e3 : void 0;
455
+ if (typeof v !== "string") return void 0;
456
+ const m = ISO.exec(v.trim());
457
+ if (!m) return void 0;
458
+ const [, y, mo, d, h = "0", mi = "0", s = "0", frac, tz] = m;
459
+ const month = Number(mo), day = Number(d), hour = Number(h), minute = Number(mi), second = Number(s);
460
+ const year = Number(y);
461
+ if (month < 1 || month > 12 || day < 1 || day > daysInMonth(year, month) || hour > 23 || minute > 59 || second > 59) return void 0;
462
+ let ms = (daysFromCivil(year, month, day) * 86400 + hour * 3600 + minute * 60 + second) * 1e3 + (frac ? Math.floor(Number(`0.${frac}`) * 1e3) : 0);
463
+ if (tz && tz !== "Z" && tz !== "z") {
464
+ const sign = tz[0] === "-" ? -1 : 1;
465
+ ms -= sign * (Number(tz.slice(1, 3)) * 60 + Number(tz.slice(4, 6))) * 6e4;
466
+ }
467
+ return ms;
468
+ }
469
+ function jsonEquals(a, b) {
470
+ if (a === b) return true;
471
+ if (a === null || b === null || a === void 0 || b === void 0) return (a ?? null) === (b ?? null);
472
+ if (typeof a === "number" && typeof b === "number") return a === b;
473
+ if (Array.isArray(a)) return Array.isArray(b) && a.length === b.length && a.every((x, i) => jsonEquals(x, b[i]));
474
+ if (typeof a === "object" && typeof b === "object" && !Array.isArray(b)) {
475
+ const ka = Object.keys(a).filter((k) => a[k] !== void 0);
476
+ const kb = Object.keys(b).filter((k) => b[k] !== void 0);
477
+ return ka.length === kb.length && ka.every((k) => Object.prototype.hasOwnProperty.call(b, k) && jsonEquals(a[k], b[k]));
478
+ }
479
+ return false;
480
+ }
481
+ function isOn(v) {
482
+ return !(v === null || v === void 0 || v === false || v === 0 || v === "");
483
+ }
484
+ function jsonTypeOf(v) {
485
+ if (v === null || v === void 0) return void 0;
486
+ if (Array.isArray(v)) return "array";
487
+ if (typeof v === "object") return "object";
488
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") return typeof v;
489
+ return void 0;
490
+ }
491
+
492
+ // src/evaluate.ts
493
+ var MAX_DEPTH = 32;
494
+ var CycleError = class extends Error {
495
+ };
496
+ function nowOf(now) {
497
+ if (now === void 0) return Date.now();
498
+ if (typeof now === "number") return now;
499
+ if (now instanceof Date) return now.getTime();
500
+ return instantOf(now) ?? Date.now();
501
+ }
502
+ function lookupAttribute(attributes, name) {
503
+ if (Object.prototype.hasOwnProperty.call(attributes, name)) return attributes[name] ?? void 0;
504
+ if (!name.includes(".")) return void 0;
505
+ let cur = attributes;
506
+ for (const part of name.split(".")) {
507
+ if (cur && typeof cur === "object" && !Array.isArray(cur) && Object.prototype.hasOwnProperty.call(cur, part)) cur = cur[part];
508
+ else return void 0;
509
+ }
510
+ return cur ?? void 0;
511
+ }
512
+ function hashValueOf(attributes, name) {
513
+ const t = textOf(lookupAttribute(attributes, name));
514
+ return t === void 0 || t === "" ? void 0 : t;
515
+ }
516
+ function isScheduleActive(schedule, now) {
517
+ if (!schedule) return true;
518
+ if (schedule.start) {
519
+ const start = instantOf(schedule.start);
520
+ if (start === void 0 || now < start) return false;
521
+ }
522
+ if (schedule.end) {
523
+ const end = instantOf(schedule.end);
524
+ if (end === void 0 || now >= end) return false;
525
+ }
526
+ return true;
527
+ }
528
+ function clamp01(n) {
529
+ return typeof n === "number" && Number.isFinite(n) ? Math.min(1, Math.max(0, n)) : 0;
530
+ }
531
+ function effectiveCoverage(rule, now) {
532
+ let coverage = clamp01(rule.coverage);
533
+ let latest = -Infinity;
534
+ for (const step of rule.ramp ?? []) {
535
+ const at = instantOf(step.at);
536
+ if (at !== void 0 && at <= now && at >= latest) {
537
+ latest = at;
538
+ coverage = clamp01(step.coverage);
539
+ }
540
+ }
541
+ return coverage;
542
+ }
543
+ var regexCache = /* @__PURE__ */ new Map();
544
+ function compile(pattern, ci) {
545
+ const k = `${ci ? "i" : "-"}:${pattern}`;
546
+ let re = regexCache.get(k);
547
+ if (re === void 0) {
548
+ try {
549
+ re = new RegExp(pattern, ci ? "i" : "");
550
+ } catch {
551
+ re = null;
552
+ }
553
+ if (regexCache.size > 500) regexCache.clear();
554
+ regexCache.set(k, re);
555
+ }
556
+ return re;
557
+ }
558
+ function lower(s, ci) {
559
+ return ci ? s.toLowerCase() : s;
560
+ }
561
+ function textValues(values, ci) {
562
+ const out = [];
563
+ for (const v of values) {
564
+ const t = textOf(v);
565
+ if (t !== void 0) out.push(lower(t, ci));
566
+ }
567
+ return out;
568
+ }
569
+ function affixMatches(salt, text, values, suffix) {
570
+ const bytes = utf8(text);
571
+ return values.some((v) => {
572
+ if (typeof v !== "string") return false;
573
+ const sep2 = v.indexOf("_");
574
+ if (sep2 <= 0) return false;
575
+ const n = Number(v.slice(0, sep2));
576
+ if (!Number.isInteger(n) || n < 0 || n > bytes.length) return false;
577
+ const part = suffix ? bytes.slice(bytes.length - n) : bytes.slice(0, n);
578
+ return hashConfidential(salt, part) === v.slice(sep2 + 1);
579
+ });
580
+ }
581
+ function evalAttribute(ctx, c) {
582
+ const raw = lookupAttribute(ctx.attributes, c.attribute);
583
+ const values = Array.isArray(c.values) ? c.values : [];
584
+ const op = c.operator;
585
+ if (op === "notExists") return raw === void 0;
586
+ if (raw === void 0) return false;
587
+ const ci = c.caseInsensitive;
588
+ switch (op) {
589
+ case "exists":
590
+ return true;
591
+ case "isType":
592
+ return jsonTypeOf(raw) === values[0];
593
+ case "isTrue":
594
+ return raw === true || typeof raw === "string" && raw.toLowerCase() === "true";
595
+ case "isFalse":
596
+ return raw === false || typeof raw === "string" && raw.toLowerCase() === "false";
597
+ case "equals":
598
+ case "notEquals":
599
+ case "in":
600
+ case "notIn":
601
+ case "contains":
602
+ case "notContains":
603
+ case "startsWith":
604
+ case "notStartsWith":
605
+ case "endsWith":
606
+ case "notEndsWith": {
607
+ const t0 = textOf(raw);
608
+ if (t0 === void 0) return false;
609
+ const t = lower(t0, ci);
610
+ const vs = textValues(values, ci);
611
+ switch (op) {
612
+ case "equals":
613
+ return vs.length > 0 && t === vs[0];
614
+ case "notEquals":
615
+ return vs.length > 0 && t !== vs[0];
616
+ case "in":
617
+ return vs.includes(t);
618
+ case "notIn":
619
+ return !vs.includes(t);
620
+ case "contains":
621
+ return vs.some((v) => t.includes(v));
622
+ case "notContains":
623
+ return !vs.some((v) => t.includes(v));
624
+ case "startsWith":
625
+ return vs.some((v) => t.startsWith(v));
626
+ case "notStartsWith":
627
+ return !vs.some((v) => t.startsWith(v));
628
+ case "endsWith":
629
+ return vs.some((v) => t.endsWith(v));
630
+ default:
631
+ return !vs.some((v) => t.endsWith(v));
632
+ }
633
+ }
634
+ case "regex":
635
+ case "notRegex": {
636
+ const t = textOf(raw);
637
+ if (t === void 0) return false;
638
+ const res = [];
639
+ for (const v of values) {
640
+ const re = typeof v === "string" ? compile(v, !!ci) : null;
641
+ if (!re) return false;
642
+ res.push(re);
643
+ }
644
+ const any = res.some((re) => re.test(t));
645
+ return op === "regex" ? any : !any;
646
+ }
647
+ case "numEquals":
648
+ case "numNotEquals":
649
+ case "gt":
650
+ case "gte":
651
+ case "lt":
652
+ case "lte": {
653
+ const a = numberOf(raw);
654
+ const b = numberOf(values[0]);
655
+ if (a === void 0 || b === void 0) return false;
656
+ if (op === "numEquals") return a === b;
657
+ if (op === "numNotEquals") return a !== b;
658
+ if (op === "gt") return a > b;
659
+ if (op === "gte") return a >= b;
660
+ if (op === "lt") return a < b;
661
+ return a <= b;
662
+ }
663
+ case "semverEquals":
664
+ case "semverNotEquals":
665
+ case "semverGt":
666
+ case "semverGte":
667
+ case "semverLt":
668
+ case "semverLte":
669
+ case "semverIn":
670
+ case "semverNotIn": {
671
+ const a = parseSemver(raw);
672
+ if (!a) return false;
673
+ const bs = values.map(parseSemver);
674
+ if (bs.some((b) => !b)) return false;
675
+ if (op === "semverIn") return bs.some((b) => compareSemver(a, b) === 0);
676
+ if (op === "semverNotIn") return !bs.some((b) => compareSemver(a, b) === 0);
677
+ if (!bs[0]) return false;
678
+ const d = compareSemver(a, bs[0]);
679
+ if (op === "semverEquals") return d === 0;
680
+ if (op === "semverNotEquals") return d !== 0;
681
+ if (op === "semverGt") return d > 0;
682
+ if (op === "semverGte") return d >= 0;
683
+ if (op === "semverLt") return d < 0;
684
+ return d <= 0;
685
+ }
686
+ case "before":
687
+ case "after": {
688
+ const a = instantOf(raw);
689
+ const b = instantOf(values[0]);
690
+ if (a === void 0 || b === void 0) return false;
691
+ return op === "before" ? a < b : a > b;
692
+ }
693
+ case "arrayContainsAny":
694
+ case "arrayNotContainsAny":
695
+ case "arrayContainsAll": {
696
+ const arr = arrayOf(raw);
697
+ if (!arr) return false;
698
+ const elems = textValues(arr, ci);
699
+ const vs = textValues(values, ci);
700
+ if (op === "arrayContainsAll") return vs.every((v) => elems.includes(v));
701
+ const any = vs.some((v) => elems.includes(v));
702
+ return op === "arrayContainsAny" ? any : !any;
703
+ }
704
+ case "equalsHashed":
705
+ case "notEqualsHashed":
706
+ case "inHashed":
707
+ case "notInHashed":
708
+ case "startsWithHashed":
709
+ case "notStartsWithHashed":
710
+ case "endsWithHashed":
711
+ case "notEndsWithHashed": {
712
+ const t = textOf(raw);
713
+ if (t === void 0) return false;
714
+ const salt = ctx.payload.salt ?? "";
715
+ if (op === "startsWithHashed" || op === "notStartsWithHashed" || op === "endsWithHashed" || op === "notEndsWithHashed") {
716
+ const any = affixMatches(salt, t, values, op === "endsWithHashed" || op === "notEndsWithHashed");
717
+ return op.startsWith("not") ? !any : any;
718
+ }
719
+ const h = hashConfidential(salt, t);
720
+ if (op === "equalsHashed") return typeof values[0] === "string" && h === values[0];
721
+ if (op === "notEqualsHashed") return typeof values[0] === "string" && h !== values[0];
722
+ if (op === "inHashed") return values.includes(h);
723
+ return !values.includes(h);
724
+ }
725
+ case "arrayContainsAnyHashed":
726
+ case "arrayNotContainsAnyHashed": {
727
+ const arr = arrayOf(raw);
728
+ if (!arr) return false;
729
+ const salt = ctx.payload.salt ?? "";
730
+ const any = arr.some((e) => {
731
+ const t = textOf(e);
732
+ return t !== void 0 && values.includes(hashConfidential(salt, t));
733
+ });
734
+ return op === "arrayContainsAnyHashed" ? any : !any;
735
+ }
736
+ default:
737
+ return false;
738
+ }
739
+ }
740
+ function enter(ctx, frame, fn) {
741
+ if (ctx.stack.includes(frame)) throw new CycleError(`Cycle detected: ${[...ctx.stack, frame].join(" \u2192 ")}`);
742
+ if (ctx.stack.length >= MAX_DEPTH) throw new CycleError(`Flag references nest deeper than ${MAX_DEPTH} levels: ${ctx.stack.join(" \u2192 ")}`);
743
+ ctx.stack.push(frame);
744
+ try {
745
+ return fn();
746
+ } finally {
747
+ ctx.stack.pop();
748
+ }
749
+ }
750
+ function segmentMatches(ctx, key) {
751
+ const seg = ctx.payload.segments?.[key];
752
+ if (!seg) return void 0;
753
+ return enter(ctx, `segment:${key}`, () => {
754
+ if (seg.kind === "list") {
755
+ const t = textOf(lookupAttribute(ctx.attributes, seg.attribute));
756
+ if (t === void 0) return false;
757
+ const needle = seg.hashed ? hashConfidential(ctx.payload.salt ?? "", t) : t;
758
+ return Array.isArray(seg.values) && seg.values.includes(needle);
759
+ }
760
+ if (seg.kind === "conditions") return allMatch(ctx, seg.conditions);
761
+ return false;
762
+ });
763
+ }
764
+ function compareFlag(op, actual, expected) {
765
+ switch (op) {
766
+ case "isOn":
767
+ return isOn(actual);
768
+ case "isOff":
769
+ return !isOn(actual);
770
+ case "equals":
771
+ return jsonEquals(actual, expected ?? null);
772
+ case "notEquals":
773
+ return !jsonEquals(actual, expected ?? null);
774
+ default:
775
+ return false;
776
+ }
777
+ }
778
+ function referencedValue(ctx, key) {
779
+ const res = evalFlag(ctx, key);
780
+ return res.reason === "FLAG_NOT_FOUND" ? void 0 : { value: res.value };
781
+ }
782
+ function conditionMatches(ctx, c) {
783
+ if (!c || typeof c !== "object") return false;
784
+ switch (c.kind) {
785
+ case "attribute":
786
+ return evalAttribute(ctx, c);
787
+ case "segment": {
788
+ const m = segmentMatches(ctx, c.segment);
789
+ if (m === void 0) return false;
790
+ if (c.operator === "in") return m;
791
+ if (c.operator === "notIn") return !m;
792
+ return false;
793
+ }
794
+ case "flag": {
795
+ const ref = referencedValue(ctx, c.flag);
796
+ return ref ? compareFlag(c.operator, ref.value, c.value) : false;
797
+ }
798
+ case "group": {
799
+ const list = Array.isArray(c.conditions) ? c.conditions : [];
800
+ let r;
801
+ if (c.op === "and") r = allMatch(ctx, list);
802
+ else if (c.op === "or") r = list.some((x) => conditionMatches(ctx, x));
803
+ else return false;
804
+ return c.negate ? !r : r;
805
+ }
806
+ default:
807
+ return false;
808
+ }
809
+ }
810
+ function allMatch(ctx, conditions) {
811
+ if (!conditions) return true;
812
+ for (const c of conditions) if (!conditionMatches(ctx, c)) return false;
813
+ return true;
814
+ }
815
+ function base(flag, value, reason) {
816
+ return { key: flag.key, value: value === void 0 ? null : value, reason, inExperiment: false };
817
+ }
818
+ function stickyDocKey(attribute, value) {
819
+ return `${attribute}||${value}`;
820
+ }
821
+ function runExperiment(ctx, flag, rule) {
822
+ const variations = Array.isArray(rule.variations) ? rule.variations : [];
823
+ if (!variations.length) return void 0;
824
+ let hashAttribute = rule.hashAttribute;
825
+ let hv = hashValueOf(ctx.attributes, rule.hashAttribute);
826
+ const fallbackValue = rule.fallbackAttribute ? hashValueOf(ctx.attributes, rule.fallbackAttribute) : void 0;
827
+ if (hv === void 0 && rule.fallbackAttribute && fallbackValue !== void 0) {
828
+ hashAttribute = rule.fallbackAttribute;
829
+ hv = fallbackValue;
830
+ }
831
+ if (hv === void 0) return void 0;
832
+ const bucketVersion = rule.bucketVersion ?? 0;
833
+ const useSticky = !!ctx.sticky && !rule.disableStickyBucketing;
834
+ let index = -1;
835
+ const bucket = hashBucket(rule.seed, hv);
836
+ let stickyUsed = false;
837
+ if (useSticky) {
838
+ const merged = {};
839
+ if (rule.fallbackAttribute && fallbackValue !== void 0) Object.assign(merged, ctx.sticky.get(stickyDocKey(rule.fallbackAttribute, fallbackValue)) ?? {});
840
+ const primary = hashValueOf(ctx.attributes, rule.hashAttribute);
841
+ if (primary !== void 0) Object.assign(merged, ctx.sticky.get(stickyDocKey(rule.hashAttribute, primary)) ?? {});
842
+ const prefix = `${rule.key}__`;
843
+ const minVersion = rule.minBucketVersion ?? 0;
844
+ for (const k of Object.keys(merged)) {
845
+ if (!k.startsWith(prefix)) continue;
846
+ const v = Number(k.slice(prefix.length));
847
+ if (Number.isInteger(v) && v < minVersion) return void 0;
848
+ }
849
+ const assigned = merged[`${prefix}${bucketVersion}`];
850
+ if (assigned !== void 0) {
851
+ const i = variations.findIndex((v) => v.id === assigned);
852
+ if (i >= 0) {
853
+ index = i;
854
+ stickyUsed = true;
855
+ }
856
+ }
857
+ }
858
+ if (index < 0) {
859
+ if (rule.namespace) {
860
+ const n = hashBucket(`__${rule.namespace.id}`, hv);
861
+ if (!(n >= rule.namespace.start && n < rule.namespace.end)) return void 0;
862
+ }
863
+ const coverage = clamp01(rule.coverage);
864
+ let cum = 0;
865
+ for (let i = 0; i < variations.length; i++) {
866
+ const w = Math.max(0, Number(variations[i].weight) || 0);
867
+ const start = cum;
868
+ const end = cum + coverage * w;
869
+ cum += w;
870
+ if (bucket >= start && bucket < end) {
871
+ index = i;
872
+ break;
873
+ }
874
+ }
875
+ if (index < 0) return void 0;
876
+ if (useSticky) {
877
+ const docKey = stickyDocKey(hashAttribute, hv);
878
+ const doc = { ...ctx.sticky.get(docKey) ?? {} };
879
+ doc[`${rule.key}__${bucketVersion}`] = variations[index].id;
880
+ ctx.sticky.set(docKey, doc);
881
+ }
882
+ }
883
+ const variation = variations[index];
884
+ return {
885
+ key: flag.key,
886
+ value: variation.value === void 0 ? null : variation.value,
887
+ reason: "EXPERIMENT",
888
+ ruleId: rule.id,
889
+ variationId: variation.id,
890
+ inExperiment: true,
891
+ experiment: { key: rule.key, name: rule.name, variationId: variation.id, variationName: variation.name, variationIndex: index, hashAttribute, hashValue: hv, bucket, stickyBucketUsed: stickyUsed }
892
+ };
893
+ }
894
+ function applyRule(ctx, flag, rule) {
895
+ switch (rule.type) {
896
+ case "force":
897
+ return { ...base(flag, rule.value, "RULE_MATCH"), ruleId: rule.id, ...rule.variationId ? { variationId: rule.variationId } : {} };
898
+ case "rollout": {
899
+ const hv = hashValueOf(ctx.attributes, rule.hashAttribute);
900
+ if (hv === void 0) return void 0;
901
+ if (hashBucket(rule.seed, hv) >= effectiveCoverage(rule, ctx.now)) return void 0;
902
+ return { ...base(flag, rule.value, "RULE_MATCH"), ruleId: rule.id, ...rule.variationId ? { variationId: rule.variationId } : {} };
903
+ }
904
+ case "split": {
905
+ const hv = hashValueOf(ctx.attributes, rule.hashAttribute);
906
+ if (hv === void 0 || !Array.isArray(rule.buckets)) return void 0;
907
+ const n = hashBucket(rule.seed, hv);
908
+ let cum = 0;
909
+ for (const b of rule.buckets) {
910
+ cum += Math.max(0, Number(b.weight) || 0);
911
+ if (n < cum) return { ...base(flag, b.value, "RULE_MATCH"), ruleId: rule.id, ...b.variationId ? { variationId: b.variationId } : {} };
912
+ }
913
+ return void 0;
914
+ }
915
+ case "experiment":
916
+ return runExperiment(ctx, flag, rule);
917
+ default:
918
+ return void 0;
919
+ }
920
+ }
921
+ function evalFlag(ctx, key) {
922
+ const flag = ctx.payload.flags?.[key];
923
+ if (!flag) return { key, value: null, reason: "FLAG_NOT_FOUND", inExperiment: false };
924
+ return enter(ctx, `flag:${key}`, () => {
925
+ if (!flag.enabled) return base(flag, flag.offValue, "DISABLED");
926
+ for (const p of flag.prerequisites ?? []) {
927
+ const ref = referencedValue(ctx, p.flag);
928
+ if (!ref || !compareFlag(p.operator, ref.value, p.value)) return base(flag, flag.offValue, "PREREQUISITE_FAILED");
929
+ }
930
+ for (const h of ctx.payload.holdouts ?? []) {
931
+ if (!Array.isArray(h.flags) || !h.flags.includes(key)) continue;
932
+ const hv = hashValueOf(ctx.attributes, h.hashAttribute);
933
+ if (hv !== void 0 && hashBucket(h.seed, hv) < clamp01(h.coverage)) return base(flag, flag.defaultValue, "HOLDOUT");
934
+ }
935
+ for (const rule of flag.rules ?? []) {
936
+ if (!rule || !isScheduleActive(rule.schedule, ctx.now)) continue;
937
+ if (!allMatch(ctx, rule.conditions)) continue;
938
+ const res = applyRule(ctx, flag, rule);
939
+ if (res) return res;
940
+ }
941
+ return base(flag, flag.defaultValue, "DEFAULT");
942
+ });
943
+ }
944
+ function evaluateFlag(payload, key, attributes = {}, options = {}) {
945
+ const ctx = { payload, attributes: attributes ?? {}, now: nowOf(options.now), sticky: options.stickyBuckets, stack: [] };
946
+ try {
947
+ return evalFlag(ctx, key);
948
+ } catch (err) {
949
+ return { key, value: null, reason: "ERROR", inExperiment: false, errorMessage: err instanceof Error ? err.message : String(err) };
950
+ }
951
+ }
952
+
953
+ // src/evaluator.ts
954
+ function typeMatches(value, fallback) {
955
+ if (value === null || value === void 0) return false;
956
+ if (fallback === null || fallback === void 0) return true;
957
+ return kindOf(value) === kindOf(fallback);
958
+ }
959
+ function makeEvaluator(resolve2, all, attributes) {
960
+ const details = (key, fallback) => {
961
+ const r = resolve2(key);
962
+ const ok = typeMatches(r.value, fallback);
963
+ return { ...r, value: ok ? r.value : fallback, usedFallback: !ok };
964
+ };
965
+ const of = (type, key, fallback) => {
966
+ const v = resolve2(key).value;
967
+ return kindOf(v) === type ? v : fallback;
968
+ };
969
+ return {
970
+ isEnabled: (key, fallback = false) => of("boolean", key, fallback),
971
+ getValue: (key, fallback) => details(key, fallback).value,
972
+ getString: (key, fallback) => of("string", key, fallback),
973
+ getNumber: (key, fallback) => of("number", key, fallback),
974
+ getBoolean: (key, fallback) => of("boolean", key, fallback),
975
+ getJson: (key, fallback) => {
976
+ const v = resolve2(key).value;
977
+ return v !== null && typeof v === "object" ? v : fallback;
978
+ },
979
+ getDetails: details,
980
+ getAll: all,
981
+ getAttributes: attributes
982
+ };
983
+ }
984
+ function notFound(key) {
985
+ return { key, value: null, reason: "FLAG_NOT_FOUND", inExperiment: false };
986
+ }
987
+
988
+ // src/sse.ts
989
+ function createSseParser(onEvent) {
990
+ let buffer = "";
991
+ const dispatch = (raw) => {
992
+ let event = "message";
993
+ const data = [];
994
+ for (const line of raw.split("\n")) {
995
+ if (!line || line.startsWith(":")) continue;
996
+ const i = line.indexOf(":");
997
+ const field = i < 0 ? line : line.slice(0, i);
998
+ const value = i < 0 ? "" : line.slice(i + 1).replace(/^ /, "");
999
+ if (field === "event") event = value;
1000
+ else if (field === "data") data.push(value);
1001
+ }
1002
+ if (data.length) onEvent(event, data.join("\n"));
1003
+ };
1004
+ return {
1005
+ feed(chunk) {
1006
+ buffer += chunk.replace(/\r\n?/g, "\n");
1007
+ let sep2;
1008
+ while ((sep2 = buffer.indexOf("\n\n")) >= 0) {
1009
+ dispatch(buffer.slice(0, sep2));
1010
+ buffer = buffer.slice(sep2 + 2);
1011
+ }
1012
+ },
1013
+ end() {
1014
+ if (buffer.trim()) dispatch(buffer);
1015
+ buffer = "";
1016
+ }
1017
+ };
1018
+ }
1019
+
1020
+ // src/client.ts
1021
+ var FLAGS_SDK_NAME = "@guidelytech/flags";
1022
+ var FLAGS_SDK_VERSION = "0.8.0";
1023
+ var DEFAULT_FLAGS_API_URL = "https://guidelytech.com";
1024
+ var SECRET_PREFIX = "gly_sk_";
1025
+ var MAX_RESULT_CACHE = 20;
1026
+ var MAX_QUEUE = 1e3;
1027
+ var USAGE_FLUSH_MS = 6e4;
1028
+ var EXPOSURE_FLUSH_MS = 5e3;
1029
+ var MAX_BACKOFF_MS = 5 * 6e4;
1030
+ var isBrowser = () => typeof window !== "undefined" && typeof document !== "undefined";
1031
+ function defaultStorage() {
1032
+ try {
1033
+ if (!isBrowser() || typeof localStorage === "undefined") return null;
1034
+ const probe = "__guidely_flags_probe__";
1035
+ localStorage.setItem(probe, "1");
1036
+ localStorage.removeItem(probe);
1037
+ return localStorage;
1038
+ } catch {
1039
+ return null;
1040
+ }
1041
+ }
1042
+ function backoff(failures, capMs) {
1043
+ const base2 = Math.min(capMs, 1e3 * 2 ** Math.max(0, failures - 1));
1044
+ return Math.round(base2 * (0.8 + Math.random() * 0.4));
1045
+ }
1046
+ var FlagsClient = class {
1047
+ constructor(options) {
1048
+ __publicField(this, "sdkKey");
1049
+ __publicField(this, "apiUrl");
1050
+ __publicField(this, "environment");
1051
+ __publicField(this, "o");
1052
+ __publicField(this, "cfg");
1053
+ __publicField(this, "secret");
1054
+ __publicField(this, "log");
1055
+ __publicField(this, "storage");
1056
+ __publicField(this, "storageKey");
1057
+ __publicField(this, "getters");
1058
+ __publicField(this, "payload", null);
1059
+ /** The payload as received (possibly encrypted): what `getPayload()` hands to browsers. */
1060
+ __publicField(this, "raw", null);
1061
+ __publicField(this, "etag", null);
1062
+ __publicField(this, "remote");
1063
+ __publicField(this, "results", null);
1064
+ __publicField(this, "resultsKey", null);
1065
+ __publicField(this, "resultCache", /* @__PURE__ */ new Map());
1066
+ __publicField(this, "context");
1067
+ __publicField(this, "attributes");
1068
+ __publicField(this, "overrides");
1069
+ __publicField(this, "devOverrides");
1070
+ __publicField(this, "urlOverrides");
1071
+ __publicField(this, "offline");
1072
+ __publicField(this, "destroyed", false);
1073
+ __publicField(this, "ready_", false);
1074
+ __publicField(this, "resolveReady");
1075
+ __publicField(this, "readyPromise");
1076
+ __publicField(this, "listeners", /* @__PURE__ */ new Set());
1077
+ __publicField(this, "inflight", null);
1078
+ __publicField(this, "lastFetchAt", 0);
1079
+ __publicField(this, "failures", 0);
1080
+ __publicField(this, "retryAt", 0);
1081
+ __publicField(this, "pollTimer", null);
1082
+ __publicField(this, "remoteTimer", null);
1083
+ __publicField(this, "flushTimer", null);
1084
+ __publicField(this, "flushDue", Infinity);
1085
+ __publicField(this, "stream", null);
1086
+ __publicField(this, "streamConnected", false);
1087
+ __publicField(this, "streamFailures", 0);
1088
+ __publicField(this, "reconnectTimer", null);
1089
+ __publicField(this, "exposed", /* @__PURE__ */ new Set());
1090
+ __publicField(this, "usage", /* @__PURE__ */ new Map());
1091
+ __publicField(this, "exposures", []);
1092
+ __publicField(this, "warned", /* @__PURE__ */ new Set());
1093
+ __publicField(this, "cleanups", []);
1094
+ __publicField(this, "lastError", "");
1095
+ /** Calls `listener` whenever values may have changed (payload, attributes, overrides, readiness). */
1096
+ __publicField(this, "subscribe", (listener) => {
1097
+ this.listeners.add(listener);
1098
+ return () => void this.listeners.delete(listener);
1099
+ });
1100
+ const o = options ?? {};
1101
+ this.o = o;
1102
+ const key = o.sdkKey ?? o.projectId ?? "";
1103
+ if (!key) throw new Error("[Guidely flags] `sdkKey` (your environment public key pk_\u2026, or a gly_sk_\u2026 secret key on servers) is required.");
1104
+ this.sdkKey = key;
1105
+ this.secret = key.startsWith(SECRET_PREFIX);
1106
+ this.environment = o.environment;
1107
+ this.apiUrl = (o.apiUrl || DEFAULT_FLAGS_API_URL).replace(/\/+$/, "");
1108
+ this.log = o.logger === null ? {} : o.logger ?? { warn: (...a) => console.warn("[Guidely flags]", ...a), error: (...a) => console.error("[Guidely flags]", ...a) };
1109
+ if (this.secret && !o.environment) throw new Error("[Guidely flags] `environment` (development | staging | production) is required with a gly_sk_ key.");
1110
+ if (this.secret && isBrowser()) this.log.warn?.("A gly_sk_ secret key is being used in a browser. Secret keys belong on servers; use the environment's public pk_ key here.");
1111
+ const dev = o.devOverrides === false ? null : readDevOverrides();
1112
+ this.cfg = {
1113
+ mode: o.pollingMode ?? "auto",
1114
+ pollMs: Math.max(1, o.pollIntervalSeconds ?? 60) * 1e3,
1115
+ ttlMs: Math.max(0, o.cacheTtlSeconds ?? 60) * 1e3,
1116
+ debounceMs: Math.max(0, o.remoteEvalDebounceMs ?? 50),
1117
+ behaviour: dev?.behaviour ?? o.overrideBehaviour ?? "localOverRemote",
1118
+ trackExposures: o.trackExposures ?? !o.trackingCallback,
1119
+ reportUsage: o.reportUsage ?? true
1120
+ };
1121
+ this.remote = !!o.remoteEval;
1122
+ this.offline = !!o.offline;
1123
+ this.context = compact(o.contextAttributes ?? {});
1124
+ this.attributes = compact(o.attributes ?? {});
1125
+ this.overrides = { ...o.overrides ?? {} };
1126
+ this.devOverrides = dev?.values ?? {};
1127
+ this.urlOverrides = o.enableUrlOverrides && isBrowser() ? parseUrlOverrides(location.search) : {};
1128
+ this.storage = o.storage === false || o.storage === null ? null : o.storage ?? defaultStorage();
1129
+ this.storageKey = `guidely:flags:${key}${o.environment && this.secret ? `:${o.environment}` : ""}`;
1130
+ this.readyPromise = new Promise((r) => this.resolveReady = r);
1131
+ if (dev) {
1132
+ dev.errors.forEach((e) => this.reportError(new Error(e)));
1133
+ const n = Object.keys(dev.values).length;
1134
+ if (dev.sources.length) this.log.warn?.(`Dev overrides from ${dev.sources.join(", ")}: ${n} flag${n === 1 ? "" : "s"}, behaviour ${this.cfg.behaviour}.`);
1135
+ }
1136
+ this.getters = makeEvaluator(
1137
+ (k) => this.resolve(k, this.getAttributes(), true),
1138
+ () => this.all(this.getAttributes()),
1139
+ () => this.getAttributes()
1140
+ );
1141
+ const boot = o.payload;
1142
+ if (isRemoteResults(boot)) this.setResults(boot, stableStringify(this.getAttributes()), false);
1143
+ else if (isFlagsPayload(boot) && !isEncryptedPayload(boot)) this.setPayload(boot, boot, null, false);
1144
+ if (o.remoteResults) {
1145
+ const r = o.remoteResults;
1146
+ this.setResults(isRemoteResults(r) && typeof r.version === "number" ? r : { version: 0, flags: o.remoteResults }, stableStringify(this.getAttributes()), false);
1147
+ }
1148
+ if (this.results) this.remote = true;
1149
+ this.wireBrowser();
1150
+ if (this.payload || this.results) this.settle();
1151
+ void this.start();
1152
+ }
1153
+ // ---------------------------------------------------------------------------
1154
+ // Getters (never throw)
1155
+ // ---------------------------------------------------------------------------
1156
+ /** Boolean flag value; `fallback` (default `false`) when missing or not a boolean. */
1157
+ isEnabled(key, fallback = false) {
1158
+ return this.getters.isEnabled(key, fallback);
1159
+ }
1160
+ /** Flag value; `fallback` when missing, null or of a different JSON type than `fallback`. */
1161
+ getValue(key, fallback) {
1162
+ return this.getters.getValue(key, fallback);
1163
+ }
1164
+ getString(key, fallback) {
1165
+ return this.getters.getString(key, fallback);
1166
+ }
1167
+ getNumber(key, fallback) {
1168
+ return this.getters.getNumber(key, fallback);
1169
+ }
1170
+ getBoolean(key, fallback) {
1171
+ return this.getters.getBoolean(key, fallback);
1172
+ }
1173
+ getJson(key, fallback) {
1174
+ return this.getters.getJson(key, fallback);
1175
+ }
1176
+ /** Value plus reason, rule, variation and experiment assignment. */
1177
+ getDetails(key, fallback) {
1178
+ return this.getters.getDetails(key, fallback);
1179
+ }
1180
+ /** Every flag's value for the current attributes (records no exposures or usage). */
1181
+ getAll() {
1182
+ return this.getters.getAll();
1183
+ }
1184
+ /**
1185
+ * Getters bound to other attributes (merged over the client's), without changing the client:
1186
+ * the per-request pattern on servers. With remote evaluation they serve results already cached
1187
+ * for these attributes, else fallbacks.
1188
+ */
1189
+ forUser(attributes) {
1190
+ const attrs = { ...this.getAttributes(), ...compact(attributes ?? {}) };
1191
+ if (this.remote) this.warnOnce("forUser:remote", "forUser() with remote evaluation only serves results cached for those attributes.");
1192
+ return makeEvaluator(
1193
+ (k) => this.resolve(k, attrs, true),
1194
+ () => this.all(attrs),
1195
+ () => ({ ...attrs })
1196
+ );
1197
+ }
1198
+ // ---------------------------------------------------------------------------
1199
+ // Attributes and overrides
1200
+ // ---------------------------------------------------------------------------
1201
+ /** Replaces the app attributes. */
1202
+ setAttributes(attributes) {
1203
+ this.changeAttributes(() => this.attributes = compact(attributes ?? {}));
1204
+ }
1205
+ /** Merges into the app attributes. */
1206
+ updateAttributes(partial) {
1207
+ this.changeAttributes(() => this.attributes = compact({ ...this.attributes, ...partial }));
1208
+ }
1209
+ /** Replaces the integration-supplied attributes (see `contextAttributes`). */
1210
+ setContextAttributes(attributes) {
1211
+ this.changeAttributes(() => this.context = compact(attributes ?? {}));
1212
+ }
1213
+ /** Context attributes with app attributes merged over them. */
1214
+ getAttributes() {
1215
+ return { ...this.context, ...this.attributes };
1216
+ }
1217
+ /** Replaces the overrides (`null` clears them). Dev and URL overrides stay. */
1218
+ setOverrides(overrides) {
1219
+ this.overrides = { ...overrides ?? {} };
1220
+ this.notify();
1221
+ }
1222
+ changeAttributes(apply) {
1223
+ const before = stableStringify(this.getAttributes());
1224
+ apply();
1225
+ const after = stableStringify(this.getAttributes());
1226
+ if (before === after) return;
1227
+ if (this.remote) {
1228
+ const cached = this.resultCache.get(after);
1229
+ if (cached) {
1230
+ this.results = cached;
1231
+ this.resultsKey = after;
1232
+ }
1233
+ this.scheduleRemote();
1234
+ }
1235
+ this.notify();
1236
+ }
1237
+ // ---------------------------------------------------------------------------
1238
+ // Lifecycle
1239
+ // ---------------------------------------------------------------------------
1240
+ /** Resolves once the first load settled (bootstrap, network success or failure). Never rejects. */
1241
+ ready() {
1242
+ return this.readyPromise;
1243
+ }
1244
+ isReady() {
1245
+ return this.ready_;
1246
+ }
1247
+ /** `true` stops all network activity; cached data keeps serving. `false` resumes and refreshes. */
1248
+ setOffline(offline) {
1249
+ if (this.offline === offline) return;
1250
+ this.offline = offline;
1251
+ if (offline) return this.stopNetwork();
1252
+ void this.refresh().then(() => this.schedule());
1253
+ }
1254
+ /** Fetches now (payload, or remote evaluation). Resolves when done; never rejects. */
1255
+ async refresh() {
1256
+ await this.fetchNow();
1257
+ }
1258
+ /**
1259
+ * The current data for server-side rendering: pass it as `payload` to the browser client (or
1260
+ * framework provider) to render the same values without a flash. Encrypted payloads stay
1261
+ * encrypted; with remote evaluation it returns the results. `null` before anything loaded.
1262
+ */
1263
+ getPayload() {
1264
+ if (this.remote) return this.results;
1265
+ return this.raw;
1266
+ }
1267
+ /** Sends pending usage counts and exposures now. */
1268
+ async flush() {
1269
+ const batch = this.takeBatch();
1270
+ if (!batch) return;
1271
+ try {
1272
+ const res = await this.request("POST", "/flags/events", { body: batch, keepalive: true });
1273
+ if (!res.ok && (res.status >= 500 || res.status === 429)) this.requeue(batch);
1274
+ } catch {
1275
+ this.requeue(batch);
1276
+ }
1277
+ }
1278
+ /** Stops timers and streams and sends pending usage. The client keeps serving cached values. */
1279
+ destroy() {
1280
+ if (this.destroyed) return;
1281
+ this.flushOnExit();
1282
+ this.destroyed = true;
1283
+ this.stopNetwork();
1284
+ if (this.flushTimer) clearTimeout(this.flushTimer);
1285
+ this.cleanups.splice(0).forEach((fn) => fn());
1286
+ this.listeners.clear();
1287
+ this.settle();
1288
+ }
1289
+ // ---------------------------------------------------------------------------
1290
+ // Evaluation
1291
+ // ---------------------------------------------------------------------------
1292
+ overrideFor(key) {
1293
+ if (Object.prototype.hasOwnProperty.call(this.urlOverrides, key)) return this.urlOverrides[key];
1294
+ if (Object.prototype.hasOwnProperty.call(this.devOverrides, key)) return this.devOverrides[key];
1295
+ if (Object.prototype.hasOwnProperty.call(this.overrides, key)) return this.overrides[key];
1296
+ return void 0;
1297
+ }
1298
+ resolve(key, attrs, track) {
1299
+ let res;
1300
+ try {
1301
+ if (this.cfg.mode === "lazy") this.lazyRefresh();
1302
+ const ov = this.overrideFor(key);
1303
+ const forced = Object.prototype.hasOwnProperty.call(this.urlOverrides, key);
1304
+ const override = ov === void 0 ? null : { key, value: ov, reason: "OVERRIDE", inExperiment: false };
1305
+ if (override && (forced || this.cfg.behaviour !== "remoteOverLocal")) res = override;
1306
+ else {
1307
+ res = this.evaluateRemoteOrLocal(key, attrs);
1308
+ if (override && res.reason === "FLAG_NOT_FOUND") res = override;
1309
+ }
1310
+ } catch (err) {
1311
+ res = { key, value: null, reason: "ERROR", inExperiment: false, errorMessage: err instanceof Error ? err.message : String(err) };
1312
+ }
1313
+ if (track) this.record(res, attrs);
1314
+ return res;
1315
+ }
1316
+ evaluateRemoteOrLocal(key, attrs) {
1317
+ if (this.remote) {
1318
+ const k = stableStringify(attrs);
1319
+ const results = k === this.resultsKey || k === stableStringify(this.getAttributes()) ? this.results : this.resultCache.get(k);
1320
+ return results?.flags[key] ?? notFound(key);
1321
+ }
1322
+ if (!this.payload) return notFound(key);
1323
+ return evaluateFlag(this.payload, key, attrs, { now: this.now(), stickyBuckets: this.o.stickyBucketService });
1324
+ }
1325
+ all(attrs) {
1326
+ const keys = /* @__PURE__ */ new Set([...Object.keys(this.overrides), ...Object.keys(this.devOverrides), ...Object.keys(this.urlOverrides)]);
1327
+ if (this.remote) for (const k of Object.keys(this.results?.flags ?? {})) keys.add(k);
1328
+ else for (const k of Object.keys(this.payload?.flags ?? {})) keys.add(k);
1329
+ const out = {};
1330
+ for (const k of keys) {
1331
+ const v = this.resolve(k, attrs, false).value;
1332
+ if (v !== null) out[k] = v;
1333
+ }
1334
+ return out;
1335
+ }
1336
+ now() {
1337
+ const n = this.o.now?.();
1338
+ return n === void 0 ? Date.now() : typeof n === "number" ? n : n.getTime();
1339
+ }
1340
+ record(res, attrs) {
1341
+ try {
1342
+ this.o.onFlagEvaluated?.(res);
1343
+ } catch (err) {
1344
+ this.reportError(err);
1345
+ }
1346
+ if (res.reason === "FLAG_NOT_FOUND") {
1347
+ if (this.ready_ && (this.payload || this.results) && isDev()) this.warnOnce(`missing:${res.key}`, `Flag "${res.key}" does not exist in this environment; serving the fallback.`);
1348
+ return;
1349
+ }
1350
+ if (res.reason === "OVERRIDE" || res.reason === "ERROR") return;
1351
+ if (this.cfg.reportUsage) {
1352
+ const value = (JSON.stringify(res.value) ?? "null").slice(0, 200);
1353
+ const k = `${res.key}\0${res.variationId ?? ""}\0${res.variationId ? "" : value}`;
1354
+ const u = this.usage.get(k);
1355
+ if (u) u.count++;
1356
+ else if (this.usage.size < MAX_QUEUE) this.usage.set(k, { flag: res.key, ...res.variationId ? { variationId: res.variationId } : {}, value, count: 1 });
1357
+ this.scheduleFlush(USAGE_FLUSH_MS);
1358
+ }
1359
+ const exp = res.inExperiment ? res.experiment : void 0;
1360
+ if (!exp) return;
1361
+ const dedupe = `${exp.key}\0${exp.hashValue}\0${exp.variationId}`;
1362
+ if (this.exposed.has(dedupe)) return;
1363
+ if (this.exposed.size >= 5e4) this.exposed.clear();
1364
+ this.exposed.add(dedupe);
1365
+ try {
1366
+ this.o.trackingCallback?.(exp, res);
1367
+ } catch (err) {
1368
+ this.reportError(err);
1369
+ }
1370
+ if (!this.cfg.trackExposures) return;
1371
+ const userId = textOf(attrs.id);
1372
+ const anonymousId = textOf(attrs.anonymousId);
1373
+ if (this.exposures.length < MAX_QUEUE) {
1374
+ this.exposures.push({ experiment: exp.key, flag: res.key, variationId: exp.variationId, hashAttribute: exp.hashAttribute, hashValue: exp.hashValue, ...userId ? { userId } : {}, ...anonymousId ? { anonymousId } : {}, timestamp: new Date(this.now()).toISOString() });
1375
+ }
1376
+ this.scheduleFlush(EXPOSURE_FLUSH_MS);
1377
+ }
1378
+ warnOnce(id, message) {
1379
+ if (this.warned.has(id)) return;
1380
+ this.warned.add(id);
1381
+ this.log.warn?.(message);
1382
+ }
1383
+ // ---------------------------------------------------------------------------
1384
+ // Data
1385
+ // ---------------------------------------------------------------------------
1386
+ setPayload(payload, raw, etag, emit) {
1387
+ const same = this.payload && this.payload.version === payload.version && this.payload.generatedAt === payload.generatedAt && this.payload.environment === payload.environment;
1388
+ this.payload = payload;
1389
+ this.raw = raw;
1390
+ this.etag = etag;
1391
+ if (!emit || same) return;
1392
+ try {
1393
+ this.o.onChange?.(payload);
1394
+ } catch (err) {
1395
+ this.reportError(err);
1396
+ }
1397
+ this.notify();
1398
+ }
1399
+ setResults(results, key, emit) {
1400
+ this.resultCache.delete(key);
1401
+ this.resultCache.set(key, results);
1402
+ while (this.resultCache.size > MAX_RESULT_CACHE) this.resultCache.delete(this.resultCache.keys().next().value);
1403
+ if (key !== stableStringify(this.getAttributes())) return;
1404
+ const same = this.results && stableStringify(this.results) === stableStringify(results);
1405
+ this.results = results;
1406
+ this.resultsKey = key;
1407
+ if (!emit || same) return;
1408
+ try {
1409
+ this.o.onChange?.(results);
1410
+ } catch (err) {
1411
+ this.reportError(err);
1412
+ }
1413
+ this.notify();
1414
+ }
1415
+ /** Decrypts when needed and applies. Returns false (keeping the previous payload) when unusable. */
1416
+ async applyRaw(raw, etag) {
1417
+ if (isEncryptedPayload(raw)) {
1418
+ if (!this.o.decryptionKey) {
1419
+ this.reportError(new Error("The payload is encrypted but no `decryptionKey` was given; keeping the previous flags."));
1420
+ return false;
1421
+ }
1422
+ try {
1423
+ const plain = JSON.parse(await decryptPayloadString(raw.encryptedPayload, this.o.decryptionKey));
1424
+ const envelope = { ...raw };
1425
+ delete envelope.encryptedPayload;
1426
+ const merged = { ...envelope, ...plain };
1427
+ if (!isFlagsPayload(merged)) throw new Error("decrypted payload has no flags");
1428
+ this.setPayload(merged, raw, etag, true);
1429
+ return true;
1430
+ } catch (err) {
1431
+ this.reportError(new Error(`Could not decrypt the flags payload (${err instanceof Error ? err.message : String(err)}); keeping the previous flags.`));
1432
+ return false;
1433
+ }
1434
+ }
1435
+ if (!isFlagsPayload(raw)) {
1436
+ this.reportError(new Error("Unexpected flags payload; keeping the previous flags."));
1437
+ return false;
1438
+ }
1439
+ this.setPayload(raw, raw, etag, true);
1440
+ return true;
1441
+ }
1442
+ async loadStored() {
1443
+ if (!this.storage) return;
1444
+ try {
1445
+ const text = await this.storage.getItem(this.storageKey);
1446
+ if (!text || this.payload) return;
1447
+ const saved = JSON.parse(text);
1448
+ if (saved?.payload && !this.payload) await this.applyRaw(saved.payload, saved.etag ?? null);
1449
+ } catch (err) {
1450
+ this.log.debug?.("could not read cached flags", err);
1451
+ }
1452
+ }
1453
+ persist() {
1454
+ if (!this.storage || !this.raw) return;
1455
+ try {
1456
+ void Promise.resolve(this.storage.setItem(this.storageKey, JSON.stringify({ payload: this.raw, etag: this.etag, savedAt: (/* @__PURE__ */ new Date()).toISOString() }))).catch(() => {
1457
+ });
1458
+ } catch {
1459
+ }
1460
+ }
1461
+ // ---------------------------------------------------------------------------
1462
+ // Network
1463
+ // ---------------------------------------------------------------------------
1464
+ url(path) {
1465
+ const q = this.secret ? `environment=${encodeURIComponent(this.environment ?? "")}` : `key=${encodeURIComponent(this.sdkKey)}`;
1466
+ return `${this.apiUrl}/api/v1/sdk${path}?${q}`;
1467
+ }
1468
+ headers(extra = {}) {
1469
+ return { ...extra, ...this.secret ? { Authorization: `Bearer ${this.sdkKey}` } : { "X-Guidely-Key": this.sdkKey } };
1470
+ }
1471
+ get fetcher() {
1472
+ if (this.o.fetch) return this.o.fetch;
1473
+ return typeof fetch === "function" ? fetch.bind(globalThis) : null;
1474
+ }
1475
+ async request(method, path, opts = {}) {
1476
+ const f = this.fetcher;
1477
+ if (!f) throw new Error("fetch is not available; pass `fetch`");
1478
+ const headers = this.headers({ Accept: "application/json" });
1479
+ if (opts.body !== void 0) headers["Content-Type"] = "application/json";
1480
+ if (opts.etag) headers["If-None-Match"] = opts.etag;
1481
+ const ac = typeof AbortController !== "undefined" ? new AbortController() : null;
1482
+ const t = ac ? timer(() => ac.abort(), opts.timeoutMs ?? 1e4) : null;
1483
+ try {
1484
+ const browserOnly = isBrowser() ? { credentials: "omit", keepalive: opts.keepalive } : {};
1485
+ const res = await f(this.url(path), { method, headers, body: opts.body === void 0 ? void 0 : JSON.stringify(opts.body), signal: ac?.signal, ...browserOnly });
1486
+ let data = null;
1487
+ if (res.status !== 304 && res.status !== 204) {
1488
+ try {
1489
+ data = await res.json();
1490
+ } catch {
1491
+ data = null;
1492
+ }
1493
+ }
1494
+ return { status: res.status, ok: res.ok, data, etag: res.headers?.get?.("etag") ?? null };
1495
+ } finally {
1496
+ if (t) clearTimeout(t);
1497
+ }
1498
+ }
1499
+ fetchNow() {
1500
+ if (this.destroyed || this.offline || this.cfg.behaviour === "localOnly") return Promise.resolve(false);
1501
+ if (this.inflight) return this.inflight;
1502
+ this.inflight = (this.remote ? this.fetchRemote() : this.fetchPayload()).catch((err) => {
1503
+ this.reportError(err);
1504
+ return false;
1505
+ }).then((ok) => {
1506
+ this.inflight = null;
1507
+ this.lastFetchAt = Date.now();
1508
+ this.failures = ok ? 0 : this.failures + 1;
1509
+ this.retryAt = ok ? 0 : Date.now() + backoff(this.failures, Math.max(this.cfg.pollMs, MAX_BACKOFF_MS));
1510
+ if (ok) this.lastError = "";
1511
+ return ok;
1512
+ });
1513
+ return this.inflight;
1514
+ }
1515
+ async fetchPayload() {
1516
+ let res;
1517
+ try {
1518
+ res = await this.request("GET", "/flags", { etag: this.payload ? this.etag : null });
1519
+ } catch (err) {
1520
+ this.reportError(new Error(`Flags request failed: ${err instanceof Error ? err.message : String(err)}`));
1521
+ return false;
1522
+ }
1523
+ if (res.status === 304) return true;
1524
+ const code = res.data?.error?.code;
1525
+ if (res.status === 403 && code === "remote_evaluation_required" && !this.secret) {
1526
+ this.log.warn?.("This environment requires remote evaluation; switching to `remoteEval: true`.");
1527
+ this.remote = true;
1528
+ return this.fetchRemote();
1529
+ }
1530
+ if (!res.ok || res.data == null) {
1531
+ this.reportError(new Error(`Flags request failed (${res.status}${code ? ` ${code}` : ""})${this.payload ? "; serving the last good flags" : ""}`));
1532
+ return false;
1533
+ }
1534
+ const applied = await this.applyRaw(res.data, res.etag);
1535
+ if (applied) this.persist();
1536
+ return applied;
1537
+ }
1538
+ async fetchRemote() {
1539
+ const attrs = this.getAttributes();
1540
+ const key = stableStringify(attrs);
1541
+ let res;
1542
+ try {
1543
+ res = await this.request("POST", "/flags/evaluate", { body: { attributes: attrs } });
1544
+ } catch (err) {
1545
+ this.reportError(new Error(`Remote evaluation failed: ${err instanceof Error ? err.message : String(err)}`));
1546
+ return false;
1547
+ }
1548
+ if (!res.ok || !res.data || !isRemoteResults(res.data)) {
1549
+ this.reportError(new Error(`Remote evaluation failed (${res.status})`));
1550
+ return false;
1551
+ }
1552
+ this.setResults(res.data, key, true);
1553
+ return true;
1554
+ }
1555
+ scheduleRemote() {
1556
+ if (this.destroyed || this.offline || this.cfg.behaviour === "localOnly") return;
1557
+ if (this.remoteTimer) clearTimeout(this.remoteTimer);
1558
+ this.remoteTimer = timer(() => {
1559
+ this.remoteTimer = null;
1560
+ void this.fetchRemote().then((ok) => {
1561
+ if (!ok) this.failures++;
1562
+ });
1563
+ }, this.cfg.debounceMs);
1564
+ }
1565
+ lazyRefresh() {
1566
+ if (this.inflight || this.offline || this.destroyed || this.cfg.behaviour === "localOnly") return;
1567
+ const now = Date.now();
1568
+ if (now - this.lastFetchAt < this.cfg.ttlMs || now < this.retryAt) return;
1569
+ this.lastFetchAt = now;
1570
+ void this.fetchNow();
1571
+ }
1572
+ async start() {
1573
+ const boot = this.o.payload;
1574
+ if (isEncryptedPayload(boot)) await this.applyRaw(boot, null);
1575
+ if (!this.payload && !this.results && !this.remote && this.cfg.behaviour !== "localOnly") await this.loadStored();
1576
+ if (this.destroyed) return;
1577
+ const bootstrapped = !!(boot || this.o.remoteResults) && !!(this.payload || this.results);
1578
+ if (this.cfg.behaviour === "localOnly" || this.offline || this.cfg.mode === "manual") return this.settle();
1579
+ if (bootstrapped) {
1580
+ this.settle();
1581
+ await this.fetchNow();
1582
+ return this.schedule();
1583
+ }
1584
+ await this.fetchNow();
1585
+ this.settle();
1586
+ this.schedule();
1587
+ }
1588
+ settle() {
1589
+ if (this.ready_) return;
1590
+ this.ready_ = true;
1591
+ this.resolveReady();
1592
+ try {
1593
+ this.o.onReady?.();
1594
+ } catch (err) {
1595
+ this.reportError(err);
1596
+ }
1597
+ this.notify();
1598
+ }
1599
+ /** Arms the poll timer / stream for the current mode. */
1600
+ schedule() {
1601
+ if (this.destroyed || this.offline || this.cfg.behaviour === "localOnly") return;
1602
+ if (this.cfg.mode === "auto") this.schedulePoll();
1603
+ else if (this.cfg.mode === "stream") {
1604
+ if (!this.stream) this.openStream();
1605
+ if (!this.streamConnected) this.schedulePoll();
1606
+ }
1607
+ }
1608
+ schedulePoll() {
1609
+ if (this.pollTimer) clearTimeout(this.pollTimer);
1610
+ const delay = this.failures ? Math.max(0, this.retryAt - Date.now()) : this.cfg.pollMs;
1611
+ this.pollTimer = timer(() => {
1612
+ this.pollTimer = null;
1613
+ if (this.cfg.mode === "stream" && this.streamConnected) return;
1614
+ if (isBrowser() && document.visibilityState === "hidden") return this.schedulePoll();
1615
+ void this.fetchNow().then(() => this.schedule());
1616
+ }, delay);
1617
+ }
1618
+ stopNetwork() {
1619
+ for (const t of [this.pollTimer, this.remoteTimer, this.reconnectTimer]) if (t) clearTimeout(t);
1620
+ this.pollTimer = this.remoteTimer = this.reconnectTimer = null;
1621
+ this.stream?.close();
1622
+ this.stream = null;
1623
+ this.streamConnected = false;
1624
+ }
1625
+ // ---------------------------------------------------------------------------
1626
+ // Streaming
1627
+ // ---------------------------------------------------------------------------
1628
+ onStreamEvent(event, data) {
1629
+ if (event !== "flags") return;
1630
+ let version;
1631
+ try {
1632
+ version = JSON.parse(data).version;
1633
+ } catch {
1634
+ return;
1635
+ }
1636
+ const current = this.remote ? this.results?.version ?? -1 : this.payload?.version ?? -1;
1637
+ if (typeof version === "number" && version > current) void this.fetchNow();
1638
+ }
1639
+ openStream() {
1640
+ if (this.destroyed || this.offline || this.stream) return;
1641
+ const url = this.url("/flags/stream");
1642
+ const ES = globalThis.EventSource;
1643
+ const opened = () => {
1644
+ this.streamConnected = true;
1645
+ this.streamFailures = 0;
1646
+ if (this.pollTimer) clearTimeout(this.pollTimer);
1647
+ this.pollTimer = null;
1648
+ };
1649
+ if (ES && !this.secret) {
1650
+ const es = new ES(url);
1651
+ es.addEventListener("flags", (e) => this.onStreamEvent("flags", e.data));
1652
+ es.onopen = opened;
1653
+ es.onerror = () => {
1654
+ es.close();
1655
+ if (this.stream === handle2) this.streamDown();
1656
+ };
1657
+ const handle2 = { close: () => es.close() };
1658
+ this.stream = handle2;
1659
+ return;
1660
+ }
1661
+ const f = this.fetcher;
1662
+ if (!f || typeof TextDecoder === "undefined") return this.streamDown(true);
1663
+ const ac = typeof AbortController !== "undefined" ? new AbortController() : null;
1664
+ const handle = { close: () => ac?.abort() };
1665
+ this.stream = handle;
1666
+ void (async () => {
1667
+ try {
1668
+ const res = await f(url, { headers: this.headers({ Accept: "text/event-stream" }), signal: ac?.signal, ...isBrowser() ? { credentials: "omit" } : {} });
1669
+ const reader = res.ok ? res.body?.getReader?.() : void 0;
1670
+ if (!reader) {
1671
+ if (this.stream === handle) this.streamDown(res.ok);
1672
+ return;
1673
+ }
1674
+ opened();
1675
+ const parser = createSseParser((event, data) => this.onStreamEvent(event, data));
1676
+ const decoder = new TextDecoder();
1677
+ for (; ; ) {
1678
+ const { done, value } = await reader.read();
1679
+ if (value) parser.feed(decoder.decode(value, { stream: true }));
1680
+ if (done) break;
1681
+ }
1682
+ parser.end();
1683
+ } catch {
1684
+ }
1685
+ if (this.stream === handle) this.streamDown();
1686
+ })();
1687
+ }
1688
+ /** The stream dropped: poll meanwhile and reconnect with backoff (`permanent`: never retry). */
1689
+ streamDown(permanent = false) {
1690
+ this.stream = null;
1691
+ this.streamConnected = false;
1692
+ if (this.destroyed || this.offline) return;
1693
+ this.schedulePoll();
1694
+ if (permanent) return;
1695
+ this.streamFailures++;
1696
+ if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
1697
+ this.reconnectTimer = timer(() => {
1698
+ this.reconnectTimer = null;
1699
+ this.openStream();
1700
+ void this.fetchNow();
1701
+ }, backoff(this.streamFailures, MAX_BACKOFF_MS));
1702
+ }
1703
+ // ---------------------------------------------------------------------------
1704
+ // Usage + exposures
1705
+ // ---------------------------------------------------------------------------
1706
+ scheduleFlush(ms) {
1707
+ if (this.destroyed) return;
1708
+ const due = Date.now() + ms;
1709
+ if (this.flushTimer && due >= this.flushDue) return;
1710
+ if (this.flushTimer) clearTimeout(this.flushTimer);
1711
+ this.flushDue = due;
1712
+ this.flushTimer = timer(() => {
1713
+ this.flushTimer = null;
1714
+ this.flushDue = Infinity;
1715
+ if (!this.offline) void this.flush();
1716
+ else if (this.usage.size || this.exposures.length) this.scheduleFlush(USAGE_FLUSH_MS);
1717
+ }, ms);
1718
+ }
1719
+ takeBatch() {
1720
+ if (!this.usage.size && !this.exposures.length) return null;
1721
+ if (this.cfg.behaviour === "localOnly") {
1722
+ this.usage.clear();
1723
+ this.exposures = [];
1724
+ return null;
1725
+ }
1726
+ const batch = { sdk: this.o.sdk ?? { name: FLAGS_SDK_NAME, version: FLAGS_SDK_VERSION } };
1727
+ if (this.usage.size) batch.usage = Array.from(this.usage.values());
1728
+ if (this.exposures.length) batch.exposures = this.exposures;
1729
+ this.usage.clear();
1730
+ this.exposures = [];
1731
+ return batch;
1732
+ }
1733
+ requeue(batch) {
1734
+ for (const u of batch.usage ?? []) {
1735
+ const k = `${u.flag}\0${u.variationId ?? ""}\0${u.variationId ? "" : u.value ?? ""}`;
1736
+ const cur = this.usage.get(k);
1737
+ if (cur) cur.count += u.count;
1738
+ else if (this.usage.size < MAX_QUEUE) this.usage.set(k, { flag: u.flag, variationId: u.variationId, value: u.value ?? "", count: u.count });
1739
+ }
1740
+ this.exposures = [...batch.exposures ?? [], ...this.exposures].slice(0, MAX_QUEUE);
1741
+ this.scheduleFlush(USAGE_FLUSH_MS);
1742
+ }
1743
+ /** Page hide / destroy: `sendBeacon` for public keys, else a `keepalive` fetch. */
1744
+ flushOnExit() {
1745
+ if (this.offline) return;
1746
+ const batch = this.takeBatch();
1747
+ if (!batch) return;
1748
+ try {
1749
+ if (!this.secret && typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function" && typeof Blob !== "undefined") {
1750
+ if (navigator.sendBeacon(this.url("/flags/events"), new Blob([JSON.stringify(batch)], { type: "text/plain;charset=UTF-8" }))) return;
1751
+ }
1752
+ } catch {
1753
+ }
1754
+ void this.request("POST", "/flags/events", { body: batch, keepalive: true }).catch(() => {
1755
+ });
1756
+ }
1757
+ wireBrowser() {
1758
+ if (!isBrowser()) return;
1759
+ const onHide = () => this.flushOnExit();
1760
+ const onVisibility = () => {
1761
+ if (document.visibilityState === "hidden") return this.flushOnExit();
1762
+ if ((this.cfg.mode === "auto" || this.cfg.mode === "stream" && !this.streamConnected) && Date.now() - this.lastFetchAt > this.cfg.pollMs) void this.fetchNow().then(() => this.schedule());
1763
+ };
1764
+ window.addEventListener("pagehide", onHide);
1765
+ document.addEventListener("visibilitychange", onVisibility);
1766
+ this.cleanups.push(() => {
1767
+ window.removeEventListener("pagehide", onHide);
1768
+ document.removeEventListener("visibilitychange", onVisibility);
1769
+ });
1770
+ }
1771
+ // ---------------------------------------------------------------------------
1772
+ notify() {
1773
+ for (const l of Array.from(this.listeners)) {
1774
+ try {
1775
+ l();
1776
+ } catch (err) {
1777
+ this.reportError(err);
1778
+ }
1779
+ }
1780
+ }
1781
+ reportError(err) {
1782
+ const error = err instanceof Error ? err : new Error(String(err));
1783
+ if (error.message !== this.lastError) this.log.error?.(error.message);
1784
+ this.lastError = error.message;
1785
+ try {
1786
+ this.o.onError?.(error);
1787
+ } catch {
1788
+ }
1789
+ }
1790
+ };
1791
+ function createFlagsClient(options) {
1792
+ return new FlagsClient(options);
1793
+ }
1794
+
1795
+ // src/code-refs.ts
1796
+ var import_node_child_process = require("child_process");
1797
+ var import_node_fs = require("fs");
1798
+ var import_node_path = require("path");
1799
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "vendor", ".next", ".svelte-kit", ".turbo", "coverage", "target", "out"]);
1800
+ var SKIP_FILES = /(^|\/)(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb?|composer\.lock|Cargo\.lock|Gemfile\.lock|poetry\.lock)$|\.(min\.js|map|png|jpe?g|gif|webp|ico|svg|pdf|zip|gz|woff2?|ttf|eot|mp4|mov|mp3|jar|class|so|dylib|dll|exe|bin)$/i;
1801
+ var MAX_FILE_BYTES = 1024 * 1024;
1802
+ var MAX_REFS = 1e4;
1803
+ function listFiles(dir) {
1804
+ try {
1805
+ const out = (0, import_node_child_process.execFileSync)("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], { cwd: dir, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], maxBuffer: 256 * 1024 * 1024 });
1806
+ const files2 = out.split("\0").filter(Boolean);
1807
+ if (files2.length) return files2.filter((f) => !f.split("/").some((p) => SKIP_DIRS.has(p)));
1808
+ } catch {
1809
+ }
1810
+ const files = [];
1811
+ const walk = (abs) => {
1812
+ let entries;
1813
+ try {
1814
+ entries = (0, import_node_fs.readdirSync)(abs, { withFileTypes: true });
1815
+ } catch {
1816
+ return;
1817
+ }
1818
+ for (const e of entries) {
1819
+ if (e.isDirectory()) {
1820
+ if (!SKIP_DIRS.has(e.name)) walk((0, import_node_path.join)(abs, e.name));
1821
+ } else if (e.isFile()) files.push((0, import_node_path.relative)(dir, (0, import_node_path.join)(abs, e.name)).split(import_node_path.sep).join("/"));
1822
+ }
1823
+ };
1824
+ walk(dir);
1825
+ return files.sort();
1826
+ }
1827
+ var escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1828
+ function scanFiles(dir, files, flagKeys) {
1829
+ const keys = Array.from(new Set(flagKeys.filter(Boolean))).sort((a, b) => b.length - a.length);
1830
+ if (!keys.length) return [];
1831
+ const re = new RegExp(`(['"\`])(${keys.map(escapeRe).join("|")})\\1`, "g");
1832
+ const refs = [];
1833
+ for (const path of files) {
1834
+ if (SKIP_FILES.test(path)) continue;
1835
+ const abs = (0, import_node_path.join)(dir, path);
1836
+ let text;
1837
+ try {
1838
+ const st = (0, import_node_fs.statSync)(abs);
1839
+ if (!st.isFile() || st.size > MAX_FILE_BYTES) continue;
1840
+ const buf = (0, import_node_fs.readFileSync)(abs);
1841
+ if (buf.subarray(0, 8e3).includes(0)) continue;
1842
+ text = buf.toString("utf8");
1843
+ } catch {
1844
+ continue;
1845
+ }
1846
+ if (!keys.some((k) => text.includes(k))) continue;
1847
+ const lines = text.split(/\r?\n/);
1848
+ for (let i = 0; i < lines.length; i++) {
1849
+ const line = lines[i];
1850
+ re.lastIndex = 0;
1851
+ const seen = /* @__PURE__ */ new Set();
1852
+ for (let m = re.exec(line); m; m = re.exec(line)) {
1853
+ const flag = m[2];
1854
+ if (seen.has(flag)) continue;
1855
+ seen.add(flag);
1856
+ refs.push({ flag, path, line: i + 1, snippet: line.trim().slice(0, 200) });
1857
+ if (refs.length >= MAX_REFS) return refs;
1858
+ }
1859
+ }
1860
+ }
1861
+ return refs;
1862
+ }
1863
+ function gitValue(dir, args) {
1864
+ try {
1865
+ return (0, import_node_child_process.execFileSync)("git", args, { cwd: dir, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || void 0;
1866
+ } catch {
1867
+ return void 0;
1868
+ }
1869
+ }
1870
+
1871
+ // src/cli-main.ts
1872
+ var HELP = `guidely-flags \u2014 Guidely feature flag tools
1873
+
1874
+ Usage:
1875
+ guidely-flags code-refs --key gly_sk_\u2026 --environment production --repo <name> [options]
1876
+ guidely-flags evaluate --key <pk_\u2026|gly_sk_\u2026> [--environment <env>] [--flag <key>] [--attributes '{"id":"u1"}']
1877
+
1878
+ code-refs: finds flag keys used as quoted string literals in your code and uploads the
1879
+ references, so the dashboard shows where each flag is used (run it in CI on your main branch).
1880
+ --api-url <url> Guidely URL (default ${DEFAULT_FLAGS_API_URL}; env GUIDELY_API_URL)
1881
+ --key <gly_sk_\u2026> Secret API key with flags:read and flags:code-refs (env GUIDELY_API_KEY)
1882
+ --environment <env> development | staging | production (env GUIDELY_ENVIRONMENT)
1883
+ --repo <name> Repository name, e.g. acme/web (env GITHUB_REPOSITORY)
1884
+ --branch <branch> Default: the current git branch
1885
+ --commit <sha> Default: the current git commit
1886
+ --dir <path> Directory to scan (default .)
1887
+ --decryption-key <key> For environments with encrypted payloads
1888
+ --dry-run Print the references instead of uploading them
1889
+
1890
+ evaluate: prints how a flag (or every flag) evaluates for some attributes.
1891
+ --flag <key> Flag key (default: all flags)
1892
+ --attributes <json> Attributes object (default {})
1893
+
1894
+ Exit codes: 0 success, 1 failure, 2 usage error.
1895
+ `;
1896
+ var UsageError = class extends Error {
1897
+ };
1898
+ function parseArgs(argv) {
1899
+ const flags = {};
1900
+ let command;
1901
+ for (let i = 0; i < argv.length; i++) {
1902
+ const a = argv[i];
1903
+ if (a.startsWith("--")) {
1904
+ const eq = a.indexOf("=");
1905
+ if (eq > 0) flags[a.slice(2, eq)] = a.slice(eq + 1);
1906
+ else if (argv[i + 1] !== void 0 && !argv[i + 1].startsWith("--")) flags[a.slice(2)] = argv[++i];
1907
+ else flags[a.slice(2)] = true;
1908
+ } else if (a === "-h") flags.help = true;
1909
+ else if (!command) command = a;
1910
+ else throw new UsageError(`Unexpected argument: ${a}`);
1911
+ }
1912
+ return { command, flags };
1913
+ }
1914
+ async function main(argv, io = {}) {
1915
+ const env2 = io.env ?? process.env;
1916
+ const out = io.out ?? ((t) => process.stdout.write(t));
1917
+ const err = io.err ?? ((t) => process.stderr.write(t));
1918
+ const doFetch = io.fetch ?? fetch;
1919
+ try {
1920
+ const { command, flags } = parseArgs(argv);
1921
+ if (!command || flags.help || command === "help") {
1922
+ out(HELP);
1923
+ return command || flags.help ? 0 : 2;
1924
+ }
1925
+ const str = (name, envName) => {
1926
+ const v = flags[name];
1927
+ if (v === true) throw new UsageError(`--${name} needs a value`);
1928
+ return v ?? (envName ? env2[envName] : void 0) ?? void 0;
1929
+ };
1930
+ const apiUrl = (str("api-url", "GUIDELY_API_URL") || DEFAULT_FLAGS_API_URL).replace(/\/+$/, "");
1931
+ const key = str("key", "GUIDELY_API_KEY");
1932
+ const environment = str("environment", "GUIDELY_ENVIRONMENT");
1933
+ if (!key) throw new UsageError("--key is required");
1934
+ if (command === "code-refs") {
1935
+ if (!key.startsWith("gly_sk_")) throw new UsageError("code-refs needs a secret gly_sk_ API key");
1936
+ if (!environment) throw new UsageError("--environment is required");
1937
+ const dir = (0, import_node_path2.resolve)(io.cwd ?? process.cwd(), str("dir") ?? ".");
1938
+ const repo = str("repo", "GITHUB_REPOSITORY");
1939
+ if (!repo) throw new UsageError("--repo is required");
1940
+ const branch = str("branch") ?? env2.GITHUB_HEAD_REF ?? env2.GITHUB_REF_NAME ?? gitValue(dir, ["rev-parse", "--abbrev-ref", "HEAD"]);
1941
+ if (!branch) throw new UsageError("--branch is required (not a git checkout)");
1942
+ const commit = str("commit") ?? env2.GITHUB_SHA ?? gitValue(dir, ["rev-parse", "HEAD"]);
1943
+ const auth = { Authorization: `Bearer ${key}`, Accept: "application/json" };
1944
+ const res = await doFetch(`${apiUrl}/api/v1/sdk/flags?environment=${encodeURIComponent(environment)}`, { headers: auth });
1945
+ if (!res.ok) throw new Error(`Downloading flags failed (${res.status}): ${(await res.text()).slice(0, 300)}`);
1946
+ let payload = await res.json();
1947
+ if (isEncryptedPayload(payload)) {
1948
+ const dk = str("decryption-key", "GUIDELY_DECRYPTION_KEY");
1949
+ if (!dk) throw new Error("The payload is encrypted: pass --decryption-key");
1950
+ payload = { ...payload, ...JSON.parse(await decryptPayloadString(payload.encryptedPayload, dk)) };
1951
+ }
1952
+ if (!isFlagsPayload(payload)) throw new Error("Unexpected flags payload");
1953
+ const keys = Object.keys(payload.flags);
1954
+ const files = listFiles(dir);
1955
+ const refs = scanFiles(dir, files, keys);
1956
+ if (refs.length >= MAX_REFS) err(`Stopped after ${MAX_REFS} references.
1957
+ `);
1958
+ if (flags["dry-run"]) {
1959
+ for (const r of refs) out(`${r.flag} ${r.path}:${r.line} ${r.snippet}
1960
+ `);
1961
+ out(`${refs.length} reference(s) to ${new Set(refs.map((r) => r.flag)).size} of ${keys.length} flag(s) in ${files.length} file(s) (dry run, nothing uploaded)
1962
+ `);
1963
+ return 0;
1964
+ }
1965
+ const up = await doFetch(`${apiUrl}/api/v1/flags/code-refs`, { method: "POST", headers: { ...auth, "Content-Type": "application/json" }, body: JSON.stringify({ repo, branch, ...commit ? { commit } : {}, refs }) });
1966
+ if (!up.ok) throw new Error(`Uploading code references failed (${up.status}): ${(await up.text()).slice(0, 300)}`);
1967
+ out(`Uploaded ${refs.length} reference(s) to ${new Set(refs.map((r) => r.flag)).size} flag(s) for ${repo}@${branch}
1968
+ `);
1969
+ return 0;
1970
+ }
1971
+ if (command === "evaluate") {
1972
+ let attributes = {};
1973
+ const raw = str("attributes");
1974
+ if (raw) {
1975
+ try {
1976
+ attributes = JSON.parse(raw);
1977
+ } catch {
1978
+ throw new UsageError("--attributes must be a JSON object");
1979
+ }
1980
+ if (!attributes || typeof attributes !== "object" || Array.isArray(attributes)) throw new UsageError("--attributes must be a JSON object");
1981
+ }
1982
+ let failure = null;
1983
+ const client = createFlagsClient({ sdkKey: key, apiUrl, environment, pollingMode: "manual", reportUsage: false, trackExposures: false, storage: false, logger: null, fetch: doFetch, decryptionKey: str("decryption-key", "GUIDELY_DECRYPTION_KEY"), onError: (e) => failure = e });
1984
+ await client.refresh();
1985
+ client.destroy();
1986
+ if (!client.getPayload()) throw failure ?? new Error("Could not load flags");
1987
+ const user = client.forUser(attributes);
1988
+ const flag = str("flag");
1989
+ const result = flag ? user.getDetails(flag, null) : user.getAll();
1990
+ out(`${JSON.stringify(result, null, 2)}
1991
+ `);
1992
+ return 0;
1993
+ }
1994
+ throw new UsageError(`Unknown command: ${command}`);
1995
+ } catch (e) {
1996
+ if (e instanceof UsageError) {
1997
+ err(`${e.message}
1998
+
1999
+ ${HELP}`);
2000
+ return 2;
2001
+ }
2002
+ err(`guidely-flags: ${e instanceof Error ? e.message : String(e)}
2003
+ `);
2004
+ return 1;
2005
+ }
2006
+ }
2007
+
2008
+ // src/cli.ts
2009
+ main(process.argv.slice(2)).then((code) => process.exit(code));
2010
+ //# sourceMappingURL=cli.cjs.map