@robbeverhelst/do 1.388.0 → 1.390.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 +495 -230
  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.388.0";
14423
+ var VERSION = "1.390.0";
14424
14424
  var API_PREFIX = "/api/v1";
14425
14425
  var DEFAULT_API_URL = "http://localhost:3001";
14426
14426
 
@@ -18515,6 +18515,7 @@ var keyDates = pgTable("key_dates", {
18515
18515
  derivedFrom: text("derived_from"),
18516
18516
  source: text("source").notNull().default("manual"),
18517
18517
  confirmedAt: timestamp("confirmed_at", { withTimezone: true }),
18518
+ bookedAt: timestamp("booked_at", { withTimezone: true }),
18518
18519
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
18519
18520
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
18520
18521
  }, (t) => [
@@ -20855,6 +20856,7 @@ var keyDateRepeatSchema = _enum2(["yearly"]);
20855
20856
  var keyDateSchema = createSelectSchema(keyDates, {
20856
20857
  date: keyDateDaySchema,
20857
20858
  confirmedAt: exports_coerce.date().nullable(),
20859
+ bookedAt: exports_coerce.date().nullish(),
20858
20860
  createdAt: exports_coerce.date(),
20859
20861
  updatedAt: exports_coerce.date()
20860
20862
  });
@@ -20884,7 +20886,8 @@ var createKeyDateInputSchema = object({
20884
20886
  leadDays: leadDaysSchema.optional(),
20885
20887
  derivedFrom: string2().trim().min(1).max(120).nullish(),
20886
20888
  source: sourceSchema.optional(),
20887
- confirmed: boolean2().optional()
20889
+ confirmed: boolean2().optional(),
20890
+ bookedAt: exports_coerce.date().nullish()
20888
20891
  }).strict();
20889
20892
  var updateKeyDateInputSchema = object({
20890
20893
  kind: keyDateKindSchema.optional(),
@@ -20895,8 +20898,10 @@ var updateKeyDateInputSchema = object({
20895
20898
  leadDays: leadDaysSchema.optional(),
20896
20899
  derivedFrom: string2().trim().min(1).max(120).nullish(),
20897
20900
  source: sourceSchema.optional(),
20898
- confirmed: boolean2().optional()
20901
+ confirmed: boolean2().optional(),
20902
+ bookedAt: exports_coerce.date().nullish()
20899
20903
  }).strict();
20904
+ var keyDateBookingInputSchema = object({ bookedAt: exports_coerce.date().nullable() }).strict();
20900
20905
  var keyDatesQuerySchema = object({
20901
20906
  nodeId: ulidSchema.optional(),
20902
20907
  kind: keyDateKindSchema.optional(),
@@ -29275,6 +29280,36 @@ var healthDataKeys = pgTable("health_data_keys", {
29275
29280
  sealedKey: text("sealed_key").notNull(),
29276
29281
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
29277
29282
  }, (t) => [primaryKey({ columns: [t.tenantId, t.userId] })]);
29283
+ var healthCycleSettings = pgTable("health_cycle_settings", {
29284
+ tenantId: text("tenant_id").notNull().references(() => tenants.id),
29285
+ userId: text("user_id").notNull().references(() => users.id),
29286
+ on: boolean4("on").notNull().default(false),
29287
+ usualLength: integer2("usual_length"),
29288
+ shareNext: boolean4("share_next").notNull().default(false),
29289
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
29290
+ }, (t) => [
29291
+ primaryKey({ columns: [t.tenantId, t.userId] }),
29292
+ check2("health_cycle_settings_length_chk", sql`${t.usualLength} IS NULL OR ${t.usualLength} BETWEEN 15 AND 60`)
29293
+ ]);
29294
+ var healthCycleDays = pgTable("health_cycle_days", {
29295
+ id: text("id").primaryKey(),
29296
+ tenantId: text("tenant_id").notNull().references(() => tenants.id),
29297
+ userId: text("user_id").notNull().references(() => users.id),
29298
+ day: date5("day", { mode: "string" }).notNull(),
29299
+ flow: text("flow"),
29300
+ symptoms: text("symptoms").array().notNull().default(sql`'{}'::text[]`),
29301
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
29302
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
29303
+ }, (t) => [
29304
+ uniqueIndex("health_cycle_days_member_day_uniq").on(t.tenantId, t.userId, t.day),
29305
+ check2("health_cycle_days_flow_chk", sql`${t.flow} IS NULL OR ${t.flow} IN ('none', 'light', 'medium', 'heavy')`)
29306
+ ]);
29307
+ var healthCycleShares = pgTable("health_cycle_shares", {
29308
+ tenantId: text("tenant_id").notNull().references(() => tenants.id),
29309
+ userId: text("user_id").notNull().references(() => users.id),
29310
+ nextOn: date5("next_on", { mode: "string" }).notNull(),
29311
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
29312
+ }, (t) => [primaryKey({ columns: [t.tenantId, t.userId] })]);
29278
29313
 
29279
29314
  // ../../packages/schema/src/contracts/health-records.ts
29280
29315
  var BODY_REGION_VALUES = [
@@ -31645,6 +31680,283 @@ var habitOperations = defineOperations({
31645
31680
  }
31646
31681
  });
31647
31682
 
31683
+ // ../../packages/schema/src/contracts/plans.ts
31684
+ var PLAN_KINDS = ["week", "day"];
31685
+ var PLAN_ITEM_ACTIONS = ["plan", "someday", "let_go"];
31686
+ var PLAN_ITEM_STATES = ["proposed", "accepted", "dismissed"];
31687
+ var PLAN_STATUSES = ["open", "settled", "replaced"];
31688
+ var PLAN_DUPLICATE_MODES = ["merge", "reject"];
31689
+ var PLAN_MERGE_OUTCOMES = [
31690
+ "scheduled_task",
31691
+ "covered_by_item",
31692
+ "already_planned"
31693
+ ];
31694
+ var PLAN_MATCH_KINDS = ["task", "time_block", "event"];
31695
+ var PLAN_MAX_ITEMS = 60;
31696
+ var PLAN_MAX_BLOCKS = 140;
31697
+ var PLAN_REASON_MAX = 280;
31698
+ var PLAN_NOTE_MAX = 2000;
31699
+ var planKindSchema = _enum2(PLAN_KINDS);
31700
+ var planItemActionSchema = _enum2(PLAN_ITEM_ACTIONS);
31701
+ var planItemStateSchema = _enum2(PLAN_ITEM_STATES);
31702
+ var planStatusSchema = _enum2(PLAN_STATUSES);
31703
+ var civilDaySchema = string2().regex(/^\d{4}-\d{2}-\d{2}$/, "expected YYYY-MM-DD").refine((s) => new Date(`${s}T00:00:00Z`).toISOString().slice(0, 10) === s, "not a real day");
31704
+ var DAY_MS = 24 * 60 * 60 * 1000;
31705
+ function addCivilDays(day, n) {
31706
+ return new Date(Date.parse(`${day}T00:00:00Z`) + n * DAY_MS).toISOString().slice(0, 10);
31707
+ }
31708
+ function planDays(kind, startsOn) {
31709
+ return kind === "week" ? Array.from({ length: 7 }, (_, i) => addCivilDays(startsOn, i)) : [startsOn];
31710
+ }
31711
+ function isMonday(day) {
31712
+ return new Date(`${day}T00:00:00Z`).getUTCDay() === 1;
31713
+ }
31714
+ var planItemInputSchema = object({
31715
+ taskId: ulidSchema,
31716
+ action: planItemActionSchema.default("plan"),
31717
+ day: civilDaySchema.optional(),
31718
+ at: exports_coerce.date().optional(),
31719
+ durationMinutes: number2().int().min(5).max(24 * 60).optional(),
31720
+ reason: string2().trim().min(1).max(PLAN_REASON_MAX).optional()
31721
+ }).strict().superRefine((v, ctx) => {
31722
+ if (v.action === "plan") {
31723
+ if (v.day === undefined === (v.at === undefined)) {
31724
+ ctx.addIssue({
31725
+ code: "custom",
31726
+ message: "a `plan` item takes exactly one of `day` or `at`",
31727
+ path: ["day"]
31728
+ });
31729
+ }
31730
+ if (v.durationMinutes !== undefined && v.at === undefined) {
31731
+ ctx.addIssue({
31732
+ code: "custom",
31733
+ message: "`durationMinutes` goes with `at` (a time)",
31734
+ path: ["durationMinutes"]
31735
+ });
31736
+ }
31737
+ } else if (v.day !== undefined || v.at !== undefined || v.durationMinutes !== undefined) {
31738
+ ctx.addIssue({
31739
+ code: "custom",
31740
+ message: `a \`${v.action}\` item takes no day, time or duration`,
31741
+ path: ["action"]
31742
+ });
31743
+ }
31744
+ });
31745
+ var planBlockInputSchema = object({
31746
+ templateId: ulidSchema.optional(),
31747
+ lifeKind: lifeBlockKindSchema,
31748
+ title: string2().trim().min(1).max(500).optional(),
31749
+ startAt: exports_coerce.date(),
31750
+ endAt: exports_coerce.date(),
31751
+ routineId: ulidSchema.optional(),
31752
+ reason: string2().trim().min(1).max(PLAN_REASON_MAX).optional()
31753
+ }).strict().refine((v) => {
31754
+ const minutes = (v.endAt.getTime() - v.startAt.getTime()) / 60000;
31755
+ return minutes > 0 && minutes <= TIME_BLOCK_MAX_MINUTES;
31756
+ }, { message: "endAt must be after startAt and at most a day later", path: ["endAt"] });
31757
+ var createPlanInputSchema = object({
31758
+ kind: planKindSchema,
31759
+ startsOn: civilDaySchema,
31760
+ forUserId: string2().min(1).optional(),
31761
+ note: string2().trim().min(1).max(PLAN_NOTE_MAX).optional(),
31762
+ items: array(planItemInputSchema).max(PLAN_MAX_ITEMS).default([]),
31763
+ blocks: array(planBlockInputSchema).max(PLAN_MAX_BLOCKS).default([]),
31764
+ onDuplicate: _enum2(PLAN_DUPLICATE_MODES).default("merge")
31765
+ }).strict().superRefine((v, ctx) => {
31766
+ if (v.items.length + v.blocks.length === 0) {
31767
+ ctx.addIssue({
31768
+ code: "custom",
31769
+ message: "a plan proposes at least one item or block",
31770
+ path: ["items"]
31771
+ });
31772
+ }
31773
+ if (v.kind === "week" && !isMonday(v.startsOn)) {
31774
+ ctx.addIssue({
31775
+ code: "custom",
31776
+ message: "a week plan starts on a Monday",
31777
+ path: ["startsOn"]
31778
+ });
31779
+ }
31780
+ const days = new Set(planDays(v.kind, v.startsOn));
31781
+ const seen = new Set;
31782
+ v.items.forEach((item, i) => {
31783
+ if (seen.has(item.taskId)) {
31784
+ ctx.addIssue({
31785
+ code: "custom",
31786
+ message: "one item per task",
31787
+ path: ["items", i, "taskId"]
31788
+ });
31789
+ }
31790
+ seen.add(item.taskId);
31791
+ if (item.day !== undefined && !days.has(item.day)) {
31792
+ ctx.addIssue({
31793
+ code: "custom",
31794
+ message: `the day falls outside the ${v.kind}`,
31795
+ path: ["items", i, "day"]
31796
+ });
31797
+ }
31798
+ });
31799
+ });
31800
+ var planDecisionInputSchema = object({
31801
+ itemIds: array(string2().min(1)).min(1).max(PLAN_MAX_ITEMS + PLAN_MAX_BLOCKS).optional()
31802
+ }).strict();
31803
+ var planAdjustInputSchema = object({
31804
+ itemId: string2().min(1),
31805
+ startAt: exports_coerce.date(),
31806
+ endAt: exports_coerce.date()
31807
+ }).strict().refine((v) => {
31808
+ const minutes = (v.endAt.getTime() - v.startAt.getTime()) / 60000;
31809
+ return minutes >= 5 && minutes <= TIME_BLOCK_MAX_MINUTES;
31810
+ }, { message: "endAt must be 5 minutes to a day after startAt", path: ["endAt"] });
31811
+ var PLAN_TEMPLATES = ["normal", "light", "away"];
31812
+ var planTemplateSchema = _enum2(PLAN_TEMPLATES);
31813
+ var planDraftInputSchema = object({
31814
+ startsOn: civilDaySchema.refine(isMonday, "a week starts on a Monday"),
31815
+ template: planTemplateSchema.default("normal")
31816
+ }).strict();
31817
+ var planPlaceInputSchema = object({
31818
+ taskId: ulidSchema,
31819
+ startAt: exports_coerce.date(),
31820
+ endAt: exports_coerce.date()
31821
+ }).strict().refine((v) => {
31822
+ const minutes = (v.endAt.getTime() - v.startAt.getTime()) / 60000;
31823
+ return minutes >= 5 && minutes <= TIME_BLOCK_MAX_MINUTES;
31824
+ }, { message: "endAt must be 5 minutes to a day after startAt", path: ["endAt"] });
31825
+ var planUnplaceInputSchema = object({ taskId: ulidSchema }).strict();
31826
+ var planListQuerySchema = object({
31827
+ kind: planKindSchema.optional(),
31828
+ status: planStatusSchema.optional(),
31829
+ startsOn: civilDaySchema.optional(),
31830
+ taskId: ulidSchema.optional()
31831
+ });
31832
+ var planTaskSchema = object({
31833
+ id: ulidSchema,
31834
+ title: string2(),
31835
+ status: taskStatusSchema,
31836
+ scheduledAt: exports_coerce.date().nullable(),
31837
+ allDay: boolean2(),
31838
+ dueAt: exports_coerce.date().nullable(),
31839
+ durationMinutes: number2().int().nullable(),
31840
+ areaId: string2().nullable(),
31841
+ projectId: string2().nullable(),
31842
+ moveCount: number2().int().nonnegative().default(0)
31843
+ });
31844
+ var planItemViewSchema = object({
31845
+ id: string2(),
31846
+ taskId: ulidSchema,
31847
+ action: planItemActionSchema,
31848
+ scheduledAt: exports_coerce.date().nullable(),
31849
+ allDay: boolean2(),
31850
+ durationMinutes: number2().int().nullable(),
31851
+ reason: string2().nullable(),
31852
+ state: planItemStateSchema,
31853
+ decidedAt: exports_coerce.date().nullable(),
31854
+ task: planTaskSchema
31855
+ });
31856
+ var planBlockViewSchema = object({
31857
+ id: string2(),
31858
+ templateId: string2().nullable(),
31859
+ lifeKind: lifeBlockKindSchema,
31860
+ title: string2(),
31861
+ startAt: exports_coerce.date(),
31862
+ endAt: exports_coerce.date(),
31863
+ routineId: string2().nullable(),
31864
+ reason: string2().nullable(),
31865
+ state: planItemStateSchema,
31866
+ decidedAt: exports_coerce.date().nullable(),
31867
+ blockId: string2().nullable()
31868
+ });
31869
+ var planViewSchema = object({
31870
+ id: string2(),
31871
+ kind: planKindSchema,
31872
+ startsOn: civilDaySchema,
31873
+ userId: string2(),
31874
+ agentId: string2().nullable(),
31875
+ note: string2().nullable(),
31876
+ template: planTemplateSchema.nullable().default(null),
31877
+ status: planStatusSchema,
31878
+ createdAt: exports_coerce.date(),
31879
+ updatedAt: exports_coerce.date(),
31880
+ items: array(planItemViewSchema),
31881
+ blocks: array(planBlockViewSchema).default([])
31882
+ });
31883
+ var planMergeSchema = object({
31884
+ blockIndex: number2().int().min(0),
31885
+ title: string2(),
31886
+ outcome: _enum2(PLAN_MERGE_OUTCOMES),
31887
+ match: object({ kind: _enum2(PLAN_MATCH_KINDS), id: string2(), title: string2() }),
31888
+ hint: string2()
31889
+ });
31890
+ var PLAN_RULE_OUTCOMES = [
31891
+ "anchored",
31892
+ "added",
31893
+ "kept_free",
31894
+ "over_limit",
31895
+ "below_min",
31896
+ "not_theirs"
31897
+ ];
31898
+ var planShapingSchema = object({
31899
+ ruleId: string2(),
31900
+ sentence: string2(),
31901
+ outcome: _enum2(PLAN_RULE_OUTCOMES),
31902
+ title: string2(),
31903
+ hint: string2()
31904
+ });
31905
+ var planCreateResultSchema = planViewSchema.extend({
31906
+ merged: array(planMergeSchema).default([]),
31907
+ shaped: array(planShapingSchema).default([])
31908
+ });
31909
+
31910
+ // ../../packages/schema/src/contracts/health-cycle.ts
31911
+ var HEALTH_CYCLE_FLOWS = ["none", "light", "medium", "heavy"];
31912
+ var healthCycleFlowSchema = _enum2(HEALTH_CYCLE_FLOWS);
31913
+ var HEALTH_CYCLE_SYMPTOMS = ["cramps", "headache", "tired", "low_mood", "skin"];
31914
+ var healthCycleSymptomSchema = _enum2(HEALTH_CYCLE_SYMPTOMS);
31915
+ var HEALTH_CYCLE_LENGTH_MIN = 15;
31916
+ var HEALTH_CYCLE_LENGTH_MAX = 60;
31917
+ var healthCycleSettingsSchema = object({
31918
+ on: boolean2(),
31919
+ usualLength: number2().int().nullable(),
31920
+ shareNext: boolean2()
31921
+ });
31922
+ var healthCycleDaySchema = object({
31923
+ day: civilDaySchema,
31924
+ flow: healthCycleFlowSchema.nullable(),
31925
+ symptoms: array(healthCycleSymptomSchema)
31926
+ });
31927
+ var healthCycleSummarySchema = object({
31928
+ lastStart: civilDaySchema.nullable(),
31929
+ day: number2().int().nullable(),
31930
+ length: number2().int(),
31931
+ lengthFrom: _enum2(["set", "cycles", "default"]),
31932
+ periodDays: number2().int().nullable(),
31933
+ nextOn: civilDaySchema.nullable(),
31934
+ fertile: tuple([number2().int(), number2().int()]).nullable(),
31935
+ lengths: array(object({ start: civilDaySchema, days: number2().int() })),
31936
+ regular: string2().nullable()
31937
+ });
31938
+ var healthCycleViewSchema = object({
31939
+ settings: healthCycleSettingsSchema,
31940
+ days: array(healthCycleDaySchema),
31941
+ summary: healthCycleSummarySchema.nullable()
31942
+ });
31943
+ var updateHealthCycleSettingsInputSchema = object({
31944
+ on: boolean2().optional(),
31945
+ usualLength: number2().int().min(HEALTH_CYCLE_LENGTH_MIN).max(HEALTH_CYCLE_LENGTH_MAX).nullable().optional(),
31946
+ shareNext: boolean2().optional()
31947
+ }).strict().refine((v) => Object.keys(v).length > 0, { message: "set one setting at least" });
31948
+ var logHealthCycleDayInputSchema = object({
31949
+ flow: healthCycleFlowSchema.nullable().optional(),
31950
+ symptoms: array(healthCycleSymptomSchema).max(HEALTH_CYCLE_SYMPTOMS.length).refine((xs) => new Set(xs).size === xs.length, { message: "a symptom once" }).optional()
31951
+ }).strict().refine((v) => v.flow !== undefined || v.symptoms !== undefined, {
31952
+ message: "give a flow or symptoms"
31953
+ });
31954
+ var healthCycleSharedSchema = array(object({
31955
+ userId: string2(),
31956
+ name: string2(),
31957
+ nextOn: civilDaySchema
31958
+ }));
31959
+
31648
31960
  // ../../packages/schema/src/contracts/health-export.ts
31649
31961
  var HEALTH_EXPORT_RESOURCE_TYPES = [
31650
31962
  "Patient",
@@ -31851,6 +32163,73 @@ var healthOperations = defineOperations({
31851
32163
  response: { description: "The profile." },
31852
32164
  errors: { 400: true, 403: "Health is off for the member, or the key acts for no member." }
31853
32165
  },
32166
+ getCycle: {
32167
+ method: "get",
32168
+ path: "/health/cycle",
32169
+ scopes: ["health:read"],
32170
+ tag: "Health",
32171
+ summary: "Get the member's cycle",
32172
+ description: "Cycle, an optional part of Health each member turns on for themselves (off by default): `settings` (`on`, the `usualLength` they set or null, `shareNext`), the `days` logged in the last 400 days (oldest first: `day`, `flow` none/light/medium/heavy or null, `symptoms`) and the `summary` they make (the cycle's `day` today, `lastStart`, the usual `length` and where it comes from, `nextOn` and the `fertile` window as cycle days, both estimates, the last six `lengths`, and `regular`). While Cycle is off, `days` is empty and `summary` null.",
32173
+ status: 200,
32174
+ response: { description: "The member's cycle." },
32175
+ errors: { 403: "The key acts for no member." }
32176
+ },
32177
+ updateCycleSettings: {
32178
+ method: "patch",
32179
+ path: "/health/cycle/settings",
32180
+ scopes: ["health:write"],
32181
+ tag: "Health",
32182
+ summary: "Change the cycle settings",
32183
+ description: "Set `usualLength` (15\u201360 days, or null to work it out from the cycles) and `shareNext` (share only the next period's date with the Space). Only the member turns Cycle `on` or off, in do; a key that tries is refused (403). Returns the cycle.",
32184
+ body: updateHealthCycleSettingsInputSchema,
32185
+ status: 200,
32186
+ response: { description: "The member's cycle." },
32187
+ errors: {
32188
+ 400: true,
32189
+ 403: "Health or Cycle is off, a key tried to turn Cycle on or off, or the key acts for no member."
32190
+ }
32191
+ },
32192
+ logCycleDay: {
32193
+ method: "put",
32194
+ path: "/health/cycle/days/{day}",
32195
+ scopes: ["health:write"],
32196
+ tag: "Health",
32197
+ summary: "Log a cycle day",
32198
+ description: "Log the day (`YYYY-MM-DD`, today or before): the `flow` (none, light, medium, heavy, or null to clear it) and the `symptoms` (cramps, headache, tired, low_mood, skin). It replaces what the day had; an absent key keeps its part. Returns the cycle.",
32199
+ body: logHealthCycleDayInputSchema,
32200
+ status: 200,
32201
+ response: { description: "The member's cycle." },
32202
+ errors: {
32203
+ 400: true,
32204
+ 403: "Health or Cycle is off for the member, or the key acts for no member."
32205
+ }
32206
+ },
32207
+ deleteCycleDay: {
32208
+ method: "delete",
32209
+ path: "/health/cycle/days/{day}",
32210
+ scopes: ["health:write"],
32211
+ tag: "Health",
32212
+ summary: "Take back a cycle day",
32213
+ description: "Nothing logged on the day any more. Returns the cycle.",
32214
+ status: 200,
32215
+ response: { description: "The member's cycle." },
32216
+ errors: {
32217
+ 400: true,
32218
+ 403: "Health or Cycle is off for the member, or the key acts for no member.",
32219
+ 404: true
32220
+ }
32221
+ },
32222
+ listSharedCycleDates: {
32223
+ method: "get",
32224
+ path: "/health/cycle/shared",
32225
+ scopes: ["health:read"],
32226
+ tag: "Health",
32227
+ summary: "List the shared next period dates",
32228
+ description: "The next period date each other member of the Space shares (`userId`, `name`, `nextOn`, an estimate), soonest first. Only the date: nothing they log is shared.",
32229
+ status: 200,
32230
+ response: { description: "The shared dates." },
32231
+ errors: { 403: "The key acts for no member." }
32232
+ },
31854
32233
  log: {
31855
32234
  method: "post",
31856
32235
  path: "/health/log",
@@ -37786,233 +38165,6 @@ var planningRuleOperations = defineOperations({
37786
38165
  })
37787
38166
  });
37788
38167
 
37789
- // ../../packages/schema/src/contracts/plans.ts
37790
- var PLAN_KINDS = ["week", "day"];
37791
- var PLAN_ITEM_ACTIONS = ["plan", "someday", "let_go"];
37792
- var PLAN_ITEM_STATES = ["proposed", "accepted", "dismissed"];
37793
- var PLAN_STATUSES = ["open", "settled", "replaced"];
37794
- var PLAN_DUPLICATE_MODES = ["merge", "reject"];
37795
- var PLAN_MERGE_OUTCOMES = [
37796
- "scheduled_task",
37797
- "covered_by_item",
37798
- "already_planned"
37799
- ];
37800
- var PLAN_MATCH_KINDS = ["task", "time_block", "event"];
37801
- var PLAN_MAX_ITEMS = 60;
37802
- var PLAN_MAX_BLOCKS = 140;
37803
- var PLAN_REASON_MAX = 280;
37804
- var PLAN_NOTE_MAX = 2000;
37805
- var planKindSchema = _enum2(PLAN_KINDS);
37806
- var planItemActionSchema = _enum2(PLAN_ITEM_ACTIONS);
37807
- var planItemStateSchema = _enum2(PLAN_ITEM_STATES);
37808
- var planStatusSchema = _enum2(PLAN_STATUSES);
37809
- var civilDaySchema = string2().regex(/^\d{4}-\d{2}-\d{2}$/, "expected YYYY-MM-DD").refine((s) => new Date(`${s}T00:00:00Z`).toISOString().slice(0, 10) === s, "not a real day");
37810
- var DAY_MS = 24 * 60 * 60 * 1000;
37811
- function addCivilDays(day, n) {
37812
- return new Date(Date.parse(`${day}T00:00:00Z`) + n * DAY_MS).toISOString().slice(0, 10);
37813
- }
37814
- function planDays(kind, startsOn) {
37815
- return kind === "week" ? Array.from({ length: 7 }, (_, i) => addCivilDays(startsOn, i)) : [startsOn];
37816
- }
37817
- function isMonday(day) {
37818
- return new Date(`${day}T00:00:00Z`).getUTCDay() === 1;
37819
- }
37820
- var planItemInputSchema = object({
37821
- taskId: ulidSchema,
37822
- action: planItemActionSchema.default("plan"),
37823
- day: civilDaySchema.optional(),
37824
- at: exports_coerce.date().optional(),
37825
- durationMinutes: number2().int().min(5).max(24 * 60).optional(),
37826
- reason: string2().trim().min(1).max(PLAN_REASON_MAX).optional()
37827
- }).strict().superRefine((v, ctx) => {
37828
- if (v.action === "plan") {
37829
- if (v.day === undefined === (v.at === undefined)) {
37830
- ctx.addIssue({
37831
- code: "custom",
37832
- message: "a `plan` item takes exactly one of `day` or `at`",
37833
- path: ["day"]
37834
- });
37835
- }
37836
- if (v.durationMinutes !== undefined && v.at === undefined) {
37837
- ctx.addIssue({
37838
- code: "custom",
37839
- message: "`durationMinutes` goes with `at` (a time)",
37840
- path: ["durationMinutes"]
37841
- });
37842
- }
37843
- } else if (v.day !== undefined || v.at !== undefined || v.durationMinutes !== undefined) {
37844
- ctx.addIssue({
37845
- code: "custom",
37846
- message: `a \`${v.action}\` item takes no day, time or duration`,
37847
- path: ["action"]
37848
- });
37849
- }
37850
- });
37851
- var planBlockInputSchema = object({
37852
- templateId: ulidSchema.optional(),
37853
- lifeKind: lifeBlockKindSchema,
37854
- title: string2().trim().min(1).max(500).optional(),
37855
- startAt: exports_coerce.date(),
37856
- endAt: exports_coerce.date(),
37857
- routineId: ulidSchema.optional(),
37858
- reason: string2().trim().min(1).max(PLAN_REASON_MAX).optional()
37859
- }).strict().refine((v) => {
37860
- const minutes = (v.endAt.getTime() - v.startAt.getTime()) / 60000;
37861
- return minutes > 0 && minutes <= TIME_BLOCK_MAX_MINUTES;
37862
- }, { message: "endAt must be after startAt and at most a day later", path: ["endAt"] });
37863
- var createPlanInputSchema = object({
37864
- kind: planKindSchema,
37865
- startsOn: civilDaySchema,
37866
- forUserId: string2().min(1).optional(),
37867
- note: string2().trim().min(1).max(PLAN_NOTE_MAX).optional(),
37868
- items: array(planItemInputSchema).max(PLAN_MAX_ITEMS).default([]),
37869
- blocks: array(planBlockInputSchema).max(PLAN_MAX_BLOCKS).default([]),
37870
- onDuplicate: _enum2(PLAN_DUPLICATE_MODES).default("merge")
37871
- }).strict().superRefine((v, ctx) => {
37872
- if (v.items.length + v.blocks.length === 0) {
37873
- ctx.addIssue({
37874
- code: "custom",
37875
- message: "a plan proposes at least one item or block",
37876
- path: ["items"]
37877
- });
37878
- }
37879
- if (v.kind === "week" && !isMonday(v.startsOn)) {
37880
- ctx.addIssue({
37881
- code: "custom",
37882
- message: "a week plan starts on a Monday",
37883
- path: ["startsOn"]
37884
- });
37885
- }
37886
- const days = new Set(planDays(v.kind, v.startsOn));
37887
- const seen = new Set;
37888
- v.items.forEach((item, i) => {
37889
- if (seen.has(item.taskId)) {
37890
- ctx.addIssue({
37891
- code: "custom",
37892
- message: "one item per task",
37893
- path: ["items", i, "taskId"]
37894
- });
37895
- }
37896
- seen.add(item.taskId);
37897
- if (item.day !== undefined && !days.has(item.day)) {
37898
- ctx.addIssue({
37899
- code: "custom",
37900
- message: `the day falls outside the ${v.kind}`,
37901
- path: ["items", i, "day"]
37902
- });
37903
- }
37904
- });
37905
- });
37906
- var planDecisionInputSchema = object({
37907
- itemIds: array(string2().min(1)).min(1).max(PLAN_MAX_ITEMS + PLAN_MAX_BLOCKS).optional()
37908
- }).strict();
37909
- var planAdjustInputSchema = object({
37910
- itemId: string2().min(1),
37911
- startAt: exports_coerce.date(),
37912
- endAt: exports_coerce.date()
37913
- }).strict().refine((v) => {
37914
- const minutes = (v.endAt.getTime() - v.startAt.getTime()) / 60000;
37915
- return minutes >= 5 && minutes <= TIME_BLOCK_MAX_MINUTES;
37916
- }, { message: "endAt must be 5 minutes to a day after startAt", path: ["endAt"] });
37917
- var PLAN_TEMPLATES = ["normal", "light", "away"];
37918
- var planTemplateSchema = _enum2(PLAN_TEMPLATES);
37919
- var planDraftInputSchema = object({
37920
- startsOn: civilDaySchema.refine(isMonday, "a week starts on a Monday"),
37921
- template: planTemplateSchema.default("normal")
37922
- }).strict();
37923
- var planPlaceInputSchema = object({
37924
- taskId: ulidSchema,
37925
- startAt: exports_coerce.date(),
37926
- endAt: exports_coerce.date()
37927
- }).strict().refine((v) => {
37928
- const minutes = (v.endAt.getTime() - v.startAt.getTime()) / 60000;
37929
- return minutes >= 5 && minutes <= TIME_BLOCK_MAX_MINUTES;
37930
- }, { message: "endAt must be 5 minutes to a day after startAt", path: ["endAt"] });
37931
- var planUnplaceInputSchema = object({ taskId: ulidSchema }).strict();
37932
- var planListQuerySchema = object({
37933
- kind: planKindSchema.optional(),
37934
- status: planStatusSchema.optional(),
37935
- startsOn: civilDaySchema.optional(),
37936
- taskId: ulidSchema.optional()
37937
- });
37938
- var planTaskSchema = object({
37939
- id: ulidSchema,
37940
- title: string2(),
37941
- status: taskStatusSchema,
37942
- scheduledAt: exports_coerce.date().nullable(),
37943
- allDay: boolean2(),
37944
- dueAt: exports_coerce.date().nullable(),
37945
- durationMinutes: number2().int().nullable(),
37946
- areaId: string2().nullable(),
37947
- projectId: string2().nullable(),
37948
- moveCount: number2().int().nonnegative().default(0)
37949
- });
37950
- var planItemViewSchema = object({
37951
- id: string2(),
37952
- taskId: ulidSchema,
37953
- action: planItemActionSchema,
37954
- scheduledAt: exports_coerce.date().nullable(),
37955
- allDay: boolean2(),
37956
- durationMinutes: number2().int().nullable(),
37957
- reason: string2().nullable(),
37958
- state: planItemStateSchema,
37959
- decidedAt: exports_coerce.date().nullable(),
37960
- task: planTaskSchema
37961
- });
37962
- var planBlockViewSchema = object({
37963
- id: string2(),
37964
- templateId: string2().nullable(),
37965
- lifeKind: lifeBlockKindSchema,
37966
- title: string2(),
37967
- startAt: exports_coerce.date(),
37968
- endAt: exports_coerce.date(),
37969
- routineId: string2().nullable(),
37970
- reason: string2().nullable(),
37971
- state: planItemStateSchema,
37972
- decidedAt: exports_coerce.date().nullable(),
37973
- blockId: string2().nullable()
37974
- });
37975
- var planViewSchema = object({
37976
- id: string2(),
37977
- kind: planKindSchema,
37978
- startsOn: civilDaySchema,
37979
- userId: string2(),
37980
- agentId: string2().nullable(),
37981
- note: string2().nullable(),
37982
- template: planTemplateSchema.nullable().default(null),
37983
- status: planStatusSchema,
37984
- createdAt: exports_coerce.date(),
37985
- updatedAt: exports_coerce.date(),
37986
- items: array(planItemViewSchema),
37987
- blocks: array(planBlockViewSchema).default([])
37988
- });
37989
- var planMergeSchema = object({
37990
- blockIndex: number2().int().min(0),
37991
- title: string2(),
37992
- outcome: _enum2(PLAN_MERGE_OUTCOMES),
37993
- match: object({ kind: _enum2(PLAN_MATCH_KINDS), id: string2(), title: string2() }),
37994
- hint: string2()
37995
- });
37996
- var PLAN_RULE_OUTCOMES = [
37997
- "anchored",
37998
- "added",
37999
- "kept_free",
38000
- "over_limit",
38001
- "below_min",
38002
- "not_theirs"
38003
- ];
38004
- var planShapingSchema = object({
38005
- ruleId: string2(),
38006
- sentence: string2(),
38007
- outcome: _enum2(PLAN_RULE_OUTCOMES),
38008
- title: string2(),
38009
- hint: string2()
38010
- });
38011
- var planCreateResultSchema = planViewSchema.extend({
38012
- merged: array(planMergeSchema).default([]),
38013
- shaped: array(planShapingSchema).default([])
38014
- });
38015
-
38016
38168
  // ../../packages/schema/src/operations/plans.ts
38017
38169
  function decision(path, verb, what) {
38018
38170
  return {
@@ -51502,6 +51654,74 @@ var HEALTH_COMMAND_SPECS = [
51502
51654
  scopes: WRITE6,
51503
51655
  returns: "HealthLabAnalyteView",
51504
51656
  notes: "Body: SetHealthLabAnalyteInput. An absent key keeps a field; null clears it."
51657
+ },
51658
+ {
51659
+ path: ["health", "cycle"],
51660
+ summary: "Show your cycle: the settings, the days logged and what they say.",
51661
+ args: [],
51662
+ flags: [],
51663
+ scopes: READ6,
51664
+ returns: "HealthCycleView",
51665
+ notes: `${routeOf(healthOperations.getCycle)}. Off until you turn it on in do: then \`days\` is empty and \`summary\` null. \`summary.nextOn\` and \`fertile\` are estimates from the usual length.`
51666
+ },
51667
+ {
51668
+ path: ["health", "cycle", "log"],
51669
+ summary: "Log a cycle day (do health cycle log 2026-10-22 --flow light --symptoms cramps,tired).",
51670
+ args: [{ name: "day", required: true, description: "The day, YYYY-MM-DD: today or before." }],
51671
+ flags: [
51672
+ DATA_FLAG,
51673
+ {
51674
+ name: "flow",
51675
+ type: "string",
51676
+ description: 'none|light|medium|heavy; "" clears it.'
51677
+ },
51678
+ {
51679
+ name: "symptoms",
51680
+ type: "string",
51681
+ description: 'cramps,headache,tired,low_mood,skin, comma-separated; "" clears them.'
51682
+ }
51683
+ ],
51684
+ scopes: WRITE6,
51685
+ returns: "HealthCycleView",
51686
+ notes: `${routeOf(healthOperations.logCycleDay)}. Body: LogHealthCycleDayInput. It replaces what the day had; a part not given stays.`
51687
+ },
51688
+ {
51689
+ path: ["health", "cycle", "unlog"],
51690
+ summary: "Take back a cycle day: nothing logged on it.",
51691
+ args: [{ name: "day", required: true, description: "The day, YYYY-MM-DD." }],
51692
+ flags: [],
51693
+ scopes: WRITE6,
51694
+ returns: "HealthCycleView"
51695
+ },
51696
+ {
51697
+ path: ["health", "cycle", "set"],
51698
+ summary: "Set your usual cycle length or share the next date (--length 30, --share on).",
51699
+ args: [],
51700
+ flags: [
51701
+ DATA_FLAG,
51702
+ {
51703
+ name: "length",
51704
+ type: "string",
51705
+ description: "Your usual length, 15\u201360 days; auto works it out from your cycles."
51706
+ },
51707
+ {
51708
+ name: "share",
51709
+ type: "string",
51710
+ description: "on|off: share only the next period's date with the Space."
51711
+ }
51712
+ ],
51713
+ scopes: WRITE6,
51714
+ returns: "HealthCycleView",
51715
+ notes: `${routeOf(healthOperations.updateCycleSettings)}. Body: UpdateHealthCycleSettingsInput. Turning Cycle on or off is yours alone, in do: a key that tries gets 403.`
51716
+ },
51717
+ {
51718
+ path: ["health", "cycle", "shared"],
51719
+ summary: "List the next period dates the Space's other members share with it.",
51720
+ args: [],
51721
+ flags: [],
51722
+ scopes: READ6,
51723
+ returns: "HealthCycleShared",
51724
+ notes: "Only the date each one shares (an estimate); nothing they log."
51505
51725
  }
51506
51726
  ];
51507
51727
  var HEALTH_RECORD_OPERATIONS = {
@@ -51722,6 +51942,51 @@ var commands23 = pairCommands(HEALTH_COMMAND_SPECS, {
51722
51942
  data: await requireClient().post(pathOf(googleHealthOperations.sync), {}),
51723
51943
  render: renderRecord
51724
51944
  }),
51945
+ "health cycle": async ({ requireClient }) => ({
51946
+ data: await requireClient().get(pathOf(healthOperations.getCycle)),
51947
+ render: renderRecord
51948
+ }),
51949
+ "health cycle log": async ({ args, flags, requireClient }) => {
51950
+ const day = requireArg(args, 0, "day");
51951
+ const fields = flagString(flags, "data") ? readDataFlag(flags) : {};
51952
+ const flow = flagString(flags, "flow");
51953
+ if (flow !== undefined)
51954
+ fields.flow = flow.trim() ? flow.trim() : null;
51955
+ const symptoms = flagString(flags, "symptoms");
51956
+ if (symptoms !== undefined) {
51957
+ fields.symptoms = symptoms.split(",").map((s) => s.trim()).filter(Boolean);
51958
+ }
51959
+ const body = parseInput(logHealthCycleDayInputSchema, fields);
51960
+ return {
51961
+ data: await requireClient().put(pathOf(healthOperations.logCycleDay, { day }), body),
51962
+ render: renderRecord
51963
+ };
51964
+ },
51965
+ "health cycle unlog": async ({ args, requireClient }) => ({
51966
+ data: await requireClient().delete(pathOf(healthOperations.deleteCycleDay, { day: requireArg(args, 0, "day") })),
51967
+ render: renderRecord
51968
+ }),
51969
+ "health cycle set": async ({ flags, requireClient }) => {
51970
+ const fields = flagString(flags, "data") ? readDataFlag(flags) : {};
51971
+ const length = flagString(flags, "length");
51972
+ if (length !== undefined)
51973
+ fields.usualLength = length === "auto" ? null : Number(length);
51974
+ const share = flagString(flags, "share");
51975
+ if (share !== undefined) {
51976
+ if (share !== "on" && share !== "off")
51977
+ throw usageError("--share is on or off");
51978
+ fields.shareNext = share === "on";
51979
+ }
51980
+ const body = parseInput(updateHealthCycleSettingsInputSchema, fields);
51981
+ return {
51982
+ data: await requireClient().patch(pathOf(healthOperations.updateCycleSettings), body),
51983
+ render: renderRecord
51984
+ };
51985
+ },
51986
+ "health cycle shared": async ({ requireClient }) => ({
51987
+ data: await requireClient().getPage(pathOf(healthOperations.listSharedCycleDates)),
51988
+ render: renderList
51989
+ }),
51725
51990
  "health consent": async ({ requireClient }) => ({
51726
51991
  data: await requireClient().get(pathOf(healthOperations.getConsent)),
51727
51992
  render: renderRecord
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robbeverhelst/do",
3
- "version": "1.388.0",
3
+ "version": "1.390.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",