@robbeverhelst/do 1.303.0 → 1.305.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 +2135 -320
  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(),
@@ -25976,10 +26108,16 @@ var moneyTransactionViewSchema = moneyTransactionSchema.omit({ dedupHash: true }
25976
26108
  nodeId: ulidSchema,
25977
26109
  kind: moneyLinkKindSchema,
25978
26110
  title: string2(),
25979
- type: string2()
26111
+ type: string2(),
26112
+ warrantyEnds: keyDateDaySchema.nullable().default(null)
25980
26113
  })).default([]),
25981
26114
  recurring: string2().nullable().default(null),
25982
- contact: object({ id: ulidSchema, name: string2(), payments: number2().int().nonnegative() }).nullable().default(null),
26115
+ contact: object({
26116
+ id: ulidSchema,
26117
+ name: string2(),
26118
+ kind: _enum2(["person", "organization"]).default("person"),
26119
+ payments: number2().int().nonnegative()
26120
+ }).nullable().default(null),
25983
26121
  split: object({
25984
26122
  parts: number2().int().positive(),
25985
26123
  even: boolean3(),
@@ -27249,6 +27387,51 @@ var steamSyncResultSchema = object({
27249
27387
  games: array(steamGameSyncSchema)
27250
27388
  });
27251
27389
 
27390
+ // ../../packages/schema/src/contracts/money-net-worth.ts
27391
+ var amount5 = string2();
27392
+ var moneySnapshotSourceSchema = _enum2(moneySnapshotSource.enumValues);
27393
+ var moneyNetWorthQuerySchema = object({
27394
+ scope: moneyBudgetScopeSchema.optional(),
27395
+ months: exports_coerce.number().int().min(2).max(60).optional()
27396
+ });
27397
+ var moneyNetWorthPointSchema = object({
27398
+ month: moneyMonthSchema,
27399
+ total: amount5.nullable(),
27400
+ have: amount5.nullable(),
27401
+ owe: amount5.nullable()
27402
+ });
27403
+ var moneyNetWorthAccountSchema = object({
27404
+ accountId: ulidSchema,
27405
+ title: string2(),
27406
+ kind: moneyAccountKindSchema,
27407
+ visibility: _enum2(NODE_VISIBILITY_VALUES),
27408
+ currency: string2(),
27409
+ value: amount5,
27410
+ source: moneySnapshotSourceSchema,
27411
+ month: moneyMonthSchema
27412
+ });
27413
+ var moneyNetWorthSchema = object({
27414
+ scope: moneyBudgetScopeSchema,
27415
+ month: moneyMonthSchema,
27416
+ total: amount5,
27417
+ have: amount5,
27418
+ owe: amount5,
27419
+ points: array(moneyNetWorthPointSchema),
27420
+ accounts: array(moneyNetWorthAccountSchema)
27421
+ });
27422
+ var setMoneyNetWorthValueInputSchema = object({
27423
+ accountId: ulidSchema,
27424
+ value: moneyAmountSchema,
27425
+ month: moneyMonthSchema.optional()
27426
+ }).strict();
27427
+ var moneySnapshotSchema = object({
27428
+ id: ulidSchema,
27429
+ accountId: ulidSchema,
27430
+ month: moneyMonthSchema,
27431
+ value: amount5,
27432
+ source: moneySnapshotSourceSchema
27433
+ });
27434
+
27252
27435
  // ../../packages/schema/src/contracts/money-splits.ts
27253
27436
  var moneyPartySchema = union([
27254
27437
  object({ userId: ulidSchema }).strict(),
@@ -27990,7 +28173,8 @@ var GUEST_EXPORT_KINDS = [
27990
28173
  "floor-plan",
27991
28174
  "church",
27992
28175
  "answers",
27993
- "invitations"
28176
+ "invitations",
28177
+ "printer"
27994
28178
  ];
27995
28179
  var guestExportKindSchema = _enum2(GUEST_EXPORT_KINDS);
27996
28180
  var thankYouGiftSchema = object({
@@ -28576,6 +28760,209 @@ function scopeGranted(granted, required) {
28576
28760
  return granted.includes(FULL_ACCESS_SCOPE);
28577
28761
  }
28578
28762
 
28763
+ // ../../packages/schema/src/net/ssrf.ts
28764
+ var BLOCKED_HOST_SUFFIXES = [".internal", ".local", ".localhost"];
28765
+ var BLOCKED_HOSTNAMES = new Set(["localhost"]);
28766
+ function parseIpv4(host) {
28767
+ const parts = host.split(".");
28768
+ if (parts.length !== 4)
28769
+ return null;
28770
+ let value = 0;
28771
+ for (const part of parts) {
28772
+ if (!/^\d{1,3}$/.test(part))
28773
+ return null;
28774
+ const octet = Number(part);
28775
+ if (octet > 255)
28776
+ return null;
28777
+ value = value * 256 + octet;
28778
+ }
28779
+ return value >>> 0;
28780
+ }
28781
+ function isBlockedIpv4(ip) {
28782
+ const inRange = (base, bits) => {
28783
+ const baseIp = parseIpv4(base);
28784
+ if (baseIp === null)
28785
+ return false;
28786
+ const mask = bits === 0 ? 0 : 4294967295 << 32 - bits >>> 0;
28787
+ return (ip & mask) === (baseIp & mask);
28788
+ };
28789
+ 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);
28790
+ }
28791
+ function parseIpv6(host) {
28792
+ let text = host;
28793
+ const zone = text.indexOf("%");
28794
+ if (zone !== -1)
28795
+ text = text.slice(0, zone);
28796
+ if (!text.includes(":"))
28797
+ return null;
28798
+ const lastColon = text.lastIndexOf(":");
28799
+ const tail = text.slice(lastColon + 1);
28800
+ if (tail.includes(".")) {
28801
+ const v4 = parseIpv4(tail);
28802
+ if (v4 === null)
28803
+ return null;
28804
+ const hex = `${(v4 >>> 16 & 65535).toString(16)}:${(v4 & 65535).toString(16)}`;
28805
+ text = text.slice(0, lastColon + 1) + hex;
28806
+ }
28807
+ const halves = text.split("::");
28808
+ if (halves.length > 2)
28809
+ return null;
28810
+ const toGroups = (segment) => {
28811
+ if (segment === "")
28812
+ return [];
28813
+ const out = [];
28814
+ for (const g of segment.split(":")) {
28815
+ if (!/^[0-9a-fA-F]{1,4}$/.test(g))
28816
+ return null;
28817
+ out.push(Number.parseInt(g, 16));
28818
+ }
28819
+ return out;
28820
+ };
28821
+ const head = toGroups(halves[0] ?? "");
28822
+ if (head === null)
28823
+ return null;
28824
+ let groups;
28825
+ if (halves.length === 2) {
28826
+ const back = toGroups(halves[1] ?? "");
28827
+ if (back === null)
28828
+ return null;
28829
+ const fill = 8 - head.length - back.length;
28830
+ if (fill < 0)
28831
+ return null;
28832
+ groups = [...head, ...Array(fill).fill(0), ...back];
28833
+ } else {
28834
+ groups = head;
28835
+ }
28836
+ return groups.length === 8 ? groups : null;
28837
+ }
28838
+ function isBlockedIpv6(groups) {
28839
+ const isZero = groups.every((g) => g === 0);
28840
+ if (isZero)
28841
+ return true;
28842
+ if (groups.slice(0, 7).every((g) => g === 0) && groups[7] === 1)
28843
+ return true;
28844
+ const isV4Mapped = groups.slice(0, 5).every((g) => g === 0) && groups[5] === 65535;
28845
+ const isV4Compat = groups.slice(0, 6).every((g) => g === 0);
28846
+ if (isV4Mapped || isV4Compat) {
28847
+ const v4 = ((groups[6] ?? 0) << 16 | (groups[7] ?? 0)) >>> 0;
28848
+ return isBlockedIpv4(v4);
28849
+ }
28850
+ const first = groups[0] ?? 0;
28851
+ if ((first & 65024) === 64512)
28852
+ return true;
28853
+ if ((first & 65472) === 65152)
28854
+ return true;
28855
+ if ((first & 65280) === 65280)
28856
+ return true;
28857
+ return false;
28858
+ }
28859
+ function isBlockedIp(host) {
28860
+ const bare = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
28861
+ const v4 = parseIpv4(bare);
28862
+ if (v4 !== null)
28863
+ return isBlockedIpv4(v4);
28864
+ const v6 = parseIpv6(bare);
28865
+ if (v6 !== null)
28866
+ return isBlockedIpv6(v6);
28867
+ return false;
28868
+ }
28869
+
28870
+ class BlockedWebhookUrlError extends Error {
28871
+ constructor(reason) {
28872
+ super(`webhook url rejected: ${reason}`);
28873
+ this.name = "BlockedWebhookUrlError";
28874
+ }
28875
+ }
28876
+ function bareHost(hostname) {
28877
+ return hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
28878
+ }
28879
+ function isInternalHost(hostname) {
28880
+ const host = hostname.toLowerCase();
28881
+ if (BLOCKED_HOSTNAMES.has(host) || BLOCKED_HOST_SUFFIXES.some((s) => host.endsWith(s))) {
28882
+ return true;
28883
+ }
28884
+ return isBlockedIp(host);
28885
+ }
28886
+ function assertSafeEgressUrl(rawUrl, policy) {
28887
+ let url;
28888
+ try {
28889
+ url = new URL(rawUrl);
28890
+ } catch {
28891
+ throw policy.fail("not a valid URL");
28892
+ }
28893
+ if (!policy.protocols.includes(url.protocol)) {
28894
+ const allowsHttp = policy.protocols.includes("http:");
28895
+ throw policy.fail(`only ${allowsHttp ? "http(s)" : "https"} URLs are allowed`);
28896
+ }
28897
+ if (url.username || url.password) {
28898
+ throw policy.fail("credentials in the URL are not allowed");
28899
+ }
28900
+ if (isInternalHost(url.hostname)) {
28901
+ const kind = isBlockedIp(url.hostname) ? "a private or reserved address" : "internal";
28902
+ throw policy.fail(`host '${url.hostname}' is ${kind}`);
28903
+ }
28904
+ return url;
28905
+ }
28906
+ function webhookUrlAllowlistFromEnv() {
28907
+ const env = globalThis.process?.env;
28908
+ const raw = env?.WEBHOOK_URL_ALLOWLIST;
28909
+ if (!raw)
28910
+ return [];
28911
+ return raw.split(",").map((h) => h.trim().toLowerCase()).filter(Boolean);
28912
+ }
28913
+ function isAllowlistedHost(hostname, allowedHosts) {
28914
+ const host = hostname.toLowerCase();
28915
+ const bare = bareHost(host);
28916
+ return allowedHosts.some((h) => h === host || h === bare);
28917
+ }
28918
+ function assertSafeWebhookUrl(rawUrl, opts = {}) {
28919
+ let url;
28920
+ try {
28921
+ url = new URL(rawUrl);
28922
+ } catch {
28923
+ throw new BlockedWebhookUrlError("not a valid URL");
28924
+ }
28925
+ const allowedHosts = opts.allowedHosts ?? webhookUrlAllowlistFromEnv();
28926
+ if (isAllowlistedHost(url.hostname, allowedHosts)) {
28927
+ return url;
28928
+ }
28929
+ return assertSafeEgressUrl(rawUrl, {
28930
+ protocols: ["https:"],
28931
+ fail: (reason) => new BlockedWebhookUrlError(reason)
28932
+ });
28933
+ }
28934
+
28935
+ // ../../packages/schema/src/contracts/webhooks.ts
28936
+ var webhookUrlSchema = string2().superRefine((value, ctx) => {
28937
+ try {
28938
+ assertSafeWebhookUrl(value);
28939
+ } catch (err) {
28940
+ ctx.addIssue({
28941
+ code: ZodIssueCode.custom,
28942
+ message: err instanceof BlockedWebhookUrlError ? err.message : "invalid webhook url"
28943
+ });
28944
+ }
28945
+ });
28946
+ var webhookEventSchema = string2().min(1).max(120).regex(/^[a-z0-9]+(\.[a-z0-9]+)+$/, "must be a dotted event name like 'task.created'");
28947
+ var webhookSubscriptionSchema = createSelectSchema(webhookSubscriptions, {
28948
+ events: array(webhookEventSchema),
28949
+ createdAt: exports_coerce.date(),
28950
+ updatedAt: exports_coerce.date()
28951
+ });
28952
+ var webhookSubscriptionPublicSchema = webhookSubscriptionSchema.omit({ secret: true });
28953
+ var createWebhookSubscriptionInputSchema = createInsertSchema(webhookSubscriptions, {
28954
+ url: webhookUrlSchema,
28955
+ events: array(webhookEventSchema).min(1),
28956
+ secret: string2().min(16).max(200).optional()
28957
+ }).omit({ id: true, tenantId: true, active: true, createdAt: true, updatedAt: true }).strict();
28958
+ var createWebhookSubscriptionResultSchema = webhookSubscriptionPublicSchema.extend({
28959
+ secret: string2()
28960
+ });
28961
+ var updateWebhookSubscriptionInputSchema = createUpdateSchema(webhookSubscriptions, {
28962
+ url: webhookUrlSchema.optional(),
28963
+ events: array(webhookEventSchema).min(1).optional()
28964
+ }).omit({ id: true, tenantId: true, secret: true, createdAt: true, updatedAt: true }).strict();
28965
+
28579
28966
  // ../../packages/schema/src/contracts/roles.ts
28580
28967
  var permissionSetSchema = createSelectSchema(permissionSets, {
28581
28968
  createdAt: exports_coerce.date(),
@@ -29624,6 +30011,37 @@ function toCollectionTypeSummary(config) {
29624
30011
  }
29625
30012
  var COLLECTION_TYPE_SUMMARIES = COLLECTION_TYPE_LIST.map(toCollectionTypeSummary);
29626
30013
 
30014
+ // ../../packages/schema/src/operations/define.ts
30015
+ function defineOperations(table) {
30016
+ return table;
30017
+ }
30018
+ var PUBLIC_API_PREFIX = "/api/v1";
30019
+ function pathOf(op, params = {}) {
30020
+ return op.path.replace(/\{([A-Za-z0-9_]+)\}/g, (_, name) => {
30021
+ const value = params[name];
30022
+ if (value === undefined)
30023
+ throw new Error(`pathOf: no value for {${name}} in ${op.path}`);
30024
+ return encodeURIComponent(value);
30025
+ });
30026
+ }
30027
+ function routeOf(op) {
30028
+ return `${op.method.toUpperCase()} ${PUBLIC_API_PREFIX}${op.path}`;
30029
+ }
30030
+
30031
+ // ../../packages/schema/src/operations/agents.ts
30032
+ var agentOperations = defineOperations({
30033
+ list: {
30034
+ method: "get",
30035
+ path: "/agents",
30036
+ scopes: ["agents:read"],
30037
+ tag: "Agents",
30038
+ summary: "List agents",
30039
+ description: "The Space's live Agents (`id`, `name`), oldest first, to resolve an Agent by name.",
30040
+ status: 200,
30041
+ response: { description: "The Space's Agents.", list: true }
30042
+ }
30043
+ });
30044
+
29627
30045
  // ../../packages/schema/src/operations/crud.ts
29628
30046
  function crudOperations(r) {
29629
30047
  const base = `/${r.segment}`;
@@ -29695,22 +30113,520 @@ function crudOperations(r) {
29695
30113
  };
29696
30114
  }
29697
30115
 
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
- }
30116
+ // ../../packages/schema/src/operations/block-templates.ts
30117
+ var crud = crudOperations({
30118
+ segment: "block-templates",
30119
+ tag: "Calendar",
30120
+ noun: "block template",
30121
+ read: "calendar:read",
30122
+ write: "calendar:write",
30123
+ create: createBlockTemplateInputSchema,
30124
+ update: updateBlockTemplateInputSchema,
30125
+ listQuery: []
30126
+ });
30127
+ var blockTemplateOperations = defineOperations({
30128
+ ...crud,
30129
+ create: { ...crud.create, webhook: undefined },
30130
+ update: { ...crud.update, webhook: undefined },
30131
+ remove: { ...crud.remove, webhook: undefined },
30132
+ week: {
30133
+ method: "get",
30134
+ path: "/block-templates/week",
30135
+ scopes: ["calendar:read"],
30136
+ tag: "Calendar",
30137
+ summary: "Lay out a week from the block templates",
30138
+ 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.",
30139
+ query: [{ name: "start", description: "The week's Monday (YYYY-MM-DD).", format: "date" }],
30140
+ querySchema: blockTemplateWeekQuerySchema,
30141
+ status: 200,
30142
+ response: { description: "The week's blocks." },
30143
+ errors: { 400: true }
30144
+ },
30145
+ defaults: {
30146
+ method: "post",
30147
+ path: "/block-templates/defaults",
30148
+ scopes: ["calendar:write"],
30149
+ tag: "Calendar",
30150
+ summary: "Seed the default week",
30151
+ 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.",
30152
+ status: 200,
30153
+ response: { description: "The member's templates." }
30154
+ }
30155
+ });
30156
+
30157
+ // ../../packages/schema/src/operations/collection-upkeep.ts
30158
+ var collectionUpkeepOperations = defineOperations({
30159
+ list: {
30160
+ method: "get",
30161
+ path: "/collection/{id}/upkeep",
30162
+ scopes: ["collections:read"],
30163
+ tag: "Collection",
30164
+ summary: "List an asset's upkeep and repairs",
30165
+ 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 }`.",
30166
+ status: 200,
30167
+ response: { description: "The asset's Tasks." },
30168
+ errors: { 404: true }
30169
+ },
30170
+ link: {
30171
+ method: "post",
30172
+ path: "/collection/{id}/upkeep",
30173
+ scopes: ["collections:write"],
30174
+ tag: "Collection",
30175
+ summary: "Tie a Task to an asset",
30176
+ 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.',
30177
+ body: linkAssetTaskInputSchema,
30178
+ status: 201,
30179
+ response: { description: "The asset's Tasks." },
30180
+ errors: { 400: true, 404: true }
30181
+ },
30182
+ unlink: {
30183
+ method: "delete",
30184
+ path: "/collection/{id}/upkeep/{taskId}",
30185
+ pathParams: { taskId: "The Task's ULID." },
30186
+ scopes: ["collections:write"],
30187
+ tag: "Collection",
30188
+ summary: "Untie a Task from an asset",
30189
+ description: "Untie the Task from this asset; the Task itself stays. Returns the asset's Tasks.",
30190
+ status: 200,
30191
+ response: { description: "The asset's Tasks." },
30192
+ errors: { 404: true }
30193
+ }
30194
+ });
30195
+
30196
+ // ../../packages/schema/src/operations/google-contacts.ts
30197
+ var googleContactsOperations = defineOperations({
30198
+ list: {
30199
+ method: "get",
30200
+ path: "/people/google-contacts",
30201
+ scopes: ["contacts:read"],
30202
+ tag: "People",
30203
+ summary: "Your Google Contacts, to pick from",
30204
+ 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`.",
30205
+ query: [{ name: "q", description: "A name or email to narrow to." }],
30206
+ querySchema: googleContactsQuerySchema,
30207
+ status: 200,
30208
+ response: { description: "The link and the candidates." },
30209
+ errors: { 403: "Not a member's key." }
30210
+ },
30211
+ import: {
30212
+ method: "post",
30213
+ path: "/people/google-contacts/import",
30214
+ scopes: ["contacts:write"],
30215
+ tag: "People",
30216
+ summary: "Import the people you picked from Google Contacts",
30217
+ 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).",
30218
+ body: importGoogleContactsInputSchema,
30219
+ status: 200,
30220
+ response: { description: "What the import did." },
30221
+ errors: {
30222
+ 400: true,
30223
+ 403: "Off, or not a member's key.",
30224
+ 409: "Google Contacts isn't connected (or asks to sign in again)."
30225
+ }
30226
+ }
30227
+ });
30228
+
30229
+ // ../../packages/schema/src/operations/google-health.ts
30230
+ var googleHealthOperations = defineOperations({
30231
+ status: {
30232
+ method: "get",
30233
+ path: "/health/google-health",
30234
+ scopes: ["health:read"],
30235
+ tag: "Health",
30236
+ summary: "Get the Google Health link",
30237
+ 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.",
30238
+ status: 200,
30239
+ response: { description: "The link's status." },
30240
+ errors: { 403: true }
30241
+ },
30242
+ sync: {
30243
+ method: "post",
30244
+ path: "/health/google-health/sync",
30245
+ scopes: ["health:write"],
30246
+ tag: "Health",
30247
+ summary: "Sync Google Health now",
30248
+ 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.",
30249
+ status: 200,
30250
+ response: { description: "What came in, and the status after it." },
30251
+ errors: { 403: true, 409: true }
30252
+ }
30253
+ });
30254
+
30255
+ // ../../packages/schema/src/operations/guest-list-events.ts
30256
+ var crud2 = crudOperations({
30257
+ segment: "guest-list-events",
30258
+ tag: "Guests",
30259
+ noun: "guest list event",
30260
+ read: "contacts:read",
30261
+ write: "contacts:write",
30262
+ create: createGuestListEventInputSchema,
30263
+ update: updateGuestListEventInputSchema,
30264
+ listQuery: [{ name: "guestListId", description: "The guest list (required)." }],
30265
+ listQuerySchema: guestListEventsQuerySchema
30266
+ });
30267
+ var guestListEventOperations = defineOperations({
30268
+ ...crud2,
30269
+ create: { ...crud2.create, webhook: "guest.event.created" },
30270
+ update: { ...crud2.update, webhook: "guest.event.updated" },
30271
+ remove: { ...crud2.remove, webhook: "guest.event.deleted" }
30272
+ });
30273
+
30274
+ // ../../packages/schema/src/operations/guest-tables.ts
30275
+ var guestTableOperations = defineOperations({
30276
+ ...crudOperations({
30277
+ segment: "guest-tables",
30278
+ tag: "Guests",
30279
+ noun: "guest table",
30280
+ read: "contacts:read",
30281
+ write: "contacts:write",
30282
+ create: createGuestTableInputSchema,
30283
+ update: updateGuestTableInputSchema,
30284
+ listQuery: [{ name: "guestListId", description: "The guest list (required)." }],
30285
+ listQuerySchema: guestTablesQuerySchema
30286
+ })
30287
+ });
30288
+
30289
+ // ../../packages/schema/src/operations/guests.ts
30290
+ var guestOperations = defineOperations({
30291
+ ...crudOperations({
30292
+ segment: "guests",
30293
+ tag: "Guests",
30294
+ noun: "guest",
30295
+ read: "contacts:read",
30296
+ write: "contacts:write",
30297
+ create: createGuestInputSchema,
30298
+ update: updateGuestInputSchema,
30299
+ listQuery: [
30300
+ { name: "guestListId", description: "Only this guest list's guests." },
30301
+ { name: "contactId", description: "Only this contact's guest rows." },
30302
+ { name: "state", description: "`yes`, `no`, `maybe` or `waiting`: their overall answer." }
30303
+ ],
30304
+ listQuerySchema: guestsQuerySchema
30305
+ }),
30306
+ sent: {
30307
+ method: "post",
30308
+ path: "/guests/sent",
30309
+ scopes: ["contacts:write"],
30310
+ tag: "Guests",
30311
+ summary: "Stamp save-the-dates or invitations as sent",
30312
+ description: "Stamps `saveTheDateAt` (`what: save_the_date`) or `inviteSentAt` (`what: invite`) on every guest in `guestIds`, at `at` or now. Returns the guests; fires `guest.sent`.",
30313
+ body: markGuestsSentInputSchema,
30314
+ status: 200,
30315
+ response: { description: "The stamped guests." },
30316
+ errors: { 400: true, 404: true },
30317
+ webhook: "guest.sent"
30318
+ }
30319
+ });
30320
+
30321
+ // ../../packages/schema/src/operations/habits.ts
30322
+ var habitListQuery = habitListQuerySchema.extend(paginationQuerySchema.shape);
30323
+ var habitOperations = defineOperations({
30324
+ list: {
30325
+ method: "get",
30326
+ path: "/habits",
30327
+ scopes: ["habits:read"],
30328
+ tag: "Habits",
30329
+ summary: "List habits",
30330
+ description: "Every growing habit (with streaks) in the Space, oldest first. `retired=true` lists the Grown shelf instead: the retired habits, history kept.",
30331
+ query: [
30332
+ { name: "areaId", description: "Only rows in this Area (ULID)." },
30333
+ {
30334
+ name: "updatedSince",
30335
+ description: "Only rows whose `updatedAt` is at or after this instant \u2014 an ISO 8601 datetime with offset, or a bare `YYYY-MM-DD` (UTC midnight).",
30336
+ format: "date-time"
30337
+ },
30338
+ {
30339
+ name: "retired",
30340
+ description: "`true` lists the retired (Grown) habits instead of the growing ones.",
30341
+ enum: ["true", "false"]
30342
+ }
30343
+ ],
30344
+ querySchema: habitListQuery,
30345
+ paginated: true,
30346
+ status: 200,
30347
+ response: { description: "The Space's habits." }
30348
+ },
30349
+ today: {
30350
+ method: "get",
30351
+ path: "/habits/today",
30352
+ scopes: ["habits:read"],
30353
+ tag: "Habits",
30354
+ summary: "What's left today",
30355
+ description: "\"What's left?\" (#866): the habits due on the key's member's day (their saved time zone), already filtered by schedule, grouped by part of day (morning, midday, evening, anytime). Each says its value today, `done`/`open`/`skipped` (never overdue: habits don't carry over), the week for an N-per-week habit, and its last check-in (`by` names the Agent). Also today's routines with how many steps are done. Quit habits are left out.",
30356
+ status: 200,
30357
+ response: { description: "Today's habits and routines." },
30358
+ operationId: "habits_today"
30359
+ },
30360
+ getAway: {
30361
+ method: "get",
30362
+ path: "/habits/away",
30363
+ scopes: ["habits:read"],
30364
+ tag: "Habits",
30365
+ summary: "Get away state",
30366
+ description: "The Space's \"Away until \u2026\": `{ until }` while away (the day you're back, YYYY-MM-DD), else `{ until: null }`.",
30367
+ status: 200,
30368
+ response: { description: "The away state." }
30369
+ },
30370
+ setAway: {
30371
+ method: "put",
30372
+ path: "/habits/away",
30373
+ scopes: ["habits:write"],
30374
+ tag: "Habits",
30375
+ summary: "Set away",
30376
+ description: '"Away till Monday": every habit rests until `until` (the day you\'re back, after today): off Now, no push, streaks and plants wait. `until: null` comes back now. Tasks and events carry on. Fires `habit.updated`.',
30377
+ body: awayInputSchema,
30378
+ status: 200,
30379
+ response: { description: "The away state." },
30380
+ errors: { 400: true },
30381
+ webhook: "habit.updated"
30382
+ },
30383
+ get: {
30384
+ method: "get",
30385
+ path: "/habits/{id}",
30386
+ scopes: ["habits:read"],
30387
+ tag: "Habits",
30388
+ summary: "Get a habit",
30389
+ description: "One habit with its streak + heatmap.",
30390
+ status: 200,
30391
+ response: { description: "The habit." },
30392
+ errors: { 404: true }
30393
+ },
30394
+ create: {
30395
+ method: "post",
30396
+ path: "/habits",
30397
+ scopes: ["habits:write"],
30398
+ tag: "Habits",
30399
+ summary: "Create a habit",
30400
+ description: "Create a habit; fires `habit.created`.",
30401
+ body: createHabitInputSchema,
30402
+ status: 201,
30403
+ response: { description: "The created habit." },
30404
+ errors: { 400: true },
30405
+ webhook: "habit.created"
30406
+ },
30407
+ update: {
30408
+ method: "patch",
30409
+ path: "/habits/{id}",
30410
+ scopes: ["habits:write"],
30411
+ tag: "Habits",
30412
+ summary: "Update a habit",
30413
+ description: "Patch a habit; fires `habit.updated`.",
30414
+ body: updateHabitInputSchema,
30415
+ status: 200,
30416
+ response: { description: "The updated habit." },
30417
+ errors: {
30418
+ 400: true,
30419
+ 404: true
30420
+ },
30421
+ webhook: "habit.updated"
30422
+ },
30423
+ remove: {
30424
+ method: "delete",
30425
+ path: "/habits/{id}",
30426
+ scopes: ["habits:write"],
30427
+ tag: "Habits",
30428
+ summary: "Archive a habit",
30429
+ description: "Soft-delete a habit; fires `habit.deleted`.",
30430
+ status: 204,
30431
+ response: { description: "Archived." },
30432
+ errors: { 404: true },
30433
+ webhook: "habit.deleted"
30434
+ },
30435
+ checkInByTitle: {
30436
+ method: "post",
30437
+ path: "/habits/check-in",
30438
+ scopes: ["habits:write"],
30439
+ tag: "Habits",
30440
+ summary: "Check in a habit by title",
30441
+ description: 'Check in the habit whose `title` or one of whose aliases is this (case-insensitive) \u2014 "wake", "did my run", "meditated". `value` defaults to 1. `mode: "add"` (default) logs it on top of the day ("12 pages"); `mode: "set"` makes it the day\'s total ("read 40 total") by writing one entry with the difference, keeping earlier check-ins (amount habits only, else 400). `occurredAt` back-dates it: an instant, or a `YYYY-MM-DD` member\'s day (noon in their zone); `set` works on the member\'s day it lands in. Several matching habits are a 409 with `error.code: "ambiguous_habit"` and `error.details.candidates` (`id`, `title`, `aliases`) to ask which; none is a 404 naming close titles; a habit counted from an ingest (Google Fit, Strava, GitHub) is a 409 \u2014 it checks itself in. An Agent\'s key records an `agent` check-in naming the Agent. `note` puts a short note on the check-in ("drill done, felt heavy" \u2192 `note: "felt heavy"`); a `set` that changes nothing puts it on the day\'s latest check-in. The refreshed habit\'s `lastCheckIn.id` is the entry, for adding a note later. Returns the refreshed habit; fires `habit.updated`.',
30442
+ body: habitCheckInByTitleInputSchema,
30443
+ status: 201,
30444
+ response: { description: "The refreshed habit." },
30445
+ errors: {
30446
+ 400: true,
30447
+ 404: true,
30448
+ 409: "Several habits answer to the title (with the candidates), or it is counted from an ingest."
30449
+ },
30450
+ webhook: "habit.updated"
30451
+ },
30452
+ addAlias: {
30453
+ method: "post",
30454
+ path: "/habits/{id}/aliases",
30455
+ scopes: ["habits:write"],
30456
+ tag: "Habits",
30457
+ summary: "Add a habit alias",
30458
+ description: 'Add another name a check-in by title matches ("wake" for "Wake up by 07:00"); one already there (ignoring case) is a no-op, at most 12. Returns the refreshed habit; fires `habit.updated`.',
30459
+ body: habitAliasInputSchema,
30460
+ status: 201,
30461
+ response: { description: "The refreshed habit." },
30462
+ errors: {
30463
+ 400: true,
30464
+ 404: true
30465
+ },
30466
+ webhook: "habit.updated"
30467
+ },
30468
+ removeAlias: {
30469
+ method: "delete",
30470
+ path: "/habits/{id}/aliases/{alias}",
30471
+ pathParams: { alias: "The alias to drop (ignoring case)." },
30472
+ scopes: ["habits:write"],
30473
+ tag: "Habits",
30474
+ summary: "Remove a habit alias",
30475
+ description: "Drop an alias; 404 when the habit doesn't have it. Returns the refreshed habit; fires `habit.updated`.",
30476
+ status: 200,
30477
+ response: { description: "The refreshed habit." },
30478
+ errors: { 404: true },
30479
+ webhook: "habit.updated"
30480
+ },
30481
+ listCheckIns: {
30482
+ method: "get",
30483
+ path: "/habits/{id}/check-ins",
30484
+ scopes: ["habits:read"],
30485
+ tag: "Habits",
30486
+ summary: "List a habit's check-ins",
30487
+ description: "A page of the habit's check-ins, newest first; pass the oldest one's `occurredAt` as `before` for the next page.",
30488
+ query: [
30489
+ { name: "before", description: "Only check-ins before this instant (ISO 8601)." },
30490
+ { name: "limit", description: "Page size, 1\u2013200 (default 50).", type: "integer" }
30491
+ ],
30492
+ querySchema: habitEntriesQuerySchema,
30493
+ status: 200,
30494
+ response: { description: "The check-ins." },
30495
+ errors: { 404: true }
30496
+ },
30497
+ checkIn: {
30498
+ method: "post",
30499
+ path: "/habits/{id}/check-ins",
30500
+ scopes: ["habits:write"],
30501
+ tag: "Habits",
30502
+ summary: "Check in",
30503
+ description: "Record a check-in, with an optional short `note`; fires `habit.updated` with the refreshed streak. A habit counted from an ingest (`source` other than `manual`) answers 409.",
30504
+ body: createHabitEntryInputSchema,
30505
+ status: 201,
30506
+ response: { description: "The refreshed habit." },
30507
+ errors: {
30508
+ 400: true,
30509
+ 404: true
30510
+ },
30511
+ webhook: "habit.updated",
30512
+ operationId: "check_in_habit"
30513
+ },
30514
+ skip: {
30515
+ method: "post",
30516
+ path: "/habits/{id}/skip",
30517
+ scopes: ["habits:write"],
30518
+ tag: "Habits",
30519
+ summary: "Skip a habit",
30520
+ description: '"Skip gym today": a neutral rest day (a `skip` entry, value 0), never a miss. The streak treats the period as neutral and spends no grace. `date` (YYYY-MM-DD) defaults to today; an optional `note` says why ("Dentist"). Skipping a skipped day changes nothing but its note. Undo by deleting the skip entry like a check-in. Returns the refreshed habit; fires `habit.updated`.',
30521
+ body: habitSkipInputSchema,
30522
+ status: 201,
30523
+ response: { description: "The refreshed habit." },
30524
+ errors: {
30525
+ 400: true,
30526
+ 404: true
30527
+ },
30528
+ webhook: "habit.updated"
30529
+ },
30530
+ pause: {
30531
+ method: "post",
30532
+ path: "/habits/{id}/pause",
30533
+ scopes: ["habits:write"],
30534
+ tag: "Habits",
30535
+ summary: "Pause a habit",
30536
+ description: '"Pause gym till the 26th": the habit rests until `until` (the day it\'s back, after today): off Now, no push, streak and plant wait. Pausing a paused habit moves its end. Returns the refreshed habit with `pause`; fires `habit.updated`.',
30537
+ body: habitPauseInputSchema,
30538
+ status: 201,
30539
+ response: { description: "The refreshed habit." },
30540
+ errors: {
30541
+ 400: true,
30542
+ 404: true
30543
+ },
30544
+ webhook: "habit.updated"
30545
+ },
30546
+ resume: {
30547
+ method: "post",
30548
+ path: "/habits/{id}/resume",
30549
+ scopes: ["habits:write"],
30550
+ tag: "Habits",
30551
+ summary: "Resume a habit",
30552
+ description: "End a habit's pause: it's back today. The rest so far stays neutral. Returns the refreshed habit; fires `habit.updated`.",
30553
+ status: 201,
30554
+ response: { description: "The refreshed habit." },
30555
+ errors: { 404: true },
30556
+ webhook: "habit.updated"
30557
+ },
30558
+ retire: {
30559
+ method: "post",
30560
+ path: "/habits/{id}/retire",
30561
+ scopes: ["habits:write"],
30562
+ tag: "Habits",
30563
+ summary: "Retire a habit",
30564
+ description: '"I\'m done with the cold showers habit": the habit moves to the Grown shelf with its plant in bloom and keeps its history. It leaves Now and Today, sends no push, takes no check-in by title and is never due; Progress keeps its past. Returns the refreshed habit with `retiredAt`; undo with `/unretire`. Fires `habit.updated`.',
30565
+ status: 201,
30566
+ response: { description: "The refreshed habit." },
30567
+ errors: { 404: true },
30568
+ webhook: "habit.updated"
30569
+ },
30570
+ unretire: {
30571
+ method: "post",
30572
+ path: "/habits/{id}/unretire",
30573
+ scopes: ["habits:write"],
30574
+ tag: "Habits",
30575
+ summary: "Bring a habit back",
30576
+ description: "Bring a grown (retired) habit back: it's growing again from today, its history intact. Returns the refreshed habit; fires `habit.updated`.",
30577
+ status: 201,
30578
+ response: { description: "The refreshed habit." },
30579
+ errors: { 404: true },
30580
+ webhook: "habit.updated"
30581
+ },
30582
+ updateCheckIn: {
30583
+ method: "patch",
30584
+ path: "/habits/{id}/check-ins/{entryId}",
30585
+ pathParams: { entryId: "The check-in entry's ULID." },
30586
+ scopes: ["habits:write"],
30587
+ tag: "Habits",
30588
+ summary: "Note a check-in",
30589
+ description: "Edit a check-in afterwards: set, replace or clear (`null` or blank) its short `note` (at most 280 characters). The amount and time stay; undo a wrong check-in instead. Returns the check-in; fires `habit.updated`.",
30590
+ body: updateHabitEntryInputSchema,
30591
+ status: 200,
30592
+ response: { description: "The check-in, with its note." },
30593
+ errors: {
30594
+ 400: true,
30595
+ 404: true
30596
+ },
30597
+ webhook: "habit.updated",
30598
+ operationId: "note_habit_check_in"
30599
+ },
30600
+ removeCheckIn: {
30601
+ method: "delete",
30602
+ path: "/habits/{id}/check-ins/{entryId}",
30603
+ pathParams: { entryId: "The check-in entry's ULID." },
30604
+ scopes: ["habits:write"],
30605
+ tag: "Habits",
30606
+ summary: "Undo a check-in",
30607
+ description: "Delete a check-in; fires `habit.updated`.",
30608
+ status: 200,
30609
+ response: { description: "The refreshed habit." },
30610
+ errors: { 404: true },
30611
+ webhook: "habit.updated",
30612
+ operationId: "undo_habit_check_in"
30613
+ }
30614
+ });
30615
+
30616
+ // ../../packages/schema/src/operations/households.ts
30617
+ var householdOperations = defineOperations({
30618
+ ...crudOperations({
30619
+ segment: "households",
30620
+ tag: "Guests",
30621
+ noun: "household",
30622
+ read: "contacts:read",
30623
+ write: "contacts:write",
30624
+ create: createHouseholdInputSchema,
30625
+ update: updateHouseholdInputSchema,
30626
+ listQuery: [{ name: "contactId", description: "Only the households this contact is in." }],
30627
+ listQuerySchema: householdsQuerySchema
30628
+ })
30629
+ });
29714
30630
 
29715
30631
  // ../../packages/schema/src/operations/inventory-items.ts
29716
30632
  var inventoryItemOperations = defineOperations({
@@ -29727,6 +30643,75 @@ var inventoryItemOperations = defineOperations({
29727
30643
  })
29728
30644
  });
29729
30645
 
30646
+ // ../../packages/schema/src/operations/mail-campaigns.ts
30647
+ var pathParams = { id: "The campaign's ULID." };
30648
+ var mailCampaignOperations = defineOperations({
30649
+ list: {
30650
+ method: "get",
30651
+ path: "/mail-campaigns",
30652
+ scopes: ["contacts:read"],
30653
+ tag: "Guests",
30654
+ summary: "List a guest list's mail campaigns",
30655
+ description: "Its drafted and sent mails, newest first.",
30656
+ query: [{ name: "guestListId", description: "The guest list (required)." }],
30657
+ querySchema: mailCampaignsQuerySchema,
30658
+ status: 200,
30659
+ response: { description: "The campaigns." },
30660
+ errors: { 400: true }
30661
+ },
30662
+ create: {
30663
+ method: "post",
30664
+ path: "/mail-campaigns",
30665
+ scopes: ["contacts:write"],
30666
+ tag: "Guests",
30667
+ summary: "Draft a mail campaign",
30668
+ 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.",
30669
+ body: createMailCampaignInputSchema,
30670
+ status: 201,
30671
+ response: { description: "The draft." },
30672
+ errors: { 400: true, 404: true }
30673
+ },
30674
+ get: {
30675
+ method: "get",
30676
+ path: "/mail-campaigns/{id}",
30677
+ pathParams,
30678
+ scopes: ["contacts:read"],
30679
+ tag: "Guests",
30680
+ summary: "Get a mail campaign",
30681
+ description: "",
30682
+ status: 200,
30683
+ response: { description: "The campaign." },
30684
+ errors: { 404: true }
30685
+ },
30686
+ update: {
30687
+ method: "patch",
30688
+ path: "/mail-campaigns/{id}",
30689
+ pathParams,
30690
+ scopes: ["contacts:write"],
30691
+ tag: "Guests",
30692
+ summary: "Edit a draft",
30693
+ 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.",
30694
+ body: updateMailCampaignInputSchema,
30695
+ status: 200,
30696
+ response: { description: "The draft." },
30697
+ errors: { 400: true, 404: true }
30698
+ },
30699
+ preview: {
30700
+ method: "get",
30701
+ path: "/mail-campaigns/{id}/preview",
30702
+ pathParams,
30703
+ scopes: ["contacts:read"],
30704
+ tag: "Guests",
30705
+ summary: "Preview a mail campaign",
30706
+ description: "The mail as a household would get it in one language (`nl` by default, or `pt`): its subject and rendered body.",
30707
+ query: [{ name: "language", description: "`nl` (default) or `pt`." }],
30708
+ querySchema: mailPreviewQuerySchema,
30709
+ status: 200,
30710
+ response: { description: "The rendered mail." },
30711
+ errors: { 400: true, 404: true }
30712
+ }
30713
+ });
30714
+
29730
30715
  // ../../packages/schema/src/operations/meal-plans.ts
29731
30716
  var mealPlanOperations = defineOperations({
29732
30717
  ...crudOperations({
@@ -29740,6 +30725,48 @@ var mealPlanOperations = defineOperations({
29740
30725
  })
29741
30726
  });
29742
30727
 
30728
+ // ../../packages/schema/src/operations/occurrences.ts
30729
+ var occurrenceOperations = defineOperations({
30730
+ list: {
30731
+ method: "get",
30732
+ path: "/occurrences",
30733
+ scopes: ["occurrences:read"],
30734
+ tag: "Occurrences",
30735
+ summary: "List a template's occurrences",
30736
+ description: "Occurrences of one task/habit template, oldest-first.",
30737
+ query: [{ name: "templateId", description: "The task/habit template's ULID.", required: true }],
30738
+ querySchema: object({ templateId: ulidSchema }),
30739
+ status: 200,
30740
+ response: { description: "The template's occurrences." },
30741
+ errors: { 400: true }
30742
+ },
30743
+ update: {
30744
+ method: "patch",
30745
+ path: "/occurrences/{id}",
30746
+ scopes: ["occurrences:write"],
30747
+ tag: "Occurrences",
30748
+ summary: "Override an occurrence",
30749
+ description: 'Reschedule/skip one occurrence ("edit this"); fires `occurrence.updated`.',
30750
+ body: updateOccurrenceInputSchema,
30751
+ status: 200,
30752
+ response: { description: "The overridden occurrence." },
30753
+ errors: { 400: true, 404: true },
30754
+ webhook: "occurrence.updated"
30755
+ },
30756
+ complete: {
30757
+ method: "post",
30758
+ path: "/occurrences/{id}/complete",
30759
+ scopes: ["occurrences:write"],
30760
+ tag: "Occurrences",
30761
+ summary: "Complete an occurrence",
30762
+ description: "Mark an occurrence done; fires `occurrence.completed`.",
30763
+ status: 201,
30764
+ response: { description: "The completed occurrence." },
30765
+ errors: { 404: true },
30766
+ webhook: "occurrence.completed"
30767
+ }
30768
+ });
30769
+
29743
30770
  // ../../packages/schema/src/operations/periods.ts
29744
30771
  var periodOperations = defineOperations({
29745
30772
  ...crudOperations({
@@ -29765,6 +30792,474 @@ var periodOperations = defineOperations({
29765
30792
  })
29766
30793
  });
29767
30794
 
30795
+ // ../../packages/schema/src/operations/planning-rules.ts
30796
+ var planningRuleOperations = defineOperations({
30797
+ ...crudOperations({
30798
+ segment: "planning/rules",
30799
+ tag: "Planning rules",
30800
+ noun: "planning rule",
30801
+ read: "planning:read",
30802
+ write: "planning:write",
30803
+ create: createPlanningRuleInputSchema,
30804
+ update: updatePlanningRuleInputSchema,
30805
+ listQuery: [
30806
+ { name: "forUserId", description: "Only this member's rules." },
30807
+ {
30808
+ name: "kind",
30809
+ description: "Only this kind (`fixed_time`, `keep_free`, `meaning`, `whose`, `limit`, `note`)."
30810
+ }
30811
+ ],
30812
+ listQuerySchema: planningRulesQuerySchema
30813
+ })
30814
+ });
30815
+
30816
+ // ../../packages/schema/src/operations/plans.ts
30817
+ function decision(path, verb, what) {
30818
+ return {
30819
+ method: "post",
30820
+ path,
30821
+ scopes: ["tasks:write"],
30822
+ tag: "Plans",
30823
+ summary: `${verb[0]?.toUpperCase()}${verb.slice(1)} plan items`,
30824
+ 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.`,
30825
+ body: planDecisionInputSchema,
30826
+ bodyOptional: true,
30827
+ status: 200,
30828
+ response: { description: "The plan, after.", view: "Plan" },
30829
+ errors: { 400: true, 404: true }
30830
+ };
30831
+ }
30832
+ var planOperations = defineOperations({
30833
+ list: {
30834
+ method: "get",
30835
+ path: "/plans",
30836
+ scopes: ["tasks:read"],
30837
+ tag: "Plans",
30838
+ summary: "List plans",
30839
+ 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).",
30840
+ query: [
30841
+ { name: "kind", description: "`week` or `day`." },
30842
+ { name: "status", description: "`open`, `settled` or `replaced`." },
30843
+ { name: "startsOn", description: "The week's Monday, or the day (`YYYY-MM-DD`)." },
30844
+ { name: "forUserId", description: "Only this member's plans." }
30845
+ ],
30846
+ querySchema: planListQuerySchema.extend({ forUserId: string2().min(1).optional() }),
30847
+ status: 200,
30848
+ response: { description: "The plans.", list: true, view: "Plan" },
30849
+ errors: { 400: true }
30850
+ },
30851
+ create: {
30852
+ method: "post",
30853
+ path: "/plans",
30854
+ scopes: ["tasks:write"],
30855
+ tag: "Plans",
30856
+ summary: "Propose a plan",
30857
+ 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.",
30858
+ body: createPlanInputSchema,
30859
+ status: 201,
30860
+ response: {
30861
+ description: "The plan, open, with the duplicates it folded in.",
30862
+ view: "PlanCreateResult"
30863
+ },
30864
+ errors: { 400: true }
30865
+ },
30866
+ get: {
30867
+ method: "get",
30868
+ path: "/plans/{id}",
30869
+ scopes: ["tasks:read"],
30870
+ tag: "Plans",
30871
+ summary: "Get a plan",
30872
+ description: "One plan with its items.",
30873
+ status: 200,
30874
+ response: { description: "The plan.", view: "Plan" },
30875
+ errors: { 404: true }
30876
+ },
30877
+ accept: {
30878
+ ...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."),
30879
+ errors: { 400: true, 404: true, 409: "A newer plan replaced this one." }
30880
+ },
30881
+ dismiss: decision("/plans/{id}/dismiss", "dismiss", "Leave proposed items out. Nothing is written to a task."),
30882
+ 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.")
30883
+ });
30884
+
30885
+ // ../../packages/schema/src/operations/routines.ts
30886
+ var routineOperations = defineOperations({
30887
+ getRun: {
30888
+ method: "get",
30889
+ path: "/routines/runs/{runId}",
30890
+ pathParams: { runId: "The routine run's ULID." },
30891
+ scopes: ["routines:read"],
30892
+ tag: "Routines",
30893
+ summary: "Get a run",
30894
+ description: "One run with its per-step state.",
30895
+ status: 200,
30896
+ response: { description: "The run." },
30897
+ errors: { 404: true }
30898
+ },
30899
+ updateRun: {
30900
+ method: "patch",
30901
+ path: "/routines/runs/{runId}",
30902
+ pathParams: { runId: "The routine run's ULID." },
30903
+ scopes: ["routines:write"],
30904
+ tag: "Routines",
30905
+ summary: "Update a run",
30906
+ description: "Advance/finish a run; fires `routine.run.updated`.",
30907
+ body: updateRoutineRunInputSchema,
30908
+ status: 200,
30909
+ response: { description: "The updated run." },
30910
+ errors: { 400: true, 404: true },
30911
+ webhook: "routine.run.updated"
30912
+ },
30913
+ updateRunStep: {
30914
+ method: "patch",
30915
+ path: "/routines/runs/{runId}/steps/{stepRunId}",
30916
+ pathParams: { runId: "The routine run's ULID.", stepRunId: "The run step's ULID." },
30917
+ scopes: ["routines:write"],
30918
+ tag: "Routines",
30919
+ summary: "Update a run step",
30920
+ description: "Check off / edit one run step; fires `routine.run.updated`.",
30921
+ body: updateRoutineRunStepInputSchema,
30922
+ status: 200,
30923
+ response: { description: "The updated run." },
30924
+ errors: { 400: true, 404: true },
30925
+ webhook: "routine.run.updated"
30926
+ },
30927
+ undoSteps: {
30928
+ method: "post",
30929
+ path: "/routines/runs/{runId}/undo",
30930
+ pathParams: { runId: "The routine run's ULID." },
30931
+ scopes: ["routines:write"],
30932
+ tag: "Routines",
30933
+ summary: "Undo All taken",
30934
+ description: 'Take an "All taken" back in one call: each named step goes back to open and loses exactly the check-in it wrote; a finished run reopens. Steps no longer done are left alone. Fires `routine.run.updated`.',
30935
+ body: routineRunUndoInputSchema,
30936
+ status: 200,
30937
+ response: { description: "The run." },
30938
+ errors: { 400: true, 404: true },
30939
+ webhook: "routine.run.updated",
30940
+ operationId: "undo_routine_all_taken"
30941
+ },
30942
+ list: {
30943
+ method: "get",
30944
+ path: "/routines",
30945
+ scopes: ["routines:read"],
30946
+ tag: "Routines",
30947
+ summary: "List routines",
30948
+ description: "Every routine (with its steps) in the Space, newest first.",
30949
+ querySchema: paginationQuerySchema,
30950
+ paginated: true,
30951
+ status: 200,
30952
+ response: { description: "The Space's routines." }
30953
+ },
30954
+ get: {
30955
+ method: "get",
30956
+ path: "/routines/{id}",
30957
+ scopes: ["routines:read"],
30958
+ tag: "Routines",
30959
+ summary: "Get a routine",
30960
+ description: "One routine with its step templates.",
30961
+ status: 200,
30962
+ response: { description: "The routine." },
30963
+ errors: { 404: true }
30964
+ },
30965
+ create: {
30966
+ method: "post",
30967
+ path: "/routines",
30968
+ scopes: ["routines:write"],
30969
+ tag: "Routines",
30970
+ summary: "Create a routine",
30971
+ description: "Create a routine (steps ride in the payload); fires `routine.created`.",
30972
+ body: createRoutineInputSchema,
30973
+ status: 201,
30974
+ response: { description: "The created routine." },
30975
+ errors: { 400: true },
30976
+ webhook: "routine.created"
30977
+ },
30978
+ update: {
30979
+ method: "patch",
30980
+ path: "/routines/{id}",
30981
+ scopes: ["routines:write"],
30982
+ tag: "Routines",
30983
+ summary: "Update a routine",
30984
+ description: "Patch a routine; fires `routine.updated`.",
30985
+ body: updateRoutineInputSchema,
30986
+ status: 200,
30987
+ response: { description: "The updated routine." },
30988
+ errors: { 400: true, 404: true },
30989
+ webhook: "routine.updated"
30990
+ },
30991
+ remove: {
30992
+ method: "delete",
30993
+ path: "/routines/{id}",
30994
+ scopes: ["routines:write"],
30995
+ tag: "Routines",
30996
+ summary: "Delete a routine",
30997
+ description: "Soft-delete a routine; fires `routine.deleted`.",
30998
+ status: 204,
30999
+ response: { description: "Deleted." },
31000
+ errors: { 404: true },
31001
+ webhook: "routine.deleted"
31002
+ },
31003
+ attachRecurrence: {
31004
+ method: "put",
31005
+ path: "/routines/{id}/recurrence",
31006
+ scopes: ["routines:write"],
31007
+ tag: "Routines",
31008
+ summary: "Attach recurrence",
31009
+ description: "Attach/replace the routine's recurrence rule; fires `routine.updated`.",
31010
+ body: updateRecurrenceRuleInputSchema,
31011
+ status: 200,
31012
+ response: { description: "The routine." },
31013
+ errors: { 400: true, 404: true },
31014
+ webhook: "routine.updated"
31015
+ },
31016
+ detachRecurrence: {
31017
+ method: "delete",
31018
+ path: "/routines/{id}/recurrence",
31019
+ scopes: ["routines:write"],
31020
+ tag: "Routines",
31021
+ summary: "Detach recurrence",
31022
+ description: "Remove the routine's recurrence rule; returns the routine; fires `routine.updated`.",
31023
+ status: 200,
31024
+ response: { description: "The routine." },
31025
+ errors: { 404: true },
31026
+ webhook: "routine.updated"
31027
+ },
31028
+ markDone: {
31029
+ method: "post",
31030
+ path: "/routines/{id}/done",
31031
+ scopes: ["routines:write"],
31032
+ tag: "Routines",
31033
+ summary: "Check in a whole routine (All taken)",
31034
+ description: "\"Took my supplements\" (#873): one call checks in every open step of today's run (the key's member's day), starting today's run when there's none. Each habit step writes a check-in (`source: routine`); a habit already done today goes done without a second one; a step whose habit is skipped today is left alone. The run finishes once nothing is open. Returns the run and `taken`, the steps it resolved; hand their `stepRunId`s to `POST /api/v1/routines/runs/{runId}/undo` to take it all back. Calling it again changes nothing. Fires `routine.run.updated`.",
31035
+ body: routineDoneInputSchema,
31036
+ status: 200,
31037
+ response: { description: "`{ run, taken }`." },
31038
+ errors: { 400: true, 404: true },
31039
+ webhook: "routine.run.updated",
31040
+ operationId: "routine_all_taken"
31041
+ },
31042
+ startRun: {
31043
+ method: "post",
31044
+ path: "/routines/{id}/runs",
31045
+ scopes: ["routines:write"],
31046
+ tag: "Routines",
31047
+ summary: "Start a run",
31048
+ description: "Start a run of the routine; fires `routine.run.started`.",
31049
+ body: startRoutineRunInputSchema,
31050
+ status: 201,
31051
+ response: { description: "The started run." },
31052
+ errors: { 400: true, 404: true },
31053
+ webhook: "routine.run.started"
31054
+ },
31055
+ listRuns: {
31056
+ method: "get",
31057
+ path: "/routines/{id}/runs",
31058
+ scopes: ["routines:read"],
31059
+ tag: "Routines",
31060
+ summary: "List a routine's runs",
31061
+ description: "The routine's runs, newest-first.",
31062
+ status: 200,
31063
+ response: { description: "The routine's runs." },
31064
+ errors: { 404: true }
31065
+ }
31066
+ });
31067
+
31068
+ // ../../packages/schema/src/operations/rsvp-answers.ts
31069
+ var rsvpAnswerOperations = defineOperations({
31070
+ list: {
31071
+ method: "get",
31072
+ path: "/rsvp-answers",
31073
+ scopes: ["contacts:read"],
31074
+ tag: "Guests",
31075
+ summary: "List RSVP answers",
31076
+ 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.",
31077
+ query: [
31078
+ { name: "guestListId", description: "The guest list (required)." },
31079
+ { name: "householdId", description: "Only this household's answers." }
31080
+ ],
31081
+ querySchema: rsvpAnswersQuerySchema,
31082
+ status: 200,
31083
+ response: { description: "The answers." },
31084
+ errors: { 400: true, 404: true }
31085
+ },
31086
+ set: {
31087
+ method: "put",
31088
+ path: "/rsvp-answers",
31089
+ scopes: ["contacts:write"],
31090
+ tag: "Guests",
31091
+ summary: "Set a household's RSVP answers",
31092
+ 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`.",
31093
+ body: setRsvpAnswersInputSchema,
31094
+ status: 200,
31095
+ response: { description: "The household's answers after." },
31096
+ errors: { 400: true, 404: true, 422: "An answer that can't be given (see the message)." },
31097
+ webhook: "rsvp.answers.updated"
31098
+ }
31099
+ });
31100
+
31101
+ // ../../packages/schema/src/operations/rsvp-questions.ts
31102
+ var rsvpQuestionOperations = defineOperations({
31103
+ ...crudOperations({
31104
+ segment: "rsvp-questions",
31105
+ tag: "Guests",
31106
+ noun: "RSVP question",
31107
+ eventBase: "rsvp.question",
31108
+ read: "contacts:read",
31109
+ write: "contacts:write",
31110
+ create: createRsvpQuestionInputSchema,
31111
+ update: updateRsvpQuestionInputSchema,
31112
+ listQuery: [{ name: "guestListId", description: "The guest list (required)." }],
31113
+ listQuerySchema: rsvpQuestionsQuerySchema
31114
+ }),
31115
+ presets: {
31116
+ method: "post",
31117
+ path: "/rsvp-questions/presets",
31118
+ scopes: ["contacts:write"],
31119
+ tag: "Guests",
31120
+ summary: "Add the built-in RSVP questions",
31121
+ 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`.",
31122
+ body: addRsvpPresetsInputSchema,
31123
+ status: 200,
31124
+ response: { description: "The list's questions." },
31125
+ errors: { 400: true, 404: true },
31126
+ webhook: "rsvp.question.created"
31127
+ }
31128
+ });
31129
+
31130
+ // ../../packages/schema/src/operations/sensors.ts
31131
+ var sensorOperations = defineOperations({
31132
+ status: {
31133
+ method: "get",
31134
+ path: "/sensors/home-assistant",
31135
+ scopes: ["places:read"],
31136
+ tag: "Sensors",
31137
+ summary: "Get the Home Assistant link",
31138
+ description: "Whether the Space has linked Home Assistant (in Settings in the app; the token is never returned), the `host` it was linked at, how many entities are mapped (`links`) and `lastReadingAt`, when the newest reading from it landed.",
31139
+ status: 200,
31140
+ response: { description: "The link's status." },
31141
+ errors: { 403: true }
31142
+ },
31143
+ entities: {
31144
+ method: "get",
31145
+ path: "/sensors/home-assistant/entities",
31146
+ scopes: ["places:read"],
31147
+ tag: "Sensors",
31148
+ summary: "List Home Assistant's entities",
31149
+ description: "Every entity Home Assistant has, by `entityId`: its name, domain, state, unit, device class, `lastUpdated`, the reading it most likely gives on a Place and on an asset (`suggested`) and the sensor link it's mapped by, or null. 409 before Home Assistant is linked; 503 when it doesn't answer.",
31150
+ status: 200,
31151
+ response: { description: "The entities, by id." },
31152
+ errors: { 403: true, 409: true, 503: "Home Assistant didn't answer, or refused the token." }
31153
+ },
31154
+ sync: {
31155
+ method: "post",
31156
+ path: "/sensors/home-assistant/sync",
31157
+ scopes: ["places:write"],
31158
+ tag: "Sensors",
31159
+ summary: "Sync Home Assistant now",
31160
+ description: "Read every mapped entity's state once and append it to the datapoints ledger: on a Place as `shed.temp`, `shed.humidity`, `shed.heater`, `shed.water_pct`, `shed.door`, `shed.feed_kg` or `shed.light` (what an animal's room card and the room alarm read), on an asset as `asset.state`, `asset.power` or `asset.energy`. A state already on the ledger, or one Home Assistant can't read, is skipped. The worker does the same every five minutes. Returns `{ syncedAt, entities, written, skipped, missing, error }` (`unauthorized`, `unavailable`, `bad_response`). 409 before Home Assistant is linked.",
31161
+ status: 200,
31162
+ response: { description: "What the sync did." },
31163
+ errors: { 403: true, 409: true }
31164
+ },
31165
+ unifiStatus: {
31166
+ method: "get",
31167
+ path: "/sensors/unifi",
31168
+ scopes: ["places:read"],
31169
+ tag: "Sensors",
31170
+ summary: "Whether UniFi is linked",
31171
+ description: "Whether the Space has linked its UniFi controller (in the app's Settings \u2192 Home), the host and site it was linked at, how many devices are mapped to an asset, and when the newest presence reading landed. Never the API key: a key can't link or unlink UniFi.",
31172
+ status: 200,
31173
+ response: { description: "The status." },
31174
+ errors: { 403: true }
31175
+ },
31176
+ unifiDevices: {
31177
+ method: "get",
31178
+ path: "/sensors/unifi/devices",
31179
+ scopes: ["places:read", "collections:read"],
31180
+ tag: "Sensors",
31181
+ summary: "The devices UniFi sees",
31182
+ description: "Every device the controller manages (gateway, switches, access points, cameras) and every client connected now: `mac`, `name`, `kind` (`device` | `client`), `model`, `online`, `firmware`, and the sensor `link` to the asset it's mapped to (behind the nodes wall). Network details. 409 before UniFi is linked; 503 when the controller can't be read.",
31183
+ status: 200,
31184
+ response: { description: "The devices." },
31185
+ errors: { 403: true, 409: true }
31186
+ },
31187
+ syncUnifi: {
31188
+ method: "post",
31189
+ path: "/sensors/unifi/sync",
31190
+ scopes: ["places:write"],
31191
+ tag: "Sensors",
31192
+ summary: "Sync UniFi now",
31193
+ description: "Map every asset whose facet names a UniFi MAC (`external.unifiMac`), ask the controller once, and put each mapped device's presence on its asset as `asset.presence` (1 online, 0 offline, with its firmware): what the asset page's Live card reads. A reading lands when the presence or firmware changes, else hourly; a device the controller doesn't list reads offline and is named in `missing`. The worker does the same every five minutes. 409 before UniFi is linked.",
31194
+ status: 200,
31195
+ response: { description: "What the sync did." },
31196
+ errors: { 403: true, 409: true }
31197
+ },
31198
+ links: {
31199
+ method: "get",
31200
+ path: "/sensors/links",
31201
+ scopes: ["places:read"],
31202
+ tag: "Sensors",
31203
+ summary: "List the mapped entities",
31204
+ description: "Every mapped entity, or only the ones on `nodeId`, with the name of what it lands on (`nodeTitle`). A mapping on an asset kept Not shared is its member's alone.",
31205
+ query: [{ name: "nodeId", description: "Only the mappings on this Place or asset." }],
31206
+ querySchema: sensorLinksQuerySchema,
31207
+ status: 200,
31208
+ response: { description: "The mappings, by entity id." },
31209
+ errors: { 400: true }
31210
+ },
31211
+ createLink: {
31212
+ method: "post",
31213
+ path: "/sensors/links",
31214
+ scopes: ["places:write"],
31215
+ tag: "Sensors",
31216
+ summary: "Map an entity",
31217
+ description: "Map a Home Assistant entity (`externalId`) to a Place, an asset, or an animal (its room: the Place it's housed in) as a `reading`: a Place takes `temp`, `humidity`, `heater`, `water`, `door`, `feeder` or `light`; an asset `state`, `power` or `energy`. Mapping an entity that's already mapped moves it. 400 when the reading doesn't fit; 404 for no such node; 409 for an animal with no room.",
31218
+ body: createSensorLinkInputSchema,
31219
+ status: 201,
31220
+ response: { description: "The mapping." },
31221
+ errors: { 400: true, 404: true, 409: true }
31222
+ },
31223
+ removeLink: {
31224
+ method: "delete",
31225
+ path: "/sensors/links/{id}",
31226
+ scopes: ["places:write"],
31227
+ tag: "Sensors",
31228
+ summary: "Unmap an entity",
31229
+ description: "Remove one mapping; the readings it gave stay on the ledger.",
31230
+ status: 204,
31231
+ response: { description: "Removed." },
31232
+ errors: { 404: true }
31233
+ }
31234
+ });
31235
+
31236
+ // ../../packages/schema/src/operations/settings.ts
31237
+ var settingsOperations = defineOperations({
31238
+ get: {
31239
+ method: "get",
31240
+ path: "/settings",
31241
+ scopes: ["settings:read"],
31242
+ tag: "Settings",
31243
+ summary: "Get settings",
31244
+ description: "The caller's settings (lazily created). The key must carry a user identity.",
31245
+ status: 200,
31246
+ response: { description: "The caller's settings." }
31247
+ },
31248
+ update: {
31249
+ method: "patch",
31250
+ path: "/settings",
31251
+ scopes: ["settings:write"],
31252
+ tag: "Settings",
31253
+ summary: "Update settings",
31254
+ description: "Patch the caller's settings; fires `settings.updated`.",
31255
+ body: updateUserSettingsInputSchema,
31256
+ status: 200,
31257
+ response: { description: "The updated settings." },
31258
+ errors: { 400: true },
31259
+ webhook: "settings.updated"
31260
+ }
31261
+ });
31262
+
29768
31263
  // ../../packages/schema/src/operations/shopping-lists.ts
29769
31264
  var shoppingListOperations = defineOperations({
29770
31265
  ...crudOperations({
@@ -29783,7 +31278,7 @@ var shoppingListOperations = defineOperations({
29783
31278
  // ../../packages/schema/src/operations/tap-tags.ts
29784
31279
  var READ = ["habits:read", "routines:read"];
29785
31280
  var WRITE = ["habits:write", "routines:write"];
29786
- var pathParams = { id: "The tag's id: the ULID the sticker's link carries (`/t/{id}`)." };
31281
+ var pathParams2 = { id: "The tag's id: the ULID the sticker's link carries (`/t/{id}`)." };
29787
31282
  var tapTagOperations = defineOperations({
29788
31283
  list: {
29789
31284
  method: "get",
@@ -29811,7 +31306,7 @@ var tapTagOperations = defineOperations({
29811
31306
  get: {
29812
31307
  method: "get",
29813
31308
  path: "/tap-tags/{id}",
29814
- pathParams,
31309
+ pathParams: pathParams2,
29815
31310
  scopes: READ,
29816
31311
  tag: "Habits",
29817
31312
  summary: "Get a tap tag",
@@ -29823,7 +31318,7 @@ var tapTagOperations = defineOperations({
29823
31318
  update: {
29824
31319
  method: "patch",
29825
31320
  path: "/tap-tags/{id}",
29826
- pathParams,
31321
+ pathParams: pathParams2,
29827
31322
  scopes: WRITE,
29828
31323
  tag: "Habits",
29829
31324
  summary: "Rename or rebind a tap tag",
@@ -29836,7 +31331,7 @@ var tapTagOperations = defineOperations({
29836
31331
  remove: {
29837
31332
  method: "delete",
29838
31333
  path: "/tap-tags/{id}",
29839
- pathParams,
31334
+ pathParams: pathParams2,
29840
31335
  scopes: WRITE,
29841
31336
  tag: "Habits",
29842
31337
  summary: "Delete a tap tag",
@@ -29848,7 +31343,7 @@ var tapTagOperations = defineOperations({
29848
31343
  tap: {
29849
31344
  method: "post",
29850
31345
  path: "/tap-tags/{id}/tap",
29851
- pathParams,
31346
+ pathParams: pathParams2,
29852
31347
  scopes: WRITE,
29853
31348
  tag: "Habits",
29854
31349
  summary: "Tap a tap tag",
@@ -29859,6 +31354,292 @@ var tapTagOperations = defineOperations({
29859
31354
  }
29860
31355
  });
29861
31356
 
31357
+ // ../../packages/schema/src/operations/tasks.ts
31358
+ var taskListQuery = taskListQuerySchema.extend(paginationQuerySchema.shape).extend({ filter: taskListFilterSchema.optional().default("all") });
31359
+ var taskOperations = defineOperations({
31360
+ list: {
31361
+ method: "get",
31362
+ path: "/tasks",
31363
+ scopes: ["tasks:read"],
31364
+ tag: "Tasks",
31365
+ summary: "List tasks",
31366
+ description: "Every task in the key's Space, newest first.",
31367
+ query: [
31368
+ {
31369
+ name: "filter",
31370
+ description: "`all` (default), `unsorted` (the Inbox's tasks to file: open, top-level, not proposed, with no planned day, Deadline, Area or Project) or `starred`.",
31371
+ enum: ["all", "unsorted", "starred"]
31372
+ },
31373
+ {
31374
+ name: "areaId",
31375
+ description: "Only rows in this Area (ULID) \u2014 a project task by its project's Area (its effective Area)."
31376
+ },
31377
+ { name: "projectId", description: "Only tasks attached to this project (ULID)." },
31378
+ {
31379
+ name: "status",
31380
+ description: "Only rows in any of these statuses (comma list or repeated key): `todo`, `in_progress`, `done`, `cancelled`."
31381
+ },
31382
+ {
31383
+ name: "tag",
31384
+ description: "Only tasks carrying every listed tag (comma list or repeated key; case-insensitive, a leading `#` is ignored)."
31385
+ },
31386
+ {
31387
+ name: "updatedSince",
31388
+ description: "Only rows whose `updatedAt` is at or after this instant \u2014 an ISO 8601 datetime with offset, or a bare `YYYY-MM-DD` (UTC midnight).",
31389
+ format: "date-time"
31390
+ },
31391
+ {
31392
+ name: "dueBefore",
31393
+ description: "Only rows due strictly before this instant (undated rows are excluded) \u2014 an ISO 8601 datetime with offset, or a bare `YYYY-MM-DD` (UTC midnight).",
31394
+ format: "date-time"
31395
+ },
31396
+ {
31397
+ name: "owner",
31398
+ description: "Only tasks with this Owner: `me`, `member:<id|email>`, `agent:<id|name>` (every desk) or `agent:<id|name>/<desk>`; `any` = owned, `none` = unowned. An unknown party is a 400."
31399
+ },
31400
+ {
31401
+ name: "waitingOn",
31402
+ description: "Only tasks waiting on this party: `me`, `member:<id|email>` or `agent:<id|name>`; `any` = waiting on someone, `none` = not waiting. An unknown party is a 400."
31403
+ },
31404
+ {
31405
+ name: "completedSince",
31406
+ description: "Only tasks finished at or after this instant (`completedAt`; open and cancelled tasks have none) \u2014 an ISO 8601 datetime with offset, or a bare `YYYY-MM-DD` (UTC midnight).",
31407
+ format: "date-time"
31408
+ },
31409
+ {
31410
+ name: "completedBefore",
31411
+ description: "Only tasks finished strictly before this instant \u2014 an ISO 8601 datetime with offset, or a bare `YYYY-MM-DD` (UTC midnight).",
31412
+ format: "date-time"
31413
+ },
31414
+ {
31415
+ name: "proposed",
31416
+ description: "`true` = only tasks Proposed by the agent (an Agent made them for a member, who hasn't accepted yet); `false` = only tasks on the plan. Absent = both.",
31417
+ type: "boolean"
31418
+ },
31419
+ {
31420
+ name: "ready",
31421
+ description: "`true` = only open tasks whose Owner marked them Ready for you; `false` = the rest. Absent = both.",
31422
+ type: "boolean"
31423
+ },
31424
+ {
31425
+ name: "drifted",
31426
+ description: "`true` = only open tasks whose planned day moved 3+ times (the weekly plan's Drifted card); `false` = the rest. Absent = both.",
31427
+ type: "boolean"
31428
+ }
31429
+ ],
31430
+ querySchema: taskListQuery,
31431
+ paginated: true,
31432
+ status: 200,
31433
+ response: { description: "The Space's tasks.", list: true }
31434
+ },
31435
+ listSubtasks: {
31436
+ method: "get",
31437
+ path: "/tasks/{id}/subtasks",
31438
+ scopes: ["tasks:read"],
31439
+ tag: "Tasks",
31440
+ summary: "List subtasks",
31441
+ description: "The direct children of a task, ordered by `sortOrder` (ascending), then newest first.",
31442
+ status: 200,
31443
+ response: { description: "The ordered subtasks." },
31444
+ errors: { 404: true }
31445
+ },
31446
+ get: {
31447
+ method: "get",
31448
+ path: "/tasks/{id}",
31449
+ scopes: ["tasks:read"],
31450
+ tag: "Tasks",
31451
+ summary: "Get a task",
31452
+ description: "Fetch one task by id.",
31453
+ status: 200,
31454
+ response: { description: "The task." },
31455
+ errors: { 404: true }
31456
+ },
31457
+ create: {
31458
+ method: "post",
31459
+ path: "/tasks",
31460
+ scopes: ["tasks:write"],
31461
+ tag: "Tasks",
31462
+ summary: "Create a task",
31463
+ description: "Create a task; fires the `task.created` webhook.",
31464
+ body: createTaskInputSchema,
31465
+ status: 201,
31466
+ response: { description: "The created task." },
31467
+ errors: { 400: true, 409: true },
31468
+ webhook: "task.created"
31469
+ },
31470
+ update: {
31471
+ method: "patch",
31472
+ path: "/tasks/{id}",
31473
+ scopes: ["tasks:write"],
31474
+ tag: "Tasks",
31475
+ summary: "Update a task",
31476
+ description: "Patch a task; fires the `task.updated` webhook.",
31477
+ body: updateTaskInputSchema,
31478
+ status: 200,
31479
+ response: { description: "The updated task." },
31480
+ errors: { 400: true, 404: true, 409: true },
31481
+ webhook: "task.updated"
31482
+ },
31483
+ cancel: {
31484
+ method: "post",
31485
+ path: "/tasks/{id}/cancel",
31486
+ scopes: ["tasks:write"],
31487
+ tag: "Tasks",
31488
+ summary: "Cancel a task",
31489
+ description: 'Drop a task: its status becomes `cancelled` and any `completedAt` is cleared, so a drop never counts as Landed (#648). Fires `task.updated`. The same as a PATCH to `status: "cancelled"`.',
31490
+ status: 200,
31491
+ response: { description: "The cancelled task." },
31492
+ errors: { 404: true },
31493
+ webhook: "task.updated"
31494
+ },
31495
+ accept: {
31496
+ method: "post",
31497
+ path: "/tasks/{id}/accept",
31498
+ scopes: ["tasks:write"],
31499
+ tag: "Tasks",
31500
+ summary: "Accept a proposal",
31501
+ description: "Take a task Proposed by the agent onto the member's plan as it was suggested: its planned day, Deadline and Area stay; `proposedAt` clears (#889). A no-op success on a task that isn't a proposal. Fires `task.updated`. The same as a PATCH to `proposed: false`.",
31502
+ status: 200,
31503
+ response: { description: "The accepted task." },
31504
+ errors: { 404: true },
31505
+ webhook: "task.updated"
31506
+ },
31507
+ notNow: {
31508
+ method: "post",
31509
+ path: "/tasks/{id}/not-now",
31510
+ scopes: ["tasks:write"],
31511
+ tag: "Tasks",
31512
+ summary: "Not now on a proposal",
31513
+ description: "Keep a task Proposed by the agent, but out of the member's Inbox for a week (`notNowUntil`, #889). Let go is `cancel`. Fires `task.updated`.",
31514
+ status: 200,
31515
+ response: { description: "The proposal, back in a week." },
31516
+ errors: { 404: true, 409: "The task isn't a proposal." },
31517
+ webhook: "task.updated"
31518
+ },
31519
+ ready: {
31520
+ method: "post",
31521
+ path: "/tasks/{id}/ready",
31522
+ scopes: ["tasks:write"],
31523
+ tag: "Tasks",
31524
+ summary: "Mark Ready for you",
31525
+ description: "The task's Owner (the Space's agent, a Desk, a coding agent) finished something the member should look at: `readyAt` is set and the task reaches Now's Needs you with Review (#902). Looks good marks it done; Send back is a PATCH to `ready: false` (it reads Working on it again). The optional `note` is what's ready, in a line, with a link when there is one; absent keeps the note already there. Fires `task.updated`.",
31526
+ body: markTaskReadyInputSchema,
31527
+ status: 200,
31528
+ response: { description: "The task, Ready for you." },
31529
+ errors: { 404: true, 409: "The task is done or let go; only an open task can be ready." },
31530
+ webhook: "task.updated"
31531
+ },
31532
+ remove: {
31533
+ method: "delete",
31534
+ path: "/tasks/{id}",
31535
+ scopes: ["tasks:write"],
31536
+ tag: "Tasks",
31537
+ summary: "Delete a task",
31538
+ description: "Delete a task; fires the `task.deleted` webhook.",
31539
+ status: 204,
31540
+ response: { description: "Deleted." },
31541
+ errors: { 404: true },
31542
+ webhook: "task.deleted"
31543
+ }
31544
+ });
31545
+
31546
+ // ../../packages/schema/src/operations/today.ts
31547
+ var todayOperations = defineOperations({
31548
+ fromEarlier: {
31549
+ method: "get",
31550
+ path: "/today/from-earlier",
31551
+ scopes: ["tasks:read"],
31552
+ tag: "Reads",
31553
+ summary: "From earlier",
31554
+ 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`.',
31555
+ query: [
31556
+ {
31557
+ name: "from",
31558
+ description: "Your local day start, an ISO instant (or a bare date, read as UTC midnight). Defaults to the current UTC day."
31559
+ },
31560
+ { name: "areaId", description: "Only tasks in this Area (own or their Project's)." }
31561
+ ],
31562
+ querySchema: fromEarlierQuerySchema,
31563
+ status: 200,
31564
+ response: { description: "From earlier.", view: "FromEarlierResponse" },
31565
+ errors: { 400: true }
31566
+ },
31567
+ rollover: {
31568
+ method: "post",
31569
+ path: "/today/rollover",
31570
+ scopes: ["tasks:write"],
31571
+ tag: "Tasks",
31572
+ summary: "Answer From earlier",
31573
+ 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.',
31574
+ body: rolloverInputSchema,
31575
+ status: 201,
31576
+ response: { description: "How many tasks and occurrences changed." },
31577
+ errors: { 400: true }
31578
+ }
31579
+ });
31580
+
31581
+ // ../../packages/schema/src/operations/webhooks.ts
31582
+ var webhookOperations = defineOperations({
31583
+ list: {
31584
+ method: "get",
31585
+ path: "/webhooks",
31586
+ scopes: ["webhooks:read"],
31587
+ tag: "Webhooks",
31588
+ summary: "List webhook subscriptions",
31589
+ description: "The Space's outbound webhook endpoints (secrets omitted), newest first.",
31590
+ querySchema: paginationQuerySchema,
31591
+ paginated: true,
31592
+ status: 200,
31593
+ response: { description: "The subscriptions." }
31594
+ },
31595
+ create: {
31596
+ method: "post",
31597
+ path: "/webhooks",
31598
+ scopes: ["webhooks:write"],
31599
+ tag: "Webhooks",
31600
+ summary: "Register a webhook subscription",
31601
+ description: "Register an endpoint + events; returns the signing secret once.",
31602
+ body: createWebhookSubscriptionInputSchema,
31603
+ status: 201,
31604
+ response: { description: "The created subscription (with its one-time secret)." },
31605
+ errors: { 400: true }
31606
+ },
31607
+ get: {
31608
+ method: "get",
31609
+ path: "/webhooks/{id}",
31610
+ scopes: ["webhooks:read"],
31611
+ tag: "Webhooks",
31612
+ summary: "Get a webhook subscription",
31613
+ description: "One subscription (secret omitted).",
31614
+ status: 200,
31615
+ response: { description: "The subscription." },
31616
+ errors: { 404: true }
31617
+ },
31618
+ update: {
31619
+ method: "patch",
31620
+ path: "/webhooks/{id}",
31621
+ scopes: ["webhooks:write"],
31622
+ tag: "Webhooks",
31623
+ summary: "Update a webhook subscription",
31624
+ description: "Patch url/events, or pause with `active: false`.",
31625
+ body: updateWebhookSubscriptionInputSchema,
31626
+ status: 200,
31627
+ response: { description: "The updated subscription." },
31628
+ errors: { 400: true, 404: true }
31629
+ },
31630
+ remove: {
31631
+ method: "delete",
31632
+ path: "/webhooks/{id}",
31633
+ scopes: ["webhooks:write"],
31634
+ tag: "Webhooks",
31635
+ summary: "Delete a webhook subscription",
31636
+ description: "Remove an endpoint.",
31637
+ status: 204,
31638
+ response: { description: "Deleted." },
31639
+ errors: { 404: true }
31640
+ }
31641
+ });
31642
+
29862
31643
  // ../../node_modules/ulidx/dist/node/index.js
29863
31644
  import crypto from "crypto";
29864
31645
 
@@ -30111,7 +31892,7 @@ function parseCapture2(input, now) {
30111
31892
 
30112
31893
  // src/meta.ts
30113
31894
  var CLI_NAME = "do";
30114
- var VERSION = "1.303.0";
31895
+ var VERSION = "1.305.0";
30115
31896
  var API_PREFIX = "/api/v1";
30116
31897
  var DEFAULT_API_URL = "http://localhost:3001";
30117
31898
 
@@ -30530,12 +32311,9 @@ function entityFromOperations(ops, spec) {
30530
32311
  };
30531
32312
  }
30532
32313
  var ENTITIES = [
30533
- {
32314
+ entityFromOperations(taskOperations, {
30534
32315
  name: "tasks",
30535
- path: "/tasks",
30536
- readScopes: ["tasks:read"],
30537
32316
  returns: "Task",
30538
- canGet: true,
30539
32317
  listFlags: [
30540
32318
  {
30541
32319
  name: "filter",
@@ -30577,21 +32355,12 @@ var ENTITIES = [
30577
32355
  boolean: true
30578
32356
  }
30579
32357
  ],
30580
- write: {
30581
- scopes: ["tasks:write"],
30582
- createSchema: createTaskInputSchema,
30583
- createInput: "CreateTaskInput",
30584
- updateSchema: updateTaskInputSchema,
30585
- updateInput: "UpdateTaskInput",
30586
- canDelete: true
30587
- }
30588
- },
30589
- {
32358
+ createInput: "CreateTaskInput",
32359
+ updateInput: "UpdateTaskInput"
32360
+ }),
32361
+ entityFromOperations(habitOperations, {
30590
32362
  name: "habits",
30591
- path: "/habits",
30592
- readScopes: ["habits:read"],
30593
32363
  returns: "HabitSummary",
30594
- canGet: true,
30595
32364
  listFlags: [
30596
32365
  AREA_FLAG,
30597
32366
  UPDATED_SINCE_FLAG,
@@ -30600,15 +32369,9 @@ var ENTITIES = [
30600
32369
  description: "list the Grown shelf (retired habits) instead of the growing ones"
30601
32370
  }
30602
32371
  ],
30603
- write: {
30604
- scopes: ["habits:write"],
30605
- createSchema: createHabitInputSchema,
30606
- createInput: "CreateHabitInput",
30607
- updateSchema: updateHabitInputSchema,
30608
- updateInput: "UpdateHabitInput",
30609
- canDelete: true
30610
- }
30611
- },
32372
+ createInput: "CreateHabitInput",
32373
+ updateInput: "UpdateHabitInput"
32374
+ }),
30612
32375
  {
30613
32376
  name: "projects",
30614
32377
  path: "/projects",
@@ -30673,22 +32436,13 @@ var ENTITIES = [
30673
32436
  canDelete: true
30674
32437
  }
30675
32438
  },
30676
- {
32439
+ entityFromOperations(routineOperations, {
30677
32440
  name: "routines",
30678
- path: "/routines",
30679
- readScopes: ["routines:read"],
30680
32441
  returns: "RoutineView",
30681
- canGet: true,
30682
32442
  listFlags: [],
30683
- write: {
30684
- scopes: ["routines:write"],
30685
- createSchema: createRoutineInputSchema,
30686
- createInput: "CreateRoutineInput",
30687
- updateSchema: updateRoutineInputSchema,
30688
- updateInput: "UpdateRoutineInput",
30689
- canDelete: true
30690
- }
30691
- },
32443
+ createInput: "CreateRoutineInput",
32444
+ updateInput: "UpdateRoutineInput"
32445
+ }),
30692
32446
  {
30693
32447
  name: "notes",
30694
32448
  path: "/notes",
@@ -30820,12 +32574,9 @@ var ENTITIES = [
30820
32574
  canDelete: true
30821
32575
  }
30822
32576
  },
30823
- {
32577
+ entityFromOperations(householdOperations, {
30824
32578
  name: "households",
30825
- path: "/households",
30826
- readScopes: ["contacts:read"],
30827
32579
  returns: "HouseholdView",
30828
- canGet: true,
30829
32580
  listFlags: [
30830
32581
  {
30831
32582
  name: "contact",
@@ -30833,15 +32584,9 @@ var ENTITIES = [
30833
32584
  query: "contactId"
30834
32585
  }
30835
32586
  ],
30836
- write: {
30837
- scopes: ["contacts:write"],
30838
- createSchema: createHouseholdInputSchema,
30839
- createInput: "CreateHouseholdInput",
30840
- updateSchema: updateHouseholdInputSchema,
30841
- updateInput: "UpdateHouseholdInput",
30842
- canDelete: true
30843
- }
30844
- },
32587
+ createInput: "CreateHouseholdInput",
32588
+ updateInput: "UpdateHouseholdInput"
32589
+ }),
30845
32590
  {
30846
32591
  name: "guest-lists",
30847
32592
  path: "/guest-lists",
@@ -30858,80 +32603,44 @@ var ENTITIES = [
30858
32603
  canDelete: true
30859
32604
  }
30860
32605
  },
30861
- {
32606
+ entityFromOperations(guestListEventOperations, {
30862
32607
  name: "guest-list-events",
30863
- path: "/guest-list-events",
30864
- readScopes: ["contacts:read"],
30865
32608
  returns: "GuestListEvent",
30866
- canGet: true,
30867
32609
  listFlags: [
30868
32610
  { name: "list", description: "the guest list's id (required)", query: "guestListId" }
30869
32611
  ],
30870
- write: {
30871
- scopes: ["contacts:write"],
30872
- createSchema: createGuestListEventInputSchema,
30873
- createInput: "CreateGuestListEventInput",
30874
- updateSchema: updateGuestListEventInputSchema,
30875
- updateInput: "UpdateGuestListEventInput",
30876
- canDelete: true
30877
- }
30878
- },
30879
- {
32612
+ createInput: "CreateGuestListEventInput",
32613
+ updateInput: "UpdateGuestListEventInput"
32614
+ }),
32615
+ entityFromOperations(guestTableOperations, {
30880
32616
  name: "guest-tables",
30881
- path: "/guest-tables",
30882
- readScopes: ["contacts:read"],
30883
32617
  returns: "GuestTableView",
30884
- canGet: true,
30885
32618
  listFlags: [
30886
32619
  { name: "list", description: "the guest list's id (required)", query: "guestListId" }
30887
32620
  ],
30888
- write: {
30889
- scopes: ["contacts:write"],
30890
- createSchema: createGuestTableInputSchema,
30891
- createInput: "CreateGuestTableInput",
30892
- updateSchema: updateGuestTableInputSchema,
30893
- updateInput: "UpdateGuestTableInput",
30894
- canDelete: true
30895
- }
30896
- },
30897
- {
32621
+ createInput: "CreateGuestTableInput",
32622
+ updateInput: "UpdateGuestTableInput"
32623
+ }),
32624
+ entityFromOperations(rsvpQuestionOperations, {
30898
32625
  name: "rsvp-questions",
30899
- path: "/rsvp-questions",
30900
- readScopes: ["contacts:read"],
30901
32626
  returns: "RsvpQuestion",
30902
- canGet: true,
30903
32627
  listFlags: [
30904
32628
  { name: "list", description: "the guest list's id (required)", query: "guestListId" }
30905
32629
  ],
30906
- write: {
30907
- scopes: ["contacts:write"],
30908
- createSchema: createRsvpQuestionInputSchema,
30909
- createInput: "CreateRsvpQuestionInput",
30910
- updateSchema: updateRsvpQuestionInputSchema,
30911
- updateInput: "UpdateRsvpQuestionInput",
30912
- canDelete: true
30913
- }
30914
- },
30915
- {
32630
+ createInput: "CreateRsvpQuestionInput",
32631
+ updateInput: "UpdateRsvpQuestionInput"
32632
+ }),
32633
+ entityFromOperations(guestOperations, {
30916
32634
  name: "guests",
30917
- path: "/guests",
30918
- readScopes: ["contacts:read"],
30919
32635
  returns: "GuestView",
30920
- canGet: true,
30921
32636
  listFlags: [
30922
32637
  { name: "list", description: "only this guest list's guests", query: "guestListId" },
30923
32638
  { name: "contact", description: "only this contact's guest rows", query: "contactId" },
30924
32639
  { name: "state", description: "yes, no, maybe or waiting (overall)", query: "state" }
30925
32640
  ],
30926
- write: {
30927
- scopes: ["contacts:write"],
30928
- createSchema: createGuestInputSchema,
30929
- createInput: "CreateGuestInput",
30930
- updateSchema: updateGuestInputSchema,
30931
- updateInput: "UpdateGuestInput",
30932
- canDelete: true
30933
- }
30934
- },
32641
+ createInput: "CreateGuestInput",
32642
+ updateInput: "UpdateGuestInput"
32643
+ }),
30935
32644
  {
30936
32645
  name: "key-dates",
30937
32646
  path: "/key-dates",
@@ -31137,12 +32846,9 @@ var ENTITIES = [
31137
32846
  createInput: "CreateMealPlanInput",
31138
32847
  updateInput: "UpdateMealPlanInput"
31139
32848
  }),
31140
- {
32849
+ entityFromOperations({ list: occurrenceOperations.list }, {
31141
32850
  name: "occurrences",
31142
- path: "/occurrences",
31143
- readScopes: ["occurrences:read"],
31144
32851
  returns: "Occurrence",
31145
- canGet: false,
31146
32852
  listFlags: [
31147
32853
  {
31148
32854
  name: "template-id",
@@ -31151,7 +32857,7 @@ var ENTITIES = [
31151
32857
  query: "templateId"
31152
32858
  }
31153
32859
  ]
31154
- },
32860
+ }),
31155
32861
  {
31156
32862
  name: "time-blocks",
31157
32863
  path: "/time-blocks",
@@ -31175,30 +32881,14 @@ var ENTITIES = [
31175
32881
  canDelete: true
31176
32882
  }
31177
32883
  },
31178
- {
32884
+ entityFromOperations(blockTemplateOperations, {
31179
32885
  name: "block-templates",
31180
- path: "/block-templates",
31181
- readScopes: ["calendar:read"],
31182
32886
  returns: "BlockTemplateView",
31183
- canGet: true,
31184
32887
  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
- }
32888
+ createInput: "CreateBlockTemplateInput",
32889
+ updateInput: "UpdateBlockTemplateInput"
32890
+ }),
32891
+ entityFromOperations({ list: webhookOperations.list, get: webhookOperations.get }, { name: "webhooks", returns: "WebhookSubscription", listFlags: [] })
31202
32892
  ];
31203
32893
  var entityByName = (name) => ENTITIES.find((e) => e.name === name);
31204
32894
  function entityOperations(e) {
@@ -31628,18 +33318,18 @@ var HEALTH_COMMAND_SPECS = [
31628
33318
  summary: "Whether your Google Health is connected, the last sync, and what stops it.",
31629
33319
  args: [],
31630
33320
  flags: [],
31631
- scopes: READ2,
33321
+ scopes: [...googleHealthOperations.status.scopes],
31632
33322
  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."
33323
+ 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
33324
  },
31635
33325
  {
31636
33326
  path: ["health", "google", "sync"],
31637
33327
  summary: "Sync your Google Health now (sleep, resting heart rate, HRV, steps, workouts), as the morning sync does.",
31638
33328
  args: [],
31639
33329
  flags: [],
31640
- scopes: WRITE3,
33330
+ scopes: [...googleHealthOperations.sync.scopes],
31641
33331
  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."
33332
+ 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
33333
  },
31644
33334
  {
31645
33335
  path: ["health", "withings", "status"],
@@ -31923,9 +33613,9 @@ var COMMAND_SPECS = [
31923
33613
  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
33614
  args: [],
31925
33615
  flags: [{ name: "area", type: "string", description: "Only tasks in this Area (id)." }],
31926
- scopes: ["tasks:read"],
33616
+ scopes: [...todayOperations.fromEarlier.scopes],
31927
33617
  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`."
33618
+ 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
33619
  },
31930
33620
  {
31931
33621
  path: ["today", "answer"],
@@ -32406,27 +34096,27 @@ var COMMAND_SPECS = [
32406
34096
  summary: "Drop a task: status cancelled, never counted as Landed.",
32407
34097
  args: [{ name: "id", required: true, description: "Task ULID." }],
32408
34098
  flags: [],
32409
- scopes: ["tasks:write"],
34099
+ scopes: [...taskOperations.cancel.scopes],
32410
34100
  returns: "Task",
32411
- notes: "POST /api/v1/tasks/{id}/cancel. Use this, not `task done`, for a dropped task."
34101
+ notes: `${routeOf(taskOperations.cancel)}. Use this, not \`task done\`, for a dropped task.`
32412
34102
  },
32413
34103
  {
32414
34104
  path: ["tasks", "accept"],
32415
34105
  summary: "Accept a task Proposed by the agent: it joins the plan as suggested (day, Deadline, Area kept).",
32416
34106
  args: [{ name: "id", required: true, description: "Task ULID." }],
32417
34107
  flags: [],
32418
- scopes: ["tasks:write"],
34108
+ scopes: [...taskOperations.accept.scopes],
32419
34109
  returns: "Task",
32420
- notes: "POST /api/v1/tasks/{id}/accept (#889). A no-op on a task that isn't a proposal. List proposals with `do tasks list --proposed`."
34110
+ notes: `${routeOf(taskOperations.accept)} (#889). A no-op on a task that isn't a proposal. List proposals with \`do tasks list --proposed\`.`
32421
34111
  },
32422
34112
  {
32423
34113
  path: ["tasks", "dismiss"],
32424
34114
  summary: "Not now on a task Proposed by the agent: it leaves the Inbox for a week, still proposed.",
32425
34115
  args: [{ name: "id", required: true, description: "Task ULID." }],
32426
34116
  flags: [],
32427
- scopes: ["tasks:write"],
34117
+ scopes: [...taskOperations.notNow.scopes],
32428
34118
  returns: "Task",
32429
- notes: "POST /api/v1/tasks/{id}/not-now (#889); 409 when the task isn't a proposal. Let go is `do task cancel`."
34119
+ notes: `${routeOf(taskOperations.notNow)} (#889); 409 when the task isn't a proposal. Let go is \`do task cancel\`.`
32430
34120
  },
32431
34121
  {
32432
34122
  path: ["block-templates", "week"],
@@ -32435,18 +34125,18 @@ var COMMAND_SPECS = [
32435
34125
  flags: [
32436
34126
  { name: "start", type: "string", description: "The week's Monday (YYYY-MM-DD). Required." }
32437
34127
  ],
32438
- scopes: ["calendar:read"],
34128
+ scopes: [...blockTemplateOperations.week.scopes],
32439
34129
  returns: "BlockTemplateInstance[]",
32440
- notes: "GET /api/v1/block-templates/week?start= (plan vs record, phase 1)."
34130
+ notes: `${routeOf(blockTemplateOperations.week)}?start= (plan vs record, phase 1).`
32441
34131
  },
32442
34132
  {
32443
34133
  path: ["block-templates", "defaults"],
32444
34134
  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
34135
  args: [],
32446
34136
  flags: [],
32447
- scopes: ["calendar:write"],
34137
+ scopes: [...blockTemplateOperations.defaults.scopes],
32448
34138
  returns: "BlockTemplateView[]",
32449
- notes: "POST /api/v1/block-templates/defaults (plan vs record, phase 1)."
34139
+ notes: `${routeOf(blockTemplateOperations.defaults)} (plan vs record, phase 1).`
32450
34140
  },
32451
34141
  {
32452
34142
  path: ["time-blocks", "record"],
@@ -32584,18 +34274,18 @@ var COMMAND_SPECS = [
32584
34274
  },
32585
34275
  { name: "for", type: "string", description: "Only this member's plans (user id)." }
32586
34276
  ],
32587
- scopes: ["tasks:read"],
34277
+ scopes: [...planOperations.list.scopes],
32588
34278
  returns: "Plan[]",
32589
- notes: "GET /api/v1/plans (#912): the latest 20."
34279
+ notes: `${routeOf(planOperations.list)} (#912): the latest 20.`
32590
34280
  },
32591
34281
  {
32592
34282
  path: ["plans", "get"],
32593
34283
  summary: "One plan proposal with its items and each item's task as it is now.",
32594
34284
  args: [{ name: "id", required: true, description: "Plan id." }],
32595
34285
  flags: [],
32596
- scopes: ["tasks:read"],
34286
+ scopes: [...planOperations.get.scopes],
32597
34287
  returns: "Plan",
32598
- notes: "GET /api/v1/plans/{id} (#912)."
34288
+ notes: `${routeOf(planOperations.get)} (#912).`
32599
34289
  },
32600
34290
  {
32601
34291
  path: ["plans", "propose"],
@@ -32608,9 +34298,9 @@ var COMMAND_SPECS = [
32608
34298
  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
34299
  }
32610
34300
  ],
32611
- scopes: ["tasks:write"],
34301
+ scopes: [...planOperations.create.scopes],
32612
34302
  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."
34303
+ 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
34304
  },
32615
34305
  {
32616
34306
  path: ["plans", "accept"],
@@ -32620,9 +34310,9 @@ var COMMAND_SPECS = [
32620
34310
  { name: "items", required: false, description: "Item ids (default: every proposed item)." }
32621
34311
  ],
32622
34312
  flags: [],
32623
- scopes: ["tasks:write"],
34313
+ scopes: [...planOperations.accept.scopes],
32624
34314
  returns: "Plan",
32625
- notes: "POST /api/v1/plans/{id}/accept (#912). Undo with `do plans undo`."
34315
+ notes: `${routeOf(planOperations.accept)} (#912). Undo with \`do plans undo\`.`
32626
34316
  },
32627
34317
  {
32628
34318
  path: ["plans", "dismiss"],
@@ -32632,9 +34322,9 @@ var COMMAND_SPECS = [
32632
34322
  { name: "items", required: false, description: "Item ids (default: every proposed item)." }
32633
34323
  ],
32634
34324
  flags: [],
32635
- scopes: ["tasks:write"],
34325
+ scopes: [...planOperations.dismiss.scopes],
32636
34326
  returns: "Plan",
32637
- notes: "POST /api/v1/plans/{id}/dismiss (#912). Nothing is written to a task."
34327
+ notes: `${routeOf(planOperations.dismiss)} (#912). Nothing is written to a task.`
32638
34328
  },
32639
34329
  {
32640
34330
  path: ["plans", "undo"],
@@ -32644,9 +34334,9 @@ var COMMAND_SPECS = [
32644
34334
  { name: "items", required: false, description: "Item ids (default: every answered item)." }
32645
34335
  ],
32646
34336
  flags: [],
32647
- scopes: ["tasks:write"],
34337
+ scopes: [...planOperations.undo.scopes],
32648
34338
  returns: "Plan",
32649
- notes: "POST /api/v1/plans/{id}/undo (#912). The items read as proposed again."
34339
+ notes: `${routeOf(planOperations.undo)} (#912). The items read as proposed again.`
32650
34340
  },
32651
34341
  {
32652
34342
  path: ["plan", "rules", "list"],
@@ -32660,9 +34350,9 @@ var COMMAND_SPECS = [
32660
34350
  description: "fixed_time, keep_free, meaning, whose, limit or note."
32661
34351
  }
32662
34352
  ],
32663
- scopes: ["planning:read"],
34353
+ scopes: [...planningRuleOperations.list.scopes],
32664
34354
  returns: "PlanningRule[]",
32665
- notes: "GET /api/v1/planning/rules. Oldest first; `addedByAgent` marks the ones an agent added."
34355
+ notes: `${routeOf(planningRuleOperations.list)}. Oldest first; \`addedByAgent\` marks the ones an agent added.`
32666
34356
  },
32667
34357
  {
32668
34358
  path: ["plan", "rules", "add"],
@@ -32680,9 +34370,9 @@ var COMMAND_SPECS = [
32680
34370
  description: "The member it is for (user id; default: the member the key acts for)."
32681
34371
  }
32682
34372
  ],
32683
- scopes: ["planning:write"],
34373
+ scopes: [...planningRuleOperations.create.scopes],
32684
34374
  returns: "PlanningRule",
32685
- notes: "POST /api/v1/planning/rules. `forUserId` (--for) names the member; a rule without --rule is a note."
34375
+ notes: `${routeOf(planningRuleOperations.create)}. \`forUserId\` (--for) names the member; a rule without --rule is a note.`
32686
34376
  },
32687
34377
  {
32688
34378
  path: ["plan", "rules", "update"],
@@ -32696,18 +34386,18 @@ var COMMAND_SPECS = [
32696
34386
  description: "The new structured part as JSON (see `plan rules add`), or `none` to make it a note."
32697
34387
  }
32698
34388
  ],
32699
- scopes: ["planning:write"],
34389
+ scopes: [...planningRuleOperations.update.scopes],
32700
34390
  returns: "PlanningRule",
32701
- notes: "PATCH /api/v1/planning/rules/{id}."
34391
+ notes: `${routeOf(planningRuleOperations.update)}.`
32702
34392
  },
32703
34393
  {
32704
34394
  path: ["plan", "rules", "remove"],
32705
34395
  summary: "Remove a planning rule (to the trash).",
32706
34396
  args: [{ name: "id", required: true, description: "Rule id." }],
32707
34397
  flags: [],
32708
- scopes: ["planning:write"],
34398
+ scopes: [...planningRuleOperations.remove.scopes],
32709
34399
  returns: "void",
32710
- notes: "DELETE /api/v1/planning/rules/{id}. Destructive: needs --allow-destructive."
34400
+ notes: `${routeOf(planningRuleOperations.remove)}. Destructive: needs --allow-destructive.`
32711
34401
  },
32712
34402
  {
32713
34403
  path: ["tasks", "ready"],
@@ -32720,16 +34410,16 @@ var COMMAND_SPECS = [
32720
34410
  description: `What's ready, in a line, with a link when there is one ("Draft ready: 38 hours, 3,420 euro \u2014 https://\u2026"); at most 500 characters. Absent keeps the note already there.`
32721
34411
  }
32722
34412
  ],
32723
- scopes: ["tasks:write"],
34413
+ scopes: [...taskOperations.ready.scopes],
32724
34414
  returns: "Task",
32725
- notes: "POST /api/v1/tasks/{id}/ready (#902); 409 when the task is done or let go. It reaches Now's Needs you with Review (Looks good = done, Send back = `ready: false`). List them with `do tasks list --ready`."
34415
+ notes: `${routeOf(taskOperations.ready)} (#902); 409 when the task is done or let go. It reaches Now's Needs you with Review (Looks good = done, Send back = \`ready: false\`). List them with \`do tasks list --ready\`.`
32726
34416
  },
32727
34417
  {
32728
34418
  path: ["task", "reopen"],
32729
34419
  summary: "Reopen a done or cancelled task (status todo; clears completedAt).",
32730
34420
  args: [{ name: "id", required: true, description: "Task ULID." }],
32731
34421
  flags: [],
32732
- scopes: ["tasks:write"],
34422
+ scopes: [...taskOperations.update.scopes],
32733
34423
  returns: "Task"
32734
34424
  },
32735
34425
  {
@@ -32749,7 +34439,7 @@ var COMMAND_SPECS = [
32749
34439
  description: "How long it takes, in minutes (sets durationMinutes)."
32750
34440
  }
32751
34441
  ],
32752
- scopes: ["tasks:write"],
34442
+ scopes: [...taskOperations.update.scopes],
32753
34443
  returns: "Task"
32754
34444
  },
32755
34445
  {
@@ -32768,7 +34458,7 @@ var COMMAND_SPECS = [
32768
34458
  description: "How long it takes, in minutes (sets durationMinutes)."
32769
34459
  }
32770
34460
  ],
32771
- scopes: ["tasks:write"],
34461
+ scopes: [...taskOperations.update.scopes],
32772
34462
  returns: "Task"
32773
34463
  },
32774
34464
  {
@@ -32789,7 +34479,7 @@ var COMMAND_SPECS = [
32789
34479
  description: 'A short note on the check-in ("felt heavy"), at most 280 characters.'
32790
34480
  }
32791
34481
  ],
32792
- scopes: ["habits:write"],
34482
+ scopes: [...habitOperations.checkIn.scopes],
32793
34483
  returns: "HabitSummary"
32794
34484
  },
