@robbeverhelst/do 1.306.0 → 1.306.1

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 +2073 -398
  2. package/package.json +1 -1
package/dist/do.js CHANGED
@@ -4026,7 +4026,7 @@ var notifications = pgTable("notifications", {
4026
4026
  id: text("id").primaryKey(),
4027
4027
  tenantId: text("tenant_id").notNull().references(() => tenants.id),
4028
4028
  userId: text("user_id").notNull().references(() => users.id),
4029
- type: notificationType("type").notNull(),
4029
+ type: notificationType("type"),
4030
4030
  title: text("title").notNull(),
4031
4031
  body: text("body"),
4032
4032
  entityId: text("entity_id"),
@@ -24797,6 +24797,121 @@ var reviewDayStatSchema = object({
24797
24797
  });
24798
24798
  var reviewStatsSchema = array(reviewDayStatSchema);
24799
24799
 
24800
+ // ../../packages/schema/src/contracts/progress.ts
24801
+ var progressDateSchema = string2().regex(/^\d{4}-\d{2}-\d{2}$/, "expected YYYY-MM-DD");
24802
+ var progressRangeDays = [7, 30, 90];
24803
+ var progressQuerySchema = object({
24804
+ days: exports_coerce.number().int().refine((n) => progressRangeDays.includes(n), {
24805
+ message: "days must be 7, 30 or 90"
24806
+ }).default(7)
24807
+ });
24808
+ var shareSchema = number2().min(0).max(1).nullable();
24809
+ var countSchema = number2().int().nonnegative();
24810
+ var progressRangeSchema = object({
24811
+ from: progressDateSchema,
24812
+ to: progressDateSchema,
24813
+ days: union([literal(7), literal(30), literal(90)]),
24814
+ timezone: string2().min(1)
24815
+ });
24816
+ var progressTodaySchema = object({
24817
+ date: progressDateSchema,
24818
+ tasksDone: countSchema,
24819
+ tasksPlanned: countSchema,
24820
+ habitsDone: countSchema,
24821
+ habitsDue: countSchema,
24822
+ blocksDone: countSchema,
24823
+ blocksPlanned: countSchema,
24824
+ completion: shareSchema
24825
+ });
24826
+ var progressDaySchema = object({
24827
+ date: progressDateSchema,
24828
+ tasksDoneMine: countSchema,
24829
+ tasksDoneAgents: countSchema,
24830
+ tasksSlipped: countSchema,
24831
+ habitsDone: countSchema,
24832
+ habitsDue: countSchema,
24833
+ blocksDone: countSchema,
24834
+ blocksPlanned: countSchema,
24835
+ completion: shareSchema,
24836
+ onTime: countSchema,
24837
+ late: countSchema
24838
+ });
24839
+ var progressPeriodFigures = object({
24840
+ from: progressDateSchema,
24841
+ to: progressDateSchema,
24842
+ tasksDone: countSchema,
24843
+ habitsDone: countSchema,
24844
+ completion: shareSchema
24845
+ });
24846
+ var progressPeriodSchema = progressPeriodFigures.extend({
24847
+ previous: progressPeriodFigures
24848
+ });
24849
+ var progressTotalsSchema = object({
24850
+ week: progressPeriodSchema,
24851
+ month: progressPeriodSchema,
24852
+ bestDay: object({ date: progressDateSchema, tasksDone: countSchema }).nullable(),
24853
+ averagePerDay: object({
24854
+ tasksDone: number2().nonnegative(),
24855
+ habitsDone: number2().nonnegative()
24856
+ }),
24857
+ onTime: countSchema,
24858
+ late: countSchema
24859
+ });
24860
+ var progressAreaSchema = object({
24861
+ areaId: string2().nullable(),
24862
+ name: string2(),
24863
+ color: string2().nullable(),
24864
+ tasksDone: countSchema,
24865
+ tasksDoneMine: countSchema.default(0),
24866
+ tasksDoneAgents: countSchema.default(0),
24867
+ habitsDone: countSchema.default(0),
24868
+ blocksDone: countSchema.default(0),
24869
+ total: countSchema.default(0),
24870
+ previousTotal: countSchema.default(0)
24871
+ });
24872
+ var progressHabitDayStateSchema = _enum2(["done", "missed", "open", "skipped", "not-due"]);
24873
+ var progressHabitSchema = object({
24874
+ habitId: string2(),
24875
+ name: string2(),
24876
+ rate: shareSchema,
24877
+ due: countSchema,
24878
+ done: countSchema,
24879
+ days: array(object({ date: progressDateSchema, state: progressHabitDayStateSchema }))
24880
+ });
24881
+ var progressResponseSchema = object({
24882
+ range: progressRangeSchema,
24883
+ today: progressTodaySchema,
24884
+ days: array(progressDaySchema),
24885
+ totals: progressTotalsSchema,
24886
+ areas: array(progressAreaSchema),
24887
+ areasHighlight: string2().nullable().default(null),
24888
+ habits: array(progressHabitSchema)
24889
+ });
24890
+ var progressItemKindSchema = _enum2(["task", "habit", "block"]);
24891
+ var progressDoneItemSchema = object({
24892
+ id: string2(),
24893
+ title: string2(),
24894
+ kind: progressItemKindSchema,
24895
+ owner: _enum2(["me", "agent"]),
24896
+ areaId: string2().nullable(),
24897
+ completedAt: exports_coerce.date(),
24898
+ late: boolean3()
24899
+ });
24900
+ var progressSlippedItemSchema = object({
24901
+ id: string2(),
24902
+ title: string2(),
24903
+ kind: progressItemKindSchema,
24904
+ dueAt: exports_coerce.date().nullable(),
24905
+ scheduledAt: exports_coerce.date().nullable(),
24906
+ areaId: string2().nullable()
24907
+ });
24908
+ var progressDayDetailSchema = object({
24909
+ date: progressDateSchema,
24910
+ timezone: string2().min(1),
24911
+ done: array(progressDoneItemSchema),
24912
+ slipped: array(progressSlippedItemSchema)
24913
+ });
24914
+
24800
24915
  // ../../packages/schema/src/contracts/insights.ts
24801
24916
  var insightsActivityPointSchema = object({
24802
24917
  day: string2(),
@@ -25075,6 +25190,7 @@ var foodDaySchema = object({
25075
25190
  completeness: _enum2(FOOD_DAY_COMPLETENESS_VALUES)
25076
25191
  });
25077
25192
  var civilDate = string2().regex(/^\d{4}-\d{2}-\d{2}$/, "a date is YYYY-MM-DD");
25193
+ var foodDateSchema = civilDate;
25078
25194
  var mealLogListQuerySchema = object({ from: exports_coerce.date(), to: exports_coerce.date() }).refine((q) => q.from < q.to, { message: "from must be before to" }).refine((q) => q.to.getTime() - q.from.getTime() <= 92 * 24 * 60 * 60 * 1000, {
25079
25195
  message: "ask for at most 92 days"
25080
25196
  });
@@ -30113,6 +30229,410 @@ function crudOperations(r) {
30113
30229
  };
30114
30230
  }
30115
30231
 
30232
+ // ../../packages/schema/src/operations/animals.ts
30233
+ var READ = ["animals:read"];
30234
+ var WRITE = ["animals:write"];
30235
+ var visit = { visitId: "The vet visit's ULID." };
30236
+ var animalOperations = defineOperations({
30237
+ ...crudOperations({
30238
+ segment: "animals",
30239
+ tag: "Animals",
30240
+ noun: "animal",
30241
+ read: "animals:read",
30242
+ write: "animals:write",
30243
+ create: createAnimalInputSchema,
30244
+ update: updateAnimalInputSchema,
30245
+ listQuery: [
30246
+ {
30247
+ name: "status",
30248
+ description: "Only this status (`planned`, `active`, `quarantine`, `rehomed`, `lost`, `died`)."
30249
+ },
30250
+ { name: "form", description: "`individual` or `group`." },
30251
+ { name: "groupId", description: "Only the members of this group." },
30252
+ {
30253
+ name: "includeEnded",
30254
+ description: "`true`: include animals that left (rehomed, lost, died); left out by default."
30255
+ }
30256
+ ],
30257
+ listQuerySchema: animalsQuerySchema
30258
+ }),
30259
+ overview: {
30260
+ method: "get",
30261
+ path: "/animals/overview",
30262
+ scopes: READ,
30263
+ tag: "Animals",
30264
+ summary: "The Animals home",
30265
+ description: "The animals still with the household, today's strip (eggs per flock against its heads with the last 7 days, the feed supplies with days left, the daily-care Habits and whether they're done today, in UTC days) `comingUp`, every animal's key dates from today on (each with `vet`, the vet its animal or flock is cared by, else null), and `periods`, the periods running on them.",
30266
+ status: 200,
30267
+ response: { description: "`{ animals, today, comingUp, periods }`." }
30268
+ },
30269
+ handover: {
30270
+ method: "get",
30271
+ path: "/animals/handover",
30272
+ scopes: READ,
30273
+ tag: "Animals",
30274
+ summary: "The holiday handover",
30275
+ description: "What a sitter needs for a trip: the `away` period given as `periodId` (404 when it isn't one), else the next `away` period still to come or running, else the Space's \"Away until \u2026\"; `trip` is null when nothing is planned. Each animal still here (planned ones and ones that left stay off) with `steps` and `aside` read from its attached note's list lines and other lines, its `speciesKey` and `speciesClass` (for its icon), and `asleep` when a hibernation runs into the trip; the trip carries the agent's `agentNote` (written on the `away` period's `rule.agentNote`); `contacts`, the linked vets then every Contact with the vet role (`kind: \"vet\"`), then each member's own Contact with a phone (`kind: \"member\"`); `trip.calendar` is null here (the calendar is the signed-in member's own); `sensors` when a room's sensors reported this week; the feed `supplies` with `lastsTrip`; the daily-care `habits` with `restsThroughTrip`. Never a chip or certificate number. Days are UTC.",
30276
+ query: [
30277
+ {
30278
+ name: "periodId",
30279
+ description: "An `away` period's ULID; the next trip when left out."
30280
+ }
30281
+ ],
30282
+ querySchema: animalHandoverQuerySchema,
30283
+ status: 200,
30284
+ response: {
30285
+ description: "`{ trip, areaId, animals, contacts, supplies, habits, generatedAt }`."
30286
+ },
30287
+ errors: { 404: "`periodId` isn't an `away` period in this Space." }
30288
+ },
30289
+ listSpecies: {
30290
+ method: "get",
30291
+ path: "/animals/species",
30292
+ scopes: READ,
30293
+ tag: "Animals",
30294
+ summary: "List species profiles",
30295
+ description: "The Space's species profiles; the built-in set (dog, cat, chicken, Hermann's tortoise, native finches with closed rings, \u2026) arrives on first use and can be edited.",
30296
+ status: 200,
30297
+ response: { description: "The species profiles." }
30298
+ },
30299
+ createSpecies: {
30300
+ method: "post",
30301
+ path: "/animals/species",
30302
+ scopes: WRITE,
30303
+ tag: "Animals",
30304
+ summary: "Add a species profile",
30305
+ description: "A species of the Space's own. `key` is a lowercase slug, unique per Space (409 when taken).",
30306
+ body: createAnimalSpeciesInputSchema,
30307
+ status: 201,
30308
+ response: { description: "The species profile." },
30309
+ errors: { 400: true }
30310
+ },
30311
+ updateSpecies: {
30312
+ method: "patch",
30313
+ path: "/animals/species/{speciesId}",
30314
+ pathParams: { speciesId: "The species' ULID." },
30315
+ scopes: WRITE,
30316
+ tag: "Animals",
30317
+ summary: "Edit a species profile",
30318
+ description: "Edit the Space's copy of a species profile; the key stays.",
30319
+ body: updateAnimalSpeciesInputSchema,
30320
+ status: 200,
30321
+ response: { description: "The species profile." },
30322
+ errors: { 400: true }
30323
+ },
30324
+ vetVisit: {
30325
+ method: "get",
30326
+ path: "/animals/vet-visits/{visitId}",
30327
+ pathParams: visit,
30328
+ scopes: READ,
30329
+ tag: "Animals",
30330
+ summary: "A vet visit, as its receipt reads",
30331
+ description: "The animal, when, the vet, the clinician, the vaccine with where it was read, the dewormer, the weight with its change since the weigh-in before, the key dates it set, the files it filed, the cost, and `undone`.",
30332
+ status: 200,
30333
+ response: { description: "The vet visit." },
30334
+ errors: { 404: true }
30335
+ },
30336
+ patchVetVisit: {
30337
+ method: "patch",
30338
+ path: "/animals/vet-visits/{visitId}",
30339
+ pathParams: visit,
30340
+ scopes: WRITE,
30341
+ tag: "Animals",
30342
+ summary: "Put a vet visit's fields right",
30343
+ description: "The receipt's Edit: `vetContactId`, `clinician`, `reason`, `vaccine`, `dewormer` or `cost` (null clears one). The ledger keeps every version; the newest wins. Fires `animal.visit.updated`.",
30344
+ body: animalVetVisitPatchSchema,
30345
+ status: 200,
30346
+ response: { description: "The vet visit." },
30347
+ errors: { 400: true, 404: true },
30348
+ webhook: "animal.visit.updated"
30349
+ },
30350
+ undoVetVisit: {
30351
+ method: "post",
30352
+ path: "/animals/vet-visits/{visitId}/undo",
30353
+ pathParams: visit,
30354
+ scopes: WRITE,
30355
+ tag: "Animals",
30356
+ summary: "Take a vet visit back",
30357
+ description: "The receipt's Undo: every write of that turn goes: the visit (its weight drops off the chart and the timeline), the key dates it set, and the files go back to the chat. Fires `animal.visit.undone`.",
30358
+ status: 200,
30359
+ response: { description: "The vet visit, `undone: true`." },
30360
+ errors: { 404: true },
30361
+ webhook: "animal.visit.undone"
30362
+ },
30363
+ timeline: {
30364
+ method: "get",
30365
+ path: "/animals/{id}/timeline",
30366
+ scopes: READ,
30367
+ tag: "Animals",
30368
+ summary: "An animal's timeline",
30369
+ description: "Newest first: weights (grams), lengths (mm), egg counts and notes logged on it (for a group, its members' too), periods starting and ending, and key dates that came round, over the last `days` (default 90).",
30370
+ query: [{ name: "days", description: "How far back, in days (1\u20133650, default 90)." }],
30371
+ querySchema: animalTimelineQuerySchema,
30372
+ status: 200,
30373
+ response: { description: "The timeline entries." },
30374
+ errors: { 404: true }
30375
+ },
30376
+ lostCard: {
30377
+ method: "get",
30378
+ path: "/animals/{id}/lost-card",
30379
+ scopes: READ,
30380
+ tag: "Animals",
30381
+ summary: "An animal's lost-pet card",
30382
+ description: "`card` is null while it's off; else its `path` (`/lost/<token>`, the page its QR code opens, no account), what it shows (`photoFileId`, `phone`, `showChipped`, `note`) and `preview`, the finder's page. `photos` are the pictures on the animal it may show; `chipOnFile` whether it can say chipped.",
30383
+ status: 200,
30384
+ response: { description: "The card's state." },
30385
+ errors: { 404: true }
30386
+ },
30387
+ setLostCard: {
30388
+ method: "put",
30389
+ path: "/animals/{id}/lost-card",
30390
+ scopes: WRITE,
30391
+ tag: "Animals",
30392
+ summary: "Turn the lost-pet card on, or change what it shows",
30393
+ description: "The name always shows; the rest only when given (`null` takes one off). An individual still with the household (a lost one included); the photo must be a picture on the animal; `showChipped` needs its chip on file and never shows the number. The link stays the same while the card is on.",
30394
+ body: animalLostCardInputSchema,
30395
+ status: 200,
30396
+ response: { description: "The card's state." },
30397
+ errors: { 400: true, 404: true }
30398
+ },
30399
+ removeLostCard: {
30400
+ method: "delete",
30401
+ path: "/animals/{id}/lost-card",
30402
+ scopes: WRITE,
30403
+ tag: "Animals",
30404
+ summary: "Turn the lost-pet card off",
30405
+ description: "Its link stops working (a printed QR too); turning it on again makes a new one.",
30406
+ status: 204,
30407
+ response: { description: "Off." },
30408
+ errors: { 404: true }
30409
+ },
30410
+ acquisition: {
30411
+ method: "get",
30412
+ path: "/animals/{id}/acquisition",
30413
+ scopes: READ,
30414
+ tag: "Animals",
30415
+ summary: "Bringing a planned animal home: its checklist",
30416
+ description: 'The Task "Bring <name> home" (`taskId`, `pickUpOn`), its steps in order (`done`, and `quarantine` for the quarantine step), `blockers` to sort before the pick-up ("No EU CITES certificate with photo on file yet") and `arrived`. A 404 when none was made.',
30417
+ status: 200,
30418
+ response: { description: "The checklist." },
30419
+ errors: { 404: true }
30420
+ },
30421
+ planAcquisition: {
30422
+ method: "post",
30423
+ path: "/animals/{id}/acquisition",
30424
+ scopes: WRITE,
30425
+ tag: "Animals",
30426
+ summary: "Plan bringing a planned animal home",
30427
+ description: 'Acquisitions are a checklist, not a form: makes the Task "Bring <name> home" in its Area (due `pickUpOn`), with a step per line of the checklist \u2014 the papers its kind needs (DogID, CatID, CITES A, closed ring), the vet check, its home, and the quarantine before it meets the others (`quarantine`, on by default for a reptile or a bird) \u2014 plus `extraSteps`. Only a `planned` animal (400 otherwise); one per animal (409).',
30428
+ body: animalAcquisitionInputSchema,
30429
+ status: 201,
30430
+ response: { description: "The checklist." },
30431
+ errors: { 400: true, 404: true, 409: "Already planned." }
30432
+ },
30433
+ arrived: {
30434
+ method: "post",
30435
+ path: "/animals/{id}/arrived",
30436
+ scopes: WRITE,
30437
+ tag: "Animals",
30438
+ summary: "A planned animal is home",
30439
+ description: "Sets `acquiredOn` (`on`, today by default) and its status: `quarantine` with a quarantine Period of `quarantineMonths` (3 when its checklist has the quarantine step), else `active`. The acquisition's Task is done; open steps stay open. Returns the animal's page.",
30440
+ body: animalArrivedInputSchema,
30441
+ status: 200,
30442
+ response: { description: "The animal." },
30443
+ errors: { 400: true, 404: true }
30444
+ },
30445
+ log: {
30446
+ method: "post",
30447
+ path: "/animals/{id}/log",
30448
+ scopes: WRITE,
30449
+ tag: "Animals",
30450
+ summary: "Log eggs, a weight, a length or a note",
30451
+ description: 'One fact as one call (`animal_log`): `{ kind: "weight", value, unit: "g"|"kg" }` (stored in grams), `{ kind: "length", value }` (mm), `{ kind: "eggs", count }` (the day\'s count on a bird or a flock; logging again that day replaces it) or `{ kind: "note", text }`; `occurredAt` back-dates it. Appended to the datapoints ledger. Returns the timeline entry; fires `animal.logged`.',
30452
+ body: animalLogInputSchema,
30453
+ status: 201,
30454
+ response: { description: "The timeline entry." },
30455
+ errors: { 400: true, 404: true },
30456
+ webhook: "animal.logged"
30457
+ },
30458
+ logVetVisit: {
30459
+ method: "post",
30460
+ path: "/animals/{id}/vet-visits",
30461
+ scopes: WRITE,
30462
+ tag: "Animals",
30463
+ summary: "Log a vet visit in one call",
30464
+ description: 'A vet visit as one call (`animal_vet_visit`, mockup 03): the visit becomes a vet Encounter about the animal (the household\'s, `space`-visible), with `vetContactId`, `clinician`, `reason`, `vaccine` `{ name, source: "sticker"|"photo"|"said" }`, `dewormer` and `cost` `{ amount, currency }` on the ledger; `weight` `{ value, unit }` is logged on the animal; each of `nextDates` `[{ id, label, date }]` becomes a key date (so a Task); `files` `[{ id, name? }]` (uploaded from chat) are filed on the animal. `id` is the visit\'s, client-minted. Returns the visit, the receipt to post with `receipt: { nodeId: <animal id>, about: "vet_visit", parts: [<visit id>] }`; fires `animal.visit.logged`.',
30465
+ body: animalVetVisitInputSchema,
30466
+ status: 201,
30467
+ response: { description: "The vet visit (the receipt)." },
30468
+ errors: { 400: true, 404: true },
30469
+ webhook: "animal.visit.logged"
30470
+ }
30471
+ });
30472
+
30473
+ // ../../packages/schema/src/operations/areas.ts
30474
+ var standard = { standardId: "The standard's id (from the Area's `standards`)." };
30475
+ var detail = "The Area detail; each standard carries `state` (`holding` / `worth_a_look`, null when unlinked), `why` in words and `heldBy`.";
30476
+ var areaOperations = defineOperations({
30477
+ ...crudOperations({
30478
+ segment: "areas",
30479
+ tag: "Areas",
30480
+ noun: "area",
30481
+ read: "areas:read",
30482
+ write: "areas:write",
30483
+ create: createAreaInputSchema,
30484
+ update: updateAreaInputSchema,
30485
+ listQuery: [
30486
+ {
30487
+ name: "archived",
30488
+ description: "`false`: only live Areas; `true`: only archived ones. Omit for both.",
30489
+ enum: ["true", "false"]
30490
+ }
30491
+ ],
30492
+ listQuerySchema: areaListQuerySchema
30493
+ }),
30494
+ overview: {
30495
+ method: "get",
30496
+ path: "/areas/overview",
30497
+ scopes: ["areas:read"],
30498
+ tag: "Areas",
30499
+ summary: "How each Area is going",
30500
+ description: 'The map of life (#901): every live, unarchived Area with its attention in words against its own usual (this fortnight vs its median fortnight over the 12 weeks before: `busier` "Busier than usual", `steady` "Steady", `quiet` "Quiet for three weeks", `resting` "Resting") and a short reason, its purpose line, at most two lines of what\'s moving, and `waiting`, what waits on the key\'s member (the only count). Activity is tasks landed, habit check-ins, notes and time blocks; under `owner=mine` (the default) agents\' work doesn\'t count. Resting Areas come last; set or clear resting with `PATCH /api/v1/areas/{id}` `{ restingUntil }`. Never a score.',
30501
+ query: [
30502
+ {
30503
+ name: "owner",
30504
+ description: "Whose work counts: `mine` (default), `agents` or `all`.",
30505
+ enum: ["mine", "agents", "all"]
30506
+ }
30507
+ ],
30508
+ querySchema: areaOverviewQuerySchema,
30509
+ status: 200,
30510
+ response: { description: "The overview.", view: "AreaOverview" },
30511
+ errors: { 400: true },
30512
+ operationId: "get_areas_overview"
30513
+ },
30514
+ sinceLastLook: {
30515
+ method: "get",
30516
+ path: "/areas/{id}/since-last-look",
30517
+ scopes: ["areas:read"],
30518
+ tag: "Areas",
30519
+ summary: "What happened in an Area since the last look",
30520
+ description: 'Areas 6: since the Area\'s last look (`lastReviewedAt`), or the last month without one, up to five tasks of the Area (its own and its Projects\') that `landed` (with `by` when an Agent did it) and that are `fresh`, and up to three that seem `stuck`, each with `why` in words ("Waiting on you since 3 October", "Deadline was 12 October", "Untouched since 1 September"). Named, never tallied.',
30521
+ status: 200,
30522
+ response: { description: "Since you last looked.", view: "AreaSinceLastLook" },
30523
+ errors: { 404: true },
30524
+ operationId: "get_area_since_last_look"
30525
+ },
30526
+ lookedAt: {
30527
+ method: "post",
30528
+ path: "/areas/{id}/looked-at",
30529
+ scopes: ["areas:write"],
30530
+ tag: "Areas",
30531
+ summary: "Record a look at an Area",
30532
+ description: 'Areas 6: "Looked at" sets the Area\'s `lastReviewedAt` to now, so its rhythm (`reviewCadence`: weekly, monthly, quarterly) starts again and it leaves Review until it comes round. Fires `area.updated`.',
30533
+ status: 200,
30534
+ response: { description: "The Area view." },
30535
+ errors: { 404: true },
30536
+ webhook: "area.updated",
30537
+ operationId: "mark_area_looked_at"
30538
+ },
30539
+ rest: {
30540
+ method: "post",
30541
+ path: "/areas/{id}/rest",
30542
+ scopes: ["areas:write"],
30543
+ tag: "Areas",
30544
+ summary: "Rest an Area until a day",
30545
+ description: "Areas 8: rest the Area until `until` (the day it's back, after today). It keeps everything filed and folds to the foot of the map; Focus, Review and the agent's filing leave it alone; its recurring tasks pause (Today leaves out their occurrences dated while it rests). `habits: \"pause\"` pauses its habits until then too (default `keep`). Fires `area.updated`.",
30546
+ body: restAreaInputSchema,
30547
+ status: 200,
30548
+ response: { description: "The Area view." },
30549
+ errors: { 400: true, 404: true },
30550
+ webhook: "area.updated",
30551
+ operationId: "rest_area"
30552
+ },
30553
+ back: {
30554
+ method: "post",
30555
+ path: "/areas/{id}/back",
30556
+ scopes: ["areas:write"],
30557
+ tag: "Areas",
30558
+ summary: "Bring a resting Area back",
30559
+ description: "Areas 8: the Area is back today. The habits it paused come back too, and its recurring tasks' occurrences that went by while it rested are skipped, so nothing piles up. Fires `area.updated`.",
30560
+ status: 200,
30561
+ response: { description: "The Area view." },
30562
+ errors: { 404: true },
30563
+ webhook: "area.updated",
30564
+ operationId: "bring_area_back"
30565
+ },
30566
+ toProject: {
30567
+ method: "post",
30568
+ path: "/areas/{id}/to-project",
30569
+ scopes: ["areas:write", "projects:write"],
30570
+ tag: "Areas",
30571
+ summary: "Make an Area a Project",
30572
+ description: "Areas 8: an Area that finishes (a wedding, a move) becomes a Project. The client mints `projectId`; the Project takes the Area's title (its purpose becomes the Project's note) and is filed under `intoAreaId` (or none). The Area's loose tasks become the Project's tasks; its own Projects, habits, routines, notes and Collection items move to `intoAreaId` beside it; the Area is archived, never deleted. Returns the receipt (`moved`) that `\u2026/to-project/undo` takes. An agent should only propose this; the member decides. Fires `area.updated`.",
30573
+ body: areaToProjectInputSchema,
30574
+ status: 200,
30575
+ response: { description: "The receipt." },
30576
+ errors: { 400: true, 404: true },
30577
+ webhook: "area.updated",
30578
+ operationId: "make_area_a_project"
30579
+ },
30580
+ undoToProject: {
30581
+ method: "post",
30582
+ path: "/areas/{id}/to-project/undo",
30583
+ scopes: ["areas:write", "projects:write"],
30584
+ tag: "Areas",
30585
+ summary: "Undo making an Area a Project",
30586
+ description: "Areas 8: hand back the receipt from `\u2026/to-project`; everything that moved comes back, the Area is active again and the Project made for it goes. Fires `area.updated`.",
30587
+ body: undoAreaToProjectInputSchema,
30588
+ status: 200,
30589
+ response: { description: "The Area view." },
30590
+ errors: { 400: true, 404: true },
30591
+ webhook: "area.updated",
30592
+ operationId: "undo_area_to_project"
30593
+ },
30594
+ addStandard: {
30595
+ method: "post",
30596
+ path: "/areas/{id}/standards",
30597
+ scopes: ["areas:write"],
30598
+ tag: "Areas",
30599
+ summary: "Add an Area standard",
30600
+ description: 'Add one standard (a plain sentence of what fine looks like) at the end; up to five per Area. `link` optionally ties it to what holds it: `{ kind: "habit", id }`, `{ kind: "task", id }` (a recurring task) or `{ kind: "metric", key }`. An Agent\'s add lands in the change list with Undo. Fires `area.updated`.',
30601
+ body: addAreaStandardInputSchema,
30602
+ status: 201,
30603
+ response: { description: detail },
30604
+ errors: { 400: true, 404: true },
30605
+ webhook: "area.updated"
30606
+ },
30607
+ updateStandard: {
30608
+ method: "patch",
30609
+ path: "/areas/{id}/standards/{standardId}",
30610
+ pathParams: standard,
30611
+ scopes: ["areas:write"],
30612
+ tag: "Areas",
30613
+ summary: "Change an Area standard",
30614
+ description: "Reword a standard or change what holds it (`link: null` unlinks it, so it's checked by hand in review). Fires `area.updated`.",
30615
+ body: updateAreaStandardInputSchema,
30616
+ status: 200,
30617
+ response: { description: detail },
30618
+ errors: { 400: true, 404: true },
30619
+ webhook: "area.updated"
30620
+ },
30621
+ removeStandard: {
30622
+ method: "delete",
30623
+ path: "/areas/{id}/standards/{standardId}",
30624
+ pathParams: standard,
30625
+ scopes: ["areas:write"],
30626
+ tag: "Areas",
30627
+ summary: "Remove an Area standard",
30628
+ description: "Remove one standard. Fires `area.updated`.",
30629
+ status: 200,
30630
+ response: { description: detail },
30631
+ errors: { 404: true },
30632
+ webhook: "area.updated"
30633
+ }
30634
+ });
30635
+
30116
30636
  // ../../packages/schema/src/operations/block-templates.ts
30117
30637
  var crud = crudOperations({
30118
30638
  segment: "block-templates",
@@ -30154,6 +30674,69 @@ var blockTemplateOperations = defineOperations({
30154
30674
  }
30155
30675
  });
30156
30676
 
30677
+ // ../../packages/schema/src/operations/calendar.ts
30678
+ var calendarOperations = defineOperations({
30679
+ connections: {
30680
+ method: "get",
30681
+ path: "/calendar/connections",
30682
+ scopes: ["calendar:read"],
30683
+ tag: "Calendar",
30684
+ summary: "List connections",
30685
+ description: "The Space's integration connections (calendar + any other provider).",
30686
+ status: 200,
30687
+ response: { description: "The connections." }
30688
+ },
30689
+ connection: {
30690
+ method: "get",
30691
+ path: "/calendar/connections/{id}",
30692
+ scopes: ["calendar:read"],
30693
+ tag: "Calendar",
30694
+ summary: "Get a connection",
30695
+ description: "One integration connection by id.",
30696
+ status: 200,
30697
+ response: { description: "The connection." },
30698
+ errors: { 404: true }
30699
+ },
30700
+ links: {
30701
+ method: "get",
30702
+ path: "/calendar/connections/{id}/links",
30703
+ scopes: ["calendar:read"],
30704
+ tag: "Calendar",
30705
+ summary: "List event links",
30706
+ description: "The local-node \u2194 remote-event links for a calendar connection.",
30707
+ status: 200,
30708
+ response: { description: "The event links." },
30709
+ errors: { 404: true }
30710
+ },
30711
+ events: {
30712
+ method: "get",
30713
+ path: "/calendar/events",
30714
+ scopes: ["calendar:read"],
30715
+ tag: "Calendar",
30716
+ summary: "List the member's events",
30717
+ description: "The member's mirrored events overlapping `[from, to)` (ISO instants, both or neither; at most 62 days; neither = the next day), each with `busyForPlacement`, `optional` and `attending`. Needs a key that acts for a member.",
30718
+ query: [
30719
+ { name: "from", description: "Window start, an ISO instant." },
30720
+ { name: "to", description: "Window end (exclusive), an ISO instant." }
30721
+ ],
30722
+ status: 200,
30723
+ response: { description: "The events.", list: true, view: "Event" },
30724
+ errors: { 400: true }
30725
+ },
30726
+ plan: {
30727
+ method: "patch",
30728
+ path: "/calendar/events/{id}/plan",
30729
+ scopes: ["calendar:write"],
30730
+ tag: "Calendar",
30731
+ summary: "Mark an event optional, or answer one",
30732
+ description: "Something the member may or may not do (watching a race) is `optional`: ask them first (a question with buttons), then record `attending` (`yes` \xB7 `no` \xB7 null = waiting). Until it's `yes` the event holds no time (`busyForPlacement` false) and nothing may be planned around it. do's own state; nothing goes to Google. `optional: false` clears the answer.",
30733
+ body: eventPlanInputSchema,
30734
+ status: 200,
30735
+ response: { description: "The event, after.", view: "Event" },
30736
+ errors: { 400: true, 404: true }
30737
+ }
30738
+ });
30739
+
30157
30740
  // ../../packages/schema/src/operations/collection-upkeep.ts
30158
30741
  var collectionUpkeepOperations = defineOperations({
30159
30742
  list: {
@@ -30193,6 +30776,386 @@ var collectionUpkeepOperations = defineOperations({
30193
30776
  }
30194
30777
  });
30195
30778
 
30779
+ // ../../packages/schema/src/operations/days.ts
30780
+ var pathParams = {
30781
+ date: {
30782
+ description: "The civil day, `YYYY-MM-DD`, in the member's zone.",
30783
+ format: "date"
30784
+ }
30785
+ };
30786
+ var pathParamSchemas = { date: dayDateSchema };
30787
+ var dayOperations = defineOperations({
30788
+ record: {
30789
+ method: "get",
30790
+ path: "/days/{date}/record",
30791
+ pathParams,
30792
+ pathParamSchemas,
30793
+ scopes: ["calendar:read"],
30794
+ tag: "Calendar",
30795
+ summary: "A day's record",
30796
+ description: "The key's member's day as the evening check-in reads it: the life blocks starting that day, each with its plan, its record, `status` (`as_planned` \xB7 `moved` \xB7 `shortened` \xB7 `extended` \xB7 `skipped` \xB7 `replaced` \xB7 `unplanned`), `recordState` (`open` \xB7 `inferred` \xB7 `confirmed` \xB7 `closed`), a confidence word and its evidence; the queued `questions` (two at most), `filledIn` (blocks the agent wrote that the member hasn't confirmed), `gaps` (30+ min with no block), `closed` and `autoClosesAt` (48 h after the day ends).",
30797
+ status: 200,
30798
+ response: { description: "The day's record." },
30799
+ errors: { 400: true }
30800
+ },
30801
+ write: {
30802
+ method: "post",
30803
+ path: "/days/{date}/record",
30804
+ pathParams,
30805
+ pathParamSchemas,
30806
+ scopes: ["calendar:write"],
30807
+ tag: "Calendar",
30808
+ summary: "Write a day's record (day_record_write)",
30809
+ description: "A set of changes to begun life blocks of that day, each with the facts behind it (`evidence`: `source` chat \xB7 edit_do \xB7 edit_google \xB7 google_health \xB7 wakehook \xB7 strava \xB7 habit \xB7 task \xB7 routine_run \xB7 do_activity \xB7 github \xB7 phone_location \xB7 plan_default, with `refKind`/`refId`). Retime a block (`blockId` + `startAt`/`endAt`), mark it `skipped` or `replacedBy` another, or mint a new one (`id` + `lifeKind` + times: an unplanned block, an `unknown` gap, a replacement). Times round to the quarter hour; status and confidence follow. An Agent's write leaves the record `inferred`; a member's confirms it. A closed day is a 409 for an Agent. Never waits for a confirmation. Returns the receipt: `receipt` (send it to `/undo`), one line in words per block, `googleUpdated` and the day.",
30810
+ body: dayRecordWriteInputSchema,
30811
+ status: 200,
30812
+ response: { description: "The receipt." },
30813
+ errors: { 400: true, 404: true, 409: true }
30814
+ },
30815
+ ask: {
30816
+ method: "post",
30817
+ path: "/days/{date}/record/ask",
30818
+ pathParams,
30819
+ pathParamSchemas,
30820
+ scopes: ["calendar:write"],
30821
+ tag: "Calendar",
30822
+ summary: "Queue a check-in question (day_record_ask)",
30823
+ description: "A fill-in question for the evening check-in about one block (`blockId`) or a gap (`id` + `startAt`/`endAt`, which becomes an `unknown` block). Two a day at most and never twice about one block (409). The member's own word in a later write answers it.",
30824
+ body: dayRecordAskInputSchema,
30825
+ status: 200,
30826
+ response: { description: "The day's record." },
30827
+ errors: { 400: true, 404: true, 409: true }
30828
+ },
30829
+ undo: {
30830
+ method: "post",
30831
+ path: "/days/{date}/record/undo",
30832
+ pathParams,
30833
+ pathParamSchemas,
30834
+ scopes: ["calendar:write"],
30835
+ tag: "Calendar",
30836
+ summary: "Undo a record write",
30837
+ description: "Takes a whole write back by its `receipt`: retimed blocks are put back, blocks it made are removed. The evidence stays as history.",
30838
+ body: dayRecordUndoInputSchema,
30839
+ status: 200,
30840
+ response: { description: "`{ undone, day }`." },
30841
+ errors: { 404: true }
30842
+ },
30843
+ close: {
30844
+ method: "post",
30845
+ path: "/days/{date}/close",
30846
+ pathParams,
30847
+ pathParamSchemas,
30848
+ scopes: ["calendar:write"],
30849
+ tag: "Calendar",
30850
+ summary: "Close a day (day_close)",
30851
+ description: "The member said the record is fine: every life block of the day is `closed` and pushed to Google at once. After this only the member changes it. A day nobody closes closes itself 48 h after it ends.",
30852
+ status: 200,
30853
+ response: { description: "The day's record." },
30854
+ errors: { 400: true }
30855
+ }
30856
+ });
30857
+
30858
+ // ../../packages/schema/src/operations/food-log.ts
30859
+ var NO_MEMBER = "The key holds no health scope by name, or has no member behind it.";
30860
+ var HIDDEN = "No such meal, or it isn't the key's member's.";
30861
+ var RECEIPT = "The meal with its `receipt`: a headline with the kcal range, a confidence word (close \xB7 estimate \xB7 rough), a line per item with its source, and the Undo / Edit / Save as my meal calls.";
30862
+ var ITEM = "Each item: `label` and one of `foodId` (from `GET /api/v1/foods/search`), `productId` or `recipeId`, with `grams`, `quantity` + `unit` (`2 boterham`, `1 el`) or `servings`; optional `gramsLow`/`gramsHigh` and `matchConfidence`. With no reference, an `estimate` `{kcal, proteinG, carbsG, fatG}` (a labelled guess). The numbers are computed: table \xD7 grams.";
30863
+ var meal = { id: "The meal's ULID." };
30864
+ var mealId = { id: ulidSchema };
30865
+ var foodLogOperations = defineOperations({
30866
+ list: {
30867
+ method: "get",
30868
+ path: "/food/logs",
30869
+ scopes: ["health:read"],
30870
+ tag: "Food",
30871
+ summary: "List meals",
30872
+ description: "The member's meals eaten in `[from, to)` (ISO instants, at most 92 days), oldest first.",
30873
+ query: [
30874
+ { name: "from", description: "Start (inclusive), an ISO instant.", required: true },
30875
+ { name: "to", description: "End (exclusive), an ISO instant.", required: true }
30876
+ ],
30877
+ querySchema: mealLogListQuerySchema,
30878
+ status: 200,
30879
+ response: { description: "The meals." },
30880
+ errors: { 400: true, 403: NO_MEMBER }
30881
+ },
30882
+ create: {
30883
+ method: "post",
30884
+ path: "/food/logs",
30885
+ scopes: ["health:write"],
30886
+ tag: "Food",
30887
+ summary: "Log a meal",
30888
+ description: `Log what the member ate in one call: \`id\`, \`title\`, \`eatenAt\`, \`mealType\` (breakfast \xB7 lunch \xB7 dinner \xB7 snack \xB7 drink), optional \`portionNote\`, \`place\`, \`flags\`, \`capture\`, \`photoFileId\`, and \`items\`. ${ITEM} Always private to the member.`,
30889
+ body: createMealLogInputSchema,
30890
+ status: 201,
30891
+ response: { description: RECEIPT },
30892
+ errors: { 400: true, 403: NO_MEMBER },
30893
+ operationId: "food_log"
30894
+ },
30895
+ get: {
30896
+ method: "get",
30897
+ path: "/food/logs/{id}",
30898
+ pathParams: meal,
30899
+ pathParamSchemas: mealId,
30900
+ scopes: ["health:read"],
30901
+ tag: "Food",
30902
+ summary: "Get a meal",
30903
+ description: "One meal: its current items, the corrected ones, and the attribution its sources require.",
30904
+ status: 200,
30905
+ response: { description: "The meal." },
30906
+ errors: { 404: HIDDEN }
30907
+ },
30908
+ correct: {
30909
+ method: "patch",
30910
+ path: "/food/logs/{id}",
30911
+ pathParams: meal,
30912
+ pathParamSchemas: mealId,
30913
+ scopes: ["health:write"],
30914
+ tag: "Food",
30915
+ summary: "Correct a meal",
30916
+ description: `Correct a meal: \`replace\` (\`[{itemId, item}]\`), \`remove\` (item ids) and \`add\` (items) add rows, never edit one; the meal's own fields update. ${ITEM}`,
30917
+ body: correctMealLogInputSchema,
30918
+ status: 200,
30919
+ response: { description: RECEIPT },
30920
+ errors: { 400: true, 404: HIDDEN },
30921
+ operationId: "food_correct"
30922
+ },
30923
+ remove: {
30924
+ method: "delete",
30925
+ path: "/food/logs/{id}",
30926
+ pathParams: meal,
30927
+ pathParamSchemas: mealId,
30928
+ scopes: ["health:write"],
30929
+ tag: "Food",
30930
+ summary: "Remove a meal",
30931
+ description: "The receipt's Undo: the meal goes to Trash.",
30932
+ status: 204,
30933
+ response: { description: "Removed." },
30934
+ errors: { 404: HIDDEN }
30935
+ },
30936
+ undoCorrection: {
30937
+ method: "post",
30938
+ path: "/food/logs/{id}/undo-correction",
30939
+ pathParams: meal,
30940
+ pathParamSchemas: mealId,
30941
+ scopes: ["health:write"],
30942
+ tag: "Food",
30943
+ summary: "Undo a meal's last correction",
30944
+ description: "Take back the latest correction: the items it replaced or removed are current again.",
30945
+ status: 200,
30946
+ response: { description: RECEIPT },
30947
+ errors: { 400: true, 404: HIDDEN }
30948
+ },
30949
+ saveMeal: {
30950
+ method: "post",
30951
+ path: "/food/logs/{id}/save-meal",
30952
+ pathParams: meal,
30953
+ pathParamSchemas: mealId,
30954
+ scopes: ["health:write"],
30955
+ tag: "Food",
30956
+ summary: "Save a meal as my meal",
30957
+ description: "Keep the meal's current items as a saved meal (a private recipe of kind `saved_meal`, in grams), optionally under a new `title`. Log it again with `recipeId` + `servings: 1`. Returns `{ recipeId }`.",
30958
+ body: saveMealInputSchema,
30959
+ bodyOptional: true,
30960
+ status: 201,
30961
+ response: { description: "The saved meal's recipe id." },
30962
+ errors: { 404: HIDDEN },
30963
+ operationId: "food_save_meal"
30964
+ },
30965
+ context: {
30966
+ method: "get",
30967
+ path: "/food/logs/{id}/context",
30968
+ pathParams: meal,
30969
+ pathParamSchemas: mealId,
30970
+ scopes: ["health:read"],
30971
+ tag: "Food",
30972
+ summary: "Show a meal in its day",
30973
+ description: "The meal page's read: the weekmenu plan for the meal's day and slot (`planStatus` as_planned \xB7 swapped \xB7 unplanned), the nearby meal block, `minutesFromPlan`, `minutesBeforeBed`, and After \u2014 Health symptom entries, an Episode that began and doses in the 12 hours after, the night's sleep and the day rating \u2014 plus the Episode that began. Read-side; nothing in it says the meal caused what followed.",
30974
+ status: 200,
30975
+ response: { description: "The meal's context." },
30976
+ errors: { 404: HIDDEN },
30977
+ operationId: "food_context"
30978
+ },
30979
+ week: {
30980
+ method: "get",
30981
+ path: "/food/week",
30982
+ scopes: ["health:read"],
30983
+ tag: "Food",
30984
+ summary: "Show a food week",
30985
+ description: "The week (Monday to Sunday) around `start` (any day of it; this week by default): each day's total (null = nothing logged, not zero), its meals with the minute they were eaten and a `tired` or `symptom` mark when a Health symptom entry followed, the weekmenu's planned meals and kcal, the meal blocks' planned times and bedtime; `usualKcal` (the median complete day of the last 28, never a target), the `weight` Metric over 30 days with its 7-day trend, and the week in numbers. Read-side; nothing in it says a meal caused what followed.",
30986
+ query: [{ name: "start", description: "", format: "date" }],
30987
+ querySchema: foodWeekQuerySchema,
30988
+ status: 200,
30989
+ response: { description: "The food week." },
30990
+ operationId: "food_week"
30991
+ },
30992
+ writeWeekRead: {
30993
+ method: "put",
30994
+ path: "/food/week/read",
30995
+ scopes: ["health:write"],
30996
+ tag: "Food",
30997
+ summary: "Write the read of a food week",
30998
+ description: "Write or replace the agent's read of the week holding `start` (any day of it): a few lines about the week (`body`, at most 2,000 characters), shown on the food week as the agent's read. It sits behind the private wall, the member's only. A week that hasn't started can't have one. Returns the week. Calm words, never a cause or a score.",
30999
+ body: writeFoodWeekReadInputSchema,
31000
+ status: 200,
31001
+ response: { description: "The food week, with its read." },
31002
+ errors: { 400: true, 403: NO_MEMBER },
31003
+ operationId: "food_week_read"
31004
+ },
31005
+ removeWeekRead: {
31006
+ method: "delete",
31007
+ path: "/food/week/read",
31008
+ scopes: ["health:write"],
31009
+ tag: "Food",
31010
+ summary: "Take back the read of a food week",
31011
+ description: "Take back the read of the week holding `start`; nothing to take back is fine.",
31012
+ query: [{ name: "start", description: "A day in the week, YYYY-MM-DD.", required: true }],
31013
+ querySchema: foodWeekReadQuerySchema,
31014
+ status: 204,
31015
+ response: { description: "Taken back." },
31016
+ errors: { 400: true, 403: NO_MEMBER },
31017
+ operationId: "food_week_read_remove"
31018
+ },
31019
+ patterns: {
31020
+ method: "get",
31021
+ path: "/food/patterns",
31022
+ scopes: ["health:read"],
31023
+ tag: "Food",
31024
+ summary: "Show food and how you feel",
31025
+ description: 'A short, fixed list of questions over the 90 days up to this week\'s Monday (so the answers hold still all week): big lunches and a "tired" note within 4 h (weekdays), dinner within 3 h of bed and a symptom that night, very hot / fried / big meals and a symptom within 6 h, days you ate more and the day rating, and each week\'s average kcal against the weight trend. Each answer is `shown`, `no_difference` or `collecting` on the Insights rules (\u2265 21 paired days and \u2265 7 a side, an effect bar, a confidence word), with its sides as counts ("13 of 18 days") or means, the days still needed, and a footer that always gives the number of days. "Goes together", never a cause.',
31026
+ status: 200,
31027
+ response: { description: "The food questions' answers." },
31028
+ operationId: "food_patterns"
31029
+ },
31030
+ proposeTrial: {
31031
+ method: "post",
31032
+ path: "/food/trials",
31033
+ scopes: ["health:write"],
31034
+ tag: "Food",
31035
+ summary: "Propose a two-week food trial",
31036
+ description: 'Propose one small change for the member to try for `days` (14 by default, 7\u201328) from `startsOn` (today or later): `title` is the change ("Split lunch"), `note` a few words on what you\'ll compare afterwards, `question` the food question it answers. It waits on Food and how you feel as "Try it for two weeks?" until the member starts it, says Not now (a week) or stops it; a newer proposal sets an older waiting one aside. Behind the private wall, the member\'s only. Still n-of-1 information, never a diagnosis.',
31037
+ body: proposeFoodTrialInputSchema,
31038
+ status: 201,
31039
+ response: { description: "The trial, proposed." },
31040
+ errors: { 400: true, 403: NO_MEMBER },
31041
+ operationId: "food_trial"
31042
+ },
31043
+ stopTrial: {
31044
+ method: "post",
31045
+ path: "/food/trials/{id}/stop",
31046
+ pathParams: { id: "The trial's ULID." },
31047
+ pathParamSchemas: { id: ulidSchema },
31048
+ scopes: ["health:write"],
31049
+ tag: "Food",
31050
+ summary: "Stop a food trial",
31051
+ description: "Stop a trial, running or still proposed.",
31052
+ status: 204,
31053
+ response: { description: "Stopped." },
31054
+ errors: { 404: "No such trial, or it isn't the key's member's." },
31055
+ operationId: "food_trial_stop"
31056
+ },
31057
+ days: {
31058
+ method: "get",
31059
+ path: "/food/days",
31060
+ scopes: ["health:read"],
31061
+ tag: "Food",
31062
+ summary: "List food days",
31063
+ description: "The `food.kcal` series: per civil day in the member's timezone (a meal before 04:00 counts for the day before), kcal with a range, macros, meals and `complete`/`partial`. A day with nothing logged has no row: missing, not zero.",
31064
+ query: [
31065
+ { name: "from", description: "First day, YYYY-MM-DD.", required: true },
31066
+ { name: "to", description: "Last day, YYYY-MM-DD.", required: true }
31067
+ ],
31068
+ querySchema: foodDaysQuerySchema,
31069
+ status: 200,
31070
+ response: { description: "The days." },
31071
+ errors: { 400: true, 403: NO_MEMBER }
31072
+ },
31073
+ day: {
31074
+ method: "get",
31075
+ path: "/food/days/{date}",
31076
+ pathParams: { date: "The day, YYYY-MM-DD." },
31077
+ pathParamSchemas: { date: foodDateSchema },
31078
+ scopes: ["health:read"],
31079
+ tag: "Food",
31080
+ summary: "Get a food day",
31081
+ description: "One day: its meals, its total (null when nothing is logged) and the household's planned meals for it.",
31082
+ status: 200,
31083
+ response: { description: "The day." },
31084
+ errors: { 400: true, 403: NO_MEMBER }
31085
+ },
31086
+ recall: {
31087
+ method: "get",
31088
+ path: "/food/recall",
31089
+ scopes: ["health:read"],
31090
+ tag: "Food",
31091
+ summary: "Recall frequent meals",
31092
+ description: `What the member eats often: their most-logged meals of the last 30 days (with the last one's id, to log it again) and their saved meals. Use it before asking what "the usual" is.`,
31093
+ status: 200,
31094
+ response: { description: "Frequent and saved meals." },
31095
+ errors: { 403: NO_MEMBER },
31096
+ operationId: "food_recall"
31097
+ }
31098
+ });
31099
+
31100
+ // ../../packages/schema/src/operations/foods.ts
31101
+ var foodOperations = defineOperations({
31102
+ search: {
31103
+ method: "get",
31104
+ path: "/foods/search",
31105
+ scopes: ["meals:read"],
31106
+ tag: "Food",
31107
+ summary: "Search foods",
31108
+ description: "Search the nutrition reference by name: NEVO and USDA generic foods first, then branded products cached from Open Food Facts. Values are per 100 g, with each source's required attribution in `licence`. With `remote=true`, also asks Open Food Facts when there are fewer than `limit` local hits; never call it per keystroke. Returns `{ data, remote }`.",
31109
+ query: [
31110
+ {
31111
+ name: "q",
31112
+ description: "At least 2 characters of the food's name or brand.",
31113
+ required: true
31114
+ },
31115
+ { name: "limit", description: "1\u201350, default 20.", type: "integer", minimum: 1, maximum: 50 },
31116
+ {
31117
+ name: "remote",
31118
+ description: "`true` to also search Open Food Facts.",
31119
+ enum: ["true", "false"]
31120
+ }
31121
+ ],
31122
+ querySchema: foodSearchQuerySchema,
31123
+ status: 200,
31124
+ response: {
31125
+ description: "What happened with Open Food Facts: `skipped` (not asked, or the cache answered), `used`, `rate_limited` (do's own guard refused to call; OFF allows 15 product reads and 10 searches a minute) or `unavailable`."
31126
+ },
31127
+ errors: { 400: true }
31128
+ },
31129
+ barcode: {
31130
+ method: "get",
31131
+ path: "/foods/barcode/{ean}",
31132
+ pathParams: {
31133
+ ean: { description: "The product's EAN/UPC barcode (8\u201314 digits).", pattern: "^\\d{8,14}$" }
31134
+ },
31135
+ pathParamSchemas: { ean: foodBarcodeSchema },
31136
+ scopes: ["meals:read"],
31137
+ tag: "Food",
31138
+ summary: "Look up a food by barcode",
31139
+ description: "A branded product by barcode, from the cache when it is under 30 days old, else from Open Food Facts (then cached). `{ found, barcode, food, remote }`; an unknown barcode is `found: false`, never an error.",
31140
+ status: 200,
31141
+ response: { description: "The product, or found: false." },
31142
+ errors: { 400: true }
31143
+ },
31144
+ get: {
31145
+ method: "get",
31146
+ path: "/foods/{id}",
31147
+ pathParams: { id: "The food's ULID." },
31148
+ pathParamSchemas: { id: ulidSchema },
31149
+ scopes: ["meals:read"],
31150
+ tag: "Food",
31151
+ summary: "Get a food",
31152
+ description: "One food from the nutrition reference.",
31153
+ status: 200,
31154
+ response: { description: "The food." },
31155
+ errors: { 404: true }
31156
+ }
31157
+ });
31158
+
30196
31159
  // ../../packages/schema/src/operations/google-contacts.ts
30197
31160
  var googleContactsOperations = defineOperations({
30198
31161
  list: {
@@ -30271,6 +31234,58 @@ var guestListEventOperations = defineOperations({
30271
31234
  remove: { ...crud2.remove, webhook: "guest.event.deleted" }
30272
31235
  });
30273
31236
 
31237
+ // ../../packages/schema/src/operations/guest-lists.ts
31238
+ var list = { id: "The guest list's ULID." };
31239
+ var guestListOperations = defineOperations({
31240
+ ...crudOperations({
31241
+ segment: "guest-lists",
31242
+ tag: "Guests",
31243
+ noun: "guest list",
31244
+ read: "contacts:read",
31245
+ write: "contacts:write",
31246
+ create: createGuestListInputSchema,
31247
+ update: updateGuestListInputSchema,
31248
+ listQuery: [{ name: "projectId", description: "Only this project's guest list." }],
31249
+ listQuerySchema: guestListsQuerySchema
31250
+ }),
31251
+ overview: {
31252
+ method: "get",
31253
+ path: "/guest-lists/{id}/overview",
31254
+ pathParams: list,
31255
+ scopes: ["contacts:read"],
31256
+ tag: "Guests",
31257
+ summary: "The guest list page",
31258
+ description: "The guests grouped by household (a guest in none is a household of one), the head count overall and per part of the day with each part's cap (a plus-one is one more head, following the guest), the project's key dates (the deadlines), the witnesses and speeches by side, and how many households got their save-the-date and invitation.",
31259
+ status: 200,
31260
+ response: { description: "The overview." },
31261
+ errors: { 404: true }
31262
+ },
31263
+ thankYou: {
31264
+ method: "get",
31265
+ path: "/guest-lists/{id}/thank-you",
31266
+ pathParams: list,
31267
+ scopes: ["contacts:read"],
31268
+ tag: "Guests",
31269
+ summary: "Thank-you cards",
31270
+ description: "The gifts for the event: `gift_received` moments on guests' contacts whose `sourceNodeId` is the list's project (log one with `POST /contact-moments`). `toThank` (oldest first) and `thanked` (newest card first); mark a card sent with `PATCH /contact-moments/{id}` `{ thankedAt }`. The gifts stay on each person's timeline after the day.",
31271
+ status: 200,
31272
+ response: { description: "`{ toThank, thanked }`." },
31273
+ errors: { 404: true }
31274
+ },
31275
+ export: {
31276
+ method: "get",
31277
+ path: "/guest-lists/{id}/exports/{kind}",
31278
+ pathParams: { ...list, kind: { enum: GUEST_EXPORT_KINDS } },
31279
+ scopes: ["contacts:read"],
31280
+ tag: "Guests",
31281
+ summary: "Export for the caterer, the venue, the room, the church, the answers or the printer",
31282
+ description: "`caterer`: a CSV of meals per table (a plus-one is one more; the meal is the dinner, else the reception, else coming at all) with each diet as a count, never a name. `venue`: a CSV of the tables and who sits at each, one row per person. `floor-plan`: a one-page PDF of the tables with their people. `church`: a CSV of who comes to the ceremony, with their side and role. `answers`: a CSV of every RSVP answer, one row per guest (household, guest, RSVP) and a column per question in form order; a household's answer is on each of its guests' rows. `invitations`: the paper invitations' inserts, a PDF of one A6 card per household with a live whole-form RSVP link \u2014 its QR (error correction M, 45 mm) to the list's `siteUrl` + `/i/<token>` (do's own `/rsvp/<token>` without one), the link, its six-character short code, and the list's `sitePassword` only while it is set; mint the links first. `printer`: the same cards as a CSV for the printer or a mail merge \u2014 household, two address lines, country, language, code and URL (a household without an address keeps its row, blank). PDFs are drawn in an embedded Noto Sans, so names print in full Unicode. Served as an attachment.",
31283
+ status: 200,
31284
+ response: { description: "The file." },
31285
+ errors: { 400: true, 404: true }
31286
+ }
31287
+ });
31288
+
30274
31289
  // ../../packages/schema/src/operations/guest-tables.ts
30275
31290
  var guestTableOperations = defineOperations({
30276
31291
  ...crudOperations({
@@ -30613,6 +31628,144 @@ var habitOperations = defineOperations({
30613
31628
  }
30614
31629
  });
30615
31630
 
31631
+ // ../../packages/schema/src/operations/house.ts
31632
+ var READ2 = ["collections:read"];
31633
+ var WRITE2 = ["collections:write"];
31634
+ var WRITE_WITH_TASKS = ["collections:write", "tasks:write"];
31635
+ var receipt = "Returns `{ item, receipt }`: the asset now, and a receipt naming everything made or changed (`itemId`, `created`, `before`, `taskIds`, `entryIds`, `files`). Post the receipt to `/api/v1/house/undo` to take it all back.";
31636
+ var houseOperations = defineOperations({
31637
+ overview: {
31638
+ method: "get",
31639
+ path: "/house",
31640
+ scopes: READ2,
31641
+ tag: "House",
31642
+ summary: "The House home",
31643
+ description: "What's broken (assets in condition `broken` or with an open repair Task), upkeep coming up (open upkeep Tasks with the day each is next due, soonest first), the top-level Places with how many things live in each, and `thingCount` / `upkeepCount` / `toolCount` / `animalCount` (the animals living here, groups counted once).",
31644
+ status: 200,
31645
+ response: { description: "The House home." }
31646
+ },
31647
+ value: {
31648
+ method: "get",
31649
+ path: "/house/value",
31650
+ scopes: READ2,
31651
+ tag: "House",
31652
+ summary: "The value inventory",
31653
+ description: "What's in the house, for the contents insurance and a claim: every owned asset with its Place, `bought` (purchase, else install day), `paid`, `now` (+ `nowSource`), `proof` (`receipt_photo` \xB7 `receipt` \xB7 `photo` \xB7 `none`: a receipt/warranty Document that `covers` it or a non-image File on it; an image File on it or its cover), `excluded` (`insure: false`, say a borrowed thing: listed, not summed) and `visibility`. `totals` sums now and paid; `policy` is the active home insurance Document with an `insuredValue`, or null. Another member's Only me things never show.",
31654
+ status: 200,
31655
+ response: { description: "The value inventory." }
31656
+ },
31657
+ year: {
31658
+ method: "get",
31659
+ path: "/house/upkeep",
31660
+ scopes: READ2,
31661
+ tag: "House",
31662
+ summary: "The upkeep year",
31663
+ description: "Every upkeep routine (a Task tied to an asset as `maintains`) across one year: `routines[].months` holds twelve marks, January first \u2014 `done` (an occurrence done, or a `service` Entry for the Task), `late` (skipped or slipped past), `due` (this month, not done), `planned` (coming, projected from the rule past what's materialized) or null. `needs` lists the Supply parts that fit an asset with a routine in `needsMonth` (next month), with `low` at or below the restock level; `spent` sums this year's costs on asset Entries (repairs + faults, services).",
31664
+ query: [
31665
+ { name: "year", description: "The year (2000\u20132100); default this one.", type: "integer" }
31666
+ ],
31667
+ querySchema: houseUpkeepYearQuerySchema,
31668
+ status: 200,
31669
+ response: { description: "The upkeep year." },
31670
+ errors: { 400: true }
31671
+ },
31672
+ asset: {
31673
+ method: "get",
31674
+ path: "/house/assets/{id}",
31675
+ scopes: READ2,
31676
+ tag: "House",
31677
+ summary: "One asset's House page",
31678
+ description: "The asset as a House page: `item` (the Item with its Entries), `trail` (the Places it sits in, outermost first), `parts` (the Supply items that fit it, kind `fits`), and `linked` (every other node tied to it either way \u2014 a Document that `covers` it, a Task, an Event \u2014 plus the Contacts on its Entries as `serviced_by`), `warrantyUntil` (the latest warranty end among the warranty papers and receipts that cover it, or null; upkeep and repair Tasks are read from `/api/v1/collection/{id}/upkeep`). Only me rows of other members never show.",
31679
+ status: 200,
31680
+ response: { description: "The asset page." },
31681
+ errors: { 404: true }
31682
+ },
31683
+ linkPart: {
31684
+ method: "post",
31685
+ path: "/house/assets/{id}/parts",
31686
+ scopes: WRITE2,
31687
+ tag: "House",
31688
+ summary: "Tie a part to an asset",
31689
+ description: "Tie a Supply item to the asset as a part that fits it (a filter, a door rope, a spare battery). Idempotent. Returns the asset page.",
31690
+ body: linkAssetPartInputSchema,
31691
+ status: 200,
31692
+ response: { description: "The asset page." },
31693
+ errors: { 400: true, 404: true }
31694
+ },
31695
+ unlinkPart: {
31696
+ method: "delete",
31697
+ path: "/house/assets/{id}/parts/{inventoryItemId}",
31698
+ pathParams: { inventoryItemId: "The Supply item's ULID." },
31699
+ scopes: WRITE2,
31700
+ tag: "House",
31701
+ summary: "Untie a part from an asset",
31702
+ description: "The Supply item stays; only the link goes. Returns the asset page.",
31703
+ status: 200,
31704
+ response: { description: "The asset page." },
31705
+ errors: { 404: true }
31706
+ },
31707
+ lend: {
31708
+ method: "post",
31709
+ path: "/house/assets/{id}/lend",
31710
+ scopes: WRITE2,
31711
+ tag: "House",
31712
+ summary: "Lend a thing, or note one borrowed (asset_lend)",
31713
+ description: "Sets the Item's `loan` \u2014 `direction` `lent` (to someone) or `borrowed` (from them), `who`, an optional `contactId`, `since` (default today), `dueBack`, `note` \u2014 and logs a `lent` Entry. Works on any Collection Item. 409 while it's already out.",
31714
+ body: lendAssetInputSchema,
31715
+ status: 200,
31716
+ response: { description: "The Item with its Entries." },
31717
+ errors: { 400: true, 404: true, 409: "Already out; return it first." }
31718
+ },
31719
+ giveBack: {
31720
+ method: "post",
31721
+ path: "/house/assets/{id}/return",
31722
+ scopes: WRITE2,
31723
+ tag: "House",
31724
+ summary: "It's back (asset_return)",
31725
+ description: "Clears the Item's `loan` and logs a `returned` Entry (`on` default today, optional `note`). 409 when it isn't out.",
31726
+ body: returnAssetInputSchema,
31727
+ status: 200,
31728
+ response: { description: "The Item with its Entries." },
31729
+ errors: { 400: true, 404: true, 409: "It isn't out." }
31730
+ },
31731
+ fileAsset: {
31732
+ method: "post",
31733
+ path: "/house/assets",
31734
+ scopes: WRITE_WITH_TASKS,
31735
+ tag: "House",
31736
+ summary: "File an asset (asset_file)",
31737
+ description: `File a thing in the house from what an agent read off a type plate, a receipt or a sentence. Matches one already in the Space (the same \`facet.serial\`, else the same \`facet.model\` in the same Place; \`match: false\` skips it) and fills in what's new, else adds an \`asset\` (status \`have\`). \`fileIds\` (uploaded first via \`/api/v1/files\`) move onto the asset; \`upkeep\` sets one recurring routine; \`fault\` logs a \`fault\` Entry and puts a repair Task on the fix-it list. ${receipt}`,
31738
+ body: fileAssetInputSchema,
31739
+ status: 201,
31740
+ response: { description: "`{ item, receipt }`." },
31741
+ errors: { 400: true }
31742
+ },
31743
+ setUpkeep: {
31744
+ method: "post",
31745
+ path: "/house/assets/{id}/upkeep",
31746
+ scopes: WRITE_WITH_TASKS,
31747
+ tag: "House",
31748
+ summary: "Set an asset's upkeep (asset_upkeep_set)",
31749
+ description: `Add one upkeep routine to the asset: a Task in its Place that repeats \`every\` days, weeks or months after the last time it was done (flexible), or every N years on a fixed day (\`unit: "year"\`, from \`startOn\`), tied to the asset as \`maintains\`. ${receipt}`,
31750
+ body: setAssetUpkeepInputSchema,
31751
+ status: 201,
31752
+ response: { description: "`{ item, receipt }`." },
31753
+ errors: { 400: true, 404: true }
31754
+ },
31755
+ undo: {
31756
+ method: "post",
31757
+ path: "/house/undo",
31758
+ scopes: WRITE_WITH_TASKS,
31759
+ tag: "House",
31760
+ summary: "Undo a House write",
31761
+ description: "Take a House write back from its receipt: its Tasks go to Trash, its Entries are removed, moved Files go back where they were, and an added asset goes to Trash (a matched one gets its facet, Place and status back). Steps already undone are skipped. Returns `{ itemId, undone: true }`.",
31762
+ body: undoHouseWriteInputSchema,
31763
+ status: 200,
31764
+ response: { description: "`{ itemId, undone }`." },
31765
+ errors: { 400: true }
31766
+ }
31767
+ });
31768
+
30616
31769
  // ../../packages/schema/src/operations/households.ts
30617
31770
  var householdOperations = defineOperations({
30618
31771
  ...crudOperations({
@@ -30644,7 +31797,7 @@ var inventoryItemOperations = defineOperations({
30644
31797
  });
30645
31798
 
30646
31799
  // ../../packages/schema/src/operations/mail-campaigns.ts
30647
- var pathParams = { id: "The campaign's ULID." };
31800
+ var pathParams2 = { id: "The campaign's ULID." };
30648
31801
  var mailCampaignOperations = defineOperations({
30649
31802
  list: {
30650
31803
  method: "get",
@@ -30674,7 +31827,7 @@ var mailCampaignOperations = defineOperations({
30674
31827
  get: {
30675
31828
  method: "get",
30676
31829
  path: "/mail-campaigns/{id}",
30677
- pathParams,
31830
+ pathParams: pathParams2,
30678
31831
  scopes: ["contacts:read"],
30679
31832
  tag: "Guests",
30680
31833
  summary: "Get a mail campaign",
@@ -30686,7 +31839,7 @@ var mailCampaignOperations = defineOperations({
30686
31839
  update: {
30687
31840
  method: "patch",
30688
31841
  path: "/mail-campaigns/{id}",
30689
- pathParams,
31842
+ pathParams: pathParams2,
30690
31843
  scopes: ["contacts:write"],
30691
31844
  tag: "Guests",
30692
31845
  summary: "Edit a draft",
@@ -30699,7 +31852,7 @@ var mailCampaignOperations = defineOperations({
30699
31852
  preview: {
30700
31853
  method: "get",
30701
31854
  path: "/mail-campaigns/{id}/preview",
30702
- pathParams,
31855
+ pathParams: pathParams2,
30703
31856
  scopes: ["contacts:read"],
30704
31857
  tag: "Guests",
30705
31858
  summary: "Preview a mail campaign",
@@ -30725,6 +31878,72 @@ var mealPlanOperations = defineOperations({
30725
31878
  })
30726
31879
  });
30727
31880
 
31881
+ // ../../packages/schema/src/operations/metrics.ts
31882
+ var NO_MEMBER2 = "The key acts for no member.";
31883
+ var metricOperations = defineOperations({
31884
+ list: {
31885
+ method: "get",
31886
+ path: "/metrics",
31887
+ scopes: ["habits:read"],
31888
+ tag: "Habits",
31889
+ summary: "List metrics",
31890
+ description: "Weight (kg), sleep (h), mood (1\u20135), the day rating, energy, bedtime, resting heart rate (bpm) and HRV (ms) as trend lines: one point per day in the member's timezone (that day's last value, hand-logged or from Google Fit or Google Health), oldest first, plus the latest. No target, no streak. The key must act for a member. Resting heart rate and HRV are health data: without `health:read` held by name their lines come back empty.",
31891
+ query: [
31892
+ { name: "days", description: "How far back, 1\u2013730 days (default 90).", type: "integer" }
31893
+ ],
31894
+ querySchema: metricsQuerySchema,
31895
+ status: 200,
31896
+ response: { description: "Every Metric with its points." },
31897
+ errors: { 403: NO_MEMBER2 }
31898
+ },
31899
+ sessions: {
31900
+ method: "get",
31901
+ path: "/metrics/sessions",
31902
+ scopes: ["habits:read"],
31903
+ tag: "Habits",
31904
+ summary: "List workout sessions",
31905
+ description: "The member's workouts from their connections (Google Fit workouts, Strava activities), newest first: when, the sport in words, its name and minutes. Read only; they come in through sync.",
31906
+ query: [
31907
+ { name: "days", description: "How far back, 1\u2013365 days (default 14).", type: "integer" }
31908
+ ],
31909
+ querySchema: sessionsQuerySchema,
31910
+ status: 200,
31911
+ response: { description: "The sessions." },
31912
+ errors: { 403: NO_MEMBER2 }
31913
+ },
31914
+ import: {
31915
+ method: "post",
31916
+ path: "/metrics/import",
31917
+ scopes: ["habits:write"],
31918
+ tag: "Habits",
31919
+ summary: "Import metrics from Health Connect",
31920
+ description: "A batch the Android app read off the phone's Health Connect: `weight` (kg), `sleep` (h) and `bedtime` (minutes after 18:00), at most 500, each with its Health Connect `recordId`. Each lands once on the member's line with source `health_connect`; a record already imported, out of its Metric's range or in the future is skipped, not refused. Returns `{ imported, skipped }`.",
31921
+ body: importMetricsInputSchema,
31922
+ status: 201,
31923
+ response: { description: "How many points landed and how many were skipped." },
31924
+ errors: { 400: true, 403: NO_MEMBER2 }
31925
+ },
31926
+ log: {
31927
+ method: "post",
31928
+ path: "/metrics/{key}",
31929
+ pathParams: {
31930
+ key: {
31931
+ description: "`weight` (kg), `sleep` (h), `mood` (1\u20135), `rating` (the day, 1\u201310), `energy` (1\u201310), `bedtime` (minutes after 18:00: 01:00 = 420), `resting_hr` (bpm) or `hrv` (ms). The last two are health data: logging one needs `health:write` too.",
31932
+ enum: metricKeySchema.options
31933
+ }
31934
+ },
31935
+ pathParamSchemas: { key: metricKeySchema },
31936
+ scopes: ["habits:write"],
31937
+ tag: "Habits",
31938
+ summary: "Log a metric",
31939
+ description: 'Log one value ("weighed 72.4"; "slept 7.5 hours"; "mood 4"; "today was a 7" \u2192 `rating`; "bed 01:00" \u2192 `bedtime` 420); `occurredAt` back-dates it, `source` says `manual` (default) or `device`. A bedtime lands on the night\'s wake day. Logging again the same day replaces that day\'s point. 400 outside the Metric\'s range. Returns the Metric\'s refreshed line.',
31940
+ body: logMetricInputSchema,
31941
+ status: 201,
31942
+ response: { description: "The Metric." },
31943
+ errors: { 400: true, 403: NO_MEMBER2 }
31944
+ }
31945
+ });
31946
+
30728
31947
  // ../../packages/schema/src/operations/occurrences.ts
30729
31948
  var occurrenceOperations = defineOperations({
30730
31949
  list: {
@@ -30882,6 +32101,208 @@ var planOperations = defineOperations({
30882
32101
  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
32102
  });
30884
32103
 
32104
+ // ../../packages/schema/src/operations/presets.ts
32105
+ var presetOperations = defineOperations({
32106
+ list: {
32107
+ method: "get",
32108
+ path: "/presets",
32109
+ scopes: ["presets:read"],
32110
+ tag: "Presets",
32111
+ summary: "List presets",
32112
+ description: "The preset gallery catalog (metadata + recipe).",
32113
+ status: 200,
32114
+ response: { description: "The preset catalog." }
32115
+ },
32116
+ install: {
32117
+ method: "post",
32118
+ path: "/presets/{key}/install",
32119
+ pathParams: { key: "The preset's catalog key." },
32120
+ scopes: ["presets:install"],
32121
+ tag: "Presets",
32122
+ summary: "Install a preset",
32123
+ description: "Install a preset's bundle into the key's Space; returns the created node ids.",
32124
+ body: installPresetBodySchema,
32125
+ bodyOptional: true,
32126
+ status: 201,
32127
+ response: { description: "The install result (created node ids)." },
32128
+ errors: { 404: "Unknown preset key." }
32129
+ }
32130
+ });
32131
+
32132
+ // ../../packages/schema/src/operations/progress.ts
32133
+ var NO_MEMBER3 = "The key acts for no member.";
32134
+ var question = {
32135
+ question: { description: "The question's key.", enum: insightsQuestionSchema.options }
32136
+ };
32137
+ var progressOperations = defineOperations({
32138
+ get: {
32139
+ method: "get",
32140
+ path: "/progress",
32141
+ scopes: ["tasks:read", "habits:read"],
32142
+ tag: "Reads",
32143
+ summary: "Progress",
32144
+ description: `What got done day by day over the last 7, 30 or 90 days, in the key's member's saved timezone ("how was my week?"): today's done vs planned, per day tasks done (mine vs agents), habits done vs due, time blocks and day completion, the week and month against the period before, best day, average per day, on time vs late, tasks per Area and each habit's rate and days. Cancelled tasks never count; an empty day's completion is null.`,
32145
+ query: [
32146
+ {
32147
+ name: "days",
32148
+ description: "The range: `7` (default), `30` or `90` days ending today.",
32149
+ type: "integer",
32150
+ enum: [7, 30, 90]
32151
+ }
32152
+ ],
32153
+ querySchema: progressQuerySchema,
32154
+ status: 200,
32155
+ response: { description: "The Progress read.", view: "ProgressResponse" },
32156
+ errors: { 400: true },
32157
+ operationId: "get_progress"
32158
+ },
32159
+ day: {
32160
+ method: "get",
32161
+ path: "/progress/days/{date}",
32162
+ pathParams: { date: { description: "The day, `YYYY-MM-DD`.", format: "date" } },
32163
+ pathParamSchemas: { date: progressDateSchema },
32164
+ scopes: ["tasks:read", "habits:read"],
32165
+ tag: "Reads",
32166
+ summary: "Progress for a day",
32167
+ description: "One day of Progress (the member's civil day): what was done \u2014 tasks, habits and time blocks, each with its owner (me or an agent) and whether it was late \u2014 and what slipped (planned that day, not finished by its end).",
32168
+ status: 200,
32169
+ response: { description: "The day's detail.", view: "ProgressDayDetail" },
32170
+ errors: { 400: true },
32171
+ operationId: "get_progress_day"
32172
+ },
32173
+ habits: {
32174
+ method: "get",
32175
+ path: "/progress/habits",
32176
+ scopes: ["habits:read"],
32177
+ tag: "Reads",
32178
+ summary: "Habits on Progress",
32179
+ description: "How the habits are going (#871), in the member's timezone: this week's grid (Monday first; each day done, skipped, empty or ahead, never missed) and the week so far, each habit's strength as a word (steady, building, settling-in) from a Loop-style smoothed score, the insights worker's patterns each with a confidence label, and when check-ins land by hour over the last 90 days.",
32180
+ status: 200,
32181
+ response: { description: "The habit insights.", view: "ProgressHabitsResponse" },
32182
+ operationId: "get_progress_habits"
32183
+ },
32184
+ commits: {
32185
+ method: "get",
32186
+ path: "/progress/commits",
32187
+ scopes: ["habits:read"],
32188
+ tag: "Reads",
32189
+ summary: "Your GitHub commits, yours and your agents'",
32190
+ description: "The key's member's commits over the last `days` (7 by default, or 30), from their GitHub connection: `byYou` (the ones they made), `byAgents` (made on their account by an agent: a bot, an agent co-author, a \"Generated with\" footer) with `agents` by name, `unsorted` (synced before commits said who made them), and `perDay` in their zone. Only theirs count toward Insights' evening activity and a GitHub-sourced habit.",
32191
+ query: [{ name: "days", description: "", type: "integer", enum: [7, 30] }],
32192
+ querySchema: githubCommitsQuerySchema,
32193
+ status: 200,
32194
+ response: { description: "The split." },
32195
+ errors: { 400: true, 403: NO_MEMBER3 }
32196
+ },
32197
+ questions: {
32198
+ method: "get",
32199
+ path: "/progress/questions",
32200
+ scopes: ["habits:read"],
32201
+ tag: "Reads",
32202
+ summary: "Insights",
32203
+ description: "Insights' answers to its fixed questions, for the key's member only: late nights vs the next day's rating, the morning routine vs the day, good runs, bedtime this week vs usual, what the best days have in common, reading this week vs usual. Each is `shown`, `no_difference` or `collecting` (with `neededDays`); recomputed weekly (Monday 04:00 in the member's zone). Quote `sentence` and `footer` verbatim; a pattern goes together, never a cause. Mention only a Medium or High one.",
32204
+ status: 200,
32205
+ response: { description: "This week's answers.", view: "InsightsAnswersResponse" },
32206
+ errors: { 403: NO_MEMBER3 },
32207
+ operationId: "get_insights"
32208
+ },
32209
+ dismiss: {
32210
+ method: "post",
32211
+ path: "/progress/questions/{question}/dismiss",
32212
+ pathParams: question,
32213
+ pathParamSchemas: { question: insightsQuestionSchema },
32214
+ scopes: ["habits:write"],
32215
+ tag: "Reads",
32216
+ summary: "Set an insight aside",
32217
+ description: 'Hide one answer as "not useful"; it stays hidden through the weekly recompute.',
32218
+ status: 200,
32219
+ response: { description: "The answers.", view: "InsightsAnswersResponse" },
32220
+ errors: { 400: true },
32221
+ operationId: "dismiss_insight"
32222
+ },
32223
+ restore: {
32224
+ method: "post",
32225
+ path: "/progress/questions/{question}/restore",
32226
+ pathParams: question,
32227
+ pathParamSchemas: { question: insightsQuestionSchema },
32228
+ scopes: ["habits:write"],
32229
+ tag: "Reads",
32230
+ summary: "Bring an insight back",
32231
+ description: "Show a set-aside answer again.",
32232
+ status: 200,
32233
+ response: { description: "The answers.", view: "InsightsAnswersResponse" },
32234
+ errors: { 400: true },
32235
+ operationId: "restore_insight"
32236
+ },
32237
+ activity: {
32238
+ method: "post",
32239
+ path: "/progress/activity",
32240
+ scopes: ["habits:write"],
32241
+ tag: "Reads",
32242
+ summary: "Record member activity",
32243
+ description: "The member was active somewhere do can't see \u2014 a message to the agent on another channel. The late-night question reads the last activity of each evening (00:00\u201304:00 counts for the evening before). `occurredAt` defaults to now; never in the future.",
32244
+ body: memberActivityInputSchema,
32245
+ status: 201,
32246
+ response: { description: "Recorded." },
32247
+ errors: { 400: true, 403: NO_MEMBER3 },
32248
+ operationId: "record_member_activity"
32249
+ }
32250
+ });
32251
+
32252
+ // ../../packages/schema/src/operations/recipes.ts
32253
+ var recipe = { id: "The recipe's ULID." };
32254
+ var recipeOperations = defineOperations({
32255
+ ...crudOperations({
32256
+ segment: "recipes",
32257
+ tag: "Meals",
32258
+ noun: "recipe",
32259
+ read: "meals:read",
32260
+ write: "meals:write",
32261
+ create: createRecipeInputSchema,
32262
+ update: updateRecipeInputSchema,
32263
+ paginated: true,
32264
+ listQuerySchema: paginationQuerySchema
32265
+ }),
32266
+ usedIn: {
32267
+ method: "get",
32268
+ path: "/recipes/{id}/used-in",
32269
+ pathParams: recipe,
32270
+ scopes: ["meals:read"],
32271
+ tag: "Meals",
32272
+ summary: "Where a recipe was used",
32273
+ description: "The meals the member ate it in (newest first, with what else was on the plate and how many servings) and the weekmenu days it's planned for from a week back on (soonest first), at most five of each. Meals are health data: a key without `health:read` gets the plans and no meals.",
32274
+ status: 200,
32275
+ response: { description: "The recipe's meals and plans." },
32276
+ errors: { 404: true }
32277
+ },
32278
+ nutrition: {
32279
+ method: "post",
32280
+ path: "/recipes/{id}/nutrition",
32281
+ pathParams: recipe,
32282
+ scopes: ["meals:write"],
32283
+ tag: "Meals",
32284
+ summary: "Recompute a recipe's nutrition",
32285
+ description: "Recompute the recipe's nutrition from its ingredients (each ingredient's product \u2192 its own values or its food's, \xD7 grams): per pot, per serving, the share of the mass matched (`coverage`) and the lines with no match or amount. Stored on the recipe; also done on every recipe write.",
32286
+ status: 200,
32287
+ response: { description: "The recipe's nutrition." },
32288
+ errors: { 404: true }
32289
+ },
32290
+ match: {
32291
+ method: "put",
32292
+ path: "/recipes/{id}/ingredients/{ingredientId}/match",
32293
+ pathParams: { ...recipe, ingredientId: "The ingredient's id." },
32294
+ scopes: ["meals:write"],
32295
+ tag: "Meals",
32296
+ summary: "Confirm or swap an ingredient's match",
32297
+ description: "Body `{ foodId }` (a food from `/api/v1/foods`): the ingredient's product becomes that reference food (a product is made for it when it has none) and the recipe's nutrition is worked out again. Returns the recipe.",
32298
+ body: recipeIngredientMatchInputSchema,
32299
+ status: 200,
32300
+ response: { description: "The recipe." },
32301
+ errors: { 404: true },
32302
+ webhook: "recipe.updated"
32303
+ }
32304
+ });
32305
+
30885
32306
  // ../../packages/schema/src/operations/routines.ts
30886
32307
  var routineOperations = defineOperations({
30887
32308
  getRun: {
@@ -31098,6 +32519,79 @@ var rsvpAnswerOperations = defineOperations({
31098
32519
  }
31099
32520
  });
31100
32521
 
32522
+ // ../../packages/schema/src/operations/rsvp-links.ts
32523
+ var link = { id: "The link's ULID." };
32524
+ var rsvpLinkOperations = defineOperations({
32525
+ list: {
32526
+ method: "get",
32527
+ path: "/rsvp-links",
32528
+ scopes: ["contacts:read"],
32529
+ tag: "Guests",
32530
+ summary: "List a guest list's RSVP links",
32531
+ description: "One per household, live or not, never with the token. `revealable` says a live link's token is kept, so it can be shown again.",
32532
+ query: [{ name: "guestListId", description: "The guest list (required)." }],
32533
+ querySchema: rsvpLinksQuerySchema,
32534
+ status: 200,
32535
+ response: { description: "The links." },
32536
+ errors: { 400: true }
32537
+ },
32538
+ mint: {
32539
+ method: "post",
32540
+ path: "/rsvp-links",
32541
+ scopes: ["contacts:write"],
32542
+ tag: "Guests",
32543
+ summary: "Mint RSVP links",
32544
+ description: "One no-login link per household on the list: the named `householdIds`, or every household without a live link of the `mode`. `mode` is `full` (the whole form, the default) or `address` (the save-the-date's: the guests' names only, and it takes only the household's postal address and language). A guest in no household gets a household of one first; a household's older live link of that mode is revoked, the other mode's stays. The token (and the form's `path`, `/rsvp/<token>`) is in this response; it is looked up by its hash and kept sealed, so `GET /rsvp-links/{id}/token` shows the same link again. Webhooks never carry it. A link stops at `expiresAt`, by default the end of the RSVP deadline day (120 days without one; at most 400). The form behind it shows that household's guests (their names, parts, answers, diet, plus-one) and its postal address, nothing else. Fires `rsvp.link.created`.",
32545
+ body: createRsvpLinksInputSchema,
32546
+ status: 201,
32547
+ response: { description: "The minted links, with their tokens." },
32548
+ errors: { 400: true, 404: true },
32549
+ webhook: "rsvp.link.created"
32550
+ },
32551
+ reveal: {
32552
+ method: "get",
32553
+ path: "/rsvp-links/{id}/token",
32554
+ pathParams: link,
32555
+ scopes: ["contacts:write"],
32556
+ tag: "Guests",
32557
+ summary: "Show an RSVP link again",
32558
+ description: "The same token (and `path`) the live link was minted with, so the mail and the paper QR carry one link, and a whole-form link's `shortCode` (six characters, printed on the paper invitation and typed at the site's `/code`, which resolves it through `POST /public/rsvp/code`; an address link has none). A revoked or expired link is 410; one minted before tokens were kept is 409: replace it.",
32559
+ status: 200,
32560
+ response: { description: "The link, with its token." },
32561
+ errors: {
32562
+ 404: true,
32563
+ 409: "Minted before tokens were kept; replace it.",
32564
+ 410: "Revoked or expired."
32565
+ }
32566
+ },
32567
+ replace: {
32568
+ method: "post",
32569
+ path: "/rsvp-links/{id}/replace",
32570
+ pathParams: link,
32571
+ scopes: ["contacts:write"],
32572
+ tag: "Guests",
32573
+ summary: "Replace an RSVP link",
32574
+ description: "A new token for the link's household, and the old link stops working (an old paper QR with it). The explicit way to re-mint. Fires `rsvp.link.created`.",
32575
+ status: 200,
32576
+ response: { description: "The new link, with its token." },
32577
+ errors: { 404: true },
32578
+ webhook: "rsvp.link.created"
32579
+ },
32580
+ revoke: {
32581
+ method: "post",
32582
+ path: "/rsvp-links/{id}/revoke",
32583
+ pathParams: link,
32584
+ scopes: ["contacts:write"],
32585
+ tag: "Guests",
32586
+ summary: "Revoke an RSVP link",
32587
+ description: "The link stops working (the form says so). Idempotent. Fires `rsvp.link.revoked`.",
32588
+ status: 200,
32589
+ response: { description: "The link." },
32590
+ errors: { 404: true },
32591
+ webhook: "rsvp.link.revoked"
32592
+ }
32593
+ });
32594
+
31101
32595
  // ../../packages/schema/src/operations/rsvp-questions.ts
31102
32596
  var rsvpQuestionOperations = defineOperations({
31103
32597
  ...crudOperations({
@@ -31127,6 +32621,75 @@ var rsvpQuestionOperations = defineOperations({
31127
32621
  }
31128
32622
  });
31129
32623
 
32624
+ // ../../packages/schema/src/operations/seating-proposals.ts
32625
+ var proposal = { id: "The proposal's ULID." };
32626
+ var seatingProposalOperations = defineOperations({
32627
+ list: {
32628
+ method: "get",
32629
+ path: "/seating-proposals",
32630
+ scopes: ["contacts:read"],
32631
+ tag: "Guests",
32632
+ summary: "List seating proposals",
32633
+ description: "A guest list's proposed plans, newest first: proposed, applied or dismissed.",
32634
+ query: [{ name: "guestListId", description: "The guest list (required)." }],
32635
+ querySchema: seatingProposalsQuerySchema,
32636
+ status: 200,
32637
+ response: { description: "The proposals." },
32638
+ errors: { 400: true }
32639
+ },
32640
+ propose: {
32641
+ method: "post",
32642
+ path: "/seating-proposals",
32643
+ scopes: ["contacts:write"],
32644
+ tag: "Guests",
32645
+ summary: "Propose a seating plan",
32646
+ description: "Store a plan (the agent's first one: households together, circles near each other): tables with a name, seats and the guest ids at each. Every guest must be on the list and seated once; a table's heads (a plus-one counts) must fit its seats. Nothing moves until a member applies it. Through an API key it is `proposedBy: agent`. Fires `seating.proposal.created`.",
32647
+ body: createSeatingProposalInputSchema,
32648
+ status: 201,
32649
+ response: { description: "The stored proposal." },
32650
+ errors: { 400: true, 404: true },
32651
+ webhook: "seating.proposal.created"
32652
+ },
32653
+ get: {
32654
+ method: "get",
32655
+ path: "/seating-proposals/{id}",
32656
+ pathParams: proposal,
32657
+ scopes: ["contacts:read"],
32658
+ tag: "Guests",
32659
+ summary: "Get a seating proposal",
32660
+ description: "",
32661
+ status: 200,
32662
+ response: { description: "The proposal." },
32663
+ errors: { 404: true }
32664
+ },
32665
+ apply: {
32666
+ method: "post",
32667
+ path: "/seating-proposals/{id}/apply",
32668
+ pathParams: proposal,
32669
+ scopes: ["contacts:write"],
32670
+ tag: "Guests",
32671
+ summary: "Apply a seating plan",
32672
+ description: "The list's tables become the plan's (the old ones go) and each guest sits where it says; guests it doesn't name are unseated. Only a proposed plan applies (409 otherwise). Fires `seating.proposal.applied`.",
32673
+ status: 200,
32674
+ response: { description: "The applied proposal." },
32675
+ errors: { 404: true, 409: "Already applied or dismissed." },
32676
+ webhook: "seating.proposal.applied"
32677
+ },
32678
+ dismiss: {
32679
+ method: "post",
32680
+ path: "/seating-proposals/{id}/dismiss",
32681
+ pathParams: proposal,
32682
+ scopes: ["contacts:write"],
32683
+ tag: "Guests",
32684
+ summary: "Dismiss a seating plan",
32685
+ description: "Set it aside without applying it. Fires `seating.proposal.dismissed`.",
32686
+ status: 200,
32687
+ response: { description: "The dismissed proposal." },
32688
+ errors: { 404: true, 409: "Already applied or dismissed." },
32689
+ webhook: "seating.proposal.dismissed"
32690
+ }
32691
+ });
32692
+
31130
32693
  // ../../packages/schema/src/operations/sensors.ts
31131
32694
  var sensorOperations = defineOperations({
31132
32695
  status: {
@@ -31276,14 +32839,14 @@ var shoppingListOperations = defineOperations({
31276
32839
  });
31277
32840
 
31278
32841
  // ../../packages/schema/src/operations/tap-tags.ts
31279
- var READ = ["habits:read", "routines:read"];
31280
- var WRITE = ["habits:write", "routines:write"];
31281
- var pathParams2 = { id: "The tag's id: the ULID the sticker's link carries (`/t/{id}`)." };
32842
+ var READ3 = ["habits:read", "routines:read"];
32843
+ var WRITE3 = ["habits:write", "routines:write"];
32844
+ var pathParams3 = { id: "The tag's id: the ULID the sticker's link carries (`/t/{id}`)." };
31282
32845
  var tapTagOperations = defineOperations({
31283
32846
  list: {
31284
32847
  method: "get",
31285
32848
  path: "/tap-tags",
31286
- scopes: READ,
32849
+ scopes: READ3,
31287
32850
  tag: "Habits",
31288
32851
  summary: "List tap tags",
31289
32852
  description: "The Space's NFC stickers: where each is (`label`), what a tap does (`habit`: check it in; `routine`: mark today's run done), its target's title and its link's `path`.",
@@ -31294,7 +32857,7 @@ var tapTagOperations = defineOperations({
31294
32857
  create: {
31295
32858
  method: "post",
31296
32859
  path: "/tap-tags",
31297
- scopes: WRITE,
32860
+ scopes: WRITE3,
31298
32861
  tag: "Habits",
31299
32862
  summary: "Make a tap tag",
31300
32863
  description: "Bind a new sticker id (a client-minted ULID) to a live habit or routine of the Space, of the tag's kind. Write `https://<your do>/t/{id}` onto an NFC sticker.",
@@ -31306,8 +32869,8 @@ var tapTagOperations = defineOperations({
31306
32869
  get: {
31307
32870
  method: "get",
31308
32871
  path: "/tap-tags/{id}",
31309
- pathParams: pathParams2,
31310
- scopes: READ,
32872
+ pathParams: pathParams3,
32873
+ scopes: READ3,
31311
32874
  tag: "Habits",
31312
32875
  summary: "Get a tap tag",
31313
32876
  description: "One tag.",
@@ -31318,8 +32881,8 @@ var tapTagOperations = defineOperations({
31318
32881
  update: {
31319
32882
  method: "patch",
31320
32883
  path: "/tap-tags/{id}",
31321
- pathParams: pathParams2,
31322
- scopes: WRITE,
32884
+ pathParams: pathParams3,
32885
+ scopes: WRITE3,
31323
32886
  tag: "Habits",
31324
32887
  summary: "Rename or rebind a tap tag",
31325
32888
  description: "Change its `label`, or bind it to another habit or routine (`kind` and `targetId` together): the sticker stays as it is.",
@@ -31331,8 +32894,8 @@ var tapTagOperations = defineOperations({
31331
32894
  remove: {
31332
32895
  method: "delete",
31333
32896
  path: "/tap-tags/{id}",
31334
- pathParams: pathParams2,
31335
- scopes: WRITE,
32897
+ pathParams: pathParams3,
32898
+ scopes: WRITE3,
31336
32899
  tag: "Habits",
31337
32900
  summary: "Delete a tap tag",
31338
32901
  description: "The sticker's link stops doing anything.",
@@ -31343,8 +32906,8 @@ var tapTagOperations = defineOperations({
31343
32906
  tap: {
31344
32907
  method: "post",
31345
32908
  path: "/tap-tags/{id}/tap",
31346
- pathParams: pathParams2,
31347
- scopes: WRITE,
32909
+ pathParams: pathParams3,
32910
+ scopes: WRITE3,
31348
32911
  tag: "Habits",
31349
32912
  summary: "Tap a tap tag",
31350
32913
  description: "What the sticker does, as the key's member: check the habit in, or take today's run of the routine. Returns the receipt (`line`) and its `undo` (the check-in's `habitId` + `entryId`, or the run's `runId` + `stepRunIds` for `POST /routines/runs/{runId}/undo`). The same member's second tap within 30 s does nothing (`outcome: \"already\"`). The key must act for a member.",
@@ -31543,6 +33106,71 @@ var taskOperations = defineOperations({
31543
33106
  }
31544
33107
  });
31545
33108
 
33109
+ // ../../packages/schema/src/operations/time-blocks.ts
33110
+ var INSTANT = "an ISO 8601 datetime with offset, or a bare `YYYY-MM-DD` (UTC midnight)";
33111
+ var timeBlockOperations = defineOperations({
33112
+ ...crudOperations({
33113
+ segment: "time-blocks",
33114
+ tag: "Calendar",
33115
+ noun: "time block",
33116
+ read: "calendar:read",
33117
+ write: "calendar:write",
33118
+ create: createTimeBlockInputSchema,
33119
+ update: updateTimeBlockInputSchema,
33120
+ listQuery: [
33121
+ {
33122
+ name: "from",
33123
+ description: `Start of the window (inclusive) \u2014 ${INSTANT}. With \`to\`; both default to the next day.`,
33124
+ format: "date-time"
33125
+ },
33126
+ {
33127
+ name: "to",
33128
+ description: `End of the window (exclusive, at most 62 days after \`from\`) \u2014 ${INSTANT}.`,
33129
+ format: "date-time"
33130
+ },
33131
+ { name: "kind", description: "Only blocks of this kind: `task`, `focus` or `life`." }
33132
+ ],
33133
+ listQuerySchema: timeBlockRangeQuerySchema
33134
+ }),
33135
+ record: {
33136
+ method: "get",
33137
+ path: "/time-blocks/{id}/record",
33138
+ scopes: ["calendar:read"],
33139
+ tag: "Calendar",
33140
+ summary: "A life block's record",
33141
+ description: "One of the key's member's life blocks planned against what happened: the block (its frozen plan and its record), the nested Routine's steps as they went (`done` with a time, `skipped`, `open`), the evidence the record rests on (`check_in`, `you`, `agent`, or only the `plan`), a confidence word (`high` \xB7 `medium` \xB7 `low`, never a number), `confirmedAt` when the member said it looks right (an Agent's later change reopens it), `undo` (the cursor `POST /agent/undo` takes back the Agent's latest changes with) and `google` (the event's title on the \"do\" calendar). 400 for a task or focus block.",
33142
+ status: 200,
33143
+ response: { description: "The block's record." },
33144
+ errors: { 400: true, 404: true }
33145
+ },
33146
+ confirm: {
33147
+ method: "post",
33148
+ path: "/time-blocks/{id}/confirm",
33149
+ scopes: ["calendar:write"],
33150
+ tag: "Calendar",
33151
+ summary: "Say a life block's record looks right",
33152
+ description: "Stamps the member's word on a begun life block's record and returns the record. 400 for a block still ahead or a work block.",
33153
+ status: 200,
33154
+ response: { description: "The block's record." },
33155
+ errors: { 400: true, 404: true }
33156
+ },
33157
+ planVsRecord: {
33158
+ method: "get",
33159
+ path: "/time-blocks/plan-vs-record",
33160
+ scopes: ["calendar:read"],
33161
+ tag: "Calendar",
33162
+ summary: "Plan vs what happened",
33163
+ description: 'How the key\'s member\'s life blocks went against their plan over the last `days` (7, 30 or 90; default 30): `week`, last week (Monday to Sunday) as strips (each block\'s planned and recorded minutes into the day, and the day in words, "Held mostly", "Evening went elsewhere"); `hours`, where that week\'s time went per group of kinds, recorded against planned; `moves`, which blocks move over the range\'s weekdays and how, in words ("Lunch \xB7 60\u201375 min \xB7 Runs about twice the 30 planned \xB7 17 of 22"), counting a record once it\'s confirmed or 48 h old, each with `latestId` (its latest block); `evening`, the weekday evenings\' tasks (Tasks planned from 17:00) held, in words with the days work ran past 17:30, and a confidence word, or null; and `suggestion`, one template length to change ("plan it like it is", apply with `PATCH /api/v1/block-templates/{id}` `{ minutes }`) or null. Moving is information, not a miss: no score, no streak.',
33164
+ query: [
33165
+ { name: "days", description: "7, 30 (default) or 90.", type: "integer", enum: [7, 30, 90] }
33166
+ ],
33167
+ querySchema: planVsRecordQuerySchema,
33168
+ status: 200,
33169
+ response: { description: "Plan vs what happened." },
33170
+ errors: { 400: true }
33171
+ }
33172
+ });
33173
+
31546
33174
  // ../../packages/schema/src/operations/today.ts
31547
33175
  var todayOperations = defineOperations({
31548
33176
  fromEarlier: {
@@ -31892,7 +33520,7 @@ function parseCapture2(input, now) {
31892
33520
 
31893
33521
  // src/meta.ts
31894
33522
  var CLI_NAME = "do";
31895
- var VERSION = "1.306.0";
33523
+ var VERSION = "1.306.1";
31896
33524
  var API_PREFIX = "/api/v1";
31897
33525
  var DEFAULT_API_URL = "http://localhost:3001";
31898
33526
 
@@ -32121,7 +33749,7 @@ function resolveConfig(io, flags) {
32121
33749
  }
32122
33750
 
32123
33751
  // src/contacts-commands.ts
32124
- var WRITE2 = ["contacts:write"];
33752
+ var WRITE4 = ["contacts:write"];
32125
33753
  var FIELD_FLAGS = [
32126
33754
  { name: "name", type: "string", description: "The display name (or pass it as the argument)." },
32127
33755
  { name: "kind", type: "string", description: "person (the default) or organization." },
@@ -32164,7 +33792,7 @@ var CONTACT_COMMAND_SPECS = [
32164
33792
  summary: "Add a contact: a person or an organisation, with their details.",
32165
33793
  args: [{ name: "name", required: false, description: "The display name." }],
32166
33794
  flags: FIELD_FLAGS,
32167
- scopes: WRITE2,
33795
+ scopes: WRITE4,
32168
33796
  returns: "ContactView",
32169
33797
  notes: 'Flags build a CreateContactInput; --data adds any other field (a flag wins). The id is minted when omitted. e.g. `do contacts create "Sam Peters" --email sam@example.com --birthday 1990-04-12 --circles <id>`.'
32170
33798
  },
@@ -32173,14 +33801,14 @@ var CONTACT_COMMAND_SPECS = [
32173
33801
  summary: "Change a contact's details.",
32174
33802
  args: [{ name: "id", required: true, description: "The contact's id." }],
32175
33803
  flags: FIELD_FLAGS,
32176
- scopes: WRITE2,
33804
+ scopes: WRITE4,
32177
33805
  returns: "ContactView",
32178
33806
  notes: "Only the flags you pass change. `none` clears a field (--email none, --birthday none). --birthday keeps the contact's other dates; --roles, --circles and --tags replace the list (a role it already has keeps its details)."
32179
33807
  }
32180
33808
  ];
32181
33809
  var MERGING_FLAGS = ["birthday", "no-year", "roles"];
32182
33810
  var NULLABLE = ["email", "phone", "company", "website", "notes"];
32183
- function list(value) {
33811
+ function list2(value) {
32184
33812
  return value.split(",").map((v) => v.trim()).filter((v) => v.length > 0);
32185
33813
  }
32186
33814
  function isNone(value) {
@@ -32212,14 +33840,14 @@ function contactBodyFromFlags(flags, base, op, current) {
32212
33840
  }
32213
33841
  const tags = flagString(flags, "tags");
32214
33842
  if (tags !== undefined)
32215
- body.tags = isNone(tags) ? [] : list(tags);
33843
+ body.tags = isNone(tags) ? [] : list2(tags);
32216
33844
  const circles = flagString(flags, "circles");
32217
33845
  if (circles !== undefined)
32218
- body.circleIds = isNone(circles) ? [] : list(circles);
33846
+ body.circleIds = isNone(circles) ? [] : list2(circles);
32219
33847
  const roles = flagString(flags, "roles");
32220
33848
  if (roles !== undefined) {
32221
33849
  const kept = new Map((current?.roles ?? []).map((r) => [r.role, r]));
32222
- body.roles = (isNone(roles) ? [] : list(roles)).map((role) => {
33850
+ body.roles = (isNone(roles) ? [] : list2(roles)).map((role) => {
32223
33851
  const had = kept.get(role);
32224
33852
  return had ? {
32225
33853
  id: had.id,
@@ -32396,12 +34024,9 @@ var ENTITIES = [
32396
34024
  canDelete: true
32397
34025
  }
32398
34026
  },
32399
- {
34027
+ entityFromOperations(areaOperations, {
32400
34028
  name: "areas",
32401
- path: "/areas",
32402
- readScopes: ["areas:read"],
32403
34029
  returns: "AreaView",
32404
- canGet: true,
32405
34030
  listFlags: [
32406
34031
  {
32407
34032
  name: "active",
@@ -32411,15 +34036,9 @@ var ENTITIES = [
32411
34036
  },
32412
34037
  { name: "archived", description: "only archived areas", query: "archived", sends: "true" }
32413
34038
  ],
32414
- write: {
32415
- scopes: ["areas:write"],
32416
- createSchema: createAreaInputSchema,
32417
- createInput: "CreateAreaInput",
32418
- updateSchema: updateAreaInputSchema,
32419
- updateInput: "UpdateAreaInput",
32420
- canDelete: true
32421
- }
32422
- },
34039
+ createInput: "CreateAreaInput",
34040
+ updateInput: "UpdateAreaInput"
34041
+ }),
32423
34042
  {
32424
34043
  name: "places",
32425
34044
  path: "/places",
@@ -32587,22 +34206,13 @@ var ENTITIES = [
32587
34206
  createInput: "CreateHouseholdInput",
32588
34207
  updateInput: "UpdateHouseholdInput"
32589
34208
  }),
32590
- {
34209
+ entityFromOperations(guestListOperations, {
32591
34210
  name: "guest-lists",
32592
- path: "/guest-lists",
32593
- readScopes: ["contacts:read"],
32594
34211
  returns: "GuestListView",
32595
- canGet: true,
32596
34212
  listFlags: [{ name: "project", description: "only this project's list", query: "projectId" }],
32597
- write: {
32598
- scopes: ["contacts:write"],
32599
- createSchema: createGuestListInputSchema,
32600
- createInput: "CreateGuestListInput",
32601
- updateSchema: updateGuestListInputSchema,
32602
- updateInput: "UpdateGuestListInput",
32603
- canDelete: true
32604
- }
32605
- },
34213
+ createInput: "CreateGuestListInput",
34214
+ updateInput: "UpdateGuestListInput"
34215
+ }),
32606
34216
  entityFromOperations(guestListEventOperations, {
32607
34217
  name: "guest-list-events",
32608
34218
  returns: "GuestListEvent",
@@ -32755,12 +34365,9 @@ var ENTITIES = [
32755
34365
  createInput: "CreateTapTagInput",
32756
34366
  updateInput: "UpdateTapTagInput"
32757
34367
  }),
32758
- {
34368
+ entityFromOperations(animalOperations, {
32759
34369
  name: "animals",
32760
- path: "/animals",
32761
- readScopes: ["animals:read"],
32762
34370
  returns: "AnimalView",
32763
- canGet: true,
32764
34371
  listFlags: [
32765
34372
  {
32766
34373
  name: "status",
@@ -32776,15 +34383,9 @@ var ENTITIES = [
32776
34383
  boolean: true
32777
34384
  }
32778
34385
  ],
32779
- write: {
32780
- scopes: ["animals:write"],
32781
- createSchema: createAnimalInputSchema,
32782
- createInput: "CreateAnimalInput",
32783
- updateSchema: updateAnimalInputSchema,
32784
- updateInput: "UpdateAnimalInput",
32785
- canDelete: true
32786
- }
32787
- },
34386
+ createInput: "CreateAnimalInput",
34387
+ updateInput: "UpdateAnimalInput"
34388
+ }),
32788
34389
  {
32789
34390
  name: "runs",
32790
34391
  path: "/runs",
@@ -32823,22 +34424,13 @@ var ENTITIES = [
32823
34424
  createInput: "CreateInventoryItemInput",
32824
34425
  updateInput: "UpdateInventoryItemInput"
32825
34426
  }),
32826
- {
34427
+ entityFromOperations(recipeOperations, {
32827
34428
  name: "recipes",
32828
- path: "/recipes",
32829
- readScopes: ["meals:read"],
32830
34429
  returns: "Recipe",
32831
- canGet: true,
32832
34430
  listFlags: [],
32833
- write: {
32834
- scopes: ["meals:write"],
32835
- createSchema: createRecipeInputSchema,
32836
- createInput: "CreateRecipeInput",
32837
- updateSchema: updateRecipeInputSchema,
32838
- updateInput: "UpdateRecipeInput",
32839
- canDelete: true
32840
- }
32841
- },
34431
+ createInput: "CreateRecipeInput",
34432
+ updateInput: "UpdateRecipeInput"
34433
+ }),
32842
34434
  entityFromOperations(mealPlanOperations, {
32843
34435
  name: "meal-plans",
32844
34436
  returns: "MealPlan",
@@ -32858,12 +34450,9 @@ var ENTITIES = [
32858
34450
  }
32859
34451
  ]
32860
34452
  }),
32861
- {
34453
+ entityFromOperations(timeBlockOperations, {
32862
34454
  name: "time-blocks",
32863
- path: "/time-blocks",
32864
- readScopes: ["calendar:read"],
32865
34455
  returns: "TimeBlockView",
32866
- canGet: true,
32867
34456
  listFlags: [
32868
34457
  {
32869
34458
  name: "from",
@@ -32872,15 +34461,9 @@ var ENTITIES = [
32872
34461
  { name: "to", description: "window end, exclusive (at most 62 days after --from)" },
32873
34462
  { name: "kind", description: "only blocks of this kind: task, focus or life" }
32874
34463
  ],
32875
- write: {
32876
- scopes: ["calendar:write"],
32877
- createSchema: createTimeBlockInputSchema,
32878
- createInput: "CreateTimeBlockInput",
32879
- updateSchema: updateTimeBlockInputSchema,
32880
- updateInput: "UpdateTimeBlockInput",
32881
- canDelete: true
32882
- }
32883
- },
34464
+ createInput: "CreateTimeBlockInput",
34465
+ updateInput: "UpdateTimeBlockInput"
34466
+ }),
32884
34467
  entityFromOperations(blockTemplateOperations, {
32885
34468
  name: "block-templates",
32886
34469
  returns: "BlockTemplateView",
@@ -32905,8 +34488,8 @@ function entityOperations(e) {
32905
34488
  }
32906
34489
 
32907
34490
  // src/health-commands.ts
32908
- var READ2 = ["health:read"];
32909
- var WRITE3 = ["health:write"];
34491
+ var READ4 = ["health:read"];
34492
+ var WRITE5 = ["health:write"];
32910
34493
  var DATA_FLAG = {
32911
34494
  name: "data",
32912
34495
  type: "string",
@@ -32920,7 +34503,7 @@ function recordSpecs(plural, singular, noun, returns, createInput, updateInput,
32920
34503
  summary: `List your health ${noun}s.`,
32921
34504
  args: [],
32922
34505
  flags: listFlags,
32923
- scopes: READ2,
34506
+ scopes: READ4,
32924
34507
  returns
32925
34508
  },
32926
34509
  {
@@ -32928,7 +34511,7 @@ function recordSpecs(plural, singular, noun, returns, createInput, updateInput,
32928
34511
  summary: `Show one health ${noun}.`,
32929
34512
  args: [id],
32930
34513
  flags: [],
32931
- scopes: READ2,
34514
+ scopes: READ4,
32932
34515
  returns
32933
34516
  },
32934
34517
  {
@@ -32936,7 +34519,7 @@ function recordSpecs(plural, singular, noun, returns, createInput, updateInput,
32936
34519
  summary: `Add a health ${noun} (--data '${createInput}').`,
32937
34520
  args: [],
32938
34521
  flags: [DATA_FLAG, ...addFlags],
32939
- scopes: WRITE3,
34522
+ scopes: WRITE5,
32940
34523
  returns,
32941
34524
  notes: `Body: ${createInput}. Always private to you; subjectNodeId names who it is about (absent = you).`
32942
34525
  },
@@ -32945,7 +34528,7 @@ function recordSpecs(plural, singular, noun, returns, createInput, updateInput,
32945
34528
  summary: `Change a health ${noun} (--data '${updateInput}').`,
32946
34529
  args: [id],
32947
34530
  flags: [DATA_FLAG],
32948
- scopes: WRITE3,
34531
+ scopes: WRITE5,
32949
34532
  returns,
32950
34533
  notes: `Body: ${updateInput}. null clears a field.`
32951
34534
  },
@@ -32954,7 +34537,7 @@ function recordSpecs(plural, singular, noun, returns, createInput, updateInput,
32954
34537
  summary: `Delete a health ${noun}.`,
32955
34538
  args: [id],
32956
34539
  flags: [],
32957
- scopes: WRITE3,
34540
+ scopes: WRITE5,
32958
34541
  returns: "Deleted",
32959
34542
  notes: "Destructive: needs --allow-destructive. An Agent's delete waits on your confirmation."
32960
34543
  }
@@ -32966,7 +34549,7 @@ var HEALTH_COMMAND_SPECS = [
32966
34549
  summary: "Show whether Health is on (only you turn it on or off, in do).",
32967
34550
  args: [],
32968
34551
  flags: [],
32969
- scopes: READ2,
34552
+ scopes: READ4,
32970
34553
  returns: "HealthConsentState",
32971
34554
  notes: "`granted` is on at today's consent text (`currentVersion`); `renewalNeeded` means you agreed to an older text, so writes wait until you agree again in do. `aiProvider` names who processes what the agent reads (null when this deployment names none)."
32972
34555
  },
@@ -32975,7 +34558,7 @@ var HEALTH_COMMAND_SPECS = [
32975
34558
  summary: "Show your health profile: blood group, allergies, lifestyle, notes, emergency contact, GP.",
32976
34559
  args: [],
32977
34560
  flags: [],
32978
- scopes: READ2,
34561
+ scopes: READ4,
32979
34562
  returns: "HealthProfile"
32980
34563
  },
32981
34564
  {
@@ -32983,7 +34566,7 @@ var HEALTH_COMMAND_SPECS = [
32983
34566
  summary: "Show your Medical ID: name, blood group, allergies, current medications, height, emergency contact.",
32984
34567
  args: [],
32985
34568
  flags: [],
32986
- scopes: READ2,
34569
+ scopes: READ4,
32987
34570
  returns: "HealthMedicalId",
32988
34571
  notes: "The few facts someone helping in an emergency needs. The emergency contact comes with their name and phone; a field not set is null. Each read by a key is logged for the member."
32989
34572
  },
@@ -33004,7 +34587,7 @@ var HEALTH_COMMAND_SPECS = [
33004
34587
  description: `Sex, in your words, for the doctor's page ("female"); "" clears it.`
33005
34588
  }
33006
34589
  ],
33007
- scopes: WRITE3,
34590
+ scopes: WRITE5,
33008
34591
  returns: "HealthProfile",
33009
34592
  notes: "Body: UpdateHealthProfileInput. An absent key keeps a field; null clears it. Lifestyle is `alcohol`, `smoking`, `exercise`, and `notes` anything else: the member's own words. Notes go on For the doctor's background; `sex` on its patient line."
33010
34593
  },
@@ -33013,7 +34596,7 @@ var HEALTH_COMMAND_SPECS = [
33013
34596
  summary: "Log one health message: find or start the Episode, add a symptom and/or a dose.",
33014
34597
  args: [],
33015
34598
  flags: [DATA_FLAG],
33016
- scopes: WRITE3,
34599
+ scopes: WRITE5,
33017
34600
  returns: "HealthReceipt",
33018
34601
  notes: 'Body: LogHealthInput, e.g. {"episode":{"title":"Sprained ankle","bodyRegion":"ankle_l"},"symptom":{"symptom":"worse on the stairs","severity":6,"severityGuessed":true},"dose":{"medication":"Ibuprofen"}}. The receipt lists each part with a line, what was guessed, and its undo. With "thread":{"slug":"<agent>"} (only an agent key that converses) the receipt card is posted there too, and postId names it.'
33019
34602
  },
@@ -33022,7 +34605,7 @@ var HEALTH_COMMAND_SPECS = [
33022
34605
  summary: `Correct a symptom entry and get a receipt (--data '{"bodyRegion":"ankle_l"}').`,
33023
34606
  args: [{ name: "entry", required: true, description: "The entry's id (its latest version)." }],
33024
34607
  flags: [DATA_FLAG],
33025
- scopes: WRITE3,
34608
+ scopes: WRITE5,
33026
34609
  returns: "HealthReceipt"
33027
34610
  },
33028
34611
  {
@@ -33033,7 +34616,7 @@ var HEALTH_COMMAND_SPECS = [
33033
34616
  { name: "trend", required: true, description: "better|same|worse, against yesterday." }
33034
34617
  ],
33035
34618
  flags: [],
33036
- scopes: WRITE3,
34619
+ scopes: WRITE5,
33037
34620
  returns: "HealthReceipt",
33038
34621
  notes: "One a day: a second check-in the same day corrects the first. The receipt carries its undo."
33039
34622
  },
@@ -33050,7 +34633,7 @@ var HEALTH_COMMAND_SPECS = [
33050
34633
  description: "Post the confirmation card in this thread (by slug); the agent's key only."
33051
34634
  }
33052
34635
  ],
33053
- scopes: WRITE3,
34636
+ scopes: WRITE5,
33054
34637
  returns: "HealthRedFlagsResult",
33055
34638
  notes: "Flags: fever, vomiting, black_stools, blood, weight_loss, night_pain, chest_pain, short_of_breath. Answers merge into those asked before; the Episode is marked checked today, and For the doctor shows it."
33056
34639
  },
@@ -33062,7 +34645,7 @@ var HEALTH_COMMAND_SPECS = [
33062
34645
  { name: "id", required: true, description: "The id from the receipt's undo." }
33063
34646
  ],
33064
34647
  flags: [],
33065
- scopes: WRITE3,
34648
+ scopes: WRITE5,
33066
34649
  returns: "HealthUndoResult",
33067
34650
  notes: "A POST, so an Agent's undo never waits on a confirmation."
33068
34651
  },
@@ -33071,7 +34654,7 @@ var HEALTH_COMMAND_SPECS = [
33071
34654
  summary: "Find the open Episode a complaint belongs to, or start one (--data UpsertHealthEpisodeInput).",
33072
34655
  args: [],
33073
34656
  flags: [DATA_FLAG],
33074
- scopes: WRITE3,
34657
+ scopes: WRITE5,
33075
34658
  returns: "HealthEpisodeUpsert"
33076
34659
  },
33077
34660
  {
@@ -33081,7 +34664,7 @@ var HEALTH_COMMAND_SPECS = [
33081
34664
  flags: [
33082
34665
  { name: "episode", type: "string", description: "The Episode; default the latest open one." }
33083
34666
  ],
33084
- scopes: READ2,
34667
+ scopes: READ4,
33085
34668
  returns: "HealthSummary"
33086
34669
  },
33087
34670
  {
@@ -33104,7 +34687,7 @@ var HEALTH_COMMAND_SPECS = [
33104
34687
  summary: "Your For-the-doctor links: sections, expiry, opens; never the secret.",
33105
34688
  args: [],
33106
34689
  flags: [],
33107
- scopes: READ2,
34690
+ scopes: READ4,
33108
34691
  returns: "HealthShare[]",
33109
34692
  notes: "Only the member makes a share, in do; a key can list and take one back."
33110
34693
  },
@@ -33113,7 +34696,7 @@ var HEALTH_COMMAND_SPECS = [
33113
34696
  summary: "Take a For-the-doctor link back: it stops opening.",
33114
34697
  args: [{ name: "id", required: true, description: "The share's id." }],
33115
34698
  flags: [],
33116
- scopes: WRITE3,
34699
+ scopes: WRITE5,
33117
34700
  returns: "HealthShare"
33118
34701
  },
33119
34702
  ...recordSpecs("conditions", "condition", "condition", "HealthConditionView", "CreateHealthConditionInput", "UpdateHealthConditionInput"),
@@ -33131,7 +34714,7 @@ var HEALTH_COMMAND_SPECS = [
33131
34714
  description: 'Their role on it, in your words ("dermatologist").'
33132
34715
  }
33133
34716
  ],
33134
- scopes: WRITE3,
34717
+ scopes: WRITE5,
33135
34718
  returns: "HealthConditionView",
33136
34719
  notes: "A cared_by link (condition \u2192 contact). Adding one already on it sets the role. The condition's page, the Health home's Care team and For the doctor list them."
33137
34720
  },
@@ -33143,7 +34726,7 @@ var HEALTH_COMMAND_SPECS = [
33143
34726
  { name: "contact", required: true, description: "The Contact's id." }
33144
34727
  ],
33145
34728
  flags: [],
33146
- scopes: WRITE3,
34729
+ scopes: WRITE5,
33147
34730
  returns: "HealthConditionView",
33148
34731
  notes: "An Agent's removal waits on your confirmation, like a delete."
33149
34732
  },
@@ -33180,7 +34763,7 @@ var HEALTH_COMMAND_SPECS = [
33180
34763
  { name: "at", type: "string", description: "When it happened (ISO); defaults to now." },
33181
34764
  { name: "note", type: "string", description: "A free-text note." }
33182
34765
  ],
33183
- scopes: WRITE3,
34766
+ scopes: WRITE5,
33184
34767
  returns: "HealthSymptomEntry",
33185
34768
  notes: "An Agent's entry is source chat. For qualifiers, send --data with LogHealthSymptomInput."
33186
34769
  },
@@ -33196,7 +34779,7 @@ var HEALTH_COMMAND_SPECS = [
33196
34779
  }
33197
34780
  ],
33198
34781
  flags: [DATA_FLAG],
33199
- scopes: WRITE3,
34782
+ scopes: WRITE5,
33200
34783
  returns: "HealthSymptomEntry",
33201
34784
  notes: "Body: CorrectHealthSymptomInput; fields not named are copied. The history stays."
33202
34785
  },
@@ -33208,7 +34791,7 @@ var HEALTH_COMMAND_SPECS = [
33208
34791
  { name: "entry", required: true, description: "The entry's id." }
33209
34792
  ],
33210
34793
  flags: [],
33211
- scopes: WRITE3,
34794
+ scopes: WRITE5,
33212
34795
  returns: "Deleted"
33213
34796
  },
33214
34797
  ...recordSpecs("visits", "visit", "visit", "HealthEncounterView", "CreateHealthEncounterInput", "UpdateHealthEncounterInput"),
@@ -33218,7 +34801,7 @@ var HEALTH_COMMAND_SPECS = [
33218
34801
  summary: "List the doses of a Medication, latest first.",
33219
34802
  args: [{ name: "med", required: true, description: "The Medication's id." }],
33220
34803
  flags: [],
33221
- scopes: READ2,
34804
+ scopes: READ4,
33222
34805
  returns: "HealthMedicationDose"
33223
34806
  },
33224
34807
  {
@@ -33232,7 +34815,7 @@ var HEALTH_COMMAND_SPECS = [
33232
34815
  { name: "at", type: "string", description: "When (ISO); defaults to now." },
33233
34816
  { name: "note", type: "string", description: "A free-text note." }
33234
34817
  ],
33235
- scopes: WRITE3,
34818
+ scopes: WRITE5,
33236
34819
  returns: "HealthMedicationDose"
33237
34820
  },
33238
34821
  {
@@ -33243,7 +34826,7 @@ var HEALTH_COMMAND_SPECS = [
33243
34826
  { name: "dose", required: true, description: "The dose's id." }
33244
34827
  ],
33245
34828
  flags: [DATA_FLAG],
33246
- scopes: WRITE3,
34829
+ scopes: WRITE5,
33247
34830
  returns: "HealthMedicationDose",
33248
34831
  notes: "Body: UpdateHealthDoseInput."
33249
34832
  },
@@ -33255,7 +34838,7 @@ var HEALTH_COMMAND_SPECS = [
33255
34838
  { name: "dose", required: true, description: "The dose's id." }
33256
34839
  ],
33257
34840
  flags: [],
33258
- scopes: WRITE3,
34841
+ scopes: WRITE5,
33259
34842
  returns: "Deleted"
33260
34843
  },
33261
34844
  ...recordSpecs("labs", "lab", "lab draw", "HealthLabPanelView", "CreateHealthLabPanelInput", "UpdateHealthLabPanelInput"),
@@ -33277,7 +34860,7 @@ var HEALTH_COMMAND_SPECS = [
33277
34860
  description: "The file's media type, when its name doesn't say."
33278
34861
  }
33279
34862
  ],
33280
- scopes: WRITE3,
34863
+ scopes: WRITE5,
33281
34864
  returns: "HealthLabImportResult",
33282
34865
  notes: "The draw stays a draft (status draft, source import) until the member checks it; the report is kept as its original. A report with no lab value in it is a 422."
33283
34866
  },
@@ -33286,7 +34869,7 @@ var HEALTH_COMMAND_SPECS = [
33286
34869
  summary: "The canonical analytes: key, printed name, group, unit, LOINC and aliases.",
33287
34870
  args: [],
33288
34871
  flags: [],
33289
- scopes: READ2,
34872
+ scopes: READ4,
33290
34873
  returns: "LabAnalyteDef[]",
33291
34874
  notes: 'A result is written under the canonical key its analyte or name stands for ("ALAT" \u2192 alt, "25-OH vitamin D" \u2192 vitamin_d); a name that matches none keeps its slug and comes back in the draw\'s `unmatched`.'
33292
34875
  },
@@ -33301,7 +34884,7 @@ var HEALTH_COMMAND_SPECS = [
33301
34884
  description: "Required: only report, change nothing."
33302
34885
  }
33303
34886
  ],
33304
- scopes: READ2,
34887
+ scopes: READ4,
33305
34888
  returns: "HealthLabNormalizeReport",
33306
34889
  notes: "Nothing is rewritten: trends already read old keys as their canonical one. Lists each result's from \u2192 to, and the keys that match no canonical analyte."
33307
34890
  },
@@ -33310,7 +34893,7 @@ var HEALTH_COMMAND_SPECS = [
33310
34893
  summary: "Every analyte your draws measured, each with its trend, grouped as a lab sheet.",
33311
34894
  args: [],
33312
34895
  flags: [],
33313
- scopes: READ2,
34896
+ scopes: READ4,
33314
34897
  returns: "HealthLabOverview"
33315
34898
  },
33316
34899
  {
@@ -33336,7 +34919,7 @@ var HEALTH_COMMAND_SPECS = [
33336
34919
  summary: "Whether your Withings scale is connected, the last sync, and what stops it.",
33337
34920
  args: [],
33338
34921
  flags: [],
33339
- scopes: READ2,
34922
+ scopes: READ4,
33340
34923
  returns: "WithingsStatus",
33341
34924
  notes: "GET /api/v1/health/withings. Connecting needs Withings' consent in a browser: Settings \u2192 Health in do. `failure` (reauth, unavailable, other) says what stops the sync, with a title and a hint."
33342
34925
  },
@@ -33345,7 +34928,7 @@ var HEALTH_COMMAND_SPECS = [
33345
34928
  summary: "Sync your Withings weigh-ins now, as the morning sync does.",
33346
34929
  args: [],
33347
34930
  flags: [],
33348
- scopes: WRITE3,
34931
+ scopes: WRITE5,
33349
34932
  returns: "WithingsSyncResult",
33350
34933
  notes: "POST /api/v1/health/withings/sync. Written and skipped, and the status after it; a sync that stops answers 200 with `status.failure`. 409 before Withings is connected, 403 while the server has it switched off."
33351
34934
  },
@@ -33354,7 +34937,7 @@ var HEALTH_COMMAND_SPECS = [
33354
34937
  summary: "One analyte's trend across draws (do health analyte vitamin_d).",
33355
34938
  args: [{ name: "analyte", required: true, description: "The analyte key (vitamin_d)." }],
33356
34939
  flags: [],
33357
- scopes: READ2,
34940
+ scopes: READ4,
33358
34941
  returns: "HealthLabAnalyteView"
33359
34942
  },
33360
34943
  {
@@ -33362,7 +34945,7 @@ var HEALTH_COMMAND_SPECS = [
33362
34945
  summary: `Keep a target band or the supplement for an analyte (--data '{"targetLow":40,"targetHigh":60}').`,
33363
34946
  args: [{ name: "analyte", required: true, description: "The analyte key (vitamin_d)." }],
33364
34947
  flags: [DATA_FLAG],
33365
- scopes: WRITE3,
34948
+ scopes: WRITE5,
33366
34949
  returns: "HealthLabAnalyteView",
33367
34950
  notes: "Body: SetHealthLabAnalyteInput. An absent key keeps a field; null clears it."
33368
34951
  }
@@ -33680,16 +35263,16 @@ var COMMAND_SPECS = [
33680
35263
  flags: [
33681
35264
  { name: "days", type: "string", description: "The range: 7 (default), 30 or 90 days." }
33682
35265
  ],
33683
- scopes: ["tasks:read", "habits:read"],
35266
+ scopes: [...progressOperations.get.scopes],
33684
35267
  returns: "ProgressResponse",
33685
- notes: "GET /api/v1/progress \u2014 days are your saved timezone's. Includes the week/month vs the period before, best day, Areas and each habit's rate."
35268
+ notes: `${routeOf(progressOperations.get)} \u2014 days are your saved timezone's. Includes the week/month vs the period before, best day, Areas and each habit's rate.`
33686
35269
  },
33687
35270
  {
33688
35271
  path: ["progress", "day"],
33689
35272
  summary: "One day of Progress: what was done (and by whom) and what slipped.",
33690
35273
  args: [{ name: "date", required: true, description: "The day, YYYY-MM-DD." }],
33691
35274
  flags: [],
33692
- scopes: ["tasks:read", "habits:read"],
35275
+ scopes: [...progressOperations.day.scopes],
33693
35276
  returns: "ProgressDayDetail"
33694
35277
  },
33695
35278
  {
@@ -33697,9 +35280,9 @@ var COMMAND_SPECS = [
33697
35280
  summary: "Habits this week: a done/skipped grid, each habit's strength word, patterns and when you check in.",
33698
35281
  args: [],
33699
35282
  flags: [],
33700
- scopes: ["habits:read"],
35283
+ scopes: [...progressOperations.habits.scopes],
33701
35284
  returns: "ProgressHabitsResponse",
33702
- notes: "GET /api/v1/progress/habits \u2014 the week is your saved timezone's, Monday first. Strength is a word (steady, building, settling-in); patterns come from the insights worker with a confidence label."
35285
+ notes: `${routeOf(progressOperations.habits)} \u2014 the week is your saved timezone's, Monday first. Strength is a word (steady, building, settling-in); patterns come from the insights worker with a confidence label.`
33703
35286
  },
33704
35287
  {
33705
35288
  path: ["journal", "day"],
@@ -33996,18 +35579,18 @@ var COMMAND_SPECS = [
33996
35579
  summary: "Your GitHub commits: the ones you made, and your agents' on your account.",
33997
35580
  args: [],
33998
35581
  flags: [{ name: "days", type: "string", description: "7 (default) or 30." }],
33999
- scopes: ["habits:read"],
35582
+ scopes: [...progressOperations.commits.scopes],
34000
35583
  returns: "GithubCommits",
34001
- notes: "GET /api/v1/progress/commits \u2014 your own only. `byYou`, `byAgents` with `agents` by name, `unsorted` (synced before the split) and `perDay`. Only your own commits count toward Insights and a GitHub-sourced habit."
35584
+ notes: `${routeOf(progressOperations.commits)} \u2014 your own only. \`byYou\`, \`byAgents\` with \`agents\` by name, \`unsorted\` (synced before the split) and \`perDay\`. Only your own commits count toward Insights and a GitHub-sourced habit.`
34002
35585
  },
34003
35586
  {
34004
35587
  path: ["progress", "questions"],
34005
35588
  summary: "Insights' answers: late nights vs the next day, the morning routine, good runs, bedtime, best days, reading.",
34006
35589
  args: [],
34007
35590
  flags: [],
34008
- scopes: ["habits:read"],
35591
+ scopes: [...progressOperations.questions.scopes],
34009
35592
  returns: "InsightsAnswersResponse",
34010
- notes: "GET /api/v1/progress/questions \u2014 this week's answers (recomputed Monday 04:00 in your zone), your own only. Each is shown, no_difference or collecting (with the days still needed); quote `sentence` and `footer` verbatim. Patterns go together; they never claim a cause."
35593
+ notes: `${routeOf(progressOperations.questions)} \u2014 this week's answers (recomputed Monday 04:00 in your zone), your own only. Each is shown, no_difference or collecting (with the days still needed); quote \`sentence\` and \`footer\` verbatim. Patterns go together; they never claim a cause.`
34011
35594
  },
34012
35595
  {
34013
35596
  path: ["progress", "dismiss"],
@@ -34020,7 +35603,7 @@ var COMMAND_SPECS = [
34020
35603
  }
34021
35604
  ],
34022
35605
  flags: [],
34023
- scopes: ["habits:write"],
35606
+ scopes: [...progressOperations.dismiss.scopes],
34024
35607
  returns: "InsightsAnswersResponse"
34025
35608
  },
34026
35609
  {
@@ -34028,7 +35611,7 @@ var COMMAND_SPECS = [
34028
35611
  summary: "Bring a set-aside Insights answer back.",
34029
35612
  args: [{ name: "question", required: true, description: "The question's key." }],
34030
35613
  flags: [],
34031
- scopes: ["habits:write"],
35614
+ scopes: [...progressOperations.restore.scopes],
34032
35615
  returns: "InsightsAnswersResponse"
34033
35616
  },
34034
35617
  {
@@ -34039,9 +35622,9 @@ var COMMAND_SPECS = [
34039
35622
  { name: "at", type: "string", description: "When (ISO); defaults to now." },
34040
35623
  { name: "channel", type: "string", description: "Where, in a word (telegram)." }
34041
35624
  ],
34042
- scopes: ["habits:write"],
35625
+ scopes: [...progressOperations.activity.scopes],
34043
35626
  returns: "MemberActivity",
34044
- notes: "POST /api/v1/progress/activity \u2014 the late-night question reads the member's last activity of each evening (00:00\u201304:00 counts for the evening before)."
35627
+ notes: `${routeOf(progressOperations.activity)} \u2014 the late-night question reads the member's last activity of each evening (00:00\u201304:00 counts for the evening before).`
34045
35628
  },
34046
35629
  {
34047
35630
  path: ["search"],
@@ -34143,27 +35726,27 @@ var COMMAND_SPECS = [
34143
35726
  summary: "A life block planned against what happened: its plan and record, the nested routine's steps, what the record rests on and how sure it is (a word).",
34144
35727
  args: [{ name: "id", description: "The time block's id.", required: true }],
34145
35728
  flags: [],
34146
- scopes: ["calendar:read"],
35729
+ scopes: [...timeBlockOperations.record.scopes],
34147
35730
  returns: "TimeBlockRecord",
34148
- notes: "GET /api/v1/time-blocks/{id}/record (plan vs record). Only a life block has a record."
35731
+ notes: `${routeOf(timeBlockOperations.record)} (plan vs record). Only a life block has a record.`
34149
35732
  },
34150
35733
  {
34151
35734
  path: ["time-blocks", "confirm"],
34152
35735
  summary: "Say a begun life block's record looks right.",
34153
35736
  args: [{ name: "id", description: "The time block's id.", required: true }],
34154
35737
  flags: [],
34155
- scopes: ["calendar:write"],
35738
+ scopes: [...timeBlockOperations.confirm.scopes],
34156
35739
  returns: "TimeBlockRecord",
34157
- notes: "POST /api/v1/time-blocks/{id}/confirm (plan vs record). An Agent's later change reopens it."
35740
+ notes: `${routeOf(timeBlockOperations.confirm)} (plan vs record). An Agent's later change reopens it.`
34158
35741
  },
34159
35742
  {
34160
35743
  path: ["time-blocks", "plan-vs-record"],
34161
35744
  summary: "How the life blocks went against their plan: last week day by day, where the time went, which blocks move (in words), and one template change worth making.",
34162
35745
  args: [],
34163
35746
  flags: [{ name: "days", type: "string", description: "7, 30 (default) or 90." }],
34164
- scopes: ["calendar:read"],
35747
+ scopes: [...timeBlockOperations.planVsRecord.scopes],
34165
35748
  returns: "PlanVsRecord",
34166
- notes: "GET /api/v1/time-blocks/plan-vs-record?days= (plan vs record). No score, no streak."
35749
+ notes: `${routeOf(timeBlockOperations.planVsRecord)}?days= (plan vs record). No score, no streak.`
34167
35750
  },
34168
35751
  {
34169
35752
  path: ["days", "record"],
@@ -34172,9 +35755,9 @@ var COMMAND_SPECS = [
34172
35755
  { name: "date", required: true, description: "The day (YYYY-MM-DD), in the member's zone." }
34173
35756
  ],
34174
35757
  flags: [],
34175
- scopes: ["calendar:read"],
35758
+ scopes: [...dayOperations.record.scopes],
34176
35759
  returns: "DayRecord",
34177
- notes: "GET /api/v1/days/{date}/record (plan vs record, phase 2). A day nobody closes closes itself 48 h after it ends."
35760
+ notes: `${routeOf(dayOperations.record)} (plan vs record, phase 2). A day nobody closes closes itself 48 h after it ends.`
34178
35761
  },
34179
35762
  {
34180
35763
  path: ["days", "write"],
@@ -34187,9 +35770,9 @@ var COMMAND_SPECS = [
34187
35770
  description: 'The DayRecordWriteInput as JSON: {"changes":[{"blockId":"\u2026","endAt":"2026-10-19T11:45:00Z","evidence":[{"source":"habit","refKind":"habit_entry","refId":"\u2026","note":"Kitchen cleanup check-in"}]},{"id":"<new ULID>","lifeKind":"together","title":"TV together","startAt":"\u2026","endAt":"\u2026","evidence":[{"source":"chat","refKind":"agent_post","refId":"\u2026"}]},{"blockId":"\u2026","replacedBy":"<the new ULID>"},{"blockId":"\u2026","skipped":true}]}. Times round to the quarter hour. Required.'
34188
35771
  }
34189
35772
  ],
34190
- scopes: ["calendar:write"],
35773
+ scopes: [...dayOperations.write.scopes],
34191
35774
  returns: "DayRecordReceipt",
34192
- notes: "POST /api/v1/days/{date}/record (plan vs record, phase 2). Never waits for a confirmation; 409 for an Agent on a closed day. Undo with `do days undo <date> <receipt>`."
35775
+ notes: `${routeOf(dayOperations.write)} (plan vs record, phase 2). Never waits for a confirmation; 409 for an Agent on a closed day. Undo with \`do days undo <date> <receipt>\`.`
34193
35776
  },
34194
35777
  {
34195
35778
  path: ["days", "ask"],
@@ -34202,9 +35785,9 @@ var COMMAND_SPECS = [
34202
35785
  description: 'The DayRecordAskInput as JSON: {"blockId":"\u2026","question":"Did lunch run long?"} or, for a gap, {"id":"<new ULID>","startAt":"\u2026","endAt":"\u2026","question":"What filled 15:00\u201316:00?"}. Required.'
34203
35786
  }
34204
35787
  ],
34205
- scopes: ["calendar:write"],
35788
+ scopes: [...dayOperations.ask.scopes],
34206
35789
  returns: "DayRecord",
34207
- notes: "POST /api/v1/days/{date}/record/ask (plan vs record, phase 2). 409 past two a day."
35790
+ notes: `${routeOf(dayOperations.ask)} (plan vs record, phase 2). 409 past two a day.`
34208
35791
  },
34209
35792
  {
34210
35793
  path: ["days", "undo"],
@@ -34214,18 +35797,18 @@ var COMMAND_SPECS = [
34214
35797
  { name: "receipt", required: true, description: "The receipt a write returned." }
34215
35798
  ],
34216
35799
  flags: [],
34217
- scopes: ["calendar:write"],
35800
+ scopes: [...dayOperations.undo.scopes],
34218
35801
  returns: "DayRecordUndoResult",
34219
- notes: "POST /api/v1/days/{date}/record/undo (plan vs record, phase 2)."
35802
+ notes: `${routeOf(dayOperations.undo)} (plan vs record, phase 2).`
34220
35803
  },
34221
35804
  {
34222
35805
  path: ["days", "close"],
34223
35806
  summary: "Close a day (day_close): the member said the record is fine.",
34224
35807
  args: [{ name: "date", required: true, description: "The day (YYYY-MM-DD)." }],
34225
35808
  flags: [],
34226
- scopes: ["calendar:write"],
35809
+ scopes: [...dayOperations.close.scopes],
34227
35810
  returns: "DayRecord",
34228
- notes: "POST /api/v1/days/{date}/close (plan vs record, phase 2). After this only the member changes the day."
35811
+ notes: `${routeOf(dayOperations.close)} (plan vs record, phase 2). After this only the member changes the day.`
34229
35812
  },
34230
35813
  {
34231
35814
  path: ["calendar", "events"],
@@ -34235,9 +35818,9 @@ var COMMAND_SPECS = [
34235
35818
  { name: "from", type: "string", description: "Window start (ISO instant); with --to." },
34236
35819
  { name: "to", type: "string", description: "Window end (ISO instant), at most 62 days on." }
34237
35820
  ],
34238
- scopes: ["calendar:read"],
35821
+ scopes: [...calendarOperations.events.scopes],
34239
35822
  returns: "Event[]",
34240
- notes: "GET /api/v1/calendar/events (week-plan feedback). Neither flag = the next day. Needs a key that acts for a member."
35823
+ notes: `${routeOf(calendarOperations.events)} (week-plan feedback). Neither flag = the next day. Needs a key that acts for a member.`
34241
35824
  },
34242
35825
  {
34243
35826
  path: ["calendar", "plan"],
@@ -34256,9 +35839,9 @@ var COMMAND_SPECS = [
34256
35839
  description: "The member's answer: yes, no, or waiting (asks again)."
34257
35840
  }
34258
35841
  ],
34259
- scopes: ["calendar:write"],
35842
+ scopes: [...calendarOperations.plan.scopes],
34260
35843
  returns: "Event",
34261
- notes: "PATCH /api/v1/calendar/events/{id}/plan. Until it's yes, an optional event holds no time; plan nothing around it."
35844
+ notes: `${routeOf(calendarOperations.plan)}. Until it's yes, an optional event holds no time; plan nothing around it.`
34262
35845
  },
34263
35846
  {
34264
35847
  path: ["plans", "list"],
@@ -34711,7 +36294,7 @@ var COMMAND_SPECS = [
34711
36294
  summary: "Show weight, sleep, mood, the day rating, energy, bedtime, resting heart rate and HRV as daily trend lines (no target, no streak). Resting heart rate and HRV need health:read too.",
34712
36295
  args: [],
34713
36296
  flags: [{ name: "days", type: "string", description: "How far back, 1\u2013730 (default 90)." }],
34714
- scopes: ["habits:read"],
36297
+ scopes: [...metricOperations.list.scopes],
34715
36298
  returns: "Metric"
34716
36299
  },
34717
36300
  {
@@ -34719,7 +36302,7 @@ var COMMAND_SPECS = [
34719
36302
  summary: "Show workout sessions your connections brought in (Google Fit, Google Health, Strava), newest first.",
34720
36303
  args: [],
34721
36304
  flags: [{ name: "days", type: "string", description: "How far back, 1\u2013365 (default 14)." }],
34722
- scopes: ["habits:read"],
36305
+ scopes: [...metricOperations.sessions.scopes],
34723
36306
  returns: "Session"
34724
36307
  },
34725
36308
  {
@@ -34745,7 +36328,7 @@ var COMMAND_SPECS = [
34745
36328
  description: "manual (default), device \u2014 a wearable or wake hook reported it \u2014 or agent \u2014 the member told the agent (the default from an Agent's key)."
34746
36329
  }
34747
36330
  ],
34748
- scopes: ["habits:write"],
36331
+ scopes: [...metricOperations.log.scopes],
34749
36332
  returns: "Metric",
34750
36333
  notes: "Logging again the same day replaces that day's point. A bedtime belongs to the night's wake day (logged at 23:30 or the next morning, it lands on the morning). The key must act for a member."
34751
36334
  },
@@ -34760,9 +36343,9 @@ var COMMAND_SPECS = [
34760
36343
  description: 'The batch as JSON: {"points":[{"key":"sleep","value":7.5,"occurredAt":"2026-10-20T07:00:00Z","recordId":"\u2026"}]} (source defaults to health_connect).'
34761
36344
  }
34762
36345
  ],
34763
- scopes: ["habits:write"],
36346
+ scopes: [...metricOperations.import.scopes],
34764
36347
  returns: "ImportMetricsResult",
34765
- notes: "POST /api/v1/metrics/import. Each record lands once (its recordId); one already imported, out of range or in the future is skipped. At most 500 points."
36348
+ notes: `${routeOf(metricOperations.import)}. Each record lands once (its recordId); one already imported, out of range or in the future is skipped. At most 500 points.`
34766
36349
  },
34767
36350
  {
34768
36351
  path: ["documents", "file"],
@@ -35254,25 +36837,25 @@ var COMMAND_SPECS = [
35254
36837
  description: "Also ask Open Food Facts when there are too few local hits."
35255
36838
  }
35256
36839
  ],
35257
- scopes: ["meals:read"],
36840
+ scopes: [...foodOperations.search.scopes],
35258
36841
  returns: "FoodSearchResult",
35259
- notes: "GET /api/v1/foods/search. Values per 100 g; `licence` is the attribution to show. `remote` says whether OFF was used, skipped, rate_limited or unavailable."
36842
+ notes: `${routeOf(foodOperations.search)}. Values per 100 g; \`licence\` is the attribution to show. \`remote\` says whether OFF was used, skipped, rate_limited or unavailable.`
35260
36843
  },
35261
36844
  {
35262
36845
  path: ["foods", "barcode"],
35263
36846
  summary: "Look up a branded product by barcode (cached Open Food Facts).",
35264
36847
  args: [{ name: "ean", required: true, description: "The EAN/UPC barcode, 8\u201314 digits." }],
35265
36848
  flags: [],
35266
- scopes: ["meals:read"],
36849
+ scopes: [...foodOperations.barcode.scopes],
35267
36850
  returns: "FoodBarcodeResult",
35268
- notes: "GET /api/v1/foods/barcode/{ean}. An unknown barcode is found: false, not an error."
36851
+ notes: `${routeOf(foodOperations.barcode)}. An unknown barcode is found: false, not an error.`
35269
36852
  },
35270
36853
  {
35271
36854
  path: ["foods", "get"],
35272
36855
  summary: "Show one food from the nutrition reference.",
35273
36856
  args: [{ name: "id", required: true, description: "Food ULID." }],
35274
36857
  flags: [],
35275
- scopes: ["meals:read"],
36858
+ scopes: [...foodOperations.get.scopes],
35276
36859
  returns: "Food"
35277
36860
  },
35278
36861
  {
@@ -35286,9 +36869,9 @@ var COMMAND_SPECS = [
35286
36869
  description: 'The CreateMealLogInput as JSON (id optional): {"title":"Boterhammen met kaas","eatenAt":"2026-10-16T12:30:00+02:00","mealType":"lunch","items":[{"label":"boterham","foodId":"\u2026","quantity":2,"unit":"boterham"},{"label":"saus","grams":20,"estimate":{"kcal":90}}]}. Find foodIds with `do foods search`.'
35287
36870
  }
35288
36871
  ],
35289
- scopes: ["health:write"],
36872
+ scopes: [...foodLogOperations.create.scopes],
35290
36873
  returns: "MealLogReceipt",
35291
- notes: "POST /api/v1/food/logs (the agent's food_log). Numbers are computed from the reference \xD7 grams; only an `estimate` brings its own. The receipt: headline with the kcal range, a confidence word, a line per item with its source, and the Undo / Edit / Save calls."
36874
+ notes: `${routeOf(foodLogOperations.create)} (the agent's food_log). Numbers are computed from the reference \xD7 grams; only an \`estimate\` brings its own. The receipt: headline with the kcal range, a confidence word, a line per item with its source, and the Undo / Edit / Save calls.`
35292
36875
  },
35293
36876
  {
35294
36877
  path: ["food", "list"],
@@ -35302,16 +36885,16 @@ var COMMAND_SPECS = [
35302
36885
  description: "End (exclusive), an ISO instant; at most 92 days after --from."
35303
36886
  }
35304
36887
  ],
35305
- scopes: ["health:read"],
36888
+ scopes: [...foodLogOperations.list.scopes],
35306
36889
  returns: "MealLogView",
35307
- notes: "GET /api/v1/food/logs."
36890
+ notes: `${routeOf(foodLogOperations.list)}.`
35308
36891
  },
35309
36892
  {
35310
36893
  path: ["food", "get"],
35311
36894
  summary: "Show one meal: its items, the corrected ones and its sources' attribution.",
35312
36895
  args: [{ name: "id", required: true, description: "Meal ULID." }],
35313
36896
  flags: [],
35314
- scopes: ["health:read"],
36897
+ scopes: [...foodLogOperations.get.scopes],
35315
36898
  returns: "MealLogView"
35316
36899
  },
35317
36900
  {
@@ -35319,9 +36902,9 @@ var COMMAND_SPECS = [
35319
36902
  summary: "Show a meal in its day: the weekmenu plan for its slot, the timing against the plan and bed, and what came after.",
35320
36903
  args: [{ name: "id", required: true, description: "Meal ULID." }],
35321
36904
  flags: [],
35322
- scopes: ["health:read"],
36905
+ scopes: [...foodLogOperations.context.scopes],
35323
36906
  returns: "MealContext",
35324
- notes: "GET /api/v1/food/logs/{id}/context. Read-side: Health symptom entries and doses in the 12 h after, an Episode that began then, the night's sleep and the day rating. Nothing in it says the meal caused what followed."
36907
+ notes: `${routeOf(foodLogOperations.context)}. Read-side: Health symptom entries and doses in the 12 h after, an Episode that began then, the night's sleep and the day rating. Nothing in it says the meal caused what followed.`
35325
36908
  },
35326
36909
  {
35327
36910
  path: ["food", "week"],
@@ -35334,36 +36917,36 @@ var COMMAND_SPECS = [
35334
36917
  description: "Any day of the week (YYYY-MM-DD); this week by default."
35335
36918
  }
35336
36919
  ],
35337
- scopes: ["health:read"],
36920
+ scopes: [...foodLogOperations.week.scopes],
35338
36921
  returns: "FoodWeek",
35339
- notes: "GET /api/v1/food/week. Read-side: no target, ever; a missing day is missing, not zero. Nothing in it says a meal caused what followed."
36922
+ notes: `${routeOf(foodLogOperations.week)}. Read-side: no target, ever; a missing day is missing, not zero. Nothing in it says a meal caused what followed.`
35340
36923
  },
35341
36924
  {
35342
36925
  path: ["food", "week-read"],
35343
36926
  summary: "Write or replace the read of a food week: a few calm lines about it, shown on the week as the agent's read.",
35344
36927
  args: [{ name: "start", required: true, description: "Any day of the week (YYYY-MM-DD)." }],
35345
36928
  flags: [{ name: "body", type: "string", description: "The read, at most 2,000 characters." }],
35346
- scopes: ["health:write"],
36929
+ scopes: [...foodLogOperations.writeWeekRead.scopes],
35347
36930
  returns: "FoodWeek",
35348
- notes: "PUT /api/v1/food/week/read (food_week_read). Private to the member; a week that hasn't started can't have one. Say what went together, never a cause or a score."
36931
+ notes: `${routeOf(foodLogOperations.writeWeekRead)} (food_week_read). Private to the member; a week that hasn't started can't have one. Say what went together, never a cause or a score.`
35349
36932
  },
35350
36933
  {
35351
36934
  path: ["food", "week-read-remove"],
35352
36935
  summary: "Take back the read of a food week.",
35353
36936
  args: [{ name: "start", required: true, description: "Any day of the week (YYYY-MM-DD)." }],
35354
36937
  flags: [],
35355
- scopes: ["health:write"],
36938
+ scopes: [...foodLogOperations.removeWeekRead.scopes],
35356
36939
  returns: "None",
35357
- notes: "DELETE /api/v1/food/week/read?start= (food_week_read_remove). From an agent's key it waits on the member's confirmation, like every agent DELETE."
36940
+ notes: `${routeOf(foodLogOperations.removeWeekRead)}?start= (food_week_read_remove). From an agent's key it waits on the member's confirmation, like every agent DELETE.`
35358
36941
  },
35359
36942
  {
35360
36943
  path: ["food", "patterns"],
35361
36944
  summary: "Show food and how you feel: the fixed food questions (big lunches and tiredness, late dinners, very hot or fried meals, intake and the day rating, the weekly average and weight).",
35362
36945
  args: [],
35363
36946
  flags: [],
35364
- scopes: ["health:read"],
36947
+ scopes: [...foodLogOperations.patterns.scopes],
35365
36948
  returns: "FoodPatterns",
35366
- notes: "GET /api/v1/food/patterns. Over the 90 days up to this week's Monday, on the Insights rules: collecting until 21 paired days and 7 on each side; 'goes together', never a cause."
36949
+ notes: `${routeOf(foodLogOperations.patterns)}. Over the 90 days up to this week's Monday, on the Insights rules: collecting until 21 paired days and 7 on each side; 'goes together', never a cause.`
35367
36950
  },
35368
36951
  {
35369
36952
  path: ["food", "trial"],
@@ -35389,18 +36972,18 @@ var COMMAND_SPECS = [
35389
36972
  },
35390
36973
  { name: "days", type: "string", description: "How many days it runs (7\u201328; default 14)." }
35391
36974
  ],
35392
- scopes: ["health:write"],
36975
+ scopes: [...foodLogOperations.proposeTrial.scopes],
35393
36976
  returns: "FoodTrial",
35394
- notes: "POST /api/v1/food/trials (food_trial). Waits on Food and how you feel until the member starts it, says Not now (a week) or stops it; a newer proposal sets an older waiting one aside. Private to the member. n-of-1 information, never a diagnosis."
36977
+ notes: `${routeOf(foodLogOperations.proposeTrial)} (food_trial). Waits on Food and how you feel until the member starts it, says Not now (a week) or stops it; a newer proposal sets an older waiting one aside. Private to the member. n-of-1 information, never a diagnosis.`
35395
36978
  },
35396
36979
  {
35397
36980
  path: ["food", "trial-stop"],
35398
36981
  summary: "Stop a food trial, running or still proposed.",
35399
36982
  args: [{ name: "id", required: true, description: "The trial's ULID." }],
35400
36983
  flags: [],
35401
- scopes: ["health:write"],
36984
+ scopes: [...foodLogOperations.stopTrial.scopes],
35402
36985
  returns: "None",
35403
- notes: "POST /api/v1/food/trials/{id}/stop (food_trial_stop)."
36986
+ notes: `${routeOf(foodLogOperations.stopTrial)} (food_trial_stop).`
35404
36987
  },
35405
36988
  {
35406
36989
  path: ["food", "correct"],
@@ -35413,16 +36996,16 @@ var COMMAND_SPECS = [
35413
36996
  description: 'The CorrectMealLogInput as JSON: {"replace":[{"itemId":"\u2026","item":{"label":"martino","foodId":"\u2026","grams":20}}],"remove":["\u2026"],"add":[\u2026],"flags":["spice:very_hot"]}.'
35414
36997
  }
35415
36998
  ],
35416
- scopes: ["health:write"],
36999
+ scopes: [...foodLogOperations.correct.scopes],
35417
37000
  returns: "MealLogReceipt",
35418
- notes: "PATCH /api/v1/food/logs/{id} (the agent's food_correct)."
37001
+ notes: `${routeOf(foodLogOperations.correct)} (the agent's food_correct).`
35419
37002
  },
35420
37003
  {
35421
37004
  path: ["food", "undo-correction"],
35422
37005
  summary: "Take back a meal's latest correction.",
35423
37006
  args: [{ name: "id", required: true, description: "Meal ULID." }],
35424
37007
  flags: [],
35425
- scopes: ["health:write"],
37008
+ scopes: [...foodLogOperations.undoCorrection.scopes],
35426
37009
  returns: "MealLogReceipt"
35427
37010
  },
35428
37011
  {
@@ -35432,7 +37015,7 @@ var COMMAND_SPECS = [
35432
37015
  flags: [
35433
37016
  { name: "allow-destructive", type: "boolean", description: "Required to actually remove." }
35434
37017
  ],
35435
- scopes: ["health:write"],
37018
+ scopes: [...foodLogOperations.remove.scopes],
35436
37019
  returns: "None",
35437
37020
  notes: "Gated behind --allow-destructive. The meal goes to Trash."
35438
37021
  },
@@ -35447,9 +37030,9 @@ var COMMAND_SPECS = [
35447
37030
  description: "The saved meal's name; defaults to the meal's."
35448
37031
  }
35449
37032
  ],
35450
- scopes: ["health:write"],
37033
+ scopes: [...foodLogOperations.saveMeal.scopes],
35451
37034
  returns: "SaveMealResult",
35452
- notes: "POST /api/v1/food/logs/{id}/save-meal (the agent's food_save_meal). Log it again with recipeId + servings: 1."
37035
+ notes: `${routeOf(foodLogOperations.saveMeal)} (the agent's food_save_meal). Log it again with recipeId + servings: 1.`
35453
37036
  },
35454
37037
  {
35455
37038
  path: ["food", "days"],
@@ -35459,7 +37042,7 @@ var COMMAND_SPECS = [
35459
37042
  { name: "from", type: "string", description: "First day, YYYY-MM-DD." },
35460
37043
  { name: "to", type: "string", description: "Last day, YYYY-MM-DD." }
35461
37044
  ],
35462
- scopes: ["health:read"],
37045
+ scopes: [...foodLogOperations.days.scopes],
35463
37046
  returns: "FoodDay",
35464
37047
  notes: "A day with nothing logged has no row: missing, not zero."
35465
37048
  },
@@ -35470,7 +37053,7 @@ var COMMAND_SPECS = [
35470
37053
  { name: "date", required: true, description: "YYYY-MM-DD, the member's day (from 04:00)." }
35471
37054
  ],
35472
37055
  flags: [],
35473
- scopes: ["health:read"],
37056
+ scopes: [...foodLogOperations.day.scopes],
35474
37057
  returns: "FoodDayView"
35475
37058
  },
35476
37059
  {
@@ -35478,27 +37061,27 @@ var COMMAND_SPECS = [
35478
37061
  summary: "What the member eats often: frequent meals of the last 30 days and saved meals.",
35479
37062
  args: [],
35480
37063
  flags: [],
35481
- scopes: ["health:read"],
37064
+ scopes: [...foodLogOperations.recall.scopes],
35482
37065
  returns: "FoodRecall",
35483
- notes: `GET /api/v1/food/recall (the agent's food_recall). Use it to resolve "the usual".`
37066
+ notes: `${routeOf(foodLogOperations.recall)} (the agent's food_recall). Use it to resolve "the usual".`
35484
37067
  },
35485
37068
  {
35486
37069
  path: ["recipes", "nutrition"],
35487
37070
  summary: "Recompute a recipe's nutrition from its ingredients.",
35488
37071
  args: [{ name: "id", required: true, description: "Recipe ULID." }],
35489
37072
  flags: [],
35490
- scopes: ["meals:write"],
37073
+ scopes: [...recipeOperations.nutrition.scopes],
35491
37074
  returns: "RecipeNutrition",
35492
- notes: "POST /api/v1/recipes/{id}/nutrition. Per pot, per serving, coverage and the lines with no match."
37075
+ notes: `${routeOf(recipeOperations.nutrition)}. Per pot, per serving, coverage and the lines with no match.`
35493
37076
  },
35494
37077
  {
35495
37078
  path: ["recipes", "used-in"],
35496
37079
  summary: "Where a recipe was used: the meals it was eaten in and the weekmenu days it's planned for.",
35497
37080
  args: [{ name: "id", required: true, description: "Recipe ULID." }],
35498
37081
  flags: [],
35499
- scopes: ["meals:read"],
37082
+ scopes: [...recipeOperations.usedIn.scopes],
35500
37083
  returns: "RecipeUsedIn",
35501
- notes: "GET /api/v1/recipes/{id}/used-in. At most five of each; meals need health:read too (without it, plans only)."
37084
+ notes: `${routeOf(recipeOperations.usedIn)}. At most five of each; meals need health:read too (without it, plans only).`
35502
37085
  },
35503
37086
  {
35504
37087
  path: ["recipes", "match"],
@@ -35509,9 +37092,9 @@ var COMMAND_SPECS = [
35509
37092
  { name: "food", required: true, description: "A food id from `do foods search`." }
35510
37093
  ],
35511
37094
  flags: [],
35512
- scopes: ["meals:write"],
37095
+ scopes: [...recipeOperations.match.scopes],
35513
37096
  returns: "RecipeDetail",
35514
- notes: "PUT /api/v1/recipes/{id}/ingredients/{ingredientId}/match. The ingredient's product becomes that food and the nutrition is worked out again."
37097
+ notes: `${routeOf(recipeOperations.match)}. The ingredient's product becomes that food and the nutrition is worked out again.`
35515
37098
  },
35516
37099
  {
35517
37100
  path: ["agents", "list"],
@@ -35885,7 +37468,7 @@ var COMMAND_SPECS = [
35885
37468
  description: "Filter to one gallery facet (habit|list|routine|area|project|recipe|contact|challenge|automation)."
35886
37469
  }
35887
37470
  ],
35888
- scopes: ["presets:read"],
37471
+ scopes: [...presetOperations.list.scopes],
35889
37472
  returns: "Preset",
35890
37473
  notes: "The catalog is code-defined; --entity filters it client-side."
35891
37474
  },
@@ -35900,7 +37483,7 @@ var COMMAND_SPECS = [
35900
37483
  description: "The day the preset's key dates count from (YYYY-MM-DD), for a preset that names one (the Wedding: the wedding day)."
35901
37484
  }
35902
37485
  ],
35903
- scopes: ["presets:install"],
37486
+ scopes: [...presetOperations.install.scopes],
35904
37487
  returns: "PresetInstallResult",
35905
37488
  notes: "Authors content as the caller, so it needs a key minted while logged in. A preset with a day (`anchor` in `presets list`) needs --on, else the API answers 422."
35906
37489
  },
@@ -36112,16 +37695,16 @@ var COMMAND_SPECS = [
36112
37695
  { name: "line", required: false, description: "The purpose, in one line (words join)." }
36113
37696
  ],
36114
37697
  flags: [{ name: "clear", type: "boolean", description: "Remove the purpose." }],
36115
- scopes: ["areas:write"],
37698
+ scopes: [...areaOperations.update.scopes],
36116
37699
  returns: "AreaView",
36117
- notes: "PATCH /api/v1/areas/:id { purpose }. It shows under the Area's name on the hub."
37700
+ notes: `${routeOf(areaOperations.update)} { purpose }. It shows under the Area's name on the hub.`
36118
37701
  },
36119
37702
  {
36120
37703
  path: ["areas", "standards", "list"],
36121
37704
  summary: "List an Area's standards with how each is doing (holding / worth a look).",
36122
37705
  args: [{ name: "id", required: true, description: "The Area's id." }],
36123
37706
  flags: [],
36124
- scopes: ["areas:read"],
37707
+ scopes: [...areaOperations.get.scopes],
36125
37708
  returns: "AreaStandardView",
36126
37709
  notes: "Each linked standard says holding or worth a look with the reason in words; an unlinked one has no state (it's checked by hand in review)."
36127
37710
  },
@@ -36133,9 +37716,9 @@ var COMMAND_SPECS = [
36133
37716
  { name: "text", required: true, description: "The standard, a plain sentence (words join)." }
36134
37717
  ],
36135
37718
  flags: STANDARD_LINK_FLAGS,
36136
- scopes: ["areas:write"],
37719
+ scopes: [...areaOperations.addStandard.scopes],
36137
37720
  returns: "AreaDetail",
36138
- notes: "POST /api/v1/areas/:id/standards. The agent adds standards only after the member said yes; the change list keeps Undo."
37721
+ notes: `${routeOf(areaOperations.addStandard)}. The agent adds standards only after the member said yes; the change list keeps Undo.`
36139
37722
  },
36140
37723
  {
36141
37724
  path: ["areas", "standards", "link"],
@@ -36148,9 +37731,9 @@ var COMMAND_SPECS = [
36148
37731
  ...STANDARD_LINK_FLAGS,
36149
37732
  { name: "none", type: "boolean", description: "Unlink it: checked by hand in review." }
36150
37733
  ],
36151
- scopes: ["areas:write"],
37734
+ scopes: [...areaOperations.updateStandard.scopes],
36152
37735
  returns: "AreaDetail",
36153
- notes: "PATCH /api/v1/areas/:id/standards/:standardId { link }."
37736
+ notes: `${routeOf(areaOperations.updateStandard)} { link }.`
36154
37737
  },
36155
37738
  {
36156
37739
  path: ["areas", "standards", "rm"],
@@ -36160,9 +37743,9 @@ var COMMAND_SPECS = [
36160
37743
  { name: "standard", required: true, description: "The standard's id." }
36161
37744
  ],
36162
37745
  flags: [],
36163
- scopes: ["areas:write"],
37746
+ scopes: [...areaOperations.removeStandard.scopes],
36164
37747
  returns: "AreaDetail",
36165
- notes: "DELETE /api/v1/areas/:id/standards/:standardId."
37748
+ notes: `${routeOf(areaOperations.removeStandard)}.`
36166
37749
  },
36167
37750
  {
36168
37751
  path: ["areas", "overview"],
@@ -36175,9 +37758,9 @@ var COMMAND_SPECS = [
36175
37758
  description: "Whose work counts: mine (default; agents' work doesn't), agents or all."
36176
37759
  }
36177
37760
  ],
36178
- scopes: ["areas:read"],
37761
+ scopes: [...areaOperations.overview.scopes],
36179
37762
  returns: "AreaOverview",
36180
- notes: 'GET /api/v1/areas/overview. Words, never a score: "Busier than usual", "Steady", "Quiet for three weeks", "Resting", each with a short reason. The only count is what waits on you. Resting Areas come last.'
37763
+ notes: `${routeOf(areaOperations.overview)}. Words, never a score: "Busier than usual", "Steady", "Quiet for three weeks", "Resting", each with a short reason. The only count is what waits on you. Resting Areas come last.`
36181
37764
  },
36182
37765
  {
36183
37766
  path: ["areas", "rest"],
@@ -36195,36 +37778,36 @@ var COMMAND_SPECS = [
36195
37778
  description: "Pause the Area's habits until then too (they keep going otherwise)."
36196
37779
  }
36197
37780
  ],
36198
- scopes: ["areas:write"],
37781
+ scopes: [...areaOperations.rest.scopes],
36199
37782
  returns: "AreaView",
36200
- notes: "POST /api/v1/areas/{id}/rest { until, habits } (Areas 8). Between active and archived: its attention reads Resting, Focus, Review and filing leave it alone, and its recurring tasks pause. Bring it back early with `do areas back`."
37783
+ notes: `${routeOf(areaOperations.rest)} { until, habits } (Areas 8). Between active and archived: its attention reads Resting, Focus, Review and filing leave it alone, and its recurring tasks pause. Bring it back early with \`do areas back\`.`
36201
37784
  },
36202
37785
  {
36203
37786
  path: ["areas", "back"],
36204
37787
  summary: "Bring a resting Area back today.",
36205
37788
  args: [{ name: "id", required: true, description: "Area ULID." }],
36206
37789
  flags: [],
36207
- scopes: ["areas:write"],
37790
+ scopes: [...areaOperations.back.scopes],
36208
37791
  returns: "AreaView",
36209
- notes: "POST /api/v1/areas/{id}/back (Areas 8). The habits it paused come back too, and the recurring tasks' occurrences that went by while it rested are skipped. Nothing was lost."
37792
+ notes: `${routeOf(areaOperations.back)} (Areas 8). The habits it paused come back too, and the recurring tasks' occurrences that went by while it rested are skipped. Nothing was lost.`
36210
37793
  },
36211
37794
  {
36212
37795
  path: ["areas", "since"],
36213
37796
  summary: "What happened in an Area since you last looked: landed, new, and what seems stuck.",
36214
37797
  args: [{ name: "id", required: true, description: "Area ULID." }],
36215
37798
  flags: [],
36216
- scopes: ["areas:read"],
37799
+ scopes: [...areaOperations.sinceLastLook.scopes],
36217
37800
  returns: "AreaSinceLastLook",
36218
- notes: 'GET /api/v1/areas/{id}/since-last-look (Areas 6). A few of each, named, never tallied; stuck ones say why in words ("Deadline was 12 October").'
37801
+ notes: `${routeOf(areaOperations.sinceLastLook)} (Areas 6). A few of each, named, never tallied; stuck ones say why in words ("Deadline was 12 October").`
36219
37802
  },
36220
37803
  {
36221
37804
  path: ["areas", "looked-at"],
36222
37805
  summary: "Record a look at an Area: its review rhythm starts again from now.",
36223
37806
  args: [{ name: "id", required: true, description: "Area ULID." }],
36224
37807
  flags: [],
36225
- scopes: ["areas:write"],
37808
+ scopes: [...areaOperations.lookedAt.scopes],
36226
37809
  returns: "AreaView",
36227
- notes: "POST /api/v1/areas/{id}/looked-at (Areas 6). The Area leaves Review until its rhythm (weekly, monthly, quarterly) comes round."
37810
+ notes: `${routeOf(areaOperations.lookedAt)} (Areas 6). The Area leaves Review until its rhythm (weekly, monthly, quarterly) comes round.`
36228
37811
  },
36229
37812
  {
36230
37813
  path: ["areas", "focus"],
@@ -36264,9 +37847,9 @@ var COMMAND_SPECS = [
36264
37847
  description: "The Area to file the new Project under (none when left out)."
36265
37848
  }
36266
37849
  ],
36267
- scopes: ["areas:write", "projects:write"],
37850
+ scopes: [...areaOperations.toProject.scopes],
36268
37851
  returns: "AreaToProjectResult",
36269
- notes: "POST /api/v1/areas/{id}/to-project (Areas 8). The Project takes the Area's title; its loose tasks go inside, its Projects, habits, routines and notes move beside it, and the Area is archived. An agent proposes this; only the member runs it."
37852
+ notes: `${routeOf(areaOperations.toProject)} (Areas 8). The Project takes the Area's title; its loose tasks go inside, its Projects, habits, routines and notes move beside it, and the Area is archived. An agent proposes this; only the member runs it.`
36270
37853
  },
36271
37854
  {
36272
37855
  path: ["areas", "undo-make-project"],
@@ -36279,27 +37862,27 @@ var COMMAND_SPECS = [
36279
37862
  description: "The receipt as JSON: { projectId, moved } from `areas make-project`."
36280
37863
  }
36281
37864
  ],
36282
- scopes: ["areas:write", "projects:write"],
37865
+ scopes: [...areaOperations.undoToProject.scopes],
36283
37866
  returns: "AreaView",
36284
- notes: "POST /api/v1/areas/{id}/to-project/undo (Areas 8). Everything comes back; the Project goes."
37867
+ notes: `${routeOf(areaOperations.undoToProject)} (Areas 8). Everything comes back; the Project goes.`
36285
37868
  },
36286
37869
  {
36287
37870
  path: ["house"],
36288
37871
  summary: "The House home: what's broken, upkeep coming up, and the Places.",
36289
37872
  args: [],
36290
37873
  flags: [],
36291
- scopes: ["collections:read"],
37874
+ scopes: [...houseOperations.overview.scopes],
36292
37875
  returns: "HouseOverview",
36293
- notes: "GET /api/v1/house."
37876
+ notes: `${routeOf(houseOperations.overview)}.`
36294
37877
  },
36295
37878
  {
36296
37879
  path: ["house", "asset"],
36297
37880
  summary: "One asset's House page: its Place trail, the parts that fit it, what it's linked to.",
36298
37881
  args: [{ name: "id", required: true, description: "Asset ULID." }],
36299
37882
  flags: [],
36300
- scopes: ["collections:read"],
37883
+ scopes: [...houseOperations.asset.scopes],
36301
37884
  returns: "HouseAsset",
36302
- notes: "GET /api/v1/house/assets/{id}. Upkeep and repairs: `do collection upkeep <id>`."
37885
+ notes: `${routeOf(houseOperations.asset)}. Upkeep and repairs: \`do collection upkeep <id>\`.`
36303
37886
  },
36304
37887
  {
36305
37888
  path: ["house", "part"],
@@ -36309,9 +37892,9 @@ var COMMAND_SPECS = [
36309
37892
  { name: "supply", required: true, description: "Supply (inventory item) ULID." }
36310
37893
  ],
36311
37894
  flags: [],
36312
- scopes: ["collections:write"],
37895
+ scopes: [...houseOperations.linkPart.scopes],
36313
37896
  returns: "HouseAsset",
36314
- notes: "POST /api/v1/house/assets/{id}/parts. Idempotent."
37897
+ notes: `${routeOf(houseOperations.linkPart)}. Idempotent.`
36315
37898
  },
36316
37899
  {
36317
37900
  path: ["house", "unpart"],
@@ -36321,9 +37904,9 @@ var COMMAND_SPECS = [
36321
37904
  { name: "supply", required: true, description: "Supply (inventory item) ULID." }
36322
37905
  ],
36323
37906
  flags: [],
36324
- scopes: ["collections:write"],
37907
+ scopes: [...houseOperations.unlinkPart.scopes],
36325
37908
  returns: "HouseAsset",
36326
- notes: "DELETE /api/v1/house/assets/{id}/parts/{inventoryItemId}."
37909
+ notes: `${routeOf(houseOperations.unlinkPart)}.`
36327
37910
  },
36328
37911
  {
36329
37912
  path: ["house", "lend"],
@@ -36338,9 +37921,9 @@ var COMMAND_SPECS = [
36338
37921
  { name: "due", type: "string", description: "Due back, YYYY-MM-DD." },
36339
37922
  { name: "note", type: "string", description: "A note, e.g. return with a full tank." }
36340
37923
  ],
36341
- scopes: ["collections:write"],
37924
+ scopes: [...houseOperations.lend.scopes],
36342
37925
  returns: "CollectionItemDetail",
36343
- notes: "POST /api/v1/house/assets/{id}/lend. Logs a lent Entry; 409 while it's already out."
37926
+ notes: `${routeOf(houseOperations.lend)}. Logs a lent Entry; 409 while it's already out.`
36344
37927
  },
36345
37928
  {
36346
37929
  path: ["house", "return"],
@@ -36350,18 +37933,18 @@ var COMMAND_SPECS = [
36350
37933
  { name: "on", type: "string", description: "YYYY-MM-DD (default today)." },
36351
37934
  { name: "note", type: "string", description: "A note." }
36352
37935
  ],
36353
- scopes: ["collections:write"],
37936
+ scopes: [...houseOperations.giveBack.scopes],
36354
37937
  returns: "CollectionItemDetail",
36355
- notes: "POST /api/v1/house/assets/{id}/return. 409 when it isn't out."
37938
+ notes: `${routeOf(houseOperations.giveBack)}. 409 when it isn't out.`
36356
37939
  },
36357
37940
  {
36358
37941
  path: ["house", "value"],
36359
37942
  summary: "What's in the house: each thing's value and proof, the sums, the home policy.",
36360
37943
  args: [],
36361
37944
  flags: [],
36362
- scopes: ["collections:read"],
37945
+ scopes: [...houseOperations.value.scopes],
36363
37946
  returns: "HouseValue",
36364
- notes: "GET /api/v1/house/value. Things marked insure: false are listed but left out of the sums."
37947
+ notes: `${routeOf(houseOperations.value)}. Things marked insure: false are listed but left out of the sums.`
36365
37948
  },
36366
37949
  {
36367
37950
  path: ["house", "year"],
@@ -36370,9 +37953,9 @@ var COMMAND_SPECS = [
36370
37953
  flags: [
36371
37954
  { name: "year", type: "string", description: "The year, e.g. 2026 (default this one)." }
36372
37955
  ],
36373
- scopes: ["collections:read"],
37956
+ scopes: [...houseOperations.year.scopes],
36374
37957
  returns: "HouseUpkeepYear",
36375
- notes: "GET /api/v1/house/upkeep. Each routine has twelve marks (done/due/planned/late/null), January first."
37958
+ notes: `${routeOf(houseOperations.year)}. Each routine has twelve marks (done/due/planned/late/null), January first.`
36376
37959
  },
36377
37960
  {
36378
37961
  path: ["house", "file"],
@@ -36386,9 +37969,9 @@ var COMMAND_SPECS = [
36386
37969
  description: 'JSON FileAssetInput, e.g. {"facet":{"brand":"Acme","model":"D-100"},"fileIds":[\u2026],"upkeep":{"title":"Clean the filter","schedule":{"every":4,"unit":"week"}}}.'
36387
37970
  }
36388
37971
  ],
36389
- scopes: ["collections:write", "tasks:write"],
37972
+ scopes: [...houseOperations.fileAsset.scopes],
36390
37973
  returns: "HouseWriteResult",
36391
- notes: "POST /api/v1/house/assets (asset_file). Returns { item, receipt }; `do house undo --data '<receipt>'` takes it back."
37974
+ notes: `${routeOf(houseOperations.fileAsset)} (asset_file). Returns { item, receipt }; \`do house undo --data '<receipt>'\` takes it back.`
36392
37975
  },
36393
37976
  {
36394
37977
  path: ["house", "upkeep"],
@@ -36404,18 +37987,18 @@ var COMMAND_SPECS = [
36404
37987
  },
36405
37988
  { name: "start", type: "string", description: "First due day (YYYY-MM-DD)." }
36406
37989
  ],
36407
- scopes: ["collections:write", "tasks:write"],
37990
+ scopes: [...houseOperations.setUpkeep.scopes],
36408
37991
  returns: "HouseWriteResult",
36409
- notes: "POST /api/v1/house/assets/{id}/upkeep (asset_upkeep_set)."
37992
+ notes: `${routeOf(houseOperations.setUpkeep)} (asset_upkeep_set).`
36410
37993
  },
36411
37994
  {
36412
37995
  path: ["house", "undo"],
36413
37996
  summary: "Undo `house file` / `house upkeep` from its receipt.",
36414
37997
  args: [],
36415
37998
  flags: [{ name: "data", type: "string", description: "The receipt as JSON." }],
36416
- scopes: ["collections:write", "tasks:write"],
37999
+ scopes: [...houseOperations.undo.scopes],
36417
38000
  returns: "{ itemId, undone }",
36418
- notes: "POST /api/v1/house/undo."
38001
+ notes: `${routeOf(houseOperations.undo)}.`
36419
38002
  },
36420
38003
  {
36421
38004
  path: ["money", "accounts"],
@@ -37054,9 +38637,9 @@ var COMMAND_SPECS = [
37054
38637
  summary: "The Animals home: eggs today, feed days left, daily care, and what's coming up.",
37055
38638
  args: [],
37056
38639
  flags: [],
37057
- scopes: ["animals:read"],
38640
+ scopes: [...animalOperations.overview.scopes],
37058
38641
  returns: "AnimalsOverview",
37059
- notes: "GET /api/v1/animals/overview. The animals still here, today's eggs per flock against its heads (last 7 days), the feed supplies with days left, the linked daily-care Habits, and every animal's key dates from today on."
38642
+ notes: `${routeOf(animalOperations.overview)}. The animals still here, today's eggs per flock against its heads (last 7 days), the feed supplies with days left, the linked daily-care Habits, and every animal's key dates from today on.`
37060
38643
  },
37061
38644
  {
37062
38645
  path: ["animals", "handover"],
@@ -37069,45 +38652,45 @@ var COMMAND_SPECS = [
37069
38652
  description: "An `away` period's id; the next trip when left out."
37070
38653
  }
37071
38654
  ],
37072
- scopes: ["animals:read"],
38655
+ scopes: [...animalOperations.handover.scopes],
37073
38656
  returns: "AnimalHandover",
37074
- notes: "GET /api/v1/animals/handover. The trip (an `away` period, else the Space's Away until \u2026), each animal still here with the steps from its attached note, the vets to call, whether the feed lasts, and whether the daily-care Habits rest. Never a chip or certificate number."
38657
+ notes: `${routeOf(animalOperations.handover)}. The trip (an \`away\` period, else the Space's Away until \u2026), each animal still here with the steps from its attached note, the vets to call, whether the feed lasts, and whether the daily-care Habits rest. Never a chip or certificate number.`
37075
38658
  },
37076
38659
  {
37077
38660
  path: ["animals", "species"],
37078
38661
  summary: "List the species profiles (the built-in set arrives on first use).",
37079
38662
  args: [],
37080
38663
  flags: [],
37081
- scopes: ["animals:read"],
38664
+ scopes: [...animalOperations.listSpecies.scopes],
37082
38665
  returns: "AnimalSpecies[]",
37083
- notes: "GET /api/v1/animals/species."
38666
+ notes: `${routeOf(animalOperations.listSpecies)}.`
37084
38667
  },
37085
38668
  {
37086
38669
  path: ["animals", "species-add"],
37087
38670
  summary: "Add a species profile of your own.",
37088
38671
  args: [],
37089
38672
  flags: [{ name: "data", type: "string", description: "CreateAnimalSpeciesInput JSON." }],
37090
- scopes: ["animals:write"],
38673
+ scopes: [...animalOperations.createSpecies.scopes],
37091
38674
  returns: "AnimalSpecies",
37092
- notes: "POST /api/v1/animals/species. `key` is a lowercase slug, unique per Space."
38675
+ notes: `${routeOf(animalOperations.createSpecies)}. \`key\` is a lowercase slug, unique per Space.`
37093
38676
  },
37094
38677
  {
37095
38678
  path: ["animals", "species-update"],
37096
38679
  summary: "Edit a species profile.",
37097
38680
  args: [{ name: "id", required: true, description: "Species ULID." }],
37098
38681
  flags: [{ name: "data", type: "string", description: "UpdateAnimalSpeciesInput JSON." }],
37099
- scopes: ["animals:write"],
38682
+ scopes: [...animalOperations.updateSpecies.scopes],
37100
38683
  returns: "AnimalSpecies",
37101
- notes: "PATCH /api/v1/animals/species/{id}."
38684
+ notes: `${routeOf(animalOperations.updateSpecies)}.`
37102
38685
  },
37103
38686
  {
37104
38687
  path: ["animals", "timeline"],
37105
38688
  summary: "An animal's history, newest first (for a group, its members' too).",
37106
38689
  args: [{ name: "id", required: true, description: "Animal ULID." }],
37107
38690
  flags: [{ name: "days", type: "string", description: "How far back (default 90)." }],
37108
- scopes: ["animals:read"],
38691
+ scopes: [...animalOperations.timeline.scopes],
37109
38692
  returns: "AnimalTimelineEntry[]",
37110
- notes: "GET /api/v1/animals/{id}/timeline. Weights (g), lengths (mm), egg counts and notes, periods starting and ending, and key dates that came round."
38693
+ notes: `${routeOf(animalOperations.timeline)}. Weights (g), lengths (mm), egg counts and notes, periods starting and ending, and key dates that came round.`
37111
38694
  },
37112
38695
  {
37113
38696
  path: ["animals", "log"],
@@ -37125,9 +38708,9 @@ var COMMAND_SPECS = [
37125
38708
  { name: "unit", type: "string", description: "For a weight: g (default) or kg." },
37126
38709
  { name: "at", type: "string", description: "When (ISO 8601); default now." }
37127
38710
  ],
37128
- scopes: ["animals:write"],
38711
+ scopes: [...animalOperations.log.scopes],
37129
38712
  returns: "AnimalTimelineEntry",
37130
- notes: 'POST /api/v1/animals/{id}/log (`animal_log`). `do animals log <hens> eggs 2`, `do animals log <dog> weight 37.8 --unit kg`, `do animals log <hen> note "eating again"`. Eggs are the day\'s count: logging again that day replaces it.'
38713
+ notes: `${routeOf(animalOperations.log)} (\`animal_log\`). \`do animals log <hens> eggs 2\`, \`do animals log <dog> weight 37.8 --unit kg\`, \`do animals log <hen> note "eating again"\`. Eggs are the day's count: logging again that day replaces it.`
37131
38714
  },
37132
38715
  {
37133
38716
  path: ["animals", "vet-visit"],
@@ -37140,18 +38723,18 @@ var COMMAND_SPECS = [
37140
38723
  description: "JSON: { vetContactId?, clinician?, reason?, vaccine?: { name, source? }, dewormer?, weight?: { value, unit? }, nextDates?: [{ label, date }], files?: [{ id, name? }], cost?: { amount, currency? }, at? }. The visit's and each date's ids are minted."
37141
38724
  }
37142
38725
  ],
37143
- scopes: ["animals:write"],
38726
+ scopes: [...animalOperations.logVetVisit.scopes],
37144
38727
  returns: "AnimalVetVisit",
37145
- notes: 'POST /api/v1/animals/{id}/vet-visits (`animal_vet_visit`, mockup 03). The visit is the receipt: post it with `receipt: { nodeId: <animal id>, about: "vet_visit", parts: [<visit id>] }`. `do animals vet-visit <dog> --data \'{"reason":"booster","vaccine":{"name":"DHPPi + L4","source":"sticker"},"weight":{"value":37.8}}\'`.'
38728
+ notes: `${routeOf(animalOperations.logVetVisit)} (\`animal_vet_visit\`, mockup 03). The visit is the receipt: post it with \`receipt: { nodeId: <animal id>, about: "vet_visit", parts: [<visit id>] }\`. \`do animals vet-visit <dog> --data '{"reason":"booster","vaccine":{"name":"DHPPi + L4","source":"sticker"},"weight":{"value":37.8}}'\`.`
37146
38729
  },
37147
38730
  {
37148
38731
  path: ["animals", "vet-visit-get"],
37149
38732
  summary: "Read a vet visit as its receipt reads.",
37150
38733
  args: [{ name: "visitId", required: true, description: "Vet visit ULID." }],
37151
38734
  flags: [],
37152
- scopes: ["animals:read"],
38735
+ scopes: [...animalOperations.vetVisit.scopes],
37153
38736
  returns: "AnimalVetVisit",
37154
- notes: "GET /api/v1/animals/vet-visits/{visitId}."
38737
+ notes: `${routeOf(animalOperations.vetVisit)}.`
37155
38738
  },
37156
38739
  {
37157
38740
  path: ["animals", "vet-visit-edit"],
@@ -37164,27 +38747,27 @@ var COMMAND_SPECS = [
37164
38747
  description: "JSON: any of vetContactId, clinician, reason, vaccine, dewormer, cost (null clears one)."
37165
38748
  }
37166
38749
  ],
37167
- scopes: ["animals:write"],
38750
+ scopes: [...animalOperations.patchVetVisit.scopes],
37168
38751
  returns: "AnimalVetVisit",
37169
- notes: "PATCH /api/v1/animals/vet-visits/{visitId}. The newest version wins."
38752
+ notes: `${routeOf(animalOperations.patchVetVisit)}. The newest version wins.`
37170
38753
  },
37171
38754
  {
37172
38755
  path: ["animals", "vet-visit-undo"],
37173
38756
  summary: "Take a vet visit back (the receipt's Undo).",
37174
38757
  args: [{ name: "visitId", required: true, description: "Vet visit ULID." }],
37175
38758
  flags: [],
37176
- scopes: ["animals:write"],
38759
+ scopes: [...animalOperations.undoVetVisit.scopes],
37177
38760
  returns: "AnimalVetVisit",
37178
- notes: "POST /api/v1/animals/vet-visits/{visitId}/undo. The visit, its weight, its key dates and the filed photos all go back."
38761
+ notes: `${routeOf(animalOperations.undoVetVisit)}. The visit, its weight, its key dates and the filed photos all go back.`
37179
38762
  },
37180
38763
  {
37181
38764
  path: ["animals", "lost-card"],
37182
38765
  summary: "An animal's lost-pet card: on or off, its link, and what it could show.",
37183
38766
  args: [{ name: "id", required: true, description: "Animal ULID." }],
37184
38767
  flags: [],
37185
- scopes: ["animals:read"],
38768
+ scopes: [...animalOperations.lostCard.scopes],
37186
38769
  returns: "AnimalLostCardState",
37187
- notes: "GET /api/v1/animals/{id}/lost-card. `card` is null while it's off; else its `path` (`/lost/<token>`, the page a QR code opens), what it shows and `preview` (the finder's page). `photos` are the pictures on the animal it may show; `chipOnFile` says whether it can say chipped."
38770
+ notes: `${routeOf(animalOperations.lostCard)}. \`card\` is null while it's off; else its \`path\` (\`/lost/<token>\`, the page a QR code opens), what it shows and \`preview\` (the finder's page). \`photos\` are the pictures on the animal it may show; \`chipOnFile\` says whether it can say chipped.`
37188
38771
  },
37189
38772
  {
37190
38773
  path: ["animals", "lost-card-set"],
@@ -37197,27 +38780,27 @@ var COMMAND_SPECS = [
37197
38780
  description: 'JSON: `{ "phone": "+32 470 \u2026", "photoFileId": "\u2026", "showChipped": true, "note": "\u2026" }`; null takes a field off.'
37198
38781
  }
37199
38782
  ],
37200
- scopes: ["animals:write"],
38783
+ scopes: [...animalOperations.setLostCard.scopes],
37201
38784
  returns: "AnimalLostCardState",
37202
- notes: "PUT /api/v1/animals/{id}/lost-card. An individual still with the household (a lost one included). The name always shows; the photo must be a picture on the animal; `showChipped` needs its chip on file and never shows the number. The link stays the same while the card is on."
38785
+ notes: `${routeOf(animalOperations.setLostCard)}. An individual still with the household (a lost one included). The name always shows; the photo must be a picture on the animal; \`showChipped\` needs its chip on file and never shows the number. The link stays the same while the card is on.`
37203
38786
  },
37204
38787
  {
37205
38788
  path: ["animals", "lost-card-off"],
37206
38789
  summary: "Turn an animal's lost-pet card off (its link stops working).",
37207
38790
  args: [{ name: "id", required: true, description: "Animal ULID." }],
37208
38791
  flags: [],
37209
- scopes: ["animals:write"],
38792
+ scopes: [...animalOperations.removeLostCard.scopes],
37210
38793
  returns: "void",
37211
- notes: "DELETE /api/v1/animals/{id}/lost-card. Destructive: a printed QR stops working, so it needs --allow-destructive. Turning it on again makes a new link."
38794
+ notes: `${routeOf(animalOperations.removeLostCard)}. Destructive: a printed QR stops working, so it needs --allow-destructive. Turning it on again makes a new link.`
37212
38795
  },
37213
38796
  {
37214
38797
  path: ["animals", "acquisition"],
37215
38798
  summary: "Bringing a planned animal home: its checklist.",
37216
38799
  args: [{ name: "id", required: true, description: "Animal ULID." }],
37217
38800
  flags: [],
37218
- scopes: ["animals:read"],
38801
+ scopes: [...animalOperations.acquisition.scopes],
37219
38802
  returns: "AnimalAcquisition",
37220
- notes: 'GET /api/v1/animals/{id}/acquisition. The Task "Bring <name> home", its steps in order, `blockers` to sort before the pick-up (papers its kind needs that aren\'t on file) and `arrived`; 404 when none was planned.'
38803
+ notes: `${routeOf(animalOperations.acquisition)}. The Task "Bring <name> home", its steps in order, \`blockers\` to sort before the pick-up (papers its kind needs that aren't on file) and \`arrived\`; 404 when none was planned.`
37221
38804
  },
37222
38805
  {
37223
38806
  path: ["animals", "acquisition-plan"],
@@ -37235,9 +38818,9 @@ var COMMAND_SPECS = [
37235
38818
  description: 'JSON: `{ "quarantine": true, "extraSteps": ["Buy a UV lamp"] }`.'
37236
38819
  }
37237
38820
  ],
37238
- scopes: ["animals:write"],
38821
+ scopes: [...animalOperations.planAcquisition.scopes],
37239
38822
  returns: "AnimalAcquisition",
37240
- notes: "POST /api/v1/animals/{id}/acquisition. The steps: the papers its kind needs (DogID, CatID, CITES A, closed ring), the vet check, its home, and the quarantine before it meets the others (on by default for a reptile or a bird). Only a planned animal; one per animal (409)."
38823
+ notes: `${routeOf(animalOperations.planAcquisition)}. The steps: the papers its kind needs (DogID, CatID, CITES A, closed ring), the vet check, its home, and the quarantine before it meets the others (on by default for a reptile or a bird). Only a planned animal; one per animal (409).`
37241
38824
  },
37242
38825
  {
37243
38826
  path: ["animals", "arrived"],
@@ -37255,9 +38838,9 @@ var COMMAND_SPECS = [
37255
38838
  description: "How long it stays apart (0\u201312); 3 when its checklist has the quarantine step."
37256
38839
  }
37257
38840
  ],
37258
- scopes: ["animals:write"],
38841
+ scopes: [...animalOperations.arrived.scopes],
37259
38842
  returns: "AnimalDetail",
37260
- notes: "POST /api/v1/animals/{id}/arrived. Sets acquiredOn and the status (`quarantine` with a Period, else `active`); open steps stay open."
38843
+ notes: `${routeOf(animalOperations.arrived)}. Sets acquiredOn and the status (\`quarantine\` with a Period, else \`active\`); open steps stay open.`
37261
38844
  },
37262
38845
  {
37263
38846
  path: ["runs", "change"],
@@ -37746,7 +39329,7 @@ var COMMAND_SPECS = [
37746
39329
  summary: "The guest list page: households, head count per part, deadlines, roles.",
37747
39330
  args: [{ name: "id", required: true, description: "The guest list's id." }],
37748
39331
  flags: [],
37749
- scopes: ["contacts:read"],
39332
+ scopes: [...guestListOperations.overview.scopes],
37750
39333
  returns: "GuestListOverview",
37751
39334
  notes: "A plus-one is one more head, following the guest's answer. keyDates are the project's (the RSVP deadline, save-the-dates, the civil notice window)."
37752
39335
  },
@@ -37808,7 +39391,7 @@ var COMMAND_SPECS = [
37808
39391
  summary: "A guest list's proposed seating plans, newest first.",
37809
39392
  args: [],
37810
39393
  flags: [{ name: "list", type: "string", description: "The guest list's id (required)." }],
37811
- scopes: ["contacts:read"],
39394
+ scopes: [...seatingProposalOperations.list.scopes],
37812
39395
  returns: "SeatingProposal"
37813
39396
  },
37814
39397
  {
@@ -37822,7 +39405,7 @@ var COMMAND_SPECS = [
37822
39405
  description: 'JSON CreateSeatingProposalInput, e.g. {"guestListId":"01\u2026","tables":[{"name":"1","seats":8,"guestIds":["01\u2026"]}],"note":"Households together."}'
37823
39406
  }
37824
39407
  ],
37825
- scopes: ["contacts:write"],
39408
+ scopes: [...seatingProposalOperations.propose.scopes],
37826
39409
  returns: "SeatingProposal",
37827
39410
  notes: "Keep households together and circles near each other. Each guest at one table; a table's heads (a plus-one counts) fit its seats. Read `do guest-lists overview` first for households and current tables."
37828
39411
  },
@@ -37831,7 +39414,7 @@ var COMMAND_SPECS = [
37831
39414
  summary: "Apply a proposed plan: the room's tables become its tables.",
37832
39415
  args: [{ name: "id", required: true, description: "The proposal's id." }],
37833
39416
  flags: [],
37834
- scopes: ["contacts:write"],
39417
+ scopes: [...seatingProposalOperations.apply.scopes],
37835
39418
  returns: "SeatingProposal"
37836
39419
  },
37837
39420
  {
@@ -37839,7 +39422,7 @@ var COMMAND_SPECS = [
37839
39422
  summary: "Set a proposed plan aside.",
37840
39423
  args: [{ name: "id", required: true, description: "The proposal's id." }],
37841
39424
  flags: [],
37842
- scopes: ["contacts:write"],
39425
+ scopes: [...seatingProposalOperations.dismiss.scopes],
37843
39426
  returns: "SeatingProposal"
37844
39427
  },
37845
39428
  {
@@ -37902,7 +39485,7 @@ var COMMAND_SPECS = [
37902
39485
  summary: "The gifts for the event: the cards still to send, then the ones sent.",
37903
39486
  args: [{ name: "id", required: true, description: "The guest list's id." }],
37904
39487
  flags: [],
37905
- scopes: ["contacts:read"],
39488
+ scopes: [...guestListOperations.thankYou.scopes],
37906
39489
  returns: "ThankYouView",
37907
39490
  notes: 'Log a gift as a gift_received moment with sourceNodeId = the project: `do contact-moments create --data \'{"contactId":"<id>","kind":"gift_received","summary":"a moka pot","sourceNodeId":"<project>"}\'`. Mark the card sent: `do contact-moments update <id> --data \'{"thankedAt":"2027-07-10"}\'`.'
37908
39491
  },
@@ -37922,7 +39505,7 @@ var COMMAND_SPECS = [
37922
39505
  description: "Where to save a PDF (default: the name the API gives it)."
37923
39506
  }
37924
39507
  ],
37925
- scopes: ["contacts:read"],
39508
+ scopes: [...guestListOperations.export.scopes],
37926
39509
  returns: "string",
37927
39510
  notes: "A CSV goes to stdout (`> tables.csv`); a PDF is saved: `do guest-lists export <id> --kind invitations --out invitations.pdf`. Mint the links first (`do rsvp-links create`); the QR opens the list's `siteUrl` + /i/<token>, and its `sitePassword` is printed only when set."
37928
39511
  },
@@ -37931,7 +39514,7 @@ var COMMAND_SPECS = [
37931
39514
  summary: "A guest list's RSVP links, one per household (never the tokens).",
37932
39515
  args: [],
37933
39516
  flags: [{ name: "list", type: "string", description: "The guest list's id (required)." }],
37934
- scopes: ["contacts:read"],
39517
+ scopes: [...rsvpLinkOperations.list.scopes],
37935
39518
  returns: "RsvpLink"
37936
39519
  },
37937
39520
  {
@@ -37945,7 +39528,7 @@ var COMMAND_SPECS = [
37945
39528
  description: 'JSON CreateRsvpLinksInput, e.g. {"guestListId":"01\u2026"} (every household without a live link), "mode":"address" for the save-the-date (address only), or with "householdIds":["01\u2026"] (re-mints, revoking the old link; prefer `do rsvp-links replace`).'
37946
39529
  }
37947
39530
  ],
37948
- scopes: ["contacts:write"],
39531
+ scopes: [...rsvpLinkOperations.mint.scopes],
37949
39532
  returns: "MintedRsvpLink",
37950
39533
  notes: "A guest in no household gets a household of one first. A link stops at the end of the RSVP deadline day (or after 120 days without one) unless expiresAt says otherwise. Send each household its own `path` on the web app."
37951
39534
  },
@@ -37954,7 +39537,7 @@ var COMMAND_SPECS = [
37954
39537
  summary: "Show a live RSVP link again: the same token it was minted with, and a whole-form link's short code.",
37955
39538
  args: [{ name: "id", required: true, description: "The link's id." }],
37956
39539
  flags: [],
37957
- scopes: ["contacts:write"],
39540
+ scopes: [...rsvpLinkOperations.reveal.scopes],
37958
39541
  returns: "MintedRsvpLink",
37959
39542
  notes: "The mail and the paper QR carry this one link; `shortCode` (six characters) is printed on the paper invitation and typed at the site's /code. A revoked or expired link is gone; one minted before tokens were kept can't be shown: replace it."
37960
39543
  },
@@ -37963,7 +39546,7 @@ var COMMAND_SPECS = [
37963
39546
  summary: "Replace an RSVP link: a new token, and the old link (and its QR) stops working.",
37964
39547
  args: [{ name: "id", required: true, description: "The link's id." }],
37965
39548
  flags: [],
37966
- scopes: ["contacts:write"],
39549
+ scopes: [...rsvpLinkOperations.replace.scopes],
37967
39550
  returns: "MintedRsvpLink"
37968
39551
  },
37969
39552
  {
@@ -37971,7 +39554,7 @@ var COMMAND_SPECS = [
37971
39554
  summary: "Stop one RSVP link working.",
37972
39555
  args: [{ name: "id", required: true, description: "The link's id." }],
37973
39556
  flags: [],
37974
- scopes: ["contacts:write"],
39557
+ scopes: [...rsvpLinkOperations.revoke.scopes],
37975
39558
  returns: "RsvpLink"
37976
39559
  },
37977
39560
  {
@@ -40570,38 +42153,40 @@ var HANDLERS = {
40570
42153
  render: renderReview
40571
42154
  }),
40572
42155
  progress: async ({ flags, requireClient }) => ({
40573
- data: await requireClient().get("/progress", { query: { days: flagString(flags, "days") } }),
42156
+ data: await requireClient().get(pathOf(progressOperations.get), {
42157
+ query: { days: flagString(flags, "days") }
42158
+ }),
40574
42159
  render: renderProgress
40575
42160
  }),
40576
42161
  "progress habits": async ({ requireClient }) => ({
40577
- data: await requireClient().get("/progress/habits"),
42162
+ data: await requireClient().get(pathOf(progressOperations.habits)),
40578
42163
  render: renderProgressHabits
40579
42164
  }),
40580
42165
  "progress commits": async ({ flags, requireClient }) => {
40581
42166
  const days = flagString(flags, "days");
40582
42167
  const query = parseInput(githubCommitsQuerySchema, days ? { days } : {});
40583
42168
  return {
40584
- data: await requireClient().get("/progress/commits", {
42169
+ data: await requireClient().get(pathOf(progressOperations.commits), {
40585
42170
  query: { days: String(query.days) }
40586
42171
  }),
40587
42172
  render: renderRecord
40588
42173
  };
40589
42174
  },
40590
42175
  "progress questions": async ({ requireClient }) => ({
40591
- data: await requireClient().get("/progress/questions"),
42176
+ data: await requireClient().get(pathOf(progressOperations.questions)),
40592
42177
  render: renderInsightsAnswers
40593
42178
  }),
40594
42179
  "progress dismiss": async ({ args, requireClient }) => {
40595
42180
  const question = parseInput(insightsQuestionSchema, requireArg(args, 0, "question"));
40596
42181
  return {
40597
- data: await requireClient().post(`/progress/questions/${question}/dismiss`, {}),
42182
+ data: await requireClient().post(pathOf(progressOperations.dismiss, { question }), {}),
40598
42183
  render: renderInsightsAnswers
40599
42184
  };
40600
42185
  },
40601
42186
  "progress restore": async ({ args, requireClient }) => {
40602
42187
  const question = parseInput(insightsQuestionSchema, requireArg(args, 0, "question"));
40603
42188
  return {
40604
- data: await requireClient().post(`/progress/questions/${question}/restore`, {}),
42189
+ data: await requireClient().post(pathOf(progressOperations.restore, { question }), {}),
40605
42190
  render: renderInsightsAnswers
40606
42191
  };
40607
42192
  },
@@ -40612,7 +42197,10 @@ var HANDLERS = {
40612
42197
  ...at ? { occurredAt: at } : {},
40613
42198
  ...channel ? { channel } : {}
40614
42199
  });
40615
- return { data: await requireClient().post("/progress/activity", input), render: renderRecord };
42200
+ return {
42201
+ data: await requireClient().post(pathOf(progressOperations.activity), input),
42202
+ render: renderRecord
42203
+ };
40616
42204
  },
40617
42205
  "progress day": async ({ args, requireClient }) => {
40618
42206
  const date = requireArg(args, 0, "date");
@@ -40620,7 +42208,7 @@ var HANDLERS = {
40620
42208
  throw usageError("progress day needs a date as YYYY-MM-DD, e.g. `do progress day 2026-10-15`");
40621
42209
  }
40622
42210
  return {
40623
- data: await requireClient().get(`/progress/days/${date}`),
42211
+ data: await requireClient().get(pathOf(progressOperations.day, { date })),
40624
42212
  render: renderProgressDay
40625
42213
  };
40626
42214
  },
@@ -41099,7 +42687,7 @@ var HANDLERS = {
41099
42687
  if (q.length < 2)
41100
42688
  throw usageError('foods search needs a name, e.g. `do foods search "gouda"`');
41101
42689
  return {
41102
- data: await requireClient().get("/foods/search", {
42690
+ data: await requireClient().get(pathOf(foodOperations.search), {
41103
42691
  query: {
41104
42692
  q,
41105
42693
  limit: flagString(flags, "limit"),
@@ -41111,11 +42699,17 @@ var HANDLERS = {
41111
42699
  },
41112
42700
  "foods barcode": async ({ args, requireClient }) => {
41113
42701
  const ean = requireArg(args, 0, "barcode");
41114
- return { data: await requireClient().get(`/foods/barcode/${ean}`), render: renderRecord };
42702
+ return {
42703
+ data: await requireClient().get(pathOf(foodOperations.barcode, { ean })),
42704
+ render: renderRecord
42705
+ };
41115
42706
  },
41116
42707
  "foods get": async ({ args, requireClient }) => {
41117
42708
  const id = requireArg(args, 0, "food id");
41118
- return { data: await requireClient().get(`/foods/${id}`), render: renderRecord };
42709
+ return {
42710
+ data: await requireClient().get(pathOf(foodOperations.get, { id })),
42711
+ render: renderRecord
42712
+ };
41119
42713
  },
41120
42714
  "documents file": async ({ args, flags, requireClient }) => {
41121
42715
  if (flagString(flags, "data") === undefined)
@@ -41262,10 +42856,13 @@ var HANDLERS = {
41262
42856
  if (flagString(flags, "data") === undefined)
41263
42857
  throw usageError("food log needs --data");
41264
42858
  const body = parseInput(createMealLogInputSchema, { id: ulid3(), ...readDataFlag(flags) });
41265
- return { data: await requireClient().post("/food/logs", body), render: renderRecord };
42859
+ return {
42860
+ data: await requireClient().post(pathOf(foodLogOperations.create), body),
42861
+ render: renderRecord
42862
+ };
41266
42863
  },
41267
42864
  "food list": async ({ flags, requireClient }) => ({
41268
- data: await requireClient().get("/food/logs", {
42865
+ data: await requireClient().get(pathOf(foodLogOperations.list), {
41269
42866
  query: {
41270
42867
  from: requireFlag(flags, "from", "food list needs --from <ISO instant>"),
41271
42868
  to: requireFlag(flags, "to", "food list needs --to <ISO instant>")
@@ -41275,19 +42872,25 @@ var HANDLERS = {
41275
42872
  }),
41276
42873
  "food get": async ({ args, requireClient }) => {
41277
42874
  const id = requireArg(args, 0, "meal id");
41278
- return { data: await requireClient().get(`/food/logs/${id}`), render: renderRecord };
42875
+ return {
42876
+ data: await requireClient().get(pathOf(foodLogOperations.get, { id })),
42877
+ render: renderRecord
42878
+ };
41279
42879
  },
41280
42880
  "food context": async ({ args, requireClient }) => {
41281
42881
  const id = requireArg(args, 0, "meal id");
41282
42882
  return {
41283
- data: await requireClient().get(`/food/logs/${id}/context`),
42883
+ data: await requireClient().get(pathOf(foodLogOperations.context, { id })),
41284
42884
  render: renderRecord
41285
42885
  };
41286
42886
  },
41287
42887
  "food week": async ({ flags, requireClient }) => {
41288
42888
  const start = flagString(flags, "start");
41289
42889
  const query = start ? `?start=${encodeURIComponent(start)}` : "";
41290
- return { data: await requireClient().get(`/food/week${query}`), render: renderRecord };
42890
+ return {
42891
+ data: await requireClient().get(`${pathOf(foodLogOperations.week)}${query}`),
42892
+ render: renderRecord
42893
+ };
41291
42894
  },
41292
42895
  "food week-read": async ({ args, flags, requireClient }) => {
41293
42896
  const start = requireArg(args, 0, "a day of the week");
@@ -41295,15 +42898,18 @@ var HANDLERS = {
41295
42898
  start,
41296
42899
  body: flagString(flags, "body")
41297
42900
  });
41298
- return { data: await requireClient().put("/food/week/read", input), render: renderRecord };
42901
+ return {
42902
+ data: await requireClient().put(pathOf(foodLogOperations.writeWeekRead), input),
42903
+ render: renderRecord
42904
+ };
41299
42905
  },
41300
42906
  "food week-read-remove": async ({ args, requireClient }) => {
41301
42907
  const start = requireArg(args, 0, "a day of the week");
41302
- await requireClient().delete(`/food/week/read?start=${encodeURIComponent(start)}`);
42908
+ await requireClient().delete(`${pathOf(foodLogOperations.removeWeekRead)}?start=${encodeURIComponent(start)}`);
41303
42909
  return { data: { ok: true, removed: start }, render: renderRecord };
41304
42910
  },
41305
42911
  "food patterns": async ({ requireClient }) => ({
41306
- data: await requireClient().get("/food/patterns"),
42912
+ data: await requireClient().get(pathOf(foodLogOperations.patterns)),
41307
42913
  render: renderRecord
41308
42914
  }),
41309
42915
  "food trial": async ({ args, flags, requireClient }) => {
@@ -41315,11 +42921,14 @@ var HANDLERS = {
41315
42921
  startsOn: flagString(flags, "starts-on"),
41316
42922
  ...days === undefined ? {} : { days: Number(days) }
41317
42923
  });
41318
- return { data: await requireClient().post("/food/trials", input), render: renderRecord };
42924
+ return {
42925
+ data: await requireClient().post(pathOf(foodLogOperations.proposeTrial), input),
42926
+ render: renderRecord
42927
+ };
41319
42928
  },
41320
42929
  "food trial-stop": async ({ args, requireClient }) => {
41321
42930
  const id = requireArg(args, 0, "a trial id");
41322
- await requireClient().post(`/food/trials/${encodeURIComponent(id)}/stop`, {});
42931
+ await requireClient().post(pathOf(foodLogOperations.stopTrial, { id }), {});
41323
42932
  return { data: { ok: true, stopped: id }, render: renderRecord };
41324
42933
  },
41325
42934
  "food correct": async ({ args, flags, requireClient }) => {
@@ -41327,31 +42936,34 @@ var HANDLERS = {
41327
42936
  if (flagString(flags, "data") === undefined)
41328
42937
  throw usageError("food correct needs --data");
41329
42938
  const body = parseInput(correctMealLogInputSchema, readDataFlag(flags));
41330
- return { data: await requireClient().patch(`/food/logs/${id}`, body), render: renderRecord };
42939
+ return {
42940
+ data: await requireClient().patch(pathOf(foodLogOperations.correct, { id }), body),
42941
+ render: renderRecord
42942
+ };
41331
42943
  },
41332
42944
  "food undo-correction": async ({ args, requireClient }) => {
41333
42945
  const id = requireArg(args, 0, "meal id");
41334
42946
  return {
41335
- data: await requireClient().post(`/food/logs/${id}/undo-correction`, {}),
42947
+ data: await requireClient().post(pathOf(foodLogOperations.undoCorrection, { id }), {}),
41336
42948
  render: renderRecord
41337
42949
  };
41338
42950
  },
41339
42951
  "food remove": async ({ args, flags, requireClient }) => {
41340
42952
  const id = requireArg(args, 0, "meal id");
41341
42953
  assertDestructiveAllowed(flags, `remove meal ${id}`);
41342
- await requireClient().delete(`/food/logs/${id}`);
42954
+ await requireClient().delete(pathOf(foodLogOperations.remove, { id }));
41343
42955
  return { data: { ok: true, removed: id }, render: renderRecord };
41344
42956
  },
41345
42957
  "food save-meal": async ({ args, flags, requireClient }) => {
41346
42958
  const id = requireArg(args, 0, "meal id");
41347
42959
  const title = flagString(flags, "title");
41348
42960
  return {
41349
- data: await requireClient().post(`/food/logs/${id}/save-meal`, title ? { title } : {}),
42961
+ data: await requireClient().post(pathOf(foodLogOperations.saveMeal, { id }), title ? { title } : {}),
41350
42962
  render: renderRecord
41351
42963
  };
41352
42964
  },
41353
42965
  "food days": async ({ flags, requireClient }) => ({
41354
- data: await requireClient().get("/food/days", {
42966
+ data: await requireClient().get(pathOf(foodLogOperations.days), {
41355
42967
  query: {
41356
42968
  from: requireFlag(flags, "from", "food days needs --from <YYYY-MM-DD>"),
41357
42969
  to: requireFlag(flags, "to", "food days needs --to <YYYY-MM-DD>")
@@ -41361,29 +42973,35 @@ var HANDLERS = {
41361
42973
  }),
41362
42974
  "food day": async ({ args, requireClient }) => {
41363
42975
  const date = requireArg(args, 0, "date (YYYY-MM-DD)");
41364
- return { data: await requireClient().get(`/food/days/${date}`), render: renderRecord };
42976
+ return {
42977
+ data: await requireClient().get(pathOf(foodLogOperations.day, { date })),
42978
+ render: renderRecord
42979
+ };
41365
42980
  },
41366
42981
  "food recall": async ({ requireClient }) => ({
41367
- data: await requireClient().get("/food/recall"),
42982
+ data: await requireClient().get(pathOf(foodLogOperations.recall)),
41368
42983
  render: renderRecord
41369
42984
  }),
41370
42985
  "recipes nutrition": async ({ args, requireClient }) => {
41371
42986
  const id = requireArg(args, 0, "recipe id");
41372
42987
  return {
41373
- data: await requireClient().post(`/recipes/${id}/nutrition`, {}),
42988
+ data: await requireClient().post(pathOf(recipeOperations.nutrition, { id }), {}),
41374
42989
  render: renderRecord
41375
42990
  };
41376
42991
  },
41377
42992
  "recipes used-in": async ({ args, requireClient }) => {
41378
42993
  const id = requireArg(args, 0, "recipe id");
41379
- return { data: await requireClient().get(`/recipes/${id}/used-in`), render: renderRecord };
42994
+ return {
42995
+ data: await requireClient().get(pathOf(recipeOperations.usedIn, { id })),
42996
+ render: renderRecord
42997
+ };
41380
42998
  },
41381
42999
  "recipes match": async ({ args, requireClient }) => {
41382
43000
  const id = requireArg(args, 0, "recipe id");
41383
43001
  const ingredient = requireArg(args, 1, "ingredient id");
41384
43002
  const foodId = requireArg(args, 2, "food id");
41385
43003
  return {
41386
- data: await requireClient().put(`/recipes/${id}/ingredients/${ingredient}/match`, {
43004
+ data: await requireClient().put(pathOf(recipeOperations.match, { id, ingredientId: ingredient }), {
41387
43005
  foodId
41388
43006
  }),
41389
43007
  render: renderRecord
@@ -41445,19 +43063,22 @@ var HANDLERS = {
41445
43063
  }),
41446
43064
  "time-blocks record": async ({ args, requireClient }) => {
41447
43065
  const id = requireArg(args, 0, "time block id");
41448
- return { data: await requireClient().get(`/time-blocks/${id}/record`), render: renderRecord };
43066
+ return {
43067
+ data: await requireClient().get(pathOf(timeBlockOperations.record, { id })),
43068
+ render: renderRecord
43069
+ };
41449
43070
  },
41450
43071
  "time-blocks confirm": async ({ args, requireClient }) => {
41451
43072
  const id = requireArg(args, 0, "time block id");
41452
43073
  return {
41453
- data: await requireClient().post(`/time-blocks/${id}/confirm`, {}),
43074
+ data: await requireClient().post(pathOf(timeBlockOperations.confirm, { id }), {}),
41454
43075
  render: renderRecord
41455
43076
  };
41456
43077
  },
41457
43078
  "time-blocks plan-vs-record": async ({ flags, requireClient }) => {
41458
43079
  const days = flagString(flags, "days");
41459
43080
  return {
41460
- data: await requireClient().get("/time-blocks/plan-vs-record", {
43081
+ data: await requireClient().get(pathOf(timeBlockOperations.planVsRecord), {
41461
43082
  query: days === undefined ? {} : { days }
41462
43083
  }),
41463
43084
  render: renderRecord
@@ -41465,7 +43086,10 @@ var HANDLERS = {
41465
43086
  },
41466
43087
  "days record": async ({ args, requireClient }) => {
41467
43088
  const date = dayDateArg(args, "record");
41468
- return { data: await requireClient().get(`/days/${date}/record`), render: renderRecord };
43089
+ return {
43090
+ data: await requireClient().get(pathOf(dayOperations.record, { date })),
43091
+ render: renderRecord
43092
+ };
41469
43093
  },
41470
43094
  "days write": async ({ args, flags, requireClient }) => {
41471
43095
  const date = dayDateArg(args, "write");
@@ -41473,7 +43097,7 @@ var HANDLERS = {
41473
43097
  throw usageError("days write needs --data");
41474
43098
  const body = parseInput(dayRecordWriteInputSchema, readDataFlag(flags));
41475
43099
  return {
41476
- data: await requireClient().post(`/days/${date}/record`, body),
43100
+ data: await requireClient().post(pathOf(dayOperations.write, { date }), body),
41477
43101
  render: renderRecord
41478
43102
  };
41479
43103
  },
@@ -41483,7 +43107,7 @@ var HANDLERS = {
41483
43107
  throw usageError("days ask needs --data");
41484
43108
  const body = parseInput(dayRecordAskInputSchema, readDataFlag(flags));
41485
43109
  return {
41486
- data: await requireClient().post(`/days/${date}/record/ask`, body),
43110
+ data: await requireClient().post(pathOf(dayOperations.ask, { date }), body),
41487
43111
  render: renderRecord
41488
43112
  };
41489
43113
  },
@@ -41491,13 +43115,16 @@ var HANDLERS = {
41491
43115
  const date = dayDateArg(args, "undo");
41492
43116
  const body = parseInput(dayRecordUndoInputSchema, { receipt: requireArg(args, 1, "receipt") });
41493
43117
  return {
41494
- data: await requireClient().post(`/days/${date}/record/undo`, body),
43118
+ data: await requireClient().post(pathOf(dayOperations.undo, { date }), body),
41495
43119
  render: renderRecord
41496
43120
  };
41497
43121
  },
41498
43122
  "days close": async ({ args, requireClient }) => {
41499
43123
  const date = dayDateArg(args, "close");
41500
- return { data: await requireClient().post(`/days/${date}/close`, {}), render: renderRecord };
43124
+ return {
43125
+ data: await requireClient().post(pathOf(dayOperations.close, { date }), {}),
43126
+ render: renderRecord
43127
+ };
41501
43128
  },
41502
43129
  "calendar events": async ({ flags, requireClient }) => {
41503
43130
  const from = flagString(flags, "from");
@@ -41505,7 +43132,9 @@ var HANDLERS = {
41505
43132
  if (from === undefined !== (to === undefined)) {
41506
43133
  throw usageError("calendar events takes --from and --to together");
41507
43134
  }
41508
- const events = await requireClient().get("/calendar/events", { query: { from, to } });
43135
+ const events = await requireClient().get(pathOf(calendarOperations.events), {
43136
+ query: { from, to }
43137
+ });
41509
43138
  return { data: events, render: renderEvents };
41510
43139
  },
41511
43140
  "calendar plan": async ({ args, flags, requireClient }) => {
@@ -41520,7 +43149,7 @@ var HANDLERS = {
41520
43149
  ...attending !== undefined ? { attending: attending === "waiting" ? null : attending } : {}
41521
43150
  });
41522
43151
  return {
41523
- data: await requireClient().patch(`/calendar/events/${id}/plan`, body),
43152
+ data: await requireClient().patch(pathOf(calendarOperations.plan, { id }), body),
41524
43153
  render: renderEvent
41525
43154
  };
41526
43155
  },
@@ -41828,11 +43457,17 @@ var HANDLERS = {
41828
43457
  },
41829
43458
  "metrics list": async ({ flags, requireClient }) => {
41830
43459
  const query = { days: flagString(flags, "days") };
41831
- return { data: await requireClient().get("/metrics", { query }), render: renderList };
43460
+ return {
43461
+ data: await requireClient().get(pathOf(metricOperations.list), { query }),
43462
+ render: renderList
43463
+ };
41832
43464
  },
41833
43465
  "metrics sessions": async ({ flags, requireClient }) => {
41834
43466
  const query = { days: flagString(flags, "days") };
41835
- return { data: await requireClient().get("/metrics/sessions", { query }), render: renderList };
43467
+ return {
43468
+ data: await requireClient().get(pathOf(metricOperations.sessions), { query }),
43469
+ render: renderList
43470
+ };
41836
43471
  },
41837
43472
  "metrics log": async ({ args, flags, requireClient }) => {
41838
43473
  const key = parseInput(metricKeySchema, requireArg(args, 0, "metric (weight|sleep|mood|rating|energy|bedtime)"));
@@ -41849,14 +43484,20 @@ var HANDLERS = {
41849
43484
  ...at ? { occurredAt: at } : {},
41850
43485
  ...source ? { source } : {}
41851
43486
  });
41852
- return { data: await requireClient().post(`/metrics/${key}`, input), render: renderRecord };
43487
+ return {
43488
+ data: await requireClient().post(pathOf(metricOperations.log, { key }), input),
43489
+ render: renderRecord
43490
+ };
41853
43491
  },
41854
43492
  "metrics import": async ({ flags, requireClient }) => {
41855
43493
  const input = parseInput(importMetricsInputSchema, {
41856
43494
  source: "health_connect",
41857
43495
  ...readDataFlag(flags)
41858
43496
  });
41859
- return { data: await requireClient().post("/metrics/import", input), render: renderRecord };
43497
+ return {
43498
+ data: await requireClient().post(pathOf(metricOperations.import), input),
43499
+ render: renderRecord
43500
+ };
41860
43501
  },
41861
43502
  "tap-tags tap": async ({ args, requireClient }) => {
41862
43503
  const id = requireArg(args, 0, "tag id");
@@ -42012,7 +43653,7 @@ var HANDLERS = {
42012
43653
  if (entity !== undefined && !presetEntitySchema.safeParse(entity).success) {
42013
43654
  throw usageError(`--entity must be one of: ${presetEntitySchema.options.join(", ")}`);
42014
43655
  }
42015
- const catalog = presetSchema.array().parse(await requireClient().get("/presets"));
43656
+ const catalog = presetSchema.array().parse(await requireClient().get(pathOf(presetOperations.list)));
42016
43657
  const presets = entity ? catalog.filter((p) => p.entity === entity) : catalog;
42017
43658
  return { data: presets, render: renderPresetList };
42018
43659
  },
@@ -42020,7 +43661,7 @@ var HANDLERS = {
42020
43661
  const key = requireArg(args, 0, "preset key");
42021
43662
  const on = flagString(flags, "on");
42022
43663
  const body = on === undefined ? undefined : parseInput(installPresetInputSchema, { on });
42023
- const result = presetInstallResultSchema.parse(await requireClient().post(`/presets/${encodeURIComponent(key)}/install`, body));
43664
+ const result = presetInstallResultSchema.parse(await requireClient().post(pathOf(presetOperations.install, { key }), body));
42024
43665
  return { data: result, render: renderPresetInstall };
42025
43666
  },
42026
43667
  "collection types": async ({ requireClient }) => {
@@ -42172,11 +43813,14 @@ var HANDLERS = {
42172
43813
  if (!clear && !line)
42173
43814
  throw usageError('areas purpose needs a line, e.g. `do areas purpose <id> "Sleep well, move often"` (or --clear)');
42174
43815
  const body = parseInput(updateAreaInputSchema, { purpose: clear ? null : line });
42175
- return { data: await requireClient().patch(`/areas/${id}`, body), render: renderRecord };
43816
+ return {
43817
+ data: await requireClient().patch(pathOf(areaOperations.update, { id }), body),
43818
+ render: renderRecord
43819
+ };
42176
43820
  },
42177
43821
  "areas standards list": async ({ args, requireClient }) => {
42178
43822
  const id = requireArg(args, 0, "area id");
42179
- const area = await requireClient().get(`/areas/${id}`);
43823
+ const area = await requireClient().get(pathOf(areaOperations.get, { id }));
42180
43824
  return { data: area.standards ?? [], render: renderStandards };
42181
43825
  },
42182
43826
  "areas standards add": async ({ args, flags, requireClient }) => {
@@ -42186,7 +43830,7 @@ var HANDLERS = {
42186
43830
  throw usageError('areas standards add needs the standard, e.g. `do areas standards add <id> "Move three times a week" --habit <habitId>`');
42187
43831
  const link = standardLink(flags);
42188
43832
  const body = parseInput(addAreaStandardInputSchema, { text, ...link ? { link } : {} });
42189
- const area = await requireClient().post(`/areas/${id}/standards`, body);
43833
+ const area = await requireClient().post(pathOf(areaOperations.addStandard, { id }), body);
42190
43834
  return { data: area.standards ?? [], render: renderStandards };
42191
43835
  },
42192
43836
  "areas standards link": async ({ args, flags, requireClient }) => {
@@ -42199,20 +43843,20 @@ var HANDLERS = {
42199
43843
  if (!none && !link)
42200
43844
  throw usageError("say what holds it: --habit <id>, --task <id>, --metric <key>, or --none");
42201
43845
  const body = parseInput(updateAreaStandardInputSchema, { link: none ? null : link });
42202
- const area = await requireClient().patch(`/areas/${id}/standards/${standard}`, body);
43846
+ const area = await requireClient().patch(pathOf(areaOperations.updateStandard, { id, standardId: standard }), body);
42203
43847
  return { data: area.standards ?? [], render: renderStandards };
42204
43848
  },
42205
43849
  "areas standards rm": async ({ args, requireClient }) => {
42206
43850
  const id = requireArg(args, 0, "area id");
42207
43851
  const standard = requireArg(args, 1, "standard id");
42208
- const area = await requireClient().delete(`/areas/${id}/standards/${standard}`);
43852
+ const area = await requireClient().delete(pathOf(areaOperations.removeStandard, { id, standardId: standard }));
42209
43853
  return { data: area.standards ?? [], render: renderStandards };
42210
43854
  },
42211
43855
  "areas overview": async ({ flags, requireClient }) => {
42212
43856
  const owner = flagString(flags, "owner");
42213
43857
  const query = parseInput(areaOverviewQuerySchema, owner ? { owner } : {});
42214
43858
  return {
42215
- data: await requireClient().get("/areas/overview", { query }),
43859
+ data: await requireClient().get(pathOf(areaOperations.overview), { query }),
42216
43860
  render: renderAreaOverview
42217
43861
  };
42218
43862
  },
@@ -42225,26 +43869,29 @@ var HANDLERS = {
42225
43869
  until,
42226
43870
  habits: flagBool(flags, "pause-habits") ? "pause" : "keep"
42227
43871
  });
42228
- return { data: await requireClient().post(`/areas/${id}/rest`, input), render: renderRecord };
43872
+ return {
43873
+ data: await requireClient().post(pathOf(areaOperations.rest, { id }), input),
43874
+ render: renderRecord
43875
+ };
42229
43876
  },
42230
43877
  "areas back": async ({ args, requireClient }) => {
42231
43878
  const id = requireArg(args, 0, "area id");
42232
43879
  return {
42233
- data: await requireClient().post(`/areas/${id}/back`, undefined),
43880
+ data: await requireClient().post(pathOf(areaOperations.back, { id }), undefined),
42234
43881
  render: renderRecord
42235
43882
  };
42236
43883
  },
42237
43884
  "areas since": async ({ args, requireClient }) => {
42238
43885
  const id = requireArg(args, 0, "area id");
42239
43886
  return {
42240
- data: await requireClient().get(`/areas/${id}/since-last-look`),
43887
+ data: await requireClient().get(pathOf(areaOperations.sinceLastLook, { id })),
42241
43888
  render: renderSinceLastLook
42242
43889
  };
42243
43890
  },
42244
43891
  "areas looked-at": async ({ args, requireClient }) => {
42245
43892
  const id = requireArg(args, 0, "area id");
42246
43893
  return {
42247
- data: await requireClient().post(`/areas/${id}/looked-at`, undefined),
43894
+ data: await requireClient().post(pathOf(areaOperations.lookedAt, { id }), undefined),
42248
43895
  render: renderRecord
42249
43896
  };
42250
43897
  },
@@ -42265,17 +43912,20 @@ var HANDLERS = {
42265
43912
  intoAreaId: flagString(flags, "into") ?? null
42266
43913
  });
42267
43914
  return {
42268
- data: await requireClient().post(`/areas/${id}/to-project`, input),
43915
+ data: await requireClient().post(pathOf(areaOperations.toProject, { id }), input),
42269
43916
  render: renderRecord
42270
43917
  };
42271
43918
  },
42272
43919
  house: async ({ requireClient }) => ({
42273
- data: await requireClient().get("/house"),
43920
+ data: await requireClient().get(pathOf(houseOperations.overview)),
42274
43921
  render: renderRecord
42275
43922
  }),
42276
43923
  "house asset": async ({ args, requireClient }) => {
42277
43924
  const id = requireArg(args, 0, "asset id");
42278
- return { data: await requireClient().get(`/house/assets/${id}`), render: renderRecord };
43925
+ return {
43926
+ data: await requireClient().get(pathOf(houseOperations.asset, { id })),
43927
+ render: renderRecord
43928
+ };
42279
43929
  },
42280
43930
  "house part": async ({ args, requireClient }) => {
42281
43931
  const id = requireArg(args, 0, "asset id");
@@ -42283,7 +43933,7 @@ var HANDLERS = {
42283
43933
  inventoryItemId: requireArg(args, 1, "supply id")
42284
43934
  });
42285
43935
  return {
42286
- data: await requireClient().post(`/house/assets/${id}/parts`, input),
43936
+ data: await requireClient().post(pathOf(houseOperations.linkPart, { id }), input),
42287
43937
  render: renderRecord
42288
43938
  };
42289
43939
  },
@@ -42291,7 +43941,7 @@ var HANDLERS = {
42291
43941
  const id = requireArg(args, 0, "asset id");
42292
43942
  const supply = requireArg(args, 1, "supply id");
42293
43943
  return {
42294
- data: await requireClient().delete(`/house/assets/${id}/parts/${supply}`),
43944
+ data: await requireClient().delete(pathOf(houseOperations.unlinkPart, { id, inventoryItemId: supply })),
42295
43945
  render: renderRecord
42296
43946
  };
42297
43947
  },
@@ -42303,7 +43953,10 @@ var HANDLERS = {
42303
43953
  ...title ? { title } : {},
42304
43954
  ...flagString(flags, "place") ? { placeId: flagString(flags, "place") } : {}
42305
43955
  });
42306
- return { data: await requireClient().post("/house/assets", input), render: renderRecord };
43956
+ return {
43957
+ data: await requireClient().post(pathOf(houseOperations.fileAsset), input),
43958
+ render: renderRecord
43959
+ };
42307
43960
  },
42308
43961
  "house upkeep": async ({ args, flags, requireClient }) => {
42309
43962
  const id = requireArg(args, 0, "asset id");
@@ -42320,7 +43973,7 @@ var HANDLERS = {
42320
43973
  }
42321
43974
  });
42322
43975
  return {
42323
- data: await requireClient().post(`/house/assets/${id}/upkeep`, input),
43976
+ data: await requireClient().post(pathOf(houseOperations.setUpkeep, { id }), input),
42324
43977
  render: renderRecord
42325
43978
  };
42326
43979
  },
@@ -42335,7 +43988,7 @@ var HANDLERS = {
42335
43988
  ...flagString(flags, "note") ? { note: flagString(flags, "note") } : {}
42336
43989
  });
42337
43990
  return {
42338
- data: await requireClient().post(`/house/assets/${id}/lend`, input),
43991
+ data: await requireClient().post(pathOf(houseOperations.lend, { id }), input),
42339
43992
  render: renderRecord
42340
43993
  };
42341
43994
  },
@@ -42346,12 +43999,12 @@ var HANDLERS = {
42346
43999
  ...flagString(flags, "note") ? { note: flagString(flags, "note") } : {}
42347
44000
  });
42348
44001
  return {
42349
- data: await requireClient().post(`/house/assets/${id}/return`, input),
44002
+ data: await requireClient().post(pathOf(houseOperations.giveBack, { id }), input),
42350
44003
  render: renderRecord
42351
44004
  };
42352
44005
  },
42353
44006
  "house value": async ({ requireClient }) => ({
42354
- data: await requireClient().get("/house/value"),
44007
+ data: await requireClient().get(pathOf(houseOperations.value)),
42355
44008
  render: renderRecord
42356
44009
  }),
42357
44010
  "house year": async ({ flags, requireClient }) => {
@@ -42360,13 +44013,16 @@ var HANDLERS = {
42360
44013
  throw usageError("--year must be a year, e.g. 2026");
42361
44014
  }
42362
44015
  return {
42363
- data: await requireClient().get(`/house/upkeep${year ? `?year=${year}` : ""}`),
44016
+ data: await requireClient().get(`${pathOf(houseOperations.year)}${year ? `?year=${year}` : ""}`),
42364
44017
  render: renderRecord
42365
44018
  };
42366
44019
  },
42367
44020
  "house undo": async ({ flags, requireClient }) => {
42368
44021
  const input = parseInput(undoHouseWriteInputSchema, readDataFlag(flags));
42369
- return { data: await requireClient().post("/house/undo", input), render: renderRecord };
44022
+ return {
44023
+ data: await requireClient().post(pathOf(houseOperations.undo), input),
44024
+ render: renderRecord
44025
+ };
42370
44026
  },
42371
44027
  "areas undo-make-project": async ({ args, flags, requireClient }) => {
42372
44028
  const id = requireArg(args, 0, "area id");
@@ -42376,7 +44032,7 @@ var HANDLERS = {
42376
44032
  moved: receipt.moved
42377
44033
  });
42378
44034
  return {
42379
- data: await requireClient().post(`/areas/${id}/to-project/undo`, input),
44035
+ data: await requireClient().post(pathOf(areaOperations.undoToProject, { id }), input),
42380
44036
  render: renderRecord
42381
44037
  };
42382
44038
  },
@@ -42780,29 +44436,32 @@ var HANDLERS = {
42780
44436
  return { data: { ok: true, removed: id }, render: renderRecord };
42781
44437
  },
42782
44438
  "animals overview": async ({ requireClient }) => ({
42783
- data: await requireClient().get("/animals/overview"),
44439
+ data: await requireClient().get(pathOf(animalOperations.overview)),
42784
44440
  render: renderRecord
42785
44441
  }),
42786
44442
  "animals handover": async ({ flags, requireClient }) => {
42787
44443
  const periodId = flagString(flags, "period");
42788
44444
  return {
42789
- data: await requireClient().get("/animals/handover", periodId ? { query: { periodId } } : {}),
44445
+ data: await requireClient().get(pathOf(animalOperations.handover), periodId ? { query: { periodId } } : {}),
42790
44446
  render: renderRecord
42791
44447
  };
42792
44448
  },
42793
44449
  "animals species": async ({ requireClient }) => ({
42794
- data: await requireClient().get("/animals/species"),
44450
+ data: await requireClient().get(pathOf(animalOperations.listSpecies)),
42795
44451
  render: renderList
42796
44452
  }),
42797
44453
  "animals species-add": async ({ flags, requireClient }) => {
42798
44454
  const body = parseInput(createAnimalSpeciesInputSchema, { id: ulid3(), ...readDataFlag(flags) });
42799
- return { data: await requireClient().post("/animals/species", body), render: renderRecord };
44455
+ return {
44456
+ data: await requireClient().post(pathOf(animalOperations.createSpecies), body),
44457
+ render: renderRecord
44458
+ };
42800
44459
  },
42801
44460
  "animals species-update": async ({ args, flags, requireClient }) => {
42802
44461
  const id = requireArg(args, 0, "species id");
42803
44462
  const body = parseInput(updateAnimalSpeciesInputSchema, readDataFlag(flags));
42804
44463
  return {
42805
- data: await requireClient().patch(`/animals/species/${id}`, body),
44464
+ data: await requireClient().patch(pathOf(animalOperations.updateSpecies, { speciesId: id }), body),
42806
44465
  render: renderRecord
42807
44466
  };
42808
44467
  },
@@ -42811,7 +44470,7 @@ var HANDLERS = {
42811
44470
  const days = flagString(flags, "days");
42812
44471
  const query = parseInput(animalTimelineQuerySchema, days ? { days } : {});
42813
44472
  return {
42814
- data: await requireClient().get(`/animals/${id}/timeline`, {
44473
+ data: await requireClient().get(pathOf(animalOperations.timeline, { id }), {
42815
44474
  query: { days: String(query.days) }
42816
44475
  }),
42817
44476
  render: renderList
@@ -42830,7 +44489,10 @@ var HANDLERS = {
42830
44489
  if (kind !== "note" && Number.isNaN(n))
42831
44490
  throw usageError("the value must be a number");
42832
44491
  const body = parseInput(animalLogInputSchema, { ...fact, ...at ? { occurredAt: at } : {} });
42833
- return { data: await requireClient().post(`/animals/${id}/log`, body), render: renderRecord };
44492
+ return {
44493
+ data: await requireClient().post(pathOf(animalOperations.log, { id }), body),
44494
+ render: renderRecord
44495
+ };
42834
44496
  },
42835
44497
  "animals vet-visit": async ({ args, flags, requireClient }) => {
42836
44498
  const id = requireArg(args, 0, "animal id");
@@ -42841,46 +44503,46 @@ var HANDLERS = {
42841
44503
  nextDates: (raw.nextDates ?? []).map((d) => ({ id: ulid3(), ...d }))
42842
44504
  });
42843
44505
  return {
42844
- data: await requireClient().post(`/animals/${id}/vet-visits`, body),
44506
+ data: await requireClient().post(pathOf(animalOperations.logVetVisit, { id }), body),
42845
44507
  render: renderRecord
42846
44508
  };
42847
44509
  },
42848
44510
  "animals vet-visit-get": async ({ args, requireClient }) => ({
42849
- data: await requireClient().get(`/animals/vet-visits/${requireArg(args, 0, "visit id")}`),
44511
+ data: await requireClient().get(pathOf(animalOperations.vetVisit, { visitId: requireArg(args, 0, "visit id") })),
42850
44512
  render: renderRecord
42851
44513
  }),
42852
44514
  "animals vet-visit-edit": async ({ args, flags, requireClient }) => {
42853
44515
  const visitId = requireArg(args, 0, "visit id");
42854
44516
  const body = parseInput(animalVetVisitPatchSchema, readDataFlag(flags));
42855
44517
  return {
42856
- data: await requireClient().patch(`/animals/vet-visits/${visitId}`, body),
44518
+ data: await requireClient().patch(pathOf(animalOperations.patchVetVisit, { visitId }), body),
42857
44519
  render: renderRecord
42858
44520
  };
42859
44521
  },
42860
44522
  "animals vet-visit-undo": async ({ args, requireClient }) => ({
42861
- data: await requireClient().post(`/animals/vet-visits/${requireArg(args, 0, "visit id")}/undo`, {}),
44523
+ data: await requireClient().post(pathOf(animalOperations.undoVetVisit, { visitId: requireArg(args, 0, "visit id") }), {}),
42862
44524
  render: renderRecord
42863
44525
  }),
42864
44526
  "animals lost-card": async ({ args, requireClient }) => ({
42865
- data: await requireClient().get(`/animals/${requireArg(args, 0, "animal id")}/lost-card`),
44527
+ data: await requireClient().get(pathOf(animalOperations.lostCard, { id: requireArg(args, 0, "animal id") })),
42866
44528
  render: renderRecord
42867
44529
  }),
42868
44530
  "animals lost-card-set": async ({ args, flags, requireClient }) => {
42869
44531
  const id = requireArg(args, 0, "animal id");
42870
44532
  const body = parseInput(animalLostCardInputSchema, readDataFlag(flags));
42871
44533
  return {
42872
- data: await requireClient().put(`/animals/${id}/lost-card`, body),
44534
+ data: await requireClient().put(pathOf(animalOperations.setLostCard, { id }), body),
42873
44535
  render: renderRecord
42874
44536
  };
42875
44537
  },
42876
44538
  "animals lost-card-off": async ({ args, flags, requireClient }) => {
42877
44539
  const id = requireArg(args, 0, "animal id");
42878
44540
  assertDestructiveAllowed(flags, `turn off the lost-pet card of ${id} (its printed QR stops working)`);
42879
- await requireClient().delete(`/animals/${id}/lost-card`);
44541
+ await requireClient().delete(pathOf(animalOperations.removeLostCard, { id }));
42880
44542
  return { data: { ok: true, animalId: id }, render: renderRecord };
42881
44543
  },
42882
44544
  "animals acquisition": async ({ args, requireClient }) => ({
42883
- data: await requireClient().get(`/animals/${requireArg(args, 0, "animal id")}/acquisition`),
44545
+ data: await requireClient().get(pathOf(animalOperations.acquisition, { id: requireArg(args, 0, "animal id") })),
42884
44546
  render: renderRecord
42885
44547
  }),
42886
44548
  "animals acquisition-plan": async ({ args, flags, requireClient }) => {
@@ -42891,7 +44553,7 @@ var HANDLERS = {
42891
44553
  ...pickUp ? { pickUpOn: pickUp } : {}
42892
44554
  });
42893
44555
  return {
42894
- data: await requireClient().post(`/animals/${id}/acquisition`, body),
44556
+ data: await requireClient().post(pathOf(animalOperations.planAcquisition, { id }), body),
42895
44557
  render: renderRecord
42896
44558
  };
42897
44559
  },
@@ -42904,7 +44566,7 @@ var HANDLERS = {
42904
44566
  ...months !== undefined ? { quarantineMonths: Number(months) } : {}
42905
44567
  });
42906
44568
  return {
42907
- data: await requireClient().post(`/animals/${id}/arrived`, body),
44569
+ data: await requireClient().post(pathOf(animalOperations.arrived, { id }), body),
42908
44570
  render: renderRecord
42909
44571
  };
42910
44572
  },
@@ -43537,7 +45199,7 @@ var HANDLERS = {
43537
45199
  "guest-lists overview": async ({ args, requireClient }) => {
43538
45200
  const id = requireArg(args, 0, "guest list id");
43539
45201
  return {
43540
- data: await requireClient().get(`/guest-lists/${id}/overview`),
45202
+ data: await requireClient().get(pathOf(guestListOperations.overview, { id })),
43541
45203
  render: renderRecord
43542
45204
  };
43543
45205
  },
@@ -43582,7 +45244,9 @@ var HANDLERS = {
43582
45244
  throw usageError("seating-proposals list needs --list <guest list id>");
43583
45245
  parseInput(seatingProposalsQuerySchema, { guestListId: list });
43584
45246
  return {
43585
- data: await requireClient().get("/seating-proposals", { query: { guestListId: list } }),
45247
+ data: await requireClient().get(pathOf(seatingProposalOperations.list), {
45248
+ query: { guestListId: list }
45249
+ }),
43586
45250
  render: renderRecord
43587
45251
  };
43588
45252
  },
@@ -43591,19 +45255,22 @@ var HANDLERS = {
43591
45255
  id: ulid3(),
43592
45256
  ...readDataFlag(flags)
43593
45257
  });
43594
- return { data: await requireClient().post("/seating-proposals", body), render: renderRecord };
45258
+ return {
45259
+ data: await requireClient().post(pathOf(seatingProposalOperations.propose), body),
45260
+ render: renderRecord
45261
+ };
43595
45262
  },
43596
45263
  "seating-proposals apply": async ({ args, requireClient }) => {
43597
45264
  const id = requireArg(args, 0, "proposal id");
43598
45265
  return {
43599
- data: await requireClient().post(`/seating-proposals/${id}/apply`, {}),
45266
+ data: await requireClient().post(pathOf(seatingProposalOperations.apply, { id }), {}),
43600
45267
  render: renderRecord
43601
45268
  };
43602
45269
  },
43603
45270
  "seating-proposals dismiss": async ({ args, requireClient }) => {
43604
45271
  const id = requireArg(args, 0, "proposal id");
43605
45272
  return {
43606
- data: await requireClient().post(`/seating-proposals/${id}/dismiss`, {}),
45273
+ data: await requireClient().post(pathOf(seatingProposalOperations.dismiss, { id }), {}),
43607
45274
  render: renderRecord
43608
45275
  };
43609
45276
  },
@@ -43654,14 +45321,14 @@ var HANDLERS = {
43654
45321
  "guest-lists thank-you": async ({ args, requireClient }) => {
43655
45322
  const id = requireArg(args, 0, "guest list id");
43656
45323
  return {
43657
- data: await requireClient().get(`/guest-lists/${id}/thank-you`),
45324
+ data: await requireClient().get(pathOf(guestListOperations.thankYou, { id })),
43658
45325
  render: renderRecord
43659
45326
  };
43660
45327
  },
43661
45328
  "guest-lists export": async ({ args, flags, requireClient }) => {
43662
45329
  const id = requireArg(args, 0, "guest list id");
43663
45330
  const kind = parseInput(guestExportKindSchema, flagString(flags, "kind") ?? "venue");
43664
- const path = `/guest-lists/${id}/exports/${kind}`;
45331
+ const path = pathOf(guestListOperations.export, { id, kind });
43665
45332
  if (kind === "floor-plan" || kind === "invitations") {
43666
45333
  const file = await requireClient().getBytes(path);
43667
45334
  const out = flagString(flags, "out") ?? basename2(file.name ?? `${kind}.pdf`);
@@ -43680,29 +45347,37 @@ var HANDLERS = {
43680
45347
  throw usageError("rsvp-links list needs --list <guest list id>");
43681
45348
  parseInput(rsvpLinksQuerySchema, { guestListId: list });
43682
45349
  return {
43683
- data: await requireClient().get("/rsvp-links", { query: { guestListId: list } }),
45350
+ data: await requireClient().get(pathOf(rsvpLinkOperations.list), {
45351
+ query: { guestListId: list }
45352
+ }),
43684
45353
  render: renderRecord
43685
45354
  };
43686
45355
  },
43687
45356
  "rsvp-links create": async ({ flags, requireClient }) => {
43688
45357
  const body = parseInput(createRsvpLinksInputSchema, readDataFlag(flags));
43689
- return { data: await requireClient().post("/rsvp-links", body), render: renderRecord };
45358
+ return {
45359
+ data: await requireClient().post(pathOf(rsvpLinkOperations.mint), body),
45360
+ render: renderRecord
45361
+ };
43690
45362
  },
43691
45363
  "rsvp-links token": async ({ args, requireClient }) => {
43692
45364
  const id = requireArg(args, 0, "rsvp link id");
43693
- return { data: await requireClient().get(`/rsvp-links/${id}/token`), render: renderRecord };
45365
+ return {
45366
+ data: await requireClient().get(pathOf(rsvpLinkOperations.reveal, { id })),
45367
+ render: renderRecord
45368
+ };
43694
45369
  },
43695
45370
  "rsvp-links replace": async ({ args, requireClient }) => {
43696
45371
  const id = requireArg(args, 0, "rsvp link id");
43697
45372
  return {
43698
- data: await requireClient().post(`/rsvp-links/${id}/replace`, {}),
45373
+ data: await requireClient().post(pathOf(rsvpLinkOperations.replace, { id }), {}),
43699
45374
  render: renderRecord
43700
45375
  };
43701
45376
  },
43702
45377
  "rsvp-links revoke": async ({ args, requireClient }) => {
43703
45378
  const id = requireArg(args, 0, "rsvp link id");
43704
45379
  return {
43705
- data: await requireClient().post(`/rsvp-links/${id}/revoke`, {}),
45380
+ data: await requireClient().post(pathOf(rsvpLinkOperations.revoke, { id }), {}),
43706
45381
  render: renderRecord
43707
45382
  };
43708
45383
  },