@robbeverhelst/do 1.347.0 → 1.349.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 +712 -103
  2. package/package.json +1 -1
package/dist/do.js CHANGED
@@ -14420,7 +14420,7 @@ var configError = (message) => new CliError("config", message, EXIT.CONFIG);
14420
14420
 
14421
14421
  // src/meta.ts
14422
14422
  var CLI_NAME = "do";
14423
- var VERSION = "1.347.0";
14423
+ var VERSION = "1.349.0";
14424
14424
  var API_PREFIX = "/api/v1";
14425
14425
  var DEFAULT_API_URL = "http://localhost:3001";
14426
14426
 
@@ -18030,7 +18030,8 @@ var habits = pgTable("habits", {
18030
18030
  eventSourceId: text("event_source_id"),
18031
18031
  eventMatch: text("event_match"),
18032
18032
  eventOffset: text("event_offset"),
18033
- durationMinutes: integer2("duration_minutes")
18033
+ durationMinutes: integer2("duration_minutes"),
18034
+ activeMonths: integer2("active_months").array()
18034
18035
  }, (t) => [
18035
18036
  index("habits_tenant_idx").on(t.tenantId),
18036
18037
  index("habits_tenant_area_idx").on(t.tenantId, t.areaId),
@@ -18048,7 +18049,8 @@ var habitEntries = pgTable("habit_entries", {
18048
18049
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
18049
18050
  externalId: text("external_id"),
18050
18051
  agentId: text("agent_id"),
18051
- note: text("note")
18052
+ note: text("note"),
18053
+ matchedName: text("matched_name")
18052
18054
  }, (t) => [
18053
18055
  index("habit_entries_habit_occurred_idx").on(t.habitId, t.occurredAt),
18054
18056
  uniqueIndex("habit_entries_habit_external_uniq").on(t.habitId, t.externalId).where(sql`${t.externalId} IS NOT NULL`)
@@ -18299,7 +18301,8 @@ var challengeParts = pgTable("challenge_parts", {
18299
18301
  challengeId: text("challenge_id").notNull().references(() => challenges.id, { onDelete: "cascade" }),
18300
18302
  kind: challengePartKind("kind").notNull(),
18301
18303
  subjectId: text("subject_id").notNull(),
18302
- position: integer2("position").notNull()
18304
+ position: integer2("position").notNull(),
18305
+ resumeOnStart: boolean4("resume_on_start").notNull().default(false)
18303
18306
  }, (t) => [
18304
18307
  index("challenge_parts_tenant_idx").on(t.tenantId),
18305
18308
  index("challenge_parts_challenge_idx").on(t.challengeId, t.position),
@@ -19216,7 +19219,10 @@ var agentChangeSchema = object({
19216
19219
  checkIn: object({
19217
19220
  value: number2(),
19218
19221
  unit: string2().nullable(),
19219
- measurementType: habitMeasurementTypeSchema
19222
+ measurementType: habitMeasurementTypeSchema,
19223
+ matched: string2().nullish(),
19224
+ target: number2().nullish(),
19225
+ dayValue: number2().nullish()
19220
19226
  }).optional()
19221
19227
  });
19222
19228
  var agentChangesPageSchema = object({
@@ -20823,7 +20829,10 @@ var animalHandoverContactSchema = object({
20823
20829
  var animalHandoverSupplySchema = animalSupplySchema.extend({
20824
20830
  lastsTrip: boolean2().nullable()
20825
20831
  });
20826
- var animalHandoverHabitSchema = nodeRefSchema.extend({ restsThroughTrip: boolean2() });
20832
+ var animalHandoverHabitSchema = nodeRefSchema.extend({
20833
+ restsThroughTrip: boolean2(),
20834
+ timeOfDay: string2().nullable().default(null)
20835
+ });
20827
20836
  var animalHandoverSchema = object({
20828
20837
  trip: animalHandoverTripSchema.nullable(),
20829
20838
  areaId: ulidSchema.nullable(),
@@ -21397,7 +21406,8 @@ var habitSchema = createSelectSchema(habits, {
21397
21406
  eventSourceId: string2().nullable().default(null),
21398
21407
  eventMatch: string2().nullable().default(null),
21399
21408
  eventOffset: string2().nullable().default(null),
21400
- durationMinutes: number2().int().nullable().default(null)
21409
+ durationMinutes: number2().int().nullable().default(null),
21410
+ activeMonths: array(number2().int()).nullable().default(null)
21401
21411
  });
21402
21412
  var habitEntrySchema = createSelectSchema(habitEntries, {
21403
21413
  occurredAt: exports_coerce.date(),
@@ -21405,7 +21415,8 @@ var habitEntrySchema = createSelectSchema(habitEntries, {
21405
21415
  externalId: string2().nullish(),
21406
21416
  kind: habitEntryKindSchema.default("check_in"),
21407
21417
  agentId: string2().nullish(),
21408
- note: string2().nullish()
21418
+ note: string2().nullish(),
21419
+ matchedName: string2().nullish()
21409
21420
  }).extend({
21410
21421
  by: string2().nullish()
21411
21422
  });
@@ -21416,6 +21427,12 @@ var habitTimeOfDaySchema = union([
21416
21427
  ]);
21417
21428
  var HABIT_DURATION_MAX_MINUTES = 600;
21418
21429
  var habitDurationMinutesSchema = number2().int().min(1).max(HABIT_DURATION_MAX_MINUTES);
21430
+ var habitActiveMonthsSchema = array(number2().int().min(1).max(12)).max(12).nullable().transform((months) => {
21431
+ if (!months)
21432
+ return null;
21433
+ const set = [...new Set(months)].sort((x, y) => x - y);
21434
+ return set.length === 0 || set.length === 12 ? null : set;
21435
+ });
21419
21436
  var habitAliasSchema = string2().trim().min(1).max(60);
21420
21437
  var habitAliasesSchema = array(habitAliasSchema).max(12).transform((aliases) => aliases.filter((a, i) => aliases.findIndex((b) => b.toLowerCase() === a.toLowerCase()) === i));
21421
21438
  var habitScheduleSchema = discriminatedUnion("kind", [
@@ -21455,7 +21472,8 @@ var createHabitInputSchema = createInsertSchema(habits, {
21455
21472
  aliases: habitAliasesSchema.optional(),
21456
21473
  timeOfDay: habitTimeOfDaySchema.nullish(),
21457
21474
  remind: boolean2().optional(),
21458
- durationMinutes: habitDurationMinutesSchema.nullish()
21475
+ durationMinutes: habitDurationMinutesSchema.nullish(),
21476
+ activeMonths: habitActiveMonthsSchema.optional()
21459
21477
  }).omit({ tenantId: true, recurrenceRuleId: true, retiredAt: true, ...eventColumns }).extend({
21460
21478
  title: string2().min(1),
21461
21479
  schedule: habitScheduleSchema.optional(),
@@ -21472,7 +21490,8 @@ var updateHabitInputSchema = createUpdateSchema(habits, {
21472
21490
  aliases: habitAliasesSchema.optional(),
21473
21491
  timeOfDay: habitTimeOfDaySchema.nullish(),
21474
21492
  remind: boolean2().optional(),
21475
- durationMinutes: habitDurationMinutesSchema.nullish()
21493
+ durationMinutes: habitDurationMinutesSchema.nullish(),
21494
+ activeMonths: habitActiveMonthsSchema.optional()
21476
21495
  }).omit({
21477
21496
  id: true,
21478
21497
  tenantId: true,
@@ -21484,7 +21503,8 @@ var updateHabitInputSchema = createUpdateSchema(habits, {
21484
21503
  ...eventColumns
21485
21504
  }).extend({
21486
21505
  title: string2().min(1).optional(),
21487
- schedule: habitScheduleSchema.optional()
21506
+ schedule: habitScheduleSchema.optional(),
21507
+ displayKind: string2().trim().min(1).max(40).optional()
21488
21508
  });
21489
21509
  var HABIT_ENTRY_NOTE_MAX = 280;
21490
21510
  var habitEntryNoteSchema = string2().trim().max(HABIT_ENTRY_NOTE_MAX).transform((note) => note === "" ? null : note);
@@ -21512,7 +21532,9 @@ var habitStreakSchema = object({
21512
21532
  });
21513
21533
  var habitRoutineRefSchema = object({
21514
21534
  id: ulidSchema,
21515
- title: string2()
21535
+ title: string2(),
21536
+ path: array(string2()).default([]),
21537
+ step: number2().int().positive().nullable().default(null)
21516
21538
  });
21517
21539
  var habitLastCheckInSchema = object({
21518
21540
  at: exports_coerce.date(),
@@ -21592,20 +21614,49 @@ var habitCheckInByTitleInputSchema = object({
21592
21614
  value: number2().nonnegative().optional(),
21593
21615
  mode: habitCheckInModeSchema.default("add"),
21594
21616
  occurredAt: union([civilDateSchema, exports_coerce.date()]).optional(),
21595
- note: habitEntryNoteSchema.nullish()
21617
+ note: habitEntryNoteSchema.nullish(),
21618
+ again: boolean2().optional()
21596
21619
  }).strict();
21597
21620
  var habitCandidateSchema = object({
21598
21621
  id: ulidSchema,
21599
21622
  title: string2(),
21600
- aliases: array(string2())
21623
+ aliases: array(string2()),
21624
+ standing: string2().optional()
21625
+ });
21626
+ var habitCheckInQuestionSchema = object({
21627
+ prompt: string2(),
21628
+ options: array(string2()).min(2)
21601
21629
  });
21602
21630
  var habitCheckInAmbiguousSchema = object({
21603
21631
  error: object({
21604
21632
  code: literal("ambiguous_habit"),
21605
21633
  message: string2(),
21606
- details: object({ candidates: array(habitCandidateSchema).min(2) })
21634
+ details: object({
21635
+ candidates: array(habitCandidateSchema).min(2),
21636
+ question: habitCheckInQuestionSchema.optional()
21637
+ })
21638
+ })
21639
+ });
21640
+ var habitCheckInAlreadyMetSchema = object({
21641
+ error: object({
21642
+ code: literal("already_met"),
21643
+ message: string2(),
21644
+ details: object({ habit: habitCandidateSchema, question: habitCheckInQuestionSchema })
21607
21645
  })
21608
21646
  });
21647
+ var habitCheckInReceiptSchema = object({
21648
+ entryId: ulidSchema,
21649
+ habitId: ulidSchema,
21650
+ title: string2(),
21651
+ matched: string2(),
21652
+ value: number2(),
21653
+ occurredAt: exports_coerce.date(),
21654
+ paused: boolean2(),
21655
+ says: string2()
21656
+ });
21657
+ var habitCheckInByTitleResultSchema = habitSummarySchema.extend({
21658
+ receipt: habitCheckInReceiptSchema.nullable()
21659
+ });
21609
21660
  var habitAliasInputSchema = object({ alias: habitAliasSchema }).strict();
21610
21661
  var habitSkipInputSchema = object({
21611
21662
  date: exports_iso.date().optional(),
@@ -22737,6 +22788,11 @@ var updateRoutineRunInputSchema = object({
22737
22788
  var routineRunsQuerySchema = object({
22738
22789
  limit: exports_coerce.number().int().min(1).max(200).optional()
22739
22790
  });
22791
+ var recentRoutineRunsQuerySchema = object({
22792
+ since: exports_coerce.date().optional(),
22793
+ status: routineRunStatusSchema.optional(),
22794
+ limit: exports_coerce.number().int().min(1).max(200).default(50)
22795
+ });
22740
22796
  var routineDoneInputSchema = object({
22741
22797
  id: ulidSchema.optional(),
22742
22798
  occurrenceDate: exports_coerce.date().nullish()
@@ -22804,7 +22860,11 @@ var routineTodayStepSchema = object({
22804
22860
  minutes: number2().int().nullable(),
22805
22861
  done: boolean2(),
22806
22862
  depth: number2().int().nonnegative().default(0),
22807
- optional: boolean2().default(false)
22863
+ optional: boolean2().default(false),
22864
+ habitId: string2().nullable().default(null),
22865
+ routineId: string2().nullable().default(null),
22866
+ habitToday: _enum2(["due", "not_due", "paused"]).nullable().default(null),
22867
+ amount: object({ value: number2(), target: number2().nullable(), unit: string2().nullable() }).nullable().default(null)
22808
22868
  });
22809
22869
  var routineTodayStateSchema = _enum2(["done", "running", "upcoming", "resting"]);
22810
22870
  var routineTodaySchema = object({
@@ -24649,6 +24709,198 @@ var calendarOperations = defineOperations({
24649
24709
  }
24650
24710
  });
24651
24711
 
24712
+ // ../../packages/schema/src/contracts/challenges.ts
24713
+ var challengeStatusSchema = _enum2(["upcoming", "active", "ended"]);
24714
+ var challengeSchema = createSelectSchema(challenges, {
24715
+ startAt: exports_coerce.date(),
24716
+ endAt: exports_coerce.date(),
24717
+ createdAt: exports_coerce.date(),
24718
+ updatedAt: exports_coerce.date()
24719
+ });
24720
+ var createChallengeInputSchema = createInsertSchema(challenges, {
24721
+ id: ulidSchema,
24722
+ title: string2().min(1),
24723
+ description: string2().nullish(),
24724
+ metric: string2().min(1),
24725
+ target: number2().int().positive(),
24726
+ startAt: exports_coerce.date(),
24727
+ endAt: exports_coerce.date(),
24728
+ subjectType: challengeSubjectTypeSchema.nullish(),
24729
+ subjectId: string2().min(1).nullish()
24730
+ }).omit({ tenantId: true, source: true, createdBy: true, createdAt: true, updatedAt: true }).strict().refine((v) => v.endAt > v.startAt, {
24731
+ message: "endAt must be after startAt",
24732
+ path: ["endAt"]
24733
+ }).refine((v) => v.subjectType == null === (v.subjectId == null), {
24734
+ message: "subjectType and subjectId must be set together",
24735
+ path: ["subjectId"]
24736
+ });
24737
+ var challengePartKindSchema = _enum2(challengePartKind.enumValues);
24738
+ var createChallengePartInputSchema = object({
24739
+ kind: challengePartKindSchema,
24740
+ subjectId: ulidSchema,
24741
+ resumeOnStart: boolean2().optional()
24742
+ }).strict().refine((p) => !p.resumeOnStart || p.kind === "habit", {
24743
+ message: "only a habit part can resume on the start day",
24744
+ path: ["resumeOnStart"]
24745
+ });
24746
+ var CHALLENGE_MAX_PARTS = 5;
24747
+ var createChallengeWithPartsInputSchema = object({
24748
+ id: ulidSchema.optional(),
24749
+ title: string2().trim().min(1),
24750
+ description: string2().nullish(),
24751
+ startOn: exports_iso.date(),
24752
+ endOn: exports_iso.date(),
24753
+ projectId: ulidSchema.nullish(),
24754
+ parts: array(createChallengePartInputSchema).min(1).max(CHALLENGE_MAX_PARTS)
24755
+ }).strict().refine((v) => v.endOn >= v.startOn, {
24756
+ message: "endOn must not be before startOn",
24757
+ path: ["endOn"]
24758
+ });
24759
+ var updateChallengeInputSchema = object({
24760
+ title: string2().trim().min(1),
24761
+ description: string2().nullish(),
24762
+ startOn: exports_iso.date(),
24763
+ endOn: exports_iso.date(),
24764
+ projectId: ulidSchema.nullish(),
24765
+ parts: array(createChallengePartInputSchema).min(1).max(CHALLENGE_MAX_PARTS)
24766
+ }).strict().refine((v) => v.endOn >= v.startOn, {
24767
+ message: "endOn must not be before startOn",
24768
+ path: ["endOn"]
24769
+ });
24770
+ var createChallengeRequestSchema = union([
24771
+ createChallengeWithPartsInputSchema,
24772
+ createChallengeInputSchema
24773
+ ]);
24774
+ var listChallengesQuerySchema = object({ projectId: ulidSchema.optional(), habitId: ulidSchema.optional() }).strict();
24775
+ var challengePartStatusSchema = _enum2(["done", "running", "waiting"]);
24776
+ var challengePartViewSchema = object({
24777
+ kind: challengePartKindSchema,
24778
+ subjectId: string2(),
24779
+ title: string2().nullable(),
24780
+ scheduleLabel: string2(),
24781
+ done: number2().int().nonnegative(),
24782
+ due: number2().int().nonnegative(),
24783
+ status: challengePartStatusSchema,
24784
+ note: string2().nullable(),
24785
+ resumeOnStart: boolean2().optional()
24786
+ });
24787
+ var challengeDayStatusSchema = _enum2(["met", "missed", "open", "later"]);
24788
+ var challengeDaySchema = object({
24789
+ date: exports_iso.date(),
24790
+ status: challengeDayStatusSchema
24791
+ });
24792
+ var joinChallengeInputSchema = object({ id: ulidSchema }).strict();
24793
+ var challengeViewSchema = challengeSchema.extend({
24794
+ participantCount: number2().int().nonnegative(),
24795
+ joined: boolean2(),
24796
+ progress: number2().int().nonnegative(),
24797
+ status: challengeStatusSchema,
24798
+ completed: boolean2(),
24799
+ subjectTitle: string2().nullable(),
24800
+ dayNumber: number2().int().nonnegative(),
24801
+ dayCount: number2().int().nonnegative(),
24802
+ projectTitle: string2().nullable(),
24803
+ parts: array(challengePartViewSchema),
24804
+ days: array(challengeDaySchema)
24805
+ });
24806
+ var challengeLeaderboardEntrySchema = object({
24807
+ userId: ulidSchema,
24808
+ email: string2(),
24809
+ progress: number2().int().nonnegative(),
24810
+ rank: number2().int().positive(),
24811
+ completed: boolean2()
24812
+ });
24813
+ var selfComparisonMetricSchema = object({
24814
+ metric: string2(),
24815
+ thisWeek: number2().int().nonnegative(),
24816
+ fourWeekAverage: number2().nonnegative(),
24817
+ deltaPct: number2().nullable(),
24818
+ personalBest: number2().int().nonnegative(),
24819
+ isPersonalBest: boolean2()
24820
+ });
24821
+ var selfComparisonSchema = object({
24822
+ weekStart: exports_coerce.date(),
24823
+ metrics: array(selfComparisonMetricSchema)
24824
+ });
24825
+
24826
+ // ../../packages/schema/src/operations/challenges.ts
24827
+ var NO_MEMBER = "The key acts for no member.";
24828
+ var challengeOperations = defineOperations({
24829
+ list: {
24830
+ method: "get",
24831
+ path: "/challenges",
24832
+ scopes: ["habits:read"],
24833
+ tag: "Habits",
24834
+ summary: "List challenges",
24835
+ description: "Every challenge in the key's Space, newest first, each with its status (upcoming, active or ended), Day N of M, its parts with done vs due so far and their status, its days (met, missed, open or later) and its linked project. `projectId` or `habitId` keeps the challenges a project or a habit is in.",
24836
+ query: [
24837
+ { name: "projectId", description: "Only the challenges linked to this project." },
24838
+ { name: "habitId", description: "Only the challenges with this habit as a part." }
24839
+ ],
24840
+ querySchema: listChallengesQuerySchema,
24841
+ status: 200,
24842
+ response: { description: "The Space's challenges.", list: true },
24843
+ errors: { 400: true, 403: NO_MEMBER }
24844
+ },
24845
+ get: {
24846
+ method: "get",
24847
+ path: "/challenges/{id}",
24848
+ scopes: ["habits:read"],
24849
+ tag: "Habits",
24850
+ summary: "Show a challenge",
24851
+ description: "One challenge with the key's member's progress: Day N of M, each part's schedule, done vs due, status and next note, and each calendar day's state.",
24852
+ status: 200,
24853
+ response: { description: "The challenge." },
24854
+ errors: { 403: NO_MEMBER, 404: true }
24855
+ },
24856
+ create: {
24857
+ method: "post",
24858
+ path: "/challenges",
24859
+ scopes: ["habits:write"],
24860
+ tag: "Habits",
24861
+ summary: "Create a challenge",
24862
+ description: "A challenge from `startOn` to `endOn` (both included) with one to five parts (`habit` or `task_series`, each by id; a paused habit part may `resumeOnStart`) and an optional `projectId`; or a legacy metric challenge (`metric`, `target`, `startAt`, `endAt`). The key's member joins it.",
24863
+ body: createChallengeRequestSchema,
24864
+ status: 201,
24865
+ response: { description: "The created challenge." },
24866
+ errors: { 400: true, 403: NO_MEMBER, 409: true }
24867
+ },
24868
+ update: {
24869
+ method: "patch",
24870
+ path: "/challenges/{id}",
24871
+ scopes: ["habits:write"],
24872
+ tag: "Habits",
24873
+ summary: "Update a challenge",
24874
+ description: "The whole form again: title, description, start and end day, project and parts (replaced in their order).",
24875
+ body: updateChallengeInputSchema,
24876
+ status: 200,
24877
+ response: { description: "The challenge." },
24878
+ errors: { 400: true, 403: NO_MEMBER, 404: true }
24879
+ },
24880
+ end: {
24881
+ method: "post",
24882
+ path: "/challenges/{id}/end",
24883
+ scopes: ["habits:write"],
24884
+ tag: "Habits",
24885
+ summary: "End a challenge early",
24886
+ description: "Its window closes now and it reads as ended. One that hasn't started is a 400 (delete it instead).",
24887
+ status: 200,
24888
+ response: { description: "The ended challenge." },
24889
+ errors: { 400: true, 403: NO_MEMBER, 404: true }
24890
+ },
24891
+ remove: {
24892
+ method: "delete",
24893
+ path: "/challenges/{id}",
24894
+ scopes: ["habits:write"],
24895
+ tag: "Habits",
24896
+ summary: "Delete a challenge",
24897
+ description: "Gone for good, with its parts and who joined; its habits and task series stay.",
24898
+ status: 204,
24899
+ response: { description: "Deleted." },
24900
+ errors: { 403: NO_MEMBER, 404: true }
24901
+ }
24902
+ });
24903
+
24652
24904
  // ../../packages/schema/src/operations/changes.ts
24653
24905
  var changesOperations = defineOperations({
24654
24906
  list: {
@@ -28849,7 +29101,7 @@ var foodPatternsSchema = object({
28849
29101
  });
28850
29102
 
28851
29103
  // ../../packages/schema/src/operations/food-log.ts
28852
- var NO_MEMBER = "The key holds no health scope by name, or has no member behind it.";
29104
+ var NO_MEMBER2 = "The key holds no health scope by name, or has no member behind it.";
28853
29105
  var HIDDEN2 = "No such meal, or it isn't the key's member's.";
28854
29106
  var RECEIPT2 = "The meal with its `receipt`: a headline with the kcal range, a confidence word (close \xB7 estimate \xB7 rough), a line per item with its source, and the Undo / Edit / Save as my meal calls.";
28855
29107
  var ITEM = "Each item: `label` and one of `foodId` (from `GET /api/v1/foods/search`), `productId` or `recipeId`, with `grams`, `quantity` + `unit` (`2 boterham`, `1 el`) or `servings`; optional `gramsLow`/`gramsHigh` and `matchConfidence`. With no reference, an `estimate` `{kcal, proteinG, carbsG, fatG}` (a labelled guess). The numbers are computed: table \xD7 grams.";
@@ -28870,7 +29122,7 @@ var foodLogOperations = defineOperations({
28870
29122
  querySchema: mealLogListQuerySchema,
28871
29123
  status: 200,
28872
29124
  response: { description: "The meals." },
28873
- errors: { 400: true, 403: NO_MEMBER }
29125
+ errors: { 400: true, 403: NO_MEMBER2 }
28874
29126
  },
28875
29127
  create: {
28876
29128
  method: "post",
@@ -28882,7 +29134,7 @@ var foodLogOperations = defineOperations({
28882
29134
  body: createMealLogInputSchema,
28883
29135
  status: 201,
28884
29136
  response: { description: RECEIPT2 },
28885
- errors: { 400: true, 403: NO_MEMBER },
29137
+ errors: { 400: true, 403: NO_MEMBER2 },
28886
29138
  operationId: "food_log"
28887
29139
  },
28888
29140
  get: {
@@ -28992,7 +29244,7 @@ var foodLogOperations = defineOperations({
28992
29244
  body: writeFoodWeekReadInputSchema,
28993
29245
  status: 200,
28994
29246
  response: { description: "The food week, with its read." },
28995
- errors: { 400: true, 403: NO_MEMBER },
29247
+ errors: { 400: true, 403: NO_MEMBER2 },
28996
29248
  operationId: "food_week_read"
28997
29249
  },
28998
29250
  removeWeekRead: {
@@ -29006,7 +29258,7 @@ var foodLogOperations = defineOperations({
29006
29258
  querySchema: foodWeekReadQuerySchema,
29007
29259
  status: 204,
29008
29260
  response: { description: "Taken back." },
29009
- errors: { 400: true, 403: NO_MEMBER },
29261
+ errors: { 400: true, 403: NO_MEMBER2 },
29010
29262
  operationId: "food_week_read_remove"
29011
29263
  },
29012
29264
  patterns: {
@@ -29030,7 +29282,7 @@ var foodLogOperations = defineOperations({
29030
29282
  body: proposeFoodTrialInputSchema,
29031
29283
  status: 201,
29032
29284
  response: { description: "The trial, proposed." },
29033
- errors: { 400: true, 403: NO_MEMBER },
29285
+ errors: { 400: true, 403: NO_MEMBER2 },
29034
29286
  operationId: "food_trial"
29035
29287
  },
29036
29288
  stopTrial: {
@@ -29061,7 +29313,7 @@ var foodLogOperations = defineOperations({
29061
29313
  querySchema: foodDaysQuerySchema,
29062
29314
  status: 200,
29063
29315
  response: { description: "The days." },
29064
- errors: { 400: true, 403: NO_MEMBER }
29316
+ errors: { 400: true, 403: NO_MEMBER2 }
29065
29317
  },
29066
29318
  day: {
29067
29319
  method: "get",
@@ -29074,7 +29326,7 @@ var foodLogOperations = defineOperations({
29074
29326
  description: "One day: its meals, its total (null when nothing is logged) and the household's planned meals for it.",
29075
29327
  status: 200,
29076
29328
  response: { description: "The day." },
29077
- errors: { 400: true, 403: NO_MEMBER }
29329
+ errors: { 400: true, 403: NO_MEMBER2 }
29078
29330
  },
29079
29331
  recall: {
29080
29332
  method: "get",
@@ -29085,7 +29337,7 @@ var foodLogOperations = defineOperations({
29085
29337
  description: `What the member eats often: their most-logged meals of the last 30 days (with the last one's id, to log it again) and their saved meals. Use it before asking what "the usual" is.`,
29086
29338
  status: 200,
29087
29339
  response: { description: "Frequent and saved meals." },
29088
- errors: { 403: NO_MEMBER },
29340
+ errors: { 403: NO_MEMBER2 },
29089
29341
  operationId: "food_recall"
29090
29342
  }
29091
29343
  });
@@ -30165,14 +30417,14 @@ var habitOperations = defineOperations({
30165
30417
  scopes: ["habits:write"],
30166
30418
  tag: "Habits",
30167
30419
  summary: "Check in a habit by title",
30168
- description: 'Check in the habit whose `title` or one of whose aliases is this (case-insensitive) \u2014 "wake", "did my run", "meditated". `value` defaults to 1. `mode: "add"` (default) logs it on top of the day ("12 pages"); `mode: "set"` makes it the day\'s total ("read 40 total") by writing one entry with the difference, keeping earlier check-ins (amount habits only, else 400). `occurredAt` back-dates it: an instant, or a `YYYY-MM-DD` member\'s day (noon in their zone); `set` works on the member\'s day it lands in. Several matching habits are a 409 with `error.code: "ambiguous_habit"` and `error.details.candidates` (`id`, `title`, `aliases`) to ask which; none is a 404 naming close titles; a habit counted from an ingest (Google Fit, Strava, GitHub) is a 409 \u2014 it checks itself in. An Agent\'s key records an `agent` check-in naming the Agent. `note` puts a short note on the check-in ("drill done, felt heavy" \u2192 `note: "felt heavy"`); a `set` that changes nothing puts it on the day\'s latest check-in. The refreshed habit\'s `lastCheckIn.id` is the entry, for adding a note later. Returns the refreshed habit; fires `habit.updated`.',
30420
+ description: 'Check in the habit whose `title` or one of whose aliases is this (case-insensitive) \u2014 "wake", "did my run", "meditated". `value` defaults to 1. `mode: "add"` (default) logs it on top of the day ("12 pages"); `mode: "set"` makes it the day\'s total ("read 40 total") by writing one entry with the difference, keeping earlier check-ins (amount habits only, else 400). `occurredAt` back-dates it: an instant, or a `YYYY-MM-DD` member\'s day (noon in their zone); `set` works on the member\'s day it lands in. Several matching habits are a 409 with `error.code: "ambiguous_habit"`, `error.details.candidates` (`id`, `title`, `aliases`, `standing`: "done, 07:03", "tonight") and `error.details.question` ("Which one? \u2026", one answer per habit and "Neither") to ask which, never a guessed tick; none is a 404 naming close titles; a habit counted from an ingest (Google Fit, Strava, GitHub) is a 409 ("Checked in by Google Fit: \u2026 counts itself.") \u2014 it checks itself in; a yes/no habit already done today is a 409 `already_met` with `error.details.habit` and `error.details.question` ("\u2026 Tick it again?"), unless `again: true`. A paused habit is ticked; its streak waits. An Agent\'s key records an `agent` check-in naming the Agent. `note` puts a short note on the check-in ("drill done, felt heavy" \u2192 `note: "felt heavy"`); a `set` that changes nothing puts it on the day\'s latest check-in. The refreshed habit\'s `lastCheckIn.id` is the entry, for adding a note later. Returns the refreshed habit with its `receipt` (`entryId`, undone with `DELETE /habits/{id}/check-ins/{entryId}`; `habitId`, `title`, `matched`: the alias or title the words matched, `value`, `occurredAt`, `paused`, `says`: "Water the plants, 3 of 4"), null when a `set` changed nothing; fires `habit.updated`.',
30169
30421
  body: habitCheckInByTitleInputSchema,
30170
30422
  status: 201,
30171
- response: { description: "The refreshed habit." },
30423
+ response: { description: "The refreshed habit, with its check-in's receipt." },
30172
30424
  errors: {
30173
30425
  400: true,
30174
30426
  404: true,
30175
- 409: "Several habits answer to the title (with the candidates), or it is counted from an ingest."
30427
+ 409: "Several habits answer to the title (with the candidates and the question), it is counted from an ingest, or a yes/no habit is already done today (already_met)."
30176
30428
  },
30177
30429
  webhook: "habit.updated"
30178
30430
  },
@@ -31326,7 +31578,8 @@ var houseUpkeepSoonSchema = object({
31326
31578
  nextOn: day.nullable(),
31327
31579
  placeTitle: string2().nullable().default(null),
31328
31580
  lastOn: day.nullable().default(null),
31329
- occurrenceId: string2().nullable().default(null)
31581
+ occurrenceId: string2().nullable().default(null),
31582
+ thisMonth: boolean2().optional()
31330
31583
  });
31331
31584
  var housePlaceTileSchema = object({
31332
31585
  id: string2(),
@@ -34387,7 +34640,7 @@ var memberOperations = defineOperations({
34387
34640
  });
34388
34641
 
34389
34642
  // ../../packages/schema/src/operations/metrics.ts
34390
- var NO_MEMBER2 = "The key acts for no member.";
34643
+ var NO_MEMBER3 = "The key acts for no member.";
34391
34644
  var metricOperations = defineOperations({
34392
34645
  list: {
34393
34646
  method: "get",
@@ -34402,7 +34655,7 @@ var metricOperations = defineOperations({
34402
34655
  querySchema: metricsQuerySchema,
34403
34656
  status: 200,
34404
34657
  response: { description: "Every Metric with its points." },
34405
- errors: { 403: NO_MEMBER2 }
34658
+ errors: { 403: NO_MEMBER3 }
34406
34659
  },
34407
34660
  sessions: {
34408
34661
  method: "get",
@@ -34417,7 +34670,7 @@ var metricOperations = defineOperations({
34417
34670
  querySchema: sessionsQuerySchema,
34418
34671
  status: 200,
34419
34672
  response: { description: "The sessions." },
34420
- errors: { 403: NO_MEMBER2 }
34673
+ errors: { 403: NO_MEMBER3 }
34421
34674
  },
34422
34675
  import: {
34423
34676
  method: "post",
@@ -34429,7 +34682,7 @@ var metricOperations = defineOperations({
34429
34682
  body: importMetricsInputSchema,
34430
34683
  status: 201,
34431
34684
  response: { description: "How many points landed and how many were skipped." },
34432
- errors: { 400: true, 403: NO_MEMBER2 }
34685
+ errors: { 400: true, 403: NO_MEMBER3 }
34433
34686
  },
34434
34687
  log: {
34435
34688
  method: "post",
@@ -34448,7 +34701,7 @@ var metricOperations = defineOperations({
34448
34701
  body: logMetricInputSchema,
34449
34702
  status: 201,
34450
34703
  response: { description: "The Metric." },
34451
- errors: { 400: true, 403: NO_MEMBER2 }
34704
+ errors: { 400: true, 403: NO_MEMBER3 }
34452
34705
  }
34453
34706
  });
34454
34707
 
@@ -37345,6 +37598,16 @@ var reviewMarkSchema = object({
37345
37598
  date: progressDateSchema,
37346
37599
  state: reviewMarkStateSchema
37347
37600
  });
37601
+ var reviewTallySchema = object({ done: countSchema2, due: countSchema2 });
37602
+ var reviewHabitsSchema = object({
37603
+ kept30: reviewTallySchema,
37604
+ kept30Before: reviewTallySchema,
37605
+ checkIns: countSchema2,
37606
+ checkInsByAgent: countSchema2,
37607
+ checkInsFromRoutines: countSchema2,
37608
+ paused: countSchema2,
37609
+ grown: countSchema2
37610
+ });
37348
37611
  var reviewNumbersSchema = object({
37349
37612
  tasksDone: countSchema2,
37350
37613
  tasksDoneDelta: number2().int(),
@@ -37367,7 +37630,13 @@ var reviewStreakSchema = object({
37367
37630
  current: countSchema2,
37368
37631
  best: countSchema2,
37369
37632
  unitLabel: string2().min(1),
37370
- marks: array(reviewMarkSchema)
37633
+ marks: array(reviewMarkSchema),
37634
+ state: _enum2(["active", "recovering", "broken"]).optional(),
37635
+ graceUsed: countSchema2.optional(),
37636
+ graceBudget: countSchema2.optional(),
37637
+ kept30: reviewTallySchema.optional(),
37638
+ period: reviewTallySchema.optional(),
37639
+ paused: boolean2().optional()
37371
37640
  });
37372
37641
  var reviewProjectSchema = object({
37373
37642
  projectId: string2().min(1),
@@ -37386,14 +37655,15 @@ var reviewSummarySchema = object({
37386
37655
  numbers: reviewNumbersSchema,
37387
37656
  series: array(reviewSeriesEntrySchema).length(8),
37388
37657
  streaks: array(reviewStreakSchema),
37389
- projects: array(reviewProjectSchema)
37658
+ projects: array(reviewProjectSchema),
37659
+ habits: reviewHabitsSchema.optional()
37390
37660
  }).refine((s) => s.series.at(-1)?.current === true, {
37391
37661
  message: "the last series entry is the asked period",
37392
37662
  path: ["series"]
37393
37663
  }).refine((s) => s.from <= s.to, { message: "from is on or before to", path: ["from"] });
37394
37664
 
37395
37665
  // ../../packages/schema/src/operations/progress.ts
37396
- var NO_MEMBER3 = "The key acts for no member.";
37666
+ var NO_MEMBER4 = "The key acts for no member.";
37397
37667
  var question = {
37398
37668
  question: { description: "The question's key.", enum: insightsQuestionSchema.options }
37399
37669
  };
@@ -37439,7 +37709,7 @@ var progressOperations = defineOperations({
37439
37709
  scopes: ["tasks:read", "habits:read"],
37440
37710
  tag: "Reads",
37441
37711
  summary: "Review summary",
37442
- description: "Plan > Review in one read, for the ISO week (Monday to Sunday) or calendar month holding `date` (default: the one holding today, in the key's member's saved timezone): the headline numbers (tasks done, mine and the agents', against the period before; habit days met and due; projects with a task done; challenges running), tasks done and habit days met for the last 8 periods (oldest first, the last one the asked period), one streak row per live habit (current and best run, `unitLabel` `N days` for a daily habit and `N in a row` otherwise, and a mark per day of the week or per week of the month: done, missed, open, later or not_due, a day it isn't due that never breaks the run), and each open project's tasks done of its total, the most recently active first.",
37712
+ description: "Plan > Review in one read, for the ISO week (Monday to Sunday) or calendar month holding `date` (default: the one holding today, in the key's member's saved timezone): the headline numbers (tasks done, mine and the agents', against the period before; habit days met and due; projects with a task done; challenges running), tasks done and habit days met for the last 8 periods (oldest first, the last one the asked period), one streak row per live habit (current and best run, `unitLabel` `N days` for a daily habit and `N in a row` otherwise, and a mark per day of the week or per week of the month: done, missed, open, later or not_due, a day it isn't due that never breaks the run), and each open project's tasks done of its total, the most recently active first. For Review \u203A Habits each streak row also carries its `state` (active, recovering or broken), grace used of its budget, `kept30` (due days met of due over the last 30 days), `period` (met of due in the asked period) and whether it rests today (`paused`), and `habits` gives the tiles: kept over the last 30 days and the 30 before, the period's check-ins with how many an agent and a routine made, and how many habits rest and have grown.",
37443
37713
  query: [
37444
37714
  {
37445
37715
  name: "span",
@@ -37482,7 +37752,7 @@ var progressOperations = defineOperations({
37482
37752
  querySchema: githubCommitsQuerySchema,
37483
37753
  status: 200,
37484
37754
  response: { description: "The split." },
37485
- errors: { 400: true, 403: NO_MEMBER3 }
37755
+ errors: { 400: true, 403: NO_MEMBER4 }
37486
37756
  },
37487
37757
  questions: {
37488
37758
  method: "get",
@@ -37493,7 +37763,7 @@ var progressOperations = defineOperations({
37493
37763
  description: "Insights' answers to its fixed questions, for the key's member only: late nights vs the next day's rating, the morning routine vs the day, good runs, bedtime this week vs usual, what the best days have in common, reading this week vs usual. Each is `shown`, `no_difference` or `collecting` (with `neededDays`); recomputed weekly (Monday 04:00 in the member's zone). Quote `sentence` and `footer` verbatim; a pattern goes together, never a cause. Mention only a Medium or High one.",
37494
37764
  status: 200,
37495
37765
  response: { description: "This week's answers.", view: "InsightsAnswersResponse" },
37496
- errors: { 403: NO_MEMBER3 },
37766
+ errors: { 403: NO_MEMBER4 },
37497
37767
  operationId: "get_insights"
37498
37768
  },
37499
37769
  dismiss: {
@@ -37534,7 +37804,7 @@ var progressOperations = defineOperations({
37534
37804
  body: memberActivityInputSchema,
37535
37805
  status: 201,
37536
37806
  response: { description: "Recorded." },
37537
- errors: { 400: true, 403: NO_MEMBER3 },
37807
+ errors: { 400: true, 403: NO_MEMBER4 },
37538
37808
  operationId: "record_member_activity"
37539
37809
  }
37540
37810
  });
@@ -38091,6 +38361,19 @@ var routineOperations = defineOperations({
38091
38361
  response: { description: "`{ today, routines }`." },
38092
38362
  operationId: "routines_today"
38093
38363
  },
38364
+ recentRuns: {
38365
+ method: "get",
38366
+ path: "/routines/runs",
38367
+ scopes: ["routines:read"],
38368
+ tag: "Routines",
38369
+ summary: "Recent runs",
38370
+ description: "The Space's recent routine runs across its live routines, newest first, each with its per-step state: `since` (a day or an instant) keeps those started at or after it, `status` (`in_progress`, `completed`, `abandoned`) keeps one kind, `limit` (1\u2013200, 50 by default) the newest. The Runs page reads the last completed run and the runs left unfinished in the last 7 days from it.",
38371
+ querySchema: recentRoutineRunsQuerySchema,
38372
+ status: 200,
38373
+ response: { description: "The runs, newest first." },
38374
+ errors: { 400: true },
38375
+ operationId: "routines_recent_runs"
38376
+ },
38094
38377
  getRun: {
38095
38378
  method: "get",
38096
38379
  path: "/routines/runs/{runId}",
@@ -44980,8 +45263,225 @@ var commands5 = [
44980
45263
  }
44981
45264
  ];
44982
45265
 
44983
- // src/commands/changes.ts
45266
+ // src/commands/challenges.ts
45267
+ var STATUS_TITLE = { active: "Active", upcoming: "Upcoming", ended: "Ended" };
45268
+ var DAY_MARK = { met: "\u25CF", missed: "\u25CB", open: "\u25CC", later: "\xB7" };
45269
+ var day3 = (at) => new Date(at).toISOString().slice(0, 10);
45270
+ function when(c) {
45271
+ if (c.status === "upcoming")
45272
+ return `starts ${day3(c.startAt)}`;
45273
+ if (c.status === "ended")
45274
+ return `ended ${day3(c.endAt)}`;
45275
+ return `Day ${c.dayNumber} of ${c.dayCount}`;
45276
+ }
45277
+ function kept(c) {
45278
+ if (c.parts.length === 0)
45279
+ return `${c.progress} / ${c.target} ${c.metric}`;
45280
+ const met = c.days.filter((d) => d.status === "met").length;
45281
+ const settled = c.days.filter((d) => d.status === "met" || d.status === "missed").length;
45282
+ return `kept ${met} of ${settled} ${settled === 1 ? "day" : "days"}`;
45283
+ }
45284
+ function oneLine(c, mode) {
45285
+ const project = c.projectTitle ? ` ${paint(mode, "dim", `\xB7 ${c.projectTitle}`)}` : "";
45286
+ return ` ${c.title} ${when(c)} ${kept(c)}${project} ${paint(mode, "dim", c.id)}`;
45287
+ }
45288
+ function renderChallenges(data, mode) {
45289
+ const page = data;
45290
+ const list = Array.isArray(page) ? page : page.data;
45291
+ if (list.length === 0)
45292
+ return paint(mode, "dim", "(no challenges)");
45293
+ return ["active", "upcoming", "ended"].map((status) => {
45294
+ const rows = list.filter((c) => c.status === status);
45295
+ if (rows.length === 0)
45296
+ return null;
45297
+ return [paint(mode, "bold", STATUS_TITLE[status]), ...rows.map((c) => oneLine(c, mode))].join(`
45298
+ `);
45299
+ }).filter((group) => group !== null).join(`
45300
+
45301
+ `);
45302
+ }
45303
+ function renderChallenge(data, mode) {
45304
+ const c = data;
45305
+ const head = [
45306
+ `${paint(mode, "bold", c.title)} ${STATUS_TITLE[c.status]} ${when(c)}`,
45307
+ ` ${day3(c.startAt)} to ${day3(c.endAt)}${c.projectTitle ? ` \xB7 ${c.projectTitle}` : ""} ${kept(c)}`
45308
+ ];
45309
+ if (c.parts.length === 0) {
45310
+ const lens = c.subjectTitle ? ` (${c.subjectTitle})` : "";
45311
+ return [...head, ` ${c.metric}: ${c.progress} of ${c.target}${lens}`].join(`
45312
+ `);
45313
+ }
45314
+ const parts = c.parts.map((p) => {
45315
+ const mark = p.status === "done" ? "\u2713" : p.status === "running" ? "\u25B8" : "\xB7";
45316
+ const note = p.note ? ` ${paint(mode, "dim", p.note)}` : "";
45317
+ return ` ${mark} ${p.title ?? "(gone)"} ${paint(mode, "dim", p.scheduleLabel)} ${p.done} of ${p.due} ${p.status}${note}`;
45318
+ });
45319
+ const strip = c.days.map((d) => DAY_MARK[d.status]).join("");
45320
+ return [
45321
+ ...head,
45322
+ "",
45323
+ "Parts",
45324
+ ...parts,
45325
+ "",
45326
+ `Days ${strip}`,
45327
+ paint(mode, "dim", " \u25CF met \u25CB missed \u25CC today \xB7 later")
45328
+ ].join(`
45329
+ `);
45330
+ }
45331
+ var PART_GRAMMAR = "`habit:<id>` or `series:<id>` (a task series); repeat --part for up to five, in order.";
45332
+ function partOf(raw, resume) {
45333
+ const [kind, id] = raw.split(":", 2);
45334
+ if (!id || kind !== "habit" && kind !== "series") {
45335
+ throw usageError(`--part takes ${PART_GRAMMAR} Got "${raw}".`);
45336
+ }
45337
+ if (kind === "series")
45338
+ return { kind: "task_series", subjectId: id };
45339
+ return { kind: "habit", subjectId: id, ...resume.has(id) ? { resumeOnStart: true } : {} };
45340
+ }
45341
+ var FORM_FLAGS = [
45342
+ { name: "from", type: "string", description: "The first day, YYYY-MM-DD." },
45343
+ { name: "to", type: "string", description: "The last day, YYYY-MM-DD (included)." },
45344
+ { name: "part", type: "string", description: `A part: ${PART_GRAMMAR}` },
45345
+ { name: "project", type: "string", description: "The project it's for (its id)." },
45346
+ { name: "description", type: "string", description: "What it's for, in a line." },
45347
+ {
45348
+ name: "resume-on-start",
45349
+ type: "string",
45350
+ description: "A paused habit part (its id) to bring back on the first day; repeat it."
45351
+ }
45352
+ ];
44984
45353
  var commands6 = [
45354
+ defineCommand({
45355
+ operation: challengeOperations.list,
45356
+ path: ["challenges", "list"],
45357
+ summary: "List the challenges: Active, Upcoming, Ended, with Day N of M.",
45358
+ flags: [
45359
+ { name: "project", type: "string", description: "Only those linked to this project." },
45360
+ { name: "habit", type: "string", description: "Only those with this habit as a part." }
45361
+ ],
45362
+ returns: "ChallengeView[]",
45363
+ run: async ({ flags, requireClient }) => {
45364
+ const project = flagString(flags, "project");
45365
+ const habit = flagString(flags, "habit");
45366
+ const query = new URLSearchParams({
45367
+ ...project ? { projectId: project } : {},
45368
+ ...habit ? { habitId: habit } : {}
45369
+ }).toString();
45370
+ return {
45371
+ data: await requireClient().get(`${pathOf(challengeOperations.list)}${query ? `?${query}` : ""}`),
45372
+ render: renderChallenges
45373
+ };
45374
+ }
45375
+ }),
45376
+ defineCommand({
45377
+ operation: challengeOperations.get,
45378
+ path: ["challenges", "show"],
45379
+ summary: "Show a challenge: its parts with done vs due, and a mark per day.",
45380
+ args: [{ name: "challenge", required: true, description: "The challenge's id." }],
45381
+ returns: "ChallengeView",
45382
+ run: async ({ args, requireClient }) => ({
45383
+ data: await requireClient().get(pathOf(challengeOperations.get, { id: requireArg(args, 0, "challenge") })),
45384
+ render: renderChallenge
45385
+ })
45386
+ }),
45387
+ defineCommand({
45388
+ operation: challengeOperations.create,
45389
+ path: ["challenges", "create"],
45390
+ summary: "Create a challenge from --from to --to with one to five --part.",
45391
+ args: [{ name: "title", required: true, description: "The challenge's title." }],
45392
+ flags: [...FORM_FLAGS],
45393
+ returns: "ChallengeView",
45394
+ run: async ({ args, flags, requireClient }) => {
45395
+ const title = args.join(" ").trim();
45396
+ const resume = new Set(flagStrings(flags, "resume-on-start"));
45397
+ const project = flagString(flags, "project");
45398
+ const description = flagString(flags, "description");
45399
+ const body = parseInput(createChallengeWithPartsInputSchema, {
45400
+ id: ulid3(),
45401
+ title,
45402
+ startOn: flagString(flags, "from"),
45403
+ endOn: flagString(flags, "to"),
45404
+ parts: flagStrings(flags, "part").map((p) => partOf(p, resume)),
45405
+ ...project ? { projectId: project } : {},
45406
+ ...description ? { description } : {}
45407
+ });
45408
+ return {
45409
+ data: await requireClient().post(pathOf(challengeOperations.create), body),
45410
+ render: renderChallenge
45411
+ };
45412
+ }
45413
+ }),
45414
+ defineCommand({
45415
+ operation: challengeOperations.update,
45416
+ path: ["challenges", "update"],
45417
+ summary: "Change a challenge: what you pass replaces it; --part replaces all its parts.",
45418
+ args: [{ name: "challenge", required: true, description: "The challenge's id." }],
45419
+ flags: [
45420
+ { name: "title", type: "string", description: "A new title." },
45421
+ ...FORM_FLAGS,
45422
+ { name: "no-project", type: "boolean", description: "Unlink its project." }
45423
+ ],
45424
+ returns: "ChallengeView",
45425
+ run: async ({ args, flags, requireClient }) => {
45426
+ const id = requireArg(args, 0, "challenge");
45427
+ const client = requireClient();
45428
+ const now = await client.get(pathOf(challengeOperations.get, { id }));
45429
+ if (now.parts.length === 0) {
45430
+ throw usageError("A metric challenge has no parts to edit; end it or delete it instead.");
45431
+ }
45432
+ const resume = new Set(flagStrings(flags, "resume-on-start"));
45433
+ const given = flagStrings(flags, "part");
45434
+ const parts = given.length ? given.map((p) => partOf(p, resume)) : now.parts.map((p) => p.kind === "habit" ? {
45435
+ kind: "habit",
45436
+ subjectId: p.subjectId,
45437
+ ...p.resumeOnStart || resume.has(p.subjectId) ? { resumeOnStart: true } : {}
45438
+ } : { kind: p.kind, subjectId: p.subjectId });
45439
+ const description = flagString(flags, "description");
45440
+ const body = parseInput(updateChallengeInputSchema, {
45441
+ title: flagString(flags, "title") ?? now.title,
45442
+ description: description ?? now.description ?? null,
45443
+ startOn: flagString(flags, "from") ?? day3(now.startAt),
45444
+ endOn: flagString(flags, "to") ?? day3(now.endAt),
45445
+ projectId: flagBool(flags, "no-project") ? null : flagString(flags, "project") ?? now.projectId,
45446
+ parts
45447
+ });
45448
+ return {
45449
+ data: await client.patch(pathOf(challengeOperations.update, { id }), body),
45450
+ render: renderChallenge
45451
+ };
45452
+ }
45453
+ }),
45454
+ defineCommand({
45455
+ operation: challengeOperations.end,
45456
+ path: ["challenges", "end"],
45457
+ summary: "End a challenge early: it closes today and reads as ended.",
45458
+ args: [{ name: "challenge", required: true, description: "The challenge's id." }],
45459
+ returns: "ChallengeView",
45460
+ run: async ({ args, requireClient }) => ({
45461
+ data: await requireClient().post(pathOf(challengeOperations.end, { id: requireArg(args, 0, "challenge") }), {}),
45462
+ render: (data, mode) => `Ended early.
45463
+ ${renderChallenge(data, mode)}`
45464
+ })
45465
+ }),
45466
+ defineCommand({
45467
+ operation: challengeOperations.remove,
45468
+ path: ["challenges", "rm"],
45469
+ summary: "Delete a challenge; its habits and task series stay.",
45470
+ args: [{ name: "challenge", required: true, description: "The challenge's id." }],
45471
+ returns: "{ ok, deleted }",
45472
+ run: async ({ args, requireClient }) => {
45473
+ const id = requireArg(args, 0, "challenge");
45474
+ await requireClient().delete(pathOf(challengeOperations.remove, { id }));
45475
+ return {
45476
+ data: { ok: true, deleted: id },
45477
+ render: () => "Deleted the challenge. Its habits and task series stay."
45478
+ };
45479
+ }
45480
+ })
45481
+ ];
45482
+
45483
+ // src/commands/changes.ts
45484
+ var commands7 = [
44985
45485
  {
44986
45486
  spec: {
44987
45487
  path: ["changes"],
@@ -45032,7 +45532,7 @@ function redact(secret) {
45032
45532
  return "********";
45033
45533
  return `${secret.slice(0, 6)}\u2026${secret.slice(-2)}`;
45034
45534
  }
45035
- var commands7 = [
45535
+ var commands8 = [
45036
45536
  {
45037
45537
  spec: {
45038
45538
  path: ["whoami"],
@@ -45200,7 +45700,7 @@ var whereFlags = (flags) => ({
45200
45700
  ...flagString(flags, "chapter") ? { chapter: flagString(flags, "chapter") } : {},
45201
45701
  ...flagString(flags, "linked-note") ? { linkedNoteId: flagString(flags, "linked-note") } : {}
45202
45702
  });
45203
- var commands8 = [
45703
+ var commands9 = [
45204
45704
  {
45205
45705
  spec: {
45206
45706
  path: ["collection", "types"],
@@ -45640,7 +46140,7 @@ var commands8 = [
45640
46140
  ];
45641
46141
 
45642
46142
  // src/commands/comments.ts
45643
- var commands9 = [
46143
+ var commands10 = [
45644
46144
  {
45645
46145
  spec: {
45646
46146
  path: ["comment"],
@@ -46305,7 +46805,7 @@ function renderKraken(data, mode) {
46305
46805
  return lines.join(`
46306
46806
  `);
46307
46807
  }
46308
- var commands10 = pairCommands(CONNECTION_COMMAND_SPECS, CONNECTION_HANDLERS);
46808
+ var commands11 = pairCommands(CONNECTION_COMMAND_SPECS, CONNECTION_HANDLERS);
46309
46809
 
46310
46810
  // src/commands/contacts.ts
46311
46811
  var FIELD_FLAGS = [
@@ -46447,7 +46947,7 @@ function contactBodyFromFlags(flags, base, op, current) {
46447
46947
  }
46448
46948
  return body;
46449
46949
  }
46450
- var commands11 = pairCommands(CONTACT_COMMAND_SPECS, {
46950
+ var commands12 = pairCommands(CONTACT_COMMAND_SPECS, {
46451
46951
  "contacts create": async ({ args, flags, requireClient }) => {
46452
46952
  const name = args.join(" ").trim();
46453
46953
  const base = { id: ulid3(), ...name ? { title: name } : {}, ...readDataFlag(flags) };
@@ -46493,7 +46993,7 @@ var commands11 = pairCommands(CONTACT_COMMAND_SPECS, {
46493
46993
  });
46494
46994
 
46495
46995
  // src/commands/data-export.ts
46496
- var commands12 = [
46996
+ var commands13 = [
46497
46997
  {
46498
46998
  spec: {
46499
46999
  path: ["export"],
@@ -46532,7 +47032,7 @@ function dayDateArg(args, verb) {
46532
47032
  }
46533
47033
  return date;
46534
47034
  }
46535
- var commands13 = [
47035
+ var commands14 = [
46536
47036
  {
46537
47037
  spec: {
46538
47038
  path: ["days", "record"],
@@ -46658,7 +47158,7 @@ var commands13 = [
46658
47158
  // src/commands/documents.ts
46659
47159
  import { readFileSync as readFileSync4 } from "fs";
46660
47160
  import { basename } from "path";
46661
- var commands14 = [
47161
+ var commands15 = [
46662
47162
  {
46663
47163
  spec: {
46664
47164
  path: ["documents", "file"],
@@ -47037,7 +47537,7 @@ async function echoThreadId(client, ref) {
47037
47537
  function splitList(value) {
47038
47538
  return value.split(",").map((v) => v.trim()).filter((v) => v.length > 0);
47039
47539
  }
47040
- var commands15 = [
47540
+ var commands16 = [
47041
47541
  {
47042
47542
  spec: {
47043
47543
  path: ["echo", "post"],
@@ -47356,7 +47856,7 @@ var commands15 = [
47356
47856
  // src/commands/files.ts
47357
47857
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
47358
47858
  import { basename as basename2 } from "path";
47359
- var commands16 = [
47859
+ var commands17 = [
47360
47860
  {
47361
47861
  spec: {
47362
47862
  path: ["files", "list"],
@@ -47526,7 +48026,7 @@ var commands16 = [
47526
48026
  ];
47527
48027
 
47528
48028
  // src/commands/food.ts
47529
- var commands17 = [
48029
+ var commands18 = [
47530
48030
  {
47531
48031
  spec: {
47532
48032
  path: ["foods", "search"],
@@ -48071,7 +48571,7 @@ var commands17 = [
48071
48571
  // src/commands/guest-lists.ts
48072
48572
  import { writeFileSync as writeFileSync3 } from "fs";
48073
48573
  import { basename as basename3 } from "path";
48074
- var commands18 = [
48574
+ var commands19 = [
48075
48575
  {
48076
48576
  spec: {
48077
48577
  path: ["guest-lists", "overview"],
@@ -48161,7 +48661,7 @@ var commands18 = [
48161
48661
  ];
48162
48662
 
48163
48663
  // src/commands/guests.ts
48164
- var commands19 = [
48664
+ var commands20 = [
48165
48665
  {
48166
48666
  spec: {
48167
48667
  path: ["guests", "sent"],
@@ -48220,9 +48720,14 @@ var SCHEDULE_FLAGS = [
48220
48720
  type: "string",
48221
48721
  description: "How long it takes (15m, 1h, 1h30m; 1\u2013600 minutes); none clears it back to 15m."
48222
48722
  },
48723
+ {
48724
+ name: "season",
48725
+ type: "string",
48726
+ description: "Only in season: a run of months (Oct-Apr) or a list (oct,nov,dec or 10,11,12); off makes it all year."
48727
+ },
48223
48728
  { name: "data", type: "string", description: "JSON for any other field of the contract." }
48224
48729
  ];
48225
- var SCHEDULE_NOTES = 'One of --every-day, --on mo,we,fr, --every 3d|2w [--from YYYY-MM-DD], --monthly 1|last sets its schedule; days it isn\'t due never break the streak. Without one, it keeps `period` + `frequencyCount` (3\xD7 a week). A schedule that follows the waste calendar goes in --data: `{"schedule":{"kind":"event","sourceId":"<its connection id>","match":"bin collection","offset":"day_before_evening"}}` (or `same_day`; a match naming a kind, "PMD", follows only those pickups). The reply\'s `nextDue` lists its next 4 due days. --time 16:30|morning|midday|evening and --for 20m put it on your do calendar at that time on each due day (a part of day at 08:00, 12:30 or 19:00; 15 minutes without --for).';
48730
+ var SCHEDULE_NOTES = 'One of --every-day, --on mo,we,fr, --every 3d|2w [--from YYYY-MM-DD], --monthly 1|last sets its schedule; days it isn\'t due never break the streak. Without one, it keeps `period` + `frequencyCount` (3\xD7 a week). A schedule that follows the waste calendar goes in --data: `{"schedule":{"kind":"event","sourceId":"<its connection id>","match":"bin collection","offset":"day_before_evening"}}` (or `same_day`; a match naming a kind, "PMD", follows only those pickups). The reply\'s `nextDue` lists its next 4 due days. --time 16:30|morning|midday|evening and --for 20m put it on your do calendar at that time on each due day (a part of day at 08:00, 12:30 or 19:00; 15 minutes without --for). --season Oct-Apr keeps it to those months: out of season it isn\'t due, its days never break the streak, and it leaves every list (--season off: all year).';
48226
48731
  function localToday() {
48227
48732
  const now = new Date;
48228
48733
  const pad = (n) => String(n).padStart(2, "0");
@@ -48282,7 +48787,45 @@ function timingFromFlags(flags) {
48282
48787
  }
48283
48788
  return out;
48284
48789
  }
48285
- var commands20 = [
48790
+ var MONTH_NAMES = [
48791
+ "jan",
48792
+ "feb",
48793
+ "mar",
48794
+ "apr",
48795
+ "may",
48796
+ "jun",
48797
+ "jul",
48798
+ "aug",
48799
+ "sep",
48800
+ "oct",
48801
+ "nov",
48802
+ "dec"
48803
+ ];
48804
+ function monthOf(raw) {
48805
+ const text = raw.trim().toLowerCase();
48806
+ const n = /^\d{1,2}$/.test(text) ? Number(text) : MONTH_NAMES.indexOf(text.slice(0, 3)) + 1;
48807
+ if (n < 1 || n > 12)
48808
+ throw usageError(`--season: "${raw}" isn't a month`);
48809
+ return n;
48810
+ }
48811
+ function seasonFromFlag(raw) {
48812
+ const text = raw.trim().toLowerCase();
48813
+ if (text === "off" || text === "none")
48814
+ return null;
48815
+ return text.split(",").flatMap((part) => {
48816
+ const [from, to] = part.split("-");
48817
+ if (to === undefined)
48818
+ return [monthOf(part)];
48819
+ const start = monthOf(from);
48820
+ const length = (monthOf(to) - start + 12) % 12 + 1;
48821
+ return Array.from({ length }, (_, i) => (start - 1 + i) % 12 + 1);
48822
+ });
48823
+ }
48824
+ function seasonFromFlags(flags) {
48825
+ const season = flagString(flags, "season");
48826
+ return season === undefined ? {} : { activeMonths: seasonFromFlag(season) };
48827
+ }
48828
+ var commands21 = [
48286
48829
  {
48287
48830
  spec: {
48288
48831
  path: ["habits", "create"],
@@ -48302,7 +48845,8 @@ var commands20 = [
48302
48845
  ...title ? { title } : {},
48303
48846
  ...readDataFlag(flags),
48304
48847
  ...schedule ? { schedule } : {},
48305
- ...timingFromFlags(flags)
48848
+ ...timingFromFlags(flags),
48849
+ ...seasonFromFlags(flags)
48306
48850
  });
48307
48851
  if (flagBool(flags, "dry-run")) {
48308
48852
  return {
@@ -48319,7 +48863,7 @@ var commands20 = [
48319
48863
  {
48320
48864
  spec: {
48321
48865
  path: ["habits", "update"],
48322
- summary: "Change a habit: its schedule (--every-day, --on, --every, --monthly), --time, --for or --data.",
48866
+ summary: "Change a habit: its schedule (--every-day, --on, --every, --monthly), --time, --for, --season or --data.",
48323
48867
  args: [{ name: "id", required: true, description: "Habit ULID." }],
48324
48868
  flags: SCHEDULE_FLAGS,
48325
48869
  scopes: [...habitOperations.update.scopes],
@@ -48332,10 +48876,11 @@ var commands20 = [
48332
48876
  const body = parseInput(updateHabitInputSchema, {
48333
48877
  ...readDataFlag(flags),
48334
48878
  ...schedule ? { schedule } : {},
48335
- ...timingFromFlags(flags)
48879
+ ...timingFromFlags(flags),
48880
+ ...seasonFromFlags(flags)
48336
48881
  });
48337
48882
  if (Object.keys(body).length === 0)
48338
- throw usageError("nothing to change \u2014 pass a schedule flag such as --on mo,we, --time, --for, or --data");
48883
+ throw usageError("nothing to change \u2014 pass a schedule flag such as --on mo,we, --time, --for, --season, or --data");
48339
48884
  const client = requireClient();
48340
48885
  if (flagBool(flags, "dry-run")) {
48341
48886
  const before = await client.get(pathOf(habitOperations.get, { id }));
@@ -48423,11 +48968,16 @@ var commands20 = [
48423
48968
  name: "note",
48424
48969
  type: "string",
48425
48970
  description: 'A short note on the check-in ("felt heavy"), at most 280 characters.'
48971
+ },
48972
+ {
48973
+ name: "again",
48974
+ type: "boolean",
48975
+ description: "Tick a yes/no habit that's already done today once more."
48426
48976
  }
48427
48977
  ],
48428
48978
  scopes: [...habitOperations.checkInByTitle.scopes],
48429
48979
  returns: "HabitSummary",
48430
- notes: `${routeOf(habitOperations.checkInByTitle)}. 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 ("<agent>, 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.`
48980
+ notes: `${routeOf(habitOperations.checkInByTitle)}. Title or alias, case-insensitive; several matches \u2192 409 with error.details.candidates (id, title, aliases, standing) and error.details.question (which one, with "Neither"), none \u2192 404 naming close titles; a habit counted from Google Fit, Strava or GitHub \u2192 409 ("Checked in by \u2026"); a yes/no habit already done today \u2192 409 already_met with its question, unless --again. The reply's receipt names the habit, the alias it matched, the value and time, its words ("Water the plants, 3 of 4"; a paused habit's "the streak waits") and entryId, the check-in to undo. --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 ("<agent>, 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.`
48431
48981
  },
48432
48982
  run: async ({ args, flags, requireClient }) => {
48433
48983
  const title = args.join(" ").trim();
@@ -48447,7 +48997,8 @@ var commands20 = [
48447
48997
  ...value !== undefined ? { value } : {},
48448
48998
  mode: set ? "set" : "add",
48449
48999
  ...at ? { occurredAt: at } : {},
48450
- ...note !== undefined ? { note } : {}
49000
+ ...note !== undefined ? { note } : {},
49001
+ ...flags.again === true ? { again: true } : {}
48451
49002
  });
48452
49003
  return {
48453
49004
  data: await requireClient().post(pathOf(habitOperations.checkInByTitle), input),
@@ -49414,7 +49965,7 @@ function healthRecordHandlers() {
49414
49965
  }
49415
49966
  return out;
49416
49967
  }
49417
- var commands21 = pairCommands(HEALTH_COMMAND_SPECS, {
49968
+ var commands22 = pairCommands(HEALTH_COMMAND_SPECS, {
49418
49969
  ...healthRecordHandlers(),
49419
49970
  "health record": async ({ flags, requireClient }) => {
49420
49971
  const body = parseInput(logHealthInputSchema, readDataFlag(flags));
@@ -49684,7 +50235,7 @@ var commands21 = pairCommands(HEALTH_COMMAND_SPECS, {
49684
50235
  });
49685
50236
 
49686
50237
  // src/commands/house.ts
49687
- var commands22 = [
50238
+ var commands23 = [
49688
50239
  {
49689
50240
  spec: {
49690
50241
  path: ["house"],
@@ -49966,7 +50517,7 @@ function renderSorting(data, _mode) {
49966
50517
  ].join(`
49967
50518
  `);
49968
50519
  }
49969
- var commands23 = [
50520
+ var commands24 = [
49970
50521
  defineCommand({
49971
50522
  operation: inboxOperations.list,
49972
50523
  path: ["inbox"],
@@ -50058,7 +50609,7 @@ var commands23 = [
50058
50609
  ];
50059
50610
 
50060
50611
  // src/commands/incidents.ts
50061
- var commands24 = [
50612
+ var commands25 = [
50062
50613
  {
50063
50614
  spec: {
50064
50615
  path: ["incidents", "open"],
@@ -50287,7 +50838,7 @@ function journalDateArg(args, verb) {
50287
50838
  }
50288
50839
  return date;
50289
50840
  }
50290
- var commands25 = [
50841
+ var commands26 = [
50291
50842
  defineCommand({
50292
50843
  operation: journalOperations.day,
50293
50844
  path: ["journal", "day"],
@@ -50850,7 +51401,7 @@ function renderCreatedKey(data, mode) {
50850
51401
  ].join(`
50851
51402
  `);
50852
51403
  }
50853
- var commands26 = [
51404
+ var commands27 = [
50854
51405
  {
50855
51406
  spec: {
50856
51407
  path: ["keys", "list"],
@@ -50986,7 +51537,7 @@ var KIND_FLAG = {
50986
51537
  type: "string",
50987
51538
  description: "The kind of an unknown word (pages, eggs, health, note, or a word taught before); remembered for the Space."
50988
51539
  };
50989
- var commands27 = [
51540
+ var commands28 = [
50990
51541
  {
50991
51542
  spec: {
50992
51543
  path: ["log"],
@@ -51061,7 +51612,7 @@ var commands27 = [
51061
51612
  ];
51062
51613
 
51063
51614
  // src/commands/mail-campaigns.ts
51064
- var commands28 = [
51615
+ var commands29 = [
51065
51616
  {
51066
51617
  spec: {
51067
51618
  path: ["mail-campaigns", "list"],
@@ -51376,7 +51927,7 @@ async function serveMcp(io, target) {
51376
51927
  }
51377
51928
 
51378
51929
  // src/commands/mcp.ts
51379
- var commands29 = [
51930
+ var commands30 = [
51380
51931
  {
51381
51932
  spec: {
51382
51933
  path: ["mcp"],
@@ -51421,7 +51972,7 @@ var commands29 = [
51421
51972
  ];
51422
51973
 
51423
51974
  // src/commands/members.ts
51424
- var commands30 = [
51975
+ var commands31 = [
51425
51976
  {
51426
51977
  spec: {
51427
51978
  path: ["invite", "create"],
@@ -51546,7 +52097,7 @@ var commands30 = [
51546
52097
  ];
51547
52098
 
51548
52099
  // src/commands/metrics.ts
51549
- var commands31 = [
52100
+ var commands32 = [
51550
52101
  {
51551
52102
  spec: {
51552
52103
  path: ["metrics", "list"],
@@ -51674,7 +52225,7 @@ async function setModuleVisibility(args, requireClient, on) {
51674
52225
  render: renderRecord
51675
52226
  };
51676
52227
  }
51677
- var commands32 = [
52228
+ var commands33 = [
51678
52229
  {
51679
52230
  spec: {
51680
52231
  path: ["modules", "list"],
@@ -51898,7 +52449,7 @@ function shareFlag(flags) {
51898
52449
  function localMonth(now) {
51899
52450
  return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
51900
52451
  }
51901
- var commands33 = [
52452
+ var commands34 = [
51902
52453
  defineCommand({
51903
52454
  operation: moneyOperations.listAccounts,
51904
52455
  path: ["money", "accounts"],
@@ -53040,7 +53591,7 @@ var commands33 = [
53040
53591
  ];
53041
53592
 
53042
53593
  // src/commands/notes.ts
53043
- var commands34 = [
53594
+ var commands35 = [
53044
53595
  {
53045
53596
  spec: {
53046
53597
  path: ["notes", "backlinks"],
@@ -53162,7 +53713,7 @@ var commands34 = [
53162
53713
  ];
53163
53714
 
53164
53715
  // src/commands/orca.ts
53165
- var commands35 = [
53716
+ var commands36 = [
53166
53717
  {
53167
53718
  spec: {
53168
53719
  path: ["orca", "list"],
@@ -53516,7 +54067,7 @@ function linkBody(args) {
53516
54067
  return parseInput(familyTreeLinkInputSchema, { kind, a: ref, b: other });
53517
54068
  throw usageError("the kind is parent, child or partner");
53518
54069
  }
53519
- var commands36 = [
54070
+ var commands37 = [
53520
54071
  {
53521
54072
  spec: {
53522
54073
  path: ["people", "propose"],
@@ -54205,7 +54756,7 @@ var commands36 = [
54205
54756
 
54206
54757
  // src/commands/places.ts
54207
54758
  import { readFileSync as readFileSync10 } from "fs";
54208
- var commands37 = [
54759
+ var commands38 = [
54209
54760
  {
54210
54761
  spec: {
54211
54762
  path: ["places", "import"],
@@ -54262,7 +54813,7 @@ async function planDecision({ args, requireClient }, verb) {
54262
54813
  render: renderPlan
54263
54814
  };
54264
54815
  }
54265
- var commands38 = [
54816
+ var commands39 = [
54266
54817
  {
54267
54818
  spec: {
54268
54819
  path: ["plans", "list"],
@@ -54489,7 +55040,7 @@ var commands38 = [
54489
55040
  ];
54490
55041
 
54491
55042
  // src/commands/presets.ts
54492
- var commands39 = [
55043
+ var commands40 = [
54493
55044
  {
54494
55045
  spec: {
54495
55046
  path: ["presets", "list"],
@@ -54586,7 +55137,7 @@ function renderReviewSummary(data, mode) {
54586
55137
  return out.join(`
54587
55138
  `);
54588
55139
  }
54589
- var commands40 = [
55140
+ var commands41 = [
54590
55141
  {
54591
55142
  spec: {
54592
55143
  path: ["progress"],
@@ -54795,7 +55346,7 @@ function renderUpdates(data, mode) {
54795
55346
  return updates.map((u) => updateLine(u, mode)).join(`
54796
55347
  `);
54797
55348
  }
54798
- var commands41 = [
55349
+ var commands42 = [
54799
55350
  defineCommand({
54800
55351
  operation: projectOperations.update,
54801
55352
  path: ["projects", "set"],
@@ -54960,6 +55511,23 @@ function renderRoutinesToday(data, mode) {
54960
55511
  }).join(`
54961
55512
  `);
54962
55513
  }
55514
+ var RUN_WORDS = {
55515
+ in_progress: "going",
55516
+ completed: "done",
55517
+ abandoned: "left"
55518
+ };
55519
+ function renderRecentRuns(data, mode) {
55520
+ const list = (data.data ?? data) || [];
55521
+ if (list.length === 0)
55522
+ return "No runs.";
55523
+ return list.map((r) => {
55524
+ const steps = r.steps.filter((s) => s.refKind !== "routine");
55525
+ const done = steps.filter((s) => s.status === "done").length;
55526
+ const when = r.startedAt.slice(0, 16).replace("T", " ");
55527
+ return `${when} ${r.routineTitle} ${paint(mode, "dim", `${RUN_WORDS[r.status] ?? r.status} \xB7 ${done} of ${steps.length} done ${r.id}`)}`;
55528
+ }).join(`
55529
+ `);
55530
+ }
54963
55531
  function renderRoutineTaskAdded(data) {
54964
55532
  const run = data;
54965
55533
  const last = run.steps?.at(-1)?.title ?? "the task";
@@ -55375,8 +55943,48 @@ var routineCommands = [
55375
55943
  }
55376
55944
  }
55377
55945
  ];
55378
- var commands42 = [
55946
+ var commands43 = [
55379
55947
  ...routineCommands,
55948
+ {
55949
+ spec: {
55950
+ path: ["routines", "runs"],
55951
+ summary: "Recent runs across your routines: when, how far, done or left.",
55952
+ args: [],
55953
+ flags: [
55954
+ {
55955
+ name: "since",
55956
+ type: "string",
55957
+ description: "Only runs started on or after this day (YYYY-MM-DD) or instant."
55958
+ },
55959
+ { name: "status", type: "string", description: "in_progress, completed or abandoned." },
55960
+ {
55961
+ name: "limit",
55962
+ type: "string",
55963
+ description: "How many, newest first (1-200, default 50)."
55964
+ }
55965
+ ],
55966
+ scopes: [...routineOperations.recentRuns.scopes],
55967
+ returns: "RoutineRunView[]",
55968
+ notes: `${routeOf(routineOperations.recentRuns)}. A routine in Trash is left out. "Left" runs (abandoned) can be finished again from the app's Runs page.`
55969
+ },
55970
+ run: async ({ flags, requireClient }) => {
55971
+ const query = parseInput(recentRoutineRunsQuerySchema, {
55972
+ since: flagString(flags, "since"),
55973
+ status: flagString(flags, "status"),
55974
+ limit: flagString(flags, "limit")
55975
+ });
55976
+ return {
55977
+ data: await requireClient().get(pathOf(routineOperations.recentRuns), {
55978
+ query: {
55979
+ since: query.since ? flagString(flags, "since") : undefined,
55980
+ status: query.status,
55981
+ limit: String(query.limit)
55982
+ }
55983
+ }),
55984
+ render: renderRecentRuns
55985
+ };
55986
+ }
55987
+ },
55380
55988
  {
55381
55989
  spec: {
55382
55990
  path: ["routines", "today"],
@@ -55458,7 +56066,7 @@ var commands42 = [
55458
56066
  ];
55459
56067
 
55460
56068
  // src/commands/rsvp-answers.ts
55461
- var commands43 = [
56069
+ var commands44 = [
55462
56070
  {
55463
56071
  spec: {
55464
56072
  path: ["rsvp-answers", "list"],
@@ -55512,7 +56120,7 @@ var commands43 = [
55512
56120
  ];
55513
56121
 
55514
56122
  // src/commands/rsvp-links.ts
55515
- var commands44 = [
56123
+ var commands45 = [
55516
56124
  {
55517
56125
  spec: {
55518
56126
  path: ["rsvp-links", "list"],
@@ -55608,7 +56216,7 @@ var commands44 = [
55608
56216
  ];
55609
56217
 
55610
56218
  // src/commands/rsvp-questions.ts
55611
- var commands45 = [
56219
+ var commands46 = [
55612
56220
  {
55613
56221
  spec: {
55614
56222
  path: ["rsvp-questions", "presets"],
@@ -56180,7 +56788,7 @@ function sessionFlags(flags) {
56180
56788
  body.occurredOn = on;
56181
56789
  return body;
56182
56790
  }
56183
- var commands46 = [
56791
+ var commands47 = [
56184
56792
  {
56185
56793
  spec: {
56186
56794
  path: ["runs", "change"],
@@ -56497,7 +57105,7 @@ var commands46 = [
56497
57105
  ];
56498
57106
 
56499
57107
  // src/commands/search.ts
56500
- var commands47 = [
57108
+ var commands48 = [
56501
57109
  {
56502
57110
  spec: {
56503
57111
  path: ["search"],
@@ -56530,7 +57138,7 @@ var commands47 = [
56530
57138
  ];
56531
57139
 
56532
57140
  // src/commands/seating-proposals.ts
56533
- var commands48 = [
57141
+ var commands49 = [
56534
57142
  {
56535
57143
  spec: {
56536
57144
  path: ["seating-proposals", "list"],
@@ -56617,7 +57225,7 @@ var commands48 = [
56617
57225
  ];
56618
57226
 
56619
57227
  // src/commands/sensors.ts
56620
- var commands49 = [
57228
+ var commands50 = [
56621
57229
  {
56622
57230
  spec: {
56623
57231
  path: ["sensors", "status"],
@@ -56777,7 +57385,7 @@ var commands49 = [
56777
57385
  ];
56778
57386
 
56779
57387
  // src/commands/settings.ts
56780
- var commands50 = [
57388
+ var commands51 = [
56781
57389
  {
56782
57390
  spec: {
56783
57391
  path: ["settings", "get"],
@@ -56811,7 +57419,7 @@ var commands50 = [
56811
57419
  ];
56812
57420
 
56813
57421
  // src/commands/sharing.ts
56814
- var commands51 = [
57422
+ var commands52 = [
56815
57423
  {
56816
57424
  spec: {
56817
57425
  path: ["share"],
@@ -56944,7 +57552,7 @@ var commands51 = [
56944
57552
  ];
56945
57553
 
56946
57554
  // src/commands/tap-tags.ts
56947
- var commands52 = [
57555
+ var commands53 = [
56948
57556
  {
56949
57557
  spec: {
56950
57558
  path: ["tap-tags", "tap"],
@@ -56972,7 +57580,7 @@ var BY = {
56972
57580
  type: "string",
56973
57581
  description: "The blocking task's ref (DO-147) or ULID."
56974
57582
  };
56975
- var commands53 = [
57583
+ var commands54 = [
56976
57584
  defineCommand({
56977
57585
  operation: taskBlockOperations.addBlocker,
56978
57586
  path: ["tasks", "block"],
@@ -57038,7 +57646,7 @@ function renderSeries(data, mode) {
57038
57646
  }).join(`
57039
57647
  `);
57040
57648
  }
57041
- var commands54 = [
57649
+ var commands55 = [
57042
57650
  defineCommand({
57043
57651
  operation: taskSeriesOperations.make,
57044
57652
  path: ["tasks", "make-series"],
@@ -57193,7 +57801,7 @@ function repeatFromFlags(flags) {
57193
57801
  out.anchor = "from_completion";
57194
57802
  return out;
57195
57803
  }
57196
- var commands55 = [
57804
+ var commands56 = [
57197
57805
  {
57198
57806
  spec: {
57199
57807
  path: ["capture"],
@@ -57651,7 +58259,7 @@ var commands55 = [
57651
58259
  ];
57652
58260
 
57653
58261
  // src/commands/time-blocks.ts
57654
- var commands56 = [
58262
+ var commands57 = [
57655
58263
  {
57656
58264
  spec: {
57657
58265
  path: ["time-blocks", "record"],
@@ -57832,7 +58440,7 @@ function renderLandedCounts(data, mode) {
57832
58440
  return out.join(`
57833
58441
  `);
57834
58442
  }
57835
- var commands57 = [
58443
+ var commands58 = [
57836
58444
  {
57837
58445
  spec: {
57838
58446
  path: ["today"],
@@ -58204,7 +58812,7 @@ function renderViews(data, mode) {
58204
58812
  function renderView(data, mode) {
58205
58813
  return renderViews([data], mode);
58206
58814
  }
58207
- var commands58 = [
58815
+ var commands59 = [
58208
58816
  defineCommand({
58209
58817
  operation: workViewOperations.list,
58210
58818
  path: ["views", "list"],
@@ -58323,7 +58931,7 @@ var commands58 = [
58323
58931
  ];
58324
58932
 
58325
58933
  // src/commands/worktree-links.ts
58326
- var commands59 = [
58934
+ var commands60 = [
58327
58935
  {
58328
58936
  spec: {
58329
58937
  path: ["worktree-links", "list"],
@@ -58488,7 +59096,8 @@ var FEATURE_COMMANDS = [
58488
59096
  ...commands56,
58489
59097
  ...commands57,
58490
59098
  ...commands58,
58491
- ...commands59
59099
+ ...commands59,
59100
+ ...commands60
58492
59101
  ];
58493
59102
 
58494
59103
  // src/registry.ts