32795
34485
  {
@@ -32816,7 +34506,7 @@ var COMMAND_SPECS = [
32816
34506
  description: 'A short note on the check-in ("felt heavy"), at most 280 characters.'
32817
34507
  }
32818
34508
  ],
32819
- scopes: ["habits:write"],
34509
+ scopes: [...habitOperations.checkInByTitle.scopes],
32820
34510
  returns: "HabitSummary",
32821
34511
  notes: `POST /api/v1/habits/check-in. Title or alias, case-insensitive; several matches \u2192 409 with error.details.candidates (id, title, aliases), none \u2192 404 naming close titles; a habit counted from Google Fit, Strava or GitHub \u2192 409. --set writes the difference to the day's total as its own check-in. An Agent's key records the check-in as that Agent's ("<agent>, 07:12"). --note puts a short note on it ("drill done, felt heavy" \u2192 --note "felt heavy"); the reply's lastCheckIn.id is the check-in, for \`do habit note\` later.`
32822
34512
  },
@@ -32828,7 +34518,7 @@ var COMMAND_SPECS = [
32828
34518
  { name: "alias", required: true, description: "The other name." }
32829
34519
  ],
32830
34520
  flags: [],
32831
- scopes: ["habits:write"],
34521
+ scopes: [...habitOperations.addAlias.scopes],
32832
34522
  returns: "HabitSummary",
