@robbeverhelst/do 1.390.0 → 1.392.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 +716 -345
  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.392.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)]);
@@ -23932,6 +23933,24 @@ var collectionSavedViews = pgTable("collection_saved_views", {
23932
23933
  }, (t) => [
23933
23934
  index("collection_saved_views_tenant_idx").on(t.tenantId)
23934
23935
  ]);
23936
+ var collectionKinds = pgTable("collection_kinds", {
23937
+ id: text("id").primaryKey(),
23938
+ tenantId: text("tenant_id").notNull().references(() => tenants.id),
23939
+ key: text("key").notNull(),
23940
+ name: text("name").notNull(),
23941
+ one: text("one").notNull(),
23942
+ many: text("many").notNull(),
23943
+ icon: text("icon").notNull().default("tag"),
23944
+ preset: text("preset"),
23945
+ fields: jsonb("fields").$type().notNull().default([]),
23946
+ statusWords: jsonb("status_words").$type().notNull(),
23947
+ view: text("view").notNull().default("grid"),
23948
+ createdByUserId: text("created_by_user_id").references(() => users.id, {
23949
+ onDelete: "set null"
23950
+ }),
23951
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
23952
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
23953
+ }, (t) => [unique("collection_kinds_tenant_key_uniq").on(t.tenantId, t.key)]);
23935
23954
 
23936
23955
  // ../../packages/schema/src/contracts/collection-values.ts
23937
23956
  var collectionStatusValues = ["want", "have", "doing", "done", "dropped", "gone"];
@@ -23945,7 +23964,8 @@ var collectionTypeValues = [
23945
23964
  "wine",
23946
23965
  "wardrobe",
23947
23966
  "asset",
23948
- "hobby"
23967
+ "hobby",
23968
+ "console"
23949
23969
  ];
23950
23970
  var collectionTypeSchema = _enum2(collectionTypeValues);
23951
23971
  var collectionViewValues = ["grid", "shelf", "map"];
@@ -26137,6 +26157,162 @@ var circleOperations = defineOperations({
26137
26157
  })
26138
26158
  });
26139
26159
 
26160
+ // ../../packages/schema/src/contracts/collection-kinds.ts
26161
+ var collectionKindFieldTypeValues = [
26162
+ "text",
26163
+ "number",
26164
+ "choice",
26165
+ "year",
26166
+ "date",
26167
+ "yesno"
26168
+ ];
26169
+ var collectionKindFieldTypeSchema = _enum2(collectionKindFieldTypeValues);
26170
+ var collectionKindIconValues = [
26171
+ "disc",
26172
+ "dice",
26173
+ "brick",
26174
+ "book",
26175
+ "pad",
26176
+ "sprout",
26177
+ "paw",
26178
+ "tag"
26179
+ ];
26180
+ var collectionKindIconSchema = _enum2(collectionKindIconValues);
26181
+ var collectionKindPresetValues = ["lego", "vinyl", "board", "custom"];
26182
+ var collectionKindPresetSchema = _enum2(collectionKindPresetValues);
26183
+ var slug = string2().min(1).max(40).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "lowercase letters and digits, dashes between");
26184
+ var collectionKindKeySchema = slug.refine((k) => !collectionTypeValues.includes(k), "that's a built-in kind");
26185
+ var collectionKindFieldSchema = object({
26186
+ key: slug,
26187
+ label: string2().trim().min(1).max(40),
26188
+ type: collectionKindFieldTypeSchema,
26189
+ choices: array(string2().trim().min(1).max(40)).max(30).optional()
26190
+ }).strict().refine((f) => f.type === "choice" || f.choices === undefined, {
26191
+ message: "only a choice field has choices",
26192
+ path: ["choices"]
26193
+ });
26194
+ var collectionKindFieldsSchema = array(collectionKindFieldSchema).max(12).refine((fs) => new Set(fs.map((f) => f.key)).size === fs.length, "each field key once");
26195
+ var word = string2().trim().min(1).max(30);
26196
+ var collectionKindStatusWordsSchema = object({ want: word, have: word, doing: word, done: word, dropped: word }).strict();
26197
+ var collectionKindSchema = object({
26198
+ id: ulidSchema,
26199
+ key: string2(),
26200
+ name: string2(),
26201
+ one: string2(),
26202
+ many: string2(),
26203
+ icon: string2(),
26204
+ preset: string2().nullable(),
26205
+ fields: array(object({
26206
+ key: string2(),
26207
+ label: string2(),
26208
+ type: string2(),
26209
+ choices: array(string2()).optional()
26210
+ })),
26211
+ statusWords: collectionKindStatusWordsSchema,
26212
+ view: string2(),
26213
+ itemCount: number2().int(),
26214
+ createdAt: exports_coerce.date(),
26215
+ updatedAt: exports_coerce.date()
26216
+ });
26217
+ var name2 = string2().trim().min(1).max(40);
26218
+ var createCollectionKindInputSchema = object({
26219
+ id: ulidSchema,
26220
+ name: name2,
26221
+ key: collectionKindKeySchema.optional(),
26222
+ one: name2.optional(),
26223
+ many: name2.optional(),
26224
+ icon: collectionKindIconSchema.optional(),
26225
+ preset: collectionKindPresetSchema.optional(),
26226
+ fields: collectionKindFieldsSchema.optional(),
26227
+ statusWords: collectionKindStatusWordsSchema.optional()
26228
+ }).strict();
26229
+ var updateCollectionKindInputSchema = object({
26230
+ name: name2.optional(),
26231
+ one: name2.optional(),
26232
+ many: name2.optional(),
26233
+ icon: collectionKindIconSchema.optional(),
26234
+ fields: collectionKindFieldsSchema.optional(),
26235
+ statusWords: collectionKindStatusWordsSchema.optional()
26236
+ }).strict();
26237
+ var f = (label, type) => ({
26238
+ key: kindSlug(label),
26239
+ label,
26240
+ type
26241
+ });
26242
+ var COLLECTION_KIND_PRESETS = {
26243
+ lego: {
26244
+ name: "LEGO",
26245
+ one: "set",
26246
+ icon: "brick",
26247
+ fields: [
26248
+ f("Set number", "text"),
26249
+ f("Pieces", "number"),
26250
+ f("Theme", "choice"),
26251
+ f("Bags", "number"),
26252
+ f("Minifigures", "number"),
26253
+ f("Released", "year")
26254
+ ],
26255
+ statusWords: {
26256
+ want: "Want",
26257
+ have: "In the box",
26258
+ doing: "Building",
26259
+ done: "Built",
26260
+ dropped: "Parted out"
26261
+ }
26262
+ },
26263
+ vinyl: {
26264
+ name: "Vinyl",
26265
+ one: "record",
26266
+ icon: "disc",
26267
+ fields: [
26268
+ f("Artist", "text"),
26269
+ f("Label", "choice"),
26270
+ f("Pressing", "text"),
26271
+ f("Speed", "number"),
26272
+ f("Condition", "choice"),
26273
+ f("Released", "year")
26274
+ ],
26275
+ statusWords: {
26276
+ want: "Want",
26277
+ have: "Have",
26278
+ doing: "On rotation",
26279
+ done: "Played out",
26280
+ dropped: "Sold"
26281
+ }
26282
+ },
26283
+ board: {
26284
+ name: "Board games",
26285
+ one: "board game",
26286
+ icon: "dice",
26287
+ fields: [
26288
+ f("Players", "number"),
26289
+ f("Play time", "text"),
26290
+ f("Designer", "text"),
26291
+ f("Expansions", "text"),
26292
+ f("Plays", "number"),
26293
+ f("Released", "year")
26294
+ ],
26295
+ statusWords: {
26296
+ want: "Want",
26297
+ have: "Have",
26298
+ doing: "Learning",
26299
+ done: "Played",
26300
+ dropped: "Gave away"
26301
+ }
26302
+ },
26303
+ custom: {
26304
+ name: "",
26305
+ one: "",
26306
+ icon: "tag",
26307
+ fields: [f("Maker", "text"), f("Year", "year"), f("Condition", "choice")],
26308
+ statusWords: { want: "Want", have: "Have", doing: "Using", done: "Done", dropped: "Gone" }
26309
+ }
26310
+ };
26311
+ function kindSlug(name) {
26312
+ const s = name.toLowerCase().normalize("NFKD").replace(/[\u0300-\u036F]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40).replace(/-+$/, "");
26313
+ return s || "kind";
26314
+ }
26315
+
26140
26316
  // ../../packages/schema/src/contracts/pagination.ts
