@sleds/cli 0.1.64 → 0.1.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +20 -0
  2. package/dist/sleds.mjs +1132 -58
  3. package/package.json +1 -1
package/dist/sleds.mjs CHANGED
@@ -6061,7 +6061,6 @@ var init_adf = __esm(() => {
6061
6061
  "decisionList"
6062
6062
  ]);
6063
6063
  });
6064
-
6065
6064
  // ../../packages/shared/src/help/whatsNew.ts
6066
6065
  var SEPT_30 = "/whats-new/2026-09-30", WHATS_NEW;
6067
6066
  var init_whatsNew = __esm(() => {
@@ -6836,6 +6835,30 @@ var init_stability = __esm(() => {
6836
6835
  init_series();
6837
6836
  });
6838
6837
 
6838
+ // ../../packages/shared/src/deploys/shipping.ts
6839
+ var DAY_MS5;
6840
+ var init_shipping = __esm(() => {
6841
+ DAY_MS5 = 24 * 60 * 60 * 1000;
6842
+ });
6843
+
6844
+ // ../../packages/shared/src/deploys/doraBands.ts
6845
+ var HOUR_MS2, DAY_MS6, WEEK_MS3, MONTH_DAYS = 30, MONTH_MS;
6846
+ var init_doraBands = __esm(() => {
6847
+ HOUR_MS2 = 60 * 60 * 1000;
6848
+ DAY_MS6 = 24 * HOUR_MS2;
6849
+ WEEK_MS3 = 7 * DAY_MS6;
6850
+ MONTH_MS = MONTH_DAYS * DAY_MS6;
6851
+ });
6852
+
6853
+ // ../../packages/shared/src/deploys/trends.ts
6854
+ var DAY_MS7;
6855
+ var init_trends = __esm(() => {
6856
+ init_batchSize();
6857
+ init_metrics();
6858
+ init_series();
6859
+ DAY_MS7 = 24 * 60 * 60 * 1000;
6860
+ });
6861
+
6839
6862
  // ../../packages/shared/src/newrelic/newrelic.ts
6840
6863
  var init_newrelic = () => {};
6841
6864
 
@@ -8353,6 +8376,9 @@ var init_src = __esm(() => {
8353
8376
  init_batchSize();
8354
8377
  init_report();
8355
8378
  init_series();
8379
+ init_shipping();
8380
+ init_doraBands();
8381
+ init_trends();
8356
8382
  init_sentry();
8357
8383
  init_newrelic();
8358
8384
  init_musher();
@@ -9071,8 +9097,8 @@ var init_seat = __esm(() => {
9071
9097
  });
9072
9098
 
9073
9099
  // src/index.ts
9074
- import { existsSync as existsSync36, realpathSync as realpathSync3 } from "fs";
9075
- import { homedir as homedir10, platform as platform3 } from "os";
9100
+ import { existsSync as existsSync37, realpathSync as realpathSync3 } from "fs";
9101
+ import { homedir as homedir11, platform as platform3 } from "os";
9076
9102
  import { fileURLToPath } from "url";
9077
9103
 
9078
9104
  // ../../packages/shared/src/index.ts
@@ -9124,6 +9150,9 @@ init_stability();
9124
9150
  init_batchSize();
9125
9151
  init_report();
9126
9152
  init_series();
9153
+ init_shipping();
9154
+ init_doraBands();
9155
+ init_trends();
9127
9156
  init_sentry();
9128
9157
  init_newrelic();
9129
9158
  init_musher();
@@ -11793,7 +11822,7 @@ function beatBody(state, flags, host = {}, cap = {}) {
11793
11822
  name: flags.name,
11794
11823
  os: host.os ?? process.platform,
11795
11824
  runner: flags.runner,
11796
- version: host.version ?? "0.1.64" ?? "dev",
11825
+ version: host.version ?? "0.1.65" ?? "dev",
11797
11826
  running: live.length,
11798
11827
  items: live.slice(0, BEAT_ITEMS).map((r) => `${r.item}/${r.node}`),
11799
11828
  ...cap.atOnce !== undefined ? { atOnce: cap.atOnce } : {},
@@ -18611,7 +18640,7 @@ async function fetchVersion(o) {
18611
18640
  const platform = o.platform ?? process.platform;
18612
18641
  const arch = o.arch ?? process.arch;
18613
18642
  const publicKey = o.publicKey ?? RELEASE_PUBLIC_KEY;
18614
- const running = o.running ?? "0.1.64";
18643
+ const running = o.running ?? "0.1.65";
18615
18644
  if (!publicKey)
18616
18645
  return { ok: false, why: "no release key yet" };
18617
18646
  if (!VERSION_RE2.test(o.version))
@@ -19837,7 +19866,7 @@ function engineUpdater(o) {
19837
19866
 
19838
19867
  // src/version.ts
19839
19868
  function cliVersion(env) {
19840
- return "0.1.64";
19869
+ return "0.1.65";
19841
19870
  }
19842
19871
 
19843
19872
  // src/commands/update.ts
@@ -20223,7 +20252,7 @@ function engineClient(o, org, breaker) {
20223
20252
  ...src,
20224
20253
  ...breaker ? { fetchFn: breaker.fetch } : {},
20225
20254
  timeoutMs: ENGINE_TIMEOUT_MS,
20226
- version: "0.1.64"
20255
+ version: "0.1.65"
20227
20256
  });
20228
20257
  const asked = org?.trim() || env.SLEDS_ORG?.trim();
20229
20258
  if (asked && !selectOrg(readConfig(o.home), org, env).seat)
@@ -20529,7 +20558,7 @@ async function driveEngine(o, flags, deps, until = {}) {
20529
20558
  runner: deps.runner.name,
20530
20559
  atOnce: flags.atOnce,
20531
20560
  interval: flags.interval,
20532
- cliVersion: "0.1.64"
20561
+ cliVersion: "0.1.65"
20533
20562
  };
20534
20563
  const beat = (checkedAt) => {
20535
20564
  const m = deps.machine;
@@ -22398,7 +22427,7 @@ function engineClient2(o, org, breaker) {
22398
22427
  ...src,
22399
22428
  ...breaker ? { fetchFn: breaker.fetch } : {},
22400
22429
  timeoutMs: ENGINE_TIMEOUT_MS2,
22401
- version: "0.1.64"
22430
+ version: "0.1.65"
22402
22431
  });
22403
22432
  const asked = org?.trim() || env.SLEDS_ORG?.trim();
22404
22433
  if (asked && !selectOrg(readConfig(o.home), org, env).seat)
@@ -22739,7 +22768,7 @@ async function driveEngine2(o, flags, deps, until = {}) {
22739
22768
  runner: deps.runner.name,
22740
22769
  atOnce: flags.atOnce,
22741
22770
  interval: flags.interval,
22742
- cliVersion: "0.1.64"
22771
+ cliVersion: "0.1.65"
22743
22772
  };
22744
22773
  const beat = (checkedAt) => {
22745
22774
  const m = deps.machine;
@@ -24850,7 +24879,7 @@ function contextFor(deps) {
24850
24879
  ...src,
24851
24880
  fetchFn: deps.fetchFn,
24852
24881
  timeoutMs: deps.timeoutMs,
24853
- version: "0.1.64"
24882
+ version: "0.1.65"
24854
24883
  }), deps.home, now) : null;
24855
24884
  return {
24856
24885
  channel: "code",
@@ -28367,9 +28396,1048 @@ var sessionEvent = async (o) => {
28367
28396
  return 0;
28368
28397
  };
28369
28398
 
28399
+ // src/commands/sleuth.ts
28400
+ import { homedir as homedir8 } from "node:os";
28401
+ import { join as join35, resolve as resolve12 } from "node:path";
28402
+
28403
+ // src/sleuth/export.ts
28404
+ import { existsSync as existsSync31, mkdirSync as mkdirSync28, readFileSync as readFileSync32, renameSync as renameSync13, writeFileSync as writeFileSync28 } from "node:fs";
28405
+ import { dirname as dirname25, join as join34 } from "node:path";
28406
+
28407
+ // src/sleuth/graphql.ts
28408
+ var SLEUTH_URL = "https://app.sleuth.io/graphql";
28409
+
28410
+ class SleuthError extends Error {
28411
+ adaptable;
28412
+ constructor(message, adaptable) {
28413
+ super(message);
28414
+ this.adaptable = adaptable;
28415
+ }
28416
+ }
28417
+ var RATE = /rate.?limit|throttl|too many requests/i;
28418
+ var backoffMs = (attempt, retryAfter) => {
28419
+ const s = Number(retryAfter);
28420
+ if (retryAfter && Number.isFinite(s) && s >= 0)
28421
+ return Math.min(s, 300) * 1000;
28422
+ return Math.min(60000, 1000 * 2 ** attempt);
28423
+ };
28424
+ var sleuthClient = (o) => {
28425
+ const url = o.url ?? SLEUTH_URL;
28426
+ const fetchFn = o.fetch ?? ((u, init) => fetch(u, init));
28427
+ const sleep = o.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
28428
+ const retries = o.retries ?? 6;
28429
+ const timeoutMs = o.timeoutMs ?? 120000;
28430
+ let scheme = "apikey";
28431
+ let settled = false;
28432
+ let count = 0;
28433
+ const request = async (query) => {
28434
+ let lastWhy = "";
28435
+ for (let attempt = 0;attempt <= retries; attempt++) {
28436
+ let res;
28437
+ count++;
28438
+ try {
28439
+ res = await fetchFn(url, {
28440
+ method: "POST",
28441
+ headers: { "content-type": "application/json", authorization: `${scheme} ${o.key}` },
28442
+ body: JSON.stringify({ query }),
28443
+ signal: AbortSignal.timeout(timeoutMs)
28444
+ });
28445
+ } catch (e) {
28446
+ lastWhy = e instanceof Error ? e.message : String(e);
28447
+ o.log?.(`sleuth: ${lastWhy} — retrying`);
28448
+ await sleep(backoffMs(attempt, null));
28449
+ continue;
28450
+ }
28451
+ if ((res.status === 401 || res.status === 403) && !settled) {
28452
+ settled = true;
28453
+ scheme = "Bearer";
28454
+ attempt--;
28455
+ continue;
28456
+ }
28457
+ if (res.status === 401 || res.status === 403)
28458
+ throw new SleuthError(`Sleuth refused the key (HTTP ${res.status}) — check SLEUTH_API_KEY`, false);
28459
+ if (res.status === 429 || res.status >= 500) {
28460
+ lastWhy = `HTTP ${res.status}`;
28461
+ const wait = backoffMs(attempt, res.headers.get("retry-after"));
28462
+ o.log?.(`sleuth: ${lastWhy} — waiting ${Math.round(wait / 1000)}s`);
28463
+ await sleep(wait);
28464
+ continue;
28465
+ }
28466
+ const text = await res.text();
28467
+ let body;
28468
+ try {
28469
+ body = JSON.parse(text);
28470
+ } catch {
28471
+ throw new SleuthError(`Sleuth answered HTTP ${res.status} with no JSON`, res.status < 500);
28472
+ }
28473
+ const errors = Array.isArray(body.errors) ? body.errors : [];
28474
+ if (res.status >= 400 && !errors.length)
28475
+ throw new SleuthError(`Sleuth answered HTTP ${res.status}`, false);
28476
+ settled = true;
28477
+ if (!body.data && errors.some((e) => RATE.test(e.message))) {
28478
+ lastWhy = errors[0]?.message ?? "rate limited";
28479
+ await sleep(backoffMs(attempt, null));
28480
+ continue;
28481
+ }
28482
+ return { data: body.data ?? null, errors };
28483
+ }
28484
+ throw new SleuthError(`Sleuth did not answer after ${retries + 1} tries (${lastWhy})`, true);
28485
+ };
28486
+ return { request, calls: () => count };
28487
+ };
28488
+
28489
+ // src/sleuth/schema.ts
28490
+ var TYPE_REF = "kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } }";
28491
+ var INTROSPECTION_QUERY = `query SledsSleuthIntrospection { __schema { queryType { name } types { kind name fields(includeDeprecated: false) { name args { name defaultValue type { ${TYPE_REF} } } type { ${TYPE_REF} } } inputFields { name defaultValue type { ${TYPE_REF} } } } } }`;
28492
+ var indexSchema = (intro) => ({
28493
+ query: intro.__schema.queryType.name,
28494
+ types: new Map(intro.__schema.types.map((t) => [t.name, t]))
28495
+ });
28496
+ var unwrap = (t) => {
28497
+ let cur = t;
28498
+ let list = false;
28499
+ const required = t.kind === "NON_NULL";
28500
+ while (cur && (cur.kind === "NON_NULL" || cur.kind === "LIST")) {
28501
+ if (cur.kind === "LIST")
28502
+ list = true;
28503
+ cur = cur.ofType;
28504
+ }
28505
+ return { name: cur?.name ?? "", list, required };
28506
+ };
28507
+ var kindOf = (s, name) => s.types.get(name)?.kind ?? "";
28508
+ var isLeaf = (s, name) => ["SCALAR", "ENUM"].includes(kindOf(s, name));
28509
+ var isComposite = (s, name) => ["OBJECT", "INTERFACE"].includes(kindOf(s, name));
28510
+ var isRequired = (a) => a.type.kind === "NON_NULL" && (a.defaultValue === null || a.defaultValue === undefined);
28511
+ var fieldOf2 = (s, type, name) => s.types.get(type)?.fields?.find((f) => f.name === name);
28512
+ var ORG = /^(org|orgSlug|organization|organizationSlug|orgId)$/i;
28513
+ var START = /^(start|startDate|startTime|startAt|from|fromDate|dateFrom|since|begin|after)$/i;
28514
+ var END = /^(end|endDate|endTime|endAt|to|toDate|dateTo|until|before)$/i;
28515
+ var CURSOR = /^(after|cursor|nextCursor|pageCursor|pageToken)$/i;
28516
+ var PAGE = /^(page|pageNumber|pageNum)$/i;
28517
+ var SIZE = /^(first|pageSize|limit|perPage|per_page|size|count|pageLimit)$/i;
28518
+ var OFFSET = /^(offset|skip)$/i;
28519
+ var DATEISH = /date|time/i;
28520
+ var dateArg = (path, typeName) => ({
28521
+ path,
28522
+ dateOnly: /^date$/i.test(typeName)
28523
+ });
28524
+ var planArgs = (s, args) => {
28525
+ const plan = { org: [], missing: [] };
28526
+ const visit = (vals, prefix, depth) => {
28527
+ for (const a of vals) {
28528
+ const u = unwrap(a.type);
28529
+ const path = [...prefix, a.name];
28530
+ const leaf = isLeaf(s, u.name);
28531
+ const dated = leaf && (DATEISH.test(u.name) || u.name === "String" && DATEISH.test(a.name) && (START.test(a.name) || END.test(a.name)));
28532
+ if (kindOf(s, u.name) === "INPUT_OBJECT") {
28533
+ const before = JSON.stringify(plan);
28534
+ const inputs = s.types.get(u.name)?.inputFields ?? [];
28535
+ if (depth < 3)
28536
+ visit(inputs, path, depth + 1);
28537
+ if (isRequired(a) && JSON.stringify(plan) === before && inputs.some(isRequired))
28538
+ plan.missing.push(path.join("."));
28539
+ continue;
28540
+ }
28541
+ if (leaf && !u.list && ORG.test(a.name))
28542
+ plan.org.push(path);
28543
+ else if (dated && START.test(a.name) && !plan.start)
28544
+ plan.start = dateArg(path, u.name);
28545
+ else if (dated && END.test(a.name) && !plan.end)
28546
+ plan.end = dateArg(path, u.name);
28547
+ else if (leaf && !dated && CURSOR.test(a.name) && !plan.cursor)
28548
+ plan.cursor = path;
28549
+ else if (leaf && PAGE.test(a.name) && !plan.page)
28550
+ plan.page = { path, first: Number(a.defaultValue ?? 1) || 0 };
28551
+ else if (leaf && SIZE.test(a.name) && !plan.size)
28552
+ plan.size = path;
28553
+ else if (leaf && OFFSET.test(a.name) && !plan.offset)
28554
+ plan.offset = path;
28555
+ else if (isRequired(a))
28556
+ plan.missing.push(path.join("."));
28557
+ }
28558
+ };
28559
+ visit(args, [], 0);
28560
+ return plan;
28561
+ };
28562
+ var windowed = (p) => !!(p.start && p.end);
28563
+ var pageable = (p) => !!(p.cursor || p.page || p.offset);
28564
+ var iso3 = (d, dateOnly) => dateOnly ? d.toISOString().slice(0, 10) : d.toISOString().replace(/\.\d{3}Z$/, "Z");
28565
+ var gqlValue = (v) => {
28566
+ if (typeof v === "string")
28567
+ return JSON.stringify(v);
28568
+ if (typeof v === "number" || typeof v === "boolean")
28569
+ return String(v);
28570
+ if (Array.isArray(v))
28571
+ return `[${v.map(gqlValue).join(", ")}]`;
28572
+ if (v && typeof v === "object")
28573
+ return `{${Object.entries(v).map(([k, x]) => `${k}: ${gqlValue(x)}`).join(", ")}}`;
28574
+ return "null";
28575
+ };
28576
+ var renderArgs = (p, v) => {
28577
+ const root = {};
28578
+ const put = (path, value) => {
28579
+ if (!path || value === undefined)
28580
+ return;
28581
+ let o = root;
28582
+ for (const k of path.slice(0, -1)) {
28583
+ if (!o[k])
28584
+ o[k] = {};
28585
+ o = o[k];
28586
+ }
28587
+ o[path[path.length - 1]] = value;
28588
+ };
28589
+ for (const path of p.org)
28590
+ put(path, v.org);
28591
+ if (p.start && v.start)
28592
+ put(p.start.path, iso3(v.start, p.start.dateOnly));
28593
+ if (p.end && v.end)
28594
+ put(p.end.path, iso3(v.end, p.end.dateOnly));
28595
+ put(p.cursor, v.cursor);
28596
+ put(p.page?.path, v.page);
28597
+ put(p.size, v.size);
28598
+ put(p.offset, v.offset);
28599
+ const body = gqlValue(root);
28600
+ return body === "{}" ? "" : `(${body.slice(1, -1)})`;
28601
+ };
28602
+ var ITEMS = ["objects", "nodes", "items", "results", "edges", "data", "entries", "records"];
28603
+ var HAS_NEXT = /^(hasNextPage|hasNext|hasMore|more)$/;
28604
+ var NEXT = /^(endCursor|nextCursor|cursor|next|nextPageCursor|nextPage)$/;
28605
+ var TOTAL = /^(totalCount|total|count|totalObjects|totalItems)$/;
28606
+ var shapeOf = (s, f) => {
28607
+ const u = unwrap(f.type);
28608
+ if (!isComposite(s, u.name))
28609
+ return;
28610
+ if (u.list)
28611
+ return { kind: "list", item: u.name };
28612
+ const t = s.types.get(u.name);
28613
+ const lists = (t?.fields ?? []).filter((x) => {
28614
+ const xu = unwrap(x.type);
28615
+ return xu.list && isComposite(s, xu.name) && !x.args.some(isRequired);
28616
+ });
28617
+ const rank = (n) => ITEMS.includes(n) ? ITEMS.indexOf(n) : ITEMS.length;
28618
+ const itemsField = [...lists].sort((a, b) => rank(a.name) - rank(b.name))[0];
28619
+ const scalars = (t?.fields ?? []).filter((x) => isLeaf(s, unwrap(x.type).name));
28620
+ const info = (t?.fields ?? []).find((x) => /^pageInfo$/i.test(x.name) && isComposite(s, unwrap(x.type).name));
28621
+ const infoScalars = info ? (s.types.get(unwrap(info.type).name)?.fields ?? []).filter((x) => isLeaf(s, unwrap(x.type).name)) : [];
28622
+ const find = (re) => {
28623
+ const own = scalars.find((x) => re.test(x.name));
28624
+ if (own)
28625
+ return [own.name];
28626
+ const inInfo = infoScalars.find((x) => re.test(x.name));
28627
+ return inInfo && info ? [info.name, inInfo.name] : undefined;
28628
+ };
28629
+ const hasNext = find(HAS_NEXT);
28630
+ const next = find(NEXT);
28631
+ const total = find(TOTAL);
28632
+ const looksLikePage = itemsField && (ITEMS.includes(itemsField.name) || hasNext || next || total || info);
28633
+ if (!itemsField || !looksLikePage)
28634
+ return { kind: "object", item: u.name };
28635
+ let item = unwrap(itemsField.type).name;
28636
+ let node;
28637
+ if (itemsField.name === "edges") {
28638
+ const n = fieldOf2(s, item, "node");
28639
+ if (n && isComposite(s, unwrap(n.type).name)) {
28640
+ node = "node";
28641
+ item = unwrap(n.type).name;
28642
+ }
28643
+ }
28644
+ return {
28645
+ kind: "page",
28646
+ item,
28647
+ items: itemsField.name,
28648
+ ...node ? { node } : {},
28649
+ ...hasNext ? { hasNext } : {},
28650
+ ...next ? { next } : {},
28651
+ ...total ? { total } : {}
28652
+ };
28653
+ };
28654
+ var wrapShape = (shape, inner) => {
28655
+ if (shape.kind !== "page")
28656
+ return inner;
28657
+ const paths = [shape.hasNext, shape.next, shape.total].filter((p) => !!p);
28658
+ const own = paths.filter((p) => p.length === 1).map((p) => p[0]);
28659
+ const info = paths.filter((p) => p.length === 2);
28660
+ const infoName = info[0]?.[0];
28661
+ const infoSel = infoName ? ` ${infoName} { ${[...new Set(info.map((p) => p[1]))].join(" ")} }` : "";
28662
+ const items = shape.node ? `${shape.items} { ${shape.node} ${inner} }` : `${shape.items} ${inner}`;
28663
+ return `{ ${items}${own.length ? ` ${[...new Set(own)].join(" ")}` : ""}${infoSel} }`;
28664
+ };
28665
+ var at = (v, path) => {
28666
+ if (!path)
28667
+ return;
28668
+ let cur = v;
28669
+ for (const k of path)
28670
+ cur = cur && typeof cur === "object" ? cur[k] : undefined;
28671
+ return cur;
28672
+ };
28673
+ var readShape = (shape, v) => {
28674
+ const objs = (xs) => Array.isArray(xs) ? xs.filter((x) => !!x && typeof x === "object") : [];
28675
+ if (v === null || v === undefined)
28676
+ return { items: [], info: {} };
28677
+ if (shape.kind === "object")
28678
+ return { items: objs([v]), info: {} };
28679
+ if (shape.kind === "list")
28680
+ return { items: objs(v), info: {} };
28681
+ const raw = objs(at(v, [shape.items]));
28682
+ const items = shape.node ? objs(raw.map((e) => e[shape.node])) : raw;
28683
+ const hasNext = at(v, shape.hasNext);
28684
+ const next = at(v, shape.next);
28685
+ const total = at(v, shape.total);
28686
+ return {
28687
+ items,
28688
+ info: {
28689
+ ...typeof hasNext === "boolean" ? { hasNext } : {},
28690
+ ...typeof next === "string" && next ? { next } : {},
28691
+ ...typeof next === "number" ? { next: String(next) } : {},
28692
+ ...typeof total === "number" ? { total } : {}
28693
+ }
28694
+ };
28695
+ };
28696
+ var IDENTITY = ["id", "uid", "slug", "key", "name"];
28697
+ var buildSelection = (s, type, depth, exclude = new Set, above = []) => {
28698
+ const t = s.types.get(type);
28699
+ if (!t?.fields)
28700
+ return null;
28701
+ const parts = [];
28702
+ for (const f of t.fields) {
28703
+ if (f.name.startsWith("__") || exclude.has(`${type}.${f.name}`))
28704
+ continue;
28705
+ if (f.args.some(isRequired))
28706
+ continue;
28707
+ const u = unwrap(f.type);
28708
+ if (isLeaf(s, u.name)) {
28709
+ parts.push(f.name);
28710
+ continue;
28711
+ }
28712
+ if (!isComposite(s, u.name))
28713
+ continue;
28714
+ const shape = shapeOf(s, f);
28715
+ if (pageable(planArgs(s, f.args)) || shape?.kind === "page")
28716
+ continue;
28717
+ const sub = depth > 0 && ![...above, type].includes(u.name) ? buildSelection(s, u.name, depth - 1, exclude, [...above, type]) : identitySelection(s, u.name, exclude);
28718
+ if (sub)
28719
+ parts.push(`${f.name} ${sub}`);
28720
+ }
28721
+ return parts.length ? `{ ${parts.join(" ")} }` : null;
28722
+ };
28723
+ var identitySelection = (s, type, exclude = new Set) => {
28724
+ const fields = (s.types.get(type)?.fields ?? []).filter((f) => IDENTITY.includes(f.name) && !exclude.has(`${type}.${f.name}`) && !f.args.some(isRequired) && isLeaf(s, unwrap(f.type).name));
28725
+ return fields.length ? `{ ${fields.map((f) => f.name).join(" ")} }` : null;
28726
+ };
28727
+ var typeFieldAt = (s, path) => {
28728
+ let type = s.query;
28729
+ let last;
28730
+ for (const seg of path) {
28731
+ if (typeof seg === "number")
28732
+ continue;
28733
+ const f = fieldOf2(s, type, seg);
28734
+ if (!f)
28735
+ return;
28736
+ last = `${type}.${seg}`;
28737
+ type = unwrap(f.type).name;
28738
+ }
28739
+ return last;
28740
+ };
28741
+ var findSection = (s, patterns, maxSteps = 4) => {
28742
+ for (const re of patterns) {
28743
+ let frontier = [{ type: s.query, steps: [] }];
28744
+ for (let d = 0;d < maxSteps && frontier.length; d++) {
28745
+ const next = [];
28746
+ for (const { type, steps } of frontier) {
28747
+ for (const f of s.types.get(type)?.fields ?? []) {
28748
+ const shape = shapeOf(s, f);
28749
+ if (!shape)
28750
+ continue;
28751
+ const args = planArgs(s, f.args);
28752
+ if (args.missing.length)
28753
+ continue;
28754
+ if (d === 0 && !args.org.length)
28755
+ continue;
28756
+ const step = { field: f, parent: type, args, shape };
28757
+ if (re.test(f.name) && shape.kind !== "object")
28758
+ return [...steps, step];
28759
+ if (shape.kind === "page" || pageable(args) || args.start || args.end)
28760
+ continue;
28761
+ if (steps.some((x) => x.shape.item === shape.item) || shape.item === s.query)
28762
+ continue;
28763
+ next.push({ type: shape.item, steps: [...steps, step] });
28764
+ }
28765
+ }
28766
+ frontier = next;
28767
+ }
28768
+ }
28769
+ return;
28770
+ };
28771
+ var structural = (steps) => {
28772
+ const out = new Set;
28773
+ for (const st of steps) {
28774
+ out.add(`${st.parent}.${st.field.name}`);
28775
+ if (st.shape.kind === "page") {
28776
+ const pageType = unwrap(st.field.type).name;
28777
+ out.add(`${pageType}.${st.shape.items}`);
28778
+ }
28779
+ }
28780
+ return out;
28781
+ };
28782
+
28783
+ // src/sleuth/export.ts
28784
+ var SECTIONS = [
28785
+ { name: "deploys", patterns: [/^orgChanges$/, /^changes$/, /^deploys$/], byMonth: true },
28786
+ { name: "projects", patterns: [/^projects$/] },
28787
+ { name: "deployments", patterns: [/^deployments$/, /^codeDeployments$/] },
28788
+ { name: "environments", patterns: [/^environments$/] },
28789
+ { name: "teams", patterns: [/^teams$/] },
28790
+ { name: "labels", patterns: [/^labels$/] },
28791
+ { name: "impactSources", patterns: [/^impactSources$/] },
28792
+ { name: "incidents", patterns: [/^incidents$/, /^orgIncidents$/, /incidents$/i], byMonth: true },
28793
+ {
28794
+ name: "flagEvents",
28795
+ patterns: [
28796
+ /^(flagEvents|featureFlagEvents|flagChanges|featureFlagChanges)$/,
28797
+ /flag\w*(event|change)s$/i
28798
+ ],
28799
+ byMonth: true
28800
+ }
28801
+ ];
28802
+ var DAY = 86400000;
28803
+ var MIN_PAGE = 10;
28804
+ var MAX_PAGES = 5000;
28805
+ var MONTH_BUDGET = 8;
28806
+ var MAX_FAILED_MONTHS = 3;
28807
+ var NESTED_SIZE = 500;
28808
+ var MAX_WARNINGS = 200;
28809
+ var DATE_KEYS = [
28810
+ "date",
28811
+ "deployedAt",
28812
+ "deployDate",
28813
+ "createdAt",
28814
+ "startedAt",
28815
+ "occurredAt",
28816
+ "timestamp",
28817
+ "time",
28818
+ "created"
28819
+ ];
28820
+ var MONTH = /^(\d{4})-(0[1-9]|1[0-2])$/;
28821
+ var monthsBetween = (since, until) => {
28822
+ const a = MONTH.exec(since);
28823
+ const b = MONTH.exec(until);
28824
+ if (!a || !b)
28825
+ return [];
28826
+ const out = [];
28827
+ let y = Number(a[1]);
28828
+ let m = Number(a[2]);
28829
+ const endKey = Number(b[1]) * 12 + Number(b[2]);
28830
+ while (y * 12 + m <= endKey) {
28831
+ out.push(`${y}-${String(m).padStart(2, "0")}`);
28832
+ m++;
28833
+ if (m > 12) {
28834
+ m = 1;
28835
+ y++;
28836
+ }
28837
+ }
28838
+ return out;
28839
+ };
28840
+ var monthWindow = (month) => {
28841
+ const [y, m] = month.split("-").map(Number);
28842
+ return { start: new Date(Date.UTC(y, m - 1, 1)), end: new Date(Date.UTC(y, m, 1)) };
28843
+ };
28844
+ var monthOf = (d) => d.toISOString().slice(0, 7);
28845
+ var keyOf2 = (item) => {
28846
+ for (const k of ["id", "uid"]) {
28847
+ const v = item[k];
28848
+ if (typeof v === "string" || typeof v === "number")
28849
+ return `${k}:${v}`;
28850
+ }
28851
+ if (typeof item.slug === "string")
28852
+ return `slug:${JSON.stringify(item._parents ?? {})}:${item.slug}`;
28853
+ return `json:${JSON.stringify(item)}`;
28854
+ };
28855
+ var itemMonth = (item) => {
28856
+ for (const k of DATE_KEYS) {
28857
+ const v = item[k];
28858
+ if (typeof v !== "string" && typeof v !== "number")
28859
+ continue;
28860
+ const d = new Date(v);
28861
+ if (!Number.isNaN(d.getTime()))
28862
+ return monthOf(d);
28863
+ }
28864
+ return;
28865
+ };
28866
+ var IDENTITY2 = ["id", "uid", "slug", "key", "name"];
28867
+ var identity = (item) => Object.fromEntries(IDENTITY2.filter((k) => item[k] !== undefined).map((k) => [k, item[k]]));
28868
+ var writeAtomic = (path, body) => {
28869
+ mkdirSync28(dirname25(path), { recursive: true });
28870
+ const tmp = `${path}.partial`;
28871
+ writeFileSync28(tmp, body);
28872
+ renameSync13(tmp, path);
28873
+ };
28874
+ var jsonl = (items) => items.length ? `${items.map((i) => JSON.stringify(i)).join(`
28875
+ `)}
28876
+ ` : "";
28877
+ var readJsonl = (path) => readFileSync32(path, "utf8").split(`
28878
+ `).filter((l) => l.trim()).map((l) => JSON.parse(l));
28879
+ var CANNOT_QUERY = /Cannot query field "(\w+)" on type "(\w+)"/;
28880
+ var exportSleuth = async (o) => {
28881
+ const log = o.log ?? (() => {});
28882
+ const now = (o.now ?? (() => new Date))();
28883
+ const currentMonth = monthOf(now);
28884
+ const exclude = new Set;
28885
+ const warnings = new Set;
28886
+ const warn = (line) => {
28887
+ if (warnings.size < MAX_WARNINGS)
28888
+ warnings.add(line);
28889
+ };
28890
+ const manifest = {
28891
+ v: 1,
28892
+ tool: "sleds sleuth export",
28893
+ ...o.cliVersion ? { cliVersion: o.cliVersion } : {},
28894
+ org: o.org,
28895
+ endpoint: o.endpoint,
28896
+ startedAt: now.toISOString(),
28897
+ since: o.since,
28898
+ until: o.until,
28899
+ depth: o.depth,
28900
+ pageSize: o.pageSize,
28901
+ schema: { file: "schema.json", types: 0 },
28902
+ sections: {},
28903
+ pruned: [],
28904
+ warnings: [],
28905
+ mismatches: [],
28906
+ requests: 0,
28907
+ complete: false
28908
+ };
28909
+ const save = () => {
28910
+ manifest.pruned = [...exclude].sort();
28911
+ manifest.warnings = [...warnings];
28912
+ manifest.requests = o.client.calls();
28913
+ writeAtomic(join34(o.out, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
28914
+ `);
28915
+ };
28916
+ mkdirSync28(o.out, { recursive: true });
28917
+ log("reading Sleuth's schema");
28918
+ const intro = await o.client.request(INTROSPECTION_QUERY);
28919
+ if (!intro.data?.__schema)
28920
+ throw new SleuthError(`Sleuth did not answer the schema query${intro.errors[0] ? ` — ${intro.errors[0].message}` : ""}`, false);
28921
+ writeAtomic(join34(o.out, "schema.json"), `${JSON.stringify(intro.data, null, 2)}
28922
+ `);
28923
+ const schema = indexSchema(intro.data);
28924
+ manifest.schema.types = schema.types.size;
28925
+ const ask = async (build, steps) => {
28926
+ const keep = structural(steps);
28927
+ const root = steps[0]?.field.name ?? "";
28928
+ for (let i = 0;i < 25; i++) {
28929
+ const r = await o.client.request(build());
28930
+ if (r.data && !r.errors.length)
28931
+ return r.data;
28932
+ const prune = new Set;
28933
+ for (const e of r.errors) {
28934
+ const m = CANNOT_QUERY.exec(e.message);
28935
+ const tf = e.path && typeFieldAt(schema, e.path) || (m ? `${m[2]}.${m[1]}` : undefined);
28936
+ if (tf && !keep.has(tf) && !exclude.has(tf))
28937
+ prune.add(tf);
28938
+ }
28939
+ const empty = !r.data || r.data[root] === null || r.data[root] === undefined;
28940
+ if (empty && prune.size) {
28941
+ for (const tf of prune) {
28942
+ exclude.add(tf);
28943
+ log(`sleuth refused ${tf} — leaving it out`);
28944
+ }
28945
+ continue;
28946
+ }
28947
+ const why = r.errors.slice(0, 3).map((e) => e.message).join("; ");
28948
+ if (empty)
28949
+ throw new SleuthError(why || "Sleuth answered with no data", true);
28950
+ for (const e of r.errors) {
28951
+ const tf = e.path ? typeFieldAt(schema, e.path) : undefined;
28952
+ warn(`${tf ? `${tf}: ` : ""}${e.message}`);
28953
+ }
28954
+ return r.data;
28955
+ }
28956
+ throw new SleuthError("Sleuth kept refusing fields after 25 tries", false);
28957
+ };
28958
+ const buildQuery = (steps, leafValues) => {
28959
+ const last = steps.length - 1;
28960
+ const argsAt = (i) => {
28961
+ const st = steps[i];
28962
+ if (i === last)
28963
+ return renderArgs(st.args, leafValues);
28964
+ return renderArgs(st.args, { org: o.org, ...st.args.size ? { size: NESTED_SIZE } : {} });
28965
+ };
28966
+ const leaf = steps[last];
28967
+ const sel = buildSelection(schema, leaf.shape.item, o.depth, exclude) ?? "{ __typename }";
28968
+ let inner = `${leaf.field.name}${argsAt(last)} ${wrapShape(leaf.shape, sel)}`;
28969
+ for (let i = last - 1;i >= 0; i--) {
28970
+ const st = steps[i];
28971
+ const ident = identitySelection(schema, st.shape.item, exclude);
28972
+ const body = `{ ${ident ? `${ident.slice(2, -2)} ` : ""}${inner} }`;
28973
+ inner = `${st.field.name}${argsAt(i)} ${wrapShape(st.shape, body)}`;
28974
+ }
28975
+ return `query { ${inner} }`;
28976
+ };
28977
+ const extract = (steps, data) => {
28978
+ let level = [
28979
+ { value: data[steps[0]?.field.name ?? ""], parents: {} }
28980
+ ];
28981
+ const items = [];
28982
+ const infos = [];
28983
+ steps.forEach((st, i) => {
28984
+ const next = [];
28985
+ for (const { value, parents } of level) {
28986
+ const r = readShape(st.shape, value);
28987
+ if (i === steps.length - 1) {
28988
+ infos.push(r.info);
28989
+ for (const it of r.items)
28990
+ items.push(Object.keys(parents).length ? { ...it, _parents: parents } : it);
28991
+ continue;
28992
+ }
28993
+ const child = steps[i + 1]?.field.name ?? "";
28994
+ for (const it of r.items)
28995
+ next.push({ value: it[child], parents: { ...parents, [st.shape.item]: identity(it) } });
28996
+ }
28997
+ level = next;
28998
+ });
28999
+ return { items, infos };
29000
+ };
29001
+ const fetchItems = async (steps, win, size) => {
29002
+ const leaf = steps[steps.length - 1];
29003
+ const a = leaf.args;
29004
+ const oneParent = steps.slice(0, -1).every((s) => s.shape.kind === "object");
29005
+ const canPage = oneParent && pageable(a);
29006
+ const out = [];
29007
+ let cursor;
29008
+ let page = a.page?.first ?? 1;
29009
+ let offset = 0;
29010
+ let prevFirst;
29011
+ let total;
29012
+ for (let n = 0;n < MAX_PAGES; n++) {
29013
+ const values = {
29014
+ org: o.org,
29015
+ ...win ? { start: win.start, end: win.end } : {},
29016
+ ...a.size ? { size: canPage ? size : Math.max(size, NESTED_SIZE) } : {},
29017
+ ...canPage && a.cursor && cursor ? { cursor } : {},
29018
+ ...canPage && a.page ? { page } : {},
29019
+ ...canPage && !a.cursor && !a.page && a.offset ? { offset } : {}
29020
+ };
29021
+ const data = await ask(() => buildQuery(steps, values), steps);
29022
+ const { items, infos } = extract(steps, data);
29023
+ if (!canPage) {
29024
+ const full = oneParent && a.size !== undefined && items.length >= (values.size ?? 0);
29025
+ const truncated = infos.filter((i) => i.hasNext === true).length || (full ? 1 : 0);
29026
+ return {
29027
+ items,
29028
+ truncated,
29029
+ ...oneParent && infos[0]?.total !== undefined ? { total: infos[0].total } : {}
29030
+ };
29031
+ }
29032
+ const info = infos[0] ?? {};
29033
+ if (n === 0 && info.total !== undefined)
29034
+ total = info.total;
29035
+ if (!items.length)
29036
+ break;
29037
+ const first = keyOf2(items[0]);
29038
+ if (first === prevFirst) {
29039
+ warn(`${leaf.field.name}: the next page repeated the last one — paging stopped there`);
29040
+ break;
29041
+ }
29042
+ prevFirst = first;
29043
+ out.push(...items);
29044
+ if (info.hasNext === false)
29045
+ break;
29046
+ if (a.cursor) {
29047
+ if (!info.next || info.next === cursor)
29048
+ break;
29049
+ cursor = info.next;
29050
+ continue;
29051
+ }
29052
+ if (info.hasNext === undefined && a.size && items.length < size)
29053
+ break;
29054
+ if (a.page)
29055
+ page = info.next && /^\d+$/.test(info.next) ? Number(info.next) : page + 1;
29056
+ else
29057
+ offset += items.length;
29058
+ }
29059
+ return { items: out, truncated: 0, ...total !== undefined ? { total } : {} };
29060
+ };
29061
+ const adaptive = async (steps, win, size, budget) => {
29062
+ try {
29063
+ return await fetchItems(steps, win, size);
29064
+ } catch (e) {
29065
+ if (!(e instanceof SleuthError) || !e.adaptable || budget.left <= 0)
29066
+ throw e;
29067
+ budget.left--;
29068
+ const leaf = steps[steps.length - 1];
29069
+ if (leaf.args.size && size > MIN_PAGE) {
29070
+ const half = Math.max(MIN_PAGE, Math.floor(size / 2));
29071
+ log(` ${e.message} — trying pages of ${half}`);
29072
+ return adaptive(steps, win, half, budget);
29073
+ }
29074
+ const days = win ? Math.floor((win.end.getTime() - win.start.getTime()) / DAY) : 0;
29075
+ if (!win || days < 2)
29076
+ throw e;
29077
+ const mid = new Date(win.start.getTime() + Math.floor(days / 2) * DAY);
29078
+ log(` ${e.message} — splitting ${win.start.toISOString().slice(0, 10)}…${win.end.toISOString().slice(0, 10)} in two`);
29079
+ const x = await adaptive(steps, { start: win.start, end: mid }, size, budget);
29080
+ const y = await adaptive(steps, { start: mid, end: win.end }, size, budget);
29081
+ return {
29082
+ items: [...x.items, ...y.items],
29083
+ truncated: x.truncated + y.truncated,
29084
+ ...x.total !== undefined && y.total !== undefined ? { total: x.total + y.total } : {}
29085
+ };
29086
+ }
29087
+ };
29088
+ const months = monthsBetween(o.since, o.until);
29089
+ const runSection = async (sec) => {
29090
+ const steps = findSection(schema, sec.patterns);
29091
+ if (!steps)
29092
+ return { found: false, why: "Sleuth's schema has no field by that name" };
29093
+ const leaf = steps[steps.length - 1];
29094
+ const res = {
29095
+ found: true,
29096
+ path: steps.map((s) => s.field.name),
29097
+ windowed: windowed(leaf.args),
29098
+ paged: pageable(leaf.args),
29099
+ count: 0,
29100
+ duplicates: 0
29101
+ };
29102
+ const seen = new Set;
29103
+ const fresh = (items) => {
29104
+ const kept = [];
29105
+ for (const it of items) {
29106
+ const k = keyOf2(it);
29107
+ if (seen.has(k))
29108
+ res.duplicates++;
29109
+ else {
29110
+ seen.add(k);
29111
+ kept.push(it);
29112
+ }
29113
+ }
29114
+ return kept;
29115
+ };
29116
+ if (sec.byMonth && res.windowed) {
29117
+ res.months = [];
29118
+ let failedInARow = 0;
29119
+ for (const month of months) {
29120
+ const file = `${sec.name}/${month}.jsonl`;
29121
+ const path = join34(o.out, file);
29122
+ if (!o.fresh && month < currentMonth && existsSync31(path)) {
29123
+ const kept = fresh(readJsonl(path));
29124
+ res.months.push({ month, file, count: kept.length, resumed: true });
29125
+ res.count += kept.length;
29126
+ continue;
29127
+ }
29128
+ try {
29129
+ const got = await adaptive(steps, monthWindow(month), o.pageSize, { left: MONTH_BUDGET });
29130
+ const kept = fresh(got.items);
29131
+ writeAtomic(path, jsonl(kept));
29132
+ res.months.push({
29133
+ month,
29134
+ file,
29135
+ count: kept.length,
29136
+ ...got.items.length !== kept.length ? { fetched: got.items.length } : {},
29137
+ ...got.total !== undefined ? { total: got.total } : {}
29138
+ });
29139
+ res.count += kept.length;
29140
+ if (got.truncated)
29141
+ res.truncated = (res.truncated ?? 0) + got.truncated;
29142
+ log(`${sec.name} ${month}: ${kept.length}`);
29143
+ failedInARow = 0;
29144
+ } catch (e) {
29145
+ const why = e instanceof Error ? e.message : String(e);
29146
+ res.months.push({ month, file, count: 0, error: why });
29147
+ log(`${sec.name} ${month}: failed — ${why}`);
29148
+ if (++failedInARow >= MAX_FAILED_MONTHS) {
29149
+ res.error = `${MAX_FAILED_MONTHS} months in a row failed; stopped at ${month} — ${why}`;
29150
+ break;
29151
+ }
29152
+ }
29153
+ }
29154
+ return res;
29155
+ }
29156
+ const got = await adaptive(steps, undefined, o.pageSize, { left: MONTH_BUDGET });
29157
+ const kept = fresh(got.items);
29158
+ res.count = kept.length;
29159
+ if (got.truncated)
29160
+ res.truncated = got.truncated;
29161
+ if (!sec.byMonth) {
29162
+ res.file = `${sec.name}.jsonl`;
29163
+ writeAtomic(join34(o.out, res.file), jsonl(kept));
29164
+ log(`${sec.name}: ${kept.length}`);
29165
+ return res;
29166
+ }
29167
+ const byMonth = new Map;
29168
+ for (const it of kept) {
29169
+ const m = itemMonth(it) ?? "undated";
29170
+ byMonth.set(m, [...byMonth.get(m) ?? [], it]);
29171
+ }
29172
+ res.months = [...byMonth.keys()].sort().map((month) => {
29173
+ const file = `${sec.name}/${month}.jsonl`;
29174
+ const list = byMonth.get(month) ?? [];
29175
+ writeAtomic(join34(o.out, file), jsonl(list));
29176
+ return { month, file, count: list.length };
29177
+ });
29178
+ log(`${sec.name}: ${kept.length} (not windowed by Sleuth; filed by each item's date)`);
29179
+ return res;
29180
+ };
29181
+ const orgRoot = (schema.types.get(schema.query)?.fields ?? []).find((f) => {
29182
+ const sh = shapeOf(schema, f);
29183
+ const args = planArgs(schema, f.args);
29184
+ return /^(organization|org)$/i.test(f.name) && sh?.kind === "object" && args.org.length && !args.missing.length;
29185
+ });
29186
+ if (orgRoot) {
29187
+ const steps = [
29188
+ {
29189
+ field: orgRoot,
29190
+ parent: schema.query,
29191
+ args: planArgs(schema, orgRoot.args),
29192
+ shape: { kind: "object", item: unwrap(orgRoot.type).name }
29193
+ }
29194
+ ];
29195
+ try {
29196
+ const sel = buildSelection(schema, unwrap(orgRoot.type).name, 0, exclude) ?? "{ __typename }";
29197
+ const data = await ask(() => `query { ${orgRoot.name}${renderArgs(steps[0]?.args ?? { org: [], missing: [] }, { org: o.org })} ${sel} }`, steps);
29198
+ writeAtomic(join34(o.out, "organization.json"), `${JSON.stringify(data[orgRoot.name], null, 2)}
29199
+ `);
29200
+ manifest.organization = { file: "organization.json" };
29201
+ } catch (e) {
29202
+ manifest.organization = { error: e instanceof Error ? e.message : String(e) };
29203
+ }
29204
+ } else
29205
+ manifest.organization = { error: "Sleuth's schema has no organization(orgSlug) field" };
29206
+ save();
29207
+ for (const sec of SECTIONS) {
29208
+ if (o.only?.length && !o.only.includes(sec.name))
29209
+ continue;
29210
+ log(`— ${sec.name}`);
29211
+ try {
29212
+ manifest.sections[sec.name] = await runSection(sec);
29213
+ } catch (e) {
29214
+ manifest.sections[sec.name] = {
29215
+ found: true,
29216
+ path: [],
29217
+ windowed: false,
29218
+ paged: false,
29219
+ count: 0,
29220
+ duplicates: 0,
29221
+ error: e instanceof Error ? e.message : String(e)
29222
+ };
29223
+ log(`${sec.name}: failed — ${e instanceof Error ? e.message : String(e)}`);
29224
+ }
29225
+ save();
29226
+ }
29227
+ const deploys = manifest.sections.deploys;
29228
+ const recap = orgRoot ? fieldOf2(schema, unwrap(orgRoot.type).name, "metricsRecap") : undefined;
29229
+ const recapArgs = recap ? planArgs(schema, recap.args) : undefined;
29230
+ if (orgRoot && recap && recapArgs && windowed(recapArgs) && !recapArgs.missing.length && fieldOf2(schema, unwrap(recap.type).name, "numOfDeploys") && deploys?.found && deploys.months) {
29231
+ const orgArgs = renderArgs(planArgs(schema, orgRoot.args), { org: o.org });
29232
+ for (const row of deploys.months) {
29233
+ if (row.error || row.month === "undated")
29234
+ continue;
29235
+ const w = monthWindow(row.month);
29236
+ try {
29237
+ const r = await o.client.request(`query { ${orgRoot.name}${orgArgs} { ${recap.name}${renderArgs(recapArgs, { start: w.start, end: w.end })} { numOfDeploys } } }`);
29238
+ const orgData = r.data?.[orgRoot.name];
29239
+ const n = orgData?.[recap.name]?.numOfDeploys;
29240
+ if (typeof n === "number")
29241
+ row.sleuthReports = n;
29242
+ } catch (e) {
29243
+ warn(`metricsRecap ${row.month}: ${e instanceof Error ? e.message : String(e)}`);
29244
+ }
29245
+ }
29246
+ }
29247
+ for (const [name, sec] of Object.entries(manifest.sections)) {
29248
+ if (!sec.found || !sec.months)
29249
+ continue;
29250
+ for (const row of sec.months) {
29251
+ const fetched = row.fetched ?? row.count;
29252
+ if (row.total !== undefined && row.total !== fetched)
29253
+ manifest.mismatches.push({
29254
+ section: name,
29255
+ month: row.month,
29256
+ exported: fetched,
29257
+ reported: row.total,
29258
+ source: "totalCount"
29259
+ });
29260
+ if (row.sleuthReports !== undefined && row.sleuthReports !== row.count)
29261
+ manifest.mismatches.push({
29262
+ section: name,
29263
+ month: row.month,
29264
+ exported: row.count,
29265
+ reported: row.sleuthReports,
29266
+ source: "metricsRecap"
29267
+ });
29268
+ }
29269
+ }
29270
+ manifest.finishedAt = new Date().toISOString();
29271
+ manifest.complete = !!orgRoot && Object.values(manifest.sections).every((s) => !s.found || !s.error && !(s.months ?? []).some((m) => m.error));
29272
+ save();
29273
+ return manifest;
29274
+ };
29275
+
29276
+ // src/commands/sleuth.ts
29277
+ var SLEUTH_USAGE = `usage: sleds sleuth export --org <slug> --out <dir> [options]
29278
+
29279
+ Archive a Sleuth.io org through its GraphQL API: the schema, the organization, every deploy
29280
+ (orgChanges) as JSONL by month, and its projects, deployments, environments, teams, labels,
29281
+ impact sources, incidents and flag events, with manifest.json counting each beside what
29282
+ Sleuth reports. Re-run to resume: finished past months on disk are not fetched again.
29283
+
29284
+ SLEUTH_API_KEY the org's API key or access token (Sleuth › Settings › API)
29285
+ --org <slug> the Sleuth org slug (required)
29286
+ --out <dir> where to write (required; created if missing)
29287
+ --since <YYYY-MM> first month (default 2022-01)
29288
+ --until <YYYY-MM> last month (default this month)
29289
+ --only <a,b> just these: ${SECTIONS.map((s) => s.name).join(", ")}
29290
+ --page-size <n> items a page (default 100; halved on its own when Sleuth struggles)
29291
+ --depth <n> how deep nested objects are read (default 2)
29292
+ --fresh fetch every month again, even the ones on disk
29293
+ --url <endpoint> the GraphQL endpoint (default ${SLEUTH_URL}; else SLEUTH_API_URL)
29294
+ --json print the manifest instead of the summary`;
29295
+ var parse3 = (argv) => {
29296
+ const words = [];
29297
+ const flags = {};
29298
+ for (let i = 0;i < argv.length; i++) {
29299
+ const a = argv[i];
29300
+ if (!a.startsWith("--")) {
29301
+ words.push(a);
29302
+ continue;
29303
+ }
29304
+ const eq = a.indexOf("=");
29305
+ if (eq !== -1) {
29306
+ flags[a.slice(2, eq)] = a.slice(eq + 1);
29307
+ continue;
29308
+ }
29309
+ const next = argv[i + 1];
29310
+ if (next !== undefined && !next.startsWith("--")) {
29311
+ flags[a.slice(2)] = next;
29312
+ i++;
29313
+ } else
29314
+ flags[a.slice(2)] = true;
29315
+ }
29316
+ return { words, flags };
29317
+ };
29318
+ var outDir = (raw, home) => resolve12(raw.startsWith("~/") ? join35(home, raw.slice(2)) : raw);
29319
+ var str15 = (f, k) => typeof f[k] === "string" ? f[k] : undefined;
29320
+ var MONTH2 = /^\d{4}-(0[1-9]|1[0-2])$/;
29321
+ var KNOWN = [
29322
+ "org",
29323
+ "out",
29324
+ "since",
29325
+ "until",
29326
+ "only",
29327
+ "page-size",
29328
+ "depth",
29329
+ "fresh",
29330
+ "url",
29331
+ "json"
29332
+ ];
29333
+ var sleuthCmd = async (o) => {
29334
+ const log = o.log ?? console.log;
29335
+ const error = o.error ?? console.error;
29336
+ const env = o.env ?? process.env;
29337
+ const { words, flags } = parse3(o.argv);
29338
+ const usage = (why) => {
29339
+ if (why)
29340
+ error(why);
29341
+ error(SLEUTH_USAGE);
29342
+ return 2;
29343
+ };
29344
+ if (words[0] !== "export" || words.length !== 1)
29345
+ return usage();
29346
+ const unknown = Object.keys(flags).filter((k) => !KNOWN.includes(k));
29347
+ if (unknown.length)
29348
+ return usage(`unknown option: --${unknown[0]}`);
29349
+ const org = str15(flags, "org");
29350
+ const outRaw = str15(flags, "out");
29351
+ if (!org || !outRaw)
29352
+ return usage("--org and --out are required");
29353
+ const key = env.SLEUTH_API_KEY?.trim();
29354
+ if (!key) {
29355
+ error("SLEUTH_API_KEY is not set — the org's key from Sleuth › Settings › API");
29356
+ return 2;
29357
+ }
29358
+ const now = (o.now ?? (() => new Date))();
29359
+ const since = str15(flags, "since") ?? "2022-01";
29360
+ const until = str15(flags, "until") ?? monthOf(now);
29361
+ if (!MONTH2.test(since) || !MONTH2.test(until) || !monthsBetween(since, until).length)
29362
+ return usage("--since and --until are months, YYYY-MM, since before until");
29363
+ const pageSize = Number(str15(flags, "page-size") ?? 100);
29364
+ const depth = Number(str15(flags, "depth") ?? 2);
29365
+ if (!Number.isInteger(pageSize) || pageSize < 1)
29366
+ return usage("--page-size is a whole number");
29367
+ if (!Number.isInteger(depth) || depth < 0 || depth > 5)
29368
+ return usage("--depth is 0 to 5");
29369
+ const only = str15(flags, "only")?.split(",").map((s) => s.trim()).filter(Boolean);
29370
+ const names = SECTIONS.map((s) => s.name);
29371
+ const bad = only?.find((n) => !names.includes(n));
29372
+ if (bad)
29373
+ return usage(`--only: no section "${bad}" (${names.join(", ")})`);
29374
+ const out = outDir(outRaw, o.home ?? homedir8());
29375
+ const endpoint = str15(flags, "url") ?? env.SLEUTH_API_URL ?? SLEUTH_URL;
29376
+ const version = cliVersion(env);
29377
+ const progress = error;
29378
+ const client = sleuthClient({
29379
+ key,
29380
+ url: endpoint,
29381
+ ...o.fetch ? { fetch: o.fetch } : {},
29382
+ ...o.sleep ? { sleep: o.sleep } : {},
29383
+ log: progress
29384
+ });
29385
+ let manifest;
29386
+ try {
29387
+ manifest = await exportSleuth({
29388
+ org,
29389
+ out,
29390
+ since,
29391
+ until,
29392
+ pageSize,
29393
+ depth,
29394
+ ...only?.length ? { only } : {},
29395
+ fresh: flags.fresh === true,
29396
+ endpoint,
29397
+ ...version ? { cliVersion: version } : {},
29398
+ client,
29399
+ now: () => now,
29400
+ log: progress
29401
+ });
29402
+ } catch (e) {
29403
+ error(`sleuth export stopped — ${e instanceof Error ? e.message : String(e)}`);
29404
+ return e instanceof SleuthError && !e.adaptable ? 2 : 1;
29405
+ }
29406
+ if (flags.json === true) {
29407
+ log(JSON.stringify(manifest));
29408
+ return manifest.complete ? 0 : 1;
29409
+ }
29410
+ log(`Sleuth org ${org} → ${out}`);
29411
+ for (const [name, s] of Object.entries(manifest.sections)) {
29412
+ if (!s.found)
29413
+ log(` ${name.padEnd(14)} not in Sleuth's schema — skipped`);
29414
+ else {
29415
+ const months = s.months ? ` across ${s.months.length} months` : "";
29416
+ const dupes = s.duplicates ? `, ${s.duplicates} duplicates dropped` : "";
29417
+ const more = s.truncated ? `, ${s.truncated} nested lists cut at one page` : "";
29418
+ log(` ${name.padEnd(14)} ${String(s.count).padStart(6)}${months}${dupes}${more}${s.error ? ` — FAILED: ${s.error}` : ""}`);
29419
+ }
29420
+ }
29421
+ if (manifest.pruned.length)
29422
+ log(` Sleuth refused ${manifest.pruned.length} fields: ${manifest.pruned.join(", ")}`);
29423
+ for (const m of manifest.mismatches)
29424
+ log(` ${m.section} ${m.month}: exported ${m.exported}, Sleuth's ${m.source} says ${m.reported}`);
29425
+ const deploys = manifest.sections.deploys;
29426
+ if (deploys?.found) {
29427
+ const checked = (deploys.months ?? []).filter((r) => r.sleuthReports !== undefined || r.total !== undefined).length;
29428
+ const off = manifest.mismatches.filter((m) => m.section === "deploys").length;
29429
+ if (!checked)
29430
+ log(" Sleuth gave no deploy count to check against");
29431
+ else if (!off)
29432
+ log(` deploy counts match what Sleuth reports in all ${checked} months checked`);
29433
+ }
29434
+ log(` ${manifest.requests} requests · manifest.json${manifest.complete ? "" : " — INCOMPLETE: re-run to resume"}`);
29435
+ return manifest.complete ? 0 : 1;
29436
+ };
29437
+
28370
29438
  // src/commands/specEvent.ts
28371
29439
  init_meter();
28372
- import { readFileSync as readFileSync32, statSync as statSync8 } from "node:fs";
29440
+ import { readFileSync as readFileSync33, statSync as statSync8 } from "node:fs";
28373
29441
  import { basename as basename9, isAbsolute as isAbsolute7, relative as relative2 } from "node:path";
28374
29442
  var HOOK_STDIN_TIMEOUT_MS3 = 1000;
28375
29443
  var ESTIMATE_TIMEOUT_MS = 4000;
@@ -28425,7 +29493,7 @@ function readFileOrNull(path) {
28425
29493
  try {
28426
29494
  if (statSync8(path).size > 2000000)
28427
29495
  return null;
28428
- return readFileSync32(path, "utf8");
29496
+ return readFileSync33(path, "utf8");
28429
29497
  } catch {
28430
29498
  return null;
28431
29499
  }
@@ -28489,7 +29557,7 @@ var specEvent = async (o) => {
28489
29557
  };
28490
29558
 
28491
29559
  // src/commands/status.ts
28492
- import { existsSync as existsSync31 } from "node:fs";
29560
+ import { existsSync as existsSync32 } from "node:fs";
28493
29561
  init_config();
28494
29562
  init_settings();
28495
29563
  var HEALTH_TIMEOUT_MS2 = 4000;
@@ -28577,7 +29645,7 @@ var status3 = async (o) => {
28577
29645
  const log = o.log ?? console.log;
28578
29646
  const nowMs = (o.now ?? Date.now)();
28579
29647
  const cfg = readConfig(o.home);
28580
- const settings = existsSync31(settingsPath(o.home)) ? readSettings(o.home) : {};
29648
+ const settings = existsSync32(settingsPath(o.home)) ? readSettings(o.home) : {};
28581
29649
  const inSettings = readProxy(settings);
28582
29650
  const picked = selectOrg(cfg, undefined, o.env ?? process.env);
28583
29651
  const viaEnv = picked.from === "env";
@@ -28770,7 +29838,7 @@ var announce = (o, events) => {
28770
29838
 
28771
29839
  // src/commands/stopEvent.ts
28772
29840
  init_meter();
28773
- import { existsSync as existsSync32, readFileSync as readFileSync33 } from "node:fs";
29841
+ import { existsSync as existsSync33, readFileSync as readFileSync34 } from "node:fs";
28774
29842
 
28775
29843
  // src/transcript.ts
28776
29844
  var isObject16 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
@@ -28871,7 +29939,7 @@ async function postTranscript(o, payload, now) {
28871
29939
  }
28872
29940
  if (!src)
28873
29941
  return false;
28874
- const read = o.readFile ?? ((p) => existsSync32(p) ? readFileSync33(p, "utf8") : null);
29942
+ const read = o.readFile ?? ((p) => existsSync33(p) ? readFileSync34(p, "utf8") : null);
28875
29943
  let text;
28876
29944
  try {
28877
29945
  text = read(path);
@@ -28949,7 +30017,7 @@ var stopEvent = async (o) => {
28949
30017
 
28950
30018
  // src/commands/uninstall.ts
28951
30019
  import { spawnSync as spawnSync9 } from "node:child_process";
28952
- import { existsSync as existsSync33, rmSync as rmSync22 } from "node:fs";
30020
+ import { existsSync as existsSync34, rmSync as rmSync22 } from "node:fs";
28953
30021
  init_config();
28954
30022
  init_meter();
28955
30023
  init_settings();
@@ -28962,7 +30030,7 @@ var uninstall2 = (o) => {
28962
30030
  (o.log ?? console.log)(` ${codexLine}`);
28963
30031
  const log = o.log ?? console.log;
28964
30032
  const removed = [];
28965
- if (existsSync33(settingsPath(o.home))) {
30033
+ if (existsSync34(settingsPath(o.home))) {
28966
30034
  let settings = readSettings(o.home);
28967
30035
  let changed = false;
28968
30036
  const cfg = readConfig(o.home);
@@ -29015,18 +30083,18 @@ var uninstall2 = (o) => {
29015
30083
  writeSettings(o.home, settings);
29016
30084
  }
29017
30085
  for (const file of [meterCachePath(o.home), ...allSeatStatePaths(o.home)]) {
29018
- if (existsSync33(file)) {
30086
+ if (existsSync34(file)) {
29019
30087
  rmSync22(file, { force: true });
29020
30088
  removed.push(file);
29021
30089
  }
29022
30090
  }
29023
30091
  const skill = skillDir(o.home);
29024
- if (existsSync33(skill)) {
30092
+ if (existsSync34(skill)) {
29025
30093
  rmSync22(skill, { recursive: true, force: true });
29026
30094
  removed.push(skill);
29027
30095
  }
29028
30096
  const plist = musherPlistPath(o.home);
29029
- if (existsSync33(plist)) {
30097
+ if (existsSync34(plist)) {
29030
30098
  (o.unloadAgent ?? defaultUnload)(plist);
29031
30099
  rmSync22(plist, { force: true });
29032
30100
  removed.push(`launchd agent ${musherLabel()}`);
@@ -29044,8 +30112,8 @@ var uninstall2 = (o) => {
29044
30112
 
29045
30113
  // src/commands/update.ts
29046
30114
  init_server();
29047
- import { existsSync as existsSync34 } from "node:fs";
29048
- import { homedir as homedir8, userInfo as userInfo4 } from "node:os";
30115
+ import { existsSync as existsSync35 } from "node:fs";
30116
+ import { homedir as homedir9, userInfo as userInfo4 } from "node:os";
29049
30117
  import { basename as basename10 } from "node:path";
29050
30118
  init_config();
29051
30119
  init_state();
@@ -29111,7 +30179,7 @@ var realPidAlive4 = (pid) => {
29111
30179
  };
29112
30180
  function machineClient(home, env) {
29113
30181
  const src = meterSource(home, undefined, { env });
29114
- return src ? fetchClient({ ...src, version: "0.1.64" }) : null;
30182
+ return src ? fetchClient({ ...src, version: "0.1.65" }) : null;
29115
30183
  }
29116
30184
  async function noteAnswer2(home, a, now) {
29117
30185
  try {
@@ -29133,7 +30201,7 @@ function busyLine(busy) {
29133
30201
  return `${n} session${n === 1 ? " is" : "s are"} running on ${on} — sleds update --now releases ${n === 1 ? "it" : "them"}`;
29134
30202
  }
29135
30203
  async function updateCmd(o) {
29136
- const home = o.home ?? homedir8();
30204
+ const home = o.home ?? homedir9();
29137
30205
  const env = o.env ?? process.env;
29138
30206
  const log = o.log ?? console.log;
29139
30207
  const error = o.error ?? console.error;
@@ -29259,7 +30327,7 @@ async function updateCmd(o) {
29259
30327
  error(`sleds ${to} could not be downloaded: ${got.why}`);
29260
30328
  return 1;
29261
30329
  }
29262
- } else if (!existsSync34(versionBin(home, to, platform))) {
30330
+ } else if (!existsSync35(versionBin(home, to, platform))) {
29263
30331
  error(`sleds ${to} is not on this machine to move back to`);
29264
30332
  return 1;
29265
30333
  }
@@ -29353,7 +30421,7 @@ var verify2 = async (o) => {
29353
30421
 
29354
30422
  // src/commands/webhooks.ts
29355
30423
  var USAGE3 = "usage: sleds webhooks [--json] | sleds webhooks deliveries <id or name> [--json]";
29356
- var str15 = (x) => typeof x === "string" ? x : "";
30424
+ var str16 = (x) => typeof x === "string" ? x : "";
29357
30425
  function ago3(at, now) {
29358
30426
  const m = Math.max(0, Math.floor((now - at) / 60000));
29359
30427
  if (m < 1)
@@ -29369,7 +30437,7 @@ function webhookLines(raw, now) {
29369
30437
  if (!rows.length)
29370
30438
  return ["no webhooks — add one in the app: Settings › Integrations › Webhooks"];
29371
30439
  return rows.map((h) => {
29372
- let host = str15(h.url);
30440
+ let host = str16(h.url);
29373
30441
  try {
29374
30442
  host = new URL(host.replace(/\{[a-z.]+\}/g, "x")).host;
29375
30443
  } catch {}
@@ -29377,21 +30445,21 @@ function webhookLines(raw, now) {
29377
30445
  const last = h.lastDelivery;
29378
30446
  const lastText = last?.at ? `last ${last.status ?? "—"} ${last.state ?? ""} ${ago3(last.at, now)}`.replace(/\s+/g, " ") : "nothing sent yet";
29379
30447
  const off = h.enabled ? "on " : "off";
29380
- const why = !h.enabled && h.disabledWhy ? ` (${str15(h.disabledWhy)})` : "";
29381
- return `${off} ${str15(h.name)} ${host} ${events.includes("*") ? "all events" : events.join(",")} ${lastText}${why} [${str15(h.id)}]`;
30448
+ const why = !h.enabled && h.disabledWhy ? ` (${str16(h.disabledWhy)})` : "";
30449
+ return `${off} ${str16(h.name)} ${host} ${events.includes("*") ? "all events" : events.join(",")} ${lastText}${why} [${str16(h.id)}]`;
29382
30450
  });
29383
30451
  }
29384
30452
  function deliveryLines(raw, now) {
29385
30453
  const j = clean(raw);
29386
30454
  const rows = Array.isArray(j.deliveries) ? j.deliveries : [];
29387
- const name = str15(j.webhook?.name);
30455
+ const name = str16(j.webhook?.name);
29388
30456
  if (!rows.length)
29389
30457
  return [`${name}: nothing sent yet`];
29390
30458
  return [
29391
30459
  `${name} — the last ${rows.length}`,
29392
30460
  ...rows.map((d) => {
29393
- const result = d.status !== null && d.status !== undefined ? String(d.status) : str15(d.error) || "—";
29394
- return `${str15(d.state).padEnd(9)} ${result.padEnd(5)} ${str15(d.event).padEnd(20)} ${str15(d.key) || "—"} ${ago3(Number(d.at), now)} tries ${d.attempts} ${str15(d.deliveryId)}`;
30461
+ const result = d.status !== null && d.status !== undefined ? String(d.status) : str16(d.error) || "—";
30462
+ return `${str16(d.state).padEnd(9)} ${result.padEnd(5)} ${str16(d.event).padEnd(20)} ${str16(d.key) || "—"} ${ago3(Number(d.at), now)} tries ${d.attempts} ${str16(d.deliveryId)}`;
29395
30463
  })
29396
30464
  ];
29397
30465
  }
@@ -29422,7 +30490,7 @@ var webhooksCmd = async (o) => {
29422
30490
  return 1;
29423
30491
  }
29424
30492
  if (r.status !== 200 || !r.json) {
29425
- const why = str15(r.json?.error) || `HTTP ${r.status}`;
30493
+ const why = str16(r.json?.error) || `HTTP ${r.status}`;
29426
30494
  error(r.status === 404 && !r.json ? "SLEDS does not know this seat — run `sleds init`" : why);
29427
30495
  return 1;
29428
30496
  }
@@ -29436,13 +30504,13 @@ var webhooksCmd = async (o) => {
29436
30504
 
29437
30505
  // src/config.ts
29438
30506
  init_settings();
29439
- import { chmodSync as chmodSync8, existsSync as existsSync35, mkdirSync as mkdirSync28, readFileSync as readFileSync34, renameSync as renameSync13, writeFileSync as writeFileSync28 } from "node:fs";
29440
- import { join as join34, resolve as resolve12 } from "node:path";
30507
+ import { chmodSync as chmodSync8, existsSync as existsSync36, mkdirSync as mkdirSync29, readFileSync as readFileSync35, renameSync as renameSync14, writeFileSync as writeFileSync29 } from "node:fs";
30508
+ import { join as join36, resolve as resolve13 } from "node:path";
29441
30509
  var configDir2 = (home, env = process.env) => {
29442
30510
  const o = env.SLEDS_HOME?.trim();
29443
- return o ? resolve12(home, o) : join34(home, ".config", "sleds");
30511
+ return o ? resolve13(home, o) : join36(home, ".config", "sleds");
29444
30512
  };
29445
- var configPath2 = (home) => join34(configDir2(home), "config.json");
30513
+ var configPath2 = (home) => join36(configDir2(home), "config.json");
29446
30514
  var isObject17 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
29447
30515
  var optString2 = (v) => typeof v === "string" ? v : undefined;
29448
30516
  var parsePriorEnv2 = (v) => {
@@ -29577,9 +30645,9 @@ var parseOrgInfo2 = (raw) => {
29577
30645
  };
29578
30646
  var readConfig2 = (home) => {
29579
30647
  const p = configPath2(home);
29580
- if (!existsSync35(p))
30648
+ if (!existsSync36(p))
29581
30649
  return {};
29582
- return parseConfig2(JSON.parse(readFileSync34(p, "utf8")));
30650
+ return parseConfig2(JSON.parse(readFileSync35(p, "utf8")));
29583
30651
  };
29584
30652
 
29585
30653
  // src/runners/exec.ts
@@ -29636,10 +30704,10 @@ function runnerFor3(name, exec = defaultExec, o = {}) {
29636
30704
  }
29637
30705
 
29638
30706
  // src/runtime.ts
29639
- import { homedir as homedir9 } from "node:os";
29640
- import { join as join35, win32 as win323 } from "node:path";
30707
+ import { homedir as homedir10 } from "node:os";
30708
+ import { join as join37, win32 as win323 } from "node:path";
29641
30709
  var isCompiled2 = (argv1 = process.argv[1]) => Boolean(argv1 && (argv1.startsWith("/$bunfs/") || argv1.startsWith("B:\\~BUN\\")));
29642
- var sledsLink2 = (home = homedir9(), platform = process.platform) => platform === "win32" ? win323.join(process.env.LOCALAPPDATA ?? win323.join(home, "AppData", "Local"), "sleds", "bin", "sleds.exe") : join35(home, ".sleds", "bin", "sleds");
30710
+ var sledsLink2 = (home = homedir10(), platform = process.platform) => platform === "win32" ? win323.join(process.env.LOCALAPPDATA ?? win323.join(home, "AppData", "Local"), "sleds", "bin", "sleds.exe") : join37(home, ".sleds", "bin", "sleds");
29643
30711
 
29644
30712
  // src/schedule.ts
29645
30713
  import { spawnSync as spawnSync11 } from "node:child_process";
@@ -29651,15 +30719,15 @@ var defaultExec5 = (cmd, args) => {
29651
30719
  };
29652
30720
 
29653
30721
  // src/settings.ts
29654
- import { dirname as dirname25, join as join36, resolve as resolve13 } from "node:path";
30722
+ import { dirname as dirname26, join as join38, resolve as resolve14 } from "node:path";
29655
30723
  var withSeatEnv2 = (command, home, env = process.env, org) => {
29656
30724
  const parts = [];
29657
30725
  const sh = env.SLEDS_HOME?.trim();
29658
30726
  const cc = env.CLAUDE_CONFIG_DIR?.trim();
29659
30727
  if (sh)
29660
- parts.push(`SLEDS_HOME=${shellWord2(resolve13(home, sh))}`);
30728
+ parts.push(`SLEDS_HOME=${shellWord2(resolve14(home, sh))}`);
29661
30729
  if (cc)
29662
- parts.push(`CLAUDE_CONFIG_DIR=${shellWord2(resolve13(home, cc))}`);
30730
+ parts.push(`CLAUDE_CONFIG_DIR=${shellWord2(resolve14(home, cc))}`);
29663
30731
  parts.push(command);
29664
30732
  return pinOrg4(parts.join(" "), org);
29665
30733
  };
@@ -29745,7 +30813,11 @@ commands:
29745
30813
  --run start Claude Code with the pack as the first prompt
29746
30814
  webhooks The org's outbound webhooks (admins); deliveries <id or name> lists one hook's
29747
30815
  last hundred deliveries. Registering and Redeliver stay in the app.
29748
- deploy A release step's one line: tell SLEDS this commit is now running in an environment.
30816
+ sleuth export --org <slug> --out <dir>: archive a Sleuth.io org before it closes \u2014
30817
+ every deploy as JSONL by month, its projects, environments, teams, incidents and
30818
+ flag events, and a manifest whose counts sit beside Sleuth's own. Key from
30819
+ SLEUTH_API_KEY; re-run to resume. \`sleds sleuth\` alone lists every option.
30820
+ deploy A release step's one line: tell SLEDS this commit is now running in an environment.
29749
30821
  --env <slug> the environment (required); --service <name> for a monorepo
29750
30822
  --sha / --repo else the CI's own variables, then git
29751
30823
  Token from SLEDS_DEPLOY_TOKEN (Settings \u203A Deploys). SLEDS down never fails a release.
@@ -29951,17 +31023,17 @@ var hookStdin = async () => {
29951
31023
  var sledsArgv = (execPath = process.execPath, script = process.argv[1], realpath, o = {}) => {
29952
31024
  if (isCompiled2(script)) {
29953
31025
  const link = sledsLink2(o.home);
29954
- return stableArgv2([(o.exists ?? existsSync36)(link) ? link : execPath], realpath, o.home);
31026
+ return stableArgv2([(o.exists ?? existsSync37)(link) ? link : execPath], realpath, o.home);
29955
31027
  }
29956
31028
  return stableArgv2(script ? [execPath, script] : [execPath], realpath, o.home);
29957
31029
  };
29958
31030
  var shellQuote = (s) => /^[\w./-]+$/.test(s) ? s : `'${s.replace(/'/g, "'\\''")}'`;
29959
- var statusLineCommand = (argv, home = homedir10()) => withSeatEnv2([...argv, "statusline"].map(shellQuote).join(" "), home);
29960
- var seatEventCommand = (argv, home = homedir10()) => withSeatEnv2([...argv, "seat-event"].map(shellQuote).join(" "), home);
29961
- var promptEventCommand = (argv, home = homedir10()) => withSeatEnv2([...argv, "prompt-event"].map(shellQuote).join(" "), home);
29962
- var sessionEventCommand = (argv, home = homedir10()) => withSeatEnv2([...argv, "session-event"].map(shellQuote).join(" "), home);
29963
- var stopEventCommand = (argv, home = homedir10()) => withSeatEnv2([...argv, "stop-event"].map(shellQuote).join(" "), home);
29964
- var specEventCommand = (argv, home = homedir10()) => withSeatEnv2([...argv, "spec-event"].map(shellQuote).join(" "), home);
31031
+ var statusLineCommand = (argv, home = homedir11()) => withSeatEnv2([...argv, "statusline"].map(shellQuote).join(" "), home);
31032
+ var seatEventCommand = (argv, home = homedir11()) => withSeatEnv2([...argv, "seat-event"].map(shellQuote).join(" "), home);
31033
+ var promptEventCommand = (argv, home = homedir11()) => withSeatEnv2([...argv, "prompt-event"].map(shellQuote).join(" "), home);
31034
+ var sessionEventCommand = (argv, home = homedir11()) => withSeatEnv2([...argv, "session-event"].map(shellQuote).join(" "), home);
31035
+ var stopEventCommand = (argv, home = homedir11()) => withSeatEnv2([...argv, "stop-event"].map(shellQuote).join(" "), home);
31036
+ var specEventCommand = (argv, home = homedir11()) => withSeatEnv2([...argv, "spec-event"].map(shellQuote).join(" "), home);
29965
31037
  var mcpFlag = (flags) => flags["no-mcp"] === true ? false : flags.mcp === true ? true : undefined;
29966
31038
  var main = async (argv) => {
29967
31039
  const { cmd, flags } = parseArgs(argv);
@@ -29969,7 +31041,7 @@ var main = async (argv) => {
29969
31041
  console.log(commandUsage(cmd));
29970
31042
  return 0;
29971
31043
  }
29972
- const home = homedir10();
31044
+ const home = homedir11();
29973
31045
  const scheduler = {
29974
31046
  platform: platform3(),
29975
31047
  home,
@@ -29981,7 +31053,7 @@ var main = async (argv) => {
29981
31053
  now: Date.now(),
29982
31054
  stderr: (line) => console.error(line),
29983
31055
  isTTY: !!process.stderr.isTTY,
29984
- version: "0.1.64"
31056
+ version: "0.1.65"
29985
31057
  });
29986
31058
  switch (cmd) {
29987
31059
  case "update":
@@ -29996,6 +31068,8 @@ var main = async (argv) => {
29996
31068
  return packCmd({ home, argv: argv.slice(1) });
29997
31069
  case "webhooks":
29998
31070
  return webhooksCmd({ home, argv: argv.slice(1) });
31071
+ case "sleuth":
31072
+ return sleuthCmd({ argv: argv.slice(1), home });
29999
31073
  case "deploy":
30000
31074
  return deployCmd({ argv: argv.slice(1) });
30001
31075
  case "sled":
@@ -30214,7 +31288,7 @@ ${renderHelpText(argv[1])}`);
30214
31288
  case "version":
30215
31289
  case "--version":
30216
31290
  case "-v":
30217
- console.log("sleds 0.1.64");
31291
+ console.log("sleds 0.1.65");
30218
31292
  return 0;
30219
31293
  default:
30220
31294
  console.error(`unknown command: ${cmd}