32833
34523
  notes: "POST /api/v1/habits/:id/aliases. One already there (ignoring case) is a no-op; at most 12."
32834
34524
  },
@@ -32840,7 +34530,7 @@ var COMMAND_SPECS = [
32840
34530
  { name: "alias", required: true, description: "The alias to drop (ignoring case)." }
32841
34531
  ],
32842
34532
  flags: [],
32843
- scopes: ["habits:write"],
34533
+ scopes: [...habitOperations.removeAlias.scopes],
32844
34534
  returns: "HabitSummary",
32845
34535
  notes: "DELETE /api/v1/habits/:id/aliases/:alias. 404 when the habit doesn't have it."
32846
34536
  },
@@ -32849,9 +34539,9 @@ var COMMAND_SPECS = [
32849
34539
  summary: "What's left today: the habits due by part of day, and today's routines.",
32850
34540
  args: [],
32851
34541
  flags: [],
32852
- scopes: ["habits:read"],
34542
+ scopes: [...habitOperations.today.scopes],
32853
34543
  returns: "HabitsTodayResponse",
32854
- notes: "GET /api/v1/habits/today. Your saved time zone's day; already filtered by schedule. Each habit says done/open/skipped (never overdue), its value today, the week for an N-per-week habit and its last check-in (`by` names the Agent). Quit habits are left out."
34544
+ notes: `${routeOf(habitOperations.today)}. Your saved time zone's day; already filtered by schedule. Each habit says done/open/skipped (never overdue), its value today, the week for an N-per-week habit and its last check-in (\`by\` names the Agent). Quit habits are left out.`
32855
34545
  },
