@robbeverhelst/do 1.297.0 → 1.299.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/do.js +748 -44
  2. package/package.json +1 -1
package/dist/do.js CHANGED
@@ -4516,6 +4516,21 @@ var sensorLinks = pgTable("sensor_links", {
4516
4516
  index("sensor_links_node_idx").on(t.nodeId)
4517
4517
  ]);
4518
4518
 
4519
+ // ../../packages/schema/src/db/tap-tags.ts
4520
+ var tapTagKind = pgEnum("tap_tag_kind", ["habit", "routine"]);
4521
+ var tapTags = pgTable("tap_tags", {
4522
+ id: text("id").primaryKey(),
4523
+ tenantId: text("tenant_id").notNull().references(() => tenants.id),
4524
+ label: text("label").notNull(),
4525
+ kind: tapTagKind("kind").notNull(),
4526
+ targetId: text("target_id").notNull().references(() => nodes.id),
4527
+ createdBy: text("created_by").references(() => users.id, { onDelete: "set null" }),
4528
+ lastTappedAt: timestamp("last_tapped_at", { withTimezone: true }),
4529
+ lastTappedBy: text("last_tapped_by").references(() => users.id, { onDelete: "set null" }),
4530
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
4531
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
4532
+ }, (t) => [index("tap_tags_tenant_idx").on(t.tenantId), index("tap_tags_target_idx").on(t.targetId)]);
4533
+
4519
4534
  // ../../packages/schema/src/db/planning-rules.ts
