@robbeverhelst/do 1.307.0 → 1.308.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 +629 -162
  2. package/package.json +1 -1
package/dist/do.js CHANGED
@@ -28197,6 +28197,21 @@ var guestListOverviewSchema = object({
28197
28197
  invites: number2().int()
28198
28198
  })
28199
28199
  });
28200
+ var rsvpDigestQuerySchema = object({ since: exports_coerce.date().optional() }).strict();
28201
+ var rsvpDigestSchema = object({
28202
+ guestListId: string2(),
28203
+ since: exports_coerce.date(),
28204
+ households: array(object({
28205
+ householdId: string2().nullable(),
28206
+ household: string2(),
28207
+ answeredAt: exports_coerce.date(),
28208
+ coming: array(string2()),
28209
+ maybe: array(string2()),
28210
+ notComing: array(string2())
28211
+ })),
28212
+ waiting: number2().int().nonnegative(),
28213
+ text: string2()
28214
+ });
28200
28215
  var RSVP_TOKEN_RE = /^rsvp_[A-Za-z0-9_-]{43}$/;
28201
28216
  var RSVP_CODE_ALPHABET = "23456789ABCDEFGHJKMNPQRSTWXYZ";
28202
28217
  var RSVP_CODE_LENGTH = 6;
@@ -28502,7 +28517,7 @@ var mailProviderEventSchema = looseObject({
28502
28517
  }).optional()
28503
28518
  })
28504
28519
  });
28505
- var MAIL_CAMPAIGN_KINDS = ["message", "save_the_date", "invitation"];
28520
+ var MAIL_CAMPAIGN_KINDS = ["message", "save_the_date", "invitation", "reminder"];
28506
28521
  var mailCampaignKindSchema = _enum2(MAIL_CAMPAIGN_KINDS);