32856
34546
  {
32857
34547
  path: ["habit", "check-ins"],
@@ -32861,7 +34551,7 @@ var COMMAND_SPECS = [
32861
34551
  { name: "before", type: "string", description: "Only check-ins before this instant (ISO)." },
32862
34552
  { name: "limit", type: "string", description: "Page size 1\u2013200 (default 50)." }
32863
34553
  ],
32864
- scopes: ["habits:read"],
34554
+ scopes: [...habitOperations.listCheckIns.scopes],
32865
34555
  returns: "HabitEntry"
32866
34556
  },
32867
34557
  {
@@ -32877,9 +34567,9 @@ var COMMAND_SPECS = [
32877
34567
  { name: "note", required: false, description: "The note, at most 280 characters." }
32878
34568
  ],
32879
34569
  flags: [{ name: "clear", type: "boolean", description: "Drop the check-in's note." }],
32880
- scopes: ["habits:write"],
34570
+ scopes: [...habitOperations.updateCheckIn.scopes],
32881
34571
  returns: "HabitEntry",
32882
- notes: "PATCH /api/v1/habits/{id}/check-ins/{entryId}. Replaces the note; the amount and time stay (undo a wrong check-in instead). A skip's note is its why."
34572
+ notes: `${routeOf(habitOperations.updateCheckIn)}. Replaces the note; the amount and time stay (undo a wrong check-in instead). A skip's note is its why.`
32883
34573
  },
