@robbeverhelst/do 1.173.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.
- package/dist/do.js +675 -38
- 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`)
|
|
@@ -3736,6 +3737,9 @@ var userSettings = pgTable("user_settings", {
|
|
|
3736
3737
|
backdrop: text("backdrop"),
|
|
3737
3738
|
enabledModules: text("enabled_modules").array().notNull().default([]),
|
|
3738
3739
|
autoRollOverdue: boolean("auto_roll_overdue").notNull().default(false),
|
|
3740
|
+
pinnedAreaIds: text("pinned_area_ids").array().notNull().default([]),
|
|
3741
|
+
focusAreaId: text("focus_area_id"),
|
|
3742
|
+
focusUntil: timestamp("focus_until", { withTimezone: true }),
|
|
3739
3743
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
3740
3744
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
3741
3745
|
}, (t) => [unique("user_settings_tenant_user_uniq").on(t.tenantId, t.userId)]);
|
|
@@ -3815,7 +3819,8 @@ var tasks = pgTable("tasks", {
|
|
|
3815
3819
|
proposedDesk: text("proposed_desk"),
|
|
3816
3820
|
notNowUntil: timestamp("not_now_until", { withTimezone: true }),
|
|
3817
3821
|
readyAt: timestamp("ready_at", { withTimezone: true }),
|
|
3818
|
-
readyNote: text("ready_note")
|
|
3822
|
+
readyNote: text("ready_note"),
|
|
3823
|
+
moveCount: integer("move_count").notNull().default(0)
|
|
3819
3824
|
}, (t) => [
|
|
3820
3825
|
index("tasks_tenant_project_idx").on(t.tenantId, t.projectId),
|
|
3821
3826
|
index("tasks_tenant_place_idx").on(t.tenantId, t.placeId),
|
|
@@ -3914,7 +3919,8 @@ var notificationType = pgEnum("notification_type", [
|
|
|
3914
3919
|
"streak_at_risk",
|
|
3915
3920
|
"reflection_nudge",
|
|
3916
3921
|
"shutdown_nudge",
|
|
3917
|
-
"habit_reminder"
|
|
3922
|
+
"habit_reminder",
|
|
3923
|
+
"echo"
|
|
3918
3924
|
]);
|
|
3919
3925
|
var notifications = pgTable("notifications", {
|
|
3920
3926
|
id: text("id").primaryKey(),
|
|
@@ -18429,7 +18435,8 @@ var taskViewSchema = taskSchema.omit({
|
|
|
18429
18435
|
source: true,
|
|
18430
18436
|
completedByUserId: true,
|
|
18431
18437
|
completedByAgentId: true,
|
|
18432
|
-
filingSuggestion: true
|
|
18438
|
+
filingSuggestion: true,
|
|
18439
|
+
moveCount: true
|
|
18433
18440
|
}).extend({
|
|
18434
18441
|
title: string2(),
|
|
18435
18442
|
parentId: ulidSchema.nullable(),
|
|
@@ -19218,21 +19225,28 @@ var areaStandardViewSchema = areaStandardSchema.extend({
|
|
|
19218
19225
|
});
|
|
19219
19226
|
var areaSchema = createSelectSchema(areas, {
|
|
19220
19227
|
purpose: string2().nullable().default(null),
|
|
19221
|
-
standards: array(areaStandardSchema).default([])
|
|
19228
|
+
standards: array(areaStandardSchema).default([]),
|
|
19229
|
+
lastReviewedAt: exports_coerce.date().nullable().default(null)
|
|
19222
19230
|
});
|
|
19223
19231
|
var areaIconSchema = string2().trim().min(1).max(64);
|
|
19224
19232
|
var createAreaInputSchema = createInsertSchema(areas, {
|
|
19225
19233
|
id: ulidSchema,
|
|
19226
19234
|
icon: areaIconSchema.nullish(),
|
|
19227
19235
|
purpose: areaPurposeSchema.nullish()
|
|
19228
|
-
}).omit({
|
|
19236
|
+
}).omit({
|
|
19237
|
+
tenantId: true,
|
|
19238
|
+
archivedAt: true,
|
|
19239
|
+
standards: true,
|
|
19240
|
+
restingUntil: true,
|
|
19241
|
+
lastReviewedAt: true
|
|
19242
|
+
}).extend({ title: string2().min(1) });
|
|
19229
19243
|
var updateAreaInputSchema = createUpdateSchema(areas, {
|
|
19230
19244
|
icon: areaIconSchema.nullish(),
|
|
19231
19245
|
archivedAt: exports_coerce.date().nullish(),
|
|
19232
19246
|
purpose: areaPurposeSchema.nullish(),
|
|
19233
19247
|
standards: array(areaStandardInputSchema).max(5).optional(),
|
|
19234
19248
|
restingUntil: exports_iso.date().nullish()
|
|
19235
|
-
}).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();
|
|
19236
19250
|
var areaViewSchema = areaSchema.extend({
|
|
19237
19251
|
title: string2(),
|
|
19238
19252
|
archivedAt: exports_coerce.date().nullable().default(null),
|
|
@@ -19289,6 +19303,24 @@ var areaOverviewSchema = object({
|
|
|
19289
19303
|
owner: areaOwnerLensSchema,
|
|
19290
19304
|
areas: array(areaOverviewItemSchema)
|
|
19291
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();
|
|
19292
19324
|
|
|
19293
19325
|
// ../../packages/schema/src/presets/facets.ts
|
|
19294
19326
|
var PANTRY_DISPLAY_KIND = "pantry";
|
|
@@ -19606,7 +19638,11 @@ function setModuleEnabled(enabled, key, on) {
|
|
|
19606
19638
|
}
|
|
19607
19639
|
|
|
19608
19640
|
// ../../packages/schema/src/contracts/settings.ts
|
|
19641
|
+
var PINNED_AREAS_MAX = 5;
|
|
19609
19642
|
var userSettingsSchema = createSelectSchema(userSettings, {
|
|
19643
|
+
pinnedAreaIds: array(string2()).default([]),
|
|
19644
|
+
focusAreaId: string2().nullable().default(null),
|
|
19645
|
+
focusUntil: exports_coerce.date().nullable().default(null),
|
|
19610
19646
|
createdAt: exports_coerce.date(),
|
|
19611
19647
|
updatedAt: exports_coerce.date()
|
|
19612
19648
|
}).omit({ autoRollOverdue: true });
|
|
@@ -19620,14 +19656,17 @@ var updateUserSettingsInputSchema = createUpdateSchema(userSettings, {
|
|
|
19620
19656
|
accent: settingsAccentSchema.optional(),
|
|
19621
19657
|
sunCity: string2().trim().max(80).transform((city) => city === "" ? null : city).nullish(),
|
|
19622
19658
|
backdrop: backdropIdSchema.nullish(),
|
|
19623
|
-
enabledModules: array(string2().trim().min(1)).transform(normalizeEnabledModules).optional()
|
|
19659
|
+
enabledModules: array(string2().trim().min(1)).transform(normalizeEnabledModules).optional(),
|
|
19660
|
+
pinnedAreaIds: array(ulidSchema).transform((ids) => [...new Set(ids)]).pipe(array(ulidSchema).max(PINNED_AREAS_MAX)).optional(),
|
|
19661
|
+
focusAreaId: ulidSchema.nullish()
|
|
19624
19662
|
}).omit({
|
|
19625
19663
|
id: true,
|
|
19626
19664
|
tenantId: true,
|
|
19627
19665
|
userId: true,
|
|
19628
19666
|
createdAt: true,
|
|
19629
19667
|
updatedAt: true,
|
|
19630
|
-
autoRollOverdue: true
|
|
19668
|
+
autoRollOverdue: true,
|
|
19669
|
+
focusUntil: true
|
|
19631
19670
|
}).strict();
|
|
19632
19671
|
|
|
19633
19672
|
// ../../packages/schema/src/contracts/shopping.ts
|
|
@@ -19914,6 +19953,136 @@ var timeBlockRangeQuerySchema = object({
|
|
|
19914
19953
|
path: ["to"]
|
|
19915
19954
|
});
|
|
19916
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
|
+
|
|
19917
20086
|
// ../../packages/schema/src/contracts/agent-chat.ts
|
|
19918
20087
|
var agentConnectionSourceSchema = _enum2(["space", "env"]);
|
|
19919
20088
|
var agentGatewayStatusSchema = _enum2(["connected", "offline", "not_connected"]);
|
|
@@ -19993,6 +20162,7 @@ var agentChangeSchema = object({
|
|
|
19993
20162
|
changedAt: exports_coerce.date(),
|
|
19994
20163
|
title: string2().nullable(),
|
|
19995
20164
|
actor: actorRefSchema,
|
|
20165
|
+
cursor: string2().optional(),
|
|
19996
20166
|
fields: array(agentChangeFieldSchema).optional(),
|
|
19997
20167
|
checkIn: object({
|
|
19998
20168
|
value: number2(),
|
|
@@ -20007,7 +20177,8 @@ var agentChangesPageSchema = object({
|
|
|
20007
20177
|
var agentChangesQuerySchema = object({
|
|
20008
20178
|
since: string2().refine((s) => s === "now" || /^\d+-\d+$/.test(s), {
|
|
20009
20179
|
message: "since must be a cursor from a prior page's nextCursor, or 'now'"
|
|
20010
|
-
})
|
|
20180
|
+
}),
|
|
20181
|
+
node: string2().trim().min(1).max(64).optional()
|
|
20011
20182
|
});
|
|
20012
20183
|
var agentConfirmationKindSchema = _enum2(["delete", "cancel", "google", "orca", "bulk"]);
|
|
20013
20184
|
var agentConfirmationStatusSchema = _enum2([
|
|
@@ -20650,16 +20821,21 @@ var EVERY_WEEKDAYS_RE = /\bevery\s+(?:weekday|weekdays|work\s?day|work\s?days)\b
|
|
|
20650
20821
|
var EVERY_WEEKDAY_RE = new RegExp(`\\bevery\\s+(${WEEKDAY_ALT})\\b`, "i");
|
|
20651
20822
|
var EVERY_UNIT_RE = /\bevery\s+(day|week|month|year)\b/i;
|
|
20652
20823
|
var ADVERB_RE = /\b(daily|weekly|monthly|yearly|annually)\b/i;
|
|
20653
|
-
var RELATIVE_RE = /\b(today|tonight|tomorrow|tmrw|tmr)\b/i;
|
|
20824
|
+
var RELATIVE_RE = /\b(today|tonight|tomorrow|tmrw|tmr|yesterday)\b/i;
|
|
20654
20825
|
var IN_N_RE = /\bin\s+(\d+)\s+(day|days|week|weeks|month|months)\b/i;
|
|
20655
20826
|
var NEXT_UNIT_RE = /\bnext\s+(week|month|year)\b/i;
|
|
20656
20827
|
var NEXT_WEEKDAY_RE = new RegExp(`\\bnext\\s+(${WEEKDAY_ALT})\\b`, "i");
|
|
20657
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");
|
|
20658
20830
|
var WEEKDAY_RE = new RegExp(`\\b(${WEEKDAY_ALT})\\b`, "i");
|
|
20659
20831
|
var TIME_AMPM_RE = /\b(?:at\s+)?(\d{1,2})(?::(\d{2}))?\s*(am|pm)\b/i;
|
|
20660
20832
|
var TIME_24H_RE = /\b(?:at\s+)?([01]?\d|2[0-3]):([0-5]\d)\b/;
|
|
20661
20833
|
var NOON_RE = /\b(noon|midday)\b/i;
|
|
20662
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;
|
|
20663
20839
|
function blank(s, start, end) {
|
|
20664
20840
|
return s.slice(0, start) + " ".repeat(end - start) + s.slice(end);
|
|
20665
20841
|
}
|
|
@@ -20695,18 +20871,84 @@ function parseWhen(input, now) {
|
|
|
20695
20871
|
scan = blank(scan, start, end);
|
|
20696
20872
|
return { span, index: start };
|
|
20697
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
|
+
}
|
|
20698
20879
|
const recurrence = takeRecurrence(scan, take);
|
|
20699
20880
|
const date = takeDate(scan, take, now);
|
|
20700
20881
|
const time = takeTime(scan, take);
|
|
20882
|
+
const durationMinutes = takeEstimate(scan, take);
|
|
20701
20883
|
const { scheduledAt, allDay } = resolveWhen({ now, recurrence, date, time });
|
|
20702
20884
|
spans.sort((a, b) => a.start - b.start);
|
|
20703
20885
|
return {
|
|
20704
20886
|
...scheduledAt ? { scheduledAt } : {},
|
|
20705
20887
|
allDay,
|
|
20706
20888
|
...recurrence ? { recurrence } : {},
|
|
20889
|
+
...deadline ? { dueAt: deadline.at, dueAllDay: deadline.allDay } : {},
|
|
20890
|
+
...durationMinutes ? { durationMinutes } : {},
|
|
20707
20891
|
spans
|
|
20708
20892
|
};
|
|
20709
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
|
+
}
|
|
20710
20952
|
function takeRecurrence(scan, take) {
|
|
20711
20953
|
const everyN = scan.match(EVERY_N_RE);
|
|
20712
20954
|
if (everyN && take(everyN, "recurrence")) {
|
|
@@ -20771,11 +21013,18 @@ function takeDate(scan, take, now) {
|
|
|
20771
21013
|
return on(addMonths(today, 1));
|
|
20772
21014
|
return on(addMonths(today, 12));
|
|
20773
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
|
+
}
|
|
20774
21021
|
const relative = scan.match(RELATIVE_RE);
|
|
20775
21022
|
if (relative && take(relative, "date")) {
|
|
20776
21023
|
const word = (relative[1] ?? "").toLowerCase();
|
|
20777
21024
|
if (word === "tonight")
|
|
20778
21025
|
return on(today, true);
|
|
21026
|
+
if (word === "yesterday")
|
|
21027
|
+
return on(addDays(today, -1));
|
|
20779
21028
|
return on(word === "today" ? today : addDays(today, 1));
|
|
20780
21029
|
}
|
|
20781
21030
|
const weekday = scan.match(WEEKDAY_RE);
|
|
@@ -20882,6 +21131,8 @@ function parseCapture(input, now) {
|
|
|
20882
21131
|
...starred ? { starred } : {},
|
|
20883
21132
|
...area ? { area } : {},
|
|
20884
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 } : {},
|
|
20885
21136
|
...when?.recurrence ? { recurrence: when.recurrence } : {}
|
|
20886
21137
|
};
|
|
20887
21138
|
}
|
|
@@ -21341,7 +21592,7 @@ function parseCapture2(input, now) {
|
|
|
21341
21592
|
|
|
21342
21593
|
// src/meta.ts
|
|
21343
21594
|
var CLI_NAME = "do";
|
|
21344
|
-
var VERSION = "1.
|
|
21595
|
+
var VERSION = "1.175.0";
|
|
21345
21596
|
var API_PREFIX = "/api/v1";
|
|
21346
21597
|
var DEFAULT_API_URL = "http://localhost:3001";
|
|
21347
21598
|
|
|
@@ -21595,6 +21846,11 @@ var ENTITIES = [
|
|
|
21595
21846
|
name: "ready",
|
|
21596
21847
|
description: "only open tasks their Owner marked Ready for you; --ready=false for the rest",
|
|
21597
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
|
|
21598
21854
|
}
|
|
21599
21855
|
],
|
|
21600
21856
|
write: {
|
|
@@ -21923,15 +22179,35 @@ var COMMAND_SPECS = [
|
|
|
21923
22179
|
{
|
|
21924
22180
|
name: "text",
|
|
21925
22181
|
required: true,
|
|
21926
|
-
description: "Task title; may embed #tag, ! (star) and
|
|
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)."
|
|
21927
22200
|
}
|
|
21928
22201
|
],
|
|
21929
|
-
flags: [],
|
|
21930
22202
|
scopes: ["tasks:write"],
|
|
21931
22203
|
returns: "Task",
|
|
21932
22204
|
grammar: {
|
|
21933
22205
|
"#tag": "Adds a tag. Repeatable: '#home #urgent'.",
|
|
21934
|
-
"!": "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'."
|
|
21935
22211
|
}
|
|
21936
22212
|
},
|
|
21937
22213
|
{
|
|
@@ -22111,6 +22387,84 @@ var COMMAND_SPECS = [
|
|
|
22111
22387
|
returns: "Task",
|
|
22112
22388
|
notes: "POST /api/v1/tasks/{id}/not-now (#889); 409 when the task isn't a proposal. Let go is `do task cancel`."
|
|
22113
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
|
+
},
|
|
22114
22468
|
{
|
|
22115
22469
|
path: ["tasks", "ready"],
|
|
22116
22470
|
summary: "Mark a task Ready for you: its Owner finished something the member should look at (a draft, a link, a worktree).",
|
|
@@ -22140,7 +22494,35 @@ var COMMAND_SPECS = [
|
|
|
22140
22494
|
args: [{ name: "id", required: true, description: "Task ULID." }],
|
|
22141
22495
|
flags: [
|
|
22142
22496
|
{ name: "days", type: "string", description: "Days from now to reschedule.", default: "1" },
|
|
22143
|
-
{
|
|
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
|
+
}
|
|
22144
22526
|
],
|
|
22145
22527
|
scopes: ["tasks:write"],
|
|
22146
22528
|
returns: "Task"
|
|
@@ -22740,6 +23122,22 @@ var COMMAND_SPECS = [
|
|
|
22740
23122
|
returns: "EchoThread",
|
|
22741
23123
|
notes: "When the current thread is still empty, that one is returned instead."
|
|
22742
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
|
+
},
|
|
22743
23141
|
{
|
|
22744
23142
|
path: ["settings", "get"],
|
|
22745
23143
|
summary: "Show the caller's settings for the Space.",
|
|
@@ -23052,12 +23450,17 @@ var COMMAND_SPECS = [
|
|
|
23052
23450
|
{
|
|
23053
23451
|
name: "until",
|
|
23054
23452
|
type: "string",
|
|
23055
|
-
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)."
|
|
23056
23459
|
}
|
|
23057
23460
|
],
|
|
23058
23461
|
scopes: ["areas:write"],
|
|
23059
23462
|
returns: "AreaView",
|
|
23060
|
-
notes: "
|
|
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`."
|
|
23061
23464
|
},
|
|
23062
23465
|
{
|
|
23063
23466
|
path: ["areas", "back"],
|
|
@@ -23066,7 +23469,52 @@ var COMMAND_SPECS = [
|
|
|
23066
23469
|
flags: [],
|
|
23067
23470
|
scopes: ["areas:write"],
|
|
23068
23471
|
returns: "AreaView",
|
|
23069
|
-
notes: "
|
|
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."
|
|
23491
|
+
},
|
|
23492
|
+
{
|
|
23493
|
+
path: ["areas", "focus"],
|
|
23494
|
+
summary: "Focus on one Area for the rest of the day: Now, Tasks and Calendar show only it.",
|
|
23495
|
+
args: [{ name: "id", required: false, description: "Area ULID (omit with --off)." }],
|
|
23496
|
+
flags: [{ name: "off", type: "boolean", description: "Show everything again." }],
|
|
23497
|
+
scopes: ["settings:write"],
|
|
23498
|
+
returns: "UserSettings",
|
|
23499
|
+
notes: "PATCH /api/v1/settings { focusAreaId }. Clears itself at the end of the member's day (focusUntil); hidden tasks don't move or go overdue."
|
|
23500
|
+
},
|
|
23501
|
+
{
|
|
23502
|
+
path: ["areas", "pin"],
|
|
23503
|
+
summary: "Pin an Area under Life in the desktop sidebar (up to five).",
|
|
23504
|
+
args: [{ name: "id", required: true, description: "Area ULID." }],
|
|
23505
|
+
flags: [],
|
|
23506
|
+
scopes: ["settings:read", "settings:write"],
|
|
23507
|
+
returns: "UserSettings",
|
|
23508
|
+
notes: "Reads then patches settings.pinnedAreaIds; a pinned Area stays where it is."
|
|
23509
|
+
},
|
|
23510
|
+
{
|
|
23511
|
+
path: ["areas", "unpin"],
|
|
23512
|
+
summary: "Take an Area out of the desktop sidebar.",
|
|
23513
|
+
args: [{ name: "id", required: true, description: "Area ULID." }],
|
|
23514
|
+
flags: [],
|
|
23515
|
+
scopes: ["settings:read", "settings:write"],
|
|
23516
|
+
returns: "UserSettings",
|
|
23517
|
+
notes: "Reads then patches settings.pinnedAreaIds."
|
|
23070
23518
|
},
|
|
23071
23519
|
{
|
|
23072
23520
|
path: ["places", "import"],
|
|
@@ -23760,18 +24208,52 @@ function renderToday(data, mode) {
|
|
|
23760
24208
|
${habits.join(`
|
|
23761
24209
|
`)}` : tasks;
|
|
23762
24210
|
}
|
|
23763
|
-
var
|
|
24211
|
+
var DAY_MS2 = 24 * 60 * 60 * 1000;
|
|
23764
24212
|
function plannedDay(at, from) {
|
|
23765
24213
|
if (!at)
|
|
23766
24214
|
return "earlier";
|
|
23767
24215
|
const day = new Date(at);
|
|
23768
|
-
const ago = from ? Math.round((Date.parse(from) - day.getTime()) /
|
|
24216
|
+
const ago = from ? Math.round((Date.parse(from) - day.getTime()) / DAY_MS2) : 7;
|
|
23769
24217
|
if (ago <= 1)
|
|
23770
24218
|
return "yesterday";
|
|
23771
24219
|
if (ago < 7)
|
|
23772
24220
|
return day.toLocaleDateString("en-GB", { weekday: "long" });
|
|
23773
24221
|
return day.toLocaleDateString("en-GB", { day: "numeric", month: "short" });
|
|
23774
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
|
+
}
|
|
23775
24257
|
function renderFromEarlier(data, mode) {
|
|
23776
24258
|
const d = data;
|
|
23777
24259
|
const tasks = d.tasks ?? [];
|
|
@@ -23811,6 +24293,28 @@ function renderAreaOverview(data, mode) {
|
|
|
23811
24293
|
return out.join(`
|
|
23812
24294
|
`);
|
|
23813
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
|
+
}
|
|
23814
24318
|
function renderFromEarlierAnswer(data, mode) {
|
|
23815
24319
|
const d = data;
|
|
23816
24320
|
const n = d.tasks ?? 0;
|
|
@@ -24242,9 +24746,24 @@ function renderProgress(data, mode) {
|
|
|
24242
24746
|
}
|
|
24243
24747
|
const areas = p.areas ?? [];
|
|
24244
24748
|
if (areas.length > 0) {
|
|
24245
|
-
out.push("", paint(mode, "bold", "
|
|
24246
|
-
|
|
24247
|
-
out.push(` ${
|
|
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
|
+
}
|
|
24248
24767
|
}
|
|
24249
24768
|
const habits = p.habits ?? [];
|
|
24250
24769
|
if (habits.length > 0) {
|
|
@@ -24550,11 +25069,17 @@ function runFixed(spec, ctx) {
|
|
|
24550
25069
|
return handler(ctx);
|
|
24551
25070
|
}
|
|
24552
25071
|
var HANDLERS = {
|
|
24553
|
-
capture: async ({ args, io, requireClient }) => {
|
|
25072
|
+
capture: async ({ args, flags, io, requireClient }) => {
|
|
24554
25073
|
const text = args.join(" ").trim();
|
|
24555
25074
|
if (text.length === 0)
|
|
24556
25075
|
throw usageError('capture needs some text, e.g. `do capture "call mum #family"`');
|
|
24557
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;
|
|
24558
25083
|
const id = ulid3();
|
|
24559
25084
|
const input = parseInput(createTaskInputSchema, {
|
|
24560
25085
|
id,
|
|
@@ -24562,7 +25087,10 @@ var HANDLERS = {
|
|
|
24562
25087
|
tags: parsed.tags,
|
|
24563
25088
|
...parsed.starred ? { starred: parsed.starred } : {},
|
|
24564
25089
|
...parsed.area ? { areaName: parsed.area } : {},
|
|
24565
|
-
...
|
|
25090
|
+
...scheduledAt ? { scheduledAt } : {},
|
|
25091
|
+
...dueAt ? { dueAt } : {},
|
|
25092
|
+
...scheduledAt || dueAt ? { allDay } : {},
|
|
25093
|
+
...duration ? { durationMinutes: duration } : {}
|
|
24566
25094
|
});
|
|
24567
25095
|
const client = requireClient();
|
|
24568
25096
|
const task = await client.post("/tasks", input);
|
|
@@ -24872,6 +25400,30 @@ var HANDLERS = {
|
|
|
24872
25400
|
const id = requireArg(args, 0, "task id");
|
|
24873
25401
|
return { data: await requireClient().post(`/tasks/${id}/not-now`, {}), render: renderTask };
|
|
24874
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"),
|
|
24875
25427
|
"tasks ready": async ({ args, flags, requireClient }) => {
|
|
24876
25428
|
const id = requireArg(args, 0, "task id");
|
|
24877
25429
|
const note = flagString(flags, "note");
|
|
@@ -24886,7 +25438,26 @@ var HANDLERS = {
|
|
|
24886
25438
|
"task snooze": async ({ args, flags, io, requireClient }) => {
|
|
24887
25439
|
const id = requireArg(args, 0, "task id");
|
|
24888
25440
|
const target = computeSnoozeTarget(flags, io.now());
|
|
24889
|
-
const
|
|
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
|
+
});
|
|
24890
25461
|
return { data: await requireClient().patch(`/tasks/${id}`, body), render: renderTask };
|
|
24891
25462
|
},
|
|
24892
25463
|
"habit log": async ({ args, flags, requireClient }) => {
|
|
@@ -25118,6 +25689,12 @@ var HANDLERS = {
|
|
|
25118
25689
|
data: await requireClient().post("/echo/threads", {}),
|
|
25119
25690
|
render: renderEchoThread
|
|
25120
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
|
+
}),
|
|
25121
25698
|
"settings get": async ({ requireClient }) => ({
|
|
25122
25699
|
data: await requireClient().get("/settings"),
|
|
25123
25700
|
render: renderRecord
|
|
@@ -25319,14 +25896,43 @@ var HANDLERS = {
|
|
|
25319
25896
|
const until = flagString(flags, "until");
|
|
25320
25897
|
if (!until)
|
|
25321
25898
|
throw usageError("areas rest needs --until <YYYY-MM-DD>, the day it's back");
|
|
25322
|
-
const input = parseInput(
|
|
25323
|
-
|
|
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 };
|
|
25324
25904
|
},
|
|
25325
25905
|
"areas back": async ({ args, requireClient }) => {
|
|
25326
25906
|
const id = requireArg(args, 0, "area id");
|
|
25327
|
-
|
|
25328
|
-
|
|
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
|
+
};
|
|
25925
|
+
},
|
|
25926
|
+
"areas focus": async ({ args, flags, requireClient }) => {
|
|
25927
|
+
const off = flagBool(flags, "off");
|
|
25928
|
+
const id = off ? null : args[0];
|
|
25929
|
+
if (id === undefined)
|
|
25930
|
+
throw usageError("areas focus needs an area id, or --off to show everything");
|
|
25931
|
+
const body = parseInput(updateUserSettingsInputSchema, { focusAreaId: id });
|
|
25932
|
+
return { data: await requireClient().patch("/settings", body), render: renderRecord };
|
|
25329
25933
|
},
|
|
25934
|
+
"areas pin": async ({ args, requireClient }) => setAreaPinned(args, requireClient, true),
|
|
25935
|
+
"areas unpin": async ({ args, requireClient }) => setAreaPinned(args, requireClient, false),
|
|
25330
25936
|
"places import": async ({ args, flags, requireClient }) => {
|
|
25331
25937
|
const path = requireArg(args, 0, "house.json path");
|
|
25332
25938
|
let file;
|
|
@@ -25545,6 +26151,16 @@ async function setModuleVisibility(args, requireClient, on) {
|
|
|
25545
26151
|
const body = parseInput(updateUserSettingsInputSchema, { enabledModules });
|
|
25546
26152
|
return { data: await client.patch("/settings", body), render: renderRecord };
|
|
25547
26153
|
}
|
|
26154
|
+
async function setAreaPinned(args, requireClient, pinned) {
|
|
26155
|
+
const id = requireArg(args, 0, "area id");
|
|
26156
|
+
const client = requireClient();
|
|
26157
|
+
const settings = await client.get("/settings");
|
|
26158
|
+
const rest = (settings.pinnedAreaIds ?? []).filter((x) => x !== id);
|
|
26159
|
+
const body = parseInput(updateUserSettingsInputSchema, {
|
|
26160
|
+
pinnedAreaIds: pinned ? [...rest, id] : rest
|
|
26161
|
+
});
|
|
26162
|
+
return { data: await client.patch("/settings", body), render: renderRecord };
|
|
26163
|
+
}
|
|
25548
26164
|
function parseTitleTags(args) {
|
|
25549
26165
|
const titleWords = [];
|
|
25550
26166
|
const tags = [];
|
|
@@ -25594,6 +26210,12 @@ function assertConfigKey(key) {
|
|
|
25594
26210
|
throw usageError(`unknown config key \`${key}\` \u2014 valid keys: ${CONFIG_KEYS.join(", ")}`);
|
|
25595
26211
|
}
|
|
25596
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
|
+
}
|
|
25597
26219
|
function parseInput(schema, value) {
|
|
25598
26220
|
const result = schema.safeParse(value);
|
|
25599
26221
|
if (!result.success) {
|
|
@@ -25602,14 +26224,29 @@ function parseInput(schema, value) {
|
|
|
25602
26224
|
}
|
|
25603
26225
|
return result.data;
|
|
25604
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
|
+
}
|
|
25605
26246
|
function computeSnoozeTarget(flags, now) {
|
|
25606
|
-
const until =
|
|
25607
|
-
if (until
|
|
25608
|
-
|
|
25609
|
-
if (Number.isNaN(date.getTime()))
|
|
25610
|
-
throw usageError(`--until is not a valid date: ${until}`);
|
|
25611
|
-
return date;
|
|
25612
|
-
}
|
|
26247
|
+
const until = flagInstant(flags, "until");
|
|
26248
|
+
if (until)
|
|
26249
|
+
return until.at;
|
|
25613
26250
|
const daysRaw = flagString(flags, "days") ?? "1";
|
|
25614
26251
|
const days = Number(daysRaw);
|
|
25615
26252
|
if (!Number.isFinite(days) || days <= 0) {
|
package/package.json
CHANGED