@sleds/cli 0.1.65 → 0.1.67

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 +2 -0
  2. package/dist/sleds.mjs +289 -78
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -115,6 +115,8 @@ sleuth-export/
115
115
 
116
116
  Nested items carry `_parents` (the project and organization they came from). The exporter reads Sleuth's schema and builds every query from it, so a field Sleuth renames or drops is followed, not a crash. Re-run the same command to resume after a stop: finished past months are skipped (`--fresh` fetches them again). `--since`/`--until YYYY-MM`, `--only deploys,projects`, `--page-size`, `--depth`; `sleds sleuth` lists them all. The command exits 1 when a section is incomplete, and the manifest says which.
117
117
 
118
+ The key goes as `authorization: apikey <key>`; when Sleuth refuses it or answers PERMISSION_DENIED, the export tries `Bearer <key>` once and keeps whichever answers. `SLEUTH_AUTH=bearer` (or `apikey`) sends that scheme alone. A key Sleuth denies under both stops the export at once (exit 2) rather than splitting the month in search of a smaller query.
119
+
118
120
  ## Docs
119
121
 
120
122
  Repo: github.com/spdyadam/sleds · Site: sleds.ai
package/dist/sleds.mjs CHANGED
@@ -1640,10 +1640,26 @@ var init_outcome = __esm(() => {
1640
1640
  OUTCOME_WINDOW_MS = OUTCOME_WINDOW_DAYS * 86400000;
1641
1641
  });
1642
1642
 
1643
+ // ../../packages/shared/src/deploys/workingHours.ts
1644
+ var MIN_MS, DAY_MS2, WEEK_MS, DAY_MIN, WORKING_HOURS_DEFAULT;
1645
+ var init_workingHours = __esm(() => {
1646
+ MIN_MS = 60 * 1000;
1647
+ DAY_MS2 = 24 * 60 * MIN_MS;
1648
+ WEEK_MS = 7 * DAY_MS2;
1649
+ DAY_MIN = 24 * 60;
1650
+ WORKING_HOURS_DEFAULT = Object.freeze({
1651
+ on: false,
1652
+ days: Object.freeze([1, 2, 3, 4, 5]),
1653
+ start: 9 * 60,
1654
+ end: 17 * 60
1655
+ });
1656
+ });
1657
+
1643
1658
  // ../../packages/shared/src/deploys/metrics.ts
1644
- var WEEK_MS;
1659
+ var WEEK_MS2;
1645
1660
  var init_metrics = __esm(() => {
1646
- WEEK_MS = 7 * 24 * 60 * 60 * 1000;
1661
+ init_workingHours();
1662
+ WEEK_MS2 = 7 * 24 * 60 * 60 * 1000;
1647
1663
  });
1648
1664
 
1649
1665
  // ../../packages/shared/src/deploys/batchSize.ts
@@ -1666,9 +1682,9 @@ function periodChange(cur, prev, better) {
1666
1682
  const dir = pct > 0 ? "up" : "down";
1667
1683
  return { pct, dir, tone: dir === better ? "success" : "error" };
1668
1684
  }
1669
- var DAY_MS2, FLAT_PCT = 0.5;
1685
+ var DAY_MS3, FLAT_PCT = 0.5;
1670
1686
  var init_period = __esm(() => {
1671
- DAY_MS2 = 24 * 60 * 60 * 1000;
1687
+ DAY_MS3 = 24 * 60 * 60 * 1000;
1672
1688
  });
1673
1689
 
1674
1690
  // ../../packages/shared/src/deploys/report.ts
@@ -1681,23 +1697,28 @@ function duration(ms) {
1681
1697
  return `${hours.toFixed(1)}h`;
1682
1698
  return `${(hours / 24).toFixed(1)}d`;
1683
1699
  }