4520
4535
  var planningRuleKind = pgEnum("planning_rule_kind", [
4521
4536
  "fixed_time",
@@ -5047,6 +5062,38 @@ var contactCircles = pgTable("contact_circles", {
5047
5062
  primaryKey({ columns: [t.contactId, t.circleId] }),
5048
5063
  index("contact_circles_circle_idx").on(t.circleId)
5049
5064
  ]);
5065
+ var personProposalKind = pgEnum("person_proposal_kind", [
5066
+ "birth",
5067
+ "move",
5068
+ "new_job",
5069
+ "wedding",
5070
+ "engagement",
5071
+ "death",
5072
+ "other"
5073
+ ]);
5074
+ var personProposalStatus = pgEnum("person_proposal_status", [
5075
+ "pending",
5076
+ "accepted",
5077
+ "skipped"
5078
+ ]);
5079
+ var personProposals = pgTable("person_proposals", {
5080
+ id: text("id").primaryKey(),
5081
+ tenantId: text("tenant_id").notNull().references(() => tenants.id),
5082
+ userId: text("user_id").notNull().references(() => users.id),
5083
+ contactId: text("contact_id").notNull().references(() => contacts.id, { onDelete: "cascade" }),
5084
+ kind: personProposalKind("kind").notNull().default("other"),
5085
+ title: text("title").notNull(),
5086
+ source: text("source").notNull(),
5087
+ heardAt: timestamp("heard_at", { withTimezone: true }).notNull(),
5088
+ items: jsonb("items").$type().notNull(),
5089
+ status: personProposalStatus("status").notNull().default("pending"),
5090
+ decidedAt: timestamp("decided_at", { withTimezone: true }),
5091
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
5092
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
5093
+ }, (t) => [
5094
+ uniqueIndex("person_proposals_source_uniq").on(t.tenantId, t.userId, t.source),
5095
+ index("person_proposals_user_status_idx").on(t.tenantId, t.userId, t.status)
5096
+ ]);
5050
5097
 
5051
5098
  // ../../packages/schema/src/db/files.ts
5052
5099
  var bytea = customType({
@@ -5096,6 +5143,7 @@ var households = pgTable("households", {
5096
5143
  address: jsonb("address").$type(),
5097
5144
  language: text("language").notNull().default("nl"),
5098
5145
  group: boolean("group").notNull().default(false),
5146
+ addressConfirmedAt: timestamp("address_confirmed_at", { withTimezone: true }),
5099
5147
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
5100
5148
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
5101
5149
  }, (t) => [index("households_tenant_idx").on(t.tenantId)]);
@@ -5109,6 +5157,9 @@ var guestLists = pgTable("guest_lists", {
5109
5157
  rsvpKeyDateId: text("rsvp_key_date_id").references(() => keyDates.id, {
5110
5158
  onDelete: "set null"
5111
5159
  }),
5160
+ finalInfoKeyDateId: text("final_info_key_date_id").references(() => keyDates.id, {
5161
+ onDelete: "set null"
5162
+ }),
5112
5163
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
5113
5164
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
5114
5165
  }, (t) => [uniqueIndex("guest_lists_project_uniq").on(t.tenantId, t.projectId)]);
@@ -5162,12 +5213,15 @@ var guests = pgTable("guests", {
5162
5213
  uniqueIndex("guests_list_contact_uniq").on(t.guestListId, t.contactId),
5163
5214
  index("guests_contact_idx").on(t.contactId)
5164
5215
  ]);
5216
+ var rsvpLinkMode = pgEnum("rsvp_link_mode", ["address", "full"]);
5165
5217
  var rsvpLinks = pgTable("rsvp_links", {
5166
5218
  id: text("id").primaryKey(),
5167
5219
  tenantId: text("tenant_id").notNull().references(() => tenants.id),
5168
5220
  guestListId: text("guest_list_id").notNull().references(() => guestLists.id, { onDelete: "cascade" }),
5169
5221
  householdId: text("household_id").notNull().references(() => households.id),
5170
5222
  tokenHash: text("token_hash").notNull().unique(),
5223
+ mode: rsvpLinkMode("mode").notNull().default("full"),
5224
+ tokenSealed: text("token_sealed"),
5171
5225
  expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
5172
5226
  revokedAt: timestamp("revoked_at", { withTimezone: true }),
5173
5227
  lastUsedAt: timestamp("last_used_at", { withTimezone: true }),
@@ -5448,6 +5502,7 @@ var EVIDENCE_SOURCES = [
5448
5502
  "routine_run",
5449
5503
  "do_activity",
5450
5504
  "github",
5505
+ "phone_location",
5451
5506
  "plan_default"
5452
5507
  ];
5453
5508
  var blockEvidence = pgTable("block_evidence", {
@@ -5464,7 +5519,7 @@ var blockEvidence = pgTable("block_evidence", {
5464
5519
  }, (t) => [
5465
5520
  index("block_evidence_block_idx").on(t.blockId, t.at),
5466
5521
  check("block_evidence_weight_range", sql`${t.weight} BETWEEN 1 AND 5`),
5467
- check("block_evidence_source_check", sql`${t.source} IN ('chat', 'edit_do', 'edit_google', 'google_health', 'wakehook', 'strava', 'habit', 'task', 'routine_run', 'do_activity', 'github', 'plan_default')`)
5522
+ check("block_evidence_source_check", sql`${t.source} IN ('chat', 'edit_do', 'edit_google', 'google_health', 'wakehook', 'strava', 'habit', 'task', 'routine_run', 'do_activity', 'github', 'phone_location', 'plan_default')`)
5468
5523
  ]);
5469
5524
 
5470
5525
  // ../../packages/schema/src/db/food-log.ts
@@ -20635,7 +20690,8 @@ var habitTodayItemSchema = object({
20635
20690
  source: habitEntrySourceSchema,
20636
20691
  by: string2().optional(),
20637
20692
  at: exports_coerce.date()
20638
- }).nullable().default(null)
20693
+ }).nullable().default(null),
20694
+ private: boolean3().default(false)
20639
20695
  });
20640
20696
  var habitTodayRoutineSchema = object({
20641
20697
  routineId: ulidSchema,
@@ -20658,9 +20714,20 @@ var METRICS = {
20658
20714
  mood: { label: "Mood", unit: "/5", min: 1, max: 5, step: 1 },
20659
20715
  rating: { label: "Day rating", unit: "/10", min: 1, max: 10, step: 1 },
20660
20716
  energy: { label: "Energy", unit: "/10", min: 1, max: 10, step: 1 },
20661
- bedtime: { label: "Bedtime", unit: "clock", min: 0, max: 720, step: 5 }
20717
+ bedtime: { label: "Bedtime", unit: "clock", min: 0, max: 720, step: 5 },
20718
+ resting_hr: { label: "Resting heart rate", unit: "bpm", min: 25, max: 200, step: 1 },
20719
+ hrv: { label: "HRV", unit: "ms", min: 1, max: 400, step: 1 }
20662
20720
  };
20663
- var metricKeySchema = _enum2(["weight", "sleep", "mood", "rating", "energy", "bedtime"]);
20721
+ var metricKeySchema = _enum2([
20722
+ "weight",
20723
+ "sleep",
20724
+ "mood",
20725
+ "rating",
20726
+ "energy",
20727
+ "bedtime",
20728
+ "resting_hr",
20729
+ "hrv"
20730
+ ]);
20664
20731
  var metricSourceSchema = _enum2([
20665
20732
  "manual",
20666
20733
  "device",
@@ -22055,6 +22122,29 @@ var animalLostCardStateSchema = object({
22055
22122
  photos: array(animalLostCardPhotoSchema),
22056
22123
  chipOnFile: boolean3()
22057
22124
  });
22125
+ var animalAcquisitionInputSchema = object({
22126
+ pickUpOn: keyDateDaySchema.nullish(),
22127
+ quarantine: boolean3().optional(),
22128
+ extraSteps: array(string2().trim().min(1).max(200)).max(10).optional()
22129
+ }).strict();
22130
+ var animalAcquisitionStepSchema = object({
22131
+ id: ulidSchema,
22132
+ title: string2(),
22133
+ done: boolean3(),
22134
+ quarantine: boolean3()
22135
+ });
22136
+ var animalAcquisitionSchema = object({
22137
+ taskId: ulidSchema,
22138
+ title: string2(),
22139
+ pickUpOn: string2().nullable(),
22140
+ arrived: boolean3(),
22141
+ steps: array(animalAcquisitionStepSchema),
22142
+ blockers: array(string2())
22143
+ });
22144
+ var animalArrivedInputSchema = object({
22145
+ on: keyDateDaySchema.optional(),
22146
+ quarantineMonths: number2().int().min(0).max(12).optional()
22147
+ }).strict();
22058
22148
 
22059
22149
  // ../../packages/schema/src/contracts/sensors.ts
22060
22150
  var sensorProviderValues = ["home_assistant", "unifi"];
@@ -22065,7 +22155,8 @@ var PLACE_SENSOR_READINGS = Object.keys(SHED_READING_TYPES);
22065
22155
  var ASSET_READING_TYPES = {
22066
22156
  state: "asset.state",
22067
22157
  power: "asset.power",
22068
- energy: "asset.energy"
22158
+ energy: "asset.energy",
22159
+ presence: "asset.presence"
22069
22160
  };
22070
22161
  var ASSET_SENSOR_READINGS = Object.keys(ASSET_READING_TYPES);
22071
22162
  var sensorReadingValues = [
@@ -22138,6 +22229,27 @@ var assetReadingSchema = object({
22138
22229
  device: string2().nullable(),
22139
22230
  at: exports_coerce.date()
22140
22231
  });
22232
+ var unifiLinkInputSchema = object({
22233
+ baseUrl: string2().trim().url().refine((u) => /^https?:\/\//i.test(u), "must start with http:// or https://"),
22234
+ apiKey: string2().trim().min(1).max(500),
22235
+ site: string2().trim().min(1).max(100).default("default")
22236
+ }).strict();
22237
+ var unifiStatusSchema = object({
22238
+ linked: boolean3(),
22239
+ host: string2().nullable(),
22240
+ site: string2().nullable(),
22241
+ links: number2().int().nonnegative(),
22242
+ lastReadingAt: string2().nullable()
22243
+ });
22244
+ var unifiDeviceSchema = object({
22245
+ mac: string2(),
22246
+ name: string2(),
22247
+ kind: _enum2(["device", "client"]),
22248
+ model: string2().nullable(),
22249
+ online: boolean3(),
22250
+ firmware: string2().nullable(),
22251
+ link: sensorLinkSchema.nullable()
22252
+ });
22141
22253
 
22142
22254
  // ../../packages/schema/src/contracts/house.ts
22143
22255
  var day = string2().regex(/^\d{4}-\d{2}-\d{2}$/, "must be a calendar day (YYYY-MM-DD)");
@@ -22927,12 +23039,15 @@ var healthProfileSchema = createSelectSchema(healthProfiles, {
22927
23039
  notes: string2().nullable().default(null),
22928
23040
  sex: string2().nullable().default(null)
22929
23041
  });
23042
+ var HEALTH_CONSENT_VERSION = "2026-10-18";
22930
23043
  var healthConsentStateSchema = object({
22931
23044
  granted: boolean3(),
22932
23045
  version: string2().nullable(),
22933
- grantedAt: exports_coerce.date().nullable()
23046
+ grantedAt: exports_coerce.date().nullable(),
23047
+ currentVersion: string2().default(HEALTH_CONSENT_VERSION),
23048
+ renewalNeeded: boolean3().default(false),
23049
+ aiProvider: string2().nullable().default(null)
22934
23050
  });
22935
- var HEALTH_CONSENT_VERSION = "2026-10-18";
22936
23051
  var subject = { subjectNodeId: ulidSchema.nullish() };
22937
23052
  var createHealthConditionInputSchema = object({
22938
23053
  id: ulidSchema.optional(),
@@ -23473,10 +23588,17 @@ var JOURNAL_SEARCH_LIMIT = 30;
23473
23588
  var journalSearchQuerySchema = object({
23474
23589
  q: string2().trim().min(2, "search for at least 2 characters").max(100)
23475
23590
  });
23591
+ var journalSearchUnderstoodSchema = object({
23592
+ words: array(string2()),
23593
+ from: journalDateSchema.nullable(),
23594
+ to: journalDateSchema.nullable(),
23595
+ feeling: _enum2(["low", "high"]).nullable()
23596
+ });
23476
23597
  var journalSearchSchema = object({
23477
23598
  q: string2(),
23478
23599
  timezone: string2().min(1),
23479
- days: array(journalDayListItemSchema).max(JOURNAL_SEARCH_LIMIT)
23600
+ days: array(journalDayListItemSchema).max(JOURNAL_SEARCH_LIMIT),
23601
+ understood: journalSearchUnderstoodSchema.nullable().default(null)
23480
23602
  });
23481
23603
  var journalLinkInputSchema = object({
23482
23604
  kind: string2().trim().regex(/^[a-z][a-z_]{0,31}$/, "a link kind is a short lowercase word"),
@@ -23799,6 +23921,60 @@ var journalWeekSchema = object({
23799
23921
  usual: number2().nullable(),
23800
23922
  numbers: journalWeekNumbersSchema
23801
23923
  });
23924
+ var journalMonthKeySchema = string2().regex(/^\d{4}-(0[1-9]|1[0-2])$/, "must be a month (YYYY-MM)");
23925
+ var journalYearKeySchema = string2().regex(/^\d{4}$/, "must be a year (YYYY)");
23926
+ var journalPeriodKeySchema = union([
23927
+ journalWeekKeySchema,
23928
+ journalMonthKeySchema,
23929
+ journalYearKeySchema
23930
+ ]);
23931
+ function journalPeriodKind(key) {
23932
+ if (journalWeekKeySchema.safeParse(key).success)
23933
+ return "week";
23934
+ if (journalMonthKeySchema.safeParse(key).success)
23935
+ return "month";
23936
+ if (journalYearKeySchema.safeParse(key).success)
23937
+ return "year";
23938
+ return null;
23939
+ }
23940
+ var journalPeriodMilestoneSchema = object({
23941
+ id: string2(),
23942
+ title: string2(),
23943
+ kind: journalMilestoneKindSchema,
23944
+ happenedOn: journalDateSchema,
23945
+ precision: journalMilestonePrecisionSchema,
23946
+ planned: boolean3()
23947
+ });
23948
+ var journalMonthSchema = object({
23949
+ month: journalMonthKeySchema,
23950
+ from: journalDateSchema,
23951
+ to: journalDateSchema,
23952
+ timezone: string2().min(1),
23953
+ recap: journalRecapSchema.nullable(),
23954
+ days: array(journalDayListItemSchema),
23955
+ usual: number2().nullable(),
23956
+ numbers: journalWeekNumbersSchema,
23957
+ milestones: array(journalPeriodMilestoneSchema)
23958
+ });
23959
+ var journalYearMonthSchema = object({
23960
+ month: journalMonthKeySchema,
23961
+ average: number2().nullable(),
23962
+ rated: number2().int().nonnegative(),
23963
+ kept: number2().int().nonnegative(),
23964
+ best: journalDateSchema.nullable()
23965
+ });
23966
+ var journalYearSchema = object({
23967
+ year: journalYearKeySchema,
23968
+ from: journalDateSchema,
23969
+ to: journalDateSchema,
23970
+ timezone: string2().min(1),
23971
+ recap: journalRecapSchema.nullable(),
23972
+ days: array(journalDayListItemSchema),
23973
+ months: array(journalYearMonthSchema),
23974
+ usual: number2().nullable(),
23975
+ numbers: journalWeekNumbersSchema,
23976
+ milestones: array(journalPeriodMilestoneSchema)
23977
+ });
23802
23978
 
23803
23979
  // ../../packages/schema/src/contracts/modules.ts
23804
23980
  var MODULE_KEYS = [
@@ -25258,6 +25434,43 @@ var personNoteUndoResultSchema = object({
25258
25434
  id: ulidSchema,
25259
25435
  removed: number2().int()
25260
25436
  });
25437
+ var personProposalKindSchema = _enum2(personProposalKind.enumValues);
25438
+ var personProposalStatusSchema = _enum2(personProposalStatus.enumValues);
25439
+ var mailSourceSchema = string2().trim().regex(/^gmail:[A-Za-z0-9_-]{1,100}$/, "a mail source is gmail:<message id>");
25440
+ var createPersonProposalInputSchema = object({
25441
+ id: ulidSchema,
25442
+ contactId: ulidSchema.optional(),
25443
+ person: string2().trim().min(1).max(120).optional(),
25444
+ kind: personProposalKindSchema.default("other"),
25445
+ title: string2().trim().min(1).max(200),
25446
+ source: mailSourceSchema,
25447
+ heardAt: exports_coerce.date(),
25448
+ items: array(personNoteItemSchema).min(1).max(10).optional()
25449
+ }).strict().refine((n) => Boolean(n.contactId) !== Boolean(n.person), {
25450
+ message: "name the person by contactId or by person, not both",
25451
+ path: ["contactId"]
25452
+ });
25453
+ var personProposalSchema = object({
25454
+ id: ulidSchema,
25455
+ contactId: ulidSchema,
25456
+ contactTitle: string2(),
25457
+ kind: personProposalKindSchema,
25458
+ title: string2(),
25459
+ source: string2(),
25460
+ heardAt: exports_coerce.date(),
25461
+ items: array(personNoteItemSchema),
25462
+ status: personProposalStatusSchema,
25463
+ decidedAt: exports_coerce.date().nullable(),
25464
+ createdAt: exports_coerce.date()
25465
+ });
25466
+ var personProposalsQuerySchema = object({
25467
+ status: personProposalStatusSchema.optional(),
25468
+ source: mailSourceSchema.optional()
25469
+ });
25470
+ var personProposalAcceptResultSchema = object({
25471
+ proposal: personProposalSchema,
25472
+ receipt: personNoteReceiptSchema
25473
+ });
25261
25474
  var personFindQuerySchema = object({
25262
25475
  q: string2().trim().min(1).max(120),
25263
25476
  limit: exports_coerce.number().int().min(1).max(20).optional()
@@ -25360,6 +25573,7 @@ var peopleOverviewSchema = object({
25360
25573
  phone: string2().nullable().default(null)
25361
25574
  })),
25362
25575
  heardLately: array(contactMomentViewSchema),
25576
+ proposals: array(personProposalSchema).default([]),
25363
25577
  lent: array(object({
25364
25578
  itemId: ulidSchema,
25365
25579
  title: string2(),
@@ -26094,7 +26308,8 @@ var warrantyFieldsSchema = object({
26094
26308
  amount: amount4,
26095
26309
  purchaseDate: keyDateDaySchema,
26096
26310
  warrantyMonths: number2().int().min(0).max(600),
26097
- serial: text4
26311
+ serial: text4,
26312
+ room: text4
26098
26313
  }).partial().strict();
26099
26314
  var payToIban = string2().transform(compactIban).pipe(string2().regex(/^[A-Z]{2}\d{2}[A-Z0-9]{8,30}$/, "expected an IBAN"));
26100
26315
  var payRef = string2().trim().min(1).max(140);
@@ -26351,6 +26566,44 @@ var incidentsQuerySchema = object({
26351
26566
  policyId: ulidSchema.optional()
26352
26567
  });
26353
26568
 
26569
+ // ../../packages/schema/src/contracts/tap-tags.ts
26570
+ var tapTagKindSchema = _enum2(tapTagKind.enumValues);
26571
+ var tapTagViewSchema = object({
26572
+ id: string2(),
26573
+ label: string2(),
26574
+ kind: tapTagKindSchema,
26575
+ targetId: string2(),
26576
+ targetTitle: string2(),
26577
+ path: string2(),
26578
+ lastTappedAt: exports_coerce.date().nullable(),
26579
+ createdAt: exports_coerce.date()
26580
+ });
26581
+ var labelSchema = string2().trim().min(1).max(80);
26582
+ var createTapTagInputSchema = object({
26583
+ id: ulidSchema,
26584
+ label: labelSchema,
26585
+ kind: tapTagKindSchema,
26586
+ targetId: string2().min(1)
26587
+ }).strict();
26588
+ var updateTapTagInputSchema = object({
26589
+ label: labelSchema.optional(),
26590
+ kind: tapTagKindSchema.optional(),
26591
+ targetId: string2().min(1).optional()
26592
+ }).strict().refine((v) => v.kind === undefined === (v.targetId === undefined), {
26593
+ message: "kind and targetId are changed together",
26594
+ path: ["targetId"]
26595
+ }).refine((v) => Object.keys(v).length > 0, { message: "nothing to change" });
26596
+ var tapUndoSchema = discriminatedUnion("kind", [
26597
+ object({ kind: literal("habit_entry"), habitId: string2(), entryId: string2() }),
26598
+ object({ kind: literal("routine_run"), runId: string2(), stepRunIds: array(string2()) })
26599
+ ]);
26600
+ var tapResultSchema = object({
26601
+ tag: tapTagViewSchema,
26602
+ outcome: _enum2(["done", "already"]),
26603
+ line: string2(),
26604
+ undo: tapUndoSchema.nullable()
26605
+ });
26606
+
26354
26607
  // ../../packages/schema/src/contracts/planning-rules.ts
26355
26608
  var PLANNING_RULE_KINDS = planningRuleKind.enumValues;
26356
26609
  var planningRuleKindSchema = _enum2(PLANNING_RULE_KINDS);
@@ -27237,6 +27490,7 @@ var householdAddressSchema = object({
27237
27490
  var householdRowSchema = createSelectSchema(households, {
27238
27491
  address: householdAddressSchema.nullable(),
27239
27492
  group: boolean3().default(false),
27493
+ addressConfirmedAt: exports_coerce.date().nullable(),
27240
27494
  createdAt: exports_coerce.date(),
27241
27495
  updatedAt: exports_coerce.date()
27242
27496
  });
@@ -27264,6 +27518,7 @@ var guestListSchema = createSelectSchema(guestLists, {
27264
27518
  var guestListViewSchema = guestListSchema.extend({
27265
27519
  projectTitle: string2(),
27266
27520
  rsvpBy: keyDateDaySchema.nullable(),
27521
+ finalInfoFrom: keyDateDaySchema.nullable().default(null),
27267
27522
  projectDay: keyDateDaySchema.nullable().default(null)
27268
27523
  });
27269
27524
  var createGuestListInputSchema = object({
@@ -27272,7 +27527,8 @@ var createGuestListInputSchema = object({
27272
27527
  tiers: array(guestTierSchema).min(1).max(6).optional(),
27273
27528
  caps: capsSchema.optional(),
27274
27529
  sides: array(sideSchema).max(4).optional(),
27275
- rsvpBy: keyDateDaySchema.nullish()
27530
+ rsvpBy: keyDateDaySchema.nullish(),
27531
+ finalInfoFrom: keyDateDaySchema.nullish()
27276
27532
  }).strict();
27277
27533
  var updateGuestListInputSchema = createGuestListInputSchema.omit({ id: true, projectId: true }).partial().strict();
27278
27534
  var guestListsQuerySchema = object({ projectId: ulidSchema.optional() });
@@ -27344,6 +27600,7 @@ var guestListOverviewSchema = object({
27344
27600
  householdId: ulidSchema.nullable(),
27345
27601
  title: string2(),
27346
27602
  address: householdAddressSchema.nullable(),
27603
+ addressConfirmedAt: exports_coerce.date().nullable(),
27347
27604
  language: rsvpLanguageSchema,
27348
27605
  group: boolean3().default(false),
27349
27606
  guests: array(guestViewSchema)
@@ -27369,16 +27626,20 @@ var guestListOverviewSchema = object({
27369
27626
  })
27370
27627
  });
27371
27628
  var RSVP_TOKEN_RE = /^rsvp_[A-Za-z0-9_-]{43}$/;
27629
+ var RSVP_LINK_MODES = ["address", "full"];
27630
+ var rsvpLinkModeSchema = _enum2(RSVP_LINK_MODES);
27372
27631
  var rsvpLinkSchema = object({
27373
27632
  id: ulidSchema,
27374
27633
  guestListId: ulidSchema,
27375
27634
  householdId: ulidSchema,
27376
27635
  householdTitle: string2(),
27636
+ mode: rsvpLinkModeSchema,
27377
27637
  expiresAt: exports_coerce.date(),
27378
27638
  revokedAt: exports_coerce.date().nullable(),
27379
27639
  lastUsedAt: exports_coerce.date().nullable(),
27380
27640
  createdAt: exports_coerce.date(),
27381
- live: boolean3()
27641
+ live: boolean3(),
27642
+ revealable: boolean3()
27382
27643
  });
27383
27644
  var mintedRsvpLinkSchema = rsvpLinkSchema.extend({
27384
27645
  token: string2().regex(RSVP_TOKEN_RE),
@@ -27387,6 +27648,7 @@ var mintedRsvpLinkSchema = rsvpLinkSchema.extend({
27387
27648
  var createRsvpLinksInputSchema = object({
27388
27649
  guestListId: ulidSchema,
27389
27650
  householdIds: array(ulidSchema).min(1).max(500).optional(),
27651
+ mode: rsvpLinkModeSchema.optional(),
27390
27652
  expiresAt: exports_coerce.date().optional()
27391
27653
  }).strict();
27392
27654
  var rsvpLinksQuerySchema = object({ guestListId: ulidSchema });
@@ -27398,14 +27660,29 @@ var publicRsvpGuestSchema = object({
27398
27660
  rsvp: answersSchema,
27399
27661
  diet: string2().nullable(),
27400
27662
  plusOne: boolean3(),
27401
- plusOneName: string2().nullable()
27663
+ plusOneName: string2().nullable(),
27664
+ table: string2().nullable().default(null)
27665
+ }).strict();
27666
+ var publicRsvpEventSchema = object({
27667
+ key: guestTierSchema,
27668
+ name: object({ nl: string2().optional(), pt: string2().optional() }),
27669
+ startsAt: exports_coerce.date().nullable(),
27670
+ endsAt: exports_coerce.date().nullable(),
27671
+ placeName: string2().nullable(),
27672
+ address: string2().nullable(),
27673
+ mapUrl: string2().nullable(),
27674
+ dressCode: object({ nl: string2().optional(), pt: string2().optional() }).nullable(),
27675
+ note: object({ nl: string2().optional(), pt: string2().optional() }).nullable()
27402
27676
  }).strict();
27403
27677
  var publicRsvpViewSchema = object({
27678
+ mode: rsvpLinkModeSchema,
27404
27679
  language: rsvpLanguageSchema,
27405
27680
  event: string2(),
27406
27681
  household: string2(),
27407
27682
  rsvpBy: keyDateDaySchema.nullable(),
27683
+ finalInfoFrom: keyDateDaySchema.nullable().default(null),
27408
27684
  tiers: array(guestTierSchema),
27685
+ events: array(publicRsvpEventSchema).default([]),
27409
27686
  guests: array(publicRsvpGuestSchema),
27410
27687
  address: householdAddressSchema.nullable(),
27411
27688
  answeredAt: exports_coerce.date().nullable()
@@ -27417,9 +27694,12 @@ var submitRsvpInputSchema = object({
27417
27694
  diet: string2().trim().max(200).nullish(),
27418
27695
  plusOneName: string2().trim().max(120).nullish(),
27419
27696
  email: string2().trim().email().max(254).nullish()
27420
- }).strict()).min(1).max(30),
27421
- address: householdAddressSchema.nullish()
27422
- }).strict();
27697
+ }).strict()).max(30).default([]),
27698
+ address: householdAddressSchema.nullish(),
27699
+ language: rsvpLanguageSchema.optional()
27700
+ }).strict().refine((i) => i.guests.length > 0 || !!i.address || !!i.language, {
27701
+ message: "nothing to send: answers, an address or a language"
27702
+ });
27423
27703
  var eventTextSchema = (max) => object({
27424
27704
  nl: string2().trim().min(1).max(max).optional(),
27425
27705
  pt: string2().trim().min(1).max(max).optional()
@@ -27488,7 +27768,7 @@ var createSeatingProposalInputSchema = object({
27488
27768
  return new Set(ids).size === ids.length;
27489
27769
  }, { message: "a guest can sit at one table only", path: ["tables"] });
27490
27770
  var seatingProposalsQuerySchema = object({ guestListId: ulidSchema });
27491
- var GUEST_EXPORT_KINDS = ["caterer", "venue", "floor-plan", "church"];
27771
+ var GUEST_EXPORT_KINDS = ["caterer", "venue", "floor-plan", "church", "answers"];
27492
27772
  var guestExportKindSchema = _enum2(GUEST_EXPORT_KINDS);
27493
27773
  var thankYouGiftSchema = object({
27494
27774
  momentId: ulidSchema,
@@ -27829,6 +28109,52 @@ var googleContactsImportResultSchema = object({
27829
28109
  missing: array(string2())
27830
28110
  });
27831
28111
 
28112
+ // ../../packages/schema/src/contracts/github.ts
28113
+ var GITHUB_RESOURCES = ["commits", "pull_requests", "issues"];
28114
+ var githubResourceSchema = _enum2(GITHUB_RESOURCES);
28115
+ var githubBase = {
28116
+ repo: string2().min(1),
28117
+ url: string2().min(1)
28118
+ };
28119
+ var githubCommitValueSchema = object({
28120
+ ...githubBase,
28121
+ sha: string2().min(1),
28122
+ message: string2().default(""),
28123
+ by: _enum2(["human", "agent"]).optional(),
28124
+ agent: string2().nullish()
28125
+ }).strict();
28126
+ var githubPullRequestValueSchema = object({
28127
+ ...githubBase,
28128
+ number: number2().int().positive(),
28129
+ title: string2().default(""),
28130
+ state: string2().min(1),
28131
+ merged: boolean3().default(false)
28132
+ }).strict();
28133
+ var githubIssueValueSchema = object({
28134
+ ...githubBase,
28135
+ number: number2().int().positive(),
28136
+ title: string2().default(""),
28137
+ state: string2().min(1)
28138
+ }).strict();
28139
+ var githubCommitsQuerySchema = object({
28140
+ days: exports_coerce.number().int().refine((d) => d === 7 || d === 30, "7 or 30").default(7)
28141
+ });
28142
+ var githubCommitsDaySchema = object({
28143
+ day: string2(),
28144
+ byYou: number2().int(),
28145
+ byAgents: number2().int()
28146
+ });
28147
+ var githubCommitsSchema = object({
28148
+ days: number2().int(),
28149
+ from: string2(),
28150
+ to: string2(),
28151
+ byYou: number2().int(),
28152
+ byAgents: number2().int(),
28153
+ unsorted: number2().int(),
28154
+ agents: array(object({ name: string2(), count: number2().int() })),
28155
+ perDay: array(githubCommitsDaySchema)
28156
+ });
28157
+
27832
28158
  // ../../packages/schema/src/contracts/calendar.ts
27833
28159
  var calendarEventLinkSchema = createSelectSchema(calendarEventLinks, {
27834
28160
  createdAt: exports_coerce.date(),
@@ -29421,7 +29747,7 @@ function parseCapture2(input, now) {
29421
29747
 
29422
29748
  // src/meta.ts
29423
29749
  var CLI_NAME = "do";
29424
- var VERSION = "1.297.0";
29750
+ var VERSION = "1.299.0";
29425
29751
  var API_PREFIX = "/api/v1";
29426
29752
  var DEFAULT_API_URL = "http://localhost:3001";
29427
29753
 
@@ -30326,6 +30652,22 @@ var ENTITIES = [
30326
30652
  canDelete: true
30327
30653
  }
30328
30654
  },
30655
+ {
30656
+ name: "tap-tags",
30657
+ path: "/tap-tags",
30658
+ readScope: "habits:read",
30659
+ returns: "TapTagView",
30660
+ canGet: true,
30661
+ listFlags: [],
30662
+ write: {
30663
+ scope: "habits:write",
30664
+ createSchema: createTapTagInputSchema,
30665
+ createInput: "CreateTapTagInput",
30666
+ updateSchema: updateTapTagInputSchema,
30667
+ updateInput: "UpdateTapTagInput",
30668
+ canDelete: true
30669
+ }
30670
+ },
30329
30671
  {
30330
30672
  name: "animals",
30331
30673
  path: "/animals",
@@ -30584,7 +30926,8 @@ var HEALTH_COMMAND_SPECS = [
30584
30926
  args: [],
30585
30927
  flags: [],
30586
30928
  scopes: READ,
30587
- returns: "HealthConsentState"
30929
+ returns: "HealthConsentState",
30930
+ 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)."
30588
30931
  },
30589
30932
  {
30590
30933
  path: ["health", "profile"],
@@ -30700,6 +31043,21 @@ var HEALTH_COMMAND_SPECS = [
30700
31043
  scopes: READ,
30701
31044
  returns: "HealthSummary"
30702
31045
  },
31046
+ {
31047
+ path: ["health", "export"],
31048
+ summary: "Take every health record with you: one FHIR R4 Bundle (JSON) with the documents inline.",
31049
+ args: [],
31050
+ flags: [
31051
+ {
31052
+ name: "no-files",
31053
+ type: "boolean",
31054
+ description: "Leave the documents' bytes out (their DocumentReferences stay)."
31055
+ }
31056
+ ],
31057
+ scopes: ["health:export"],
31058
+ returns: "HealthExportBundle",
31059
+ notes: "Streams GET /api/v1/health/export verbatim: redirect it to a file (`do health export > health.json`). The export shows in your Who looked."
31060
+ },
30703
31061
  {
30704
31062
  path: ["health", "shares"],
30705
31063
  summary: "Your For-the-doctor links: sections, expiry, opens; never the secret.",
@@ -31316,7 +31674,7 @@ var COMMAND_SPECS = [
31316
31674
  flags: [],
31317
31675
  scopes: ["journal:read"],
31318
31676
  returns: "JournalSearch",
31319
- notes: "GET /api/v1/journal/search?q= \u2014 matches the one-liner, highlights, the summary and entries of your own days (at most 30). journal:read is a sensitive scope."
31677
+ 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."
31320
31678
  },
31321
31679
  {
31322
31680
  path: ["journal", "recall"],
@@ -31354,10 +31712,34 @@ var COMMAND_SPECS = [
31354
31712
  returns: "JournalWeek",
31355
31713
  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."
31356
31714
  },
31715
+ {
31716
+ path: ["journal", "month"],
31717
+ summary: "One month of the Journal: its recap, its days, the usual rating, the numbers from Progress and the milestones that happened in it.",
31718
+ args: [{ name: "month", required: true, description: "The month, e.g. 2026-10." }],
31719
+ flags: [],
31720
+ scopes: ["journal:read"],
31721
+ returns: "JournalMonth",
31722
+ 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."
31723
+ },
31724
+ {
31725
+ path: ["journal", "year"],
31726
+ 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.",
31727
+ args: [{ name: "year", required: true, description: "The year, e.g. 2026." }],
31728
+ flags: [],
31729
+ scopes: ["journal:read"],
31730
+ returns: "JournalYear",
31731
+ 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."
31732
+ },
31357
31733
  {
31358
31734
  path: ["journal", "recap"],
31359
- summary: "Write or replace a week's recap: the prose, its best moments and one pattern line.",
31360
- args: [{ name: "week", required: true, description: "The ISO week, e.g. 2026-W42." }],
31735
+ summary: "Write or replace a week's, a month's or a year's recap: the prose, its best moments and one pattern line.",
31736
+ args: [
31737
+ {
31738
+ name: "period",
31739
+ required: true,
31740
+ description: "The ISO week (2026-W42), the month (2026-10) or the year (2026)."
31741
+ }
31742
+ ],
31361
31743
  flags: [
31362
31744
  { name: "body", type: "string", description: "The recap in markdown." },
31363
31745
  {
@@ -31368,7 +31750,7 @@ var COMMAND_SPECS = [
31368
31750
  ],
31369
31751
  scopes: ["journal:write"],
31370
31752
  returns: "JournalWeek",
31371
- notes: "PUT /api/v1/journal/weeks/{week}/recap (journal_write_recap). The body replaces the prose; moments and pattern replace theirs when given (pattern null clears it). Returns the week."
31753
+ 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)."
31372
31754
  },
31373
31755
  {
31374
31756
  path: ["journal", "write"],
@@ -31535,6 +31917,15 @@ var COMMAND_SPECS = [
31535
31917
  returns: "JournalExport",
31536
31918
  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."
31537
31919
  },
31920
+ {
31921
+ path: ["progress", "commits"],
31922
+ summary: "Your GitHub commits: the ones you made, and your agents' on your account.",
31923
+ args: [],
31924
+ flags: [{ name: "days", type: "string", description: "7 (default) or 30." }],
31925
+ scopes: ["habits:read"],
31926
+ returns: "GithubCommits",
31927
+ 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."
31928
+ },
31538
31929
  {
31539
31930
  path: ["progress", "questions"],
31540
31931
  summary: "Insights' answers: late nights vs the next day, the morning routine, good runs, bedtime, best days, reading.",
@@ -32232,9 +32623,18 @@ var COMMAND_SPECS = [
32232
32623
  returns: "AwayState",
32233
32624
  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."
32234
32625
  },
32626
+ {
32627
+ path: ["tap-tags", "tap"],
32628
+ summary: "Tap a tag as if the phone read its sticker: check its habit in, or mark today's run of its routine done.",
32629
+ args: [{ name: "id", required: true, description: "The tag id (the ULID in /t/<id>)." }],
32630
+ flags: [],
32631
+ scopes: ["habits:write", "routines:write"],
32632
+ returns: "TapResult",
32633
+ notes: "POST /api/v1/tap-tags/{id}/tap. Acts as the key's member; a second tap within 30 s does nothing (outcome already). The receipt's undo names the check-in (DELETE /habits/{habitId}/entries/{entryId}) or the run's steps (POST /routines/runs/{runId}/undo)."
32634
+ },
32235
32635
  {
32236
32636
  path: ["metrics", "list"],
32237
- summary: "Show weight, sleep, mood, the day rating, energy and bedtime as daily trend lines (no target, no streak).",
32637
+ 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.",
32238
32638
  args: [],
32239
32639
  flags: [{ name: "days", type: "string", description: "How far back, 1\u2013730 (default 90)." }],
32240
32640
  scopes: ["habits:read"],
@@ -32242,7 +32642,7 @@ var COMMAND_SPECS = [
32242
32642
  },
32243
32643
  {
32244
32644
  path: ["metrics", "sessions"],
32245
- summary: "Show workout sessions your connections brought in (Google Fit, Strava), newest first.",
32645
+ summary: "Show workout sessions your connections brought in (Google Fit, Google Health, Strava), newest first.",
32246
32646
  args: [],
32247
32647
  flags: [{ name: "days", type: "string", description: "How far back, 1\u2013365 (default 14)." }],
32248
32648
  scopes: ["habits:read"],
@@ -32250,9 +32650,13 @@ var COMMAND_SPECS = [
32250
32650
  },
32251
32651
  {
32252
32652
  path: ["metrics", "log"],
32253
- summary: "Log a value: weight (kg), sleep (h), mood (1\u20135), rating (1\u201310), energy (1\u201310) or bedtime (HH:MM).",
32653
+ summary: "Log a value: weight (kg), sleep (h), mood (1\u20135), rating (1\u201310), energy (1\u201310), bedtime (HH:MM), resting_hr (bpm) or hrv (ms; these two need health:write).",
32254
32654
  args: [
32255
- { name: "key", required: true, description: "weight|sleep|mood|rating|energy|bedtime." },
32655
+ {
32656
+ name: "key",
32657
+ required: true,
32658
+ description: "weight|sleep|mood|rating|energy|bedtime|resting_hr|hrv."
32659
+ },
32256
32660
  {
32257
32661
  name: "value",
32258
32662
  required: true,
@@ -34691,6 +35095,55 @@ var COMMAND_SPECS = [
34691
35095
  returns: "void",
34692
35096
  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."
34693
35097
  },
35098
+ {
35099
+ path: ["animals", "acquisition"],
35100
+ summary: "Bringing a planned animal home: its checklist.",
35101
+ args: [{ name: "id", required: true, description: "Animal ULID." }],
35102
+ flags: [],
35103
+ scopes: ["animals:read"],
35104
+ returns: "AnimalAcquisition",
35105
+ 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.'
35106
+ },
35107
+ {
35108
+ path: ["animals", "acquisition-plan"],
35109
+ summary: "Plan bringing a planned animal home: a Task with a step per line of the checklist.",
35110
+ args: [{ name: "id", required: true, description: "Animal ULID." }],
35111
+ flags: [
35112
+ {
35113
+ name: "pick-up",
35114
+ type: "string",
35115
+ description: "The pick-up day, YYYY-MM-DD (the Task's due day)."
35116
+ },
35117
+ {
35118
+ name: "data",
35119
+ type: "string",
35120
+ description: 'JSON: `{ "quarantine": true, "extraSteps": ["Buy a UV lamp"] }`.'
35121
+ }
35122
+ ],
35123
+ scopes: ["animals:write"],
35124
+ returns: "AnimalAcquisition",
35125
+ 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)."
35126
+ },
35127
+ {
35128
+ path: ["animals", "arrived"],
35129
+ summary: "A planned animal is home: its status, the quarantine Period, the Task done.",
35130
+ args: [{ name: "id", required: true, description: "Animal ULID." }],
35131
+ flags: [
35132
+ {
35133
+ name: "on",
35134
+ type: "string",
35135
+ description: "The day it came home, YYYY-MM-DD (today by default)."
35136
+ },
35137
+ {
35138
+ name: "quarantine-months",
35139
+ type: "string",
35140
+ description: "How long it stays apart (0\u201312); 3 when its checklist has the quarantine step."
35141
+ }
35142
+ ],
35143
+ scopes: ["animals:write"],
35144
+ returns: "AnimalDetail",
35145
+ notes: "POST /api/v1/animals/{id}/arrived. Sets acquiredOn and the status (`quarantine` with a Period, else `active`); open steps stay open."
35146
+ },
34694
35147
  {
34695
35148
  path: ["runs", "change"],
34696
35149
  summary: "Apply a chat update to a run: its changes, with a receipt and Undo.",
@@ -34812,6 +35265,33 @@ var COMMAND_SPECS = [
34812
35265
  returns: "SensorSyncResult",
34813
35266
  notes: "POST /api/v1/sensors/home-assistant/sync. A room's readings feed its card and the room alarm; an asset's show on its page. Returns entities, written, skipped, missing and the error (unauthorized, unavailable, bad_response). 409 before Home Assistant is linked."
34814
35267
  },
35268
+ {
35269
+ path: ["sensors", "unifi"],
35270
+ summary: "Whether the Space has linked UniFi, how many devices are mapped, the newest reading.",
35271
+ args: [],
35272
+ flags: [],
35273
+ scopes: ["places:read"],
35274
+ returns: "UnifiStatus",
35275
+ notes: "GET /api/v1/sensors/unifi. Linking UniFi (address + API key) is a member's, in Settings \u2192 Home; a key never sees the API key."
35276
+ },
35277
+ {
35278
+ path: ["sensors", "unifi-devices"],
35279
+ summary: "Every device and connected client UniFi sees, with the asset each is mapped to.",
35280
+ args: [],
35281
+ flags: [],
35282
+ scopes: ["places:read", "collections:read"],
35283
+ returns: "UnifiDevice[]",
35284
+ notes: "GET /api/v1/sensors/unifi/devices. Map one with `do sensors map <mac> <asset> --reading presence --provider unifi`, or give the asset `external.unifiMac` on its facet. 409 before UniFi is linked."
35285
+ },
35286
+ {
35287
+ path: ["sensors", "unifi-sync"],
35288
+ summary: "Put every mapped device's presence (online/offline, firmware) on its asset now.",
35289
+ args: [],
35290
+ flags: [],
35291
+ scopes: ["places:write"],
35292
+ returns: "SensorSyncResult",
35293
+ notes: "POST /api/v1/sensors/unifi/sync. Assets whose facet names a UniFi MAC are mapped first; readings land as asset.presence on change, else hourly. The worker does the same every five minutes. 409 before UniFi is linked."
35294
+ },
34815
35295
  {
34816
35296
  path: ["sensors", "links"],
34817
35297
  summary: "The mapped entities, or the ones on one Place or asset.",
@@ -34836,7 +35316,12 @@ var COMMAND_SPECS = [
34836
35316
  {
34837
35317
  name: "reading",
34838
35318
  type: "string",
34839
- description: "A room's temp|humidity|heater|water|door|feeder|light, or an asset's state|power|energy."
35319
+ description: "A room's temp|humidity|heater|water|door|feeder|light, or an asset's state|power|energy|presence."
35320
+ },
35321
+ {
35322
+ name: "provider",
35323
+ type: "string",
35324
+ description: "home_assistant (default) or unifi: a UniFi device by its MAC, onto an asset's presence."
34840
35325
  },
34841
35326
  { name: "label", type: "string", description: "A name for it (Home Assistant's own)." }
34842
35327
  ],
@@ -34891,6 +35376,52 @@ var COMMAND_SPECS = [
34891
35376
  returns: "RunDetail",
34892
35377
  notes: "PATCH /api/v1/runs/{id}/sessions/{sessionId}."
34893
35378
  },
35379
+ {
35380
+ path: ["people", "propose"],
35381
+ summary: "Propose a life event read in the member's mail; nothing is written until they save it.",
35382
+ args: [],
35383
+ flags: [
35384
+ {
35385
+ name: "data",
35386
+ type: "string",
35387
+ description: 'JSON CreatePersonProposalInput, e.g. {"person":"Sam","kind":"birth","title":"Baby girl, Mila","source":"gmail:18c2f0a1b2","heardAt":"2026-10-18T08:00:00Z"}.'
35388
+ }
35389
+ ],
35390
+ scopes: ["contacts:write"],
35391
+ returns: "PersonProposal",
35392
+ notes: "Needs a member's key and PEOPLE_MAIL_PROPOSALS_ENABLED on the server (else forbidden). Kinds: birth, move, new_job, wedding, engagement, death, other. Without items, saving writes one life update (the title, on heardAt). The same mail again returns the proposal it already made. Only the member whose key it is sees it."
35393
+ },
35394
+ {
35395
+ path: ["people", "proposals"],
35396
+ summary: "Your life events from mail: pending, saved or skipped.",
35397
+ args: [],
35398
+ flags: [
35399
+ { name: "status", type: "string", description: "pending, accepted or skipped." },
35400
+ {
35401
+ name: "source",
35402
+ type: "string",
35403
+ description: "gmail:<id>: whether this mail was proposed already (the watch dedupes with it)."
35404
+ }
35405
+ ],
35406
+ scopes: ["contacts:read"],
35407
+ returns: "PersonProposal"
35408
+ },
35409
+ {
35410
+ path: ["people", "accept"],
35411
+ summary: "Save a proposal: write its life update (and its items) on the person.",
35412
+ args: [{ name: "id", required: true, description: "The proposal's id." }],
35413
+ flags: [],
35414
+ scopes: ["contacts:write"],
35415
+ returns: "PersonProposalAcceptResult"
35416
+ },
35417
+ {
35418
+ path: ["people", "skip"],
35419
+ summary: "Skip a proposal: nothing is written, and the mail isn't proposed again.",
35420
+ args: [{ name: "id", required: true, description: "The proposal's id." }],
35421
+ flags: [],
35422
+ scopes: ["contacts:write"],
35423
+ returns: "PersonProposal"
35424
+ },
34894
35425
  {
34895
35426
  path: ["runs", "sessions", "delete"],
34896
35427
  summary: "Delete a session (its changes stay applied; destructive).",
@@ -35248,13 +35779,13 @@ var COMMAND_SPECS = [
35248
35779
  },
35249
35780
  {
35250
35781
  path: ["guest-lists", "export"],
35251
- summary: "Export for the caterer (CSV) or the venue (CSV) to stdout, or save the floor plan (PDF) to a file.",
35782
+ summary: "Export for the caterer, the venue, the church or the RSVP answers (CSV) to stdout, or save the floor plan (PDF) to a file.",
35252
35783
  args: [{ name: "id", required: true, description: "The guest list's id." }],
35253
35784
  flags: [
35254
35785
  {
35255
35786
  name: "kind",
35256
35787
  type: "string",
35257
- description: "caterer (meals and diets per table), venue (who sits where) or floor-plan (PDF)."
35788
+ description: "caterer (meals and diets per table), venue (who sits where), church (who comes to the ceremony), answers (every RSVP answer, a row per guest) or floor-plan (PDF)."
35258
35789
  },
35259
35790
  {
35260
35791
  name: "out",
@@ -35282,13 +35813,30 @@ var COMMAND_SPECS = [
35282
35813
  {
35283
35814
  name: "data",
35284
35815
  type: "string",
35285
- description: 'JSON CreateRsvpLinksInput, e.g. {"guestListId":"01\u2026"} (every household without a live link) or with "householdIds":["01\u2026"] (re-mints, revoking the old link).'
35816
+ 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`).'
35286
35817
  }
35287
35818
  ],
35288
35819
  scopes: ["contacts:write"],
35289
35820
  returns: "MintedRsvpLink",
35290
35821
  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."
35291
35822
  },
35823
+ {
35824
+ path: ["rsvp-links", "token"],
35825
+ summary: "Show a live RSVP link again: the same token it was minted with.",
35826
+ args: [{ name: "id", required: true, description: "The link's id." }],
35827
+ flags: [],
35828
+ scopes: ["contacts:write"],
35829
+ returns: "MintedRsvpLink",
35830
+ notes: "The mail and the paper QR carry this one link. A revoked or expired link is gone; one minted before tokens were kept can't be shown: replace it."
35831
+ },
35832
+ {
35833
+ path: ["rsvp-links", "replace"],
35834
+ summary: "Replace an RSVP link: a new token, and the old link (and its QR) stops working.",
35835
+ args: [{ name: "id", required: true, description: "The link's id." }],
35836
+ flags: [],
35837
+ scopes: ["contacts:write"],
35838
+ returns: "MintedRsvpLink"
35839
+ },
35292
35840
  {
35293
35841
  path: ["rsvp-links", "revoke"],
35294
35842
  summary: "Stop one RSVP link working.",
@@ -36761,9 +37309,15 @@ function renderJournalDays(data, mode) {
36761
37309
  }
36762
37310
  function renderJournalSearch(data, mode) {
36763
37311
  const d = data;
36764
- if ((d.days ?? []).length === 0)
36765
- return paint(mode, "dim", "(no journal days mention that)");
36766
- return renderJournalDays({ days: d.days }, mode);
37312
+ const u = d.understood;
37313
+ const read = u && (u.from || u.feeling) ? paint(mode, "dim", `Read as: ${[
37314
+ u.from && u.to ? u.from === u.to ? u.from : `${u.from} \u2013 ${u.to}` : null,
37315
+ ...u.words,
37316
+ u.feeling === "low" ? "heavier days" : u.feeling === "high" ? "brighter days" : null
37317
+ ].filter(Boolean).join(" \xB7 ")}`) : null;
37318
+ const body = (d.days ?? []).length === 0 ? paint(mode, "dim", "(no journal days mention that)") : renderJournalDays({ days: d.days }, mode);
37319
+ return read ? `${read}
37320
+ ${body}` : body;
36767
37321
  }
36768
37322
  function renderJournalRecall(data, mode) {
36769
37323
  const d = data;
@@ -36866,14 +37420,32 @@ function renderJournalMilestone(data, mode) {
36866
37420
  return out.join(`
36867
37421
  `);
36868
37422
  }
36869
- function renderJournalWeek(data, mode) {
37423
+ function renderJournalPeriod(data, mode) {
36870
37424
  const d = data;
36871
- const out = [`Week ${d.week?.slice(6) ?? "?"} ${paint(mode, "dim", `${d.from} \u2013 ${d.to}`)}`];
37425
+ const kind = d.week ? "week" : d.month ? "month" : "year";
37426
+ const head = d.week ? `Week ${d.week.slice(6)}` : d.month ?? d.year ?? "?";
37427
+ const out = [`${head} ${paint(mode, "dim", `${d.from} \u2013 ${d.to}`)}`];
36872
37428
  const days = d.days ?? [];
36873
- const ratings = days.flatMap((day) => day.metrics?.rating === undefined ? [] : [`${day.date.slice(5)} ${day.metrics.rating}`]);
36874
- if (ratings.length > 0) {
36875
- const usual = d.usual == null ? "" : paint(mode, "dim", ` (usual ${d.usual})`);
36876
- out.push(`${ratings.join(" \xB7 ")}${usual}`);
37429
+ const usual = d.usual == null ? "" : paint(mode, "dim", ` (usual ${d.usual})`);
37430
+ if (kind === "week") {
37431
+ const ratings = days.flatMap((day) => day.metrics?.rating === undefined ? [] : [`${day.date.slice(5)} ${day.metrics.rating}`]);
37432
+ if (ratings.length > 0)
37433
+ out.push(`${ratings.join(" \xB7 ")}${usual}`);
37434
+ } else if (kind === "month") {
37435
+ const ratings = days.flatMap((day) => day.metrics?.rating === undefined ? [] : [day.metrics.rating]);
37436
+ if (ratings.length > 0) {
37437
+ const avg = Math.round(ratings.reduce((a, b) => a + b, 0) / ratings.length * 10) / 10;
37438
+ out.push(`avg ${avg} \xB7 ${ratings.length} rated${usual}`);
37439
+ }
37440
+ } else {
37441
+ for (const m of d.months ?? []) {
37442
+ if (m.rated === 0 && m.kept === 0)
37443
+ continue;
37444
+ const avg = m.average == null ? "\u2014" : String(m.average);
37445
+ out.push(` ${m.month} avg ${avg} \xB7 ${m.rated} rated \xB7 ${m.kept} kept`);
37446
+ }
37447
+ if (d.usual != null)
37448
+ out.push(paint(mode, "dim", ` usual ${d.usual} the year before`));
36877
37449
  }
36878
37450
  if (d.recap) {
36879
37451
  out.push("", d.recap.body);
@@ -36885,7 +37457,12 @@ function renderJournalWeek(data, mode) {
36885
37457
  if (d.recap.pattern)
36886
37458
  out.push("", `${paint(mode, "dim", "Pattern")} ${d.recap.pattern}`);
36887
37459
  } else {
36888
- out.push("", paint(mode, "dim", "(no recap for this week yet)"));
37460
+ out.push("", paint(mode, "dim", `(no recap for this ${kind} yet)`));
37461
+ }
37462
+ if ((d.milestones ?? []).length > 0) {
37463
+ out.push("", paint(mode, "dim", "Milestones"));
37464
+ for (const m of d.milestones ?? [])
37465
+ out.push(` \u25C6 ${m.title}${paint(mode, "dim", ` \xB7 ${m.happenedOn}`)}`);
36889
37466
  }
36890
37467
  const n = d.numbers;
36891
37468
  if (n) {
@@ -37853,6 +38430,16 @@ var HANDLERS = {
37853
38430
  data: await requireClient().get("/progress/habits"),
37854
38431
  render: renderProgressHabits
37855
38432
  }),
38433
+ "progress commits": async ({ flags, requireClient }) => {
38434
+ const days = flagString(flags, "days");
38435
+ const query = parseInput(githubCommitsQuerySchema, days ? { days } : {});
38436
+ return {
38437
+ data: await requireClient().get("/progress/commits", {
38438
+ query: { days: String(query.days) }
38439
+ }),
38440
+ render: renderRecord
38441
+ };
38442
+ },
37856
38443
  "progress questions": async ({ requireClient }) => ({
37857
38444
  data: await requireClient().get("/progress/questions"),
37858
38445
  render: renderInsightsAnswers
@@ -37945,19 +38532,43 @@ var HANDLERS = {
37945
38532
  const week = journalWeekArg(args, "week");
37946
38533
  return {
37947
38534
  data: await requireClient().get(`/journal/weeks/${week}`),
37948
- render: renderJournalWeek
38535
+ render: renderJournalPeriod
38536
+ };
38537
+ },
38538
+ "journal month": async ({ args, requireClient }) => {
38539
+ const month = args[0] ?? "";
38540
+ if (journalPeriodKind(month) !== "month") {
38541
+ throw usageError("journal month needs a month as YYYY-MM, e.g. `do journal month 2026-10`");
38542
+ }
38543
+ return {
38544
+ data: await requireClient().get(`/journal/months/${month}`),
38545
+ render: renderJournalPeriod
38546
+ };
38547
+ },
38548
+ "journal year": async ({ args, requireClient }) => {
38549
+ const year = args[0] ?? "";
38550
+ if (journalPeriodKind(year) !== "year") {
38551
+ throw usageError("journal year needs a year as YYYY, e.g. `do journal year 2026`");
38552
+ }
38553
+ return {
38554
+ data: await requireClient().get(`/journal/years/${year}`),
38555
+ render: renderJournalPeriod
37949
38556
  };
37950
38557
  },
37951
38558
  "journal recap": async ({ args, flags, requireClient }) => {
37952
- const week = journalWeekArg(args, "recap");
38559
+ const period = args[0] ?? "";
38560
+ const kind = journalPeriodKind(period);
38561
+ if (kind === null) {
38562
+ throw usageError("journal recap needs a week (2026-W42), a month (2026-10) or a year (2026), e.g. `do journal recap 2026-W42`");
38563
+ }
37953
38564
  const body = { ...readDataFlag(flags) };
37954
38565
  const text = flagString(flags, "body");
37955
38566
  if (text !== undefined)
37956
38567
  body.body = text;
37957
38568
  const input = parseInput(journalWriteRecapInputSchema, body);
37958
38569
  return {
37959
- data: await requireClient().put(`/journal/weeks/${week}/recap`, input),
37960
- render: renderJournalWeek
38570
+ data: await requireClient().put(`/journal/${kind}s/${period}/recap`, input),
38571
+ render: renderJournalPeriod
37961
38572
  };
37962
38573
  },
37963
38574
  "journal write": async ({ args, flags, requireClient }) => {
@@ -39040,6 +39651,13 @@ var HANDLERS = {
39040
39651
  });
39041
39652
  return { data: await requireClient().post("/metrics/import", input), render: renderRecord };
39042
39653
  },
39654
+ "tap-tags tap": async ({ args, requireClient }) => {
39655
+ const id = requireArg(args, 0, "tag id");
39656
+ return {
39657
+ data: await requireClient().post(`/tap-tags/${encodeURIComponent(id)}/tap`, {}),
39658
+ render: renderRecord
39659
+ };
39660
+ },
39043
39661
  "agents list": async ({ requireClient }) => ({
39044
39662
  data: await requireClient().getPage("/agents"),
39045
39663
  render: renderList
@@ -40025,6 +40643,35 @@ var HANDLERS = {
40025
40643
  await requireClient().delete(`/animals/${id}/lost-card`);
40026
40644
  return { data: { ok: true, animalId: id }, render: renderRecord };
40027
40645
  },
40646
+ "animals acquisition": async ({ args, requireClient }) => ({
40647
+ data: await requireClient().get(`/animals/${requireArg(args, 0, "animal id")}/acquisition`),
40648
+ render: renderRecord
40649
+ }),
40650
+ "animals acquisition-plan": async ({ args, flags, requireClient }) => {
40651
+ const id = requireArg(args, 0, "animal id");
40652
+ const pickUp = flagString(flags, "pick-up");
40653
+ const body = parseInput(animalAcquisitionInputSchema, {
40654
+ ...readDataFlag(flags),
40655
+ ...pickUp ? { pickUpOn: pickUp } : {}
40656
+ });
40657
+ return {
40658
+ data: await requireClient().post(`/animals/${id}/acquisition`, body),
40659
+ render: renderRecord
40660
+ };
40661
+ },
40662
+ "animals arrived": async ({ args, flags, requireClient }) => {
40663
+ const id = requireArg(args, 0, "animal id");
40664
+ const on = flagString(flags, "on");
40665
+ const months = flagString(flags, "quarantine-months");
40666
+ const body = parseInput(animalArrivedInputSchema, {
40667
+ ...on ? { on } : {},
40668
+ ...months !== undefined ? { quarantineMonths: Number(months) } : {}
40669
+ });
40670
+ return {
40671
+ data: await requireClient().post(`/animals/${id}/arrived`, body),
40672
+ render: renderRecord
40673
+ };
40674
+ },
40028
40675
  "runs change": async ({ args, flags, requireClient }) => {
40029
40676
  const id = requireArg(args, 0, "run id");
40030
40677
  const body = { ...readDataFlag(flags) };
@@ -40124,6 +40771,18 @@ var HANDLERS = {
40124
40771
  data: await requireClient().post("/sensors/home-assistant/sync", {}),
40125
40772
  render: renderRecord
40126
40773
  }),
40774
+ "sensors unifi": async ({ requireClient }) => ({
40775
+ data: await requireClient().get("/sensors/unifi"),
40776
+ render: renderRecord
40777
+ }),
40778
+ "sensors unifi-devices": async ({ requireClient }) => ({
40779
+ data: await requireClient().get("/sensors/unifi/devices"),
40780
+ render: renderList
40781
+ }),
40782
+ "sensors unifi-sync": async ({ requireClient }) => ({
40783
+ data: await requireClient().post("/sensors/unifi/sync", {}),
40784
+ render: renderRecord
40785
+ }),
40127
40786
  "sensors links": async ({ flags, requireClient }) => ({
40128
40787
  data: await requireClient().get("/sensors/links", {
40129
40788
  query: { nodeId: flagString(flags, "node") }
@@ -40135,6 +40794,7 @@ var HANDLERS = {
40135
40794
  externalId: requireArg(args, 0, "entity id"),
40136
40795
  nodeId: requireArg(args, 1, "node id"),
40137
40796
  reading: flagString(flags, "reading"),
40797
+ ...flagString(flags, "provider") ? { provider: flagString(flags, "provider") } : {},
40138
40798
  ...flagString(flags, "label") ? { label: flagString(flags, "label") } : {}
40139
40799
  });
40140
40800
  return { data: await requireClient().post("/sensors/links", body), render: renderRecord };
@@ -40231,6 +40891,12 @@ var HANDLERS = {
40231
40891
  }),
40232
40892
  render: renderRecord
40233
40893
  }),
40894
+ "health export": async ({ flags, requireClient }) => {
40895
+ const raw = await requireClient().getText("/health/export", {
40896
+ query: { files: flagBool(flags, "no-files") ? "false" : undefined }
40897
+ });
40898
+ return { raw, data: undefined, render: () => raw };
40899
+ },
40234
40900
  "health shares": async ({ requireClient }) => ({
40235
40901
  data: await requireClient().getPage("/health/shares"),
40236
40902
  render: renderList
@@ -40489,6 +41155,33 @@ var HANDLERS = {
40489
41155
  render: renderRecord
40490
41156
  };
40491
41157
  },
41158
+ "people propose": async ({ flags, requireClient }) => {
41159
+ const body = parseInput(createPersonProposalInputSchema, {
41160
+ id: ulid3(),
41161
+ ...readDataFlag(flags)
41162
+ });
41163
+ return { data: await requireClient().post("/people/proposals", body), render: renderRecord };
41164
+ },
41165
+ "people proposals": async ({ flags, requireClient }) => {
41166
+ const status = flagString(flags, "status");
41167
+ const source = flagString(flags, "source");
41168
+ const query = parseInput(personProposalsQuerySchema, {
41169
+ ...status ? { status } : {},
41170
+ ...source ? { source } : {}
41171
+ });
41172
+ return {
41173
+ data: await requireClient().get("/people/proposals", { query }),
41174
+ render: renderRecord
41175
+ };
41176
+ },
41177
+ "people accept": async ({ args, requireClient }) => ({
41178
+ data: await requireClient().post(`/people/proposals/${requireArg(args, 0, "proposal id")}/accept`, {}),
41179
+ render: renderRecord
41180
+ }),
41181
+ "people skip": async ({ args, requireClient }) => ({
41182
+ data: await requireClient().post(`/people/proposals/${requireArg(args, 0, "proposal id")}/skip`, {}),
41183
+ render: renderRecord
41184
+ }),
40492
41185
  "people brief": async ({ args, requireClient }) => {
40493
41186
  const id = requireArg(args, 0, "contact id");
40494
41187
  return { data: await requireClient().get(`/people/${id}/brief`), render: renderRecord };
@@ -40722,6 +41415,17 @@ var HANDLERS = {
40722
41415
  const body = parseInput(createRsvpLinksInputSchema, readDataFlag(flags));
40723
41416
  return { data: await requireClient().post("/rsvp-links", body), render: renderRecord };
40724
41417
  },
41418
+ "rsvp-links token": async ({ args, requireClient }) => {
41419
+ const id = requireArg(args, 0, "rsvp link id");
41420
+ return { data: await requireClient().get(`/rsvp-links/${id}/token`), render: renderRecord };
41421
+ },
41422
+ "rsvp-links replace": async ({ args, requireClient }) => {
41423
+ const id = requireArg(args, 0, "rsvp link id");
41424
+ return {
41425
+ data: await requireClient().post(`/rsvp-links/${id}/replace`, {}),
41426
+ render: renderRecord
41427
+ };
41428
+ },
40725
41429
  "rsvp-links revoke": async ({ args, requireClient }) => {
40726
41430
  const id = requireArg(args, 0, "rsvp link id");
40727
41431
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robbeverhelst/do",
3
- "version": "1.297.0",
3
+ "version": "1.299.0",
4
4
  "private": false,
5
5
  "description": "do — the agent-drivable CLI over the public /api/v1 surface",
6
6
  "homepage": "https://github.com/robbeverhelst/do/tree/main/apps/cli",