28507
28522
  var MAIL_TEMPLATES = [
28508
28523
  "message",
@@ -28565,7 +28580,8 @@ var mailRecipientHouseholdSchema = object({
28565
28580
  name: string2(),
28566
28581
  language: mailLanguageSchema,
28567
28582
  addresses: number2().int().nonnegative(),
28568
- status: householdMailStatusSchema.nullable()
28583
+ status: householdMailStatusSchema.nullable(),
28584
+ link: boolean3().nullable().default(null)
28569
28585
  });
28570
28586
  var mailCampaignViewSchema = mailCampaignSchema.extend({
28571
28587
  households: array(mailRecipientHouseholdSchema),
@@ -31239,6 +31255,27 @@ var commentOperations = defineOperations({
31239
31255
  }
31240
31256
  });
31241
31257
 
31258
+ // ../../packages/schema/src/operations/contacts.ts
31259
+ var contactOperations = defineOperations({
31260
+ ...crudOperations({
31261
+ segment: "contacts",
31262
+ tag: "Contacts",
31263
+ noun: "contact",
31264
+ read: "contacts:read",
31265
+ write: "contacts:write",
31266
+ create: createContactInputSchema,
31267
+ update: updateContactInputSchema,
31268
+ listQuery: [
31269
+ { name: "kind", description: "Only `person` or only `organization` contacts." },
31270
+ {
31271
+ name: "role",
31272
+ description: "Only contacts with this role (`insurer`, `telecom`, `gp`, `vet`, \u2026)."
31273
+ }
31274
+ ],
31275
+ listQuerySchema: contactsQuerySchema
31276
+ })
31277
+ });
31278
+
31242
31279
  // ../../packages/schema/src/operations/days.ts
31243
31280
  var pathParams = {
31244
31281
  date: {
@@ -31735,6 +31772,21 @@ var guestListOperations = defineOperations({
31735
31772
  response: { description: "`{ toThank, thanked }`." },
31736
31773
  errors: { 404: true }
31737
31774
  },
31775
+ rsvpDigest: {
31776
+ method: "get",
31777
+ path: "/guest-lists/{id}/rsvp-digest",
31778
+ pathParams: list,
31779
+ scopes: ["contacts:read"],
31780
+ tag: "Guests",
31781
+ summary: "The RSVPs that came in",
31782
+ description: "For the agent's daily digest (wedding C8): each household whose answer came in or changed through its RSVP link since `since` (default: the last 24 hours), oldest first, with who's `coming`, `maybe` and `notComing` by name, how many households are still `waiting` (nobody answered yet), and the same as plain lines in `text` (\"Sam & Kai answered. Coming: Sam, Kai.\"). The couple get each answer as a do notification, never a mail.",
31783
+ query: [{ name: "since", description: "An ISO instant; default 24 hours ago." }],
31784
+ querySchema: rsvpDigestQuerySchema,
31785
+ operationId: "get_guest_list_rsvp_digest",
31786
+ status: 200,
31787
+ response: { description: "`{ guestListId, since, households, waiting, text }`." },
31788
+ errors: { 400: true, 404: true }
31789
+ },
31738
31790
  export: {
31739
31791
  method: "get",
31740
31792
  path: "/guest-lists/{id}/exports/{kind}",
@@ -32298,6 +32350,357 @@ var inviteOperations = defineOperations({
32298
32350
  }
32299
32351
  });
32300
32352
 
32353
+ // ../../packages/schema/src/operations/journal.ts
32354
+ var journalOperations = defineOperations({
32355
+ range: {
32356
+ method: "get",
32357
+ path: "/journal/days",
32358
+ scopes: ["journal:read"],
32359
+ tag: "Journal",
32360
+ summary: "Journal days in a range",
32361
+ description: "Journal home's read: every day in `[from, to]` (at most 366 days, the key's member's civil days) that has a Journal day or a Metric value, newest first. Each carries the Journal day's title, status, highlights, tags and a plain excerpt of its summary when there is one, and that day's Metric values by key (`rating`, `mood`, `sleep`, \u2026), with how many photos and entries it holds and the first nodes it links to. The Journal is owner-only: a key reads only its own member's days.",
32362
+ afterScopes: "`*` does not cover it.",
32363
+ query: [
32364
+ { name: "from", description: "The first day, `YYYY-MM-DD`.", format: "date" },
32365
+ { name: "to", description: "The last day, `YYYY-MM-DD`.", format: "date" }
32366
+ ],
32367
+ querySchema: journalRangeQuerySchema,
32368
+ status: 200,
32369
+ response: { description: "The days.", view: "JournalDayList" },
32370
+ errors: { 400: true },
32371
+ operationId: "list_journal_days"
32372
+ },
32373
+ search: {
32374
+ method: "get",
32375
+ path: "/journal/search",
32376
+ scopes: ["journal:read"],
32377
+ tag: "Journal",
32378
+ summary: "Search Journal days",
32379
+ description: "The key's member's days that answer `q` (at least 2 characters), best match first, at most 30, each shaped like a day of `GET /journal/days`. Keyword and date, no model: the words left after small words (each with a few that mean the same, English and Dutch) scored where they're found (one-liner and tags, then highlights, then the summary and entries); a date phrase (`in March`, `last week`, `14 October`, `2026-02`, `yesterday`) narrows to its window, or alone reads it; a question leaning on a heavy or a bright day (`rough`, `tired`, `a great day`) adds the days rated 4 or less / 8 or more (within the window, else the last year). `understood` says how it was read. Owner-only: another member's days never match.",
32380
+ afterScopes: "`*` does not cover it.",
32381
+ query: [{ name: "q", description: "What to look for." }],
32382
+ querySchema: journalSearchQuerySchema,
32383
+ status: 200,
32384
+ response: { description: "The matching days.", view: "JournalSearch" },
32385
+ errors: { 400: true }
32386
+ },
32387
+ day: {
32388
+ method: "get",
32389
+ path: "/journal/days/{date}",
32390
+ scopes: ["journal:read"],
32391
+ tag: "Journal",
32392
+ summary: "A Journal day",
32393
+ description: "One civil day of the key's member: the Journal day (its summary, entries, highlights and tags; null when none was written yet) and what happened that day, read live: the Metric values, what landed (tasks and time blocks, by the member or an agent), the habits due and whether they happened, the calendar events, and the nodes the day links to.",
32394
+ afterScopes: "`*` does not cover it.",
32395
+ pathParams: {
32396
+ date: { description: "The day, `YYYY-MM-DD`.", format: "date" }
32397
+ },
32398
+ pathParamSchemas: { date: journalDateSchema },
32399
+ status: 200,
32400
+ response: { description: "The day.", view: "JournalSnapshot" },
32401
+ errors: { 400: true },
32402
+ operationId: "get_journal_day"
32403
+ },
32404
+ like: {
32405
+ method: "get",
32406
+ path: "/journal/days/{date}/like",
32407
+ scopes: ["journal:read"],
32408
+ tag: "Journal",
32409
+ summary: "Days like this one",
32410
+ description: "\"When did I feel like this before?\" (`journal_like`): the key's member's other days most like this one, by Postgres full text over each day's one-liner, highlights, tags, summary and entries, queried with this day's distinctive words (each widened by a few that mean the same, English and Dutch), then ranked by the words and tags they share and how close the rating is. Each day is shaped like the range's, with `shared` (this day's words the other has) and `sharedTags`; `words` is how this day was read. Words, not meanings: there are no embeddings. A day hidden from memories is never offered. A day with nothing written is a 404.",
32411
+ afterScopes: "`*` does not cover it.",
32412
+ pathParams: {
32413
+ date: { description: "The day, `YYYY-MM-DD`.", format: "date" }
32414
+ },
32415
+ pathParamSchemas: { date: journalDateSchema },
32416
+ query: [{ name: "limit", description: "At most this many days (1\u201310, 5 by default)." }],
32417
+ querySchema: journalLikeQuerySchema,
32418
+ status: 200,
32419
+ response: { description: "The days most like it.", view: "JournalLike" },
32420
+ errors: { 400: true, 404: true }
32421
+ },
32422
+ export: {
32423
+ method: "get",
32424
+ path: "/journal/export",
32425
+ scopes: ["journal:export"],
32426
+ tag: "Journal",
32427
+ summary: "Export the Journal as markdown",
32428
+ description: "Every Journal day of the key's member changed at or after `since` (all without it) as a markdown file: `path` (`YYYY/YYYY-MM-DD.md`), front matter (date, title, status, Metric values, tags, `do_id`), the summary, highlights, entries and links as `do://node/<id>`. Links into other sensitive domains stay out. Store `nextSince` and pass it next time. The agent's runtime pulls this nightly into its memory; do is the record and the files are read-only copies.",
32429
+ query: [
32430
+ {
32431
+ name: "since",
32432
+ description: "Only days changed at or after this instant (ISO).",
32433
+ format: "date-time"
32434
+ }
32435
+ ],
32436
+ querySchema: journalExportQuerySchema,
32437
+ status: 200,
32438
+ response: { description: "The files.", view: "JournalExport" },
32439
+ errors: { 400: true },
32440
+ operationId: "export_journal"
32441
+ },
32442
+ write: {
32443
+ method: "post",
32444
+ path: "/journal/days/{date}",
32445
+ scopes: ["journal:write"],
32446
+ tag: "Journal",
32447
+ summary: "Write a Journal day",
32448
+ description: "The agent's evening write (`journal_write_day`) or the member's: creates the day when missing and sets what's given. `summary` (markdown) replaces the summary entry, `highlights` (at most 3) replace the day's, `tags` and `links` (`{ kind, nodeId }`, e.g. `saw` a contact, `about` an event) are added, `metrics` (`{ rating: 7, mood: 4 }`) are logged on each Metric's line for that day. A future date, an unknown Metric or a value out of range is a 400. Returns the day's snapshot, the receipt; an agent's write shows in the change list with Undo.",
32449
+ pathParams: {
32450
+ date: { description: "The day, `YYYY-MM-DD`.", format: "date" }
32451
+ },
32452
+ pathParamSchemas: { date: journalDateSchema },
32453
+ body: journalWriteDayInputSchema,
32454
+ status: 200,
32455
+ response: { description: "The day.", view: "JournalSnapshot" },
32456
+ errors: { 400: true },
32457
+ operationId: "journal_write_day"
32458
+ },
32459
+ addEntry: {
32460
+ method: "post",
32461
+ path: "/journal/days/{date}/entries",
32462
+ scopes: ["journal:write"],
32463
+ tag: "Journal",
32464
+ summary: "Add a Journal entry",
32465
+ description: "Add an entry to the day (`journal_add_entry`): a markdown body and an optional title. The day is made when missing. Returns the snapshot.",
32466
+ pathParams: {
32467
+ date: { description: "The day, `YYYY-MM-DD`.", format: "date" }
32468
+ },
32469
+ pathParamSchemas: { date: journalDateSchema },
32470
+ body: journalAddEntryInputSchema,
32471
+ status: 200,
32472
+ response: { description: "The day.", view: "JournalSnapshot" },
32473
+ errors: { 400: true },
32474
+ operationId: "journal_add_entry"
32475
+ },
32476
+ correct: {
32477
+ method: "patch",
32478
+ path: "/journal/days/{date}",
32479
+ scopes: ["journal:write"],
32480
+ tag: "Journal",
32481
+ summary: "Correct a Journal day",
32482
+ description: "Fix the day (`journal_correct`): `title`, `summary` (null clears it), `highlights` and `tags` replace what's there, `removeLinks` unlinks nodes, `metrics` log a corrected value for the day, `status` and `hiddenFromMemories` set those. The member's own correction marks the day reviewed. 404 when the date has no Journal day. Returns the snapshot.",
32483
+ pathParams: {
32484
+ date: { description: "The day, `YYYY-MM-DD`.", format: "date" }
32485
+ },
32486
+ pathParamSchemas: { date: journalDateSchema },
32487
+ body: journalCorrectInputSchema,
32488
+ status: 200,
32489
+ response: { description: "The day.", view: "JournalSnapshot" },
32490
+ errors: { 400: true, 404: "No Journal day on that date." },
32491
+ operationId: "journal_correct"
32492
+ },
32493
+ stageImport: {
32494
+ method: "post",
32495
+ path: "/journal/imports",
32496
+ scopes: ["journal:write"],
32497
+ tag: "Journal",
32498
+ summary: "Stage daily notes for the backfill",
32499
+ description: "Stage parsed daily notes (`do journal import push <dir>`): one item per day (at most 400 a request) with its source file and hash, what becomes its entry (`summary`, `retro` or `log`), a one-liner, the summary's text (health lines left out), a clear `rating` or a `ratingQuestion` (\"~5/10\"), a highlight, whether it may be a milestone or had health lines, and the links counted. Nothing is written into the Journal: the member reviews and approves each month in the app, and a key can't approve. A day still waiting is replaced; one already imported is left alone (`alreadyImported`). Staged days stand behind the Journal's wall.",
32500
+ body: journalImportUploadInputSchema,
32501
+ status: 200,
32502
+ response: { description: "How the upload went.", view: "JournalImportUpload" },
32503
+ errors: { 400: true }
32504
+ },
32505
+ importOverview: {
32506
+ method: "get",
32507
+ path: "/journal/imports",
32508
+ scopes: ["journal:read"],
32509
+ tag: "Journal",
32510
+ summary: "What the backfill has staged",
32511
+ description: "The key's member's staged days: the source folder, how many files over which dates, what the parser found (summaries, ratings and the unclear ones, milestone candidates, days with health lines, links), each month's batch (days, ready, needing the member, imported), and one batch's days (`?batch=YYYY-MM`, else the newest still waiting), each with its `state`: `ready`, `needs_you`, `today` (never imported), `imported` or `skipped`.",
32512
+ afterScopes: "`*` does not cover it.",
32513
+ query: [{ name: "batch", description: "A month, `YYYY-MM`." }],
32514
+ querySchema: journalImportQuerySchema,
32515
+ status: 200,
32516
+ response: { description: "The overview.", view: "JournalImportOverview" },
32517
+ errors: { 400: true }
32518
+ },
32519
+ milestones: {
32520
+ method: "get",
32521
+ path: "/journal/milestones",
32522
+ scopes: ["journal:read"],
32523
+ tag: "Journal",
32524
+ summary: "The life timeline",
32525
+ description: "The key's member's milestones and the Space's shared ones, oldest first: title, kind, when (`happenedOn` at its `precision`: year, month or day), `planned` (still ahead), `status` (`suggested` by an agent and not kept yet, or `kept`), `visibility` (`owner` = Only me, the default; `space` = shared), the nodes it is about (each with its title, type and, for a Project, its `status`), the Journal day it happened on (rating, photos) when the date is a day, and whether it reminds yearly.",
32526
+ afterScopes: "`*` does not cover it.",
32527
+ status: 200,
32528
+ response: { description: "The timeline.", view: "JournalMilestoneList" }
32529
+ },
32530
+ createMilestone: {
32531
+ method: "post",
32532
+ path: "/journal/milestones",
32533
+ scopes: ["journal:write"],
32534
+ tag: "Journal",
32535
+ summary: "Mark a milestone",
32536
+ description: "Mark a milestone (`journal_mark_milestone`): a title, a kind, when and how exactly, planned or not, a line about it, Only me (default) or shared, the nodes it is about (`links`: a Journal day, a paper, a project, a person) and `remindYearly` (a yearly `anniversary` Key date; needs a day). An agent's mark is `suggested` unless it sends `status: kept`; the member keeps or removes it.",
32537
+ body: journalCreateMilestoneInputSchema,
32538
+ status: 201,
32539
+ response: { description: "The milestone.", view: "JournalMilestone" },
32540
+ errors: { 400: true }
32541
+ },
32542
+ updateMilestone: {
32543
+ method: "patch",
32544
+ path: "/journal/milestones/{id}",
32545
+ scopes: ["journal:write"],
32546
+ tag: "Journal",
32547
+ summary: "Change a milestone",
32548
+ description: "Change what's given; `status: kept` keeps a suggestion; `addLinks` / `removeLinks` link or unlink; `remindYearly` turns the yearly Key date on or off (it moves with the date). 404 for someone else's Only me milestone.",
32549
+ pathParams: {
32550
+ id: "The milestone's id."
32551
+ },
32552
+ pathParamSchemas: { id: ulidSchema },
32553
+ body: journalUpdateMilestoneInputSchema,
32554
+ status: 200,
32555
+ response: { description: "The milestone.", view: "JournalMilestone" },
32556
+ errors: { 400: true, 404: "No such milestone." }
32557
+ },
32558
+ removeMilestone: {
32559
+ method: "delete",
32560
+ path: "/journal/milestones/{id}",
32561
+ scopes: ["journal:write"],
32562
+ tag: "Journal",
32563
+ summary: "Remove a milestone",
32564
+ description: "Remove a milestone (to the trash) and its yearly reminder.",
32565
+ pathParams: {
32566
+ id: "The milestone's id."
32567
+ },
32568
+ pathParamSchemas: { id: ulidSchema },
32569
+ status: 204,
32570
+ response: { description: "Removed." },
32571
+ errors: { 404: "No such milestone." }
32572
+ },
32573
+ week: {
32574
+ method: "get",
32575
+ path: "/journal/weeks/{week}",
32576
+ scopes: ["journal:read"],
32577
+ tag: "Journal",
32578
+ summary: "A Journal week",
32579
+ description: "One ISO week of the key's member: the week's recap (the prose, up to 3 best moments, one pattern line; null until written), the week's days with a Journal day or a Metric value (as `GET /journal/days`), `usual` (the mean day rating over the eight weeks before) and the numbers from Progress and the Metrics: what landed (and how much of it was the member's own), each habit's done and due days, the average sleep and the nights to bed after midnight.",
32580
+ afterScopes: "`*` does not cover it.",
32581
+ pathParams: {
32582
+ week: "The ISO week, `YYYY-Www` (e.g. `2026-W42`)."
32583
+ },
32584
+ pathParamSchemas: { week: journalWeekKeySchema },
32585
+ status: 200,
32586
+ response: { description: "The week.", view: "JournalWeek" },
32587
+ errors: { 400: true }
32588
+ },
32589
+ writeRecap: {
32590
+ method: "put",
32591
+ path: "/journal/weeks/{week}/recap",
32592
+ scopes: ["journal:write"],
32593
+ tag: "Journal",
32594
+ summary: "Write a week's recap",
32595
+ description: "The agent's Sunday recap (`journal_write_recap`) or the member's: `body` (markdown) replaces the prose; `moments` (at most 3, `{ text, date?, nodeId? }`) and `pattern` replace theirs when given (`pattern: null` clears it). The recap is owner-only like the days. A week that hasn't started is a 400. Returns the week.",
32596
+ pathParams: {
32597
+ week: "The ISO week, `YYYY-Www` (e.g. `2026-W42`)."
32598
+ },
32599
+ pathParamSchemas: { week: journalWeekKeySchema },
32600
+ body: journalWriteRecapInputSchema,
32601
+ status: 200,
32602
+ response: { description: "The week.", view: "JournalWeek" },
32603
+ errors: { 400: true }
32604
+ },
32605
+ recall: {
32606
+ method: "get",
32607
+ path: "/journal/recall",
32608
+ scopes: ["journal:read"],
32609
+ tag: "Journal",
32610
+ summary: "Recall the Journal",
32611
+ description: "The agent's recall (`journal_recall`): \"what did I do / how was I around <date or topic>\" over the key's member's own days only (another member's days never answer). `q` (2+ characters) finds the days that mention it; `around` (a day) reads 3 days either side; `from`/`to` read a window (at most 366 days); words and a window combine; with neither, the last 7 days. Returns the days newest first (as `GET /journal/days`, at most `limit`, 30 by default), how many were rated and their mean, the member's kept milestones within a week of the window, and the window's last day in up to 3 earlier years (a day hidden from memories left out), so an answer can cite dates.",
32612
+ afterScopes: "`*` does not cover it.",
32613
+ query: [
32614
+ { name: "q", description: "Words to look for (2+ characters)." },
32615
+ { name: "around", description: "A day, YYYY-MM-DD: 3 days either side." },
32616
+ { name: "from", description: "The window's first day, YYYY-MM-DD." },
32617
+ { name: "to", description: "The window's last day, YYYY-MM-DD." },
32618
+ { name: "limit", description: "At most this many days (1\u201330)." }
32619
+ ],
32620
+ querySchema: journalRecallQuerySchema,
32621
+ status: 200,
32622
+ response: { description: "The days that answer.", view: "JournalRecall" },
32623
+ errors: { 400: true },
32624
+ operationId: "journal_recall"
32625
+ },
32626
+ digest: {
32627
+ method: "get",
32628
+ path: "/journal/digest",
32629
+ scopes: ["journal:read"],
32630
+ tag: "Journal",
32631
+ summary: "The Journal digest",
32632
+ description: "The last 7 days of the key's member's Journal, for the agent's private session context: each day's one line and rating (oldest first), the member's kept milestones a week either side, and `text`, the same as a few plain lines (a few hundred tokens).",
32633
+ afterScopes: "`*` does not cover it.",
32634
+ status: 200,
32635
+ response: { description: "The digest.", view: "JournalDigest" },
32636
+ operationId: "journal_digest"
32637
+ },
32638
+ month: {
32639
+ method: "get",
32640
+ path: "/journal/months/{month}",
32641
+ scopes: ["journal:read"],
32642
+ tag: "Journal",
32643
+ summary: "A Journal month",
32644
+ description: "One month of the key's member: the month's recap (the prose, up to 3 best moments, one pattern line; null until written), the month's days with a Journal day or a Metric value (as `GET /journal/days`), `usual` (the mean day rating over the three months before), the numbers from Progress and the Metrics (what landed and how much was the member's own, each habit's done and due days, the average sleep and the nights to bed after midnight) and the member's own kept milestones that happened in it.",
32645
+ afterScopes: "`*` does not cover it.",
32646
+ pathParams: {
32647
+ month: "The month, `YYYY-MM` (e.g. `2026-10`)."
32648
+ },
32649
+ pathParamSchemas: { month: journalMonthKeySchema },
32650
+ status: 200,
32651
+ response: { description: "The month.", view: "JournalMonth" },
32652
+ errors: { 400: true }
32653
+ },
32654
+ writeMonthRecap: {
32655
+ method: "put",
32656
+ path: "/journal/months/{month}/recap",
32657
+ scopes: ["journal:write"],
32658
+ tag: "Journal",
32659
+ summary: "Write a month's recap",
32660
+ description: "The agent's recap on the 1st or the member's: `body` (markdown) replaces the prose; `moments` (at most 3, `{ text, date?, nodeId? }`) and `pattern` replace theirs when given (`pattern: null` clears it). Owner-only like the days. A month that hasn't started is a 400. Returns the month.",
32661
+ pathParams: {
32662
+ month: "The month, `YYYY-MM` (e.g. `2026-10`)."
32663
+ },
32664
+ pathParamSchemas: { month: journalMonthKeySchema },
32665
+ body: journalWriteRecapInputSchema,
32666
+ status: 200,
32667
+ response: { description: "The month.", view: "JournalMonth" },
32668
+ errors: { 400: true }
32669
+ },
32670
+ year: {
32671
+ method: "get",
32672
+ path: "/journal/years/{year}",
32673
+ scopes: ["journal:read"],
32674
+ tag: "Journal",
32675
+ summary: "A Journal year",
32676
+ description: "One year of the key's member: the year's recap (null until written), the year's days (its rating pixels), each begun month's average rating, rated and kept days and best day, `usual` (the mean day rating of the year before), the numbers from Progress and the Metrics, and the member's own kept milestones of the year.",
32677
+ afterScopes: "`*` does not cover it.",
32678
+ pathParams: {
32679
+ year: "The year, `YYYY` (e.g. `2026`)."
32680
+ },
32681
+ pathParamSchemas: { year: journalYearKeySchema },
32682
+ status: 200,
32683
+ response: { description: "The year.", view: "JournalYear" },
32684
+ errors: { 400: true }
32685
+ },
32686
+ writeYearRecap: {
32687
+ method: "put",
32688
+ path: "/journal/years/{year}/recap",
32689
+ scopes: ["journal:write"],
32690
+ tag: "Journal",
32691
+ summary: "Write a year's recap",
32692
+ description: "The agent's recap on 1 January or the member's, the same input as a week's: `body` replaces the prose; `moments` and `pattern` replace theirs when given. Owner-only like the days. A year that hasn't started is a 400. Returns the year.",
32693
+ pathParams: {
32694
+ year: "The year, `YYYY` (e.g. `2026`)."
32695
+ },
32696
+ pathParamSchemas: { year: journalYearKeySchema },
32697
+ body: journalWriteRecapInputSchema,
32698
+ status: 200,
32699
+ response: { description: "The year.", view: "JournalYear" },
32700
+ errors: { 400: true }
32701
+ }
32702
+ });
32703
+
32301
32704
  // ../../packages/schema/src/operations/keys.ts
32302
32705
  var keyOperations = defineOperations({
32303
32706
  list: {
@@ -32558,6 +32961,31 @@ var nodeOperations = defineOperations({
32558
32961
  }
32559
32962
  });
32560
32963
 
32964
+ // ../../packages/schema/src/operations/notes.ts
32965
+ var noteOperations = defineOperations({
32966
+ ...crudOperations({
32967
+ segment: "notes",
32968
+ tag: "Notes",
32969
+ noun: "note",
32970
+ read: "notes:read",
32971
+ write: "notes:write",
32972
+ create: createNoteInputSchema,
32973
+ update: updateNoteInputSchema,
32974
+ view: "Note"
32975
+ }),
32976
+ backlinks: {
32977
+ method: "get",
32978
+ path: "/notes/{id}/backlinks",
32979
+ scopes: ["notes:read"],
32980
+ tag: "Notes",
32981
+ summary: "List backlinks",
32982
+ description: "The notes that link to this note.",
32983
+ status: 200,
32984
+ response: { description: "The backlinking notes." },
32985
+ errors: { 404: true }
32986
+ }
32987
+ });
32988
+
32561
32989
  // ../../packages/schema/src/operations/occurrences.ts
32562
32990
  var occurrenceOperations = defineOperations({
32563
32991
  list: {
@@ -34828,7 +35256,7 @@ function parseCapture2(input, now) {
34828
35256
 
34829
35257
  // src/meta.ts
34830
35258
  var CLI_NAME = "do";
34831
- var VERSION = "1.307.0";
35259
+ var VERSION = "1.308.1";
34832
35260
  var API_PREFIX = "/api/v1";
34833
35261
  var DEFAULT_API_URL = "http://localhost:3001";
34834
35262
 
@@ -35057,7 +35485,6 @@ function resolveConfig(io, flags) {
35057
35485
  }
35058
35486
 
35059
35487
  // src/contacts-commands.ts
35060
- var WRITE6 = ["contacts:write"];
35061
35488
  var FIELD_FLAGS = [
35062
35489
  { name: "name", type: "string", description: "The display name (or pass it as the argument)." },
35063
35490
  { name: "kind", type: "string", description: "person (the default) or organization." },
@@ -35100,7 +35527,7 @@ var CONTACT_COMMAND_SPECS = [
35100
35527
  summary: "Add a contact: a person or an organisation, with their details.",
35101
35528
  args: [{ name: "name", required: false, description: "The display name." }],
35102
35529
  flags: FIELD_FLAGS,
35103
- scopes: WRITE6,
35530
+ scopes: [...contactOperations.create.scopes],
35104
35531
  returns: "ContactView",
35105
35532
  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>`.'
35106
35533
  },
@@ -35109,7 +35536,7 @@ var CONTACT_COMMAND_SPECS = [
35109
35536
  summary: "Change a contact's details.",
35110
35537
  args: [{ name: "id", required: true, description: "The contact's id." }],
35111
35538
  flags: FIELD_FLAGS,
35112
- scopes: WRITE6,
35539
+ scopes: [...contactOperations.update.scopes],
35113
35540
  returns: "ContactView",
35114
35541
  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)."
35115
35542
  }
@@ -35361,32 +35788,20 @@ var ENTITIES = [
35361
35788
  createInput: "CreateRoutineInput",
35362
35789
  updateInput: "UpdateRoutineInput"
35363
35790
  }),
35364
- {
35791
+ entityFromOperations(noteOperations, {
35365
35792
  name: "notes",
35366
- path: "/notes",
35367
- readScopes: ["notes:read"],
35368
35793
  returns: "Note",
35369
- canGet: true,
35370
35794
  listFlags: [
35371
35795
  { name: "q", description: "full-text search over the title and body", query: "q" },
35372
35796
  { name: "area", description: "filter to notes filed under this area id", query: "areaId" },
35373
35797
  { name: "tag", description: "filter to notes carrying this tag", query: "tag" }
35374
35798
  ],
35375
- write: {
35376
- scopes: ["notes:write"],
35377
- createSchema: createNoteInputSchema,
35378
- createInput: "CreateNoteInput",
35379
- updateSchema: updateNoteInputSchema,
35380
- updateInput: "UpdateNoteInput",
35381
- canDelete: true
35382
- }
35383
- },
35384
- {
35799
+ createInput: "CreateNoteInput",
35800
+ updateInput: "UpdateNoteInput"
35801
+ }),
35802
+ entityFromOperations(contactOperations, {
35385
35803
  name: "contacts",
35386
- path: "/contacts",
35387
- readScopes: ["contacts:read"],
35388
35804
  returns: "Contact",
35389
- canGet: true,
35390
35805
  listFlags: [
35391
35806
  { name: "kind", description: "only person or only organization contacts", query: "kind" },
35392
35807
  {
@@ -35401,15 +35816,9 @@ var ENTITIES = [
35401
35816
  sends: "true"
35402
35817
  }
35403
35818
  ],
35404
- write: {
35405
- scopes: ["contacts:write"],
35406
- createSchema: createContactInputSchema,
35407
- createInput: "CreateContactInput",
35408
- updateSchema: updateContactInputSchema,
35409
- updateInput: "UpdateContactInput",
35410
- canDelete: true
35411
- }
35412
- },
35819
+ createInput: "CreateContactInput",
35820
+ updateInput: "UpdateContactInput"
35821
+ }),
35413
35822
  {
35414
35823
  name: "contact-moments",
35415
35824
  path: "/contact-moments",
@@ -35779,7 +36188,7 @@ function entityOperations(e) {
35779
36188
 
35780
36189
  // src/health-commands.ts
35781
36190
  var READ6 = ["health:read"];
35782
- var WRITE7 = ["health:write"];
36191
+ var WRITE6 = ["health:write"];
35783
36192
  var DATA_FLAG = {
35784
36193
  name: "data",
35785
36194
  type: "string",
@@ -35809,7 +36218,7 @@ function recordSpecs(plural, singular, noun, returns, createInput, updateInput,
35809
36218
  summary: `Add a health ${noun} (--data '${createInput}').`,
35810
36219
  args: [],
35811
36220
  flags: [DATA_FLAG, ...addFlags],
35812
- scopes: WRITE7,
36221
+ scopes: WRITE6,
35813
36222
  returns,
35814
36223
  notes: `Body: ${createInput}. Always private to you; subjectNodeId names who it is about (absent = you).`
35815
36224
  },
@@ -35818,7 +36227,7 @@ function recordSpecs(plural, singular, noun, returns, createInput, updateInput,
35818
36227
  summary: `Change a health ${noun} (--data '${updateInput}').`,
35819
36228
  args: [id],
35820
36229
  flags: [DATA_FLAG],
35821
- scopes: WRITE7,
36230
+ scopes: WRITE6,
35822
36231
  returns,
35823
36232
  notes: `Body: ${updateInput}. null clears a field.`
35824
36233
  },
@@ -35827,7 +36236,7 @@ function recordSpecs(plural, singular, noun, returns, createInput, updateInput,
35827
36236
  summary: `Delete a health ${noun}.`,
35828
36237
  args: [id],
35829
36238
  flags: [],
35830
- scopes: WRITE7,
36239
+ scopes: WRITE6,
35831
36240
  returns: "Deleted",
35832
36241
  notes: "Destructive: needs --allow-destructive. An Agent's delete waits on your confirmation."
35833
36242
  }
@@ -35877,7 +36286,7 @@ var HEALTH_COMMAND_SPECS = [
35877
36286
  description: `Sex, in your words, for the doctor's page ("female"); "" clears it.`
35878
36287
  }
35879
36288
  ],
35880
- scopes: WRITE7,
36289
+ scopes: WRITE6,
35881
36290
  returns: "HealthProfile",
35882
36291
  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."
35883
36292
  },
@@ -35886,7 +36295,7 @@ var HEALTH_COMMAND_SPECS = [
35886
36295
  summary: "Log one health message: find or start the Episode, add a symptom and/or a dose.",
35887
36296
  args: [],
35888
36297
  flags: [DATA_FLAG],
35889
- scopes: WRITE7,
36298
+ scopes: WRITE6,
35890
36299
  returns: "HealthReceipt",
35891
36300
  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.'
35892
36301
  },
@@ -35895,7 +36304,7 @@ var HEALTH_COMMAND_SPECS = [
35895
36304
  summary: `Correct a symptom entry and get a receipt (--data '{"bodyRegion":"ankle_l"}').`,
35896
36305
  args: [{ name: "entry", required: true, description: "The entry's id (its latest version)." }],
35897
36306
  flags: [DATA_FLAG],
35898
- scopes: WRITE7,
36307
+ scopes: WRITE6,
35899
36308
  returns: "HealthReceipt"
35900
36309
  },
35901
36310
  {
@@ -35906,7 +36315,7 @@ var HEALTH_COMMAND_SPECS = [
35906
36315
  { name: "trend", required: true, description: "better|same|worse, against yesterday." }
35907
36316
  ],
35908
36317
  flags: [],
35909
- scopes: WRITE7,
36318
+ scopes: WRITE6,
35910
36319
  returns: "HealthReceipt",
35911
36320
  notes: "One a day: a second check-in the same day corrects the first. The receipt carries its undo."
35912
36321
  },
@@ -35923,7 +36332,7 @@ var HEALTH_COMMAND_SPECS = [
35923
36332
  description: "Post the confirmation card in this thread (by slug); the agent's key only."
35924
36333
  }
35925
36334
  ],
35926
- scopes: WRITE7,
36335
+ scopes: WRITE6,
35927
36336
  returns: "HealthRedFlagsResult",
35928
36337
  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."
35929
36338
  },
@@ -35935,7 +36344,7 @@ var HEALTH_COMMAND_SPECS = [
35935
36344
  { name: "id", required: true, description: "The id from the receipt's undo." }
35936
36345
  ],
35937
36346
  flags: [],
35938
- scopes: WRITE7,
36347
+ scopes: WRITE6,
35939
36348
  returns: "HealthUndoResult",
35940
36349
  notes: "A POST, so an Agent's undo never waits on a confirmation."
35941
36350
  },
@@ -35944,7 +36353,7 @@ var HEALTH_COMMAND_SPECS = [
35944
36353
  summary: "Find the open Episode a complaint belongs to, or start one (--data UpsertHealthEpisodeInput).",
35945
36354
  args: [],
35946
36355
  flags: [DATA_FLAG],
35947
- scopes: WRITE7,
36356
+ scopes: WRITE6,
35948
36357
  returns: "HealthEpisodeUpsert"
35949
36358
  },
35950
36359
  {
@@ -35986,7 +36395,7 @@ var HEALTH_COMMAND_SPECS = [
35986
36395
  summary: "Take a For-the-doctor link back: it stops opening.",
35987
36396
  args: [{ name: "id", required: true, description: "The share's id." }],
35988
36397
  flags: [],
35989
- scopes: WRITE7,
36398
+ scopes: WRITE6,
35990
36399
  returns: "HealthShare"
35991
36400
  },
35992
36401
  ...recordSpecs("conditions", "condition", "condition", "HealthConditionView", "CreateHealthConditionInput", "UpdateHealthConditionInput"),
@@ -36004,7 +36413,7 @@ var HEALTH_COMMAND_SPECS = [
36004
36413
  description: 'Their role on it, in your words ("dermatologist").'
36005
36414
  }
36006
36415
  ],
36007
- scopes: WRITE7,
36416
+ scopes: WRITE6,
36008
36417
  returns: "HealthConditionView",
36009
36418
  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."
36010
36419
  },
@@ -36016,7 +36425,7 @@ var HEALTH_COMMAND_SPECS = [
36016
36425
  { name: "contact", required: true, description: "The Contact's id." }
36017
36426
  ],
36018
36427
  flags: [],
36019
- scopes: WRITE7,
36428
+ scopes: WRITE6,
36020
36429
  returns: "HealthConditionView",
36021
36430
  notes: "An Agent's removal waits on your confirmation, like a delete."
36022
36431
  },
@@ -36053,7 +36462,7 @@ var HEALTH_COMMAND_SPECS = [
36053
36462
  { name: "at", type: "string", description: "When it happened (ISO); defaults to now." },
36054
36463
  { name: "note", type: "string", description: "A free-text note." }
36055
36464
  ],
36056
- scopes: WRITE7,
36465
+ scopes: WRITE6,
36057
36466
  returns: "HealthSymptomEntry",
36058
36467
  notes: "An Agent's entry is source chat. For qualifiers, send --data with LogHealthSymptomInput."
36059
36468
  },
@@ -36069,7 +36478,7 @@ var HEALTH_COMMAND_SPECS = [
36069
36478
  }
36070
36479
  ],
36071
36480
  flags: [DATA_FLAG],
36072
- scopes: WRITE7,
36481
+ scopes: WRITE6,
36073
36482
  returns: "HealthSymptomEntry",
36074
36483
  notes: "Body: CorrectHealthSymptomInput; fields not named are copied. The history stays."
36075
36484
  },
@@ -36081,7 +36490,7 @@ var HEALTH_COMMAND_SPECS = [
36081
36490
  { name: "entry", required: true, description: "The entry's id." }
36082
36491
  ],
36083
36492
  flags: [],
36084
- scopes: WRITE7,
36493
+ scopes: WRITE6,
36085
36494
  returns: "Deleted"
36086
36495
  },
36087
36496
  ...recordSpecs("visits", "visit", "visit", "HealthEncounterView", "CreateHealthEncounterInput", "UpdateHealthEncounterInput"),
@@ -36105,7 +36514,7 @@ var HEALTH_COMMAND_SPECS = [
36105
36514
  { name: "at", type: "string", description: "When (ISO); defaults to now." },
36106
36515
  { name: "note", type: "string", description: "A free-text note." }
36107
36516
  ],
36108
- scopes: WRITE7,
36517
+ scopes: WRITE6,
36109
36518
  returns: "HealthMedicationDose"
36110
36519
  },
