@robbeverhelst/do 1.161.0 → 1.162.1

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 +438 -26
  2. package/package.json +1 -1
package/dist/do.js CHANGED
@@ -3574,6 +3574,7 @@ var agents = pgTable("agents", {
3574
3574
  tenantId: text("tenant_id").notNull().references(() => tenants.id),
3575
3575
  name: text("name").notNull(),
3576
3576
  revokedAt: timestamp("revoked_at", { withTimezone: true }),
3577
+ trustedDesks: text("trusted_desks").array().notNull().default([]),
3577
3578
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
3578
3579
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
3579
3580
  }, (t) => [
@@ -3599,8 +3600,13 @@ var areas = pgTable("areas", {
3599
3600
  color: areaColor("color").notNull().default("teal"),
3600
3601
  icon: text("icon"),
3601
3602
  reviewCadence: reviewCadence("review_cadence"),
3602
- archivedAt: timestamp("archived_at", { withTimezone: true })
3603
- }, (t) => [index("areas_tenant_idx").on(t.tenantId)]);
3603
+ archivedAt: timestamp("archived_at", { withTimezone: true }),
3604
+ purpose: text("purpose"),
3605
+ standards: jsonb("standards").$type().notNull().default(sql`'[]'::jsonb`)
3606
+ }, (t) => [
3607
+ index("areas_tenant_idx").on(t.tenantId),
3608
+ check("areas_standards_chk", sql`jsonb_typeof(${t.standards}) = 'array' AND jsonb_array_length(${t.standards}) <= 5`)
3609
+ ]);
3604
3610
 
3605
3611
  // ../../packages/schema/src/db/core.ts
3606
3612
  var routineKind = pgEnum("routine_kind", ["sequence", "time_blocked", "checklist"]);
