@robbeverhelst/do 1.342.0 → 1.344.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 +1794 -88
  2. package/package.json +1 -1
package/dist/do.js CHANGED
@@ -971,7 +971,9 @@ var GLOBAL_BOOLEAN_FLAGS = [
971
971
  "allow-destructive",
972
972
  "dry-run",
973
973
  "draft",
974
- "no-year"
974
+ "no-year",
975
+ "after-done",
976
+ "no-repeat"
975
977
  ];
976
978
  var SHORT_ALIASES = {
977
979
  h: "help",
@@ -14418,7 +14420,7 @@ var configError = (message) => new CliError("config", message, EXIT.CONFIG);
14418
14420
 
14419
14421
  // src/meta.ts
14420
14422
  var CLI_NAME = "do";
14421
- var VERSION = "1.342.0";
14423
+ var VERSION = "1.344.0";
14422
14424
  var API_PREFIX = "/api/v1";
14423
14425
  var DEFAULT_API_URL = "http://localhost:3001";
14424
14426
 
@@ -17982,9 +17984,13 @@ var tasks = pgTable("tasks", {
17982
17984
  readyAt: timestamp("ready_at", { withTimezone: true }),
17983
17985
  readyNote: text("ready_note"),
17984
17986
  moveCount: integer2("move_count").notNull().default(0),
17985
- movedFrom: timestamp("moved_from", { withTimezone: true })
17987
+ movedFrom: timestamp("moved_from", { withTimezone: true }),
17988
+ seriesId: text("series_id"),
17989
+ seriesLabel: text("series_label"),
17990
+ seriesSlot: date5("series_slot", { mode: "string" })
17986
17991
  }, (t) => [
17987
17992
  index("tasks_tenant_project_idx").on(t.tenantId, t.projectId),
17993
+ uniqueIndex("tasks_series_slot_uniq").on(t.seriesId, t.seriesSlot).where(sql`${t.seriesId} IS NOT NULL`),
17988
17994
  unique("tasks_tenant_number_uniq").on(t.tenantId, t.number),
17989
17995
  index("tasks_tenant_place_idx").on(t.tenantId, t.placeId),
17990
17996
  index("tasks_milestone_idx").on(t.milestoneId),
@@ -18023,11 +18029,13 @@ var habits = pgTable("habits", {
18023
18029
  retiredAt: timestamp("retired_at", { withTimezone: true }),
18024
18030
  eventSourceId: text("event_source_id"),
18025
18031
  eventMatch: text("event_match"),
18026
- eventOffset: text("event_offset")
18032
+ eventOffset: text("event_offset"),
18033
+ durationMinutes: integer2("duration_minutes")
18027
18034
  }, (t) => [
18028
18035
  index("habits_tenant_idx").on(t.tenantId),
18029
18036
  index("habits_tenant_area_idx").on(t.tenantId, t.areaId),
18030
- check2("habits_time_of_day_chk", sql`${t.timeOfDay} IS NULL OR ${t.timeOfDay} IN ('morning', 'midday', 'evening') OR ${t.timeOfDay} ~ '^([01][0-9]|2[0-3]):[0-5][0-9]$'`)
18037
+ check2("habits_time_of_day_chk", sql`${t.timeOfDay} IS NULL OR ${t.timeOfDay} IN ('morning', 'midday', 'evening') OR ${t.timeOfDay} ~ '^([01][0-9]|2[0-3]):[0-5][0-9]$'`),
18038
+ check2("habits_duration_minutes_chk", sql`${t.durationMinutes} IS NULL OR ${t.durationMinutes} BETWEEN 1 AND 600`)
18031
18039
  ]);
18032
18040
  var habitEntries = pgTable("habit_entries", {
18033
18041
  id: text("id").primaryKey(),
@@ -18081,7 +18089,7 @@ var occurrences = pgTable("occurrences", {
18081
18089
 
18082
18090
  // ../../packages/schema/src/db/core.ts
18083
18091
  var routineKind = pgEnum("routine_kind", ["sequence", "time_blocked", "checklist"]);
18084
- var routineStepRef = pgEnum("routine_step_ref", ["free", "habit", "task"]);
18092
+ var routineStepRef = pgEnum("routine_step_ref", ["free", "habit", "task", "routine"]);
18085
18093
  var projectStatus = pgEnum("project_status", [
18086
18094
  "planning",
18087
18095
  "active",
@@ -18091,6 +18099,12 @@ var projectStatus = pgEnum("project_status", [
18091
18099
  ]);
18092
18100
  var settingsTheme = pgEnum("settings_theme", ["system", "light", "dark"]);
18093
18101
  var settingsDensity = pgEnum("settings_density", ["comfortable", "compact"]);
18102
+ var settingsGlass = pgEnum("settings_glass", ["liquid", "frosted", "solid"]);
18103
+ var settingsThemePack = pgEnum("settings_theme_pack", ["do", "skyrim", "pokemon"]);
18104
+ var settingsThemePackScope = pgEnum("settings_theme_pack_scope", [
18105
+ "section",
18106
+ "everywhere"
18107
+ ]);
18094
18108
  var settingsAccent = pgEnum("settings_accent", ["teal", "purple", "amber", "rose"]);
18095
18109
  var routineRunStatus = pgEnum("routine_run_status", [
18096
18110
  "in_progress",
@@ -18124,6 +18138,8 @@ var routineSteps = pgTable("routine_steps", {
18124
18138
  refKind: routineStepRef("ref_kind").notNull().default("free"),
18125
18139
  habitId: text("habit_id").references(() => habits.id),
18126
18140
  taskId: text("task_id").references(() => tasks.id),
18141
+ childRoutineId: text("child_routine_id").references(() => routines.id),
18142
+ optional: boolean4("optional").notNull().default(false),
18127
18143
  sortOrder: integer2("sort_order").notNull().default(0),
18128
18144
  durationMinutes: integer2("duration_minutes"),
18129
18145
  notes: text("notes"),
@@ -18131,11 +18147,14 @@ var routineSteps = pgTable("routine_steps", {
18131
18147
  }, (t) => [
18132
18148
  index("routine_steps_routine_order_idx").on(t.routineId, t.sortOrder),
18133
18149
  index("routine_steps_habit_idx").on(t.habitId),
18150
+ index("routine_steps_child_routine_idx").on(t.childRoutineId),
18134
18151
  check2("routine_steps_ref_kind_chk", sql`(
18135
- (${t.refKind} = 'free' AND ${t.habitId} IS NULL AND ${t.taskId} IS NULL) OR
18136
- (${t.refKind} = 'habit' AND ${t.habitId} IS NOT NULL AND ${t.taskId} IS NULL) OR
18137
- (${t.refKind} = 'task' AND ${t.taskId} IS NOT NULL AND ${t.habitId} IS NULL)
18138
- )`)
18152
+ (${t.refKind} = 'free' AND ${t.habitId} IS NULL AND ${t.taskId} IS NULL AND ${t.childRoutineId} IS NULL) OR
18153
+ (${t.refKind} = 'habit' AND ${t.habitId} IS NOT NULL AND ${t.taskId} IS NULL AND ${t.childRoutineId} IS NULL) OR
18154
+ (${t.refKind} = 'task' AND ${t.taskId} IS NOT NULL AND ${t.habitId} IS NULL AND ${t.childRoutineId} IS NULL) OR
18155
+ (${t.refKind} = 'routine' AND ${t.childRoutineId} IS NOT NULL AND ${t.habitId} IS NULL AND ${t.taskId} IS NULL)
18156
+ )`),
18157
+ check2("routine_steps_not_self_chk", sql`${t.childRoutineId} IS NULL OR ${t.childRoutineId} <> ${t.routineId}`)
18139
18158
  ]);
18140
18159
  var routineRuns = pgTable("routine_runs", {
18141
18160
  id: text("id").primaryKey(),
@@ -18155,6 +18174,9 @@ var routineRunSteps = pgTable("routine_run_steps", {
18155
18174
  refKind: routineStepRef("ref_kind").notNull().default("free"),
18156
18175
  habitId: text("habit_id").references(() => habits.id),
18157
18176
  taskId: text("task_id").references(() => tasks.id),
18177
+ childRoutineId: text("child_routine_id").references(() => routines.id),
18178
+ groupStepId: text("group_step_id").references(() => routineRunSteps.id),
18179
+ optional: boolean4("optional").notNull().default(false),
18158
18180
  reason: text("reason"),
18159
18181
  habitEntryId: text("habit_entry_id").references(() => habitEntries.id),
18160
18182
  sortOrder: integer2("sort_order").notNull().default(0),
@@ -18165,7 +18187,8 @@ var routineRunSteps = pgTable("routine_run_steps", {
18165
18187
  completedAt: timestamp("completed_at", { withTimezone: true })
18166
18188
  }, (t) => [
18167
18189
  index("routine_run_steps_run_order_idx").on(t.runId, t.sortOrder),
18168
- check2("routine_run_steps_ref_kind_chk", sql`(${t.refKind} <> 'habit' OR ${t.habitId} IS NOT NULL)`)
18190
+ check2("routine_run_steps_ref_kind_chk", sql`(${t.refKind} <> 'habit' OR ${t.habitId} IS NOT NULL)`),
18191
+ check2("routine_run_steps_routine_chk", sql`(${t.refKind} <> 'routine' OR ${t.childRoutineId} IS NOT NULL)`)
18169
18192
  ]);
18170
18193
  var projects = pgTable("projects", {
18171
18194
  ...leafColumns(),
@@ -18220,6 +18243,14 @@ var userSettings = pgTable("user_settings", {
18220
18243
  calendarZoom: integer2("calendar_zoom"),
18221
18244
  lifeBlockSharing: jsonb("life_block_sharing").$type(),
18222
18245
  recentSearches: jsonb("recent_searches").$type().notNull().default([]),
18246
+ glass: settingsGlass("glass").notNull().default("liquid"),
18247
+ followSky: boolean4("follow_sky").notNull().default(true),
18248
+ nightUntilWake: boolean4("night_until_wake").notNull().default(false),
18249
+ largerText: boolean4("larger_text").notNull().default(false),
18250
+ reduceMotion: boolean4("reduce_motion").notNull().default(false),
18251
+ showStreakNumbers: boolean4("show_streak_numbers").notNull().default(true),
18252
+ themePack: settingsThemePack("theme_pack").notNull().default("do"),
18253
+ themePackScope: settingsThemePackScope("theme_pack_scope").notNull().default("section"),
18223
18254
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
18224
18255
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
18225
18256
  }, (t) => [unique("user_settings_tenant_user_uniq").on(t.tenantId, t.userId)]);
@@ -18998,14 +19029,24 @@ var recipeIngredients = pgTable("recipe_ingredients", {
18998
19029
  index("recipe_ingredients_tenant_idx").on(t.tenantId),
18999
19030
  index("recipe_ingredients_product_idx").on(t.productId)
19000
19031
  ]);
19032
+ var MEAL_PLAN_STATES = ["planned", "cooked", "out", "unsure", "draft"];
19001
19033
  var mealPlans = pgTable("meal_plans", {
19002
19034
  ...leafColumns(),
19003
19035
  recipeId: text("recipe_id").references(() => recipes.id),
19004
19036
  plannedFor: timestamp("planned_for", { withTimezone: true }).notNull(),
19005
19037
  mealType: mealType("meal_type").notNull().default("dinner"),
19006
19038
  servings: integer2("servings"),
19007
- notes: text("notes")
19008
- }, (t) => [index("meal_plans_tenant_planned_idx").on(t.tenantId, t.plannedFor)]);
19039
+ notes: text("notes"),
19040
+ state: text("state", { enum: MEAL_PLAN_STATES }).notNull().default("planned"),
19041
+ cookUserId: text("cook_user_id").references(() => users.id, { onDelete: "set null" }),
19042
+ company: text("company"),
19043
+ leftoverPortions: integer2("leftover_portions"),
19044
+ leftoverEatBy: date5("leftover_eat_by", { mode: "string" }),
19045
+ leftoverFrozen: boolean4("leftover_frozen").notNull().default(false)
19046
+ }, (t) => [
19047
+ index("meal_plans_tenant_planned_idx").on(t.tenantId, t.plannedFor),
19048
+ check2("meal_plans_state_check", sql`${t.state} IN ('planned', 'cooked', 'out', 'unsure', 'draft')`)
19049
+ ]);
19009
19050
 
19010
19051
  // ../../packages/schema/src/db/reflections.ts
19011
19052
  var reflectionKind = pgEnum("reflection_kind", ["daily", "weekly"]);
@@ -21355,7 +21396,8 @@ var habitSchema = createSelectSchema(habits, {
21355
21396
  retiredAt: exports_coerce.date().nullable().default(null),
21356
21397
  eventSourceId: string2().nullable().default(null),
21357
21398
  eventMatch: string2().nullable().default(null),
21358
- eventOffset: string2().nullable().default(null)
21399
+ eventOffset: string2().nullable().default(null),
21400
+ durationMinutes: number2().int().nullable().default(null)
21359
21401
  });
21360
21402
  var habitEntrySchema = createSelectSchema(habitEntries, {
21361
21403
  occurredAt: exports_coerce.date(),
@@ -21372,6 +21414,8 @@ var habitTimeOfDaySchema = union([
21372
21414
  _enum2(HABIT_PARTS_OF_DAY),
21373
21415
  string2().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "Use a 24-hour time, like 07:30")
21374
21416
  ]);
21417
+ var HABIT_DURATION_MAX_MINUTES = 600;
21418
+ var habitDurationMinutesSchema = number2().int().min(1).max(HABIT_DURATION_MAX_MINUTES);
21375
21419
  var habitAliasSchema = string2().trim().min(1).max(60);
21376
21420
  var habitAliasesSchema = array(habitAliasSchema).max(12).transform((aliases) => aliases.filter((a, i) => aliases.findIndex((b) => b.toLowerCase() === a.toLowerCase()) === i));
21377
21421
  var habitScheduleSchema = discriminatedUnion("kind", [
@@ -21410,7 +21454,8 @@ var createHabitInputSchema = createInsertSchema(habits, {
21410
21454
  areaId: ulidSchema.nullish(),
21411
21455
  aliases: habitAliasesSchema.optional(),
21412
21456
  timeOfDay: habitTimeOfDaySchema.nullish(),
21413
- remind: boolean2().optional()
21457
+ remind: boolean2().optional(),
21458
+ durationMinutes: habitDurationMinutesSchema.nullish()
21414
21459
  }).omit({ tenantId: true, recurrenceRuleId: true, retiredAt: true, ...eventColumns }).extend({
21415
21460
  title: string2().min(1),
21416
21461
  schedule: habitScheduleSchema.optional(),
@@ -21426,7 +21471,8 @@ var updateHabitInputSchema = createUpdateSchema(habits, {
21426
21471
  areaId: ulidSchema.nullish(),
21427
21472
  aliases: habitAliasesSchema.optional(),
21428
21473
  timeOfDay: habitTimeOfDaySchema.nullish(),
21429
- remind: boolean2().optional()
21474
+ remind: boolean2().optional(),
21475
+ durationMinutes: habitDurationMinutesSchema.nullish()
21430
21476
  }).omit({
21431
21477
  id: true,
21432
21478
  tenantId: true,
@@ -21477,7 +21523,7 @@ var habitLastCheckInSchema = object({
21477
21523
  note: string2().nullish()
21478
21524
  });
21479
21525
  var habitPauseSchema = object({
21480
- until: exports_iso.date(),
21526
+ until: exports_iso.date().nullable(),
21481
21527
  by: _enum2(["habit", "away"])
21482
21528
  });
21483
21529
  var habitTodaySchema = object({
@@ -21493,6 +21539,7 @@ var habitSummarySchema = habitSchema.extend({
21493
21539
  inRoutines: array(habitRoutineRefSchema).default([]),
21494
21540
  lastCheckIn: habitLastCheckInSchema.nullable().default(null),
21495
21541
  pause: habitPauseSchema.nullable().default(null),
21542
+ paused: boolean2().default(false),
21496
21543
  today: habitTodaySchema.default({ due: true, done: false }),
21497
21544
  schedule: habitScheduleSchema.default({ kind: "daily" }),
21498
21545
  nextDue: array(exports_iso.date()).default([])
@@ -21520,6 +21567,20 @@ var habitYearSchema = object({
21520
21567
  marks: array(habitDayMarkSchema)
21521
21568
  }))
21522
21569
  });
21570
+ var HABIT_DUE_MAX_DAYS = 62;
21571
+ var habitDueQuerySchema = object({ from: exports_iso.date(), to: exports_iso.date() }).strict().refine((q) => q.from <= q.to, { message: "`to` is before `from`", path: ["to"] }).refine((q) => (Date.parse(q.to) - Date.parse(q.from)) / 86400000 + 1 <= HABIT_DUE_MAX_DAYS, {
21572
+ message: `at most ${HABIT_DUE_MAX_DAYS} days`,
21573
+ path: ["to"]
21574
+ });
21575
+ var habitDueDateSchema = object({
21576
+ habitId: ulidSchema,
21577
+ title: string2(),
21578
+ date: exports_iso.date(),
21579
+ time: string2().regex(/^\d{2}:\d{2}$/).nullable(),
21580
+ scheduleLabel: string2().min(1),
21581
+ daily: boolean2(),
21582
+ streakLabel: string2().min(1)
21583
+ });
21523
21584
  var habitEntriesQuerySchema = object({
21524
21585
  before: exports_coerce.date().optional(),
21525
21586
  limit: exports_coerce.number().int().min(1).max(200).default(50)
@@ -21550,7 +21611,7 @@ var habitSkipInputSchema = object({
21550
21611
  date: exports_iso.date().optional(),
21551
21612
  note: habitEntryNoteSchema.nullish()
21552
21613
  }).strict();
21553
- var habitPauseInputSchema = object({ until: exports_iso.date() }).strict();
21614
+ var habitPauseInputSchema = object({ until: exports_iso.date().nullish() }).strict();
21554
21615
  var awayInputSchema = object({ until: exports_iso.date().nullable() }).strict();
21555
21616
  var awayStateSchema = object({
21556
21617
  until: exports_iso.date().nullable()
@@ -21582,12 +21643,78 @@ var habitTodayRoutineSchema = object({
21582
21643
  stepsDone: number2().int().nonnegative(),
21583
21644
  stepsTotal: number2().int().nonnegative()
21584
21645
  });
21585
- var habitsTodayResponseSchema = object({
21646
+ var habitsTodayBaseSchema = object({
21586
21647
  date: string2().regex(/^\d{4}-\d{2}-\d{2}$/),
21587
21648
  timezone: string2().min(1),
21588
21649
  groups: array(object({ part: habitDayPartSchema, habits: array(habitTodayItemSchema) })),
21589
21650
  routines: array(habitTodayRoutineSchema)
21590
21651
  });
21652
+ var habitMomentSchema = _enum2(["morning", "day", "evening"]);
21653
+ var habitMomentStateSchema = _enum2(["done", "going", "tonight", "open"]);
21654
+ var habitRowKindSchema = _enum2(["tick", "amount", "timer", "days-without"]);
21655
+ var HABIT_STRIP_DAYS = 14;
21656
+ var habitStripDaySchema = _enum2([
21657
+ "done",
21658
+ "missed",
21659
+ "not_due",
21660
+ "today_open",
21661
+ "today_done"
21662
+ ]);
21663
+ var habitStreakUnitSchema = _enum2(["days", "in_a_row", "since"]);
21664
+ var habitSectionStreakSchema = object({
21665
+ current: number2().int().nonnegative(),
21666
+ best: number2().int().nonnegative(),
21667
+ unit: habitStreakUnitSchema,
21668
+ since: exports_iso.date().nullable().default(null)
21669
+ });
21670
+ var habitSectionRowSchema = object({
21671
+ habitId: ulidSchema,
21672
+ title: string2(),
21673
+ kind: habitRowKindSchema,
21674
+ schedule: habitScheduleSchema,
21675
+ scheduleWords: string2(),
21676
+ sourceWords: string2().nullable(),
21677
+ done: boolean2(),
21678
+ amount: object({ value: number2(), target: number2().nullable(), unit: string2().nullable() }).nullable(),
21679
+ strip: array(habitStripDaySchema).length(HABIT_STRIP_DAYS),
21680
+ streak: habitSectionStreakSchema,
21681
+ routineStep: object({
21682
+ routineId: ulidSchema,
21683
+ title: string2(),
21684
+ step: number2().int().positive(),
21685
+ of: number2().int().positive()
21686
+ }).nullable()
21687
+ });
21688
+ var habitSectionMomentSchema = object({
21689
+ moment: habitMomentSchema,
21690
+ label: string2(),
21691
+ routines: array(object({ id: ulidSchema, title: string2(), time: string2().nullable() })),
21692
+ state: habitMomentStateSchema,
21693
+ going: number2().int().nonnegative().default(0),
21694
+ rows: array(habitSectionRowSchema)
21695
+ });
21696
+ var habitNotDueSchema = object({
21697
+ id: ulidSchema,
21698
+ title: string2(),
21699
+ nextDue: exports_iso.date().nullable()
21700
+ });
21701
+ var habitWeekDaySchema = object({
21702
+ day: exports_iso.date(),
21703
+ done: number2().int().nonnegative().nullable(),
21704
+ due: number2().int().nonnegative()
21705
+ });
21706
+ var habitSectionTotalsSchema = object({
21707
+ dueToday: number2().int().nonnegative(),
21708
+ doneToday: number2().int().nonnegative(),
21709
+ weekDone: number2().int().nonnegative(),
21710
+ weekDue: number2().int().nonnegative()
21711
+ });
21712
+ var habitsTodayResponseSchema = habitsTodayBaseSchema.extend({
21713
+ moments: array(habitSectionMomentSchema).default([]),
21714
+ notDue: array(habitNotDueSchema).default([]),
21715
+ week: array(habitWeekDaySchema).default([]),
21716
+ totals: habitSectionTotalsSchema.default({ dueToday: 0, doneToday: 0, weekDone: 0, weekDue: 0 })
21717
+ });
21591
21718
 
21592
21719
  // ../../packages/schema/src/contracts/metrics.ts
21593
21720
  var METRICS = {
@@ -21828,6 +21955,78 @@ var taskBlockerSchema = object({
21828
21955
  dueAt: exports_coerce.date().nullable()
21829
21956
  });
21830
21957
 
21958
+ // ../../packages/schema/src/db/task-series.ts
21959
+ var taskSeries = pgTable("task_series", {
21960
+ id: text("id").primaryKey(),
21961
+ tenantId: text("tenant_id").notNull().references(() => tenants.id),
21962
+ title: text("title").notNull(),
21963
+ recurrenceRuleId: text("recurrence_rule_id").notNull().references(() => recurrenceRules.id),
21964
+ startsOn: date5("starts_on", { mode: "string" }).notNull(),
21965
+ occurrenceLabel: text("occurrence_label").notNull().default("date"),
21966
+ leadDays: integer2("lead_days").notNull().default(14),
21967
+ areaId: text("area_id").references(() => areas.id, { onDelete: "set null" }),
21968
+ projectId: text("project_id").references(() => projects.id, { onDelete: "set null" }),
21969
+ tags: text("tags").array().notNull().default([]),
21970
+ priority: workPriority("priority").notNull().default("none"),
21971
+ durationMinutes: integer2("duration_minutes"),
21972
+ ownerUserId: text("owner_user_id").references(() => users.id, { onDelete: "set null" }),
21973
+ ownerAgentId: text("owner_agent_id").references(() => agents.id, { onDelete: "set null" }),
21974
+ subtaskTitles: text("subtask_titles").array().notNull().default([]),
21975
+ active: boolean4("active").notNull().default(true),
21976
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
21977
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
21978
+ }, (t) => [
21979
+ index("task_series_tenant_idx").on(t.tenantId, t.active)
21980
+ ]);
21981
+
21982
+ // ../../packages/schema/src/contracts/task-series.ts
21983
+ var taskSeriesRuleSchema = object({
21984
+ freq: _enum2(["weekly", "monthly", "yearly"]),
21985
+ interval: number2().int().min(1).max(52).default(1)
21986
+ }).strict();
21987
+ var taskSeriesLabelSchema = _enum2(["quarter", "month", "date"]);
21988
+ var leadDaysSchema2 = number2().int().min(0).max(365);
21989
+ var taskSeriesRowSchema = createSelectSchema(taskSeries, {
21990
+ occurrenceLabel: taskSeriesLabelSchema,
21991
+ startsOn: exports_iso.date(),
21992
+ createdAt: exports_coerce.date(),
21993
+ updatedAt: exports_coerce.date()
21994
+ });
21995
+ var makeTaskSeriesInputSchema = object({
21996
+ rule: taskSeriesRuleSchema,
21997
+ occurrenceLabel: taskSeriesLabelSchema.default("date"),
21998
+ leadDays: leadDaysSchema2.default(14)
21999
+ }).strict();
22000
+ var updateTaskSeriesInputSchema = object({
22001
+ title: string2().trim().min(1).max(500).optional(),
22002
+ rule: taskSeriesRuleSchema.optional(),
22003
+ occurrenceLabel: taskSeriesLabelSchema.optional(),
22004
+ leadDays: leadDaysSchema2.optional(),
22005
+ active: boolean2().optional()
22006
+ }).strict();
22007
+ var taskSeriesItemSchema = object({
22008
+ taskId: ulidSchema.nullable(),
22009
+ label: string2(),
22010
+ slot: exports_iso.date(),
22011
+ status: taskStatusSchema.nullable(),
22012
+ completedAt: exports_coerce.date().nullable()
22013
+ });
22014
+ var taskSeriesStripSchema = object({
22015
+ id: ulidSchema,
22016
+ title: string2(),
22017
+ rhythm: string2(),
22018
+ items: array(taskSeriesItemSchema),
22019
+ nextMadeOn: exports_iso.date().nullable()
22020
+ });
22021
+ var taskSeriesViewSchema = taskSeriesStripSchema.extend({
22022
+ rule: taskSeriesRuleSchema,
22023
+ occurrenceLabel: taskSeriesLabelSchema,
22024
+ leadDays: number2().int(),
22025
+ startsOn: exports_iso.date(),
22026
+ active: boolean2(),
22027
+ made: number2().int().nonnegative()
22028
+ });
22029
+
21831
22030
  // ../../packages/schema/src/contracts/task-source.ts
21832
22031
  var taskSourceSchema = object({
21833
22032
  kind: string2().trim().min(1).max(40),
@@ -22000,7 +22199,10 @@ var taskSchema = createSelectSchema(tasks, {
22000
22199
  movedFrom: exports_coerce.date().nullable().default(null),
22001
22200
  waitingFor: string2().nullable().default(null),
22002
22201
  nudgedAt: exports_coerce.date().nullable().default(null),
22003
- nudgeCount: number2().int().nonnegative().default(0)
22202
+ nudgeCount: number2().int().nonnegative().default(0),
22203
+ seriesId: string2().nullable().default(null),
22204
+ seriesLabel: string2().nullable().default(null),
22205
+ seriesSlot: exports_iso.date().nullable().default(null)
22004
22206
  });
22005
22207
  var PARTY_RE = /^(?:(me)|(member|agent|contact):([^/]+?))(?:\/(.+))?$/;
22006
22208
  function partySchema(allowDesk, allowContact = false) {
@@ -22055,6 +22257,55 @@ var taskFilingSuggestionSchema = object({
22055
22257
  var completedAtInputSchema = exports_coerce.date().refine((d) => d.getTime() <= Date.now() + 60000, "completedAt can't be in the future").nullish();
22056
22258
  var taskNextStepSchema = string2().trim().min(1).max(500);
22057
22259
  var taskDeliverableSchema = string2().trim().min(1).max(500);
22260
+ var taskRecurrenceAnchorSchema = _enum2(["fixed", "from_completion"]);
22261
+ function isTimeZone(zone) {
22262
+ try {
22263
+ new Intl.DateTimeFormat("en", { timeZone: zone });
22264
+ return true;
22265
+ } catch {
22266
+ return false;
22267
+ }
22268
+ }
22269
+ var taskRecurrenceInputSchema = object({
22270
+ freq: recurrenceFreqSchema,
22271
+ interval: number2().int().positive().default(1),
22272
+ byDay: array(byDaySchema).min(1).optional(),
22273
+ byMonthDay: number2().int().min(-1).max(31).refine((d) => d !== 0, "byMonthDay is 1\u201331, or -1 for the last day").optional(),
22274
+ anchor: taskRecurrenceAnchorSchema.default("fixed"),
22275
+ timezone: string2().refine(isTimeZone, "an IANA time zone, e.g. Europe/Brussels").optional()
22276
+ }).strict().superRefine((v, ctx) => {
22277
+ const issue = (path, message) => ctx.addIssue({ code: "custom", path, message });
22278
+ if (v.anchor === "from_completion") {
22279
+ if (v.freq === "yearly")
22280
+ issue(["freq"], "a from_completion rule is daily, weekly or monthly");
22281
+ if (v.byDay)
22282
+ issue(["byDay"], "a from_completion rule takes no byDay");
22283
+ if (v.byMonthDay !== undefined)
22284
+ issue(["byMonthDay"], "a from_completion rule takes no byMonthDay");
22285
+ return;
22286
+ }
22287
+ if (v.byDay && v.freq === "daily")
22288
+ issue(["byDay"], "byDay needs a weekly or monthly freq");
22289
+ const ordinal = v.byDay?.findIndex((code) => parseByDay(code)?.ordinal != null) ?? -1;
22290
+ if (ordinal >= 0 && v.freq !== "monthly" && v.freq !== "yearly") {
22291
+ issue(["byDay", ordinal], "an ordinal weekday (1SU, -1FR) needs a monthly or yearly freq");
22292
+ }
22293
+ if (v.byMonthDay !== undefined && v.freq !== "monthly") {
22294
+ issue(["byMonthDay"], "byMonthDay needs a monthly freq");
22295
+ }
22296
+ if (v.byMonthDay !== undefined && v.byDay) {
22297
+ issue(["byMonthDay"], "pass byDay or byMonthDay, not both");
22298
+ }
22299
+ });
22300
+ var taskRecurrenceSchema = object({
22301
+ ruleId: ulidSchema,
22302
+ freq: recurrenceFreqSchema,
22303
+ interval: number2().int().positive(),
22304
+ byDay: array(string2()).nullable(),
22305
+ byMonthDay: number2().int().nullable(),
22306
+ anchor: taskRecurrenceAnchorSchema,
22307
+ timezone: string2()
22308
+ });
22058
22309
  var taskViewSchema = taskSchema.omit({
22059
22310
  ownerUserId: true,
22060
22311
  ownerAgentId: true,
@@ -22089,7 +22340,9 @@ var taskViewSchema = taskSchema.omit({
22089
22340
  filingSuggestion: taskFilingSuggestionSchema.nullable().default(null),
22090
22341
  blockedBy: array(taskBlockerSchema).default([]),
22091
22342
  blocks: number2().int().nonnegative().default(0),
22092
- isBlocked: boolean2().default(false)
22343
+ isBlocked: boolean2().default(false),
22344
+ recurrence: taskRecurrenceSchema.nullable().default(null),
22345
+ series: taskSeriesStripSchema.nullable().default(null)
22093
22346
  });
22094
22347
  var taskProjectIdSchema = ulidSchema.nullish();
22095
22348
  var taskAreaIdSchema = ulidSchema.nullish();
@@ -22125,6 +22378,9 @@ var createTaskInputSchema = createInsertSchema(tasks, {
22125
22378
  completedByUserId: true,
22126
22379
  completedByAgentId: true,
22127
22380
  filingSuggestion: true,
22381
+ seriesId: true,
22382
+ seriesLabel: true,
22383
+ seriesSlot: true,
22128
22384
  proposedAt: true,
22129
22385
  proposedDesk: true,
22130
22386
  notNowUntil: true,
@@ -22143,7 +22399,8 @@ var createTaskInputSchema = createInsertSchema(tasks, {
22143
22399
  source: taskSourceSchema.nullish(),
22144
22400
  nextStep: taskNextStepSchema.nullish(),
22145
22401
  proposed: boolean2().optional(),
22146
- fromDesk: deskSchema.nullish()
22402
+ fromDesk: deskSchema.nullish(),
22403
+ recurrence: taskRecurrenceInputSchema.nullish()
22147
22404
  });
22148
22405
  var taskReadyNoteSchema = string2().trim().min(1).max(500);
22149
22406
  var markTaskReadyInputSchema = object({ note: taskReadyNoteSchema.nullish() }).strict();
@@ -22178,6 +22435,9 @@ var updateTaskInputSchema = createUpdateSchema(tasks, {
22178
22435
  completedByUserId: true,
22179
22436
  completedByAgentId: true,
22180
22437
  filingSuggestion: true,
22438
+ seriesId: true,
22439
+ seriesLabel: true,
22440
+ seriesSlot: true,
22181
22441
  proposedAt: true,
22182
22442
  proposedDesk: true,
22183
22443
  readyAt: true,
@@ -22196,7 +22456,8 @@ var updateTaskInputSchema = createUpdateSchema(tasks, {
22196
22456
  proposed: boolean2().optional(),
22197
22457
  notNowUntil: exports_coerce.date().nullish(),
22198
22458
  ready: boolean2().optional(),
22199
- readyNote: taskReadyNoteSchema.nullish()
22459
+ readyNote: taskReadyNoteSchema.nullish(),
22460
+ recurrence: taskRecurrenceInputSchema.nullish()
22200
22461
  }).strict();
22201
22462
  var taskListFilterSchema = _enum2(["all", "unsorted", "starred"]);
22202
22463
 
@@ -22306,7 +22567,9 @@ var attachTaskInputSchema = object({ taskId: ulidSchema });
22306
22567
  var routineDayTimesSchema = partialRecord(_enum2(["MO", "TU", "WE", "TH", "FR", "SA", "SU"]), string2().regex(/^([01]\d|2[0-3]):[0-5]\d$/));
22307
22568
  var routineSchema = createSelectSchema(routines);
22308
22569
  var routineStepSchema = createSelectSchema(routineSteps, {
22309
- createdAt: exports_coerce.date()
22570
+ createdAt: exports_coerce.date(),
22571
+ childRoutineId: string2().nullish(),
22572
+ optional: boolean2().optional()
22310
22573
  });
22311
22574
  var routineStepInputSchema = object({
22312
22575
  id: ulidSchema,
@@ -22314,12 +22577,36 @@ var routineStepInputSchema = object({
22314
22577
  refKind: routineStepRefSchema.default("free"),
22315
22578
  habitId: ulidSchema.nullish(),
22316
22579
  taskId: ulidSchema.nullish(),
22580
+ childRoutineId: ulidSchema.nullish(),
22317
22581
  newHabit: createHabitInputSchema.optional(),
22582
+ optional: boolean2().optional(),
22318
22583
  sortOrder: number2().int().min(0).optional(),
22319
22584
  durationMinutes: number2().int().positive().nullish(),
22320
22585
  notes: string2().nullish()
22321
22586
  }).superRefine((step, ctx) => {
22322
- if (step.refKind === "habit") {
22587
+ if (step.refKind !== "routine" && step.childRoutineId) {
22588
+ ctx.addIssue({
22589
+ code: ZodIssueCode.custom,
22590
+ message: "only a routine step sets childRoutineId",
22591
+ path: ["childRoutineId"]
22592
+ });
22593
+ }
22594
+ if (step.refKind === "routine") {
22595
+ if (!step.childRoutineId) {
22596
+ ctx.addIssue({
22597
+ code: ZodIssueCode.custom,
22598
+ message: "a routine step needs childRoutineId",
22599
+ path: ["childRoutineId"]
22600
+ });
22601
+ }
22602
+ if (step.habitId || step.taskId || step.newHabit) {
22603
+ ctx.addIssue({
22604
+ code: ZodIssueCode.custom,
22605
+ message: "a routine step cannot set a habit/task link",
22606
+ path: ["refKind"]
22607
+ });
22608
+ }
22609
+ } else if (step.refKind === "habit") {
22323
22610
  const linkId = step.newHabit?.id ?? step.habitId;
22324
22611
  if (!linkId) {
22325
22612
  ctx.addIssue({
@@ -22375,6 +22662,16 @@ var createRoutineInputSchema = createInsertSchema(routines, {
22375
22662
  parentId: ulidSchema.nullish(),
22376
22663
  steps: array(routineStepInputSchema).optional(),
22377
22664
  dayTimes: routineDayTimesSchema.optional()
22665
+ }).superRefine((input, ctx) => {
22666
+ for (const [i, step] of (input.steps ?? []).entries()) {
22667
+ if (step.childRoutineId && step.childRoutineId === input.id) {
22668
+ ctx.addIssue({
22669
+ code: ZodIssueCode.custom,
22670
+ message: "a routine cannot contain itself",
22671
+ path: ["steps", i, "childRoutineId"]
22672
+ });
22673
+ }
22674
+ }
22378
22675
  });
22379
22676
  var updateRoutineInputSchema = createUpdateSchema(routines, {
22380
22677
  areaId: ulidSchema.nullish()
@@ -22405,7 +22702,10 @@ var routineRunSchema = createSelectSchema(routineRuns, {
22405
22702
  var routineRunStepSchema = createSelectSchema(routineRunSteps, {
22406
22703
  completedAt: exports_coerce.date().nullable(),
22407
22704
  taskId: string2().nullable().default(null),
22408
- reason: string2().nullable().default(null)
22705
+ reason: string2().nullable().default(null),
22706
+ childRoutineId: string2().nullish(),
22707
+ groupStepId: string2().nullish(),
22708
+ optional: boolean2().optional()
22409
22709
  });
22410
22710
  var routineRunStepViewSchema = routineRunStepSchema.extend({
22411
22711
  habit: habitSummarySchema.nullable().default(null),
@@ -22495,14 +22795,16 @@ var addRoutineTaskStepInputSchema = object({
22495
22795
  var reorderRoutineStepsInputSchema = object({
22496
22796
  stepIds: array(ulidSchema).min(1).max(200).refine((ids) => new Set(ids).size === ids.length, "stepIds must not repeat")
22497
22797
  }).strict();
22498
- var routineTodayStepKindSchema = _enum2(["step", "habit", "task"]);
22798
+ var routineTodayStepKindSchema = _enum2(["step", "habit", "task", "routine"]);
22499
22799
  var routineTodayStepSchema = object({
22500
22800
  n: number2().int().positive(),
22501
22801
  title: string2(),
22502
22802
  kind: routineTodayStepKindSchema,
22503
22803
  meta: string2().nullable(),
22504
22804
  minutes: number2().int().nullable(),
22505
- done: boolean2()
22805
+ done: boolean2(),
22806
+ depth: number2().int().nonnegative().default(0),
22807
+ optional: boolean2().default(false)
22506
22808
  });
22507
22809
  var routineTodayStateSchema = _enum2(["done", "running", "upcoming", "resting"]);
22508
22810
  var routineTodaySchema = object({
@@ -23889,7 +24191,7 @@ var timeBlockViewSchema = timeBlockSchema.omit({
23889
24191
  var timeBlockStepSchema = object({
23890
24192
  id: string2(),
23891
24193
  title: string2(),
23892
- refKind: _enum2(["free", "habit", "task"]),
24194
+ refKind: routineStepRefSchema,
23893
24195
  status: _enum2(["done", "skipped", "open"]),
23894
24196
  at: exports_coerce.date().nullable(),
23895
24197
  habitId: string2().nullable().optional()
@@ -24109,6 +24411,7 @@ var calendarEventLinks = pgTable("calendar_event_links", {
24109
24411
  connectionId: text("connection_id").notNull().references(() => connections.id, { onDelete: "cascade" }),
24110
24412
  nodeId: text("node_id").notNull().references(() => nodes.id, { onDelete: "cascade" }),
24111
24413
  purpose: calendarLinkPurpose("purpose").notNull().default("block"),
24414
+ slot: text("slot").notNull().default(""),
24112
24415
  externalCalendarId: text("external_calendar_id").notNull(),
24113
24416
  externalEventId: text("external_event_id").notNull(),
24114
24417
  etag: text("etag"),
@@ -24117,7 +24420,7 @@ var calendarEventLinks = pgTable("calendar_event_links", {
24117
24420
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
24118
24421
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
24119
24422
  }, (t) => [
24120
- unique("calendar_event_links_connection_node_uniq").on(t.connectionId, t.nodeId, t.purpose),
24423
+ unique("calendar_event_links_connection_node_uniq").on(t.connectionId, t.nodeId, t.purpose, t.slot),
24121
24424
  unique("calendar_event_links_connection_event_uniq").on(t.connectionId, t.externalEventId),
24122
24425
  index("calendar_event_links_tenant_connection_idx").on(t.tenantId, t.connectionId),
24123
24426
  index("calendar_event_links_node_idx").on(t.nodeId)
@@ -24179,7 +24482,8 @@ var calendarConnectionCalendars = pgTable("calendar_connection_calendars", {
24179
24482
  var calendarEventLinkSchema = createSelectSchema(calendarEventLinks, {
24180
24483
  createdAt: exports_coerce.date(),
24181
24484
  updatedAt: exports_coerce.date(),
24182
- lastSyncedAt: exports_coerce.date()
24485
+ lastSyncedAt: exports_coerce.date(),
24486
+ slot: string2().default("")
24183
24487
  });
24184
24488
  var calendarSyncResultSchema = object({
24185
24489
  connectionId: string2(),
@@ -29751,11 +30055,32 @@ var habitOperations = defineOperations({
29751
30055
  scopes: ["habits:read"],
29752
30056
  tag: "Habits",
29753
30057
  summary: "What's left today",
29754
- description: "\"What's left?\" (#866): the habits due on the key's member's day (their saved time zone), already filtered by schedule, grouped by part of day (morning, midday, evening, anytime). Each says its value today, `done`/`open`/`skipped` (never overdue: habits don't carry over), the week for an N-per-week habit, and its last check-in (`by` names the Agent). Also today's routines with how many steps are done. Quit habits are left out.",
30058
+ description: "\"What's left?\" (#866): the habits due on the key's member's day (their saved time zone), already filtered by schedule, grouped by part of day (morning, midday, evening, anytime). Each says its value today, `done`/`open`/`skipped` (never overdue: habits don't carry over), the week for an N-per-week habit, and its last check-in (`by` names the Agent). Also today's routines with how many steps are done. Quit habits are left out of `groups`. The Habits section (V3-47) rides along: `moments` (morning, day, evening; habits inside their routine, each with its schedule in words, today's amount, a 14-day strip where a day it isn't due is `not_due`, never `missed`, and its streak in days, in a row or since), `notDue` (live, unpaused habits off today, by next due day), this Monday-to-Sunday `week` counting only due habits, and `totals`.",
29755
30059
  status: 200,
29756
30060
  response: { description: "Today's habits and routines." },
29757
30061
  operationId: "habits_today"
29758
30062
  },
30063
+ due: {
30064
+ method: "get",
30065
+ path: "/habits/due",
30066
+ scopes: ["habits:read"],
30067
+ tag: "Habits",
30068
+ summary: "Habit due dates in a range",
30069
+ description: "Plan's Month and agendas (V3-22): every growing habit's due days from `from` through `to` (the key's member's civil days, both included, at most 62), by its schedule alone, the same rule the list and today read. A day it rests (paused, away) or before it began isn't one; quit and event-scheduled habits are left out. Each row is `{ habitId, title, date, time, scheduleLabel, daily, streakLabel }`: `time` its exact `HH:MM` or null, `daily` true for a habit due every day, `streakLabel` `N days` for a daily habit and `N in a row` otherwise. Sorted by date, then time.",
30070
+ query: [
30071
+ { name: "from", description: "The first day (YYYY-MM-DD).", format: "date", required: true },
30072
+ {
30073
+ name: "to",
30074
+ description: "The last day (YYYY-MM-DD), at most 62 days on.",
30075
+ format: "date",
30076
+ required: true
30077
+ }
30078
+ ],
30079
+ querySchema: habitDueQuerySchema,
30080
+ status: 200,
30081
+ response: { description: "The habits' due days." },
30082
+ errors: { 400: true }
30083
+ },
29759
30084
  getAway: {
29760
30085
  method: "get",
29761
30086
  path: "/habits/away",
@@ -29932,8 +30257,9 @@ var habitOperations = defineOperations({
29932
30257
  scopes: ["habits:write"],
29933
30258
  tag: "Habits",
29934
30259
  summary: "Pause a habit",
29935
- description: '"Pause gym till the 26th": the habit rests until `until` (the day it\'s back, after today): off Now, no push, streak and plant wait. Pausing a paused habit moves its end. Returns the refreshed habit with `pause`; fires `habit.updated`.',
30260
+ description: '"Pause gym till the 26th": the habit rests until `until` (the day it\'s back, after today): off Now, no push, streak and plant wait. No body, or `until: null`, pauses open-ended: it rests until `/resume` (`paused` true, `pausedUntil` null). Pausing a paused habit moves its end. Returns the refreshed habit with `pause` and `paused`; fires `habit.updated`.',
29936
30261
  body: habitPauseInputSchema,
30262
+ bodyOptional: true,
29937
30263
  status: 201,
29938
30264
  response: { description: "The refreshed habit." },
29939
30265
  errors: {
@@ -30003,9 +30329,9 @@ var habitOperations = defineOperations({
30003
30329
  scopes: ["habits:write"],
30004
30330
  tag: "Habits",
30005
30331
  summary: "Undo a check-in",
30006
- description: "Delete a check-in; fires `habit.updated`.",
30332
+ description: "Delete a wrong or test check-in (or a skip): the streak, today state and heatmap recompute from what's left. The entry must be this habit's: another habit's, another Space's or one already gone is a 404. Find the id with `GET /habits/{id}/check-ins` or `lastCheckIn.id`. Returns the refreshed habit; fires `habit.updated`.",
30007
30333
  status: 200,
30008
- response: { description: "The refreshed habit." },
30334
+ response: { description: "The refreshed habit, its streak recomputed." },
30009
30335
  errors: { 404: true },
30010
30336
  webhook: "habit.updated",
30011
30337
  operationId: "undo_habit_check_in"
@@ -31171,6 +31497,12 @@ var houseValueSchema = object({
31171
31497
  }).nullable()
31172
31498
  });
31173
31499
  var upkeepMonthMarkSchema = _enum2(["done", "due", "planned", "late"]).nullable();
31500
+ var upkeepRoutineKindSchema = _enum2(["habit", "series"]);
31501
+ var upkeepSeriesRunSchema = object({
31502
+ year: number2().int(),
31503
+ mark: _enum2(["done", "running", "late"]),
31504
+ taskId: string2()
31505
+ });
31174
31506
  var houseUpkeepRoutineSchema = object({
31175
31507
  taskId: string2(),
31176
31508
  title: string2(),
@@ -31180,7 +31512,14 @@ var houseUpkeepRoutineSchema = object({
31180
31512
  months: array(upkeepMonthMarkSchema).length(12),
31181
31513
  bookedOn: day.nullable().default(null),
31182
31514
  itemState: string2().nullable().default(null),
31183
- certified: boolean2().default(false)
31515
+ certified: boolean2().default(false),
31516
+ kind: upkeepRoutineKindSchema.default("habit"),
31517
+ placeTitle: string2().nullable().default(null),
31518
+ cadenceWords: string2().default(""),
31519
+ nextOn: day.nullable().default(null),
31520
+ nextWords: string2().default(""),
31521
+ late: boolean2().default(false),
31522
+ seriesRuns: array(upkeepSeriesRunSchema).default([])
31184
31523
  });
31185
31524
  var houseUpkeepNeedSchema = object({
31186
31525
  id: string2(),
@@ -31197,6 +31536,12 @@ var houseUpkeepYearSchema = object({
31197
31536
  year: number2().int(),
31198
31537
  currentMonth: number2().int().min(1).max(12).nullable(),
31199
31538
  routines: array(houseUpkeepRoutineSchema),
31539
+ counts: object({
31540
+ all: number2().int().min(0),
31541
+ habits: number2().int().min(0),
31542
+ series: number2().int().min(0),
31543
+ late: number2().int().min(0)
31544
+ }).default({ all: 0, habits: 0, series: 0, late: 0 }),
31200
31545
  needsMonth: number2().int().min(1).max(12),
31201
31546
  needs: array(houseUpkeepNeedSchema),
31202
31547
  spent: object({
@@ -33028,6 +33373,7 @@ var apiKeys = pgTable("api_keys", {
33028
33373
  scopes: text("scopes").array().notNull().default([]),
33029
33374
  lastUsedAt: timestamp("last_used_at", { withTimezone: true }),
33030
33375
  revokedAt: timestamp("revoked_at", { withTimezone: true }),
33376
+ expiresAt: timestamp("expires_at", { withTimezone: true }),
33031
33377
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
33032
33378
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
33033
33379
  }, (t) => [index("api_keys_tenant_idx").on(t.tenantId)]);
@@ -33124,6 +33470,22 @@ var API_KEY_SCOPE_VALUES = [
33124
33470
  "private:*",
33125
33471
  "*"
33126
33472
  ];
33473
+ var API_TOKEN_SECTION_VALUES = [
33474
+ "today",
33475
+ "plan",
33476
+ "work",
33477
+ "habits",
33478
+ "journal",
33479
+ "home",
33480
+ "food",
33481
+ "collection",
33482
+ "money",
33483
+ "health",
33484
+ "people",
33485
+ "animals",
33486
+ "agents",
33487
+ "settings"
33488
+ ];
33127
33489
  var FULL_ACCESS_SCOPE = "*";
33128
33490
  var CONNECTIONS_WRITE_SCOPE = "connections:write";
33129
33491
  function scopeGranted(granted, required) {
@@ -33143,16 +33505,25 @@ var apiKeyScopesInputSchema = array(apiKeyScopeSchema).refine((scopes) => !scope
33143
33505
  });
33144
33506
  var apiKeySchema = createSelectSchema(apiKeys, {
33145
33507
  scopes: array(apiKeyScopeSchema),
33508
+ expiresAt: exports_coerce.date().nullable().default(null),
33146
33509
  agentId: string2().nullable().default(null),
33147
33510
  lastUsedAt: exports_coerce.date().nullable(),
33148
33511
  revokedAt: exports_coerce.date().nullable(),
33149
33512
  createdAt: exports_coerce.date(),
33150
33513
  updatedAt: exports_coerce.date()
33151
33514
  });
33152
- var apiKeyPublicSchema = apiKeySchema.omit({ keyHash: true });
33515
+ var apiKeySectionAccessSchema = object({
33516
+ section: _enum2(API_TOKEN_SECTION_VALUES),
33517
+ access: _enum2(["read", "write"])
33518
+ }).strict();
33519
+ var apiKeyPublicSchema = apiKeySchema.omit({ keyHash: true }).extend({
33520
+ sections: array(apiKeySectionAccessSchema).default([]),
33521
+ scopeLine: string2().default("")
33522
+ });
33153
33523
  var createApiKeyInputSchema = createInsertSchema(apiKeys, {
33154
33524
  name: string2().min(1).max(120),
33155
33525
  scopes: apiKeyScopesInputSchema.optional(),
33526
+ expiresAt: exports_coerce.date().nullish(),
33156
33527
  agentId: ulidSchema.nullish()
33157
33528
  }).omit({
33158
33529
  id: true,
@@ -33164,7 +33535,27 @@ var createApiKeyInputSchema = createInsertSchema(apiKeys, {
33164
33535
  revokedAt: true,
33165
33536
  createdAt: true,
33166
33537
  updatedAt: true
33167
- }).strict();
33538
+ }).extend({
33539
+ sections: array(apiKeySectionAccessSchema).min(1).max(API_TOKEN_SECTION_VALUES.length).optional()
33540
+ }).strict().refine((v) => v.scopes === undefined || v.sections === undefined, {
33541
+ message: "give scopes or sections, not both",
33542
+ path: ["sections"]
33543
+ }).refine((v) => !v.expiresAt || v.expiresAt.getTime() > Date.now(), {
33544
+ message: "an expiry must be in the future",
33545
+ path: ["expiresAt"]
33546
+ });
33547
+ var createKeyFromKeyInputSchema = object({
33548
+ name: string2().min(1).max(120),
33549
+ scopes: apiKeyScopesInputSchema.optional(),
33550
+ sections: array(apiKeySectionAccessSchema).min(1).max(API_TOKEN_SECTION_VALUES.length).optional(),
33551
+ expiresAt: exports_coerce.date().nullish()
33552
+ }).strict().refine((v) => v.scopes === undefined || v.sections === undefined, {
33553
+ message: "give scopes or sections, not both",
33554
+ path: ["sections"]
33555
+ }).refine((v) => !v.expiresAt || v.expiresAt.getTime() > Date.now(), {
33556
+ message: "an expiry must be in the future",
33557
+ path: ["expiresAt"]
33558
+ });
33168
33559
  var createApiKeyResultSchema = apiKeyPublicSchema.extend({ key: string2() });
33169
33560
  var updateApiKeyInputSchema = object({
33170
33561
  name: string2().min(1).max(120).optional(),
@@ -33210,6 +33601,20 @@ var keyOperations = defineOperations({
33210
33601
  response: { description: "The keys." },
33211
33602
  errors: { 403: true }
33212
33603
  },
33604
+ create: {
33605
+ method: "post",
33606
+ path: "/keys",
33607
+ scopes: ["keys:write"],
33608
+ tag: "Identity",
33609
+ summary: "Mint an API key",
33610
+ description: 'A key for the member behind this key, by `sections` (each `read` or `write`: Today/Inbox, Plan, Work, Habits, Journal, Home, Food, Collection, Money, Health, People, Animals, Agents, Settings; the server keeps the scopes they stand for) or by `scopes`, not both, with an optional `expiresAt` (null never expires; past it the key gets 401 "This token expired on <date>"). Never more than the calling key holds (403), never an Agent\'s key. The plaintext `key` is in the answer once; the key carries its `scopeLine` ("Work, Plan: read and write").',
33611
+ body: createKeyFromKeyInputSchema,
33612
+ status: 201,
33613
+ response: { description: "The key, with its one-time secret." },
33614
+ errors: { 400: true, 403: true },
33615
+ throttle: "strict",
33616
+ mcp: false
33617
+ },
33213
33618
  update: {
33214
33619
  method: "patch",
33215
33620
  path: "/keys/{id}",
@@ -33772,19 +34177,37 @@ var mailCampaignOperations = defineOperations({
33772
34177
  });
33773
34178
 
33774
34179
  // ../../packages/schema/src/contracts/meal-plans.ts
34180
+ var mealPlanStateSchema = _enum2(MEAL_PLAN_STATES);
34181
+ var civilDate2 = string2().regex(/^\d{4}-\d{2}-\d{2}$/, "a date is YYYY-MM-DD");
34182
+ var weekMenuFields = {
34183
+ state: mealPlanStateSchema.optional(),
34184
+ cookUserId: ulidSchema.nullish(),
34185
+ company: string2().max(120).nullish(),
34186
+ leftoverPortions: number2().int().positive().nullish(),
34187
+ leftoverEatBy: civilDate2.nullish(),
34188
+ leftoverFrozen: boolean2().optional()
34189
+ };
33775
34190
  var mealPlanSchema = createSelectSchema(mealPlans, {
33776
- plannedFor: exports_coerce.date()
34191
+ plannedFor: exports_coerce.date(),
34192
+ state: mealPlanStateSchema.default("planned"),
34193
+ cookUserId: string2().nullable().default(null),
34194
+ company: string2().nullable().default(null),
34195
+ leftoverPortions: number2().int().nullable().default(null),
34196
+ leftoverEatBy: string2().nullable().default(null),
34197
+ leftoverFrozen: boolean2().default(false)
33777
34198
  });
33778
34199
  var createMealPlanInputSchema = createInsertSchema(mealPlans, {
33779
34200
  id: ulidSchema,
33780
34201
  recipeId: ulidSchema.nullish(),
33781
34202
  plannedFor: exports_coerce.date(),
33782
- servings: number2().int().positive().nullish()
34203
+ servings: number2().int().positive().nullish(),
34204
+ ...weekMenuFields
33783
34205
  }).omit({ tenantId: true }).extend({ title: string2().min(1) });
33784
34206
  var updateMealPlanInputSchema = createUpdateSchema(mealPlans, {
33785
34207
  recipeId: ulidSchema.nullish(),
33786
34208
  plannedFor: exports_coerce.date().optional(),
33787
- servings: number2().int().positive().nullish()
34209
+ servings: number2().int().positive().nullish(),
34210
+ ...weekMenuFields
33788
34211
  }).omit({ id: true, tenantId: true }).extend({ title: string2().min(1).optional() }).strict();
33789
34212
  var mealPlanViewSchema = mealPlanSchema.extend({
33790
34213
  title: string2(),
@@ -33794,6 +34217,10 @@ var mealPlanViewSchema = mealPlanSchema.extend({
33794
34217
  deletedAt: exports_coerce.date().nullable(),
33795
34218
  recipeTitle: string2().nullable()
33796
34219
  });
34220
+ var mealPlanListQuerySchema = object({
34221
+ recipeId: ulidSchema.optional(),
34222
+ state: mealPlanStateSchema.optional()
34223
+ });
33797
34224
  var generateGroceryListInputSchema = object({
33798
34225
  id: ulidSchema,
33799
34226
  title: string2().min(1),
@@ -33815,6 +34242,59 @@ var mealPlanCookResultSchema = object({
33815
34242
  consumedCount: number2().int().min(0),
33816
34243
  ingredients: array(cookedIngredientSchema)
33817
34244
  });
34245
+ var cookMealPlanInputSchema = object({
34246
+ leftoverPortions: number2().int().positive().nullish(),
34247
+ leftoverEatBy: civilDate2.nullish()
34248
+ }).strict();
34249
+ var mealWeekQuerySchema = object({ start: civilDate2.optional() });
34250
+ var acceptMealDraftInputSchema = object({ start: civilDate2 }).strict();
34251
+ var mealWeekCookSchema = object({
34252
+ userId: string2(),
34253
+ name: string2(),
34254
+ initial: string2()
34255
+ });
34256
+ var mealWeekSlotSchema = object({
34257
+ id: ulidSchema,
34258
+ title: string2(),
34259
+ recipeId: ulidSchema.nullable(),
34260
+ state: mealPlanStateSchema,
34261
+ cook: mealWeekCookSchema.nullable(),
34262
+ company: string2().nullable(),
34263
+ notes: string2().nullable(),
34264
+ time: string2().nullable()
34265
+ });
34266
+ var mealWeekDaySchema = object({
34267
+ date: civilDate2,
34268
+ isToday: boolean2(),
34269
+ dinner: mealWeekSlotSchema.nullable(),
34270
+ lunch: mealWeekSlotSchema.nullable()
34271
+ });
34272
+ var mealWeekLeftoverSchema = object({
34273
+ mealPlanId: ulidSchema,
34274
+ title: string2(),
34275
+ portions: number2().int(),
34276
+ cookedOn: civilDate2,
34277
+ eatBy: civilDate2.nullable(),
34278
+ frozen: boolean2()
34279
+ });
34280
+ var mealWeekDraftDaySchema = object({
34281
+ date: civilDate2,
34282
+ dinner: mealWeekSlotSchema.nullable(),
34283
+ why: string2().nullable()
34284
+ });
34285
+ var mealWeekSchema = object({
34286
+ week: number2().int().min(1).max(53),
34287
+ start: civilDate2,
34288
+ end: civilDate2,
34289
+ days: array(mealWeekDaySchema).length(7),
34290
+ summary: object({
34291
+ planned: number2().int().min(0),
34292
+ cooked: number2().int().min(0),
34293
+ out: number2().int().min(0)
34294
+ }),
34295
+ leftovers: array(mealWeekLeftoverSchema),
34296
+ draft: array(mealWeekDraftDaySchema).length(7)
34297
+ });
33818
34298
 
33819
34299
  // ../../packages/schema/src/operations/meal-plans.ts
33820
34300
  var mealPlanOperations = defineOperations({
@@ -33825,8 +34305,43 @@ var mealPlanOperations = defineOperations({
33825
34305
  read: "meals:read",
33826
34306
  write: "meals:write",
33827
34307
  create: createMealPlanInputSchema,
33828
- update: updateMealPlanInputSchema
33829
- })
34308
+ update: updateMealPlanInputSchema,
34309
+ listQuery: [
34310
+ { name: "recipeId", description: "Only the plans of this recipe (its ULID)." },
34311
+ {
34312
+ name: "state",
34313
+ description: "Only plans in this state (`planned`, `cooked`, `out`, `unsure`, `draft`); `cooked` with `recipeId` is who cooked a recipe when."
34314
+ }
34315
+ ],
34316
+ listQuerySchema: mealPlanListQuerySchema
34317
+ }),
34318
+ week: {
34319
+ method: "get",
34320
+ path: "/meal-plans/week",
34321
+ scopes: ["meals:read"],
34322
+ tag: "Meals",
34323
+ summary: "Show the week menu",
34324
+ description: "The week (Monday to Sunday) from `start` (any day is moved to its Monday; this week by default): each day's dinner and lunch with its state (planned \xB7 cooked \xB7 out \xB7 unsure), who cooks and the company of a meal out; the dinner counts (planned, cooked, out); the leftovers still to eat; and next week's `draft` dinners, each with its one-line why (the draft plan's notes).",
34325
+ query: [{ name: "start", description: "A day of the week, YYYY-MM-DD.", format: "date" }],
34326
+ querySchema: mealWeekQuerySchema,
34327
+ status: 200,
34328
+ response: { description: "The week menu." },
34329
+ errors: { 400: true },
34330
+ operationId: "meal_week"
34331
+ },
34332
+ acceptDraft: {
34333
+ method: "post",
34334
+ path: "/meal-plans/week/accept-draft",
34335
+ scopes: ["meals:write"],
34336
+ tag: "Meals",
34337
+ summary: "Accept a week's draft",
34338
+ description: "Turn every `draft` meal plan in the week from `start` into a `planned` one and return that week's menu. Accepting a week with no drafts left changes nothing.",
34339
+ body: acceptMealDraftInputSchema,
34340
+ status: 200,
34341
+ response: { description: "The week menu, its drafts planned." },
34342
+ errors: { 400: true },
34343
+ operationId: "meal_week_accept_draft"
34344
+ }
33830
34345
  });
33831
34346
 
33832
34347
  // ../../packages/schema/src/operations/members.ts
@@ -37434,7 +37949,8 @@ var recipeLibrarySchema = object({
37434
37949
  recipes: array(object({
37435
37950
  id: ulidSchema,
37436
37951
  lastCookedAt: exports_coerce.date().nullable(),
37437
- ingredients: array(string2())
37952
+ ingredients: array(string2()),
37953
+ cookedCount: number2().int().min(0).optional()
37438
37954
  })),
37439
37955
  menu: array(object({
37440
37956
  planId: ulidSchema,
@@ -37443,7 +37959,9 @@ var recipeLibrarySchema = object({
37443
37959
  plannedFor: exports_coerce.date(),
37444
37960
  mealType: mealTypeSchema,
37445
37961
  cooked: boolean2(),
37446
- onList: array(string2())
37962
+ onList: array(string2()),
37963
+ state: mealPlanStateSchema.optional(),
37964
+ cook: mealWeekCookSchema.nullable().optional()
37447
37965
  }))
37448
37966
  });
37449
37967
  var recipeIngredientMatchInputSchema = object({ foodId: string2().min(1) }).strict();
@@ -37646,7 +38164,7 @@ var routineOperations = defineOperations({
37646
38164
  scopes: ["routines:write"],
37647
38165
  tag: "Routines",
37648
38166
  summary: "Update a routine",
37649
- description: "Patch a routine; fires `routine.updated`.",
38167
+ description: "Patch a routine; fires `routine.updated`. `steps`, when present, replaces the template in one call, in the order given: a step whose `id` is already the routine's is updated in place (its runs keep pointing at it), one left out is dropped (past runs keep their snapshot), a new `id` is added. `steps: []` empties the routine. The habits and tasks the steps link are never changed.",
37650
38168
  body: updateRoutineInputSchema,
37651
38169
  status: 200,
37652
38170
  response: { description: "The updated routine." },
@@ -37659,12 +38177,25 @@ var routineOperations = defineOperations({
37659
38177
  scopes: ["routines:write"],
37660
38178
  tag: "Routines",
37661
38179
  summary: "Delete a routine",
37662
- description: "Soft-delete a routine; fires `routine.deleted`.",
38180
+ description: "Soft-delete a routine into Trash; its steps and runs stay for history, and the habits and tasks its steps link are untouched. Fires `routine.deleted`.",
37663
38181
  status: 204,
37664
38182
  response: { description: "Deleted." },
37665
38183
  errors: { 404: true },
37666
38184
  webhook: "routine.deleted"
37667
38185
  },
38186
+ archive: {
38187
+ method: "post",
38188
+ path: "/routines/{id}/archive",
38189
+ scopes: ["routines:write"],
38190
+ tag: "Routines",
38191
+ summary: "Archive a routine",
38192
+ description: "Put the routine in Trash: the same soft delete as `DELETE /api/v1/routines/{id}`, restorable like any other node. Its steps and runs stay for history; the habits and tasks its steps link are untouched. Fires `routine.deleted`.",
38193
+ status: 204,
38194
+ response: { description: "Archived." },
38195
+ errors: { 404: true },
38196
+ webhook: "routine.deleted",
38197
+ operationId: "archive_routine"
38198
+ },
37668
38199
  attachRecurrence: {
37669
38200
  method: "put",
37670
38201
  path: "/routines/{id}/recurrence",
@@ -38906,6 +39437,9 @@ var PILL_PAGES_MAX = 4;
38906
39437
  var CALENDAR_ZOOM_MIN = 75;
38907
39438
  var CALENDAR_ZOOM_MAX = 300;
38908
39439
  var calendarZoomSchema = number2().int().min(CALENDAR_ZOOM_MIN).max(CALENDAR_ZOOM_MAX);
39440
+ var settingsGlassSchema = _enum2(settingsGlass.enumValues);
39441
+ var themePackSchema = _enum2(settingsThemePack.enumValues);
39442
+ var themePackScopeSchema = _enum2(settingsThemePackScope.enumValues);
38909
39443
  var userSettingsSchema = createSelectSchema(userSettings, {
38910
39444
  pinnedAreaIds: array(string2()).default([]),
38911
39445
  focusAreaId: string2().nullable().default(null),
@@ -38916,6 +39450,14 @@ var userSettingsSchema = createSelectSchema(userSettings, {
38916
39450
  pillPages: array(string2()).default([]).transform((ids) => ids.filter((id) => pillPageSchema.safeParse(id).success)),
38917
39451
  foodDiaryMode: boolean2().default(false),
38918
39452
  calendarZoom: number2().int().nullable().default(null),
39453
+ glass: settingsGlassSchema.default("liquid"),
39454
+ followSky: boolean2().default(true),
39455
+ nightUntilWake: boolean2().default(false),
39456
+ largerText: boolean2().default(false),
39457
+ reduceMotion: boolean2().default(false),
39458
+ showStreakNumbers: boolean2().default(true),
39459
+ themePack: themePackSchema.default("do"),
39460
+ themePackScope: themePackScopeSchema.default("section"),
38919
39461
  createdAt: exports_coerce.date(),
38920
39462
  updatedAt: exports_coerce.date()
38921
39463
  }).omit({ autoRollOverdue: true });
@@ -38935,7 +39477,15 @@ var updateUserSettingsInputSchema = createUpdateSchema(userSettings, {
38935
39477
  quietHours: quietHoursSchema.nullish(),
38936
39478
  lifeBlockSharing: lifeBlockSharingSchema.nullish(),
38937
39479
  calendarZoom: calendarZoomSchema.nullish(),
38938
- pillPages: array(pillPageSchema).transform((ids) => [...new Set(ids)]).pipe(array(pillPageSchema).max(PILL_PAGES_MAX)).optional()
39480
+ pillPages: array(pillPageSchema).transform((ids) => [...new Set(ids)]).pipe(array(pillPageSchema).max(PILL_PAGES_MAX)).optional(),
39481
+ glass: settingsGlassSchema.optional(),
39482
+ followSky: boolean2().optional(),
39483
+ nightUntilWake: boolean2().optional(),
39484
+ largerText: boolean2().optional(),
39485
+ reduceMotion: boolean2().optional(),
39486
+ showStreakNumbers: boolean2().optional(),
39487
+ themePack: themePackSchema.optional(),
39488
+ themePackScope: themePackScopeSchema.optional()
38939
39489
  }).omit({
38940
39490
  id: true,
38941
39491
  tenantId: true,
@@ -39395,6 +39945,66 @@ var taskBlockOperations = defineOperations({
39395
39945
  }
39396
39946
  });
39397
39947
 
39948
+ // ../../packages/schema/src/operations/task-series.ts
39949
+ var taskSeriesOperations = defineOperations({
39950
+ make: {
39951
+ method: "post",
39952
+ path: "/tasks/{id}/make-series",
39953
+ scopes: ["tasks:write"],
39954
+ tag: "Tasks",
39955
+ summary: "Make a task the first of a series",
39956
+ description: "The task (by id or ref, `DO-150`) becomes slot 0 of a new series on `rule` (every N weeks, months or years; a quarter is 3 months), its Deadline (else its planned day, else today) the first slot. Its Area, Project, labels, priority, estimate, Owner and sub-task titles are the template each next task copies; the next is made `leadDays` before its slot and named by `occurrenceLabel` (`quarter`, `month`, `date`). A task already in a series is a 409.",
39957
+ body: makeTaskSeriesInputSchema,
39958
+ status: 201,
39959
+ response: { description: "The series, with its strip." },
39960
+ errors: { 400: true, 404: true, 409: true }
39961
+ },
39962
+ list: {
39963
+ method: "get",
39964
+ path: "/task-series",
39965
+ scopes: ["tasks:read"],
39966
+ tag: "Tasks",
39967
+ summary: "List task series",
39968
+ description: "The Space's task series, stopped ones too: each with its rhythm in words, its settings, how many tasks it made and its strip (up to two past occurrences, the latest, and the next with the day it's made).",
39969
+ status: 200,
39970
+ response: { description: "The series." }
39971
+ },
39972
+ get: {
39973
+ method: "get",
39974
+ path: "/task-series/{id}",
39975
+ scopes: ["tasks:read"],
39976
+ tag: "Tasks",
39977
+ summary: "Show a task series",
39978
+ description: "One series with its settings and its strip.",
39979
+ status: 200,
39980
+ response: { description: "The series." },
39981
+ errors: { 404: true }
39982
+ },
39983
+ update: {
39984
+ method: "patch",
39985
+ path: "/task-series/{id}",
39986
+ scopes: ["tasks:write"],
39987
+ tag: "Tasks",
39988
+ summary: "Update a task series",
39989
+ description: "Rename it, change its rhythm, naming or lead days, or stop (`active: false`) and resume it.",
39990
+ body: updateTaskSeriesInputSchema,
39991
+ status: 200,
39992
+ response: { description: "The series." },
39993
+ errors: { 400: true, 404: true }
39994
+ },
39995
+ stop: {
39996
+ method: "delete",
39997
+ path: "/task-series/{id}",
39998
+ scopes: ["tasks:write"],
39999
+ tag: "Tasks",
40000
+ summary: "Stop a task series",
40001
+ description: "No more tasks are made; every task it made stays.",
40002
+ status: 204,
40003
+ response: { description: "Stopped." },
40004
+ errors: { 404: true }
40005
+ }
40006
+ });
40007
+
39398
40008
  // ../../packages/schema/src/operations/tasks.ts
39399
40009
  var taskListQuery = taskListQuerySchema.extend(paginationQuerySchema.shape).extend({ filter: taskListFilterSchema.optional().default("all") });
39400
40010
  var taskOperations = defineOperations({
@@ -39498,7 +40108,7 @@ var taskOperations = defineOperations({
39498
40108
  scopes: ["tasks:read"],
39499
40109
  tag: "Tasks",
39500
40110
  summary: "Get a task",
39501
- description: "Fetch one task by id (ULID) or by its ref (`DO-142`, case-insensitive).",
40111
+ description: "Fetch one task by id (ULID) or by its ref (`DO-142`, case-insensitive). A repeating task carries its rule as `recurrence` (null for a one-off).",
39502
40112
  status: 200,
39503
40113
  response: { description: "The task." },
39504
40114
  errors: { 404: true }
@@ -39509,7 +40119,7 @@ var taskOperations = defineOperations({
39509
40119
  scopes: ["tasks:write"],
39510
40120
  tag: "Tasks",
39511
40121
  summary: "Create a task",
39512
- description: "Create a task; fires the `task.created` webhook.",
40122
+ description: "Create a task; fires the `task.created` webhook. An optional `recurrence` makes it repeat: `freq` daily/weekly/monthly, `interval`, `byDay` / `byMonthDay` for a `fixed` (calendar) series, or `anchor: from_completion` (the next one is due `interval` after the last was done).",
39513
40123
  body: createTaskInputSchema,
39514
40124
  status: 201,
39515
40125
  response: { description: "The created task." },
@@ -39522,7 +40132,7 @@ var taskOperations = defineOperations({
39522
40132
  scopes: ["tasks:write"],
39523
40133
  tag: "Tasks",
39524
40134
  summary: "Update a task",
39525
- description: "Patch a task; fires the `task.updated` webhook.",
40135
+ description: 'Patch a task; fires the `task.updated` webhook. `recurrence` sets or changes its repeat rule and `recurrence: null` ends the series (the done history stays). On a repeating task `status: "done"` finishes the open occurrence; the task stays open for the next one.',
39526
40136
  body: updateTaskInputSchema,
39527
40137
  status: 200,
39528
40138
  response: { description: "The updated task." },
@@ -39692,6 +40302,67 @@ var todayOperations = defineOperations({
39692
40302
  }
39693
40303
  });
39694
40304
 
40305
+ // ../../packages/schema/src/contracts/waste-calendar.ts
40306
+ var WASTE_KIND_VALUES = ["rest", "pmd", "paper", "garden", "glass", "other"];
40307
+ var wasteKindSchema = _enum2(WASTE_KIND_VALUES);
40308
+ var civilDate3 = exports_iso.date();
40309
+ var wasteCollectionSchema = object({
40310
+ date: civilDate3,
40311
+ kinds: array(wasteKindSchema).min(1),
40312
+ movedBy: string2().min(1).optional()
40313
+ });
40314
+ var wasteCalendarConnectInputSchema = object({
40315
+ feedUrl: url({ protocol: /^https?$/ }).max(2000)
40316
+ }).strict();
40317
+ var wasteCalendarNextQuerySchema = object({
40318
+ limit: exports_coerce.number().int().min(1).max(12).default(4)
40319
+ });
40320
+ var wasteCalendarNextItemSchema = wasteCollectionSchema.extend({
40321
+ kindsWords: string2().min(1),
40322
+ binsOutAt: string2().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/)
40323
+ });
40324
+ var wasteCalendarNextSchema = object({
40325
+ connected: boolean2(),
40326
+ collections: array(wasteCalendarNextItemSchema),
40327
+ lastSyncAt: exports_coerce.date().nullable(),
40328
+ lastSyncLine: string2().nullable(),
40329
+ lastError: string2().nullable()
40330
+ });
40331
+
40332
+ // ../../packages/schema/src/operations/waste-calendar.ts
40333
+ var CONNECT3 = ["connections:write"];
40334
+ var AFTER3 = "`*` and `private:*` never grant `connections:write`: a key holds it by name, and a guest's key never does.";
40335
+ var wasteCalendarOperations = defineOperations({
40336
+ connect: {
40337
+ method: "post",
40338
+ path: "/integrations/waste-calendar",
40339
+ scopes: CONNECT3,
40340
+ afterScopes: AFTER3,
40341
+ tag: "Connections",
40342
+ summary: "Connect the waste calendar",
40343
+ description: "Keep the household's waste-collection iCal `feedUrl` (the municipality's or the waste app's), sealed, and read it now; it's read again every morning at 06:00. Connecting again replaces the URL. The URL is never returned. Answers what's next, as `next` does: a feed that can't be read still connects, with `lastError` saying why.",
40344
+ body: wasteCalendarConnectInputSchema,
40345
+ status: 200,
40346
+ response: { description: "The next collections." },
40347
+ errors: { 400: true, 403: true },
40348
+ throttle: "strict",
40349
+ mcp: false
40350
+ },
40351
+ next: {
40352
+ method: "get",
40353
+ path: "/integrations/waste-calendar/next",
40354
+ scopes: ["settings:read"],
40355
+ tag: "Connections",
40356
+ summary: "The next waste collections",
40357
+ description: 'The next `limit` (1\u201312, 4 by default) collection days from today in the connecting member\'s zone: each its `date`, `kinds` (`rest`, `pmd`, `paper`, `garden`, `glass`, `other`), `kindsWords` ("Rest and PMD", "Garden, moved by the holiday"), `movedBy` when the feed says why it moved, and `binsOutAt` (the evening before at 20:00, local). With `lastSyncAt`, `lastSyncLine` ("Today 06:00, every morning") and the last read\'s `lastError`; `connected: false` with none set up.',
40358
+ query: [{ name: "limit", description: "How many collection days (1\u201312, default 4)." }],
40359
+ querySchema: wasteCalendarNextQuerySchema,
40360
+ status: 200,
40361
+ response: { description: "The next collections." },
40362
+ errors: { 403: true }
40363
+ }
40364
+ });
40365
+
39695
40366
  // ../../packages/schema/src/db/webhooks.ts
39696
40367
  var webhookSubscriptions = pgTable("webhook_subscriptions", {
39697
40368
  id: text("id").primaryKey(),
@@ -40672,6 +41343,14 @@ function addMonths(d, months) {
40672
41343
  var TAG_RE = /(?:^|\s)#([\p{L}\p{N}][\p{L}\p{N}_-]*)/gu;
40673
41344
  var STAR_RE = /(?:^|\s)!(star|flag|s|f)?(?=\s|$)/giu;
40674
41345
  var AREA_RE = /(?:^|\s)~([\p{L}\p{N}][\p{L}\p{N}_-]*)/gu;
41346
+ var PRIORITY_RE = /(?:^|\s)(?:p([123])|!(high|medium|low))(?=\s|$)/giu;
41347
+ var PRIORITY_OF_P = { "1": "high", "2": "medium", "3": "low" };
41348
+ function capturePriority(token) {
41349
+ const m = /^(?:p([123])|!(high|medium|low))$/iu.exec(token.trim());
41350
+ if (!m)
41351
+ return null;
41352
+ return m[1] ? PRIORITY_OF_P[m[1]] ?? null : m[2]?.toLowerCase();
41353
+ }
40675
41354
  function parseCapture(input, now) {
40676
41355
  const tags = [];
40677
41356
  for (const match of input.matchAll(TAG_RE)) {
@@ -40692,7 +41371,12 @@ function parseCapture(input, now) {
40692
41371
  break;
40693
41372
  }
40694
41373
  }
40695
- let stripped = blankMatches(input, [TAG_RE, STAR_RE, AREA_RE]);
41374
+ let priority;
41375
+ for (const match of input.matchAll(PRIORITY_RE)) {
41376
+ priority = capturePriority(match[0]) ?? undefined;
41377
+ break;
41378
+ }
41379
+ let stripped = blankMatches(input, [TAG_RE, STAR_RE, AREA_RE, PRIORITY_RE]);
40696
41380
  const when = now ? parseWhen(stripped, now) : undefined;
40697
41381
  if (when) {
40698
41382
  for (const span of [...when.spans].sort((a, b) => b.start - a.start)) {
@@ -40705,6 +41389,7 @@ function parseCapture(input, now) {
40705
41389
  tags,
40706
41390
  ...starred ? { starred } : {},
40707
41391
  ...area ? { area } : {},
41392
+ ...priority ? { priority } : {},
40708
41393
  ...when?.scheduledAt ? { scheduledAt: when.scheduledAt, allDay: when.allDay } : {},
40709
41394
  ...when?.dueAt ? { dueAt: when.dueAt, ...when.scheduledAt ? {} : { allDay: when.dueAllDay } } : {},
40710
41395
  ...when?.durationMinutes ? { durationMinutes: when.durationMinutes } : {},
@@ -41399,7 +42084,7 @@ function renderHabit(data, mode) {
41399
42084
  const pct = typeof streak?.completionRate === "number" ? ` \xB7 ${Math.round(streak.completionRate * 100)}% lately` : "";
41400
42085
  const recovering = streak?.state === "recovering" ? paint(mode, "yellow", " \xB7 recovering") : "";
41401
42086
  const counts = streak ? ` \xB7 streak ${streak.current ?? 0} (best ${streak.longest ?? 0})` : "";
41402
- const rest = h.pause ? ` \xB7 ${h.pause.by === "away" ? "away" : "paused"}, back ${h.pause.until}` : streak?.skippedToday ? " \xB7 rest day" : "";
42087
+ const rest = h.pause ? ` \xB7 ${h.pause.by === "away" ? "away" : "paused"}${h.pause.until ? `, back ${h.pause.until}` : " until resumed"}` : streak?.skippedToday ? " \xB7 rest day" : "";
41403
42088
  const last = h.lastCheckIn?.id ? `
41404
42089
  ${paint(mode, "dim", `last check-in ${h.lastCheckIn.id}`)}${h.lastCheckIn.note ? ` "${h.lastCheckIn.note}"` : ""}` : "";
41405
42090
  return `${h.title ?? "(habit)"} \u2014 ${flame}${pct}${counts}${recovering}${rest}
@@ -41441,10 +42126,11 @@ function renderKeys(data, mode) {
41441
42126
  return paint(mode, "dim", "(no keys)");
41442
42127
  return keys.map((key) => {
41443
42128
  const scopes = key.scopes ?? [];
41444
- const reach = sayFullAccess(scopes) ?? (scopes.join(", ") || "(no scopes)");
42129
+ const reach = key.scopeLine || sayFullAccess(scopes) || scopes.join(", ") || "(no scopes)";
42130
+ const expires = key.expiresAt ? paint(mode, "dim", ` \xB7 expires ${key.expiresAt.slice(0, 10)}`) : "";
41445
42131
  const changed = key.changes?.[0] ? paint(mode, "dim", ` \xB7 changed ${key.changes[0].at.slice(0, 10)}`) : "";
41446
42132
  return `${key.name} ${paint(mode, "dim", key.id)}
41447
- ${reach}${changed}`;
42133
+ ${reach}${expires}${changed}`;
41448
42134
  }).join(`
41449
42135
  `);
41450
42136
  }
@@ -44845,6 +45531,32 @@ var CONNECTION_COMMAND_SPECS = [
44845
45531
  returns: "MoneyKrakenLinkStatus",
44846
45532
  notes: `${routeOf(krakenLinkOperations.remove)}. Gated behind --allow-destructive.`
44847
45533
  },
45534
+ {
45535
+ path: ["connections", "waste", "next"],
45536
+ summary: "The next waste collections: each day's kinds in words and when the bins go out (the evening before, at 20:00).",
45537
+ args: [],
45538
+ flags: [
45539
+ { name: "limit", type: "string", description: "How many collection days (1\u201312, default 4)." }
45540
+ ],
45541
+ scopes: scopesOf(wasteCalendarOperations.next),
45542
+ returns: "WasteCalendarNext",
45543
+ notes: `${routeOf(wasteCalendarOperations.next)}. A pickup that moved says why ("Garden, moved by the holiday").`
45544
+ },
45545
+ {
45546
+ path: ["connections", "waste", "connect"],
45547
+ summary: "Connect the household's waste calendar: its iCal feed URL, read from a file, then read it.",
45548
+ args: [],
45549
+ flags: [
45550
+ {
45551
+ name: "feed-file",
45552
+ type: "string",
45553
+ description: "Path to a file holding the feed's URL (one line), or - to read it from stdin."
45554
+ }
45555
+ ],
45556
+ scopes: scopesOf(wasteCalendarOperations.connect),
45557
+ returns: "WasteCalendarNext",
45558
+ notes: `${routeOf(wasteCalendarOperations.connect)}. The URL can carry a private token, so it's read from the file and sealed; it is never a flag, never printed and never read back. It's read again every morning at 06:00; connecting again replaces it.`
45559
+ },
44848
45560
  {
44849
45561
  path: ["doccle", "connect"],
44850
45562
  summary: "Give do your Doccle login so the nightly import can run. do checks it with Doccle first and keeps it sealed.",
@@ -44977,6 +45689,24 @@ var CONNECTION_HANDLERS = {
44977
45689
  data: await guarded([], () => requireClient().get(pathOf(krakenLinkOperations.status))),
44978
45690
  render: renderKraken
44979
45691
  }),
45692
+ "connections waste next": async ({ flags, requireClient }) => {
45693
+ const limit = flagString(flags, "limit");
45694
+ if (limit !== undefined && !/^\d+$/.test(limit))
45695
+ throw usageError("--limit takes a number (1\u201312)");
45696
+ const path = `${pathOf(wasteCalendarOperations.next)}${limit ? `?limit=${limit}` : ""}`;
45697
+ return {
45698
+ data: await guarded([], () => requireClient().get(path)),
45699
+ render: renderWaste
45700
+ };
45701
+ },
45702
+ "connections waste connect": async ({ io, flags, requireClient }) => {
45703
+ refuseSecretFlags(flags, ["url", "feed", "feed-url"], "--feed-file <path>");
45704
+ const feedUrl = (await readSecret(io, flags, "feed-file", "the feed's URL", "line")).trim();
45705
+ return {
45706
+ data: await guarded([feedUrl], () => requireClient().post(pathOf(wasteCalendarOperations.connect), { feedUrl })),
45707
+ render: renderWaste
45708
+ };
45709
+ },
44980
45710
  "money kraken-link set": async ({ io, flags, requireClient }) => {
44981
45711
  refuseSecretFlags(flags, ["key", "api-key", "secret", "api-secret", "private-key"], "--key-file <path>");
44982
45712
  const text = await readSecret(io, flags, "key-file", "the Kraken key", "all");
@@ -45063,6 +45793,30 @@ function refuseSecretFlags(flags, names, instead) {
45063
45793
  }
45064
45794
  }
45065
45795
  }
45796
+ function renderWaste(data, mode) {
45797
+ const next = data;
45798
+ if (!next || !Array.isArray(next.collections))
45799
+ return renderRecord(data, mode);
45800
+ if (!next.connected)
45801
+ return "No waste calendar connected. Run do connections waste connect.";
45802
+ const day = (iso) => new Date(`${iso.slice(0, 10)}T12:00:00Z`).toLocaleDateString("en-GB", {
45803
+ weekday: "short",
45804
+ day: "numeric",
45805
+ month: "short",
45806
+ timeZone: "UTC"
45807
+ });
45808
+ const weekday = (iso) => new Date(`${iso.slice(0, 10)}T12:00:00Z`).toLocaleDateString("en-GB", {
45809
+ weekday: "short",
45810
+ timeZone: "UTC"
45811
+ });
45812
+ const lines = next.collections.map((c) => `${day(c.date)} \xB7 ${c.kindsWords} \xB7 bins out ${weekday(c.binsOutAt)} ${c.binsOutAt.slice(11)}`);
45813
+ return [
45814
+ ...lines.length > 0 ? lines : ["No collections coming up."],
45815
+ ...next.lastSyncLine ? [`Read: ${next.lastSyncLine}`] : [],
45816
+ ...next.lastError ? [`Last read failed: ${next.lastError}`] : []
45817
+ ].join(`
45818
+ `);
45819
+ }
45066
45820
  async function readSecret(io, flags, flag, what, take) {
45067
45821
  const path = flagString(flags, flag);
45068
45822
  if (path === undefined || path.length === 0) {
@@ -46447,6 +47201,45 @@ var commands16 = [
46447
47201
  }
46448
47202
  ];
46449
47203
 
47204
+ // ../../packages/domain/src/date.ts
47205
+ var formatters = new Map;
47206
+ function dateTimeFormat(locale, options) {
47207
+ const key = `${locale ?? ""}|${JSON.stringify(options)}`;
47208
+ let formatter = formatters.get(key);
47209
+ if (!formatter) {
47210
+ formatter = new Intl.DateTimeFormat(locale, options);
47211
+ formatters.set(key, formatter);
47212
+ }
47213
+ return formatter;
47214
+ }
47215
+ var STYLES = ["dateStyle", "timeStyle"];
47216
+ var DATE_FIELDS = ["weekday", "year", "month", "day", ...STYLES];
47217
+ var CLOCK_PARTS = new Set(["hour", "minute", "second", "fractionalSecond", "dayPeriod"]);
47218
+ function dayInZone(now, zone) {
47219
+ return dateTimeFormat("en-CA", {
47220
+ timeZone: zone,
47221
+ year: "numeric",
47222
+ month: "2-digit",
47223
+ day: "2-digit"
47224
+ }).format(now);
47225
+ }
47226
+ function dayOfUtc(t) {
47227
+ const d = new Date(t);
47228
+ const pad = (n) => String(n).padStart(2, "0");
47229
+ return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}`;
47230
+ }
47231
+ function utcOfDay(day) {
47232
+ const [y, m, d] = day.split("-").map(Number);
47233
+ return Date.UTC(y, m - 1, d);
47234
+ }
47235
+ function shiftDay2(day, n) {
47236
+ return dayOfUtc(utcOfDay(day) + n * 86400000);
47237
+ }
47238
+ function mondayOfDay(day) {
47239
+ const weekday = (new Date(utcOfDay(day)).getUTCDay() + 6) % 7;
47240
+ return shiftDay2(day, -weekday);
47241
+ }
47242
+
46450
47243
  // src/commands/food.ts
46451
47244
  var commands17 = [
46452
47245
  {
@@ -46606,6 +47399,86 @@ var commands17 = [
46606
47399
  };
46607
47400
  }
46608
47401
  },
47402
+ {
47403
+ spec: {
47404
+ path: ["food", "menu"],
47405
+ summary: "Show the week menu: each day's dinner and lunch with who cooks and its state, the counts, the leftovers and next week's draft.",
47406
+ args: [],
47407
+ flags: [
47408
+ {
47409
+ name: "start",
47410
+ type: "string",
47411
+ description: "Any day of the week (YYYY-MM-DD); this week by default."
47412
+ }
47413
+ ],
47414
+ scopes: [...mealPlanOperations.week.scopes],
47415
+ returns: "MealWeek",
47416
+ notes: `${routeOf(mealPlanOperations.week)}. A draft dinner's \`why\` is its plan's notes; write drafts with \`do meal-plans create\` and state "draft".`
47417
+ },
47418
+ run: async ({ flags, requireClient }) => {
47419
+ const start = flagString(flags, "start");
47420
+ return {
47421
+ data: await requireClient().get(pathOf(mealPlanOperations.week), {
47422
+ query: { start }
47423
+ }),
47424
+ render: renderRecord
47425
+ };
47426
+ }
47427
+ },
47428
+ {
47429
+ spec: {
47430
+ path: ["food", "plans"],
47431
+ summary: "List meal plans, by recipe and state: --recipe <id> --state cooked is who cooked it when.",
47432
+ args: [],
47433
+ flags: [
47434
+ { name: "recipe", type: "string", description: "Only this recipe's plans (its ULID)." },
47435
+ {
47436
+ name: "state",
47437
+ type: "string",
47438
+ description: "Only this state: planned, cooked, out, unsure or draft."
47439
+ }
47440
+ ],
47441
+ scopes: [...mealPlanOperations.list.scopes],
47442
+ returns: "MealPlan",
47443
+ notes: `${routeOf(mealPlanOperations.list)}. \`cookUserId\` is who cooked it; \`notes\` is the plan's note.`
47444
+ },
47445
+ run: async ({ flags, requireClient }) => {
47446
+ const query = parseInput(mealPlanListQuerySchema, {
47447
+ recipeId: flagString(flags, "recipe"),
47448
+ state: flagString(flags, "state")
47449
+ });
47450
+ return {
47451
+ data: await requireClient().get(pathOf(mealPlanOperations.list), { query }),
47452
+ render: renderRecord
47453
+ };
47454
+ }
47455
+ },
47456
+ {
47457
+ spec: {
47458
+ path: ["food", "draft", "accept"],
47459
+ summary: "Accept a week's draft menu: its draft dinners become planned ones.",
47460
+ args: [],
47461
+ flags: [
47462
+ {
47463
+ name: "start",
47464
+ type: "string",
47465
+ description: "Any day of the week (YYYY-MM-DD); next week by default."
47466
+ }
47467
+ ],
47468
+ scopes: [...mealPlanOperations.acceptDraft.scopes],
47469
+ returns: "MealWeek",
47470
+ notes: `${routeOf(mealPlanOperations.acceptDraft)}. Idempotent: a week with no drafts left is unchanged.`
47471
+ },
47472
+ run: async ({ flags, requireClient }) => {
47473
+ const zone = Intl.DateTimeFormat().resolvedOptions().timeZone;
47474
+ const start = flagString(flags, "start") ?? shiftDay2(mondayOfDay(dayInZone(new Date, zone)), 7);
47475
+ const body = parseInput(acceptMealDraftInputSchema, { start });
47476
+ return {
47477
+ data: await requireClient().post(pathOf(mealPlanOperations.acceptDraft), body),
47478
+ render: renderRecord
47479
+ };
47480
+ }
47481
+ },
46609
47482
  {
46610
47483
  spec: {
46611
47484
  path: ["food", "week-read"],
@@ -47052,9 +47925,19 @@ var SCHEDULE_FLAGS = [
47052
47925
  type: "string",
47053
47926
  description: "Due monthly on this day of the month: 1\u201331 (a 31 falls on a shorter month's last day), or last."
47054
47927
  },
47928
+ {
47929
+ name: "time",
47930
+ type: "string",
47931
+ description: "When in the day: a 24-hour time (16:30) or a part of day (morning, midday, evening); none clears it."
47932
+ },
47933
+ {
47934
+ name: "for",
47935
+ type: "string",
47936
+ description: "How long it takes (15m, 1h, 1h30m; 1\u2013600 minutes); none clears it back to 15m."
47937
+ },
47055
47938
  { name: "data", type: "string", description: "JSON for any other field of the contract." }
47056
47939
  ];
47057
- var SCHEDULE_NOTES = "One of --every-day, --on mo,we,fr, --every 3d|2w [--from YYYY-MM-DD], --monthly 1|last sets its schedule; days it isn't due never break the streak. Without one, it keeps `period` + `frequencyCount` (3\xD7 a week). The reply's `nextDue` lists its next 4 due days.";
47940
+ var SCHEDULE_NOTES = "One of --every-day, --on mo,we,fr, --every 3d|2w [--from YYYY-MM-DD], --monthly 1|last sets its schedule; days it isn't due never break the streak. Without one, it keeps `period` + `frequencyCount` (3\xD7 a week). The reply's `nextDue` lists its next 4 due days. --time 16:30|morning|midday|evening and --for 20m put it on your do calendar at that time on each due day (a part of day at 08:00, 12:30 or 19:00; 15 minutes without --for).";
47058
47941
  function localToday() {
47059
47942
  const now = new Date;
47060
47943
  const pad = (n) => String(n).padStart(2, "0");
@@ -47094,11 +47977,31 @@ function scheduleFromFlags(flags, today = localToday()) {
47094
47977
  }
47095
47978
  return;
47096
47979
  }
47980
+ function minutesFromFlag(raw) {
47981
+ const text = raw.trim().toLowerCase();
47982
+ const m = /^(?:(\d+)\s*h)?\s*(?:(\d+)\s*m?)?$/.exec(text);
47983
+ if (!text || !m || m[1] === undefined && m[2] === undefined)
47984
+ throw usageError("--for takes a length like 15m, 1h or 1h30m");
47985
+ return Number(m[1] ?? 0) * 60 + Number(m[2] ?? 0);
47986
+ }
47987
+ function timingFromFlags(flags) {
47988
+ const time = flagString(flags, "time");
47989
+ const length = flagString(flags, "for");
47990
+ const out = {};
47991
+ if (time !== undefined) {
47992
+ const t = time.trim().toLowerCase();
47993
+ out.timeOfDay = t === "none" ? null : /^\d:\d\d$/.test(t) ? `0${t}` : t;
47994
+ }
47995
+ if (length !== undefined) {
47996
+ out.durationMinutes = length.trim().toLowerCase() === "none" ? null : minutesFromFlag(length);
47997
+ }
47998
+ return out;
47999
+ }
47097
48000
  var commands20 = [
47098
48001
  {
47099
48002
  spec: {
47100
48003
  path: ["habits", "create"],
47101
- summary: "Create a habit, with an optional schedule (every day, weekdays, every N, monthly).",
48004
+ summary: "Create a habit, with an optional schedule (every day, weekdays, every N, monthly), time and length.",
47102
48005
  args: [{ name: "title", required: false, description: "The habit's title." }],
47103
48006
  flags: SCHEDULE_FLAGS,
47104
48007
  scopes: [...habitOperations.create.scopes],
@@ -47113,7 +48016,8 @@ var commands20 = [
47113
48016
  measurementType: "binary",
47114
48017
  ...title ? { title } : {},
47115
48018
  ...readDataFlag(flags),
47116
- ...schedule ? { schedule } : {}
48019
+ ...schedule ? { schedule } : {},
48020
+ ...timingFromFlags(flags)
47117
48021
  });
47118
48022
  if (flagBool(flags, "dry-run")) {
47119
48023
  return {
@@ -47130,7 +48034,7 @@ var commands20 = [
47130
48034
  {
47131
48035
  spec: {
47132
48036
  path: ["habits", "update"],
47133
- summary: "Change a habit: its schedule (--every-day, --on, --every, --monthly) or --data.",
48037
+ summary: "Change a habit: its schedule (--every-day, --on, --every, --monthly), --time, --for or --data.",
47134
48038
  args: [{ name: "id", required: true, description: "Habit ULID." }],
47135
48039
  flags: SCHEDULE_FLAGS,
47136
48040
  scopes: [...habitOperations.update.scopes],
@@ -47142,10 +48046,11 @@ var commands20 = [
47142
48046
  const schedule = scheduleFromFlags(flags);
47143
48047
  const body = parseInput(updateHabitInputSchema, {
47144
48048
  ...readDataFlag(flags),
47145
- ...schedule ? { schedule } : {}
48049
+ ...schedule ? { schedule } : {},
48050
+ ...timingFromFlags(flags)
47146
48051
  });
47147
48052
  if (Object.keys(body).length === 0)
47148
- throw usageError("nothing to change \u2014 pass a schedule flag such as --on mo,we, or --data");
48053
+ throw usageError("nothing to change \u2014 pass a schedule flag such as --on mo,we, --time, --for, or --data");
47149
48054
  const client = requireClient();
47150
48055
  if (flagBool(flags, "dry-run")) {
47151
48056
  const before = await client.get(pathOf(habitOperations.get, { id }));
@@ -47322,10 +48227,39 @@ var commands20 = [
47322
48227
  flags: [],
47323
48228
  scopes: [...habitOperations.today.scopes],
47324
48229
  returns: "HabitsTodayResponse",
47325
- notes: `${routeOf(habitOperations.today)}. Your saved time zone's day; already filtered by schedule. Each habit says done/open/skipped (never overdue), its value today, the week for an N-per-week habit and its last check-in (\`by\` names the Agent). Quit habits are left out.`
48230
+ notes: `${routeOf(habitOperations.today)}. Your saved time zone's day; already filtered by schedule. Each habit says done/open/skipped (never overdue), its value today, the week for an N-per-week habit and its last check-in (\`by\` names the Agent). Quit habits are left out of \`groups\`. The Habits section rides along: \`moments\` (morning, day, evening; each habit inside its routine, with \`scheduleWords\`, today's \`amount\`, a 14-day \`strip\` oldest first where a day it isn't due is \`not_due\`, never \`missed\`, its \`streak\` in days, in a row or since, and its \`routineStep\`), \`notDue\` (every live, unpaused habit off today, by \`nextDue\`), this Monday-to-Sunday \`week\` (due habits only; a day after today has \`done: null\`) and \`totals\`.`
47326
48231
  },
47327
48232
  run: readOperation(habitOperations.today, renderHabitsToday)
47328
48233
  },
48234
+ {
48235
+ spec: {
48236
+ path: ["habits", "due"],
48237
+ summary: "Each habit's due days in a range, by its schedule (Plan's Month and agendas).",
48238
+ args: [],
48239
+ flags: [
48240
+ { name: "from", type: "string", description: "The first day (YYYY-MM-DD). Required." },
48241
+ {
48242
+ name: "to",
48243
+ type: "string",
48244
+ description: "The last day (YYYY-MM-DD), at most 62 days on. Required."
48245
+ }
48246
+ ],
48247
+ scopes: [...habitOperations.due.scopes],
48248
+ returns: "HabitDueDate[]",
48249
+ notes: `${routeOf(habitOperations.due)}?from=&to=. Your saved time zone's days, both ends included. One row per habit per due day: \`time\` (HH:MM or null), \`scheduleLabel\`, \`daily\` (due every day) and \`streakLabel\` (\`N days\` daily, \`N in a row\` otherwise). Paused days, quit and event-scheduled habits are left out.`
48250
+ },
48251
+ run: async ({ flags, requireClient }) => {
48252
+ const from = flagString(flags, "from");
48253
+ const to = flagString(flags, "to");
48254
+ if (from === undefined || to === undefined) {
48255
+ throw usageError("habits due needs --from and --to");
48256
+ }
48257
+ return {
48258
+ data: await requireClient().get(pathOf(habitOperations.due), { query: { from, to } }),
48259
+ render: renderList
48260
+ };
48261
+ }
48262
+ },
47329
48263
  {
47330
48264
  spec: {
47331
48265
  path: ["habit", "check-ins"],
@@ -47388,6 +48322,32 @@ var commands20 = [
47388
48322
  };
47389
48323
  }
47390
48324
  },
48325
+ {
48326
+ spec: {
48327
+ path: ["habit", "undo"],
48328
+ summary: "Undo a wrong or test check-in (or a skip); the streak recomputes.",
48329
+ args: [
48330
+ { name: "id", required: true, description: "Habit ULID." },
48331
+ {
48332
+ name: "entry-id",
48333
+ required: true,
48334
+ description: "The check-in's ULID (`do habit check-ins <id>`, or lastCheckIn.id)."
48335
+ }
48336
+ ],
48337
+ flags: [],
48338
+ scopes: [...habitOperations.removeCheckIn.scopes],
48339
+ returns: "HabitSummary",
48340
+ notes: `${routeOf(habitOperations.removeCheckIn)}. The check-in must be this habit's: another habit's, or one already gone, is a 404. Prints the refreshed habit.`
48341
+ },
48342
+ run: async ({ args, requireClient }) => {
48343
+ const id = requireArg(args, 0, "habit id");
48344
+ const entryId = requireArg(args, 1, "check-in id");
48345
+ return {
48346
+ data: await requireClient().delete(pathOf(habitOperations.removeCheckIn, { id, entryId })),
48347
+ render: renderHabit
48348
+ };
48349
+ }
48350
+ },
47391
48351
  {
47392
48352
  spec: {
47393
48353
  path: ["habit", "skip"],
@@ -47481,25 +48441,23 @@ var commands20 = [
47481
48441
  {
47482
48442
  spec: {
47483
48443
  path: ["habit", "pause"],
47484
- summary: "Pause a habit until the day it's back: off Now, no push, the plant waits.",
48444
+ summary: "Pause a habit until the day it's back, or until resumed: off Now, no push, the plant waits.",
47485
48445
  args: [{ name: "id", required: true, description: "Habit ULID." }],
47486
48446
  flags: [
47487
48447
  {
47488
48448
  name: "until",
47489
48449
  type: "string",
47490
- description: "The day it's back (YYYY-MM-DD), after today. Required."
48450
+ description: "The day it's back (YYYY-MM-DD), after today. Leave it out to pause until `habit resume`."
47491
48451
  }
47492
48452
  ],
47493
48453
  scopes: [...habitOperations.pause.scopes],
47494
48454
  returns: "HabitSummary",
47495
- notes: `${routeOf(habitOperations.pause)}. Pausing a paused habit moves the day it's back. Paused days are neutral: the streak is frozen, never broken.`
48455
+ notes: `${routeOf(habitOperations.pause)}. Without --until the pause is open-ended: it lasts until \`habit resume\`. Pausing a paused habit moves the day it's back. Paused days are neutral: the streak is frozen, never broken.`
47496
48456
  },
47497
48457
  run: async ({ args, flags, requireClient }) => {
47498
48458
  const id = requireArg(args, 0, "habit id");
47499
48459
  const until = flagString(flags, "until");
47500
- if (!until)
47501
- throw usageError("habit pause needs --until <YYYY-MM-DD>, the day it's back");
47502
- const input = parseInput(habitPauseInputSchema, { until });
48460
+ const input = parseInput(habitPauseInputSchema, until ? { until } : {});
47503
48461
  return {
47504
48462
  data: await requireClient().post(pathOf(habitOperations.pause, { id }), input),
47505
48463
  render: renderHabit
@@ -49596,6 +50554,17 @@ var commands25 = [
49596
50554
  ];
49597
50555
 
49598
50556
  // src/commands/keys.ts
50557
+ function renderCreatedKey(data, mode) {
50558
+ const key = data.data ?? data;
50559
+ if (!key?.key)
50560
+ return renderRecord(data, mode);
50561
+ return [
50562
+ `${key.name}: ${key.scopeLine || "(no scopes)"}`,
50563
+ key.expiresAt ? `Expires ${key.expiresAt.slice(0, 10)}.` : "Never expires.",
50564
+ `Key (shown once): ${key.key}`
50565
+ ].join(`
50566
+ `);
50567
+ }
49599
50568
  var commands26 = [
49600
50569
  {
49601
50570
  spec: {
@@ -49609,6 +50578,57 @@ var commands26 = [
49609
50578
  },
49610
50579
  run: readOperation(keyOperations.list, renderKeys, {}, { paged: true })
49611
50580
  },
50581
+ {
50582
+ spec: {
50583
+ path: ["keys", "create"],
50584
+ summary: "Mint an API key by section, each read or write, with an optional expiry. Prints the key once.",
50585
+ args: [],
50586
+ flags: [
50587
+ { name: "name", type: "string", description: "What the key is for (e.g. 'CI deploy')." },
50588
+ {
50589
+ name: "section",
50590
+ type: "string",
50591
+ description: "A section and its access, <section>:<read|write>, repeated: today, plan, work, habits, journal, home, food, collection, money, health, people, animals, agents, settings."
50592
+ },
50593
+ {
50594
+ name: "expires",
50595
+ type: "string",
50596
+ description: "When it stops working, a date (YYYY-MM-DD). Left out: never."
50597
+ }
50598
+ ],
50599
+ scopes: [...keyOperations.create.scopes],
50600
+ returns: "ApiKeyCreated",
50601
+ notes: "The key is for the member behind this key, never more than this key holds (exit 3), never an Agent's. The secret is printed once, then never again; past its expiry the key gets 401 'This token expired on <date>'."
50602
+ },
50603
+ run: async ({ flags, requireClient }) => {
50604
+ const name = flagString(flags, "name");
50605
+ if (!name)
50606
+ throw usageError("keys create needs --name <name>");
50607
+ const sections = flagStrings(flags, "section").map((raw) => {
50608
+ const [section, access] = raw.split(":").map((part) => part.trim());
50609
+ if (!section || access !== "read" && access !== "write") {
50610
+ throw usageError(`--section takes <section>:<read|write>, not '${raw}'`);
50611
+ }
50612
+ return { section, access };
50613
+ });
50614
+ if (sections.length === 0) {
50615
+ throw usageError("keys create needs at least one --section <section>:<read|write>");
50616
+ }
50617
+ const expires = flagString(flags, "expires");
50618
+ if (expires !== undefined && !/^\d{4}-\d{2}-\d{2}$/.test(expires)) {
50619
+ throw usageError("--expires takes a date, YYYY-MM-DD");
50620
+ }
50621
+ const body = {
50622
+ name,
50623
+ sections,
50624
+ ...expires ? { expiresAt: `${expires}T00:00:00.000Z` } : {}
50625
+ };
50626
+ return {
50627
+ data: await requireClient().post(pathOf(keyOperations.create), body),
50628
+ render: renderCreatedKey
50629
+ };
50630
+ }
50631
+ },
49612
50632
  {
49613
50633
  spec: {
49614
50634
  path: ["keys", "update"],
@@ -49629,7 +50649,7 @@ var commands26 = [
49629
50649
  ],
49630
50650
  scopes: [...keyOperations.update.scopes],
49631
50651
  returns: "ApiKey",
49632
- notes: "A key never grants more than it holds: it can't widen its own scopes or change a key that reaches further (exit 3). Every change is recorded on the key. Minting and revoking stay in the app."
50652
+ notes: "A key never grants more than it holds: it can't widen its own scopes or change a key that reaches further (exit 3). Every change is recorded on the key. Revoking stays in the app."
49633
50653
  },
49634
50654
  run: async ({ args, flags, requireClient }) => {
49635
50655
  const id = requireArg(args, 0, "key id");
@@ -53622,7 +54642,9 @@ function renderRoutinesToday(data, mode) {
53622
54642
  const steps = r.steps.map((s) => {
53623
54643
  const meta = s.meta ? paint(mode, "dim", ` \xB7 ${s.meta}`) : "";
53624
54644
  const kind = s.kind === "step" ? "" : ` [${s.kind}]`;
53625
- return ` ${s.done ? "\u2713" : "\u25CB"} ${s.n}. ${s.title}${kind}${meta}`;
54645
+ const optional = s.optional ? " (optional)" : "";
54646
+ const indent = " ".repeat(s.depth ?? 0);
54647
+ return ` ${indent}${s.done ? "\u2713" : "\u25CB"} ${s.n}. ${s.title}${kind}${optional}${meta}`;
53626
54648
  });
53627
54649
  return [head, ...steps].join(`
53628
54650
  `);
@@ -53634,7 +54656,418 @@ function renderRoutineTaskAdded(data) {
53634
54656
  const last = run.steps?.at(-1)?.title ?? "the task";
53635
54657
  return `${run.routineTitle ?? "(routine)"}: ${last} is the last step of today's run.`;
53636
54658
  }
54659
+ var STEP_GRAMMAR = "A step is `habit:<habit id>` (a habit step, titled after the habit), `task:<task id>` (a Work task step, titled after the task), `routine:<routine id>` (a nested routine, titled after it: a run walks its steps in this place) or any other text (a plain step with that title).";
54660
+ var ROUTINE_FLAGS = [
54661
+ { name: "kind", type: "string", description: "sequence, time_blocked or checklist." },
54662
+ { name: "area", type: "string", description: "The Area's ULID (`none` unfiles it)." },
54663
+ { name: "notes", type: "string", description: "The routine's notes." },
54664
+ { name: "data", type: "string", description: "JSON (or @file) with the rest of the input." }
54665
+ ];
54666
+ function routineFields(flags) {
54667
+ const out = { ...readDataFlag(flags) };
54668
+ const title = flagString(flags, "title");
54669
+ const kind = flagString(flags, "kind");
54670
+ const area = flagString(flags, "area");
54671
+ const notes = flagString(flags, "notes");
54672
+ if (title !== undefined)
54673
+ out.title = title;
54674
+ if (kind !== undefined)
54675
+ out.kind = kind;
54676
+ if (area !== undefined)
54677
+ out.areaId = area === "none" ? null : area;
54678
+ if (notes !== undefined)
54679
+ out.notes = notes;
54680
+ return out;
54681
+ }
54682
+ var LINK_PATH = {
54683
+ habit: habitOperations.get,
54684
+ task: taskOperations.get,
54685
+ routine: routineOperations.get
54686
+ };
54687
+ async function stepFrom(client, item, title) {
54688
+ const link = /^(habit|task|routine):(\S+)$/.exec(item.trim());
54689
+ if (!link) {
54690
+ const text = (title ?? item).trim();
54691
+ if (!text)
54692
+ throw usageError("a step needs a title");
54693
+ return { id: ulid3(), title: text, refKind: "free" };
54694
+ }
54695
+ const kind = link[1];
54696
+ const id = link[2];
54697
+ const named = title ?? (await client.get(pathOf(LINK_PATH[kind], { id }))).title ?? kind;
54698
+ if (kind === "habit")
54699
+ return { id: ulid3(), title: named, refKind: "habit", habitId: id };
54700
+ if (kind === "task")
54701
+ return { id: ulid3(), title: named, refKind: "task", taskId: id };
54702
+ return { id: ulid3(), title: named, refKind: "routine", childRoutineId: id };
54703
+ }
54704
+ function stepInput(step, sortOrder) {
54705
+ return {
54706
+ id: step.id,
54707
+ title: step.title,
54708
+ refKind: step.refKind,
54709
+ habitId: step.refKind === "habit" ? step.habitId : null,
54710
+ taskId: step.refKind === "task" ? step.taskId : null,
54711
+ childRoutineId: step.refKind === "routine" ? step.childRoutineId : null,
54712
+ optional: step.optional,
54713
+ sortOrder,
54714
+ durationMinutes: step.durationMinutes,
54715
+ notes: step.notes
54716
+ };
54717
+ }
54718
+ function stepIndex(steps, token) {
54719
+ const byId = steps.findIndex((s) => s.id === token);
54720
+ if (byId >= 0)
54721
+ return byId;
54722
+ const n = Number(token);
54723
+ if (Number.isInteger(n) && n >= 1 && n <= steps.length)
54724
+ return n - 1;
54725
+ throw usageError(`no step ${token} in this routine \u2014 name its id or its number`);
54726
+ }
54727
+ function positionOf(raw, max, name) {
54728
+ const n = Number(raw);
54729
+ if (!Number.isInteger(n) || n < 1 || n > max) {
54730
+ throw usageError(`${name} must be a number from 1 to ${max}`);
54731
+ }
54732
+ return n - 1;
54733
+ }
54734
+ function renderRoutine(data) {
54735
+ const r = data;
54736
+ const steps = (r.steps ?? []).map((s, i) => {
54737
+ const kind = s.refKind === "free" ? "" : ` [${s.refKind}]`;
54738
+ const optional = s.optional ? " (optional)" : "";
54739
+ return ` ${i + 1}. ${s.title}${kind}${optional} ${s.id}`;
54740
+ });
54741
+ return [
54742
+ `${r.title ?? "(routine)"} ${r.id}`,
54743
+ ...steps.length ? steps : [" (no steps)"]
54744
+ ].join(`
54745
+ `);
54746
+ }
54747
+ function putSteps(client, id, steps) {
54748
+ const body = parseInput(updateRoutineInputSchema, {
54749
+ steps: steps.map((s, i) => ({ ...s, sortOrder: i }))
54750
+ });
54751
+ return client.patch(pathOf(routineOperations.update, { id }), body);
54752
+ }
54753
+ function ruleAt(recurrence, at, timezone) {
54754
+ if (!/^([01]\d|2[0-3]):[0-5]\d$/.test(at))
54755
+ throw usageError("--at takes a 24-hour time: 07:30");
54756
+ if (!recurrence)
54757
+ return { mode: "rigid", freq: "daily", interval: 1, timezone, timeOfDay: at };
54758
+ const keep = {
54759
+ mode: recurrence.mode,
54760
+ timezone: recurrence.timezone,
54761
+ interval: recurrence.interval,
54762
+ freq: recurrence.freq,
54763
+ byDay: recurrence.byDay,
54764
+ until: recurrence.until,
54765
+ count: recurrence.count,
54766
+ unit: recurrence.unit,
54767
+ anchor: recurrence.anchor
54768
+ };
54769
+ return {
54770
+ ...Object.fromEntries(Object.entries(keep).filter(([, v]) => v !== null && v !== undefined)),
54771
+ timeOfDay: at
54772
+ };
54773
+ }
54774
+ function localZone() {
54775
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
54776
+ }
54777
+ function readRoutine(client, id) {
54778
+ return client.get(pathOf(routineOperations.get, { id }));
54779
+ }
54780
+ var routineCommands = [
54781
+ {
54782
+ spec: {
54783
+ path: ["routines", "create"],
54784
+ summary: "Create a routine with its steps.",
54785
+ args: [{ name: "title", required: false, description: "The routine's title." }],
54786
+ flags: [
54787
+ ...ROUTINE_FLAGS,
54788
+ { name: "step", type: "string", description: "A step, in order; repeat it. See grammar." }
54789
+ ],
54790
+ scopes: [...routineOperations.create.scopes],
54791
+ returns: "RoutineView",
54792
+ grammar: { step: STEP_GRAMMAR },
54793
+ notes: `${routeOf(routineOperations.create)}. The id is minted when omitted; --data carries the rest of a CreateRoutineInput. A linked step's title is read from its habit or task. e.g. \`do routines create "Wind down" --step "Lights low" --step habit:<id>\`.`
54794
+ },
54795
+ run: async ({ args, flags, requireClient }) => {
54796
+ const title = args.join(" ").trim();
54797
+ const client = requireClient();
54798
+ const steps = [];
54799
+ for (const item of flagStrings(flags, "step"))
54800
+ steps.push(await stepFrom(client, item));
54801
+ const body = parseInput(createRoutineInputSchema, {
54802
+ id: ulid3(),
54803
+ ...title ? { title } : {},
54804
+ ...routineFields(flags),
54805
+ ...steps.length ? { steps: steps.map((s, i) => ({ ...s, sortOrder: i })) } : {}
54806
+ });
54807
+ if (flagBool(flags, "dry-run")) {
54808
+ return {
54809
+ data: { dryRun: true, op: "create", entity: "routines", after: body },
54810
+ render: renderRecord
54811
+ };
54812
+ }
54813
+ return {
54814
+ data: await client.post(pathOf(routineOperations.create), body),
54815
+ render: renderRoutine
54816
+ };
54817
+ }
54818
+ },
54819
+ {
54820
+ spec: {
54821
+ path: ["routines", "update"],
54822
+ summary: "Change a routine: its title, kind, Area, notes, start time (--at), or --data.",
54823
+ args: [{ name: "id", required: true, description: "Routine ULID." }],
54824
+ flags: [
54825
+ { name: "title", type: "string", description: "A new title." },
54826
+ {
54827
+ name: "at",
54828
+ type: "string",
54829
+ description: "Its start time, 24-hour (07:30); one that doesn't repeat starts daily."
54830
+ },
54831
+ ...ROUTINE_FLAGS
54832
+ ],
54833
+ scopes: [...routineOperations.update.scopes],
54834
+ returns: "RoutineView",
54835
+ notes: `${routeOf(routineOperations.update)}; --at is ${routeOf(routineOperations.attachRecurrence)} with the routine's rule as it is and its time moved (a routine that doesn't repeat yet becomes daily at that time, in this machine's zone). Only what you pass changes; its steps change with \`do routines steps\`. A routine at a time lands on your do calendar, each child routine and own step as its own event.`
54836
+ },
54837
+ run: async ({ args, flags, requireClient }) => {
54838
+ const id = requireArg(args, 0, "routine id");
54839
+ const at = flagString(flags, "at");
54840
+ const body = parseInput(updateRoutineInputSchema, routineFields(flags));
54841
+ if (Object.keys(body).length === 0 && at === undefined) {
54842
+ throw usageError("nothing to change \u2014 pass --title, --kind, --area, --notes, --at or --data");
54843
+ }
54844
+ const client = requireClient();
54845
+ if (at !== undefined) {
54846
+ const before = await readRoutine(client, id);
54847
+ const rule = parseInput(updateRecurrenceRuleInputSchema, ruleAt(before.recurrence, at.trim(), localZone()));
54848
+ if (flagBool(flags, "dry-run")) {
54849
+ return {
54850
+ data: {
54851
+ dryRun: true,
54852
+ op: "update",
54853
+ entity: "routines",
54854
+ id,
54855
+ before,
54856
+ recurrence: rule,
54857
+ changes: body
54858
+ },
54859
+ render: renderRecord
54860
+ };
54861
+ }
54862
+ const timed = await client.put(pathOf(routineOperations.attachRecurrence, { id }), rule);
54863
+ if (Object.keys(body).length === 0)
54864
+ return { data: timed, render: renderRoutine };
54865
+ return {
54866
+ data: await client.patch(pathOf(routineOperations.update, { id }), body),
54867
+ render: renderRoutine
54868
+ };
54869
+ }
54870
+ if (flagBool(flags, "dry-run")) {
54871
+ const before = await readRoutine(client, id);
54872
+ return {
54873
+ data: {
54874
+ dryRun: true,
54875
+ op: "update",
54876
+ entity: "routines",
54877
+ id,
54878
+ before,
54879
+ after: mergePreview(before, body),
54880
+ changes: body
54881
+ },
54882
+ render: renderRecord
54883
+ };
54884
+ }
54885
+ return {
54886
+ data: await client.patch(pathOf(routineOperations.update, { id }), body),
54887
+ render: renderRoutine
54888
+ };
54889
+ }
54890
+ },
54891
+ {
54892
+ spec: {
54893
+ path: ["routines", "steps", "set"],
54894
+ summary: "Replace a routine's steps with a new ordered list, or --clear them all.",
54895
+ args: [
54896
+ { name: "id", required: true, description: "Routine ULID." },
54897
+ { name: "steps", required: false, description: "The steps, in order. See grammar." }
54898
+ ],
54899
+ flags: [{ name: "clear", type: "boolean", description: "Empty the routine (no steps)." }],
54900
+ scopes: [...routineOperations.update.scopes],
54901
+ returns: "RoutineView",
54902
+ grammar: { step: STEP_GRAMMAR },
54903
+ notes: `${routeOf(routineOperations.update)} with \`steps\`, in one call. The habits and tasks the old steps linked are untouched; past runs keep their snapshot.`
54904
+ },
54905
+ run: async ({ args, flags, requireClient }) => {
54906
+ const id = requireArg(args, 0, "routine id");
54907
+ const items = args.slice(1);
54908
+ const clear = flagBool(flags, "clear");
54909
+ if (items.length === 0 && !clear) {
54910
+ throw usageError("routines steps set needs the steps in order, or --clear to empty it");
54911
+ }
54912
+ if (items.length > 0 && clear)
54913
+ throw usageError("--clear takes no steps");
54914
+ const client = requireClient();
54915
+ const steps = [];
54916
+ for (const item of items)
54917
+ steps.push(await stepFrom(client, item));
54918
+ return { data: await putSteps(client, id, steps), render: renderRoutine };
54919
+ }
54920
+ },
54921
+ {
54922
+ spec: {
54923
+ path: ["routines", "steps", "add"],
54924
+ summary: "Add a step to a routine (or a nested routine), at the end or --at a position.",
54925
+ args: [
54926
+ { name: "id", required: true, description: "Routine ULID." },
54927
+ {
54928
+ name: "step",
54929
+ required: false,
54930
+ description: "The step. See grammar. Leave it out with --routine."
54931
+ }
54932
+ ],
54933
+ flags: [
54934
+ { name: "at", type: "string", description: "Its position, 1-based (default: last)." },
54935
+ { name: "title", type: "string", description: "Its title (instead of the linked name)." },
54936
+ {
54937
+ name: "routine",
54938
+ type: "string",
54939
+ description: "Nest this routine (its ULID) as the step; same as `routine:<id>`."
54940
+ },
54941
+ {
54942
+ name: "optional",
54943
+ type: "boolean",
54944
+ description: "An optional step: it never keeps the routine from being done."
54945
+ }
54946
+ ],
54947
+ scopes: [...routineOperations.get.scopes, ...routineOperations.update.scopes],
54948
+ returns: "RoutineView",
54949
+ grammar: { step: STEP_GRAMMAR },
54950
+ notes: `${routeOf(routineOperations.get)}, then ${routeOf(routineOperations.update)} with every step (the others keep their ids). A nested routine (\`--routine <id>\`) walks its steps in this place when the routine runs, to any depth; one that would contain itself is a 400. e.g. \`do routines steps add <morning> --routine <teeth> --optional\`.`
54951
+ },
54952
+ run: async ({ args, flags, requireClient }) => {
54953
+ const id = requireArg(args, 0, "routine id");
54954
+ const child = flagString(flags, "routine");
54955
+ const words = args.slice(1).join(" ").trim();
54956
+ if (child && words)
54957
+ throw usageError("--routine is the step; pass no other step with it");
54958
+ const item = child ? `routine:${child}` : words;
54959
+ if (!item)
54960
+ throw usageError("routines steps add needs the step: `habit:<id>`, `task:<id>`, `routine:<id>` (or --routine <id>) or a title");
54961
+ const client = requireClient();
54962
+ const routine = await readRoutine(client, id);
54963
+ const steps = routine.steps.map((s, i) => stepInput(s, i));
54964
+ const at = flagString(flags, "at");
54965
+ const index = at === undefined ? steps.length : positionOf(at, steps.length + 1, "--at");
54966
+ const step = await stepFrom(client, item, flagString(flags, "title"));
54967
+ if (flagBool(flags, "optional"))
54968
+ step.optional = true;
54969
+ steps.splice(index, 0, step);
54970
+ return { data: await putSteps(client, id, steps), render: renderRoutine };
54971
+ }
54972
+ },
54973
+ {
54974
+ spec: {
54975
+ path: ["routines", "steps", "rm"],
54976
+ summary: "Remove a step from a routine.",
54977
+ args: [
54978
+ { name: "id", required: true, description: "Routine ULID." },
54979
+ { name: "step", required: true, description: "The step's ULID or its number (1-based)." }
54980
+ ],
54981
+ flags: [],
54982
+ scopes: [...routineOperations.get.scopes, ...routineOperations.update.scopes],
54983
+ returns: "RoutineView",
54984
+ notes: `${routeOf(routineOperations.get)}, then ${routeOf(routineOperations.update)} without it. A habit or task it linked is untouched; past runs keep it.`
54985
+ },
54986
+ run: async ({ args, requireClient }) => {
54987
+ const id = requireArg(args, 0, "routine id");
54988
+ const token = requireArg(args, 1, "step");
54989
+ const client = requireClient();
54990
+ const routine = await readRoutine(client, id);
54991
+ const index = stepIndex(routine.steps, token);
54992
+ const steps = routine.steps.filter((_, i) => i !== index).map((s, i) => stepInput(s, i));
54993
+ return { data: await putSteps(client, id, steps), render: renderRoutine };
54994
+ }
54995
+ },
54996
+ {
54997
+ spec: {
54998
+ path: ["routines", "steps", "move"],
54999
+ summary: "Move a routine's step to a new position.",
55000
+ args: [
55001
+ { name: "id", required: true, description: "Routine ULID." },
55002
+ { name: "step", required: true, description: "The step's ULID or its number (1-based)." },
55003
+ { name: "position", required: true, description: "Where it goes, 1-based." }
55004
+ ],
55005
+ flags: [],
55006
+ scopes: [...routineOperations.get.scopes, ...routineOperations.reorderSteps.scopes],
55007
+ returns: "RoutineView",
55008
+ notes: `${routeOf(routineOperations.get)}, then ${routeOf(routineOperations.reorderSteps)}. Runs already started keep their order.`
55009
+ },
55010
+ run: async ({ args, requireClient }) => {
55011
+ const id = requireArg(args, 0, "routine id");
55012
+ const token = requireArg(args, 1, "step");
55013
+ const to = requireArg(args, 2, "position");
55014
+ const client = requireClient();
55015
+ const routine = await readRoutine(client, id);
55016
+ const from = stepIndex(routine.steps, token);
55017
+ const ids = routine.steps.map((s) => s.id);
55018
+ const [moved] = ids.splice(from, 1);
55019
+ ids.splice(positionOf(to, routine.steps.length, "position"), 0, moved);
55020
+ const input = parseInput(reorderRoutineStepsInputSchema, { stepIds: ids });
55021
+ return {
55022
+ data: await client.patch(pathOf(routineOperations.reorderSteps, { id }), input),
55023
+ render: renderRoutine
55024
+ };
55025
+ }
55026
+ },
55027
+ {
55028
+ spec: {
55029
+ path: ["routines", "archive"],
55030
+ summary: "Archive a routine: into Trash, restorable.",
55031
+ args: [{ name: "id", required: true, description: "Routine ULID." }],
55032
+ flags: [],
55033
+ scopes: [...routineOperations.archive.scopes],
55034
+ returns: "{ ok, archived }",
55035
+ notes: `${routeOf(routineOperations.archive)}. The habits and tasks its steps link are untouched.`
55036
+ },
55037
+ run: async ({ args, flags, requireClient }) => {
55038
+ const id = requireArg(args, 0, "routine id");
55039
+ const client = requireClient();
55040
+ if (flagBool(flags, "dry-run")) {
55041
+ const before = await readRoutine(client, id);
55042
+ return {
55043
+ data: { dryRun: true, op: "archive", entity: "routines", id, before, softDelete: true },
55044
+ render: renderRecord
55045
+ };
55046
+ }
55047
+ await client.post(pathOf(routineOperations.archive, { id }), {});
55048
+ return { data: { ok: true, archived: id, softDelete: true }, render: renderRecord };
55049
+ }
55050
+ },
55051
+ {
55052
+ spec: {
55053
+ path: ["routines", "rm"],
55054
+ summary: "Delete a routine (into Trash); needs --allow-destructive.",
55055
+ args: [{ name: "id", required: true, description: "Routine ULID." }],
55056
+ flags: [],
55057
+ scopes: [...routineOperations.remove.scopes],
55058
+ returns: "{ ok, deleted }",
55059
+ notes: `${routeOf(routineOperations.remove)}. The same soft delete as archive, behind the destructive guardrail.`
55060
+ },
55061
+ run: async ({ args, flags, requireClient }) => {
55062
+ const id = requireArg(args, 0, "routine id");
55063
+ assertDestructiveAllowed(flags, `delete routines ${id}`);
55064
+ await requireClient().delete(pathOf(routineOperations.remove, { id }));
55065
+ return { data: { ok: true, deleted: id, softDelete: true }, render: renderRecord };
55066
+ }
55067
+ }
55068
+ ];
53637
55069
  var commands42 = [
55070
+ ...routineCommands,
53638
55071
  {
53639
55072
  spec: {
53640
55073
  path: ["routines", "today"],
@@ -53643,7 +55076,7 @@ var commands42 = [
53643
55076
  flags: [],
53644
55077
  scopes: [...routineOperations.today.scopes],
53645
55078
  returns: "RoutinesTodayResult",
53646
- notes: `${routeOf(routineOperations.today)}. Today is your saved time zone's day. A routine is on it when its rule has it due, it has a slot today, or it ran today. Each step's \`kind\` is step, habit or task (a Work task); \`rhythm\` is its last 14 days, oldest first (all, some, none, off).`
55079
+ notes: `${routeOf(routineOperations.today)}. Today is your saved time zone's day. A routine is on it when its rule has it due, it has a slot today, or it ran today. Each step's \`kind\` is step, habit, task (a Work task) or routine (a nested routine's heading, its steps one \`depth\` deeper; V3-121); \`rhythm\` is its last 14 days, oldest first (all, some, none, off).`
53647
55080
  },
53648
55081
  run: async ({ requireClient }) => ({
53649
55082
  data: await requireClient().get(pathOf(routineOperations.today)),
@@ -55055,7 +56488,8 @@ var commands50 = [
55055
56488
  args: [],
55056
56489
  flags: [{ name: "data", type: "string", description: "JSON of fields to change." }],
55057
56490
  scopes: [...settingsOperations.update.scopes],
55058
- returns: "UserSettings"
56491
+ returns: "UserSettings",
56492
+ notes: `Appearance keys: glass (liquid|frosted|solid), followSky, nightUntilWake, largerText, reduceMotion, showStreakNumbers, themePack (do|skyrim|pokemon), themePackScope (section|everywhere). E.g. --data '{"showStreakNumbers":false}'.`
55059
56493
  },
55060
56494
  run: async ({ flags, requireClient }) => {
55061
56495
  const body = parseInput(updateUserSettingsInputSchema, readDataFlag(flags));
@@ -55264,6 +56698,119 @@ var commands53 = [
55264
56698
  })
55265
56699
  ];
55266
56700
 
56701
+ // ../../packages/domain/src/task-series.ts
56702
+ function rhythmFromWord(word) {
56703
+ const map = {
56704
+ week: { freq: "weekly", interval: 1 },
56705
+ weekly: { freq: "weekly", interval: 1 },
56706
+ fortnight: { freq: "weekly", interval: 2 },
56707
+ month: { freq: "monthly", interval: 1 },
56708
+ monthly: { freq: "monthly", interval: 1 },
56709
+ quarter: { freq: "monthly", interval: 3 },
56710
+ quarterly: { freq: "monthly", interval: 3 },
56711
+ "half-year": { freq: "monthly", interval: 6 },
56712
+ year: { freq: "yearly", interval: 1 },
56713
+ yearly: { freq: "yearly", interval: 1 }
56714
+ };
56715
+ return map[word.trim().toLowerCase()] ?? null;
56716
+ }
56717
+
56718
+ // src/commands/task-series.ts
56719
+ function renderSeries(data, mode) {
56720
+ const page = data;
56721
+ const list = Array.isArray(page) ? page : ("data" in page) ? page.data : [page];
56722
+ if (list.length === 0)
56723
+ return paint(mode, "dim", "(no task series)");
56724
+ return list.map((s) => {
56725
+ const next = s.active ? s.nextMadeOn ? `next made ${s.nextMadeOn}` : "next made" : "stopped";
56726
+ const items = s.items.map((i) => i.taskId ? `${i.label} (${i.status ?? "?"})` : `${i.label} (to come)`).join(", ");
56727
+ return ` ${s.title} ${paint(mode, "dim", s.rhythm)} ${next} ${paint(mode, "dim", s.id)}
56728
+ ${items}`;
56729
+ }).join(`
56730
+ `);
56731
+ }
56732
+ var commands54 = [
56733
+ defineCommand({
56734
+ operation: taskSeriesOperations.make,
56735
+ path: ["tasks", "make-series"],
56736
+ summary: "Make a task the first of a series: each next one is its own task.",
56737
+ args: [{ name: "task", required: true, description: "The task's ref (DO-150) or id." }],
56738
+ flags: [
56739
+ {
56740
+ name: "every",
56741
+ type: "string",
56742
+ description: "week, fortnight, month, quarter, half-year or year."
56743
+ },
56744
+ {
56745
+ name: "label",
56746
+ type: "string",
56747
+ description: "How each is named: quarter (Q1\u2026), month (October) or date (Mon 26 Oct)."
56748
+ },
56749
+ {
56750
+ name: "lead",
56751
+ type: "string",
56752
+ description: "Make each next task this many days before it's due (default 14)."
56753
+ }
56754
+ ],
56755
+ returns: "TaskSeries",
56756
+ run: async ({ args, flags, requireClient }) => {
56757
+ const task = requireArg(args, 0, "task");
56758
+ const every = flagString(flags, "every");
56759
+ const rule = every ? rhythmFromWord(every) : null;
56760
+ if (!rule) {
56761
+ throw usageError("tasks make-series needs --every week|fortnight|month|quarter|half-year|year");
56762
+ }
56763
+ const label = flagString(flags, "label");
56764
+ const lead = flagString(flags, "lead");
56765
+ const body = parseInput(makeTaskSeriesInputSchema, {
56766
+ rule,
56767
+ ...label ? { occurrenceLabel: label.trim().toLowerCase() } : {},
56768
+ ...lead !== undefined ? { leadDays: Number(lead) } : {}
56769
+ });
56770
+ return {
56771
+ data: await requireClient().post(pathOf(taskSeriesOperations.make, { id: task }), body),
56772
+ render: renderSeries
56773
+ };
56774
+ }
56775
+ }),
56776
+ defineCommand({
56777
+ operation: taskSeriesOperations.list,
56778
+ path: ["series", "list"],
56779
+ summary: "List the task series, with their rhythm and what's next.",
56780
+ returns: "TaskSeries[]",
56781
+ run: async ({ requireClient }) => ({
56782
+ data: await requireClient().get(pathOf(taskSeriesOperations.list)),
56783
+ render: renderSeries
56784
+ })
56785
+ }),
56786
+ defineCommand({
56787
+ operation: taskSeriesOperations.get,
56788
+ path: ["series", "show"],
56789
+ summary: "Show a task series: its strip of tasks and the next to come.",
56790
+ args: [{ name: "series", required: true, description: "The series' id." }],
56791
+ returns: "TaskSeries",
56792
+ run: async ({ args, requireClient }) => ({
56793
+ data: await requireClient().get(pathOf(taskSeriesOperations.get, { id: requireArg(args, 0, "series") })),
56794
+ render: renderSeries
56795
+ })
56796
+ }),
56797
+ defineCommand({
56798
+ operation: taskSeriesOperations.stop,
56799
+ path: ["series", "stop"],
56800
+ summary: "Stop a task series: no more tasks are made; the ones made stay.",
56801
+ args: [{ name: "series", required: true, description: "The series' id." }],
56802
+ returns: "{ ok, stopped }",
56803
+ run: async ({ args, requireClient }) => {
56804
+ const id = requireArg(args, 0, "series");
56805
+ await requireClient().delete(pathOf(taskSeriesOperations.stop, { id }));
56806
+ return {
56807
+ data: { ok: true, stopped: id },
56808
+ render: () => "Stopped the series. Its tasks stay."
56809
+ };
56810
+ }
56811
+ })
56812
+ ];
56813
+
55267
56814
  // src/capture.ts
55268
56815
  function parseCapture2(input, now) {
55269
56816
  const parsed = parseCapture(input, now);
@@ -55274,7 +56821,70 @@ function parseCapture2(input, now) {
55274
56821
  }
55275
56822
 
55276
56823
  // src/commands/tasks.ts
55277
- var commands54 = [
56824
+ var REPEAT_FLAGS = [
56825
+ {
56826
+ name: "repeat",
56827
+ type: "string",
56828
+ description: "Make it repeat: daily, weekly, monthly or yearly. On update it replaces the rule."
56829
+ },
56830
+ {
56831
+ name: "every",
56832
+ type: "string",
56833
+ description: "With --repeat: every N days/weeks/months (default 1)."
56834
+ },
56835
+ {
56836
+ name: "on",
56837
+ type: "string",
56838
+ description: "With --repeat weekly (or monthly): the weekdays, comma-separated: mo,th. Monthly also takes 1su (the 1st Sunday) or -1fr (the last Friday)."
56839
+ },
56840
+ {
56841
+ name: "day",
56842
+ type: "string",
56843
+ description: "With --repeat monthly: the day of the month, 1\u201331 or last (a shorter month uses its last day)."
56844
+ },
56845
+ {
56846
+ name: "after-done",
56847
+ type: "boolean",
56848
+ description: "With --repeat: the next one is due N after the last was done, not on the calendar (anchor from_completion)."
56849
+ }
56850
+ ];
56851
+ var REPEAT_DETAIL = ["every", "on", "day", "after-done"];
56852
+ function repeatFromFlags(flags) {
56853
+ const repeat = flagString(flags, "repeat");
56854
+ if (flagBool(flags, "no-repeat")) {
56855
+ if (repeat !== undefined)
56856
+ throw usageError("--repeat and --no-repeat can't be combined");
56857
+ return null;
56858
+ }
56859
+ if (repeat === undefined) {
56860
+ const stray = REPEAT_DETAIL.find((f) => flags[f] !== undefined);
56861
+ if (stray)
56862
+ throw usageError(`--${stray} goes with --repeat daily|weekly|monthly`);
56863
+ return;
56864
+ }
56865
+ const out = { freq: repeat.trim().toLowerCase() };
56866
+ const every = flagString(flags, "every");
56867
+ if (every !== undefined) {
56868
+ if (!/^\d+$/.test(every.trim()))
56869
+ throw usageError("--every must be a whole number, e.g. --every 2");
56870
+ out.interval = Number(every);
56871
+ }
56872
+ const on = flagString(flags, "on");
56873
+ if (on !== undefined) {
56874
+ out.byDay = on.split(",").map((d) => d.trim().toUpperCase()).filter((d) => d.length > 0);
56875
+ }
56876
+ const day = flagString(flags, "day");
56877
+ if (day !== undefined) {
56878
+ const d = day.trim().toLowerCase();
56879
+ if (d !== "last" && !/^-?\d+$/.test(d))
56880
+ throw usageError("--day must be 1\u201331 or last");
56881
+ out.byMonthDay = d === "last" ? -1 : Number(d);
56882
+ }
56883
+ if (flagBool(flags, "after-done"))
56884
+ out.anchor = "from_completion";
56885
+ return out;
56886
+ }
56887
+ var commands55 = [
55278
56888
  {
55279
56889
  spec: {
55280
56890
  path: ["capture"],
@@ -55331,21 +56941,21 @@ var commands54 = [
55331
56941
  title: parsed.title,
55332
56942
  tags: parsed.tags,
55333
56943
  ...parsed.starred ? { starred: parsed.starred } : {},
56944
+ ...parsed.priority ? { priority: parsed.priority } : {},
55334
56945
  ...parsed.area ? { areaName: parsed.area } : {},
55335
56946
  ...scheduledAt ? { scheduledAt } : {},
55336
56947
  ...dueAt ? { dueAt } : {},
55337
56948
  ...scheduledAt || dueAt ? { allDay } : {},
55338
- ...duration ? { durationMinutes: duration } : {}
56949
+ ...duration ? { durationMinutes: duration } : {},
56950
+ ...parsed.recurrence ? {
56951
+ recurrence: {
56952
+ freq: parsed.recurrence.freq,
56953
+ interval: parsed.recurrence.interval,
56954
+ ...parsed.recurrence.byDay ? { byDay: parsed.recurrence.byDay } : {}
56955
+ }
56956
+ } : {}
55339
56957
  });
55340
- const client = requireClient();
55341
- const task = await client.post(pathOf(taskOperations.create), input);
55342
- if (parsed.recurrence) {
55343
- const rule = parseInput(createRecurrenceRuleInputSchema, {
55344
- templateId: id,
55345
- ...parsed.recurrence
55346
- });
55347
- await client.post("/recurrence/rules", rule);
55348
- }
56958
+ const task = await requireClient().post(pathOf(taskOperations.create), input);
55349
56959
  return { data: task, render: renderTask };
55350
56960
  }
55351
56961
  },
@@ -55633,11 +57243,106 @@ var commands54 = [
55633
57243
  render: renderTask
55634
57244
  };
55635
57245
  }
57246
+ }),
57247
+ defineCommand({
57248
+ operation: taskOperations.create,
57249
+ path: ["tasks", "create"],
57250
+ summary: "Create a task; --repeat makes it repeat.",
57251
+ returns: "Task",
57252
+ args: [
57253
+ {
57254
+ name: "title",
57255
+ required: false,
57256
+ description: "The task's title (or pass it in --data)."
57257
+ }
57258
+ ],
57259
+ flags: [
57260
+ {
57261
+ name: "data",
57262
+ type: "string",
57263
+ description: "JSON CreateTaskInput (or @file) for any other field."
57264
+ },
57265
+ ...REPEAT_FLAGS
57266
+ ],
57267
+ notes: ' \u2014 e.g. `do tasks create "Descale the kettle" --repeat monthly --after-done`, `do tasks create "Gym" --repeat weekly --on mo,th`, `do tasks create "Pay rent" --repeat monthly --day 1`. The id is minted when omitted.',
57268
+ run: async ({ args, flags, requireClient }) => {
57269
+ const title = args.join(" ").trim();
57270
+ const recurrence = repeatFromFlags(flags);
57271
+ if (recurrence === null)
57272
+ throw usageError("--no-repeat is for `tasks update`");
57273
+ const body = parseInput(createTaskInputSchema, {
57274
+ id: ulid3(),
57275
+ ...readDataFlag(flags),
57276
+ ...title ? { title } : {},
57277
+ ...recurrence ? { recurrence } : {}
57278
+ });
57279
+ if (flagBool(flags, "dry-run")) {
57280
+ return {
57281
+ data: { dryRun: true, op: "create", entity: "tasks", after: body },
57282
+ render: renderRecord
57283
+ };
57284
+ }
57285
+ return {
57286
+ data: await requireClient().post(pathOf(taskOperations.create), body),
57287
+ render: renderTask
57288
+ };
57289
+ }
57290
+ }),
57291
+ defineCommand({
57292
+ operation: taskOperations.update,
57293
+ path: ["tasks", "update"],
57294
+ summary: "Change a task; --repeat sets its repeat rule, --no-repeat ends the series.",
57295
+ returns: "Task",
57296
+ args: [{ name: "id", required: true, description: "Task ULID or ref (DO-142)." }],
57297
+ flags: [
57298
+ {
57299
+ name: "data",
57300
+ type: "string",
57301
+ description: "JSON UpdateTaskInput (or @file): the fields to change."
57302
+ },
57303
+ ...REPEAT_FLAGS,
57304
+ {
57305
+ name: "no-repeat",
57306
+ type: "boolean",
57307
+ description: "Stop the series: no more occurrences; the done history stays."
57308
+ }
57309
+ ],
57310
+ notes: " \u2014 --repeat replaces the whole rule (pass every part you want kept). On a repeating task, `do task done` finishes the open occurrence and the task stays open for the next one.",
57311
+ run: async ({ args, flags, requireClient }) => {
57312
+ const id = requireArg(args, 0, "task id");
57313
+ const recurrence = repeatFromFlags(flags);
57314
+ const body = parseInput(updateTaskInputSchema, {
57315
+ ...readDataFlag(flags),
57316
+ ...recurrence !== undefined ? { recurrence } : {}
57317
+ });
57318
+ if (Object.keys(body).length === 0)
57319
+ throw usageError("nothing to change \u2014 pass --repeat, --no-repeat or --data");
57320
+ const client = requireClient();
57321
+ if (flagBool(flags, "dry-run")) {
57322
+ const before = await client.get(pathOf(taskOperations.get, { id }));
57323
+ return {
57324
+ data: {
57325
+ dryRun: true,
57326
+ op: "update",
57327
+ entity: "tasks",
57328
+ id,
57329
+ before,
57330
+ after: mergePreview(before, body),
57331
+ changes: body
57332
+ },
57333
+ render: renderRecord
57334
+ };
57335
+ }
57336
+ return {
57337
+ data: await client.patch(pathOf(taskOperations.update, { id }), body),
57338
+ render: renderTask
57339
+ };
57340
+ }
55636
57341
  })
55637
57342
  ];
55638
57343
 
55639
57344
  // src/commands/time-blocks.ts
55640
- var commands55 = [
57345
+ var commands56 = [
55641
57346
  {
55642
57347
  spec: {
55643
57348
  path: ["time-blocks", "record"],
@@ -55818,7 +57523,7 @@ function renderLandedCounts(data, mode) {
55818
57523
  return out.join(`
55819
57524
  `);
55820
57525
  }
55821
- var commands56 = [
57526
+ var commands57 = [
55822
57527
  {
55823
57528
  spec: {
55824
57529
  path: ["today"],
@@ -56190,7 +57895,7 @@ function renderViews(data, mode) {
56190
57895
  function renderView(data, mode) {
56191
57896
  return renderViews([data], mode);
56192
57897
  }
56193
- var commands57 = [
57898
+ var commands58 = [
56194
57899
  defineCommand({
56195
57900
  operation: workViewOperations.list,
56196
57901
  path: ["views", "list"],
@@ -56309,7 +58014,7 @@ var commands57 = [
56309
58014
  ];
56310
58015
 
56311
58016
  // src/commands/worktree-links.ts
56312
- var commands58 = [
58017
+ var commands59 = [
56313
58018
  {
56314
58019
  spec: {
56315
58020
  path: ["worktree-links", "list"],
@@ -56473,7 +58178,8 @@ var FEATURE_COMMANDS = [
56473
58178
  ...commands55,
56474
58179
  ...commands56,
56475
58180
  ...commands57,
56476
- ...commands58
58181
+ ...commands58,
58182
+ ...commands59
56477
58183
  ];
56478
58184
 
56479
58185
  // src/registry.ts