26141
26317
  var paginationQuerySchema = object({
26142
26318
  limit: exports_coerce.number().int().positive().optional(),
@@ -26147,6 +26323,7 @@ var paginationQuerySchema = object({
26147
26323
  var READ2 = ["collections:read"];
26148
26324
  var WRITE2 = ["collections:write"];
26149
26325
  var entryParams = { entryId: "The entry's ULID." };
26326
+ var kindParams = { kindId: "The kind's ULID." };
26150
26327
  var collectionOperations = defineOperations({
26151
26328
  ...crudOperations({
26152
26329
  segment: "collection",
@@ -26202,6 +26379,65 @@ var collectionOperations = defineOperations({
26202
26379
  status: 200,
26203
26380
  response: { description: "The Type registry." }
26204
26381
  },
26382
+ listKinds: {
26383
+ method: "get",
26384
+ path: "/collection/kinds",
26385
+ scopes: READ2,
26386
+ tag: "Collection",
26387
+ summary: "List the Space's own kinds",
26388
+ description: "The kinds the Space added (vinyl, board games, \u2026) beside the built-in Types, by name: each with its `key` (what an Item of it carries as its `type`), `name`, `one`/`many`, `icon`, the `preset` it started from, its `fields` (key, label, type: `text`, `number`, `choice`, `year`, `date` or `yesno`, and a choice field's `choices`; the first three show on its tile), its `statusWords` for want, have, doing, done and dropped, its `view` (`grid`) and `itemCount`.",
26389
+ status: 200,
26390
+ response: { description: "The kinds." }
26391
+ },
26392
+ createKind: {
26393
+ method: "post",
26394
+ path: "/collection/kinds",
26395
+ scopes: WRITE2,
26396
+ tag: "Collection",
26397
+ summary: "Add a kind",
26398
+ description: "Start from a `preset` (`lego`, `vinyl`, `board` or `custom`, \"Something else\") and a `name`; what's left out comes from the preset (the icon, the fields, the status words), the `key` is the name's slug (never a built-in Type; 409 when the Space has it) and `one`/`many` come from the name. It starts on the clean grid.",
26399
+ body: createCollectionKindInputSchema,
26400
+ status: 201,
26401
+ response: { description: "The kind." },
26402
+ errors: { 400: true, 409: "The Space already has a kind with this key." }
26403
+ },
26404
+ getKind: {
26405
+ method: "get",
26406
+ path: "/collection/kinds/{kindId}",
26407
+ pathParams: kindParams,
26408
+ scopes: READ2,
26409
+ tag: "Collection",
26410
+ summary: "A kind of the Space's own",
26411
+ description: "The same shape as a row of the list.",
26412
+ status: 200,
26413
+ response: { description: "The kind." },
26414
+ errors: { 404: true }
26415
+ },
26416
+ updateKind: {
26417
+ method: "patch",
26418
+ path: "/collection/kinds/{kindId}",
26419
+ pathParams: kindParams,
26420
+ scopes: WRITE2,
26421
+ tag: "Collection",
26422
+ summary: "Change a kind",
26423
+ description: "Its name, one/many, icon, `fields` (replaces the list) or `statusWords`; a field left out stays. The key never changes.",
26424
+ body: updateCollectionKindInputSchema,
26425
+ status: 200,
26426
+ response: { description: "The kind." },
26427
+ errors: { 400: true, 404: true }
26428
+ },
26429
+ removeKind: {
26430
+ method: "delete",
26431
+ path: "/collection/kinds/{kindId}",
26432
+ pathParams: kindParams,
26433
+ scopes: WRITE2,
26434
+ tag: "Collection",
26435
+ summary: "Remove a kind",
26436
+ description: "Only a kind with no Items (409 otherwise: move or remove them first).",
26437
+ status: 204,
26438
+ response: { description: "Removed." },
26439
+ errors: { 404: true, 409: "The kind still has Items." }
26440
+ },
26205
26441
  backfillCovers: {
26206
26442
  method: "post",
26207
26443
  path: "/collection/covers",
@@ -29311,6 +29547,271 @@ var healthCycleShares = pgTable("health_cycle_shares", {
29311
29547
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
29312
29548
  }, (t) => [primaryKey({ columns: [t.tenantId, t.userId] })]);
29313
29549
 
29550
+ // ../../packages/schema/src/contracts/modules.ts
29551
+ var MODULE_KEYS = [
29552
+ "tasks",
29553
+ "habits",
29554
+ "routines",
29555
+ "challenges",
29556
+ "projects",
29557
+ "areas",
29558
+ "notes",
29559
+ "shopping",
29560
+ "inventory",
29561
+ "meals",
29562
+ "animals",
29563
+ "contacts",
29564
+ "sharing",
29565
+ "automations"
29566
+ ];
29567
+ var moduleKeySchema = _enum2(MODULE_KEYS);
29568
+ var FEATURE_MODULES = [
29569
+ {
29570
+ key: "today",
29571
+ label: "Today",
29572
+ description: "Your daily dashboard \u2014 always on.",
29573
+ group: "System",
29574
+ defaultEnabled: true,
29575
+ system: true
29576
+ },
29577
+ {
29578
+ key: "inbox",
29579
+ label: "Inbox",
29580
+ description: "Quick capture and triage \u2014 always on.",
29581
+ group: "System",
29582
+ defaultEnabled: true,
29583
+ system: true
29584
+ },
29585
+ {
29586
+ key: "settings",
29587
+ label: "Settings",
29588
+ description: "This screen \u2014 always on.",
29589
+ group: "System",
29590
+ defaultEnabled: true,
29591
+ system: true
29592
+ },
29593
+ {
29594
+ key: "tasks",
29595
+ label: "Tasks",
29596
+ description: "To-dos, due dates, and starred focus.",
29597
+ group: "Track",
29598
+ defaultEnabled: true
29599
+ },
29600
+ {
29601
+ key: "habits",
29602
+ label: "Habits",
29603
+ description: "Daily routines and streaks.",
29604
+ group: "Track",
29605
+ defaultEnabled: true
29606
+ },
29607
+ {
29608
+ key: "routines",
29609
+ label: "Routines",
29610
+ description: "Repeatable checklists you run.",
29611
+ group: "Track",
29612
+ defaultEnabled: true
29613
+ },
29614
+ {
29615
+ key: "challenges",
29616
+ label: "Challenges",
29617
+ description: "Time-boxed goals and competitions.",
29618
+ group: "Track",
29619
+ defaultEnabled: false
29620
+ },
29621
+ {
29622
+ key: "projects",
29623
+ label: "Projects",
29624
+ description: "Multi-step work with milestones.",
29625
+ group: "Organize",
29626
+ defaultEnabled: true
29627
+ },
29628
+ {
29629
+ key: "areas",
29630
+ label: "Areas",
29631
+ description: "Ongoing life domains (PARA areas).",
29632
+ group: "Organize",
29633
+ defaultEnabled: true
29634
+ },
29635
+ {
29636
+ key: "notes",
29637
+ label: "Notes",
29638
+ description: "Quick notes and documents.",
29639
+ group: "Organize",
29640
+ defaultEnabled: true
29641
+ },
29642
+ {
29643
+ key: "shopping",
29644
+ label: "Shopping",
29645
+ description: "Lists and groceries.",
29646
+ group: "Household",
29647
+ defaultEnabled: true
29648
+ },
29649
+ {
29650
+ key: "inventory",
29651
+ label: "Inventory",
29652
+ description: "Things you own and stock.",
29653
+ group: "Household",
29654
+ defaultEnabled: true
29655
+ },
29656
+ {
29657
+ key: "meals",
29658
+ label: "Meals",
29659
+ description: "Recipes and meal plans.",
29660
+ group: "Household",
29661
+ defaultEnabled: false
29662
+ },
29663
+ {
29664
+ key: "animals",
29665
+ label: "Animals",
29666
+ description: "Pets and flocks: eggs, weights, feed and care.",
29667
+ group: "Household",
29668
+ defaultEnabled: false
29669
+ },
29670
+ {
29671
+ key: "contacts",
29672
+ label: "Contacts",
29673
+ description: "People and relationships.",
29674
+ group: "People",
29675
+ defaultEnabled: false
29676
+ },
29677
+ {
29678
+ key: "sharing",
29679
+ label: "Members & sharing",
29680
+ description: "Members, roles, per-item sharing and comments.",
29681
+ group: "People",
29682
+ defaultEnabled: false
29683
+ },
29684
+ {
29685
+ key: "automations",
29686
+ label: "Automations",
29687
+ description: "Trigger \u2192 condition \u2192 action rules.",
29688
+ group: "Manage",
29689
+ defaultEnabled: false
29690
+ }
29691
+ ];
29692
+ var TOGGLEABLE_MODULES = FEATURE_MODULES.filter((m) => !m.system);
29693
+ function isModuleKey(value) {
29694
+ return MODULE_KEYS.includes(value);
29695
+ }
29696
+ function defaultEnabledModules() {
29697
+ return TOGGLEABLE_MODULES.filter((m) => m.defaultEnabled).map((m) => m.key);
29698
+ }
29699
+ function normalizeEnabledModules(keys) {
29700
+ const present = new Set(keys.filter(isModuleKey));
29701
+ return MODULE_KEYS.filter((key) => present.has(key));
29702
+ }
29703
+ function effectiveEnabledModules(enabled) {
29704
+ const known = normalizeEnabledModules(enabled ?? []);
29705
+ return known.length > 0 ? known : defaultEnabledModules();
29706
+ }
29707
+ function setModuleEnabled(enabled, key, on) {
29708
+ const next = new Set(effectiveEnabledModules(enabled));
29709
+ if (on)
29710
+ next.add(key);
29711
+ else
29712
+ next.delete(key);
29713
+ return MODULE_KEYS.filter((candidate) => next.has(candidate));
29714
+ }
29715
+
29716
+ // ../../packages/schema/src/contracts/settings.ts
29717
+ var clockTimeSchema = string2().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "a time like 22:00");
29718
+ var quietHoursSchema = object({
29719
+ from: clockTimeSchema,
29720
+ until: clockTimeSchema,
29721
+ days: array(number2().int().min(0).max(6)).max(7)
29722
+ });
29723
+ var PINNED_AREAS_MAX = 5;
29724
+ var PILL_PAGES = [
29725
+ "now",
29726
+ "habits",
29727
+ "tasks",
29728
+ "calendar",
29729
+ "areas",
29730
+ "house",
29731
+ "collection",
29732
+ "animals",
29733
+ "notes",
29734
+ "progress",
29735
+ "landed",
29736
+ "review",
29737
+ "agents",
29738
+ "profile",
29739
+ "settings",
29740
+ "alerts",
29741
+ "trash"
29742
+ ];
29743
+ var pillPageSchema = _enum2(PILL_PAGES);
29744
+ var PILL_PAGES_MAX = 4;
29745
+ var CALENDAR_ZOOM_MIN = 75;
29746
+ var CALENDAR_ZOOM_MAX = 300;
29747
+ var calendarZoomSchema = number2().int().min(CALENDAR_ZOOM_MIN).max(CALENDAR_ZOOM_MAX);
29748
+ var settingsGlassSchema = _enum2(settingsGlass.enumValues);
29749
+ var themePackSchema = _enum2(settingsThemePack.enumValues);
29750
+ var themePackScopeSchema = _enum2(settingsThemePackScope.enumValues);
29751
+ var settingsUiSchema = _enum2(settingsUi.enumValues);
29752
+ var HEALTH_TODAY_HINTS = ["supplements", "sleep", "appointment", "episode"];
29753
+ var healthTodayHintSchema = _enum2(HEALTH_TODAY_HINTS);
29754
+ var HEALTH_TODAY_HINTS_DEFAULT = ["supplements", "sleep"];
29755
+ var userSettingsSchema = createSelectSchema(userSettings, {
29756
+ pinnedAreaIds: array(string2()).default([]),
29757
+ focusAreaId: string2().nullable().default(null),
29758
+ focusUntil: exports_coerce.date().nullable().default(null),
29759
+ quietHours: quietHoursSchema.nullable().default(null),
29760
+ notifications: record(string2(), unknown()).nullable().default(null),
29761
+ lifeBlockSharing: lifeBlockSharingSchema.nullable().default(null),
29762
+ pillPages: array(string2()).default([]).transform((ids) => ids.filter((id) => pillPageSchema.safeParse(id).success)),
29763
+ foodDiaryMode: boolean2().default(false),
29764
+ calendarZoom: number2().int().nullable().default(null),
29765
+ glass: settingsGlassSchema.default("liquid"),
29766
+ followSky: boolean2().default(true),
29767
+ nightUntilWake: boolean2().default(false),
29768
+ largerText: boolean2().default(false),
29769
+ reduceMotion: boolean2().default(false),
29770
+ showStreakNumbers: boolean2().default(true),
29771
+ themePack: themePackSchema.default("do"),
29772
+ themePackScope: themePackScopeSchema.default("section"),
29773
+ ui: settingsUiSchema.default("v1"),
29774
+ healthTodayHints: array(string2()).default([...HEALTH_TODAY_HINTS_DEFAULT]).transform((ids) => ids.filter((id) => healthTodayHintSchema.safeParse(id).success)),
29775
+ createdAt: exports_coerce.date(),
29776
+ updatedAt: exports_coerce.date()
29777
+ }).omit({ autoRollOverdue: true });
29778
+ var updateUserSettingsInputSchema = createUpdateSchema(userSettings, {
29779
+ displayName: string2().trim().max(80).nullish(),
29780
+ avatarUrl: union([string2().url().max(2048), literal("")]).nullish(),
29781
+ timezone: string2().min(1).optional(),
29782
+ locale: string2().min(1).optional(),
29783
+ theme: settingsThemeSchema.optional(),
29784
+ density: settingsDensitySchema.optional(),
29785
+ accent: settingsAccentSchema.optional(),
29786
+ sunCity: string2().trim().max(80).transform((city) => city === "" ? null : city).nullish(),
29787
+ backdrop: backdropIdSchema.nullish(),
29788
+ enabledModules: array(string2().trim().min(1)).transform(normalizeEnabledModules).optional(),
29789
+ pinnedAreaIds: array(ulidSchema).transform((ids) => [...new Set(ids)]).pipe(array(ulidSchema).max(PINNED_AREAS_MAX)).optional(),
29790
+ focusAreaId: ulidSchema.nullish(),
29791
+ quietHours: quietHoursSchema.nullish(),
29792
+ lifeBlockSharing: lifeBlockSharingSchema.nullish(),
29793
+ calendarZoom: calendarZoomSchema.nullish(),
29794
+ pillPages: array(pillPageSchema).transform((ids) => [...new Set(ids)]).pipe(array(pillPageSchema).max(PILL_PAGES_MAX)).optional(),
29795
+ glass: settingsGlassSchema.optional(),
29796
+ followSky: boolean2().optional(),
29797
+ nightUntilWake: boolean2().optional(),
29798
+ largerText: boolean2().optional(),
29799
+ reduceMotion: boolean2().optional(),
29800
+ showStreakNumbers: boolean2().optional(),
29801
+ themePack: themePackSchema.optional(),
29802
+ themePackScope: themePackScopeSchema.optional(),
29803
+ ui: settingsUiSchema.optional(),
29804
+ healthTodayHints: array(healthTodayHintSchema).transform((ids) => HEALTH_TODAY_HINTS.filter((id) => ids.includes(id))).optional()
29805
+ }).omit({
29806
+ id: true,
29807
+ tenantId: true,
29808
+ userId: true,
29809
+ createdAt: true,
29810
+ updatedAt: true,
29811
+ autoRollOverdue: true,
29812
+ focusUntil: true
29813
+ }).strict();
29814
+
29314
29815
  // ../../packages/schema/src/contracts/health-records.ts
29315
29816
  var BODY_REGION_VALUES = [
29316
29817
  "head",
@@ -29587,6 +30088,14 @@ var updateHealthProfileInputSchema = object({
29587
30088
  emergencyContactId: ulidSchema.nullish(),
29588
30089
  primaryGpContactId: ulidSchema.nullish()
29589
30090
  }).strict();
30091
+ var healthTodaySchema = object({
30092
+ hints: array(healthTodayHintSchema),
30093
+ on: boolean2(),
30094
+ supplementHabitIds: array(string2()),
30095
+ sleep: object({ hours: number2() }).nullable(),
30096
+ appointment: object({ id: string2(), title: string2(), at: exports_coerce.date() }).nullable(),
30097
+ episode: object({ id: string2(), title: string2(), startedAt: exports_coerce.date() }).nullable()
30098
+ });
29590
30099
  var grantHealthConsentInputSchema = object({ version: literal(HEALTH_CONSENT_VERSION) }).strict();
29591
30100
  var HEALTH_ERASE_PHRASE = "delete my health data";
29592
30101
  var eraseHealthDataInputSchema = object({ confirm: literal(HEALTH_ERASE_PHRASE) }).strict();
@@ -37987,7 +38496,7 @@ var planningRules = pgTable("planning_rules", {
37987
38496
  var PLANNING_RULE_KINDS = planningRuleKind.enumValues;
37988
38497
  var planningRuleKindSchema = _enum2(PLANNING_RULE_KINDS);
37989
38498
  var PLANNING_RULE_SENTENCE_MAX = 280;
37990
- var clockTimeSchema = string2().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "expected HH:MM (24 h)");
38499
+ var clockTimeSchema2 = string2().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "expected HH:MM (24 h)");
37991
38500
  var ruleWeekdaysSchema = array(number2().int().min(1).max(7)).min(1).max(7).transform((days) => [...new Set(days)].sort((a, b) => a - b));
37992
38501
  var phraseSchema = string2().trim().min(1).max(80);
37993
38502
  function clockMinutes(clock) {
@@ -37998,15 +38507,15 @@ var fixedTimeRuleSchema = object({
37998
38507
  kind: literal("fixed_time"),
37999
38508
  what: phraseSchema,
38000
38509
  days: ruleWeekdaysSchema,
38001
- at: clockTimeSchema,
38510
+ at: clockTimeSchema2,
38002
38511
  minutes: number2().int().min(5).max(24 * 60).default(60),
38003
38512
  lifeKind: lifeBlockKindSchema.optional()
38004
38513
  }).strict();
38005
38514
  var keepFreeRuleSchema = object({
38006
38515
  kind: literal("keep_free"),
38007
38516
  days: ruleWeekdaysSchema,
38008
- from: clockTimeSchema,
38009
- to: clockTimeSchema,
38517
+ from: clockTimeSchema2,
38518
+ to: clockTimeSchema2,
38010
38519
  what: phraseSchema.optional()
38011
38520
  }).strict().refine((r) => clockMinutes(r.to) > clockMinutes(r.from), {
38012
38521
  message: "`to` comes after `from`",
@@ -40065,18 +40574,18 @@ var runPlayStatusValues = [
40065
40574
  "dropped"
40066
40575
  ];
40067
40576
  var runPlayStatusSchema = _enum2(runPlayStatusValues);
40068
- var name2 = string2().trim().min(1).max(200);
40577
+ var name3 = string2().trim().min(1).max(200);
40069
40578
  var text5 = string2().trim().max(2000);
40070
40579
  var shortText2 = string2().trim().max(300);
40071
40580
  var isoDate = string2().regex(/^\d{4}-\d{2}-\d{2}$/, "must be an ISO date (YYYY-MM-DD)");
40072
40581
  var ref = string2().trim().min(1).max(200);
40073
40582
  var runCharacterSchema = object({
40074
- name: name2,
40583
+ name: name3,
40075
40584
  pronoun: _enum2(["he", "she", "they"]).nullish(),
40076
40585
  subtitle: shortText2.nullish(),
40077
40586
  level: number2().int().min(0).max(1e4).nullish(),
40078
40587
  build: shortText2.nullish(),
40079
- stats: array(object({ k: name2, v: union([number2(), shortText2]) }).strict()).max(40).optional(),
40588
+ stats: array(object({ k: name3, v: union([number2(), shortText2]) }).strict()).max(40).optional(),
40080
40589
  traits: array(shortText2).max(20).optional(),
40081
40590
  backstory: text5.nullish(),
40082
40591
  rules: array(shortText2).max(30).optional(),
@@ -40085,12 +40594,12 @@ var runCharacterSchema = object({
40085
40594
  var runArcSchema = object({
40086
40595
  title: shortText2.nullish(),
40087
40596
  chapters: array(object({
40088
- key: name2,
40089
- name: name2,
40597
+ key: name3,
40598
+ name: name3,
40090
40599
  range: shortText2.nullish(),
40091
40600
  moral: text5.nullish()
40092
40601
  }).strict()).max(40),
40093
- current: name2.nullish()
40602
+ current: name3.nullish()
40094
40603
  }).strict();
40095
40604
  var runStepStatusValues = ["done", "now", "todo", "skipped"];
40096
40605
  var runStepStatusSchema = _enum2(runStepStatusValues);
@@ -40103,7 +40612,7 @@ var runQuestStatusValues = ["active", "paused", "planned", "done", "failed"];
40103
40612
  var runQuestStatusSchema = _enum2(runQuestStatusValues);
40104
40613
  var runQuestSchema = object({
40105
40614
  ref: ref.nullish(),
40106
- name: name2,
40615
+ name: name3,
40107
40616
  line: shortText2.nullish(),
40108
40617
  status: runQuestStatusSchema.default("active"),
40109
40618
  objective: text5.nullish(),
@@ -40118,7 +40627,7 @@ var runQuestSchema = object({
40118
40627
  }).strict()).max(40).optional(),
40119
40628
  pausedAt: shortText2.nullish(),
40120
40629
  why: text5.nullish(),
40121
- revisitAt: name2.nullish()
40630
+ revisitAt: name3.nullish()
40122
40631
  }).strict();
40123
40632
  var runNextStepsSchema = object({
40124
40633
  items: array(shortText2.pipe(string2().min(1))).max(3),
@@ -40127,50 +40636,50 @@ var runNextStepsSchema = object({
40127
40636
  }).strict();
40128
40637
  var runChoiceSchema = object({
40129
40638
  ref: ref.nullish(),
40130
- title: name2,
40639
+ title: name3,
40131
40640
  place: shortText2.nullish(),
40132
40641
  status: _enum2(["made", "planned"]).default("made"),
40133
40642
  options: array(shortText2).max(6).optional(),
40134
40643
  why: text5.nullish(),
40135
40644
  consequence: text5.nullish(),
40136
- revisitAt: name2.nullish(),
40137
- chapter: name2.nullish(),
40645
+ revisitAt: name3.nullish(),
40646
+ chapter: name3.nullish(),
40138
40647
  tone: _enum2(["good", "grey", "dark"]).nullish(),
40139
40648
  on: isoDate.nullish()
40140
40649
  }).strict();
40141
40650
  var runHomeSchema = object({
40142
40651
  ref: ref.nullish(),
40143
- name: name2,
40652
+ name: name3,
40144
40653
  role: _enum2(["home", "collector", "storage", "planned", "later"]).default("home"),
40145
40654
  place: shortText2.nullish(),
40146
- upgrades: array(object({ name: name2, done: boolean2().default(false) }).strict()).max(40).optional(),
40655
+ upgrades: array(object({ name: name3, done: boolean2().default(false) }).strict()).max(40).optional(),
40147
40656
  steward: shortText2.nullish(),
40148
40657
  note: text5.nullish()
40149
40658
  }).strict();
40150
40659
  var runPersonSchema = object({
40151
40660
  ref: ref.nullish(),
40152
- name: name2,
40661
+ name: name3,
40153
40662
  role: _enum2(["child", "spouse", "steward", "follower", "pet", "rival", "other"]),
40154
40663
  status: _enum2(["with", "planned", "lost"]).default("with"),
40155
40664
  home: shortText2.nullish(),
40156
40665
  note: text5.nullish()
40157
40666
  }).strict();
40158
40667
  var runMountSchema = object({
40159
- name: name2,
40668
+ name: name3,
40160
40669
  status: _enum2(["with", "planned", "lost"]).default("with"),
40161
40670
  note: text5.nullish()
40162
40671
  }).strict();
40163
40672
  var runUniqueSchema = object({
40164
40673
  ref: ref.nullish(),
40165
- name: name2,
40674
+ name: name3,
40166
40675
  status: _enum2(["have", "now", "want"]).default("have"),
40167
40676
  source: shortText2.nullish(),
40168
40677
  displayAt: shortText2.nullish(),
40169
- set: name2.nullish()
40678
+ set: name3.nullish()
40170
40679
  }).strict();
40171
40680
  var runChecklistSchema = object({
40172
40681
  key: string2().trim().min(1).max(60).regex(/^[a-z0-9][a-z0-9_-]*$/, "must be a lowercase slug"),
40173
- label: name2,
40682
+ label: name3,
40174
40683
  total: number2().int().min(0).max(5000).nullish(),
40175
40684
  items: array(object({ id: ref, label: shortText2.nullish() }).strict()).max(5000).optional(),
40176
40685
  done: array(ref).max(5000),
@@ -40179,7 +40688,7 @@ var runChecklistSchema = object({
40179
40688
  legend: shortText2.nullish()
40180
40689
  }).strict();
40181
40690
  var runTeamMemberSchema = object({
40182
- species: name2,
40691
+ species: name3,
40183
40692
  nick: shortText2.nullish(),
40184
40693
  level: number2().int().min(1).max(100).nullish(),
40185
40694
  nature: shortText2.nullish(),
@@ -40193,8 +40702,8 @@ var runAchievementsSchema = object({
40193
40702
  source: _enum2(["steam", "xbox", "psn", "manual"]).default("manual"),
40194
40703
  total: number2().int().min(0).max(5000).nullish(),
40195
40704
  enabled: boolean2().default(true),
40196
- unlocked: array(object({ name: name2, on: isoDate.nullish(), hint: shortText2.nullish(), key: ref.nullish() }).strict()).max(5000),
40197
- locked: array(object({ name: name2, hint: shortText2.nullish(), key: ref.nullish() }).strict()).max(5000).optional(),
40705
+ unlocked: array(object({ name: name3, on: isoDate.nullish(), hint: shortText2.nullish(), key: ref.nullish() }).strict()).max(5000),
40706
+ locked: array(object({ name: name3, hint: shortText2.nullish(), key: ref.nullish() }).strict()).max(5000).optional(),
40198
40707
  syncedAt: string2().datetime({ offset: true }).nullish()
40199
40708
  }).strict();
40200
40709
  var runRareFindsSchema = object({
@@ -40204,7 +40713,7 @@ var runRareFindsSchema = object({
40204
40713
  }).strict();
40205
40714
  var runInstallSchema = object({
40206
40715
  edition: shortText2.nullish(),
40207
- mods: array(object({ name: name2, url: string2().url().max(2000).nullish() }).strict()).max(200).default([]),
40716
+ mods: array(object({ name: name3, url: string2().url().max(2000).nullish() }).strict()).max(200).default([]),
40208
40717
  savePath: string2().trim().max(500).nullish(),
40209
40718
  lastBackup: isoDate.nullish(),
40210
40719
  notes: text5.nullish()
@@ -40214,7 +40723,7 @@ var runSaveFields = {
40214
40723
  game: _enum2(["skyrim"]).default("skyrim"),
40215
40724
  fileName: shortText2.nullish(),
40216
40725
  saveNumber: number2().int().min(0).max(1e9),
40217
- playerName: name2,
40726
+ playerName: name3,
40218
40727
  level: number2().int().min(0).max(1e4),
40219
40728
  location: shortText2,
40220
40729
  race: shortText2.nullish(),
@@ -40260,7 +40769,7 @@ var runFacetSchema = object({
40260
40769
  install: runInstallSchema.nullish(),
40261
40770
  save: runSaveSchema.nullish(),
40262
40771
  money: object({ goal: shortText2.nullish(), purpose: shortText2.nullish() }).strict().nullish(),
40263
- links: array(object({ label: name2, url: string2().url().max(2000) }).strict()).max(50).optional()
40772
+ links: array(object({ label: name3, url: string2().url().max(2000) }).strict()).max(50).optional()
40264
40773
  }).strict();
40265
40774
  var runListSectionValues = [
40266
40775
  "orderOfPlay",
@@ -40414,12 +40923,12 @@ var runSaveHeaderResultSchema = object({
40414
40923
  var createRunInputSchema = object({
40415
40924
  id: ulidSchema,
40416
40925
  gameId: ulidSchema,
40417
- title: name2,
40926
+ title: name3,
40418
40927
  status: runPlayStatusSchema.optional(),
40419
40928
  facet: runFacetSchema.optional()
40420
40929
  }).strict();
40421
40930
  var updateRunInputSchema = object({
40422
- title: name2.optional(),
40931
+ title: name3.optional(),
40423
40932
  status: runPlayStatusSchema.optional(),
40424
40933
  facet: runFacetSchema.optional()
40425
40934
  }).strict();
@@ -40808,325 +41317,65 @@ var sensorOperations = defineOperations({
40808
41317
  response: { description: "The status." },
40809
41318
  errors: { 403: true }
40810
41319
  },
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
40955
- },
40956
- {
40957
- key: "notes",
40958
- label: "Notes",
40959
- description: "Quick notes and documents.",
40960
- group: "Organize",
40961
- defaultEnabled: true
40962
- },
40963
- {
40964
- key: "shopping",
40965
- label: "Shopping",
40966
- description: "Lists and groceries.",
40967
- group: "Household",
40968
- defaultEnabled: true
40969
- },
40970
- {
40971
- key: "inventory",
40972
- label: "Inventory",
40973
- description: "Things you own and stock.",
40974
- group: "Household",
40975
- defaultEnabled: true
40976
- },
40977
- {
40978
- key: "meals",
40979
- label: "Meals",
40980
- description: "Recipes and meal plans.",
40981
- group: "Household",
40982
- defaultEnabled: false
41320
+ unifiDevices: {
41321
+ method: "get",
41322
+ path: "/sensors/unifi/devices",
41323
+ scopes: ["places:read", "collections:read"],
41324
+ tag: "Sensors",
41325
+ summary: "The devices UniFi sees",
41326
+ 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.",
41327
+ status: 200,
41328
+ response: { description: "The devices." },
41329
+ errors: { 403: true, 409: true }
40983
41330
  },
40984
- {
40985
- key: "animals",
40986
- label: "Animals",
40987
- description: "Pets and flocks: eggs, weights, feed and care.",
40988
- group: "Household",
40989
- defaultEnabled: false
41331
+ syncUnifi: {
41332
+ method: "post",
41333
+ path: "/sensors/unifi/sync",
41334
+ scopes: ["places:write"],
41335
+ tag: "Sensors",
41336
+ summary: "Sync UniFi now",
41337
+ 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.",
41338
+ status: 200,
41339
+ response: { description: "What the sync did." },
41340
+ errors: { 403: true, 409: true }
40990
41341
  },
40991
- {
40992
- key: "contacts",
40993
- label: "Contacts",
40994
- description: "People and relationships.",
40995
- group: "People",
40996
- defaultEnabled: false
41342
+ links: {
41343
+ method: "get",
41344
+ path: "/sensors/links",
41345
+ scopes: ["places:read"],
41346
+ tag: "Sensors",
41347
+ summary: "List the mapped entities",
41348
+ 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.",
41349
+ query: [{ name: "nodeId", description: "Only the mappings on this Place or asset." }],
41350
+ querySchema: sensorLinksQuerySchema,
41351
+ status: 200,
41352
+ response: { description: "The mappings, by entity id." },
41353
+ errors: { 400: true }
40997
41354
  },
40998
- {
40999
- key: "sharing",
41000
- label: "Members & sharing",
41001
- description: "Members, roles, per-item sharing and comments.",
41002
- group: "People",
41003
- defaultEnabled: false
41355
+ createLink: {
41356
+ method: "post",
41357
+ path: "/sensors/links",
41358
+ scopes: ["places:write"],
41359
+ tag: "Sensors",
41360
+ summary: "Map an entity",
41361
+ 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.",
41362
+ body: createSensorLinkInputSchema,
41363
+ status: 201,
41364
+ response: { description: "The mapping." },
41365
+ errors: { 400: true, 404: true, 409: true }
41004
41366
  },
41005
- {
41006
- key: "automations",
41007
- label: "Automations",
41008
- description: "Trigger \u2192 condition \u2192 action rules.",
41009
- group: "Manage",
41010
- defaultEnabled: false
41367
+ removeLink: {
41368
+ method: "delete",
41369
+ path: "/sensors/links/{id}",
41370
+ scopes: ["places:write"],
41371
+ tag: "Sensors",
41372
+ summary: "Unmap an entity",
41373
+ description: "Remove one mapping; the readings it gave stay on the ledger.",
41374
+ status: 204,
41375
+ response: { description: "Removed." },
41376
+ errors: { 404: true }
41011
41377
  }
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
41378
  });
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
41379
 
41131
41380
  // ../../packages/schema/src/operations/settings.ts
41132
41381
  var settingsOperations = defineOperations({
@@ -43246,6 +43495,18 @@ var hobbyFacetSchema = object({
43246
43495
  with: string2().min(1).max(80).nullish(),
43247
43496
  coverUrl
43248
43497
  }).strict();
43498
+ var consoleFacetSchema = object({
43499
+ model: string2().min(1).max(120).nullish(),
43500
+ region: string2().min(1).max(40).nullish(),
43501
+ condition: string2().min(1).max(80).nullish(),
43502
+ boxed: boolean2().nullish(),
43503
+ extras: string2().min(1).max(200).nullish(),
43504
+ year: number2().int().min(1950).max(2100).nullish(),
43505
+ works: boolean2().nullish(),
43506
+ fixNote: string2().min(1).max(200).nullish(),
43507
+ plays: array(string2().trim().min(1).max(40)).max(12).nullish(),
43508
+ coverUrl
43509
+ }).strict();
43249
43510
  var placeFacetSchema = object({
43250
43511
  address: string2().min(1).max(400).nullish(),
43251
43512
  lat: number2().min(-90).max(90).nullish(),
@@ -43482,6 +43743,16 @@ var COLLECTION_TYPES = {
43482
43743
  facetSchema: hobbyFacetSchema,
43483
43744
  view: "grid",
43484
43745
  subtitleKey: "kind"
43746
+ },
43747
+ console: {
43748
+ key: "console",
43749
+ label: "Consoles",
43750
+ itemNoun: "console",
43751
+ statuses: ["want", "doing", "have", "done"],
43752
+ statusLabels: { want: "Wanted", doing: "Hooked up", have: "In the cupboard", done: "Retired" },
43753
+ facetSchema: consoleFacetSchema,
43754
+ view: "grid",
43755
+ subtitleKey: "model"
43485
43756
  }
43486
43757
  };
43487
43758
  var COLLECTION_TYPE_LIST = collectionTypeValues.map((k) => COLLECTION_TYPES[k]);
@@ -47095,6 +47366,106 @@ var commands9 = [
47095
47366
  return { data: types, render: renderList };
47096
47367
  }
47097
47368
  },
47369
+ {
47370
+ spec: {
47371
+ path: ["collection", "kinds"],
47372
+ summary: "List the Space's own kinds (vinyl, board games, \u2026) with their fields and words.",
47373
+ args: [],
47374
+ flags: [],
47375
+ scopes: [...collectionOperations.listKinds.scopes],
47376
+ returns: "CollectionKind[]",
47377
+ notes: `${routeOf(collectionOperations.listKinds)}. Each kind's \`key\` is what an Item of it carries as its \`type\`.`
47378
+ },
47379
+ run: async ({ requireClient }) => ({
47380
+ data: await requireClient().getPage(pathOf(collectionOperations.listKinds)),
47381
+ render: renderList
47382
+ })
47383
+ },
47384
+ {
47385
+ spec: {
47386
+ path: ["collection", "kind"],
47387
+ summary: "One of the Space's own kinds.",
47388
+ args: [{ name: "id", required: true, description: "Kind ULID." }],
47389
+ flags: [],
47390
+ scopes: [...collectionOperations.getKind.scopes],
47391
+ returns: "CollectionKind",
47392
+ notes: `${routeOf(collectionOperations.getKind)}.`
47393
+ },
47394
+ run: readOperation(collectionOperations.getKind, renderRecord, { kindId: "kind id" })
47395
+ },
47396
+ {
47397
+ spec: {
47398
+ path: ["collection", "kind-add"],
47399
+ summary: "Add a kind: from a preset (lego, vinyl, board, custom) or from scratch.",
47400
+ args: [{ name: "name", required: true, description: 'Its name ("Vinyl").' }],
47401
+ flags: [
47402
+ {
47403
+ name: "preset",
47404
+ type: "string",
47405
+ description: `Start from ${Object.keys(COLLECTION_KIND_PRESETS).join(", ")}; what's left out comes from it.`
47406
+ },
47407
+ {
47408
+ name: "data",
47409
+ type: "string",
47410
+ description: 'More JSON: `{ "key": "\u2026", "one": "record", "many": "records", "icon": "disc", "fields": [{ "key": "artist", "label": "Artist", "type": "text" }], "statusWords": { \u2026 } }`. The id is minted.'
47411
+ }
47412
+ ],
47413
+ scopes: [...collectionOperations.createKind.scopes],
47414
+ returns: "CollectionKind",
47415
+ notes: `${routeOf(collectionOperations.createKind)}. The key is the name's slug unless given; a key the Space has is a 409. \`do collection kind-add Vinyl --preset vinyl\`.`
47416
+ },
47417
+ run: async ({ args, flags, requireClient }) => {
47418
+ const name = requireArg(args, 0, "name");
47419
+ const preset = flagString(flags, "preset");
47420
+ if (preset)
47421
+ parseInput(collectionKindPresetSchema, preset);
47422
+ const body = parseInput(createCollectionKindInputSchema, {
47423
+ id: ulid3(),
47424
+ name,
47425
+ ...preset ? { preset } : {},
47426
+ ...readDataFlag(flags)
47427
+ });
47428
+ return {
47429
+ data: await requireClient().post(pathOf(collectionOperations.createKind), body),
47430
+ render: renderRecord
47431
+ };
47432
+ }
47433
+ },
47434
+ {
47435
+ spec: {
47436
+ path: ["collection", "kind-update"],
47437
+ summary: "Change a kind: its name, one/many, icon, fields or status words (the key stays).",
47438
+ args: [{ name: "id", required: true, description: "Kind ULID." }],
47439
+ flags: [{ name: "data", type: "string", description: "UpdateCollectionKindInput JSON." }],
47440
+ scopes: [...collectionOperations.updateKind.scopes],
47441
+ returns: "CollectionKind",
47442
+ notes: `${routeOf(collectionOperations.updateKind)}. \`fields\` replaces the list.`
47443
+ },
47444
+ run: async ({ args, flags, requireClient }) => {
47445
+ const kindId = requireArg(args, 0, "kind id");
47446
+ const body = parseInput(updateCollectionKindInputSchema, readDataFlag(flags));
47447
+ return {
47448
+ data: await requireClient().patch(pathOf(collectionOperations.updateKind, { kindId }), body),
47449
+ render: renderRecord
47450
+ };
47451
+ }
47452
+ },
47453
+ {
47454
+ spec: {
47455
+ path: ["collection", "kind-remove"],
47456
+ summary: "Remove a kind that has no Items.",
47457
+ args: [{ name: "id", required: true, description: "Kind ULID." }],
47458
+ flags: [],
47459
+ scopes: [...collectionOperations.removeKind.scopes],
47460
+ returns: "void",
47461
+ notes: `${routeOf(collectionOperations.removeKind)}. A 409 while it still has Items.`
47462
+ },
47463
+ run: async ({ args, requireClient }) => {
47464
+ const kindId = requireArg(args, 0, "kind id");
47465
+ await requireClient().delete(pathOf(collectionOperations.removeKind, { kindId }));
47466
+ return { data: { ok: true, kindId }, render: renderRecord };
47467
+ }
47468
+ },
47098
47469
  {
47099
47470
  spec: {
47100
47471
  path: ["collection", "list"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robbeverhelst/do",
3
- "version": "1.390.0",
3
+ "version": "1.392.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",