@robbeverhelst/do 1.390.0 → 1.391.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 +391 -354
  2. package/package.json +1 -1
package/dist/do.js CHANGED
@@ -14420,7 +14420,7 @@ var configError = (message) => new CliError("config", message, EXIT.CONFIG);
14420
14420
 
14421
14421
  // src/meta.ts
14422
14422
  var CLI_NAME = "do";
14423
- var VERSION = "1.390.0";
14423
+ var VERSION = "1.391.0";
14424
14424
  var API_PREFIX = "/api/v1";
14425
14425
  var DEFAULT_API_URL = "http://localhost:3001";
14426
14426
 
@@ -18260,6 +18260,7 @@ var userSettings = pgTable("user_settings", {
18260
18260
  themePack: settingsThemePack("theme_pack").notNull().default("do"),
18261
18261
  themePackScope: settingsThemePackScope("theme_pack_scope").notNull().default("section"),
18262
18262
  ui: settingsUi("ui").notNull().default("v1"),
18263
+ healthTodayHints: text("health_today_hints").array().notNull().default(sql`'{supplements,sleep}'::text[]`),
18263
18264
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
18264
18265
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
18265
18266
  }, (t) => [unique("user_settings_tenant_user_uniq").on(t.tenantId, t.userId)]);
@@ -23945,7 +23946,8 @@ var collectionTypeValues = [
23945
23946
  "wine",
23946
23947
  "wardrobe",
23947
23948
  "asset",
23948
- "hobby"
23949
+ "hobby",
23950
+ "console"
23949
23951
  ];
23950
23952
  var collectionTypeSchema = _enum2(collectionTypeValues);
23951
23953
  var collectionViewValues = ["grid", "shelf", "map"];
@@ -29311,6 +29313,271 @@ var healthCycleShares = pgTable("health_cycle_shares", {
29311
29313
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
29312
29314
  }, (t) => [primaryKey({ columns: [t.tenantId, t.userId] })]);
29313
29315
 
