@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.
- package/dist/do.js +1794 -88
- 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.
|
|
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'
|
|
18136
|
-
(${t.refKind} = 'habit'
|
|
18137
|
-
(${t.refKind} = 'task'
|
|
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
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
}).
|
|
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;
|
|
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:
|
|
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
|
|
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"}
|
|
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)
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|