@whittlelabs/sifter 0.30.17 → 0.30.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -134,6 +134,8 @@ Sixteen checks over everything the Sifter needs before it can review anything: a
134
134
 
135
135
  `init` and `run` run the relevant subset themselves, so `doctor` is for when the Sifter looks healthy and nothing is happening.
136
136
 
137
+ The login check asks the CLI itself (`claude auth status`), so a login kept in the macOS Keychain counts, and it asks with the settings a review loads. A signed-out CLI fails it with the remedy `claude auth login`; a signed-in one passes as, for example, `signed in (claude.ai, max; not verified)`, because only `--deep` proves the login works. It never prints the email or organization the CLI reports. A `claude` too old to have `auth status` is checked the old way, by looking for its credentials file.
138
+
137
139
  ```bash
138
140
  whittle-sifter doctor --deep # also spend one real turn proving the claude login works
139
141
  whittle-sifter doctor --json # the verdicts as JSON, and nothing else
package/bin.js CHANGED
@@ -12475,6 +12475,12 @@ var require_checks = __commonJS({
12475
12475
  const defaults = (0, environment_1.executorDefaultsFor)(subject.brand, config.auth.jobsApiUrl)?.[WORKSPACE_EXECUTOR] ?? {};
12476
12476
  return { ...defaults, ...user };
12477
12477
  }
12478
+ function claudeSettingsOf(config) {
12479
+ return config.executors[WORKSPACE_EXECUTOR]?.claudeSettings;
12480
+ }
12481
+ function describeLogin(status) {
12482
+ return [status.authMethod ?? "unknown method", status.subscriptionType].filter((part) => part !== null).join(", ");
12483
+ }
12478
12484
  function runRoot(config, subject, osTmpDir) {
12479
12485
  const settings = claudeCodeSettings(config, subject);
12480
12486
  const tmpDir = settings?.tmpDir;
@@ -12769,6 +12775,14 @@ var require_checks = __commonJS({
12769
12775
  if (probes.ambientAnthropicKey()) {
12770
12776
  return warn("claude-auth", "ANTHROPIC_API_KEY is set and will bill that account, shadowing any interactive login", "unset ANTHROPIC_API_KEY if you meant to use your Claude Code login");
12771
12777
  }
12778
+ const settingSources = claudeSettingsOf(config) === "user" ? "user" : "";
12779
+ const status = await probes.claudeAuthStatus(settingSources);
12780
+ if (status !== null) {
12781
+ if (!status.loggedIn) {
12782
+ return fail("claude-auth", "the claude CLI is not signed in", settingSources === "" ? "run `claude auth login`; if your login or provider is configured in Claude Code settings (apiKeyHelper, or an env block), set `claudeSettings: user` on the claude-code executor instead" : "run `claude auth login`");
12783
+ }
12784
+ return pass("claude-auth", `signed in (${describeLogin(status)}; not verified)`);
12785
+ }
12772
12786
  const store = await probes.claudeCredentialStore();
12773
12787
  if (store === null) {
12774
12788
  return warn("claude-auth", "no credential store found for the claude CLI (a macOS login may live in the Keychain, which this cannot read)", "run `claude` once and sign in, or run doctor --deep to spend one turn proving it");
@@ -21382,6 +21396,7 @@ var require_probes = __commonJS({
21382
21396
  };
21383
21397
  Object.defineProperty(exports2, "__esModule", { value: true });
21384
21398
  exports2.createProbes = createProbes;
21399
+ exports2.parseClaudeAuthStatus = parseClaudeAuthStatus;
21385
21400
  var child_process_1 = require("child_process");
21386
21401
  var fs_1 = require("fs");
21387
21402
  var promises_1 = require("fs/promises");
@@ -21600,6 +21615,10 @@ var require_probes = __commonJS({
21600
21615
  const candidate = path_1.default.join(configDir, ".credentials.json");
21601
21616
  return (0, fs_1.existsSync)(candidate) ? candidate : null;
21602
21617
  },
21618
+ async claudeAuthStatus(settingSources) {
21619
+ const outcome = await runForAnyExit("claude", ["--setting-sources", settingSources, "auth", "status", "--json"], VERSION_TIMEOUT_MS);
21620
+ return outcome === null ? null : parseClaudeAuthStatus(outcome);
21621
+ },
21603
21622
  ambientAnthropicKey: () => (process.env.ANTHROPIC_API_KEY ?? "").length > 0,
21604
21623
  async claudeRoundTrip() {
21605
21624
  const output = await runForOutput(
@@ -21620,13 +21639,37 @@ var require_probes = __commonJS({
21620
21639
  const outcome = await runCapturing(bin, args, timeoutMs);
21621
21640
  return outcome.ok ? outcome.stdout : null;
21622
21641
  }
21642
+ async function runForAnyExit(bin, args, timeoutMs) {
21643
+ const outcome = await runCapturing(bin, args, timeoutMs);
21644
+ if (outcome.ok)
21645
+ return outcome.stdout;
21646
+ return outcome.exited ? outcome.stdout : null;
21647
+ }
21648
+ function parseClaudeAuthStatus(stdout) {
21649
+ let parsed;
21650
+ try {
21651
+ parsed = JSON.parse(stdout);
21652
+ } catch {
21653
+ return null;
21654
+ }
21655
+ if (parsed === null || typeof parsed !== "object")
21656
+ return null;
21657
+ const { loggedIn, authMethod, subscriptionType } = parsed;
21658
+ if (typeof loggedIn !== "boolean")
21659
+ return null;
21660
+ return {
21661
+ loggedIn,
21662
+ authMethod: typeof authMethod === "string" && authMethod.length > 0 ? authMethod : null,
21663
+ subscriptionType: typeof subscriptionType === "string" && subscriptionType.length > 0 ? subscriptionType : null
21664
+ };
21665
+ }
21623
21666
  function runCapturing(bin, args, timeoutMs) {
21624
21667
  return new Promise((resolve) => {
21625
21668
  let child;
21626
21669
  try {
21627
21670
  child = (0, child_process_1.spawn)(bin, args, { stdio: ["ignore", "pipe", "pipe"] });
21628
21671
  } catch (err) {
21629
- resolve({ ok: false, reason: describe(err) });
21672
+ resolve({ ok: false, reason: describe(err), exited: false });
21630
21673
  return;
21631
21674
  }
21632
21675
  let stdout = "";
@@ -21640,7 +21683,7 @@ var require_probes = __commonJS({
21640
21683
  };
21641
21684
  const timer = setTimeout(() => {
21642
21685
  child.kill("SIGKILL");
21643
- settle({ ok: false, reason: `did not finish within ${Math.round(timeoutMs / 1e3)}s` });
21686
+ settle({ ok: false, reason: `did not finish within ${Math.round(timeoutMs / 1e3)}s`, exited: false });
21644
21687
  }, timeoutMs);
21645
21688
  child.stdout?.on("data", (chunk) => {
21646
21689
  stdout += chunk.toString("utf8");
@@ -21650,7 +21693,7 @@ var require_probes = __commonJS({
21650
21693
  });
21651
21694
  child.on("error", (err) => {
21652
21695
  clearTimeout(timer);
21653
- settle({ ok: false, reason: describe(err) });
21696
+ settle({ ok: false, reason: describe(err), exited: false });
21654
21697
  });
21655
21698
  child.on("close", (code, signal) => {
21656
21699
  clearTimeout(timer);
@@ -21660,7 +21703,8 @@ var require_probes = __commonJS({
21660
21703
  }
21661
21704
  const status = code === null ? `was killed by ${signal ?? "a signal"}` : `exited ${code}`;
21662
21705
  const said = stderr.split("\n").map((line) => line.trim()).find((line) => line.length > 0);
21663
- settle({ ok: false, reason: said ? `${status}: ${said}` : status });
21706
+ const reason = said ? `${status}: ${said}` : status;
21707
+ settle(code === null ? { ok: false, reason, exited: false } : { ok: false, reason, exited: true, stdout: stdout.trim() });
21664
21708
  });
21665
21709
  });
21666
21710
  }
@@ -26966,7 +27010,7 @@ var import_path = require("path");
26966
27010
  var import_promises = require("fs/promises");
26967
27011
  var import_yaml = __toESM(require_dist3());
26968
27012
  var import_shuttle = __toESM(require_dist5());
26969
- var buildVersion = true ? "0.30.17" : pkg.version;
27013
+ var buildVersion = true ? "0.30.18" : pkg.version;
26970
27014
  var sifterBrand = {
26971
27015
  product: {
26972
27016
  id: "sifter",