@binaryandthread/cli 0.3.1 → 0.3.2

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.
@@ -1378,7 +1378,7 @@ function registerStage(program, makeClient, loadConfig) {
1378
1378
  brand
1379
1379
  });
1380
1380
  if (res.save) {
1381
- const { saveConfig } = await import("./config-GVEB4R7I.js");
1381
+ const { saveConfig } = await import("./config-WV4DTQXU.js");
1382
1382
  saveConfig(res.save);
1383
1383
  }
1384
1384
  printResult(res, json);
@@ -1434,4 +1434,4 @@ export {
1434
1434
  runStage,
1435
1435
  registerStage
1436
1436
  };
1437
- //# sourceMappingURL=chunk-VDOFLQ2P.js.map
1437
+ //# sourceMappingURL=chunk-BFMH7VCE.js.map
@@ -63,4 +63,4 @@ export {
63
63
  saveConfig,
64
64
  forgetConfig
65
65
  };
66
- //# sourceMappingURL=chunk-ZNDKXHMV.js.map
66
+ //# sourceMappingURL=chunk-RGFMXWD5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts"],"sourcesContent":["// Local config — ~/.config/bt/config.json.\n//\n// Holds ONLY non-sensitive fields (SECURITY G15): cart_id, email, region, defaults.\n// The auth TOKEN is NEVER here — it goes in the OS keychain in Wave 5. Persisting a\n// token in plaintext config would be the exact leak G15 forbids.\n//\n// Writes are ATOMIC (F-B1): write to config.json.tmp then fs.renameSync, so a crash\n// mid-write never corrupts the file (rename is atomic on POSIX + Windows same-volume).\n\nimport * as fs from \"node:fs\"\nimport * as os from \"node:os\"\nimport * as path from \"node:path\"\n\nexport interface BtConfig {\n cart_id?: string\n email?: string\n region?: string\n defaultSize?: string\n defaultColour?: string\n defaultBranch?: string\n /** RUNTIME-ONLY (ENG-21): where the effective region came from this run — flag, saved config, or\n * a locale guess. Deliberately NOT in ALLOWED_KEYS, so saveConfig always strips it. */\n region_source?: \"flag\" | \"config\" | \"locale\"\n}\n\n// Persisted keys exclude the RUNTIME-ONLY fields (region_source) — typing ALLOWED_KEYS as the full\n// `keyof BtConfig` wrongly widened `key` to include region_source's literal union, so `out[key] = v`\n// (a string) failed to typecheck. PersistedKey narrows it to the string-valued, persistable fields.\ntype PersistedKey = Exclude<keyof BtConfig, \"region_source\">\n\n// Only these keys are ever persisted — anything else (notably `token`) is dropped (G15).\nconst ALLOWED_KEYS: Array<PersistedKey> = [\n \"cart_id\",\n \"email\",\n \"region\",\n \"defaultSize\",\n \"defaultColour\",\n \"defaultBranch\",\n]\n\n/** The config directory: $XDG_CONFIG_HOME/bt or ~/.config/bt. */\nexport function configDir(): string {\n const xdg = process.env.XDG_CONFIG_HOME\n const base = xdg && xdg.trim() ? xdg : path.join(os.homedir(), \".config\")\n return path.join(base, \"bt\")\n}\n\nexport function configPath(): string {\n return path.join(configDir(), \"config.json\")\n}\n\n/** Load the config, or {} if none / unreadable. Never throws. */\nexport function loadConfig(): BtConfig {\n try {\n const raw = fs.readFileSync(configPath(), \"utf8\")\n const parsed = JSON.parse(raw) as Record<string, unknown>\n return sanitize(parsed)\n } catch {\n return {}\n }\n}\n\n/** Merge-and-save the given fields. Strips any non-allowlisted key (G15). Atomic (F-B1). */\nexport function saveConfig(patch: Partial<BtConfig>): BtConfig {\n const merged = { ...loadConfig(), ...sanitize(patch as Record<string, unknown>) }\n const dir = configDir()\n fs.mkdirSync(dir, { recursive: true })\n const target = configPath()\n const tmp = target + \".tmp\"\n // write to a temp file then atomically rename (F-B1)\n fs.writeFileSync(tmp, JSON.stringify(merged, null, 2), { encoding: \"utf8\", mode: 0o600 })\n fs.renameSync(tmp, target)\n return merged\n}\n\n/** Wipe the local config file (G15 erasure — `bt forget`). Never throws. */\nexport function forgetConfig(): void {\n try {\n fs.rmSync(configPath(), { force: true })\n } catch {\n /* ignore */\n }\n}\n\n/** Keep only allowlisted, non-sensitive keys (drops `token` and anything unexpected). */\nfunction sanitize(obj: Record<string, unknown>): BtConfig {\n const out: BtConfig = {}\n for (const key of ALLOWED_KEYS) {\n const v = obj[key]\n if (typeof v === \"string\" && v.length > 0) {\n out[key] = v\n }\n }\n return out\n}\n"],"mappings":";;;AASA,YAAY,QAAQ;AACpB,YAAY,QAAQ;AACpB,YAAY,UAAU;AAoBtB,IAAM,eAAoC;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,SAAS,YAAoB;AAClC,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,OAAO,OAAO,IAAI,KAAK,IAAI,MAAW,UAAQ,WAAQ,GAAG,SAAS;AACxE,SAAY,UAAK,MAAM,IAAI;AAC7B;AAEO,SAAS,aAAqB;AACnC,SAAY,UAAK,UAAU,GAAG,aAAa;AAC7C;AAGO,SAAS,aAAuB;AACrC,MAAI;AACF,UAAM,MAAS,gBAAa,WAAW,GAAG,MAAM;AAChD,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,WAAO,SAAS,MAAM;AAAA,EACxB,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAGO,SAAS,WAAW,OAAoC;AAC7D,QAAM,SAAS,EAAE,GAAG,WAAW,GAAG,GAAG,SAAS,KAAgC,EAAE;AAChF,QAAM,MAAM,UAAU;AACtB,EAAG,aAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AACrC,QAAM,SAAS,WAAW;AAC1B,QAAM,MAAM,SAAS;AAErB,EAAG,iBAAc,KAAK,KAAK,UAAU,QAAQ,MAAM,CAAC,GAAG,EAAE,UAAU,QAAQ,MAAM,IAAM,CAAC;AACxF,EAAG,cAAW,KAAK,MAAM;AACzB,SAAO;AACT;AAGO,SAAS,eAAqB;AACnC,MAAI;AACF,IAAG,UAAO,WAAW,GAAG,EAAE,OAAO,KAAK,CAAC;AAAA,EACzC,QAAQ;AAAA,EAER;AACF;AAGA,SAAS,SAAS,KAAwC;AACxD,QAAM,MAAgB,CAAC;AACvB,aAAW,OAAO,cAAc;AAC9B,UAAM,IAAI,IAAI,GAAG;AACjB,QAAI,OAAO,MAAM,YAAY,EAAE,SAAS,GAAG;AACzC,UAAI,GAAG,IAAI;AAAA,IACb;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
@@ -5,7 +5,7 @@ import {
5
5
  forgetConfig,
6
6
  loadConfig,
7
7
  saveConfig
8
- } from "./chunk-ZNDKXHMV.js";
8
+ } from "./chunk-RGFMXWD5.js";
9
9
  import "./chunk-7OCVIDC7.js";
