@whittlelabs/sifter 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,8 +4,8 @@ The Whittle Sifter is a paired AI reviewer for [Whittle Sift](https://sift.whitt
4
4
 
5
5
  ## Install
6
6
 
7
- `@whittlelabs/sifter` is published publicly to npmjs.com (released by
8
- `sifter-release.yml` on a version bump):
7
+ `@whittlelabs/sifter` is published publicly to npmjs.com (released by the
8
+ delivery pipeline on a version bump):
9
9
 
10
10
  ```bash
11
11
  npm install -g @whittlelabs/sifter
package/bin.js CHANGED
@@ -18983,11 +18983,36 @@ var require_client2 = __commonJS({
18983
18983
  return this.request("POST", "/api/actor-tokens", { actor });
18984
18984
  }
18985
18985
  // ── Identity ────────────────────────────────────────────────────
18986
+ /**
18987
+ * Sign up. This NEVER returns an account or a session: Keep answers 201 with
18988
+ * an email-verification challenge, which is answered at
18989
+ * POST /api/challenges/:token/response to mint a session.
18990
+ */
18986
18991
  async createAccount(email, password, name) {
18987
- return this.request("POST", "/api/accounts", { email, password, name });
18992
+ const body = await this.envelope("POST", "/api/accounts", { email, password, name });
18993
+ if (!body.challenge) {
18994
+ throw new Error("sign-up returned no challenge; the contract expects one");
18995
+ }
18996
+ return body.challenge;
18988
18997
  }
18998
+ /**
18999
+ * Sign in. Two legitimate outcomes, neither an error: a session, or a
19000
+ * challenge (MFA, unverified email). Both arrive on HTTP 200.
19001
+ */
18989
19002
  async createSession(email, password) {
18990
- return this.request("POST", "/api/sessions", { email, password });
19003
+ const body = await this.envelope("POST", "/api/sessions", { email, password });
19004
+ if (body.challenge) {
19005
+ return { kind: "challenge", challenge: body.challenge };
19006
+ }
19007
+ const data = body.data;
19008
+ if (!data?.session) {
19009
+ throw new Error("sign-in returned neither a session nor a challenge");
19010
+ }
19011
+ return { kind: "session", user: data.user, session: data.session };
19012
+ }
19013
+ /** Answer a challenge and obtain the session it was standing in for. */
19014
+ async respondToChallenge(challengeToken, type, response) {
19015
+ return this.request("POST", `/api/challenges/${encodeURIComponent(challengeToken)}/response`, { type, response });
18991
19016
  }
18992
19017
  async destroySession(refreshToken) {
18993
19018
  await this.request("DELETE", "/api/sessions", refreshToken ? { refreshToken } : void 0);
@@ -19133,6 +19158,28 @@ var require_client2 = __commonJS({
19133
19158
  return this.request("GET", `/api/users/${encodeURIComponent(userId)}/github-token`);
19134
19159
  }
19135
19160
  // ── HTTP ────────────────────────────────────────────────────────
19161
+ /**
19162
+ * Reads the response envelope WITHOUT treating `success: false` as a
19163
+ * failure. Sign-in and sign-up use it because a challenge arrives on a 2xx
19164
+ * with success:false and no error object — `request` would raise a
19165
+ * misleading "Request failed with status 200" and give the caller no way to
19166
+ * tell MFA from a bad password (whittlelabs#530). A real error still throws.
19167
+ */
19168
+ async envelope(method, path, body) {
19169
+ const response = await fetch(`${this.baseUrl}${path}`, {
19170
+ method,
19171
+ headers: this.headers,
19172
+ body: body ? JSON.stringify(body) : void 0
19173
+ });
19174
+ const json = await response.json();
19175
+ if (!response.ok || !json.success && !json.challenge) {
19176
+ const error = new Error(json.error?.message ?? `Request failed with status ${response.status}`);
19177
+ error.code = json.error?.code;
19178
+ error.statusCode = response.status;
19179
+ throw error;
19180
+ }
19181
+ return json;
19182
+ }
19136
19183
  async request(method, path, body) {
19137
19184
  const url = `${this.baseUrl}${path}`;
19138
19185
  const response = await fetch(url, {
@@ -22321,7 +22368,7 @@ var import_path = require("path");
22321
22368
  var import_promises = require("fs/promises");
22322
22369
  var import_yaml = __toESM(require_dist());
22323
22370
  var import_shuttle = __toESM(require_dist5());
22324
- var buildVersion = true ? "0.23.0" : pkg.version;
22371
+ var buildVersion = true ? "0.24.0" : pkg.version;
22325
22372
  var sifterBrand = {
22326
22373
  product: {
22327
22374
  id: "sifter",