36111
36520
  {
@@ -36116,7 +36525,7 @@ var HEALTH_COMMAND_SPECS = [
36116
36525
  { name: "dose", required: true, description: "The dose's id." }
36117
36526
  ],
36118
36527
  flags: [DATA_FLAG],
36119
- scopes: WRITE7,
36528
+ scopes: WRITE6,
36120
36529
  returns: "HealthMedicationDose",
36121
36530
  notes: "Body: UpdateHealthDoseInput."
36122
36531
  },
@@ -36128,7 +36537,7 @@ var HEALTH_COMMAND_SPECS = [
36128
36537
  { name: "dose", required: true, description: "The dose's id." }
36129
36538
  ],
36130
36539
  flags: [],
36131
- scopes: WRITE7,
36540
+ scopes: WRITE6,
36132
36541
  returns: "Deleted"
36133
36542
  },
36134
36543
  ...recordSpecs("labs", "lab", "lab draw", "HealthLabPanelView", "CreateHealthLabPanelInput", "UpdateHealthLabPanelInput"),
@@ -36150,7 +36559,7 @@ var HEALTH_COMMAND_SPECS = [
36150
36559
  description: "The file's media type, when its name doesn't say."
36151
36560
  }
36152
36561
  ],
36153
- scopes: WRITE7,
36562
+ scopes: WRITE6,
36154
36563
  returns: "HealthLabImportResult",