32884
34574
  {
32885
34575
  path: ["habit", "skip"],
@@ -32893,9 +34583,9 @@ var COMMAND_SPECS = [
32893
34583
  description: 'Why, if you like ("Dentist"); shows in the log.'
32894
34584
  }
32895
34585
  ],
32896
- scopes: ["habits:write"],
34586
+ scopes: [...habitOperations.skip.scopes],
32897
34587
  returns: "HabitSummary",
32898
- notes: "POST /api/v1/habits/{id}/skip. The streak treats the period as neutral and spends no grace; the habit leaves Now for the day. Skipping a skipped day changes nothing. Undo: delete the skip entry (`do habit check-ins <id>` lists it with kind `skip`)."
34588
+ notes: `${routeOf(habitOperations.skip)}. The streak treats the period as neutral and spends no grace; the habit leaves Now for the day. Skipping a skipped day changes nothing. Undo: delete the skip entry (\`do habit check-ins <id>\` lists it with kind \`skip\`).`
32899
34589
  },
32900
34590
  {
32901
34591
  path: ["routine", "done"],
@@ -32908,9 +34598,9 @@ var COMMAND_SPECS = [
32908
34598
  }
32909
34599
  ],
32910
34600
  flags: [],
32911
- scopes: ["routines:write"],
34601
+ scopes: [...routineOperations.markDone.scopes],
32912
34602
  returns: "RoutineDoneResult",
