@wspc/cli 0.0.14 → 0.0.16

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/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { Command as Command62 } from "commander";
4
+ import { Command as Command63 } from "commander";
5
5
 
6
6
  // src/generated/cli/invite/accept.ts
7
7
  import { Command } from "commander";
@@ -970,6 +970,11 @@ var emailDelete = (options) => (options.client ?? client).post({
970
970
  ...options.headers
971
971
  }
972
972
  });
973
+ var emailDomainDelete = (options) => (options.client ?? client).delete({
974
+ security: [{ scheme: "bearer", type: "http" }],
975
+ url: "/email/domains/{domain}",
976
+ ...options
977
+ });
973
978
  var emailDomainGet = (options) => (options.client ?? client).get({
974
979
  security: [{ scheme: "bearer", type: "http" }],
975
980
  url: "/email/domains/{domain}",
@@ -1175,6 +1180,7 @@ var V1_CRED_KEYS = [
1175
1180
  function migrateEnv(raw) {
1176
1181
  const api_base = typeof raw.api_base === "string" ? raw.api_base : "";
1177
1182
  const env = { api_base, accounts: {} };
1183
+ if (typeof raw.consistency_bookmark === "string") env.consistency_bookmark = raw.consistency_bookmark;
1178
1184
  if (typeof raw.client_id === "string") env.client_id = raw.client_id;
1179
1185
  if (raw.accounts && typeof raw.accounts === "object") {
1180
1186
  env.accounts = raw.accounts;
@@ -1242,6 +1248,49 @@ var ConfigStore = class {
1242
1248
  }
1243
1249
  await fs.writeFile(this.configFile, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
1244
1250
  }
1251
+ /**
1252
+ * Read-modify-write the config under a cross-process file lock. The mutator
1253
+ * runs against a FRESH read taken inside the lock and edits it in place, so
1254
+ * two writers (e.g. token refresh and the consistency-bookmark writeback)
1255
+ * can't clobber each other from stale snapshots — the bug that revoked whole
1256
+ * refresh-token families when several CLI sessions ran concurrently.
1257
+ */
1258
+ async update(mutate) {
1259
+ await this.withLock(async () => {
1260
+ const config = await this.read();
1261
+ mutate(config);
1262
+ await this.write(config);
1263
+ });
1264
+ }
1265
+ async withLock(fn) {
1266
+ await fs.mkdir(this.configDir, { recursive: true, mode: 448 });
1267
+ const lockFile = this.configFile + ".lock";
1268
+ const STALE_MS = 1e4;
1269
+ const RETRY_MS = 25;
1270
+ const MAX_WAIT_MS = 5e3;
1271
+ let waited = 0;
1272
+ for (; ; ) {
1273
+ try {
1274
+ const fh = await fs.open(lockFile, "wx");
1275
+ await fh.close();
1276
+ break;
1277
+ } catch (e) {
1278
+ if (e.code !== "EEXIST") throw e;
1279
+ const age = await fs.stat(lockFile).then((s) => Date.now() - s.mtimeMs).catch(() => Infinity);
1280
+ if (age > STALE_MS || waited >= MAX_WAIT_MS) {
1281
+ await fs.rm(lockFile, { force: true });
1282
+ continue;
1283
+ }
1284
+ await new Promise((r) => setTimeout(r, RETRY_MS));
1285
+ waited += RETRY_MS;
1286
+ }
1287
+ }
1288
+ try {
1289
+ return await fn();
1290
+ } finally {
1291
+ await fs.rm(lockFile, { force: true });
1292
+ }
1293
+ }
1245
1294
  async currentEnv() {
1246
1295
  const c = await this.read();
1247
1296
  const name = c.current_env;
@@ -1252,10 +1301,79 @@ var ConfigStore = class {
1252
1301
  }
1253
1302
  };
1254
1303
 
1304
+ // src/handwritten/auth/consistency-fetch.ts
1305
+ var HEADER = "x-consistency-bookmark";
1306
+ var INVALID_BOOKMARK = "INVALID_CONSISTENCY_BOOKMARK";
1307
+ function normalizeBasePath(pathname) {
1308
+ const trimmed = pathname.replace(/\/+$/, "");
1309
+ return trimmed === "" ? "/" : trimmed;
1310
+ }
1311
+ function isUnderApiBase(url, apiBase) {
1312
+ const base = new URL(apiBase);
1313
+ const basePath = normalizeBasePath(base.pathname);
1314
+ return url.origin === base.origin && (basePath === "/" || url.pathname === basePath || url.pathname.startsWith(`${basePath}/`));
1315
+ }
1316
+ function isJsonContentType(contentType) {
1317
+ const mediaType = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
1318
+ return mediaType === "application/json" || mediaType.endsWith("+json");
1319
+ }
1320
+ async function responseHasInvalidBookmark(response) {
1321
+ const contentType = response.headers.get("content-type") ?? "";
1322
+ if (!isJsonContentType(contentType)) return false;
1323
+ try {
1324
+ const body = await response.clone().json();
1325
+ return body.error?.code === INVALID_BOOKMARK;
1326
+ } catch {
1327
+ return false;
1328
+ }
1329
+ }
1330
+ function createConsistencyFetch(opts) {
1331
+ const fetchImpl = opts.fetchImpl ?? fetch;
1332
+ return async (input, init) => {
1333
+ const request = new Request(input, init);
1334
+ const url = new URL(request.url);
1335
+ const applies = isUnderApiBase(url, opts.apiBase);
1336
+ let outgoing = request;
1337
+ let injectedStoredBookmark = false;
1338
+ if (applies && !outgoing.headers.has(HEADER)) {
1339
+ const config = await opts.store.read();
1340
+ const bookmark = config.envs[opts.envName]?.consistency_bookmark;
1341
+ if (bookmark) {
1342
+ const headers = new Headers(outgoing.headers);
1343
+ headers.set(HEADER, bookmark);
1344
+ outgoing = new Request(outgoing, { headers });
1345
+ injectedStoredBookmark = true;
1346
+ }
1347
+ }
1348
+ if (!applies && outgoing.headers.has(HEADER)) {
1349
+ const headers = new Headers(outgoing.headers);
1350
+ headers.delete(HEADER);
1351
+ outgoing = new Request(outgoing, { headers });
1352
+ }
1353
+ const response = await fetchImpl(outgoing);
1354
+ if (!applies) return response;
1355
+ const nextBookmark = response.headers.get(HEADER);
1356
+ const shouldCheckInvalidBookmark = injectedStoredBookmark && !nextBookmark;
1357
+ const invalidBookmark = shouldCheckInvalidBookmark ? await responseHasInvalidBookmark(response) : false;
1358
+ const shouldClearBookmark = invalidBookmark;
1359
+ if (!nextBookmark && !shouldClearBookmark) return response;
1360
+ await opts.store.update((config) => {
1361
+ const env = config.envs[opts.envName];
1362
+ if (!env) return;
1363
+ if (nextBookmark) {
1364
+ env.consistency_bookmark = nextBookmark;
1365
+ } else if (shouldClearBookmark) {
1366
+ delete env.consistency_bookmark;
1367
+ }
1368
+ });
1369
+ return response;
1370
+ };
1371
+ }
1372
+
1255
1373
  // src/version.ts
1256
- var VERSION = "0.0.14";
1257
- var SPEC_SHA = "7842b7a9";
1258
- var SPEC_FETCHED_AT = "2026-06-10T05:27:40.161Z";
1374
+ var VERSION = "0.0.16";
1375
+ var SPEC_SHA = "b505a817";
1376
+ var SPEC_FETCHED_AT = "2026-06-16T07:42:43.911Z";
1259
1377
  var API_BASE = "https://api.wspc.ai";
1260
1378
 
1261
1379
  // src/index.ts
@@ -1268,17 +1386,30 @@ var WspcAuthExpiredError = class extends Error {
1268
1386
  };
1269
1387
 
1270
1388
  // src/handwritten/auth/sdk-auth.ts
1389
+ var USER_AGENT = `@wspc/cli/${VERSION}`;
1390
+ async function expiredMessage(res) {
1391
+ try {
1392
+ const body = await res.clone().json();
1393
+ if (!body.error) return void 0;
1394
+ const detail = body.error_description ? `: ${body.error_description}` : "";
1395
+ return `wspc token refresh failed (${body.error}${detail}); re-authenticate via \`wspc login\``;
1396
+ } catch {
1397
+ return void 0;
1398
+ }
1399
+ }
1271
1400
  function createAuthInterceptor(mode) {
1272
1401
  if ("apiKey" in mode) {
1273
1402
  const apiKey = mode.apiKey;
1403
+ const fetchImpl2 = mode.fetchImpl ?? fetch;
1274
1404
  return {
1275
1405
  async onRequest(req) {
1276
1406
  req.headers.set("authorization", `Bearer ${apiKey}`);
1407
+ req.headers.set("user-agent", USER_AGENT);
1277
1408
  return req;
1278
1409
  },
1279
1410
  async execute(req) {
1280
1411
  const out = await this.onRequest(req.clone());
1281
- return fetch(out);
1412
+ return fetchImpl2(out);
1282
1413
  }
1283
1414
  };
1284
1415
  }
@@ -1289,6 +1420,7 @@ function createAuthInterceptor(mode) {
1289
1420
  return {
1290
1421
  async onRequest(req) {
1291
1422
  req.headers.set("authorization", `Bearer ${accessToken}`);
1423
+ req.headers.set("user-agent", USER_AGENT);
1292
1424
  return req;
1293
1425
  },
1294
1426
  async execute(req) {
@@ -1296,7 +1428,10 @@ function createAuthInterceptor(mode) {
1296
1428
  if (first.status !== 401) return first;
1297
1429
  const refreshRes = await fetchImpl(`${mode.baseUrl}/auth/oauth/token`, {
1298
1430
  method: "POST",
1299
- headers: { "content-type": "application/x-www-form-urlencoded" },
1431
+ headers: {
1432
+ "content-type": "application/x-www-form-urlencoded",
1433
+ "user-agent": USER_AGENT
1434
+ },
1300
1435
  body: new URLSearchParams({
1301
1436
  grant_type: "refresh_token",
1302
1437
  refresh_token: refreshToken,
@@ -1304,7 +1439,7 @@ function createAuthInterceptor(mode) {
1304
1439
  })
1305
1440
  });
1306
1441
  if (!refreshRes.ok) {
1307
- throw new WspcAuthExpiredError();
1442
+ throw new WspcAuthExpiredError(await expiredMessage(refreshRes));
1308
1443
  }
1309
1444
  const tokens = await refreshRes.json();
1310
1445
  accessToken = tokens.access_token;
@@ -1357,10 +1492,10 @@ function resolveAccount(config, opts = {}) {
1357
1492
  }
1358
1493
 
1359
1494
  // src/handwritten/auth/load-sdk-client.ts
1360
- function buildInterceptor(store, resolved) {
1495
+ function buildInterceptor(store, resolved, fetchImpl) {
1361
1496
  const { envName, apiBase, clientId, email, creds } = resolved;
1362
1497
  if (creds.api_key) {
1363
- return createAuthInterceptor({ apiKey: creds.api_key });
1498
+ return createAuthInterceptor({ apiKey: creds.api_key, fetchImpl });
1364
1499
  }
1365
1500
  if (!clientId) {
1366
1501
  throw new Error(
@@ -1372,14 +1507,15 @@ function buildInterceptor(store, resolved) {
1372
1507
  refreshToken: creds.refresh_token,
1373
1508
  baseUrl: apiBase,
1374
1509
  clientId,
1510
+ fetchImpl,
1375
1511
  onTokenRefresh: async ({ accessToken, refreshToken, expiresAt }) => {
1376
- const cfg = await store.read();
1377
- const a = cfg.envs[envName]?.accounts?.[email];
1378
- if (!a) return;
1379
- a.access_token = accessToken;
1380
- a.refresh_token = refreshToken;
1381
- a.access_token_expires_at = expiresAt;
1382
- await store.write(cfg);
1512
+ await store.update((cfg) => {
1513
+ const a = cfg.envs[envName]?.accounts?.[email];
1514
+ if (!a) return;
1515
+ a.access_token = accessToken;
1516
+ a.refresh_token = refreshToken;
1517
+ a.access_token_expires_at = expiresAt;
1518
+ });
1383
1519
  }
1384
1520
  });
1385
1521
  }
@@ -1387,7 +1523,13 @@ async function loadSdkClient(opts = {}) {
1387
1523
  const store = opts.store ?? new ConfigStore();
1388
1524
  const config = await store.read();
1389
1525
  const resolved = resolveAccount(config, { accountOverride: process.env.WSPC_ACCOUNT });
1390
- const interceptor = buildInterceptor(store, resolved);
1526
+ const consistencyFetch = createConsistencyFetch({
1527
+ store,
1528
+ envName: resolved.envName,
1529
+ apiBase: resolved.apiBase,
1530
+ fetchImpl: opts.fetchImpl
1531
+ });
1532
+ const interceptor = buildInterceptor(store, resolved, consistencyFetch);
1391
1533
  const rawClient = createClient(
1392
1534
  createConfig({
1393
1535
  baseUrl: resolved.apiBase,
@@ -1400,7 +1542,13 @@ async function loadAuthedFetch(opts = {}) {
1400
1542
  const store = opts.store ?? new ConfigStore();
1401
1543
  const config = await store.read();
1402
1544
  const resolved = resolveAccount(config, { accountOverride: process.env.WSPC_ACCOUNT });
1403
- const interceptor = buildInterceptor(store, resolved);
1545
+ const consistencyFetch = createConsistencyFetch({
1546
+ store,
1547
+ envName: resolved.envName,
1548
+ apiBase: resolved.apiBase,
1549
+ fetchImpl: opts.fetchImpl
1550
+ });
1551
+ const interceptor = buildInterceptor(store, resolved, consistencyFetch);
1404
1552
  const authedFetch = (input, init) => interceptor.execute(new Request(input, init));
1405
1553
  return { fetch: authedFetch, baseUrl: resolved.apiBase };
1406
1554
  }
@@ -2416,7 +2564,7 @@ var eventGetCommand = new Command19("show").description("Get a calendar event by
2416
2564
 
2417
2565
  // src/generated/cli/event/set.ts
2418
2566
  import { Command as Command20 } from "commander";
2419
- var eventUpdateCommand = new Command20("set").description("Update a calendar event").argument("<id>", "id").option("--expected-version <value>", "Optional optimistic lock. Omit to let the server use the current version; pass only to fail the call if someone else has mutated the event since you last read. On mismatch the server returns 409 `VERSION_CONFLICT` with `extra.expected_version` and `extra.actual_version`.").option("--title <value>", "New event title. Omit to leave unchanged.").option("--description <value>", "New description. Markdown formatted (CommonMark + GFM tables, strikethrough, task lists). Pass an empty string to clear; omit to leave unchanged.").option("--start <value>", "Accepts ISO 8601 datetime with offset (e.g. `2026-06-01T12:30:00+08:00`) for timed events, or ISO date-only (e.g. `2026-06-01`) for all-day. The `wspc` CLI additionally accepts natural-language phrases (`tomorrow 12:30pm`, `next Monday 9am`) and resolves them to ISO before sending; the server itself only accepts ISO. All-day uses RFC 5545 exclusive end: a one-day event on 6/1 is `start=2026-06-01, end=2026-06-02`; both endpoints must be the same type.").option("--end <value>", "Accepts ISO 8601 datetime with offset (e.g. `2026-06-01T12:30:00+08:00`) for timed events, or ISO date-only (e.g. `2026-06-01`) for all-day. The `wspc` CLI additionally accepts natural-language phrases (`tomorrow 12:30pm`, `next Monday 9am`) and resolves them to ISO before sending; the server itself only accepts ISO. All-day uses RFC 5545 exclusive end: a one-day event on 6/1 is `start=2026-06-01, end=2026-06-02`; both endpoints must be the same type.").option("-l, --location <value>", "New location. Pass an empty string to clear; omit to leave unchanged.").option("-u, --url <value>", "New meeting link. Pass an empty string to clear; omit to leave unchanged.").option("--status <value>", "Lifecycle status. `confirmed`: the event will happen (default). `tentative`: organizer has not finalized; still visible in lists. `cancelled`: the event was called off but the record is kept so attendees can be notified and history audited; distinct from soft-delete (DELETE `/calendar/events/{id}`) which hides the event from default list responses.").option("--attendee <value>", "If provided, REPLACES the attendee list (after case-insensitive email dedupe, up to 50). Added attendees receive a fresh invitation, kept attendees receive an update email, removed attendees receive a cancellation.", (val, memo) => {
2567
+ var eventUpdateCommand = new Command20("set").description("Update a calendar event").argument("<id>", "id").option("--expected-version <value>", "Optional optimistic lock. Omit to let the server use the current version; pass only to fail the call if someone else has mutated the event since you last read. On mismatch the server returns 409 `VERSION_CONFLICT` and includes the current and sent versions in the message.").option("--title <value>", "New event title. Omit to leave unchanged.").option("--description <value>", "New description. Markdown formatted (CommonMark + GFM tables, strikethrough, task lists). Pass an empty string to clear; omit to leave unchanged.").option("--start <value>", "Accepts ISO 8601 datetime with offset (e.g. `2026-06-01T12:30:00+08:00`) for timed events, or ISO date-only (e.g. `2026-06-01`) for all-day. The `wspc` CLI additionally accepts natural-language phrases (`tomorrow 12:30pm`, `next Monday 9am`) and resolves them to ISO before sending; the server itself only accepts ISO. All-day uses RFC 5545 exclusive end: a one-day event on 6/1 is `start=2026-06-01, end=2026-06-02`; both endpoints must be the same type.").option("--end <value>", "Accepts ISO 8601 datetime with offset (e.g. `2026-06-01T12:30:00+08:00`) for timed events, or ISO date-only (e.g. `2026-06-01`) for all-day. The `wspc` CLI additionally accepts natural-language phrases (`tomorrow 12:30pm`, `next Monday 9am`) and resolves them to ISO before sending; the server itself only accepts ISO. All-day uses RFC 5545 exclusive end: a one-day event on 6/1 is `start=2026-06-01, end=2026-06-02`; both endpoints must be the same type.").option("-l, --location <value>", "New location. Pass an empty string to clear; omit to leave unchanged.").option("-u, --url <value>", "New meeting link. Pass an empty string to clear; omit to leave unchanged.").option("--status <value>", "Lifecycle status. `confirmed`: the event will happen (default). `tentative`: organizer has not finalized; still visible in lists. `cancelled`: the event was called off but the record is kept so attendees can be notified and history audited; distinct from soft-delete (DELETE `/calendar/events/{id}`) which hides the event from default list responses.").option("--attendee <value>", "If provided, REPLACES the attendee list (after case-insensitive email dedupe, up to 50). Added attendees receive a fresh invitation, kept attendees receive an update email, removed attendees receive a cancellation.", (val, memo) => {
2420
2568
  memo.push(val);
2421
2569
  return memo;
2422
2570
  }, []).option("--all-day", "all_day").option("--tz <zone>", "IANA timezone for relative time parsing").action(async (id, opts) => {
@@ -2614,9 +2762,30 @@ var emailDeleteCommand = new Command27("rm").description("Soft-delete inbound em
2614
2762
  render({ kind: "email_delete", display: { "shape": "object", "format": {} } }, result.data);
2615
2763
  });
2616
2764
 
2617
- // src/generated/cli/domain/show.ts
2765
+ // src/generated/cli/domain/rm.ts
2618
2766
  import { Command as Command28 } from "commander";
2619
- var emailDomainGetCommand = new Command28("show").description("Get one cached custom domain").argument("<domain>", "domain").action(async (domain, opts) => {
2767
+ var emailDomainDeleteCommand = new Command28("rm").description("Delete a custom email domain").argument("<domain>", "domain").action(async (domain, opts) => {
2768
+ const client2 = await loadSdkClient();
2769
+ const result = await emailDomainDelete({
2770
+ client: client2._rawClient,
2771
+ path: {
2772
+ domain
2773
+ }
2774
+ });
2775
+ if (result.error || !result.response?.ok) {
2776
+ process.stderr.write(
2777
+ `HTTP ${result.response?.status ?? "?"}: ${JSON.stringify(result.error ?? "unknown error", null, 2)}
2778
+ `
2779
+ );
2780
+ process.exitCode = 1;
2781
+ return;
2782
+ }
2783
+ render({ kind: "email_domain_delete", display: void 0 }, result.data);
2784
+ });
2785
+
2786
+ // src/generated/cli/domain/show.ts
2787
+ import { Command as Command29 } from "commander";
2788
+ var emailDomainGetCommand = new Command29("show").description("Get one cached custom domain").argument("<domain>", "domain").action(async (domain, opts) => {
2620
2789
  const client2 = await loadSdkClient();
2621
2790
  const result = await emailDomainGet({
2622
2791
  client: client2._rawClient,
@@ -2636,8 +2805,8 @@ var emailDomainGetCommand = new Command28("show").description("Get one cached cu
2636
2805
  });
2637
2806
 
2638
2807
  // src/generated/cli/email/show.ts
2639
- import { Command as Command29 } from "commander";
2640
- var emailGetCommand = new Command29("show").description("Get an inbound email by id").argument("<id>", "id").option("--include-html <value>", "When `true`, fetch the HTML body from R2 and include it as `html_body` in the response. Costs an extra R2 read; omit if you only need text.").option("--include-deleted <value>", "When `true`, allow fetching a soft-deleted email. Defaults to `false` (returns 404 for soft-deleted rows).").action(async (id, opts) => {
2808
+ import { Command as Command30 } from "commander";
2809
+ var emailGetCommand = new Command30("show").description("Get an inbound email by id").argument("<id>", "id").option("--include-html <value>", "When `true`, fetch the HTML body from R2 and include it as `html_body` in the response. Costs an extra R2 read; omit if you only need text.").option("--include-deleted <value>", "When `true`, allow fetching a soft-deleted email. Defaults to `false` (returns 404 for soft-deleted rows).").action(async (id, opts) => {
2641
2810
  const client2 = await loadSdkClient();
2642
2811
  const result = await emailGet({
2643
2812
  client: client2._rawClient,
@@ -2661,8 +2830,8 @@ var emailGetCommand = new Command29("show").description("Get an inbound email by
2661
2830
  });
2662
2831
 
2663
2832
  // src/generated/cli/email/ls.ts
2664
- import { Command as Command30 } from "commander";
2665
- var emailListCommand = new Command30("ls").description("List inbound emails").option("--limit <value>", "Max items to return (clamped to 1-100). Defaults to 20 server-side.").option("--alias-email <value>", "If set, only return emails received on this full alias email address.").option("--unread-only <value>", "When `true`, only return emails with `is_read=false`.").option("--since <value>", "Unix epoch milliseconds \u2014 only return emails with `received_at >= since`. Useful for incremental sync.").option("--cursor <value>", "Opaque pagination cursor returned in `next_cursor` of a previous response.").option("--include-deleted <value>", "When `true`, also return soft-deleted emails. Defaults to `false`.").action(async (opts) => {
2833
+ import { Command as Command31 } from "commander";
2834
+ var emailListCommand = new Command31("ls").description("List inbound emails").option("--limit <value>", "Max items to return (clamped to 1-100). Defaults to 20 server-side.").option("--alias-email <value>", "If set, only return emails received on this full alias email address.").option("--unread-only <value>", "When `true`, only return emails with `is_read=false`.").option("--since <value>", "Unix epoch milliseconds \u2014 only return emails with `received_at >= since`. Useful for incremental sync.").option("--cursor <value>", "Opaque pagination cursor returned in `next_cursor` of a previous response.").option("--include-deleted <value>", "When `true`, also return soft-deleted emails. Defaults to `false`.").action(async (opts) => {
2666
2835
  const client2 = await loadSdkClient();
2667
2836
  const result = await emailList({
2668
2837
  client: client2._rawClient,
@@ -2687,8 +2856,8 @@ var emailListCommand = new Command30("ls").description("List inbound emails").op
2687
2856
  });
2688
2857
 
2689
2858
  // src/generated/cli/email/read.ts
2690
- import { Command as Command31 } from "commander";
2691
- var emailMarkReadCommand = new Command31("read").description("Mark inbound emails as read").argument("<id...>", "id").action(async (id, opts) => {
2859
+ import { Command as Command32 } from "commander";
2860
+ var emailMarkReadCommand = new Command32("read").description("Mark inbound emails as read").argument("<id...>", "id").action(async (id, opts) => {
2692
2861
  const idRaw = id;
2693
2862
  const ids = idRaw.length > 0 ? idRaw : void 0;
2694
2863
  const client2 = await loadSdkClient();
@@ -2710,8 +2879,8 @@ var emailMarkReadCommand = new Command31("read").description("Mark inbound email
2710
2879
  });
2711
2880
 
2712
2881
  // src/generated/cli/email/unread.ts
2713
- import { Command as Command32 } from "commander";
2714
- var emailMarkUnreadCommand = new Command32("unread").description("Mark inbound emails as unread").argument("<id...>", "id").action(async (id, opts) => {
2882
+ import { Command as Command33 } from "commander";
2883
+ var emailMarkUnreadCommand = new Command33("unread").description("Mark inbound emails as unread").argument("<id...>", "id").action(async (id, opts) => {
2715
2884
  const idRaw = id;
2716
2885
  const ids = idRaw.length > 0 ? idRaw : void 0;
2717
2886
  const client2 = await loadSdkClient();
@@ -2733,8 +2902,8 @@ var emailMarkUnreadCommand = new Command32("unread").description("Mark inbound e
2733
2902
  });
2734
2903
 
2735
2904
  // src/generated/cli/domain/verify.ts
2736
- import { Command as Command33 } from "commander";
2737
- var emailDomainVerifyCommand = new Command33("verify").description("Verify a custom domain with the provider").argument("<domain>", "domain").action(async (domain, opts) => {
2905
+ import { Command as Command34 } from "commander";
2906
+ var emailDomainVerifyCommand = new Command34("verify").description("Verify a custom domain with the provider").argument("<domain>", "domain").action(async (domain, opts) => {
2738
2907
  const client2 = await loadSdkClient();
2739
2908
  const result = await emailDomainVerify({
2740
2909
  client: client2._rawClient,
@@ -2754,8 +2923,8 @@ var emailDomainVerifyCommand = new Command33("verify").description("Verify a cus
2754
2923
  });
2755
2924
 
2756
2925
  // src/generated/cli/push/config/rm.ts
2757
- import { Command as Command34 } from "commander";
2758
- var pushConfigDeleteCommand = new Command34("rm").description("Remove a push transport").argument("<transport>", "transport").action(async (transport, opts) => {
2926
+ import { Command as Command35 } from "commander";
2927
+ var pushConfigDeleteCommand = new Command35("rm").description("Remove a push transport").argument("<transport>", "transport").action(async (transport, opts) => {
2759
2928
  const client2 = await loadSdkClient();
2760
2929
  const result = await pushConfigDelete({
2761
2930
  client: client2._rawClient,
@@ -2775,8 +2944,8 @@ var pushConfigDeleteCommand = new Command34("rm").description("Remove a push tra
2775
2944
  });
2776
2945
 
2777
2946
  // src/generated/cli/push/config/set.ts
2778
- import { Command as Command35 } from "commander";
2779
- var pushConfigSetCommand = new Command35("set").description("Register or update a push transport").option("--transport <value>", "Transport discriminator. `telegram` is the only supported value today \u2014 push delivers via a Telegram bot DM. Future transports (web push, iOS/Android, generic webhook) will be added as additional discriminator values.").option("--target-bot-username <value>", "Telegram bot username (with leading `@`, 5\u201332 alphanumeric/underscore characters). This is the bot the user has already started a chat with \u2014 wspc DMs notifications to it via the Telegram Bot API.").action(async (opts) => {
2947
+ import { Command as Command36 } from "commander";
2948
+ var pushConfigSetCommand = new Command36("set").description("Register or update a push transport").option("--transport <value>", "Transport discriminator. `telegram` is the only supported value today \u2014 push delivers via a Telegram bot DM. Future transports (web push, iOS/Android, generic webhook) will be added as additional discriminator values.").option("--target-bot-username <value>", "Telegram bot username (with leading `@`, 5\u201332 alphanumeric/underscore characters). This is the bot the user has already started a chat with \u2014 wspc DMs notifications to it via the Telegram Bot API.").action(async (opts) => {
2780
2949
  const client2 = await loadSdkClient();
2781
2950
  const result = await pushConfigSet({
2782
2951
  client: client2._rawClient,
@@ -2799,8 +2968,8 @@ var pushConfigSetCommand = new Command35("set").description("Register or update
2799
2968
  });
2800
2969
 
2801
2970
  // src/generated/cli/push/config/show.ts
2802
- import { Command as Command36 } from "commander";
2803
- var pushConfigGetCommand = new Command36("show").description("List the caller's push transports").action(async (opts) => {
2971
+ import { Command as Command37 } from "commander";
2972
+ var pushConfigGetCommand = new Command37("show").description("List the caller's push transports").action(async (opts) => {
2804
2973
  const client2 = await loadSdkClient();
2805
2974
  const result = await pushConfigGet({
2806
2975
  client: client2._rawClient
@@ -2817,8 +2986,8 @@ var pushConfigGetCommand = new Command36("show").description("List the caller's
2817
2986
  });
2818
2987
 
2819
2988
  // src/generated/cli/push/test.ts
2820
- import { Command as Command37 } from "commander";
2821
- var pushTestCommand = new Command37("test").description("Send a test push notification").option("--transport <value>", "Which transport to send the test message through. Must match a transport the caller has already registered via `POST /push/config`; today only `telegram` is supported.").action(async (opts) => {
2989
+ import { Command as Command38 } from "commander";
2990
+ var pushTestCommand = new Command38("test").description("Send a test push notification").option("--transport <value>", "Which transport to send the test message through. Must match a transport the caller has already registered via `POST /push/config`; today only `telegram` is supported.").action(async (opts) => {
2822
2991
  const client2 = await loadSdkClient();
2823
2992
  const result = await pushTest({
2824
2993
  client: client2._rawClient,
@@ -2841,8 +3010,8 @@ var pushTestCommand = new Command37("test").description("Send a test push notifi
2841
3010
  });
2842
3011
 
2843
3012
  // src/generated/cli/todo/comment/add.ts
2844
- import { Command as Command38 } from "commander";
2845
- var todoCommentCreateCommand = new Command38("add").description("Add a comment to a todo").argument("<id>", "id").argument("<content>", "content").action(async (id, content, opts) => {
3013
+ import { Command as Command39 } from "commander";
3014
+ var todoCommentCreateCommand = new Command39("add").description("Add a comment to a todo").argument("<id>", "id").argument("<content>", "content").action(async (id, content, opts) => {
2846
3015
  const client2 = await loadSdkClient();
2847
3016
  const result = await todoCommentCreate({
2848
3017
  client: client2._rawClient,
@@ -2865,8 +3034,8 @@ var todoCommentCreateCommand = new Command38("add").description("Add a comment t
2865
3034
  });
2866
3035
 
2867
3036
  // src/generated/cli/todo/comment/ls.ts
2868
- import { Command as Command39 } from "commander";
2869
- var todoCommentListCommand = new Command39("ls").description("List comments on a todo").argument("<id>", "id").option("--order <value>", "order").option("--include-deleted <value>", "include_deleted").option("--limit <value>", "Max comments to return. Clamped to [1, 200]. Default 50 server-side.").option("--cursor <value>", "Opaque pagination cursor returned in `next_cursor` of a previous response.").action(async (id, opts) => {
3037
+ import { Command as Command40 } from "commander";
3038
+ var todoCommentListCommand = new Command40("ls").description("List comments on a todo").argument("<id>", "id").option("--order <value>", "order").option("--include-deleted <value>", "include_deleted").option("--limit <value>", "Max comments to return. Clamped to [1, 200]. Default 50 server-side.").option("--cursor <value>", "Opaque pagination cursor returned in `next_cursor` of a previous response.").action(async (id, opts) => {
2870
3039
  const client2 = await loadSdkClient();
2871
3040
  const result = await todoCommentList({
2872
3041
  client: client2._rawClient,
@@ -2892,8 +3061,8 @@ var todoCommentListCommand = new Command39("ls").description("List comments on a
2892
3061
  });
2893
3062
 
2894
3063
  // src/generated/cli/todo/project/add.ts
2895
- import { Command as Command40 } from "commander";
2896
- var projectCreateCommand = new Command40("add").description("Create a project").argument("<name>", "name").option("--default-todo-type-id <value>", "default_todo_type_id").action(async (name, opts) => {
3064
+ import { Command as Command41 } from "commander";
3065
+ var projectCreateCommand = new Command41("add").description("Create a project").argument("<name>", "name").option("--default-todo-type-id <value>", "default_todo_type_id").action(async (name, opts) => {
2897
3066
  const client2 = await loadSdkClient();
2898
3067
  const result = await projectCreate({
2899
3068
  client: client2._rawClient,
@@ -2914,8 +3083,8 @@ var projectCreateCommand = new Command40("add").description("Create a project").
2914
3083
  });
2915
3084
 
2916
3085
  // src/generated/cli/todo/project/ls.ts
2917
- import { Command as Command41 } from "commander";
2918
- var projectListCommand = new Command41("ls").description("List projects").option("--include-deleted <value>", "Set to `true` to include soft-deleted projects in the response.").action(async (opts) => {
3086
+ import { Command as Command42 } from "commander";
3087
+ var projectListCommand = new Command42("ls").description("List projects").option("--include-deleted <value>", "Set to `true` to include soft-deleted projects in the response.").action(async (opts) => {
2919
3088
  const client2 = await loadSdkClient();
2920
3089
  const result = await projectList({
2921
3090
  client: client2._rawClient,
@@ -2935,8 +3104,8 @@ var projectListCommand = new Command41("ls").description("List projects").option
2935
3104
  });
2936
3105
 
2937
3106
  // src/generated/cli/todo/rule/add.ts
2938
- import { Command as Command42 } from "commander";
2939
- var recurrenceRuleCreateCommand = new Command42("add").description("Create a recurring todo rule").argument("<title>", "title").option("--rrule <value>", "rrule").option("--dtstart <value>", "dtstart").option("--description <value>", "description").option("--parent-id <value>", "parent_id").option("-p, --project <value>", "Project for the recurrence rule, its template todo, and all materialized instances. Must be an active project in the caller's organization.").option("-t, --type <value>", "type_id").action(async (title, opts) => {
3107
+ import { Command as Command43 } from "commander";
3108
+ var recurrenceRuleCreateCommand = new Command43("add").description("Create a recurring todo rule").argument("<title>", "title").option("--rrule <value>", "rrule").option("--dtstart <value>", "dtstart").option("--description <value>", "description").option("--parent-id <value>", "parent_id").option("-p, --project <value>", "Project for the recurrence rule, its template todo, and all materialized instances. Must be an active project in the caller's organization.").option("-t, --type <value>", "type_id").action(async (title, opts) => {
2940
3109
  const client2 = await loadSdkClient();
2941
3110
  const result = await recurrenceRuleCreate({
2942
3111
  client: client2._rawClient,
@@ -2962,8 +3131,8 @@ var recurrenceRuleCreateCommand = new Command42("add").description("Create a rec
2962
3131
  });
2963
3132
 
2964
3133
  // src/generated/cli/todo/rule/ls.ts
2965
- import { Command as Command43 } from "commander";
2966
- var recurrenceRuleListCommand = new Command43("ls").description("List recurring todo rules").option("--project-id <value>", "Project id filter. Required. Unknown, cross-organization, or soft-deleted project ids return NOT_FOUND.").option("--user-id <value>", "user_id").action(async (opts) => {
3134
+ import { Command as Command44 } from "commander";
3135
+ var recurrenceRuleListCommand = new Command44("ls").description("List recurring todo rules").option("--project-id <value>", "Project id filter. Required. Unknown, cross-organization, or soft-deleted project ids return NOT_FOUND.").option("--user-id <value>", "user_id").action(async (opts) => {
2967
3136
  const client2 = await loadSdkClient();
2968
3137
  const result = await recurrenceRuleList({
2969
3138
  client: client2._rawClient,
@@ -2984,7 +3153,7 @@ var recurrenceRuleListCommand = new Command43("ls").description("List recurring
2984
3153
  });
2985
3154
 
2986
3155
  // src/generated/cli/todo/add.ts
2987
- import { Command as Command44 } from "commander";
3156
+ import { Command as Command45 } from "commander";
2988
3157
 
2989
3158
  // src/handwritten/utils/parse-json-field.ts
2990
3159
  function parseJsonField(raw, flag) {
@@ -2999,7 +3168,7 @@ function parseJsonField(raw, flag) {
2999
3168
  }
3000
3169
 
3001
3170
  // src/generated/cli/todo/add.ts
3002
- var todoCreateCommand = new Command44("add").description("Create a todo").argument("<title>", "title").option("-p, --project <value>", "Project id to assign this todo to. It must be an active project in the caller's organization.").option("--description <value>", "Free-form details about the todo. Fully supports GFM Markdown (tables, strikethrough, task lists). Stored verbatim; client applications are responsible for rendering. Optional. Passing `null` is strictly rejected.").option("--parent-id <value>", "Parent todo ID (`tod_<ULID>`) to attach this todo as a child under another todo. Omit or pass `null` to create a root-level todo. Nesting is limited to one level; attempting to set a child todo as a parent will trigger `PARENT_IS_CHILD`. To make a subtask appear on every occurrence of a recurring rule, set this to that rule's template todo id (the template id returned when the rule is created); the server re-materializes future occurrences so each carries the subtask.").option("--status <value>", "Initial status of the todo. Omit to default to `open`. Allowed values: `open`, `in_progress`, `done`, `cancelled`.").option("--due-at <value>", 'Optional calendar due date in ISO date-only format (`YYYY-MM-DD`). Stored without timezone offsets to represent the same local calendar day globally. Pass `""` or omit the field to skip setting a due date. Passing `null` is strictly rejected.').option("--type-id <value>", "Type id this todo belongs to. Omit to use the project's default type. When project_id is also supplied, the type must belong to the same project. New server-generated type ids use typ_<ULID>; legacy ids remain accepted.").option("--custom-fields <value>", "Custom field values keyed by the field's immutable `key` (not the human `label`). Each value must match the declared field type: string fields require string values, and string_array fields require string arrays. Providing a key that is not declared on the resolved todo type is strictly rejected with `UNDECLARED_FIELD`. Missing required fields that lack a default value are rejected with `FIELD_REQUIRED`. Defaults declared on the type are auto-applied at create time.").action(async (title, opts) => {
3171
+ var todoCreateCommand = new Command45("add").description("Create a todo").argument("<title>", "title").option("-p, --project <value>", "Project id to assign this todo to. It must be an active project in the caller's organization.").option("--description <value>", "Free-form details about the todo. Fully supports GFM Markdown (tables, strikethrough, task lists). Stored verbatim; client applications are responsible for rendering. Optional. Passing `null` is strictly rejected.").option("--parent-id <value>", "Parent todo ID (`tod_<ULID>`) to attach this todo as a child under another todo. Omit or pass `null` to create a root-level todo. Nesting is limited to one level; attempting to set a child todo as a parent will trigger `PARENT_IS_CHILD`. To make a subtask appear on every occurrence of a recurring rule, set this to that rule's template todo id (the template id returned when the rule is created); the server re-materializes future occurrences so each carries the subtask.").option("--status <value>", "Initial status of the todo. Omit to default to `open`. Allowed values: `open`, `in_progress`, `done`, `cancelled`.").option("--due-at <value>", 'Optional calendar due date in ISO date-only format (`YYYY-MM-DD`). Stored without timezone offsets to represent the same local calendar day globally. Pass `""` or omit the field to skip setting a due date. Passing `null` is strictly rejected.').option("--type-id <value>", "Type id this todo belongs to. Omit to use the project's default type. When project_id is also supplied, the type must belong to the same project. New server-generated type ids use typ_<ULID>; legacy ids remain accepted.").option("--custom-fields <value>", "Custom field values keyed by the field's immutable `key` (not the human `label`). Each value must match the declared field type: string fields require string values, and string_array fields require string arrays. Providing a key that is not declared on the resolved todo type is strictly rejected with `UNDECLARED_FIELD`. Missing required fields that lack a default value are rejected with `FIELD_REQUIRED`. Defaults declared on the type are auto-applied at create time.").action(async (title, opts) => {
3003
3172
  const client2 = await loadSdkClient();
3004
3173
  const result = await todoCreate({
3005
3174
  client: client2._rawClient,
@@ -3026,8 +3195,8 @@ var todoCreateCommand = new Command44("add").description("Create a todo").argume
3026
3195
  });
3027
3196
 
3028
3197
  // src/generated/cli/todo/ls.ts
3029
- import { Command as Command45 } from "commander";
3030
- var todoListCommand = new Command45("ls").description("List todos with filters").option("-p, --project <value>", "Filter by project. Required. Unknown, cross-organization, or soft-deleted project ids return NOT_FOUND.").option("--user-id <value>", "user_id").option("--parent-id <value>", "parent_id").option("-s, --status <value>", "status").option("--include-deleted <value>", "include_deleted").option("--include-templates <value>", "include_templates").option("--due-after <value>", "due_after").option("--due-before <value>", "due_before").option("--type-id <value>", "type_id").option("--sort-by <value>", "sort_by").option("--order <value>", "order").option("--include-orphan-fields <value>", "include_orphan_fields").option("--limit <value>", "Max todos to return. Clamped to [1, 200]. Default 50 server-side.").option("--cursor <value>", "Opaque pagination cursor returned in `next_cursor` of a previous response.").action(async (opts) => {
3198
+ import { Command as Command46 } from "commander";
3199
+ var todoListCommand = new Command46("ls").description("List todos with filters").option("-p, --project <value>", "Filter by project. Required. Unknown, cross-organization, or soft-deleted project ids return NOT_FOUND.").option("--user-id <value>", "user_id").option("--parent-id <value>", "parent_id").option("-s, --status <value>", "status").option("--include-deleted <value>", "include_deleted").option("--include-templates <value>", "include_templates").option("--due-after <value>", "due_after").option("--due-before <value>", "due_before").option("--type-id <value>", "type_id").option("--sort-by <value>", "sort_by").option("--order <value>", "order").option("--include-orphan-fields <value>", "include_orphan_fields").option("--limit <value>", "Max todos to return. Clamped to [1, 200]. Default 50 server-side.").option("--cursor <value>", "Opaque pagination cursor returned in `next_cursor` of a previous response.").action(async (opts) => {
3031
3200
  const client2 = await loadSdkClient();
3032
3201
  const result = await todoList({
3033
3202
  client: client2._rawClient,
@@ -3060,8 +3229,8 @@ var todoListCommand = new Command45("ls").description("List todos with filters")
3060
3229
  });
3061
3230
 
3062
3231
  // src/generated/cli/todo/type/ls.ts
3063
- import { Command as Command46 } from "commander";
3064
- var todoTypeListCommand = new Command46("ls").description("List todo types").option("--project-id <value>", "Project id filter. Required. Unknown, cross-organization, or soft-deleted project ids return NOT_FOUND.").option("--user-id <value>", "user_id").option("--include-deleted <value>", "include_deleted").action(async (opts) => {
3232
+ import { Command as Command47 } from "commander";
3233
+ var todoTypeListCommand = new Command47("ls").description("List todo types").option("--project-id <value>", "Project id filter. Required. Unknown, cross-organization, or soft-deleted project ids return NOT_FOUND.").option("--user-id <value>", "user_id").option("--include-deleted <value>", "include_deleted").action(async (opts) => {
3065
3234
  const client2 = await loadSdkClient();
3066
3235
  const result = await todoTypeList({
3067
3236
  client: client2._rawClient,
@@ -3083,8 +3252,8 @@ var todoTypeListCommand = new Command46("ls").description("List todo types").opt
3083
3252
  });
3084
3253
 
3085
3254
  // src/generated/cli/todo/comment/rm.ts
3086
- import { Command as Command47 } from "commander";
3087
- var todoCommentDeleteCommand = new Command47("rm").description("Soft-delete a comment").argument("<id>", "id").action(async (id, opts) => {
3255
+ import { Command as Command48 } from "commander";
3256
+ var todoCommentDeleteCommand = new Command48("rm").description("Soft-delete a comment").argument("<id>", "id").action(async (id, opts) => {
3088
3257
  const client2 = await loadSdkClient();
3089
3258
  const result = await todoCommentDelete({
3090
3259
  client: client2._rawClient,
@@ -3104,8 +3273,8 @@ var todoCommentDeleteCommand = new Command47("rm").description("Soft-delete a co
3104
3273
  });
3105
3274
 
3106
3275
  // src/generated/cli/todo/comment/edit.ts
3107
- import { Command as Command48 } from "commander";
3108
- var todoCommentUpdateCommand = new Command48("edit").description("Edit a comment").argument("<id>", "id").argument("<content>", "content").action(async (id, content, opts) => {
3276
+ import { Command as Command49 } from "commander";
3277
+ var todoCommentUpdateCommand = new Command49("edit").description("Edit a comment").argument("<id>", "id").argument("<content>", "content").action(async (id, content, opts) => {
3109
3278
  const client2 = await loadSdkClient();
3110
3279
  const result = await todoCommentUpdate({
3111
3280
  client: client2._rawClient,
@@ -3128,8 +3297,8 @@ var todoCommentUpdateCommand = new Command48("edit").description("Edit a comment
3128
3297
  });
3129
3298
 
3130
3299
  // src/generated/cli/todo/rule/rm.ts
3131
- import { Command as Command49 } from "commander";
3132
- var recurrenceRuleDeleteCommand = new Command49("rm").description("Delete a recurring todo rule").argument("<id>", "id").option("--expected-version <value>", "expected_version").action(async (id, opts) => {
3300
+ import { Command as Command50 } from "commander";
3301
+ var recurrenceRuleDeleteCommand = new Command50("rm").description("Delete a recurring todo rule").argument("<id>", "id").option("--expected-version <value>", "expected_version").action(async (id, opts) => {
3133
3302
  const client2 = await loadSdkClient();
3134
3303
  const result = await recurrenceRuleDelete({
3135
3304
  client: client2._rawClient,
@@ -3152,8 +3321,8 @@ var recurrenceRuleDeleteCommand = new Command49("rm").description("Delete a recu
3152
3321
  });
3153
3322
 
3154
3323
  // src/generated/cli/todo/rule/show.ts
3155
- import { Command as Command50 } from "commander";
3156
- var recurrenceRuleGetCommand = new Command50("show").description("Get a recurring todo rule").argument("<id>", "id").action(async (id, opts) => {
3324
+ import { Command as Command51 } from "commander";
3325
+ var recurrenceRuleGetCommand = new Command51("show").description("Get a recurring todo rule").argument("<id>", "id").action(async (id, opts) => {
3157
3326
  const client2 = await loadSdkClient();
3158
3327
  const result = await recurrenceRuleGet({
3159
3328
  client: client2._rawClient,
@@ -3173,8 +3342,8 @@ var recurrenceRuleGetCommand = new Command50("show").description("Get a recurrin
3173
3342
  });
3174
3343
 
3175
3344
  // src/generated/cli/todo/rm.ts
3176
- import { Command as Command51 } from "commander";
3177
- var todoDeleteCommand = new Command51("rm").description("Soft-delete a todo").argument("<id>", "id").option("--expected-version <value>", "expected_version").option("--cascade <value>", "cascade").action(async (id, opts) => {
3345
+ import { Command as Command52 } from "commander";
3346
+ var todoDeleteCommand = new Command52("rm").description("Soft-delete a todo").argument("<id>", "id").option("--expected-version <value>", "expected_version").option("--cascade <value>", "cascade").action(async (id, opts) => {
3178
3347
  const client2 = await loadSdkClient();
3179
3348
  const result = await todoDelete({
3180
3349
  client: client2._rawClient,
@@ -3198,8 +3367,8 @@ var todoDeleteCommand = new Command51("rm").description("Soft-delete a todo").ar
3198
3367
  });
3199
3368
 
3200
3369
  // src/generated/cli/todo/show.ts
3201
- import { Command as Command52 } from "commander";
3202
- var todoGetCommand = new Command52("show").description("Get a todo by id").argument("<id>", "id").option("--include-deleted <value>", "include_deleted").option("--include-orphan-fields <value>", "include_orphan_fields").action(async (id, opts) => {
3370
+ import { Command as Command53 } from "commander";
3371
+ var todoGetCommand = new Command53("show").description("Get a todo by id").argument("<id>", "id").option("--include-deleted <value>", "include_deleted").option("--include-orphan-fields <value>", "include_orphan_fields").action(async (id, opts) => {
3203
3372
  const client2 = await loadSdkClient();
3204
3373
  const result = await todoGet({
3205
3374
  client: client2._rawClient,
@@ -3224,8 +3393,8 @@ var todoGetCommand = new Command52("show").description("Get a todo by id").argum
3224
3393
  });
3225
3394
 
3226
3395
  // src/generated/cli/todo/update.ts
3227
- import { Command as Command53 } from "commander";
3228
- var todoUpdateCommand = new Command53("update").description("Update a todo").argument("<id>", "id").option("--expected-version <value>", "expected_version").option("--title <value>", "New title. Omit to leave the existing title unchanged. Must be non-empty when supplied.").option("--description <value>", 'New description. Markdown formatted (CommonMark + GFM tables, strikethrough, task lists). Pass empty string `""` explicitly to clear an existing description, or omit to leave unchanged. Passing `null` is strictly rejected.').option("--parent-id <value>", "Re-parent the todo. Pass a valid parent ID to attach under another todo, pass `null` to move it back to the root level, or omit to leave unchanged. Nesting is limited to one level; attempting to set a child todo as a parent will trigger `PARENT_IS_CHILD`.").option("--status <value>", "New status of the todo. Allowed transitions: `open` \u2794 `in_progress` \u2794 `done`. `cancelled` represents a terminal state. Transitioning to `done` automatically emits a `captureTodoCompleted` analytics event. Omit to leave the existing status unchanged.").option("--due-at <value>", 'Update calendar due date in ISO date-only format (`YYYY-MM-DD`). Pass `""` explicitly to clear an existing due date, or omit to leave it unchanged. Passing `null` is strictly rejected.').option("--type-id <value>", "Re-assign this todo to a different active type. The new type must belong to the todo's same project; otherwise the request fails with TYPE_PROJECT_MISMATCH. New server-generated type ids use typ_<ULID>; legacy ids remain accepted.").option("--custom-fields <value>", "PATCH semantics: only the keys present in this map change. Pass `null` for a key (e.g. `custom_fields: { priority: null }`) to explicitly delete that custom field value. Array values are replaced wholesale with no element-level diff. Providing a key that is not declared on the effective todo type is rejected with `UNDECLARED_FIELD`.").option("--user-id <value>", "Reassign the owner (assignee) user ID of this todo. Target user must belong to the same organization.").action(async (id, opts) => {
3396
+ import { Command as Command54 } from "commander";
3397
+ var todoUpdateCommand = new Command54("update").description("Update a todo").argument("<id>", "id").option("--expected-version <value>", "expected_version").option("--title <value>", "New title. Omit to leave the existing title unchanged. Must be non-empty when supplied.").option("--description <value>", 'New description. Markdown formatted (CommonMark + GFM tables, strikethrough, task lists). Pass empty string `""` explicitly to clear an existing description, or omit to leave unchanged. Passing `null` is strictly rejected.').option("--parent-id <value>", "Re-parent the todo. Pass a valid parent ID to attach under another todo, pass `null` to move it back to the root level, or omit to leave unchanged. Nesting is limited to one level; attempting to set a child todo as a parent will trigger `PARENT_IS_CHILD`.").option("--status <value>", "New status of the todo. Allowed transitions: `open` \u2794 `in_progress` \u2794 `done`. `cancelled` represents a terminal state. Transitioning to `done` automatically emits a `captureTodoCompleted` analytics event. Omit to leave the existing status unchanged.").option("--due-at <value>", 'Update calendar due date in ISO date-only format (`YYYY-MM-DD`). Pass `""` explicitly to clear an existing due date, or omit to leave it unchanged. Passing `null` is strictly rejected.').option("--type-id <value>", "Re-assign this todo to a different active type. The new type must belong to the todo's same project; otherwise the request fails with TYPE_PROJECT_MISMATCH. New server-generated type ids use typ_<ULID>; legacy ids remain accepted.").option("--custom-fields <value>", "PATCH semantics: only the keys present in this map change. Pass `null` for a key (e.g. `custom_fields: { priority: null }`) to explicitly delete that custom field value. Array values are replaced wholesale with no element-level diff. Providing a key that is not declared on the effective todo type is rejected with `UNDECLARED_FIELD`.").option("--user-id <value>", "Reassign the owner (assignee) user ID of this todo. Target user must belong to the same organization.").action(async (id, opts) => {
3229
3398
  const client2 = await loadSdkClient();
3230
3399
  const result = await todoUpdate({
3231
3400
  client: client2._rawClient,
@@ -3291,6 +3460,7 @@ function registerGeneratedCommands(root) {
3291
3460
  const root_domain = root.command("domain").description("domain commands");
3292
3461
  root_domain.addCommand(emailDomainCreateCommand);
3293
3462
  root_domain.addCommand(emailDomainListCommand);
3463
+ root_domain.addCommand(emailDomainDeleteCommand);
3294
3464
  root_domain.addCommand(emailDomainGetCommand);
3295
3465
  root_domain.addCommand(emailDomainVerifyCommand);
3296
3466
  const root_email = root.command("email").description("email commands");
@@ -3329,12 +3499,17 @@ function registerGeneratedCommands(root) {
3329
3499
  }
3330
3500
 
3331
3501
  // src/handwritten/commands/login.ts
3332
- import { Command as Command54 } from "commander";
3502
+ import { Command as Command55 } from "commander";
3333
3503
 
3334
3504
  // src/handwritten/auth/device-flow.ts
3335
3505
  var DEFAULT_SLEEP = (ms) => new Promise((r) => setTimeout(r, ms));
3336
3506
  async function runDeviceFlow(opts) {
3337
- const fetchImpl = opts.fetchImpl ?? fetch;
3507
+ const fetchImpl = opts.store && opts.envName ? createConsistencyFetch({
3508
+ store: opts.store,
3509
+ envName: opts.envName,
3510
+ apiBase: opts.baseUrl,
3511
+ fetchImpl: opts.fetchImpl
3512
+ }) : opts.fetchImpl ?? fetch;
3338
3513
  const sleep = opts.sleepMs ?? DEFAULT_SLEEP;
3339
3514
  const codeRes = await fetchImpl(`${opts.baseUrl}/auth/oauth/device`, {
3340
3515
  method: "POST",
@@ -3390,10 +3565,19 @@ async function runDeviceFlow(opts) {
3390
3565
  var DEFAULT_CLIENT_NAME = "wspc CLI";
3391
3566
  var DEFAULT_REDIRECT_URI = "http://localhost";
3392
3567
  async function ensureClientId(opts) {
3393
- const fetchImpl = opts.fetchImpl ?? fetch;
3568
+ const fetchImpl = createConsistencyFetch({
3569
+ store: opts.store,
3570
+ envName: opts.envName,
3571
+ apiBase: opts.baseUrl,
3572
+ fetchImpl: opts.fetchImpl
3573
+ });
3394
3574
  const c = await opts.store.read();
3395
3575
  const existing = c.envs[opts.envName]?.client_id;
3396
3576
  if (existing) return existing;
3577
+ const targetEnv = c.envs[opts.envName] ??= { api_base: opts.baseUrl, accounts: {} };
3578
+ targetEnv.api_base = opts.baseUrl;
3579
+ targetEnv.accounts ??= {};
3580
+ await opts.store.write(c);
3397
3581
  const res = await fetchImpl(`${opts.baseUrl}/auth/oauth/register`, {
3398
3582
  method: "POST",
3399
3583
  headers: { "content-type": "application/json" },
@@ -3419,7 +3603,12 @@ async function ensureClientId(opts) {
3419
3603
 
3420
3604
  // src/handwritten/auth/fetch-me.ts
3421
3605
  async function fetchMe(opts) {
3422
- const f = opts.fetchImpl ?? fetch;
3606
+ const f = opts.store && opts.envName ? createConsistencyFetch({
3607
+ store: opts.store,
3608
+ envName: opts.envName,
3609
+ apiBase: opts.baseUrl,
3610
+ fetchImpl: opts.fetchImpl
3611
+ }) : opts.fetchImpl ?? fetch;
3423
3612
  const res = await f(`${opts.baseUrl}/auth/me`, {
3424
3613
  headers: { authorization: `Bearer ${opts.token}` }
3425
3614
  });
@@ -3448,7 +3637,16 @@ async function runLogin(opts) {
3448
3637
  const now = opts.now ?? Date.now;
3449
3638
  const me = opts.fetchMe ?? ((o) => fetchMe(o));
3450
3639
  if (opts.apiKey) {
3451
- const who2 = await me({ baseUrl: opts.baseUrl, token: opts.apiKey });
3640
+ const initial = await opts.store.read();
3641
+ getOrCreateEnv(initial, envName, opts.baseUrl);
3642
+ initial.current_env = envName;
3643
+ await opts.store.write(initial);
3644
+ const who2 = await me({
3645
+ baseUrl: opts.baseUrl,
3646
+ token: opts.apiKey,
3647
+ store: opts.store,
3648
+ envName
3649
+ });
3452
3650
  const c2 = await opts.store.read();
3453
3651
  const env2 = getOrCreateEnv(c2, envName, opts.baseUrl);
3454
3652
  const prev2 = env2.accounts[who2.email] ?? { email: who2.email };
@@ -3474,6 +3672,8 @@ async function runLogin(opts) {
3474
3672
  const result = await flow({
3475
3673
  baseUrl: opts.baseUrl,
3476
3674
  clientId,
3675
+ store: opts.store,
3676
+ envName,
3477
3677
  onPrompt: (p) => {
3478
3678
  const prompt = p;
3479
3679
  opts.output.writeJson({ event: "device_code_issued", ...prompt });
@@ -3486,7 +3686,12 @@ async function runLogin(opts) {
3486
3686
  `);
3487
3687
  }
3488
3688
  });
3489
- const who = await me({ baseUrl: opts.baseUrl, token: result.access_token });
3689
+ const who = await me({
3690
+ baseUrl: opts.baseUrl,
3691
+ token: result.access_token,
3692
+ store: opts.store,
3693
+ envName
3694
+ });
3490
3695
  const c = await opts.store.read();
3491
3696
  const env = getOrCreateEnv(c, envName, opts.baseUrl);
3492
3697
  const prev = env.accounts[who.email] ?? { email: who.email };
@@ -3516,7 +3721,7 @@ function resolveLoginTarget(opts, env) {
3516
3721
  function wantsJson(opts, env) {
3517
3722
  return opts.json === true || env.WSPC_OUTPUT === "json";
3518
3723
  }
3519
- var loginCommand = new Command54("login").description("Log in via OAuth device flow (default) or API key").option("--api-key <key>", "Log in with a wspc API key (escape hatch)").option("--api-base <url>", "Target API base URL (default: production)").option("--env <name>", "Config env name to store credentials under").option("--json", "Emit machine-readable events to stdout").action(async (opts) => {
3724
+ var loginCommand = new Command55("login").description("Log in via OAuth device flow (default) or API key").option("--api-key <key>", "Log in with a wspc API key (escape hatch)").option("--api-base <url>", "Target API base URL (default: production)").option("--env <name>", "Config env name to store credentials under").option("--json", "Emit machine-readable events to stdout").action(async (opts) => {
3520
3725
  const store = new ConfigStore();
3521
3726
  const { baseUrl, envName } = resolveLoginTarget(opts, process.env);
3522
3727
  const output = wantsJson(opts, process.env) ? { write: () => {
@@ -3535,7 +3740,7 @@ var loginCommand = new Command54("login").description("Log in via OAuth device f
3535
3740
  });
3536
3741
 
3537
3742
  // src/handwritten/commands/logout.ts
3538
- import { Command as Command55 } from "commander";
3743
+ import { Command as Command56 } from "commander";
3539
3744
 
3540
3745
  // src/handwritten/auth/logout.ts
3541
3746
  async function runLogout(opts) {
@@ -3563,7 +3768,7 @@ async function runLogout(opts) {
3563
3768
  }
3564
3769
 
3565
3770
  // src/handwritten/commands/logout.ts
3566
- var logoutCommand = new Command55("logout").description("Log out an account (default: the active account in the current env)").argument("[email]", "Email of the account to log out").option("--all", "Log out every account in the current env").action(async (email, opts) => {
3771
+ var logoutCommand = new Command56("logout").description("Log out an account (default: the active account in the current env)").argument("[email]", "Email of the account to log out").option("--all", "Log out every account in the current env").action(async (email, opts) => {
3567
3772
  const res = await runLogout({ store: new ConfigStore(), email, all: opts.all });
3568
3773
  if (res.removed.length === 0) {
3569
3774
  process.stdout.write("nothing to log out\n");
@@ -3576,7 +3781,7 @@ var logoutCommand = new Command55("logout").description("Log out an account (def
3576
3781
  });
3577
3782
 
3578
3783
  // src/handwritten/commands/whoami.ts
3579
- import { Command as Command56 } from "commander";
3784
+ import { Command as Command57 } from "commander";
3580
3785
  var ENV_DISPLAY = {
3581
3786
  shape: "object",
3582
3787
  fields: ["name", "api_base", "account", "actor", "agent_label"]
@@ -3608,7 +3813,7 @@ async function backfillActiveEmail(store, envName, email, userId) {
3608
3813
  await store.write(cfg);
3609
3814
  }
3610
3815
  }
3611
- var whoamiCommand = new Command56("whoami").description("Show the active env, signed-in account, and organization").action(async () => {
3816
+ var whoamiCommand = new Command57("whoami").description("Show the active env, signed-in account, and organization").action(async () => {
3612
3817
  const store = new ConfigStore();
3613
3818
  const config = await store.read();
3614
3819
  let resolved;
@@ -3661,8 +3866,8 @@ function printLoggedOut() {
3661
3866
  }
3662
3867
 
3663
3868
  // src/handwritten/commands/config.ts
3664
- import { Command as Command57 } from "commander";
3665
- var configCommand = new Command57("config").description("Manage wspc local config");
3869
+ import { Command as Command58 } from "commander";
3870
+ var configCommand = new Command58("config").description("Manage wspc local config");
3666
3871
  registerRenderer("config_show", (data) => {
3667
3872
  const d = data;
3668
3873
  if (d.envs.length === 0) {
@@ -3733,7 +3938,7 @@ configCommand.command("use <env>").description("Switch current_env").action(asyn
3733
3938
  });
3734
3939
 
3735
3940
  // src/handwritten/commands/account.ts
3736
- import { Command as Command58 } from "commander";
3941
+ import { Command as Command59 } from "commander";
3737
3942
  async function listAccounts(store) {
3738
3943
  const c = await store.read();
3739
3944
  const envName = c.current_env;
@@ -3774,7 +3979,7 @@ registerRenderer("account_ls", (data) => {
3774
3979
  ]);
3775
3980
  process.stdout.write(table(headers, body));
3776
3981
  });
3777
- var accountCommand = new Command58("account").description("Manage logged-in accounts");
3982
+ var accountCommand = new Command59("account").description("Manage logged-in accounts");
3778
3983
  accountCommand.command("ls").description("List accounts in the current env (active marked with \u2713)").action(async () => {
3779
3984
  const accounts = await listAccounts(new ConfigStore());
3780
3985
  render({ kind: "account_ls" }, { accounts });
@@ -3786,7 +3991,7 @@ accountCommand.command("switch <email>").description("Set the active account for
3786
3991
  });
3787
3992
 
3788
3993
  // src/handwritten/commands/todo-done.ts
3789
- import { Command as Command59 } from "commander";
3994
+ import { Command as Command60 } from "commander";
3790
3995
  var TODO_UPDATE_DISPLAY = {
3791
3996
  shape: "object",
3792
3997
  format: {
@@ -3804,7 +4009,7 @@ var TODO_UPDATE_DISPLAY = {
3804
4009
  deleted_at: "relative-time"
3805
4010
  }
3806
4011
  };
3807
- var todoDoneCommand = new Command59("done").description("Mark a todo done (sugar for `update <id> --status done`)").argument("<id>", "Todo id").action(async (id) => {
4012
+ var todoDoneCommand = new Command60("done").description("Mark a todo done (sugar for `update <id> --status done`)").argument("<id>", "Todo id").action(async (id) => {
3808
4013
  const client2 = await loadSdkClient();
3809
4014
  const result = await todoUpdate({
3810
4015
  client: client2._rawClient,
@@ -3823,7 +4028,7 @@ var todoDoneCommand = new Command59("done").description("Mark a todo done (sugar
3823
4028
  });
3824
4029
 
3825
4030
  // src/handwritten/commands/email/send.ts
3826
- import { Command as Command60 } from "commander";
4031
+ import { Command as Command61 } from "commander";
3827
4032
  import { readFile, stat } from "fs/promises";
3828
4033
  import { basename } from "path";
3829
4034
 
@@ -3881,7 +4086,7 @@ async function resolveAttachment(input) {
3881
4086
  `--attach ${input}: neither a readable file nor a valid <prefix>_<ulid>:<idx> reference.`
3882
4087
  );
3883
4088
  }
3884
- var sendCommand = new Command60("send").description("Send an outbound email").requiredOption("--from <alias-email>", "alias email to send from").option("--to <addr...>", "recipient address (repeatable)", []).option("--subject <text>", "subject").option("--text <body>", "plain-text body").option("--text-file <path>", "read text body from file").option("--reply <id>", "inbound email id to reply to").option("--attach <path-or-ref...>", "attachment (file path or eml_xxx:idx)", []).requiredOption("--idempotency-key <key>", "idempotency key").action(async (opts) => {
4089
+ var sendCommand = new Command61("send").description("Send an outbound email").requiredOption("--from <alias-email>", "alias email to send from").option("--to <addr...>", "recipient address (repeatable)", []).option("--subject <text>", "subject").option("--text <body>", "plain-text body").option("--text-file <path>", "read text body from file").option("--reply <id>", "inbound email id to reply to").option("--attach <path-or-ref...>", "attachment (file path or eml_xxx:idx)", []).requiredOption("--idempotency-key <key>", "idempotency key").action(async (opts) => {
3885
4090
  const isReply = Boolean(opts.reply);
3886
4091
  const to = opts.to;
3887
4092
  const attachInputs = opts.attach;
@@ -3968,7 +4173,7 @@ var sendCommand = new Command60("send").description("Send an outbound email").re
3968
4173
  });
3969
4174
 
3970
4175
  // src/handwritten/commands/email/attachment.ts
3971
- import { Command as Command61 } from "commander";
4176
+ import { Command as Command62 } from "commander";
3972
4177
  import { createWriteStream } from "fs";
3973
4178
  import { Readable } from "stream";
3974
4179
  import { pipeline } from "stream/promises";
@@ -3985,7 +4190,7 @@ function parseContentDispositionFilename(header) {
3985
4190
  }
3986
4191
 
3987
4192
  // src/handwritten/commands/email/attachment.ts
3988
- var attachmentCommand = new Command61("attachment").description("Download an inbound email attachment by index").argument("<email-id>").argument("<idx>").option("--output <path>", "output file path").option("--include-deleted", "allow downloads from soft-deleted parent emails").action(async (emailId, idxArg, opts) => {
4193
+ var attachmentCommand = new Command62("attachment").description("Download an inbound email attachment by index").argument("<email-id>").argument("<idx>").option("--output <path>", "output file path").option("--include-deleted", "allow downloads from soft-deleted parent emails").action(async (emailId, idxArg, opts) => {
3989
4194
  const idx = Number(idxArg);
3990
4195
  if (!Number.isInteger(idx) || idx < 0) {
3991
4196
  process.stderr.write(`<idx> must be a non-negative integer (got "${idxArg}")
@@ -4018,7 +4223,7 @@ var attachmentCommand = new Command61("attachment").description("Download an inb
4018
4223
 
4019
4224
  // src/cli.ts
4020
4225
  function buildProgram() {
4021
- const program = new Command62().name("wspc").description("Official CLI for wspc.ai").version(`wspc ${VERSION} (spec ${SPEC_SHA}, fetched ${SPEC_FETCHED_AT})`).option("--json", "Output raw JSON (machine-readable)").option("--account <email>", "Run as a specific account (overrides the active account)").hook("preAction", (_thisCommand, actionCommand) => {
4226
+ const program = new Command63().name("wspc").description("Official CLI for wspc.ai").version(`wspc ${VERSION} (spec ${SPEC_SHA}, fetched ${SPEC_FETCHED_AT})`).option("--json", "Output raw JSON (machine-readable)").option("--account <email>", "Run as a specific account (overrides the active account)").hook("preAction", (_thisCommand, actionCommand) => {
4022
4227
  const globals = actionCommand.optsWithGlobals();
4023
4228
  if (globals.json) process.env.WSPC_OUTPUT = "json";
4024
4229
  if (globals.account) process.env.WSPC_ACCOUNT = String(globals.account);