36155
36564
  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."
36156
36565
  },
@@ -36235,7 +36644,7 @@ var HEALTH_COMMAND_SPECS = [
36235
36644
  summary: `Keep a target band or the supplement for an analyte (--data '{"targetLow":40,"targetHigh":60}').`,
36236
36645
  args: [{ name: "analyte", required: true, description: "The analyte key (vitamin_d)." }],
36237
36646
  flags: [DATA_FLAG],
36238
- scopes: WRITE7,
36647
+ scopes: WRITE6,
36239
36648
  returns: "HealthLabAnalyteView",
36240
36649
  notes: "Body: SetHealthLabAnalyteInput. An absent key keeps a field; null clears it."
36241
36650
  }
@@ -36461,7 +36870,7 @@ var COMMAND_SPECS = [
36461
36870
  description: "How long it takes, in minutes (overrides an estimate in the text)."
36462
36871
  }
36463
36872
  ],
36464
- scopes: ["tasks:write"],
36873
+ scopes: [...taskOperations.create.scopes],
36465
36874
  returns: "Task",
36466
36875
  grammar: {
36467
36876
  "#tag": "Adds a tag. Repeatable: '#home #urgent'.",
@@ -36477,9 +36886,9 @@ var COMMAND_SPECS = [
36477
36886
  summary: "Show today's actionable tasks (scheduled, due, overdue, in-progress), then the habits and routines due today.",
36478
36887
  args: [],
36479
36888
  flags: [],
36480
- scopes: ["tasks:read"],
36889
+ scopes: [...taskOperations.list.scopes],
36481
36890
  returns: "TodaySummary",
36482
- notes: "Tasks are composed client-side from GET /api/v1/tasks (the local day from your clock); habits and routines come from GET /api/v1/habits/today (your saved time zone's day) under `habits`, left out when the key lacks habits:read."
36891
+ notes: `Tasks are composed client-side from ${routeOf(taskOperations.list)} (the local day from your clock); habits and routines come from ${routeOf(habitOperations.today)} (your saved time zone's day) under \`habits\`, left out when the key lacks habits:read.`
36483
36892
  },
36484
36893
  {
36485
36894
  path: ["today", "earlier"],
@@ -36502,9 +36911,9 @@ var COMMAND_SPECS = [
36502
36911
  { name: "ids", required: true, description: "One or more task ids." }
36503
36912
  ],
36504
36913
  flags: [],
36505
- scopes: ["tasks:write"],
36914
+ scopes: [...todayOperations.rollover.scopes],
36506
36915
  returns: "RolloverResult",
36507
- notes: "POST /api/v1/today/rollover. `later` reads the day from `do today earlier` first. Let go is cancelled, never done, so it stays out of Landed; undo with `do task reopen`."
36916
+ notes: `${routeOf(todayOperations.rollover)}. \`later\` reads the day from \`do today earlier\` first. Let go is cancelled, never done, so it stays out of Landed; undo with \`do task reopen\`.`
36508
36917
  },
36509
36918
  {
36510
36919
  path: ["now"],
@@ -36579,9 +36988,9 @@ var COMMAND_SPECS = [
36579
36988
  summary: "One Journal day: the summary, entries and highlights, and what happened (Metrics, landed, habits, events, links).",
36580
36989
  args: [{ name: "date", required: true, description: "The day, YYYY-MM-DD." }],
36581
36990
  flags: [],
36582
- scopes: ["journal:read"],
36991
+ scopes: [...journalOperations.day.scopes],
36583
36992
  returns: "JournalSnapshot",
36584
- notes: "GET /api/v1/journal/days/{date} \u2014 your own days only. journal:read is a sensitive scope: a full-access (*) key doesn't hold it. `day` is null until a Journal day was written."
36993
+ notes: `${routeOf(journalOperations.day)} \u2014 your own days only. journal:read is a sensitive scope: a full-access (*) key doesn't hold it. \`day\` is null until a Journal day was written.`
36585
36994
  },
36586
36995
  {
36587
36996
  path: ["journal", "days"],
@@ -36595,18 +37004,18 @@ var COMMAND_SPECS = [
36595
37004
  description: "The last day, YYYY-MM-DD (at most 366 days on)."
36596
37005
  }
36597
37006
  ],
36598
- scopes: ["journal:read"],
37007
+ scopes: [...journalOperations.range.scopes],
36599
37008
  returns: "JournalDayList",
36600
- notes: "GET /api/v1/journal/days \u2014 lists only days with a Journal day or a Metric value. journal:read is a sensitive scope."
37009
+ notes: `${routeOf(journalOperations.range)} \u2014 lists only days with a Journal day or a Metric value. journal:read is a sensitive scope.`
36601
37010
  },
36602
37011
  {
36603
37012
  path: ["journal", "search"],
36604
37013
  summary: "Your Journal days that mention some words, newest first.",
36605
37014
  args: [{ name: "words", required: true, description: "What to look for (2+ characters)." }],
36606
37015
  flags: [],
36607
- scopes: ["journal:read"],
37016
+ scopes: [...journalOperations.search.scopes],
36608
37017
  returns: "JournalSearch",
36609
- notes: "GET /api/v1/journal/search?q= \u2014 your own days only, best match first (at most 30): the words (and a few that mean the same, English and Dutch) in the one-liner, tags, highlights, summary and entries; a date phrase (in March, last week, 14 October, 2026-02) narrows to its window; a heavy or bright question (rough, tired, a great day) adds the days rated 4 or less / 8 or more. `understood` says how it was read. journal:read is a sensitive scope."
37018
+ notes: `${routeOf(journalOperations.search)}?q= \u2014 your own days only, best match first (at most 30): the words (and a few that mean the same, English and Dutch) in the one-liner, tags, highlights, summary and entries; a date phrase (in March, last week, 14 October, 2026-02) narrows to its window; a heavy or bright question (rough, tired, a great day) adds the days rated 4 or less / 8 or more. \`understood\` says how it was read. journal:read is a sensitive scope.`
36610
37019
  },
36611
37020
  {
36612
37021
  path: ["journal", "recall"],
@@ -36622,9 +37031,9 @@ var COMMAND_SPECS = [
36622
37031
  { name: "to", type: "string", description: "The window's last day, YYYY-MM-DD." },
36623
37032
  { name: "limit", type: "string", description: "At most this many days (1\u201330)." }
36624
37033
  ],
36625
- scopes: ["journal:read"],
37034
+ scopes: [...journalOperations.recall.scopes],
36626
37035
  returns: "JournalRecall",
36627
- notes: "GET /api/v1/journal/recall (journal_recall) \u2014 your own days only, dated so an answer can cite them; another member's days never answer. --around can't be combined with --from/--to. journal:read is a sensitive scope."
37036
+ notes: `${routeOf(journalOperations.recall)} (journal_recall) \u2014 your own days only, dated so an answer can cite them; another member's days never answer. --around can't be combined with --from/--to. journal:read is a sensitive scope.`
36628
37037
  },
36629
37038
  {
36630
37039
  path: ["journal", "like"],
@@ -36637,45 +37046,45 @@ var COMMAND_SPECS = [
36637
37046
  description: "At most this many days (1\u201310, 5 by default)."
36638
37047
  }
36639
37048
  ],
36640
- scopes: ["journal:read"],
37049
+ scopes: [...journalOperations.like.scopes],
36641
37050
  returns: "JournalLike",
36642
- notes: "GET /api/v1/journal/days/{date}/like (journal_like) \u2014 your own days only, by Postgres full text over the one-liner, highlights, tags, summary and entries (the day's own words, with a few that mean the same), then shared tags and a close rating. Words, not meanings: no embeddings. A day hidden from memories is never offered. journal:read is a sensitive scope."
37051
+ notes: `${routeOf(journalOperations.like)} (journal_like) \u2014 your own days only, by Postgres full text over the one-liner, highlights, tags, summary and entries (the day's own words, with a few that mean the same), then shared tags and a close rating. Words, not meanings: no embeddings. A day hidden from memories is never offered. journal:read is a sensitive scope.`
36643
37052
  },