32913
- notes: "POST /api/v1/routines/{id}/done. Today's run (your saved time zone's day), started if there's none. Each habit step checks in its habit; a habit already done today isn't counted twice; a step whose habit is skipped today is left alone. Calling it again changes nothing. Returns `run` and `taken` (the steps it resolved). Undo all of it with `do routine undo <run.id> <taken stepRunIds\u2026>`."
34603
+ notes: `${routeOf(routineOperations.markDone)}. Today's run (your saved time zone's day), started if there's none. Each habit step checks in its habit; a habit already done today isn't counted twice; a step whose habit is skipped today is left alone. Calling it again changes nothing. Returns \`run\` and \`taken\` (the steps it resolved). Undo all of it with \`do routine undo <run.id> <taken stepRunIds\u2026>\`.`
32914
34604
  },
32915
34605
  {
32916
34606
  path: ["routine", "undo"],
@@ -32928,9 +34618,9 @@ var COMMAND_SPECS = [
32928
34618
  }
32929
34619
  ],
32930
34620
  flags: [],
32931
- scopes: ["routines:write"],
34621
+ scopes: [...routineOperations.undoSteps.scopes],
32932
34622
  returns: "RoutineRunView",
32933
- notes: "POST /api/v1/routines/runs/{runId}/undo. Exactly the check-ins those steps wrote are removed; a finished run reopens. Steps no longer done are left alone."
34623
+ notes: `${routeOf(routineOperations.undoSteps)}. Exactly the check-ins those steps wrote are removed; a finished run reopens. Steps no longer done are left alone.`
32934
34624
  },
32935
34625
  {
32936
34626
  path: ["habit", "pause"],
@@ -32943,9 +34633,9 @@ var COMMAND_SPECS = [
32943
34633
  description: "The day it's back (YYYY-MM-DD), after today. Required."
32944
34634
  }
32945
34635
  ],
32946
- scopes: ["habits:write"],
34636
+ scopes: [...habitOperations.pause.scopes],
32947
34637
  returns: "HabitSummary",
32948
- notes: "POST /api/v1/habits/{id}/pause. Pausing a paused habit moves the day it's back. Paused days are neutral: the streak is frozen, never broken."
34638
+ notes: `${routeOf(habitOperations.pause)}. Pausing a paused habit moves the day it's back. Paused days are neutral: the streak is frozen, never broken.`
32949
34639
  },
32950
34640
  {
32951
34641
  path: ["habit", "remind"],
@@ -32955,36 +34645,36 @@ var COMMAND_SPECS = [
32955
34645
  { name: "state", required: true, description: "`on` or `off`." }
32956
34646
  ],
32957
34647
  flags: [],
32958
- scopes: ["habits:write"],
34648
+ scopes: [...habitOperations.update.scopes],
32959
34649
  returns: "HabitSummary",
32960
- notes: "PATCH /api/v1/habits/{id} {remind}. Off by default. On, the habit sends one Web Push a day at its time of day (a part of day: morning 08:00, midday 12:00, evening 18:00; none: 09:00), only in waking hours (07:00\u201322:00), with Done / +1 / Skip today buttons; not when it's done, skipped, paused or off today."
34650
+ notes: `${routeOf(habitOperations.update)} {remind}. Off by default. On, the habit sends one Web Push a day at its time of day (a part of day: morning 08:00, midday 12:00, evening 18:00; none: 09:00), only in waking hours (07:00\u201322:00), with Done / +1 / Skip today buttons; not when it's done, skipped, paused or off today.`
32961
34651
  },
32962
34652
  {
32963
34653
  path: ["habit", "resume"],
32964
34654
  summary: "Resume a paused habit: it's back today.",
32965
34655
  args: [{ name: "id", required: true, description: "Habit ULID." }],
32966
34656
  flags: [],
32967
- scopes: ["habits:write"],
34657
+ scopes: [...habitOperations.resume.scopes],
32968
34658
  returns: "HabitSummary",
32969
- notes: "POST /api/v1/habits/{id}/resume. The rest so far stays neutral."
34659
+ notes: `${routeOf(habitOperations.resume)}. The rest so far stays neutral.`
32970
34660
  },
32971
34661
  {
32972
34662
  path: ["habit", "retire"],
32973
34663
  summary: `Retire a habit to the Grown shelf ("I'm done with the cold showers habit"): history kept.`,
32974
34664
  args: [{ name: "id", required: true, description: "Habit ULID." }],
32975
34665
  flags: [],
32976
- scopes: ["habits:write"],
34666
+ scopes: [...habitOperations.retire.scopes],
32977
34667
  returns: "HabitSummary",
32978
- notes: "POST /api/v1/habits/{id}/retire. Not a delete: it keeps every check-in and its plant blooms on the Grown shelf. It leaves Now and Today, sends no push, takes no check-in by title and is never due; Progress keeps its past. `do habits list --retired` lists the shelf. Undo: `do habit unretire <id>`."
34668
+ notes: `${routeOf(habitOperations.retire)}. Not a delete: it keeps every check-in and its plant blooms on the Grown shelf. It leaves Now and Today, sends no push, takes no check-in by title and is never due; Progress keeps its past. \`do habits list --retired\` lists the shelf. Undo: \`do habit unretire <id>\`.`
32979
34669
  },
32980
34670
  {
32981
34671
  path: ["habit", "unretire"],
32982
34672
  summary: "Bring a grown (retired) habit back: growing again from today.",
32983
34673
  args: [{ name: "id", required: true, description: "Habit ULID." }],
32984
34674
  flags: [],
32985
- scopes: ["habits:write"],
34675
+ scopes: [...habitOperations.unretire.scopes],
32986
34676
  returns: "HabitSummary",
32987
- notes: "POST /api/v1/habits/{id}/unretire. Its history is intact."
34677
+ notes: `${routeOf(habitOperations.unretire)}. Its history is intact.`
32988
34678
  },