29316
+ // ../../packages/schema/src/contracts/modules.ts
29317
+ var MODULE_KEYS = [
29318
+ "tasks",
29319
+ "habits",
29320
+ "routines",
29321
+ "challenges",
29322
+ "projects",
29323
+ "areas",
29324
+ "notes",
29325
+ "shopping",
29326
+ "inventory",
29327
+ "meals",
29328
+ "animals",
29329
+ "contacts",
29330
+ "sharing",
29331
+ "automations"
29332
+ ];
29333
+ var moduleKeySchema = _enum2(MODULE_KEYS);
29334
+ var FEATURE_MODULES = [
29335
+ {
29336
+ key: "today",
29337
+ label: "Today",
29338
+ description: "Your daily dashboard \u2014 always on.",
29339
+ group: "System",
29340
+ defaultEnabled: true,
29341
+ system: true
29342
+ },
29343
+ {
29344
+ key: "inbox",
29345
+ label: "Inbox",
29346
+ description: "Quick capture and triage \u2014 always on.",
29347
+ group: "System",
29348
+ defaultEnabled: true,
29349
+ system: true
29350
+ },
29351
+ {
29352
+ key: "settings",
29353
+ label: "Settings",
29354
+ description: "This screen \u2014 always on.",
29355
+ group: "System",
29356
+ defaultEnabled: true,
29357
+ system: true
29358
+ },
29359
+ {
29360
+ key: "tasks",
29361
+ label: "Tasks",
29362
+ description: "To-dos, due dates, and starred focus.",
29363
+ group: "Track",
29364
+ defaultEnabled: true
29365
+ },
29366
+ {
29367
+ key: "habits",
29368
+ label: "Habits",
29369
+ description: "Daily routines and streaks.",
29370
+ group: "Track",
29371
+ defaultEnabled: true
29372
+ },
29373
+ {
29374
+ key: "routines",
29375
+ label: "Routines",
29376
+ description: "Repeatable checklists you run.",
29377
+ group: "Track",
29378
+ defaultEnabled: true
29379
+ },
29380
+ {
29381
+ key: "challenges",
29382
+ label: "Challenges",
29383
+ description: "Time-boxed goals and competitions.",
29384
+ group: "Track",
29385
+ defaultEnabled: false
29386
+ },
29387
+ {
29388
+ key: "projects",
29389
+ label: "Projects",
29390
+ description: "Multi-step work with milestones.",
29391
+ group: "Organize",
29392
+ defaultEnabled: true
29393
+ },
29394
+ {
29395
+ key: "areas",
29396
+ label: "Areas",
29397
+ description: "Ongoing life domains (PARA areas).",
29398
+ group: "Organize",
29399
+ defaultEnabled: true
29400
+ },
29401
+ {
29402
+ key: "notes",
29403
+ label: "Notes",
29404
+ description: "Quick notes and documents.",
29405
+ group: "Organize",
29406
+ defaultEnabled: true
29407
+ },
29408
+ {
29409
+ key: "shopping",
29410
+ label: "Shopping",
29411
+ description: "Lists and groceries.",
29412
+ group: "Household",
29413
+ defaultEnabled: true
29414
+ },
29415
+ {
29416
+ key: "inventory",
29417
+ label: "Inventory",
29418
+ description: "Things you own and stock.",
29419
+ group: "Household",
29420
+ defaultEnabled: true
29421
+ },
29422
+ {
29423
+ key: "meals",
29424
+ label: "Meals",
29425
+ description: "Recipes and meal plans.",
29426
+ group: "Household",
29427
+ defaultEnabled: false
29428
+ },
29429
+ {
29430
+ key: "animals",
29431
+ label: "Animals",
29432
+ description: "Pets and flocks: eggs, weights, feed and care.",
29433
+ group: "Household",
29434
+ defaultEnabled: false
29435
+ },
29436
+ {
29437
+ key: "contacts",
29438
+ label: "Contacts",
29439
+ description: "People and relationships.",
29440
+ group: "People",
29441
+ defaultEnabled: false
29442
+ },
29443
+ {
29444
+ key: "sharing",
29445
+ label: "Members & sharing",
29446
+ description: "Members, roles, per-item sharing and comments.",
29447
+ group: "People",
29448
+ defaultEnabled: false
29449
+ },
29450
+ {
29451
+ key: "automations",
29452
+ label: "Automations",
29453
+ description: "Trigger \u2192 condition \u2192 action rules.",
29454
+ group: "Manage",
29455
+ defaultEnabled: false
29456
+ }
29457
+ ];
29458
+ var TOGGLEABLE_MODULES = FEATURE_MODULES.filter((m) => !m.system);
29459
+ function isModuleKey(value) {
29460
+ return MODULE_KEYS.includes(value);
29461
+ }
29462
+ function defaultEnabledModules() {
29463
+ return TOGGLEABLE_MODULES.filter((m) => m.defaultEnabled).map((m) => m.key);
29464
+ }
29465
+ function normalizeEnabledModules(keys) {
29466
+ const present = new Set(keys.filter(isModuleKey));
29467
+ return MODULE_KEYS.filter((key) => present.has(key));
29468
+ }
29469
+ function effectiveEnabledModules(enabled) {
29470
+ const known = normalizeEnabledModules(enabled ?? []);
29471
+ return known.length > 0 ? known : defaultEnabledModules();
29472
+ }
29473
+ function setModuleEnabled(enabled, key, on) {
29474
+ const next = new Set(effectiveEnabledModules(enabled));
29475
+ if (on)
29476
+ next.add(key);
29477
+ else
29478
+ next.delete(key);
29479
+ return MODULE_KEYS.filter((candidate) => next.has(candidate));
29480
+ }
29481
+
29482
+ // ../../packages/schema/src/contracts/settings.ts
29483
+ var clockTimeSchema = string2().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "a time like 22:00");
29484
+ var quietHoursSchema = object({
29485
+ from: clockTimeSchema,
29486
+ until: clockTimeSchema,
29487
+ days: array(number2().int().min(0).max(6)).max(7)
29488
+ });
29489
+ var PINNED_AREAS_MAX = 5;
29490
+ var PILL_PAGES = [
29491
+ "now",
29492
+ "habits",
29493
+ "tasks",
29494
+ "calendar",
29495
+ "areas",
29496
+ "house",
29497
+ "collection",
29498
+ "animals",
29499
+ "notes",
29500
+ "progress",
29501
+ "landed",
29502
+ "review",
29503
+ "agents",
29504
+ "profile",
29505
+ "settings",
29506
+ "alerts",
29507
+ "trash"
29508
+ ];
29509
+ var pillPageSchema = _enum2(PILL_PAGES);
29510
+ var PILL_PAGES_MAX = 4;
29511
+ var CALENDAR_ZOOM_MIN = 75;
29512
+ var CALENDAR_ZOOM_MAX = 300;
29513
+ var calendarZoomSchema = number2().int().min(CALENDAR_ZOOM_MIN).max(CALENDAR_ZOOM_MAX);
29514
+ var settingsGlassSchema = _enum2(settingsGlass.enumValues);
29515
+ var themePackSchema = _enum2(settingsThemePack.enumValues);
29516
+ var themePackScopeSchema = _enum2(settingsThemePackScope.enumValues);
29517
+ var settingsUiSchema = _enum2(settingsUi.enumValues);
29518
+ var HEALTH_TODAY_HINTS = ["supplements", "sleep", "appointment", "episode"];
29519
+ var healthTodayHintSchema = _enum2(HEALTH_TODAY_HINTS);
29520
+ var HEALTH_TODAY_HINTS_DEFAULT = ["supplements", "sleep"];
29521
+ var userSettingsSchema = createSelectSchema(userSettings, {
29522
+ pinnedAreaIds: array(string2()).default([]),
29523
+ focusAreaId: string2().nullable().default(null),
29524
+ focusUntil: exports_coerce.date().nullable().default(null),
29525
+ quietHours: quietHoursSchema.nullable().default(null),
29526
+ notifications: record(string2(), unknown()).nullable().default(null),
29527
+ lifeBlockSharing: lifeBlockSharingSchema.nullable().default(null),
29528
+ pillPages: array(string2()).default([]).transform((ids) => ids.filter((id) => pillPageSchema.safeParse(id).success)),
29529
+ foodDiaryMode: boolean2().default(false),
29530
+ calendarZoom: number2().int().nullable().default(null),
29531
+ glass: settingsGlassSchema.default("liquid"),
29532
+ followSky: boolean2().default(true),
29533
+ nightUntilWake: boolean2().default(false),
29534
+ largerText: boolean2().default(false),
29535
+ reduceMotion: boolean2().default(false),
29536
+ showStreakNumbers: boolean2().default(true),
29537
+ themePack: themePackSchema.default("do"),
29538
+ themePackScope: themePackScopeSchema.default("section"),
29539
+ ui: settingsUiSchema.default("v1"),
29540
+ healthTodayHints: array(string2()).default([...HEALTH_TODAY_HINTS_DEFAULT]).transform((ids) => ids.filter((id) => healthTodayHintSchema.safeParse(id).success)),
29541
+ createdAt: exports_coerce.date(),
29542
+ updatedAt: exports_coerce.date()
29543
+ }).omit({ autoRollOverdue: true });
29544
+ var updateUserSettingsInputSchema = createUpdateSchema(userSettings, {
29545
+ displayName: string2().trim().max(80).nullish(),
29546
+ avatarUrl: union([string2().url().max(2048), literal("")]).nullish(),
29547
+ timezone: string2().min(1).optional(),
29548
+ locale: string2().min(1).optional(),
29549
+ theme: settingsThemeSchema.optional(),
29550
+ density: settingsDensitySchema.optional(),
29551
+ accent: settingsAccentSchema.optional(),
29552
+ sunCity: string2().trim().max(80).transform((city) => city === "" ? null : city).nullish(),
29553
+ backdrop: backdropIdSchema.nullish(),
29554
+ enabledModules: array(string2().trim().min(1)).transform(normalizeEnabledModules).optional(),
29555
+ pinnedAreaIds: array(ulidSchema).transform((ids) => [...new Set(ids)]).pipe(array(ulidSchema).max(PINNED_AREAS_MAX)).optional(),
29556
+ focusAreaId: ulidSchema.nullish(),
29557
+ quietHours: quietHoursSchema.nullish(),
29558
+ lifeBlockSharing: lifeBlockSharingSchema.nullish(),
29559
+ calendarZoom: calendarZoomSchema.nullish(),
29560
+ pillPages: array(pillPageSchema).transform((ids) => [...new Set(ids)]).pipe(array(pillPageSchema).max(PILL_PAGES_MAX)).optional(),
29561
+ glass: settingsGlassSchema.optional(),
29562
+ followSky: boolean2().optional(),
29563
+ nightUntilWake: boolean2().optional(),
29564
+ largerText: boolean2().optional(),
29565
+ reduceMotion: boolean2().optional(),
29566
+ showStreakNumbers: boolean2().optional(),
29567
+ themePack: themePackSchema.optional(),
29568
+ themePackScope: themePackScopeSchema.optional(),
29569
+ ui: settingsUiSchema.optional(),
29570
+ healthTodayHints: array(healthTodayHintSchema).transform((ids) => HEALTH_TODAY_HINTS.filter((id) => ids.includes(id))).optional()
29571
+ }).omit({
29572
+ id: true,
29573
+ tenantId: true,
29574
+ userId: true,
29575
+ createdAt: true,
29576
+ updatedAt: true,
29577
+ autoRollOverdue: true,
29578
+ focusUntil: true
29579
+ }).strict();
29580
+
29314
29581
  // ../../packages/schema/src/contracts/health-records.ts