36644
37053
  {
36645
37054
  path: ["journal", "digest"],
36646
37055
  summary: "The last 7 days of your Journal as a short digest: each day's line and rating, and the milestones a week either side.",
36647
37056
  args: [],
36648
37057
  flags: [],
36649
- scopes: ["journal:read"],
37058
+ scopes: [...journalOperations.digest.scopes],
36650
37059
  returns: "JournalDigest",
36651
- notes: "GET /api/v1/journal/digest \u2014 `text` is the few plain lines an agent keeps in its private session. journal:read is a sensitive scope."
37060
+ notes: `${routeOf(journalOperations.digest)} \u2014 \`text\` is the few plain lines an agent keeps in its private session. journal:read is a sensitive scope.`
36652
37061
  },
36653
37062
  {
36654
37063
  path: ["journal", "week"],
36655
37064
  summary: "One week of the Journal: its recap (prose, best moments, a pattern), its days, the usual rating and the numbers from Progress.",
36656
37065
  args: [{ name: "week", required: true, description: "The ISO week, e.g. 2026-W42." }],
36657
37066
  flags: [],
36658
- scopes: ["journal:read"],
37067
+ scopes: [...journalOperations.week.scopes],
36659
37068
  returns: "JournalWeek",
36660
- notes: "GET /api/v1/journal/weeks/{week} \u2014 your own week only. `recap` is null until one was written. journal:read is a sensitive scope."
37069
+ notes: `${routeOf(journalOperations.week)} \u2014 your own week only. \`recap\` is null until one was written. journal:read is a sensitive scope.`
36661
37070
  },
36662
37071
  {
36663
37072
  path: ["journal", "month"],
36664
37073
  summary: "One month of the Journal: its recap, its days, the usual rating, the numbers from Progress and the milestones that happened in it.",
36665
37074
  args: [{ name: "month", required: true, description: "The month, e.g. 2026-10." }],
36666
37075
  flags: [],
36667
- scopes: ["journal:read"],
37076
+ scopes: [...journalOperations.month.scopes],
36668
37077
  returns: "JournalMonth",
36669
- notes: "GET /api/v1/journal/months/{month} \u2014 your own month only. `recap` is null until one was written; `usual` is the mean rating over the three months before. journal:read is a sensitive scope."
37078
+ notes: `${routeOf(journalOperations.month)} \u2014 your own month only. \`recap\` is null until one was written; \`usual\` is the mean rating over the three months before. journal:read is a sensitive scope.`
36670
37079
  },
36671
37080
  {
36672
37081
  path: ["journal", "year"],
36673
37082
  summary: "One year of the Journal: its recap, its days (the rating pixels), each month's average and best day, the usual, the numbers and the year's milestones.",
36674
37083
  args: [{ name: "year", required: true, description: "The year, e.g. 2026." }],
36675
37084
  flags: [],
36676
- scopes: ["journal:read"],
37085
+ scopes: [...journalOperations.year.scopes],
36677
37086
  returns: "JournalYear",
36678
- notes: "GET /api/v1/journal/years/{year} \u2014 your own year only. `recap` is null until one was written; `usual` is the mean rating of the year before. journal:read is a sensitive scope."
37087
+ notes: `${routeOf(journalOperations.year)} \u2014 your own year only. \`recap\` is null until one was written; \`usual\` is the mean rating of the year before. journal:read is a sensitive scope.`
36679
37088
  },
36680
37089
  {
36681
37090
  path: ["journal", "recap"],
@@ -36695,9 +37104,9 @@ var COMMAND_SPECS = [
36695
37104
  description: 'The JournalWriteRecapInput as JSON, merged under --body: {"body":"\u2026","moments":[{"text":"\u2026","date":"2026-10-14","nodeId":"\u2026"}],"pattern":"\u2026"}.'
36696
37105
  }
36697
37106
  ],
36698
- scopes: ["journal:write"],
37107
+ scopes: [...journalOperations.writeRecap.scopes],
36699
37108
  returns: "JournalWeek",
36700
- notes: "PUT /api/v1/journal/weeks/{week}/recap (journal_write_recap), /api/v1/journal/months/{month}/recap or /api/v1/journal/years/{year}/recap, by the period's shape. The body replaces the prose; moments and pattern replace theirs when given (pattern null clears it). Returns the period (a JournalWeek, JournalMonth or JournalYear)."
37109
+ notes: `${routeOf(journalOperations.writeRecap)} (journal_write_recap), /api/v1/journal/months/{month}/recap or /api/v1/journal/years/{year}/recap, by the period's shape. The body replaces the prose; moments and pattern replace theirs when given (pattern null clears it). Returns the period (a JournalWeek, JournalMonth or JournalYear).`
36701
37110
  },
36702
37111
  {
36703
37112
  path: ["journal", "write"],
@@ -36713,9 +37122,9 @@ var COMMAND_SPECS = [
36713
37122
  description: 'The JournalWriteDayInput as JSON, merged under the flags: {"summary":"\u2026","highlights":[{"text":"\u2026","nodeId":"\u2026"}],"tags":["Home"],"links":[{"kind":"saw","nodeId":"\u2026"}],"metrics":{"rating":7,"mood":4}}.'
36714
37123
  }
36715
37124
  ],
36716
- scopes: ["journal:write"],
37125
+ scopes: [...journalOperations.write.scopes],
36717
37126
  returns: "JournalSnapshot",
36718
- notes: "POST /api/v1/journal/days/{date} (journal_write_day). Makes the day when missing. Highlights replace; tags and links add. An agent's write shows in the member's change list with Undo."
37127
+ notes: `${routeOf(journalOperations.write)} (journal_write_day). Makes the day when missing. Highlights replace; tags and links add. An agent's write shows in the member's change list with Undo.`
36719
37128
  },
36720
37129
  {
36721
37130
  path: ["journal", "add"],
@@ -36725,9 +37134,9 @@ var COMMAND_SPECS = [
36725
37134
  { name: "text", required: true, description: "The entry, markdown; the rest of the line." }
36726
37135
  ],
36727
37136
  flags: [{ name: "title", type: "string", description: "The entry's title." }],
36728
- scopes: ["journal:write"],
37137
+ scopes: [...journalOperations.addEntry.scopes],
36729
37138
  returns: "JournalSnapshot",
36730
- notes: "POST /api/v1/journal/days/{date}/entries (journal_add_entry)."
37139
+ notes: `${routeOf(journalOperations.addEntry)} (journal_add_entry).`
36731
37140
  },
36732
37141
  {
36733
37142
  path: ["journal", "correct"],
@@ -36740,9 +37149,9 @@ var COMMAND_SPECS = [
36740
37149
  description: 'The JournalCorrectInput as JSON: {"title":"\u2026","summary":null,"highlights":[\u2026],"tags":[\u2026],"removeLinks":["\u2026"],"metrics":{"rating":6},"status":"reviewed","hiddenFromMemories":true}.'
36741
37150
  }
36742
37151
  ],
36743
- scopes: ["journal:write"],
37152
+ scopes: [...journalOperations.correct.scopes],
36744
37153
  returns: "JournalSnapshot",
36745
- notes: "PATCH /api/v1/journal/days/{date} (journal_correct). 404 when the date has no Journal day."
37154
+ notes: `${routeOf(journalOperations.correct)} (journal_correct). 404 when the date has no Journal day.`
36746
37155
  },
36747
37156
  {
36748
37157
  path: ["journal", "import", "push"],
@@ -36762,27 +37171,27 @@ var COMMAND_SPECS = [
36762
37171
  description: "How the days name their folder (default memory/daily)."
36763
37172
  }
36764
37173
  ],
36765
- scopes: ["journal:write"],
37174
+ scopes: [...journalOperations.stageImport.scopes],
36766
37175
  returns: "JournalImportUpload",
36767
- notes: "POST /api/v1/journal/imports, 400 days a request. The day's '## Day summary' becomes its entry; a clear 'n/10' rating is kept, '~5/10' or '3-4/10' becomes a question; health and automation lines never reach the text. Nothing is written into the Journal until the member approves the month (the app only; a key can't approve). Re-running replaces what's still waiting."
37176
+ notes: `${routeOf(journalOperations.stageImport)}, 400 days a request. The day's '## Day summary' becomes its entry; a clear 'n/10' rating is kept, '~5/10' or '3-4/10' becomes a question; health and automation lines never reach the text. Nothing is written into the Journal until the member approves the month (the app only; a key can't approve). Re-running replaces what's still waiting.`
36768
37177
  },
36769
37178
  {
36770
37179
  path: ["journal", "import", "status"],
36771
37180
  summary: "What the backfill has staged: the files, what the parser found, each month's batch.",
36772
37181
  args: [],
36773
37182
  flags: [{ name: "batch", type: "string", description: "Show this month's days (YYYY-MM)." }],
36774
- scopes: ["journal:read"],
37183
+ scopes: [...journalOperations.importOverview.scopes],
36775
37184
  returns: "JournalImportOverview",
36776
- notes: "GET /api/v1/journal/imports. journal:read is a sensitive scope."
37185
+ notes: `${routeOf(journalOperations.importOverview)}. journal:read is a sensitive scope.`
36777
37186
  },
36778
37187
  {
36779
37188
  path: ["journal", "milestones"],
36780
37189
  summary: "Your life timeline: your milestones and the Space's shared ones, oldest first, with the suggestions.",
36781
37190
  args: [],
36782
37191
  flags: [],
36783
- scopes: ["journal:read"],
37192
+ scopes: [...journalOperations.milestones.scopes],
36784
37193
  returns: "JournalMilestoneList",
36785
- notes: "GET /api/v1/journal/milestones. A milestone is Only me unless shared; `status: suggested` is an agent's mark you haven't kept yet. journal:read is a sensitive scope."
37194
+ notes: `${routeOf(journalOperations.milestones)}. A milestone is Only me unless shared; \`status: suggested\` is an agent's mark you haven't kept yet. journal:read is a sensitive scope.`
36786
37195
  },
36787
37196
  {
36788
37197
  path: ["journal", "milestone", "add"],
@@ -36809,9 +37218,9 @@ var COMMAND_SPECS = [
36809
37218
  description: 'The JournalCreateMilestoneInput as JSON, merged under the flags: {"description":"\u2026","visibility":"space","links":["<node id>"],"remindYearly":true,"status":"kept"}.'
36810
37219
  }
36811
37220
  ],
36812
- scopes: ["journal:write"],
37221
+ scopes: [...journalOperations.createMilestone.scopes],
36813
37222
  returns: "JournalMilestone",
36814
- notes: "POST /api/v1/journal/milestones (journal_mark_milestone). Only me unless visibility is space. links point it at what it's about (a Journal day, a paper, a project, a person); remindYearly adds a yearly Key date."
37223
+ notes: `${routeOf(journalOperations.createMilestone)} (journal_mark_milestone). Only me unless visibility is space. links point it at what it's about (a Journal day, a paper, a project, a person); remindYearly adds a yearly Key date.`
36815
37224
  },