32989
34679
  {
32990
34680
  path: ["away"],
@@ -33828,7 +35518,7 @@ var COMMAND_SPECS = [
33828
35518
  summary: "List the Space's Agents (id + name).",
33829
35519
  args: [],
33830
35520
  flags: [],
33831
- scopes: ["agents:read"],
35521
+ scopes: [...agentOperations.list.scopes],
33832
35522
  returns: "AgentSummary",
33833
35523
  notes: "Creating an Agent and minting its keys stays a member's job in the app."
33834
35524
  },
@@ -34145,7 +35835,7 @@ var COMMAND_SPECS = [
34145
35835
  summary: "Show the caller's settings for the Space.",
34146
35836
  args: [],
34147
35837
  flags: [],
34148
- scopes: ["settings:read"],
35838
+ scopes: [...settingsOperations.get.scopes],
34149
35839
  returns: "UserSettings",
34150
35840
  notes: "Needs a key minted while logged in (it carries the user identity)."
34151
35841
  },
@@ -34154,7 +35844,7 @@ var COMMAND_SPECS = [
34154
35844
  summary: "Patch the caller's settings.",
34155
35845
  args: [],
34156
35846
  flags: [{ name: "data", type: "string", description: "JSON of fields to change." }],
34157
- scopes: ["settings:write"],
35847
+ scopes: [...settingsOperations.update.scopes],
34158
35848
  returns: "UserSettings"
34159
35849
  },
34160
35850
  {
@@ -34312,9 +36002,9 @@ var COMMAND_SPECS = [
34312
36002
  summary: "An asset's upkeep and repair Tasks, open ones first.",
34313
36003
  args: [{ name: "id", required: true, description: "Asset (Collection Item) ULID." }],
34314
36004
  flags: [],
34315
- scopes: ["collections:read"],
36005
+ scopes: [...collectionUpkeepOperations.list.scopes],
34316
36006
  returns: "AssetUpkeep",
34317
- notes: "GET /api/v1/collection/{id}/upkeep (LifeOS House). Returns { itemId, upkeep, repairs }."
36007
+ notes: `${routeOf(collectionUpkeepOperations.list)} (LifeOS House). Returns { itemId, upkeep, repairs }.`
34318
36008
  },
34319
36009
  {
34320
36010
  path: ["collection", "link-task"],
@@ -34324,9 +36014,9 @@ var COMMAND_SPECS = [
34324
36014
  { name: "taskId", required: true, description: "Task ULID." }
34325
36015
  ],
34326
36016
  flags: [{ name: "repair", type: "boolean", description: "A one-off repair, not upkeep." }],
34327
- scopes: ["collections:write"],
36017
+ scopes: [...collectionUpkeepOperations.link.scopes],
34328
36018
  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."
36019
+ notes: `${routeOf(collectionUpkeepOperations.link)}. Make the Task (recurrence, Place, Area) first. Finishing it writes the asset's service / repair Entry by itself.`
34330
36020
  },
34331
36021
  {
34332
36022
  path: ["collection", "unlink-task"],
@@ -34336,7 +36026,7 @@ var COMMAND_SPECS = [
34336
36026
  { name: "taskId", required: true, description: "Task ULID." }
34337
36027
  ],
34338
36028
  flags: [],
34339
- scopes: ["collections:write"],
36029
+ scopes: [...collectionUpkeepOperations.unlink.scopes],
34340
36030
  returns: "AssetUpkeep"
34341
36031
  },
34342
36032
  {
@@ -35183,6 +36873,35 @@ var COMMAND_SPECS = [
35183
36873
  returns: "MoneyYear",
35184
36874
  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
36875
  },
36876
+ {
36877
+ path: ["money", "net-worth"],
36878
+ summary: "Net worth now and its month-end line (the accounts that count in it).",
36879
+ args: [],
36880
+ flags: [
36881
+ { name: "scope", type: "string", description: "all (default) | space | owner." },
36882
+ {
36883
+ name: "months",
36884
+ type: "string",
36885
+ description: "How many months the line covers (2\u201360, default 12)."
36886
+ }
36887
+ ],
36888
+ scopes: ["finance:read"],
36889
+ returns: "MoneyNetWorth",
36890
+ 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."
36891
+ },
36892
+ {
36893
+ path: ["money", "net-worth-set"],
36894
+ summary: "Set an account's value for a month by hand (a house, a car); again replaces it.",
36895
+ args: [{ name: "accountId", required: true, description: "Account ULID." }],
36896
+ flags: [
36897
+ { name: "value", type: "string", description: "The value, e.g. 310000 or 1234.56." },
36898
+ { name: "month", type: "string", description: "The month, YYYY-MM (default this month)." },
36899
+ { name: "data", type: "string", description: "SetMoneyNetWorthValueInput JSON instead." }
36900
+ ],
36901
+ scopes: ["finance:write"],
36902
+ returns: "MoneySnapshot",
36903
+ 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`."
36904
+ },
35186
36905
  {
35187
36906
  path: ["money", "goals"],
35188
36907
  summary: "List money goals: what's saved toward each, soonest first.",
@@ -35639,63 +37358,63 @@ var COMMAND_SPECS = [
35639
37358
  summary: "Whether the Space has linked Home Assistant, how many entities are mapped, the newest reading.",
35640
37359
  args: [],
35641
37360
  flags: [],
35642
- scopes: ["places:read"],
37361
+ scopes: [...sensorOperations.status.scopes],
35643
37362
  returns: "HomeAssistantStatus",
35644
- notes: "GET /api/v1/sensors/home-assistant. Home Assistant is linked in the app's Settings \u2192 Home; the token is never returned."
37363
+ notes: `${routeOf(sensorOperations.status)}. Home Assistant is linked in the app's Settings \u2192 Home; the token is never returned.`
35645
37364
  },
35646
37365
  {
35647
37366
  path: ["sensors", "entities"],
35648
37367
  summary: "Every entity Home Assistant has, with its state, the reading it likely gives and what it's mapped to.",
35649
37368
  args: [],
35650
37369
  flags: [],
35651
- scopes: ["places:read"],
37370
+ scopes: [...sensorOperations.entities.scopes],
35652
37371
  returns: "HomeAssistantEntity[]",
35653
- notes: "GET /api/v1/sensors/home-assistant/entities. 409 before Home Assistant is linked; 503 when it doesn't answer."
37372
+ notes: `${routeOf(sensorOperations.entities)}. 409 before Home Assistant is linked; 503 when it doesn't answer.`
35654
37373
  },
35655
37374
  {
35656
37375
  path: ["sensors", "sync"],
35657
37376
  summary: "Read every mapped entity now and put its state on the ledger, as the five-minute sync does.",
35658
37377
  args: [],
35659
37378
  flags: [],
35660
- scopes: ["places:write"],
37379
+ scopes: [...sensorOperations.sync.scopes],
35661
37380
  returns: "SensorSyncResult",
35662
- notes: "POST /api/v1/sensors/home-assistant/sync. A room's readings feed its card and the room alarm; an asset's show on its page. Returns entities, written, skipped, missing and the error (unauthorized, unavailable, bad_response). 409 before Home Assistant is linked."
37381
+ notes: `${routeOf(sensorOperations.sync)}. A room's readings feed its card and the room alarm; an asset's show on its page. Returns entities, written, skipped, missing and the error (unauthorized, unavailable, bad_response). 409 before Home Assistant is linked.`
35663
37382
  },
35664
37383
  {
35665
37384
  path: ["sensors", "unifi"],
35666
37385
  summary: "Whether the Space has linked UniFi, how many devices are mapped, the newest reading.",
35667
37386
  args: [],
35668
37387
  flags: [],
35669
- scopes: ["places:read"],
37388
+ scopes: [...sensorOperations.unifiStatus.scopes],
35670
37389
  returns: "UnifiStatus",
35671
- notes: "GET /api/v1/sensors/unifi. Linking UniFi (address + API key) is a member's, in Settings \u2192 Home; a key never sees the API key."
37390
+ notes: `${routeOf(sensorOperations.unifiStatus)}. Linking UniFi (address + API key) is a member's, in Settings \u2192 Home; a key never sees the API key.`
35672
37391
  },
35673
37392
  {
35674
37393
  path: ["sensors", "unifi-devices"],
35675
37394
  summary: "Every device and connected client UniFi sees, with the asset each is mapped to.",
35676
37395
  args: [],
35677
37396
  flags: [],
35678
- scopes: ["places:read", "collections:read"],
37397
+ scopes: [...sensorOperations.unifiDevices.scopes],
35679
37398
  returns: "UnifiDevice[]",
35680
- notes: "GET /api/v1/sensors/unifi/devices. Map one with `do sensors map <mac> <asset> --reading presence --provider unifi`, or give the asset `external.unifiMac` on its facet. 409 before UniFi is linked."
37399
+ notes: `${routeOf(sensorOperations.unifiDevices)}. Map one with \`do sensors map <mac> <asset> --reading presence --provider unifi\`, or give the asset \`external.unifiMac\` on its facet. 409 before UniFi is linked.`
35681
37400
  },
35682
37401
  {
35683
37402
  path: ["sensors", "unifi-sync"],
35684
37403
  summary: "Put every mapped device's presence (online/offline, firmware) on its asset now.",
35685
37404
  args: [],
35686
37405
  flags: [],
35687
- scopes: ["places:write"],
37406
+ scopes: [...sensorOperations.syncUnifi.scopes],
35688
37407
  returns: "SensorSyncResult",
35689
- notes: "POST /api/v1/sensors/unifi/sync. Assets whose facet names a UniFi MAC are mapped first; readings land as asset.presence on change, else hourly. The worker does the same every five minutes. 409 before UniFi is linked."
37408
+ notes: `${routeOf(sensorOperations.syncUnifi)}. Assets whose facet names a UniFi MAC are mapped first; readings land as asset.presence on change, else hourly. The worker does the same every five minutes. 409 before UniFi is linked.`
35690
37409
  },
35691
37410
  {
35692
37411
  path: ["sensors", "links"],
35693
37412
  summary: "The mapped entities, or the ones on one Place or asset.",
35694
37413
  args: [],
35695
37414
  flags: [{ name: "node", type: "string", description: "Only the mappings on this node ULID." }],
35696
- scopes: ["places:read"],
37415
+ scopes: [...sensorOperations.links.scopes],
35697
37416
  returns: "SensorLink[]",
35698
- notes: "GET /api/v1/sensors/links."
37417
+ notes: `${routeOf(sensorOperations.links)}.`
35699
37418
  },
35700
37419
  {
35701
37420
  path: ["sensors", "map"],
@@ -35721,18 +37440,18 @@ var COMMAND_SPECS = [
35721
37440
  },
35722
37441
  { name: "label", type: "string", description: "A name for it (Home Assistant's own)." }
35723
37442
  ],
35724
- scopes: ["places:write"],
37443
+ scopes: [...sensorOperations.createLink.scopes],
35725
37444
  returns: "SensorLink",
35726
- notes: "POST /api/v1/sensors/links. An animal maps to the Place it's housed in (409 when it has none). Mapping an entity again moves it."
37445
+ notes: `${routeOf(sensorOperations.createLink)}. An animal maps to the Place it's housed in (409 when it has none). Mapping an entity again moves it.`
35727
37446
  },
35728
37447
  {
35729
37448
  path: ["sensors", "unmap"],
35730
37449
  summary: "Unmap an entity; the readings it gave stay.",
35731
37450
  args: [{ name: "link", required: true, description: "The sensor link ULID." }],
35732
37451
  flags: [],
35733
- scopes: ["places:write"],
37452
+ scopes: [...sensorOperations.removeLink.scopes],
35734
37453
  returns: "None",
35735
- notes: "DELETE /api/v1/sensors/links/{id}."
37454
+ notes: `${routeOf(sensorOperations.removeLink)}.`
35736
37455
  },
35737
37456
  {
35738
37457
  path: ["runs", "sessions", "list"],
@@ -35932,7 +37651,7 @@ var COMMAND_SPECS = [
35932
37651
  summary: "Your Google Contacts, to pick from: who they'd merge into and what importing adds.",
35933
37652
  args: [],
35934
37653
  flags: [{ name: "q", type: "string", description: "A name or email to narrow to." }],
35935
- scopes: ["contacts:read"],
37654
+ scopes: [...googleContactsOperations.list.scopes],
35936
37655
  returns: "GoogleContactsList",
35937
37656
  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
37657
  },
@@ -35947,7 +37666,7 @@ var COMMAND_SPECS = [
35947
37666
  }
35948
37667
  ],
35949
37668
  flags: [],
35950
- scopes: ["contacts:write"],
37669
+ scopes: [...googleContactsOperations.import.scopes],
35951
37670
  returns: "GoogleContactsImportResult",
35952
37671
  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
37672
  },
@@ -36042,7 +37761,7 @@ var COMMAND_SPECS = [
36042
37761
  description: 'JSON MarkGuestsSentInput, e.g. {"guestIds":["01\u2026"],"what":"save_the_date"} (or "invite"; "at" defaults to now).'
36043
37762
  }
36044
37763
  ],
36045
- scopes: ["contacts:write"],
37764
+ scopes: [...guestOperations.sent.scopes],
36046
37765
  returns: "GuestView"
36047
37766
  },
36048
37767
  {
@@ -36056,7 +37775,7 @@ var COMMAND_SPECS = [
36056
37775
  description: 'JSON AddRsvpPresetsInput, e.g. {"guestListId":"01\u2026","presets":["diet","message"]}'
36057
37776
  }
36058
37777
  ],
36059
- scopes: ["contacts:write"],
37778
+ scopes: [...rsvpQuestionOperations.presets.scopes],
36060
37779
  returns: "RsvpQuestion"
36061
37780
  },
36062
37781
  {
@@ -36067,7 +37786,7 @@ var COMMAND_SPECS = [
36067
37786
  { name: "list", type: "string", description: "The guest list's id (required)." },
36068
37787
  { name: "household", type: "string", description: "Only this household's answers." }
36069
37788
  ],
36070
- scopes: ["contacts:read"],
37789
+ scopes: [...rsvpAnswerOperations.list.scopes],
36071
37790
  returns: "RsvpAnswer"
36072
37791
  },
36073
37792
  {
@@ -36081,7 +37800,7 @@ var COMMAND_SPECS = [
36081
37800
  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
37801
  }
36083
37802
  ],
36084
- scopes: ["contacts:write"],
37803
+ scopes: [...rsvpAnswerOperations.set.scopes],
36085
37804
  returns: "RsvpAnswer"
36086
37805
  },
36087
37806
  {
@@ -36128,7 +37847,7 @@ var COMMAND_SPECS = [
36128
37847
  summary: "A guest list's mail campaigns (save-the-dates, invitations), newest first.",
36129
37848
  args: [],
36130
37849
  flags: [{ name: "list", type: "string", description: "The guest list's id (required)." }],
36131
- scopes: ["contacts:read"],
37850
+ scopes: [...mailCampaignOperations.list.scopes],
36132
37851
  returns: "MailCampaignView",
36133
37852
  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
37853
  },
@@ -36137,7 +37856,7 @@ var COMMAND_SPECS = [
36137
37856
  summary: "One mail campaign, with its households and how far it got.",
36138
37857
  args: [{ name: "id", required: true, description: "The campaign's id." }],
36139
37858
  flags: [],
36140
- scopes: ["contacts:read"],
37859
+ scopes: [...mailCampaignOperations.get.scopes],
36141
37860
  returns: "MailCampaignView"
36142
37861
  },
36143
37862
  {
@@ -36151,7 +37870,7 @@ var COMMAND_SPECS = [
36151
37870
  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
37871
  }
36153
37872
  ],
36154
- scopes: ["contacts:write"],
37873
+ scopes: [...mailCampaignOperations.create.scopes],
36155
37874
  returns: "MailCampaignView",
36156
37875
  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
37876
  },
@@ -36166,7 +37885,7 @@ var COMMAND_SPECS = [
36166
37885
  description: 'JSON UpdateMailCampaignInput, e.g. {"data":{"nl":{\u2026}}}'
36167
37886
  }
36168
37887
  ],
36169
- scopes: ["contacts:write"],
37888
+ scopes: [...mailCampaignOperations.update.scopes],
36170
37889
  returns: "MailCampaignView",
36171
37890
  notes: "Only a draft changes (409 otherwise); an edit makes the last test stale."
36172
37891
  },
@@ -36175,7 +37894,7 @@ var COMMAND_SPECS = [
36175
37894
  summary: "Draw a campaign's mail as its first household would get it.",
36176
37895
  args: [{ name: "id", required: true, description: "The campaign's id." }],
36177
37896
  flags: [{ name: "language", type: "string", description: "nl (default) or pt." }],
36178
- scopes: ["contacts:read"],
37897
+ scopes: [...mailCampaignOperations.preview.scopes],
36179
37898
  returns: "MailPreview"
36180
37899
  },
