@robbeverhelst/do 1.174.0 → 1.176.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.
- package/dist/do.js +629 -37
- 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`)
|
|
@@ -3739,6 +3740,7 @@ var userSettings = pgTable("user_settings", {
|
|
|
3739
3740
|
pinnedAreaIds: text("pinned_area_ids").array().notNull().default([]),
|
|
3740
3741
|
focusAreaId: text("focus_area_id"),
|
|
3741
3742
|
focusUntil: timestamp("focus_until", { withTimezone: true }),
|
|
3743
|
+
quietHours: jsonb("quiet_hours").$type(),
|
|
3742
3744
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
3743
3745
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
3744
3746
|
}, (t) => [unique("user_settings_tenant_user_uniq").on(t.tenantId, t.userId)]);
|
|
@@ -3818,7 +3820,8 @@ var tasks = pgTable("tasks", {
|
|
|
3818
3820
|
proposedDesk: text("proposed_desk"),
|
|
3819
3821
|
notNowUntil: timestamp("not_now_until", { withTimezone: true }),
|
|
3820
3822
|
readyAt: timestamp("ready_at", { withTimezone: true }),
|
|
3821
|
-
readyNote: text("ready_note")
|
|
3823
|
+
readyNote: text("ready_note"),
|
|
3824
|
+
moveCount: integer("move_count").notNull().default(0)
|
|
3822
3825
|
}, (t) => [
|
|
3823
3826
|
index("tasks_tenant_project_idx").on(t.tenantId, t.projectId),
|
|
3824
3827
|
index("tasks_tenant_place_idx").on(t.tenantId, t.placeId),
|
|
@@ -3917,7 +3920,8 @@ var notificationType = pgEnum("notification_type", [
|
|
|
3917
3920
|
"streak_at_risk",
|
|
3918
3921
|
"reflection_nudge",
|
|
3919
3922
|
"shutdown_nudge",
|
|
3920
|
-
"habit_reminder"
|
|
3923
|
+
"habit_reminder",
|
|
3924
|
+
"echo"
|
|
3921
3925
|
]);
|
|
3922
3926
|
var notifications = pgTable("notifications", {
|
|
3923
3927
|
id: text("id").primaryKey(),
|
|
@@ -3941,6 +3945,9 @@ var pushSubscriptions = pgTable("push_subscriptions", {
|
|
|
3941
3945
|
endpoint: text("endpoint").notNull(),
|
|
3942
3946
|
p256dh: text("p256dh").notNull(),
|
|
3943
3947
|
auth: text("auth").notNull(),
|
|
3948
|
+
userAgent: text("user_agent"),
|
|
3949
|
+
lastDeliveredAt: timestamp("last_delivered_at", { withTimezone: true }),
|
|
3950
|
+
lastSeenAt: timestamp("last_seen_at", { withTimezone: true }),
|
|
3944
3951
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
3945
3952
|
}, (t) => [unique("push_subscriptions_endpoint_uniq").on(t.endpoint)]);
|
|
3946
3953
|
|
|
@@ -18432,7 +18439,8 @@ var taskViewSchema = taskSchema.omit({
|
|
|
18432
18439
|
source: true,
|
|
18433
18440
|
completedByUserId: true,
|
|
18434
18441
|
completedByAgentId: true,
|
|
18435
|
-
filingSuggestion: true
|
|
18442
|
+
filingSuggestion: true,
|
|
18443
|
+
moveCount: true
|
|
18436
18444
|
}).extend({
|
|
18437
18445
|
title: string2(),
|
|
18438
18446
|
parentId: ulidSchema.nullable(),
|
|
@@ -19221,21 +19229,28 @@ var areaStandardViewSchema = areaStandardSchema.extend({
|
|
|
19221
19229
|
});
|
|
19222
19230
|
var areaSchema = createSelectSchema(areas, {
|
|
19223
19231
|
purpose: string2().nullable().default(null),
|
|
19224
|
-
standards: array(areaStandardSchema).default([])
|
|
19232
|
+
standards: array(areaStandardSchema).default([]),
|
|
19233
|
+
lastReviewedAt: exports_coerce.date().nullable().default(null)
|
|
19225
19234
|
});
|
|
19226
19235
|
var areaIconSchema = string2().trim().min(1).max(64);
|
|
19227
19236
|
var createAreaInputSchema = createInsertSchema(areas, {
|
|
19228
19237
|
id: ulidSchema,
|
|
19229
19238
|
icon: areaIconSchema.nullish(),
|
|
19230
19239
|
purpose: areaPurposeSchema.nullish()
|
|
19231
|
-
}).omit({
|
|
19240
|
+
}).omit({
|
|
19241
|
+
tenantId: true,
|
|
19242
|
+
archivedAt: true,
|
|
19243
|
+
standards: true,
|
|
19244
|
+
restingUntil: true,
|
|
19245
|
+
lastReviewedAt: true
|
|
19246
|
+
}).extend({ title: string2().min(1) });
|
|
19232
19247
|
var updateAreaInputSchema = createUpdateSchema(areas, {
|
|
19233
19248
|
icon: areaIconSchema.nullish(),
|
|
19234
19249
|
archivedAt: exports_coerce.date().nullish(),
|
|
19235
19250
|
purpose: areaPurposeSchema.nullish(),
|
|
19236
19251
|
standards: array(areaStandardInputSchema).max(5).optional(),
|
|
19237
19252
|
restingUntil: exports_iso.date().nullish()
|
|
19238
|
-
}).omit({ id: true, tenantId: true }).extend({ title: string2().min(1).optional() }).strict();
|
|
19253
|
+
}).omit({ id: true, tenantId: true, lastReviewedAt: true }).extend({ title: string2().min(1).optional() }).strict();
|
|
19239
19254
|
var areaViewSchema = areaSchema.extend({
|
|
19240
19255
|
title: string2(),
|
|
19241
19256
|
archivedAt: exports_coerce.date().nullable().default(null),
|
|
@@ -19292,6 +19307,24 @@ var areaOverviewSchema = object({
|
|
|
19292
19307
|
owner: areaOwnerLensSchema,
|
|
19293
19308
|
areas: array(areaOverviewItemSchema)
|
|
19294
19309
|
});
|
|
19310
|
+
var areaLookItemSchema = object({
|
|
19311
|
+
id: string2(),
|
|
19312
|
+
title: string2(),
|
|
19313
|
+
by: string2().nullable().default(null),
|
|
19314
|
+
why: string2().nullable().default(null)
|
|
19315
|
+
});
|
|
19316
|
+
var areaSinceLastLookSchema = object({
|
|
19317
|
+
areaId: string2(),
|
|
19318
|
+
lastReviewedAt: exports_coerce.date().nullable(),
|
|
19319
|
+
since: exports_coerce.date(),
|
|
19320
|
+
landed: array(areaLookItemSchema),
|
|
19321
|
+
fresh: array(areaLookItemSchema),
|
|
19322
|
+
stuck: array(areaLookItemSchema)
|
|
19323
|
+
});
|
|
19324
|
+
var restAreaInputSchema = object({
|
|
19325
|
+
until: exports_iso.date(),
|
|
19326
|
+
habits: _enum2(["keep", "pause"]).default("keep")
|
|
19327
|
+
}).strict();
|
|
19295
19328
|
|
|
19296
19329
|
// ../../packages/schema/src/presets/facets.ts
|
|
19297
19330
|
var PANTRY_DISPLAY_KIND = "pantry";
|
|
@@ -19609,11 +19642,18 @@ function setModuleEnabled(enabled, key, on) {
|
|
|
19609
19642
|
}
|
|
19610
19643
|
|
|
19611
19644
|
// ../../packages/schema/src/contracts/settings.ts
|
|
19645
|
+
var clockTimeSchema = string2().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "a time like 22:00");
|
|
19646
|
+
var quietHoursSchema = object({
|
|
19647
|
+
from: clockTimeSchema,
|
|
19648
|
+
until: clockTimeSchema,
|
|
19649
|
+
days: array(number2().int().min(0).max(6)).max(7)
|
|
19650
|
+
});
|
|
19612
19651
|
var PINNED_AREAS_MAX = 5;
|
|
19613
19652
|
var userSettingsSchema = createSelectSchema(userSettings, {
|
|
19614
19653
|
pinnedAreaIds: array(string2()).default([]),
|
|
19615
19654
|
focusAreaId: string2().nullable().default(null),
|
|
19616
19655
|
focusUntil: exports_coerce.date().nullable().default(null),
|
|
19656
|
+
quietHours: quietHoursSchema.nullable().default(null),
|
|
19617
19657
|
createdAt: exports_coerce.date(),
|
|
19618
19658
|
updatedAt: exports_coerce.date()
|
|
19619
19659
|
}).omit({ autoRollOverdue: true });
|
|
@@ -19629,7 +19669,8 @@ var updateUserSettingsInputSchema = createUpdateSchema(userSettings, {
|
|
|
19629
19669
|
backdrop: backdropIdSchema.nullish(),
|
|
19630
19670
|
enabledModules: array(string2().trim().min(1)).transform(normalizeEnabledModules).optional(),
|
|
19631
19671
|
pinnedAreaIds: array(ulidSchema).transform((ids) => [...new Set(ids)]).pipe(array(ulidSchema).max(PINNED_AREAS_MAX)).optional(),
|
|
19632
|
-
focusAreaId: ulidSchema.nullish()
|
|
19672
|
+
focusAreaId: ulidSchema.nullish(),
|
|
19673
|
+
quietHours: quietHoursSchema.nullish()
|
|
19633
19674
|
}).omit({
|
|
19634
19675
|
id: true,
|
|
19635
19676
|
tenantId: true,
|
|
@@ -19924,6 +19965,136 @@ var timeBlockRangeQuerySchema = object({
|
|
|
19924
19965
|
path: ["to"]
|
|
19925
19966
|
});
|
|
19926
19967
|
|
|
19968
|
+
// ../../packages/schema/src/contracts/plans.ts
|
|
19969
|
+
var PLAN_KINDS = ["week", "day"];
|
|
19970
|
+
var PLAN_ITEM_ACTIONS = ["plan", "someday", "let_go"];
|
|
19971
|
+
var PLAN_ITEM_STATES = ["proposed", "accepted", "dismissed"];
|
|
19972
|
+
var PLAN_STATUSES = ["open", "settled", "replaced"];
|
|
19973
|
+
var PLAN_MAX_ITEMS = 60;
|
|
19974
|
+
var PLAN_REASON_MAX = 280;
|
|
19975
|
+
var PLAN_NOTE_MAX = 2000;
|
|
19976
|
+
var planKindSchema = _enum2(PLAN_KINDS);
|
|
19977
|
+
var planItemActionSchema = _enum2(PLAN_ITEM_ACTIONS);
|
|
19978
|
+
var planItemStateSchema = _enum2(PLAN_ITEM_STATES);
|
|
19979
|
+
var planStatusSchema = _enum2(PLAN_STATUSES);
|
|
19980
|
+
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");
|
|
19981
|
+
var DAY_MS = 24 * 60 * 60 * 1000;
|
|
19982
|
+
function addCivilDays(day, n) {
|
|
19983
|
+
return new Date(Date.parse(`${day}T00:00:00Z`) + n * DAY_MS).toISOString().slice(0, 10);
|
|
19984
|
+
}
|
|
19985
|
+
function planDays(kind, startsOn) {
|
|
19986
|
+
return kind === "week" ? Array.from({ length: 7 }, (_, i) => addCivilDays(startsOn, i)) : [startsOn];
|
|
19987
|
+
}
|
|
19988
|
+
function isMonday(day) {
|
|
19989
|
+
return new Date(`${day}T00:00:00Z`).getUTCDay() === 1;
|
|
19990
|
+
}
|
|
19991
|
+
var planItemInputSchema = object({
|
|
19992
|
+
taskId: ulidSchema,
|
|
19993
|
+
action: planItemActionSchema.default("plan"),
|
|
19994
|
+
day: civilDaySchema.optional(),
|
|
19995
|
+
at: exports_coerce.date().optional(),
|
|
19996
|
+
durationMinutes: number2().int().min(5).max(24 * 60).optional(),
|
|
19997
|
+
reason: string2().trim().min(1).max(PLAN_REASON_MAX).optional()
|
|
19998
|
+
}).strict().superRefine((v, ctx) => {
|
|
19999
|
+
if (v.action === "plan") {
|
|
20000
|
+
if (v.day === undefined === (v.at === undefined)) {
|
|
20001
|
+
ctx.addIssue({
|
|
20002
|
+
code: "custom",
|
|
20003
|
+
message: "a `plan` item takes exactly one of `day` or `at`",
|
|
20004
|
+
path: ["day"]
|
|
20005
|
+
});
|
|
20006
|
+
}
|
|
20007
|
+
if (v.durationMinutes !== undefined && v.at === undefined) {
|
|
20008
|
+
ctx.addIssue({
|
|
20009
|
+
code: "custom",
|
|
20010
|
+
message: "`durationMinutes` goes with `at` (a time)",
|
|
20011
|
+
path: ["durationMinutes"]
|
|
20012
|
+
});
|
|
20013
|
+
}
|
|
20014
|
+
} else if (v.day !== undefined || v.at !== undefined || v.durationMinutes !== undefined) {
|
|
20015
|
+
ctx.addIssue({
|
|
20016
|
+
code: "custom",
|
|
20017
|
+
message: `a \`${v.action}\` item takes no day, time or duration`,
|
|
20018
|
+
path: ["action"]
|
|
20019
|
+
});
|
|
20020
|
+
}
|
|
20021
|
+
});
|
|
20022
|
+
var createPlanInputSchema = object({
|
|
20023
|
+
kind: planKindSchema,
|
|
20024
|
+
startsOn: civilDaySchema,
|
|
20025
|
+
forUserId: string2().min(1).optional(),
|
|
20026
|
+
note: string2().trim().min(1).max(PLAN_NOTE_MAX).optional(),
|
|
20027
|
+
items: array(planItemInputSchema).min(1).max(PLAN_MAX_ITEMS)
|
|
20028
|
+
}).strict().superRefine((v, ctx) => {
|
|
20029
|
+
if (v.kind === "week" && !isMonday(v.startsOn)) {
|
|
20030
|
+
ctx.addIssue({
|
|
20031
|
+
code: "custom",
|
|
20032
|
+
message: "a week plan starts on a Monday",
|
|
20033
|
+
path: ["startsOn"]
|
|
20034
|
+
});
|
|
20035
|
+
}
|
|
20036
|
+
const days = new Set(planDays(v.kind, v.startsOn));
|
|
20037
|
+
const seen = new Set;
|
|
20038
|
+
v.items.forEach((item, i) => {
|
|
20039
|
+
if (seen.has(item.taskId)) {
|
|
20040
|
+
ctx.addIssue({
|
|
20041
|
+
code: "custom",
|
|
20042
|
+
message: "one item per task",
|
|
20043
|
+
path: ["items", i, "taskId"]
|
|
20044
|
+
});
|
|
20045
|
+
}
|
|
20046
|
+
seen.add(item.taskId);
|
|
20047
|
+
if (item.day !== undefined && !days.has(item.day)) {
|
|
20048
|
+
ctx.addIssue({
|
|
20049
|
+
code: "custom",
|
|
20050
|
+
message: `the day falls outside the ${v.kind}`,
|
|
20051
|
+
path: ["items", i, "day"]
|
|
20052
|
+
});
|
|
20053
|
+
}
|
|
20054
|
+
});
|
|
20055
|
+
});
|
|
20056
|
+
var planDecisionInputSchema = object({ itemIds: array(string2().min(1)).min(1).max(PLAN_MAX_ITEMS).optional() }).strict();
|
|
20057
|
+
var planListQuerySchema = object({
|
|
20058
|
+
kind: planKindSchema.optional(),
|
|
20059
|
+
status: planStatusSchema.optional(),
|
|
20060
|
+
startsOn: civilDaySchema.optional()
|
|
20061
|
+
});
|
|
20062
|
+
var planTaskSchema = object({
|
|
20063
|
+
id: ulidSchema,
|
|
20064
|
+
title: string2(),
|
|
20065
|
+
status: taskStatusSchema,
|
|
20066
|
+
scheduledAt: exports_coerce.date().nullable(),
|
|
20067
|
+
allDay: boolean3(),
|
|
20068
|
+
dueAt: exports_coerce.date().nullable(),
|
|
20069
|
+
durationMinutes: number2().int().nullable(),
|
|
20070
|
+
areaId: string2().nullable(),
|
|
20071
|
+
projectId: string2().nullable()
|
|
20072
|
+
});
|
|
20073
|
+
var planItemViewSchema = object({
|
|
20074
|
+
id: string2(),
|
|
20075
|
+
taskId: ulidSchema,
|
|
20076
|
+
action: planItemActionSchema,
|
|
20077
|
+
scheduledAt: exports_coerce.date().nullable(),
|
|
20078
|
+
allDay: boolean3(),
|
|
20079
|
+
durationMinutes: number2().int().nullable(),
|
|
20080
|
+
reason: string2().nullable(),
|
|
20081
|
+
state: planItemStateSchema,
|
|
20082
|
+
decidedAt: exports_coerce.date().nullable(),
|
|
20083
|
+
task: planTaskSchema
|
|
20084
|
+
});
|
|
20085
|
+
var planViewSchema = object({
|
|
20086
|
+
id: string2(),
|
|
20087
|
+
kind: planKindSchema,
|
|
20088
|
+
startsOn: civilDaySchema,
|
|
20089
|
+
userId: string2(),
|
|
20090
|
+
agentId: string2().nullable(),
|
|
20091
|
+
note: string2().nullable(),
|
|
20092
|
+
status: planStatusSchema,
|
|
20093
|
+
createdAt: exports_coerce.date(),
|
|
20094
|
+
updatedAt: exports_coerce.date(),
|
|
20095
|
+
items: array(planItemViewSchema)
|
|
20096
|
+
});
|
|
20097
|
+
|
|
19927
20098
|
// ../../packages/schema/src/contracts/agent-chat.ts
|
|
19928
20099
|
var agentConnectionSourceSchema = _enum2(["space", "env"]);
|
|
19929
20100
|
var agentGatewayStatusSchema = _enum2(["connected", "offline", "not_connected"]);
|
|
@@ -20003,6 +20174,7 @@ var agentChangeSchema = object({
|
|
|
20003
20174
|
changedAt: exports_coerce.date(),
|
|
20004
20175
|
title: string2().nullable(),
|
|
20005
20176
|
actor: actorRefSchema,
|
|
20177
|
+
cursor: string2().optional(),
|
|
20006
20178
|
fields: array(agentChangeFieldSchema).optional(),
|
|
20007
20179
|
checkIn: object({
|
|
20008
20180
|
value: number2(),
|
|
@@ -20017,7 +20189,8 @@ var agentChangesPageSchema = object({
|
|
|
20017
20189
|
var agentChangesQuerySchema = object({
|
|
20018
20190
|
since: string2().refine((s) => s === "now" || /^\d+-\d+$/.test(s), {
|
|
20019
20191
|
message: "since must be a cursor from a prior page's nextCursor, or 'now'"
|
|
20020
|
-
})
|
|
20192
|
+
}),
|
|
20193
|
+
node: string2().trim().min(1).max(64).optional()
|
|
20021
20194
|
});
|
|
20022
20195
|
var agentConfirmationKindSchema = _enum2(["delete", "cancel", "google", "orca", "bulk"]);
|
|
20023
20196
|
var agentConfirmationStatusSchema = _enum2([
|
|
@@ -20660,16 +20833,21 @@ var EVERY_WEEKDAYS_RE = /\bevery\s+(?:weekday|weekdays|work\s?day|work\s?days)\b
|
|
|
20660
20833
|
var EVERY_WEEKDAY_RE = new RegExp(`\\bevery\\s+(${WEEKDAY_ALT})\\b`, "i");
|
|
20661
20834
|
var EVERY_UNIT_RE = /\bevery\s+(day|week|month|year)\b/i;
|
|
20662
20835
|
var ADVERB_RE = /\b(daily|weekly|monthly|yearly|annually)\b/i;
|
|
20663
|
-
var RELATIVE_RE = /\b(today|tonight|tomorrow|tmrw|tmr)\b/i;
|
|
20836
|
+
var RELATIVE_RE = /\b(today|tonight|tomorrow|tmrw|tmr|yesterday)\b/i;
|
|
20664
20837
|
var IN_N_RE = /\bin\s+(\d+)\s+(day|days|week|weeks|month|months)\b/i;
|
|
20665
20838
|
var NEXT_UNIT_RE = /\bnext\s+(week|month|year)\b/i;
|
|
20666
20839
|
var NEXT_WEEKDAY_RE = new RegExp(`\\bnext\\s+(${WEEKDAY_ALT})\\b`, "i");
|
|
20667
20840
|
var THIS_WEEKDAY_RE = new RegExp(`\\bthis\\s+(${WEEKDAY_ALT})\\b`, "i");
|
|
20841
|
+
var LAST_WEEKDAY_RE = new RegExp(`\\blast\\s+(${WEEKDAY_ALT})\\b`, "i");
|
|
20668
20842
|
var WEEKDAY_RE = new RegExp(`\\b(${WEEKDAY_ALT})\\b`, "i");
|
|
20669
20843
|
var TIME_AMPM_RE = /\b(?:at\s+)?(\d{1,2})(?::(\d{2}))?\s*(am|pm)\b/i;
|
|
20670
20844
|
var TIME_24H_RE = /\b(?:at\s+)?([01]?\d|2[0-3]):([0-5]\d)\b/;
|
|
20671
20845
|
var NOON_RE = /\b(noon|midday)\b/i;
|
|
20672
20846
|
var MIDNIGHT_RE = /\bmidnight\b/i;
|
|
20847
|
+
var DEADLINE_KEYWORD_RE = /\b(?:by|due(?:\s+on)?)\s+/gi;
|
|
20848
|
+
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;
|
|
20849
|
+
var ESTIMATE_H_RE = /(?<!\bin\s+)\b(?:for\s+)?(\d{1,2}(?:[.,]\d)?)\s?(?:h|hrs?|hours?)\b/i;
|
|
20850
|
+
var ESTIMATE_M_RE = /(?<!\bin\s+)\b(?:for\s+)?(\d{1,3})\s?(?:m|mins?|minutes?)\b/i;
|
|
20673
20851
|
function blank(s, start, end) {
|
|
20674
20852
|
return s.slice(0, start) + " ".repeat(end - start) + s.slice(end);
|
|
20675
20853
|
}
|
|
@@ -20705,18 +20883,84 @@ function parseWhen(input, now) {
|
|
|
20705
20883
|
scan = blank(scan, start, end);
|
|
20706
20884
|
return { span, index: start };
|
|
20707
20885
|
};
|
|
20886
|
+
const deadline = takeDeadline(scan, now);
|
|
20887
|
+
if (deadline) {
|
|
20888
|
+
spans.push(deadline.span);
|
|
20889
|
+
scan = blank(scan, deadline.span.start, deadline.span.end);
|
|
20890
|
+
}
|
|
20708
20891
|
const recurrence = takeRecurrence(scan, take);
|
|
20709
20892
|
const date = takeDate(scan, take, now);
|
|
20710
20893
|
const time = takeTime(scan, take);
|
|
20894
|
+
const durationMinutes = takeEstimate(scan, take);
|
|
20711
20895
|
const { scheduledAt, allDay } = resolveWhen({ now, recurrence, date, time });
|
|
20712
20896
|
spans.sort((a, b) => a.start - b.start);
|
|
20713
20897
|
return {
|
|
20714
20898
|
...scheduledAt ? { scheduledAt } : {},
|
|
20715
20899
|
allDay,
|
|
20716
20900
|
...recurrence ? { recurrence } : {},
|
|
20901
|
+
...deadline ? { dueAt: deadline.at, dueAllDay: deadline.allDay } : {},
|
|
20902
|
+
...durationMinutes ? { durationMinutes } : {},
|
|
20717
20903
|
spans
|
|
20718
20904
|
};
|
|
20719
20905
|
}
|
|
20906
|
+
function anchored(re) {
|
|
20907
|
+
return new RegExp(`^${re.source.replace(/^\\b/, "")}`, re.flags.replace("g", ""));
|
|
20908
|
+
}
|
|
20909
|
+
var DEADLINE_DATES = [
|
|
20910
|
+
IN_N_RE,
|
|
20911
|
+
NEXT_WEEKDAY_RE,
|
|
20912
|
+
THIS_WEEKDAY_RE,
|
|
20913
|
+
NEXT_UNIT_RE,
|
|
20914
|
+
RELATIVE_RE,
|
|
20915
|
+
WEEKDAY_RE
|
|
20916
|
+
].map(anchored);
|
|
20917
|
+
var DEADLINE_TIMES = [TIME_AMPM_RE, TIME_24H_RE, NOON_RE, MIDNIGHT_RE].map(anchored);
|
|
20918
|
+
var accept = (m) => m?.index === undefined ? null : { span: { start: 0, end: 0, kind: "deadline" }, index: m.index };
|
|
20919
|
+
function firstMatch(text, res) {
|
|
20920
|
+
for (const re of res) {
|
|
20921
|
+
const m = re.exec(text);
|
|
20922
|
+
if (m)
|
|
20923
|
+
return m[0];
|
|
20924
|
+
}
|
|
20925
|
+
return null;
|
|
20926
|
+
}
|
|
20927
|
+
function takeDeadline(scan, now) {
|
|
20928
|
+
for (const keyword of scan.matchAll(DEADLINE_KEYWORD_RE)) {
|
|
20929
|
+
const start = keyword.index ?? 0;
|
|
20930
|
+
let end = start + keyword[0].length;
|
|
20931
|
+
const dateText = firstMatch(scan.slice(end), DEADLINE_DATES);
|
|
20932
|
+
const date = dateText ? takeDate(dateText, accept, now) : null;
|
|
20933
|
+
if (dateText && date)
|
|
20934
|
+
end += dateText.length;
|
|
20935
|
+
const gap = /^\s*/.exec(scan.slice(end))?.[0] ?? "";
|
|
20936
|
+
const timeText = firstMatch(scan.slice(end + gap.length), DEADLINE_TIMES);
|
|
20937
|
+
const time = timeText ? takeTime(timeText, accept) : null;
|
|
20938
|
+
if (timeText && time)
|
|
20939
|
+
end += gap.length + timeText.length;
|
|
20940
|
+
if (!date && !time)
|
|
20941
|
+
continue;
|
|
20942
|
+
const day = date?.day ?? startOfDay(now);
|
|
20943
|
+
const span = { start, end, kind: "deadline" };
|
|
20944
|
+
if (!time)
|
|
20945
|
+
return { at: day, allDay: true, span };
|
|
20946
|
+
const at = new Date(day);
|
|
20947
|
+
at.setHours(time.hours, time.minutes, 0, 0);
|
|
20948
|
+
return { at, allDay: false, span };
|
|
20949
|
+
}
|
|
20950
|
+
return null;
|
|
20951
|
+
}
|
|
20952
|
+
function takeEstimate(scan, take) {
|
|
20953
|
+
const hm = scan.match(ESTIMATE_HM_RE);
|
|
20954
|
+
if (hm && take(hm, "estimate"))
|
|
20955
|
+
return Number(hm[1]) * 60 + Number(hm[2]);
|
|
20956
|
+
const h = scan.match(ESTIMATE_H_RE);
|
|
20957
|
+
if (h && take(h, "estimate"))
|
|
20958
|
+
return Math.round(Number((h[1] ?? "0").replace(",", ".")) * 60);
|
|
20959
|
+
const m = scan.match(ESTIMATE_M_RE);
|
|
20960
|
+
if (m && take(m, "estimate"))
|
|
20961
|
+
return Number(m[1]);
|
|
20962
|
+
return;
|
|
20963
|
+
}
|
|
20720
20964
|
function takeRecurrence(scan, take) {
|
|
20721
20965
|
const everyN = scan.match(EVERY_N_RE);
|
|
20722
20966
|
if (everyN && take(everyN, "recurrence")) {
|
|
@@ -20781,11 +21025,18 @@ function takeDate(scan, take, now) {
|
|
|
20781
21025
|
return on(addMonths(today, 1));
|
|
20782
21026
|
return on(addMonths(today, 12));
|
|
20783
21027
|
}
|
|
21028
|
+
const lastWeekday = scan.match(LAST_WEEKDAY_RE);
|
|
21029
|
+
if (lastWeekday && take(lastWeekday, "date")) {
|
|
21030
|
+
const { dow } = weekdayByName(lastWeekday[1]);
|
|
21031
|
+
return on(addDays(today, -((now.getDay() - dow + 7) % 7 || 7)));
|
|
21032
|
+
}
|
|
20784
21033
|
const relative = scan.match(RELATIVE_RE);
|
|
20785
21034
|
if (relative && take(relative, "date")) {
|
|
20786
21035
|
const word = (relative[1] ?? "").toLowerCase();
|
|
20787
21036
|
if (word === "tonight")
|
|
20788
21037
|
return on(today, true);
|
|
21038
|
+
if (word === "yesterday")
|
|
21039
|
+
return on(addDays(today, -1));
|
|
20789
21040
|
return on(word === "today" ? today : addDays(today, 1));
|
|
20790
21041
|
}
|
|
20791
21042
|
const weekday = scan.match(WEEKDAY_RE);
|
|
@@ -20892,6 +21143,8 @@ function parseCapture(input, now) {
|
|
|
20892
21143
|
...starred ? { starred } : {},
|
|
20893
21144
|
...area ? { area } : {},
|
|
20894
21145
|
...when?.scheduledAt ? { scheduledAt: when.scheduledAt, allDay: when.allDay } : {},
|
|
21146
|
+
...when?.dueAt ? { dueAt: when.dueAt, ...when.scheduledAt ? {} : { allDay: when.dueAllDay } } : {},
|
|
21147
|
+
...when?.durationMinutes ? { durationMinutes: when.durationMinutes } : {},
|
|
20895
21148
|
...when?.recurrence ? { recurrence: when.recurrence } : {}
|
|
20896
21149
|
};
|
|
20897
21150
|
}
|
|
@@ -21351,7 +21604,7 @@ function parseCapture2(input, now) {
|
|
|
21351
21604
|
|
|
21352
21605
|
// src/meta.ts
|
|
21353
21606
|
var CLI_NAME = "do";
|
|
21354
|
-
var VERSION = "1.
|
|
21607
|
+
var VERSION = "1.176.0";
|
|
21355
21608
|
var API_PREFIX = "/api/v1";
|
|
21356
21609
|
var DEFAULT_API_URL = "http://localhost:3001";
|
|
21357
21610
|
|
|
@@ -21605,6 +21858,11 @@ var ENTITIES = [
|
|
|
21605
21858
|
name: "ready",
|
|
21606
21859
|
description: "only open tasks their Owner marked Ready for you; --ready=false for the rest",
|
|
21607
21860
|
boolean: true
|
|
21861
|
+
},
|
|
21862
|
+
{
|
|
21863
|
+
name: "drifted",
|
|
21864
|
+
description: "only open tasks whose planned day moved 3+ times; --drifted=false for the rest",
|
|
21865
|
+
boolean: true
|
|
21608
21866
|
}
|
|
21609
21867
|
],
|
|
21610
21868
|
write: {
|
|
@@ -21933,15 +22191,35 @@ var COMMAND_SPECS = [
|
|
|
21933
22191
|
{
|
|
21934
22192
|
name: "text",
|
|
21935
22193
|
required: true,
|
|
21936
|
-
description: "Task title; may embed #tag, ! (star) and
|
|
22194
|
+
description: "Task title; may embed #tag, ! (star), ~area, a when, a deadline and an estimate (see grammar)."
|
|
22195
|
+
}
|
|
22196
|
+
],
|
|
22197
|
+
flags: [
|
|
22198
|
+
{
|
|
22199
|
+
name: "at",
|
|
22200
|
+
type: "string",
|
|
22201
|
+
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."
|
|
22202
|
+
},
|
|
22203
|
+
{
|
|
22204
|
+
name: "due",
|
|
22205
|
+
type: "string",
|
|
22206
|
+
description: "The Deadline, ISO date/time (overrides a by/due phrase in the text)."
|
|
22207
|
+
},
|
|
22208
|
+
{
|
|
22209
|
+
name: "duration",
|
|
22210
|
+
type: "string",
|
|
22211
|
+
description: "How long it takes, in minutes (overrides an estimate in the text)."
|
|
21937
22212
|
}
|
|
21938
22213
|
],
|
|
21939
|
-
flags: [],
|
|
21940
22214
|
scopes: ["tasks:write"],
|
|
21941
22215
|
returns: "Task",
|
|
21942
22216
|
grammar: {
|
|
21943
22217
|
"#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."
|
|
22218
|
+
"!": "Stars the task \u2014 the 1\u20132 things that matter (aliases: !star/!s/!flag/!f). Drives Today's focus and the starred filter.",
|
|
22219
|
+
"~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`).",
|
|
22220
|
+
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.",
|
|
22221
|
+
"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.",
|
|
22222
|
+
estimate: "How long it takes (durationMinutes): '30m', '45 min', '1h', '1.5h', '1h30', '2 hours', 'for 20 mins'. Not 'in 2 hours'."
|
|
21945
22223
|
}
|
|
21946
22224
|
},
|
|
21947
22225
|
{
|
|
@@ -22121,6 +22399,84 @@ var COMMAND_SPECS = [
|
|
|
22121
22399
|
returns: "Task",
|
|
22122
22400
|
notes: "POST /api/v1/tasks/{id}/not-now (#889); 409 when the task isn't a proposal. Let go is `do task cancel`."
|
|
22123
22401
|
},
|
|
22402
|
+
{
|
|
22403
|
+
path: ["plans", "list"],
|
|
22404
|
+
summary: "Plan proposals: the weeks and days the agent proposed, newest first, each item with its answer.",
|
|
22405
|
+
args: [],
|
|
22406
|
+
flags: [
|
|
22407
|
+
{ name: "kind", type: "string", description: "week or day." },
|
|
22408
|
+
{ name: "status", type: "string", description: "open, settled or replaced." },
|
|
22409
|
+
{
|
|
22410
|
+
name: "starts-on",
|
|
22411
|
+
type: "string",
|
|
22412
|
+
description: "The week's Monday, or the day (YYYY-MM-DD)."
|
|
22413
|
+
},
|
|
22414
|
+
{ name: "for", type: "string", description: "Only this member's plans (user id)." }
|
|
22415
|
+
],
|
|
22416
|
+
scopes: ["tasks:read"],
|
|
22417
|
+
returns: "Plan[]",
|
|
22418
|
+
notes: "GET /api/v1/plans (#912): the latest 20."
|
|
22419
|
+
},
|
|
22420
|
+
{
|
|
22421
|
+
path: ["plans", "get"],
|
|
22422
|
+
summary: "One plan proposal with its items and each item's task as it is now.",
|
|
22423
|
+
args: [{ name: "id", required: true, description: "Plan id." }],
|
|
22424
|
+
flags: [],
|
|
22425
|
+
scopes: ["tasks:read"],
|
|
22426
|
+
returns: "Plan",
|
|
22427
|
+
notes: "GET /api/v1/plans/{id} (#912)."
|
|
22428
|
+
},
|
|
22429
|
+
{
|
|
22430
|
+
path: ["plans", "propose"],
|
|
22431
|
+
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.",
|
|
22432
|
+
args: [],
|
|
22433
|
+
flags: [
|
|
22434
|
+
{
|
|
22435
|
+
name: "data",
|
|
22436
|
+
type: "string",
|
|
22437
|
+
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"}]}.'
|
|
22438
|
+
}
|
|
22439
|
+
],
|
|
22440
|
+
scopes: ["tasks:write"],
|
|
22441
|
+
returns: "Plan",
|
|
22442
|
+
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."
|
|
22443
|
+
},
|
|
22444
|
+
{
|
|
22445
|
+
path: ["plans", "accept"],
|
|
22446
|
+
summary: "Accept a plan's proposed items (all of them, or the item ids given): each task is written as the item says.",
|
|
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}/accept (#912). Undo with `do plans undo`."
|
|
22455
|
+
},
|
|
22456
|
+
{
|
|
22457
|
+
path: ["plans", "dismiss"],
|
|
22458
|
+
summary: "Leave a plan's proposed items out (all of them, or the item ids given).",
|
|
22459
|
+
args: [
|
|
22460
|
+
{ name: "id", required: true, description: "Plan id." },
|
|
22461
|
+
{ name: "items", required: false, description: "Item ids (default: every proposed item)." }
|
|
22462
|
+
],
|
|
22463
|
+
flags: [],
|
|
22464
|
+
scopes: ["tasks:write"],
|
|
22465
|
+
returns: "Plan",
|
|
22466
|
+
notes: "POST /api/v1/plans/{id}/dismiss (#912). Nothing is written to a task."
|
|
22467
|
+
},
|
|
22468
|
+
{
|
|
22469
|
+
path: ["plans", "undo"],
|
|
22470
|
+
summary: "Undo a plan's answers (all of them, or the item ids given): accepted tasks go back as they were.",
|
|
22471
|
+
args: [
|
|
22472
|
+
{ name: "id", required: true, description: "Plan id." },
|
|
22473
|
+
{ name: "items", required: false, description: "Item ids (default: every answered item)." }
|
|
22474
|
+
],
|
|
22475
|
+
flags: [],
|
|
22476
|
+
scopes: ["tasks:write"],
|
|
22477
|
+
returns: "Plan",
|
|
22478
|
+
notes: "POST /api/v1/plans/{id}/undo (#912). The items read as proposed again."
|
|
22479
|
+
},
|
|
22124
22480
|
{
|
|
22125
22481
|
path: ["tasks", "ready"],
|
|
22126
22482
|
summary: "Mark a task Ready for you: its Owner finished something the member should look at (a draft, a link, a worktree).",
|
|
@@ -22150,7 +22506,35 @@ var COMMAND_SPECS = [
|
|
|
22150
22506
|
args: [{ name: "id", required: true, description: "Task ULID." }],
|
|
22151
22507
|
flags: [
|
|
22152
22508
|
{ name: "days", type: "string", description: "Days from now to reschedule.", default: "1" },
|
|
22153
|
-
{
|
|
22509
|
+
{
|
|
22510
|
+
name: "until",
|
|
22511
|
+
type: "string",
|
|
22512
|
+
description: "Absolute date/time (ISO) to reschedule to. With a time it becomes a timed task (allDay false); a date only stays all-day."
|
|
22513
|
+
},
|
|
22514
|
+
{
|
|
22515
|
+
name: "duration",
|
|
22516
|
+
type: "string",
|
|
22517
|
+
description: "How long it takes, in minutes (sets durationMinutes)."
|
|
22518
|
+
}
|
|
22519
|
+
],
|
|
22520
|
+
scopes: ["tasks:write"],
|
|
22521
|
+
returns: "Task"
|
|
22522
|
+
},
|
|
22523
|
+
{
|
|
22524
|
+
path: ["task", "schedule"],
|
|
22525
|
+
summary: "Plan a task at a day or a time, so a timed one shows on the calendar.",
|
|
22526
|
+
args: [{ name: "id", required: true, description: "Task ULID." }],
|
|
22527
|
+
flags: [
|
|
22528
|
+
{
|
|
22529
|
+
name: "at",
|
|
22530
|
+
type: "string",
|
|
22531
|
+
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."
|
|
22532
|
+
},
|
|
22533
|
+
{
|
|
22534
|
+
name: "duration",
|
|
22535
|
+
type: "string",
|
|
22536
|
+
description: "How long it takes, in minutes (sets durationMinutes)."
|
|
22537
|
+
}
|
|
22154
22538
|
],
|
|
22155
22539
|
scopes: ["tasks:write"],
|
|
22156
22540
|
returns: "Task"
|
|
@@ -22750,6 +23134,22 @@ var COMMAND_SPECS = [
|
|
|
22750
23134
|
returns: "EchoThread",
|
|
22751
23135
|
notes: "When the current thread is still empty, that one is returned instead."
|
|
22752
23136
|
},
|
|
23137
|
+
{
|
|
23138
|
+
path: ["echo", "threads", "push"],
|
|
23139
|
+
summary: "Set what one of your agent threads may push to your devices.",
|
|
23140
|
+
args: [
|
|
23141
|
+
{ name: "id", required: true, description: "Thread ULID (see `echo threads list`)." },
|
|
23142
|
+
{
|
|
23143
|
+
name: "setting",
|
|
23144
|
+
required: true,
|
|
23145
|
+
description: "all (Needs you, and Told quietly), needs_you (only what waits on you) or off."
|
|
23146
|
+
}
|
|
23147
|
+
],
|
|
23148
|
+
flags: [],
|
|
23149
|
+
scopes: ["agents:write"],
|
|
23150
|
+
returns: "EchoThreadSummary",
|
|
23151
|
+
notes: "For later and Silent posts never push. The home thread and conversations start at all, a standing thread you add at needs_you."
|
|
23152
|
+
},
|
|
22753
23153
|
{
|
|
22754
23154
|
path: ["settings", "get"],
|
|
22755
23155
|
summary: "Show the caller's settings for the Space.",
|
|
@@ -23062,12 +23462,17 @@ var COMMAND_SPECS = [
|
|
|
23062
23462
|
{
|
|
23063
23463
|
name: "until",
|
|
23064
23464
|
type: "string",
|
|
23065
|
-
description: "The day it's back (YYYY-MM-DD). Required."
|
|
23465
|
+
description: "The day it's back (YYYY-MM-DD, after today). Required."
|
|
23466
|
+
},
|
|
23467
|
+
{
|
|
23468
|
+
name: "pause-habits",
|
|
23469
|
+
type: "boolean",
|
|
23470
|
+
description: "Pause the Area's habits until then too (they keep going otherwise)."
|
|
23066
23471
|
}
|
|
23067
23472
|
],
|
|
23068
23473
|
scopes: ["areas:write"],
|
|
23069
23474
|
returns: "AreaView",
|
|
23070
|
-
notes: "
|
|
23475
|
+
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
23476
|
},
|
|
23072
23477
|
{
|
|
23073
23478
|
path: ["areas", "back"],
|
|
@@ -23076,7 +23481,25 @@ var COMMAND_SPECS = [
|
|
|
23076
23481
|
flags: [],
|
|
23077
23482
|
scopes: ["areas:write"],
|
|
23078
23483
|
returns: "AreaView",
|
|
23079
|
-
notes: "
|
|
23484
|
+
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."
|
|
23485
|
+
},
|
|
23486
|
+
{
|
|
23487
|
+
path: ["areas", "since"],
|
|
23488
|
+
summary: "What happened in an Area since you last looked: landed, new, and what seems stuck.",
|
|
23489
|
+
args: [{ name: "id", required: true, description: "Area ULID." }],
|
|
23490
|
+
flags: [],
|
|
23491
|
+
scopes: ["areas:read"],
|
|
23492
|
+
returns: "AreaSinceLastLook",
|
|
23493
|
+
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").'
|
|
23494
|
+
},
|
|
23495
|
+
{
|
|
23496
|
+
path: ["areas", "looked-at"],
|
|
23497
|
+
summary: "Record a look at an Area: its review rhythm starts again from now.",
|
|
23498
|
+
args: [{ name: "id", required: true, description: "Area ULID." }],
|
|
23499
|
+
flags: [],
|
|
23500
|
+
scopes: ["areas:write"],
|
|
23501
|
+
returns: "AreaView",
|
|
23502
|
+
notes: "POST /api/v1/areas/{id}/looked-at (Areas 6). The Area leaves Review until its rhythm (weekly, monthly, quarterly) comes round."
|
|
23080
23503
|
},
|
|
23081
23504
|
{
|
|
23082
23505
|
path: ["areas", "focus"],
|
|
@@ -23797,18 +24220,52 @@ function renderToday(data, mode) {
|
|
|
23797
24220
|
${habits.join(`
|
|
23798
24221
|
`)}` : tasks;
|
|
23799
24222
|
}
|
|
23800
|
-
var
|
|
24223
|
+
var DAY_MS2 = 24 * 60 * 60 * 1000;
|
|
23801
24224
|
function plannedDay(at, from) {
|
|
23802
24225
|
if (!at)
|
|
23803
24226
|
return "earlier";
|
|
23804
24227
|
const day = new Date(at);
|
|
23805
|
-
const ago = from ? Math.round((Date.parse(from) - day.getTime()) /
|
|
24228
|
+
const ago = from ? Math.round((Date.parse(from) - day.getTime()) / DAY_MS2) : 7;
|
|
23806
24229
|
if (ago <= 1)
|
|
23807
24230
|
return "yesterday";
|
|
23808
24231
|
if (ago < 7)
|
|
23809
24232
|
return day.toLocaleDateString("en-GB", { weekday: "long" });
|
|
23810
24233
|
return day.toLocaleDateString("en-GB", { day: "numeric", month: "short" });
|
|
23811
24234
|
}
|
|
24235
|
+
function planItemWords(i) {
|
|
24236
|
+
if (i.action === "someday")
|
|
24237
|
+
return "back to Someday";
|
|
24238
|
+
if (i.action === "let_go")
|
|
24239
|
+
return "let go";
|
|
24240
|
+
if (!i.scheduledAt)
|
|
24241
|
+
return "planned";
|
|
24242
|
+
const at = new Date(i.scheduledAt);
|
|
24243
|
+
const day = at.toLocaleDateString("en-GB", { weekday: "short", day: "numeric", month: "short" });
|
|
24244
|
+
return i.allDay ? day : `${day} ${at.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" })}`;
|
|
24245
|
+
}
|
|
24246
|
+
function renderPlan(data, mode) {
|
|
24247
|
+
const p = data;
|
|
24248
|
+
const head = `${p.kind === "day" ? "Day" : "Week"} of ${p.startsOn ?? "?"} \xB7 ${p.status ?? "?"}`;
|
|
24249
|
+
const items = (p.items ?? []).map((i) => {
|
|
24250
|
+
const state = i.state === "proposed" ? "\xB7" : i.state === "accepted" ? "\u2713" : "\u2013";
|
|
24251
|
+
const reason = i.reason ? paint(mode, "dim", ` \u2014 ${i.reason}`) : "";
|
|
24252
|
+
return ` ${state} ${i.task?.title ?? "(untitled)"}: ${planItemWords(i)}${reason} ${paint(mode, "dim", i.id)}`;
|
|
24253
|
+
});
|
|
24254
|
+
return [
|
|
24255
|
+
`${paint(mode, "bold", head)} ${paint(mode, "dim", p.id)}`,
|
|
24256
|
+
...p.note ? [paint(mode, "dim", p.note)] : [],
|
|
24257
|
+
...items
|
|
24258
|
+
].join(`
|
|
24259
|
+
`);
|
|
24260
|
+
}
|
|
24261
|
+
function renderPlans(data, mode) {
|
|
24262
|
+
const plans = data ?? [];
|
|
24263
|
+
if (plans.length === 0)
|
|
24264
|
+
return paint(mode, "dim", "No plans.");
|
|
24265
|
+
return plans.map((p) => renderPlan(p, mode)).join(`
|
|
24266
|
+
|
|
24267
|
+
`);
|
|
24268
|
+
}
|
|
23812
24269
|
function renderFromEarlier(data, mode) {
|
|
23813
24270
|
const d = data;
|
|
23814
24271
|
const tasks = d.tasks ?? [];
|
|
@@ -23848,6 +24305,28 @@ function renderAreaOverview(data, mode) {
|
|
|
23848
24305
|
return out.join(`
|
|
23849
24306
|
`);
|
|
23850
24307
|
}
|
|
24308
|
+
function renderSinceLastLook(data, mode) {
|
|
24309
|
+
const d = data;
|
|
24310
|
+
const day = new Date(d.since).toLocaleDateString("en-GB", { day: "numeric", month: "long" });
|
|
24311
|
+
const out = [
|
|
24312
|
+
paint(mode, "bold", d.lastReviewedAt ? `Since you last looked, on ${day}` : `Since ${day} (never looked at)`)
|
|
24313
|
+
];
|
|
24314
|
+
const group = (label, items, line) => {
|
|
24315
|
+
if (items.length === 0)
|
|
24316
|
+
return;
|
|
24317
|
+
out.push(` ${label}`);
|
|
24318
|
+
for (const i of items)
|
|
24319
|
+
out.push(` \xB7 ${line(i)}`);
|
|
24320
|
+
};
|
|
24321
|
+
group("Landed", d.landed, (i) => i.by ? `${i.title} (${i.by})` : i.title);
|
|
24322
|
+
group("New", d.fresh, (i) => i.title);
|
|
24323
|
+
group("Stuck?", d.stuck, (i) => i.why ? `${i.title} ${paint(mode, "dim", `\xB7 ${i.why}`)}` : i.title);
|
|
24324
|
+
if (d.landed.length + d.fresh.length + d.stuck.length === 0) {
|
|
24325
|
+
out.push(paint(mode, "dim", " Nothing landed, new or stuck."));
|
|
24326
|
+
}
|
|
24327
|
+
return out.join(`
|
|
24328
|
+
`);
|
|
24329
|
+
}
|
|
23851
24330
|
function renderFromEarlierAnswer(data, mode) {
|
|
23852
24331
|
const d = data;
|
|
23853
24332
|
const n = d.tasks ?? 0;
|
|
@@ -24279,9 +24758,24 @@ function renderProgress(data, mode) {
|
|
|
24279
24758
|
}
|
|
24280
24759
|
const areas = p.areas ?? [];
|
|
24281
24760
|
if (areas.length > 0) {
|
|
24282
|
-
out.push("", paint(mode, "bold", "
|
|
24283
|
-
|
|
24284
|
-
out.push(` ${
|
|
24761
|
+
out.push("", paint(mode, "bold", "Where your attention went"));
|
|
24762
|
+
if (p.areasHighlight)
|
|
24763
|
+
out.push(` ${p.areasHighlight}`);
|
|
24764
|
+
for (const a of areas) {
|
|
24765
|
+
const total = a.total ?? a.tasksDone;
|
|
24766
|
+
if (a.total === undefined) {
|
|
24767
|
+
out.push(` ${a.name}: ${total}`);
|
|
24768
|
+
continue;
|
|
24769
|
+
}
|
|
24770
|
+
const mine = (a.tasksDoneMine ?? 0) + (a.habitsDone ?? 0) + (a.blocksDone ?? 0);
|
|
24771
|
+
const parts = [
|
|
24772
|
+
`${a.tasksDone} task(s)`,
|
|
24773
|
+
`${a.habitsDone ?? 0} check-in(s)`,
|
|
24774
|
+
`${a.blocksDone ?? 0} block(s)`
|
|
24775
|
+
].join(", ");
|
|
24776
|
+
const split = a.tasksDoneAgents ? ` \xB7 you ${mine}, agents ${a.tasksDoneAgents}` : "";
|
|
24777
|
+
out.push(` ${a.name}: ${total}${split} ${paint(mode, "dim", `(${parts}; ${a.previousTotal ?? 0} before)`)}`);
|
|
24778
|
+
}
|
|
24285
24779
|
}
|
|
24286
24780
|
const habits = p.habits ?? [];
|
|
24287
24781
|
if (habits.length > 0) {
|
|
@@ -24587,11 +25081,17 @@ function runFixed(spec, ctx) {
|
|
|
24587
25081
|
return handler(ctx);
|
|
24588
25082
|
}
|
|
24589
25083
|
var HANDLERS = {
|
|
24590
|
-
capture: async ({ args, io, requireClient }) => {
|
|
25084
|
+
capture: async ({ args, flags, io, requireClient }) => {
|
|
24591
25085
|
const text = args.join(" ").trim();
|
|
24592
25086
|
if (text.length === 0)
|
|
24593
25087
|
throw usageError('capture needs some text, e.g. `do capture "call mum #family"`');
|
|
24594
25088
|
const parsed = parseCapture2(text, io.now());
|
|
25089
|
+
const at = flagInstant(flags, "at");
|
|
25090
|
+
const due = flagInstant(flags, "due");
|
|
25091
|
+
const duration = flagMinutes(flags, "duration") ?? parsed.durationMinutes;
|
|
25092
|
+
const scheduledAt = at?.at ?? parsed.scheduledAt;
|
|
25093
|
+
const dueAt = due?.at ?? parsed.dueAt;
|
|
25094
|
+
const allDay = at ? at.allDay : parsed.scheduledAt ? parsed.allDay : due?.allDay ?? parsed.allDay;
|
|
24595
25095
|
const id = ulid3();
|
|
24596
25096
|
const input = parseInput(createTaskInputSchema, {
|
|
24597
25097
|
id,
|
|
@@ -24599,7 +25099,10 @@ var HANDLERS = {
|
|
|
24599
25099
|
tags: parsed.tags,
|
|
24600
25100
|
...parsed.starred ? { starred: parsed.starred } : {},
|
|
24601
25101
|
...parsed.area ? { areaName: parsed.area } : {},
|
|
24602
|
-
...
|
|
25102
|
+
...scheduledAt ? { scheduledAt } : {},
|
|
25103
|
+
...dueAt ? { dueAt } : {},
|
|
25104
|
+
...scheduledAt || dueAt ? { allDay } : {},
|
|
25105
|
+
...duration ? { durationMinutes: duration } : {}
|
|
24603
25106
|
});
|
|
24604
25107
|
const client = requireClient();
|
|
24605
25108
|
const task = await client.post("/tasks", input);
|
|
@@ -24909,6 +25412,30 @@ var HANDLERS = {
|
|
|
24909
25412
|
const id = requireArg(args, 0, "task id");
|
|
24910
25413
|
return { data: await requireClient().post(`/tasks/${id}/not-now`, {}), render: renderTask };
|
|
24911
25414
|
},
|
|
25415
|
+
"plans list": async ({ flags, requireClient }) => {
|
|
25416
|
+
const plans = await requireClient().get("/plans", {
|
|
25417
|
+
query: {
|
|
25418
|
+
kind: flagString(flags, "kind"),
|
|
25419
|
+
status: flagString(flags, "status"),
|
|
25420
|
+
startsOn: flagString(flags, "starts-on"),
|
|
25421
|
+
forUserId: flagString(flags, "for")
|
|
25422
|
+
}
|
|
25423
|
+
});
|
|
25424
|
+
return { data: plans, render: renderPlans };
|
|
25425
|
+
},
|
|
25426
|
+
"plans get": async ({ args, requireClient }) => {
|
|
25427
|
+
const id = requireArg(args, 0, "plan id");
|
|
25428
|
+
return { data: await requireClient().get(`/plans/${id}`), render: renderPlan };
|
|
25429
|
+
},
|
|
25430
|
+
"plans propose": async ({ flags, requireClient }) => {
|
|
25431
|
+
if (flagString(flags, "data") === undefined)
|
|
25432
|
+
throw usageError("plans propose needs --data");
|
|
25433
|
+
const body = parseInput(createPlanInputSchema, readDataFlag(flags));
|
|
25434
|
+
return { data: await requireClient().post("/plans", body), render: renderPlan };
|
|
25435
|
+
},
|
|
25436
|
+
"plans accept": async (ctx) => planDecision(ctx, "accept"),
|
|
25437
|
+
"plans dismiss": async (ctx) => planDecision(ctx, "dismiss"),
|
|
25438
|
+
"plans undo": async (ctx) => planDecision(ctx, "undo"),
|
|
24912
25439
|
"tasks ready": async ({ args, flags, requireClient }) => {
|
|
24913
25440
|
const id = requireArg(args, 0, "task id");
|
|
24914
25441
|
const note = flagString(flags, "note");
|
|
@@ -24923,7 +25450,26 @@ var HANDLERS = {
|
|
|
24923
25450
|
"task snooze": async ({ args, flags, io, requireClient }) => {
|
|
24924
25451
|
const id = requireArg(args, 0, "task id");
|
|
24925
25452
|
const target = computeSnoozeTarget(flags, io.now());
|
|
24926
|
-
const
|
|
25453
|
+
const until = flagInstant(flags, "until");
|
|
25454
|
+
const duration = flagMinutes(flags, "duration");
|
|
25455
|
+
const body = parseInput(updateTaskInputSchema, {
|
|
25456
|
+
scheduledAt: target,
|
|
25457
|
+
...until ? { allDay: until.allDay } : {},
|
|
25458
|
+
...duration ? { durationMinutes: duration } : {}
|
|
25459
|
+
});
|
|
25460
|
+
return { data: await requireClient().patch(`/tasks/${id}`, body), render: renderTask };
|
|
25461
|
+
},
|
|
25462
|
+
"task schedule": async ({ args, flags, requireClient }) => {
|
|
25463
|
+
const id = requireArg(args, 0, "task id");
|
|
25464
|
+
const at = flagInstant(flags, "at");
|
|
25465
|
+
if (!at)
|
|
25466
|
+
throw usageError("task schedule needs --at, e.g. --at '2026-09-09T16:00:00+02:00'");
|
|
25467
|
+
const duration = flagMinutes(flags, "duration");
|
|
25468
|
+
const body = parseInput(updateTaskInputSchema, {
|
|
25469
|
+
scheduledAt: at.at,
|
|
25470
|
+
allDay: at.allDay,
|
|
25471
|
+
...duration ? { durationMinutes: duration } : {}
|
|
25472
|
+
});
|
|
24927
25473
|
return { data: await requireClient().patch(`/tasks/${id}`, body), render: renderTask };
|
|
24928
25474
|
},
|
|
24929
25475
|
"habit log": async ({ args, flags, requireClient }) => {
|
|
@@ -25155,6 +25701,12 @@ var HANDLERS = {
|
|
|
25155
25701
|
data: await requireClient().post("/echo/threads", {}),
|
|
25156
25702
|
render: renderEchoThread
|
|
25157
25703
|
}),
|
|
25704
|
+
"echo threads push": async ({ args, requireClient }) => ({
|
|
25705
|
+
data: await requireClient().patch(`/echo/threads/${requireArg(args, 0, "thread id")}`, {
|
|
25706
|
+
push: requireArg(args, 1, "push setting (needs_you, all or off)")
|
|
25707
|
+
}),
|
|
25708
|
+
render: renderRecord
|
|
25709
|
+
}),
|
|
25158
25710
|
"settings get": async ({ requireClient }) => ({
|
|
25159
25711
|
data: await requireClient().get("/settings"),
|
|
25160
25712
|
render: renderRecord
|
|
@@ -25356,13 +25908,32 @@ var HANDLERS = {
|
|
|
25356
25908
|
const until = flagString(flags, "until");
|
|
25357
25909
|
if (!until)
|
|
25358
25910
|
throw usageError("areas rest needs --until <YYYY-MM-DD>, the day it's back");
|
|
25359
|
-
const input = parseInput(
|
|
25360
|
-
|
|
25911
|
+
const input = parseInput(restAreaInputSchema, {
|
|
25912
|
+
until,
|
|
25913
|
+
habits: flagBool(flags, "pause-habits") ? "pause" : "keep"
|
|
25914
|
+
});
|
|
25915
|
+
return { data: await requireClient().post(`/areas/${id}/rest`, input), render: renderRecord };
|
|
25361
25916
|
},
|
|
25362
25917
|
"areas back": async ({ args, requireClient }) => {
|
|
25363
25918
|
const id = requireArg(args, 0, "area id");
|
|
25364
|
-
|
|
25365
|
-
|
|
25919
|
+
return {
|
|
25920
|
+
data: await requireClient().post(`/areas/${id}/back`, undefined),
|
|
25921
|
+
render: renderRecord
|
|
25922
|
+
};
|
|
25923
|
+
},
|
|
25924
|
+
"areas since": async ({ args, requireClient }) => {
|
|
25925
|
+
const id = requireArg(args, 0, "area id");
|
|
25926
|
+
return {
|
|
25927
|
+
data: await requireClient().get(`/areas/${id}/since-last-look`),
|
|
25928
|
+
render: renderSinceLastLook
|
|
25929
|
+
};
|
|
25930
|
+
},
|
|
25931
|
+
"areas looked-at": async ({ args, requireClient }) => {
|
|
25932
|
+
const id = requireArg(args, 0, "area id");
|
|
25933
|
+
return {
|
|
25934
|
+
data: await requireClient().post(`/areas/${id}/looked-at`, undefined),
|
|
25935
|
+
render: renderRecord
|
|
25936
|
+
};
|
|
25366
25937
|
},
|
|
25367
25938
|
"areas focus": async ({ args, flags, requireClient }) => {
|
|
25368
25939
|
const off = flagBool(flags, "off");
|
|
@@ -25651,6 +26222,12 @@ function assertConfigKey(key) {
|
|
|
25651
26222
|
throw usageError(`unknown config key \`${key}\` \u2014 valid keys: ${CONFIG_KEYS.join(", ")}`);
|
|
25652
26223
|
}
|
|
25653
26224
|
}
|
|
26225
|
+
async function planDecision({ args, requireClient }, verb) {
|
|
26226
|
+
const id = requireArg(args, 0, "plan id");
|
|
26227
|
+
const itemIds = args.slice(1);
|
|
26228
|
+
const body = parseInput(planDecisionInputSchema, itemIds.length > 0 ? { itemIds } : {});
|
|
26229
|
+
return { data: await requireClient().post(`/plans/${id}/${verb}`, body), render: renderPlan };
|
|
26230
|
+
}
|
|
25654
26231
|
function parseInput(schema, value) {
|
|
25655
26232
|
const result = schema.safeParse(value);
|
|
25656
26233
|
if (!result.success) {
|
|
@@ -25659,14 +26236,29 @@ function parseInput(schema, value) {
|
|
|
25659
26236
|
}
|
|
25660
26237
|
return result.data;
|
|
25661
26238
|
}
|
|
26239
|
+
function flagInstant(flags, name) {
|
|
26240
|
+
const raw = flagString(flags, name);
|
|
26241
|
+
if (raw === undefined)
|
|
26242
|
+
return;
|
|
26243
|
+
const dayOnly = /^\d{4}-\d{2}-\d{2}$/.exec(raw.trim());
|
|
26244
|
+
const at = dayOnly ? new Date(`${raw.trim()}T00:00:00`) : new Date(raw);
|
|
26245
|
+
if (Number.isNaN(at.getTime()))
|
|
26246
|
+
throw usageError(`--${name} is not a valid date: ${raw}`);
|
|
26247
|
+
return { at, allDay: dayOnly !== null };
|
|
26248
|
+
}
|
|
26249
|
+
function flagMinutes(flags, name) {
|
|
26250
|
+
const raw = flagString(flags, name);
|
|
26251
|
+
if (raw === undefined)
|
|
26252
|
+
return;
|
|
26253
|
+
const minutes = Number(raw);
|
|
26254
|
+
if (!Number.isInteger(minutes) || minutes <= 0)
|
|
26255
|
+
throw usageError(`--${name} must be a whole number of minutes`);
|
|
26256
|
+
return minutes;
|
|
26257
|
+
}
|
|
25662
26258
|
function computeSnoozeTarget(flags, now) {
|
|
25663
|
-
const until =
|
|
25664
|
-
if (until
|
|
25665
|
-
|
|
25666
|
-
if (Number.isNaN(date.getTime()))
|
|
25667
|
-
throw usageError(`--until is not a valid date: ${until}`);
|
|
25668
|
-
return date;
|
|
25669
|
-
}
|
|
26259
|
+
const until = flagInstant(flags, "until");
|
|
26260
|
+
if (until)
|
|
26261
|
+
return until.at;
|
|
25670
26262
|
const daysRaw = flagString(flags, "days") ?? "1";
|
|
25671
26263
|
const days = Number(daysRaw);
|
|
25672
26264
|
if (!Number.isFinite(days) || days <= 0) {
|
package/package.json
CHANGED