36816
37225
  {
36817
37226
  path: ["journal", "milestone", "edit"],
@@ -36825,9 +37234,9 @@ var COMMAND_SPECS = [
36825
37234
  description: 'The JournalUpdateMilestoneInput as JSON: {"title":"\u2026","happenedOn":"2026-10-14","planned":false,"addLinks":["<id>"],"removeLinks":["<id>"],"remindYearly":true}.'
36826
37235
  }
36827
37236
  ],
36828
- scopes: ["journal:write"],
37237
+ scopes: [...journalOperations.updateMilestone.scopes],
36829
37238
  returns: "JournalMilestone",
36830
- notes: "PATCH /api/v1/journal/milestones/{id}."
37239
+ notes: `${routeOf(journalOperations.updateMilestone)}.`
36831
37240
  },
36832
37241
  {
36833
37242
  path: ["journal", "milestone", "remove"],
@@ -36840,9 +37249,9 @@ var COMMAND_SPECS = [
36840
37249
  description: "Required to actually remove it."
36841
37250
  }
36842
37251
  ],
36843
- scopes: ["journal:write"],
37252
+ scopes: [...journalOperations.removeMilestone.scopes],
36844
37253
  returns: "None",
36845
- notes: "DELETE /api/v1/journal/milestones/{id}. Goes to the trash like any node."
37254
+ notes: `${routeOf(journalOperations.removeMilestone)}. Goes to the trash like any node.`
36846
37255
  },
36847
37256
  {
36848
37257
  path: ["journal", "export"],
@@ -36860,9 +37269,9 @@ var COMMAND_SPECS = [
36860
37269
  description: "Write the files under this directory instead of printing them."
36861
37270
  }
36862
37271
  ],
36863
- scopes: ["journal:export"],
37272
+ scopes: [...journalOperations.export.scopes],
36864
37273
  returns: "JournalExport",
36865
- notes: "GET /api/v1/journal/export. Prints `nextSince`; pass it as --since next time. do is the record: the files are read-only copies and are overwritten."
37274
+ notes: `${routeOf(journalOperations.export)}. Prints \`nextSince\`; pass it as --since next time. do is the record: the files are read-only copies and are overwritten.`
36866
37275
  },
36867
37276
  {
36868
37277
  path: ["progress", "commits"],
@@ -36960,7 +37369,7 @@ var COMMAND_SPECS = [
36960
37369
  description: "When it was finished (ISO); back-dates completedAt. Default: now."
36961
37370
  }
36962
37371
  ],
36963
- scopes: ["tasks:write"],
37372
+ scopes: [...new Set([...taskOperations.get.scopes, ...taskOperations.update.scopes])],
36964
37373
  returns: "Task",
36965
37374
  notes: "Sets completedAt (the basis of Landed); --at corrects it on an already-done task."
36966
37375
  },
@@ -37381,7 +37790,7 @@ var COMMAND_SPECS = [
37381
37790
  ],
37382
37791
  scopes: [...habitOperations.checkInByTitle.scopes],
37383
37792
  returns: "HabitSummary",
37384
- notes: `POST /api/v1/habits/check-in. Title or alias, case-insensitive; several matches \u2192 409 with error.details.candidates (id, title, aliases), none \u2192 404 naming close titles; a habit counted from Google Fit, Strava or GitHub \u2192 409. --set writes the difference to the day's total as its own check-in. An Agent's key records the check-in as that Agent's ("<agent>, 07:12"). --note puts a short note on it ("drill done, felt heavy" \u2192 --note "felt heavy"); the reply's lastCheckIn.id is the check-in, for \`do habit note\` later.`
37793
+ notes: `${routeOf(habitOperations.checkInByTitle)}. Title or alias, case-insensitive; several matches \u2192 409 with error.details.candidates (id, title, aliases), none \u2192 404 naming close titles; a habit counted from Google Fit, Strava or GitHub \u2192 409. --set writes the difference to the day's total as its own check-in. An Agent's key records the check-in as that Agent's ("<agent>, 07:12"). --note puts a short note on it ("drill done, felt heavy" \u2192 --note "felt heavy"); the reply's lastCheckIn.id is the check-in, for \`do habit note\` later.`
37385
37794
  },