36181
37900
  {
@@ -36195,7 +37914,7 @@ var COMMAND_SPECS = [
36195
37914
  {
36196
37915
  name: "kind",
36197
37916
  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)."
37917
+ 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
37918
  },
36200
37919
  {
36201
37920
  name: "out",
@@ -38767,7 +40486,7 @@ var HANDLERS = {
38767
40486
  "today earlier": async ({ flags, io, requireClient }) => {
38768
40487
  const area = flagString(flags, "area");
38769
40488
  return {
38770
- data: await requireClient().get("/today/from-earlier", {
40489
+ data: await requireClient().get(pathOf(todayOperations.fromEarlier), {
38771
40490
  query: { from: localDayStart(io.now()).toISOString(), areaId: area }
38772
40491
  }),
38773
40492
  render: renderFromEarlier
@@ -39692,27 +41411,36 @@ var HANDLERS = {
39692
41411
  },
39693
41412
  "task cancel": async ({ args, requireClient }) => {
39694
41413
  const id = requireArg(args, 0, "task id");
39695
- return { data: await requireClient().post(`/tasks/${id}/cancel`, {}), render: renderTask };
41414
+ return {
41415
+ data: await requireClient().post(pathOf(taskOperations.cancel, { id }), {}),
41416
+ render: renderTask
41417
+ };
39696
41418
  },
39697
41419
  "tasks accept": async ({ args, requireClient }) => {
39698
41420
  const id = requireArg(args, 0, "task id");
39699
- return { data: await requireClient().post(`/tasks/${id}/accept`, {}), render: renderTask };
41421
+ return {
41422
+ data: await requireClient().post(pathOf(taskOperations.accept, { id }), {}),
41423
+ render: renderTask
41424
+ };
39700
41425
  },
39701
41426
  "tasks dismiss": async ({ args, requireClient }) => {
39702
41427
  const id = requireArg(args, 0, "task id");
39703
- return { data: await requireClient().post(`/tasks/${id}/not-now`, {}), render: renderTask };
41428
+ return {
41429
+ data: await requireClient().post(pathOf(taskOperations.notNow, { id }), {}),
41430
+ render: renderTask
41431
+ };
39704
41432
  },
39705
41433
  "block-templates week": async ({ flags, requireClient }) => {
39706
41434
  const start = flagString(flags, "start");
39707
41435
  if (start === undefined)
39708
41436
  throw usageError("block-templates week needs --start");
39709
41437
  return {
39710
- data: await requireClient().get("/block-templates/week", { query: { start } }),
41438
+ data: await requireClient().get(pathOf(blockTemplateOperations.week), { query: { start } }),
39711
41439
  render: renderRecord
39712
41440
  };
39713
41441
  },
39714
41442
  "block-templates defaults": async ({ requireClient }) => ({
39715
- data: await requireClient().post("/block-templates/defaults", {}),
41443
+ data: await requireClient().post(pathOf(blockTemplateOperations.defaults), {}),
39716
41444
  render: renderRecord
39717
41445
  }),
39718
41446
  "time-blocks record": async ({ args, requireClient }) => {
@@ -39797,7 +41525,7 @@ var HANDLERS = {
39797
41525
  };
39798
41526
  },
39799
41527
  "plans list": async ({ flags, requireClient }) => {
39800
- const plans = await requireClient().get("/plans", {
41528
+ const plans = await requireClient().get(pathOf(planOperations.list), {
39801
41529
  query: {
39802
41530
  kind: flagString(flags, "kind"),
39803
41531
  status: flagString(flags, "status"),
@@ -39809,19 +41537,25 @@ var HANDLERS = {
39809
41537
  },
39810
41538
  "plans get": async ({ args, requireClient }) => {
39811
41539
  const id = requireArg(args, 0, "plan id");
39812
- return { data: await requireClient().get(`/plans/${id}`), render: renderPlan };
41540
+ return {
41541
+ data: await requireClient().get(pathOf(planOperations.get, { id })),
41542
+ render: renderPlan
41543
+ };
39813
41544
  },
39814
41545
  "plans propose": async ({ flags, requireClient }) => {
39815
41546
  if (flagString(flags, "data") === undefined)
39816
41547
  throw usageError("plans propose needs --data");
39817
41548
  const body = parseInput(createPlanInputSchema, readDataFlag(flags));
39818
- return { data: await requireClient().post("/plans", body), render: renderPlan };
41549
+ return {
41550
+ data: await requireClient().post(pathOf(planOperations.create), body),
41551
+ render: renderPlan
41552
+ };
39819
41553
  },
39820
41554
  "plans accept": async (ctx) => planDecision(ctx, "accept"),
39821
41555
  "plans dismiss": async (ctx) => planDecision(ctx, "dismiss"),
39822
41556
  "plans undo": async (ctx) => planDecision(ctx, "undo"),
39823
41557
  "plan rules list": async ({ flags, requireClient }) => {
39824
- const rules = await requireClient().get("/planning/rules", {
41558
+ const rules = await requireClient().get(pathOf(planningRuleOperations.list), {
39825
41559
  query: { forUserId: flagString(flags, "for"), kind: flagString(flags, "kind") }
39826
41560
  });
39827
41561
  return { data: rules, render: renderPlanningRules };
@@ -39838,7 +41572,7 @@ var HANDLERS = {
39838
41572
  ...flagString(flags, "for") ? { forUserId: flagString(flags, "for") } : {}
39839
41573
  });
39840
41574
  return {
39841
- data: await requireClient().post("/planning/rules", body),
41575
+ data: await requireClient().post(pathOf(planningRuleOperations.create), body),
39842
41576
  render: renderPlanningRule
39843
41577
  };
39844
41578
  },
@@ -39850,26 +41584,32 @@ var HANDLERS = {
39850
41584
  ...rule !== undefined ? { rule } : {}
39851
41585
  });
39852
41586
  return {
39853
- data: await requireClient().patch(`/planning/rules/${id}`, body),
41587
+ data: await requireClient().patch(pathOf(planningRuleOperations.update, { id }), body),
39854
41588
  render: renderPlanningRule
39855
41589
  };
39856
41590
  },
39857
41591
  "plan rules remove": async ({ args, flags, requireClient }) => {
39858
41592
  const id = requireArg(args, 0, "rule id");
39859
41593
  assertDestructiveAllowed(flags, "remove a planning rule");
39860
- await requireClient().delete(`/planning/rules/${id}`);
41594
+ await requireClient().delete(pathOf(planningRuleOperations.remove, { id }));
39861
41595
  return { data: { id, removed: true }, render: () => `Removed rule ${id}.` };
39862
41596
  },
39863
41597
  "tasks ready": async ({ args, flags, requireClient }) => {
39864
41598
  const id = requireArg(args, 0, "task id");
39865
41599
  const note = flagString(flags, "note");
39866
41600
  const body = parseInput(markTaskReadyInputSchema, note !== undefined ? { note } : {});
39867
- return { data: await requireClient().post(`/tasks/${id}/ready`, body), render: renderTask };
41601
+ return {
41602
+ data: await requireClient().post(pathOf(taskOperations.ready, { id }), body),
41603
+ render: renderTask
41604
+ };
39868
41605
  },
39869
41606
  "task reopen": async ({ args, requireClient }) => {
39870
41607
  const id = requireArg(args, 0, "task id");
39871
41608
  const body = parseInput(updateTaskInputSchema, { status: "todo" });
39872
- return { data: await requireClient().patch(`/tasks/${id}`, body), render: renderTask };
41609
+ return {
41610
+ data: await requireClient().patch(pathOf(taskOperations.update, { id }), body),
41611
+ render: renderTask
41612
+ };
39873
41613
  },
39874
41614
  "task snooze": async ({ args, flags, io, requireClient }) => {
39875
41615
  const id = requireArg(args, 0, "task id");
@@ -39881,7 +41621,10 @@ var HANDLERS = {
39881
41621
  ...until ? { allDay: until.allDay } : {},
39882
41622
  ...duration ? { durationMinutes: duration } : {}
39883
41623
  });
39884
- return { data: await requireClient().patch(`/tasks/${id}`, body), render: renderTask };
41624
+ return {
41625
+ data: await requireClient().patch(pathOf(taskOperations.update, { id }), body),
41626
+ render: renderTask
41627
+ };
39885
41628
  },
39886
41629
  "task schedule": async ({ args, flags, requireClient }) => {
39887
41630
  const id = requireArg(args, 0, "task id");
@@ -39894,7 +41637,10 @@ var HANDLERS = {
39894
41637
  allDay: at.allDay,
39895
41638
  ...duration ? { durationMinutes: duration } : {}
39896
41639
  });
39897
- return { data: await requireClient().patch(`/tasks/${id}`, body), render: renderTask };
41640
+ return {
41641
+ data: await requireClient().patch(pathOf(taskOperations.update, { id }), body),
41642
+ render: renderTask
41643
+ };
39898
41644
  },
39899
41645
  "habit log": async ({ args, flags, requireClient }) => {
39900
41646
  const id = requireArg(args, 0, "habit id");
@@ -39911,7 +41657,7 @@ var HANDLERS = {
39911
41657
  ...note !== undefined ? { note } : {}
39912
41658
  });
39913
41659
  return {
39914
- data: await requireClient().post(`/habits/${id}/check-ins`, input),
41660
+ data: await requireClient().post(pathOf(habitOperations.checkIn, { id }), input),
39915
41661
  render: renderHabit
39916
41662
  };
39917
41663
  },
@@ -39935,7 +41681,10 @@ var HANDLERS = {
39935
41681
  ...at ? { occurredAt: at } : {},
39936
41682
  ...note !== undefined ? { note } : {}
39937
41683
  });
39938
- return { data: await requireClient().post("/habits/check-in", input), render: renderHabit };
41684
+ return {
41685
+ data: await requireClient().post(pathOf(habitOperations.checkInByTitle), input),
41686
+ render: renderHabit
41687
+ };
39939
41688
  },
39940
41689
  "habit alias add": async ({ args, requireClient }) => {
39941
41690
  const id = requireArg(args, 0, "habit id");
@@ -39944,7 +41693,7 @@ var HANDLERS = {
39944
41693
  throw usageError("habit alias add needs an alias, e.g. `do habit alias add <id> wake`");
39945
41694
  const input = parseInput(habitAliasInputSchema, { alias });
39946
41695
  return {
39947
- data: await requireClient().post(`/habits/${id}/aliases`, input),
41696
+ data: await requireClient().post(pathOf(habitOperations.addAlias, { id }), input),
39948
41697
  render: renderHabit
39949
41698
  };
39950
41699
  },
@@ -39954,12 +41703,12 @@ var HANDLERS = {
39954
41703
  if (!alias)
39955
41704
  throw usageError("habit alias rm needs the alias to drop");
39956
41705
  return {
39957
- data: await requireClient().delete(`/habits/${id}/aliases/${encodeURIComponent(alias)}`),
41706
+ data: await requireClient().delete(pathOf(habitOperations.removeAlias, { id, alias })),
39958
41707
  render: renderHabit
39959
41708
  };
39960
41709
  },
39961
41710
  "habits today": async ({ requireClient }) => ({
39962
- data: await requireClient().get("/habits/today"),
41711
+ data: await requireClient().get(pathOf(habitOperations.today)),
39963
41712
  render: renderHabitsToday
39964
41713
  }),
39965
41714
  "habit check-ins": async ({ args, flags, requireClient }) => {
@@ -39969,7 +41718,7 @@ var HANDLERS = {
39969
41718
  limit: flagString(flags, "limit")
39970
41719
  };
39971
41720
  return {
39972
- data: await requireClient().get(`/habits/${id}/check-ins`, { query }),
41721
+ data: await requireClient().get(pathOf(habitOperations.listCheckIns, { id }), { query }),
39973
41722
  render: renderList
39974
41723
  };
39975
41724
  },
@@ -39984,7 +41733,7 @@ var HANDLERS = {
39984
41733
  throw usageError('habit note needs the note, e.g. `do habit note <id> <check-in id> "felt heavy"`');
39985
41734
  const input = parseInput(updateHabitEntryInputSchema, { note: clear ? null : text });
39986
41735
  return {
39987
- data: await requireClient().patch(`/habits/${id}/check-ins/${entryId}`, input),
41736
+ data: await requireClient().patch(pathOf(habitOperations.updateCheckIn, { id, entryId }), input),
39988
41737
  render: renderHabitEntry
39989
41738
  };
39990
41739
  },
@@ -39996,7 +41745,10 @@ var HANDLERS = {
39996
41745
  ...date ? { date } : {},
39997
41746
  ...note !== undefined ? { note } : {}
39998
41747
  });
39999
- return { data: await requireClient().post(`/habits/${id}/skip`, input), render: renderHabit };
41748
+ return {
41749
+ data: await requireClient().post(pathOf(habitOperations.skip, { id }), input),
41750
+ render: renderHabit
41751
+ };
40000
41752
  },
40001
41753
  "habit pause": async ({ args, flags, requireClient }) => {
40002
41754
  const id = requireArg(args, 0, "habit id");
@@ -40004,7 +41756,10 @@ var HANDLERS = {
40004
41756
  if (!until)
40005
41757
  throw usageError("habit pause needs --until <YYYY-MM-DD>, the day it's back");
40006
41758
  const input = parseInput(habitPauseInputSchema, { until });
40007
- return { data: await requireClient().post(`/habits/${id}/pause`, input), render: renderHabit };
41759
+ return {
41760
+ data: await requireClient().post(pathOf(habitOperations.pause, { id }), input),
41761
+ render: renderHabit
41762
+ };
40008
41763
  },
40009
41764
  "habit remind": async ({ args, requireClient }) => {
40010
41765
  const id = requireArg(args, 0, "habit id");
@@ -40012,19 +41767,31 @@ var HANDLERS = {
40012
41767
  if (state !== "on" && state !== "off")
40013
41768
  throw usageError("habit remind takes `on` or `off`");
40014
41769
  const input = parseInput(updateHabitInputSchema, { remind: state === "on" });
40015
- return { data: await requireClient().patch(`/habits/${id}`, input), render: renderHabit };
41770
+ return {
41771
+ data: await requireClient().patch(pathOf(habitOperations.update, { id }), input),
41772
+ render: renderHabit
41773
+ };
40016
41774
  },
40017
41775
  "habit resume": async ({ args, requireClient }) => {
40018
41776
  const id = requireArg(args, 0, "habit id");
40019
- return { data: await requireClient().post(`/habits/${id}/resume`, {}), render: renderHabit };
41777
+ return {
41778
+ data: await requireClient().post(pathOf(habitOperations.resume, { id }), {}),
41779
+ render: renderHabit
41780
+ };
40020
41781
  },
40021
41782
  "habit retire": async ({ args, requireClient }) => {
40022
41783
  const id = requireArg(args, 0, "habit id");
40023
- return { data: await requireClient().post(`/habits/${id}/retire`, {}), render: renderHabit };
41784
+ return {
41785
+ data: await requireClient().post(pathOf(habitOperations.retire, { id }), {}),
41786
+ render: renderHabit
41787
+ };
40024
41788
  },
40025
41789
  "habit unretire": async ({ args, requireClient }) => {
40026
41790
  const id = requireArg(args, 0, "habit id");
40027
- return { data: await requireClient().post(`/habits/${id}/unretire`, {}), render: renderHabit };
41791
+ return {
41792
+ data: await requireClient().post(pathOf(habitOperations.unretire, { id }), {}),
41793
+ render: renderHabit
41794
+ };
40028
41795
  },
40029
41796
  away: async ({ args, flags, requireClient }) => {
40030
41797
  const until = flagString(flags, "until");
@@ -40043,7 +41810,7 @@ var HANDLERS = {
40043
41810
  const id = requireArg(args, 0, "routine id");
40044
41811
  const input = parseInput(routineDoneInputSchema, {});
40045
41812
  return {
40046
- data: await requireClient().post(`/routines/${id}/done`, input),
41813
+ data: await requireClient().post(pathOf(routineOperations.markDone, { id }), input),
40047
41814
  render: renderRoutineDone
40048
41815
  };
40049
41816
  },
@@ -40055,7 +41822,7 @@ var HANDLERS = {
40055
41822
  }
40056
41823
  const input = parseInput(routineRunUndoInputSchema, { stepRunIds });
40057
41824
  return {
40058
- data: await requireClient().post(`/routines/runs/${runId}/undo`, input),
41825
+ data: await requireClient().post(pathOf(routineOperations.undoSteps, { runId }), input),
40059
41826
  render: renderRoutineUndo
40060
41827
  };
40061
41828
  },
@@ -40099,7 +41866,7 @@ var HANDLERS = {
40099
41866
  };
40100
41867
  },
40101
41868
  "agents list": async ({ requireClient }) => ({
40102
- data: await requireClient().getPage("/agents"),
41869
+ data: await requireClient().getPage(pathOf(agentOperations.list)),
40103
41870
  render: renderList
40104
41871
  }),
40105
41872
  "agent connection get": async ({ requireClient }) => ({
@@ -40215,12 +41982,15 @@ var HANDLERS = {
40215
41982
  };
40216
41983
  },
40217
41984
  "settings get": async ({ requireClient }) => ({
40218
- data: await requireClient().get("/settings"),
41985
+ data: await requireClient().get(pathOf(settingsOperations.get)),
40219
41986
  render: renderRecord
40220
41987
  }),
40221
41988
  "settings update": async ({ flags, requireClient }) => {
40222
41989
  const body = parseInput(updateUserSettingsInputSchema, readDataFlag(flags));
40223
- return { data: await requireClient().patch("/settings", body), render: renderRecord };
41990
+ return {
41991
+ data: await requireClient().patch(pathOf(settingsOperations.update), body),
41992
+ render: renderRecord
41993
+ };
40224
41994
  },
40225
41995
  "modules list": async ({ requireClient }) => {
40226
41996
  const settings = await requireClient().get("/settings");
@@ -40314,7 +42084,10 @@ var HANDLERS = {
40314
42084
  },
40315
42085
  "collection upkeep": async ({ args, requireClient }) => {
40316
42086
  const id = requireArg(args, 0, "item id");
40317
- return { data: await requireClient().get(`/collection/${id}/upkeep`), render: renderRecord };
42087
+ return {
42088
+ data: await requireClient().get(pathOf(collectionUpkeepOperations.list, { id })),
42089
+ render: renderRecord
42090
+ };
40318
42091
  },
40319
42092
  "collection link-task": async ({ args, flags, requireClient }) => {
40320
42093
  const id = requireArg(args, 0, "item id");
@@ -40324,7 +42097,7 @@ var HANDLERS = {
40324
42097
  kind: flagBool(flags, "repair") ? "repairs" : "maintains"
40325
42098
  });
40326
42099
  return {
40327
- data: await requireClient().post(`/collection/${id}/upkeep`, body),
42100
+ data: await requireClient().post(pathOf(collectionUpkeepOperations.link, { id }), body),
40328
42101
  render: renderRecord
40329
42102
  };
40330
42103
  },
@@ -40332,7 +42105,7 @@ var HANDLERS = {
40332
42105
  const id = requireArg(args, 0, "item id");
40333
42106
  const taskId = requireArg(args, 1, "task id");
40334
42107
  return {
40335
- data: await requireClient().delete(`/collection/${id}/upkeep/${taskId}`),
42108
+ data: await requireClient().delete(pathOf(collectionUpkeepOperations.unlink, { id, taskId })),
40336
42109
  render: renderRecord
40337
42110
  };
40338
42111
  },
@@ -40897,6 +42670,23 @@ var HANDLERS = {
40897
42670
  });
40898
42671
  return { data: await requireClient().post("/money/budget/copy", body), render: renderRecord };
40899
42672
  },
42673
+ "money net-worth": async ({ flags, requireClient }) => ({
42674
+ data: await requireClient().get("/money/net-worth", {
42675
+ query: { scope: flagString(flags, "scope"), months: flagString(flags, "months") }
42676
+ }),
42677
+ render: renderRecord
42678
+ }),
42679
+ "money net-worth-set": async ({ args, flags, requireClient }) => {
42680
+ const body = flagString(flags, "data") !== undefined ? parseInput(setMoneyNetWorthValueInputSchema, readDataFlag(flags)) : parseInput(setMoneyNetWorthValueInputSchema, {
42681
+ accountId: requireArg(args, 0, "account id"),
42682
+ value: requireFlag(flags, "value", "money net-worth-set needs --value <amount>"),
42683
+ month: flagString(flags, "month")
42684
+ });
42685
+ return {
42686
+ data: await requireClient().put("/money/net-worth/values", body),
42687
+ render: renderRecord
42688
+ };
42689
+ },
40900
42690
  "money goals": async ({ requireClient }) => ({
40901
42691
  data: await requireClient().get("/money/goals"),
40902
42692
  render: renderList
@@ -41206,31 +42996,31 @@ var HANDLERS = {
41206
42996
  return { data: await requireClient().post("/runs/steam/sync", body), render: renderRecord };
41207
42997
  },
41208
42998
  "sensors status": async ({ requireClient }) => ({
41209
- data: await requireClient().get("/sensors/home-assistant"),
42999
+ data: await requireClient().get(pathOf(sensorOperations.status)),
41210
43000
  render: renderRecord
41211
43001
  }),
41212
43002
  "sensors entities": async ({ requireClient }) => ({
41213
- data: await requireClient().get("/sensors/home-assistant/entities"),
43003
+ data: await requireClient().get(pathOf(sensorOperations.entities)),
41214
43004
  render: renderList
41215
43005
  }),
41216
43006
  "sensors sync": async ({ requireClient }) => ({
41217
- data: await requireClient().post("/sensors/home-assistant/sync", {}),
43007
+ data: await requireClient().post(pathOf(sensorOperations.sync), {}),
41218
43008
  render: renderRecord
41219
43009
  }),
41220
43010
  "sensors unifi": async ({ requireClient }) => ({
41221
- data: await requireClient().get("/sensors/unifi"),
43011
+ data: await requireClient().get(pathOf(sensorOperations.unifiStatus)),
41222
43012
  render: renderRecord
41223
43013
  }),
41224
43014
  "sensors unifi-devices": async ({ requireClient }) => ({
41225
- data: await requireClient().get("/sensors/unifi/devices"),
43015
+ data: await requireClient().get(pathOf(sensorOperations.unifiDevices)),
41226
43016
  render: renderList
41227
43017
  }),
41228
43018
  "sensors unifi-sync": async ({ requireClient }) => ({
41229
- data: await requireClient().post("/sensors/unifi/sync", {}),
43019
+ data: await requireClient().post(pathOf(sensorOperations.syncUnifi), {}),
41230
43020
  render: renderRecord
41231
43021
  }),
41232
43022
  "sensors links": async ({ flags, requireClient }) => ({
41233
- data: await requireClient().get("/sensors/links", {
43023
+ data: await requireClient().get(pathOf(sensorOperations.links), {
41234
43024
  query: { nodeId: flagString(flags, "node") }
41235
43025
  }),
41236
43026
  render: renderList
@@ -41243,11 +43033,14 @@ var HANDLERS = {
41243
43033
  ...flagString(flags, "provider") ? { provider: flagString(flags, "provider") } : {},
41244
43034
  ...flagString(flags, "label") ? { label: flagString(flags, "label") } : {}
41245
43035
  });
41246
- return { data: await requireClient().post("/sensors/links", body), render: renderRecord };
43036
+ return {
43037
+ data: await requireClient().post(pathOf(sensorOperations.createLink), body),
43038
+ render: renderRecord
43039
+ };
41247
43040
  },
41248
43041
  "sensors unmap": async ({ args, requireClient }) => {
41249
43042
  const id = requireArg(args, 0, "sensor link id");
41250
- await requireClient().delete(`/sensors/links/${id}`);
43043
+ await requireClient().delete(pathOf(sensorOperations.removeLink, { id }));
41251
43044
  return { data: { ok: true, unmapped: id }, render: renderRecord };
41252
43045
  },
41253
43046
  "runs sessions list": async ({ args, requireClient }) => {
@@ -41360,11 +43153,11 @@ var HANDLERS = {
41360
43153
  render: renderRecord
41361
43154
  }),
41362
43155
  "health google status": async ({ requireClient }) => ({
41363
- data: await requireClient().get("/health/google-health"),
43156
+ data: await requireClient().get(pathOf(googleHealthOperations.status)),
41364
43157
  render: renderRecord
41365
43158
  }),
41366
43159
  "health google sync": async ({ requireClient }) => ({
41367
- data: await requireClient().post("/health/google-health/sync", {}),
43160
+ data: await requireClient().post(pathOf(googleHealthOperations.sync), {}),
41368
43161
  render: renderRecord
41369
43162
  }),
41370
43163
  "health consent": async ({ requireClient }) => ({
@@ -41595,7 +43388,9 @@ var HANDLERS = {
41595
43388
  "people google": async ({ flags, requireClient }) => {
41596
43389
  const q = flagString(flags, "q");
41597
43390
  return {
41598
- data: await requireClient().get("/people/google-contacts", { query: q ? { q } : {} }),
43391
+ data: await requireClient().get(pathOf(googleContactsOperations.list), {
43392
+ query: q ? { q } : {}
43393
+ }),
41599
43394
  render: renderRecord
41600
43395
  };
41601
43396
  },
@@ -41605,7 +43400,7 @@ var HANDLERS = {
41605
43400
  }
41606
43401
  const body = parseInput(importGoogleContactsInputSchema, { resourceNames: args });
41607
43402
  return {
41608
- data: await requireClient().post("/people/google-contacts/import", body),
43403
+ data: await requireClient().post(pathOf(googleContactsOperations.import), body),
41609
43404
  render: renderRecord
41610
43405
  };
41611
43406
  },
@@ -41748,12 +43543,15 @@ var HANDLERS = {
41748
43543
  },
41749
43544
  "guests sent": async ({ flags, requireClient }) => {
41750
43545
  const body = parseInput(markGuestsSentInputSchema, readDataFlag(flags));
41751
- return { data: await requireClient().post("/guests/sent", body), render: renderRecord };
43546
+ return {
43547
+ data: await requireClient().post(pathOf(guestOperations.sent), body),
43548
+ render: renderRecord
43549
+ };
41752
43550
  },
41753
43551
  "rsvp-questions presets": async ({ flags, requireClient }) => {
41754
43552
  const body = parseInput(addRsvpPresetsInputSchema, readDataFlag(flags));
41755
43553
  return {
41756
- data: await requireClient().post("/rsvp-questions/presets", body),
43554
+ data: await requireClient().post(pathOf(rsvpQuestionOperations.presets), body),
41757
43555
  render: renderRecord
41758
43556
  };
41759
43557
  },
@@ -41766,11 +43564,17 @@ var HANDLERS = {
41766
43564
  guestListId: list,
41767
43565
  ...household ? { householdId: household } : {}
41768
43566
  });
41769
- return { data: await requireClient().get("/rsvp-answers", { query }), render: renderRecord };
43567
+ return {
43568
+ data: await requireClient().get(pathOf(rsvpAnswerOperations.list), { query }),
43569
+ render: renderRecord
43570
+ };
41770
43571
  },
41771
43572
  "rsvp-answers set": async ({ flags, requireClient }) => {
41772
43573
  const body = parseInput(setRsvpAnswersInputSchema, readDataFlag(flags));
41773
- return { data: await requireClient().put("/rsvp-answers", body), render: renderRecord };
43574
+ return {
43575
+ data: await requireClient().put(pathOf(rsvpAnswerOperations.set), body),
43576
+ render: renderRecord
43577
+ };
41774
43578
  },
41775
43579
  "seating-proposals list": async ({ flags, requireClient }) => {
41776
43580
  const list = flagString(flags, "list");
@@ -41809,23 +43613,31 @@ var HANDLERS = {
41809
43613
  throw usageError("mail-campaigns list needs --list <guest list id>");
41810
43614
  parseInput(mailCampaignsQuerySchema, { guestListId: list });
41811
43615
  return {
41812
- data: await requireClient().get("/mail-campaigns", { query: { guestListId: list } }),
43616
+ data: await requireClient().get(pathOf(mailCampaignOperations.list), {
43617
+ query: { guestListId: list }
43618
+ }),
41813
43619
  render: renderRecord
41814
43620
  };
41815
43621
  },
41816
43622
  "mail-campaigns get": async ({ args, requireClient }) => {
41817
43623
  const id = requireArg(args, 0, "campaign id");
41818
- return { data: await requireClient().get(`/mail-campaigns/${id}`), render: renderRecord };
43624
+ return {
43625
+ data: await requireClient().get(pathOf(mailCampaignOperations.get, { id })),
43626
+ render: renderRecord
43627
+ };
41819
43628
  },
41820
43629
  "mail-campaigns create": async ({ flags, requireClient }) => {
41821
43630
  const body = parseInput(createMailCampaignInputSchema, { id: ulid3(), ...readDataFlag(flags) });
41822
- return { data: await requireClient().post("/mail-campaigns", body), render: renderRecord };
43631
+ return {
43632
+ data: await requireClient().post(pathOf(mailCampaignOperations.create), body),
43633
+ render: renderRecord
43634
+ };
41823
43635
  },
41824
43636
  "mail-campaigns update": async ({ args, flags, requireClient }) => {
41825
43637
  const id = requireArg(args, 0, "campaign id");
41826
43638
  const body = parseInput(updateMailCampaignInputSchema, readDataFlag(flags));
41827
43639
  return {
41828
- data: await requireClient().patch(`/mail-campaigns/${id}`, body),
43640
+ data: await requireClient().patch(pathOf(mailCampaignOperations.update, { id }), body),
41829
43641
  render: renderRecord
41830
43642
  };
41831
43643
  },
@@ -41835,7 +43647,7 @@ var HANDLERS = {
41835
43647
  language: flagString(flags, "language") ?? "nl"
41836
43648
  });
41837
43649
  return {
41838
- data: await requireClient().get(`/mail-campaigns/${id}/preview`, { query }),
43650
+ data: await requireClient().get(pathOf(mailCampaignOperations.preview, { id }), { query }),
41839
43651
  render: renderRecord
41840
43652
  };
41841
43653
  },
@@ -42310,7 +44122,10 @@ async function planDecision({ args, requireClient }, verb) {
42310
44122
  const id = requireArg(args, 0, "plan id");
42311
44123
  const itemIds = args.slice(1);
42312
44124
  const body = parseInput(planDecisionInputSchema, itemIds.length > 0 ? { itemIds } : {});
42313
- return { data: await requireClient().post(`/plans/${id}/${verb}`, body), render: renderPlan };
44125
+ return {
44126
+ data: await requireClient().post(pathOf(planOperations[verb], { id }), body),
44127
+ render: renderPlan
44128
+ };
42314
44129
  }
42315
44130
  function parseInput(schema, value) {
42316
44131
  const result = schema.safeParse(value);