@robbeverhelst/do 1.298.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 +135 -18
  2. package/package.json +1 -1
package/dist/do.js CHANGED
@@ -5502,6 +5502,7 @@ var EVIDENCE_SOURCES = [
5502
5502
  "routine_run",
5503
5503
  "do_activity",
5504
5504
  "github",
5505
+ "phone_location",
5505
5506
  "plan_default"
5506
5507
  ];
5507
5508
  var blockEvidence = pgTable("block_evidence", {
@@ -5518,7 +5519,7 @@ var blockEvidence = pgTable("block_evidence", {
5518
5519
  }, (t) => [
5519
5520
  index("block_evidence_block_idx").on(t.blockId, t.at),
5520
5521
  check("block_evidence_weight_range", sql`${t.weight} BETWEEN 1 AND 5`),
5521
- 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')`)
5522
5523
  ]);
5523
5524
 
5524
5525
  // ../../packages/schema/src/db/food-log.ts
@@ -20713,9 +20714,20 @@ var METRICS = {
20713
20714
  mood: { label: "Mood", unit: "/5", min: 1, max: 5, step: 1 },
20714
20715
  rating: { label: "Day rating", unit: "/10", min: 1, max: 10, step: 1 },
20715
20716
  energy: { label: "Energy", unit: "/10", min: 1, max: 10, step: 1 },
20716
- 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 }
20717
20720
  };
20718
- 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
+ ]);
20719
20731
  var metricSourceSchema = _enum2([
20720
20732
  "manual",
20721
20733
  "device",
@@ -22143,7 +22155,8 @@ var PLACE_SENSOR_READINGS = Object.keys(SHED_READING_TYPES);
22143
22155
  var ASSET_READING_TYPES = {
22144
22156
  state: "asset.state",
22145
22157
  power: "asset.power",
22146
- energy: "asset.energy"
22158
+ energy: "asset.energy",
22159
+ presence: "asset.presence"
22147
22160
  };
22148
22161
  var ASSET_SENSOR_READINGS = Object.keys(ASSET_READING_TYPES);
22149
22162
  var sensorReadingValues = [
@@ -22216,6 +22229,27 @@ var assetReadingSchema = object({
22216
22229
  device: string2().nullable(),
22217
22230
  at: exports_coerce.date()
22218
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
+ });
22219
22253
 
22220
22254
  // ../../packages/schema/src/contracts/house.ts
22221
22255
  var day = string2().regex(/^\d{4}-\d{2}-\d{2}$/, "must be a calendar day (YYYY-MM-DD)");
@@ -23554,10 +23588,17 @@ var JOURNAL_SEARCH_LIMIT = 30;
23554
23588
  var journalSearchQuerySchema = object({
23555
23589
  q: string2().trim().min(2, "search for at least 2 characters").max(100)
23556
23590
  });
23591
+ var journalSearchUnderstoodSchema = object({
23592
+ words: array(string2()),
23593
+ from: journalDateSchema.nullable(),
23594
+ to: journalDateSchema.nullable(),
23595
+ feeling: _enum2(["low", "high"]).nullable()
23596
+ });
23557
23597
  var journalSearchSchema = object({
23558
23598
  q: string2(),
23559
23599
  timezone: string2().min(1),
23560
- days: array(journalDayListItemSchema).max(JOURNAL_SEARCH_LIMIT)
23600
+ days: array(journalDayListItemSchema).max(JOURNAL_SEARCH_LIMIT),
23601
+ understood: journalSearchUnderstoodSchema.nullable().default(null)
23561
23602
  });
23562
23603
  var journalLinkInputSchema = object({
23563
23604
  kind: string2().trim().regex(/^[a-z][a-z_]{0,31}$/, "a link kind is a short lowercase word"),
@@ -27727,7 +27768,7 @@ var createSeatingProposalInputSchema = object({
27727
27768
  return new Set(ids).size === ids.length;
27728
27769
  }, { message: "a guest can sit at one table only", path: ["tables"] });
27729
27770
  var seatingProposalsQuerySchema = object({ guestListId: ulidSchema });
27730
- var GUEST_EXPORT_KINDS = ["caterer", "venue", "floor-plan", "church"];
27771
+ var GUEST_EXPORT_KINDS = ["caterer", "venue", "floor-plan", "church", "answers"];
27731
27772
  var guestExportKindSchema = _enum2(GUEST_EXPORT_KINDS);
27732
27773
  var thankYouGiftSchema = object({
27733
27774
  momentId: ulidSchema,
@@ -29706,7 +29747,7 @@ function parseCapture2(input, now) {
29706
29747
 
29707
29748
  // src/meta.ts
29708
29749
  var CLI_NAME = "do";
29709
- var VERSION = "1.298.0";
29750
+ var VERSION = "1.299.0";
29710
29751
  var API_PREFIX = "/api/v1";
29711
29752
  var DEFAULT_API_URL = "http://localhost:3001";
29712
29753
 
@@ -31002,6 +31043,21 @@ var HEALTH_COMMAND_SPECS = [
31002
31043
  scopes: READ,
31003
31044
  returns: "HealthSummary"
31004
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
+ },
31005
31061
  {
31006
31062
  path: ["health", "shares"],
31007
31063
  summary: "Your For-the-doctor links: sections, expiry, opens; never the secret.",
@@ -31618,7 +31674,7 @@ var COMMAND_SPECS = [
31618
31674
  flags: [],
31619
31675
  scopes: ["journal:read"],
31620
31676
  returns: "JournalSearch",
31621
- 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."
31622
31678
  },
31623
31679
  {
31624
31680
  path: ["journal", "recall"],
@@ -32578,7 +32634,7 @@ var COMMAND_SPECS = [
32578
32634
  },
32579
32635
  {
32580
32636
  path: ["metrics", "list"],
32581
- 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.",
32582
32638
  args: [],
32583
32639
  flags: [{ name: "days", type: "string", description: "How far back, 1\u2013730 (default 90)." }],
32584
32640
  scopes: ["habits:read"],
@@ -32586,7 +32642,7 @@ var COMMAND_SPECS = [
32586
32642
  },
32587
32643
  {
32588
32644
  path: ["metrics", "sessions"],
32589
- 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.",
32590
32646
  args: [],
32591
32647
  flags: [{ name: "days", type: "string", description: "How far back, 1\u2013365 (default 14)." }],
32592
32648
  scopes: ["habits:read"],
@@ -32594,9 +32650,13 @@ var COMMAND_SPECS = [
32594
32650
  },
32595
32651
  {
32596
32652
  path: ["metrics", "log"],
32597
- 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).",
32598
32654
  args: [
32599
- { 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
+ },
32600
32660
  {
32601
32661
  name: "value",
32602
32662
  required: true,
@@ -35205,6 +35265,33 @@ var COMMAND_SPECS = [
35205
35265
  returns: "SensorSyncResult",
35206
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."
35207
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
+ },
35208
35295
  {
35209
35296
  path: ["sensors", "links"],
35210
35297
  summary: "The mapped entities, or the ones on one Place or asset.",
@@ -35229,7 +35316,12 @@ var COMMAND_SPECS = [
35229
35316
  {
35230
35317
  name: "reading",
35231
35318
  type: "string",
35232
- 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."
35233
35325
  },
35234
35326
  { name: "label", type: "string", description: "A name for it (Home Assistant's own)." }
35235
35327
  ],
@@ -35687,13 +35779,13 @@ var COMMAND_SPECS = [
35687
35779
  },
35688
35780
  {
35689
35781
  path: ["guest-lists", "export"],
35690
- 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.",
35691
35783
  args: [{ name: "id", required: true, description: "The guest list's id." }],
35692
35784
  flags: [
35693
35785
  {
35694
35786
  name: "kind",
35695
35787
  type: "string",
35696
- 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)."
35697
35789
  },
35698
35790
  {
35699
35791
  name: "out",
@@ -37217,9 +37309,15 @@ function renderJournalDays(data, mode) {
37217
37309
  }
37218
37310
  function renderJournalSearch(data, mode) {
37219
37311
  const d = data;
37220
- if ((d.days ?? []).length === 0)
37221
- return paint(mode, "dim", "(no journal days mention that)");
37222
- 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;
37223
37321
  }
37224
37322
  function renderJournalRecall(data, mode) {
37225
37323
  const d = data;
@@ -40673,6 +40771,18 @@ var HANDLERS = {
40673
40771
  data: await requireClient().post("/sensors/home-assistant/sync", {}),
40674
40772
  render: renderRecord
40675
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
+ }),
40676
40786
  "sensors links": async ({ flags, requireClient }) => ({
40677
40787
  data: await requireClient().get("/sensors/links", {
40678
40788
  query: { nodeId: flagString(flags, "node") }
@@ -40684,6 +40794,7 @@ var HANDLERS = {
40684
40794
  externalId: requireArg(args, 0, "entity id"),
40685
40795
  nodeId: requireArg(args, 1, "node id"),
40686
40796
  reading: flagString(flags, "reading"),
40797
+ ...flagString(flags, "provider") ? { provider: flagString(flags, "provider") } : {},
40687
40798
  ...flagString(flags, "label") ? { label: flagString(flags, "label") } : {}
40688
40799
  });
40689
40800
  return { data: await requireClient().post("/sensors/links", body), render: renderRecord };
@@ -40780,6 +40891,12 @@ var HANDLERS = {
40780
40891
  }),
40781
40892
  render: renderRecord
40782
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
+ },
40783
40900
  "health shares": async ({ requireClient }) => ({
40784
40901
  data: await requireClient().getPage("/health/shares"),
40785
40902
  render: renderList
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robbeverhelst/do",
3
- "version": "1.298.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",