@robbeverhelst/do 1.159.1 → 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 +857 -45
  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()
18693
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()
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, {
@@ -19652,9 +19803,16 @@ var agentPageContextSchema = object({
19652
19803
  }).strict();
19653
19804
  var agentChatInputSchema = object({
19654
19805
  message: string2().trim().min(1).max(8000),
19655
- context: agentPageContextSchema
19806
+ context: agentPageContextSchema,
19807
+ threadId: string2().trim().min(1).max(64).optional()
19656
19808
  }).strict();
19657
19809
  var agentChatEventSchema = discriminatedUnion("type", [
19810
+ object({
19811
+ type: literal("turn"),
19812
+ id: string2(),
19813
+ threadId: string2(),
19814
+ since: string2().nullable()
19815
+ }),
19658
19816
  object({ type: literal("delta"), text: string2() }),
19659
19817
  object({ type: literal("done") }),
19660
19818
  object({ type: literal("error"), message: string2() })
@@ -19680,7 +19838,12 @@ var agentChangeSchema = object({
19680
19838
  changedAt: exports_coerce.date(),
19681
19839
  title: string2().nullable(),
19682
19840
  actor: actorRefSchema,
19683
- fields: array(agentChangeFieldSchema).optional()
19841
+ fields: array(agentChangeFieldSchema).optional(),
19842
+ checkIn: object({
19843
+ value: number2(),
19844
+ unit: string2().nullable(),
19845
+ measurementType: habitMeasurementTypeSchema
19846
+ }).optional()
19684
19847
  });
19685
19848
  var agentChangesPageSchema = object({
19686
19849
  data: array(agentChangeSchema),
@@ -20836,7 +20999,7 @@ function parseCapture2(input, now) {
20836
20999
 
20837
21000
  // src/meta.ts
20838
21001
  var CLI_NAME = "do";
20839
- var VERSION = "1.159.1";
21002
+ var VERSION = "1.161.0";
20840
21003
  var API_PREFIX = "/api/v1";
20841
21004
  var DEFAULT_API_URL = "http://localhost:3001";
20842
21005
 
@@ -21097,7 +21260,14 @@ var ENTITIES = [
21097
21260
  readScope: "habits:read",
21098
21261
  returns: "HabitSummary",
21099
21262
  canGet: true,
21100
- 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
+ ],
21101
21271
  write: {
21102
21272
  scope: "habits:write",
21103
21273
  createSchema: createHabitInputSchema,
@@ -21401,12 +21571,12 @@ var COMMAND_SPECS = [
21401
21571
  },
21402
21572
  {
21403
21573
  path: ["today"],
21404
- 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.",
21405
21575
  args: [],
21406
21576
  flags: [],
21407
21577
  scopes: ["tasks:read"],
21408
21578
  returns: "TodaySummary",
21409
- 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."
21410
21580
  },
21411
21581
  {
21412
21582
  path: ["now"],
@@ -21448,6 +21618,34 @@ var COMMAND_SPECS = [
21448
21618
  scopes: ["tasks:read"],
21449
21619
  returns: "ReviewDayStat"
21450
21620
  },
21621
+ {
21622
+ path: ["progress"],
21623
+ summary: "How the last 7/30/90 days went: tasks, habits and time blocks done per day.",
21624
+ args: [],
21625
+ flags: [
21626
+ { name: "days", type: "string", description: "The range: 7 (default), 30 or 90 days." }
21627
+ ],
21628
+ scopes: ["tasks:read", "habits:read"],
21629
+ returns: "ProgressResponse",
21630
+ notes: "GET /api/v1/progress \u2014 days are your saved timezone's. Includes the week/month vs the period before, best day, Areas and each habit's rate."
21631
+ },
21632
+ {
21633
+ path: ["progress", "day"],
21634
+ summary: "One day of Progress: what was done (and by whom) and what slipped.",
21635
+ args: [{ name: "date", required: true, description: "The day, YYYY-MM-DD." }],
21636
+ flags: [],
21637
+ scopes: ["tasks:read", "habits:read"],
21638
+ returns: "ProgressDayDetail"
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
+ },
21451
21649
  {
21452
21650
  path: ["search"],
21453
21651
  summary: "Search node titles and note bodies across the Space.",
@@ -21535,22 +21733,76 @@ var COMMAND_SPECS = [
21535
21733
  description: "Amount logged (binary habits use 1).",
21536
21734
  default: "1"
21537
21735
  },
21538
- { 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
+ }
21539
21742
  ],
21540
21743
  scopes: ["habits:write"],
21541
21744
  returns: "HabitSummary"
21542
21745
  },
21543
21746
  {
21544
21747
  path: ["habit", "check-in"],
21545
- summary: 'Check a habit in by its title ("did my run").',
21546
- 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
+ ],
21547
21752
  flags: [
21548
21753
  { name: "value", type: "string", description: "Amount logged; defaults to 1." },
21549
- { 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
+ }
21769
+ ],
21770
+ scopes: ["habits:write"],
21771
+ returns: "HabitSummary",
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)." }
21550
21792
  ],
21793
+ flags: [],
21551
21794
  scopes: ["habits:write"],
21552
21795
  returns: "HabitSummary",
21553
- 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."
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."
21554
21806
  },
21555
21807
  {
21556
21808
  path: ["habit", "check-ins"],
@@ -21563,6 +21815,149 @@ var COMMAND_SPECS = [
21563
21815
  scopes: ["habits:read"],
21564
21816
  returns: "HabitEntry"
21565
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
+ },
21566
21961
  {
21567
21962
  path: ["metrics", "list"],
21568
21963
  summary: "Show weight, sleep and mood as daily trend lines (no target, no streak).",
@@ -21797,6 +22192,42 @@ var COMMAND_SPECS = [
21797
22192
  returns: "None",
21798
22193
  notes: "Owner/admin only. An Agent's key gets a 428 confirmation_required until the member confirms in the Echo panel."
21799
22194
  },
22195
+ {
22196
+ path: ["echo", "threads", "list"],
22197
+ summary: "List your recent Echo threads, newest first.",
22198
+ args: [],
22199
+ flags: [{ name: "limit", type: "string", description: "How many, 1\u201350 (default 10)." }],
22200
+ scopes: ["agents:read"],
22201
+ returns: "EchoThreadSummary",
22202
+ notes: "The key's member's own threads; the title is the thread's first question."
22203
+ },
22204
+ {
22205
+ path: ["echo", "threads", "current"],
22206
+ summary: "Show the Echo thread the panel opens on, with its turns.",
22207
+ args: [],
22208
+ flags: [],
22209
+ scopes: ["agents:read"],
22210
+ returns: "{ thread: EchoThread | null }",
22211
+ notes: "The most recently used thread; `thread` is null before the first question."
22212
+ },
22213
+ {
22214
+ path: ["echo", "threads", "show"],
22215
+ summary: "Show one of your Echo threads, with its turns.",
22216
+ args: [{ name: "id", required: true, description: "Thread ULID (see `echo threads list`)." }],
22217
+ flags: [],
22218
+ scopes: ["agents:read"],
22219
+ returns: "EchoThread",
22220
+ notes: "Another member's thread is a 404."
22221
+ },
22222
+ {
22223
+ path: ["echo", "threads", "new"],
22224
+ summary: "Start a new Echo conversation (it becomes the current thread).",
22225
+ args: [],
22226
+ flags: [],
22227
+ scopes: ["agents:write"],
22228
+ returns: "EchoThread",
22229
+ notes: "When the current thread is still empty, that one is returned instead."
22230
+ },
21800
22231
  {
21801
22232
  path: ["settings", "get"],
21802
22233
  summary: "Show the caller's settings for the Space.",
@@ -22694,15 +23125,81 @@ function renderTask(data, mode) {
22694
23125
  }
22695
23126
  function renderToday(data, mode) {
22696
23127
  const result = data;
22697
- if (result.count === 0) {
22698
- return paint(mode, "green", `Nothing scheduled for ${result.date}. Inbox zero. \u2713`);
22699
- }
22700
- const header = paint(mode, "bold", `Today \xB7 ${result.date} \xB7 ${result.count} item(s)`);
22701
- const lines = result.items.map((item) => {
22702
- const tag = paint(mode, bucketColor(item.bucket), `[${item.bucket}]`);
22703
- const star = item.starred ? paint(mode, "yellow", " \u2605") : "";
22704
- return ` ${tag} ${item.title}${star} ${paint(mode, "dim", item.id)}`;
22705
- });
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.`;
22706
23203
  return [header, ...lines].join(`
22707
23204
  `);
22708
23205
  }
@@ -22717,13 +23214,45 @@ function bucketColor(bucket) {
22717
23214
  }
22718
23215
  function renderHabit(data, mode) {
22719
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
+ }
22720
23221
  const streak = h.streak;
22721
23222
  const flame = streak?.satisfiedToday ? paint(mode, "green", "\u2713 today") : "not yet today";
22722
23223
  const pct = typeof streak?.completionRate === "number" ? ` \xB7 ${Math.round(streak.completionRate * 100)}% lately` : "";
22723
23224
  const recovering = streak?.state === "recovering" ? paint(mode, "yellow", " \xB7 recovering") : "";
22724
23225
  const counts = streak ? ` \xB7 streak ${streak.current ?? 0} (best ${streak.longest ?? 0})` : "";
22725
- return `${h.title ?? "(habit)"} \u2014 ${flame}${pct}${counts}${recovering}
22726
- ${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.";
22727
23256
  }
22728
23257
  function renderWhoami(data, mode) {
22729
23258
  const w = data;
@@ -22914,6 +23443,26 @@ function shortDate(iso) {
22914
23443
  return iso;
22915
23444
  return d.toISOString().slice(0, 16).replace("T", " ");
22916
23445
  }
23446
+ function renderEchoThread(data, mode) {
23447
+ const wrapped = data;
23448
+ const thread = wrapped && typeof wrapped === "object" && "thread" in wrapped ? wrapped.thread : data;
23449
+ if (!thread)
23450
+ return paint(mode, "dim", "No Echo thread yet.");
23451
+ const head = `${thread.title ?? "New conversation"} ${paint(mode, "dim", thread.id ?? "")}`;
23452
+ const turns = thread.turns ?? [];
23453
+ if (turns.length === 0)
23454
+ return `${head}
23455
+ ${paint(mode, "dim", "(nothing asked yet)")}`;
23456
+ const lines = turns.flatMap((t) => {
23457
+ const note = t.status === "error" ? ` (failed: ${t.error ?? "error"})` : t.status === "cancelled" ? " (stopped)" : t.status === "streaming" ? " (still answering)" : "";
23458
+ return [
23459
+ ` ${paint(mode, "bold", "You")} ${t.question ?? ""}`,
23460
+ ` ${paint(mode, "bold", "Echo")} ${t.reply ?? ""}${paint(mode, "dim", note)}`
23461
+ ];
23462
+ });
23463
+ return [head, ...lines].join(`
23464
+ `);
23465
+ }
22917
23466
  function actorText(actor) {
22918
23467
  if (!actor)
22919
23468
  return "system";
@@ -22950,6 +23499,106 @@ function renderReview(data, mode) {
22950
23499
  return days.map((d) => `${d.date ?? "?"} ${d.completedTasks ?? 0} task(s) \xB7 ${d.completedOccurrences ?? 0} habit/routine`).join(`
22951
23500
  `);
22952
23501
  }
23502
+ var percent = (share) => share === null || share === undefined ? "\u2013" : `${Math.round(share * 100)}%`;
23503
+ function delta(now, before) {
23504
+ const d = now - before;
23505
+ return d > 0 ? `+${d}` : d < 0 ? `\u2212${-d}` : "\xB10";
23506
+ }
23507
+ function renderProgress(data, mode) {
23508
+ const p = data;
23509
+ const t = p.today;
23510
+ const out = [
23511
+ paint(mode, "bold", `Progress \xB7 ${p.range?.from ?? "?"} \u2192 ${p.range?.to ?? "?"} (${p.range?.timezone ?? "?"})`)
23512
+ ];
23513
+ if (t) {
23514
+ out.push(`Today ${t.tasksDone}/${t.tasksPlanned} tasks \xB7 ${t.habitsDone}/${t.habitsDue} habits \xB7 ${t.blocksDone}/${t.blocksPlanned} blocks \xB7 ${percent(t.completion)} done`);
23515
+ }
23516
+ const days = p.days ?? [];
23517
+ if (days.length > 0) {
23518
+ out.push("");
23519
+ for (const d of days) {
23520
+ const agents = d.tasksDoneAgents ? ` (+${d.tasksDoneAgents} by agents)` : "";
23521
+ out.push(`${d.date} ${d.tasksDoneMine} task(s)${agents} \xB7 ${d.habitsDone}/${d.habitsDue} habits \xB7 ${percent(d.completion)}`);
23522
+ }
23523
+ }
23524
+ const totals = p.totals;
23525
+ if (totals) {
23526
+ const line = (label, x) => `${label} ${x.tasksDone} tasks (${delta(x.tasksDone, x.previous.tasksDone)}) \xB7 ${x.habitsDone} habits (${delta(x.habitsDone, x.previous.habitsDone)}) \xB7 ${percent(x.completion)} done`;
23527
+ out.push("", line("This week ", totals.week), line("This month", totals.month));
23528
+ const best = totals.bestDay ? `${totals.bestDay.date} (${totals.bestDay.tasksDone})` : "\u2013";
23529
+ out.push(`Best day ${best} \xB7 ${totals.averagePerDay.tasksDone} tasks/day \xB7 on time ${totals.onTime}, late ${totals.late}`);
23530
+ }
23531
+ const areas = p.areas ?? [];
23532
+ if (areas.length > 0) {
23533
+ out.push("", paint(mode, "bold", "Areas"));
23534
+ for (const a of areas)
23535
+ out.push(` ${a.name}: ${a.tasksDone}`);
23536
+ }
23537
+ const habits = p.habits ?? [];
23538
+ if (habits.length > 0) {
23539
+ out.push("", paint(mode, "bold", "Habits"));
23540
+ for (const h of habits)
23541
+ out.push(` ${h.name}: ${h.done}/${h.due} (${percent(h.rate)})`);
23542
+ }
23543
+ return out.join(`
23544
+ `);
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
+ }
23584
+ function renderProgressDay(data, mode) {
23585
+ const d = data;
23586
+ const done = d.done ?? [];
23587
+ const slipped = d.slipped ?? [];
23588
+ const out = [
23589
+ paint(mode, "bold", `${d.date ?? "?"} \xB7 ${done.length} done \xB7 ${slipped.length} slipped`)
23590
+ ];
23591
+ for (const item of done) {
23592
+ const tags = [item.kind, item.owner === "agent" ? "agent" : null, item.late ? "late" : null].filter(Boolean).join(", ");
23593
+ out.push(` \u2713 ${item.title} ${paint(mode, "dim", `(${tags})`)}`);
23594
+ }
23595
+ for (const item of slipped)
23596
+ out.push(` \xB7 ${item.title} ${paint(mode, "dim", `(${item.kind})`)}`);
23597
+ if (done.length === 0 && slipped.length === 0)
23598
+ out.push(paint(mode, "dim", " (nothing that day)"));
23599
+ return out.join(`
23600
+ `);
23601
+ }
22953
23602
  function renderSearch(data, mode) {
22954
23603
  const hits = Array.isArray(data) ? data : [];
22955
23604
  if (hits.length === 0)
@@ -23085,7 +23734,7 @@ async function dispatchEntity(entity, rest, flags, requireClient) {
23085
23734
  if (op === "list") {
23086
23735
  const query = {};
23087
23736
  for (const f of entity.listFlags) {
23088
- const value = flagString(flags, f.name);
23737
+ const value = flags[f.name] === true ? "true" : flagString(flags, f.name);
23089
23738
  if (value !== undefined)
23090
23739
  query[f.query ?? f.name] = value;
23091
23740
  else if (f.required)
@@ -23203,8 +23852,16 @@ var HANDLERS = {
23203
23852
  tasks.push(...page.data);
23204
23853
  cursor = page.nextCursor ?? undefined;
23205
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
+ });
23206
23860
  return {
23207
- data: selectToday(tasks, io.now()),
23861
+ data: {
23862
+ ...selectToday(tasks, io.now()),
23863
+ ...Array.isArray(habits?.groups) ? { habits } : {}
23864
+ },
23208
23865
  render: renderToday
23209
23866
  };
23210
23867
  },
@@ -23255,6 +23912,24 @@ var HANDLERS = {
23255
23912
  }),
23256
23913
  render: renderReview
23257
23914
  }),
23915
+ progress: async ({ flags, requireClient }) => ({
23916
+ data: await requireClient().get("/progress", { query: { days: flagString(flags, "days") } }),
23917
+ render: renderProgress
23918
+ }),
23919
+ "progress habits": async ({ requireClient }) => ({
23920
+ data: await requireClient().get("/progress/habits"),
23921
+ render: renderProgressHabits
23922
+ }),
23923
+ "progress day": async ({ args, requireClient }) => {
23924
+ const date = requireArg(args, 0, "date");
23925
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
23926
+ throw usageError("progress day needs a date as YYYY-MM-DD, e.g. `do progress day 2026-10-15`");
23927
+ }
23928
+ return {
23929
+ data: await requireClient().get(`/progress/days/${date}`),
23930
+ render: renderProgressDay
23931
+ };
23932
+ },
23258
23933
  search: async ({ args, flags, requireClient }) => {
23259
23934
  const q = args.join(" ").trim();
23260
23935
  if (q.length === 0)
@@ -23434,10 +24109,12 @@ var HANDLERS = {
23434
24109
  if (Number.isNaN(value))
23435
24110
  throw usageError("--value must be a number");
23436
24111
  const at = flagString(flags, "at");
24112
+ const note = flagString(flags, "note");
23437
24113
  const input = parseInput(createHabitEntryInputSchema, {
23438
24114
  id: ulid3(),
23439
24115
  value,
23440
- ...at ? { occurredAt: at } : {}
24116
+ ...at ? { occurredAt: at } : {},
24117
+ ...note !== undefined ? { note } : {}
23441
24118
  });
23442
24119
  return {
23443
24120
  data: await requireClient().post(`/habits/${id}/check-ins`, input),
@@ -23453,13 +24130,44 @@ var HANDLERS = {
23453
24130
  if (value !== undefined && Number.isNaN(value))
23454
24131
  throw usageError("--value must be a number");
23455
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");
23456
24137
  const input = parseInput(habitCheckInByTitleInputSchema, {
23457
24138
  title,
23458
24139
  ...value !== undefined ? { value } : {},
23459
- ...at ? { occurredAt: at } : {}
24140
+ mode: set ? "set" : "add",
24141
+ ...at ? { occurredAt: at } : {},
24142
+ ...note !== undefined ? { note } : {}
23460
24143
  });
23461
24144
  return { data: await requireClient().post("/habits/check-in", input), render: renderHabit };
23462
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
+ }),
23463
24171
  "habit check-ins": async ({ args, flags, requireClient }) => {
23464
24172
  const id = requireArg(args, 0, "habit id");
23465
24173
  const query = {
@@ -23471,6 +24179,92 @@ var HANDLERS = {
23471
24179
  render: renderList
23472
24180
  };
23473
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
+ },
23474
24268
  "metrics list": async ({ flags, requireClient }) => {
23475
24269
  const query = { days: flagString(flags, "days") };
23476
24270
  return { data: await requireClient().get("/metrics", { query }), render: renderList };
@@ -23513,6 +24307,24 @@ var HANDLERS = {
23513
24307
  await requireClient().delete("/agent/connection");
23514
24308
  return { data: { ok: true, removed: "agent connection" }, render: renderRecord };
23515
24309
  },
24310
+ "echo threads list": async ({ flags, requireClient }) => ({
24311
+ data: await requireClient().getPage("/echo/threads", {
24312
+ query: { limit: flagString(flags, "limit") }
24313
+ }),
24314
+ render: renderList
24315
+ }),
24316
+ "echo threads current": async ({ requireClient }) => ({
24317
+ data: await requireClient().get("/echo/threads/current"),
24318
+ render: renderEchoThread
24319
+ }),
24320
+ "echo threads show": async ({ args, requireClient }) => ({
24321
+ data: await requireClient().get(`/echo/threads/${requireArg(args, 0, "thread id")}`),
24322
+ render: renderEchoThread
24323
+ }),
24324
+ "echo threads new": async ({ requireClient }) => ({
24325
+ data: await requireClient().post("/echo/threads", {}),
24326
+ render: renderEchoThread
24327
+ }),
23516
24328
  "settings get": async ({ requireClient }) => ({
23517
24329
  data: await requireClient().get("/settings"),
23518
24330
  render: renderRecord
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robbeverhelst/do",
3
- "version": "1.159.1",
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",