@robbeverhelst/do 1.303.0 → 1.304.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.
Files changed (2) hide show
  1. package/dist/do.js +1110 -140
  2. package/package.json +1 -1
package/dist/do.js CHANGED
@@ -4949,6 +4949,20 @@ var moneyRecurring = pgTable("money_recurring", {
4949
4949
  check("money_recurring_amount", sql`${t.expectedAmount} > 0`),
4950
4950
  check("money_recurring_business_share", sql`${t.businessShare} BETWEEN 0 AND 100`)
4951
4951
  ]);
4952
+ var moneySnapshotSource = pgEnum("money_snapshot_source", ["balance", "ledger", "manual"]);
4953
+ var moneySnapshots = pgTable("money_snapshots", {
4954
+ id: text("id").primaryKey(),
4955
+ tenantId: text("tenant_id").notNull().references(() => tenants.id),
4956
+ accountId: text("account_id").notNull().references(() => moneyAccounts.id, { onDelete: "cascade" }),
4957
+ month: char("month", { length: 7 }).notNull(),
4958
+ value: numeric("value", { precision: 14, scale: 2 }).notNull(),
4959
+ source: moneySnapshotSource("source").notNull().default("balance"),
4960
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
4961
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
4962
+ }, (t) => [
4963
+ unique("money_snapshots_account_month_key").on(t.tenantId, t.accountId, t.month),
4964
+ check("money_snapshots_month", sql`${t.month} ~ '^[0-9]{4}-(0[1-9]|1[0-2])$'`)
4965
+ ]);
4952
4966
 
4953
4967
  // ../../packages/schema/src/db/money-connections.ts