1684
- function change(pair, better) {
1700
+ function changeWord(pair, better) {
1685
1701
  const { current, previous } = pair;
1686
1702
  if (current == null)
1687
- return "";
1703
+ return null;
1688
1704
  if (previous == null)
1689
- return " (none before)";
1705
+ return "none before";
1690
1706
  const c = periodChange(current, previous, better);
1691
1707
  if (c.dir === "flat")
1692
- return " (flat)";
1708
+ return "flat";
1693
1709
  if (c.pct == null)
1694
- return " (none before)";
1695
- return ` (${c.pct > 0 ? "+" : "−"}${Math.round(Math.abs(c.pct))}%)`;
1710
+ return "none before";
1711
+ return `${c.pct > 0 ? "+" : "−"}${Math.round(Math.abs(c.pct))}%`;
1696
1712
  }
1697
- function timeFigure(label, pair) {
1713
+ function change(pair, better) {
1714
+ const word = changeWord(pair, better);
1715
+ return word == null ? "" : ` (${word})`;
1716
+ }
1717
+ function timeFigure(label, pair, note) {
1698
1718
  if (pair.current == null)
1699
1719
  return `${label} none`;
1700
- return `${label} ${duration(pair.current)}${change(pair, "down")}`;
1720
+ const notes = [note, changeWord(pair, "down")].filter(Boolean).join(", ");
1721
+ return `${label} ${duration(pair.current)}${notes ? ` (${notes})` : ""}`;
1701
1722
  }
1702
1723
  function rateFigure(pair) {
1703
1724
  const { current, previous } = pair;
@@ -1715,29 +1736,31 @@ function batchFigure(pair) {
1715
1736
  const before = total(pair.previous) === 0 ? "none before" : `was ${tiers(pair.previous)}`;
1716
1737
  return `batch ${tiers(pair.current)} (${before})`;
1717
1738
  }
1718
- function lineText(l) {
1739
+ function lineText(l, clock) {
1719
1740
  const head = `${l.name} (${l.slug})`;
1720
1741
  if (l.production == null)
1721
1742
  return `${head} · no production environment set, so no deploy metrics`;
1722
1743
  const deploys = `${l.deploys.current} deploy${l.deploys.current === 1 ? "" : "s"}${change(l.deploys, "up")}`;
1723
1744
  return [
1724
1745
  `${head} · ${l.production}: ${deploys}`,
1725
- timeFigure("lead time", l.leadTime),
1746
+ timeFigure("lead time", l.leadTime, clock === "working" ? "working hours" : undefined),
1726
1747
  rateFigure(l.failRate),
1727
1748
  timeFigure("time to recover", l.recover),
1728
1749
  batchFigure(l.batch)
1729
1750
  ].join(" · ");
1730
1751
  }
1731
1752
  function metricsText(r) {
1753
+ const clock = r.clock ?? "wall";
1732
1754
  const sameYear = r.from.slice(0, 4) === r.to.slice(0, 4);
1733
1755
  const span = `${dayLabel(r.from, !sameYear)} – ${dayLabel(r.to, true)}`;
1734
1756
  const days = `${r.days} day${r.days === 1 ? "" : "s"}`;
1735
- const header = `${r.scope === "org" ? "Org" : "Workspace"} · ${span} (${days}), against the ${days} before`;
1736
- const lines = r.lines.length ? r.lines.map(lineText) : [r.scope === "org" ? "No workspaces in this org." : "No workspace."];
1757
+ const window = clock === "working" ? ` · lead time in working hours${r.hours ? `, ${r.hours}` : ""}` : "";
1758
+ const header = `${r.scope === "org" ? "Org" : "Workspace"} · ${span} (${days}), against the ${days} before${window}`;
1759
+ const lines = r.lines.length ? r.lines.map((l) => lineText(l, clock)) : [r.scope === "org" ? "No workspaces in this org." : "No workspace."];
1737
1760
  return [header, ...lines].join(`
1738
1761
  `);
1739
1762
  }
1740
- var DAY_MS3, METRICS_RANGES, tiers = (c) => TIERS.map((t) => `${t}${c[t]}`).join(" "), total = (c) => TIERS.reduce((n, t) => n + c[t], 0), dayLabel = (day, year) => new Date(`${day}T00:00:00Z`).toLocaleDateString("en-US", {
1763
+ var DAY_MS4, METRICS_RANGES, tiers = (c) => TIERS.map((t) => `${t}${c[t]}`).join(" "), total = (c) => TIERS.reduce((n, t) => n + c[t], 0), dayLabel = (day, year) => new Date(`${day}T00:00:00Z`).toLocaleDateString("en-US", {
1741
1764
  month: "short",
1742
1765
  day: "numeric",
1743
1766
  ...year ? { year: "numeric" } : {},
@@ -1746,7 +1769,7 @@ var DAY_MS3, METRICS_RANGES, tiers = (c) => TIERS.map((t) => `${t}${c[t]}`).join
1746
1769
  var init_report = __esm(() => {
1747
1770
  init_batchSize();
1748
1771
  init_period();
1749
- DAY_MS3 = 24 * 60 * 60 * 1000;
1772
+ DAY_MS4 = 24 * 60 * 60 * 1000;
1750
1773
  METRICS_RANGES = [
1751
1774
  { key: "2w", days: 14 },
1752
1775
  { key: "1m", days: 30 },
@@ -2241,6 +2264,15 @@ var init_route = __esm(() => {
2241
2264
  };
2242
2265
  });
2243
2266
 
2267
+ // ../../packages/shared/src/route/verdict.ts
2268
+ function toneOf(x) {
2269
+ return typeof x === "string" && VERDICT_TONES.includes(x) ? x : undefined;
2270
+ }
2271
+ var VERDICT_TONES;
2272
+ var init_verdict = __esm(() => {
2273
+ VERDICT_TONES = ["go", "change", "stop"];
2274
+ });
2275
+
2244
2276
  // ../../packages/shared/src/mcp/annotations.ts
2245
2277
  function withAnnotations(tools) {
2246
2278
  return tools.map((t) => {
@@ -3698,6 +3730,8 @@ async function runTool(ctx, args) {
3698
3730
  const answer = a ? {
3699
3731
  md: str5(a.md),
3700
3732
  cites: strList(a.cites) ?? [],
3733
+ ...str5(a.verdict)?.trim() ? { verdict: str5(a.verdict)?.trim() } : {},
3734
+ ...toneOf(a.tone) ? { tone: toneOf(a.tone) } : {},
3701
3735
  ...typeof a.irreversible === "boolean" ? { irreversible: a.irreversible } : {}
3702
3736
  } : null;
3703
3737
  const r = isObject4(args.release) ? args.release : null;
@@ -4664,6 +4698,7 @@ var init_server = __esm(() => {
4664
4698
  init_brief();
4665
4699
  init_due();
4666
4700
  init_route();
4701
+ init_verdict();
4667
4702
  init_annotations();
4668
4703
  init_format();
4669
4704
  init_plan2();
@@ -5052,7 +5087,7 @@ var init_server = __esm(() => {
5052
5087
  },
5053
5088
  {
5054
5089
  name: "run",
5055
- description: "An engine's word on a run it holds (spec 26): with nothing but `runId`, a heartbeat that renews the lease (every few minutes while the session runs; a lapsed lease returns the node to ready). `park: {question}` when the session needs a person — the node parks with the question, the seat is free, the engine takes the next node. `fail: {log}` when the session exited without a PR — back to ready once, parked the second time. `watch` updates where to watch it. On a decider's (answerer's) run: `answer: {md, cites}` when the record settles the question, `release: {why, waitsOn, cites}` when the record says the node must wait on other items (its session stops, the node goes back to ready without a seat, and it is not taken again until those land), `escalate: {why, suggested?, forPerson?}` when it does not — `forPerson: {relation: {key, how}}` when the fix is a relation only a person can set (`blockedBy SL-1474`), which the person's card then sets in one press — SLEDS may refuse an answer and escalate it, and says why. When the PR opens: `actual` (or `land`) with `node` and state `pr`; the merge lands the node from the PR body's `SLEDS-Node:` line.",
5090
+ description: "An engine's word on a run it holds (spec 26): with nothing but `runId`, a heartbeat that renews the lease (every few minutes while the session runs; a lapsed lease returns the node to ready). `park: {question}` when the session needs a person — the node parks with the question, the seat is free, the engine takes the next node. `fail: {log}` when the session exited without a PR — back to ready once, parked the second time. `watch` updates where to watch it. On a decider's (answerer's) run: `answer: {verdict, tone, md, cites}` when the record settles the question, `release: {why, waitsOn, cites}` when the record says the node must wait on other items (its session stops, the node goes back to ready without a seat, and it is not taken again until those land), `escalate: {why, suggested?, forPerson?}` when it does not — `forPerson: {relation: {key, how}}` when the fix is a relation only a person can set (`blockedBy SL-1474`), which the person's card then sets in one press — SLEDS may refuse an answer and escalate it, and says why. When the PR opens: `actual` (or `land`) with `node` and state `pr`; the merge lands the node from the PR body's `SLEDS-Node:` line.",
5056
5091
  inputSchema: {
5057
5092
  type: "object",
5058
5093
  properties: {
@@ -5078,6 +5113,15 @@ var init_server = __esm(() => {
5078
5113
  description: "A decider's answer, on its own run: the record settles the question.",
5079
5114
  properties: {
5080
5115
  md: { type: "string", description: "The answer as you would tell the session." },
5116
+ verdict: {
5117
+ type: "string",
5118
+ description: "SL-2172: the verdict on one line, in plain words — md's first line. The card leads with it."
5119
+ },
5120
+ tone: {
5121
+ type: "string",
5122
+ enum: ["go", "change", "stop"],
5123
+ description: "go: go ahead as filed · change: change the plan · stop: stop or hold."
5124
+ },
5081
5125
  cites: {
5082
5126
  type: "array",
5083
5127
  items: { type: "string" },
@@ -6821,11 +6865,11 @@ var init_deploys = __esm(() => {
6821
6865
  });
6822
6866
 
6823
6867
  // ../../packages/shared/src/deploys/series.ts
6824
- var DAY_MS4, WEEK_MS2;
6868
+ var DAY_MS5, WEEK_MS3;
6825
6869
  var init_series = __esm(() => {
6826
6870
  init_metrics();
6827
- DAY_MS4 = 24 * 60 * 60 * 1000;
6828
- WEEK_MS2 = 7 * DAY_MS4;
6871
+ DAY_MS5 = 24 * 60 * 60 * 1000;
6872
+ WEEK_MS3 = 7 * DAY_MS5;
6829
6873
  });
6830
6874
 
6831
6875
  // ../../packages/shared/src/deploys/stability.ts
@@ -6836,27 +6880,40 @@ var init_stability = __esm(() => {
6836
6880
  });
6837
6881
 
6838
6882
  // ../../packages/shared/src/deploys/shipping.ts
6839
- var DAY_MS5;
6883
+ var DAY_MS6;
6840
6884
  var init_shipping = __esm(() => {
6841
- DAY_MS5 = 24 * 60 * 60 * 1000;
6885
+ DAY_MS6 = 24 * 60 * 60 * 1000;
6842
6886
  });
6843
6887
 
6844
6888
  // ../../packages/shared/src/deploys/doraBands.ts
6845
- var HOUR_MS2, DAY_MS6, WEEK_MS3, MONTH_DAYS = 30, MONTH_MS;
6889
+ var HOUR_MS2, DAY_MS7, WEEK_MS4, MONTH_DAYS = 30, MONTH_MS;
6846
6890
  var init_doraBands = __esm(() => {
6847
6891
  HOUR_MS2 = 60 * 60 * 1000;
6848
- DAY_MS6 = 24 * HOUR_MS2;
6849
- WEEK_MS3 = 7 * DAY_MS6;
6850
- MONTH_MS = MONTH_DAYS * DAY_MS6;
6892
+ DAY_MS7 = 24 * HOUR_MS2;
6893
+ WEEK_MS4 = 7 * DAY_MS7;
6894
+ MONTH_MS = MONTH_DAYS * DAY_MS7;
6851
6895
  });
6852
6896
 
6853
6897
  // ../../packages/shared/src/deploys/trends.ts
6854
- var DAY_MS7;
6898
+ var DAY_MS8;
6855
6899
  var init_trends = __esm(() => {
6856
6900
  init_batchSize();
6857
6901
  init_metrics();
6858
6902
  init_series();
6859
- DAY_MS7 = 24 * 60 * 60 * 1000;
6903
+ DAY_MS8 = 24 * 60 * 60 * 1000;
6904
+ });
6905
+
6906
+ // ../../packages/shared/src/deploys/sleuth.ts
6907
+ var SLEUTH_WORKING_HOURS;
6908
+ var init_sleuth = __esm(() => {
6909
+ init_deploys();
6910
+ init_workingHours();
6911
+ SLEUTH_WORKING_HOURS = Object.freeze({
6912
+ on: true,
6913
+ days: Object.freeze([1, 2, 3, 4, 5]),
6914
+ start: 14 * 60,
6915
+ end: 23 * 60
6916
+ });
6860
6917
  });
6861
6918
 
6862
6919
  // ../../packages/shared/src/newrelic/newrelic.ts
@@ -7489,7 +7546,7 @@ function eventFrame(o) {
7489
7546
  "",
7490
7547
  "## Post one verdict, then stop",
7491
7548
  "",
7492
- `- ${o.answer} Post it with the SLEDS \`${run}\` tool, \`{runId: "${a.runId}", answer: {md, cites}}\` — \`md\` as the session it goes to would read it, \`cites\` the list of pointers.`,
7549
+ `- ${o.answer} Post it with the SLEDS \`${run}\` tool, \`{runId: "${a.runId}", answer: {verdict, tone, md, cites}}\` — \`md\` as the session it goes to would read it, \`cites\` the list of pointers.`,
7493
7550
  `- ${o.escalate} Post it with \`${run}\`, \`{runId: "${a.runId}", escalate: {why, suggested?, cites?, irreversible?}}\` — \`why\` in one line for the person (with any action only a person can take), \`suggested\` your best answer as the session would be told it, when you have one. Mark \`irreversible: true\` when it is.`,
7494
7551
  ...VERDICT_SHAPE,
7495
7552
  "- Read what the tool answers: an answer can come back `escalated` with `refused` (irreversible, no cites, or the autonomy is ask). That is SLEDS's call, not a failure; do not post again.",
@@ -7701,6 +7758,7 @@ var init_engine = __esm(() => {
7701
7758
  };
7702
7759
  VERDICT_SHAPE = [
7703
7760
  "- **Verdict first.** The first line of `md` (and of `why`) is the verdict on its own, in plain words: what to do, or what is so. No file paths, line numbers or links in it.",
7761
+ "- On an answer, state that line as `verdict` too, and its `tone`: `go` (go ahead as filed), `change` (change the plan) or `stop` (stop or hold).",
7704
7762
  "- Then at most 3 short points, one line each, as a `-` list. Cites go in `cites`, not in the prose.",
7705
7763
  "- **One cite per span, no prose in it.** Code is `code:<path>:<line>`, the path from the repo root (`code:apps/web/src/App.tsx:42`); cite each line on its own, the cards merge them by file."
7706
7764
  ];
@@ -8379,6 +8437,8 @@ var init_src = __esm(() => {
8379
8437
  init_shipping();
8380
8438
  init_doraBands();
8381
8439
  init_trends();
8440
+ init_workingHours();
8441
+ init_sleuth();
8382
8442
  init_sentry();
8383
8443
  init_newrelic();
8384
8444
  init_musher();
@@ -8390,6 +8450,7 @@ var init_src = __esm(() => {
8390
8450
  init_say();
8391
8451
  init_engine();
8392
8452
  init_sources();
8453
+ init_verdict();
8393
8454
  init_otlp();
8394
8455
  init_session();
8395
8456
  init_effort();
@@ -9153,6 +9214,8 @@ init_series();
9153
9214
  init_shipping();
9154
9215
  init_doraBands();
9155
9216
  init_trends();
9217
+ init_workingHours();
9218
+ init_sleuth();
9156
9219
  init_sentry();
9157
9220
  init_newrelic();
9158
9221
  init_musher();
@@ -9164,6 +9227,7 @@ init_reshape();
9164
9227
  init_say();
9165
9228
  init_engine();
9166
9229
  init_sources();
9230
+ init_verdict();
9167
9231
  init_otlp();
9168
9232
  init_session();
9169
9233
  init_effort();
@@ -11822,7 +11886,7 @@ function beatBody(state, flags, host = {}, cap = {}) {
11822
11886
  name: flags.name,
11823
11887
  os: host.os ?? process.platform,
11824
11888
  runner: flags.runner,
11825
- version: host.version ?? "0.1.65" ?? "dev",
11889
+ version: host.version ?? "0.1.67" ?? "dev",
11826
11890
  running: live.length,
11827
11891
  items: live.slice(0, BEAT_ITEMS).map((r) => `${r.item}/${r.node}`),
11828
11892
  ...cap.atOnce !== undefined ? { atOnce: cap.atOnce } : {},
@@ -12938,7 +13002,7 @@ function answerPrompt(o) {
12938
13002
  "",
12939
13003
  "## Post one verdict, then stop",
12940
13004
  "",
12941
- `- Settled by the record: the SLEDS \`${run}\` tool with \`{runId: "${c.runId}", answer: {md, cites}}\` — \`md\` is the answer as you would tell the session, \`cites\` the list of pointers.`,
13005
+ `- Settled by the record: the SLEDS \`${run}\` tool with \`{runId: "${c.runId}", answer: {verdict, tone, md, cites}}\` — \`md\` is the answer as you would tell the session, \`cites\` the list of pointers.`,
12942
13006
  `- The record says node ${c.node} must wait for another item to land ("build nothing until #123 merges") and that item is on the board: \`${run}\` with \`{runId: "${c.runId}", release: {why, waitsOn, cites}}\` — \`waitsOn\` the keys of the items it waits on (\`SL-123\`). It frees the seat: node ${c.node}'s session is closed, and the node is retaken fresh once they land. Release rather than answer "stay parked", which only parks it again.`,
12943
13007
  `- Not settled, irreversible, or unsure: \`${run}\` with \`{runId: "${c.runId}", escalate: {why, suggested?, cites?, irreversible?}}\` — \`why\` in one line for the person (with any action only a person can take), \`suggested\` your best answer as the session would be told it, when you have one. Mark \`irreversible: true\` when it is, even if the session did not.`,
12944
13008
  `- When the fix is a relation on the item only a person can set (node ${c.node} looks like it waits on SL-1474, but the record does not settle it, so it is not a release), escalate with \`forPerson: {relation: {key, how}}\` — \`{key: "SL-1474", how: "blockedBy"}\` — so the person's card sets it in one press. Never tell the session to set it: a session cannot set relations.`,
@@ -18640,7 +18704,7 @@ async function fetchVersion(o) {
18640
18704
  const platform = o.platform ?? process.platform;
18641
18705
  const arch = o.arch ?? process.arch;
18642
18706
  const publicKey = o.publicKey ?? RELEASE_PUBLIC_KEY;
18643
- const running = o.running ?? "0.1.65";
18707
+ const running = o.running ?? "0.1.67";
18644
18708
  if (!publicKey)
18645
18709
  return { ok: false, why: "no release key yet" };
18646
18710
  if (!VERSION_RE2.test(o.version))
@@ -19866,7 +19930,7 @@ function engineUpdater(o) {
19866
19930
 
19867
19931
  // src/version.ts
19868
19932
  function cliVersion(env) {
19869
- return "0.1.65";
19933
+ return "0.1.67";
19870
19934
  }
19871
19935
 
19872
19936
  // src/commands/update.ts
@@ -20252,7 +20316,7 @@ function engineClient(o, org, breaker) {
20252
20316
  ...src,
20253
20317
  ...breaker ? { fetchFn: breaker.fetch } : {},
20254
20318
  timeoutMs: ENGINE_TIMEOUT_MS,
20255
- version: "0.1.65"
20319
+ version: "0.1.67"
20256
20320
  });
20257
20321
  const asked = org?.trim() || env.SLEDS_ORG?.trim();
20258
20322
  if (asked && !selectOrg(readConfig(o.home), org, env).seat)
@@ -20558,7 +20622,7 @@ async function driveEngine(o, flags, deps, until = {}) {
20558
20622
  runner: deps.runner.name,
20559
20623
  atOnce: flags.atOnce,
20560
20624
  interval: flags.interval,
20561
- cliVersion: "0.1.65"
20625
+ cliVersion: "0.1.67"
20562
20626
  };
20563
20627
  const beat = (checkedAt) => {
20564
20628
  const m = deps.machine;
@@ -22427,7 +22491,7 @@ function engineClient2(o, org, breaker) {
22427
22491
  ...src,
22428
22492
  ...breaker ? { fetchFn: breaker.fetch } : {},
22429
22493
  timeoutMs: ENGINE_TIMEOUT_MS2,
22430
- version: "0.1.65"
22494
+ version: "0.1.67"
22431
22495
  });
22432
22496
  const asked = org?.trim() || env.SLEDS_ORG?.trim();
22433
22497
  if (asked && !selectOrg(readConfig(o.home), org, env).seat)
@@ -22768,7 +22832,7 @@ async function driveEngine2(o, flags, deps, until = {}) {
22768
22832
  runner: deps.runner.name,
22769
22833
  atOnce: flags.atOnce,
22770
22834
  interval: flags.interval,
22771
- cliVersion: "0.1.65"
22835
+ cliVersion: "0.1.67"
22772
22836
  };
22773
22837
  const beat = (checkedAt) => {
22774
22838
  const m = deps.machine;
@@ -24879,7 +24943,7 @@ function contextFor(deps) {
24879
24943
  ...src,
24880
24944
  fetchFn: deps.fetchFn,
24881
24945
  timeoutMs: deps.timeoutMs,
24882
- version: "0.1.65"
24946
+ version: "0.1.67"
24883
24947
  }), deps.home, now) : null;
24884
24948
  return {
24885
24949
  channel: "code",
@@ -28414,6 +28478,15 @@ class SleuthError extends Error {
28414
28478
  this.adaptable = adaptable;
28415
28479
  }
28416
28480
  }
28481
+
28482
+ class SleuthDenied extends SleuthError {
28483
+ constructor(message) {
28484
+ super(message, false);
28485
+ }
28486
+ }
28487
+ var DENIED = /\bPERMISSION_DENIED\b/;
28488
+ var isDenial = (e) => DENIED.test(e.message) || typeof e.extensions?.code === "string" && DENIED.test(e.extensions.code);
28489
+ var deniedOutright = (data, errors) => errors.some(isDenial) && (!data || Object.values(data).every((v) => v === null));
28417
28490
  var RATE = /rate.?limit|throttl|too many requests/i;
28418
28491
  var backoffMs = (attempt, retryAfter) => {
28419
28492
  const s = Number(retryAfter);
@@ -28427,11 +28500,17 @@ var sleuthClient = (o) => {
28427
28500
  const sleep = o.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
28428
28501
  const retries = o.retries ?? 6;
28429
28502
  const timeoutMs = o.timeoutMs ?? 120000;
28430
- let scheme = "apikey";
28431
- let settled = false;
28503
+ const forced = o.scheme !== undefined;
28504
+ let scheme = o.scheme ?? "apikey";
28505
+ let settled = forced;
28506
+ let deniedOnce = false;
28507
+ let kept = forced;
28432
28508
  let count = 0;
28509
+ const other = (s) => s === "apikey" ? "Bearer" : "apikey";
28433
28510
  const request = async (query) => {
28434
28511
  let lastWhy = "";
28512
+ const startedAs = scheme;
28513
+ let switched = false;
28435
28514
  for (let attempt = 0;attempt <= retries; attempt++) {
28436
28515
  let res;
28437
28516
  count++;
@@ -28455,7 +28534,7 @@ var sleuthClient = (o) => {
28455
28534
  continue;
28456
28535
  }
28457
28536
  if (res.status === 401 || res.status === 403)
28458
- throw new SleuthError(`Sleuth refused the key (HTTP ${res.status}) — check SLEUTH_API_KEY`, false);
28537
+ throw new SleuthError(`Sleuth refused the key (HTTP ${res.status})${forced ? ` as ${scheme} (SLEUTH_AUTH)` : ""} — check SLEUTH_API_KEY`, false);
28459
28538
  if (res.status === 429 || res.status >= 500) {
28460
28539
  lastWhy = `HTTP ${res.status}`;
28461
28540
  const wait = backoffMs(attempt, res.headers.get("retry-after"));
@@ -28474,21 +28553,71 @@ var sleuthClient = (o) => {
28474
28553
  if (res.status >= 400 && !errors.length)
28475
28554
  throw new SleuthError(`Sleuth answered HTTP ${res.status}`, false);
28476
28555
  settled = true;
28477
- if (!body.data && errors.some((e) => RATE.test(e.message))) {
28556
+ const data = body.data ?? null;
28557
+ if (!data && errors.some((e) => RATE.test(e.message))) {
28478
28558
  lastWhy = errors[0]?.message ?? "rate limited";
28479
28559
  await sleep(backoffMs(attempt, null));
28480
28560
  continue;
28481
28561
  }
28482
- return { data: body.data ?? null, errors };
28562
+ if (deniedOutright(data, errors)) {
28563
+ deniedOnce = true;
28564
+ const root = errors.find(isDenial)?.path?.[0];
28565
+ const what = typeof root === "string" ? ` to ${root}` : "";
28566
+ if (!kept && !switched) {
28567
+ switched = true;
28568
+ scheme = other(scheme);
28569
+ o.log?.(`sleuth: PERMISSION_DENIED${what} as ${startedAs} — trying ${scheme}`);
28570
+ attempt--;
28571
+ continue;
28572
+ }
28573
+ if (switched) {
28574
+ scheme = startedAs;
28575
+ throw new SleuthDenied(`Sleuth answered PERMISSION_DENIED${what} as both apikey and Bearer — the key cannot read it; check its scopes in Sleuth › Settings › API`);
28576
+ }
28577
+ throw new SleuthDenied(forced ? `Sleuth answered PERMISSION_DENIED${what} as ${scheme} (SLEUTH_AUTH) — unset SLEUTH_AUTH to try both schemes, or check the key's scopes` : `Sleuth answered PERMISSION_DENIED${what} as ${scheme}, the scheme its other queries take — the key cannot read it`);
28578
+ }
28579
+ if (deniedOnce)
28580
+ kept = true;
28581
+ return { data, errors };
28483
28582
  }
28484
28583
  throw new SleuthError(`Sleuth did not answer after ${retries + 1} tries (${lastWhy})`, true);
28485
28584
  };
28486
- return { request, calls: () => count };
28585
+ return { request, calls: () => count, scheme: () => scheme };
28487
28586
  };
28488
28587
 
28489
28588
  // 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 } } } } }";
28589
+ var MAX_QUERY_DEPTH = 10;
28590
+ var TYPE_REF = "kind name ofType { kind name ofType { kind name ofType { kind name } } }";
28491
28591
  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} } } } } }`;
28592
+ var queryDepth = (query) => {
28593
+ let braces = 0;
28594
+ let parens = 0;
28595
+ let inString = false;
28596
+ let max = 0;
28597
+ for (let i = 0;i < query.length; i++) {
28598
+ const c = query[i];
28599
+ if (inString) {
28600
+ if (c === "\\")
28601
+ i++;
28602
+ else if (c === '"')
28603
+ inString = false;
28604
+ } else if (c === '"')
28605
+ inString = true;
28606
+ else if (c === "(")
28607
+ parens++;
28608
+ else if (c === ")")
28609
+ parens--;
28610
+ else if (parens > 0)
28611
+ continue;
28612
+ else if (c === "{")
28613
+ braces++;
28614
+ else if (c === "}")
28615
+ braces--;
28616
+ else if (braces > max && /[A-Za-z_]/.test(c))
28617
+ max = braces;
28618
+ }
28619
+ return max;
28620
+ };
28492
28621
  var indexSchema = (intro) => ({
28493
28622
  query: intro.__schema.queryType.name,
28494
28623
  types: new Map(intro.__schema.types.map((t) => [t.name, t]))
@@ -28514,9 +28643,20 @@ var START = /^(start|startDate|startTime|startAt|from|fromDate|dateFrom|since|be
28514
28643
  var END = /^(end|endDate|endTime|endAt|to|toDate|dateTo|until|before)$/i;
28515
28644
  var CURSOR = /^(after|cursor|nextCursor|pageCursor|pageToken)$/i;
28516
28645
  var PAGE = /^(page|pageNumber|pageNum)$/i;
28517
- var SIZE = /^(first|pageSize|limit|perPage|per_page|size|count|pageLimit)$/i;
28646
+ var SIZE_NAMES = [
28647
+ "pagesize",
28648
+ "first",
28649
+ "limit",
28650
+ "perpage",
28651
+ "per_page",
28652
+ "pagelimit",
28653
+ "size",
28654
+ "count"
28655
+ ];
28656
+ var sizeRank = (name) => SIZE_NAMES.indexOf(name.toLowerCase());
28518
28657
  var OFFSET = /^(offset|skip)$/i;
28519
28658
  var DATEISH = /date|time/i;
28659
+ var NUMERIC = /^(Int|Float|Long|BigInt|PositiveInt|NonNegativeInt)$/i;
28520
28660
  var dateArg = (path, typeName) => ({
28521
28661
  path,
28522
28662
  dateOnly: /^date$/i.test(typeName)
@@ -28524,18 +28664,21 @@ var dateArg = (path, typeName) => ({
28524
28664
  var planArgs = (s, args) => {
28525
28665
  const plan = { org: [], missing: [] };
28526
28666
  const visit = (vals, prefix, depth) => {
28667
+ const unfilled = [];
28527
28668
  for (const a of vals) {
28528
28669
  const u = unwrap(a.type);
28529
28670
  const path = [...prefix, a.name];
28530
28671
  const leaf = isLeaf(s, u.name);
28672
+ const numeric = leaf && !u.list && NUMERIC.test(u.name);
28531
28673
  const dated = leaf && (DATEISH.test(u.name) || u.name === "String" && DATEISH.test(a.name) && (START.test(a.name) || END.test(a.name)));
28532
28674
  if (kindOf(s, u.name) === "INPUT_OBJECT") {
28533
28675
  const before = JSON.stringify(plan);
28534
28676
  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("."));
28677
+ const inner = depth < 3 ? visit(inputs, path, depth + 1) : [];
28678
+ if (JSON.stringify(plan) !== before)
28679
+ unfilled.push(...inner);
28680
+ else if (isRequired(a) && inputs.some(isRequired))
28681
+ unfilled.push(path.join("."));
28539
28682
  continue;
28540
28683
  }
28541
28684
  if (leaf && !u.list && ORG.test(a.name))
@@ -28546,17 +28689,20 @@ var planArgs = (s, args) => {
28546
28689
  plan.end = dateArg(path, u.name);
28547
28690
  else if (leaf && !dated && CURSOR.test(a.name) && !plan.cursor)
28548
28691
  plan.cursor = path;
28549
- else if (leaf && PAGE.test(a.name) && !plan.page)
28692
+ else if (numeric && PAGE.test(a.name) && !plan.page)
28550
28693
  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)
28694
+ else if (numeric && sizeRank(a.name) >= 0) {
28695
+ const held = plan.size?.[plan.size.length - 1];
28696
+ if (!held || sizeRank(a.name) < sizeRank(held))
28697
+ plan.size = path;
28698
+ } else if (numeric && OFFSET.test(a.name) && !plan.offset)
28554
28699
  plan.offset = path;
28555
28700
  else if (isRequired(a))
28556
- plan.missing.push(path.join("."));
28701
+ unfilled.push(path.join("."));
28557
28702
  }
28703
+ return unfilled;
28558
28704
  };
28559
- visit(args, [], 0);
28705
+ plan.missing = visit(args, [], 0);
28560
28706
  return plan;
28561
28707
  };
28562
28708
  var windowed = (p) => !!(p.start && p.end);
@@ -28877,6 +29023,24 @@ var jsonl = (items) => items.length ? `${items.map((i) => JSON.stringify(i)).joi
28877
29023
  var readJsonl = (path) => readFileSync32(path, "utf8").split(`
28878
29024
  `).filter((l) => l.trim()).map((l) => JSON.parse(l));
28879
29025
  var CANNOT_QUERY = /Cannot query field "(\w+)" on type "(\w+)"/;
29026
+ var TOO_DEEP = /\bmax(imum)?\b.{0,24}\bdepth\b|\btoo deep\b/i;
29027
+ var PERMISSION_DENIED = /\bPERMISSION_DENIED\b/;
29028
+ var ORG_DENIED_NOTE = "Sleuth refused this key the organization object. Deploys come through orgChanges and do not need it; what is read under the organization (projects, teams, labels, metricsRecap counts) is missing from this export.";
29029
+ var whyNotFound = (schema, patterns) => {
29030
+ for (const re of patterns)
29031
+ for (const [type, t] of schema.types) {
29032
+ const f = (t.fields ?? []).find((x) => re.test(x.name));
29033
+ if (!f)
29034
+ continue;
29035
+ const args = planArgs(schema, f.args);
29036
+ if (args.missing.length)
29037
+ return `${type}.${f.name} needs ${args.missing.join(", ")}, which the export cannot fill`;
29038
+ if (type === schema.query && !args.org.length)
29039
+ return `${type}.${f.name} takes no org argument`;
29040
+ return `${type}.${f.name} is not reachable from the org`;
29041
+ }
29042
+ return "Sleuth's schema has no field by that name";
29043
+ };
28880
29044
  var exportSleuth = async (o) => {
28881
29045
  const log = o.log ?? (() => {});
28882
29046
  const now = (o.now ?? (() => new Date))();
@@ -28922,13 +29086,26 @@ var exportSleuth = async (o) => {
28922
29086
  `);
28923
29087
  const schema = indexSchema(intro.data);
28924
29088
  manifest.schema.types = schema.types.size;
29089
+ const fitDepth = new Map;
29090
+ let answered = false;
28925
29091
  const ask = async (build, steps) => {
28926
29092
  const keep = structural(steps);
28927
29093
  const root = steps[0]?.field.name ?? "";
29094
+ const path = steps.map((s) => s.field.name).join(".");
29095
+ let depth = fitDepth.get(path) ?? o.depth;
28928
29096
  for (let i = 0;i < 25; i++) {
28929
- const r = await o.client.request(build());
28930
- if (r.data && !r.errors.length)
29097
+ const r = await o.client.request(build(depth));
29098
+ if (r.data && !r.errors.length) {
29099
+ answered = true;
28931
29100
  return r.data;
29101
+ }
29102
+ const tooDeep = r.errors.some((e) => TOO_DEEP.test(e.message));
29103
+ if (!r.data && tooDeep && depth > 0) {
29104
+ depth--;
29105
+ fitDepth.set(path, depth);
29106
+ log(`sleuth said the query was too deep — reading nested objects ${depth} deep`);
29107
+ continue;
29108
+ }
28932
29109
  const prune = new Set;
28933
29110
  for (const e of r.errors) {
28934
29111
  const m = CANNOT_QUERY.exec(e.message);
@@ -28945,17 +29122,21 @@ var exportSleuth = async (o) => {
28945
29122
  continue;
28946
29123
  }
28947
29124
  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);
29125
+ if (empty) {
29126
+ if (r.errors.some(isDenial))
29127
+ throw new SleuthDenied(why);
29128
+ throw new SleuthError(why || "Sleuth answered with no data", !tooDeep);
29129
+ }
28950
29130
  for (const e of r.errors) {
28951
29131
  const tf = e.path ? typeFieldAt(schema, e.path) : undefined;
28952
29132
  warn(`${tf ? `${tf}: ` : ""}${e.message}`);
28953
29133
  }
29134
+ answered = true;
28954
29135
  return r.data;
28955
29136
  }
28956
29137
  throw new SleuthError("Sleuth kept refusing fields after 25 tries", false);
28957
29138
  };
28958
- const buildQuery = (steps, leafValues) => {
29139
+ const buildQuery = (steps, leafValues, depth) => {
28959
29140
  const last = steps.length - 1;
28960
29141
  const argsAt = (i) => {
28961
29142
  const st = steps[i];
@@ -28964,15 +29145,24 @@ var exportSleuth = async (o) => {
28964
29145
  return renderArgs(st.args, { org: o.org, ...st.args.size ? { size: NESTED_SIZE } : {} });
28965
29146
  };
28966
29147
  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} }`;
29148
+ const render = (d) => {
29149
+ const sel = buildSelection(schema, leaf.shape.item, d, exclude) ?? "{ __typename }";
29150
+ let inner = `${leaf.field.name}${argsAt(last)} ${wrapShape(leaf.shape, sel)}`;
29151
+ for (let i = last - 1;i >= 0; i--) {
29152
+ const st = steps[i];
29153
+ const ident = identitySelection(schema, st.shape.item, exclude);
29154
+ const body = `{ ${ident ? `${ident.slice(2, -2)} ` : ""}${inner} }`;
29155
+ inner = `${st.field.name}${argsAt(i)} ${wrapShape(st.shape, body)}`;
29156
+ }
29157
+ return `query { ${inner} }`;
29158
+ };
29159
+ let d = depth;
29160
+ let q = render(d);
29161
+ while (d > 0 && queryDepth(q) > MAX_QUERY_DEPTH)
29162
+ q = render(--d);
29163
+ if (d < o.depth)
29164
+ warn(`${leaf.field.name}: nested objects read ${d} deep, not ${o.depth} — Sleuth's max depth`);
29165
+ return q;
28976
29166
  };
28977
29167
  const extract = (steps, data) => {
28978
29168
  let level = [
@@ -29018,7 +29208,7 @@ var exportSleuth = async (o) => {
29018
29208
  ...canPage && a.page ? { page } : {},
29019
29209
  ...canPage && !a.cursor && !a.page && a.offset ? { offset } : {}
29020
29210
  };
29021
- const data = await ask(() => buildQuery(steps, values), steps);
29211
+ const data = await ask((depth) => buildQuery(steps, values, depth), steps);
29022
29212
  const { items, infos } = extract(steps, data);
29023
29213
  if (!canPage) {
29024
29214
  const full = oneParent && a.size !== undefined && items.length >= (values.size ?? 0);
@@ -29086,10 +29276,11 @@ var exportSleuth = async (o) => {
29086
29276
  }
29087
29277
  };
29088
29278
  const months = monthsBetween(o.since, o.until);
29279
+ let denial;
29089
29280
  const runSection = async (sec) => {
29090
29281
  const steps = findSection(schema, sec.patterns);
29091
29282
  if (!steps)
29092
- return { found: false, why: "Sleuth's schema has no field by that name" };
29283
+ return { found: false, why: whyNotFound(schema, sec.patterns) };
29093
29284
  const leaf = steps[steps.length - 1];
29094
29285
  const res = {
29095
29286
  found: true,
@@ -29145,6 +29336,11 @@ var exportSleuth = async (o) => {
29145
29336
  const why = e instanceof Error ? e.message : String(e);
29146
29337
  res.months.push({ month, file, count: 0, error: why });
29147
29338
  log(`${sec.name} ${month}: failed — ${why}`);
29339
+ if (e instanceof SleuthDenied) {
29340
+ res.error = `stopped at ${month} — ${why}`;
29341
+ denial = e;
29342
+ break;
29343
+ }
29148
29344
  if (++failedInARow >= MAX_FAILED_MONTHS) {
29149
29345
  res.error = `${MAX_FAILED_MONTHS} months in a row failed; stopped at ${month} — ${why}`;
29150
29346
  break;
@@ -29199,7 +29395,8 @@ var exportSleuth = async (o) => {
29199
29395
  `);
29200
29396
  manifest.organization = { file: "organization.json" };
29201
29397
  } catch (e) {
29202
- manifest.organization = { error: e instanceof Error ? e.message : String(e) };
29398
+ const error = e instanceof Error ? e.message : String(e);
29399
+ manifest.organization = e instanceof SleuthDenied || PERMISSION_DENIED.test(error) ? { error, note: ORG_DENIED_NOTE } : { error };
29203
29400
  }
29204
29401
  } else
29205
29402
  manifest.organization = { error: "Sleuth's schema has no organization(orgSlug) field" };
@@ -29211,6 +29408,8 @@ var exportSleuth = async (o) => {
29211
29408
  try {
29212
29409
  manifest.sections[sec.name] = await runSection(sec);
29213
29410
  } catch (e) {
29411
+ if (e instanceof SleuthDenied)
29412
+ denial = e;
29214
29413
  manifest.sections[sec.name] = {
29215
29414
  found: true,
29216
29415
  path: [],
@@ -29223,6 +29422,8 @@ var exportSleuth = async (o) => {
29223
29422
  log(`${sec.name}: failed — ${e instanceof Error ? e.message : String(e)}`);
29224
29423
  }
29225
29424
  save();
29425
+ if (denial && !answered)
29426
+ throw denial;
29226
29427
  }
29227
29428
  const deploys = manifest.sections.deploys;
29228
29429
  const recap = orgRoot ? fieldOf2(schema, unwrap(orgRoot.type).name, "metricsRecap") : undefined;
@@ -29282,6 +29483,8 @@ var SLEUTH_USAGE = `usage: sleds sleuth export --org <slug> --out <dir> [options
29282
29483
  Sleuth reports. Re-run to resume: finished past months on disk are not fetched again.
29283
29484
 
29284
29485
  SLEUTH_API_KEY the org's API key or access token (Sleuth › Settings › API)
29486
+ SLEUTH_AUTH apikey or bearer: send only that scheme (default: apikey, switching to
29487
+ Bearer once if Sleuth refuses or answers PERMISSION_DENIED)
29285
29488
  --org <slug> the Sleuth org slug (required)
29286
29489
  --out <dir> where to write (required; created if missing)
29287
29490
  --since <YYYY-MM> first month (default 2022-01)
@@ -29316,6 +29519,7 @@ var parse3 = (argv) => {
29316
29519
  return { words, flags };
29317
29520
  };
29318
29521
  var outDir = (raw, home) => resolve12(raw.startsWith("~/") ? join35(home, raw.slice(2)) : raw);
29522
+ var SCHEMES = { apikey: "apikey", bearer: "Bearer" };
29319
29523
  var str15 = (f, k) => typeof f[k] === "string" ? f[k] : undefined;
29320
29524
  var MONTH2 = /^\d{4}-(0[1-9]|1[0-2])$/;
29321
29525
  var KNOWN = [
@@ -29355,6 +29559,12 @@ var sleuthCmd = async (o) => {
29355
29559
  error("SLEUTH_API_KEY is not set — the org's key from Sleuth › Settings › API");
29356
29560
  return 2;
29357
29561
  }
29562
+ const authRaw = env.SLEUTH_AUTH?.trim().toLowerCase();
29563
+ const scheme = authRaw && Object.hasOwn(SCHEMES, authRaw) ? SCHEMES[authRaw] : undefined;
29564
+ if (authRaw && !scheme) {
29565
+ error(`SLEUTH_AUTH is "${env.SLEUTH_AUTH}" — apikey or bearer, or unset to try both`);
29566
+ return 2;
29567
+ }
29358
29568
  const now = (o.now ?? (() => new Date))();
29359
29569
  const since = str15(flags, "since") ?? "2022-01";
29360
29570
  const until = str15(flags, "until") ?? monthOf(now);
@@ -29377,6 +29587,7 @@ var sleuthCmd = async (o) => {
29377
29587
  const progress = error;
29378
29588
  const client = sleuthClient({
29379
29589
  key,
29590
+ ...scheme ? { scheme } : {},
29380
29591
  url: endpoint,
29381
29592
  ...o.fetch ? { fetch: o.fetch } : {},
29382
29593
  ...o.sleep ? { sleep: o.sleep } : {},
@@ -30179,7 +30390,7 @@ var realPidAlive4 = (pid) => {
30179
30390
  };
30180
30391
  function machineClient(home, env) {
30181
30392
  const src = meterSource(home, undefined, { env });
30182
- return src ? fetchClient({ ...src, version: "0.1.65" }) : null;
30393
+ return src ? fetchClient({ ...src, version: "0.1.67" }) : null;
30183
30394
  }
30184
30395
  async function noteAnswer2(home, a, now) {
30185
30396
  try {
@@ -31053,7 +31264,7 @@ var main = async (argv) => {
31053
31264
  now: Date.now(),
31054
31265
  stderr: (line) => console.error(line),
31055
31266
  isTTY: !!process.stderr.isTTY,
31056
- version: "0.1.65"
31267
+ version: "0.1.67"
31057
31268
  });
31058
31269
  switch (cmd) {
31059
31270
  case "update":
@@ -31288,7 +31499,7 @@ ${renderHelpText(argv[1])}`);
31288
31499
  case "version":
31289
31500
  case "--version":
31290
31501
  case "-v":
31291
- console.log("sleds 0.1.65");
31502
+ console.log("sleds 0.1.67");
31292
31503
  return 0;
31293
31504
  default:
31294
31505
  console.error(`unknown command: ${cmd}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleds/cli",
3
- "version": "0.1.65",
3
+ "version": "0.1.67",
4
4
  "description": "SLEDS CLI — the cost and limits of AI agent work, decided at the wall. Point Claude Code at a SLEDS proxy; estimate before you run; hold, switch model, or run overnight when you hit a limit.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",