@robbeverhelst/do 1.174.0 → 1.175.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 +616 -36
  2. package/package.json +1 -1
package/dist/do.js CHANGED
@@ -3607,7 +3607,8 @@ var areas = pgTable("areas", {
3607
3607
  archivedAt: timestamp("archived_at", { withTimezone: true }),
3608
3608
  purpose: text("purpose"),
3609
3609
  standards: jsonb("standards").$type().notNull().default(sql`'[]'::jsonb`),
3610
- restingUntil: date("resting_until", { mode: "string" })
3610
+ restingUntil: date("resting_until", { mode: "string" }),
3611
+ lastReviewedAt: timestamp("last_reviewed_at", { withTimezone: true })
3611
3612
  }, (t) => [
3612
3613
  index("areas_tenant_idx").on(t.tenantId),
3613
3614
  check("areas_standards_chk", sql`jsonb_typeof(${t.standards}) = 'array' AND jsonb_array_length(${t.standards}) <= 5`)
@@ -3818,7 +3819,8 @@ var tasks = pgTable("tasks", {
3818
3819
  proposedDesk: text("proposed_desk"),
3819
3820
  notNowUntil: timestamp("not_now_until", { withTimezone: true }),
3820
3821
  readyAt: timestamp("ready_at", { withTimezone: true }),
3821
- readyNote: text("ready_note")
3822
+ readyNote: text("ready_note"),
3823
+ moveCount: integer("move_count").notNull().default(0)
3822
3824
  }, (t) => [
3823
3825
  index("tasks_tenant_project_idx").on(t.tenantId, t.projectId),
3824
3826
  index("tasks_tenant_place_idx").on(t.tenantId, t.placeId),
@@ -3917,7 +3919,8 @@ var notificationType = pgEnum("notification_type", [
3917
3919
  "streak_at_risk",
3918
3920
  "reflection_nudge",
3919
3921
  "shutdown_nudge",
3920
- "habit_reminder"
3922
+ "habit_reminder",
3923
+ "echo"
3921
3924
  ]);
3922
3925
  var notifications = pgTable("notifications", {
3923
3926
  id: text("id").primaryKey(),
@@ -18432,7 +18435,8 @@ var taskViewSchema = taskSchema.omit({
18432
18435
  source: true,
18433
18436
  completedByUserId: true,
18434
18437
  completedByAgentId: true,
18435
- filingSuggestion: true
18438
+ filingSuggestion: true,
18439
+ moveCount: true
18436
18440
  }).extend({
18437
18441
  title: string2(),
18438
18442
  parentId: ulidSchema.nullable(),
@@ -19221,21 +19225,28 @@ var areaStandardViewSchema = areaStandardSchema.extend({
19221
19225
  });
19222
19226
  var areaSchema = createSelectSchema(areas, {
19223
19227
  purpose: string2().nullable().default(null),
19224
- standards: array(areaStandardSchema).default([])
19228
+ standards: array(areaStandardSchema).default([]),
19229
+ lastReviewedAt: exports_coerce.date().nullable().default(null)
19225
19230
  });
19226
19231
  var areaIconSchema = string2().trim().min(1).max(64);
19227
19232
  var createAreaInputSchema = createInsertSchema(areas, {
19228
19233
  id: ulidSchema,
19229
19234
  icon: areaIconSchema.nullish(),
19230
19235
  purpose: areaPurposeSchema.nullish()
19231
- }).omit({ tenantId: true, archivedAt: true, standards: true, restingUntil: true }).extend({ title: string2().min(1) });
19236
+ }).omit({
19237
+ tenantId: true,
19238
+ archivedAt: true,
19239
+ standards: true,
19240
+ restingUntil: true,
19241
+ lastReviewedAt: true
19242
+ }).extend({ title: string2().min(1) });
19232
19243
  var updateAreaInputSchema = createUpdateSchema(areas, {
19233
19244
  icon: areaIconSchema.nullish(),
19234
19245
  archivedAt: exports_coerce.date().nullish(),
19235
19246
  purpose: areaPurposeSchema.nullish(),
19236
19247
  standards: array(areaStandardInputSchema).max(5).optional(),
19237
19248
  restingUntil: exports_iso.date().nullish()
19238
- }).omit({ id: true, tenantId: true }).extend({ title: string2().min(1).optional() }).strict();
19249
+ }).omit({ id: true, tenantId: true, lastReviewedAt: true }).extend({ title: string2().min(1).optional() }).strict();
19239
19250
  var areaViewSchema = areaSchema.extend({
19240
19251
  title: string2(),
19241
19252
  archivedAt: exports_coerce.date().nullable().default(null),
@@ -19292,6 +19303,24 @@ var areaOverviewSchema = object({
19292
19303
  owner: areaOwnerLensSchema,
19293
19304
  areas: array(areaOverviewItemSchema)
19294
19305
  });
19306
+ var areaLookItemSchema = object({
19307
+ id: string2(),
19308
+ title: string2(),
19309
+ by: string2().nullable().default(null),
19310
+ why: string2().nullable().default(null)
19311
+ });
19312
+ var areaSinceLastLookSchema = object({
19313
+ areaId: string2(),
19314
+ lastReviewedAt: exports_coerce.date().nullable(),
19315
+ since: exports_coerce.date(),
19316
+ landed: array(areaLookItemSchema),
19317
+ fresh: array(areaLookItemSchema),
19318
+ stuck: array(areaLookItemSchema)
19319
+ });
19320
+ var restAreaInputSchema = object({
19321
+ until: exports_iso.date(),
19322
+ habits: _enum2(["keep", "pause"]).default("keep")
19323
+ }).strict();
19295
19324
 
19296
19325
  // ../../packages/schema/src/presets/facets.ts
19297
19326
  var PANTRY_DISPLAY_KIND = "pantry";
@@ -19924,6 +19953,136 @@ var timeBlockRangeQuerySchema = object({
19924
19953
  path: ["to"]
19925
19954
  });
19926
19955
 
19956
+ // ../../packages/schema/src/contracts/plans.ts
19957
+ var PLAN_KINDS = ["week", "day"];
19958
+ var PLAN_ITEM_ACTIONS = ["plan", "someday", "let_go"];
19959
+ var PLAN_ITEM_STATES = ["proposed", "accepted", "dismissed"];
19960
+ var PLAN_STATUSES = ["open", "settled", "replaced"];
19961
+ var PLAN_MAX_ITEMS = 60;
19962
+ var PLAN_REASON_MAX = 280;
19963
+ var PLAN_NOTE_MAX = 2000;
19964
+ var planKindSchema = _enum2(PLAN_KINDS);
19965
+ var planItemActionSchema = _enum2(PLAN_ITEM_ACTIONS);
19966
+ var planItemStateSchema = _enum2(PLAN_ITEM_STATES);
19967
+ var planStatusSchema = _enum2(PLAN_STATUSES);
19968
+ var civilDaySchema = string2().regex(/^\d{4}-\d{2}-\d{2}$/, "expected YYYY-MM-DD").refine((s) => new Date(`${s}T00:00:00Z`).toISOString().slice(0, 10) === s, "not a real day");
19969
+ var DAY_MS = 24 * 60 * 60 * 1000;
19970
+ function addCivilDays(day, n) {
19971
+ return new Date(Date.parse(`${day}T00:00:00Z`) + n * DAY_MS).toISOString().slice(0, 10);
19972
+ }
19973
+ function planDays(kind, startsOn) {
19974
+ return kind === "week" ? Array.from({ length: 7 }, (_, i) => addCivilDays(startsOn, i)) : [startsOn];
19975
+ }
19976
+ function isMonday(day) {
19977
+ return new Date(`${day}T00:00:00Z`).getUTCDay() === 1;
19978
+ }
19979
+ var planItemInputSchema = object({
19980
+ taskId: ulidSchema,
19981
+ action: planItemActionSchema.default("plan"),
19982
+ day: civilDaySchema.optional(),
19983
+ at: exports_coerce.date().optional(),
19984
+ durationMinutes: number2().int().min(5).max(24 * 60).optional(),
19985
+ reason: string2().trim().min(1).max(PLAN_REASON_MAX).optional()
19986
+ }).strict().superRefine((v, ctx) => {
19987
+ if (v.action === "plan") {
19988
+ if (v.day === undefined === (v.at === undefined)) {
19989
+ ctx.addIssue({
19990
+ code: "custom",
19991
+ message: "a `plan` item takes exactly one of `day` or `at`",
19992
+ path: ["day"]
19993
+ });
19994
+ }
19995
+ if (v.durationMinutes !== undefined && v.at === undefined) {
19996
+ ctx.addIssue({
19997
+ code: "custom",
19998
+ message: "`durationMinutes` goes with `at` (a time)",
19999
+ path: ["durationMinutes"]
20000
+ });
20001
+ }
20002
+ } else if (v.day !== undefined || v.at !== undefined || v.durationMinutes !== undefined) {
20003
+ ctx.addIssue({
20004
+ code: "custom",
20005
+ message: `a \`${v.action}\` item takes no day, time or duration`,
20006
+ path: ["action"]
20007
+ });
20008
+ }
20009
+ });
20010
+ var createPlanInputSchema = object({
20011
+ kind: planKindSchema,
20012
+ startsOn: civilDaySchema,
20013
+ forUserId: string2().min(1).optional(),
20014
+ note: string2().trim().min(1).max(PLAN_NOTE_MAX).optional(),
20015
+ items: array(planItemInputSchema).min(1).max(PLAN_MAX_ITEMS)
20016
+ }).strict().superRefine((v, ctx) => {
20017
+ if (v.kind === "week" && !isMonday(v.startsOn)) {
20018
+ ctx.addIssue({
20019
+ code: "custom",
20020
+ message: "a week plan starts on a Monday",
20021
+ path: ["startsOn"]
20022
+ });
20023
+ }
20024
+ const days = new Set(planDays(v.kind, v.startsOn));
20025
+ const seen = new Set;
20026
+ v.items.forEach((item, i) => {
20027
+ if (seen.has(item.taskId)) {
20028
+ ctx.addIssue({
20029
+ code: "custom",
20030
+ message: "one item per task",
20031
+ path: ["items", i, "taskId"]
20032
+ });
20033
+ }
20034
+ seen.add(item.taskId);
20035
+ if (item.day !== undefined && !days.has(item.day)) {
20036
+ ctx.addIssue({
20037
+ code: "custom",
20038
+ message: `the day falls outside the ${v.kind}`,
20039
+ path: ["items", i, "day"]
20040
+ });
20041
+ }
20042
+ });
20043
+ });
20044
+ var planDecisionInputSchema = object({ itemIds: array(string2().min(1)).min(1).max(PLAN_MAX_ITEMS).optional() }).strict();
20045
+ var planListQuerySchema = object({
20046
+ kind: planKindSchema.optional(),
20047
+ status: planStatusSchema.optional(),
20048
+ startsOn: civilDaySchema.optional()
20049
+ });
20050
+ var planTaskSchema = object({
20051
+ id: ulidSchema,
20052
+ title: string2(),
20053
+ status: taskStatusSchema,
20054
+ scheduledAt: exports_coerce.date().nullable(),
20055
+ allDay: boolean3(),
20056
+ dueAt: exports_coerce.date().nullable(),
20057
+ durationMinutes: number2().int().nullable(),
20058
+ areaId: string2().nullable(),
20059
+ projectId: string2().nullable()
20060
+ });
20061
+ var planItemViewSchema = object({
20062
+ id: string2(),
20063
+ taskId: ulidSchema,
20064
+ action: planItemActionSchema,
20065
+ scheduledAt: exports_coerce.date().nullable(),
20066
+ allDay: boolean3(),
20067
+ durationMinutes: number2().int().nullable(),
20068
+ reason: string2().nullable(),
20069
+ state: planItemStateSchema,
20070
+ decidedAt: exports_coerce.date().nullable(),
20071
+ task: planTaskSchema
20072
+ });
20073
+ var planViewSchema = object({
20074
+ id: string2(),
20075
+ kind: planKindSchema,
20076
+ startsOn: civilDaySchema,
20077
+ userId: string2(),
20078
+ agentId: string2().nullable(),
20079
+ note: string2().nullable(),
20080
+ status: planStatusSchema,
20081
+ createdAt: exports_coerce.date(),
20082
+ updatedAt: exports_coerce.date(),
20083
+ items: array(planItemViewSchema)
20084
+ });
20085
+
19927
20086
  // ../../packages/schema/src/contracts/agent-chat.ts
19928
20087
  var agentConnectionSourceSchema = _enum2(["space", "env"]);
19929
20088
  var agentGatewayStatusSchema = _enum2(["connected", "offline", "not_connected"]);
@@ -20003,6 +20162,7 @@ var agentChangeSchema = object({
20003
20162
  changedAt: exports_coerce.date(),
20004
20163
  title: string2().nullable(),
20005
20164
  actor: actorRefSchema,
20165
+ cursor: string2().optional(),
20006
20166
  fields: array(agentChangeFieldSchema).optional(),
20007
20167
  checkIn: object({
20008
20168
  value: number2(),
@@ -20017,7 +20177,8 @@ var agentChangesPageSchema = object({
20017
20177
  var agentChangesQuerySchema = object({
20018
20178
  since: string2().refine((s) => s === "now" || /^\d+-\d+$/.test(s), {
20019
20179
  message: "since must be a cursor from a prior page's nextCursor, or 'now'"
20020
- })
20180
+ }),
20181
+ node: string2().trim().min(1).max(64).optional()
20021
20182
  });
20022
20183
  var agentConfirmationKindSchema = _enum2(["delete", "cancel", "google", "orca", "bulk"]);
20023
20184
  var agentConfirmationStatusSchema = _enum2([
@@ -20660,16 +20821,21 @@ var EVERY_WEEKDAYS_RE = /\bevery\s+(?:weekday|weekdays|work\s?day|work\s?days)\b
20660
20821
  var EVERY_WEEKDAY_RE = new RegExp(`\\bevery\\s+(${WEEKDAY_ALT})\\b`, "i");
20661
20822
  var EVERY_UNIT_RE = /\bevery\s+(day|week|month|year)\b/i;
20662
20823
  var ADVERB_RE = /\b(daily|weekly|monthly|yearly|annually)\b/i;
20663
- var RELATIVE_RE = /\b(today|tonight|tomorrow|tmrw|tmr)\b/i;
20824
+ var RELATIVE_RE = /\b(today|tonight|tomorrow|tmrw|tmr|yesterday)\b/i;
20664
20825
  var IN_N_RE = /\bin\s+(\d+)\s+(day|days|week|weeks|month|months)\b/i;
20665
20826
  var NEXT_UNIT_RE = /\bnext\s+(week|month|year)\b/i;
20666
20827
  var NEXT_WEEKDAY_RE = new RegExp(`\\bnext\\s+(${WEEKDAY_ALT})\\b`, "i");
20667
20828
  var THIS_WEEKDAY_RE = new RegExp(`\\bthis\\s+(${WEEKDAY_ALT})\\b`, "i");
20829
+ var LAST_WEEKDAY_RE = new RegExp(`\\blast\\s+(${WEEKDAY_ALT})\\b`, "i");
20668
20830
  var WEEKDAY_RE = new RegExp(`\\b(${WEEKDAY_ALT})\\b`, "i");
20669
20831
  var TIME_AMPM_RE = /\b(?:at\s+)?(\d{1,2})(?::(\d{2}))?\s*(am|pm)\b/i;
20670
20832
  var TIME_24H_RE = /\b(?:at\s+)?([01]?\d|2[0-3]):([0-5]\d)\b/;
20671
20833
  var NOON_RE = /\b(noon|midday)\b/i;
20672
20834
  var MIDNIGHT_RE = /\bmidnight\b/i;
20835
+ var DEADLINE_KEYWORD_RE = /\b(?:by|due(?:\s+on)?)\s+/gi;
20836
+ var ESTIMATE_HM_RE = /(?<!\bin\s+)\b(?:for\s+)?(\d{1,2})\s?h(?:ours?|rs?)?\s?(\d{1,2})\s?(?:m|mins?|minutes?)?\b/i;
20837
+ var ESTIMATE_H_RE = /(?<!\bin\s+)\b(?:for\s+)?(\d{1,2}(?:[.,]\d)?)\s?(?:h|hrs?|hours?)\b/i;
20838
+ var ESTIMATE_M_RE = /(?<!\bin\s+)\b(?:for\s+)?(\d{1,3})\s?(?:m|mins?|minutes?)\b/i;
20673
20839
  function blank(s, start, end) {
20674
20840
  return s.slice(0, start) + " ".repeat(end - start) + s.slice(end);
20675
20841
  }
@@ -20705,18 +20871,84 @@ function parseWhen(input, now) {
20705
20871
  scan = blank(scan, start, end);
20706
20872
  return { span, index: start };
20707
20873
  };
20874
+ const deadline = takeDeadline(scan, now);
20875
+ if (deadline) {
20876
+ spans.push(deadline.span);
20877
+ scan = blank(scan, deadline.span.start, deadline.span.end);
20878
+ }
20708
20879
  const recurrence = takeRecurrence(scan, take);
20709
20880
  const date = takeDate(scan, take, now);
20710
20881
  const time = takeTime(scan, take);
20882
+ const durationMinutes = takeEstimate(scan, take);
20711
20883
  const { scheduledAt, allDay } = resolveWhen({ now, recurrence, date, time });
20712
20884
  spans.sort((a, b) => a.start - b.start);
20713
20885
  return {
20714
20886
  ...scheduledAt ? { scheduledAt } : {},
20715
20887
  allDay,
20716
20888
  ...recurrence ? { recurrence } : {},
20889
+ ...deadline ? { dueAt: deadline.at, dueAllDay: deadline.allDay } : {},
20890
+ ...durationMinutes ? { durationMinutes } : {},
20717
20891
  spans
20718
20892
  };
20719
20893
  }
20894
+ function anchored(re) {
20895
+ return new RegExp(`^${re.source.replace(/^\\b/, "")}`, re.flags.replace("g", ""));
20896
+ }
20897
+ var DEADLINE_DATES = [
20898
+ IN_N_RE,
20899
+ NEXT_WEEKDAY_RE,
20900
+ THIS_WEEKDAY_RE,
20901
+ NEXT_UNIT_RE,
20902
+ RELATIVE_RE,
20903
+ WEEKDAY_RE
20904
+ ].map(anchored);
20905
+ var DEADLINE_TIMES = [TIME_AMPM_RE, TIME_24H_RE, NOON_RE, MIDNIGHT_RE].map(anchored);
20906
+ var accept = (m) => m?.index === undefined ? null : { span: { start: 0, end: 0, kind: "deadline" }, index: m.index };
20907
+ function firstMatch(text, res) {
20908
+ for (const re of res) {
20909
+ const m = re.exec(text);
20910
+ if (m)
20911
+ return m[0];
20912
+ }
20913
+ return null;
20914
+ }
20915
+ function takeDeadline(scan, now) {
20916
+ for (const keyword of scan.matchAll(DEADLINE_KEYWORD_RE)) {
20917
+ const start = keyword.index ?? 0;
20918
+ let end = start + keyword[0].length;
20919
+ const dateText = firstMatch(scan.slice(end), DEADLINE_DATES);
20920
+ const date = dateText ? takeDate(dateText, accept, now) : null;
20921
+ if (dateText && date)
20922
+ end += dateText.length;
20923
+ const gap = /^\s*/.exec(scan.slice(end))?.[0] ?? "";
20924
+ const timeText = firstMatch(scan.slice(end + gap.length), DEADLINE_TIMES);
20925
+ const time = timeText ? takeTime(timeText, accept) : null;
20926
+ if (timeText && time)
20927
+ end += gap.length + timeText.length;
20928
+ if (!date && !time)
20929
+ continue;
20930
+ const day = date?.day ?? startOfDay(now);
20931
+ const span = { start, end, kind: "deadline" };
20932
+ if (!time)
20933
+ return { at: day, allDay: true, span };
20934
+ const at = new Date(day);
20935
+ at.setHours(time.hours, time.minutes, 0, 0);
20936
+ return { at, allDay: false, span };
20937
+ }
20938
+ return null;
20939
+ }
20940
+ function takeEstimate(scan, take) {
20941
+ const hm = scan.match(ESTIMATE_HM_RE);
20942
+ if (hm && take(hm, "estimate"))
20943
+ return Number(hm[1]) * 60 + Number(hm[2]);
20944
+ const h = scan.match(ESTIMATE_H_RE);
20945
+ if (h && take(h, "estimate"))
20946
+ return Math.round(Number((h[1] ?? "0").replace(",", ".")) * 60);
20947
+ const m = scan.match(ESTIMATE_M_RE);
20948
+ if (m && take(m, "estimate"))
20949
+ return Number(m[1]);
20950
+ return;
20951
+ }
20720
20952
  function takeRecurrence(scan, take) {
20721
20953
  const everyN = scan.match(EVERY_N_RE);
20722
20954
  if (everyN && take(everyN, "recurrence")) {
@@ -20781,11 +21013,18 @@ function takeDate(scan, take, now) {
20781
21013
  return on(addMonths(today, 1));
20782
21014
  return on(addMonths(today, 12));
20783
21015
  }
21016
+ const lastWeekday = scan.match(LAST_WEEKDAY_RE);
21017
+ if (lastWeekday && take(lastWeekday, "date")) {
21018
+ const { dow } = weekdayByName(lastWeekday[1]);
21019
+ return on(addDays(today, -((now.getDay() - dow + 7) % 7 || 7)));
21020
+ }
20784
21021
  const relative = scan.match(RELATIVE_RE);
20785
21022
  if (relative && take(relative, "date")) {
20786
21023
  const word = (relative[1] ?? "").toLowerCase();
20787
21024
  if (word === "tonight")
20788
21025
  return on(today, true);
21026
+ if (word === "yesterday")
21027
+ return on(addDays(today, -1));
20789
21028
  return on(word === "today" ? today : addDays(today, 1));
20790
21029
  }
20791
21030
  const weekday = scan.match(WEEKDAY_RE);
@@ -20892,6 +21131,8 @@ function parseCapture(input, now) {
20892
21131
  ...starred ? { starred } : {},
20893
21132
  ...area ? { area } : {},
20894
21133
  ...when?.scheduledAt ? { scheduledAt: when.scheduledAt, allDay: when.allDay } : {},
21134
+ ...when?.dueAt ? { dueAt: when.dueAt, ...when.scheduledAt ? {} : { allDay: when.dueAllDay } } : {},
21135
+ ...when?.durationMinutes ? { durationMinutes: when.durationMinutes } : {},
20895
21136
  ...when?.recurrence ? { recurrence: when.recurrence } : {}
20896
21137
  };
20897
21138
  }
@@ -21351,7 +21592,7 @@ function parseCapture2(input, now) {
21351
21592
 
21352
21593
  // src/meta.ts
21353
21594
  var CLI_NAME = "do";
21354
- var VERSION = "1.174.0";
21595
+ var VERSION = "1.175.0";
21355
21596
  var API_PREFIX = "/api/v1";
21356
21597
  var DEFAULT_API_URL = "http://localhost:3001";
21357
21598
 
@@ -21605,6 +21846,11 @@ var ENTITIES = [
21605
21846
  name: "ready",
21606
21847
  description: "only open tasks their Owner marked Ready for you; --ready=false for the rest",
21607
21848
  boolean: true
21849
+ },
21850
+ {
21851
+ name: "drifted",
21852
+ description: "only open tasks whose planned day moved 3+ times; --drifted=false for the rest",
21853
+ boolean: true
21608
21854
  }
21609
21855
  ],
21610
21856
  write: {
@@ -21933,15 +22179,35 @@ var COMMAND_SPECS = [
21933
22179
  {
21934
22180
  name: "text",
21935
22181
  required: true,
21936
- description: "Task title; may embed #tag, ! (star) and ~area."
22182
+ description: "Task title; may embed #tag, ! (star), ~area, a when, a deadline and an estimate (see grammar)."
22183
+ }
22184
+ ],
22185
+ flags: [
22186
+ {
22187
+ name: "at",
22188
+ type: "string",
22189
+ description: "Plan it at this ISO date/time (overrides a when in the text). With a time ('2026-09-09T16:00:00+02:00') it is a timed task (allDay false) that shows on the calendar; a date only ('2026-09-09') is all-day."
22190
+ },
22191
+ {
22192
+ name: "due",
22193
+ type: "string",
22194
+ description: "The Deadline, ISO date/time (overrides a by/due phrase in the text)."
22195
+ },
22196
+ {
22197
+ name: "duration",
22198
+ type: "string",
22199
+ description: "How long it takes, in minutes (overrides an estimate in the text)."
21937
22200
  }
21938
22201
  ],
21939
- flags: [],
21940
22202
  scopes: ["tasks:write"],
21941
22203
  returns: "Task",
21942
22204
  grammar: {
21943
22205
  "#tag": "Adds a tag. Repeatable: '#home #urgent'.",
21944
- "!": "Stars the task \u2014 the 1\u20132 things that matter (aliases: !star/!s/!flag/!f). Drives Today's focus and the starred filter."
22206
+ "!": "Stars the task \u2014 the 1\u20132 things that matter (aliases: !star/!s/!flag/!f). Drives Today's focus and the starred filter.",
22207
+ "~area": "Files it into an existing Area by its title, case-insensitive: '~garden' for 'Garden'. One word (letters, digits, - and _), so an Area named with spaces can't be named here \u2014 file it after with `do tasks update <id> --data '{\"areaId\":\"<id>\"}'`. An unknown name files nothing (never creates an Area); the first ~area wins. Valid names: `do areas list --json` (their `title`).",
22208
+ when: "The planned day and time: 'today', 'tomorrow 3pm', 'fri', 'next monday', 'in 3 days', 'at 16:00', 'tonight' (18:00); the past for back-filling: 'yesterday', 'last friday'. A time makes it a timed task (allDay false). 'every monday' / 'every 2 weeks' / 'daily' attaches a recurrence.",
22209
+ "by / due": "The Deadline, never the plan: 'by friday', 'due fri', 'due tomorrow 5pm', 'by 17:00' (today). 'by' with no date after it ('pay by card') stays in the title.",
22210
+ estimate: "How long it takes (durationMinutes): '30m', '45 min', '1h', '1.5h', '1h30', '2 hours', 'for 20 mins'. Not 'in 2 hours'."
21945
22211
  }
21946
22212
  },
21947
22213
  {
@@ -22121,6 +22387,84 @@ var COMMAND_SPECS = [
22121
22387
  returns: "Task",
22122
22388
  notes: "POST /api/v1/tasks/{id}/not-now (#889); 409 when the task isn't a proposal. Let go is `do task cancel`."
22123
22389
  },
22390
+ {
22391
+ path: ["plans", "list"],
22392
+ summary: "Plan proposals: the weeks and days the agent proposed, newest first, each item with its answer.",
22393
+ args: [],
22394
+ flags: [
22395
+ { name: "kind", type: "string", description: "week or day." },
22396
+ { name: "status", type: "string", description: "open, settled or replaced." },
22397
+ {
22398
+ name: "starts-on",
22399
+ type: "string",
22400
+ description: "The week's Monday, or the day (YYYY-MM-DD)."
22401
+ },
22402
+ { name: "for", type: "string", description: "Only this member's plans (user id)." }
22403
+ ],
22404
+ scopes: ["tasks:read"],
22405
+ returns: "Plan[]",
22406
+ notes: "GET /api/v1/plans (#912): the latest 20."
22407
+ },
22408
+ {
22409
+ path: ["plans", "get"],
22410
+ summary: "One plan proposal with its items and each item's task as it is now.",
22411
+ args: [{ name: "id", required: true, description: "Plan id." }],
22412
+ flags: [],
22413
+ scopes: ["tasks:read"],
22414
+ returns: "Plan",
22415
+ notes: "GET /api/v1/plans/{id} (#912)."
22416
+ },
22417
+ {
22418
+ path: ["plans", "propose"],
22419
+ summary: "Propose a week (or a day) for a member: plan tasks on a day or at a time, back to Someday, or Let go. Nothing changes until the member accepts.",
22420
+ args: [],
22421
+ flags: [
22422
+ {
22423
+ name: "data",
22424
+ type: "string",
22425
+ description: 'The CreatePlanInput as JSON: {"kind":"week","startsOn":"2026-10-19","note":"\u2026","items":[{"taskId":"\u2026","day":"2026-10-20","reason":"Deadline Friday"},{"taskId":"\u2026","at":"2026-10-21T09:00:00Z","durationMinutes":45},{"taskId":"\u2026","action":"someday"},{"taskId":"\u2026","action":"let_go"}]}.'
22426
+ }
22427
+ ],
22428
+ scopes: ["tasks:write"],
22429
+ returns: "Plan",
22430
+ notes: "POST /api/v1/plans (#912). A week starts on a Monday; days are the member's, times are ISO instants inside the week. `forUserId` names the member (default: the member the key acts for). A newer plan for the same week or day replaces the open one."
22431
+ },
22432
+ {
22433
+ path: ["plans", "accept"],
22434
+ summary: "Accept a plan's proposed items (all of them, or the item ids given): each task is written as the item says.",
22435
+ args: [
22436
+ { name: "id", required: true, description: "Plan id." },
22437
+ { name: "items", required: false, description: "Item ids (default: every proposed item)." }
22438
+ ],
22439
+ flags: [],
22440
+ scopes: ["tasks:write"],
22441
+ returns: "Plan",
22442
+ notes: "POST /api/v1/plans/{id}/accept (#912). Undo with `do plans undo`."
22443
+ },
22444
+ {
22445
+ path: ["plans", "dismiss"],
22446
+ summary: "Leave a plan's proposed items out (all of them, or the item ids given).",
22447
+ args: [
22448
+ { name: "id", required: true, description: "Plan id." },
22449
+ { name: "items", required: false, description: "Item ids (default: every proposed item)." }
22450
+ ],
22451
+ flags: [],
22452
+ scopes: ["tasks:write"],
22453
+ returns: "Plan",
22454
+ notes: "POST /api/v1/plans/{id}/dismiss (#912). Nothing is written to a task."
22455
+ },
22456
+ {
22457
+ path: ["plans", "undo"],
22458
+ summary: "Undo a plan's answers (all of them, or the item ids given): accepted tasks go back as they were.",
22459
+ args: [
22460
+ { name: "id", required: true, description: "Plan id." },
22461
+ { name: "items", required: false, description: "Item ids (default: every answered item)." }
22462
+ ],
22463
+ flags: [],
22464
+ scopes: ["tasks:write"],
22465
+ returns: "Plan",
22466
+ notes: "POST /api/v1/plans/{id}/undo (#912). The items read as proposed again."
22467
+ },
22124
22468
  {
22125
22469
  path: ["tasks", "ready"],
22126
22470
  summary: "Mark a task Ready for you: its Owner finished something the member should look at (a draft, a link, a worktree).",
@@ -22150,7 +22494,35 @@ var COMMAND_SPECS = [
22150
22494
  args: [{ name: "id", required: true, description: "Task ULID." }],
22151
22495
  flags: [
22152
22496
  { name: "days", type: "string", description: "Days from now to reschedule.", default: "1" },
22153
- { name: "until", type: "string", description: "Absolute date/time (ISO) to reschedule to." }
22497
+ {
22498
+ name: "until",
22499
+ type: "string",
22500
+ description: "Absolute date/time (ISO) to reschedule to. With a time it becomes a timed task (allDay false); a date only stays all-day."
22501
+ },
22502
+ {
22503
+ name: "duration",
22504
+ type: "string",
22505
+ description: "How long it takes, in minutes (sets durationMinutes)."
22506
+ }
22507
+ ],
22508
+ scopes: ["tasks:write"],
22509
+ returns: "Task"
22510
+ },
22511
+ {
22512
+ path: ["task", "schedule"],
22513
+ summary: "Plan a task at a day or a time, so a timed one shows on the calendar.",
22514
+ args: [{ name: "id", required: true, description: "Task ULID." }],
22515
+ flags: [
22516
+ {
22517
+ name: "at",
22518
+ type: "string",
22519
+ description: "Required. ISO date/time. With a time ('2026-09-09T16:00:00+02:00') the task is timed (allDay false); a date only ('2026-09-09') is all-day."
22520
+ },
22521
+ {
22522
+ name: "duration",
22523
+ type: "string",
22524
+ description: "How long it takes, in minutes (sets durationMinutes)."
22525
+ }
22154
22526
  ],
22155
22527
  scopes: ["tasks:write"],
22156
22528
  returns: "Task"
@@ -22750,6 +23122,22 @@ var COMMAND_SPECS = [
22750
23122
  returns: "EchoThread",
22751
23123
  notes: "When the current thread is still empty, that one is returned instead."
22752
23124
  },
23125
+ {
23126
+ path: ["echo", "threads", "push"],
23127
+ summary: "Set what one of your agent threads may push to your devices.",
23128
+ args: [
23129
+ { name: "id", required: true, description: "Thread ULID (see `echo threads list`)." },
23130
+ {
23131
+ name: "setting",
23132
+ required: true,
23133
+ description: "all (Needs you, and Told quietly), needs_you (only what waits on you) or off."
23134
+ }
23135
+ ],
23136
+ flags: [],
23137
+ scopes: ["agents:write"],
23138
+ returns: "EchoThreadSummary",
23139
+ notes: "For later and Silent posts never push. The home thread and conversations start at all, a standing thread you add at needs_you."
23140
+ },
22753
23141
  {
22754
23142
  path: ["settings", "get"],
22755
23143
  summary: "Show the caller's settings for the Space.",
@@ -23062,12 +23450,17 @@ var COMMAND_SPECS = [
23062
23450
  {
23063
23451
  name: "until",
23064
23452
  type: "string",
23065
- description: "The day it's back (YYYY-MM-DD). Required."
23453
+ description: "The day it's back (YYYY-MM-DD, after today). Required."
23454
+ },
23455
+ {
23456
+ name: "pause-habits",
23457
+ type: "boolean",
23458
+ description: "Pause the Area's habits until then too (they keep going otherwise)."
23066
23459
  }
23067
23460
  ],
23068
23461
  scopes: ["areas:write"],
23069
23462
  returns: "AreaView",
23070
- notes: "PATCH /api/v1/areas/{id} { restingUntil }. Between active and archived: its attention reads Resting, back in that month. Bring it back early with `do areas back`."
23463
+ notes: "POST /api/v1/areas/{id}/rest { until, habits } (Areas 8). Between active and archived: its attention reads Resting, Focus, Review and filing leave it alone, and its recurring tasks pause. Bring it back early with `do areas back`."
23071
23464
  },
23072
23465
  {
23073
23466
  path: ["areas", "back"],
@@ -23076,7 +23469,25 @@ var COMMAND_SPECS = [
23076
23469
  flags: [],
23077
23470
  scopes: ["areas:write"],
23078
23471
  returns: "AreaView",
23079
- notes: "PATCH /api/v1/areas/{id} { restingUntil: null }. Nothing was lost while it rested."
23472
+ notes: "POST /api/v1/areas/{id}/back (Areas 8). The habits it paused come back too, and the recurring tasks' occurrences that went by while it rested are skipped. Nothing was lost."
23473
+ },
23474
+ {
23475
+ path: ["areas", "since"],
23476
+ summary: "What happened in an Area since you last looked: landed, new, and what seems stuck.",
23477
+ args: [{ name: "id", required: true, description: "Area ULID." }],
23478
+ flags: [],
23479
+ scopes: ["areas:read"],
23480
+ returns: "AreaSinceLastLook",
23481
+ notes: 'GET /api/v1/areas/{id}/since-last-look (Areas 6). A few of each, named, never tallied; stuck ones say why in words ("Deadline was 12 October").'
23482
+ },
23483
+ {
23484
+ path: ["areas", "looked-at"],
23485
+ summary: "Record a look at an Area: its review rhythm starts again from now.",
23486
+ args: [{ name: "id", required: true, description: "Area ULID." }],
23487
+ flags: [],
23488
+ scopes: ["areas:write"],
23489
+ returns: "AreaView",
23490
+ notes: "POST /api/v1/areas/{id}/looked-at (Areas 6). The Area leaves Review until its rhythm (weekly, monthly, quarterly) comes round."
23080
23491
  },
23081
23492
  {
23082
23493
  path: ["areas", "focus"],
@@ -23797,18 +24208,52 @@ function renderToday(data, mode) {
23797
24208
  ${habits.join(`
23798
24209
  `)}` : tasks;
23799
24210
  }
23800
- var DAY_MS = 24 * 60 * 60 * 1000;
24211
+ var DAY_MS2 = 24 * 60 * 60 * 1000;
23801
24212
  function plannedDay(at, from) {
23802
24213
  if (!at)
23803
24214
  return "earlier";
23804
24215
  const day = new Date(at);
23805
- const ago = from ? Math.round((Date.parse(from) - day.getTime()) / DAY_MS) : 7;
24216
+ const ago = from ? Math.round((Date.parse(from) - day.getTime()) / DAY_MS2) : 7;
23806
24217
  if (ago <= 1)
23807
24218
  return "yesterday";
23808
24219
  if (ago < 7)
23809
24220
  return day.toLocaleDateString("en-GB", { weekday: "long" });
23810
24221
  return day.toLocaleDateString("en-GB", { day: "numeric", month: "short" });
23811
24222
  }
24223
+ function planItemWords(i) {
24224
+ if (i.action === "someday")
24225
+ return "back to Someday";
24226
+ if (i.action === "let_go")
24227
+ return "let go";
24228
+ if (!i.scheduledAt)
24229
+ return "planned";
24230
+ const at = new Date(i.scheduledAt);
24231
+ const day = at.toLocaleDateString("en-GB", { weekday: "short", day: "numeric", month: "short" });
24232
+ return i.allDay ? day : `${day} ${at.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" })}`;
24233
+ }
24234
+ function renderPlan(data, mode) {
24235
+ const p = data;
24236
+ const head = `${p.kind === "day" ? "Day" : "Week"} of ${p.startsOn ?? "?"} \xB7 ${p.status ?? "?"}`;
24237
+ const items = (p.items ?? []).map((i) => {
24238
+ const state = i.state === "proposed" ? "\xB7" : i.state === "accepted" ? "\u2713" : "\u2013";
24239
+ const reason = i.reason ? paint(mode, "dim", ` \u2014 ${i.reason}`) : "";
24240
+ return ` ${state} ${i.task?.title ?? "(untitled)"}: ${planItemWords(i)}${reason} ${paint(mode, "dim", i.id)}`;
24241
+ });
24242
+ return [
24243
+ `${paint(mode, "bold", head)} ${paint(mode, "dim", p.id)}`,
24244
+ ...p.note ? [paint(mode, "dim", p.note)] : [],
24245
+ ...items
24246
+ ].join(`
24247
+ `);
24248
+ }
24249
+ function renderPlans(data, mode) {
24250
+ const plans = data ?? [];
24251
+ if (plans.length === 0)
24252
+ return paint(mode, "dim", "No plans.");
24253
+ return plans.map((p) => renderPlan(p, mode)).join(`
24254
+
24255
+ `);
24256
+ }
23812
24257
  function renderFromEarlier(data, mode) {
23813
24258
  const d = data;
23814
24259
  const tasks = d.tasks ?? [];
@@ -23848,6 +24293,28 @@ function renderAreaOverview(data, mode) {
23848
24293
  return out.join(`
23849
24294
  `);
23850
24295
  }
24296
+ function renderSinceLastLook(data, mode) {
24297
+ const d = data;
24298
+ const day = new Date(d.since).toLocaleDateString("en-GB", { day: "numeric", month: "long" });
24299
+ const out = [
24300
+ paint(mode, "bold", d.lastReviewedAt ? `Since you last looked, on ${day}` : `Since ${day} (never looked at)`)
24301
+ ];
24302
+ const group = (label, items, line) => {
24303
+ if (items.length === 0)
24304
+ return;
24305
+ out.push(` ${label}`);
24306
+ for (const i of items)
24307
+ out.push(` \xB7 ${line(i)}`);
24308
+ };
24309
+ group("Landed", d.landed, (i) => i.by ? `${i.title} (${i.by})` : i.title);
24310
+ group("New", d.fresh, (i) => i.title);
24311
+ group("Stuck?", d.stuck, (i) => i.why ? `${i.title} ${paint(mode, "dim", `\xB7 ${i.why}`)}` : i.title);
24312
+ if (d.landed.length + d.fresh.length + d.stuck.length === 0) {
24313
+ out.push(paint(mode, "dim", " Nothing landed, new or stuck."));
24314
+ }
24315
+ return out.join(`
24316
+ `);
24317
+ }
23851
24318
  function renderFromEarlierAnswer(data, mode) {
23852
24319
  const d = data;
23853
24320
  const n = d.tasks ?? 0;
@@ -24279,9 +24746,24 @@ function renderProgress(data, mode) {
24279
24746
  }
24280
24747
  const areas = p.areas ?? [];
24281
24748
  if (areas.length > 0) {
24282
- out.push("", paint(mode, "bold", "Areas"));
24283
- for (const a of areas)
24284
- out.push(` ${a.name}: ${a.tasksDone}`);
24749
+ out.push("", paint(mode, "bold", "Where your attention went"));
24750
+ if (p.areasHighlight)
24751
+ out.push(` ${p.areasHighlight}`);
24752
+ for (const a of areas) {
24753
+ const total = a.total ?? a.tasksDone;
24754
+ if (a.total === undefined) {
24755
+ out.push(` ${a.name}: ${total}`);
24756
+ continue;
24757
+ }
24758
+ const mine = (a.tasksDoneMine ?? 0) + (a.habitsDone ?? 0) + (a.blocksDone ?? 0);
24759
+ const parts = [
24760
+ `${a.tasksDone} task(s)`,
24761
+ `${a.habitsDone ?? 0} check-in(s)`,
24762
+ `${a.blocksDone ?? 0} block(s)`
24763
+ ].join(", ");
24764
+ const split = a.tasksDoneAgents ? ` \xB7 you ${mine}, agents ${a.tasksDoneAgents}` : "";
24765
+ out.push(` ${a.name}: ${total}${split} ${paint(mode, "dim", `(${parts}; ${a.previousTotal ?? 0} before)`)}`);
24766
+ }
24285
24767
  }
24286
24768
  const habits = p.habits ?? [];
24287
24769
  if (habits.length > 0) {
@@ -24587,11 +25069,17 @@ function runFixed(spec, ctx) {
24587
25069
  return handler(ctx);
24588
25070
  }
24589
25071
  var HANDLERS = {
24590
- capture: async ({ args, io, requireClient }) => {
25072
+ capture: async ({ args, flags, io, requireClient }) => {
24591
25073
  const text = args.join(" ").trim();
24592
25074
  if (text.length === 0)
24593
25075
  throw usageError('capture needs some text, e.g. `do capture "call mum #family"`');
24594
25076
  const parsed = parseCapture2(text, io.now());
25077
+ const at = flagInstant(flags, "at");
25078
+ const due = flagInstant(flags, "due");
25079
+ const duration = flagMinutes(flags, "duration") ?? parsed.durationMinutes;
25080
+ const scheduledAt = at?.at ?? parsed.scheduledAt;
25081
+ const dueAt = due?.at ?? parsed.dueAt;
25082
+ const allDay = at ? at.allDay : parsed.scheduledAt ? parsed.allDay : due?.allDay ?? parsed.allDay;
24595
25083
  const id = ulid3();
24596
25084
  const input = parseInput(createTaskInputSchema, {
24597
25085
  id,
@@ -24599,7 +25087,10 @@ var HANDLERS = {
24599
25087
  tags: parsed.tags,
24600
25088
  ...parsed.starred ? { starred: parsed.starred } : {},
24601
25089
  ...parsed.area ? { areaName: parsed.area } : {},
24602
- ...parsed.scheduledAt ? { scheduledAt: parsed.scheduledAt, allDay: parsed.allDay } : {}
25090
+ ...scheduledAt ? { scheduledAt } : {},
25091
+ ...dueAt ? { dueAt } : {},
25092
+ ...scheduledAt || dueAt ? { allDay } : {},
25093
+ ...duration ? { durationMinutes: duration } : {}
24603
25094
  });
24604
25095
  const client = requireClient();
24605
25096
  const task = await client.post("/tasks", input);
@@ -24909,6 +25400,30 @@ var HANDLERS = {
24909
25400
  const id = requireArg(args, 0, "task id");
24910
25401
  return { data: await requireClient().post(`/tasks/${id}/not-now`, {}), render: renderTask };
24911
25402
  },
25403
+ "plans list": async ({ flags, requireClient }) => {
25404
+ const plans = await requireClient().get("/plans", {
25405
+ query: {
25406
+ kind: flagString(flags, "kind"),
25407
+ status: flagString(flags, "status"),
25408
+ startsOn: flagString(flags, "starts-on"),
25409
+ forUserId: flagString(flags, "for")
25410
+ }
25411
+ });
25412
+ return { data: plans, render: renderPlans };
25413
+ },
25414
+ "plans get": async ({ args, requireClient }) => {
25415
+ const id = requireArg(args, 0, "plan id");
25416
+ return { data: await requireClient().get(`/plans/${id}`), render: renderPlan };
25417
+ },
25418
+ "plans propose": async ({ flags, requireClient }) => {
25419
+ if (flagString(flags, "data") === undefined)
25420
+ throw usageError("plans propose needs --data");
25421
+ const body = parseInput(createPlanInputSchema, readDataFlag(flags));
25422
+ return { data: await requireClient().post("/plans", body), render: renderPlan };
25423
+ },
25424
+ "plans accept": async (ctx) => planDecision(ctx, "accept"),
25425
+ "plans dismiss": async (ctx) => planDecision(ctx, "dismiss"),
25426
+ "plans undo": async (ctx) => planDecision(ctx, "undo"),
24912
25427
  "tasks ready": async ({ args, flags, requireClient }) => {
24913
25428
  const id = requireArg(args, 0, "task id");
24914
25429
  const note = flagString(flags, "note");
@@ -24923,7 +25438,26 @@ var HANDLERS = {
24923
25438
  "task snooze": async ({ args, flags, io, requireClient }) => {
24924
25439
  const id = requireArg(args, 0, "task id");
24925
25440
  const target = computeSnoozeTarget(flags, io.now());
24926
- const body = parseInput(updateTaskInputSchema, { scheduledAt: target });
25441
+ const until = flagInstant(flags, "until");
25442
+ const duration = flagMinutes(flags, "duration");
25443
+ const body = parseInput(updateTaskInputSchema, {
25444
+ scheduledAt: target,
25445
+ ...until ? { allDay: until.allDay } : {},
25446
+ ...duration ? { durationMinutes: duration } : {}
25447
+ });
25448
+ return { data: await requireClient().patch(`/tasks/${id}`, body), render: renderTask };
25449
+ },
25450
+ "task schedule": async ({ args, flags, requireClient }) => {
25451
+ const id = requireArg(args, 0, "task id");
25452
+ const at = flagInstant(flags, "at");
25453
+ if (!at)
25454
+ throw usageError("task schedule needs --at, e.g. --at '2026-09-09T16:00:00+02:00'");
25455
+ const duration = flagMinutes(flags, "duration");
25456
+ const body = parseInput(updateTaskInputSchema, {
25457
+ scheduledAt: at.at,
25458
+ allDay: at.allDay,
25459
+ ...duration ? { durationMinutes: duration } : {}
25460
+ });
24927
25461
  return { data: await requireClient().patch(`/tasks/${id}`, body), render: renderTask };
24928
25462
  },
24929
25463
  "habit log": async ({ args, flags, requireClient }) => {
@@ -25155,6 +25689,12 @@ var HANDLERS = {
25155
25689
  data: await requireClient().post("/echo/threads", {}),
25156
25690
  render: renderEchoThread
25157
25691
  }),
25692
+ "echo threads push": async ({ args, requireClient }) => ({
25693
+ data: await requireClient().patch(`/echo/threads/${requireArg(args, 0, "thread id")}`, {
25694
+ push: requireArg(args, 1, "push setting (needs_you, all or off)")
25695
+ }),
25696
+ render: renderRecord
25697
+ }),
25158
25698
  "settings get": async ({ requireClient }) => ({
25159
25699
  data: await requireClient().get("/settings"),
25160
25700
  render: renderRecord
@@ -25356,13 +25896,32 @@ var HANDLERS = {
25356
25896
  const until = flagString(flags, "until");
25357
25897
  if (!until)
25358
25898
  throw usageError("areas rest needs --until <YYYY-MM-DD>, the day it's back");
25359
- const input = parseInput(updateAreaInputSchema, { restingUntil: until });
25360
- return { data: await requireClient().patch(`/areas/${id}`, input), render: renderRecord };
25899
+ const input = parseInput(restAreaInputSchema, {
25900
+ until,
25901
+ habits: flagBool(flags, "pause-habits") ? "pause" : "keep"
25902
+ });
25903
+ return { data: await requireClient().post(`/areas/${id}/rest`, input), render: renderRecord };
25361
25904
  },
25362
25905
  "areas back": async ({ args, requireClient }) => {
25363
25906
  const id = requireArg(args, 0, "area id");
25364
- const input = parseInput(updateAreaInputSchema, { restingUntil: null });
25365
- return { data: await requireClient().patch(`/areas/${id}`, input), render: renderRecord };
25907
+ return {
25908
+ data: await requireClient().post(`/areas/${id}/back`, undefined),
25909
+ render: renderRecord
25910
+ };
25911
+ },
25912
+ "areas since": async ({ args, requireClient }) => {
25913
+ const id = requireArg(args, 0, "area id");
25914
+ return {
25915
+ data: await requireClient().get(`/areas/${id}/since-last-look`),
25916
+ render: renderSinceLastLook
25917
+ };
25918
+ },
25919
+ "areas looked-at": async ({ args, requireClient }) => {
25920
+ const id = requireArg(args, 0, "area id");
25921
+ return {
25922
+ data: await requireClient().post(`/areas/${id}/looked-at`, undefined),
25923
+ render: renderRecord
25924
+ };
25366
25925
  },
25367
25926
  "areas focus": async ({ args, flags, requireClient }) => {
25368
25927
  const off = flagBool(flags, "off");
@@ -25651,6 +26210,12 @@ function assertConfigKey(key) {
25651
26210
  throw usageError(`unknown config key \`${key}\` \u2014 valid keys: ${CONFIG_KEYS.join(", ")}`);
25652
26211
  }
25653
26212
  }
26213
+ async function planDecision({ args, requireClient }, verb) {
26214
+ const id = requireArg(args, 0, "plan id");
26215
+ const itemIds = args.slice(1);
26216
+ const body = parseInput(planDecisionInputSchema, itemIds.length > 0 ? { itemIds } : {});
26217
+ return { data: await requireClient().post(`/plans/${id}/${verb}`, body), render: renderPlan };
26218
+ }
25654
26219
  function parseInput(schema, value) {
25655
26220
  const result = schema.safeParse(value);
25656
26221
  if (!result.success) {
@@ -25659,14 +26224,29 @@ function parseInput(schema, value) {
25659
26224
  }
25660
26225
  return result.data;
25661
26226
  }
26227
+ function flagInstant(flags, name) {
26228
+ const raw = flagString(flags, name);
26229
+ if (raw === undefined)
26230
+ return;
26231
+ const dayOnly = /^\d{4}-\d{2}-\d{2}$/.exec(raw.trim());
26232
+ const at = dayOnly ? new Date(`${raw.trim()}T00:00:00`) : new Date(raw);
26233
+ if (Number.isNaN(at.getTime()))
26234
+ throw usageError(`--${name} is not a valid date: ${raw}`);
26235
+ return { at, allDay: dayOnly !== null };
26236
+ }
26237
+ function flagMinutes(flags, name) {
26238
+ const raw = flagString(flags, name);
26239
+ if (raw === undefined)
26240
+ return;
26241
+ const minutes = Number(raw);
26242
+ if (!Number.isInteger(minutes) || minutes <= 0)
26243
+ throw usageError(`--${name} must be a whole number of minutes`);
26244
+ return minutes;
26245
+ }
25662
26246
  function computeSnoozeTarget(flags, now) {
25663
- const until = flagString(flags, "until");
25664
- if (until !== undefined) {
25665
- const date = new Date(until);
25666
- if (Number.isNaN(date.getTime()))
25667
- throw usageError(`--until is not a valid date: ${until}`);
25668
- return date;
25669
- }
26247
+ const until = flagInstant(flags, "until");
26248
+ if (until)
26249
+ return until.at;
25670
26250
  const daysRaw = flagString(flags, "days") ?? "1";
25671
26251
  const days = Number(daysRaw);
25672
26252
  if (!Number.isFinite(days) || days <= 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robbeverhelst/do",
3
- "version": "1.174.0",
3
+ "version": "1.175.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",