@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.
Files changed (2) hide show
  1. package/dist/do.js +680 -44
  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 })
@@ -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", ["manual", "routine", "ingest"]);
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
- }).omit({ tenantId: true, recurrenceRuleId: true }).extend({
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
- }).omit({ id: true, tenantId: true, measurementType: true, recurrenceRuleId: true }).extend({ title: string2().min(1).optional() });
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
- }).omit({ tenantId: true, habitId: true, createdAt: true });
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
- occurredAt: exports_coerce.date().optional()
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.160.0";
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: [AREA_FLAG, UPDATED_SINCE_FLAG],
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: "Composed client-side from GET /api/v1/tasks; uses the local day from your clock."
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: [{ name: "title", required: true, description: "The habit's exact title." }],
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
- { name: "at", type: "string", description: "When it occurred (ISO); defaults to now." }
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: "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."
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
- 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
- });
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
- return `${h.title ?? "(habit)"} \u2014 ${flame}${pct}${counts}${recovering}
22788
- ${paint(mode, "dim", h.id)}`;
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: selectToday(tasks, io.now()),
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
- ...at ? { occurredAt: at } : {}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robbeverhelst/do",
3
- "version": "1.160.0",
3
+ "version": "1.161.0",
4
4
  "private": false,
5
5
  "description": "do — the agent-drivable CLI over the public /api/v1 surface",
6
6
  "homepage": "https://github.com/robbeverhelst/do/tree/main/apps/cli",