10
10
  export {
11
11
  configDir,
@@ -14,4 +14,4 @@ export {
14
14
  loadConfig,
15
15
  saveConfig
16
16
  };
17
- //# sourceMappingURL=config-GVEB4R7I.js.map
17
+ //# sourceMappingURL=config-WV4DTQXU.js.map
@@ -7,7 +7,7 @@ import {
7
7
  runStage,
8
8
  runView,
9
9
  sizesForColour
10
- } from "./chunk-VDOFLQ2P.js";
10
+ } from "./chunk-BFMH7VCE.js";
11
11
  import "./chunk-7OCVIDC7.js";
12
12
 
13
13
  // src/interactive.ts
@@ -96,7 +96,7 @@ async function stageInteractive(client, config, product, brand, select, print, s
96
96
  if (res.save && saveConfig) {
97
97
  saveConfig(res.save);
98
98
  } else if (res.save) {
99
- const { saveConfig: realSave } = await import("./config-GVEB4R7I.js");
99
+ const { saveConfig: realSave } = await import("./config-WV4DTQXU.js");
100
100
  realSave(res.save);
101
101
  }
102
102
  print(res.lines);
@@ -104,4 +104,4 @@ async function stageInteractive(client, config, product, brand, select, print, s
104
104
  export {
105
105
  runInteractiveBrowse
106
106
  };
107
- //# sourceMappingURL=interactive-JALKLT56.js.map
107
+ //# sourceMappingURL=interactive-BOS6VMRJ.js.map
@@ -19,7 +19,7 @@ async function pollUntilPlaced(client, sessionId, opts = {}) {
19
19
  opts.onTick?.(attempt);
20
20
  const result = await client.poll(sessionId);
21
21
  if (result.status === "placed") {
22
- return { status: "placed", order_id: result.order_id };
22
+ return { status: "placed", order_id: result.order_id, summary: result.summary ?? null };
23
23
  }
24
24
  if (result.status === "expired") {
25
25
  return { status: "expired", order_id: result.order_id };
@@ -36,4 +36,4 @@ async function pollUntilPlaced(client, sessionId, opts = {}) {
36
36
  export {
37
37
  pollUntilPlaced
38
38
  };
39
- //# sourceMappingURL=poll-O3VUD5WV.js.map
39
+ //# sourceMappingURL=poll-II3TVJLK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/poll.ts"],"sourcesContent":["// Bounded poll loop — the checkout-completion watcher (F-C5 / F-I3).\n//\n// After the browser hop, the CLI polls the bt_session until the order is placed. This loop\n// is the single most safety-critical control flow in the write path that runs unattended:\n// it MUST be bounded so a closed-browser / never-returns buyer can never hang the terminal\n// forever (F-I3). It polls every `intervalMs` (5s), stops the instant the session is\n// \"placed\" or \"expired\", and stops at a `deadlineMs` (20-min cap) returning \"timeout\".\n//\n// The clock + sleep are INJECTED (defaults wired to the real ones) so the deadline is\n// deterministically testable without burning 20 minutes of wall-clock — and so nothing\n// authoritative is ever computed from an un-injectable clock (F-I5).\n\nimport type { PollResult, SettledOrderSummary } from \"@binaryandthread/store-client\"\n\n/** The minimal client surface the poll loop needs (so tests can mock just this). */\nexport interface PollClientLike {\n poll(sessionId: string): Promise<PollResult>\n}\n\nexport interface PollOptions {\n /** poll cadence in ms (default 5s). */\n intervalMs?: number\n /** hard deadline in ms from the first attempt (default 20 min). After this → \"timeout\". */\n deadlineMs?: number\n /** injectable monotonic clock (default Date.now). */\n now?: () => number\n /** injectable sleep (default a real setTimeout-backed sleep). */\n sleep?: (ms: number) => Promise<void>\n /** optional progress callback (the spinner / status line in the CLI). */\n onTick?: (attempt: number) => void\n}\n\n/** The terminal result of the poll loop. \"timeout\" is OUR status, distinct from the server's. */\nexport interface PollOutcome {\n status: \"placed\" | \"expired\" | \"timeout\"\n order_id: string | null\n /** The settled, discount-aware order summary — present when placed (ENG-39). */\n summary?: SettledOrderSummary | null\n}\n\nconst DEFAULT_INTERVAL_MS = 5_000\nconst DEFAULT_DEADLINE_MS = 20 * 60 * 1_000 // 20 minutes (F-C5)\n\nfunction realSleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\n/**\n * Poll `client.poll(sessionId)` until the order is placed, the session expires, or the\n * deadline is hit. NEVER loops forever: the deadline is the hard ceiling (F-I3). Cancellable\n * by the caller via SIGINT — the cart stays intact server-side, so a Ctrl-C is fully\n * resumable with `bt checkout --resume` (no local in-flight state to corrupt, F-I1).\n */\nexport async function pollUntilPlaced(\n client: PollClientLike,\n sessionId: string,\n opts: PollOptions = {}\n): Promise<PollOutcome> {\n const intervalMs = opts.intervalMs ?? DEFAULT_INTERVAL_MS\n const deadlineMs = opts.deadlineMs ?? DEFAULT_DEADLINE_MS\n const now = opts.now ?? Date.now\n const sleep = opts.sleep ?? realSleep\n\n const start = now()\n let attempt = 0\n\n for (;;) {\n attempt += 1\n opts.onTick?.(attempt)\n\n const result = await client.poll(sessionId)\n if (result.status === \"placed\") {\n return { status: \"placed\", order_id: result.order_id, summary: result.summary ?? null }\n }\n if (result.status === \"expired\") {\n return { status: \"expired\", order_id: result.order_id }\n }\n\n // Still pending. If we've reached the deadline, stop — never loop forever (F-I3).\n if (now() - start >= deadlineMs) {\n return { status: \"timeout\", order_id: null }\n }\n\n await sleep(intervalMs)\n\n // Re-check the deadline AFTER sleeping so we never start another poll past the cap\n // (bounded overshoot: at most one interval beyond the deadline).\n if (now() - start >= deadlineMs) {\n return { status: \"timeout\", order_id: null }\n }\n }\n}\n"],"mappings":";;;;AAwCA,IAAM,sBAAsB;AAC5B,IAAM,sBAAsB,KAAK,KAAK;AAEtC,SAAS,UAAU,IAA2B;AAC5C,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;AAQA,eAAsB,gBACpB,QACA,WACA,OAAoB,CAAC,GACC;AACtB,QAAM,aAAa,KAAK,cAAc;AACtC,QAAM,aAAa,KAAK,cAAc;AACtC,QAAM,MAAM,KAAK,OAAO,KAAK;AAC7B,QAAM,QAAQ,KAAK,SAAS;AAE5B,QAAM,QAAQ,IAAI;AAClB,MAAI,UAAU;AAEd,aAAS;AACP,eAAW;AACX,SAAK,SAAS,OAAO;AAErB,UAAM,SAAS,MAAM,OAAO,KAAK,SAAS;AAC1C,QAAI,OAAO,WAAW,UAAU;AAC9B,aAAO,EAAE,QAAQ,UAAU,UAAU,OAAO,UAAU,SAAS,OAAO,WAAW,KAAK;AAAA,IACxF;AACA,QAAI,OAAO,WAAW,WAAW;AAC/B,aAAO,EAAE,QAAQ,WAAW,UAAU,OAAO,SAAS;AAAA,IACxD;AAGA,QAAI,IAAI,IAAI,SAAS,YAAY;AAC/B,aAAO,EAAE,QAAQ,WAAW,UAAU,KAAK;AAAA,IAC7C;AAEA,UAAM,MAAM,UAAU;AAItB,QAAI,IAAI,IAAI,SAAS,YAAY;AAC/B,aAAO,EAAE,QAAQ,WAAW,UAAU,KAAK;AAAA,IAC7C;AAAA,EACF;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@binaryandthread/cli",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "bt — the Binary & Thread terminal store. Buy heavyweight pieces with the ceremony of a production deploy. A dumb, well-tested client over the Store API; cards never touch the terminal.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/config.ts"],"sourcesContent":["// Local config — ~/.config/bt/config.json.\n//\n// Holds ONLY non-sensitive fields (SECURITY G15): cart_id, email, region, defaults.\n// The auth TOKEN is NEVER here — it goes in the OS keychain in Wave 5. Persisting a\n// token in plaintext config would be the exact leak G15 forbids.\n//\n// Writes are ATOMIC (F-B1): write to config.json.tmp then fs.renameSync, so a crash\n// mid-write never corrupts the file (rename is atomic on POSIX + Windows same-volume).\n\nimport * as fs from \"node:fs\"\nimport * as os from \"node:os\"\nimport * as path from \"node:path\"\n\nexport interface BtConfig {\n cart_id?: string\n email?: string\n region?: string\n defaultSize?: string\n defaultColour?: string\n defaultBranch?: string\n /** RUNTIME-ONLY (ENG-21): where the effective region came from this run — flag, saved config, or\n * a locale guess. Deliberately NOT in ALLOWED_KEYS, so saveConfig always strips it. */\n region_source?: \"flag\" | \"config\" | \"locale\"\n}\n\n// Only these keys are ever persisted — anything else (notably `token`) is dropped (G15).\nconst ALLOWED_KEYS: Array<keyof BtConfig> = [\n \"cart_id\",\n \"email\",\n \"region\",\n \"defaultSize\",\n \"defaultColour\",\n \"defaultBranch\",\n]\n\n/** The config directory: $XDG_CONFIG_HOME/bt or ~/.config/bt. */\nexport function configDir(): string {\n const xdg = process.env.XDG_CONFIG_HOME\n const base = xdg && xdg.trim() ? xdg : path.join(os.homedir(), \".config\")\n return path.join(base, \"bt\")\n}\n\nexport function configPath(): string {\n return path.join(configDir(), \"config.json\")\n}\n\n/** Load the config, or {} if none / unreadable. Never throws. */\nexport function loadConfig(): BtConfig {\n try {\n const raw = fs.readFileSync(configPath(), \"utf8\")\n const parsed = JSON.parse(raw) as Record<string, unknown>\n return sanitize(parsed)\n } catch {\n return {}\n }\n}\n\n/** Merge-and-save the given fields. Strips any non-allowlisted key (G15). Atomic (F-B1). */\nexport function saveConfig(patch: Partial<BtConfig>): BtConfig {\n const merged = { ...loadConfig(), ...sanitize(patch as Record<string, unknown>) }\n const dir = configDir()\n fs.mkdirSync(dir, { recursive: true })\n const target = configPath()\n const tmp = target + \".tmp\"\n // write to a temp file then atomically rename (F-B1)\n fs.writeFileSync(tmp, JSON.stringify(merged, null, 2), { encoding: \"utf8\", mode: 0o600 })\n fs.renameSync(tmp, target)\n return merged\n}\n\n/** Wipe the local config file (G15 erasure — `bt forget`). Never throws. */\nexport function forgetConfig(): void {\n try {\n fs.rmSync(configPath(), { force: true })\n } catch {\n /* ignore */\n }\n}\n\n/** Keep only allowlisted, non-sensitive keys (drops `token` and anything unexpected). */\nfunction sanitize(obj: Record<string, unknown>): BtConfig {\n const out: BtConfig = {}\n for (const key of ALLOWED_KEYS) {\n const v = obj[key]\n if (typeof v === \"string\" && v.length > 0) {\n out[key] = v\n }\n }\n return out\n}\n"],"mappings":";;;AASA,YAAY,QAAQ;AACpB,YAAY,QAAQ;AACpB,YAAY,UAAU;AAetB,IAAM,eAAsC;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,SAAS,YAAoB;AAClC,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,OAAO,OAAO,IAAI,KAAK,IAAI,MAAW,UAAQ,WAAQ,GAAG,SAAS;AACxE,SAAY,UAAK,MAAM,IAAI;AAC7B;AAEO,SAAS,aAAqB;AACnC,SAAY,UAAK,UAAU,GAAG,aAAa;AAC7C;AAGO,SAAS,aAAuB;AACrC,MAAI;AACF,UAAM,MAAS,gBAAa,WAAW,GAAG,MAAM;AAChD,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,WAAO,SAAS,MAAM;AAAA,EACxB,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAGO,SAAS,WAAW,OAAoC;AAC7D,QAAM,SAAS,EAAE,GAAG,WAAW,GAAG,GAAG,SAAS,KAAgC,EAAE;AAChF,QAAM,MAAM,UAAU;AACtB,EAAG,aAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AACrC,QAAM,SAAS,WAAW;AAC1B,QAAM,MAAM,SAAS;AAErB,EAAG,iBAAc,KAAK,KAAK,UAAU,QAAQ,MAAM,CAAC,GAAG,EAAE,UAAU,QAAQ,MAAM,IAAM,CAAC;AACxF,EAAG,cAAW,KAAK,MAAM;AACzB,SAAO;AACT;AAGO,SAAS,eAAqB;AACnC,MAAI;AACF,IAAG,UAAO,WAAW,GAAG,EAAE,OAAO,KAAK,CAAC;AAAA,EACzC,QAAQ;AAAA,EAER;AACF;AAGA,SAAS,SAAS,KAAwC;AACxD,QAAM,MAAgB,CAAC;AACvB,aAAW,OAAO,cAAc;AAC9B,UAAM,IAAI,IAAI,GAAG;AACjB,QAAI,OAAO,MAAM,YAAY,EAAE,SAAS,GAAG;AACzC,UAAI,GAAG,IAAI;AAAA,IACb;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/poll.ts"],"sourcesContent":["// Bounded poll loop — the checkout-completion watcher (F-C5 / F-I3).\n//\n// After the browser hop, the CLI polls the bt_session until the order is placed. This loop\n// is the single most safety-critical control flow in the write path that runs unattended:\n// it MUST be bounded so a closed-browser / never-returns buyer can never hang the terminal\n// forever (F-I3). It polls every `intervalMs` (5s), stops the instant the session is\n// \"placed\" or \"expired\", and stops at a `deadlineMs` (20-min cap) returning \"timeout\".\n//\n// The clock + sleep are INJECTED (defaults wired to the real ones) so the deadline is\n// deterministically testable without burning 20 minutes of wall-clock — and so nothing\n// authoritative is ever computed from an un-injectable clock (F-I5).\n\nimport type { PollResult } from \"@binaryandthread/store-client\"\n\n/** The minimal client surface the poll loop needs (so tests can mock just this). */\nexport interface PollClientLike {\n poll(sessionId: string): Promise<PollResult>\n}\n\nexport interface PollOptions {\n /** poll cadence in ms (default 5s). */\n intervalMs?: number\n /** hard deadline in ms from the first attempt (default 20 min). After this → \"timeout\". */\n deadlineMs?: number\n /** injectable monotonic clock (default Date.now). */\n now?: () => number\n /** injectable sleep (default a real setTimeout-backed sleep). */\n sleep?: (ms: number) => Promise<void>\n /** optional progress callback (the spinner / status line in the CLI). */\n onTick?: (attempt: number) => void\n}\n\n/** The terminal result of the poll loop. \"timeout\" is OUR status, distinct from the server's. */\nexport interface PollOutcome {\n status: \"placed\" | \"expired\" | \"timeout\"\n order_id: string | null\n}\n\nconst DEFAULT_INTERVAL_MS = 5_000\nconst DEFAULT_DEADLINE_MS = 20 * 60 * 1_000 // 20 minutes (F-C5)\n\nfunction realSleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\n/**\n * Poll `client.poll(sessionId)` until the order is placed, the session expires, or the\n * deadline is hit. NEVER loops forever: the deadline is the hard ceiling (F-I3). Cancellable\n * by the caller via SIGINT — the cart stays intact server-side, so a Ctrl-C is fully\n * resumable with `bt checkout --resume` (no local in-flight state to corrupt, F-I1).\n */\nexport async function pollUntilPlaced(\n client: PollClientLike,\n sessionId: string,\n opts: PollOptions = {}\n): Promise<PollOutcome> {\n const intervalMs = opts.intervalMs ?? DEFAULT_INTERVAL_MS\n const deadlineMs = opts.deadlineMs ?? DEFAULT_DEADLINE_MS\n const now = opts.now ?? Date.now\n const sleep = opts.sleep ?? realSleep\n\n const start = now()\n let attempt = 0\n\n for (;;) {\n attempt += 1\n opts.onTick?.(attempt)\n\n const result = await client.poll(sessionId)\n if (result.status === \"placed\") {\n return { status: \"placed\", order_id: result.order_id }\n }\n if (result.status === \"expired\") {\n return { status: \"expired\", order_id: result.order_id }\n }\n\n // Still pending. If we've reached the deadline, stop — never loop forever (F-I3).\n if (now() - start >= deadlineMs) {\n return { status: \"timeout\", order_id: null }\n }\n\n await sleep(intervalMs)\n\n // Re-check the deadline AFTER sleeping so we never start another poll past the cap\n // (bounded overshoot: at most one interval beyond the deadline).\n if (now() - start >= deadlineMs) {\n return { status: \"timeout\", order_id: null }\n }\n }\n}\n"],"mappings":";;;;AAsCA,IAAM,sBAAsB;AAC5B,IAAM,sBAAsB,KAAK,KAAK;AAEtC,SAAS,UAAU,IAA2B;AAC5C,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;AAQA,eAAsB,gBACpB,QACA,WACA,OAAoB,CAAC,GACC;AACtB,QAAM,aAAa,KAAK,cAAc;AACtC,QAAM,aAAa,KAAK,cAAc;AACtC,QAAM,MAAM,KAAK,OAAO,KAAK;AAC7B,QAAM,QAAQ,KAAK,SAAS;AAE5B,QAAM,QAAQ,IAAI;AAClB,MAAI,UAAU;AAEd,aAAS;AACP,eAAW;AACX,SAAK,SAAS,OAAO;AAErB,UAAM,SAAS,MAAM,OAAO,KAAK,SAAS;AAC1C,QAAI,OAAO,WAAW,UAAU;AAC9B,aAAO,EAAE,QAAQ,UAAU,UAAU,OAAO,SAAS;AAAA,IACvD;AACA,QAAI,OAAO,WAAW,WAAW;AAC/B,aAAO,EAAE,QAAQ,WAAW,UAAU,OAAO,SAAS;AAAA,IACxD;AAGA,QAAI,IAAI,IAAI,SAAS,YAAY;AAC/B,aAAO,EAAE,QAAQ,WAAW,UAAU,KAAK;AAAA,IAC7C;AAEA,UAAM,MAAM,UAAU;AAItB,QAAI,IAAI,IAAI,SAAS,YAAY;AAC/B,aAAO,EAAE,QAAQ,WAAW,UAAU,KAAK;AAAA,IAC7C;AAAA,EACF;AACF;","names":[]}