@robbeverhelst/do 1.160.0 → 1.162.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/do.js +1117 -69
  2. 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 })
@@ -3572,6 +3574,7 @@ var agents = pgTable("agents", {
3572
3574
  tenantId: text("tenant_id").notNull().references(() => tenants.id),
3573
3575
  name: text("name").notNull(),
3574
3576
  revokedAt: timestamp("revoked_at", { withTimezone: true }),
3577
+ trustedDesks: text("trusted_desks").array().notNull().default([]),
3575
3578
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
3576
3579
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
3577
3580
  }, (t) => [
@@ -3597,8 +3600,13 @@ var areas = pgTable("areas", {
3597
3600
  color: areaColor("color").notNull().default("teal"),
3598
3601
  icon: text("icon"),
3599
3602
  reviewCadence: reviewCadence("review_cadence"),
3600
- archivedAt: timestamp("archived_at", { withTimezone: true })
3601
- }, (t) => [index("areas_tenant_idx").on(t.tenantId)]);
3603
+ archivedAt: timestamp("archived_at", { withTimezone: true }),
3604
+ purpose: text("purpose"),
3605
+ standards: jsonb("standards").$type().notNull().default(sql`'[]'::jsonb`)
3606
+ }, (t) => [
3607
+ index("areas_tenant_idx").on(t.tenantId),
3608
+ check("areas_standards_chk", sql`jsonb_typeof(${t.standards}) = 'array' AND jsonb_array_length(${t.standards}) <= 5`)
3609
+ ]);
3602
3610
 
3603
3611
  // ../../packages/schema/src/db/core.ts
3604
3612
  var routineKind = pgEnum("routine_kind", ["sequence", "time_blocked", "checklist"]);
@@ -3746,7 +3754,13 @@ var habitMeasurementType = pgEnum("habit_measurement_type", [
3746
3754
  "negative"
3747
3755
  ]);
3748
3756
  var habitPeriod = pgEnum("habit_period", ["day", "week", "month"]);
3749
- var habitEntrySource = pgEnum("habit_entry_source", ["manual", "routine", "ingest"]);
3757
+ var habitEntrySource = pgEnum("habit_entry_source", [
3758
+ "manual",
3759
+ "routine",
3760
+ "ingest",
3761
+ "agent"
3762
+ ]);
3763
+ var habitEntryKind = pgEnum("habit_entry_kind", ["check_in", "skip"]);
3750
3764
  var habitSource = pgEnum("habit_source", ["manual", "google_fit", "strava", "github"]);
3751
3765
  var recurrenceMode = pgEnum("recurrence_mode", ["rigid", "flexible"]);
3752
3766
  var recurrenceFreq = pgEnum("recurrence_freq", ["daily", "weekly", "monthly", "yearly"]);