37386
37795
  {
37387
37796
  path: ["habit", "alias", "add"],
@@ -37393,7 +37802,7 @@ var COMMAND_SPECS = [
37393
37802
  flags: [],
37394
37803
  scopes: [...habitOperations.addAlias.scopes],
37395
37804
  returns: "HabitSummary",
37396
- notes: "POST /api/v1/habits/:id/aliases. One already there (ignoring case) is a no-op; at most 12."
37805
+ notes: `${routeOf(habitOperations.addAlias)}. One already there (ignoring case) is a no-op; at most 12.`
37397
37806
  },
37398
37807
  {
37399
37808
  path: ["habit", "alias", "rm"],
@@ -37405,7 +37814,7 @@ var COMMAND_SPECS = [
37405
37814
  flags: [],
37406
37815
  scopes: [...habitOperations.removeAlias.scopes],
37407
37816
  returns: "HabitSummary",
37408
- notes: "DELETE /api/v1/habits/:id/aliases/:alias. 404 when the habit doesn't have it."
37817
+ notes: `${routeOf(habitOperations.removeAlias)}. 404 when the habit doesn't have it.`
37409
37818
  },
37410
37819
  {
37411
37820
  path: ["habits", "today"],
@@ -37568,7 +37977,7 @@ var COMMAND_SPECS = [
37568
37977
  ],
37569
37978
  scopes: ["habits:read", "habits:write"],
37570
37979
  returns: "AwayState",
37571
- notes: "GET/PUT /api/v1/habits/away. While away, habits leave Now, reminders stop and plants wait; tasks and events carry on. Reading needs habits:read, setting habits:write."
37980
+ notes: `${habitOperations.getAway.method.toUpperCase()}/${routeOf(habitOperations.setAway)}. While away, habits leave Now, reminders stop and plants wait; tasks and events carry on. Reading needs habits:read, setting habits:write.`
37572
37981
  },
37573
37982
  {
37574
37983
  path: ["tap-tags", "tap"],
@@ -37855,7 +38264,7 @@ var COMMAND_SPECS = [
37855
38264
  summary: "List the notes/entities whose body [[wikilinks]] reference a note.",
37856
38265
  args: [{ name: "id", required: true, description: "Note (node) ULID." }],
37857
38266
  flags: [],
37858
- scopes: ["notes:read"],
38267
+ scopes: [...noteOperations.backlinks.scopes],
37859
38268
  returns: "Backlink",
37860
38269
  notes: "The reverse of the links a note's body declares; cross-entity."
37861
38270
  },
@@ -38011,7 +38420,7 @@ var COMMAND_SPECS = [
38011
38420
  ],
38012
38421
  scopes: [...worktreeLinkOperations.set.scopes],
38013
38422
  returns: "WorktreeLink",
38014
- notes: "PUT /api/v1/worktree-links/{node}. --host and --worktree are required."
38423
+ notes: `${routeOf(worktreeLinkOperations.set)}. --host and --worktree are required.`
38015
38424
  },
38016
38425
  {
38017
38426
  path: ["worktree-links", "clear"],
@@ -38725,16 +39134,16 @@ var COMMAND_SPECS = [
38725
39134
  summary: "List feature-modules and whether each is enabled for the caller.",
38726
39135
  args: [],
38727
39136
  flags: [],
38728
- scopes: ["settings:read"],
39137
+ scopes: [...settingsOperations.get.scopes],
38729
39138
  returns: "ModuleStatus",
38730
- notes: "Reads enabledModules from GET /api/v1/settings; an empty set means every module is on."
39139
+ notes: `Reads enabledModules from ${routeOf(settingsOperations.get)}; an empty set means every module is on.`
38731
39140
  },
38732
39141
  {
38733
39142
  path: ["modules", "enable"],
38734
39143
  summary: "Enable a feature-module (surfaces it in nav / quick-add / \u2318K).",
38735
39144
  args: [{ name: "key", required: true, description: "Module key (see `do modules list`)." }],
38736
39145
  flags: [],
38737
- scopes: ["settings:read", "settings:write"],
39146
+ scopes: [...new Set([...settingsOperations.get.scopes, ...settingsOperations.update.scopes])],
38738
39147
  returns: "UserSettings",
38739
39148
  notes: "Reads then patches settings; only the module visibility list changes."
38740
39149
  },
@@ -38743,7 +39152,7 @@ var COMMAND_SPECS = [
38743
39152
  summary: "Disable a feature-module (hides its surfaces \u2014 nothing is deleted).",
38744
39153
  args: [{ name: "key", required: true, description: "Module key (see `do modules list`)." }],
38745
39154
  flags: [],
38746
- scopes: ["settings:read", "settings:write"],
39155
+ scopes: [...new Set([...settingsOperations.get.scopes, ...settingsOperations.update.scopes])],
38747
39156
  returns: "UserSettings",
38748
39157
  notes: "Reads then patches settings; only the module visibility list changes."
38749
39158
  },
@@ -38784,7 +39193,7 @@ var COMMAND_SPECS = [
38784
39193
  flags: [],
38785
39194
  scopes: [...collectionOperations.types.scopes],
38786
39195
  returns: "CollectionTypeSummary",
38787
- notes: "The Type registry is code-defined (ADR 0017); served from GET /api/v1/collection/types."
39196
+ notes: `The Type registry is code-defined (ADR 0017); served from ${routeOf(collectionOperations.types)}.`
38788
39197
  },
38789
39198
  {
38790
39199
  path: ["collection", "list"],
@@ -39104,16 +39513,16 @@ var COMMAND_SPECS = [
39104
39513
  summary: "Focus on one Area for the rest of the day: Now, Tasks and Calendar show only it.",
39105
39514
  args: [{ name: "id", required: false, description: "Area ULID (omit with --off)." }],
39106
39515
  flags: [{ name: "off", type: "boolean", description: "Show everything again." }],
39107
- scopes: ["settings:write"],
39516
+ scopes: [...settingsOperations.update.scopes],
39108
39517
  returns: "UserSettings",
39109
- notes: "PATCH /api/v1/settings { focusAreaId }. Clears itself at the end of the member's day (focusUntil); hidden tasks don't move or go overdue."
39518
+ notes: `${routeOf(settingsOperations.update)} { focusAreaId }. Clears itself at the end of the member's day (focusUntil); hidden tasks don't move or go overdue.`
39110
39519
  },
39111
39520
  {
39112
39521
  path: ["areas", "pin"],
39113
39522
  summary: "Pin an Area under Life in the desktop sidebar (up to five).",
39114
39523
  args: [{ name: "id", required: true, description: "Area ULID." }],
39115
39524
  flags: [],
39116
- scopes: ["settings:read", "settings:write"],
39525
+ scopes: [...new Set([...settingsOperations.get.scopes, ...settingsOperations.update.scopes])],
39117
39526
  returns: "UserSettings",
39118
39527
  notes: "Reads then patches settings.pinnedAreaIds; a pinned Area stays where it is."
39119
39528
  },
@@ -39122,7 +39531,7 @@ var COMMAND_SPECS = [
39122
39531
  summary: "Take an Area out of the desktop sidebar.",
39123
39532
  args: [{ name: "id", required: true, description: "Area ULID." }],
39124
39533
  flags: [],
39125
- scopes: ["settings:read", "settings:write"],
39534
+ scopes: [...new Set([...settingsOperations.get.scopes, ...settingsOperations.update.scopes])],
39126
39535
  returns: "UserSettings",
39127
39536
  notes: "Reads then patches settings.pinnedAreaIds."
39128
39537
  },
@@ -40779,6 +41188,17 @@ var COMMAND_SPECS = [
40779
41188
  returns: "ThankYouView",
40780
41189
  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"}\'`.'
40781
41190
  },
41191
+ {
41192
+ path: ["guest-lists", "rsvp-digest"],
41193
+ summary: "The RSVPs that came in since a moment (default the last 24 hours), for a daily digest.",
41194
+ args: [{ name: "id", required: true, description: "The guest list's id." }],
41195
+ flags: [
41196
+ { name: "since", type: "string", description: "An ISO instant; default 24 hours ago." }
41197
+ ],
41198
+ scopes: [...guestListOperations.rsvpDigest.scopes],
41199
+ returns: "RsvpDigest",
41200
+ notes: "Each household that answered or changed its answer, with who's coming, maybe and not coming by name, how many households still wait, and the same as plain lines in `text`. The couple get each answer as a do notification, never a mail."
41201
+ },
40782
41202
  {
40783
41203
  path: ["guest-lists", "export"],
40784
41204
  summary: "Export for the caterer, the venue, the church or the RSVP answers (CSV) to stdout, or save the floor plan or the paper invitations (PDF) to a file.",
@@ -41108,7 +41528,7 @@ var COMMAND_SPECS = [
41108
41528
  ],
41109
41529
  scopes: [],
41110
41530
  returns: "LoginResult",
41111
- notes: "Writes ~/.config/do/config.json (0600) after a successful GET /api/v1/whoami."
41531
+ notes: `Writes ~/.config/do/config.json (0600) after a successful ${routeOf(whoamiOperations.whoami)}.`
41112
41532
  },
41113
41533
  {
41114
41534
  path: ["config", "set"],
@@ -43290,6 +43710,11 @@ async function dispatchEntity(entity, rest, flags, requireClient) {
43290
43710
  }
43291
43711
  throw usageError(`unknown operation for ${entity.name}: \`${op ?? ""}\` (use ${entityOperations(entity).join(", ")})`);
43292
43712
  }
43713
+ var RECAP_OPERATION = {
43714
+ week: journalOperations.writeRecap,
43715
+ month: journalOperations.writeMonthRecap,
43716
+ year: journalOperations.writeYearRecap
43717
+ };
43293
43718
  function runFixed(spec, ctx) {
43294
43719
  const key = spec.path.join(" ");
43295
43720
  const handler = HANDLERS[key];
@@ -43322,7 +43747,7 @@ var HANDLERS = {
43322
43747
  ...duration ? { durationMinutes: duration } : {}
43323
43748
  });
43324
43749
  const client = requireClient();
43325
- const task = await client.post("/tasks", input);
43750
+ const task = await client.post(pathOf(taskOperations.create), input);
43326
43751
  if (parsed.recurrence) {
43327
43752
  const rule = parseInput(createRecurrenceRuleInputSchema, {
43328
43753
  templateId: id,
@@ -43337,13 +43762,13 @@ var HANDLERS = {
43337
43762
  const tasks = [];
43338
43763
  let cursor;
43339
43764
  do {
43340
- const page = await client.getPage("/tasks", {
43765
+ const page = await client.getPage(pathOf(taskOperations.list), {
43341
43766
  query: { filter: "all", cursor }
43342
43767
  });
43343
43768
  tasks.push(...page.data);
43344
43769
  cursor = page.nextCursor ?? undefined;
43345
43770
  } while (cursor);
43346
- const habits = await client.get("/habits/today").catch((error) => {
43771
+ const habits = await client.get(pathOf(habitOperations.today)).catch((error) => {
43347
43772
  if (error instanceof CliError && (error.code === "auth" || error.exitCode === EXIT.NOT_FOUND))
43348
43773
  return;
43349
43774
  throw error;
@@ -43381,7 +43806,7 @@ var HANDLERS = {
43381
43806
  to = today.toISOString();
43382
43807
  body = { action: "reschedule", to, taskIds };
43383
43808
  } else if (answer === "later") {
43384
- const earlier = await client.get("/today/from-earlier", {
43809
+ const earlier = await client.get(pathOf(todayOperations.fromEarlier), {
43385
43810
  query: { from: today.toISOString() }
43386
43811
  });
43387
43812
  to = earlier.laterThisWeek;
@@ -43389,7 +43814,7 @@ var HANDLERS = {
43389
43814
  } else {
43390
43815
  body = { action: answer === "someday" ? "someday" : "drop", taskIds };
43391
43816
  }
43392
- const result = await client.post("/today/rollover", body);
43817
+ const result = await client.post(pathOf(todayOperations.rollover), body);
43393
43818
  return {
43394
43819
  data: { answer, ...to ? { to } : {}, ...result },
43395
43820
  render: renderFromEarlierAnswer
@@ -43508,7 +43933,7 @@ var HANDLERS = {
43508
43933
  throw usageError("journal day needs a date as YYYY-MM-DD, e.g. `do journal day 2026-10-15`");
43509
43934
  }
43510
43935
  return {
43511
- data: await requireClient().get(`/journal/days/${date}`),
43936
+ data: await requireClient().get(pathOf(journalOperations.day, { date })),
43512
43937
  render: renderJournalDay
43513
43938
  };
43514
43939
  },
@@ -43519,7 +43944,7 @@ var HANDLERS = {
43519
43944
  throw usageError("journal days needs --from and --to as YYYY-MM-DD");
43520
43945
  }
43521
43946
  return {
43522
- data: await requireClient().get("/journal/days", { query: { from, to } }),
43947
+ data: await requireClient().get(pathOf(journalOperations.range), { query: { from, to } }),
43523
43948
  render: renderJournalDays
43524
43949
  };
43525
43950
  },
@@ -43528,7 +43953,7 @@ var HANDLERS = {
43528
43953
  if (q.length < 2)
43529
43954
  throw usageError("journal search needs the words to look for");
43530
43955
  return {
43531
- data: await requireClient().get("/journal/search", { query: { q } }),
43956
+ data: await requireClient().get(pathOf(journalOperations.search), { query: { q } }),
43532
43957
  render: renderJournalSearch
43533
43958
  };
43534
43959
  },
@@ -43545,7 +43970,7 @@ var HANDLERS = {
43545
43970
  };
43546
43971
  parseInput(journalRecallQuerySchema, Object.fromEntries(Object.entries(query).filter(([, v]) => v !== undefined)));
43547
43972
  return {
43548
- data: await requireClient().get("/journal/recall", { query }),
43973
+ data: await requireClient().get(pathOf(journalOperations.recall), { query }),
43549
43974
  render: renderJournalRecall
43550
43975
  };
43551
43976
  },
@@ -43557,18 +43982,20 @@ var HANDLERS = {
43557
43982
  const limit = flagString(flags, "limit");
43558
43983
  parseInput(journalLikeQuerySchema, limit === undefined ? {} : { limit });
43559
43984
  return {
43560
- data: await requireClient().get(`/journal/days/${date}/like`, { query: { limit } }),
43985
+ data: await requireClient().get(pathOf(journalOperations.like, { date }), {
43986
+ query: { limit }
43987
+ }),
43561
43988
  render: renderJournalLike
43562
43989
  };
43563
43990
  },
43564
43991
  "journal digest": async ({ requireClient }) => ({
43565
- data: await requireClient().get("/journal/digest"),
43992
+ data: await requireClient().get(pathOf(journalOperations.digest)),
43566
43993
  render: renderJournalDigest
43567
43994
  }),
43568
43995
  "journal week": async ({ args, requireClient }) => {
43569
43996
  const week = journalWeekArg(args, "week");
43570
43997
  return {
43571
- data: await requireClient().get(`/journal/weeks/${week}`),
43998
+ data: await requireClient().get(pathOf(journalOperations.week, { week })),
43572
43999
  render: renderJournalPeriod
43573
44000
  };
43574
44001
  },
@@ -43578,7 +44005,7 @@ var HANDLERS = {
43578
44005
  throw usageError("journal month needs a month as YYYY-MM, e.g. `do journal month 2026-10`");
43579
44006
  }
43580
44007
  return {
43581
- data: await requireClient().get(`/journal/months/${month}`),
44008
+ data: await requireClient().get(pathOf(journalOperations.month, { month })),
43582
44009
  render: renderJournalPeriod
43583
44010
  };
43584
44011
  },
@@ -43588,7 +44015,7 @@ var HANDLERS = {
43588
44015
  throw usageError("journal year needs a year as YYYY, e.g. `do journal year 2026`");
43589
44016
  }
43590
44017
  return {
43591
- data: await requireClient().get(`/journal/years/${year}`),
44018
+ data: await requireClient().get(pathOf(journalOperations.year, { year })),
43592
44019
  render: renderJournalPeriod
43593
44020
  };
43594
44021
  },
@@ -43604,7 +44031,7 @@ var HANDLERS = {
43604
44031
  body.body = text;
43605
44032
  const input = parseInput(journalWriteRecapInputSchema, body);
43606
44033
  return {
43607
- data: await requireClient().put(`/journal/${kind}s/${period}/recap`, input),
44034
+ data: await requireClient().put(pathOf(RECAP_OPERATION[kind], { [kind]: period }), input),
43608
44035
  render: renderJournalPeriod
43609
44036
  };
43610
44037
  },
@@ -43629,7 +44056,7 @@ var HANDLERS = {
43629
44056
  }
43630
44057
  const input = parseInput(journalWriteDayInputSchema, body);
43631
44058
  return {
43632
- data: await requireClient().post(`/journal/days/${date}`, input),
44059
+ data: await requireClient().post(pathOf(journalOperations.write, { date }), input),
43633
44060
  render: renderJournalDay
43634
44061
  };
43635
44062
  },
@@ -43644,7 +44071,7 @@ var HANDLERS = {
43644
44071
  ...title !== undefined ? { title } : {}
43645
44072
  });
43646
44073
  return {
43647
- data: await requireClient().post(`/journal/days/${date}/entries`, input),
44074
+ data: await requireClient().post(pathOf(journalOperations.addEntry, { date }), input),
43648
44075
  render: renderJournalDay
43649
44076
  };
43650
44077
  },
@@ -43654,7 +44081,7 @@ var HANDLERS = {
43654
44081
  throw usageError("journal correct needs --data");
43655
44082
  const input = parseInput(journalCorrectInputSchema, readDataFlag(flags));
43656
44083
  return {
43657
- data: await requireClient().patch(`/journal/days/${date}`, input),
44084
+ data: await requireClient().patch(pathOf(journalOperations.correct, { date }), input),
43658
44085
  render: renderJournalDay
43659
44086
  };
43660
44087
  },
@@ -43672,7 +44099,7 @@ var HANDLERS = {
43672
44099
  const input = parseInput(journalImportUploadInputSchema, {
43673
44100
  items: items.slice(i, i + JOURNAL_IMPORT_MAX_ITEMS)
43674
44101
  });
43675
- const res = await requireClient().post("/journal/imports", input);
44102
+ const res = await requireClient().post(pathOf(journalOperations.stageImport), input);
43676
44103
  staged += res.staged;
43677
44104
  alreadyImported += res.alreadyImported;
43678
44105
  }
@@ -43684,12 +44111,14 @@ var HANDLERS = {
43684
44111
  "journal import status": async ({ flags, requireClient }) => {
43685
44112
  const batch = flagString(flags, "batch");
43686
44113
  return {
43687
- data: await requireClient().get("/journal/imports", { query: batch ? { batch } : {} }),
44114
+ data: await requireClient().get(pathOf(journalOperations.importOverview), {
44115
+ query: batch ? { batch } : {}
44116
+ }),
43688
44117
  render: renderJournalImportStatus
43689
44118
  };
43690
44119
  },
43691
44120
  "journal milestones": async ({ requireClient }) => ({
43692
- data: await requireClient().get("/journal/milestones"),
44121
+ data: await requireClient().get(pathOf(journalOperations.milestones)),
43693
44122
  render: renderJournalMilestones
43694
44123
  }),
43695
44124
  "journal milestone add": async ({ args, flags, requireClient }) => {
@@ -43710,7 +44139,7 @@ var HANDLERS = {
43710
44139
  body.planned = true;
43711
44140
  const input = parseInput(journalCreateMilestoneInputSchema, body);
43712
44141
  return {
43713
- data: await requireClient().post("/journal/milestones", input),
44142
+ data: await requireClient().post(pathOf(journalOperations.createMilestone), input),
43714
44143
  render: renderJournalMilestone
43715
44144
  };
43716
44145
  },
@@ -43721,20 +44150,20 @@ var HANDLERS = {
43721
44150
  body.status = "kept";
43722
44151
  const input = parseInput(journalUpdateMilestoneInputSchema, body);
43723
44152
  return {
43724
- data: await requireClient().patch(`/journal/milestones/${id}`, input),
44153
+ data: await requireClient().patch(pathOf(journalOperations.updateMilestone, { id }), input),
43725
44154
  render: renderJournalMilestone
43726
44155
  };
43727
44156
  },
43728
44157
  "journal milestone remove": async ({ args, flags, requireClient }) => {
43729
44158
  const id = requireArg(args, 0, "milestone id");
43730
44159
  assertDestructiveAllowed(flags, `remove milestone ${id}`);
43731
- await requireClient().delete(`/journal/milestones/${id}`);
44160
+ await requireClient().delete(pathOf(journalOperations.removeMilestone, { id }));
43732
44161
  return { data: { ok: true, removed: id }, render: renderRecord };
43733
44162
  },
43734
44163
  "journal export": async ({ flags, requireClient }) => {
43735
44164
  const since = flagString(flags, "since");
43736
44165
  const out = flagString(flags, "out");
43737
- const data = await requireClient().get("/journal/export", {
44166
+ const data = await requireClient().get(pathOf(journalOperations.export), {
43738
44167
  query: { since }
43739
44168
  });
43740
44169
  if (out !== undefined) {
@@ -43803,13 +44232,16 @@ var HANDLERS = {
43803
44232
  }
43804
44233
  const client = requireClient();
43805
44234
  if (flagBool(flags, "dry-run")) {
43806
- const before = await client.get(`/notes/${id}`);
44235
+ const before = await client.get(pathOf(noteOperations.get, { id }));
43807
44236
  return {
43808
44237
  data: { dryRun: true, op: "update", entity: "notes", id, before, changes: body },
43809
44238
  render: renderRecord
43810
44239
  };
43811
44240
  }
43812
- return { data: await client.patch(`/notes/${id}`, body), render: renderRecord };
44241
+ return {
44242
+ data: await client.patch(pathOf(noteOperations.update, { id }), body),
44243
+ render: renderRecord
44244
+ };
43813
44245
  },
43814
44246
  "notes detach": async ({ args, flags, requireClient }) => {
43815
44247
  const node = requireArg(args, 0, "node id");
@@ -44300,7 +44732,7 @@ var HANDLERS = {
44300
44732
  "notes backlinks": async ({ args, requireClient }) => {
44301
44733
  const id = requireArg(args, 0, "note id");
44302
44734
  return {
44303
- data: await requireClient().get(`/notes/${id}/backlinks`),
44735
+ data: await requireClient().get(pathOf(noteOperations.backlinks, { id })),
44304
44736
  render: renderList
44305
44737
  };
44306
44738
  },
@@ -44308,14 +44740,17 @@ var HANDLERS = {
44308
44740
  const id = requireArg(args, 0, "task id");
44309
44741
  const at = flagString(flags, "at");
44310
44742
  const client = requireClient();
44311
- const current = await client.get(`/tasks/${id}`);
44743
+ const current = await client.get(pathOf(taskOperations.get, { id }));
44312
44744
  if (current.status === "done" && at === undefined)
44313
44745
  return { data: current, render: renderTask };
44314
44746
  const body = parseInput(updateTaskInputSchema, {
44315
44747
  status: "done",
44316
44748
  ...at !== undefined ? { completedAt: at } : {}
44317
44749
  });
44318
- return { data: await client.patch(`/tasks/${id}`, body), render: renderTask };
44750
+ return {
44751
+ data: await client.patch(pathOf(taskOperations.update, { id }), body),
44752
+ render: renderTask
44753
+ };
44319
44754
  },
44320
44755
  "task cancel": async ({ args, requireClient }) => {
44321
44756
  const id = requireArg(args, 0, "task id");
@@ -44720,10 +45155,16 @@ var HANDLERS = {
44720
45155
  if (off && until)
44721
45156
  throw usageError("`do away off` comes back now; it takes no --until");
44722
45157
  if (!off && !until) {
44723
- return { data: await requireClient().get("/habits/away"), render: renderAway };
45158
+ return {
45159
+ data: await requireClient().get(pathOf(habitOperations.getAway)),
45160
+ render: renderAway
45161
+ };
44724
45162
  }
44725
45163
  const input = parseInput(awayInputSchema, { until: off ? null : until });
44726
- return { data: await requireClient().put("/habits/away", input), render: renderAway };
45164
+ return {
45165
+ data: await requireClient().put(pathOf(habitOperations.setAway), input),
45166
+ render: renderAway
45167
+ };
44727
45168
  },
44728
45169
  "routine done": async ({ args, requireClient }) => {
44729
45170
  const id = requireArg(args, 0, "routine id");
@@ -44927,7 +45368,7 @@ var HANDLERS = {
44927
45368
  };
44928
45369
  },
44929
45370
  "modules list": async ({ requireClient }) => {
44930
- const settings = await requireClient().get("/settings");
45371
+ const settings = await requireClient().get(pathOf(settingsOperations.get));
44931
45372
  const enabled = new Set(effectiveEnabledModules(settings.enabledModules));
44932
45373
  const rows = FEATURE_MODULES.map((module) => ({
44933
45374
  key: module.key,
@@ -45206,7 +45647,10 @@ var HANDLERS = {
45206
45647
  if (id === undefined)
45207
45648
  throw usageError("areas focus needs an area id, or --off to show everything");
45208
45649
  const body = parseInput(updateUserSettingsInputSchema, { focusAreaId: id });
45209
- return { data: await requireClient().patch("/settings", body), render: renderRecord };
45650
+ return {
45651
+ data: await requireClient().patch(pathOf(settingsOperations.update), body),
45652
+ render: renderRecord
45653
+ };
45210
45654
  },
45211
45655
  "areas pin": async ({ args, requireClient }) => setAreaPinned(args, requireClient, true),
45212
45656
  "areas unpin": async ({ args, requireClient }) => setAreaPinned(args, requireClient, false),
@@ -45900,7 +46344,7 @@ var HANDLERS = {
45900
46344
  const input = await saveUpload(path);
45901
46345
  if (dryRun)
45902
46346
  return { ...input, screenshot: input.screenshot ? "(png)" : null };
45903
- return requireClient().post(`/runs/${id}/save-header`, input);
46347
+ return requireClient().post(pathOf(runOperations.uploadSaveHeader, { id }), input);
45904
46348
  };
45905
46349
  if (!flagBool(flags, "watch")) {
45906
46350
  return { data: await send(await resolveSave(target)), render: renderRecord };
@@ -46424,13 +46868,16 @@ var HANDLERS = {
46424
46868
  render: renderRecord
46425
46869
  };
46426
46870
  }
46427
- return { data: await requireClient().post("/contacts", body), render: renderRecord };
46871
+ return {
46872
+ data: await requireClient().post(pathOf(contactOperations.create), body),
46873
+ render: renderRecord
46874
+ };
46428
46875
  },
46429
46876
  "contacts update": async ({ args, flags, requireClient }) => {
46430
46877
  const id = requireArg(args, 0, "contact id");
46431
46878
  const client = requireClient();
46432
46879
  const dryRun = flagBool(flags, "dry-run");
46433
- const before = dryRun || MERGING_FLAGS.some((f) => flags[f] !== undefined) ? await client.get(`/contacts/${id}`) : undefined;
46880
+ const before = dryRun || MERGING_FLAGS.some((f) => flags[f] !== undefined) ? await client.get(pathOf(contactOperations.get, { id })) : undefined;
46434
46881
  const body = parseInput(updateContactInputSchema, contactBodyFromFlags(flags, readDataFlag(flags), "update", before));
46435
46882
  if (Object.keys(body).length === 0)
46436
46883
  throw usageError("nothing to change \u2014 pass a flag such as --email, or --data");
@@ -46448,7 +46895,10 @@ var HANDLERS = {
46448
46895
  render: renderRecord
46449
46896
  };
46450
46897
  }
46451
- return { data: await client.patch(`/contacts/${id}`, body), render: renderRecord };
46898
+ return {
46899
+ data: await client.patch(pathOf(contactOperations.update, { id }), body),
46900
+ render: renderRecord
46901
+ };
46452
46902
  },
46453
46903
  "people tree": async ({ flags, requireClient }) => {
46454
46904
  const side = flagString(flags, "side");
@@ -46642,6 +47092,17 @@ var HANDLERS = {
46642
47092
  render: renderRecord
46643
47093
  };
46644
47094
  },
47095
+ "guest-lists rsvp-digest": async ({ args, flags, requireClient }) => {
47096
+ const id = requireArg(args, 0, "guest list id");
47097
+ const since = flagString(flags, "since");
47098
+ const query = parseInput(rsvpDigestQuerySchema, since ? { since } : {});
47099
+ return {
47100
+ data: await requireClient().get(pathOf(guestListOperations.rsvpDigest, { id }), {
47101
+ query: query.since ? { since: query.since.toISOString() } : {}
47102
+ }),
47103
+ render: renderRecord
47104
+ };
47105
+ },
46645
47106
  "guest-lists export": async ({ args, flags, requireClient }) => {
46646
47107
  const id = requireArg(args, 0, "guest list id");
46647
47108
  const kind = parseInput(guestExportKindSchema, flagString(flags, "kind") ?? "venue");
@@ -46857,7 +47318,7 @@ var HANDLERS = {
46857
47318
  if (!config.apiKey) {
46858
47319
  throw configError("provide --api-key <key> (or set DO_API_KEY) to log in");
46859
47320
  }
46860
- const auth = await requireClient().get("/whoami");
47321
+ const auth = await requireClient().get(pathOf(whoamiOperations.whoami));
46861
47322
  const path = writeConfigFile(io, { apiUrl: config.apiUrl, apiKey: config.apiKey });
46862
47323
  return {
46863
47324
  data: {
@@ -46935,20 +47396,26 @@ async function setModuleVisibility(args, requireClient, on) {
46935
47396
  throw usageError(`unknown module \`${key}\` \u2014 valid keys: ${MODULE_KEYS.join(", ")}`);
46936
47397
  }
46937
47398
  const client = requireClient();
46938
- const settings = await client.get("/settings");
47399
+ const settings = await client.get(pathOf(settingsOperations.get));
46939
47400
  const enabledModules = setModuleEnabled(settings.enabledModules, key, on);
46940
47401
  const body = parseInput(updateUserSettingsInputSchema, { enabledModules });
46941
- return { data: await client.patch("/settings", body), render: renderRecord };
47402
+ return {
47403
+ data: await client.patch(pathOf(settingsOperations.update), body),
47404
+ render: renderRecord
47405
+ };
46942
47406
  }
46943
47407
  async function setAreaPinned(args, requireClient, pinned) {
46944
47408
  const id = requireArg(args, 0, "area id");
46945
47409
  const client = requireClient();
46946
- const settings = await client.get("/settings");
47410
+ const settings = await client.get(pathOf(settingsOperations.get));
46947
47411
  const rest = (settings.pinnedAreaIds ?? []).filter((x) => x !== id);
46948
47412
  const body = parseInput(updateUserSettingsInputSchema, {
46949
47413
  pinnedAreaIds: pinned ? [...rest, id] : rest
46950
47414
  });
46951
- return { data: await client.patch("/settings", body), render: renderRecord };
47415
+ return {
47416
+ data: await client.patch(pathOf(settingsOperations.update), body),
47417
+ render: renderRecord
47418
+ };
46952
47419
  }
46953
47420
  function parseTitleTags(args) {
46954
47421
  const titleWords = [];
@@ -47272,10 +47739,10 @@ async function postText(io, args) {
47272
47739
  return text;
47273
47740
  }
47274
47741
  async function homeSlug(client) {
47275
- const me = await client.get("/whoami");
47742
+ const me = await client.get(pathOf(whoamiOperations.whoami));
47276
47743
  if (!me.agentId)
47277
47744
  throw usageError("this key isn't an Agent's: pass --thread <slug|id>");
47278
- const { data } = await client.getPage("/agents");
47745
+ const { data } = await client.getPage(pathOf(agentOperations.list));
47279
47746
  const agent = data.find((a) => a.id === me.agentId);
47280
47747
  if (!agent)
47281
47748
  throw usageError("couldn't read the agent's name: pass --thread <slug|id>");
@@ -47285,7 +47752,7 @@ var ULID = /^[0-9A-HJKMNP-TV-Z]{26}$/i;
47285
47752
  async function resolveAgentId(client, agent) {
47286
47753
  if (ULID.test(agent))
47287
47754
  return agent;
47288
- const { data } = await client.getPage("/agents");
47755
+ const { data } = await client.getPage(pathOf(agentOperations.list));
47289
47756
  const wanted = agent.trim().toLowerCase();
47290
47757
  const match = data.find((a) => a.name.toLowerCase() === wanted);
47291
47758
  if (!match) {