@@ -3796,7 +3802,12 @@ var tasks = pgTable("tasks", {
3796
3802
  completedByUserId: text("completed_by_user_id"),
3797
3803
  completedByAgentId: text("completed_by_agent_id"),
3798
3804
  placeId: text("place_id").references(() => places.id, { onDelete: "set null" }),
3799
- filingSuggestion: jsonb("filing_suggestion").$type()
3805
+ filingSuggestion: jsonb("filing_suggestion").$type(),
3806
+ proposedAt: timestamp("proposed_at", { withTimezone: true }),
3807
+ proposedDesk: text("proposed_desk"),
3808
+ notNowUntil: timestamp("not_now_until", { withTimezone: true }),
3809
+ readyAt: timestamp("ready_at", { withTimezone: true }),
3810
+ readyNote: text("ready_note")
3800
3811
  }, (t) => [
3801
3812
  index("tasks_tenant_project_idx").on(t.tenantId, t.projectId),
3802
3813
  index("tasks_tenant_place_idx").on(t.tenantId, t.placeId),
@@ -3811,6 +3822,8 @@ var tasks = pgTable("tasks", {
3811
3822
  index("tasks_tenant_scheduled_idx").on(t.tenantId, t.scheduledAt),
3812
3823
  index("tasks_tenant_due_idx").on(t.tenantId, t.dueAt),
3813
3824
  index("tasks_tenant_completed_idx").on(t.tenantId, t.completedAt),
3825
+ index("tasks_tenant_proposed_idx").on(t.tenantId, t.proposedAt).where(sql`${t.proposedAt} IS NOT NULL`),
3826
+ index("tasks_tenant_ready_idx").on(t.tenantId, t.readyAt).where(sql`${t.readyAt} IS NOT NULL`),
3814
3827
  check("tasks_area_xor_project_chk", sql`${t.projectId} IS NULL OR ${t.areaId} IS NULL`)
3815
3828
  ]);
3816
3829
  var habits = pgTable("habits", {
@@ -18173,13 +18186,20 @@ var reflectionKindSchema = _enum2(reflectionKind.enumValues);
18173
18186
 
18174
18187
  // ../../packages/schema/src/contracts/agents.ts
18175
18188
  var agentSchema = createSelectSchema(agents, {
18189
+ trustedDesks: array(string2()).default([]),
18176
18190
  revokedAt: exports_coerce.date().nullable(),
18177
18191
  createdAt: exports_coerce.date(),
18178
18192
  updatedAt: exports_coerce.date()
18179
18193
  });
18180
18194
  var agentNameSchema = string2().trim().min(1).max(60);
18181
18195
  var createAgentInputSchema = object({ name: agentNameSchema }).strict();
18182
- var updateAgentInputSchema = object({ name: agentNameSchema }).strict();
18196
+ var deskSchema = string2().trim().toLowerCase().regex(/^[a-z0-9][a-z0-9_-]{0,39}$/, "a desk is a lower-case slug like `finance`");
18197
+ var updateAgentInputSchema = object({
18198
+ name: agentNameSchema.optional(),
18199
+ trustedDesks: array(deskSchema).max(40).transform((desks) => [...new Set(desks)]).optional()
18200
+ }).strict().refine((v) => v.name !== undefined || v.trustedDesks !== undefined, {
18201
+ message: "send a name or trustedDesks"
18202
+ });
18183
18203
  var actorKindSchema = _enum2(["member", "agent"]);
18184
18204
  var actorRefSchema = object({
18185
18205
  kind: actorKindSchema,
@@ -18342,9 +18362,13 @@ var taskSchema = createSelectSchema(tasks, {
18342
18362
  startAt: exports_coerce.date().nullable(),
18343
18363
  dueAt: exports_coerce.date().nullable(),
18344
18364
  completedAt: exports_coerce.date().nullable().default(null),
18345
- placeId: string2().nullable().default(null)
18365
+ placeId: string2().nullable().default(null),
18366
+ proposedAt: exports_coerce.date().nullable().default(null),
18367
+ proposedDesk: string2().nullable().default(null),
18368
+ notNowUntil: exports_coerce.date().nullable().default(null),
18369
+ readyAt: exports_coerce.date().nullable().default(null),
18370
+ readyNote: string2().nullable().default(null)
18346
18371
  });
18347
- var deskSchema = string2().trim().toLowerCase().regex(/^[a-z0-9][a-z0-9_-]{0,39}$/, "a desk is a lower-case slug like `finance`");
18348
18372
  var PARTY_RE = /^(?:(me)|(member|agent):([^/]+?))(?:\/(.+))?$/;
18349
18373
  function partySchema(allowDesk) {
18350
18374
  return string2().trim().min(1).max(200).transform((raw, ctx) => {
@@ -18439,7 +18463,12 @@ var createTaskInputSchema = createInsertSchema(tasks, {
18439
18463
  waitingSince: true,
18440
18464
  completedByUserId: true,
18441
18465
  completedByAgentId: true,
18442
- filingSuggestion: true
18466
+ filingSuggestion: true,
18467
+ proposedAt: true,
18468
+ proposedDesk: true,
18469
+ notNowUntil: true,
18470
+ readyAt: true,
18471
+ readyNote: true
18443
18472
  }).extend({
18444
18473
  title: string2().min(1),
18445
18474
  parentId: ulidSchema.nullish(),
@@ -18449,8 +18478,12 @@ var createTaskInputSchema = createInsertSchema(tasks, {
18449
18478
  owner: taskOwnerInputSchema.nullish(),
18450
18479
  waitingOn: taskWaitingOnInputSchema.nullish(),
18451
18480
  source: taskSourceSchema.nullish(),
18452
- nextStep: taskNextStepSchema.nullish()
18481
+ nextStep: taskNextStepSchema.nullish(),
18482
+ proposed: boolean3().optional(),
18483
+ fromDesk: deskSchema.nullish()
18453
18484
  });
18485
+ var taskReadyNoteSchema = string2().trim().min(1).max(500);
18486
+ var markTaskReadyInputSchema = object({ note: taskReadyNoteSchema.nullish() }).strict();
18454
18487
  var updateTaskInputSchema = createUpdateSchema(tasks, {
18455
18488
  completedAt: completedAtInputSchema,
18456
18489
  scheduledAt: exports_coerce.date().nullish(),
@@ -18473,7 +18506,10 @@ var updateTaskInputSchema = createUpdateSchema(tasks, {
18473
18506
  waitingSince: true,
18474
18507
  completedByUserId: true,
18475
18508
  completedByAgentId: true,
18476
- filingSuggestion: true
18509
+ filingSuggestion: true,
18510
+ proposedAt: true,
18511
+ proposedDesk: true,
18512
+ readyAt: true
18477
18513
  }).extend({
18478
18514
  title: string2().min(1).optional(),
18479
18515
  parentId: ulidSchema.nullish(),
@@ -18482,7 +18518,11 @@ var updateTaskInputSchema = createUpdateSchema(tasks, {
18482
18518
  owner: taskOwnerInputSchema.nullish(),
18483
18519
  waitingOn: taskWaitingOnInputSchema.nullish(),
18484
18520
  source: taskSourceSchema.nullish(),
18485
- nextStep: taskNextStepSchema.nullish()
18521
+ nextStep: taskNextStepSchema.nullish(),
18522
+ proposed: boolean3().optional(),
18523
+ notNowUntil: exports_coerce.date().nullish(),
18524
+ ready: boolean3().optional(),
18525
+ readyNote: taskReadyNoteSchema.nullish()
18486
18526
  }).strict();
18487
18527
  var taskListFilterSchema = _enum2(["all", "unsorted", "starred"]);
18488
18528
 
@@ -19128,15 +19168,50 @@ var attachedNoteViewSchema = object({
19128
19168
  var setAttachedNoteInputSchema = object({ body: string2().max(ATTACHED_NOTE_BODY_MAX) }).strict();
19129
19169
 
19130
19170
  // ../../packages/schema/src/contracts/areas.ts
19131
- var areaSchema = createSelectSchema(areas);
19171
+ var areaPurposeSchema = string2().trim().max(140).transform((v) => v.length === 0 ? null : v);
19172
+ var areaStandardTextSchema = string2().trim().min(1).max(120);
19173
+ var linkedNodeIdSchema = string2().trim().min(1).max(64);
19174
+ var areaStandardLinkSchema = discriminatedUnion("kind", [
19175
+ object({ kind: literal("habit"), id: linkedNodeIdSchema }).strict(),
19176
+ object({ kind: literal("task"), id: linkedNodeIdSchema }).strict(),
19177
+ object({ kind: literal("metric"), key: metricKeySchema }).strict()
19178
+ ]);
19179
+ var areaStandardSchema = object({
19180
+ id: string2(),
19181
+ text: string2(),
19182
+ link: areaStandardLinkSchema.nullable().default(null)
19183
+ });
19184
+ var areaStandardInputSchema = object({
19185
+ id: string2().min(1).max(64).optional(),
19186
+ text: areaStandardTextSchema,
19187
+ link: areaStandardLinkSchema.nullish()
19188
+ }).strict();
19189
+ var addAreaStandardInputSchema = object({ text: areaStandardTextSchema, link: areaStandardLinkSchema.nullish() }).strict();
19190
+ var updateAreaStandardInputSchema = object({
19191
+ text: areaStandardTextSchema.optional(),
19192
+ link: areaStandardLinkSchema.nullable().optional()
19193
+ }).strict();
19194
+ var areaStandardStateSchema = _enum2(["holding", "worth_a_look"]);
19195
+ var areaStandardViewSchema = areaStandardSchema.extend({
19196
+ state: areaStandardStateSchema.nullable(),
19197
+ why: string2().nullable(),
19198
+ heldBy: string2().nullable()
19199
+ });
19200
+ var areaSchema = createSelectSchema(areas, {
19201
+ purpose: string2().nullable().default(null),
19202
+ standards: array(areaStandardSchema).default([])
19203
+ });
19132
19204
  var areaIconSchema = string2().trim().min(1).max(64);
19133
19205
  var createAreaInputSchema = createInsertSchema(areas, {
19134
19206
  id: ulidSchema,
19135
- icon: areaIconSchema.nullish()
19136
- }).omit({ tenantId: true, archivedAt: true }).extend({ title: string2().min(1) });
19207
+ icon: areaIconSchema.nullish(),
19208
+ purpose: areaPurposeSchema.nullish()
19209
+ }).omit({ tenantId: true, archivedAt: true, standards: true }).extend({ title: string2().min(1) });
19137
19210
  var updateAreaInputSchema = createUpdateSchema(areas, {
19138
19211
  icon: areaIconSchema.nullish(),
19139
- archivedAt: exports_coerce.date().nullish()
19212
+ archivedAt: exports_coerce.date().nullish(),
19213
+ purpose: areaPurposeSchema.nullish(),
19214
+ standards: array(areaStandardInputSchema).max(5).optional()
19140
19215
  }).omit({ id: true, tenantId: true }).extend({ title: string2().min(1).optional() }).strict();
19141
19216
  var areaViewSchema = areaSchema.extend({
19142
19217
  title: string2(),
@@ -19147,12 +19222,14 @@ var areaViewSchema = areaSchema.extend({
19147
19222
  projectCount: number2().int().min(0),
19148
19223
  taskCount: number2().int().min(0),
19149
19224
  habitCount: number2().int().min(0),
19150
- routineCount: number2().int().min(0)
19225
+ routineCount: number2().int().min(0),
19226
+ standardsWorthALook: number2().int().min(0).default(0)
19151
19227
  });
19152
19228
  var areaProjectSchema = projectViewSchema.extend({
19153
19229
  tasks: array(taskViewSchema)
19154
19230
  });
19155
19231
  var areaDetailSchema = areaViewSchema.extend({
19232
+ standards: array(areaStandardViewSchema).default([]),
19156
19233
  projects: array(areaProjectSchema),
19157
19234
  habits: array(habitSummarySchema),
19158
19235
  routines: array(routineViewSchema),
@@ -19479,7 +19556,7 @@ function setModuleEnabled(enabled, key, on) {
19479
19556
  var userSettingsSchema = createSelectSchema(userSettings, {
19480
19557
  createdAt: exports_coerce.date(),
19481
19558
  updatedAt: exports_coerce.date()
19482
- });
19559
+ }).omit({ autoRollOverdue: true });
19483
19560
  var updateUserSettingsInputSchema = createUpdateSchema(userSettings, {
19484
19561
  displayName: string2().trim().max(80).nullish(),
19485
19562
  avatarUrl: union([string2().url().max(2048), literal("")]).nullish(),
@@ -19490,9 +19567,15 @@ var updateUserSettingsInputSchema = createUpdateSchema(userSettings, {
19490
19567
  accent: settingsAccentSchema.optional(),
19491
19568
  sunCity: string2().trim().max(80).transform((city) => city === "" ? null : city).nullish(),
19492
19569
  backdrop: backdropIdSchema.nullish(),
19493
- enabledModules: array(string2().trim().min(1)).transform(normalizeEnabledModules).optional(),
19494
- autoRollOverdue: boolean3().optional()
19495
- }).omit({ id: true, tenantId: true, userId: true, createdAt: true, updatedAt: true }).strict();
19570
+ enabledModules: array(string2().trim().min(1)).transform(normalizeEnabledModules).optional()
19571
+ }).omit({
19572
+ id: true,
19573
+ tenantId: true,
19574
+ userId: true,
19575
+ createdAt: true,
19576
+ updatedAt: true,
19577
+ autoRollOverdue: true
19578
+ }).strict();
19496
19579
 
19497
19580
  // ../../packages/schema/src/contracts/shopping.ts
19498
19581
  var shoppingRecurrenceUnitSchema = _enum2(["day", "week", "month"]);
@@ -19758,7 +19841,8 @@ var timeBlockTaskSchema = object({
19758
19841
  id: ulidSchema,
19759
19842
  title: string2(),
19760
19843
  status: string2(),
19761
- areaId: string2().nullable()
19844
+ areaId: string2().nullable(),
19845
+ projectId: string2().nullable().optional()
19762
19846
  });
19763
19847
  var timeBlockViewSchema = timeBlockSchema.extend({
19764
19848
  title: string2(),
@@ -20999,7 +21083,7 @@ function parseCapture2(input, now) {
20999
21083
 
21000
21084
  // src/meta.ts
21001
21085
  var CLI_NAME = "do";
21002
- var VERSION = "1.161.0";
21086
+ var VERSION = "1.162.1";
21003
21087
  var API_PREFIX = "/api/v1";
21004
21088
  var DEFAULT_API_URL = "http://localhost:3001";
21005
21089
 
@@ -21222,7 +21306,7 @@ var ENTITIES = [
21222
21306
  listFlags: [
21223
21307
  {
21224
21308
  name: "filter",
21225
- description: "all (default), unsorted (the GTD inbox) or starred",
21309
+ description: "all (default), unsorted (the Inbox's tasks to file: no day, Deadline, Area or Project; never a proposal) or starred",
21226
21310
  query: "filter"
21227
21311
  },
21228
21312
  { ...AREA_FLAG, description: "only tasks in this area id (a project task by its project's)" },
@@ -21243,7 +21327,17 @@ var ENTITIES = [
21243
21327
  description: "waiting on: me, member:<id|email>, agent:<id|name>, any or none"
21244
21328
  },
21245
21329
  { name: "completedSince", description: "finished at or after this instant (ISO or date)" },
21246
- { name: "completedBefore", description: "finished before this instant (ISO or date)" }
21330
+ { name: "completedBefore", description: "finished before this instant (ISO or date)" },
21331
+ {
21332
+ name: "proposed",
21333
+ description: "only tasks Proposed by Echo (made by an Agent for a member, not accepted yet); --proposed=false for only the plan",
21334
+ boolean: true
21335
+ },
21336
+ {
21337
+ name: "ready",
21338
+ description: "only open tasks their Owner marked Ready for you; --ready=false for the rest",
21339
+ boolean: true
21340
+ }
21247
21341
  ],
21248
21342
  write: {
21249
21343
  scope: "tasks:write",
@@ -21307,7 +21401,15 @@ var ENTITIES = [
21307
21401
  readScope: "areas:read",
21308
21402
  returns: "AreaView",
21309
21403
  canGet: true,
21310
- listFlags: [],
21404
+ listFlags: [
21405
+ {
21406
+ name: "active",
21407
+ description: "only live areas (not archived)",
21408
+ query: "archived",
21409
+ sends: "false"
21410
+ },
21411
+ { name: "archived", description: "only archived areas", query: "archived", sends: "true" }
21412
+ ],
21311
21413
  write: {
21312
21414
  scope: "areas:write",
21313
21415
  createSchema: createAreaInputSchema,
@@ -21518,6 +21620,11 @@ var PAGINATION_FLAGS = [
21518
21620
  description: "Opaque keyset cursor from a previous page's nextCursor."
21519
21621
  }
21520
21622
  ];
21623
+ var STANDARD_LINK_FLAGS = [
21624
+ { name: "habit", type: "string", description: "Held by this habit (its id)." },
21625
+ { name: "task", type: "string", description: "Held by this recurring task (its id)." },
21626
+ { name: "metric", type: "string", description: "Held by this Metric: weight, sleep or mood." }
21627
+ ];
21521
21628
  var GLOBAL_FLAGS = [
21522
21629
  { name: "json", type: "boolean", description: "Emit JSON (automatic when stdout is piped)." },
21523
21630
  { name: "quiet", type: "boolean", description: "Suppress human output (errors still report)." },
@@ -21578,6 +21685,31 @@ var COMMAND_SPECS = [
21578
21685
  returns: "TodaySummary",
21579
21686
  notes: "Tasks are composed client-side from GET /api/v1/tasks (the local day from your clock); habits and routines come from GET /api/v1/habits/today (your saved time zone's day) under `habits`, left out when the key lacks habits:read."
21580
21687
  },
21688
+ {
21689
+ path: ["today", "earlier"],
21690
+ summary: "From earlier: planned tasks of the last 14 days that weren't done, newest first, and the day Later this week lands on.",
21691
+ args: [],
21692
+ flags: [{ name: "area", type: "string", description: "Only tasks in this Area (id)." }],
21693
+ scopes: ["tasks:read"],
21694
+ returns: "FromEarlierResponse",
21695
+ notes: "GET /api/v1/today/from-earlier, from your local day. Nothing here is late: timed tasks, time blocks and agents' work never roll, and a plan older than 14 days just waits where it was filed. Answer with `do today answer`."
21696
+ },
21697
+ {
21698
+ path: ["today", "answer"],
21699
+ summary: "Answer From earlier for one or more tasks: today, later (this week), someday or let-go.",
21700
+ args: [
21701
+ {
21702
+ name: "answer",
21703
+ required: true,
21704
+ description: "today (plan it today) \xB7 later (the quiet day later this week) \xB7 someday (no date, kept in its Area or Project) \xB7 let-go (close it without doing it)."
21705
+ },
21706
+ { name: "ids", required: true, description: "One or more task ids." }
21707
+ ],
21708
+ flags: [],
21709
+ scopes: ["tasks:write"],
21710
+ returns: "RolloverResult",
21711
+ notes: "POST /api/v1/today/rollover. `later` reads the day from `do today earlier` first. Let go is cancelled, never done, so it stays out of Landed; undo with `do task reopen`."
21712
+ },
21581
21713
  {
21582
21714
  path: ["now"],
21583
21715
  summary: "What needs you now: what's waiting on you, then the day's timeline.",
@@ -21703,6 +21835,39 @@ var COMMAND_SPECS = [
21703
21835
  returns: "Task",
21704
21836
  notes: "POST /api/v1/tasks/{id}/cancel. Use this, not `task done`, for a dropped task."
21705
21837
  },
21838
+ {
21839
+ path: ["tasks", "accept"],
21840
+ summary: "Accept a task Proposed by Echo: it joins the plan as suggested (day, Deadline, Area kept).",
21841
+ args: [{ name: "id", required: true, description: "Task ULID." }],
21842
+ flags: [],
21843
+ scopes: ["tasks:write"],
21844
+ returns: "Task",
21845
+ notes: "POST /api/v1/tasks/{id}/accept (#889). A no-op on a task that isn't a proposal. List proposals with `do tasks list --proposed`."
21846
+ },
21847
+ {
21848
+ path: ["tasks", "dismiss"],
21849
+ summary: "Not now on a task Proposed by Echo: it leaves the Inbox for a week, still proposed.",
21850
+ args: [{ name: "id", required: true, description: "Task ULID." }],
21851
+ flags: [],
21852
+ scopes: ["tasks:write"],
21853
+ returns: "Task",
21854
+ notes: "POST /api/v1/tasks/{id}/not-now (#889); 409 when the task isn't a proposal. Let go is `do task cancel`."
21855
+ },
21856
+ {
21857
+ path: ["tasks", "ready"],
21858
+ summary: "Mark a task Ready for you: its Owner finished something the member should look at (a draft, a link, a worktree).",
21859
+ args: [{ name: "id", required: true, description: "Task ULID." }],
21860
+ flags: [
21861
+ {
21862
+ name: "note",
21863
+ type: "string",
21864
+ description: `What's ready, in a line, with a link when there is one ("Draft ready: 38 hours, 3,420 euro \u2014 https://\u2026"); at most 500 characters. Absent keeps the note already there.`
21865
+ }
21866
+ ],
21867
+ scopes: ["tasks:write"],
21868
+ returns: "Task",
21869
+ notes: "POST /api/v1/tasks/{id}/ready (#902); 409 when the task is done or let go. It reaches Now's Needs you with Review (Looks good = done, Send back = `ready: false`). List them with `do tasks list --ready`."
21870
+ },
21706
21871
  {
21707
21872
  path: ["task", "reopen"],
21708
21873
  summary: "Reopen a done or cancelled task (status todo; clears completedAt).",
@@ -22448,6 +22613,66 @@ var COMMAND_SPECS = [
22448
22613
  returns: "CollectionCoverBackfillResult",
22449
22614
  notes: "Returns { checked, found }; run again while checked is 25 to work through more."
22450
22615
  },
22616
+ {
22617
+ path: ["areas", "purpose"],
22618
+ summary: "Set an Area's purpose: one line of what it's for.",
22619
+ args: [
22620
+ { name: "id", required: true, description: "The Area's id." },
22621
+ { name: "line", required: false, description: "The purpose, in one line (words join)." }
22622
+ ],
22623
+ flags: [{ name: "clear", type: "boolean", description: "Remove the purpose." }],
22624
+ scopes: ["areas:write"],
22625
+ returns: "AreaView",
22626
+ notes: "PATCH /api/v1/areas/:id { purpose }. It shows under the Area's name on the hub."
22627
+ },
22628
+ {
22629
+ path: ["areas", "standards", "list"],
22630
+ summary: "List an Area's standards with how each is doing (holding / worth a look).",
22631
+ args: [{ name: "id", required: true, description: "The Area's id." }],
22632
+ flags: [],
22633
+ scopes: ["areas:read"],
22634
+ returns: "AreaStandardView",
22635
+ notes: "Each linked standard says holding or worth a look with the reason in words; an unlinked one has no state (it's checked by hand in review)."
22636
+ },
22637
+ {
22638
+ path: ["areas", "standards", "add"],
22639
+ summary: "Add a standard (what fine looks like) to an Area; up to five.",
22640
+ args: [
22641
+ { name: "id", required: true, description: "The Area's id." },
22642
+ { name: "text", required: true, description: "The standard, a plain sentence (words join)." }
22643
+ ],
22644
+ flags: STANDARD_LINK_FLAGS,
22645
+ scopes: ["areas:write"],
22646
+ returns: "AreaDetail",
22647
+ notes: "POST /api/v1/areas/:id/standards. Echo adds standards only after the member said yes; the change list keeps Undo."
22648
+ },
22649
+ {
22650
+ path: ["areas", "standards", "link"],
22651
+ summary: "Change what holds a standard: a habit, a recurring task, a Metric, or none.",
22652
+ args: [
22653
+ { name: "id", required: true, description: "The Area's id." },
22654
+ { name: "standard", required: true, description: "The standard's id." }
22655
+ ],
22656
+ flags: [
22657
+ ...STANDARD_LINK_FLAGS,
22658
+ { name: "none", type: "boolean", description: "Unlink it: checked by hand in review." }
22659
+ ],
22660
+ scopes: ["areas:write"],
22661
+ returns: "AreaDetail",
22662
+ notes: "PATCH /api/v1/areas/:id/standards/:standardId { link }."
22663
+ },
22664
+ {
22665
+ path: ["areas", "standards", "rm"],
22666
+ summary: "Remove a standard from an Area.",
22667
+ args: [
22668
+ { name: "id", required: true, description: "The Area's id." },
22669
+ { name: "standard", required: true, description: "The standard's id." }
22670
+ ],
22671
+ flags: [],
22672
+ scopes: ["areas:write"],
22673
+ returns: "AreaDetail",
22674
+ notes: "DELETE /api/v1/areas/:id/standards/:standardId."
22675
+ },
22451
22676
  {
22452
22677
  path: ["places", "import"],
22453
22678
  summary: "Import the rooms of the house once from Gaard's house.json.",
@@ -23140,6 +23365,49 @@ function renderToday(data, mode) {
23140
23365
  ${habits.join(`
23141
23366
  `)}` : tasks;
23142
23367
  }
23368
+ var DAY_MS = 24 * 60 * 60 * 1000;
23369
+ function plannedDay(at, from) {
23370
+ if (!at)
23371
+ return "earlier";
23372
+ const day = new Date(at);
23373
+ const ago = from ? Math.round((Date.parse(from) - day.getTime()) / DAY_MS) : 7;
23374
+ if (ago <= 1)
23375
+ return "yesterday";
23376
+ if (ago < 7)
23377
+ return day.toLocaleDateString("en-GB", { weekday: "long" });
23378
+ return day.toLocaleDateString("en-GB", { day: "numeric", month: "short" });
23379
+ }
23380
+ function renderFromEarlier(data, mode) {
23381
+ const d = data;
23382
+ const tasks = d.tasks ?? [];
23383
+ if (tasks.length === 0)
23384
+ return paint(mode, "dim", "Nothing from earlier.");
23385
+ const later = d.laterThisWeek ? new Date(d.laterThisWeek).toLocaleDateString("en-GB", { weekday: "long" }) : null;
23386
+ return [
23387
+ paint(mode, "bold", "From earlier \xB7 planned, not done. Nothing is late."),
23388
+ ...tasks.map((t) => ` \xB7 ${t.title ?? "(untitled)"} ${paint(mode, "dim", `\u2014 planned ${plannedDay(t.scheduledAt, d.from)}`)} ${paint(mode, "dim", t.id)}`),
23389
+ "",
23390
+ paint(mode, "dim", `Answer with \`do today answer <today|later|someday|let-go> <id\u2026>\`${later ? `; later is ${later}` : ""}.`)
23391
+ ].join(`
23392
+ `);
23393
+ }
23394
+ function renderFromEarlierAnswer(data, mode) {
23395
+ const d = data;
23396
+ const n = d.tasks ?? 0;
23397
+ const tasks = `${n} task${n === 1 ? "" : "s"}`;
23398
+ if (n === 0)
23399
+ return paint(mode, "dim", "Nothing changed: those tasks are already done, let go or gone.");
23400
+ switch (d.answer) {
23401
+ case "today":
23402
+ return `Planned for today: ${tasks}.`;
23403
+ case "later":
23404
+ return `Planned ${d.to ? new Date(d.to).toLocaleDateString("en-GB", { weekday: "long" }) : "later this week"}: ${tasks}.`;
23405
+ case "someday":
23406
+ return `Moved to Someday: ${tasks}, kept where they were filed.`;
23407
+ default:
23408
+ return `Let go: ${tasks}. ${paint(mode, "dim", "Changed your mind? `do task reopen <id>`.")}`;
23409
+ }
23410
+ }
23143
23411
  var PART_LABEL = {
23144
23412
  morning: "Morning",
23145
23413
  midday: "Midday",
@@ -23478,10 +23746,15 @@ function renderNow(data, mode) {
23478
23746
  const waiting = now.waitingOnYou ?? [];
23479
23747
  const head = paint(mode, "bold", `Waiting on ${actorText(now.for)} \xB7 ${waiting.length}`);
23480
23748
  const lines = waiting.length ? waiting.map((t) => taskLine(t, mode)) : [paint(mode, "dim", " nothing is waiting on you")];
23749
+ const ready = now.readyForYou ?? [];
23750
+ const readyLines = ready.length ? [
23751
+ paint(mode, "bold", "Ready for you"),
23752
+ ...ready.map((t) => taskLine(t, mode) + (t.readyNote ? paint(mode, "dim", ` \u2014 ${t.readyNote}`) : ""))
23753
+ ] : [];
23481
23754
  const tl = now.timeline ?? {};
23482
23755
  const count = (xs) => xs?.length ?? 0;
23483
23756
  const timeline = paint(mode, "dim", `Today: ${count(tl.scheduledTasks)} scheduled \xB7 ${count(tl.dueOccurrences)} due \xB7 ${count(tl.overdueTasks) + count(tl.overdueOccurrences)} overdue`);
23484
- return [head, ...lines, timeline].join(`
23757
+ return [head, ...lines, ...readyLines, timeline].join(`
23485
23758
  `);
23486
23759
  }
23487
23760
  function renderLanded(data, mode) {
@@ -23618,6 +23891,18 @@ function renderMcpTools(data, mode) {
23618
23891
  return [paint(mode, "bold", `${tools.length} tools`), ...lines].join(`
23619
23892
  `);
23620
23893
  }
23894
+ function renderStandards(data, mode) {
23895
+ const list = Array.isArray(data) ? data : [];
23896
+ if (list.length === 0)
23897
+ return paint(mode, "dim", "No standards yet.");
23898
+ return list.map((s) => {
23899
+ const state = s.state === "holding" ? `Holding${s.why ? `: ${s.why}` : ""}` : s.state === "worth_a_look" ? `Worth a look${s.why ? `: ${s.why}` : ""}` : "Checked by hand, in review";
23900
+ const held = s.heldBy ? ` \xB7 ${s.heldBy}` : "";
23901
+ return ` \xB7 ${s.text ?? "(untitled)"}
23902
+ ${paint(mode, "dim", `${state}${held} ${s.id ?? ""}`)}`;
23903
+ }).join(`
23904
+ `);
23905
+ }
23621
23906
 
23622
23907
  // src/today.ts
23623
23908
  var OPEN_STATUSES = new Set(["todo", "in_progress"]);
@@ -23734,9 +24019,19 @@ async function dispatchEntity(entity, rest, flags, requireClient) {
23734
24019
  if (op === "list") {
23735
24020
  const query = {};
23736
24021
  for (const f of entity.listFlags) {
24022
+ const key = f.query ?? f.name;
24023
+ if (f.sends !== undefined) {
24024
+ if (flags[f.name] === undefined)
24025
+ continue;
24026
+ const other = entity.listFlags.find((o) => o !== f && o.sends !== undefined && (o.query ?? o.name) === key && (o.name in flags));
24027
+ if (other)
24028
+ throw usageError(`--${other.name} and --${f.name} can't be combined`);
24029
+ query[key] = f.sends;
24030
+ continue;
24031
+ }
23737
24032
  const value = flags[f.name] === true ? "true" : flagString(flags, f.name);
23738
24033
  if (value !== undefined)
23739
- query[f.query ?? f.name] = value;
24034
+ query[key] = value;
23740
24035
  else if (f.required)
23741
24036
  throw usageError(`\`${entity.name} list\` requires --${f.name}`);
23742
24037
  }
@@ -23865,6 +24160,45 @@ var HANDLERS = {
23865
24160
  render: renderToday
23866
24161
  };
23867
24162
  },
24163
+ "today earlier": async ({ flags, io, requireClient }) => {
24164
+ const area = flagString(flags, "area");
24165
+ return {
24166
+ data: await requireClient().get("/today/from-earlier", {
24167
+ query: { from: localDayStart(io.now()).toISOString(), areaId: area }
24168
+ }),
24169
+ render: renderFromEarlier
24170
+ };
24171
+ },
24172
+ "today answer": async ({ args, io, requireClient }) => {
24173
+ const answer = requireArg(args, 0, "answer");
24174
+ const taskIds = args.slice(1);
24175
+ if (!FROM_EARLIER_ANSWERS.includes(answer)) {
24176
+ throw usageError(`answer must be one of ${FROM_EARLIER_ANSWERS.join(", ")} (got \`${answer}\`)`);
24177
+ }
24178
+ if (taskIds.length === 0)
24179
+ throw usageError("today answer needs at least one task id");
24180
+ const client = requireClient();
24181
+ const today = localDayStart(io.now());
24182
+ let body;
24183
+ let to;
24184
+ if (answer === "today") {
24185
+ to = today.toISOString();
24186
+ body = { action: "reschedule", to, taskIds };
24187
+ } else if (answer === "later") {
24188
+ const earlier = await client.get("/today/from-earlier", {
24189
+ query: { from: today.toISOString() }
24190
+ });
24191
+ to = earlier.laterThisWeek;
24192
+ body = { action: "reschedule", to, taskIds };
24193
+ } else {
24194
+ body = { action: answer === "someday" ? "someday" : "drop", taskIds };
24195
+ }
24196
+ const result = await client.post("/today/rollover", body);
24197
+ return {
24198
+ data: { answer, ...to ? { to } : {}, ...result },
24199
+ render: renderFromEarlierAnswer
24200
+ };
24201
+ },
23868
24202
  export: async ({ flags, requireClient }) => {
23869
24203
  const format = flagString(flags, "format") ?? "json";
23870
24204
  if (format !== "json" && format !== "ndjson") {
@@ -24091,6 +24425,20 @@ var HANDLERS = {
24091
24425
  const id = requireArg(args, 0, "task id");
24092
24426
  return { data: await requireClient().post(`/tasks/${id}/cancel`, {}), render: renderTask };
24093
24427
  },
24428
+ "tasks accept": async ({ args, requireClient }) => {
24429
+ const id = requireArg(args, 0, "task id");
24430
+ return { data: await requireClient().post(`/tasks/${id}/accept`, {}), render: renderTask };
24431
+ },
24432
+ "tasks dismiss": async ({ args, requireClient }) => {
24433
+ const id = requireArg(args, 0, "task id");
24434
+ return { data: await requireClient().post(`/tasks/${id}/not-now`, {}), render: renderTask };
24435
+ },
24436
+ "tasks ready": async ({ args, flags, requireClient }) => {
24437
+ const id = requireArg(args, 0, "task id");
24438
+ const note = flagString(flags, "note");
24439
+ const body = parseInput(markTaskReadyInputSchema, note !== undefined ? { note } : {});
24440
+ return { data: await requireClient().post(`/tasks/${id}/ready`, body), render: renderTask };
24441
+ },
24094
24442
  "task reopen": async ({ args, requireClient }) => {
24095
24443
  const id = requireArg(args, 0, "task id");
24096
24444
  const body = parseInput(updateTaskInputSchema, { status: "todo" });
@@ -24463,6 +24811,51 @@ var HANDLERS = {
24463
24811
  const result = collectionCoverBackfillResultSchema.parse(await requireClient().post("/collection/covers", undefined));
24464
24812
  return { data: result, render: renderRecord };
24465
24813
  },
24814
+ "areas purpose": async ({ args, flags, requireClient }) => {
24815
+ const id = requireArg(args, 0, "area id");
24816
+ const line = args.slice(1).join(" ").trim();
24817
+ const clear = flagBool(flags, "clear");
24818
+ if (clear && line)
24819
+ throw usageError("pass a purpose or --clear, not both");
24820
+ if (!clear && !line)
24821
+ throw usageError('areas purpose needs a line, e.g. `do areas purpose <id> "Sleep well, move often"` (or --clear)');
24822
+ const body = parseInput(updateAreaInputSchema, { purpose: clear ? null : line });
24823
+ return { data: await requireClient().patch(`/areas/${id}`, body), render: renderRecord };
24824
+ },
24825
+ "areas standards list": async ({ args, requireClient }) => {
24826
+ const id = requireArg(args, 0, "area id");
24827
+ const area = await requireClient().get(`/areas/${id}`);
24828
+ return { data: area.standards ?? [], render: renderStandards };
24829
+ },
24830
+ "areas standards add": async ({ args, flags, requireClient }) => {
24831
+ const id = requireArg(args, 0, "area id");
24832
+ const text = args.slice(1).join(" ").trim();
24833
+ if (!text)
24834
+ throw usageError('areas standards add needs the standard, e.g. `do areas standards add <id> "Move three times a week" --habit <habitId>`');
24835
+ const link = standardLink(flags);
24836
+ const body = parseInput(addAreaStandardInputSchema, { text, ...link ? { link } : {} });
24837
+ const area = await requireClient().post(`/areas/${id}/standards`, body);
24838
+ return { data: area.standards ?? [], render: renderStandards };
24839
+ },
24840
+ "areas standards link": async ({ args, flags, requireClient }) => {
24841
+ const id = requireArg(args, 0, "area id");
24842
+ const standard = requireArg(args, 1, "standard id");
24843
+ const link = standardLink(flags);
24844
+ const none = flagBool(flags, "none");
24845
+ if (none && link)
24846
+ throw usageError("pass one of --habit, --task, --metric or --none");
24847
+ if (!none && !link)
24848
+ throw usageError("say what holds it: --habit <id>, --task <id>, --metric <key>, or --none");
24849
+ const body = parseInput(updateAreaStandardInputSchema, { link: none ? null : link });
24850
+ const area = await requireClient().patch(`/areas/${id}/standards/${standard}`, body);
24851
+ return { data: area.standards ?? [], render: renderStandards };
24852
+ },
24853
+ "areas standards rm": async ({ args, requireClient }) => {
24854
+ const id = requireArg(args, 0, "area id");
24855
+ const standard = requireArg(args, 1, "standard id");
24856
+ const area = await requireClient().delete(`/areas/${id}/standards/${standard}`);
24857
+ return { data: area.standards ?? [], render: renderStandards };
24858
+ },
24466
24859
  "places import": async ({ args, flags, requireClient }) => {
24467
24860
  const path = requireArg(args, 0, "house.json path");
24468
24861
  let file;
@@ -24697,6 +25090,21 @@ function isoDay(date) {
24697
25090
  const day = String(date.getDate()).padStart(2, "0");
24698
25091
  return `${date.getFullYear()}-${month}-${day}`;
24699
25092
  }
25093
+ function standardLink(flags) {
25094
+ const habit = flagString(flags, "habit");
25095
+ const task = flagString(flags, "task");
25096
+ const metric = flagString(flags, "metric");
25097
+ const given = [habit, task, metric].filter((v) => v !== undefined);
25098
+ if (given.length > 1)
25099
+ throw usageError("pass one of --habit, --task or --metric");
25100
+ if (habit !== undefined)
25101
+ return { kind: "habit", id: habit };
25102
+ if (task !== undefined)
25103
+ return { kind: "task", id: task };
25104
+ if (metric !== undefined)
25105
+ return { kind: "metric", key: metric };
25106
+ return null;
25107
+ }
24700
25108
  function requireArg(args, index, name) {
24701
25109
  const value = args[index];
24702
25110
  if (value === undefined || value.length === 0) {
@@ -24808,6 +25216,10 @@ function assertDestructiveAllowed(flags, action) {
24808
25216
  throw new CliError("destructive_blocked", `refusing to ${action} without --allow-destructive (a guardrail for autonomous agents)`, EXIT.USAGE);
24809
25217
  }
24810
25218
  }
25219
+ var FROM_EARLIER_ANSWERS = ["today", "later", "someday", "let-go"];
25220
+ function localDayStart(now) {
25221
+ return new Date(now.getFullYear(), now.getMonth(), now.getDate());
25222
+ }
24811
25223
 
24812
25224
  // src/run.ts
24813
25225
  async function run(argv, io) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robbeverhelst/do",
3
- "version": "1.161.0",
3
+ "version": "1.162.1",
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",