@robbeverhelst/do 1.341.0 → 1.343.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 +912 -98
- 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.343.0";
|
|
14422
14424
|
var API_PREFIX = "/api/v1";
|
|
14423
14425
|
var DEFAULT_API_URL = "http://localhost:3001";
|
|
14424
14426
|
|
|
@@ -18020,7 +18022,10 @@ var habits = pgTable("habits", {
|
|
|
18020
18022
|
pausedFrom: date5("paused_from", { mode: "string" }),
|
|
18021
18023
|
pausedUntil: date5("paused_until", { mode: "string" }),
|
|
18022
18024
|
remind: boolean4("remind").notNull().default(false),
|
|
18023
|
-
retiredAt: timestamp("retired_at", { withTimezone: true })
|
|
18025
|
+
retiredAt: timestamp("retired_at", { withTimezone: true }),
|
|
18026
|
+
eventSourceId: text("event_source_id"),
|
|
18027
|
+
eventMatch: text("event_match"),
|
|
18028
|
+
eventOffset: text("event_offset")
|
|
18024
18029
|
}, (t) => [
|
|
18025
18030
|
index("habits_tenant_idx").on(t.tenantId),
|
|
18026
18031
|
index("habits_tenant_area_idx").on(t.tenantId, t.areaId),
|
|
@@ -18050,6 +18055,8 @@ var recurrenceRules = pgTable("recurrence_rules", {
|
|
|
18050
18055
|
interval: integer2("interval").notNull().default(1),
|
|
18051
18056
|
freq: recurrenceFreq("freq"),
|
|
18052
18057
|
byDay: text("by_day").array(),
|
|
18058
|
+
byMonthDay: integer2("by_month_day"),
|
|
18059
|
+
startsOn: date5("starts_on", { mode: "string" }),
|
|
18053
18060
|
timeOfDay: text("time_of_day"),
|
|
18054
18061
|
until: timestamp("until", { withTimezone: true }),
|
|
18055
18062
|
count: integer2("count"),
|
|
@@ -21269,6 +21276,75 @@ var animalOperations = defineOperations({
|
|
|
21269
21276
|
// ../../packages/schema/src/contracts/habit-sources.ts
|
|
21270
21277
|
var habitSourceSchema = _enum2(["manual", "google_fit", "strava", "github"]);
|
|
21271
21278
|
|
|
21279
|
+
// ../../packages/schema/src/contracts/recurrence.ts
|
|
21280
|
+
var recurrenceRuleSchema = createSelectSchema(recurrenceRules, {
|
|
21281
|
+
until: exports_coerce.date().nullable(),
|
|
21282
|
+
byMonthDay: number2().int().nullable().default(null),
|
|
21283
|
+
startsOn: exports_iso.date().nullable().default(null)
|
|
21284
|
+
});
|
|
21285
|
+
var occurrenceSchema = createSelectSchema(occurrences, {
|
|
21286
|
+
occurrenceDate: exports_coerce.date(),
|
|
21287
|
+
completedAt: exports_coerce.date().nullable()
|
|
21288
|
+
});
|
|
21289
|
+
var byDayCodeSchema = _enum2(["MO", "TU", "WE", "TH", "FR", "SA", "SU"]);
|
|
21290
|
+
var byDayOrdinalSchema = _enum2(["1", "2", "3", "4", "-1"]);
|
|
21291
|
+
var byDaySchema = union([
|
|
21292
|
+
byDayCodeSchema,
|
|
21293
|
+
templateLiteral([byDayOrdinalSchema, byDayCodeSchema])
|
|
21294
|
+
]);
|
|
21295
|
+
var BY_DAY_RE = /^(-1|[1-4])?(MO|TU|WE|TH|FR|SA|SU)$/;
|
|
21296
|
+
function parseByDay(code) {
|
|
21297
|
+
const m = BY_DAY_RE.exec(code);
|
|
21298
|
+
if (!m)
|
|
21299
|
+
return null;
|
|
21300
|
+
return { ordinal: m[1] ? Number(m[1]) : null, weekday: m[2] };
|
|
21301
|
+
}
|
|
21302
|
+
var recurrenceRuleParams = {
|
|
21303
|
+
timezone: string2().min(1).default("UTC"),
|
|
21304
|
+
interval: number2().int().positive().default(1),
|
|
21305
|
+
mode: recurrenceModeSchema,
|
|
21306
|
+
timeOfDay: string2().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "time-of-day must be HH:MM (24h)").nullish(),
|
|
21307
|
+
freq: recurrenceFreqSchema.optional(),
|
|
21308
|
+
byDay: array(byDaySchema).optional(),
|
|
21309
|
+
until: exports_coerce.date().optional(),
|
|
21310
|
+
count: number2().int().positive().optional(),
|
|
21311
|
+
unit: recurrenceUnitSchema.optional(),
|
|
21312
|
+
anchor: recurrenceAnchorSchema.optional()
|
|
21313
|
+
};
|
|
21314
|
+
function refineRecurrenceRule(v, ctx) {
|
|
21315
|
+
if (v.mode === "rigid" && !v.freq) {
|
|
21316
|
+
ctx.addIssue({ code: "custom", path: ["freq"], message: "rigid rules require a freq" });
|
|
21317
|
+
}
|
|
21318
|
+
const ordinal = v.byDay?.findIndex((code) => parseByDay(code)?.ordinal != null) ?? -1;
|
|
21319
|
+
if (ordinal >= 0 && v.freq !== "monthly" && v.freq !== "yearly") {
|
|
21320
|
+
ctx.addIssue({
|
|
21321
|
+
code: "custom",
|
|
21322
|
+
path: ["byDay", ordinal],
|
|
21323
|
+
message: "an ordinal weekday (1SU, -1FR) needs a monthly or yearly freq"
|
|
21324
|
+
});
|
|
21325
|
+
}
|
|
21326
|
+
if (v.mode === "flexible") {
|
|
21327
|
+
if (!v.unit) {
|
|
21328
|
+
ctx.addIssue({ code: "custom", path: ["unit"], message: "flexible rules require a unit" });
|
|
21329
|
+
}
|
|
21330
|
+
if (!v.anchor) {
|
|
21331
|
+
ctx.addIssue({
|
|
21332
|
+
code: "custom",
|
|
21333
|
+
path: ["anchor"],
|
|
21334
|
+
message: "flexible rules require an anchor"
|
|
21335
|
+
});
|
|
21336
|
+
}
|
|
21337
|
+
}
|
|
21338
|
+
}
|
|
21339
|
+
var createRecurrenceRuleInputSchema = object({
|
|
21340
|
+
templateId: ulidSchema,
|
|
21341
|
+
...recurrenceRuleParams
|
|
21342
|
+
}).superRefine(refineRecurrenceRule);
|
|
21343
|
+
var updateRecurrenceRuleInputSchema = object({ ...recurrenceRuleParams }).superRefine(refineRecurrenceRule);
|
|
21344
|
+
var updateOccurrenceInputSchema = createUpdateSchema(occurrences, {
|
|
21345
|
+
occurrenceDate: exports_coerce.date().optional()
|
|
21346
|
+
}).pick({ occurrenceDate: true, status: true }).strict();
|
|
21347
|
+
|
|
21272
21348
|
// ../../packages/schema/src/contracts/habits.ts
|
|
21273
21349
|
var habitSchema = createSelectSchema(habits, {
|
|
21274
21350
|
source: habitSourceSchema.default("manual"),
|
|
@@ -21278,7 +21354,10 @@ var habitSchema = createSelectSchema(habits, {
|
|
|
21278
21354
|
pausedFrom: exports_iso.date().nullable().default(null),
|
|
21279
21355
|
pausedUntil: exports_iso.date().nullable().default(null),
|
|
21280
21356
|
remind: boolean2().default(false),
|
|
21281
|
-
retiredAt: exports_coerce.date().nullable().default(null)
|
|
21357
|
+
retiredAt: exports_coerce.date().nullable().default(null),
|
|
21358
|
+
eventSourceId: string2().nullable().default(null),
|
|
21359
|
+
eventMatch: string2().nullable().default(null),
|
|
21360
|
+
eventOffset: string2().nullable().default(null)
|
|
21282
21361
|
});
|
|
21283
21362
|
var habitEntrySchema = createSelectSchema(habitEntries, {
|
|
21284
21363
|
occurredAt: exports_coerce.date(),
|
|
@@ -21297,6 +21376,30 @@ var habitTimeOfDaySchema = union([
|
|
|
21297
21376
|
]);
|
|
21298
21377
|
var habitAliasSchema = string2().trim().min(1).max(60);
|
|
21299
21378
|
var habitAliasesSchema = array(habitAliasSchema).max(12).transform((aliases) => aliases.filter((a, i) => aliases.findIndex((b) => b.toLowerCase() === a.toLowerCase()) === i));
|
|
21379
|
+
var habitScheduleSchema = discriminatedUnion("kind", [
|
|
21380
|
+
object({ kind: literal("daily") }),
|
|
21381
|
+
object({
|
|
21382
|
+
kind: literal("weekdays"),
|
|
21383
|
+
days: array(byDayCodeSchema).min(1).max(7).refine((days) => new Set(days).size === days.length, "each weekday once")
|
|
21384
|
+
}),
|
|
21385
|
+
object({
|
|
21386
|
+
kind: literal("every"),
|
|
21387
|
+
interval: number2().int().min(2),
|
|
21388
|
+
unit: _enum2(["day", "week"]),
|
|
21389
|
+
anchor: exports_iso.date()
|
|
21390
|
+
}),
|
|
21391
|
+
object({
|
|
21392
|
+
kind: literal("monthly"),
|
|
21393
|
+
dayOfMonth: union([number2().int().min(1).max(31), literal(-1)])
|
|
21394
|
+
}),
|
|
21395
|
+
object({
|
|
21396
|
+
kind: literal("event"),
|
|
21397
|
+
sourceId: ulidSchema,
|
|
21398
|
+
match: string2().trim().min(1).max(120),
|
|
21399
|
+
offset: _enum2(["day_before_evening", "same_day"])
|
|
21400
|
+
})
|
|
21401
|
+
]);
|
|
21402
|
+
var eventColumns = { eventSourceId: true, eventMatch: true, eventOffset: true };
|
|
21300
21403
|
var createHabitInputSchema = createInsertSchema(habits, {
|
|
21301
21404
|
id: ulidSchema,
|
|
21302
21405
|
source: habitSourceSchema.optional(),
|
|
@@ -21310,8 +21413,9 @@ var createHabitInputSchema = createInsertSchema(habits, {
|
|
|
21310
21413
|
aliases: habitAliasesSchema.optional(),
|
|
21311
21414
|
timeOfDay: habitTimeOfDaySchema.nullish(),
|
|
21312
21415
|
remind: boolean2().optional()
|
|
21313
|
-
}).omit({ tenantId: true, recurrenceRuleId: true, retiredAt: true }).extend({
|
|
21416
|
+
}).omit({ tenantId: true, recurrenceRuleId: true, retiredAt: true, ...eventColumns }).extend({
|
|
21314
21417
|
title: string2().min(1),
|
|
21418
|
+
schedule: habitScheduleSchema.optional(),
|
|
21315
21419
|
displayKind: string2().min(1).nullish()
|
|
21316
21420
|
});
|
|
21317
21421
|
var updateHabitInputSchema = createUpdateSchema(habits, {
|
|
@@ -21332,8 +21436,12 @@ var updateHabitInputSchema = createUpdateSchema(habits, {
|
|
|
21332
21436
|
recurrenceRuleId: true,
|
|
21333
21437
|
pausedFrom: true,
|
|
21334
21438
|
pausedUntil: true,
|
|
21335
|
-
retiredAt: true
|
|
21336
|
-
|
|
21439
|
+
retiredAt: true,
|
|
21440
|
+
...eventColumns
|
|
21441
|
+
}).extend({
|
|
21442
|
+
title: string2().min(1).optional(),
|
|
21443
|
+
schedule: habitScheduleSchema.optional()
|
|
21444
|
+
});
|
|
21337
21445
|
var HABIT_ENTRY_NOTE_MAX = 280;
|
|
21338
21446
|
var habitEntryNoteSchema = string2().trim().max(HABIT_ENTRY_NOTE_MAX).transform((note) => note === "" ? null : note);
|
|
21339
21447
|
var createHabitEntryInputSchema = createInsertSchema(habitEntries, {
|
|
@@ -21387,7 +21495,9 @@ var habitSummarySchema = habitSchema.extend({
|
|
|
21387
21495
|
inRoutines: array(habitRoutineRefSchema).default([]),
|
|
21388
21496
|
lastCheckIn: habitLastCheckInSchema.nullable().default(null),
|
|
21389
21497
|
pause: habitPauseSchema.nullable().default(null),
|
|
21390
|
-
today: habitTodaySchema.default({ due: true, done: false })
|
|
21498
|
+
today: habitTodaySchema.default({ due: true, done: false }),
|
|
21499
|
+
schedule: habitScheduleSchema.default({ kind: "daily" }),
|
|
21500
|
+
nextDue: array(exports_iso.date()).default([])
|
|
21391
21501
|
});
|
|
21392
21502
|
var habitHeatmapCellSchema = object({
|
|
21393
21503
|
date: string2(),
|
|
@@ -21474,12 +21584,78 @@ var habitTodayRoutineSchema = object({
|
|
|
21474
21584
|
stepsDone: number2().int().nonnegative(),
|
|
21475
21585
|
stepsTotal: number2().int().nonnegative()
|
|
21476
21586
|
});
|
|
21477
|
-
var
|
|
21587
|
+
var habitsTodayBaseSchema = object({
|
|
21478
21588
|
date: string2().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
21479
21589
|
timezone: string2().min(1),
|
|
21480
21590
|
groups: array(object({ part: habitDayPartSchema, habits: array(habitTodayItemSchema) })),
|
|
21481
21591
|
routines: array(habitTodayRoutineSchema)
|
|
21482
21592
|
});
|
|
21593
|
+
var habitMomentSchema = _enum2(["morning", "day", "evening"]);
|
|
21594
|
+
var habitMomentStateSchema = _enum2(["done", "going", "tonight", "open"]);
|
|
21595
|
+
var habitRowKindSchema = _enum2(["tick", "amount", "timer", "days-without"]);
|
|
21596
|
+
var HABIT_STRIP_DAYS = 14;
|
|
21597
|
+
var habitStripDaySchema = _enum2([
|
|
21598
|
+
"done",
|
|
21599
|
+
"missed",
|
|
21600
|
+
"not_due",
|
|
21601
|
+
"today_open",
|
|
21602
|
+
"today_done"
|
|
21603
|
+
]);
|
|
21604
|
+
var habitStreakUnitSchema = _enum2(["days", "in_a_row", "since"]);
|
|
21605
|
+
var habitSectionStreakSchema = object({
|
|
21606
|
+
current: number2().int().nonnegative(),
|
|
21607
|
+
best: number2().int().nonnegative(),
|
|
21608
|
+
unit: habitStreakUnitSchema,
|
|
21609
|
+
since: exports_iso.date().nullable().default(null)
|
|
21610
|
+
});
|
|
21611
|
+
var habitSectionRowSchema = object({
|
|
21612
|
+
habitId: ulidSchema,
|
|
21613
|
+
title: string2(),
|
|
21614
|
+
kind: habitRowKindSchema,
|
|
21615
|
+
schedule: habitScheduleSchema,
|
|
21616
|
+
scheduleWords: string2(),
|
|
21617
|
+
sourceWords: string2().nullable(),
|
|
21618
|
+
done: boolean2(),
|
|
21619
|
+
amount: object({ value: number2(), target: number2().nullable(), unit: string2().nullable() }).nullable(),
|
|
21620
|
+
strip: array(habitStripDaySchema).length(HABIT_STRIP_DAYS),
|
|
21621
|
+
streak: habitSectionStreakSchema,
|
|
21622
|
+
routineStep: object({
|
|
21623
|
+
routineId: ulidSchema,
|
|
21624
|
+
title: string2(),
|
|
21625
|
+
step: number2().int().positive(),
|
|
21626
|
+
of: number2().int().positive()
|
|
21627
|
+
}).nullable()
|
|
21628
|
+
});
|
|
21629
|
+
var habitSectionMomentSchema = object({
|
|
21630
|
+
moment: habitMomentSchema,
|
|
21631
|
+
label: string2(),
|
|
21632
|
+
routines: array(object({ id: ulidSchema, title: string2(), time: string2().nullable() })),
|
|
21633
|
+
state: habitMomentStateSchema,
|
|
21634
|
+
going: number2().int().nonnegative().default(0),
|
|
21635
|
+
rows: array(habitSectionRowSchema)
|
|
21636
|
+
});
|
|
21637
|
+
var habitNotDueSchema = object({
|
|
21638
|
+
id: ulidSchema,
|
|
21639
|
+
title: string2(),
|
|
21640
|
+
nextDue: exports_iso.date().nullable()
|
|
21641
|
+
});
|
|
21642
|
+
var habitWeekDaySchema = object({
|
|
21643
|
+
day: exports_iso.date(),
|
|
21644
|
+
done: number2().int().nonnegative().nullable(),
|
|
21645
|
+
due: number2().int().nonnegative()
|
|
21646
|
+
});
|
|
21647
|
+
var habitSectionTotalsSchema = object({
|
|
21648
|
+
dueToday: number2().int().nonnegative(),
|
|
21649
|
+
doneToday: number2().int().nonnegative(),
|
|
21650
|
+
weekDone: number2().int().nonnegative(),
|
|
21651
|
+
weekDue: number2().int().nonnegative()
|
|
21652
|
+
});
|
|
21653
|
+
var habitsTodayResponseSchema = habitsTodayBaseSchema.extend({
|
|
21654
|
+
moments: array(habitSectionMomentSchema).default([]),
|
|
21655
|
+
notDue: array(habitNotDueSchema).default([]),
|
|
21656
|
+
week: array(habitWeekDaySchema).default([]),
|
|
21657
|
+
totals: habitSectionTotalsSchema.default({ dueToday: 0, doneToday: 0, weekDone: 0, weekDue: 0 })
|
|
21658
|
+
});
|
|
21483
21659
|
|
|
21484
21660
|
// ../../packages/schema/src/contracts/metrics.ts
|
|
21485
21661
|
var METRICS = {
|
|
@@ -21947,6 +22123,55 @@ var taskFilingSuggestionSchema = object({
|
|
|
21947
22123
|
var completedAtInputSchema = exports_coerce.date().refine((d) => d.getTime() <= Date.now() + 60000, "completedAt can't be in the future").nullish();
|
|
21948
22124
|
var taskNextStepSchema = string2().trim().min(1).max(500);
|
|
21949
22125
|
var taskDeliverableSchema = string2().trim().min(1).max(500);
|
|
22126
|
+
var taskRecurrenceAnchorSchema = _enum2(["fixed", "from_completion"]);
|
|
22127
|
+
function isTimeZone(zone) {
|
|
22128
|
+
try {
|
|
22129
|
+
new Intl.DateTimeFormat("en", { timeZone: zone });
|
|
22130
|
+
return true;
|
|
22131
|
+
} catch {
|
|
22132
|
+
return false;
|
|
22133
|
+
}
|
|
22134
|
+
}
|
|
22135
|
+
var taskRecurrenceInputSchema = object({
|
|
22136
|
+
freq: recurrenceFreqSchema,
|
|
22137
|
+
interval: number2().int().positive().default(1),
|
|
22138
|
+
byDay: array(byDaySchema).min(1).optional(),
|
|
22139
|
+
byMonthDay: number2().int().min(-1).max(31).refine((d) => d !== 0, "byMonthDay is 1\u201331, or -1 for the last day").optional(),
|
|
22140
|
+
anchor: taskRecurrenceAnchorSchema.default("fixed"),
|
|
22141
|
+
timezone: string2().refine(isTimeZone, "an IANA time zone, e.g. Europe/Brussels").optional()
|
|
22142
|
+
}).strict().superRefine((v, ctx) => {
|
|
22143
|
+
const issue = (path, message) => ctx.addIssue({ code: "custom", path, message });
|
|
22144
|
+
if (v.anchor === "from_completion") {
|
|
22145
|
+
if (v.freq === "yearly")
|
|
22146
|
+
issue(["freq"], "a from_completion rule is daily, weekly or monthly");
|
|
22147
|
+
if (v.byDay)
|
|
22148
|
+
issue(["byDay"], "a from_completion rule takes no byDay");
|
|
22149
|
+
if (v.byMonthDay !== undefined)
|
|
22150
|
+
issue(["byMonthDay"], "a from_completion rule takes no byMonthDay");
|
|
22151
|
+
return;
|
|
22152
|
+
}
|
|
22153
|
+
if (v.byDay && v.freq === "daily")
|
|
22154
|
+
issue(["byDay"], "byDay needs a weekly or monthly freq");
|
|
22155
|
+
const ordinal = v.byDay?.findIndex((code) => parseByDay(code)?.ordinal != null) ?? -1;
|
|
22156
|
+
if (ordinal >= 0 && v.freq !== "monthly" && v.freq !== "yearly") {
|
|
22157
|
+
issue(["byDay", ordinal], "an ordinal weekday (1SU, -1FR) needs a monthly or yearly freq");
|
|
22158
|
+
}
|
|
22159
|
+
if (v.byMonthDay !== undefined && v.freq !== "monthly") {
|
|
22160
|
+
issue(["byMonthDay"], "byMonthDay needs a monthly freq");
|
|
22161
|
+
}
|
|
22162
|
+
if (v.byMonthDay !== undefined && v.byDay) {
|
|
22163
|
+
issue(["byMonthDay"], "pass byDay or byMonthDay, not both");
|
|
22164
|
+
}
|
|
22165
|
+
});
|
|
22166
|
+
var taskRecurrenceSchema = object({
|
|
22167
|
+
ruleId: ulidSchema,
|
|
22168
|
+
freq: recurrenceFreqSchema,
|
|
22169
|
+
interval: number2().int().positive(),
|
|
22170
|
+
byDay: array(string2()).nullable(),
|
|
22171
|
+
byMonthDay: number2().int().nullable(),
|
|
22172
|
+
anchor: taskRecurrenceAnchorSchema,
|
|
22173
|
+
timezone: string2()
|
|
22174
|
+
});
|
|
21950
22175
|
var taskViewSchema = taskSchema.omit({
|
|
21951
22176
|
ownerUserId: true,
|
|
21952
22177
|
ownerAgentId: true,
|
|
@@ -21981,7 +22206,8 @@ var taskViewSchema = taskSchema.omit({
|
|
|
21981
22206
|
filingSuggestion: taskFilingSuggestionSchema.nullable().default(null),
|
|
21982
22207
|
blockedBy: array(taskBlockerSchema).default([]),
|
|
21983
22208
|
blocks: number2().int().nonnegative().default(0),
|
|
21984
|
-
isBlocked: boolean2().default(false)
|
|
22209
|
+
isBlocked: boolean2().default(false),
|
|
22210
|
+
recurrence: taskRecurrenceSchema.nullable().default(null)
|
|
21985
22211
|
});
|
|
21986
22212
|
var taskProjectIdSchema = ulidSchema.nullish();
|
|
21987
22213
|
var taskAreaIdSchema = ulidSchema.nullish();
|
|
@@ -22035,7 +22261,8 @@ var createTaskInputSchema = createInsertSchema(tasks, {
|
|
|
22035
22261
|
source: taskSourceSchema.nullish(),
|
|
22036
22262
|
nextStep: taskNextStepSchema.nullish(),
|
|
22037
22263
|
proposed: boolean2().optional(),
|
|
22038
|
-
fromDesk: deskSchema.nullish()
|
|
22264
|
+
fromDesk: deskSchema.nullish(),
|
|
22265
|
+
recurrence: taskRecurrenceInputSchema.nullish()
|
|
22039
22266
|
});
|
|
22040
22267
|
var taskReadyNoteSchema = string2().trim().min(1).max(500);
|
|
22041
22268
|
var markTaskReadyInputSchema = object({ note: taskReadyNoteSchema.nullish() }).strict();
|
|
@@ -22088,7 +22315,8 @@ var updateTaskInputSchema = createUpdateSchema(tasks, {
|
|
|
22088
22315
|
proposed: boolean2().optional(),
|
|
22089
22316
|
notNowUntil: exports_coerce.date().nullish(),
|
|
22090
22317
|
ready: boolean2().optional(),
|
|
22091
|
-
readyNote: taskReadyNoteSchema.nullish()
|
|
22318
|
+
readyNote: taskReadyNoteSchema.nullish(),
|
|
22319
|
+
recurrence: taskRecurrenceInputSchema.nullish()
|
|
22092
22320
|
}).strict();
|
|
22093
22321
|
var taskListFilterSchema = _enum2(["all", "unsorted", "starred"]);
|
|
22094
22322
|
|
|
@@ -22194,73 +22422,6 @@ var projectDetailSchema = projectViewSchema.extend({
|
|
|
22194
22422
|
});
|
|
22195
22423
|
var attachTaskInputSchema = object({ taskId: ulidSchema });
|
|
22196
22424
|
|
|
22197
|
-
// ../../packages/schema/src/contracts/recurrence.ts
|
|
22198
|
-
var recurrenceRuleSchema = createSelectSchema(recurrenceRules, {
|
|
22199
|
-
until: exports_coerce.date().nullable()
|
|
22200
|
-
});
|
|
22201
|
-
var occurrenceSchema = createSelectSchema(occurrences, {
|
|
22202
|
-
occurrenceDate: exports_coerce.date(),
|
|
22203
|
-
completedAt: exports_coerce.date().nullable()
|
|
22204
|
-
});
|
|
22205
|
-
var byDayCodeSchema = _enum2(["MO", "TU", "WE", "TH", "FR", "SA", "SU"]);
|
|
22206
|
-
var byDayOrdinalSchema = _enum2(["1", "2", "3", "4", "-1"]);
|
|
22207
|
-
var byDaySchema = union([
|
|
22208
|
-
byDayCodeSchema,
|
|
22209
|
-
templateLiteral([byDayOrdinalSchema, byDayCodeSchema])
|
|
22210
|
-
]);
|
|
22211
|
-
var BY_DAY_RE = /^(-1|[1-4])?(MO|TU|WE|TH|FR|SA|SU)$/;
|
|
22212
|
-
function parseByDay(code) {
|
|
22213
|
-
const m = BY_DAY_RE.exec(code);
|
|
22214
|
-
if (!m)
|
|
22215
|
-
return null;
|
|
22216
|
-
return { ordinal: m[1] ? Number(m[1]) : null, weekday: m[2] };
|
|
22217
|
-
}
|
|
22218
|
-
var recurrenceRuleParams = {
|
|
22219
|
-
timezone: string2().min(1).default("UTC"),
|
|
22220
|
-
interval: number2().int().positive().default(1),
|
|
22221
|
-
mode: recurrenceModeSchema,
|
|
22222
|
-
timeOfDay: string2().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "time-of-day must be HH:MM (24h)").nullish(),
|
|
22223
|
-
freq: recurrenceFreqSchema.optional(),
|
|
22224
|
-
byDay: array(byDaySchema).optional(),
|
|
22225
|
-
until: exports_coerce.date().optional(),
|
|
22226
|
-
count: number2().int().positive().optional(),
|
|
22227
|
-
unit: recurrenceUnitSchema.optional(),
|
|
22228
|
-
anchor: recurrenceAnchorSchema.optional()
|
|
22229
|
-
};
|
|
22230
|
-
function refineRecurrenceRule(v, ctx) {
|
|
22231
|
-
if (v.mode === "rigid" && !v.freq) {
|
|
22232
|
-
ctx.addIssue({ code: "custom", path: ["freq"], message: "rigid rules require a freq" });
|
|
22233
|
-
}
|
|
22234
|
-
const ordinal = v.byDay?.findIndex((code) => parseByDay(code)?.ordinal != null) ?? -1;
|
|
22235
|
-
if (ordinal >= 0 && v.freq !== "monthly" && v.freq !== "yearly") {
|
|
22236
|
-
ctx.addIssue({
|
|
22237
|
-
code: "custom",
|
|
22238
|
-
path: ["byDay", ordinal],
|
|
22239
|
-
message: "an ordinal weekday (1SU, -1FR) needs a monthly or yearly freq"
|
|
22240
|
-
});
|
|
22241
|
-
}
|
|
22242
|
-
if (v.mode === "flexible") {
|
|
22243
|
-
if (!v.unit) {
|
|
22244
|
-
ctx.addIssue({ code: "custom", path: ["unit"], message: "flexible rules require a unit" });
|
|
22245
|
-
}
|
|
22246
|
-
if (!v.anchor) {
|
|
22247
|
-
ctx.addIssue({
|
|
22248
|
-
code: "custom",
|
|
22249
|
-
path: ["anchor"],
|
|
22250
|
-
message: "flexible rules require an anchor"
|
|
22251
|
-
});
|
|
22252
|
-
}
|
|
22253
|
-
}
|
|
22254
|
-
}
|
|
22255
|
-
var createRecurrenceRuleInputSchema = object({
|
|
22256
|
-
templateId: ulidSchema,
|
|
22257
|
-
...recurrenceRuleParams
|
|
22258
|
-
}).superRefine(refineRecurrenceRule);
|
|
22259
|
-
var updateRecurrenceRuleInputSchema = object({ ...recurrenceRuleParams }).superRefine(refineRecurrenceRule);
|
|
22260
|
-
var updateOccurrenceInputSchema = createUpdateSchema(occurrences, {
|
|
22261
|
-
occurrenceDate: exports_coerce.date().optional()
|
|
22262
|
-
}).pick({ occurrenceDate: true, status: true }).strict();
|
|
22263
|
-
|
|
22264
22425
|
// ../../packages/schema/src/contracts/routines.ts
|
|
22265
22426
|
var routineDayTimesSchema = partialRecord(_enum2(["MO", "TU", "WE", "TH", "FR", "SA", "SU"]), string2().regex(/^([01]\d|2[0-3]):[0-5]\d$/));
|
|
22266
22427
|
var routineSchema = createSelectSchema(routines);
|
|
@@ -29710,7 +29871,7 @@ var habitOperations = defineOperations({
|
|
|
29710
29871
|
scopes: ["habits:read"],
|
|
29711
29872
|
tag: "Habits",
|
|
29712
29873
|
summary: "What's left today",
|
|
29713
|
-
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
|
|
29874
|
+
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`.",
|
|
29714
29875
|
status: 200,
|
|
29715
29876
|
response: { description: "Today's habits and routines." },
|
|
29716
29877
|
operationId: "habits_today"
|
|
@@ -29962,9 +30123,9 @@ var habitOperations = defineOperations({
|
|
|
29962
30123
|
scopes: ["habits:write"],
|
|
29963
30124
|
tag: "Habits",
|
|
29964
30125
|
summary: "Undo a check-in",
|
|
29965
|
-
description: "Delete a check-in; fires `habit.updated`.",
|
|
30126
|
+
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`.",
|
|
29966
30127
|
status: 200,
|
|
29967
|
-
response: { description: "The refreshed habit." },
|
|
30128
|
+
response: { description: "The refreshed habit, its streak recomputed." },
|
|
29968
30129
|
errors: { 404: true },
|
|
29969
30130
|
webhook: "habit.updated",
|
|
29970
30131
|
operationId: "undo_habit_check_in"
|
|
@@ -37605,7 +37766,7 @@ var routineOperations = defineOperations({
|
|
|
37605
37766
|
scopes: ["routines:write"],
|
|
37606
37767
|
tag: "Routines",
|
|
37607
37768
|
summary: "Update a routine",
|
|
37608
|
-
description: "Patch a routine; fires `routine.updated`.",
|
|
37769
|
+
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.",
|
|
37609
37770
|
body: updateRoutineInputSchema,
|
|
37610
37771
|
status: 200,
|
|
37611
37772
|
response: { description: "The updated routine." },
|
|
@@ -37618,12 +37779,25 @@ var routineOperations = defineOperations({
|
|
|
37618
37779
|
scopes: ["routines:write"],
|
|
37619
37780
|
tag: "Routines",
|
|
37620
37781
|
summary: "Delete a routine",
|
|
37621
|
-
description: "Soft-delete a routine;
|
|
37782
|
+
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`.",
|
|
37622
37783
|
status: 204,
|
|
37623
37784
|
response: { description: "Deleted." },
|
|
37624
37785
|
errors: { 404: true },
|
|
37625
37786
|
webhook: "routine.deleted"
|
|
37626
37787
|
},
|
|
37788
|
+
archive: {
|
|
37789
|
+
method: "post",
|
|
37790
|
+
path: "/routines/{id}/archive",
|
|
37791
|
+
scopes: ["routines:write"],
|
|
37792
|
+
tag: "Routines",
|
|
37793
|
+
summary: "Archive a routine",
|
|
37794
|
+
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`.",
|
|
37795
|
+
status: 204,
|
|
37796
|
+
response: { description: "Archived." },
|
|
37797
|
+
errors: { 404: true },
|
|
37798
|
+
webhook: "routine.deleted",
|
|
37799
|
+
operationId: "archive_routine"
|
|
37800
|
+
},
|
|
37627
37801
|
attachRecurrence: {
|
|
37628
37802
|
method: "put",
|
|
37629
37803
|
path: "/routines/{id}/recurrence",
|
|
@@ -39457,7 +39631,7 @@ var taskOperations = defineOperations({
|
|
|
39457
39631
|
scopes: ["tasks:read"],
|
|
39458
39632
|
tag: "Tasks",
|
|
39459
39633
|
summary: "Get a task",
|
|
39460
|
-
description: "Fetch one task by id (ULID) or by its ref (`DO-142`, case-insensitive).",
|
|
39634
|
+
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).",
|
|
39461
39635
|
status: 200,
|
|
39462
39636
|
response: { description: "The task." },
|
|
39463
39637
|
errors: { 404: true }
|
|
@@ -39468,7 +39642,7 @@ var taskOperations = defineOperations({
|
|
|
39468
39642
|
scopes: ["tasks:write"],
|
|
39469
39643
|
tag: "Tasks",
|
|
39470
39644
|
summary: "Create a task",
|
|
39471
|
-
description: "Create a task; fires the `task.created` webhook.",
|
|
39645
|
+
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).",
|
|
39472
39646
|
body: createTaskInputSchema,
|
|
39473
39647
|
status: 201,
|
|
39474
39648
|
response: { description: "The created task." },
|
|
@@ -39481,7 +39655,7 @@ var taskOperations = defineOperations({
|
|
|
39481
39655
|
scopes: ["tasks:write"],
|
|
39482
39656
|
tag: "Tasks",
|
|
39483
39657
|
summary: "Update a task",
|
|
39484
|
-
description:
|
|
39658
|
+
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.',
|
|
39485
39659
|
body: updateTaskInputSchema,
|
|
39486
39660
|
status: 200,
|
|
39487
39661
|
response: { description: "The updated task." },
|
|
@@ -46989,7 +47163,144 @@ var commands19 = [
|
|
|
46989
47163
|
];
|
|
46990
47164
|
|
|
46991
47165
|
// src/commands/habits.ts
|
|
47166
|
+
var SCHEDULE_FLAGS = [
|
|
47167
|
+
{ name: "every-day", type: "boolean", description: "Due every day." },
|
|
47168
|
+
{
|
|
47169
|
+
name: "on",
|
|
47170
|
+
type: "string",
|
|
47171
|
+
description: "Due on these weekdays, comma-separated (mo,we,fr)."
|
|
47172
|
+
},
|
|
47173
|
+
{
|
|
47174
|
+
name: "every",
|
|
47175
|
+
type: "string",
|
|
47176
|
+
description: "Due every N days or weeks (3d, 2w), counted in calendar days from --from, never from the last check-in."
|
|
47177
|
+
},
|
|
47178
|
+
{
|
|
47179
|
+
name: "from",
|
|
47180
|
+
type: "string",
|
|
47181
|
+
description: "The day --every counts from, YYYY-MM-DD (default today)."
|
|
47182
|
+
},
|
|
47183
|
+
{
|
|
47184
|
+
name: "monthly",
|
|
47185
|
+
type: "string",
|
|
47186
|
+
description: "Due monthly on this day of the month: 1\u201331 (a 31 falls on a shorter month's last day), or last."
|
|
47187
|
+
},
|
|
47188
|
+
{ name: "data", type: "string", description: "JSON for any other field of the contract." }
|
|
47189
|
+
];
|
|
47190
|
+
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.";
|
|
47191
|
+
function localToday() {
|
|
47192
|
+
const now = new Date;
|
|
47193
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
47194
|
+
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
|
|
47195
|
+
}
|
|
47196
|
+
function scheduleFromFlags(flags, today = localToday()) {
|
|
47197
|
+
const on = flagString(flags, "on");
|
|
47198
|
+
const every = flagString(flags, "every");
|
|
47199
|
+
const monthly = flagString(flags, "monthly");
|
|
47200
|
+
const given = [flagBool(flags, "every-day"), on, every, monthly].filter((v) => v !== undefined && v !== false);
|
|
47201
|
+
if (given.length > 1)
|
|
47202
|
+
throw usageError("pick one schedule: --every-day, --on, --every or --monthly");
|
|
47203
|
+
if (flagString(flags, "from") !== undefined && every === undefined)
|
|
47204
|
+
throw usageError("--from goes with --every");
|
|
47205
|
+
if (flagBool(flags, "every-day"))
|
|
47206
|
+
return { kind: "daily" };
|
|
47207
|
+
if (on !== undefined) {
|
|
47208
|
+
const days = on.split(",").map((d) => d.trim().toUpperCase()).filter((d) => d.length > 0);
|
|
47209
|
+
return { kind: "weekdays", days };
|
|
47210
|
+
}
|
|
47211
|
+
if (every !== undefined) {
|
|
47212
|
+
const m = /^(\d+)\s*([dw])$/i.exec(every.trim());
|
|
47213
|
+
if (!m)
|
|
47214
|
+
throw usageError("--every takes a number of days or weeks: 3d or 2w");
|
|
47215
|
+
return {
|
|
47216
|
+
kind: "every",
|
|
47217
|
+
interval: Number(m[1]),
|
|
47218
|
+
unit: m[2]?.toLowerCase() === "w" ? "week" : "day",
|
|
47219
|
+
anchor: flagString(flags, "from") ?? today
|
|
47220
|
+
};
|
|
47221
|
+
}
|
|
47222
|
+
if (monthly !== undefined) {
|
|
47223
|
+
const day = monthly.trim().toLowerCase() === "last" ? -1 : Number(monthly);
|
|
47224
|
+
if (!Number.isInteger(day))
|
|
47225
|
+
throw usageError("--monthly takes a day of the month (1\u201331) or last");
|
|
47226
|
+
return { kind: "monthly", dayOfMonth: day };
|
|
47227
|
+
}
|
|
47228
|
+
return;
|
|
47229
|
+
}
|
|
46992
47230
|
var commands20 = [
|
|
47231
|
+
{
|
|
47232
|
+
spec: {
|
|
47233
|
+
path: ["habits", "create"],
|
|
47234
|
+
summary: "Create a habit, with an optional schedule (every day, weekdays, every N, monthly).",
|
|
47235
|
+
args: [{ name: "title", required: false, description: "The habit's title." }],
|
|
47236
|
+
flags: SCHEDULE_FLAGS,
|
|
47237
|
+
scopes: [...habitOperations.create.scopes],
|
|
47238
|
+
returns: "HabitSummary",
|
|
47239
|
+
notes: `${routeOf(habitOperations.create)}. --data carries the rest of a CreateHabitInput; the id is minted and a yes/no measurement assumed when omitted. ${SCHEDULE_NOTES} e.g. \`do habits create "Water the plants" --every 3d\`.`
|
|
47240
|
+
},
|
|
47241
|
+
run: async ({ args, flags, requireClient }) => {
|
|
47242
|
+
const title = args.join(" ").trim();
|
|
47243
|
+
const schedule = scheduleFromFlags(flags);
|
|
47244
|
+
const body = parseInput(createHabitInputSchema, {
|
|
47245
|
+
id: ulid3(),
|
|
47246
|
+
measurementType: "binary",
|
|
47247
|
+
...title ? { title } : {},
|
|
47248
|
+
...readDataFlag(flags),
|
|
47249
|
+
...schedule ? { schedule } : {}
|
|
47250
|
+
});
|
|
47251
|
+
if (flagBool(flags, "dry-run")) {
|
|
47252
|
+
return {
|
|
47253
|
+
data: { dryRun: true, op: "create", entity: "habits", after: body },
|
|
47254
|
+
render: renderRecord
|
|
47255
|
+
};
|
|
47256
|
+
}
|
|
47257
|
+
return {
|
|
47258
|
+
data: await requireClient().post(pathOf(habitOperations.create), body),
|
|
47259
|
+
render: renderRecord
|
|
47260
|
+
};
|
|
47261
|
+
}
|
|
47262
|
+
},
|
|
47263
|
+
{
|
|
47264
|
+
spec: {
|
|
47265
|
+
path: ["habits", "update"],
|
|
47266
|
+
summary: "Change a habit: its schedule (--every-day, --on, --every, --monthly) or --data.",
|
|
47267
|
+
args: [{ name: "id", required: true, description: "Habit ULID." }],
|
|
47268
|
+
flags: SCHEDULE_FLAGS,
|
|
47269
|
+
scopes: [...habitOperations.update.scopes],
|
|
47270
|
+
returns: "HabitSummary",
|
|
47271
|
+
notes: `${routeOf(habitOperations.update)}. Only what you pass changes. ${SCHEDULE_NOTES}`
|
|
47272
|
+
},
|
|
47273
|
+
run: async ({ args, flags, requireClient }) => {
|
|
47274
|
+
const id = requireArg(args, 0, "habit id");
|
|
47275
|
+
const schedule = scheduleFromFlags(flags);
|
|
47276
|
+
const body = parseInput(updateHabitInputSchema, {
|
|
47277
|
+
...readDataFlag(flags),
|
|
47278
|
+
...schedule ? { schedule } : {}
|
|
47279
|
+
});
|
|
47280
|
+
if (Object.keys(body).length === 0)
|
|
47281
|
+
throw usageError("nothing to change \u2014 pass a schedule flag such as --on mo,we, or --data");
|
|
47282
|
+
const client = requireClient();
|
|
47283
|
+
if (flagBool(flags, "dry-run")) {
|
|
47284
|
+
const before = await client.get(pathOf(habitOperations.get, { id }));
|
|
47285
|
+
return {
|
|
47286
|
+
data: {
|
|
47287
|
+
dryRun: true,
|
|
47288
|
+
op: "update",
|
|
47289
|
+
entity: "habits",
|
|
47290
|
+
id,
|
|
47291
|
+
before,
|
|
47292
|
+
after: mergePreview(before, body),
|
|
47293
|
+
changes: body
|
|
47294
|
+
},
|
|
47295
|
+
render: renderRecord
|
|
47296
|
+
};
|
|
47297
|
+
}
|
|
47298
|
+
return {
|
|
47299
|
+
data: await client.patch(pathOf(habitOperations.update, { id }), body),
|
|
47300
|
+
render: renderRecord
|
|
47301
|
+
};
|
|
47302
|
+
}
|
|
47303
|
+
},
|
|
46993
47304
|
{
|
|
46994
47305
|
spec: {
|
|
46995
47306
|
path: ["habit", "log"],
|
|
@@ -47144,7 +47455,7 @@ var commands20 = [
|
|
|
47144
47455
|
flags: [],
|
|
47145
47456
|
scopes: [...habitOperations.today.scopes],
|
|
47146
47457
|
returns: "HabitsTodayResponse",
|
|
47147
|
-
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
|
|
47458
|
+
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\`.`
|
|
47148
47459
|
},
|
|
47149
47460
|
run: readOperation(habitOperations.today, renderHabitsToday)
|
|
47150
47461
|
},
|
|
@@ -47210,6 +47521,32 @@ var commands20 = [
|
|
|
47210
47521
|
};
|
|
47211
47522
|
}
|
|
47212
47523
|
},
|
|
47524
|
+
{
|
|
47525
|
+
spec: {
|
|
47526
|
+
path: ["habit", "undo"],
|
|
47527
|
+
summary: "Undo a wrong or test check-in (or a skip); the streak recomputes.",
|
|
47528
|
+
args: [
|
|
47529
|
+
{ name: "id", required: true, description: "Habit ULID." },
|
|
47530
|
+
{
|
|
47531
|
+
name: "entry-id",
|
|
47532
|
+
required: true,
|
|
47533
|
+
description: "The check-in's ULID (`do habit check-ins <id>`, or lastCheckIn.id)."
|
|
47534
|
+
}
|
|
47535
|
+
],
|
|
47536
|
+
flags: [],
|
|
47537
|
+
scopes: [...habitOperations.removeCheckIn.scopes],
|
|
47538
|
+
returns: "HabitSummary",
|
|
47539
|
+
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.`
|
|
47540
|
+
},
|
|
47541
|
+
run: async ({ args, requireClient }) => {
|
|
47542
|
+
const id = requireArg(args, 0, "habit id");
|
|
47543
|
+
const entryId = requireArg(args, 1, "check-in id");
|
|
47544
|
+
return {
|
|
47545
|
+
data: await requireClient().delete(pathOf(habitOperations.removeCheckIn, { id, entryId })),
|
|
47546
|
+
render: renderHabit
|
|
47547
|
+
};
|
|
47548
|
+
}
|
|
47549
|
+
},
|
|
47213
47550
|
{
|
|
47214
47551
|
spec: {
|
|
47215
47552
|
path: ["habit", "skip"],
|
|
@@ -53456,7 +53793,327 @@ function renderRoutineTaskAdded(data) {
|
|
|
53456
53793
|
const last = run.steps?.at(-1)?.title ?? "the task";
|
|
53457
53794
|
return `${run.routineTitle ?? "(routine)"}: ${last} is the last step of today's run.`;
|
|
53458
53795
|
}
|
|
53796
|
+
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) or any other text (a plain step with that title).";
|
|
53797
|
+
var ROUTINE_FLAGS = [
|
|
53798
|
+
{ name: "kind", type: "string", description: "sequence, time_blocked or checklist." },
|
|
53799
|
+
{ name: "area", type: "string", description: "The Area's ULID (`none` unfiles it)." },
|
|
53800
|
+
{ name: "notes", type: "string", description: "The routine's notes." },
|
|
53801
|
+
{ name: "data", type: "string", description: "JSON (or @file) with the rest of the input." }
|
|
53802
|
+
];
|
|
53803
|
+
function routineFields(flags) {
|
|
53804
|
+
const out = { ...readDataFlag(flags) };
|
|
53805
|
+
const title = flagString(flags, "title");
|
|
53806
|
+
const kind = flagString(flags, "kind");
|
|
53807
|
+
const area = flagString(flags, "area");
|
|
53808
|
+
const notes = flagString(flags, "notes");
|
|
53809
|
+
if (title !== undefined)
|
|
53810
|
+
out.title = title;
|
|
53811
|
+
if (kind !== undefined)
|
|
53812
|
+
out.kind = kind;
|
|
53813
|
+
if (area !== undefined)
|
|
53814
|
+
out.areaId = area === "none" ? null : area;
|
|
53815
|
+
if (notes !== undefined)
|
|
53816
|
+
out.notes = notes;
|
|
53817
|
+
return out;
|
|
53818
|
+
}
|
|
53819
|
+
async function stepFrom(client, item, title) {
|
|
53820
|
+
const link = /^(habit|task):(\S+)$/.exec(item.trim());
|
|
53821
|
+
if (!link) {
|
|
53822
|
+
const text = (title ?? item).trim();
|
|
53823
|
+
if (!text)
|
|
53824
|
+
throw usageError("a step needs a title");
|
|
53825
|
+
return { id: ulid3(), title: text, refKind: "free" };
|
|
53826
|
+
}
|
|
53827
|
+
const kind = link[1];
|
|
53828
|
+
const id = link[2];
|
|
53829
|
+
const named = title ?? (await client.get(kind === "habit" ? pathOf(habitOperations.get, { id }) : pathOf(taskOperations.get, { id }))).title ?? kind;
|
|
53830
|
+
return kind === "habit" ? { id: ulid3(), title: named, refKind: "habit", habitId: id } : { id: ulid3(), title: named, refKind: "task", taskId: id };
|
|
53831
|
+
}
|
|
53832
|
+
function stepInput(step, sortOrder) {
|
|
53833
|
+
return {
|
|
53834
|
+
id: step.id,
|
|
53835
|
+
title: step.title,
|
|
53836
|
+
refKind: step.refKind,
|
|
53837
|
+
habitId: step.refKind === "habit" ? step.habitId : null,
|
|
53838
|
+
taskId: step.refKind === "task" ? step.taskId : null,
|
|
53839
|
+
sortOrder,
|
|
53840
|
+
durationMinutes: step.durationMinutes,
|
|
53841
|
+
notes: step.notes
|
|
53842
|
+
};
|
|
53843
|
+
}
|
|
53844
|
+
function stepIndex(steps, token) {
|
|
53845
|
+
const byId = steps.findIndex((s) => s.id === token);
|
|
53846
|
+
if (byId >= 0)
|
|
53847
|
+
return byId;
|
|
53848
|
+
const n = Number(token);
|
|
53849
|
+
if (Number.isInteger(n) && n >= 1 && n <= steps.length)
|
|
53850
|
+
return n - 1;
|
|
53851
|
+
throw usageError(`no step ${token} in this routine \u2014 name its id or its number`);
|
|
53852
|
+
}
|
|
53853
|
+
function positionOf(raw, max, name) {
|
|
53854
|
+
const n = Number(raw);
|
|
53855
|
+
if (!Number.isInteger(n) || n < 1 || n > max) {
|
|
53856
|
+
throw usageError(`${name} must be a number from 1 to ${max}`);
|
|
53857
|
+
}
|
|
53858
|
+
return n - 1;
|
|
53859
|
+
}
|
|
53860
|
+
function renderRoutine(data) {
|
|
53861
|
+
const r = data;
|
|
53862
|
+
const steps = (r.steps ?? []).map((s, i) => {
|
|
53863
|
+
const kind = s.refKind === "free" ? "" : ` [${s.refKind}]`;
|
|
53864
|
+
return ` ${i + 1}. ${s.title}${kind} ${s.id}`;
|
|
53865
|
+
});
|
|
53866
|
+
return [
|
|
53867
|
+
`${r.title ?? "(routine)"} ${r.id}`,
|
|
53868
|
+
...steps.length ? steps : [" (no steps)"]
|
|
53869
|
+
].join(`
|
|
53870
|
+
`);
|
|
53871
|
+
}
|
|
53872
|
+
function putSteps(client, id, steps) {
|
|
53873
|
+
const body = parseInput(updateRoutineInputSchema, {
|
|
53874
|
+
steps: steps.map((s, i) => ({ ...s, sortOrder: i }))
|
|
53875
|
+
});
|
|
53876
|
+
return client.patch(pathOf(routineOperations.update, { id }), body);
|
|
53877
|
+
}
|
|
53878
|
+
function readRoutine(client, id) {
|
|
53879
|
+
return client.get(pathOf(routineOperations.get, { id }));
|
|
53880
|
+
}
|
|
53881
|
+
var routineCommands = [
|
|
53882
|
+
{
|
|
53883
|
+
spec: {
|
|
53884
|
+
path: ["routines", "create"],
|
|
53885
|
+
summary: "Create a routine with its steps.",
|
|
53886
|
+
args: [{ name: "title", required: false, description: "The routine's title." }],
|
|
53887
|
+
flags: [
|
|
53888
|
+
...ROUTINE_FLAGS,
|
|
53889
|
+
{ name: "step", type: "string", description: "A step, in order; repeat it. See grammar." }
|
|
53890
|
+
],
|
|
53891
|
+
scopes: [...routineOperations.create.scopes],
|
|
53892
|
+
returns: "RoutineView",
|
|
53893
|
+
grammar: { step: STEP_GRAMMAR },
|
|
53894
|
+
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>\`.`
|
|
53895
|
+
},
|
|
53896
|
+
run: async ({ args, flags, requireClient }) => {
|
|
53897
|
+
const title = args.join(" ").trim();
|
|
53898
|
+
const client = requireClient();
|
|
53899
|
+
const steps = [];
|
|
53900
|
+
for (const item of flagStrings(flags, "step"))
|
|
53901
|
+
steps.push(await stepFrom(client, item));
|
|
53902
|
+
const body = parseInput(createRoutineInputSchema, {
|
|
53903
|
+
id: ulid3(),
|
|
53904
|
+
...title ? { title } : {},
|
|
53905
|
+
...routineFields(flags),
|
|
53906
|
+
...steps.length ? { steps: steps.map((s, i) => ({ ...s, sortOrder: i })) } : {}
|
|
53907
|
+
});
|
|
53908
|
+
if (flagBool(flags, "dry-run")) {
|
|
53909
|
+
return {
|
|
53910
|
+
data: { dryRun: true, op: "create", entity: "routines", after: body },
|
|
53911
|
+
render: renderRecord
|
|
53912
|
+
};
|
|
53913
|
+
}
|
|
53914
|
+
return {
|
|
53915
|
+
data: await client.post(pathOf(routineOperations.create), body),
|
|
53916
|
+
render: renderRoutine
|
|
53917
|
+
};
|
|
53918
|
+
}
|
|
53919
|
+
},
|
|
53920
|
+
{
|
|
53921
|
+
spec: {
|
|
53922
|
+
path: ["routines", "update"],
|
|
53923
|
+
summary: "Change a routine: its title, kind, Area, notes, or --data.",
|
|
53924
|
+
args: [{ name: "id", required: true, description: "Routine ULID." }],
|
|
53925
|
+
flags: [{ name: "title", type: "string", description: "A new title." }, ...ROUTINE_FLAGS],
|
|
53926
|
+
scopes: [...routineOperations.update.scopes],
|
|
53927
|
+
returns: "RoutineView",
|
|
53928
|
+
notes: `${routeOf(routineOperations.update)}. Only what you pass changes; its steps change with \`do routines steps\`.`
|
|
53929
|
+
},
|
|
53930
|
+
run: async ({ args, flags, requireClient }) => {
|
|
53931
|
+
const id = requireArg(args, 0, "routine id");
|
|
53932
|
+
const body = parseInput(updateRoutineInputSchema, routineFields(flags));
|
|
53933
|
+
if (Object.keys(body).length === 0) {
|
|
53934
|
+
throw usageError("nothing to change \u2014 pass --title, --kind, --area, --notes or --data");
|
|
53935
|
+
}
|
|
53936
|
+
const client = requireClient();
|
|
53937
|
+
if (flagBool(flags, "dry-run")) {
|
|
53938
|
+
const before = await readRoutine(client, id);
|
|
53939
|
+
return {
|
|
53940
|
+
data: {
|
|
53941
|
+
dryRun: true,
|
|
53942
|
+
op: "update",
|
|
53943
|
+
entity: "routines",
|
|
53944
|
+
id,
|
|
53945
|
+
before,
|
|
53946
|
+
after: mergePreview(before, body),
|
|
53947
|
+
changes: body
|
|
53948
|
+
},
|
|
53949
|
+
render: renderRecord
|
|
53950
|
+
};
|
|
53951
|
+
}
|
|
53952
|
+
return {
|
|
53953
|
+
data: await client.patch(pathOf(routineOperations.update, { id }), body),
|
|
53954
|
+
render: renderRoutine
|
|
53955
|
+
};
|
|
53956
|
+
}
|
|
53957
|
+
},
|
|
53958
|
+
{
|
|
53959
|
+
spec: {
|
|
53960
|
+
path: ["routines", "steps", "set"],
|
|
53961
|
+
summary: "Replace a routine's steps with a new ordered list, or --clear them all.",
|
|
53962
|
+
args: [
|
|
53963
|
+
{ name: "id", required: true, description: "Routine ULID." },
|
|
53964
|
+
{ name: "steps", required: false, description: "The steps, in order. See grammar." }
|
|
53965
|
+
],
|
|
53966
|
+
flags: [{ name: "clear", type: "boolean", description: "Empty the routine (no steps)." }],
|
|
53967
|
+
scopes: [...routineOperations.update.scopes],
|
|
53968
|
+
returns: "RoutineView",
|
|
53969
|
+
grammar: { step: STEP_GRAMMAR },
|
|
53970
|
+
notes: `${routeOf(routineOperations.update)} with \`steps\`, in one call. The habits and tasks the old steps linked are untouched; past runs keep their snapshot.`
|
|
53971
|
+
},
|
|
53972
|
+
run: async ({ args, flags, requireClient }) => {
|
|
53973
|
+
const id = requireArg(args, 0, "routine id");
|
|
53974
|
+
const items = args.slice(1);
|
|
53975
|
+
const clear = flagBool(flags, "clear");
|
|
53976
|
+
if (items.length === 0 && !clear) {
|
|
53977
|
+
throw usageError("routines steps set needs the steps in order, or --clear to empty it");
|
|
53978
|
+
}
|
|
53979
|
+
if (items.length > 0 && clear)
|
|
53980
|
+
throw usageError("--clear takes no steps");
|
|
53981
|
+
const client = requireClient();
|
|
53982
|
+
const steps = [];
|
|
53983
|
+
for (const item of items)
|
|
53984
|
+
steps.push(await stepFrom(client, item));
|
|
53985
|
+
return { data: await putSteps(client, id, steps), render: renderRoutine };
|
|
53986
|
+
}
|
|
53987
|
+
},
|
|
53988
|
+
{
|
|
53989
|
+
spec: {
|
|
53990
|
+
path: ["routines", "steps", "add"],
|
|
53991
|
+
summary: "Add a step to a routine, at the end or --at a position.",
|
|
53992
|
+
args: [
|
|
53993
|
+
{ name: "id", required: true, description: "Routine ULID." },
|
|
53994
|
+
{ name: "step", required: true, description: "The step. See grammar." }
|
|
53995
|
+
],
|
|
53996
|
+
flags: [
|
|
53997
|
+
{ name: "at", type: "string", description: "Its position, 1-based (default: last)." },
|
|
53998
|
+
{ name: "title", type: "string", description: "Its title (instead of the linked name)." }
|
|
53999
|
+
],
|
|
54000
|
+
scopes: [...routineOperations.get.scopes, ...routineOperations.update.scopes],
|
|
54001
|
+
returns: "RoutineView",
|
|
54002
|
+
grammar: { step: STEP_GRAMMAR },
|
|
54003
|
+
notes: `${routeOf(routineOperations.get)}, then ${routeOf(routineOperations.update)} with every step (the others keep their ids).`
|
|
54004
|
+
},
|
|
54005
|
+
run: async ({ args, flags, requireClient }) => {
|
|
54006
|
+
const id = requireArg(args, 0, "routine id");
|
|
54007
|
+
const item = args.slice(1).join(" ").trim();
|
|
54008
|
+
if (!item)
|
|
54009
|
+
throw usageError("routines steps add needs the step: `habit:<id>`, `task:<id>` or a title");
|
|
54010
|
+
const client = requireClient();
|
|
54011
|
+
const routine = await readRoutine(client, id);
|
|
54012
|
+
const steps = routine.steps.map((s, i) => stepInput(s, i));
|
|
54013
|
+
const at = flagString(flags, "at");
|
|
54014
|
+
const index = at === undefined ? steps.length : positionOf(at, steps.length + 1, "--at");
|
|
54015
|
+
steps.splice(index, 0, await stepFrom(client, item, flagString(flags, "title")));
|
|
54016
|
+
return { data: await putSteps(client, id, steps), render: renderRoutine };
|
|
54017
|
+
}
|
|
54018
|
+
},
|
|
54019
|
+
{
|
|
54020
|
+
spec: {
|
|
54021
|
+
path: ["routines", "steps", "rm"],
|
|
54022
|
+
summary: "Remove a step from a routine.",
|
|
54023
|
+
args: [
|
|
54024
|
+
{ name: "id", required: true, description: "Routine ULID." },
|
|
54025
|
+
{ name: "step", required: true, description: "The step's ULID or its number (1-based)." }
|
|
54026
|
+
],
|
|
54027
|
+
flags: [],
|
|
54028
|
+
scopes: [...routineOperations.get.scopes, ...routineOperations.update.scopes],
|
|
54029
|
+
returns: "RoutineView",
|
|
54030
|
+
notes: `${routeOf(routineOperations.get)}, then ${routeOf(routineOperations.update)} without it. A habit or task it linked is untouched; past runs keep it.`
|
|
54031
|
+
},
|
|
54032
|
+
run: async ({ args, requireClient }) => {
|
|
54033
|
+
const id = requireArg(args, 0, "routine id");
|
|
54034
|
+
const token = requireArg(args, 1, "step");
|
|
54035
|
+
const client = requireClient();
|
|
54036
|
+
const routine = await readRoutine(client, id);
|
|
54037
|
+
const index = stepIndex(routine.steps, token);
|
|
54038
|
+
const steps = routine.steps.filter((_, i) => i !== index).map((s, i) => stepInput(s, i));
|
|
54039
|
+
return { data: await putSteps(client, id, steps), render: renderRoutine };
|
|
54040
|
+
}
|
|
54041
|
+
},
|
|
54042
|
+
{
|
|
54043
|
+
spec: {
|
|
54044
|
+
path: ["routines", "steps", "move"],
|
|
54045
|
+
summary: "Move a routine's step to a new position.",
|
|
54046
|
+
args: [
|
|
54047
|
+
{ name: "id", required: true, description: "Routine ULID." },
|
|
54048
|
+
{ name: "step", required: true, description: "The step's ULID or its number (1-based)." },
|
|
54049
|
+
{ name: "position", required: true, description: "Where it goes, 1-based." }
|
|
54050
|
+
],
|
|
54051
|
+
flags: [],
|
|
54052
|
+
scopes: [...routineOperations.get.scopes, ...routineOperations.reorderSteps.scopes],
|
|
54053
|
+
returns: "RoutineView",
|
|
54054
|
+
notes: `${routeOf(routineOperations.get)}, then ${routeOf(routineOperations.reorderSteps)}. Runs already started keep their order.`
|
|
54055
|
+
},
|
|
54056
|
+
run: async ({ args, requireClient }) => {
|
|
54057
|
+
const id = requireArg(args, 0, "routine id");
|
|
54058
|
+
const token = requireArg(args, 1, "step");
|
|
54059
|
+
const to = requireArg(args, 2, "position");
|
|
54060
|
+
const client = requireClient();
|
|
54061
|
+
const routine = await readRoutine(client, id);
|
|
54062
|
+
const from = stepIndex(routine.steps, token);
|
|
54063
|
+
const ids = routine.steps.map((s) => s.id);
|
|
54064
|
+
const [moved] = ids.splice(from, 1);
|
|
54065
|
+
ids.splice(positionOf(to, routine.steps.length, "position"), 0, moved);
|
|
54066
|
+
const input = parseInput(reorderRoutineStepsInputSchema, { stepIds: ids });
|
|
54067
|
+
return {
|
|
54068
|
+
data: await client.patch(pathOf(routineOperations.reorderSteps, { id }), input),
|
|
54069
|
+
render: renderRoutine
|
|
54070
|
+
};
|
|
54071
|
+
}
|
|
54072
|
+
},
|
|
54073
|
+
{
|
|
54074
|
+
spec: {
|
|
54075
|
+
path: ["routines", "archive"],
|
|
54076
|
+
summary: "Archive a routine: into Trash, restorable.",
|
|
54077
|
+
args: [{ name: "id", required: true, description: "Routine ULID." }],
|
|
54078
|
+
flags: [],
|
|
54079
|
+
scopes: [...routineOperations.archive.scopes],
|
|
54080
|
+
returns: "{ ok, archived }",
|
|
54081
|
+
notes: `${routeOf(routineOperations.archive)}. The habits and tasks its steps link are untouched.`
|
|
54082
|
+
},
|
|
54083
|
+
run: async ({ args, flags, requireClient }) => {
|
|
54084
|
+
const id = requireArg(args, 0, "routine id");
|
|
54085
|
+
const client = requireClient();
|
|
54086
|
+
if (flagBool(flags, "dry-run")) {
|
|
54087
|
+
const before = await readRoutine(client, id);
|
|
54088
|
+
return {
|
|
54089
|
+
data: { dryRun: true, op: "archive", entity: "routines", id, before, softDelete: true },
|
|
54090
|
+
render: renderRecord
|
|
54091
|
+
};
|
|
54092
|
+
}
|
|
54093
|
+
await client.post(pathOf(routineOperations.archive, { id }), {});
|
|
54094
|
+
return { data: { ok: true, archived: id, softDelete: true }, render: renderRecord };
|
|
54095
|
+
}
|
|
54096
|
+
},
|
|
54097
|
+
{
|
|
54098
|
+
spec: {
|
|
54099
|
+
path: ["routines", "rm"],
|
|
54100
|
+
summary: "Delete a routine (into Trash); needs --allow-destructive.",
|
|
54101
|
+
args: [{ name: "id", required: true, description: "Routine ULID." }],
|
|
54102
|
+
flags: [],
|
|
54103
|
+
scopes: [...routineOperations.remove.scopes],
|
|
54104
|
+
returns: "{ ok, deleted }",
|
|
54105
|
+
notes: `${routeOf(routineOperations.remove)}. The same soft delete as archive, behind the destructive guardrail.`
|
|
54106
|
+
},
|
|
54107
|
+
run: async ({ args, flags, requireClient }) => {
|
|
54108
|
+
const id = requireArg(args, 0, "routine id");
|
|
54109
|
+
assertDestructiveAllowed(flags, `delete routines ${id}`);
|
|
54110
|
+
await requireClient().delete(pathOf(routineOperations.remove, { id }));
|
|
54111
|
+
return { data: { ok: true, deleted: id, softDelete: true }, render: renderRecord };
|
|
54112
|
+
}
|
|
54113
|
+
}
|
|
54114
|
+
];
|
|
53459
54115
|
var commands42 = [
|
|
54116
|
+
...routineCommands,
|
|
53460
54117
|
{
|
|
53461
54118
|
spec: {
|
|
53462
54119
|
path: ["routines", "today"],
|
|
@@ -55096,6 +55753,69 @@ function parseCapture2(input, now) {
|
|
|
55096
55753
|
}
|
|
55097
55754
|
|
|
55098
55755
|
// src/commands/tasks.ts
|
|
55756
|
+
var REPEAT_FLAGS = [
|
|
55757
|
+
{
|
|
55758
|
+
name: "repeat",
|
|
55759
|
+
type: "string",
|
|
55760
|
+
description: "Make it repeat: daily, weekly, monthly or yearly. On update it replaces the rule."
|
|
55761
|
+
},
|
|
55762
|
+
{
|
|
55763
|
+
name: "every",
|
|
55764
|
+
type: "string",
|
|
55765
|
+
description: "With --repeat: every N days/weeks/months (default 1)."
|
|
55766
|
+
},
|
|
55767
|
+
{
|
|
55768
|
+
name: "on",
|
|
55769
|
+
type: "string",
|
|
55770
|
+
description: "With --repeat weekly (or monthly): the weekdays, comma-separated: mo,th. Monthly also takes 1su (the 1st Sunday) or -1fr (the last Friday)."
|
|
55771
|
+
},
|
|
55772
|
+
{
|
|
55773
|
+
name: "day",
|
|
55774
|
+
type: "string",
|
|
55775
|
+
description: "With --repeat monthly: the day of the month, 1\u201331 or last (a shorter month uses its last day)."
|
|
55776
|
+
},
|
|
55777
|
+
{
|
|
55778
|
+
name: "after-done",
|
|
55779
|
+
type: "boolean",
|
|
55780
|
+
description: "With --repeat: the next one is due N after the last was done, not on the calendar (anchor from_completion)."
|
|
55781
|
+
}
|
|
55782
|
+
];
|
|
55783
|
+
var REPEAT_DETAIL = ["every", "on", "day", "after-done"];
|
|
55784
|
+
function repeatFromFlags(flags) {
|
|
55785
|
+
const repeat = flagString(flags, "repeat");
|
|
55786
|
+
if (flagBool(flags, "no-repeat")) {
|
|
55787
|
+
if (repeat !== undefined)
|
|
55788
|
+
throw usageError("--repeat and --no-repeat can't be combined");
|
|
55789
|
+
return null;
|
|
55790
|
+
}
|
|
55791
|
+
if (repeat === undefined) {
|
|
55792
|
+
const stray = REPEAT_DETAIL.find((f) => flags[f] !== undefined);
|
|
55793
|
+
if (stray)
|
|
55794
|
+
throw usageError(`--${stray} goes with --repeat daily|weekly|monthly`);
|
|
55795
|
+
return;
|
|
55796
|
+
}
|
|
55797
|
+
const out = { freq: repeat.trim().toLowerCase() };
|
|
55798
|
+
const every = flagString(flags, "every");
|
|
55799
|
+
if (every !== undefined) {
|
|
55800
|
+
if (!/^\d+$/.test(every.trim()))
|
|
55801
|
+
throw usageError("--every must be a whole number, e.g. --every 2");
|
|
55802
|
+
out.interval = Number(every);
|
|
55803
|
+
}
|
|
55804
|
+
const on = flagString(flags, "on");
|
|
55805
|
+
if (on !== undefined) {
|
|
55806
|
+
out.byDay = on.split(",").map((d) => d.trim().toUpperCase()).filter((d) => d.length > 0);
|
|
55807
|
+
}
|
|
55808
|
+
const day = flagString(flags, "day");
|
|
55809
|
+
if (day !== undefined) {
|
|
55810
|
+
const d = day.trim().toLowerCase();
|
|
55811
|
+
if (d !== "last" && !/^-?\d+$/.test(d))
|
|
55812
|
+
throw usageError("--day must be 1\u201331 or last");
|
|
55813
|
+
out.byMonthDay = d === "last" ? -1 : Number(d);
|
|
55814
|
+
}
|
|
55815
|
+
if (flagBool(flags, "after-done"))
|
|
55816
|
+
out.anchor = "from_completion";
|
|
55817
|
+
return out;
|
|
55818
|
+
}
|
|
55099
55819
|
var commands54 = [
|
|
55100
55820
|
{
|
|
55101
55821
|
spec: {
|
|
@@ -55157,17 +55877,16 @@ var commands54 = [
|
|
|
55157
55877
|
...scheduledAt ? { scheduledAt } : {},
|
|
55158
55878
|
...dueAt ? { dueAt } : {},
|
|
55159
55879
|
...scheduledAt || dueAt ? { allDay } : {},
|
|
55160
|
-
...duration ? { durationMinutes: duration } : {}
|
|
55880
|
+
...duration ? { durationMinutes: duration } : {},
|
|
55881
|
+
...parsed.recurrence ? {
|
|
55882
|
+
recurrence: {
|
|
55883
|
+
freq: parsed.recurrence.freq,
|
|
55884
|
+
interval: parsed.recurrence.interval,
|
|
55885
|
+
...parsed.recurrence.byDay ? { byDay: parsed.recurrence.byDay } : {}
|
|
55886
|
+
}
|
|
55887
|
+
} : {}
|
|
55161
55888
|
});
|
|
55162
|
-
const
|
|
55163
|
-
const task = await client.post(pathOf(taskOperations.create), input);
|
|
55164
|
-
if (parsed.recurrence) {
|
|
55165
|
-
const rule = parseInput(createRecurrenceRuleInputSchema, {
|
|
55166
|
-
templateId: id,
|
|
55167
|
-
...parsed.recurrence
|
|
55168
|
-
});
|
|
55169
|
-
await client.post("/recurrence/rules", rule);
|
|
55170
|
-
}
|
|
55889
|
+
const task = await requireClient().post(pathOf(taskOperations.create), input);
|
|
55171
55890
|
return { data: task, render: renderTask };
|
|
55172
55891
|
}
|
|
55173
55892
|
},
|
|
@@ -55455,6 +56174,101 @@ var commands54 = [
|
|
|
55455
56174
|
render: renderTask
|
|
55456
56175
|
};
|
|
55457
56176
|
}
|
|
56177
|
+
}),
|
|
56178
|
+
defineCommand({
|
|
56179
|
+
operation: taskOperations.create,
|
|
56180
|
+
path: ["tasks", "create"],
|
|
56181
|
+
summary: "Create a task; --repeat makes it repeat.",
|
|
56182
|
+
returns: "Task",
|
|
56183
|
+
args: [
|
|
56184
|
+
{
|
|
56185
|
+
name: "title",
|
|
56186
|
+
required: false,
|
|
56187
|
+
description: "The task's title (or pass it in --data)."
|
|
56188
|
+
}
|
|
56189
|
+
],
|
|
56190
|
+
flags: [
|
|
56191
|
+
{
|
|
56192
|
+
name: "data",
|
|
56193
|
+
type: "string",
|
|
56194
|
+
description: "JSON CreateTaskInput (or @file) for any other field."
|
|
56195
|
+
},
|
|
56196
|
+
...REPEAT_FLAGS
|
|
56197
|
+
],
|
|
56198
|
+
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.',
|
|
56199
|
+
run: async ({ args, flags, requireClient }) => {
|
|
56200
|
+
const title = args.join(" ").trim();
|
|
56201
|
+
const recurrence = repeatFromFlags(flags);
|
|
56202
|
+
if (recurrence === null)
|
|
56203
|
+
throw usageError("--no-repeat is for `tasks update`");
|
|
56204
|
+
const body = parseInput(createTaskInputSchema, {
|
|
56205
|
+
id: ulid3(),
|
|
56206
|
+
...readDataFlag(flags),
|
|
56207
|
+
...title ? { title } : {},
|
|
56208
|
+
...recurrence ? { recurrence } : {}
|
|
56209
|
+
});
|
|
56210
|
+
if (flagBool(flags, "dry-run")) {
|
|
56211
|
+
return {
|
|
56212
|
+
data: { dryRun: true, op: "create", entity: "tasks", after: body },
|
|
56213
|
+
render: renderRecord
|
|
56214
|
+
};
|
|
56215
|
+
}
|
|
56216
|
+
return {
|
|
56217
|
+
data: await requireClient().post(pathOf(taskOperations.create), body),
|
|
56218
|
+
render: renderTask
|
|
56219
|
+
};
|
|
56220
|
+
}
|
|
56221
|
+
}),
|
|
56222
|
+
defineCommand({
|
|
56223
|
+
operation: taskOperations.update,
|
|
56224
|
+
path: ["tasks", "update"],
|
|
56225
|
+
summary: "Change a task; --repeat sets its repeat rule, --no-repeat ends the series.",
|
|
56226
|
+
returns: "Task",
|
|
56227
|
+
args: [{ name: "id", required: true, description: "Task ULID or ref (DO-142)." }],
|
|
56228
|
+
flags: [
|
|
56229
|
+
{
|
|
56230
|
+
name: "data",
|
|
56231
|
+
type: "string",
|
|
56232
|
+
description: "JSON UpdateTaskInput (or @file): the fields to change."
|
|
56233
|
+
},
|
|
56234
|
+
...REPEAT_FLAGS,
|
|
56235
|
+
{
|
|
56236
|
+
name: "no-repeat",
|
|
56237
|
+
type: "boolean",
|
|
56238
|
+
description: "Stop the series: no more occurrences; the done history stays."
|
|
56239
|
+
}
|
|
56240
|
+
],
|
|
56241
|
+
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.",
|
|
56242
|
+
run: async ({ args, flags, requireClient }) => {
|
|
56243
|
+
const id = requireArg(args, 0, "task id");
|
|
56244
|
+
const recurrence = repeatFromFlags(flags);
|
|
56245
|
+
const body = parseInput(updateTaskInputSchema, {
|
|
56246
|
+
...readDataFlag(flags),
|
|
56247
|
+
...recurrence !== undefined ? { recurrence } : {}
|
|
56248
|
+
});
|
|
56249
|
+
if (Object.keys(body).length === 0)
|
|
56250
|
+
throw usageError("nothing to change \u2014 pass --repeat, --no-repeat or --data");
|
|
56251
|
+
const client = requireClient();
|
|
56252
|
+
if (flagBool(flags, "dry-run")) {
|
|
56253
|
+
const before = await client.get(pathOf(taskOperations.get, { id }));
|
|
56254
|
+
return {
|
|
56255
|
+
data: {
|
|
56256
|
+
dryRun: true,
|
|
56257
|
+
op: "update",
|
|
56258
|
+
entity: "tasks",
|
|
56259
|
+
id,
|
|
56260
|
+
before,
|
|
56261
|
+
after: mergePreview(before, body),
|
|
56262
|
+
changes: body
|
|
56263
|
+
},
|
|
56264
|
+
render: renderRecord
|
|
56265
|
+
};
|
|
56266
|
+
}
|
|
56267
|
+
return {
|
|
56268
|
+
data: await client.patch(pathOf(taskOperations.update, { id }), body),
|
|
56269
|
+
render: renderTask
|
|
56270
|
+
};
|
|
56271
|
+
}
|
|
55458
56272
|
})
|
|
55459
56273
|
];
|
|
55460
56274
|
|
package/package.json
CHANGED