29315
29582
  var BODY_REGION_VALUES = [
29316
29583
  "head",
@@ -29587,6 +29854,14 @@ var updateHealthProfileInputSchema = object({
29587
29854
  emergencyContactId: ulidSchema.nullish(),
29588
29855
  primaryGpContactId: ulidSchema.nullish()
29589
29856
  }).strict();
29857
+ var healthTodaySchema = object({
29858
+ hints: array(healthTodayHintSchema),
29859
+ on: boolean2(),
29860
+ supplementHabitIds: array(string2()),
29861
+ sleep: object({ hours: number2() }).nullable(),
29862
+ appointment: object({ id: string2(), title: string2(), at: exports_coerce.date() }).nullable(),
29863
+ episode: object({ id: string2(), title: string2(), startedAt: exports_coerce.date() }).nullable()
29864
+ });
29590
29865
  var grantHealthConsentInputSchema = object({ version: literal(HEALTH_CONSENT_VERSION) }).strict();
29591
29866
  var HEALTH_ERASE_PHRASE = "delete my health data";
29592
29867
  var eraseHealthDataInputSchema = object({ confirm: literal(HEALTH_ERASE_PHRASE) }).strict();
@@ -37987,7 +38262,7 @@ var planningRules = pgTable("planning_rules", {
37987
38262
  var PLANNING_RULE_KINDS = planningRuleKind.enumValues;
37988
38263
  var planningRuleKindSchema = _enum2(PLANNING_RULE_KINDS);
37989
38264
  var PLANNING_RULE_SENTENCE_MAX = 280;
37990
- var clockTimeSchema = string2().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "expected HH:MM (24 h)");
38265
+ var clockTimeSchema2 = string2().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "expected HH:MM (24 h)");
37991
38266
  var ruleWeekdaysSchema = array(number2().int().min(1).max(7)).min(1).max(7).transform((days) => [...new Set(days)].sort((a, b) => a - b));
37992
38267
  var phraseSchema = string2().trim().min(1).max(80);
37993
38268
  function clockMinutes(clock) {
@@ -37998,15 +38273,15 @@ var fixedTimeRuleSchema = object({
37998
38273
  kind: literal("fixed_time"),
37999
38274
  what: phraseSchema,
38000
38275
  days: ruleWeekdaysSchema,
38001
- at: clockTimeSchema,
38276
+ at: clockTimeSchema2,
38002
38277
  minutes: number2().int().min(5).max(24 * 60).default(60),
38003
38278
  lifeKind: lifeBlockKindSchema.optional()
38004
38279
  }).strict();
38005
38280
  var keepFreeRuleSchema = object({
38006
38281
  kind: literal("keep_free"),
38007
38282
  days: ruleWeekdaysSchema,
38008
- from: clockTimeSchema,
38009
- to: clockTimeSchema,
38283
+ from: clockTimeSchema2,
38284
+ to: clockTimeSchema2,
38010
38285
  what: phraseSchema.optional()
38011
38286
  }).strict().refine((r) => clockMinutes(r.to) > clockMinutes(r.from), {
38012
38287
  message: "`to` comes after `from`",
@@ -40769,364 +41044,104 @@ var sensorOperations = defineOperations({
40769
41044
  path: "/sensors/home-assistant",
40770
41045
  scopes: ["places:read"],
40771
41046
  tag: "Sensors",
40772
- summary: "Get the Home Assistant link",
40773
- description: "Whether the Space has linked Home Assistant (in Settings in the app; the token is never returned), the `host` it was linked at, how many entities are mapped (`links`) and `lastReadingAt`, when the newest reading from it landed.",
40774
- status: 200,
40775
- response: { description: "The link's status." },
40776
- errors: { 403: true }
40777
- },
40778
- entities: {
40779
- method: "get",
40780
- path: "/sensors/home-assistant/entities",
40781
- scopes: ["places:read"],
40782
- tag: "Sensors",
40783
- summary: "List Home Assistant's entities",
40784
- description: "Every entity Home Assistant has, by `entityId`: its name, domain, state, unit, device class, `lastUpdated`, the reading it most likely gives on a Place and on an asset (`suggested`) and the sensor link it's mapped by, or null. 409 before Home Assistant is linked; 503 when it doesn't answer.",
40785
- status: 200,
40786
- response: { description: "The entities, by id." },
40787
- errors: { 403: true, 409: true, 503: "Home Assistant didn't answer, or refused the token." }
40788
- },
40789
- sync: {
40790
- method: "post",
40791
- path: "/sensors/home-assistant/sync",
40792
- scopes: ["places:write"],
40793
- tag: "Sensors",
40794
- summary: "Sync Home Assistant now",
40795
- description: "Read every mapped entity's state once and append it to the datapoints ledger: on a Place as `shed.temp`, `shed.humidity`, `shed.heater`, `shed.water_pct`, `shed.door`, `shed.feed_kg` or `shed.light` (what an animal's room card and the room alarm read), on an asset as `asset.state`, `asset.power` or `asset.energy`. A state already on the ledger, or one Home Assistant can't read, is skipped. The worker does the same every five minutes. Returns `{ syncedAt, entities, written, skipped, missing, error }` (`unauthorized`, `unavailable`, `bad_response`). 409 before Home Assistant is linked.",
40796
- status: 200,
40797
- response: { description: "What the sync did." },
40798
- errors: { 403: true, 409: true }
40799
- },
40800
- unifiStatus: {
40801
- method: "get",
40802
- path: "/sensors/unifi",
40803
- scopes: ["places:read"],
40804
- tag: "Sensors",
40805
- summary: "Whether UniFi is linked",
40806
- description: "Whether the Space has linked its UniFi controller (in the app's Settings \u2192 Home), the host and site it was linked at, how many devices are mapped to an asset, and when the newest presence reading landed. Never the API key: a key can't link or unlink UniFi.",
40807
- status: 200,
40808
- response: { description: "The status." },
40809
- errors: { 403: true }
40810
- },
40811
- unifiDevices: {
40812
- method: "get",
40813
- path: "/sensors/unifi/devices",
40814
- scopes: ["places:read", "collections:read"],
40815
- tag: "Sensors",
40816
- summary: "The devices UniFi sees",
40817
- description: "Every device the controller manages (gateway, switches, access points, cameras) and every client connected now: `mac`, `name`, `kind` (`device` | `client`), `model`, `online`, `firmware`, and the sensor `link` to the asset it's mapped to (behind the nodes wall). Network details. 409 before UniFi is linked; 503 when the controller can't be read.",
40818
- status: 200,
40819
- response: { description: "The devices." },
40820
- errors: { 403: true, 409: true }
40821
- },
40822
- syncUnifi: {
40823
- method: "post",
40824
- path: "/sensors/unifi/sync",
40825
- scopes: ["places:write"],
40826
- tag: "Sensors",
40827
- summary: "Sync UniFi now",
40828
- description: "Map every asset whose facet names a UniFi MAC (`external.unifiMac`), ask the controller once, and put each mapped device's presence on its asset as `asset.presence` (1 online, 0 offline, with its firmware): what the asset page's Live card reads. A reading lands when the presence or firmware changes, else hourly; a device the controller doesn't list reads offline and is named in `missing`. The worker does the same every five minutes. 409 before UniFi is linked.",
40829
- status: 200,
40830
- response: { description: "What the sync did." },
40831
- errors: { 403: true, 409: true }
40832
- },
40833
- links: {
40834
- method: "get",
40835
- path: "/sensors/links",
40836
- scopes: ["places:read"],
40837
- tag: "Sensors",
40838
- summary: "List the mapped entities",
40839
- description: "Every mapped entity, or only the ones on `nodeId`, with the name of what it lands on (`nodeTitle`). A mapping on an asset kept Not shared is its member's alone.",
40840
- query: [{ name: "nodeId", description: "Only the mappings on this Place or asset." }],
40841
- querySchema: sensorLinksQuerySchema,
40842
- status: 200,
40843
- response: { description: "The mappings, by entity id." },
40844
- errors: { 400: true }
40845
- },
40846
- createLink: {
40847
- method: "post",
40848
- path: "/sensors/links",
40849
- scopes: ["places:write"],
40850
- tag: "Sensors",
40851
- summary: "Map an entity",
40852
- description: "Map a Home Assistant entity (`externalId`) to a Place, an asset, or an animal (its room: the Place it's housed in) as a `reading`: a Place takes `temp`, `humidity`, `heater`, `water`, `door`, `feeder` or `light`; an asset `state`, `power` or `energy`. Mapping an entity that's already mapped moves it. 400 when the reading doesn't fit; 404 for no such node; 409 for an animal with no room.",
40853
- body: createSensorLinkInputSchema,
40854
- status: 201,
40855
- response: { description: "The mapping." },
40856
- errors: { 400: true, 404: true, 409: true }
40857
- },
40858
- removeLink: {
40859
- method: "delete",
40860
- path: "/sensors/links/{id}",
40861
- scopes: ["places:write"],
40862
- tag: "Sensors",
40863
- summary: "Unmap an entity",
40864
- description: "Remove one mapping; the readings it gave stay on the ledger.",
40865
- status: 204,
40866
- response: { description: "Removed." },
40867
- errors: { 404: true }
40868
- }
40869
- });
40870
-
40871
- // ../../packages/schema/src/contracts/modules.ts
40872
- var MODULE_KEYS = [
40873
- "tasks",
40874
- "habits",
40875
- "routines",
40876
- "challenges",
40877
- "projects",
40878
- "areas",
40879
- "notes",
40880
- "shopping",
40881
- "inventory",
40882
- "meals",
40883
- "animals",
40884
- "contacts",
40885
- "sharing",
40886
- "automations"
40887
- ];
40888
- var moduleKeySchema = _enum2(MODULE_KEYS);
40889
- var FEATURE_MODULES = [
40890
- {
40891
- key: "today",
40892
- label: "Today",
40893
- description: "Your daily dashboard \u2014 always on.",
40894
- group: "System",
40895
- defaultEnabled: true,
40896
- system: true
40897
- },
40898
- {
40899
- key: "inbox",
40900
- label: "Inbox",
40901
- description: "Quick capture and triage \u2014 always on.",
40902
- group: "System",
40903
- defaultEnabled: true,
40904
- system: true
40905
- },
40906
- {
40907
- key: "settings",
40908
- label: "Settings",
40909
- description: "This screen \u2014 always on.",
40910
- group: "System",
40911
- defaultEnabled: true,
40912
- system: true
40913
- },
40914
- {
40915
- key: "tasks",
40916
- label: "Tasks",
40917
- description: "To-dos, due dates, and starred focus.",
40918
- group: "Track",
40919
- defaultEnabled: true
40920
- },
40921
- {
40922
- key: "habits",
40923
- label: "Habits",
40924
- description: "Daily routines and streaks.",
40925
- group: "Track",
40926
- defaultEnabled: true
40927
- },
40928
- {
40929
- key: "routines",
40930
- label: "Routines",
40931
- description: "Repeatable checklists you run.",
40932
- group: "Track",
40933
- defaultEnabled: true
40934
- },
40935
- {
40936
- key: "challenges",
40937
- label: "Challenges",
40938
- description: "Time-boxed goals and competitions.",
40939
- group: "Track",
40940
- defaultEnabled: false
40941
- },
40942
- {
40943
- key: "projects",
40944
- label: "Projects",
40945
- description: "Multi-step work with milestones.",
40946
- group: "Organize",
40947
- defaultEnabled: true
40948
- },
40949
- {
40950
- key: "areas",
40951
- label: "Areas",
40952
- description: "Ongoing life domains (PARA areas).",
40953
- group: "Organize",
40954
- defaultEnabled: true
41047
+ summary: "Get the Home Assistant link",
41048
+ description: "Whether the Space has linked Home Assistant (in Settings in the app; the token is never returned), the `host` it was linked at, how many entities are mapped (`links`) and `lastReadingAt`, when the newest reading from it landed.",
41049
+ status: 200,
41050
+ response: { description: "The link's status." },
41051
+ errors: { 403: true }
40955
41052
  },
40956
- {
40957
- key: "notes",
40958
- label: "Notes",
40959
- description: "Quick notes and documents.",
40960
- group: "Organize",
40961
- defaultEnabled: true
41053
+ entities: {
41054
+ method: "get",
41055
+ path: "/sensors/home-assistant/entities",
41056
+ scopes: ["places:read"],
41057
+ tag: "Sensors",
41058
+ summary: "List Home Assistant's entities",
41059
+ description: "Every entity Home Assistant has, by `entityId`: its name, domain, state, unit, device class, `lastUpdated`, the reading it most likely gives on a Place and on an asset (`suggested`) and the sensor link it's mapped by, or null. 409 before Home Assistant is linked; 503 when it doesn't answer.",
41060
+ status: 200,
41061
+ response: { description: "The entities, by id." },
41062
+ errors: { 403: true, 409: true, 503: "Home Assistant didn't answer, or refused the token." }
40962
41063
  },
40963
- {
40964
- key: "shopping",
40965
- label: "Shopping",
40966
- description: "Lists and groceries.",
40967
- group: "Household",
40968
- defaultEnabled: true
41064
+ sync: {
41065
+ method: "post",
41066
+ path: "/sensors/home-assistant/sync",
41067
+ scopes: ["places:write"],
41068
+ tag: "Sensors",
41069
+ summary: "Sync Home Assistant now",
41070
+ description: "Read every mapped entity's state once and append it to the datapoints ledger: on a Place as `shed.temp`, `shed.humidity`, `shed.heater`, `shed.water_pct`, `shed.door`, `shed.feed_kg` or `shed.light` (what an animal's room card and the room alarm read), on an asset as `asset.state`, `asset.power` or `asset.energy`. A state already on the ledger, or one Home Assistant can't read, is skipped. The worker does the same every five minutes. Returns `{ syncedAt, entities, written, skipped, missing, error }` (`unauthorized`, `unavailable`, `bad_response`). 409 before Home Assistant is linked.",
41071
+ status: 200,
41072
+ response: { description: "What the sync did." },
41073
+ errors: { 403: true, 409: true }
40969
41074
  },
40970
- {
40971
- key: "inventory",
40972
- label: "Inventory",
40973
- description: "Things you own and stock.",
40974
- group: "Household",
40975
- defaultEnabled: true
41075
+ unifiStatus: {
41076
+ method: "get",
41077
+ path: "/sensors/unifi",
41078
+ scopes: ["places:read"],
41079
+ tag: "Sensors",
41080
+ summary: "Whether UniFi is linked",
41081
+ description: "Whether the Space has linked its UniFi controller (in the app's Settings \u2192 Home), the host and site it was linked at, how many devices are mapped to an asset, and when the newest presence reading landed. Never the API key: a key can't link or unlink UniFi.",
41082
+ status: 200,
41083
+ response: { description: "The status." },
41084
+ errors: { 403: true }
40976
41085
  },
40977
- {
40978
- key: "meals",
40979
- label: "Meals",
40980
- description: "Recipes and meal plans.",
40981
- group: "Household",
40982
- defaultEnabled: false
41086
+ unifiDevices: {
41087
+ method: "get",
41088
+ path: "/sensors/unifi/devices",
41089
+ scopes: ["places:read", "collections:read"],
41090
+ tag: "Sensors",
41091
+ summary: "The devices UniFi sees",
41092
+ description: "Every device the controller manages (gateway, switches, access points, cameras) and every client connected now: `mac`, `name`, `kind` (`device` | `client`), `model`, `online`, `firmware`, and the sensor `link` to the asset it's mapped to (behind the nodes wall). Network details. 409 before UniFi is linked; 503 when the controller can't be read.",
41093
+ status: 200,
41094
+ response: { description: "The devices." },
41095
+ errors: { 403: true, 409: true }
40983
41096
  },
40984
- {
40985
- key: "animals",
40986
- label: "Animals",
40987
- description: "Pets and flocks: eggs, weights, feed and care.",
40988
- group: "Household",
40989
- defaultEnabled: false
41097
+ syncUnifi: {
41098
+ method: "post",
41099
+ path: "/sensors/unifi/sync",
41100
+ scopes: ["places:write"],
41101
+ tag: "Sensors",
41102
+ summary: "Sync UniFi now",
41103
+ description: "Map every asset whose facet names a UniFi MAC (`external.unifiMac`), ask the controller once, and put each mapped device's presence on its asset as `asset.presence` (1 online, 0 offline, with its firmware): what the asset page's Live card reads. A reading lands when the presence or firmware changes, else hourly; a device the controller doesn't list reads offline and is named in `missing`. The worker does the same every five minutes. 409 before UniFi is linked.",
41104
+ status: 200,
41105
+ response: { description: "What the sync did." },
41106
+ errors: { 403: true, 409: true }
40990
41107
  },
40991
- {
40992
- key: "contacts",
40993
- label: "Contacts",
40994
- description: "People and relationships.",
40995
- group: "People",
40996
- defaultEnabled: false
41108
+ links: {
41109
+ method: "get",
41110
+ path: "/sensors/links",
41111
+ scopes: ["places:read"],
41112
+ tag: "Sensors",
41113
+ summary: "List the mapped entities",
41114
+ description: "Every mapped entity, or only the ones on `nodeId`, with the name of what it lands on (`nodeTitle`). A mapping on an asset kept Not shared is its member's alone.",
41115
+ query: [{ name: "nodeId", description: "Only the mappings on this Place or asset." }],
41116
+ querySchema: sensorLinksQuerySchema,
41117
+ status: 200,
41118
+ response: { description: "The mappings, by entity id." },
41119
+ errors: { 400: true }
40997
41120
  },
40998
- {
40999
- key: "sharing",
41000
- label: "Members & sharing",
41001
- description: "Members, roles, per-item sharing and comments.",
41002
- group: "People",
41003
- defaultEnabled: false
41121
+ createLink: {
41122
+ method: "post",
41123
+ path: "/sensors/links",
41124
+ scopes: ["places:write"],
41125
+ tag: "Sensors",
41126
+ summary: "Map an entity",
41127
+ description: "Map a Home Assistant entity (`externalId`) to a Place, an asset, or an animal (its room: the Place it's housed in) as a `reading`: a Place takes `temp`, `humidity`, `heater`, `water`, `door`, `feeder` or `light`; an asset `state`, `power` or `energy`. Mapping an entity that's already mapped moves it. 400 when the reading doesn't fit; 404 for no such node; 409 for an animal with no room.",
41128
+ body: createSensorLinkInputSchema,
41129
+ status: 201,
41130
+ response: { description: "The mapping." },
41131
+ errors: { 400: true, 404: true, 409: true }
41004
41132
  },
41005
- {
41006
- key: "automations",
41007
- label: "Automations",
41008
- description: "Trigger \u2192 condition \u2192 action rules.",
41009
- group: "Manage",
41010
- defaultEnabled: false
41133
+ removeLink: {
41134
+ method: "delete",
41135
+ path: "/sensors/links/{id}",
41136
+ scopes: ["places:write"],
41137
+ tag: "Sensors",
41138
+ summary: "Unmap an entity",
41139
+ description: "Remove one mapping; the readings it gave stay on the ledger.",
41140
+ status: 204,
41141
+ response: { description: "Removed." },
41142
+ errors: { 404: true }
41011
41143
  }
41012
- ];
41013
- var TOGGLEABLE_MODULES = FEATURE_MODULES.filter((m) => !m.system);
41014
- function isModuleKey(value) {
41015
- return MODULE_KEYS.includes(value);
41016
- }
41017
- function defaultEnabledModules() {
41018
- return TOGGLEABLE_MODULES.filter((m) => m.defaultEnabled).map((m) => m.key);
41019
- }
41020
- function normalizeEnabledModules(keys) {
41021
- const present = new Set(keys.filter(isModuleKey));
41022
- return MODULE_KEYS.filter((key) => present.has(key));
41023
- }
41024
- function effectiveEnabledModules(enabled) {
41025
- const known = normalizeEnabledModules(enabled ?? []);
41026
- return known.length > 0 ? known : defaultEnabledModules();
41027
- }
41028
- function setModuleEnabled(enabled, key, on) {
41029
- const next = new Set(effectiveEnabledModules(enabled));
41030
- if (on)
41031
- next.add(key);
41032
- else
41033
- next.delete(key);
41034
- return MODULE_KEYS.filter((candidate) => next.has(candidate));
41035
- }
41036
-
41037
- // ../../packages/schema/src/contracts/settings.ts
41038
- var clockTimeSchema2 = string2().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "a time like 22:00");
41039
- var quietHoursSchema = object({
41040
- from: clockTimeSchema2,
41041
- until: clockTimeSchema2,
41042
- days: array(number2().int().min(0).max(6)).max(7)
41043
41144
  });
41044
- var PINNED_AREAS_MAX = 5;
41045
- var PILL_PAGES = [
41046
- "now",
41047
- "habits",
41048
- "tasks",
41049
- "calendar",
41050
- "areas",
41051
- "house",
41052
- "collection",
41053
- "animals",
41054
- "notes",
41055
- "progress",
41056
- "landed",
41057
- "review",
41058
- "agents",
41059
- "profile",
41060
- "settings",
41061
- "alerts",
41062
- "trash"
41063
- ];
41064
- var pillPageSchema = _enum2(PILL_PAGES);
41065
- var PILL_PAGES_MAX = 4;
41066
- var CALENDAR_ZOOM_MIN = 75;
41067
- var CALENDAR_ZOOM_MAX = 300;
41068
- var calendarZoomSchema = number2().int().min(CALENDAR_ZOOM_MIN).max(CALENDAR_ZOOM_MAX);
41069
- var settingsGlassSchema = _enum2(settingsGlass.enumValues);
41070
- var themePackSchema = _enum2(settingsThemePack.enumValues);
41071
- var themePackScopeSchema = _enum2(settingsThemePackScope.enumValues);
41072
- var settingsUiSchema = _enum2(settingsUi.enumValues);
41073
- var userSettingsSchema = createSelectSchema(userSettings, {
41074
- pinnedAreaIds: array(string2()).default([]),
41075
- focusAreaId: string2().nullable().default(null),
41076
- focusUntil: exports_coerce.date().nullable().default(null),
41077
- quietHours: quietHoursSchema.nullable().default(null),
41078
- notifications: record(string2(), unknown()).nullable().default(null),
41079
- lifeBlockSharing: lifeBlockSharingSchema.nullable().default(null),
41080
- pillPages: array(string2()).default([]).transform((ids) => ids.filter((id) => pillPageSchema.safeParse(id).success)),
41081
- foodDiaryMode: boolean2().default(false),
41082
- calendarZoom: number2().int().nullable().default(null),
41083
- glass: settingsGlassSchema.default("liquid"),
41084
- followSky: boolean2().default(true),
41085
- nightUntilWake: boolean2().default(false),
41086
- largerText: boolean2().default(false),
41087
- reduceMotion: boolean2().default(false),
41088
- showStreakNumbers: boolean2().default(true),
41089
- themePack: themePackSchema.default("do"),
41090
- themePackScope: themePackScopeSchema.default("section"),
41091
- ui: settingsUiSchema.default("v1"),
41092
- createdAt: exports_coerce.date(),
41093
- updatedAt: exports_coerce.date()
41094
- }).omit({ autoRollOverdue: true });
41095
- var updateUserSettingsInputSchema = createUpdateSchema(userSettings, {
41096
- displayName: string2().trim().max(80).nullish(),
41097
- avatarUrl: union([string2().url().max(2048), literal("")]).nullish(),
41098
- timezone: string2().min(1).optional(),
41099
- locale: string2().min(1).optional(),
41100
- theme: settingsThemeSchema.optional(),
41101
- density: settingsDensitySchema.optional(),
41102
- accent: settingsAccentSchema.optional(),
41103
- sunCity: string2().trim().max(80).transform((city) => city === "" ? null : city).nullish(),
41104
- backdrop: backdropIdSchema.nullish(),
41105
- enabledModules: array(string2().trim().min(1)).transform(normalizeEnabledModules).optional(),
41106
- pinnedAreaIds: array(ulidSchema).transform((ids) => [...new Set(ids)]).pipe(array(ulidSchema).max(PINNED_AREAS_MAX)).optional(),
41107
- focusAreaId: ulidSchema.nullish(),
41108
- quietHours: quietHoursSchema.nullish(),
41109
- lifeBlockSharing: lifeBlockSharingSchema.nullish(),
41110
- calendarZoom: calendarZoomSchema.nullish(),
41111
- pillPages: array(pillPageSchema).transform((ids) => [...new Set(ids)]).pipe(array(pillPageSchema).max(PILL_PAGES_MAX)).optional(),
41112
- glass: settingsGlassSchema.optional(),
41113
- followSky: boolean2().optional(),
41114
- nightUntilWake: boolean2().optional(),
41115
- largerText: boolean2().optional(),
41116
- reduceMotion: boolean2().optional(),
41117
- showStreakNumbers: boolean2().optional(),
41118
- themePack: themePackSchema.optional(),
41119
- themePackScope: themePackScopeSchema.optional(),
41120
- ui: settingsUiSchema.optional()
41121
- }).omit({
41122
- id: true,
41123
- tenantId: true,
41124
- userId: true,
41125
- createdAt: true,
41126
- updatedAt: true,
41127
- autoRollOverdue: true,
41128
- focusUntil: true
41129
- }).strict();
41130
41145
 
41131
41146
  // ../../packages/schema/src/operations/settings.ts
41132
41147
  var settingsOperations = defineOperations({
@@ -43246,6 +43261,18 @@ var hobbyFacetSchema = object({
43246
43261
  with: string2().min(1).max(80).nullish(),
43247
43262
  coverUrl
43248
43263
  }).strict();
43264
+ var consoleFacetSchema = object({
43265
+ model: string2().min(1).max(120).nullish(),
43266
+ region: string2().min(1).max(40).nullish(),
43267
+ condition: string2().min(1).max(80).nullish(),
43268
+ boxed: boolean2().nullish(),
43269
+ extras: string2().min(1).max(200).nullish(),
43270
+ year: number2().int().min(1950).max(2100).nullish(),
43271
+ works: boolean2().nullish(),
43272
+ fixNote: string2().min(1).max(200).nullish(),
43273
+ plays: array(string2().trim().min(1).max(40)).max(12).nullish(),
43274
+ coverUrl
43275
+ }).strict();
43249
43276
  var placeFacetSchema = object({
43250
43277
  address: string2().min(1).max(400).nullish(),
43251
43278
  lat: number2().min(-90).max(90).nullish(),
@@ -43482,6 +43509,16 @@ var COLLECTION_TYPES = {
43482
43509
  facetSchema: hobbyFacetSchema,
43483
43510
  view: "grid",
43484
43511
  subtitleKey: "kind"
43512
+ },
43513
+ console: {
43514
+ key: "console",
43515
+ label: "Consoles",
43516
+ itemNoun: "console",
43517
+ statuses: ["want", "doing", "have", "done"],
43518
+ statusLabels: { want: "Wanted", doing: "Hooked up", have: "In the cupboard", done: "Retired" },
43519
+ facetSchema: consoleFacetSchema,
43520
+ view: "grid",
43521
+ subtitleKey: "model"
43485
43522
  }
43486
43523
  };
43487
43524
  var COLLECTION_TYPE_LIST = collectionTypeValues.map((k) => COLLECTION_TYPES[k]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robbeverhelst/do",
3
- "version": "1.390.0",
3
+ "version": "1.391.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",