@@ -3788,7 +3802,12 @@ var tasks = pgTable("tasks", {
3788
3802
  completedByUserId: text("completed_by_user_id"),
3789
3803
  completedByAgentId: text("completed_by_agent_id"),
3790
3804
  placeId: text("place_id").references(() => places.id, { onDelete: "set null" }),
3791
- filingSuggestion: jsonb("filing_suggestion").$type()
3805
+ filingSuggestion: jsonb("filing_suggestion").$type(),
3806
+ proposedAt: timestamp("proposed_at", { withTimezone: true }),
3807
+ proposedDesk: text("proposed_desk"),
3808
+ notNowUntil: timestamp("not_now_until", { withTimezone: true }),
3809
+ readyAt: timestamp("ready_at", { withTimezone: true }),
3810
+ readyNote: text("ready_note")
3792
3811
  }, (t) => [
3793
3812
  index("tasks_tenant_project_idx").on(t.tenantId, t.projectId),
3794
3813
  index("tasks_tenant_place_idx").on(t.tenantId, t.placeId),
@@ -3803,6 +3822,8 @@ var tasks = pgTable("tasks", {
3803
3822
  index("tasks_tenant_scheduled_idx").on(t.tenantId, t.scheduledAt),
3804
3823
  index("tasks_tenant_due_idx").on(t.tenantId, t.dueAt),
3805
3824
  index("tasks_tenant_completed_idx").on(t.tenantId, t.completedAt),
3825
+ index("tasks_tenant_proposed_idx").on(t.tenantId, t.proposedAt).where(sql`${t.proposedAt} IS NOT NULL`),
3826
+ index("tasks_tenant_ready_idx").on(t.tenantId, t.readyAt).where(sql`${t.readyAt} IS NOT NULL`),
3806
3827
  check("tasks_area_xor_project_chk", sql`${t.projectId} IS NULL OR ${t.areaId} IS NULL`)
3807
3828
  ]);
3808
3829
  var habits = pgTable("habits", {
@@ -3816,10 +3837,17 @@ var habits = pgTable("habits", {
3816
3837
  recurrenceRuleId: text("recurrence_rule_id").references(() => recurrenceRules.id),
3817
3838
  areaId: text("area_id").references(() => areas.id),
3818
3839
  source: habitSource("source").notNull().default("manual"),
3819
- sourceMetric: text("source_metric")
3840
+ sourceMetric: text("source_metric"),
3841
+ aliases: text("aliases").array().notNull().default(sql`'{}'::text[]`),
3842
+ timeOfDay: text("time_of_day"),
3843
+ pausedFrom: date("paused_from", { mode: "string" }),
3844
+ pausedUntil: date("paused_until", { mode: "string" }),
3845
+ remind: boolean("remind").notNull().default(false),
3846
+ retiredAt: timestamp("retired_at", { withTimezone: true })
3820
3847
  }, (t) => [
3821
3848
  index("habits_tenant_idx").on(t.tenantId),
3822
- index("habits_tenant_area_idx").on(t.tenantId, t.areaId)
3849
+ index("habits_tenant_area_idx").on(t.tenantId, t.areaId),
3850
+ 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
3851
  ]);
3824
3852
  var habitEntries = pgTable("habit_entries", {
3825
3853
  id: text("id").primaryKey(),
@@ -3827,9 +3855,12 @@ var habitEntries = pgTable("habit_entries", {
3827
3855
  habitId: text("habit_id").notNull().references(() => habits.id),
3828
3856
  value: doublePrecision("value").notNull().default(1),
3829
3857
  source: habitEntrySource("source").notNull().default("manual"),
3858
+ kind: habitEntryKind("kind").notNull().default("check_in"),
3830
3859
  occurredAt: timestamp("occurred_at", { withTimezone: true }).notNull(),
3831
3860
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
3832
- externalId: text("external_id")
3861
+ externalId: text("external_id"),
3862
+ agentId: text("agent_id"),
3863
+ note: text("note")
3833
3864
  }, (t) => [
3834
3865
  index("habit_entries_habit_occurred_idx").on(t.habitId, t.occurredAt),
3835
3866
  uniqueIndex("habit_entries_habit_external_uniq").on(t.habitId, t.externalId).where(sql`${t.externalId} IS NOT NULL`)
@@ -3874,7 +3905,8 @@ var notificationType = pgEnum("notification_type", [
3874
3905
  "automation",
3875
3906
  "streak_at_risk",
3876
3907
  "reflection_nudge",
3877
- "shutdown_nudge"
3908
+ "shutdown_nudge",
3909
+ "habit_reminder"
3878
3910
  ]);
3879
3911
  var notifications = pgTable("notifications", {
3880
3912
  id: text("id").primaryKey(),
@@ -18036,7 +18068,7 @@ var spaceSchema = createSelectSchema(tenants, {
18036
18068
  createdAt: exports_coerce.date(),
18037
18069
  updatedAt: exports_coerce.date(),
18038
18070
  deletedAt: exports_coerce.date().nullable()
18039
- }).omit({ gaardHouseUrl: true });
18071
+ }).omit({ gaardHouseUrl: true, awayFrom: true, awayUntil: true });
18040
18072
  var backdropIdSchema = string2().trim().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "a backdrop id is lowercase words joined by dashes").max(40);
18041
18073
  var updateSpaceInputSchema = object({
18042
18074
  name: string2().trim().min(1).max(120).optional(),
@@ -18128,6 +18160,7 @@ var routineRunStepStatusSchema = _enum2(routineRunStepStatus.enumValues);
18128
18160
  var habitMeasurementTypeSchema = _enum2(habitMeasurementType.enumValues);
18129
18161
  var habitPeriodSchema = _enum2(habitPeriod.enumValues);
18130
18162
  var habitEntrySourceSchema = _enum2(habitEntrySource.enumValues);
18163
+ var habitEntryKindSchema = _enum2(habitEntryKind.enumValues);
18131
18164
  var recurrenceModeSchema = _enum2(recurrenceMode.enumValues);
18132
18165
  var recurrenceFreqSchema = _enum2(recurrenceFreq.enumValues);
18133
18166
  var recurrenceUnitSchema = _enum2(recurrenceUnit.enumValues);
@@ -18153,13 +18186,20 @@ var reflectionKindSchema = _enum2(reflectionKind.enumValues);
18153
18186
 
18154
18187
  // ../../packages/schema/src/contracts/agents.ts
18155
18188
  var agentSchema = createSelectSchema(agents, {
18189
+ trustedDesks: array(string2()).default([]),
18156
18190
  revokedAt: exports_coerce.date().nullable(),
18157
18191
  createdAt: exports_coerce.date(),
18158
18192
  updatedAt: exports_coerce.date()
18159
18193
  });
18160
18194
  var agentNameSchema = string2().trim().min(1).max(60);
18161
18195
  var createAgentInputSchema = object({ name: agentNameSchema }).strict();
18162
- var updateAgentInputSchema = object({ name: agentNameSchema }).strict();
18196
+ var deskSchema = string2().trim().toLowerCase().regex(/^[a-z0-9][a-z0-9_-]{0,39}$/, "a desk is a lower-case slug like `finance`");
18197
+ var updateAgentInputSchema = object({
18198
+ name: agentNameSchema.optional(),
18199
+ trustedDesks: array(deskSchema).max(40).transform((desks) => [...new Set(desks)]).optional()
18200
+ }).strict().refine((v) => v.name !== undefined || v.trustedDesks !== undefined, {
18201
+ message: "send a name or trustedDesks"
18202
+ });
18163
18203
  var actorKindSchema = _enum2(["member", "agent"]);
18164
18204
  var actorRefSchema = object({
18165
18205
  kind: actorKindSchema,
@@ -18322,9 +18362,13 @@ var taskSchema = createSelectSchema(tasks, {
18322
18362
  startAt: exports_coerce.date().nullable(),
18323
18363
  dueAt: exports_coerce.date().nullable(),
18324
18364
  completedAt: exports_coerce.date().nullable().default(null),
18325
- placeId: string2().nullable().default(null)
18365
+ placeId: string2().nullable().default(null),
18366
+ proposedAt: exports_coerce.date().nullable().default(null),
18367
+ proposedDesk: string2().nullable().default(null),
18368
+ notNowUntil: exports_coerce.date().nullable().default(null),
18369
+ readyAt: exports_coerce.date().nullable().default(null),
18370
+ readyNote: string2().nullable().default(null)
18326
18371
  });
18327
- var deskSchema = string2().trim().toLowerCase().regex(/^[a-z0-9][a-z0-9_-]{0,39}$/, "a desk is a lower-case slug like `finance`");
18328
18372
  var PARTY_RE = /^(?:(me)|(member|agent):([^/]+?))(?:\/(.+))?$/;
18329
18373
  function partySchema(allowDesk) {
18330
18374
  return string2().trim().min(1).max(200).transform((raw, ctx) => {
@@ -18419,7 +18463,12 @@ var createTaskInputSchema = createInsertSchema(tasks, {
18419
18463
  waitingSince: true,
18420
18464
  completedByUserId: true,
18421
18465
  completedByAgentId: true,
18422
- filingSuggestion: true
18466
+ filingSuggestion: true,
18467
+ proposedAt: true,
18468
+ proposedDesk: true,
18469
+ notNowUntil: true,
18470
+ readyAt: true,
18471
+ readyNote: true
18423
18472
  }).extend({
18424
18473
  title: string2().min(1),
18425
18474
  parentId: ulidSchema.nullish(),
@@ -18429,8 +18478,12 @@ var createTaskInputSchema = createInsertSchema(tasks, {
18429
18478
  owner: taskOwnerInputSchema.nullish(),
18430
18479
  waitingOn: taskWaitingOnInputSchema.nullish(),
18431
18480
  source: taskSourceSchema.nullish(),
18432
- nextStep: taskNextStepSchema.nullish()
18481
+ nextStep: taskNextStepSchema.nullish(),
18482
+ proposed: boolean3().optional(),
18483
+ fromDesk: deskSchema.nullish()
18433
18484
  });
18485
+ var taskReadyNoteSchema = string2().trim().min(1).max(500);
18486
+ var markTaskReadyInputSchema = object({ note: taskReadyNoteSchema.nullish() }).strict();
18434
18487
  var updateTaskInputSchema = createUpdateSchema(tasks, {
18435
18488
  completedAt: completedAtInputSchema,
18436
18489
  scheduledAt: exports_coerce.date().nullish(),
@@ -18453,7 +18506,10 @@ var updateTaskInputSchema = createUpdateSchema(tasks, {
18453
18506
  waitingSince: true,
18454
18507
  completedByUserId: true,
18455
18508
  completedByAgentId: true,
18456
- filingSuggestion: true
18509
+ filingSuggestion: true,
18510
+ proposedAt: true,
18511
+ proposedDesk: true,
18512
+ readyAt: true
18457
18513
  }).extend({
18458
18514
  title: string2().min(1).optional(),
18459
18515
  parentId: ulidSchema.nullish(),
@@ -18462,7 +18518,11 @@ var updateTaskInputSchema = createUpdateSchema(tasks, {
18462
18518
  owner: taskOwnerInputSchema.nullish(),
18463
18519
  waitingOn: taskWaitingOnInputSchema.nullish(),
18464
18520
  source: taskSourceSchema.nullish(),
18465
- nextStep: taskNextStepSchema.nullish()
18521
+ nextStep: taskNextStepSchema.nullish(),
18522
+ proposed: boolean3().optional(),
18523
+ notNowUntil: exports_coerce.date().nullish(),
18524
+ ready: boolean3().optional(),
18525
+ readyNote: taskReadyNoteSchema.nullish()
18466
18526
  }).strict();
18467
18527
  var taskListFilterSchema = _enum2(["all", "unsorted", "starred"]);
18468
18528
 
@@ -18606,13 +18666,31 @@ var habitSourceSchema = _enum2(["manual", "google_fit", "strava", "github"]);
18606
18666
  // ../../packages/schema/src/contracts/habits.ts
18607
18667
  var habitSchema = createSelectSchema(habits, {
18608
18668
  source: habitSourceSchema.default("manual"),
18609
- sourceMetric: string2().nullable().default(null)
18669
+ sourceMetric: string2().nullable().default(null),
18670
+ aliases: array(string2()).default([]),
18671
+ timeOfDay: string2().nullable().default(null),
18672
+ pausedFrom: exports_iso.date().nullable().default(null),
18673
+ pausedUntil: exports_iso.date().nullable().default(null),
18674
+ remind: boolean3().default(false),
18675
+ retiredAt: exports_coerce.date().nullable().default(null)
18610
18676
  });
18611
18677
  var habitEntrySchema = createSelectSchema(habitEntries, {
18612
18678
  occurredAt: exports_coerce.date(),
18613
18679
  createdAt: exports_coerce.date(),
18614
- externalId: string2().nullish()
18680
+ externalId: string2().nullish(),
18681
+ kind: habitEntryKindSchema.default("check_in"),
18682
+ agentId: string2().nullish(),
18683
+ note: string2().nullish()
18684
+ }).extend({
18685
+ by: string2().nullish()
18615
18686
  });
18687
+ var HABIT_PARTS_OF_DAY = ["morning", "midday", "evening"];
18688
+ var habitTimeOfDaySchema = union([
18689
+ _enum2(HABIT_PARTS_OF_DAY),
18690
+ string2().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "Use a 24-hour time, like 07:30")
18691
+ ]);
18692
+ var habitAliasSchema = string2().trim().min(1).max(60);
18693
+ var habitAliasesSchema = array(habitAliasSchema).max(12).transform((aliases) => aliases.filter((a, i) => aliases.findIndex((b) => b.toLowerCase() === a.toLowerCase()) === i));
18616
18694
  var createHabitInputSchema = createInsertSchema(habits, {
18617
18695
  id: ulidSchema,
18618
18696
  source: habitSourceSchema.optional(),
@@ -18622,8 +18700,11 @@ var createHabitInputSchema = createInsertSchema(habits, {
18622
18700
  targetUnit: string2().min(1).nullish(),
18623
18701
  period: habitPeriodSchema.default("day"),
18624
18702
  frequencyCount: number2().int().positive().default(1),
18625
- areaId: ulidSchema.nullish()
18626
- }).omit({ tenantId: true, recurrenceRuleId: true }).extend({
18703
+ areaId: ulidSchema.nullish(),
18704
+ aliases: habitAliasesSchema.optional(),
18705
+ timeOfDay: habitTimeOfDaySchema.nullish(),
18706
+ remind: boolean3().optional()
18707
+ }).omit({ tenantId: true, recurrenceRuleId: true, retiredAt: true }).extend({
18627
18708
  title: string2().min(1),
18628
18709
  displayKind: string2().min(1).nullish()
18629
18710
  });
@@ -18634,14 +18715,29 @@ var updateHabitInputSchema = createUpdateSchema(habits, {
18634
18715
  targetUnit: string2().min(1).nullish(),
18635
18716
  period: habitPeriodSchema.optional(),
18636
18717
  frequencyCount: number2().int().positive().optional(),
18637
- areaId: ulidSchema.nullish()
18638
- }).omit({ id: true, tenantId: true, measurementType: true, recurrenceRuleId: true }).extend({ title: string2().min(1).optional() });
18718
+ areaId: ulidSchema.nullish(),
18719
+ aliases: habitAliasesSchema.optional(),
18720
+ timeOfDay: habitTimeOfDaySchema.nullish(),
18721
+ remind: boolean3().optional()
18722
+ }).omit({
18723
+ id: true,
18724
+ tenantId: true,
18725
+ measurementType: true,
18726
+ recurrenceRuleId: true,
18727
+ pausedFrom: true,
18728
+ pausedUntil: true,
18729
+ retiredAt: true
18730
+ }).extend({ title: string2().min(1).optional() });
18731
+ var HABIT_ENTRY_NOTE_MAX = 280;
18732
+ var habitEntryNoteSchema = string2().trim().max(HABIT_ENTRY_NOTE_MAX).transform((note) => note === "" ? null : note);
18639
18733
  var createHabitEntryInputSchema = createInsertSchema(habitEntries, {
18640
18734
  id: ulidSchema,
18641
18735
  value: number2().nonnegative().default(1),
18642
18736
  source: habitEntrySourceSchema.default("manual"),
18643
- occurredAt: exports_coerce.date().optional()
18644
- }).omit({ tenantId: true, habitId: true, createdAt: true });
18737
+ occurredAt: exports_coerce.date().optional(),
18738
+ note: habitEntryNoteSchema.nullish()
18739
+ }).omit({ tenantId: true, habitId: true, createdAt: true, agentId: true, kind: true });
18740
+ var updateHabitEntryInputSchema = object({ note: habitEntryNoteSchema.nullable() }).strict();
18645
18741
  var habitStreakStateSchema = _enum2(["active", "recovering", "broken"]);
18646
18742
  var habitStreakSchema = object({
18647
18743
  current: number2().int().nonnegative(),
@@ -18652,7 +18748,8 @@ var habitStreakSchema = object({
18652
18748
  completionRate: number2().min(0).max(1),
18653
18749
  state: habitStreakStateSchema,
18654
18750
  graceBudget: number2().int().nonnegative(),
18655
- graceRemaining: number2().int().nonnegative()
18751
+ graceRemaining: number2().int().nonnegative(),
18752
+ skippedToday: boolean3().default(false)
18656
18753
  });
18657
18754
  var habitRoutineRefSchema = object({
18658
18755
  id: ulidSchema,
@@ -18661,7 +18758,19 @@ var habitRoutineRefSchema = object({
18661
18758
  var habitLastCheckInSchema = object({
18662
18759
  at: exports_coerce.date(),
18663
18760
  value: number2(),
18664
- source: habitEntrySourceSchema
18761
+ source: habitEntrySourceSchema,
18762
+ by: string2().nullish(),
18763
+ id: string2().optional(),
18764
+ note: string2().nullish()
18765
+ });
18766
+ var habitPauseSchema = object({
18767
+ until: exports_iso.date(),
18768
+ by: _enum2(["habit", "away"])
18769
+ });
18770
+ var habitTodaySchema = object({
18771
+ due: boolean3(),
18772
+ done: boolean3(),
18773
+ week: object({ done: number2().int().nonnegative(), target: number2().int().positive() }).optional()
18665
18774
  });
18666
18775
  var habitSummarySchema = habitSchema.extend({
18667
18776
  title: string2(),
@@ -18669,12 +18778,16 @@ var habitSummarySchema = habitSchema.extend({
18669
18778
  displayKind: string2().default("default"),
18670
18779
  streak: habitStreakSchema,
18671
18780
  inRoutines: array(habitRoutineRefSchema).default([]),
18672
- lastCheckIn: habitLastCheckInSchema.nullable().default(null)
18781
+ lastCheckIn: habitLastCheckInSchema.nullable().default(null),
18782
+ pause: habitPauseSchema.nullable().default(null),
18783
+ today: habitTodaySchema.default({ due: true, done: false })
18673
18784
  });
18674
18785
  var habitHeatmapCellSchema = object({
18675
18786
  date: string2(),
18676
18787
  value: number2(),
18677
- satisfied: boolean3()
18788
+ satisfied: boolean3(),
18789
+ rest: boolean3().default(false),
18790
+ notes: array(string2()).optional()
18678
18791
  });
18679
18792
  var habitDetailSchema = habitSummarySchema.extend({
18680
18793
  heatmap: array(habitHeatmapCellSchema),
@@ -18686,11 +18799,69 @@ var habitEntriesQuerySchema = object({
18686
18799
  before: exports_coerce.date().optional(),
18687
18800
  limit: exports_coerce.number().int().min(1).max(200).default(50)
18688
18801
  });
18802
+ var civilDateSchema = string2().regex(/^\d{4}-\d{2}-\d{2}$/, "expected YYYY-MM-DD");
18803
+ var habitCheckInModeSchema = _enum2(["add", "set"]);
18689
18804
  var habitCheckInByTitleInputSchema = object({
18690
18805
  title: string2().trim().min(1).max(300),
18691
18806
  value: number2().nonnegative().optional(),
18692
- occurredAt: exports_coerce.date().optional()
18807
+ mode: habitCheckInModeSchema.default("add"),
18808
+ occurredAt: union([civilDateSchema, exports_coerce.date()]).optional(),
18809
+ note: habitEntryNoteSchema.nullish()
18810
+ }).strict();
18811
+ var habitCandidateSchema = object({
18812
+ id: ulidSchema,
18813
+ title: string2(),
18814
+ aliases: array(string2())
18815
+ });
18816
+ var habitCheckInAmbiguousSchema = object({
18817
+ error: object({
18818
+ code: literal("ambiguous_habit"),
18819
+ message: string2(),
18820
+ details: object({ candidates: array(habitCandidateSchema).min(2) })
18821
+ })
18822
+ });
18823
+ var habitAliasInputSchema = object({ alias: habitAliasSchema }).strict();
18824
+ var habitSkipInputSchema = object({
18825
+ date: exports_iso.date().optional(),
18826
+ note: habitEntryNoteSchema.nullish()
18693
18827
  }).strict();
18828
+ var habitPauseInputSchema = object({ until: exports_iso.date() }).strict();
18829
+ var awayInputSchema = object({ until: exports_iso.date().nullable() }).strict();
18830
+ var awayStateSchema = object({
18831
+ until: exports_iso.date().nullable()
18832
+ });
18833
+ var habitDayPartSchema = _enum2(["morning", "midday", "evening", "anytime"]);
18834
+ var habitTodayStateSchema = _enum2(["done", "open", "skipped"]);
18835
+ var habitTodayItemSchema = object({
18836
+ habitId: ulidSchema,
18837
+ title: string2(),
18838
+ aliases: array(string2()).default([]),
18839
+ measurementType: habitMeasurementTypeSchema,
18840
+ unit: string2().nullable(),
18841
+ target: number2().nullable(),
18842
+ timeOfDay: string2().nullable().default(null),
18843
+ value: number2(),
18844
+ state: habitTodayStateSchema,
18845
+ week: object({ done: number2().int().nonnegative(), target: number2().int().positive() }).optional(),
18846
+ last: object({
18847
+ source: habitEntrySourceSchema,
18848
+ by: string2().optional(),
18849
+ at: exports_coerce.date()
18850
+ }).nullable().default(null)
18851
+ });
18852
+ var habitTodayRoutineSchema = object({
18853
+ routineId: ulidSchema,
18854
+ title: string2(),
18855
+ timeOfDay: string2().nullable(),
18856
+ stepsDone: number2().int().nonnegative(),
18857
+ stepsTotal: number2().int().nonnegative()
18858
+ });
18859
+ var habitsTodayResponseSchema = object({
18860
+ date: string2().regex(/^\d{4}-\d{2}-\d{2}$/),
18861
+ timezone: string2().min(1),
18862
+ groups: array(object({ part: habitDayPartSchema, habits: array(habitTodayItemSchema) })),
18863
+ routines: array(habitTodayRoutineSchema)
18864
+ });
18694
18865
 
18695
18866
  // ../../packages/schema/src/contracts/metrics.ts
18696
18867
  var metricKeySchema = _enum2(["weight", "sleep", "mood"]);
@@ -18880,6 +19051,26 @@ var updateRoutineRunStepInputSchema = object({
18880
19051
  var updateRoutineRunInputSchema = object({
18881
19052
  status: routineRunStatusSchema.optional()
18882
19053
  }).strict();
19054
+ var routineRunsQuerySchema = object({
19055
+ limit: exports_coerce.number().int().min(1).max(200).optional()
19056
+ });
19057
+ var routineDoneInputSchema = object({
19058
+ id: ulidSchema.optional(),
19059
+ occurrenceDate: exports_coerce.date().nullish()
19060
+ }).strict();
19061
+ var routineDoneStepSchema = object({
19062
+ stepRunId: string2(),
19063
+ title: string2(),
19064
+ habitId: string2().nullable(),
19065
+ habitEntryId: string2().nullable()
19066
+ });
19067
+ var routineDoneResultSchema = object({
19068
+ run: routineRunViewSchema,
19069
+ taken: array(routineDoneStepSchema)
19070
+ });
19071
+ var routineRunUndoInputSchema = object({
19072
+ stepRunIds: array(string2().min(1)).min(1).max(100)
19073
+ }).strict();
18883
19074
 
18884
19075
  // ../../packages/schema/src/contracts/projects.ts
18885
19076
  var projectSchema = createSelectSchema(projects, {
@@ -18977,15 +19168,50 @@ var attachedNoteViewSchema = object({
18977
19168
  var setAttachedNoteInputSchema = object({ body: string2().max(ATTACHED_NOTE_BODY_MAX) }).strict();
18978
19169
 
18979
19170
  // ../../packages/schema/src/contracts/areas.ts
18980
- var areaSchema = createSelectSchema(areas);
19171
+ var areaPurposeSchema = string2().trim().max(140).transform((v) => v.length === 0 ? null : v);
19172
+ var areaStandardTextSchema = string2().trim().min(1).max(120);
19173
+ var linkedNodeIdSchema = string2().trim().min(1).max(64);
19174
+ var areaStandardLinkSchema = discriminatedUnion("kind", [
19175
+ object({ kind: literal("habit"), id: linkedNodeIdSchema }).strict(),
19176
+ object({ kind: literal("task"), id: linkedNodeIdSchema }).strict(),
19177
+ object({ kind: literal("metric"), key: metricKeySchema }).strict()
19178
+ ]);
19179
+ var areaStandardSchema = object({
19180
+ id: string2(),
19181
+ text: string2(),
19182
+ link: areaStandardLinkSchema.nullable().default(null)
19183
+ });
19184
+ var areaStandardInputSchema = object({
19185
+ id: string2().min(1).max(64).optional(),
19186
+ text: areaStandardTextSchema,
19187
+ link: areaStandardLinkSchema.nullish()
19188
+ }).strict();
19189
+ var addAreaStandardInputSchema = object({ text: areaStandardTextSchema, link: areaStandardLinkSchema.nullish() }).strict();
19190
+ var updateAreaStandardInputSchema = object({
19191
+ text: areaStandardTextSchema.optional(),
19192
+ link: areaStandardLinkSchema.nullable().optional()
19193
+ }).strict();
19194
+ var areaStandardStateSchema = _enum2(["holding", "worth_a_look"]);
19195
+ var areaStandardViewSchema = areaStandardSchema.extend({
19196
+ state: areaStandardStateSchema.nullable(),
19197
+ why: string2().nullable(),
19198
+ heldBy: string2().nullable()
19199
+ });
19200
+ var areaSchema = createSelectSchema(areas, {
19201
+ purpose: string2().nullable().default(null),
19202
+ standards: array(areaStandardSchema).default([])
19203
+ });
18981
19204
  var areaIconSchema = string2().trim().min(1).max(64);
18982
19205
  var createAreaInputSchema = createInsertSchema(areas, {
18983
19206
  id: ulidSchema,
18984
- icon: areaIconSchema.nullish()
18985
- }).omit({ tenantId: true, archivedAt: true }).extend({ title: string2().min(1) });
19207
+ icon: areaIconSchema.nullish(),
19208
+ purpose: areaPurposeSchema.nullish()
19209
+ }).omit({ tenantId: true, archivedAt: true, standards: true }).extend({ title: string2().min(1) });
18986
19210
  var updateAreaInputSchema = createUpdateSchema(areas, {
18987
19211
  icon: areaIconSchema.nullish(),
18988
- archivedAt: exports_coerce.date().nullish()
19212
+ archivedAt: exports_coerce.date().nullish(),
19213
+ purpose: areaPurposeSchema.nullish(),
19214
+ standards: array(areaStandardInputSchema).max(5).optional()
18989
19215
  }).omit({ id: true, tenantId: true }).extend({ title: string2().min(1).optional() }).strict();
18990
19216
  var areaViewSchema = areaSchema.extend({
18991
19217
  title: string2(),
@@ -18996,12 +19222,14 @@ var areaViewSchema = areaSchema.extend({
18996
19222
  projectCount: number2().int().min(0),
18997
19223
  taskCount: number2().int().min(0),
18998
19224
  habitCount: number2().int().min(0),
18999
- routineCount: number2().int().min(0)
19225
+ routineCount: number2().int().min(0),
19226
+ standardsWorthALook: number2().int().min(0).default(0)
19000
19227
  });
19001
19228
  var areaProjectSchema = projectViewSchema.extend({
19002
19229
  tasks: array(taskViewSchema)
19003
19230
  });
19004
19231
  var areaDetailSchema = areaViewSchema.extend({
19232
+ standards: array(areaStandardViewSchema).default([]),
19005
19233
  projects: array(areaProjectSchema),
19006
19234
  habits: array(habitSummarySchema),
19007
19235
  routines: array(routineViewSchema),
@@ -19328,7 +19556,7 @@ function setModuleEnabled(enabled, key, on) {
19328
19556
  var userSettingsSchema = createSelectSchema(userSettings, {
19329
19557
  createdAt: exports_coerce.date(),
19330
19558
  updatedAt: exports_coerce.date()
19331
- });
19559
+ }).omit({ autoRollOverdue: true });
19332
19560
  var updateUserSettingsInputSchema = createUpdateSchema(userSettings, {
19333
19561
  displayName: string2().trim().max(80).nullish(),
19334
19562
  avatarUrl: union([string2().url().max(2048), literal("")]).nullish(),
@@ -19339,9 +19567,15 @@ var updateUserSettingsInputSchema = createUpdateSchema(userSettings, {
19339
19567
  accent: settingsAccentSchema.optional(),
19340
19568
  sunCity: string2().trim().max(80).transform((city) => city === "" ? null : city).nullish(),
19341
19569
  backdrop: backdropIdSchema.nullish(),
19342
- enabledModules: array(string2().trim().min(1)).transform(normalizeEnabledModules).optional(),
19343
- autoRollOverdue: boolean3().optional()
19344
- }).omit({ id: true, tenantId: true, userId: true, createdAt: true, updatedAt: true }).strict();
19570
+ enabledModules: array(string2().trim().min(1)).transform(normalizeEnabledModules).optional()
19571
+ }).omit({
19572
+ id: true,
19573
+ tenantId: true,
19574
+ userId: true,
19575
+ createdAt: true,
19576
+ updatedAt: true,
19577
+ autoRollOverdue: true
19578
+ }).strict();
19345
19579
 
19346
19580
  // ../../packages/schema/src/contracts/shopping.ts
19347
19581
  var shoppingRecurrenceUnitSchema = _enum2(["day", "week", "month"]);
@@ -19607,7 +19841,8 @@ var timeBlockTaskSchema = object({
19607
19841
  id: ulidSchema,
19608
19842
  title: string2(),
19609
19843
  status: string2(),
19610
- areaId: string2().nullable()
19844
+ areaId: string2().nullable(),
19845
+ projectId: string2().nullable().optional()
19611
19846
  });
19612
19847
  var timeBlockViewSchema = timeBlockSchema.extend({
19613
19848
  title: string2(),
@@ -19687,7 +19922,12 @@ var agentChangeSchema = object({
19687
19922
  changedAt: exports_coerce.date(),
19688
19923
  title: string2().nullable(),
19689
19924
  actor: actorRefSchema,
19690
- fields: array(agentChangeFieldSchema).optional()
19925
+ fields: array(agentChangeFieldSchema).optional(),
19926
+ checkIn: object({
19927
+ value: number2(),
19928
+ unit: string2().nullable(),
19929
+ measurementType: habitMeasurementTypeSchema
19930
+ }).optional()
19691
19931
  });
19692
19932
  var agentChangesPageSchema = object({
19693
19933
  data: array(agentChangeSchema),
@@ -20843,7 +21083,7 @@ function parseCapture2(input, now) {
20843
21083
 
20844
21084
  // src/meta.ts
20845
21085
  var CLI_NAME = "do";
20846
- var VERSION = "1.160.0";
21086
+ var VERSION = "1.162.0";
20847
21087
  var API_PREFIX = "/api/v1";
20848
21088
  var DEFAULT_API_URL = "http://localhost:3001";
20849
21089
 
@@ -21066,7 +21306,7 @@ var ENTITIES = [
21066
21306
  listFlags: [
21067
21307
  {
21068
21308
  name: "filter",
21069
- description: "all (default), unsorted (the GTD inbox) or starred",
21309
+ description: "all (default), unsorted (the Inbox's tasks to file: no day, Deadline, Area or Project; never a proposal) or starred",
21070
21310
  query: "filter"
21071
21311
  },
21072
21312
  { ...AREA_FLAG, description: "only tasks in this area id (a project task by its project's)" },
@@ -21087,7 +21327,17 @@ var ENTITIES = [
21087
21327
  description: "waiting on: me, member:<id|email>, agent:<id|name>, any or none"
21088
21328
  },
21089
21329
  { name: "completedSince", description: "finished at or after this instant (ISO or date)" },
21090
- { name: "completedBefore", description: "finished before this instant (ISO or date)" }
21330
+ { name: "completedBefore", description: "finished before this instant (ISO or date)" },
21331
+ {
21332
+ name: "proposed",
21333
+ description: "only tasks Proposed by Echo (made by an Agent for a member, not accepted yet); --proposed=false for only the plan",
21334
+ boolean: true
21335
+ },
21336
+ {
21337
+ name: "ready",
21338
+ description: "only open tasks their Owner marked Ready for you; --ready=false for the rest",
21339
+ boolean: true
21340
+ }
21091
21341
  ],
21092
21342
  write: {
21093
21343
  scope: "tasks:write",
@@ -21104,7 +21354,14 @@ var ENTITIES = [
21104
21354
  readScope: "habits:read",
21105
21355
  returns: "HabitSummary",
21106
21356
  canGet: true,
21107
- listFlags: [AREA_FLAG, UPDATED_SINCE_FLAG],
21357
+ listFlags: [
21358
+ AREA_FLAG,
21359
+ UPDATED_SINCE_FLAG,
21360
+ {
21361
+ name: "retired",
21362
+ description: "list the Grown shelf (retired habits) instead of the growing ones"
21363
+ }
21364
+ ],
21108
21365
  write: {
21109
21366
  scope: "habits:write",
21110
21367
  createSchema: createHabitInputSchema,
@@ -21144,7 +21401,15 @@ var ENTITIES = [
21144
21401
  readScope: "areas:read",
21145
21402
  returns: "AreaView",
21146
21403
  canGet: true,
21147
- listFlags: [],
21404
+ listFlags: [
21405
+ {
21406
+ name: "active",
21407
+ description: "only live areas (not archived)",
21408
+ query: "archived",
21409
+ sends: "false"
21410
+ },
21411
+ { name: "archived", description: "only archived areas", query: "archived", sends: "true" }
21412
+ ],
21148
21413
  write: {
21149
21414
  scope: "areas:write",
21150
21415
  createSchema: createAreaInputSchema,
@@ -21355,6 +21620,11 @@ var PAGINATION_FLAGS = [
21355
21620
  description: "Opaque keyset cursor from a previous page's nextCursor."
21356
21621
  }
21357
21622
  ];
21623
+ var STANDARD_LINK_FLAGS = [
21624
+ { name: "habit", type: "string", description: "Held by this habit (its id)." },
21625
+ { name: "task", type: "string", description: "Held by this recurring task (its id)." },
21626
+ { name: "metric", type: "string", description: "Held by this Metric: weight, sleep or mood." }
21627
+ ];
21358
21628
  var GLOBAL_FLAGS = [
21359
21629
  { name: "json", type: "boolean", description: "Emit JSON (automatic when stdout is piped)." },
21360
21630
  { name: "quiet", type: "boolean", description: "Suppress human output (errors still report)." },
@@ -21408,12 +21678,37 @@ var COMMAND_SPECS = [
21408
21678
  },
21409
21679
  {
21410
21680
  path: ["today"],
21411
- summary: "Show today's actionable tasks (scheduled, due, overdue, in-progress).",
21681
+ summary: "Show today's actionable tasks (scheduled, due, overdue, in-progress), then the habits and routines due today.",
21412
21682
  args: [],
21413
21683
  flags: [],
21414
21684
  scopes: ["tasks:read"],
21415
21685
  returns: "TodaySummary",
21416
- notes: "Composed client-side from GET /api/v1/tasks; uses the local day from your clock."
21686
+ 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."
21687
+ },
21688
+ {
21689
+ path: ["today", "earlier"],
21690
+ summary: "From earlier: planned tasks of the last 14 days that weren't done, newest first, and the day Later this week lands on.",
21691
+ args: [],
21692
+ flags: [{ name: "area", type: "string", description: "Only tasks in this Area (id)." }],
21693
+ scopes: ["tasks:read"],
21694
+ returns: "FromEarlierResponse",
21695
+ notes: "GET /api/v1/today/from-earlier, from your local day. Nothing here is late: timed tasks, time blocks and agents' work never roll, and a plan older than 14 days just waits where it was filed. Answer with `do today answer`."
21696
+ },
21697
+ {
21698
+ path: ["today", "answer"],
21699
+ summary: "Answer From earlier for one or more tasks: today, later (this week), someday or let-go.",
21700
+ args: [
21701
+ {
21702
+ name: "answer",
21703
+ required: true,
21704
+ description: "today (plan it today) \xB7 later (the quiet day later this week) \xB7 someday (no date, kept in its Area or Project) \xB7 let-go (close it without doing it)."
21705
+ },
21706
+ { name: "ids", required: true, description: "One or more task ids." }
21707
+ ],
21708
+ flags: [],
21709
+ scopes: ["tasks:write"],
21710
+ returns: "RolloverResult",
21711
+ notes: "POST /api/v1/today/rollover. `later` reads the day from `do today earlier` first. Let go is cancelled, never done, so it stays out of Landed; undo with `do task reopen`."
21417
21712
  },
21418
21713
  {
21419
21714
  path: ["now"],
@@ -21474,6 +21769,15 @@ var COMMAND_SPECS = [
21474
21769
  scopes: ["tasks:read", "habits:read"],
21475
21770
  returns: "ProgressDayDetail"
21476
21771
  },
21772
+ {
21773
+ path: ["progress", "habits"],
21774
+ summary: "Habits this week: a done/skipped grid, each habit's strength word, patterns and when you check in.",
21775
+ args: [],
21776
+ flags: [],
21777
+ scopes: ["habits:read"],
21778
+ returns: "ProgressHabitsResponse",
21779
+ 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."
21780
+ },
21477
21781
  {
21478
21782
  path: ["search"],
21479
21783
  summary: "Search node titles and note bodies across the Space.",
@@ -21531,6 +21835,39 @@ var COMMAND_SPECS = [
21531
21835
  returns: "Task",
21532
21836
  notes: "POST /api/v1/tasks/{id}/cancel. Use this, not `task done`, for a dropped task."
21533
21837
  },
21838
+ {
21839
+ path: ["tasks", "accept"],
21840
+ summary: "Accept a task Proposed by Echo: it joins the plan as suggested (day, Deadline, Area kept).",
21841
+ args: [{ name: "id", required: true, description: "Task ULID." }],
21842
+ flags: [],
21843
+ scopes: ["tasks:write"],
21844
+ returns: "Task",
21845
+ notes: "POST /api/v1/tasks/{id}/accept (#889). A no-op on a task that isn't a proposal. List proposals with `do tasks list --proposed`."
21846
+ },
21847
+ {
21848
+ path: ["tasks", "dismiss"],
21849
+ summary: "Not now on a task Proposed by Echo: it leaves the Inbox for a week, still proposed.",
21850
+ args: [{ name: "id", required: true, description: "Task ULID." }],
21851
+ flags: [],
21852
+ scopes: ["tasks:write"],
21853
+ returns: "Task",
21854
+ notes: "POST /api/v1/tasks/{id}/not-now (#889); 409 when the task isn't a proposal. Let go is `do task cancel`."
21855
+ },
21856
+ {
21857
+ path: ["tasks", "ready"],
21858
+ summary: "Mark a task Ready for you: its Owner finished something the member should look at (a draft, a link, a worktree).",
21859
+ args: [{ name: "id", required: true, description: "Task ULID." }],
21860
+ flags: [
21861
+ {
21862
+ name: "note",
21863
+ type: "string",
21864
+ description: `What's ready, in a line, with a link when there is one ("Draft ready: 38 hours, 3,420 euro \u2014 https://\u2026"); at most 500 characters. Absent keeps the note already there.`
21865
+ }
21866
+ ],
21867
+ scopes: ["tasks:write"],
21868
+ returns: "Task",
21869
+ notes: "POST /api/v1/tasks/{id}/ready (#902); 409 when the task is done or let go. It reaches Now's Needs you with Review (Looks good = done, Send back = `ready: false`). List them with `do tasks list --ready`."
21870
+ },
21534
21871
  {
21535
21872
  path: ["task", "reopen"],
21536
21873
  summary: "Reopen a done or cancelled task (status todo; clears completedAt).",
@@ -21561,22 +21898,76 @@ var COMMAND_SPECS = [
21561
21898
  description: "Amount logged (binary habits use 1).",
21562
21899
  default: "1"
21563
21900
  },
21564
- { name: "at", type: "string", description: "When it occurred (ISO); defaults to now." }
21901
+ { name: "at", type: "string", description: "When it occurred (ISO); defaults to now." },
21902
+ {
21903
+ name: "note",
21904
+ type: "string",
21905
+ description: 'A short note on the check-in ("felt heavy"), at most 280 characters.'
21906
+ }
21565
21907
  ],
21566
21908
  scopes: ["habits:write"],
21567
21909
  returns: "HabitSummary"
21568
21910
  },
21569
21911
  {
21570
21912
  path: ["habit", "check-in"],
21571
- summary: 'Check a habit in by its title ("did my run").',
21572
- args: [{ name: "title", required: true, description: "The habit's exact title." }],
21913
+ summary: 'Check a habit in by its title or an alias ("wake", "did my run").',
21914
+ args: [
21915
+ { name: "title", required: true, description: "The habit's title or one of its aliases." }
21916
+ ],
21573
21917
  flags: [
21574
21918
  { name: "value", type: "string", description: "Amount logged; defaults to 1." },
21575
- { name: "at", type: "string", description: "When it occurred (ISO); defaults to now." }
21919
+ {
21920
+ name: "set",
21921
+ type: "boolean",
21922
+ description: "Make --value the day's total instead of adding it (amount habits)."
21923
+ },
21924
+ {
21925
+ name: "at",
21926
+ type: "string",
21927
+ description: "When it occurred: an ISO instant, or a day (YYYY-MM-DD, your time zone); defaults to now."
21928
+ },
21929
+ {
21930
+ name: "note",
21931
+ type: "string",
21932
+ description: 'A short note on the check-in ("felt heavy"), at most 280 characters.'
21933
+ }
21934
+ ],
21935
+ scopes: ["habits:write"],
21936
+ returns: "HabitSummary",
21937
+ 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.`
21938
+ },
21939
+ {
21940
+ path: ["habit", "alias", "add"],
21941
+ summary: 'Give a habit another name check-ins match ("wake" for "Wake up by 07:00").',
21942
+ args: [
21943
+ { name: "id", required: true, description: "Habit ULID." },
21944
+ { name: "alias", required: true, description: "The other name." }
21576
21945
  ],
21946
+ flags: [],
21577
21947
  scopes: ["habits:write"],
21578
21948
  returns: "HabitSummary",
21579
- notes: "POST /api/v1/habits/check-in. Exact title (case-insensitive); several matches \u2192 409, none \u2192 404 naming close titles; a habit counted from Google Fit, Strava or GitHub \u2192 409."
21949
+ notes: "POST /api/v1/habits/:id/aliases. One already there (ignoring case) is a no-op; at most 12."
21950
+ },
21951
+ {
21952
+ path: ["habit", "alias", "rm"],
21953
+ summary: "Drop one of a habit's aliases.",
21954
+ args: [
21955
+ { name: "id", required: true, description: "Habit ULID." },
21956
+ { name: "alias", required: true, description: "The alias to drop (ignoring case)." }
21957
+ ],
21958
+ flags: [],
21959
+ scopes: ["habits:write"],
21960
+ returns: "HabitSummary",
21961
+ notes: "DELETE /api/v1/habits/:id/aliases/:alias. 404 when the habit doesn't have it."
21962
+ },
21963
+ {
21964
+ path: ["habits", "today"],
21965
+ summary: "What's left today: the habits due by part of day, and today's routines.",
21966
+ args: [],
21967
+ flags: [],
21968
+ scopes: ["habits:read"],
21969
+ returns: "HabitsTodayResponse",
21970
+ 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
21971
  },
21581
21972
  {
21582
21973
  path: ["habit", "check-ins"],
@@ -21589,6 +21980,149 @@ var COMMAND_SPECS = [
21589
21980
  scopes: ["habits:read"],
21590
21981
  returns: "HabitEntry"
21591
21982
  },
21983
+ {
21984
+ path: ["habit", "note"],
21985
+ summary: 'Put a note on a check-in afterwards ("felt heavy"), or clear it.',
21986
+ args: [
21987
+ { name: "id", required: true, description: "Habit ULID." },
21988
+ {
21989
+ name: "entry-id",
21990
+ required: true,
21991
+ description: "The check-in's ULID (`do habit check-ins <id>`, or lastCheckIn.id)."
21992
+ },
21993
+ { name: "note", required: false, description: "The note, at most 280 characters." }
21994
+ ],
21995
+ flags: [{ name: "clear", type: "boolean", description: "Drop the check-in's note." }],
21996
+ scopes: ["habits:write"],
21997
+ returns: "HabitEntry",
21998
+ 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."
21999
+ },
22000
+ {
22001
+ path: ["habit", "skip"],
22002
+ summary: 'Skip a day: a neutral rest day, never a miss ("skip gym today").',
22003
+ args: [{ name: "id", required: true, description: "Habit ULID." }],
22004
+ flags: [
22005
+ { name: "date", type: "string", description: "The day (YYYY-MM-DD); defaults to today." },
22006
+ {
22007
+ name: "note",
22008
+ type: "string",
22009
+ description: 'Why, if you like ("Dentist"); shows in the log.'
22010
+ }
22011
+ ],
22012
+ scopes: ["habits:write"],
22013
+ returns: "HabitSummary",
22014
+ 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`)."
22015
+ },
22016
+ {
22017
+ path: ["routine", "done"],
22018
+ summary: `All taken: check in every open step of today's routine in one go ("took my supplements").`,
22019
+ args: [
22020
+ {
22021
+ name: "id",
22022
+ required: true,
22023
+ description: "Routine ULID (`do habits today` lists today's)."
22024
+ }
22025
+ ],
22026
+ flags: [],
22027
+ scopes: ["routines:write"],
22028
+ returns: "RoutineDoneResult",
22029
+ 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>`."
22030
+ },
22031
+ {
22032
+ path: ["routine", "undo"],
22033
+ summary: "Take an All taken back in one go: its steps reopen and lose their check-ins.",
22034
+ args: [
22035
+ {
22036
+ name: "runId",
22037
+ required: true,
22038
+ description: "The run's ULID (`run.id` from `do routine done`)."
22039
+ },
22040
+ {
22041
+ name: "stepRunIds",
22042
+ required: true,
22043
+ description: "The `taken` step ids from `do routine done`, space- or comma-separated."
22044
+ }
22045
+ ],
22046
+ flags: [],
22047
+ scopes: ["routines:write"],
22048
+ returns: "RoutineRunView",
22049
+ 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."
22050
+ },
22051
+ {
22052
+ path: ["habit", "pause"],
22053
+ summary: "Pause a habit until the day it's back: off Now, no push, the plant waits.",
22054
+ args: [{ name: "id", required: true, description: "Habit ULID." }],
22055
+ flags: [
22056
+ {
22057
+ name: "until",
22058
+ type: "string",
22059
+ description: "The day it's back (YYYY-MM-DD), after today. Required."
22060
+ }
22061
+ ],
22062
+ scopes: ["habits:write"],
22063
+ returns: "HabitSummary",
22064
+ 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."
22065
+ },
22066
+ {
22067
+ path: ["habit", "remind"],
22068
+ summary: "Turn a habit's daily reminder on or off: one push at its time, never a follow-up.",
22069
+ args: [
22070
+ { name: "id", required: true, description: "Habit ULID." },
22071
+ { name: "state", required: true, description: "`on` or `off`." }
22072
+ ],
22073
+ flags: [],
22074
+ scopes: ["habits:write"],
22075
+ returns: "HabitSummary",
22076
+ 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."
22077
+ },
22078
+ {
22079
+ path: ["habit", "resume"],
22080
+ summary: "Resume a paused habit: it's back today.",
22081
+ args: [{ name: "id", required: true, description: "Habit ULID." }],
22082
+ flags: [],
22083
+ scopes: ["habits:write"],
22084
+ returns: "HabitSummary",
22085
+ notes: "POST /api/v1/habits/{id}/resume. The rest so far stays neutral."
22086
+ },
22087
+ {
22088
+ path: ["habit", "retire"],
22089
+ summary: `Retire a habit to the Grown shelf ("I'm done with the cold showers habit"): history kept.`,
22090
+ args: [{ name: "id", required: true, description: "Habit ULID." }],
22091
+ flags: [],
22092
+ scopes: ["habits:write"],
22093
+ returns: "HabitSummary",
22094
+ 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>`."
22095
+ },
22096
+ {
22097
+ path: ["habit", "unretire"],
22098
+ summary: "Bring a grown (retired) habit back: growing again from today.",
22099
+ args: [{ name: "id", required: true, description: "Habit ULID." }],
22100
+ flags: [],
22101
+ scopes: ["habits:write"],
22102
+ returns: "HabitSummary",
22103
+ notes: "POST /api/v1/habits/{id}/unretire. Its history is intact."
22104
+ },
22105
+ {
22106
+ path: ["away"],
22107
+ summary: 'Away until a day ("away till Monday"): every habit rests. `do away off` comes back.',
22108
+ args: [
22109
+ {
22110
+ name: "off",
22111
+ required: false,
22112
+ description: "`off` comes back now; omit it (and --until) to show the away state."
22113
+ }
22114
+ ],
22115
+ flags: [
22116
+ {
22117
+ name: "until",
22118
+ type: "string",
22119
+ description: "The day you're back (YYYY-MM-DD), after today."
22120
+ }
22121
+ ],
22122
+ scopes: ["habits:read", "habits:write"],
22123
+ returns: "AwayState",
22124
+ 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."
22125
+ },
21592
22126
  {
21593
22127
  path: ["metrics", "list"],
21594
22128
  summary: "Show weight, sleep and mood as daily trend lines (no target, no streak).",
@@ -22079,6 +22613,66 @@ var COMMAND_SPECS = [
22079
22613
  returns: "CollectionCoverBackfillResult",
22080
22614
  notes: "Returns { checked, found }; run again while checked is 25 to work through more."
22081
22615
  },
22616
+ {
22617
+ path: ["areas", "purpose"],
22618
+ summary: "Set an Area's purpose: one line of what it's for.",
22619
+ args: [
22620
+ { name: "id", required: true, description: "The Area's id." },
22621
+ { name: "line", required: false, description: "The purpose, in one line (words join)." }
22622
+ ],
22623
+ flags: [{ name: "clear", type: "boolean", description: "Remove the purpose." }],
22624
+ scopes: ["areas:write"],
22625
+ returns: "AreaView",
22626
+ notes: "PATCH /api/v1/areas/:id { purpose }. It shows under the Area's name on the hub."
22627
+ },
22628
+ {
22629
+ path: ["areas", "standards", "list"],
22630
+ summary: "List an Area's standards with how each is doing (holding / worth a look).",
22631
+ args: [{ name: "id", required: true, description: "The Area's id." }],
22632
+ flags: [],
22633
+ scopes: ["areas:read"],
22634
+ returns: "AreaStandardView",
22635
+ notes: "Each linked standard says holding or worth a look with the reason in words; an unlinked one has no state (it's checked by hand in review)."
22636
+ },
22637
+ {
22638
+ path: ["areas", "standards", "add"],
22639
+ summary: "Add a standard (what fine looks like) to an Area; up to five.",
22640
+ args: [
22641
+ { name: "id", required: true, description: "The Area's id." },
22642
+ { name: "text", required: true, description: "The standard, a plain sentence (words join)." }
22643
+ ],
22644
+ flags: STANDARD_LINK_FLAGS,
22645
+ scopes: ["areas:write"],
22646
+ returns: "AreaDetail",
22647
+ notes: "POST /api/v1/areas/:id/standards. Echo adds standards only after the member said yes; the change list keeps Undo."
22648
+ },
22649
+ {
22650
+ path: ["areas", "standards", "link"],
22651
+ summary: "Change what holds a standard: a habit, a recurring task, a Metric, or none.",
22652
+ args: [
22653
+ { name: "id", required: true, description: "The Area's id." },
22654
+ { name: "standard", required: true, description: "The standard's id." }
22655
+ ],
22656
+ flags: [
22657
+ ...STANDARD_LINK_FLAGS,
22658
+ { name: "none", type: "boolean", description: "Unlink it: checked by hand in review." }
22659
+ ],
22660
+ scopes: ["areas:write"],
22661
+ returns: "AreaDetail",
22662
+ notes: "PATCH /api/v1/areas/:id/standards/:standardId { link }."
22663
+ },
22664
+ {
22665
+ path: ["areas", "standards", "rm"],
22666
+ summary: "Remove a standard from an Area.",
22667
+ args: [
22668
+ { name: "id", required: true, description: "The Area's id." },
22669
+ { name: "standard", required: true, description: "The standard's id." }
22670
+ ],
22671
+ flags: [],
22672
+ scopes: ["areas:write"],
22673
+ returns: "AreaDetail",
22674
+ notes: "DELETE /api/v1/areas/:id/standards/:standardId."
22675
+ },
22082
22676
  {
22083
22677
  path: ["places", "import"],
22084
22678
  summary: "Import the rooms of the house once from Gaard's house.json.",
@@ -22756,15 +23350,124 @@ function renderTask(data, mode) {
22756
23350
  }
22757
23351
  function renderToday(data, mode) {
22758
23352
  const result = data;
22759
- if (result.count === 0) {
22760
- return paint(mode, "green", `Nothing scheduled for ${result.date}. Inbox zero. \u2713`);
22761
- }
22762
- const header = paint(mode, "bold", `Today \xB7 ${result.date} \xB7 ${result.count} item(s)`);
22763
- const lines = result.items.map((item) => {
22764
- const tag = paint(mode, bucketColor(item.bucket), `[${item.bucket}]`);
22765
- const star = item.starred ? paint(mode, "yellow", " \u2605") : "";
22766
- return ` ${tag} ${item.title}${star} ${paint(mode, "dim", item.id)}`;
22767
- });
23353
+ const tasks = result.count === 0 ? paint(mode, "green", `Nothing scheduled for ${result.date}. Inbox zero. \u2713`) : [
23354
+ paint(mode, "bold", `Today \xB7 ${result.date} \xB7 ${result.count} item(s)`),
23355
+ ...result.items.map((item) => {
23356
+ const tag = paint(mode, bucketColor(item.bucket), `[${item.bucket}]`);
23357
+ const star = item.starred ? paint(mode, "yellow", " \u2605") : "";
23358
+ return ` ${tag} ${item.title}${star} ${paint(mode, "dim", item.id)}`;
23359
+ })
23360
+ ].join(`
23361
+ `);
23362
+ const habits = result.habits ? habitLines(result.habits, mode) : [];
23363
+ return habits.length > 0 ? `${tasks}
23364
+
23365
+ ${habits.join(`
23366
+ `)}` : tasks;
23367
+ }
23368
+ var DAY_MS = 24 * 60 * 60 * 1000;
23369
+ function plannedDay(at, from) {
23370
+ if (!at)
23371
+ return "earlier";
23372
+ const day = new Date(at);
23373
+ const ago = from ? Math.round((Date.parse(from) - day.getTime()) / DAY_MS) : 7;
23374
+ if (ago <= 1)
23375
+ return "yesterday";
23376
+ if (ago < 7)
23377
+ return day.toLocaleDateString("en-GB", { weekday: "long" });
23378
+ return day.toLocaleDateString("en-GB", { day: "numeric", month: "short" });
23379
+ }
23380
+ function renderFromEarlier(data, mode) {
23381
+ const d = data;
23382
+ const tasks = d.tasks ?? [];
23383
+ if (tasks.length === 0)
23384
+ return paint(mode, "dim", "Nothing from earlier.");
23385
+ const later = d.laterThisWeek ? new Date(d.laterThisWeek).toLocaleDateString("en-GB", { weekday: "long" }) : null;
23386
+ return [
23387
+ paint(mode, "bold", "From earlier \xB7 planned, not done. Nothing is late."),
23388
+ ...tasks.map((t) => ` \xB7 ${t.title ?? "(untitled)"} ${paint(mode, "dim", `\u2014 planned ${plannedDay(t.scheduledAt, d.from)}`)} ${paint(mode, "dim", t.id)}`),
23389
+ "",
23390
+ paint(mode, "dim", `Answer with \`do today answer <today|later|someday|let-go> <id\u2026>\`${later ? `; later is ${later}` : ""}.`)
23391
+ ].join(`
23392
+ `);
23393
+ }
23394
+ function renderFromEarlierAnswer(data, mode) {
23395
+ const d = data;
23396
+ const n = d.tasks ?? 0;
23397
+ const tasks = `${n} task${n === 1 ? "" : "s"}`;
23398
+ if (n === 0)
23399
+ return paint(mode, "dim", "Nothing changed: those tasks are already done, let go or gone.");
23400
+ switch (d.answer) {
23401
+ case "today":
23402
+ return `Planned for today: ${tasks}.`;
23403
+ case "later":
23404
+ return `Planned ${d.to ? new Date(d.to).toLocaleDateString("en-GB", { weekday: "long" }) : "later this week"}: ${tasks}.`;
23405
+ case "someday":
23406
+ return `Moved to Someday: ${tasks}, kept where they were filed.`;
23407
+ default:
23408
+ return `Let go: ${tasks}. ${paint(mode, "dim", "Changed your mind? `do task reopen <id>`.")}`;
23409
+ }
23410
+ }
23411
+ var PART_LABEL = {
23412
+ morning: "Morning",
23413
+ midday: "Midday",
23414
+ evening: "Evening",
23415
+ anytime: "Any time today"
23416
+ };
23417
+ function clock(at, timezone) {
23418
+ return new Intl.DateTimeFormat("en-GB", {
23419
+ timeZone: timezone,
23420
+ hour: "2-digit",
23421
+ minute: "2-digit",
23422
+ hourCycle: "h23"
23423
+ }).format(new Date(at));
23424
+ }
23425
+ function civilDay(at, timezone) {
23426
+ return new Intl.DateTimeFormat("en-CA", {
23427
+ timeZone: timezone,
23428
+ year: "numeric",
23429
+ month: "2-digit",
23430
+ day: "2-digit"
23431
+ }).format(new Date(at));
23432
+ }
23433
+ function habitLines(day, mode) {
23434
+ const lines = [];
23435
+ for (const group of day.groups) {
23436
+ lines.push(paint(mode, "bold", PART_LABEL[group.part]));
23437
+ for (const h of group.habits) {
23438
+ const mark = h.state === "done" ? paint(mode, "green", "\u2713") : h.state === "skipped" ? paint(mode, "dim", "\u2013") : "\u25CB";
23439
+ const meta = [];
23440
+ if (h.timeOfDay)
23441
+ meta.push(h.timeOfDay);
23442
+ if (h.target !== null && h.measurementType !== "binary") {
23443
+ meta.push(`${h.value} of ${h.target}${h.unit ? ` ${h.unit}` : ""}`);
23444
+ }
23445
+ if (h.week)
23446
+ meta.push(`${h.week.done} of ${h.week.target} this week`);
23447
+ if (h.last?.by && civilDay(h.last.at, day.timezone) === day.date) {
23448
+ meta.push(`${h.last.by}, ${clock(h.last.at, day.timezone)}`);
23449
+ }
23450
+ const tail = meta.length > 0 ? paint(mode, "dim", ` \xB7 ${meta.join(" \xB7 ")}`) : "";
23451
+ lines.push(` ${mark} ${h.title}${tail} ${paint(mode, "dim", h.habitId)}`);
23452
+ }
23453
+ }
23454
+ if (day.routines.length > 0) {
23455
+ lines.push(paint(mode, "bold", "Routines"));
23456
+ for (const r of day.routines) {
23457
+ const when = r.timeOfDay ? ` ${r.timeOfDay}` : "";
23458
+ const steps = paint(mode, "dim", ` \xB7 ${r.stepsDone} of ${r.stepsTotal} steps`);
23459
+ lines.push(` ${r.title}${when}${steps} ${paint(mode, "dim", r.routineId)}`);
23460
+ }
23461
+ }
23462
+ return lines;
23463
+ }
23464
+ function renderHabitsToday(data, mode) {
23465
+ const day = data;
23466
+ const lines = habitLines(day, mode);
23467
+ const header = paint(mode, "bold", `Habits \xB7 ${day.date} (${day.timezone})`);
23468
+ if (lines.length === 0)
23469
+ return `${header}
23470
+ Nothing due today.`;
22768
23471
  return [header, ...lines].join(`
22769
23472
  `);
22770
23473
  }
@@ -22779,13 +23482,45 @@ function bucketColor(bucket) {
22779
23482
  }
22780
23483
  function renderHabit(data, mode) {
22781
23484
  const h = data;
23485
+ if (h.retiredAt) {
23486
+ return `${h.title ?? "(habit)"} \u2014 grown ${h.retiredAt.slice(0, 10)}, on the Grown shelf with its history
23487
+ ${paint(mode, "dim", h.id)}`;
23488
+ }
22782
23489
  const streak = h.streak;
22783
23490
  const flame = streak?.satisfiedToday ? paint(mode, "green", "\u2713 today") : "not yet today";
22784
23491
  const pct = typeof streak?.completionRate === "number" ? ` \xB7 ${Math.round(streak.completionRate * 100)}% lately` : "";
22785
23492
  const recovering = streak?.state === "recovering" ? paint(mode, "yellow", " \xB7 recovering") : "";
22786
23493
  const counts = streak ? ` \xB7 streak ${streak.current ?? 0} (best ${streak.longest ?? 0})` : "";
22787
- return `${h.title ?? "(habit)"} \u2014 ${flame}${pct}${counts}${recovering}
22788
- ${paint(mode, "dim", h.id)}`;
23494
+ const rest = h.pause ? ` \xB7 ${h.pause.by === "away" ? "away" : "paused"}, back ${h.pause.until}` : streak?.skippedToday ? " \xB7 rest day" : "";
23495
+ const last = h.lastCheckIn?.id ? `
23496
+ ${paint(mode, "dim", `last check-in ${h.lastCheckIn.id}`)}${h.lastCheckIn.note ? ` "${h.lastCheckIn.note}"` : ""}` : "";
23497
+ return `${h.title ?? "(habit)"} \u2014 ${flame}${pct}${counts}${recovering}${rest}
23498
+ ${paint(mode, "dim", h.id)}${last}`;
23499
+ }
23500
+ function renderHabitEntry(data, mode) {
23501
+ const e = data;
23502
+ const said = e.note ? `Noted: "${e.note}"` : "No note.";
23503
+ return `${said}
23504
+ ${paint(mode, "dim", e.id ?? "")}`;
23505
+ }
23506
+ function renderRoutineDone(data, mode) {
23507
+ const d = data;
23508
+ const title = d.run?.routineTitle ?? "(routine)";
23509
+ const taken = d.taken ?? [];
23510
+ if (taken.length === 0)
23511
+ return `${title}: nothing left to check in today.`;
23512
+ const names = taken.map((t) => t.title).join(", ");
23513
+ const undo = `do routine undo ${d.run?.id ?? "<run>"} ${taken.map((t) => t.stepRunId).join(" ")}`;
23514
+ return `${title}: checked in ${names}.
23515
+ ${paint(mode, "dim", `undo: ${undo}`)}`;
23516
+ }
23517
+ function renderRoutineUndo(data) {
23518
+ const run = data;
23519
+ return `${run.routineTitle ?? "(routine)"}: undone. Those check-ins are gone.`;
23520
+ }
23521
+ function renderAway(data) {
23522
+ const until = data.until;
23523
+ return until ? `Away until ${until}. Habits are resting; tasks carry on.` : "Here. Habits are on.";
22789
23524
  }
22790
23525
  function renderWhoami(data, mode) {
22791
23526
  const w = data;
@@ -23011,10 +23746,15 @@ function renderNow(data, mode) {
23011
23746
  const waiting = now.waitingOnYou ?? [];
23012
23747
  const head = paint(mode, "bold", `Waiting on ${actorText(now.for)} \xB7 ${waiting.length}`);
23013
23748
  const lines = waiting.length ? waiting.map((t) => taskLine(t, mode)) : [paint(mode, "dim", " nothing is waiting on you")];
23749
+ const ready = now.readyForYou ?? [];
23750
+ const readyLines = ready.length ? [
23751
+ paint(mode, "bold", "Ready for you"),
23752
+ ...ready.map((t) => taskLine(t, mode) + (t.readyNote ? paint(mode, "dim", ` \u2014 ${t.readyNote}`) : ""))
23753
+ ] : [];
23014
23754
  const tl = now.timeline ?? {};
23015
23755
  const count = (xs) => xs?.length ?? 0;
23016
23756
  const timeline = paint(mode, "dim", `Today: ${count(tl.scheduledTasks)} scheduled \xB7 ${count(tl.dueOccurrences)} due \xB7 ${count(tl.overdueTasks) + count(tl.overdueOccurrences)} overdue`);
23017
- return [head, ...lines, timeline].join(`
23757
+ return [head, ...lines, ...readyLines, timeline].join(`
23018
23758
  `);
23019
23759
  }
23020
23760
  function renderLanded(data, mode) {
@@ -23076,6 +23816,44 @@ function renderProgress(data, mode) {
23076
23816
  return out.join(`
23077
23817
  `);
23078
23818
  }
23819
+ var CELL = {
23820
+ done: "\u25A0",
23821
+ skipped: "\u2013",
23822
+ empty: "\xB7",
23823
+ ahead: " "
23824
+ };
23825
+ var hh = (h) => `${String(h).padStart(2, "0")}:00`;
23826
+ var usually = (when) => when?.usual ? `usually ${hh(when.usual.from)}\u2013${hh(when.usual.to)}` : null;
23827
+ function renderProgressHabits(data, mode) {
23828
+ const p = data;
23829
+ const out = [
23830
+ paint(mode, "bold", `Habits \xB7 week of ${p.week?.from ?? "?"} \u2192 ${p.week?.to ?? "?"} (${p.timezone ?? "?"})`)
23831
+ ];
23832
+ const habits = p.habits ?? [];
23833
+ if (habits.length === 0)
23834
+ out.push(paint(mode, "dim", " (no habits yet)"));
23835
+ const width = Math.max(0, ...habits.map((h) => h.name.length));
23836
+ for (const h of habits) {
23837
+ const cells = h.week.map((d) => CELL[d.state]).join(" ");
23838
+ const count = h.weekOf === null ? `${h.weekDone} done` : `${h.weekDone} of ${h.weekOf}`;
23839
+ const extra = [h.strengthWord.replace("-", " "), usually(h.when)].filter(Boolean).join(" \xB7 ");
23840
+ out.push(` ${h.name.padEnd(width)} ${cells} ${count} \xB7 ${extra}`);
23841
+ }
23842
+ const patterns = p.patterns ?? [];
23843
+ out.push("", paint(mode, "bold", "Patterns"));
23844
+ if (patterns.length === 0) {
23845
+ out.push(paint(mode, "dim", " (none yet: patterns need a few weeks of check-ins)"));
23846
+ }
23847
+ for (const x of patterns) {
23848
+ out.push(` ${x.title}`, ` ${x.body}`, ` ${paint(mode, "dim", x.confidenceLabel)}`);
23849
+ }
23850
+ const when = usually(p.when);
23851
+ if (when && p.when) {
23852
+ out.push("", `When ${when} (${Math.round((p.when.usual?.share ?? 0) * 100)}% of ${p.when.checkins} check-ins)`);
23853
+ }
23854
+ return out.join(`
23855
+ `);
23856
+ }
23079
23857
  function renderProgressDay(data, mode) {
23080
23858
  const d = data;
23081
23859
  const done = d.done ?? [];
@@ -23113,6 +23891,18 @@ function renderMcpTools(data, mode) {
23113
23891
  return [paint(mode, "bold", `${tools.length} tools`), ...lines].join(`
23114
23892
  `);
23115
23893
  }
23894
+ function renderStandards(data, mode) {
23895
+ const list = Array.isArray(data) ? data : [];
23896
+ if (list.length === 0)
23897
+ return paint(mode, "dim", "No standards yet.");
23898
+ return list.map((s) => {
23899
+ const state = s.state === "holding" ? `Holding${s.why ? `: ${s.why}` : ""}` : s.state === "worth_a_look" ? `Worth a look${s.why ? `: ${s.why}` : ""}` : "Checked by hand, in review";
23900
+ const held = s.heldBy ? ` \xB7 ${s.heldBy}` : "";
23901
+ return ` \xB7 ${s.text ?? "(untitled)"}
23902
+ ${paint(mode, "dim", `${state}${held} ${s.id ?? ""}`)}`;
23903
+ }).join(`
23904
+ `);
23905
+ }
23116
23906
 
23117
23907
  // src/today.ts
23118
23908
  var OPEN_STATUSES = new Set(["todo", "in_progress"]);
@@ -23229,9 +24019,19 @@ async function dispatchEntity(entity, rest, flags, requireClient) {
23229
24019
  if (op === "list") {
23230
24020
  const query = {};
23231
24021
  for (const f of entity.listFlags) {
23232
- const value = flagString(flags, f.name);
24022
+ const key = f.query ?? f.name;
24023
+ if (f.sends !== undefined) {
24024
+ if (flags[f.name] === undefined)
24025
+ continue;
24026
+ const other = entity.listFlags.find((o) => o !== f && o.sends !== undefined && (o.query ?? o.name) === key && (o.name in flags));
24027
+ if (other)
24028
+ throw usageError(`--${other.name} and --${f.name} can't be combined`);
24029
+ query[key] = f.sends;
24030
+ continue;
24031
+ }
24032
+ const value = flags[f.name] === true ? "true" : flagString(flags, f.name);
23233
24033
  if (value !== undefined)
23234
- query[f.query ?? f.name] = value;
24034
+ query[key] = value;
23235
24035
  else if (f.required)
23236
24036
  throw usageError(`\`${entity.name} list\` requires --${f.name}`);
23237
24037
  }
@@ -23347,11 +24147,58 @@ var HANDLERS = {
23347
24147
  tasks.push(...page.data);
23348
24148
  cursor = page.nextCursor ?? undefined;
23349
24149
  } while (cursor);
24150
+ const habits = await client.get("/habits/today").catch((error) => {
24151
+ if (error instanceof CliError && (error.code === "auth" || error.exitCode === EXIT.NOT_FOUND))
24152
+ return;
24153
+ throw error;
24154
+ });
23350
24155
  return {
23351
- data: selectToday(tasks, io.now()),
24156
+ data: {
24157
+ ...selectToday(tasks, io.now()),
24158
+ ...Array.isArray(habits?.groups) ? { habits } : {}
24159
+ },
23352
24160
  render: renderToday
23353
24161
  };
23354
24162
  },
24163
+ "today earlier": async ({ flags, io, requireClient }) => {
24164
+ const area = flagString(flags, "area");
24165
+ return {
24166
+ data: await requireClient().get("/today/from-earlier", {
24167
+ query: { from: localDayStart(io.now()).toISOString(), areaId: area }
24168
+ }),
24169
+ render: renderFromEarlier
24170
+ };
24171
+ },
24172
+ "today answer": async ({ args, io, requireClient }) => {
24173
+ const answer = requireArg(args, 0, "answer");
24174
+ const taskIds = args.slice(1);
24175
+ if (!FROM_EARLIER_ANSWERS.includes(answer)) {
24176
+ throw usageError(`answer must be one of ${FROM_EARLIER_ANSWERS.join(", ")} (got \`${answer}\`)`);
24177
+ }
24178
+ if (taskIds.length === 0)
24179
+ throw usageError("today answer needs at least one task id");
24180
+ const client = requireClient();
24181
+ const today = localDayStart(io.now());
24182
+ let body;
24183
+ let to;
24184
+ if (answer === "today") {
24185
+ to = today.toISOString();
24186
+ body = { action: "reschedule", to, taskIds };
24187
+ } else if (answer === "later") {
24188
+ const earlier = await client.get("/today/from-earlier", {
24189
+ query: { from: today.toISOString() }
24190
+ });
24191
+ to = earlier.laterThisWeek;
24192
+ body = { action: "reschedule", to, taskIds };
24193
+ } else {
24194
+ body = { action: answer === "someday" ? "someday" : "drop", taskIds };
24195
+ }
24196
+ const result = await client.post("/today/rollover", body);
24197
+ return {
24198
+ data: { answer, ...to ? { to } : {}, ...result },
24199
+ render: renderFromEarlierAnswer
24200
+ };
24201
+ },
23355
24202
  export: async ({ flags, requireClient }) => {
23356
24203
  const format = flagString(flags, "format") ?? "json";
23357
24204
  if (format !== "json" && format !== "ndjson") {
@@ -23403,6 +24250,10 @@ var HANDLERS = {
23403
24250
  data: await requireClient().get("/progress", { query: { days: flagString(flags, "days") } }),
23404
24251
  render: renderProgress
23405
24252
  }),
24253
+ "progress habits": async ({ requireClient }) => ({
24254
+ data: await requireClient().get("/progress/habits"),
24255
+ render: renderProgressHabits
24256
+ }),
23406
24257
  "progress day": async ({ args, requireClient }) => {
23407
24258
  const date = requireArg(args, 0, "date");
23408
24259
  if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
@@ -23574,6 +24425,20 @@ var HANDLERS = {
23574
24425
  const id = requireArg(args, 0, "task id");
23575
24426
  return { data: await requireClient().post(`/tasks/${id}/cancel`, {}), render: renderTask };
23576
24427
  },
24428
+ "tasks accept": async ({ args, requireClient }) => {
24429
+ const id = requireArg(args, 0, "task id");
24430
+ return { data: await requireClient().post(`/tasks/${id}/accept`, {}), render: renderTask };
24431
+ },
24432
+ "tasks dismiss": async ({ args, requireClient }) => {
24433
+ const id = requireArg(args, 0, "task id");
24434
+ return { data: await requireClient().post(`/tasks/${id}/not-now`, {}), render: renderTask };
24435
+ },
24436
+ "tasks ready": async ({ args, flags, requireClient }) => {
24437
+ const id = requireArg(args, 0, "task id");
24438
+ const note = flagString(flags, "note");
24439
+ const body = parseInput(markTaskReadyInputSchema, note !== undefined ? { note } : {});
24440
+ return { data: await requireClient().post(`/tasks/${id}/ready`, body), render: renderTask };
24441
+ },
23577
24442
  "task reopen": async ({ args, requireClient }) => {
23578
24443
  const id = requireArg(args, 0, "task id");
23579
24444
  const body = parseInput(updateTaskInputSchema, { status: "todo" });
@@ -23592,10 +24457,12 @@ var HANDLERS = {
23592
24457
  if (Number.isNaN(value))
23593
24458
  throw usageError("--value must be a number");
23594
24459
  const at = flagString(flags, "at");
24460
+ const note = flagString(flags, "note");
23595
24461
  const input = parseInput(createHabitEntryInputSchema, {
23596
24462
  id: ulid3(),
23597
24463
  value,
23598
- ...at ? { occurredAt: at } : {}
24464
+ ...at ? { occurredAt: at } : {},
24465
+ ...note !== undefined ? { note } : {}
23599
24466
  });
23600
24467
  return {
23601
24468
  data: await requireClient().post(`/habits/${id}/check-ins`, input),
@@ -23611,13 +24478,44 @@ var HANDLERS = {
23611
24478
  if (value !== undefined && Number.isNaN(value))
23612
24479
  throw usageError("--value must be a number");
23613
24480
  const at = flagString(flags, "at");
24481
+ const set = flags.set === true;
24482
+ if (set && value === undefined)
24483
+ throw usageError("--set needs --value, the day's new total");
24484
+ const note = flagString(flags, "note");
23614
24485
  const input = parseInput(habitCheckInByTitleInputSchema, {
23615
24486
  title,
23616
24487
  ...value !== undefined ? { value } : {},
23617
- ...at ? { occurredAt: at } : {}
24488
+ mode: set ? "set" : "add",
24489
+ ...at ? { occurredAt: at } : {},
24490
+ ...note !== undefined ? { note } : {}
23618
24491
  });
23619
24492
  return { data: await requireClient().post("/habits/check-in", input), render: renderHabit };
23620
24493
  },
24494
+ "habit alias add": async ({ args, requireClient }) => {
24495
+ const id = requireArg(args, 0, "habit id");
24496
+ const alias = args.slice(1).join(" ").trim();
24497
+ if (!alias)
24498
+ throw usageError("habit alias add needs an alias, e.g. `do habit alias add <id> wake`");
24499
+ const input = parseInput(habitAliasInputSchema, { alias });
24500
+ return {
24501
+ data: await requireClient().post(`/habits/${id}/aliases`, input),
24502
+ render: renderHabit
24503
+ };
24504
+ },
24505
+ "habit alias rm": async ({ args, requireClient }) => {
24506
+ const id = requireArg(args, 0, "habit id");
24507
+ const alias = args.slice(1).join(" ").trim();
24508
+ if (!alias)
24509
+ throw usageError("habit alias rm needs the alias to drop");
24510
+ return {
24511
+ data: await requireClient().delete(`/habits/${id}/aliases/${encodeURIComponent(alias)}`),
24512
+ render: renderHabit
24513
+ };
24514
+ },
24515
+ "habits today": async ({ requireClient }) => ({
24516
+ data: await requireClient().get("/habits/today"),
24517
+ render: renderHabitsToday
24518
+ }),
23621
24519
  "habit check-ins": async ({ args, flags, requireClient }) => {
23622
24520
  const id = requireArg(args, 0, "habit id");
23623
24521
  const query = {
@@ -23629,6 +24527,92 @@ var HANDLERS = {
23629
24527
  render: renderList
23630
24528
  };
23631
24529
  },
24530
+ "habit note": async ({ args, flags, requireClient }) => {
24531
+ const id = requireArg(args, 0, "habit id");
24532
+ const entryId = requireArg(args, 1, "check-in id");
24533
+ const text = args.slice(2).join(" ").trim();
24534
+ const clear = flags.clear === true;
24535
+ if (clear && text)
24536
+ throw usageError("habit note takes a note or --clear, not both");
24537
+ if (!clear && !text)
24538
+ throw usageError('habit note needs the note, e.g. `do habit note <id> <check-in id> "felt heavy"`');
24539
+ const input = parseInput(updateHabitEntryInputSchema, { note: clear ? null : text });
24540
+ return {
24541
+ data: await requireClient().patch(`/habits/${id}/check-ins/${entryId}`, input),
24542
+ render: renderHabitEntry
24543
+ };
24544
+ },
24545
+ "habit skip": async ({ args, flags, requireClient }) => {
24546
+ const id = requireArg(args, 0, "habit id");
24547
+ const date = flagString(flags, "date");
24548
+ const note = flagString(flags, "note");
24549
+ const input = parseInput(habitSkipInputSchema, {
24550
+ ...date ? { date } : {},
24551
+ ...note !== undefined ? { note } : {}
24552
+ });
24553
+ return { data: await requireClient().post(`/habits/${id}/skip`, input), render: renderHabit };
24554
+ },
24555
+ "habit pause": async ({ args, flags, requireClient }) => {
24556
+ const id = requireArg(args, 0, "habit id");
24557
+ const until = flagString(flags, "until");
24558
+ if (!until)
24559
+ throw usageError("habit pause needs --until <YYYY-MM-DD>, the day it's back");
24560
+ const input = parseInput(habitPauseInputSchema, { until });
24561
+ return { data: await requireClient().post(`/habits/${id}/pause`, input), render: renderHabit };
24562
+ },
24563
+ "habit remind": async ({ args, requireClient }) => {
24564
+ const id = requireArg(args, 0, "habit id");
24565
+ const state = requireArg(args, 1, "on|off");
24566
+ if (state !== "on" && state !== "off")
24567
+ throw usageError("habit remind takes `on` or `off`");
24568
+ const input = parseInput(updateHabitInputSchema, { remind: state === "on" });
24569
+ return { data: await requireClient().patch(`/habits/${id}`, input), render: renderHabit };
24570
+ },
24571
+ "habit resume": async ({ args, requireClient }) => {
24572
+ const id = requireArg(args, 0, "habit id");
24573
+ return { data: await requireClient().post(`/habits/${id}/resume`, {}), render: renderHabit };
24574
+ },
24575
+ "habit retire": async ({ args, requireClient }) => {
24576
+ const id = requireArg(args, 0, "habit id");
24577
+ return { data: await requireClient().post(`/habits/${id}/retire`, {}), render: renderHabit };
24578
+ },
24579
+ "habit unretire": async ({ args, requireClient }) => {
24580
+ const id = requireArg(args, 0, "habit id");
24581
+ return { data: await requireClient().post(`/habits/${id}/unretire`, {}), render: renderHabit };
24582
+ },
24583
+ away: async ({ args, flags, requireClient }) => {
24584
+ const until = flagString(flags, "until");
24585
+ const off = args[0] === "off";
24586
+ if (args.length > 0 && !off)
24587
+ throw usageError("`do away` takes `off`, or --until <YYYY-MM-DD>");
24588
+ if (off && until)
24589
+ throw usageError("`do away off` comes back now; it takes no --until");
24590
+ if (!off && !until) {
24591
+ return { data: await requireClient().get("/habits/away"), render: renderAway };
24592
+ }
24593
+ const input = parseInput(awayInputSchema, { until: off ? null : until });
24594
+ return { data: await requireClient().put("/habits/away", input), render: renderAway };
24595
+ },
24596
+ "routine done": async ({ args, requireClient }) => {
24597
+ const id = requireArg(args, 0, "routine id");
24598
+ const input = parseInput(routineDoneInputSchema, {});
24599
+ return {
24600
+ data: await requireClient().post(`/routines/${id}/done`, input),
24601
+ render: renderRoutineDone
24602
+ };
24603
+ },
24604
+ "routine undo": async ({ args, requireClient }) => {
24605
+ const runId = requireArg(args, 0, "run id");
24606
+ const stepRunIds = args.slice(1).flatMap((a) => a.split(",")).map((a) => a.trim()).filter(Boolean);
24607
+ if (stepRunIds.length === 0) {
24608
+ throw usageError("routine undo needs the step ids `do routine done` printed, e.g. `do routine undo <run> <step> <step>`");
24609
+ }
24610
+ const input = parseInput(routineRunUndoInputSchema, { stepRunIds });
24611
+ return {
24612
+ data: await requireClient().post(`/routines/runs/${runId}/undo`, input),
24613
+ render: renderRoutineUndo
24614
+ };
24615
+ },
23632
24616
  "metrics list": async ({ flags, requireClient }) => {
23633
24617
  const query = { days: flagString(flags, "days") };
23634
24618
  return { data: await requireClient().get("/metrics", { query }), render: renderList };
@@ -23827,6 +24811,51 @@ var HANDLERS = {
23827
24811
  const result = collectionCoverBackfillResultSchema.parse(await requireClient().post("/collection/covers", undefined));
23828
24812
  return { data: result, render: renderRecord };
23829
24813
  },
24814
+ "areas purpose": async ({ args, flags, requireClient }) => {
24815
+ const id = requireArg(args, 0, "area id");
24816
+ const line = args.slice(1).join(" ").trim();
24817
+ const clear = flagBool(flags, "clear");
24818
+ if (clear && line)
24819
+ throw usageError("pass a purpose or --clear, not both");
24820
+ if (!clear && !line)
24821
+ throw usageError('areas purpose needs a line, e.g. `do areas purpose <id> "Sleep well, move often"` (or --clear)');
24822
+ const body = parseInput(updateAreaInputSchema, { purpose: clear ? null : line });
24823
+ return { data: await requireClient().patch(`/areas/${id}`, body), render: renderRecord };
24824
+ },
24825
+ "areas standards list": async ({ args, requireClient }) => {
24826
+ const id = requireArg(args, 0, "area id");
24827
+ const area = await requireClient().get(`/areas/${id}`);
24828
+ return { data: area.standards ?? [], render: renderStandards };
24829
+ },
24830
+ "areas standards add": async ({ args, flags, requireClient }) => {
24831
+ const id = requireArg(args, 0, "area id");
24832
+ const text = args.slice(1).join(" ").trim();
24833
+ if (!text)
24834
+ throw usageError('areas standards add needs the standard, e.g. `do areas standards add <id> "Move three times a week" --habit <habitId>`');
24835
+ const link = standardLink(flags);
24836
+ const body = parseInput(addAreaStandardInputSchema, { text, ...link ? { link } : {} });
24837
+ const area = await requireClient().post(`/areas/${id}/standards`, body);
24838
+ return { data: area.standards ?? [], render: renderStandards };
24839
+ },
24840
+ "areas standards link": async ({ args, flags, requireClient }) => {
24841
+ const id = requireArg(args, 0, "area id");
24842
+ const standard = requireArg(args, 1, "standard id");
24843
+ const link = standardLink(flags);
24844
+ const none = flagBool(flags, "none");
24845
+ if (none && link)
24846
+ throw usageError("pass one of --habit, --task, --metric or --none");
24847
+ if (!none && !link)
24848
+ throw usageError("say what holds it: --habit <id>, --task <id>, --metric <key>, or --none");
24849
+ const body = parseInput(updateAreaStandardInputSchema, { link: none ? null : link });
24850
+ const area = await requireClient().patch(`/areas/${id}/standards/${standard}`, body);
24851
+ return { data: area.standards ?? [], render: renderStandards };
24852
+ },
24853
+ "areas standards rm": async ({ args, requireClient }) => {
24854
+ const id = requireArg(args, 0, "area id");
24855
+ const standard = requireArg(args, 1, "standard id");
24856
+ const area = await requireClient().delete(`/areas/${id}/standards/${standard}`);
24857
+ return { data: area.standards ?? [], render: renderStandards };
24858
+ },
23830
24859
  "places import": async ({ args, flags, requireClient }) => {
23831
24860
  const path = requireArg(args, 0, "house.json path");
23832
24861
  let file;
@@ -24061,6 +25090,21 @@ function isoDay(date) {
24061
25090
  const day = String(date.getDate()).padStart(2, "0");
24062
25091
  return `${date.getFullYear()}-${month}-${day}`;
24063
25092
  }
25093
+ function standardLink(flags) {
25094
+ const habit = flagString(flags, "habit");
25095
+ const task = flagString(flags, "task");
25096
+ const metric = flagString(flags, "metric");
25097
+ const given = [habit, task, metric].filter((v) => v !== undefined);
25098
+ if (given.length > 1)
25099
+ throw usageError("pass one of --habit, --task or --metric");
25100
+ if (habit !== undefined)
25101
+ return { kind: "habit", id: habit };
25102
+ if (task !== undefined)
25103
+ return { kind: "task", id: task };
25104
+ if (metric !== undefined)
25105
+ return { kind: "metric", key: metric };
25106
+ return null;
25107
+ }
24064
25108
  function requireArg(args, index, name) {
24065
25109
  const value = args[index];
24066
25110
  if (value === undefined || value.length === 0) {
@@ -24172,6 +25216,10 @@ function assertDestructiveAllowed(flags, action) {
24172
25216
  throw new CliError("destructive_blocked", `refusing to ${action} without --allow-destructive (a guardrail for autonomous agents)`, EXIT.USAGE);
24173
25217
  }
24174
25218
  }
25219
+ var FROM_EARLIER_ANSWERS = ["today", "later", "someday", "let-go"];
25220
+ function localDayStart(now) {
25221
+ return new Date(now.getFullYear(), now.getMonth(), now.getDate());
25222
+ }
24175
25223
 
24176
25224
  // src/run.ts
24177
25225
  async function run(argv, io) {