@robbeverhelst/do 1.160.0 → 1.161.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 +680 -44
- package/package.json +1 -1
package/dist/do.js
CHANGED
|
@@ -3514,6 +3514,8 @@ var tenants = pgTable("tenants", {
|
|
|
3514
3514
|
name: text("name").notNull(),
|
|
3515
3515
|
backdrop: text("backdrop"),
|
|
3516
3516
|
gaardHouseUrl: text("gaard_house_url"),
|
|
3517
|
+
awayFrom: date("away_from", { mode: "string" }),
|
|
3518
|
+
awayUntil: date("away_until", { mode: "string" }),
|
|
3517
3519
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
3518
3520
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
3519
3521
|
deletedAt: timestamp("deleted_at", { withTimezone: true })
|
|
@@ -3746,7 +3748,13 @@ var habitMeasurementType = pgEnum("habit_measurement_type", [
|
|
|
3746
3748
|
"negative"
|
|
3747
3749
|
]);
|
|
3748
3750
|
var habitPeriod = pgEnum("habit_period", ["day", "week", "month"]);
|
|
3749
|
-
var habitEntrySource = pgEnum("habit_entry_source", [
|
|
3751
|
+
var habitEntrySource = pgEnum("habit_entry_source", [
|
|
3752
|
+
"manual",
|
|
3753
|
+
"routine",
|
|
3754
|
+
"ingest",
|
|
3755
|
+
"agent"
|
|
3756
|
+
]);
|
|
3757
|
+
var habitEntryKind = pgEnum("habit_entry_kind", ["check_in", "skip"]);
|
|
3750
3758
|
var habitSource = pgEnum("habit_source", ["manual", "google_fit", "strava", "github"]);
|
|
3751
3759
|
var recurrenceMode = pgEnum("recurrence_mode", ["rigid", "flexible"]);
|
|
3752
3760
|
var recurrenceFreq = pgEnum("recurrence_freq", ["daily", "weekly", "monthly", "yearly"]);
|
|
@@ -3816,10 +3824,17 @@ var habits = pgTable("habits", {
|
|
|
3816
3824
|
recurrenceRuleId: text("recurrence_rule_id").references(() => recurrenceRules.id),
|
|
3817
3825
|
areaId: text("area_id").references(() => areas.id),
|
|
3818
3826
|
source: habitSource("source").notNull().default("manual"),
|
|
3819
|
-
sourceMetric: text("source_metric")
|
|
3827
|
+
sourceMetric: text("source_metric"),
|
|
3828
|
+
aliases: text("aliases").array().notNull().default(sql`'{}'::text[]`),
|
|
3829
|
+
timeOfDay: text("time_of_day"),
|
|
3830
|
+
pausedFrom: date("paused_from", { mode: "string" }),
|
|
3831
|
+
pausedUntil: date("paused_until", { mode: "string" }),
|
|
3832
|
+
remind: boolean("remind").notNull().default(false),
|
|
3833
|
+
retiredAt: timestamp("retired_at", { withTimezone: true })
|
|
3820
3834
|
}, (t) => [
|
|
3821
3835
|
index("habits_tenant_idx").on(t.tenantId),
|
|
3822
|
-
index("habits_tenant_area_idx").on(t.tenantId, t.areaId)
|
|
3836
|
+
index("habits_tenant_area_idx").on(t.tenantId, t.areaId),
|
|
3837
|
+
check("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]$'`)
|
|
3823
3838
|
]);
|
|
3824
3839
|
var habitEntries = pgTable("habit_entries", {
|
|
3825
3840
|
id: text("id").primaryKey(),
|
|
@@ -3827,9 +3842,12 @@ var habitEntries = pgTable("habit_entries", {
|
|
|
3827
3842
|
habitId: text("habit_id").notNull().references(() => habits.id),
|
|
3828
3843
|
value: doublePrecision("value").notNull().default(1),
|
|
3829
3844
|
source: habitEntrySource("source").notNull().default("manual"),
|
|
3845
|
+
kind: habitEntryKind("kind").notNull().default("check_in"),
|
|
3830
3846
|
occurredAt: timestamp("occurred_at", { withTimezone: true }).notNull(),
|
|
3831
3847
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
3832
|
-
externalId: text("external_id")
|
|
3848
|
+
externalId: text("external_id"),
|
|
3849
|
+
agentId: text("agent_id"),
|
|
3850
|
+
note: text("note")
|
|
3833
3851
|
}, (t) => [
|
|
3834
3852
|
index("habit_entries_habit_occurred_idx").on(t.habitId, t.occurredAt),
|
|
3835
3853
|
uniqueIndex("habit_entries_habit_external_uniq").on(t.habitId, t.externalId).where(sql`${t.externalId} IS NOT NULL`)
|
|
@@ -3874,7 +3892,8 @@ var notificationType = pgEnum("notification_type", [
|
|
|
3874
3892
|
"automation",
|
|
3875
3893
|
"streak_at_risk",
|
|
3876
3894
|
"reflection_nudge",
|
|
3877
|
-
"shutdown_nudge"
|
|
3895
|
+
"shutdown_nudge",
|
|
3896
|
+
"habit_reminder"
|
|
3878
3897
|
]);
|
|
3879
3898
|
var notifications = pgTable("notifications", {
|
|
3880
3899
|
id: text("id").primaryKey(),
|
|
@@ -18036,7 +18055,7 @@ var spaceSchema = createSelectSchema(tenants, {
|
|
|
18036
18055
|
createdAt: exports_coerce.date(),
|
|
18037
18056
|
updatedAt: exports_coerce.date(),
|
|
18038
18057
|
deletedAt: exports_coerce.date().nullable()
|
|
18039
|
-
}).omit({ gaardHouseUrl: true });
|
|
18058
|
+
}).omit({ gaardHouseUrl: true, awayFrom: true, awayUntil: true });
|
|
18040
18059
|
var backdropIdSchema = string2().trim().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "a backdrop id is lowercase words joined by dashes").max(40);
|
|
18041
18060
|
var updateSpaceInputSchema = object({
|
|
18042
18061
|
name: string2().trim().min(1).max(120).optional(),
|
|
@@ -18128,6 +18147,7 @@ var routineRunStepStatusSchema = _enum2(routineRunStepStatus.enumValues);
|
|
|
18128
18147
|
var habitMeasurementTypeSchema = _enum2(habitMeasurementType.enumValues);
|
|
18129
18148
|
var habitPeriodSchema = _enum2(habitPeriod.enumValues);
|
|
18130
18149
|
var habitEntrySourceSchema = _enum2(habitEntrySource.enumValues);
|
|
18150
|
+
var habitEntryKindSchema = _enum2(habitEntryKind.enumValues);
|
|
18131
18151
|
var recurrenceModeSchema = _enum2(recurrenceMode.enumValues);
|
|
18132
18152
|
var recurrenceFreqSchema = _enum2(recurrenceFreq.enumValues);
|
|
18133
18153
|
var recurrenceUnitSchema = _enum2(recurrenceUnit.enumValues);
|
|
@@ -18606,13 +18626,31 @@ var habitSourceSchema = _enum2(["manual", "google_fit", "strava", "github"]);
|
|
|
18606
18626
|
// ../../packages/schema/src/contracts/habits.ts
|
|
18607
18627
|
var habitSchema = createSelectSchema(habits, {
|
|
18608
18628
|
source: habitSourceSchema.default("manual"),
|
|
18609
|
-
sourceMetric: string2().nullable().default(null)
|
|
18629
|
+
sourceMetric: string2().nullable().default(null),
|
|
18630
|
+
aliases: array(string2()).default([]),
|
|
18631
|
+
timeOfDay: string2().nullable().default(null),
|
|
18632
|
+
pausedFrom: exports_iso.date().nullable().default(null),
|
|
18633
|
+
pausedUntil: exports_iso.date().nullable().default(null),
|
|
18634
|
+
remind: boolean3().default(false),
|
|
18635
|
+
retiredAt: exports_coerce.date().nullable().default(null)
|
|
18610
18636
|
});
|
|
18611
18637
|
var habitEntrySchema = createSelectSchema(habitEntries, {
|
|
18612
18638
|
occurredAt: exports_coerce.date(),
|
|
18613
18639
|
createdAt: exports_coerce.date(),
|
|
18614
|
-
externalId: string2().nullish()
|
|
18640
|
+
externalId: string2().nullish(),
|
|
18641
|
+
kind: habitEntryKindSchema.default("check_in"),
|
|
18642
|
+
agentId: string2().nullish(),
|
|
18643
|
+
note: string2().nullish()
|
|
18644
|
+
}).extend({
|
|
18645
|
+
by: string2().nullish()
|
|
18615
18646
|
});
|
|
18647
|
+
var HABIT_PARTS_OF_DAY = ["morning", "midday", "evening"];
|
|
18648
|
+
var habitTimeOfDaySchema = union([
|
|
18649
|
+
_enum2(HABIT_PARTS_OF_DAY),
|
|
18650
|
+
string2().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "Use a 24-hour time, like 07:30")
|
|
18651
|
+
]);
|
|
18652
|
+
var habitAliasSchema = string2().trim().min(1).max(60);
|
|
18653
|
+
var habitAliasesSchema = array(habitAliasSchema).max(12).transform((aliases) => aliases.filter((a, i) => aliases.findIndex((b) => b.toLowerCase() === a.toLowerCase()) === i));
|
|
18616
18654
|
var createHabitInputSchema = createInsertSchema(habits, {
|
|
18617
18655
|
id: ulidSchema,
|
|
18618
18656
|
source: habitSourceSchema.optional(),
|
|
@@ -18622,8 +18660,11 @@ var createHabitInputSchema = createInsertSchema(habits, {
|
|
|
18622
18660
|
targetUnit: string2().min(1).nullish(),
|
|
18623
18661
|
period: habitPeriodSchema.default("day"),
|
|
18624
18662
|
frequencyCount: number2().int().positive().default(1),
|
|
18625
|
-
areaId: ulidSchema.nullish()
|
|
18626
|
-
|
|
18663
|
+
areaId: ulidSchema.nullish(),
|
|
18664
|
+
aliases: habitAliasesSchema.optional(),
|
|
18665
|
+
timeOfDay: habitTimeOfDaySchema.nullish(),
|
|
18666
|
+
remind: boolean3().optional()
|
|
18667
|
+
}).omit({ tenantId: true, recurrenceRuleId: true, retiredAt: true }).extend({
|
|
18627
18668
|
title: string2().min(1),
|
|
18628
18669
|
displayKind: string2().min(1).nullish()
|
|
18629
18670
|
});
|
|
@@ -18634,14 +18675,29 @@ var updateHabitInputSchema = createUpdateSchema(habits, {
|
|
|
18634
18675
|
targetUnit: string2().min(1).nullish(),
|
|
18635
18676
|
period: habitPeriodSchema.optional(),
|
|
18636
18677
|
frequencyCount: number2().int().positive().optional(),
|
|
18637
|
-
areaId: ulidSchema.nullish()
|
|
18638
|
-
|
|
18678
|
+
areaId: ulidSchema.nullish(),
|
|
18679
|
+
aliases: habitAliasesSchema.optional(),
|
|
18680
|
+
timeOfDay: habitTimeOfDaySchema.nullish(),
|
|
18681
|
+
remind: boolean3().optional()
|
|
18682
|
+
}).omit({
|
|
18683
|
+
id: true,
|
|
18684
|
+
tenantId: true,
|
|
18685
|
+
measurementType: true,
|
|
18686
|
+
recurrenceRuleId: true,
|
|
18687
|
+
pausedFrom: true,
|
|
18688
|
+
pausedUntil: true,
|
|
18689
|
+
retiredAt: true
|
|
18690
|
+
}).extend({ title: string2().min(1).optional() });
|
|
18691
|
+
var HABIT_ENTRY_NOTE_MAX = 280;
|
|
18692
|
+
var habitEntryNoteSchema = string2().trim().max(HABIT_ENTRY_NOTE_MAX).transform((note) => note === "" ? null : note);
|
|
18639
18693
|
var createHabitEntryInputSchema = createInsertSchema(habitEntries, {
|
|
18640
18694
|
id: ulidSchema,
|
|
18641
18695
|
value: number2().nonnegative().default(1),
|
|
18642
18696
|
source: habitEntrySourceSchema.default("manual"),
|
|
18643
|
-
occurredAt: exports_coerce.date().optional()
|
|
18644
|
-
|
|
18697
|
+
occurredAt: exports_coerce.date().optional(),
|
|
18698
|
+
note: habitEntryNoteSchema.nullish()
|
|
18699
|
+
}).omit({ tenantId: true, habitId: true, createdAt: true, agentId: true, kind: true });
|
|
18700
|
+
var updateHabitEntryInputSchema = object({ note: habitEntryNoteSchema.nullable() }).strict();
|
|
18645
18701
|
var habitStreakStateSchema = _enum2(["active", "recovering", "broken"]);
|
|
18646
18702
|
var habitStreakSchema = object({
|
|
18647
18703
|
current: number2().int().nonnegative(),
|
|
@@ -18652,7 +18708,8 @@ var habitStreakSchema = object({
|
|
|
18652
18708
|
completionRate: number2().min(0).max(1),
|
|
18653
18709
|
state: habitStreakStateSchema,
|
|
18654
18710
|
graceBudget: number2().int().nonnegative(),
|
|
18655
|
-
graceRemaining: number2().int().nonnegative()
|
|
18711
|
+
graceRemaining: number2().int().nonnegative(),
|
|
18712
|
+
skippedToday: boolean3().default(false)
|
|
18656
18713
|
});
|
|
18657
18714
|
var habitRoutineRefSchema = object({
|
|
18658
18715
|
id: ulidSchema,
|
|
@@ -18661,7 +18718,19 @@ var habitRoutineRefSchema = object({
|
|
|
18661
18718
|
var habitLastCheckInSchema = object({
|
|
18662
18719
|
at: exports_coerce.date(),
|
|
18663
18720
|
value: number2(),
|
|
18664
|
-
source: habitEntrySourceSchema
|
|
18721
|
+
source: habitEntrySourceSchema,
|
|
18722
|
+
by: string2().nullish(),
|
|
18723
|
+
id: string2().optional(),
|
|
18724
|
+
note: string2().nullish()
|
|
18725
|
+
});
|
|
18726
|
+
var habitPauseSchema = object({
|
|
18727
|
+
until: exports_iso.date(),
|
|
18728
|
+
by: _enum2(["habit", "away"])
|
|
18729
|
+
});
|
|
18730
|
+
var habitTodaySchema = object({
|
|
18731
|
+
due: boolean3(),
|
|
18732
|
+
done: boolean3(),
|
|
18733
|
+
week: object({ done: number2().int().nonnegative(), target: number2().int().positive() }).optional()
|
|
18665
18734
|
});
|
|
18666
18735
|
var habitSummarySchema = habitSchema.extend({
|
|
18667
18736
|
title: string2(),
|
|
@@ -18669,12 +18738,16 @@ var habitSummarySchema = habitSchema.extend({
|
|
|
18669
18738
|
displayKind: string2().default("default"),
|
|
18670
18739
|
streak: habitStreakSchema,
|
|
18671
18740
|
inRoutines: array(habitRoutineRefSchema).default([]),
|
|
18672
|
-
lastCheckIn: habitLastCheckInSchema.nullable().default(null)
|
|
18741
|
+
lastCheckIn: habitLastCheckInSchema.nullable().default(null),
|
|
18742
|
+
pause: habitPauseSchema.nullable().default(null),
|
|
18743
|
+
today: habitTodaySchema.default({ due: true, done: false })
|
|
18673
18744
|
});
|
|
18674
18745
|
var habitHeatmapCellSchema = object({
|
|
18675
18746
|
date: string2(),
|
|
18676
18747
|
value: number2(),
|
|
18677
|
-
satisfied: boolean3()
|
|
18748
|
+
satisfied: boolean3(),
|
|
18749
|
+
rest: boolean3().default(false),
|
|
18750
|
+
notes: array(string2()).optional()
|
|
18678
18751
|
});
|
|
18679
18752
|
var habitDetailSchema = habitSummarySchema.extend({
|
|
18680
18753
|
heatmap: array(habitHeatmapCellSchema),
|
|
@@ -18686,11 +18759,69 @@ var habitEntriesQuerySchema = object({
|
|
|
18686
18759
|
before: exports_coerce.date().optional(),
|
|
18687
18760
|
limit: exports_coerce.number().int().min(1).max(200).default(50)
|
|
18688
18761
|
});
|
|
18762
|
+
var civilDateSchema = string2().regex(/^\d{4}-\d{2}-\d{2}$/, "expected YYYY-MM-DD");
|
|
18763
|
+
var habitCheckInModeSchema = _enum2(["add", "set"]);
|
|
18689
18764
|
var habitCheckInByTitleInputSchema = object({
|
|
18690
18765
|
title: string2().trim().min(1).max(300),
|
|
18691
18766
|
value: number2().nonnegative().optional(),
|
|
18692
|
-
|
|
18767
|
+
mode: habitCheckInModeSchema.default("add"),
|
|
18768
|
+
occurredAt: union([civilDateSchema, exports_coerce.date()]).optional(),
|
|
18769
|
+
note: habitEntryNoteSchema.nullish()
|
|
18770
|
+
}).strict();
|
|
18771
|
+
var habitCandidateSchema = object({
|
|
18772
|
+
id: ulidSchema,
|
|
18773
|
+
title: string2(),
|
|
18774
|
+
aliases: array(string2())
|
|
18775
|
+
});
|
|
18776
|
+
var habitCheckInAmbiguousSchema = object({
|
|
18777
|
+
error: object({
|
|
18778
|
+
code: literal("ambiguous_habit"),
|
|
18779
|
+
message: string2(),
|
|
18780
|
+
details: object({ candidates: array(habitCandidateSchema).min(2) })
|
|
18781
|
+
})
|
|
18782
|
+
});
|
|
18783
|
+
var habitAliasInputSchema = object({ alias: habitAliasSchema }).strict();
|
|
18784
|
+
var habitSkipInputSchema = object({
|
|
18785
|
+
date: exports_iso.date().optional(),
|
|
18786
|
+
note: habitEntryNoteSchema.nullish()
|
|
18693
18787
|
}).strict();
|
|
18788
|
+
var habitPauseInputSchema = object({ until: exports_iso.date() }).strict();
|
|
18789
|
+
var awayInputSchema = object({ until: exports_iso.date().nullable() }).strict();
|
|
18790
|
+
var awayStateSchema = object({
|
|
18791
|
+
until: exports_iso.date().nullable()
|
|
18792
|
+
});
|
|
18793
|
+
var habitDayPartSchema = _enum2(["morning", "midday", "evening", "anytime"]);
|
|
18794
|
+
var habitTodayStateSchema = _enum2(["done", "open", "skipped"]);
|
|
18795
|
+
var habitTodayItemSchema = object({
|
|
18796
|
+
habitId: ulidSchema,
|
|
18797
|
+
title: string2(),
|
|
18798
|
+
aliases: array(string2()).default([]),
|
|
18799
|
+
measurementType: habitMeasurementTypeSchema,
|
|
18800
|
+
unit: string2().nullable(),
|
|
18801
|
+
target: number2().nullable(),
|
|
18802
|
+
timeOfDay: string2().nullable().default(null),
|
|
18803
|
+
value: number2(),
|
|
18804
|
+
state: habitTodayStateSchema,
|
|
18805
|
+
week: object({ done: number2().int().nonnegative(), target: number2().int().positive() }).optional(),
|
|
18806
|
+
last: object({
|
|
18807
|
+
source: habitEntrySourceSchema,
|
|
18808
|
+
by: string2().optional(),
|
|
18809
|
+
at: exports_coerce.date()
|
|
18810
|
+
}).nullable().default(null)
|
|
18811
|
+
});
|
|
18812
|
+
var habitTodayRoutineSchema = object({
|
|
18813
|
+
routineId: ulidSchema,
|
|
18814
|
+
title: string2(),
|
|
18815
|
+
timeOfDay: string2().nullable(),
|
|
18816
|
+
stepsDone: number2().int().nonnegative(),
|
|
18817
|
+
stepsTotal: number2().int().nonnegative()
|
|
18818
|
+
});
|
|
18819
|
+
var habitsTodayResponseSchema = object({
|
|
18820
|
+
date: string2().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
18821
|
+
timezone: string2().min(1),
|
|
18822
|
+
groups: array(object({ part: habitDayPartSchema, habits: array(habitTodayItemSchema) })),
|
|
18823
|
+
routines: array(habitTodayRoutineSchema)
|
|
18824
|
+
});
|
|
18694
18825
|
|
|
18695
18826
|
// ../../packages/schema/src/contracts/metrics.ts
|
|
18696
18827
|
var metricKeySchema = _enum2(["weight", "sleep", "mood"]);
|
|
@@ -18880,6 +19011,26 @@ var updateRoutineRunStepInputSchema = object({
|
|
|
18880
19011
|
var updateRoutineRunInputSchema = object({
|
|
18881
19012
|
status: routineRunStatusSchema.optional()
|
|
18882
19013
|
}).strict();
|
|
19014
|
+
var routineRunsQuerySchema = object({
|
|
19015
|
+
limit: exports_coerce.number().int().min(1).max(200).optional()
|
|
19016
|
+
});
|
|
19017
|
+
var routineDoneInputSchema = object({
|
|
19018
|
+
id: ulidSchema.optional(),
|
|
19019
|
+
occurrenceDate: exports_coerce.date().nullish()
|
|
19020
|
+
}).strict();
|
|
19021
|
+
var routineDoneStepSchema = object({
|
|
19022
|
+
stepRunId: string2(),
|
|
19023
|
+
title: string2(),
|
|
19024
|
+
habitId: string2().nullable(),
|
|
19025
|
+
habitEntryId: string2().nullable()
|
|
19026
|
+
});
|
|
19027
|
+
var routineDoneResultSchema = object({
|
|
19028
|
+
run: routineRunViewSchema,
|
|
19029
|
+
taken: array(routineDoneStepSchema)
|
|
19030
|
+
});
|
|
19031
|
+
var routineRunUndoInputSchema = object({
|
|
19032
|
+
stepRunIds: array(string2().min(1)).min(1).max(100)
|
|
19033
|
+
}).strict();
|
|
18883
19034
|
|
|
18884
19035
|
// ../../packages/schema/src/contracts/projects.ts
|
|
18885
19036
|
var projectSchema = createSelectSchema(projects, {
|
|
@@ -19687,7 +19838,12 @@ var agentChangeSchema = object({
|
|
|
19687
19838
|
changedAt: exports_coerce.date(),
|
|
19688
19839
|
title: string2().nullable(),
|
|
19689
19840
|
actor: actorRefSchema,
|
|
19690
|
-
fields: array(agentChangeFieldSchema).optional()
|
|
19841
|
+
fields: array(agentChangeFieldSchema).optional(),
|
|
19842
|
+
checkIn: object({
|
|
19843
|
+
value: number2(),
|
|
19844
|
+
unit: string2().nullable(),
|
|
19845
|
+
measurementType: habitMeasurementTypeSchema
|
|
19846
|
+
}).optional()
|
|
19691
19847
|
});
|
|
19692
19848
|
var agentChangesPageSchema = object({
|
|
19693
19849
|
data: array(agentChangeSchema),
|
|
@@ -20843,7 +20999,7 @@ function parseCapture2(input, now) {
|
|
|
20843
20999
|
|
|
20844
21000
|
// src/meta.ts
|
|
20845
21001
|
var CLI_NAME = "do";
|
|
20846
|
-
var VERSION = "1.
|
|
21002
|
+
var VERSION = "1.161.0";
|
|
20847
21003
|
var API_PREFIX = "/api/v1";
|
|
20848
21004
|
var DEFAULT_API_URL = "http://localhost:3001";
|
|
20849
21005
|
|
|
@@ -21104,7 +21260,14 @@ var ENTITIES = [
|
|
|
21104
21260
|
readScope: "habits:read",
|
|
21105
21261
|
returns: "HabitSummary",
|
|
21106
21262
|
canGet: true,
|
|
21107
|
-
listFlags: [
|
|
21263
|
+
listFlags: [
|
|
21264
|
+
AREA_FLAG,
|
|
21265
|
+
UPDATED_SINCE_FLAG,
|
|
21266
|
+
{
|
|
21267
|
+
name: "retired",
|
|
21268
|
+
description: "list the Grown shelf (retired habits) instead of the growing ones"
|
|
21269
|
+
}
|
|
21270
|
+
],
|
|
21108
21271
|
write: {
|
|
21109
21272
|
scope: "habits:write",
|
|
21110
21273
|
createSchema: createHabitInputSchema,
|
|
@@ -21408,12 +21571,12 @@ var COMMAND_SPECS = [
|
|
|
21408
21571
|
},
|
|
21409
21572
|
{
|
|
21410
21573
|
path: ["today"],
|
|
21411
|
-
summary: "Show today's actionable tasks (scheduled, due, overdue, in-progress).",
|
|
21574
|
+
summary: "Show today's actionable tasks (scheduled, due, overdue, in-progress), then the habits and routines due today.",
|
|
21412
21575
|
args: [],
|
|
21413
21576
|
flags: [],
|
|
21414
21577
|
scopes: ["tasks:read"],
|
|
21415
21578
|
returns: "TodaySummary",
|
|
21416
|
-
notes: "
|
|
21579
|
+
notes: "Tasks are composed client-side from GET /api/v1/tasks (the local day from your clock); habits and routines come from GET /api/v1/habits/today (your saved time zone's day) under `habits`, left out when the key lacks habits:read."
|
|
21417
21580
|
},
|
|
21418
21581
|
{
|
|
21419
21582
|
path: ["now"],
|
|
@@ -21474,6 +21637,15 @@ var COMMAND_SPECS = [
|
|
|
21474
21637
|
scopes: ["tasks:read", "habits:read"],
|
|
21475
21638
|
returns: "ProgressDayDetail"
|
|
21476
21639
|
},
|
|
21640
|
+
{
|
|
21641
|
+
path: ["progress", "habits"],
|
|
21642
|
+
summary: "Habits this week: a done/skipped grid, each habit's strength word, patterns and when you check in.",
|
|
21643
|
+
args: [],
|
|
21644
|
+
flags: [],
|
|
21645
|
+
scopes: ["habits:read"],
|
|
21646
|
+
returns: "ProgressHabitsResponse",
|
|
21647
|
+
notes: "GET /api/v1/progress/habits \u2014 the week is your saved timezone's, Monday first. Strength is a word (steady, building, settling-in); patterns come from the insights worker with a confidence label."
|
|
21648
|
+
},
|
|
21477
21649
|
{
|
|
21478
21650
|
path: ["search"],
|
|
21479
21651
|
summary: "Search node titles and note bodies across the Space.",
|
|
@@ -21561,22 +21733,76 @@ var COMMAND_SPECS = [
|
|
|
21561
21733
|
description: "Amount logged (binary habits use 1).",
|
|
21562
21734
|
default: "1"
|
|
21563
21735
|
},
|
|
21564
|
-
{ name: "at", type: "string", description: "When it occurred (ISO); defaults to now." }
|
|
21736
|
+
{ name: "at", type: "string", description: "When it occurred (ISO); defaults to now." },
|
|
21737
|
+
{
|
|
21738
|
+
name: "note",
|
|
21739
|
+
type: "string",
|
|
21740
|
+
description: 'A short note on the check-in ("felt heavy"), at most 280 characters.'
|
|
21741
|
+
}
|
|
21565
21742
|
],
|
|
21566
21743
|
scopes: ["habits:write"],
|
|
21567
21744
|
returns: "HabitSummary"
|
|
21568
21745
|
},
|
|
21569
21746
|
{
|
|
21570
21747
|
path: ["habit", "check-in"],
|
|
21571
|
-
summary: 'Check a habit in by its title ("did my run").',
|
|
21572
|
-
args: [
|
|
21748
|
+
summary: 'Check a habit in by its title or an alias ("wake", "did my run").',
|
|
21749
|
+
args: [
|
|
21750
|
+
{ name: "title", required: true, description: "The habit's title or one of its aliases." }
|
|
21751
|
+
],
|
|
21573
21752
|
flags: [
|
|
21574
21753
|
{ name: "value", type: "string", description: "Amount logged; defaults to 1." },
|
|
21575
|
-
{
|
|
21754
|
+
{
|
|
21755
|
+
name: "set",
|
|
21756
|
+
type: "boolean",
|
|
21757
|
+
description: "Make --value the day's total instead of adding it (amount habits)."
|
|
21758
|
+
},
|
|
21759
|
+
{
|
|
21760
|
+
name: "at",
|
|
21761
|
+
type: "string",
|
|
21762
|
+
description: "When it occurred: an ISO instant, or a day (YYYY-MM-DD, your time zone); defaults to now."
|
|
21763
|
+
},
|
|
21764
|
+
{
|
|
21765
|
+
name: "note",
|
|
21766
|
+
type: "string",
|
|
21767
|
+
description: 'A short note on the check-in ("felt heavy"), at most 280 characters.'
|
|
21768
|
+
}
|
|
21576
21769
|
],
|
|
21577
21770
|
scopes: ["habits:write"],
|
|
21578
21771
|
returns: "HabitSummary",
|
|
21579
|
-
notes:
|
|
21772
|
+
notes: `POST /api/v1/habits/check-in. Title or alias, case-insensitive; several matches \u2192 409 with error.details.candidates (id, title, aliases), none \u2192 404 naming close titles; a habit counted from Google Fit, Strava or GitHub \u2192 409. --set writes the difference to the day's total as its own check-in. An Agent's key records the check-in as that Agent's ("Echo, 07:12"). --note puts a short note on it ("drill done, felt heavy" \u2192 --note "felt heavy"); the reply's lastCheckIn.id is the check-in, for \`do habit note\` later.`
|
|
21773
|
+
},
|
|
21774
|
+
{
|
|
21775
|
+
path: ["habit", "alias", "add"],
|
|
21776
|
+
summary: 'Give a habit another name check-ins match ("wake" for "Wake up by 07:00").',
|
|
21777
|
+
args: [
|
|
21778
|
+
{ name: "id", required: true, description: "Habit ULID." },
|
|
21779
|
+
{ name: "alias", required: true, description: "The other name." }
|
|
21780
|
+
],
|
|
21781
|
+
flags: [],
|
|
21782
|
+
scopes: ["habits:write"],
|
|
21783
|
+
returns: "HabitSummary",
|
|
21784
|
+
notes: "POST /api/v1/habits/:id/aliases. One already there (ignoring case) is a no-op; at most 12."
|
|
21785
|
+
},
|
|
21786
|
+
{
|
|
21787
|
+
path: ["habit", "alias", "rm"],
|
|
21788
|
+
summary: "Drop one of a habit's aliases.",
|
|
21789
|
+
args: [
|
|
21790
|
+
{ name: "id", required: true, description: "Habit ULID." },
|
|
21791
|
+
{ name: "alias", required: true, description: "The alias to drop (ignoring case)." }
|
|
21792
|
+
],
|
|
21793
|
+
flags: [],
|
|
21794
|
+
scopes: ["habits:write"],
|
|
21795
|
+
returns: "HabitSummary",
|
|
21796
|
+
notes: "DELETE /api/v1/habits/:id/aliases/:alias. 404 when the habit doesn't have it."
|
|
21797
|
+
},
|
|
21798
|
+
{
|
|
21799
|
+
path: ["habits", "today"],
|
|
21800
|
+
summary: "What's left today: the habits due by part of day, and today's routines.",
|
|
21801
|
+
args: [],
|
|
21802
|
+
flags: [],
|
|
21803
|
+
scopes: ["habits:read"],
|
|
21804
|
+
returns: "HabitsTodayResponse",
|
|
21805
|
+
notes: "GET /api/v1/habits/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."
|
|
21580
21806
|
},
|
|
21581
21807
|
{
|
|
21582
21808
|
path: ["habit", "check-ins"],
|
|
@@ -21589,6 +21815,149 @@ var COMMAND_SPECS = [
|
|
|
21589
21815
|
scopes: ["habits:read"],
|
|
21590
21816
|
returns: "HabitEntry"
|
|
21591
21817
|
},
|
|
21818
|
+
{
|
|
21819
|
+
path: ["habit", "note"],
|
|
21820
|
+
summary: 'Put a note on a check-in afterwards ("felt heavy"), or clear it.',
|
|
21821
|
+
args: [
|
|
21822
|
+
{ name: "id", required: true, description: "Habit ULID." },
|
|
21823
|
+
{
|
|
21824
|
+
name: "entry-id",
|
|
21825
|
+
required: true,
|
|
21826
|
+
description: "The check-in's ULID (`do habit check-ins <id>`, or lastCheckIn.id)."
|
|
21827
|
+
},
|
|
21828
|
+
{ name: "note", required: false, description: "The note, at most 280 characters." }
|
|
21829
|
+
],
|
|
21830
|
+
flags: [{ name: "clear", type: "boolean", description: "Drop the check-in's note." }],
|
|
21831
|
+
scopes: ["habits:write"],
|
|
21832
|
+
returns: "HabitEntry",
|
|
21833
|
+
notes: "PATCH /api/v1/habits/{id}/check-ins/{entryId}. Replaces the note; the amount and time stay (undo a wrong check-in instead). A skip's note is its why."
|
|
21834
|
+
},
|
|
21835
|
+
{
|
|
21836
|
+
path: ["habit", "skip"],
|
|
21837
|
+
summary: 'Skip a day: a neutral rest day, never a miss ("skip gym today").',
|
|
21838
|
+
args: [{ name: "id", required: true, description: "Habit ULID." }],
|
|
21839
|
+
flags: [
|
|
21840
|
+
{ name: "date", type: "string", description: "The day (YYYY-MM-DD); defaults to today." },
|
|
21841
|
+
{
|
|
21842
|
+
name: "note",
|
|
21843
|
+
type: "string",
|
|
21844
|
+
description: 'Why, if you like ("Dentist"); shows in the log.'
|
|
21845
|
+
}
|
|
21846
|
+
],
|
|
21847
|
+
scopes: ["habits:write"],
|
|
21848
|
+
returns: "HabitSummary",
|
|
21849
|
+
notes: "POST /api/v1/habits/{id}/skip. The streak treats the period as neutral and spends no grace; the habit leaves Now for the day. Skipping a skipped day changes nothing. Undo: delete the skip entry (`do habit check-ins <id>` lists it with kind `skip`)."
|
|
21850
|
+
},
|
|
21851
|
+
{
|
|
21852
|
+
path: ["routine", "done"],
|
|
21853
|
+
summary: `All taken: check in every open step of today's routine in one go ("took my supplements").`,
|
|
21854
|
+
args: [
|
|
21855
|
+
{
|
|
21856
|
+
name: "id",
|
|
21857
|
+
required: true,
|
|
21858
|
+
description: "Routine ULID (`do habits today` lists today's)."
|
|
21859
|
+
}
|
|
21860
|
+
],
|
|
21861
|
+
flags: [],
|
|
21862
|
+
scopes: ["routines:write"],
|
|
21863
|
+
returns: "RoutineDoneResult",
|
|
21864
|
+
notes: "POST /api/v1/routines/{id}/done. Today's run (your saved time zone's day), started if there's none. Each habit step checks in its habit; a habit already done today isn't counted twice; a step whose habit is skipped today is left alone. Calling it again changes nothing. Returns `run` and `taken` (the steps it resolved). Undo all of it with `do routine undo <run.id> <taken stepRunIds\u2026>`."
|
|
21865
|
+
},
|
|
21866
|
+
{
|
|
21867
|
+
path: ["routine", "undo"],
|
|
21868
|
+
summary: "Take an All taken back in one go: its steps reopen and lose their check-ins.",
|
|
21869
|
+
args: [
|
|
21870
|
+
{
|
|
21871
|
+
name: "runId",
|
|
21872
|
+
required: true,
|
|
21873
|
+
description: "The run's ULID (`run.id` from `do routine done`)."
|
|
21874
|
+
},
|
|
21875
|
+
{
|
|
21876
|
+
name: "stepRunIds",
|
|
21877
|
+
required: true,
|
|
21878
|
+
description: "The `taken` step ids from `do routine done`, space- or comma-separated."
|
|
21879
|
+
}
|
|
21880
|
+
],
|
|
21881
|
+
flags: [],
|
|
21882
|
+
scopes: ["routines:write"],
|
|
21883
|
+
returns: "RoutineRunView",
|
|
21884
|
+
notes: "POST /api/v1/routines/runs/{runId}/undo. Exactly the check-ins those steps wrote are removed; a finished run reopens. Steps no longer done are left alone."
|
|
21885
|
+
},
|
|
21886
|
+
{
|
|
21887
|
+
path: ["habit", "pause"],
|
|
21888
|
+
summary: "Pause a habit until the day it's back: off Now, no push, the plant waits.",
|
|
21889
|
+
args: [{ name: "id", required: true, description: "Habit ULID." }],
|
|
21890
|
+
flags: [
|
|
21891
|
+
{
|
|
21892
|
+
name: "until",
|
|
21893
|
+
type: "string",
|
|
21894
|
+
description: "The day it's back (YYYY-MM-DD), after today. Required."
|
|
21895
|
+
}
|
|
21896
|
+
],
|
|
21897
|
+
scopes: ["habits:write"],
|
|
21898
|
+
returns: "HabitSummary",
|
|
21899
|
+
notes: "POST /api/v1/habits/{id}/pause. Pausing a paused habit moves the day it's back. Paused days are neutral: the streak is frozen, never broken."
|
|
21900
|
+
},
|
|
21901
|
+
{
|
|
21902
|
+
path: ["habit", "remind"],
|
|
21903
|
+
summary: "Turn a habit's daily reminder on or off: one push at its time, never a follow-up.",
|
|
21904
|
+
args: [
|
|
21905
|
+
{ name: "id", required: true, description: "Habit ULID." },
|
|
21906
|
+
{ name: "state", required: true, description: "`on` or `off`." }
|
|
21907
|
+
],
|
|
21908
|
+
flags: [],
|
|
21909
|
+
scopes: ["habits:write"],
|
|
21910
|
+
returns: "HabitSummary",
|
|
21911
|
+
notes: "PATCH /api/v1/habits/{id} {remind}. Off by default. On, the habit sends one Web Push a day at its time of day (a part of day: morning 08:00, midday 12:00, evening 18:00; none: 09:00), only in waking hours (07:00\u201322:00), with Done / +1 / Skip today buttons; not when it's done, skipped, paused or off today."
|
|
21912
|
+
},
|
|
21913
|
+
{
|
|
21914
|
+
path: ["habit", "resume"],
|
|
21915
|
+
summary: "Resume a paused habit: it's back today.",
|
|
21916
|
+
args: [{ name: "id", required: true, description: "Habit ULID." }],
|
|
21917
|
+
flags: [],
|
|
21918
|
+
scopes: ["habits:write"],
|
|
21919
|
+
returns: "HabitSummary",
|
|
21920
|
+
notes: "POST /api/v1/habits/{id}/resume. The rest so far stays neutral."
|
|
21921
|
+
},
|
|
21922
|
+
{
|
|
21923
|
+
path: ["habit", "retire"],
|
|
21924
|
+
summary: `Retire a habit to the Grown shelf ("I'm done with the cold showers habit"): history kept.`,
|
|
21925
|
+
args: [{ name: "id", required: true, description: "Habit ULID." }],
|
|
21926
|
+
flags: [],
|
|
21927
|
+
scopes: ["habits:write"],
|
|
21928
|
+
returns: "HabitSummary",
|
|
21929
|
+
notes: "POST /api/v1/habits/{id}/retire. Not a delete: it keeps every check-in and its plant blooms on the Grown shelf. It leaves Now and Today, sends no push, takes no check-in by title and is never due; Progress keeps its past. `do habits list --retired` lists the shelf. Undo: `do habit unretire <id>`."
|
|
21930
|
+
},
|
|
21931
|
+
{
|
|
21932
|
+
path: ["habit", "unretire"],
|
|
21933
|
+
summary: "Bring a grown (retired) habit back: growing again from today.",
|
|
21934
|
+
args: [{ name: "id", required: true, description: "Habit ULID." }],
|
|
21935
|
+
flags: [],
|
|
21936
|
+
scopes: ["habits:write"],
|
|
21937
|
+
returns: "HabitSummary",
|
|
21938
|
+
notes: "POST /api/v1/habits/{id}/unretire. Its history is intact."
|
|
21939
|
+
},
|
|
21940
|
+
{
|
|
21941
|
+
path: ["away"],
|
|
21942
|
+
summary: 'Away until a day ("away till Monday"): every habit rests. `do away off` comes back.',
|
|
21943
|
+
args: [
|
|
21944
|
+
{
|
|
21945
|
+
name: "off",
|
|
21946
|
+
required: false,
|
|
21947
|
+
description: "`off` comes back now; omit it (and --until) to show the away state."
|
|
21948
|
+
}
|
|
21949
|
+
],
|
|
21950
|
+
flags: [
|
|
21951
|
+
{
|
|
21952
|
+
name: "until",
|
|
21953
|
+
type: "string",
|
|
21954
|
+
description: "The day you're back (YYYY-MM-DD), after today."
|
|
21955
|
+
}
|
|
21956
|
+
],
|
|
21957
|
+
scopes: ["habits:read", "habits:write"],
|
|
21958
|
+
returns: "AwayState",
|
|
21959
|
+
notes: "GET/PUT /api/v1/habits/away. While away, habits leave Now, reminders stop and plants wait; tasks and events carry on. Reading needs habits:read, setting habits:write."
|
|
21960
|
+
},
|
|
21592
21961
|
{
|
|
21593
21962
|
path: ["metrics", "list"],
|
|
21594
21963
|
summary: "Show weight, sleep and mood as daily trend lines (no target, no streak).",
|
|
@@ -22756,15 +23125,81 @@ function renderTask(data, mode) {
|
|
|
22756
23125
|
}
|
|
22757
23126
|
function renderToday(data, mode) {
|
|
22758
23127
|
const result = data;
|
|
22759
|
-
|
|
22760
|
-
|
|
22761
|
-
|
|
22762
|
-
|
|
22763
|
-
|
|
22764
|
-
|
|
22765
|
-
|
|
22766
|
-
|
|
22767
|
-
|
|
23128
|
+
const tasks = result.count === 0 ? paint(mode, "green", `Nothing scheduled for ${result.date}. Inbox zero. \u2713`) : [
|
|
23129
|
+
paint(mode, "bold", `Today \xB7 ${result.date} \xB7 ${result.count} item(s)`),
|
|
23130
|
+
...result.items.map((item) => {
|
|
23131
|
+
const tag = paint(mode, bucketColor(item.bucket), `[${item.bucket}]`);
|
|
23132
|
+
const star = item.starred ? paint(mode, "yellow", " \u2605") : "";
|
|
23133
|
+
return ` ${tag} ${item.title}${star} ${paint(mode, "dim", item.id)}`;
|
|
23134
|
+
})
|
|
23135
|
+
].join(`
|
|
23136
|
+
`);
|
|
23137
|
+
const habits = result.habits ? habitLines(result.habits, mode) : [];
|
|
23138
|
+
return habits.length > 0 ? `${tasks}
|
|
23139
|
+
|
|
23140
|
+
${habits.join(`
|
|
23141
|
+
`)}` : tasks;
|
|
23142
|
+
}
|
|
23143
|
+
var PART_LABEL = {
|
|
23144
|
+
morning: "Morning",
|
|
23145
|
+
midday: "Midday",
|
|
23146
|
+
evening: "Evening",
|
|
23147
|
+
anytime: "Any time today"
|
|
23148
|
+
};
|
|
23149
|
+
function clock(at, timezone) {
|
|
23150
|
+
return new Intl.DateTimeFormat("en-GB", {
|
|
23151
|
+
timeZone: timezone,
|
|
23152
|
+
hour: "2-digit",
|
|
23153
|
+
minute: "2-digit",
|
|
23154
|
+
hourCycle: "h23"
|
|
23155
|
+
}).format(new Date(at));
|
|
23156
|
+
}
|
|
23157
|
+
function civilDay(at, timezone) {
|
|
23158
|
+
return new Intl.DateTimeFormat("en-CA", {
|
|
23159
|
+
timeZone: timezone,
|
|
23160
|
+
year: "numeric",
|
|
23161
|
+
month: "2-digit",
|
|
23162
|
+
day: "2-digit"
|
|
23163
|
+
}).format(new Date(at));
|
|
23164
|
+
}
|
|
23165
|
+
function habitLines(day, mode) {
|
|
23166
|
+
const lines = [];
|
|
23167
|
+
for (const group of day.groups) {
|
|
23168
|
+
lines.push(paint(mode, "bold", PART_LABEL[group.part]));
|
|
23169
|
+
for (const h of group.habits) {
|
|
23170
|
+
const mark = h.state === "done" ? paint(mode, "green", "\u2713") : h.state === "skipped" ? paint(mode, "dim", "\u2013") : "\u25CB";
|
|
23171
|
+
const meta = [];
|
|
23172
|
+
if (h.timeOfDay)
|
|
23173
|
+
meta.push(h.timeOfDay);
|
|
23174
|
+
if (h.target !== null && h.measurementType !== "binary") {
|
|
23175
|
+
meta.push(`${h.value} of ${h.target}${h.unit ? ` ${h.unit}` : ""}`);
|
|
23176
|
+
}
|
|
23177
|
+
if (h.week)
|
|
23178
|
+
meta.push(`${h.week.done} of ${h.week.target} this week`);
|
|
23179
|
+
if (h.last?.by && civilDay(h.last.at, day.timezone) === day.date) {
|
|
23180
|
+
meta.push(`${h.last.by}, ${clock(h.last.at, day.timezone)}`);
|
|
23181
|
+
}
|
|
23182
|
+
const tail = meta.length > 0 ? paint(mode, "dim", ` \xB7 ${meta.join(" \xB7 ")}`) : "";
|
|
23183
|
+
lines.push(` ${mark} ${h.title}${tail} ${paint(mode, "dim", h.habitId)}`);
|
|
23184
|
+
}
|
|
23185
|
+
}
|
|
23186
|
+
if (day.routines.length > 0) {
|
|
23187
|
+
lines.push(paint(mode, "bold", "Routines"));
|
|
23188
|
+
for (const r of day.routines) {
|
|
23189
|
+
const when = r.timeOfDay ? ` ${r.timeOfDay}` : "";
|
|
23190
|
+
const steps = paint(mode, "dim", ` \xB7 ${r.stepsDone} of ${r.stepsTotal} steps`);
|
|
23191
|
+
lines.push(` ${r.title}${when}${steps} ${paint(mode, "dim", r.routineId)}`);
|
|
23192
|
+
}
|
|
23193
|
+
}
|
|
23194
|
+
return lines;
|
|
23195
|
+
}
|
|
23196
|
+
function renderHabitsToday(data, mode) {
|
|
23197
|
+
const day = data;
|
|
23198
|
+
const lines = habitLines(day, mode);
|
|
23199
|
+
const header = paint(mode, "bold", `Habits \xB7 ${day.date} (${day.timezone})`);
|
|
23200
|
+
if (lines.length === 0)
|
|
23201
|
+
return `${header}
|
|
23202
|
+
Nothing due today.`;
|
|
22768
23203
|
return [header, ...lines].join(`
|
|
22769
23204
|
`);
|
|
22770
23205
|
}
|
|
@@ -22779,13 +23214,45 @@ function bucketColor(bucket) {
|
|
|
22779
23214
|
}
|
|
22780
23215
|
function renderHabit(data, mode) {
|
|
22781
23216
|
const h = data;
|
|
23217
|
+
if (h.retiredAt) {
|
|
23218
|
+
return `${h.title ?? "(habit)"} \u2014 grown ${h.retiredAt.slice(0, 10)}, on the Grown shelf with its history
|
|
23219
|
+
${paint(mode, "dim", h.id)}`;
|
|
23220
|
+
}
|
|
22782
23221
|
const streak = h.streak;
|
|
22783
23222
|
const flame = streak?.satisfiedToday ? paint(mode, "green", "\u2713 today") : "not yet today";
|
|
22784
23223
|
const pct = typeof streak?.completionRate === "number" ? ` \xB7 ${Math.round(streak.completionRate * 100)}% lately` : "";
|
|
22785
23224
|
const recovering = streak?.state === "recovering" ? paint(mode, "yellow", " \xB7 recovering") : "";
|
|
22786
23225
|
const counts = streak ? ` \xB7 streak ${streak.current ?? 0} (best ${streak.longest ?? 0})` : "";
|
|
22787
|
-
|
|
22788
|
-
|
|
23226
|
+
const rest = h.pause ? ` \xB7 ${h.pause.by === "away" ? "away" : "paused"}, back ${h.pause.until}` : streak?.skippedToday ? " \xB7 rest day" : "";
|
|
23227
|
+
const last = h.lastCheckIn?.id ? `
|
|
23228
|
+
${paint(mode, "dim", `last check-in ${h.lastCheckIn.id}`)}${h.lastCheckIn.note ? ` "${h.lastCheckIn.note}"` : ""}` : "";
|
|
23229
|
+
return `${h.title ?? "(habit)"} \u2014 ${flame}${pct}${counts}${recovering}${rest}
|
|
23230
|
+
${paint(mode, "dim", h.id)}${last}`;
|
|
23231
|
+
}
|
|
23232
|
+
function renderHabitEntry(data, mode) {
|
|
23233
|
+
const e = data;
|
|
23234
|
+
const said = e.note ? `Noted: "${e.note}"` : "No note.";
|
|
23235
|
+
return `${said}
|
|
23236
|
+
${paint(mode, "dim", e.id ?? "")}`;
|
|
23237
|
+
}
|
|
23238
|
+
function renderRoutineDone(data, mode) {
|
|
23239
|
+
const d = data;
|
|
23240
|
+
const title = d.run?.routineTitle ?? "(routine)";
|
|
23241
|
+
const taken = d.taken ?? [];
|
|
23242
|
+
if (taken.length === 0)
|
|
23243
|
+
return `${title}: nothing left to check in today.`;
|
|
23244
|
+
const names = taken.map((t) => t.title).join(", ");
|
|
23245
|
+
const undo = `do routine undo ${d.run?.id ?? "<run>"} ${taken.map((t) => t.stepRunId).join(" ")}`;
|
|
23246
|
+
return `${title}: checked in ${names}.
|
|
23247
|
+
${paint(mode, "dim", `undo: ${undo}`)}`;
|
|
23248
|
+
}
|
|
23249
|
+
function renderRoutineUndo(data) {
|
|
23250
|
+
const run = data;
|
|
23251
|
+
return `${run.routineTitle ?? "(routine)"}: undone. Those check-ins are gone.`;
|
|
23252
|
+
}
|
|
23253
|
+
function renderAway(data) {
|
|
23254
|
+
const until = data.until;
|
|
23255
|
+
return until ? `Away until ${until}. Habits are resting; tasks carry on.` : "Here. Habits are on.";
|
|
22789
23256
|
}
|
|
22790
23257
|
function renderWhoami(data, mode) {
|
|
22791
23258
|
const w = data;
|
|
@@ -23076,6 +23543,44 @@ function renderProgress(data, mode) {
|
|
|
23076
23543
|
return out.join(`
|
|
23077
23544
|
`);
|
|
23078
23545
|
}
|
|
23546
|
+
var CELL = {
|
|
23547
|
+
done: "\u25A0",
|
|
23548
|
+
skipped: "\u2013",
|
|
23549
|
+
empty: "\xB7",
|
|
23550
|
+
ahead: " "
|
|
23551
|
+
};
|
|
23552
|
+
var hh = (h) => `${String(h).padStart(2, "0")}:00`;
|
|
23553
|
+
var usually = (when) => when?.usual ? `usually ${hh(when.usual.from)}\u2013${hh(when.usual.to)}` : null;
|
|
23554
|
+
function renderProgressHabits(data, mode) {
|
|
23555
|
+
const p = data;
|
|
23556
|
+
const out = [
|
|
23557
|
+
paint(mode, "bold", `Habits \xB7 week of ${p.week?.from ?? "?"} \u2192 ${p.week?.to ?? "?"} (${p.timezone ?? "?"})`)
|
|
23558
|
+
];
|
|
23559
|
+
const habits = p.habits ?? [];
|
|
23560
|
+
if (habits.length === 0)
|
|
23561
|
+
out.push(paint(mode, "dim", " (no habits yet)"));
|
|
23562
|
+
const width = Math.max(0, ...habits.map((h) => h.name.length));
|
|
23563
|
+
for (const h of habits) {
|
|
23564
|
+
const cells = h.week.map((d) => CELL[d.state]).join(" ");
|
|
23565
|
+
const count = h.weekOf === null ? `${h.weekDone} done` : `${h.weekDone} of ${h.weekOf}`;
|
|
23566
|
+
const extra = [h.strengthWord.replace("-", " "), usually(h.when)].filter(Boolean).join(" \xB7 ");
|
|
23567
|
+
out.push(` ${h.name.padEnd(width)} ${cells} ${count} \xB7 ${extra}`);
|
|
23568
|
+
}
|
|
23569
|
+
const patterns = p.patterns ?? [];
|
|
23570
|
+
out.push("", paint(mode, "bold", "Patterns"));
|
|
23571
|
+
if (patterns.length === 0) {
|
|
23572
|
+
out.push(paint(mode, "dim", " (none yet: patterns need a few weeks of check-ins)"));
|
|
23573
|
+
}
|
|
23574
|
+
for (const x of patterns) {
|
|
23575
|
+
out.push(` ${x.title}`, ` ${x.body}`, ` ${paint(mode, "dim", x.confidenceLabel)}`);
|
|
23576
|
+
}
|
|
23577
|
+
const when = usually(p.when);
|
|
23578
|
+
if (when && p.when) {
|
|
23579
|
+
out.push("", `When ${when} (${Math.round((p.when.usual?.share ?? 0) * 100)}% of ${p.when.checkins} check-ins)`);
|
|
23580
|
+
}
|
|
23581
|
+
return out.join(`
|
|
23582
|
+
`);
|
|
23583
|
+
}
|
|
23079
23584
|
function renderProgressDay(data, mode) {
|
|
23080
23585
|
const d = data;
|
|
23081
23586
|
const done = d.done ?? [];
|
|
@@ -23229,7 +23734,7 @@ async function dispatchEntity(entity, rest, flags, requireClient) {
|
|
|
23229
23734
|
if (op === "list") {
|
|
23230
23735
|
const query = {};
|
|
23231
23736
|
for (const f of entity.listFlags) {
|
|
23232
|
-
const value = flagString(flags, f.name);
|
|
23737
|
+
const value = flags[f.name] === true ? "true" : flagString(flags, f.name);
|
|
23233
23738
|
if (value !== undefined)
|
|
23234
23739
|
query[f.query ?? f.name] = value;
|
|
23235
23740
|
else if (f.required)
|
|
@@ -23347,8 +23852,16 @@ var HANDLERS = {
|
|
|
23347
23852
|
tasks.push(...page.data);
|
|
23348
23853
|
cursor = page.nextCursor ?? undefined;
|
|
23349
23854
|
} while (cursor);
|
|
23855
|
+
const habits = await client.get("/habits/today").catch((error) => {
|
|
23856
|
+
if (error instanceof CliError && (error.code === "auth" || error.exitCode === EXIT.NOT_FOUND))
|
|
23857
|
+
return;
|
|
23858
|
+
throw error;
|
|
23859
|
+
});
|
|
23350
23860
|
return {
|
|
23351
|
-
data:
|
|
23861
|
+
data: {
|
|
23862
|
+
...selectToday(tasks, io.now()),
|
|
23863
|
+
...Array.isArray(habits?.groups) ? { habits } : {}
|
|
23864
|
+
},
|
|
23352
23865
|
render: renderToday
|
|
23353
23866
|
};
|
|
23354
23867
|
},
|
|
@@ -23403,6 +23916,10 @@ var HANDLERS = {
|
|
|
23403
23916
|
data: await requireClient().get("/progress", { query: { days: flagString(flags, "days") } }),
|
|
23404
23917
|
render: renderProgress
|
|
23405
23918
|
}),
|
|
23919
|
+
"progress habits": async ({ requireClient }) => ({
|
|
23920
|
+
data: await requireClient().get("/progress/habits"),
|
|
23921
|
+
render: renderProgressHabits
|
|
23922
|
+
}),
|
|
23406
23923
|
"progress day": async ({ args, requireClient }) => {
|
|
23407
23924
|
const date = requireArg(args, 0, "date");
|
|
23408
23925
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
@@ -23592,10 +24109,12 @@ var HANDLERS = {
|
|
|
23592
24109
|
if (Number.isNaN(value))
|
|
23593
24110
|
throw usageError("--value must be a number");
|
|
23594
24111
|
const at = flagString(flags, "at");
|
|
24112
|
+
const note = flagString(flags, "note");
|
|
23595
24113
|
const input = parseInput(createHabitEntryInputSchema, {
|
|
23596
24114
|
id: ulid3(),
|
|
23597
24115
|
value,
|
|
23598
|
-
...at ? { occurredAt: at } : {}
|
|
24116
|
+
...at ? { occurredAt: at } : {},
|
|
24117
|
+
...note !== undefined ? { note } : {}
|
|
23599
24118
|
});
|
|
23600
24119
|
return {
|
|
23601
24120
|
data: await requireClient().post(`/habits/${id}/check-ins`, input),
|
|
@@ -23611,13 +24130,44 @@ var HANDLERS = {
|
|
|
23611
24130
|
if (value !== undefined && Number.isNaN(value))
|
|
23612
24131
|
throw usageError("--value must be a number");
|
|
23613
24132
|
const at = flagString(flags, "at");
|
|
24133
|
+
const set = flags.set === true;
|
|
24134
|
+
if (set && value === undefined)
|
|
24135
|
+
throw usageError("--set needs --value, the day's new total");
|
|
24136
|
+
const note = flagString(flags, "note");
|
|
23614
24137
|
const input = parseInput(habitCheckInByTitleInputSchema, {
|
|
23615
24138
|
title,
|
|
23616
24139
|
...value !== undefined ? { value } : {},
|
|
23617
|
-
|
|
24140
|
+
mode: set ? "set" : "add",
|
|
24141
|
+
...at ? { occurredAt: at } : {},
|
|
24142
|
+
...note !== undefined ? { note } : {}
|
|
23618
24143
|
});
|
|
23619
24144
|
return { data: await requireClient().post("/habits/check-in", input), render: renderHabit };
|
|
23620
24145
|
},
|
|
24146
|
+
"habit alias add": async ({ args, requireClient }) => {
|
|
24147
|
+
const id = requireArg(args, 0, "habit id");
|
|
24148
|
+
const alias = args.slice(1).join(" ").trim();
|
|
24149
|
+
if (!alias)
|
|
24150
|
+
throw usageError("habit alias add needs an alias, e.g. `do habit alias add <id> wake`");
|
|
24151
|
+
const input = parseInput(habitAliasInputSchema, { alias });
|
|
24152
|
+
return {
|
|
24153
|
+
data: await requireClient().post(`/habits/${id}/aliases`, input),
|
|
24154
|
+
render: renderHabit
|
|
24155
|
+
};
|
|
24156
|
+
},
|
|
24157
|
+
"habit alias rm": async ({ args, requireClient }) => {
|
|
24158
|
+
const id = requireArg(args, 0, "habit id");
|
|
24159
|
+
const alias = args.slice(1).join(" ").trim();
|
|
24160
|
+
if (!alias)
|
|
24161
|
+
throw usageError("habit alias rm needs the alias to drop");
|
|
24162
|
+
return {
|
|
24163
|
+
data: await requireClient().delete(`/habits/${id}/aliases/${encodeURIComponent(alias)}`),
|
|
24164
|
+
render: renderHabit
|
|
24165
|
+
};
|
|
24166
|
+
},
|
|
24167
|
+
"habits today": async ({ requireClient }) => ({
|
|
24168
|
+
data: await requireClient().get("/habits/today"),
|
|
24169
|
+
render: renderHabitsToday
|
|
24170
|
+
}),
|
|
23621
24171
|
"habit check-ins": async ({ args, flags, requireClient }) => {
|
|
23622
24172
|
const id = requireArg(args, 0, "habit id");
|
|
23623
24173
|
const query = {
|
|
@@ -23629,6 +24179,92 @@ var HANDLERS = {
|
|
|
23629
24179
|
render: renderList
|
|
23630
24180
|
};
|
|
23631
24181
|
},
|
|
24182
|
+
"habit note": async ({ args, flags, requireClient }) => {
|
|
24183
|
+
const id = requireArg(args, 0, "habit id");
|
|
24184
|
+
const entryId = requireArg(args, 1, "check-in id");
|
|
24185
|
+
const text = args.slice(2).join(" ").trim();
|
|
24186
|
+
const clear = flags.clear === true;
|
|
24187
|
+
if (clear && text)
|
|
24188
|
+
throw usageError("habit note takes a note or --clear, not both");
|
|
24189
|
+
if (!clear && !text)
|
|
24190
|
+
throw usageError('habit note needs the note, e.g. `do habit note <id> <check-in id> "felt heavy"`');
|
|
24191
|
+
const input = parseInput(updateHabitEntryInputSchema, { note: clear ? null : text });
|
|
24192
|
+
return {
|
|
24193
|
+
data: await requireClient().patch(`/habits/${id}/check-ins/${entryId}`, input),
|
|
24194
|
+
render: renderHabitEntry
|
|
24195
|
+
};
|
|
24196
|
+
},
|
|
24197
|
+
"habit skip": async ({ args, flags, requireClient }) => {
|
|
24198
|
+
const id = requireArg(args, 0, "habit id");
|
|
24199
|
+
const date = flagString(flags, "date");
|
|
24200
|
+
const note = flagString(flags, "note");
|
|
24201
|
+
const input = parseInput(habitSkipInputSchema, {
|
|
24202
|
+
...date ? { date } : {},
|
|
24203
|
+
...note !== undefined ? { note } : {}
|
|
24204
|
+
});
|
|
24205
|
+
return { data: await requireClient().post(`/habits/${id}/skip`, input), render: renderHabit };
|
|
24206
|
+
},
|
|
24207
|
+
"habit pause": async ({ args, flags, requireClient }) => {
|
|
24208
|
+
const id = requireArg(args, 0, "habit id");
|
|
24209
|
+
const until = flagString(flags, "until");
|
|
24210
|
+
if (!until)
|
|
24211
|
+
throw usageError("habit pause needs --until <YYYY-MM-DD>, the day it's back");
|
|
24212
|
+
const input = parseInput(habitPauseInputSchema, { until });
|
|
24213
|
+
return { data: await requireClient().post(`/habits/${id}/pause`, input), render: renderHabit };
|
|
24214
|
+
},
|
|
24215
|
+
"habit remind": async ({ args, requireClient }) => {
|
|
24216
|
+
const id = requireArg(args, 0, "habit id");
|
|
24217
|
+
const state = requireArg(args, 1, "on|off");
|
|
24218
|
+
if (state !== "on" && state !== "off")
|
|
24219
|
+
throw usageError("habit remind takes `on` or `off`");
|
|
24220
|
+
const input = parseInput(updateHabitInputSchema, { remind: state === "on" });
|
|
24221
|
+
return { data: await requireClient().patch(`/habits/${id}`, input), render: renderHabit };
|
|
24222
|
+
},
|
|
24223
|
+
"habit resume": async ({ args, requireClient }) => {
|
|
24224
|
+
const id = requireArg(args, 0, "habit id");
|
|
24225
|
+
return { data: await requireClient().post(`/habits/${id}/resume`, {}), render: renderHabit };
|
|
24226
|
+
},
|
|
24227
|
+
"habit retire": async ({ args, requireClient }) => {
|
|
24228
|
+
const id = requireArg(args, 0, "habit id");
|
|
24229
|
+
return { data: await requireClient().post(`/habits/${id}/retire`, {}), render: renderHabit };
|
|
24230
|
+
},
|
|
24231
|
+
"habit unretire": async ({ args, requireClient }) => {
|
|
24232
|
+
const id = requireArg(args, 0, "habit id");
|
|
24233
|
+
return { data: await requireClient().post(`/habits/${id}/unretire`, {}), render: renderHabit };
|
|
24234
|
+
},
|
|
24235
|
+
away: async ({ args, flags, requireClient }) => {
|
|
24236
|
+
const until = flagString(flags, "until");
|
|
24237
|
+
const off = args[0] === "off";
|
|
24238
|
+
if (args.length > 0 && !off)
|
|
24239
|
+
throw usageError("`do away` takes `off`, or --until <YYYY-MM-DD>");
|
|
24240
|
+
if (off && until)
|
|
24241
|
+
throw usageError("`do away off` comes back now; it takes no --until");
|
|
24242
|
+
if (!off && !until) {
|
|
24243
|
+
return { data: await requireClient().get("/habits/away"), render: renderAway };
|
|
24244
|
+
}
|
|
24245
|
+
const input = parseInput(awayInputSchema, { until: off ? null : until });
|
|
24246
|
+
return { data: await requireClient().put("/habits/away", input), render: renderAway };
|
|
24247
|
+
},
|
|
24248
|
+
"routine done": async ({ args, requireClient }) => {
|
|
24249
|
+
const id = requireArg(args, 0, "routine id");
|
|
24250
|
+
const input = parseInput(routineDoneInputSchema, {});
|
|
24251
|
+
return {
|
|
24252
|
+
data: await requireClient().post(`/routines/${id}/done`, input),
|
|
24253
|
+
render: renderRoutineDone
|
|
24254
|
+
};
|
|
24255
|
+
},
|
|
24256
|
+
"routine undo": async ({ args, requireClient }) => {
|
|
24257
|
+
const runId = requireArg(args, 0, "run id");
|
|
24258
|
+
const stepRunIds = args.slice(1).flatMap((a) => a.split(",")).map((a) => a.trim()).filter(Boolean);
|
|
24259
|
+
if (stepRunIds.length === 0) {
|
|
24260
|
+
throw usageError("routine undo needs the step ids `do routine done` printed, e.g. `do routine undo <run> <step> <step>`");
|
|
24261
|
+
}
|
|
24262
|
+
const input = parseInput(routineRunUndoInputSchema, { stepRunIds });
|
|
24263
|
+
return {
|
|
24264
|
+
data: await requireClient().post(`/routines/runs/${runId}/undo`, input),
|
|
24265
|
+
render: renderRoutineUndo
|
|
24266
|
+
};
|
|
24267
|
+
},
|
|
23632
24268
|
"metrics list": async ({ flags, requireClient }) => {
|
|
23633
24269
|
const query = { days: flagString(flags, "days") };
|
|
23634
24270
|
return { data: await requireClient().get("/metrics", { query }), render: renderList };
|
package/package.json
CHANGED