4954
4968
  var moneyConnectionStatus = pgEnum("money_connection_status", [
@@ -5632,6 +5646,18 @@ var foodWeekReads = pgTable("food_week_reads", {
5632
5646
  weekStart: text("week_start").notNull()
5633
5647
  }, (t) => [unique("food_week_reads_owner_week_uniq").on(t.tenantId, t.ownerUserId, t.weekStart)]);
5634
5648
 
5649
+ // ../../packages/schema/src/db/webhooks.ts
5650
+ var webhookSubscriptions = pgTable("webhook_subscriptions", {
5651
+ id: text("id").primaryKey(),
5652
+ tenantId: text("tenant_id").notNull().references(() => tenants.id),
5653
+ url: text("url").notNull(),
5654
+ events: text("events").array().notNull().default([]),
5655
+ secret: text("secret").notNull(),
5656
+ active: boolean("active").notNull().default(true),
5657
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
5658
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
5659
+ }, (t) => [index("webhook_subscriptions_tenant_idx").on(t.tenantId)]);
5660
+
5635
5661
  // ../../packages/schema/src/db/collaboration.ts
5636
5662
  var capability = pgEnum("collab_capability", ["view", "comment", "edit", "admin"]);
5637
5663
  var grantSubjectType = pgEnum("grant_subject_type", ["member", "role"]);
@@ -20509,6 +20535,21 @@ var collectionCoverBackfillResultSchema = object({
20509
20535
  found: number2().int().min(0),
20510
20536
  colored: number2().int().min(0).default(0)
20511
20537
  });
20538
+ function normalizeTag(raw) {
20539
+ return raw.trim().toLowerCase().replace(/\s+/g, " ");
20540
+ }
20541
+ function normalizeTags(raw) {
20542
+ const seen = new Set;
20543
+ const out = [];
20544
+ for (const r of raw) {
20545
+ const tag = normalizeTag(r);
20546
+ if (!tag || seen.has(tag))
20547
+ continue;
20548
+ seen.add(tag);
20549
+ out.push(tag);
20550
+ }
20551
+ return out;
20552
+ }
20512
20553
  var collectionSavedViewSchema = object({
20513
20554
  id: string2().min(1),
20514
20555
  name: string2().min(1),
@@ -20547,6 +20588,53 @@ var collectionLogResultSchema = object({
20547
20588
  item: collectionItemDetailSchema
20548
20589
  });
20549
20590
 
20591
+ // ../../packages/schema/src/contracts/list-filters.ts
20592
+ function csvList(item) {
20593
+ return preprocess((raw) => (Array.isArray(raw) ? raw : [raw]).flatMap((v) => typeof v === "string" ? v.split(",") : [v]).map((v) => typeof v === "string" ? v.trim() : v).filter((v) => v !== ""), array(item).min(1));
20594
+ }
20595
+ var booleanQuerySchema = _enum2(["true", "false", "1", "0"]).transform((v) => v === "true" || v === "1");
20596
+ var instantQuerySchema = union([exports_iso.datetime({ offset: true }), exports_iso.date()]).transform((s) => new Date(s));
20597
+ var tagListSchema = csvList(string2().min(1)).transform((tags, ctx) => {
20598
+ const normalized = normalizeTags(tags.map((t) => t.replace(/^#/, "")));
20599
+ if (normalized.length === 0) {
20600
+ ctx.addIssue({ code: "custom", message: "must name at least one tag" });
20601
+ return NEVER;
20602
+ }
20603
+ return normalized;
20604
+ });
20605
+ function partyFilterSchema(party) {
20606
+ return union([_enum2(["any", "none"]), party]);
20607
+ }
20608
+ var taskListQuerySchema = object({
20609
+ areaId: ulidSchema.optional(),
20610
+ projectId: ulidSchema.optional(),
20611
+ status: csvList(taskStatusSchema).optional(),
20612
+ tag: tagListSchema.optional(),
20613
+ updatedSince: instantQuerySchema.optional(),
20614
+ dueBefore: instantQuerySchema.optional(),
20615
+ owner: partyFilterSchema(taskOwnerInputSchema).optional(),
20616
+ waitingOn: partyFilterSchema(taskWaitingOnInputSchema).optional(),
20617
+ completedSince: instantQuerySchema.optional(),
20618
+ completedBefore: instantQuerySchema.optional(),
20619
+ proposed: booleanQuerySchema.optional(),
20620
+ ready: booleanQuerySchema.optional(),
20621
+ drifted: booleanQuerySchema.optional()
20622
+ });
20623
+ var projectListQuerySchema = object({
20624
+ areaId: ulidSchema.optional(),
20625
+ status: csvList(projectStatusSchema).optional(),
20626
+ updatedSince: instantQuerySchema.optional(),
20627
+ dueBefore: instantQuerySchema.optional()
20628
+ });
20629
+ var habitListQuerySchema = object({
20630
+ areaId: ulidSchema.optional(),
20631
+ updatedSince: instantQuerySchema.optional(),
20632
+ retired: _enum2(["true", "false"]).transform((v) => v === "true").optional()
20633
+ });
20634
+ var areaListQuerySchema = object({
20635
+ archived: _enum2(["true", "false"]).transform((v) => v === "true").optional()
20636
+ });
20637
+
20550
20638
  // ../../packages/schema/src/contracts/habit-sources.ts
20551
20639
  var habitSourceSchema = _enum2(["manual", "google_fit", "strava", "github"]);
20552
20640
 
@@ -24665,6 +24753,50 @@ var dayRecordUndoResultSchema = object({
24665
24753
  });
24666
24754
  var STATUS_QUARTER_MS = RECORD_ROUND_MINUTES * 60000;
24667
24755
 
24756
+ // ../../packages/schema/src/contracts/today.ts
24757
+ var occurrenceViewSchema = occurrenceSchema.extend({
24758
+ title: string2(),
24759
+ templateKind: _enum2(["task", "habit", "routine"]),
24760
+ measurementType: habitMeasurementTypeSchema.nullable()
24761
+ });
24762
+ var todayResponseSchema = object({
24763
+ from: exports_coerce.date(),
24764
+ to: exports_coerce.date(),
24765
+ scheduledTasks: array(taskViewSchema),
24766
+ dueOccurrences: array(occurrenceViewSchema),
24767
+ overdueTasks: array(taskViewSchema),
24768
+ overdueOccurrences: array(occurrenceViewSchema)
24769
+ });
24770
+ var rolloverInputSchema = object({
24771
+ taskIds: array(string2().min(1)).default([]),
24772
+ occurrenceIds: array(string2().min(1)).default([]),
24773
+ action: _enum2(["reschedule", "someday", "drop"]),
24774
+ to: exports_coerce.date().optional()
24775
+ }).refine((v) => v.action !== "reschedule" || v.to !== undefined, {
24776
+ message: "`to` is required when action is `reschedule`",
24777
+ path: ["to"]
24778
+ });
24779
+ var rolloverResultSchema = object({
24780
+ tasks: number2().int().nonnegative(),
24781
+ occurrences: number2().int().nonnegative()
24782
+ });
24783
+ var fromEarlierQuerySchema = object({
24784
+ from: instantQuerySchema.optional(),
24785
+ areaId: ulidSchema.optional()
24786
+ });
24787
+ var fromEarlierResponseSchema = object({
24788
+ from: exports_coerce.date(),
24789
+ since: exports_coerce.date(),
24790
+ laterThisWeek: exports_coerce.date(),
24791
+ tasks: array(taskViewSchema)
24792
+ });
24793
+ var reviewDayStatSchema = object({
24794
+ date: string2(),
24795
+ completedTasks: number2().int().nonnegative(),
24796
+ completedOccurrences: number2().int().nonnegative()
24797
+ });
24798
+ var reviewStatsSchema = array(reviewDayStatSchema);
24799
+
24668
24800
  // ../../packages/schema/src/contracts/insights.ts
24669
24801
  var insightsActivityPointSchema = object({
24670
24802
  day: string2(),
@@ -27249,6 +27381,51 @@ var steamSyncResultSchema = object({
27249
27381
  games: array(steamGameSyncSchema)
27250
27382
  });
27251
27383
 
27384
+ // ../../packages/schema/src/contracts/money-net-worth.ts
27385
+ var amount5 = string2();
27386
+ var moneySnapshotSourceSchema = _enum2(moneySnapshotSource.enumValues);
27387
+ var moneyNetWorthQuerySchema = object({
27388
+ scope: moneyBudgetScopeSchema.optional(),
27389
+ months: exports_coerce.number().int().min(2).max(60).optional()
27390
+ });
27391
+ var moneyNetWorthPointSchema = object({
27392
+ month: moneyMonthSchema,
27393
+ total: amount5.nullable(),
27394
+ have: amount5.nullable(),
27395
+ owe: amount5.nullable()
27396
+ });
27397
+ var moneyNetWorthAccountSchema = object({
27398
+ accountId: ulidSchema,
27399
+ title: string2(),
27400
+ kind: moneyAccountKindSchema,
27401
+ visibility: _enum2(NODE_VISIBILITY_VALUES),
27402
+ currency: string2(),
27403
+ value: amount5,
27404
+ source: moneySnapshotSourceSchema,
27405
+ month: moneyMonthSchema
27406
+ });
27407
+ var moneyNetWorthSchema = object({
27408
+ scope: moneyBudgetScopeSchema,
27409
+ month: moneyMonthSchema,
27410
+ total: amount5,
27411
+ have: amount5,
27412
+ owe: amount5,
27413
+ points: array(moneyNetWorthPointSchema),
27414
+ accounts: array(moneyNetWorthAccountSchema)
27415
+ });
27416
+ var setMoneyNetWorthValueInputSchema = object({
27417
+ accountId: ulidSchema,
27418
+ value: moneyAmountSchema,
27419
+ month: moneyMonthSchema.optional()
27420
+ }).strict();
27421
+ var moneySnapshotSchema = object({
27422
+ id: ulidSchema,
27423
+ accountId: ulidSchema,
27424
+ month: moneyMonthSchema,
27425
+ value: amount5,
27426
+ source: moneySnapshotSourceSchema
27427
+ });
27428
+
27252
27429
  // ../../packages/schema/src/contracts/money-splits.ts
27253
27430
  var moneyPartySchema = union([
27254
27431
  object({ userId: ulidSchema }).strict(),
@@ -27990,7 +28167,8 @@ var GUEST_EXPORT_KINDS = [
27990
28167
  "floor-plan",
27991
28168
  "church",
27992
28169
  "answers",
27993
- "invitations"
28170
+ "invitations",
28171
+ "printer"
27994
28172
  ];
27995
28173
  var guestExportKindSchema = _enum2(GUEST_EXPORT_KINDS);
27996
28174
  var thankYouGiftSchema = object({
@@ -28576,6 +28754,209 @@ function scopeGranted(granted, required) {
28576
28754
  return granted.includes(FULL_ACCESS_SCOPE);
28577
28755
  }
28578
28756
 
28757
+ // ../../packages/schema/src/net/ssrf.ts
28758
+ var BLOCKED_HOST_SUFFIXES = [".internal", ".local", ".localhost"];
28759
+ var BLOCKED_HOSTNAMES = new Set(["localhost"]);
28760
+ function parseIpv4(host) {
28761
+ const parts = host.split(".");
28762
+ if (parts.length !== 4)
28763
+ return null;
28764
+ let value = 0;
28765
+ for (const part of parts) {
28766
+ if (!/^\d{1,3}$/.test(part))
28767
+ return null;
28768
+ const octet = Number(part);
28769
+ if (octet > 255)
28770
+ return null;
28771
+ value = value * 256 + octet;
28772
+ }
28773
+ return value >>> 0;
28774
+ }
28775
+ function isBlockedIpv4(ip) {
28776
+ const inRange = (base, bits) => {
28777
+ const baseIp = parseIpv4(base);
28778
+ if (baseIp === null)
28779
+ return false;
28780
+ const mask = bits === 0 ? 0 : 4294967295 << 32 - bits >>> 0;
28781
+ return (ip & mask) === (baseIp & mask);
28782
+ };
28783
+ return inRange("0.0.0.0", 8) || inRange("10.0.0.0", 8) || inRange("100.64.0.0", 10) || inRange("127.0.0.0", 8) || inRange("169.254.0.0", 16) || inRange("172.16.0.0", 12) || inRange("192.0.0.0", 24) || inRange("192.0.2.0", 24) || inRange("192.168.0.0", 16) || inRange("198.18.0.0", 15) || inRange("224.0.0.0", 4) || inRange("240.0.0.0", 4);
28784
+ }
28785
+ function parseIpv6(host) {
28786
+ let text = host;
28787
+ const zone = text.indexOf("%");
28788
+ if (zone !== -1)
28789
+ text = text.slice(0, zone);
28790
+ if (!text.includes(":"))
28791
+ return null;
28792
+ const lastColon = text.lastIndexOf(":");
28793
+ const tail = text.slice(lastColon + 1);
28794
+ if (tail.includes(".")) {
28795
+ const v4 = parseIpv4(tail);
28796
+ if (v4 === null)
28797
+ return null;
28798
+ const hex = `${(v4 >>> 16 & 65535).toString(16)}:${(v4 & 65535).toString(16)}`;
28799
+ text = text.slice(0, lastColon + 1) + hex;
28800
+ }
28801
+ const halves = text.split("::");
28802
+ if (halves.length > 2)
28803
+ return null;
28804
+ const toGroups = (segment) => {
28805
+ if (segment === "")
28806
+ return [];
28807
+ const out = [];
28808
+ for (const g of segment.split(":")) {
28809
+ if (!/^[0-9a-fA-F]{1,4}$/.test(g))
28810
+ return null;
28811
+ out.push(Number.parseInt(g, 16));
28812
+ }
28813
+ return out;
28814
+ };
28815
+ const head = toGroups(halves[0] ?? "");
28816
+ if (head === null)
28817
+ return null;
28818
+ let groups;
28819
+ if (halves.length === 2) {
28820
+ const back = toGroups(halves[1] ?? "");
28821
+ if (back === null)
28822
+ return null;
28823
+ const fill = 8 - head.length - back.length;
28824
+ if (fill < 0)
28825
+ return null;
28826
+ groups = [...head, ...Array(fill).fill(0), ...back];
28827
+ } else {
28828
+ groups = head;
28829
+ }
28830
+ return groups.length === 8 ? groups : null;
28831
+ }
28832
+ function isBlockedIpv6(groups) {
28833
+ const isZero = groups.every((g) => g === 0);
28834
+ if (isZero)
28835
+ return true;
28836
+ if (groups.slice(0, 7).every((g) => g === 0) && groups[7] === 1)
28837
+ return true;
28838
+ const isV4Mapped = groups.slice(0, 5).every((g) => g === 0) && groups[5] === 65535;
28839
+ const isV4Compat = groups.slice(0, 6).every((g) => g === 0);
28840
+ if (isV4Mapped || isV4Compat) {
28841
+ const v4 = ((groups[6] ?? 0) << 16 | (groups[7] ?? 0)) >>> 0;
28842
+ return isBlockedIpv4(v4);
28843
+ }
28844
+ const first = groups[0] ?? 0;
28845
+ if ((first & 65024) === 64512)
28846
+ return true;
28847
+ if ((first & 65472) === 65152)
28848
+ return true;
28849
+ if ((first & 65280) === 65280)
28850
+ return true;
28851
+ return false;
28852
+ }
28853
+ function isBlockedIp(host) {
28854
+ const bare = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
28855
+ const v4 = parseIpv4(bare);
28856
+ if (v4 !== null)
28857
+ return isBlockedIpv4(v4);
28858
+ const v6 = parseIpv6(bare);
28859
+ if (v6 !== null)
28860
+ return isBlockedIpv6(v6);
28861
+ return false;
28862
+ }
28863
+
28864
+ class BlockedWebhookUrlError extends Error {
28865
+ constructor(reason) {
28866
+ super(`webhook url rejected: ${reason}`);
28867
+ this.name = "BlockedWebhookUrlError";
28868
+ }
28869
+ }
28870
+ function bareHost(hostname) {
28871
+ return hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
28872
+ }
28873
+ function isInternalHost(hostname) {
28874
+ const host = hostname.toLowerCase();
28875
+ if (BLOCKED_HOSTNAMES.has(host) || BLOCKED_HOST_SUFFIXES.some((s) => host.endsWith(s))) {
28876
+ return true;
28877
+ }
28878
+ return isBlockedIp(host);
28879
+ }
28880
+ function assertSafeEgressUrl(rawUrl, policy) {
28881
+ let url;
28882
+ try {
28883
+ url = new URL(rawUrl);
28884
+ } catch {
28885
+ throw policy.fail("not a valid URL");
28886
+ }
28887
+ if (!policy.protocols.includes(url.protocol)) {
28888
+ const allowsHttp = policy.protocols.includes("http:");
28889
+ throw policy.fail(`only ${allowsHttp ? "http(s)" : "https"} URLs are allowed`);
28890
+ }
28891
+ if (url.username || url.password) {
28892
+ throw policy.fail("credentials in the URL are not allowed");
28893
+ }
28894
+ if (isInternalHost(url.hostname)) {
28895
+ const kind = isBlockedIp(url.hostname) ? "a private or reserved address" : "internal";
28896
+ throw policy.fail(`host '${url.hostname}' is ${kind}`);
28897
+ }
28898
+ return url;
28899
+ }
28900
+ function webhookUrlAllowlistFromEnv() {
28901
+ const env = globalThis.process?.env;
28902
+ const raw = env?.WEBHOOK_URL_ALLOWLIST;
28903
+ if (!raw)
28904
+ return [];
28905
+ return raw.split(",").map((h) => h.trim().toLowerCase()).filter(Boolean);
28906
+ }
28907
+ function isAllowlistedHost(hostname, allowedHosts) {
28908
+ const host = hostname.toLowerCase();
28909
+ const bare = bareHost(host);
28910
+ return allowedHosts.some((h) => h === host || h === bare);
28911
+ }
28912
+ function assertSafeWebhookUrl(rawUrl, opts = {}) {
28913
+ let url;
28914
+ try {
28915
+ url = new URL(rawUrl);
28916
+ } catch {
28917
+ throw new BlockedWebhookUrlError("not a valid URL");
28918
+ }
28919
+ const allowedHosts = opts.allowedHosts ?? webhookUrlAllowlistFromEnv();
28920
+ if (isAllowlistedHost(url.hostname, allowedHosts)) {
28921
+ return url;
28922
+ }
28923
+ return assertSafeEgressUrl(rawUrl, {
28924
+ protocols: ["https:"],
28925
+ fail: (reason) => new BlockedWebhookUrlError(reason)
28926
+ });
28927
+ }
28928
+
28929
+ // ../../packages/schema/src/contracts/webhooks.ts
28930
+ var webhookUrlSchema = string2().superRefine((value, ctx) => {
28931
+ try {
28932
+ assertSafeWebhookUrl(value);
28933
+ } catch (err) {
28934
+ ctx.addIssue({
28935
+ code: ZodIssueCode.custom,
28936
+ message: err instanceof BlockedWebhookUrlError ? err.message : "invalid webhook url"
28937
+ });
28938
+ }
28939
+ });
28940
+ var webhookEventSchema = string2().min(1).max(120).regex(/^[a-z0-9]+(\.[a-z0-9]+)+$/, "must be a dotted event name like 'task.created'");
28941
+ var webhookSubscriptionSchema = createSelectSchema(webhookSubscriptions, {
28942
+ events: array(webhookEventSchema),
28943
+ createdAt: exports_coerce.date(),
28944
+ updatedAt: exports_coerce.date()
28945
+ });
28946
+ var webhookSubscriptionPublicSchema = webhookSubscriptionSchema.omit({ secret: true });
28947
+ var createWebhookSubscriptionInputSchema = createInsertSchema(webhookSubscriptions, {
28948
+ url: webhookUrlSchema,
28949
+ events: array(webhookEventSchema).min(1),
28950
+ secret: string2().min(16).max(200).optional()
28951
+ }).omit({ id: true, tenantId: true, active: true, createdAt: true, updatedAt: true }).strict();
28952
+ var createWebhookSubscriptionResultSchema = webhookSubscriptionPublicSchema.extend({
28953
+ secret: string2()
28954
+ });
28955
+ var updateWebhookSubscriptionInputSchema = createUpdateSchema(webhookSubscriptions, {
28956
+ url: webhookUrlSchema.optional(),
28957
+ events: array(webhookEventSchema).min(1).optional()
28958
+ }).omit({ id: true, tenantId: true, secret: true, createdAt: true, updatedAt: true }).strict();
28959
+
28579
28960
  // ../../packages/schema/src/contracts/roles.ts
28580
28961
  var permissionSetSchema = createSelectSchema(permissionSets, {
28581
28962
  createdAt: exports_coerce.date(),
@@ -29624,6 +30005,37 @@ function toCollectionTypeSummary(config) {
29624
30005
  }
29625
30006
  var COLLECTION_TYPE_SUMMARIES = COLLECTION_TYPE_LIST.map(toCollectionTypeSummary);
29626
30007
 
30008
+ // ../../packages/schema/src/operations/define.ts
30009
+ function defineOperations(table) {
30010
+ return table;
30011
+ }
30012
+ var PUBLIC_API_PREFIX = "/api/v1";
30013
+ function pathOf(op, params = {}) {
30014
+ return op.path.replace(/\{([A-Za-z0-9_]+)\}/g, (_, name) => {
30015
+ const value = params[name];
30016
+ if (value === undefined)
30017
+ throw new Error(`pathOf: no value for {${name}} in ${op.path}`);
30018
+ return encodeURIComponent(value);
30019
+ });
30020
+ }
30021
+ function routeOf(op) {
30022
+ return `${op.method.toUpperCase()} ${PUBLIC_API_PREFIX}${op.path}`;
30023
+ }
30024
+
30025
+ // ../../packages/schema/src/operations/agents.ts
30026
+ var agentOperations = defineOperations({
30027
+ list: {
30028
+ method: "get",
30029
+ path: "/agents",
30030
+ scopes: ["agents:read"],
30031
+ tag: "Agents",
30032
+ summary: "List agents",
30033
+ description: "The Space's live Agents (`id`, `name`), oldest first, to resolve an Agent by name.",
30034
+ status: 200,
30035
+ response: { description: "The Space's Agents.", list: true }
30036
+ }
30037
+ });
30038
+
29627
30039
  // ../../packages/schema/src/operations/crud.ts
29628
30040
  function crudOperations(r) {
29629
30041
  const base = `/${r.segment}`;
@@ -29695,22 +30107,144 @@ function crudOperations(r) {
29695
30107
  };
29696
30108
  }
29697
30109
 
29698
- // ../../packages/schema/src/operations/define.ts
29699
- function defineOperations(table) {
29700
- return table;
29701
- }
29702
- var PUBLIC_API_PREFIX = "/api/v1";
29703
- function pathOf(op, params = {}) {
29704
- return op.path.replace(/\{([A-Za-z0-9_]+)\}/g, (_, name) => {
29705
- const value = params[name];
29706
- if (value === undefined)
29707
- throw new Error(`pathOf: no value for {${name}} in ${op.path}`);
29708
- return encodeURIComponent(value);
29709
- });
29710
- }
29711
- function routeOf(op) {
29712
- return `${op.method.toUpperCase()} ${PUBLIC_API_PREFIX}${op.path}`;
29713
- }
30110
+ // ../../packages/schema/src/operations/block-templates.ts
30111
+ var crud = crudOperations({
30112
+ segment: "block-templates",
30113
+ tag: "Calendar",
30114
+ noun: "block template",
30115
+ read: "calendar:read",
30116
+ write: "calendar:write",
30117
+ create: createBlockTemplateInputSchema,
30118
+ update: updateBlockTemplateInputSchema,
30119
+ listQuery: []
30120
+ });
30121
+ var blockTemplateOperations = defineOperations({
30122
+ ...crud,
30123
+ create: { ...crud.create, webhook: undefined },
30124
+ update: { ...crud.update, webhook: undefined },
30125
+ remove: { ...crud.remove, webhook: undefined },
30126
+ week: {
30127
+ method: "get",
30128
+ path: "/block-templates/week",
30129
+ scopes: ["calendar:read"],
30130
+ tag: "Calendar",
30131
+ summary: "Lay out a week from the block templates",
30132
+ description: "Every life block the key's member's templates lay out for the week starting `start` (a Monday), in time order, on their wall clock: `{ templateId, lifeKind, title, startAt, endAt, routineId }`. Writes nothing; this is what Sunday planning proposes.",
30133
+ query: [{ name: "start", description: "The week's Monday (YYYY-MM-DD).", format: "date" }],
30134
+ querySchema: blockTemplateWeekQuerySchema,
30135
+ status: 200,
30136
+ response: { description: "The week's blocks." },
30137
+ errors: { 400: true }
30138
+ },
30139
+ defaults: {
30140
+ method: "post",
30141
+ path: "/block-templates/defaults",
30142
+ scopes: ["calendar:write"],
30143
+ tag: "Calendar",
30144
+ summary: "Seed the default week",
30145
+ description: "Give a member with no block templates the default week: sleep, the morning, reading, two work blocks, lunch and dinner with clean-up, a buffer, a free evening and a wind-down. A member who has templates keeps them; the call then returns them unchanged.",
30146
+ status: 200,
30147
+ response: { description: "The member's templates." }
30148
+ }
30149
+ });
30150
+
30151
+ // ../../packages/schema/src/operations/collection-upkeep.ts
30152
+ var collectionUpkeepOperations = defineOperations({
30153
+ list: {
30154
+ method: "get",
30155
+ path: "/collection/{id}/upkeep",
30156
+ scopes: ["collections:read"],
30157
+ tag: "Collection",
30158
+ summary: "List an asset's upkeep and repairs",
30159
+ description: "The Tasks that look after this asset: `upkeep` (`maintains` \u2014 usually recurring, e.g. every 4 weeks from the last time) and `repairs` (one-off fix-it Tasks), open ones first. 404 unless the item is a live `asset`. Returns `{ itemId, upkeep, repairs }`.",
30160
+ status: 200,
30161
+ response: { description: "The asset's Tasks." },
30162
+ errors: { 404: true }
30163
+ },
30164
+ link: {
30165
+ method: "post",
30166
+ path: "/collection/{id}/upkeep",
30167
+ scopes: ["collections:write"],
30168
+ tag: "Collection",
30169
+ summary: "Tie a Task to an asset",
30170
+ description: 'Tie a Task in the Space to this asset as its upkeep (`kind: "maintains"`, the default) or a repair (`"repairs"`). Make the Task (its recurrence, Place, Area) through the tasks API first. When a tied Task is finished \u2014 a one-off marked done, or one occurrence of a recurring one checked off \u2014 the asset gets a `service` / `repair` Entry with the Task on it, once per day, and upkeep moves its `lastServicedAt` forward. Returns the asset\'s Tasks.',
30171
+ body: linkAssetTaskInputSchema,
30172
+ status: 201,
30173
+ response: { description: "The asset's Tasks." },
30174
+ errors: { 400: true, 404: true }
30175
+ },
30176
+ unlink: {
30177
+ method: "delete",
30178
+ path: "/collection/{id}/upkeep/{taskId}",
30179
+ pathParams: { taskId: "The Task's ULID." },
30180
+ scopes: ["collections:write"],
30181
+ tag: "Collection",
30182
+ summary: "Untie a Task from an asset",
30183
+ description: "Untie the Task from this asset; the Task itself stays. Returns the asset's Tasks.",
30184
+ status: 200,
30185
+ response: { description: "The asset's Tasks." },
30186
+ errors: { 404: true }
30187
+ }
30188
+ });
30189
+
30190
+ // ../../packages/schema/src/operations/google-contacts.ts
30191
+ var googleContactsOperations = defineOperations({
30192
+ list: {
30193
+ method: "get",
30194
+ path: "/people/google-contacts",
30195
+ scopes: ["contacts:read"],
30196
+ tag: "People",
30197
+ summary: "Your Google Contacts, to pick from",
30198
+ description: "The key's member's Google Contacts link (`status`: `available`, `mode`, `connected`, `account`) and the people in it matching `q` (a name or an email; everyone without), each with `resourceName`, `name`, `emails`, `phones`, `birthday` (`yearKnown: false` for a day and month only), the contact in do they'd merge into (`match`: by `email`, `phone` or a unique `name`; never a company) and what importing adds (`adds`). Not connected: no candidates. Connecting needs a browser (People \u2192 Import). A key with no member is a 403. Off until `GOOGLE_CONTACTS_API` is `fake` or `live`.",
30199
+ query: [{ name: "q", description: "A name or email to narrow to." }],
30200
+ querySchema: googleContactsQuerySchema,
30201
+ status: 200,
30202
+ response: { description: "The link and the candidates." },
30203
+ errors: { 403: "Not a member's key." }
30204
+ },
30205
+ import: {
30206
+ method: "post",
30207
+ path: "/people/google-contacts/import",
30208
+ scopes: ["contacts:write"],
30209
+ tag: "People",
30210
+ summary: "Import the people you picked from Google Contacts",
30211
+ description: "Only the picked people (`resourceNames`, Google's `people/\u2026` ids, at most 200). Each merges into the contact it matches and fills in only a missing email, phone or birthday (a key date that becomes a Task), else becomes a new contact, so importing twice never duplicates anyone. Returns `{ created, merged (with what each got: added), missing }`. Not connected is a 409; off until `GOOGLE_CONTACTS_API` is set (403).",
30212
+ body: importGoogleContactsInputSchema,
30213
+ status: 200,
30214
+ response: { description: "What the import did." },
30215
+ errors: {
30216
+ 400: true,
30217
+ 403: "Off, or not a member's key.",
30218
+ 409: "Google Contacts isn't connected (or asks to sign in again)."
30219
+ }
30220
+ }
30221
+ });
30222
+
30223
+ // ../../packages/schema/src/operations/google-health.ts
30224
+ var googleHealthOperations = defineOperations({
30225
+ status: {
30226
+ method: "get",
30227
+ path: "/health/google-health",
30228
+ scopes: ["health:read"],
30229
+ tag: "Health",
30230
+ summary: "Get the Google Health link",
30231
+ description: "The key's member's Google Health link (connected in the app, Settings \u2192 Health): `available` and `mode` (off \xB7 fake \xB7 live, the server's switch), `connected`, the Google `account`, `lastSyncAt` and each resource's (sleep, resting_heart_rate, hrv, steps, workouts), and `failure` (`reauth` \xB7 `not_linked` \xB7 `unavailable` \xB7 `other`, with a title and a hint) when the sync stopped, and `nutrition` (writing the member's food back to Google Health, opt-in in the app: `enabled`, `granted` (the link has the write scope), `lastWrittenDay`, `failure`). Never another member's.",
30232
+ status: 200,
30233
+ response: { description: "The link's status." },
30234
+ errors: { 403: true }
30235
+ },
30236
+ sync: {
30237
+ method: "post",
30238
+ path: "/health/google-health/sync",
30239
+ scopes: ["health:write"],
30240
+ tag: "Health",
30241
+ summary: "Sync Google Health now",
30242
+ description: "As the key's member, one sync of their Google Health, as the 07:30 morning sync does: last night's sleep (on the wake day, joining the Sleep and Bedtime Metrics), the day's resting heart rate and HRV, steps and workouts (joining the sessions) onto their own ledger, nothing twice. Returns written and skipped per resource and the status after it; a sync that stops answers 200 with `status.failure`. 409 before the member connected, 403 while the server has Google Health off.",
30243
+ status: 200,
30244
+ response: { description: "What came in, and the status after it." },
30245
+ errors: { 403: true, 409: true }
30246
+ }
30247
+ });
29714
30248
 
29715
30249
  // ../../packages/schema/src/operations/inventory-items.ts
29716
30250
  var inventoryItemOperations = defineOperations({
@@ -29727,6 +30261,75 @@ var inventoryItemOperations = defineOperations({
29727
30261
  })
29728
30262
  });
29729
30263
 
30264
+ // ../../packages/schema/src/operations/mail-campaigns.ts
30265
+ var pathParams = { id: "The campaign's ULID." };
30266
+ var mailCampaignOperations = defineOperations({
30267
+ list: {
30268
+ method: "get",
30269
+ path: "/mail-campaigns",
30270
+ scopes: ["contacts:read"],
30271
+ tag: "Guests",
30272
+ summary: "List a guest list's mail campaigns",
30273
+ description: "Its drafted and sent mails, newest first.",
30274
+ query: [{ name: "guestListId", description: "The guest list (required)." }],
30275
+ querySchema: mailCampaignsQuerySchema,
30276
+ status: 200,
30277
+ response: { description: "The campaigns." },
30278
+ errors: { 400: true }
30279
+ },
30280
+ create: {
30281
+ method: "post",
30282
+ path: "/mail-campaigns",
30283
+ scopes: ["contacts:write"],
30284
+ tag: "Guests",
30285
+ summary: "Draft a mail campaign",
30286
+ description: "A draft for a guest list's households: its `kind` (`message`, `save_the_date` or `invitation`), the template's `data` (the copy per language, Dutch required) and an optional reply address. A key can draft, preview and edit, never test or send: only a member's own session sends.",
30287
+ body: createMailCampaignInputSchema,
30288
+ status: 201,
30289
+ response: { description: "The draft." },
30290
+ errors: { 400: true, 404: true }
30291
+ },
30292
+ get: {
30293
+ method: "get",
30294
+ path: "/mail-campaigns/{id}",
30295
+ pathParams,
30296
+ scopes: ["contacts:read"],
30297
+ tag: "Guests",
30298
+ summary: "Get a mail campaign",
30299
+ description: "",
30300
+ status: 200,
30301
+ response: { description: "The campaign." },
30302
+ errors: { 404: true }
30303
+ },
30304
+ update: {
30305
+ method: "patch",
30306
+ path: "/mail-campaigns/{id}",
30307
+ pathParams,
30308
+ scopes: ["contacts:write"],
30309
+ tag: "Guests",
30310
+ summary: "Edit a draft",
30311
+ description: "Change its kind, data or reply address. Any edit makes the last test stale: a new test is needed before it can be sent.",
30312
+ body: updateMailCampaignInputSchema,
30313
+ status: 200,
30314
+ response: { description: "The draft." },
30315
+ errors: { 400: true, 404: true }
30316
+ },
30317
+ preview: {
30318
+ method: "get",
30319
+ path: "/mail-campaigns/{id}/preview",
30320
+ pathParams,
30321
+ scopes: ["contacts:read"],
30322
+ tag: "Guests",
30323
+ summary: "Preview a mail campaign",
30324
+ description: "The mail as a household would get it in one language (`nl` by default, or `pt`): its subject and rendered body.",
30325
+ query: [{ name: "language", description: "`nl` (default) or `pt`." }],
30326
+ querySchema: mailPreviewQuerySchema,
30327
+ status: 200,
30328
+ response: { description: "The rendered mail." },
30329
+ errors: { 400: true, 404: true }
30330
+ }
30331
+ });
30332
+
29730
30333
  // ../../packages/schema/src/operations/meal-plans.ts
29731
30334
  var mealPlanOperations = defineOperations({
29732
30335
  ...crudOperations({
@@ -29740,6 +30343,48 @@ var mealPlanOperations = defineOperations({
29740
30343
  })
29741
30344
  });
29742
30345
 
30346
+ // ../../packages/schema/src/operations/occurrences.ts
30347
+ var occurrenceOperations = defineOperations({
30348
+ list: {
30349
+ method: "get",
30350
+ path: "/occurrences",
30351
+ scopes: ["occurrences:read"],
30352
+ tag: "Occurrences",
30353
+ summary: "List a template's occurrences",
30354
+ description: "Occurrences of one task/habit template, oldest-first.",
30355
+ query: [{ name: "templateId", description: "The task/habit template's ULID.", required: true }],
30356
+ querySchema: object({ templateId: ulidSchema }),
30357
+ status: 200,
30358
+ response: { description: "The template's occurrences." },
30359
+ errors: { 400: true }
30360
+ },
30361
+ update: {
30362
+ method: "patch",
30363
+ path: "/occurrences/{id}",
30364
+ scopes: ["occurrences:write"],
30365
+ tag: "Occurrences",
30366
+ summary: "Override an occurrence",
30367
+ description: 'Reschedule/skip one occurrence ("edit this"); fires `occurrence.updated`.',
30368
+ body: updateOccurrenceInputSchema,
30369
+ status: 200,
30370
+ response: { description: "The overridden occurrence." },
30371
+ errors: { 400: true, 404: true },
30372
+ webhook: "occurrence.updated"
30373
+ },
30374
+ complete: {
30375
+ method: "post",
30376
+ path: "/occurrences/{id}/complete",
30377
+ scopes: ["occurrences:write"],
30378
+ tag: "Occurrences",
30379
+ summary: "Complete an occurrence",
30380
+ description: "Mark an occurrence done; fires `occurrence.completed`.",
30381
+ status: 201,
30382
+ response: { description: "The completed occurrence." },
30383
+ errors: { 404: true },
30384
+ webhook: "occurrence.completed"
30385
+ }
30386
+ });
30387
+
29743
30388
  // ../../packages/schema/src/operations/periods.ts
29744
30389
  var periodOperations = defineOperations({
29745
30390
  ...crudOperations({
@@ -29765,6 +30410,185 @@ var periodOperations = defineOperations({
29765
30410
  })
29766
30411
  });
29767
30412
 
30413
+ // ../../packages/schema/src/operations/planning-rules.ts
30414
+ var planningRuleOperations = defineOperations({
30415
+ ...crudOperations({
30416
+ segment: "planning/rules",
30417
+ tag: "Planning rules",
30418
+ noun: "planning rule",
30419
+ read: "planning:read",
30420
+ write: "planning:write",
30421
+ create: createPlanningRuleInputSchema,
30422
+ update: updatePlanningRuleInputSchema,
30423
+ listQuery: [
30424
+ { name: "forUserId", description: "Only this member's rules." },
30425
+ {
30426
+ name: "kind",
30427
+ description: "Only this kind (`fixed_time`, `keep_free`, `meaning`, `whose`, `limit`, `note`)."
30428
+ }
30429
+ ],
30430
+ listQuerySchema: planningRulesQuerySchema
30431
+ })
30432
+ });
30433
+
30434
+ // ../../packages/schema/src/operations/plans.ts
30435
+ function decision(path, verb, what) {
30436
+ return {
30437
+ method: "post",
30438
+ path,
30439
+ scopes: ["tasks:write"],
30440
+ tag: "Plans",
30441
+ summary: `${verb[0]?.toUpperCase()}${verb.slice(1)} plan items`,
30442
+ description: `${what} \`itemIds\` narrows it to those items; absent (or an empty body) answers every item it applies to. An unknown item id is a 404.`,
30443
+ body: planDecisionInputSchema,
30444
+ bodyOptional: true,
30445
+ status: 200,
30446
+ response: { description: "The plan, after.", view: "Plan" },
30447
+ errors: { 400: true, 404: true }
30448
+ };
30449
+ }
30450
+ var planOperations = defineOperations({
30451
+ list: {
30452
+ method: "get",
30453
+ path: "/plans",
30454
+ scopes: ["tasks:read"],
30455
+ tag: "Plans",
30456
+ summary: "List plans",
30457
+ description: "The Space's plan proposals, newest first (the latest 20), each with its items and each item's task as it is now. `status`: `open` (an item waits for an answer), `settled` (none does) or `replaced` (a newer plan for the same week or day took its place).",
30458
+ query: [
30459
+ { name: "kind", description: "`week` or `day`." },
30460
+ { name: "status", description: "`open`, `settled` or `replaced`." },
30461
+ { name: "startsOn", description: "The week's Monday, or the day (`YYYY-MM-DD`)." },
30462
+ { name: "forUserId", description: "Only this member's plans." }
30463
+ ],
30464
+ querySchema: planListQuerySchema.extend({ forUserId: string2().min(1).optional() }),
30465
+ status: 200,
30466
+ response: { description: "The plans.", list: true, view: "Plan" },
30467
+ errors: { 400: true }
30468
+ },
30469
+ create: {
30470
+ method: "post",
30471
+ path: "/plans",
30472
+ scopes: ["tasks:write"],
30473
+ tag: "Plans",
30474
+ summary: "Propose a plan",
30475
+ description: "The agent proposes a week (`kind: week`, `startsOn` a Monday) or a day for a member (`forUserId`, default the member the key acts for). Each item is about one open task: `plan` it on a `day` (all-day, in the member's zone) or `at` a time (with `durationMinutes` when known), take it back to `someday`, or `let_go`. `reason` is the line shown with it. Nothing is written to a task until the member accepts; a newer plan for the same week or day replaces an open one. An Agent's own work joins only while it waits on the member. A block that is already there (a task, a life block or an event on that day with a similar title) is folded in, not added twice: a task is planned into the block's time, anything else is left out, and `merged` lists each with a hint; `onDuplicate: reject` refuses the plan with those hints instead.",
30476
+ body: createPlanInputSchema,
30477
+ status: 201,
30478
+ response: {
30479
+ description: "The plan, open, with the duplicates it folded in.",
30480
+ view: "PlanCreateResult"
30481
+ },
30482
+ errors: { 400: true }
30483
+ },
30484
+ get: {
30485
+ method: "get",
30486
+ path: "/plans/{id}",
30487
+ scopes: ["tasks:read"],
30488
+ tag: "Plans",
30489
+ summary: "Get a plan",
30490
+ description: "One plan with its items.",
30491
+ status: 200,
30492
+ response: { description: "The plan.", view: "Plan" },
30493
+ errors: { 404: true }
30494
+ },
30495
+ accept: {
30496
+ ...decision("/plans/{id}/accept", "accept", "Accept proposed items: each task is written as the item says (a planned day or time, Someday, or cancelled for Let go; a task still Proposed by the agent joins the plan too), and the item keeps what it replaced for Undo. An item whose task has closed since is dismissed instead."),
30497
+ errors: { 400: true, 404: true, 409: "A newer plan replaced this one." }
30498
+ },
30499
+ dismiss: decision("/plans/{id}/dismiss", "dismiss", "Leave proposed items out. Nothing is written to a task."),
30500
+ undo: decision("/plans/{id}/undo", "undo", "Undo answered items: an accepted item's task is put back as it was before; a dismissed item simply waits again. Both read as proposed afterwards.")
30501
+ });
30502
+
30503
+ // ../../packages/schema/src/operations/rsvp-answers.ts
30504
+ var rsvpAnswerOperations = defineOperations({
30505
+ list: {
30506
+ method: "get",
30507
+ path: "/rsvp-answers",
30508
+ scopes: ["contacts:read"],
30509
+ tag: "Guests",
30510
+ summary: "List RSVP answers",
30511
+ description: "A guest list's answers to its RSVP questions (`guestListId`, required), or one household's (`householdId`): `{ questionId, householdId, guestId, value }`, `guestId` null for the household's own.",
30512
+ query: [
30513
+ { name: "guestListId", description: "The guest list (required)." },
30514
+ { name: "householdId", description: "Only this household's answers." }
30515
+ ],
30516
+ querySchema: rsvpAnswersQuerySchema,
30517
+ status: 200,
30518
+ response: { description: "The answers." },
30519
+ errors: { 400: true, 404: true }
30520
+ },
30521
+ set: {
30522
+ method: "put",
30523
+ path: "/rsvp-answers",
30524
+ scopes: ["contacts:write"],
30525
+ tag: "Guests",
30526
+ summary: "Set a household's RSVP answers",
30527
+ description: "Lays `answers` over the household's stored ones (`value: null` clears one; answers left out stay) and returns what stands. An unknown question, a guest not in the household, a per-guest question without a guest, an event the guest isn't invited to, or a value that doesn't fit the kind is a 422; an answer whose question no longer shows (its `showIf`) is dropped. Fires `rsvp.answers.updated`.",
30528
+ body: setRsvpAnswersInputSchema,
30529
+ status: 200,
30530
+ response: { description: "The household's answers after." },
30531
+ errors: { 400: true, 404: true, 422: "An answer that can't be given (see the message)." },
30532
+ webhook: "rsvp.answers.updated"
30533
+ }
30534
+ });
30535
+
30536
+ // ../../packages/schema/src/operations/rsvp-questions.ts
30537
+ var rsvpQuestionOperations = defineOperations({
30538
+ ...crudOperations({
30539
+ segment: "rsvp-questions",
30540
+ tag: "Guests",
30541
+ noun: "RSVP question",
30542
+ eventBase: "rsvp.question",
30543
+ read: "contacts:read",
30544
+ write: "contacts:write",
30545
+ create: createRsvpQuestionInputSchema,
30546
+ update: updateRsvpQuestionInputSchema,
30547
+ listQuery: [{ name: "guestListId", description: "The guest list (required)." }],
30548
+ listQuerySchema: rsvpQuestionsQuerySchema
30549
+ }),
30550
+ presets: {
30551
+ method: "post",
30552
+ path: "/rsvp-questions/presets",
30553
+ scopes: ["contacts:write"],
30554
+ tag: "Guests",
30555
+ summary: "Add the built-in RSVP questions",
30556
+ description: "Adds the preset questions after the list's own (`diet`: per guest, shown to a guest who said yes to anything, with vegetarian \xB7 vegan \xB7 gluten-free \xB7 lactose-free \xB7 nut allergy \xB7 other, and a text line shown when other is picked; `message`: the household's, once). A preset the list already has stays as it is. Returns the list's questions; fires `rsvp.question.created`.",
30557
+ body: addRsvpPresetsInputSchema,
30558
+ status: 200,
30559
+ response: { description: "The list's questions." },
30560
+ errors: { 400: true, 404: true },
30561
+ webhook: "rsvp.question.created"
30562
+ }
30563
+ });
30564
+
30565
+ // ../../packages/schema/src/operations/settings.ts
30566
+ var settingsOperations = defineOperations({
30567
+ get: {
30568
+ method: "get",
30569
+ path: "/settings",
30570
+ scopes: ["settings:read"],
30571
+ tag: "Settings",
30572
+ summary: "Get settings",
30573
+ description: "The caller's settings (lazily created). The key must carry a user identity.",
30574
+ status: 200,
30575
+ response: { description: "The caller's settings." }
30576
+ },
30577
+ update: {
30578
+ method: "patch",
30579
+ path: "/settings",
30580
+ scopes: ["settings:write"],
30581
+ tag: "Settings",
30582
+ summary: "Update settings",
30583
+ description: "Patch the caller's settings; fires `settings.updated`.",
30584
+ body: updateUserSettingsInputSchema,
30585
+ status: 200,
30586
+ response: { description: "The updated settings." },
30587
+ errors: { 400: true },
30588
+ webhook: "settings.updated"
30589
+ }
30590
+ });
30591
+
29768
30592
  // ../../packages/schema/src/operations/shopping-lists.ts
29769
30593
  var shoppingListOperations = defineOperations({
29770
30594
  ...crudOperations({
@@ -29783,7 +30607,7 @@ var shoppingListOperations = defineOperations({
29783
30607
  // ../../packages/schema/src/operations/tap-tags.ts
29784
30608
  var READ = ["habits:read", "routines:read"];
29785
30609
  var WRITE = ["habits:write", "routines:write"];
29786
- var pathParams = { id: "The tag's id: the ULID the sticker's link carries (`/t/{id}`)." };
30610
+ var pathParams2 = { id: "The tag's id: the ULID the sticker's link carries (`/t/{id}`)." };
29787
30611
  var tapTagOperations = defineOperations({
29788
30612
  list: {
29789
30613
  method: "get",
@@ -29811,7 +30635,7 @@ var tapTagOperations = defineOperations({
29811
30635
  get: {
29812
30636
  method: "get",
29813
30637
  path: "/tap-tags/{id}",
29814
- pathParams,
30638
+ pathParams: pathParams2,
29815
30639
  scopes: READ,
29816
30640
  tag: "Habits",
29817
30641
  summary: "Get a tap tag",
@@ -29823,7 +30647,7 @@ var tapTagOperations = defineOperations({
29823
30647
  update: {
29824
30648
  method: "patch",
29825
30649
  path: "/tap-tags/{id}",
29826
- pathParams,
30650
+ pathParams: pathParams2,
29827
30651
  scopes: WRITE,
29828
30652
  tag: "Habits",
29829
30653
  summary: "Rename or rebind a tap tag",
@@ -29836,7 +30660,7 @@ var tapTagOperations = defineOperations({
29836
30660
  remove: {
29837
30661
  method: "delete",
29838
30662
  path: "/tap-tags/{id}",
29839
- pathParams,
30663
+ pathParams: pathParams2,
29840
30664
  scopes: WRITE,
29841
30665
  tag: "Habits",
29842
30666
  summary: "Delete a tap tag",
@@ -29848,7 +30672,7 @@ var tapTagOperations = defineOperations({
29848
30672
  tap: {
29849
30673
  method: "post",
29850
30674
  path: "/tap-tags/{id}/tap",
29851
- pathParams,
30675
+ pathParams: pathParams2,
29852
30676
  scopes: WRITE,
29853
30677
  tag: "Habits",
29854
30678
  summary: "Tap a tap tag",
@@ -29859,6 +30683,103 @@ var tapTagOperations = defineOperations({
29859
30683
  }
29860
30684
  });
29861
30685
 
30686
+ // ../../packages/schema/src/operations/today.ts
30687
+ var todayOperations = defineOperations({
30688
+ fromEarlier: {
30689
+ method: "get",
30690
+ path: "/today/from-earlier",
30691
+ scopes: ["tasks:read"],
30692
+ tag: "Reads",
30693
+ summary: "From earlier",
30694
+ description: 'The planned tasks that slipped (#888): open, non-recurring tasks whose all-day planned day falls in the 14 days before `from`, newest plan first. Timed tasks, tasks placed in a past time block and agent-owned work never roll; older plans simply leave the list (nothing is rewritten). A Space back from 21+ days away gets no pile. Also names `laterThisWeek`, the quietest later day this week (Monday-first; tomorrow on a Sunday) that "Later this week" lands on. Answer with `POST /api/v1/today/rollover`.',
30695
+ query: [
30696
+ {
30697
+ name: "from",
30698
+ description: "Your local day start, an ISO instant (or a bare date, read as UTC midnight). Defaults to the current UTC day."
30699
+ },
30700
+ { name: "areaId", description: "Only tasks in this Area (own or their Project's)." }
30701
+ ],
30702
+ querySchema: fromEarlierQuerySchema,
30703
+ status: 200,
30704
+ response: { description: "From earlier.", view: "FromEarlierResponse" },
30705
+ errors: { 400: true }
30706
+ },
30707
+ rollover: {
30708
+ method: "post",
30709
+ path: "/today/rollover",
30710
+ scopes: ["tasks:write"],
30711
+ tag: "Tasks",
30712
+ summary: "Answer From earlier",
30713
+ description: 'Answer many tasks (and habit/routine occurrences) at once: `reschedule` to `to` (all-day; From earlier\'s Today and Later this week), `someday` (clear the planned day, keep the Area or Project; occurrences untouched), or `drop` ("Let go": tasks cancelled, occurrences skipped; a cancelled task never counts as Landed). Only open tasks and unresolved occurrences change; unknown ids are skipped. Returns how many of each moved. Occurrence ids need `habits:write` too.',
30714
+ body: rolloverInputSchema,
30715
+ status: 201,
30716
+ response: { description: "How many tasks and occurrences changed." },
30717
+ errors: { 400: true }
30718
+ }
30719
+ });
30720
+
30721
+ // ../../packages/schema/src/operations/webhooks.ts
30722
+ var webhookOperations = defineOperations({
30723
+ list: {
30724
+ method: "get",
30725
+ path: "/webhooks",
30726
+ scopes: ["webhooks:read"],
30727
+ tag: "Webhooks",
30728
+ summary: "List webhook subscriptions",
30729
+ description: "The Space's outbound webhook endpoints (secrets omitted), newest first.",
30730
+ querySchema: paginationQuerySchema,
30731
+ paginated: true,
30732
+ status: 200,
30733
+ response: { description: "The subscriptions." }
30734
+ },
30735
+ create: {
30736
+ method: "post",
30737
+ path: "/webhooks",
30738
+ scopes: ["webhooks:write"],
30739
+ tag: "Webhooks",
30740
+ summary: "Register a webhook subscription",
30741
+ description: "Register an endpoint + events; returns the signing secret once.",
30742
+ body: createWebhookSubscriptionInputSchema,
30743
+ status: 201,
30744
+ response: { description: "The created subscription (with its one-time secret)." },
30745
+ errors: { 400: true }
30746
+ },
30747
+ get: {
30748
+ method: "get",
30749
+ path: "/webhooks/{id}",
30750
+ scopes: ["webhooks:read"],
30751
+ tag: "Webhooks",
30752
+ summary: "Get a webhook subscription",
30753
+ description: "One subscription (secret omitted).",
30754
+ status: 200,
30755
+ response: { description: "The subscription." },
30756
+ errors: { 404: true }
30757
+ },
30758
+ update: {
30759
+ method: "patch",
30760
+ path: "/webhooks/{id}",
30761
+ scopes: ["webhooks:write"],
30762
+ tag: "Webhooks",
30763
+ summary: "Update a webhook subscription",
30764
+ description: "Patch url/events, or pause with `active: false`.",
30765
+ body: updateWebhookSubscriptionInputSchema,
30766
+ status: 200,
30767
+ response: { description: "The updated subscription." },
30768
+ errors: { 400: true, 404: true }
30769
+ },
30770
+ remove: {
30771
+ method: "delete",
30772
+ path: "/webhooks/{id}",
30773
+ scopes: ["webhooks:write"],
30774
+ tag: "Webhooks",
30775
+ summary: "Delete a webhook subscription",
30776
+ description: "Remove an endpoint.",
30777
+ status: 204,
30778
+ response: { description: "Deleted." },
30779
+ errors: { 404: true }
30780
+ }
30781
+ });
30782
+
29862
30783
  // ../../node_modules/ulidx/dist/node/index.js
29863
30784
  import crypto from "crypto";
29864
30785
 
@@ -30111,7 +31032,7 @@ function parseCapture2(input, now) {
30111
31032
 
30112
31033
  // src/meta.ts
30113
31034
  var CLI_NAME = "do";
30114
- var VERSION = "1.303.0";
31035
+ var VERSION = "1.304.0";
30115
31036
  var API_PREFIX = "/api/v1";
30116
31037
  var DEFAULT_API_URL = "http://localhost:3001";
30117
31038
 
@@ -30894,24 +31815,15 @@ var ENTITIES = [
30894
31815
  canDelete: true
30895
31816
  }
30896
31817
  },
30897
- {
31818
+ entityFromOperations(rsvpQuestionOperations, {
30898
31819
  name: "rsvp-questions",
30899
- path: "/rsvp-questions",
30900
- readScopes: ["contacts:read"],
30901
31820
  returns: "RsvpQuestion",
30902
- canGet: true,
30903
31821
  listFlags: [
30904
31822
  { name: "list", description: "the guest list's id (required)", query: "guestListId" }
30905
31823
  ],
30906
- write: {
30907
- scopes: ["contacts:write"],
30908
- createSchema: createRsvpQuestionInputSchema,
30909
- createInput: "CreateRsvpQuestionInput",
30910
- updateSchema: updateRsvpQuestionInputSchema,
30911
- updateInput: "UpdateRsvpQuestionInput",
30912
- canDelete: true
30913
- }
30914
- },
31824
+ createInput: "CreateRsvpQuestionInput",
31825
+ updateInput: "UpdateRsvpQuestionInput"
31826
+ }),
30915
31827
  {
30916
31828
  name: "guests",
30917
31829
  path: "/guests",
@@ -31137,12 +32049,9 @@ var ENTITIES = [
31137
32049
  createInput: "CreateMealPlanInput",
31138
32050
  updateInput: "UpdateMealPlanInput"
31139
32051
  }),
31140
- {
32052
+ entityFromOperations({ list: occurrenceOperations.list }, {
31141
32053
  name: "occurrences",
31142
- path: "/occurrences",
31143
- readScopes: ["occurrences:read"],
31144
32054
  returns: "Occurrence",
31145
- canGet: false,
31146
32055
  listFlags: [
31147
32056
  {
31148
32057
  name: "template-id",
@@ -31151,7 +32060,7 @@ var ENTITIES = [
31151
32060
  query: "templateId"
31152
32061
  }
31153
32062
  ]
31154
- },
32063
+ }),
31155
32064
  {
31156
32065
  name: "time-blocks",
31157
32066
  path: "/time-blocks",
@@ -31175,30 +32084,14 @@ var ENTITIES = [
31175
32084
  canDelete: true
31176
32085
  }
31177
32086
  },
31178
- {
32087
+ entityFromOperations(blockTemplateOperations, {
31179
32088
  name: "block-templates",
31180
- path: "/block-templates",
31181
- readScopes: ["calendar:read"],
31182
32089
  returns: "BlockTemplateView",
31183
- canGet: true,
31184
32090
  listFlags: [],
31185
- write: {
31186
- scopes: ["calendar:write"],
31187
- createSchema: createBlockTemplateInputSchema,
31188
- createInput: "CreateBlockTemplateInput",
31189
- updateSchema: updateBlockTemplateInputSchema,
31190
- updateInput: "UpdateBlockTemplateInput",
31191
- canDelete: true
31192
- }
31193
- },
31194
- {
31195
- name: "webhooks",
31196
- path: "/webhooks",
31197
- readScopes: ["webhooks:read"],
31198
- returns: "WebhookSubscription",
31199
- canGet: true,
31200
- listFlags: []
31201
- }
32091
+ createInput: "CreateBlockTemplateInput",
32092
+ updateInput: "UpdateBlockTemplateInput"
32093
+ }),
32094
+ entityFromOperations({ list: webhookOperations.list, get: webhookOperations.get }, { name: "webhooks", returns: "WebhookSubscription", listFlags: [] })
31202
32095
  ];
31203
32096
  var entityByName = (name) => ENTITIES.find((e) => e.name === name);
31204
32097
  function entityOperations(e) {
@@ -31628,18 +32521,18 @@ var HEALTH_COMMAND_SPECS = [
31628
32521
  summary: "Whether your Google Health is connected, the last sync, and what stops it.",
31629
32522
  args: [],
31630
32523
  flags: [],
31631
- scopes: READ2,
32524
+ scopes: [...googleHealthOperations.status.scopes],
31632
32525
  returns: "GoogleHealthStatus",
31633
- notes: "GET /api/v1/health/google-health. Connecting needs Google's consent in a browser: Settings \u2192 Health in do. `failure` (reauth, not_linked, unavailable, other) says what stops the sync, with a title and a hint. `nutrition` says whether your food is written back to Google Health (turned on in do; each settled day once, one entry per meal type) and the last day written."
32526
+ notes: `${routeOf(googleHealthOperations.status)}. Connecting needs Google's consent in a browser: Settings \u2192 Health in do. \`failure\` (reauth, not_linked, unavailable, other) says what stops the sync, with a title and a hint. \`nutrition\` says whether your food is written back to Google Health (turned on in do; each settled day once, one entry per meal type) and the last day written.`
31634
32527
  },
31635
32528
  {
31636
32529
  path: ["health", "google", "sync"],
31637
32530
  summary: "Sync your Google Health now (sleep, resting heart rate, HRV, steps, workouts), as the morning sync does.",
31638
32531
  args: [],
31639
32532
  flags: [],
31640
- scopes: WRITE3,
32533
+ scopes: [...googleHealthOperations.sync.scopes],
31641
32534
  returns: "GoogleHealthSyncResult",
31642
- notes: "POST /api/v1/health/google-health/sync. Written and skipped per resource, and the status after it; a sync that stops answers 200 with `status.failure`. 409 before Google Health is connected, 403 while the server has it switched off."
32535
+ notes: `${routeOf(googleHealthOperations.sync)}. Written and skipped per resource, and the status after it; a sync that stops answers 200 with \`status.failure\`. 409 before Google Health is connected, 403 while the server has it switched off.`
31643
32536
  },
31644
32537
  {
31645
32538
  path: ["health", "withings", "status"],
@@ -31923,9 +32816,9 @@ var COMMAND_SPECS = [
31923
32816
  summary: "From earlier: planned tasks of the last 14 days that weren't done, newest first, and the day Later this week lands on.",
31924
32817
  args: [],
31925
32818
  flags: [{ name: "area", type: "string", description: "Only tasks in this Area (id)." }],
31926
- scopes: ["tasks:read"],
32819
+ scopes: [...todayOperations.fromEarlier.scopes],
31927
32820
  returns: "FromEarlierResponse",
31928
- notes: "GET /api/v1/today/from-earlier, from your local day. Nothing here is late: timed tasks, time blocks and agents' work never roll, and a plan older than 14 days just waits where it was filed. Answer with `do today answer`."
32821
+ notes: `${routeOf(todayOperations.fromEarlier)}, from your local day. Nothing here is late: timed tasks, time blocks and agents' work never roll, and a plan older than 14 days just waits where it was filed. Answer with \`do today answer\`.`
31929
32822
  },
31930
32823
  {
31931
32824
  path: ["today", "answer"],
@@ -32435,18 +33328,18 @@ var COMMAND_SPECS = [
32435
33328
  flags: [
32436
33329
  { name: "start", type: "string", description: "The week's Monday (YYYY-MM-DD). Required." }
32437
33330
  ],
32438
- scopes: ["calendar:read"],
33331
+ scopes: [...blockTemplateOperations.week.scopes],
32439
33332
  returns: "BlockTemplateInstance[]",
32440
- notes: "GET /api/v1/block-templates/week?start= (plan vs record, phase 1)."
33333
+ notes: `${routeOf(blockTemplateOperations.week)}?start= (plan vs record, phase 1).`
32441
33334
  },
32442
33335
  {
32443
33336
  path: ["block-templates", "defaults"],
32444
33337
  summary: "Give a member with no block templates the default week (sleep, morning, work, meals, a free evening). A no-op when they have templates.",
32445
33338
  args: [],
32446
33339
  flags: [],
32447
- scopes: ["calendar:write"],
33340
+ scopes: [...blockTemplateOperations.defaults.scopes],
32448
33341
  returns: "BlockTemplateView[]",
32449
- notes: "POST /api/v1/block-templates/defaults (plan vs record, phase 1)."
33342
+ notes: `${routeOf(blockTemplateOperations.defaults)} (plan vs record, phase 1).`
32450
33343
  },
32451
33344
  {
32452
33345
  path: ["time-blocks", "record"],
@@ -32584,18 +33477,18 @@ var COMMAND_SPECS = [
32584
33477
  },
32585
33478
  { name: "for", type: "string", description: "Only this member's plans (user id)." }
32586
33479
  ],
32587
- scopes: ["tasks:read"],
33480
+ scopes: [...planOperations.list.scopes],
32588
33481
  returns: "Plan[]",
32589
- notes: "GET /api/v1/plans (#912): the latest 20."
33482
+ notes: `${routeOf(planOperations.list)} (#912): the latest 20.`
32590
33483
  },
32591
33484
  {
32592
33485
  path: ["plans", "get"],
32593
33486
  summary: "One plan proposal with its items and each item's task as it is now.",
32594
33487
  args: [{ name: "id", required: true, description: "Plan id." }],
32595
33488
  flags: [],
32596
- scopes: ["tasks:read"],
33489
+ scopes: [...planOperations.get.scopes],
32597
33490
  returns: "Plan",
32598
- notes: "GET /api/v1/plans/{id} (#912)."
33491
+ notes: `${routeOf(planOperations.get)} (#912).`
32599
33492
  },
32600
33493
  {
32601
33494
  path: ["plans", "propose"],
@@ -32608,9 +33501,9 @@ var COMMAND_SPECS = [
32608
33501
  description: 'The CreatePlanInput as JSON: {"kind":"week","startsOn":"2026-10-19","note":"\u2026","items":[{"taskId":"\u2026","day":"2026-10-20","reason":"Deadline Friday"},{"taskId":"\u2026","at":"2026-10-21T09:00:00Z","durationMinutes":45},{"taskId":"\u2026","action":"someday"},{"taskId":"\u2026","action":"let_go"}],"blocks":[{"templateId":"\u2026","lifeKind":"meal","startAt":"2026-10-19T10:30:00Z","endAt":"2026-10-19T11:30:00Z"}]}. Blocks are life blocks (lay a week out with `do block-templates week`); a plan needs at least one item or block.'
32609
33502
  }
32610
33503
  ],
32611
- scopes: ["tasks:write"],
33504
+ scopes: [...planOperations.create.scopes],
32612
33505
  returns: "Plan",
32613
- notes: "POST /api/v1/plans (#912). A week starts on a Monday; days are the member's, times are ISO instants inside the week. `forUserId` names the member (default: the member the key acts for). A newer plan for the same week or day replaces the open one."
33506
+ notes: `${routeOf(planOperations.create)} (#912). A week starts on a Monday; days are the member's, times are ISO instants inside the week. \`forUserId\` names the member (default: the member the key acts for). A newer plan for the same week or day replaces the open one.`
32614
33507
  },
32615
33508
  {
32616
33509
  path: ["plans", "accept"],
@@ -32620,9 +33513,9 @@ var COMMAND_SPECS = [
32620
33513
  { name: "items", required: false, description: "Item ids (default: every proposed item)." }
32621
33514
  ],
32622
33515
  flags: [],
32623
- scopes: ["tasks:write"],
33516
+ scopes: [...planOperations.accept.scopes],
32624
33517
  returns: "Plan",
32625
- notes: "POST /api/v1/plans/{id}/accept (#912). Undo with `do plans undo`."
33518
+ notes: `${routeOf(planOperations.accept)} (#912). Undo with \`do plans undo\`.`
32626
33519
  },
32627
33520
  {
32628
33521
  path: ["plans", "dismiss"],
@@ -32632,9 +33525,9 @@ var COMMAND_SPECS = [
32632
33525
  { name: "items", required: false, description: "Item ids (default: every proposed item)." }
32633
33526
  ],
32634
33527
  flags: [],
32635
- scopes: ["tasks:write"],
33528
+ scopes: [...planOperations.dismiss.scopes],
32636
33529
  returns: "Plan",
32637
- notes: "POST /api/v1/plans/{id}/dismiss (#912). Nothing is written to a task."
33530
+ notes: `${routeOf(planOperations.dismiss)} (#912). Nothing is written to a task.`
32638
33531
  },
32639
33532
  {
32640
33533
  path: ["plans", "undo"],
@@ -32644,9 +33537,9 @@ var COMMAND_SPECS = [
32644
33537
  { name: "items", required: false, description: "Item ids (default: every answered item)." }
32645
33538
  ],
32646
33539
  flags: [],
32647
- scopes: ["tasks:write"],
33540
+ scopes: [...planOperations.undo.scopes],
32648
33541
  returns: "Plan",
32649
- notes: "POST /api/v1/plans/{id}/undo (#912). The items read as proposed again."
33542
+ notes: `${routeOf(planOperations.undo)} (#912). The items read as proposed again.`
32650
33543
  },
32651
33544
  {
32652
33545
  path: ["plan", "rules", "list"],
@@ -32660,9 +33553,9 @@ var COMMAND_SPECS = [
32660
33553
  description: "fixed_time, keep_free, meaning, whose, limit or note."
32661
33554
  }
32662
33555
  ],
32663
- scopes: ["planning:read"],
33556
+ scopes: [...planningRuleOperations.list.scopes],
32664
33557
  returns: "PlanningRule[]",
32665
- notes: "GET /api/v1/planning/rules. Oldest first; `addedByAgent` marks the ones an agent added."
33558
+ notes: `${routeOf(planningRuleOperations.list)}. Oldest first; \`addedByAgent\` marks the ones an agent added.`
32666
33559
  },
32667
33560
  {
32668
33561
  path: ["plan", "rules", "add"],
@@ -32680,9 +33573,9 @@ var COMMAND_SPECS = [
32680
33573
  description: "The member it is for (user id; default: the member the key acts for)."
32681
33574
  }
32682
33575
  ],
32683
- scopes: ["planning:write"],
33576
+ scopes: [...planningRuleOperations.create.scopes],
32684
33577
  returns: "PlanningRule",
32685
- notes: "POST /api/v1/planning/rules. `forUserId` (--for) names the member; a rule without --rule is a note."
33578
+ notes: `${routeOf(planningRuleOperations.create)}. \`forUserId\` (--for) names the member; a rule without --rule is a note.`
32686
33579
  },
32687
33580
  {
32688
33581
  path: ["plan", "rules", "update"],
@@ -32696,18 +33589,18 @@ var COMMAND_SPECS = [
32696
33589
  description: "The new structured part as JSON (see `plan rules add`), or `none` to make it a note."
32697
33590
  }
32698
33591
  ],
32699
- scopes: ["planning:write"],
33592
+ scopes: [...planningRuleOperations.update.scopes],
32700
33593
  returns: "PlanningRule",
32701
- notes: "PATCH /api/v1/planning/rules/{id}."
33594
+ notes: `${routeOf(planningRuleOperations.update)}.`
32702
33595
  },
32703
33596
  {
32704
33597
  path: ["plan", "rules", "remove"],
32705
33598
  summary: "Remove a planning rule (to the trash).",
32706
33599
  args: [{ name: "id", required: true, description: "Rule id." }],
32707
33600
  flags: [],
32708
- scopes: ["planning:write"],
33601
+ scopes: [...planningRuleOperations.remove.scopes],
32709
33602
  returns: "void",
32710
- notes: "DELETE /api/v1/planning/rules/{id}. Destructive: needs --allow-destructive."
33603
+ notes: `${routeOf(planningRuleOperations.remove)}. Destructive: needs --allow-destructive.`
32711
33604
  },
32712
33605
  {
32713
33606
  path: ["tasks", "ready"],
@@ -33828,7 +34721,7 @@ var COMMAND_SPECS = [
33828
34721
  summary: "List the Space's Agents (id + name).",
33829
34722
  args: [],
33830
34723
  flags: [],
33831
- scopes: ["agents:read"],
34724
+ scopes: [...agentOperations.list.scopes],
33832
34725
  returns: "AgentSummary",
33833
34726
  notes: "Creating an Agent and minting its keys stays a member's job in the app."
33834
34727
  },
@@ -34145,7 +35038,7 @@ var COMMAND_SPECS = [
34145
35038
  summary: "Show the caller's settings for the Space.",
34146
35039
  args: [],
34147
35040
  flags: [],
34148
- scopes: ["settings:read"],
35041
+ scopes: [...settingsOperations.get.scopes],
34149
35042
  returns: "UserSettings",
34150
35043
  notes: "Needs a key minted while logged in (it carries the user identity)."
34151
35044
  },
@@ -34154,7 +35047,7 @@ var COMMAND_SPECS = [
34154
35047
  summary: "Patch the caller's settings.",
34155
35048
  args: [],
34156
35049
  flags: [{ name: "data", type: "string", description: "JSON of fields to change." }],
34157
- scopes: ["settings:write"],
35050
+ scopes: [...settingsOperations.update.scopes],
34158
35051
  returns: "UserSettings"
34159
35052
  },
34160
35053
  {
@@ -34312,9 +35205,9 @@ var COMMAND_SPECS = [
34312
35205
  summary: "An asset's upkeep and repair Tasks, open ones first.",
34313
35206
  args: [{ name: "id", required: true, description: "Asset (Collection Item) ULID." }],
34314
35207
  flags: [],
34315
- scopes: ["collections:read"],
35208
+ scopes: [...collectionUpkeepOperations.list.scopes],
34316
35209
  returns: "AssetUpkeep",
34317
- notes: "GET /api/v1/collection/{id}/upkeep (LifeOS House). Returns { itemId, upkeep, repairs }."
35210
+ notes: `${routeOf(collectionUpkeepOperations.list)} (LifeOS House). Returns { itemId, upkeep, repairs }.`
34318
35211
  },
34319
35212
  {
34320
35213
  path: ["collection", "link-task"],
@@ -34324,9 +35217,9 @@ var COMMAND_SPECS = [
34324
35217
  { name: "taskId", required: true, description: "Task ULID." }
34325
35218
  ],
34326
35219
  flags: [{ name: "repair", type: "boolean", description: "A one-off repair, not upkeep." }],
34327
- scopes: ["collections:write"],
35220
+ scopes: [...collectionUpkeepOperations.link.scopes],
34328
35221
  returns: "AssetUpkeep",
34329
- notes: "POST /api/v1/collection/{id}/upkeep. Make the Task (recurrence, Place, Area) first. Finishing it writes the asset's service / repair Entry by itself."
35222
+ notes: `${routeOf(collectionUpkeepOperations.link)}. Make the Task (recurrence, Place, Area) first. Finishing it writes the asset's service / repair Entry by itself.`
34330
35223
  },
34331
35224
  {
34332
35225
  path: ["collection", "unlink-task"],
@@ -34336,7 +35229,7 @@ var COMMAND_SPECS = [
34336
35229
  { name: "taskId", required: true, description: "Task ULID." }
34337
35230
  ],
34338
35231
  flags: [],
34339
- scopes: ["collections:write"],
35232
+ scopes: [...collectionUpkeepOperations.unlink.scopes],
34340
35233
  returns: "AssetUpkeep"
34341
35234
  },
34342
35235
  {
@@ -35183,6 +36076,35 @@ var COMMAND_SPECS = [
35183
36076
  returns: "MoneyYear",
35184
36077
  notes: "GET /api/v1/money/year. totals (income, spent, saved) and lastYear (the same months a year earlier, null with nothing booked then); months Jan\u2013Dec; spending and income by category, biggest first, each with lastYear; recurring streams with thisYear, lastYear and change (new \xB7 stopped \xB7 up \xB7 down \xB7 same); goals with what went toward them this year. A year under way counts to this month (through)."
35185
36078
  },
36079
+ {
36080
+ path: ["money", "net-worth"],
36081
+ summary: "Net worth now and its month-end line (the accounts that count in it).",
36082
+ args: [],
36083
+ flags: [
36084
+ { name: "scope", type: "string", description: "all (default) | space | owner." },
36085
+ {
36086
+ name: "months",
36087
+ type: "string",
36088
+ description: "How many months the line covers (2\u201360, default 12)."
36089
+ }
36090
+ ],
36091
+ scopes: ["finance:read"],
36092
+ returns: "MoneyNetWorth",
36093
+ notes: "GET /api/v1/money/net-worth. total, have and owe this month; points per month (oldest first, total null before any value); accounts biggest first with value, source (balance \xB7 ledger \xB7 manual) and the month it's from. A loan or a card always counts as owed."
36094
+ },
36095
+ {
36096
+ path: ["money", "net-worth-set"],
36097
+ summary: "Set an account's value for a month by hand (a house, a car); again replaces it.",
36098
+ args: [{ name: "accountId", required: true, description: "Account ULID." }],
36099
+ flags: [
36100
+ { name: "value", type: "string", description: "The value, e.g. 310000 or 1234.56." },
36101
+ { name: "month", type: "string", description: "The month, YYYY-MM (default this month)." },
36102
+ { name: "data", type: "string", description: "SetMoneyNetWorthValueInput JSON instead." }
36103
+ ],
36104
+ scopes: ["finance:write"],
36105
+ returns: "MoneySnapshot",
36106
+ notes: "PUT /api/v1/money/net-worth/values. A month ahead is refused. This month's value also becomes the account's balance unless a bank link keeps it. `do money net-worth-set <accountId> --value 310000`."
36107
+ },
35186
36108
  {
35187
36109
  path: ["money", "goals"],
35188
36110
  summary: "List money goals: what's saved toward each, soonest first.",
@@ -35932,7 +36854,7 @@ var COMMAND_SPECS = [
35932
36854
  summary: "Your Google Contacts, to pick from: who they'd merge into and what importing adds.",
35933
36855
  args: [],
35934
36856
  flags: [{ name: "q", type: "string", description: "A name or email to narrow to." }],
35935
- scopes: ["contacts:read"],
36857
+ scopes: [...googleContactsOperations.list.scopes],
35936
36858
  returns: "GoogleContactsList",
35937
36859
  notes: "Connecting needs a browser (People \u2192 Import). Not connected: candidates is empty and status.connected false. A key with no member is forbidden."
35938
36860
  },
@@ -35947,7 +36869,7 @@ var COMMAND_SPECS = [
35947
36869
  }
35948
36870
  ],
35949
36871
  flags: [],
35950
- scopes: ["contacts:write"],
36872
+ scopes: [...googleContactsOperations.import.scopes],
35951
36873
  returns: "GoogleContactsImportResult",
35952
36874
  notes: "Each merges into the contact it matches (email, then phone, then a unique name) and fills in only a missing email, phone or birthday; the rest become new contacts. Ids Google no longer has come back as missing. Not connected is a conflict."
35953
36875
  },
@@ -36056,7 +36978,7 @@ var COMMAND_SPECS = [
36056
36978
  description: 'JSON AddRsvpPresetsInput, e.g. {"guestListId":"01\u2026","presets":["diet","message"]}'
36057
36979
  }
36058
36980
  ],
36059
- scopes: ["contacts:write"],
36981
+ scopes: [...rsvpQuestionOperations.presets.scopes],
36060
36982
  returns: "RsvpQuestion"
36061
36983
  },
36062
36984
  {
@@ -36067,7 +36989,7 @@ var COMMAND_SPECS = [
36067
36989
  { name: "list", type: "string", description: "The guest list's id (required)." },
36068
36990
  { name: "household", type: "string", description: "Only this household's answers." }
36069
36991
  ],
36070
- scopes: ["contacts:read"],
36992
+ scopes: [...rsvpAnswerOperations.list.scopes],
36071
36993
  returns: "RsvpAnswer"
36072
36994
  },
36073
36995
  {
@@ -36081,7 +37003,7 @@ var COMMAND_SPECS = [
36081
37003
  description: 'JSON SetRsvpAnswersInput, e.g. {"guestListId":"01\u2026","householdId":"01\u2026","answers":[{"questionId":"01\u2026","value":true},{"questionId":"01\u2026","guestId":"01\u2026","value":["vegan"]}]}'
36082
37004
  }
36083
37005
  ],
36084
- scopes: ["contacts:write"],
37006
+ scopes: [...rsvpAnswerOperations.set.scopes],
36085
37007
  returns: "RsvpAnswer"
36086
37008
  },
36087
37009
  {
@@ -36128,7 +37050,7 @@ var COMMAND_SPECS = [
36128
37050
  summary: "A guest list's mail campaigns (save-the-dates, invitations), newest first.",
36129
37051
  args: [],
36130
37052
  flags: [{ name: "list", type: "string", description: "The guest list's id (required)." }],
36131
- scopes: ["contacts:read"],
37053
+ scopes: [...mailCampaignOperations.list.scopes],
36132
37054
  returns: "MailCampaignView",
36133
37055
  notes: "Each campaign carries the households it reaches (a draft: who it would go to) with their status, its mails by status, and readyToSend."
36134
37056
  },
@@ -36137,7 +37059,7 @@ var COMMAND_SPECS = [
36137
37059
  summary: "One mail campaign, with its households and how far it got.",
36138
37060
  args: [{ name: "id", required: true, description: "The campaign's id." }],
36139
37061
  flags: [],
36140
- scopes: ["contacts:read"],
37062
+ scopes: [...mailCampaignOperations.get.scopes],
36141
37063
  returns: "MailCampaignView"
36142
37064
  },
36143
37065
  {
@@ -36151,7 +37073,7 @@ var COMMAND_SPECS = [
36151
37073
  description: 'JSON CreateMailCampaignInput, e.g. {"guestListId":"01\u2026","kind":"save_the_date","template":"message","data":{"nl":{"subject":"\u2026","heading":"\u2026","paragraphs":["\u2026"]},"pt":{\u2026},"footer":{"deleteBy":"2027-12-31"}},"replyTo":"hallo@example.com"}'
36152
37074
  }
36153
37075
  ],
36154
- scopes: ["contacts:write"],
37076
+ scopes: [...mailCampaignOperations.create.scopes],
36155
37077
  returns: "MailCampaignView",
36156
37078
  notes: "Drafting only: a member previews it, sends a test to the Space's members and sends it from the Mail tab. An agent can never test or send."
36157
37079
  },
@@ -36166,7 +37088,7 @@ var COMMAND_SPECS = [
36166
37088
  description: 'JSON UpdateMailCampaignInput, e.g. {"data":{"nl":{\u2026}}}'
36167
37089
  }
36168
37090
  ],
36169
- scopes: ["contacts:write"],
37091
+ scopes: [...mailCampaignOperations.update.scopes],
36170
37092
  returns: "MailCampaignView",
36171
37093
  notes: "Only a draft changes (409 otherwise); an edit makes the last test stale."
36172
37094
  },
@@ -36175,7 +37097,7 @@ var COMMAND_SPECS = [
36175
37097
  summary: "Draw a campaign's mail as its first household would get it.",
36176
37098
  args: [{ name: "id", required: true, description: "The campaign's id." }],
36177
37099
  flags: [{ name: "language", type: "string", description: "nl (default) or pt." }],
36178
- scopes: ["contacts:read"],
37100
+ scopes: [...mailCampaignOperations.preview.scopes],
36179
37101
  returns: "MailPreview"
36180
37102
  },
36181
37103
  {
@@ -36195,7 +37117,7 @@ var COMMAND_SPECS = [
36195
37117
  {
36196
37118
  name: "kind",
36197
37119
  type: "string",
36198
- description: "caterer (meals and diets per table), venue (who sits where), church (who comes to the ceremony), answers (every RSVP answer, a row per guest), floor-plan (PDF) or invitations (PDF: a card per household with a live RSVP link, its QR and code)."
37120
+ description: "caterer (meals and diets per table), venue (who sits where), church (who comes to the ceremony), answers (every RSVP answer, a row per guest), floor-plan (PDF), invitations (PDF: a card per household with a live RSVP link, its QR and code) or printer (CSV of those cards: household, address lines, country, language, code, URL)."
36199
37121
  },
36200
37122
  {
36201
37123
  name: "out",
@@ -38767,7 +39689,7 @@ var HANDLERS = {
38767
39689
  "today earlier": async ({ flags, io, requireClient }) => {
38768
39690
  const area = flagString(flags, "area");
38769
39691
  return {
38770
- data: await requireClient().get("/today/from-earlier", {
39692
+ data: await requireClient().get(pathOf(todayOperations.fromEarlier), {
38771
39693
  query: { from: localDayStart(io.now()).toISOString(), areaId: area }
38772
39694
  }),
38773
39695
  render: renderFromEarlier
@@ -39707,12 +40629,12 @@ var HANDLERS = {
39707
40629
  if (start === undefined)
39708
40630
  throw usageError("block-templates week needs --start");
39709
40631
  return {
39710
- data: await requireClient().get("/block-templates/week", { query: { start } }),
40632
+ data: await requireClient().get(pathOf(blockTemplateOperations.week), { query: { start } }),
39711
40633
  render: renderRecord
39712
40634
  };
39713
40635
  },
39714
40636
  "block-templates defaults": async ({ requireClient }) => ({
39715
- data: await requireClient().post("/block-templates/defaults", {}),
40637
+ data: await requireClient().post(pathOf(blockTemplateOperations.defaults), {}),
39716
40638
  render: renderRecord
39717
40639
  }),
39718
40640
  "time-blocks record": async ({ args, requireClient }) => {
@@ -39797,7 +40719,7 @@ var HANDLERS = {
39797
40719
  };
39798
40720
  },
39799
40721
  "plans list": async ({ flags, requireClient }) => {
39800
- const plans = await requireClient().get("/plans", {
40722
+ const plans = await requireClient().get(pathOf(planOperations.list), {
39801
40723
  query: {
39802
40724
  kind: flagString(flags, "kind"),
39803
40725
  status: flagString(flags, "status"),
@@ -39809,19 +40731,25 @@ var HANDLERS = {
39809
40731
  },
39810
40732
  "plans get": async ({ args, requireClient }) => {
39811
40733
  const id = requireArg(args, 0, "plan id");
39812
- return { data: await requireClient().get(`/plans/${id}`), render: renderPlan };
40734
+ return {
40735
+ data: await requireClient().get(pathOf(planOperations.get, { id })),
40736
+ render: renderPlan
40737
+ };
39813
40738
  },
39814
40739
  "plans propose": async ({ flags, requireClient }) => {
39815
40740
  if (flagString(flags, "data") === undefined)
39816
40741
  throw usageError("plans propose needs --data");
39817
40742
  const body = parseInput(createPlanInputSchema, readDataFlag(flags));
39818
- return { data: await requireClient().post("/plans", body), render: renderPlan };
40743
+ return {
40744
+ data: await requireClient().post(pathOf(planOperations.create), body),
40745
+ render: renderPlan
40746
+ };
39819
40747
  },
39820
40748
  "plans accept": async (ctx) => planDecision(ctx, "accept"),
39821
40749
  "plans dismiss": async (ctx) => planDecision(ctx, "dismiss"),
39822
40750
  "plans undo": async (ctx) => planDecision(ctx, "undo"),
39823
40751
  "plan rules list": async ({ flags, requireClient }) => {
39824
- const rules = await requireClient().get("/planning/rules", {
40752
+ const rules = await requireClient().get(pathOf(planningRuleOperations.list), {
39825
40753
  query: { forUserId: flagString(flags, "for"), kind: flagString(flags, "kind") }
39826
40754
  });
39827
40755
  return { data: rules, render: renderPlanningRules };
@@ -39838,7 +40766,7 @@ var HANDLERS = {
39838
40766
  ...flagString(flags, "for") ? { forUserId: flagString(flags, "for") } : {}
39839
40767
  });
39840
40768
  return {
39841
- data: await requireClient().post("/planning/rules", body),
40769
+ data: await requireClient().post(pathOf(planningRuleOperations.create), body),
39842
40770
  render: renderPlanningRule
39843
40771
  };
39844
40772
  },
@@ -39850,14 +40778,14 @@ var HANDLERS = {
39850
40778
  ...rule !== undefined ? { rule } : {}
39851
40779
  });
39852
40780
  return {
39853
- data: await requireClient().patch(`/planning/rules/${id}`, body),
40781
+ data: await requireClient().patch(pathOf(planningRuleOperations.update, { id }), body),
39854
40782
  render: renderPlanningRule
39855
40783
  };
39856
40784
  },
39857
40785
  "plan rules remove": async ({ args, flags, requireClient }) => {
39858
40786
  const id = requireArg(args, 0, "rule id");
39859
40787
  assertDestructiveAllowed(flags, "remove a planning rule");
39860
- await requireClient().delete(`/planning/rules/${id}`);
40788
+ await requireClient().delete(pathOf(planningRuleOperations.remove, { id }));
39861
40789
  return { data: { id, removed: true }, render: () => `Removed rule ${id}.` };
39862
40790
  },
39863
40791
  "tasks ready": async ({ args, flags, requireClient }) => {
@@ -40099,7 +41027,7 @@ var HANDLERS = {
40099
41027
  };
40100
41028
  },
40101
41029
  "agents list": async ({ requireClient }) => ({
40102
- data: await requireClient().getPage("/agents"),
41030
+ data: await requireClient().getPage(pathOf(agentOperations.list)),
40103
41031
  render: renderList
40104
41032
  }),
40105
41033
  "agent connection get": async ({ requireClient }) => ({
@@ -40215,12 +41143,15 @@ var HANDLERS = {
40215
41143
  };
40216
41144
  },
40217
41145
  "settings get": async ({ requireClient }) => ({
40218
- data: await requireClient().get("/settings"),
41146
+ data: await requireClient().get(pathOf(settingsOperations.get)),
40219
41147
  render: renderRecord
40220
41148
  }),
40221
41149
  "settings update": async ({ flags, requireClient }) => {
40222
41150
  const body = parseInput(updateUserSettingsInputSchema, readDataFlag(flags));
40223
- return { data: await requireClient().patch("/settings", body), render: renderRecord };
41151
+ return {
41152
+ data: await requireClient().patch(pathOf(settingsOperations.update), body),
41153
+ render: renderRecord
41154
+ };
40224
41155
  },
40225
41156
  "modules list": async ({ requireClient }) => {
40226
41157
  const settings = await requireClient().get("/settings");
@@ -40314,7 +41245,10 @@ var HANDLERS = {
40314
41245
  },
40315
41246
  "collection upkeep": async ({ args, requireClient }) => {
40316
41247
  const id = requireArg(args, 0, "item id");
40317
- return { data: await requireClient().get(`/collection/${id}/upkeep`), render: renderRecord };
41248
+ return {
41249
+ data: await requireClient().get(pathOf(collectionUpkeepOperations.list, { id })),
41250
+ render: renderRecord
41251
+ };
40318
41252
  },
40319
41253
  "collection link-task": async ({ args, flags, requireClient }) => {
40320
41254
  const id = requireArg(args, 0, "item id");
@@ -40324,7 +41258,7 @@ var HANDLERS = {
40324
41258
  kind: flagBool(flags, "repair") ? "repairs" : "maintains"
40325
41259
  });
40326
41260
  return {
40327
- data: await requireClient().post(`/collection/${id}/upkeep`, body),
41261
+ data: await requireClient().post(pathOf(collectionUpkeepOperations.link, { id }), body),
40328
41262
  render: renderRecord
40329
41263
  };
40330
41264
  },
@@ -40332,7 +41266,7 @@ var HANDLERS = {
40332
41266
  const id = requireArg(args, 0, "item id");
40333
41267
  const taskId = requireArg(args, 1, "task id");
40334
41268
  return {
40335
- data: await requireClient().delete(`/collection/${id}/upkeep/${taskId}`),
41269
+ data: await requireClient().delete(pathOf(collectionUpkeepOperations.unlink, { id, taskId })),
40336
41270
  render: renderRecord
40337
41271
  };
40338
41272
  },
@@ -40897,6 +41831,23 @@ var HANDLERS = {
40897
41831
  });
40898
41832
  return { data: await requireClient().post("/money/budget/copy", body), render: renderRecord };
40899
41833
  },
41834
+ "money net-worth": async ({ flags, requireClient }) => ({
41835
+ data: await requireClient().get("/money/net-worth", {
41836
+ query: { scope: flagString(flags, "scope"), months: flagString(flags, "months") }
41837
+ }),
41838
+ render: renderRecord
41839
+ }),
41840
+ "money net-worth-set": async ({ args, flags, requireClient }) => {
41841
+ const body = flagString(flags, "data") !== undefined ? parseInput(setMoneyNetWorthValueInputSchema, readDataFlag(flags)) : parseInput(setMoneyNetWorthValueInputSchema, {
41842
+ accountId: requireArg(args, 0, "account id"),
41843
+ value: requireFlag(flags, "value", "money net-worth-set needs --value <amount>"),
41844
+ month: flagString(flags, "month")
41845
+ });
41846
+ return {
41847
+ data: await requireClient().put("/money/net-worth/values", body),
41848
+ render: renderRecord
41849
+ };
41850
+ },
40900
41851
  "money goals": async ({ requireClient }) => ({
40901
41852
  data: await requireClient().get("/money/goals"),
40902
41853
  render: renderList
@@ -41360,11 +42311,11 @@ var HANDLERS = {
41360
42311
  render: renderRecord
41361
42312
  }),
41362
42313
  "health google status": async ({ requireClient }) => ({
41363
- data: await requireClient().get("/health/google-health"),
42314
+ data: await requireClient().get(pathOf(googleHealthOperations.status)),
41364
42315
  render: renderRecord
41365
42316
  }),
41366
42317
  "health google sync": async ({ requireClient }) => ({
41367
- data: await requireClient().post("/health/google-health/sync", {}),
42318
+ data: await requireClient().post(pathOf(googleHealthOperations.sync), {}),
41368
42319
  render: renderRecord
41369
42320
  }),
41370
42321
  "health consent": async ({ requireClient }) => ({
@@ -41595,7 +42546,9 @@ var HANDLERS = {
41595
42546
  "people google": async ({ flags, requireClient }) => {
41596
42547
  const q = flagString(flags, "q");
41597
42548
  return {
41598
- data: await requireClient().get("/people/google-contacts", { query: q ? { q } : {} }),
42549
+ data: await requireClient().get(pathOf(googleContactsOperations.list), {
42550
+ query: q ? { q } : {}
42551
+ }),
41599
42552
  render: renderRecord
41600
42553
  };
41601
42554
  },
@@ -41605,7 +42558,7 @@ var HANDLERS = {
41605
42558
  }
41606
42559
  const body = parseInput(importGoogleContactsInputSchema, { resourceNames: args });
41607
42560
  return {
41608
- data: await requireClient().post("/people/google-contacts/import", body),
42561
+ data: await requireClient().post(pathOf(googleContactsOperations.import), body),
41609
42562
  render: renderRecord
41610
42563
  };
41611
42564
  },
@@ -41753,7 +42706,7 @@ var HANDLERS = {
41753
42706
  "rsvp-questions presets": async ({ flags, requireClient }) => {
41754
42707
  const body = parseInput(addRsvpPresetsInputSchema, readDataFlag(flags));
41755
42708
  return {
41756
- data: await requireClient().post("/rsvp-questions/presets", body),
42709
+ data: await requireClient().post(pathOf(rsvpQuestionOperations.presets), body),
41757
42710
  render: renderRecord
41758
42711
  };
41759
42712
  },
@@ -41766,11 +42719,17 @@ var HANDLERS = {
41766
42719
  guestListId: list,
41767
42720
  ...household ? { householdId: household } : {}
41768
42721
  });
41769
- return { data: await requireClient().get("/rsvp-answers", { query }), render: renderRecord };
42722
+ return {
42723
+ data: await requireClient().get(pathOf(rsvpAnswerOperations.list), { query }),
42724
+ render: renderRecord
42725
+ };
41770
42726
  },
41771
42727
  "rsvp-answers set": async ({ flags, requireClient }) => {
41772
42728
  const body = parseInput(setRsvpAnswersInputSchema, readDataFlag(flags));
41773
- return { data: await requireClient().put("/rsvp-answers", body), render: renderRecord };
42729
+ return {
42730
+ data: await requireClient().put(pathOf(rsvpAnswerOperations.set), body),
42731
+ render: renderRecord
42732
+ };
41774
42733
  },
41775
42734
  "seating-proposals list": async ({ flags, requireClient }) => {
41776
42735
  const list = flagString(flags, "list");
@@ -41809,23 +42768,31 @@ var HANDLERS = {
41809
42768
  throw usageError("mail-campaigns list needs --list <guest list id>");
41810
42769
  parseInput(mailCampaignsQuerySchema, { guestListId: list });
41811
42770
  return {
41812
- data: await requireClient().get("/mail-campaigns", { query: { guestListId: list } }),
42771
+ data: await requireClient().get(pathOf(mailCampaignOperations.list), {
42772
+ query: { guestListId: list }
42773
+ }),
41813
42774
  render: renderRecord
41814
42775
  };
41815
42776
  },
41816
42777
  "mail-campaigns get": async ({ args, requireClient }) => {
41817
42778
  const id = requireArg(args, 0, "campaign id");
41818
- return { data: await requireClient().get(`/mail-campaigns/${id}`), render: renderRecord };
42779
+ return {
42780
+ data: await requireClient().get(pathOf(mailCampaignOperations.get, { id })),
42781
+ render: renderRecord
42782
+ };
41819
42783
  },
41820
42784
  "mail-campaigns create": async ({ flags, requireClient }) => {
41821
42785
  const body = parseInput(createMailCampaignInputSchema, { id: ulid3(), ...readDataFlag(flags) });
41822
- return { data: await requireClient().post("/mail-campaigns", body), render: renderRecord };
42786
+ return {
42787
+ data: await requireClient().post(pathOf(mailCampaignOperations.create), body),
42788
+ render: renderRecord
42789
+ };
41823
42790
  },
41824
42791
  "mail-campaigns update": async ({ args, flags, requireClient }) => {
41825
42792
  const id = requireArg(args, 0, "campaign id");
41826
42793
  const body = parseInput(updateMailCampaignInputSchema, readDataFlag(flags));
41827
42794
  return {
41828
- data: await requireClient().patch(`/mail-campaigns/${id}`, body),
42795
+ data: await requireClient().patch(pathOf(mailCampaignOperations.update, { id }), body),
41829
42796
  render: renderRecord
41830
42797
  };
41831
42798
  },
@@ -41835,7 +42802,7 @@ var HANDLERS = {
41835
42802
  language: flagString(flags, "language") ?? "nl"
41836
42803
  });
41837
42804
  return {
41838
- data: await requireClient().get(`/mail-campaigns/${id}/preview`, { query }),
42805
+ data: await requireClient().get(pathOf(mailCampaignOperations.preview, { id }), { query }),
41839
42806
  render: renderRecord
41840
42807
  };
41841
42808
  },
@@ -42310,7 +43277,10 @@ async function planDecision({ args, requireClient }, verb) {
42310
43277
  const id = requireArg(args, 0, "plan id");
42311
43278
  const itemIds = args.slice(1);
42312
43279
  const body = parseInput(planDecisionInputSchema, itemIds.length > 0 ? { itemIds } : {});
42313
- return { data: await requireClient().post(`/plans/${id}/${verb}`, body), render: renderPlan };
43280
+ return {
43281
+ data: await requireClient().post(pathOf(planOperations[verb], { id }), body),
43282
+ render: renderPlan
43283
+ };
42314
43284
  }
42315
43285
  function parseInput(schema, value) {
42316
43286
  const result = schema.safeParse(value);