@robbeverhelst/do 1.333.0 → 1.334.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 +492 -36
- package/package.json +1 -1
package/dist/do.js
CHANGED
|
@@ -983,6 +983,10 @@ function parseArgs(argv, booleanFlags = GLOBAL_BOOLEAN_FLAGS) {
|
|
|
983
983
|
const positionals = [];
|
|
984
984
|
const flags = {};
|
|
985
985
|
let positionalOnly = false;
|
|
986
|
+
const setValue = (name, value) => {
|
|
987
|
+
const was = flags[name];
|
|
988
|
+
flags[name] = typeof was === "string" ? [was, value] : Array.isArray(was) ? [...was, value] : value;
|
|
989
|
+
};
|
|
986
990
|
for (let i = 0;i < argv.length; i++) {
|
|
987
991
|
const token = argv[i];
|
|
988
992
|
if (token === undefined)
|
|
@@ -999,7 +1003,7 @@ function parseArgs(argv, booleanFlags = GLOBAL_BOOLEAN_FLAGS) {
|
|
|
999
1003
|
const body = token.slice(2);
|
|
1000
1004
|
const eq = body.indexOf("=");
|
|
1001
1005
|
if (eq !== -1) {
|
|
1002
|
-
|
|
1006
|
+
setValue(body.slice(0, eq), body.slice(eq + 1));
|
|
1003
1007
|
continue;
|
|
1004
1008
|
}
|
|
1005
1009
|
if (booleans.has(body)) {
|
|
@@ -1008,7 +1012,7 @@ function parseArgs(argv, booleanFlags = GLOBAL_BOOLEAN_FLAGS) {
|
|
|
1008
1012
|
}
|
|
1009
1013
|
const next = argv[i + 1];
|
|
1010
1014
|
if (next !== undefined && (next === "-" || !next.startsWith("-"))) {
|
|
1011
|
-
|
|
1015
|
+
setValue(body, next);
|
|
1012
1016
|
i++;
|
|
1013
1017
|
} else {
|
|
1014
1018
|
flags[body] = true;
|
|
@@ -1030,7 +1034,11 @@ function parseArgs(argv, booleanFlags = GLOBAL_BOOLEAN_FLAGS) {
|
|
|
1030
1034
|
}
|
|
1031
1035
|
function flagString(flags, name) {
|
|
1032
1036
|
const v = flags[name];
|
|
1033
|
-
return typeof v === "string" ? v : undefined;
|
|
1037
|
+
return typeof v === "string" ? v : Array.isArray(v) ? v.at(-1) : undefined;
|
|
1038
|
+
}
|
|
1039
|
+
function flagStrings(flags, name) {
|
|
1040
|
+
const v = flags[name];
|
|
1041
|
+
return typeof v === "string" ? [v] : Array.isArray(v) ? v : [];
|
|
1034
1042
|
}
|
|
1035
1043
|
function flagBool(flags, name) {
|
|
1036
1044
|
return flags[name] === true;
|
|
@@ -14410,7 +14418,7 @@ var configError = (message) => new CliError("config", message, EXIT.CONFIG);
|
|
|
14410
14418
|
|
|
14411
14419
|
// src/meta.ts
|
|
14412
14420
|
var CLI_NAME = "do";
|
|
14413
|
-
var VERSION = "1.
|
|
14421
|
+
var VERSION = "1.334.0";
|
|
14414
14422
|
var API_PREFIX = "/api/v1";
|
|
14415
14423
|
var DEFAULT_API_URL = "http://localhost:3001";
|
|
14416
14424
|
|
|
@@ -17867,6 +17875,7 @@ var automationRuns = pgTable("automation_runs", {
|
|
|
17867
17875
|
automationId: text("automation_id").notNull().references(() => automations.id),
|
|
17868
17876
|
eventType: text("event_type").notNull(),
|
|
17869
17877
|
eventEntityId: text("event_entity_id").notNull(),
|
|
17878
|
+
eventValue: jsonb("event_value"),
|
|
17870
17879
|
status: automationRunStatus("status").notNull(),
|
|
17871
17880
|
detail: text("detail"),
|
|
17872
17881
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
@@ -20424,7 +20433,8 @@ var animalDayCountSchema = object({ day: string2(), count: number2().int() });
|
|
|
20424
20433
|
var animalWeightPointSchema = object({
|
|
20425
20434
|
day: string2(),
|
|
20426
20435
|
grams: number2(),
|
|
20427
|
-
at: exports_coerce.date()
|
|
20436
|
+
at: exports_coerce.date(),
|
|
20437
|
+
note: string2().nullish()
|
|
20428
20438
|
});
|
|
20429
20439
|
var animalFileSchema = object({
|
|
20430
20440
|
id: ulidSchema,
|
|
@@ -20545,6 +20555,7 @@ var animalLogInputSchema = discriminatedUnion("kind", [
|
|
|
20545
20555
|
kind: literal("weight"),
|
|
20546
20556
|
value: number2().positive().max(200000),
|
|
20547
20557
|
unit: _enum2(["g", "kg"]).default("g"),
|
|
20558
|
+
note: string2().trim().min(1).max(120).optional(),
|
|
20548
20559
|
occurredAt: occurredAtSchema
|
|
20549
20560
|
}).strict(),
|
|
20550
20561
|
object({
|
|
@@ -22030,13 +22041,25 @@ var occurrenceSchema = createSelectSchema(occurrences, {
|
|
|
22030
22041
|
completedAt: exports_coerce.date().nullable()
|
|
22031
22042
|
});
|
|
22032
22043
|
var byDayCodeSchema = _enum2(["MO", "TU", "WE", "TH", "FR", "SA", "SU"]);
|
|
22044
|
+
var byDayOrdinalSchema = _enum2(["1", "2", "3", "4", "-1"]);
|
|
22045
|
+
var byDaySchema = union([
|
|
22046
|
+
byDayCodeSchema,
|
|
22047
|
+
templateLiteral([byDayOrdinalSchema, byDayCodeSchema])
|
|
22048
|
+
]);
|
|
22049
|
+
var BY_DAY_RE = /^(-1|[1-4])?(MO|TU|WE|TH|FR|SA|SU)$/;
|
|
22050
|
+
function parseByDay(code) {
|
|
22051
|
+
const m = BY_DAY_RE.exec(code);
|
|
22052
|
+
if (!m)
|
|
22053
|
+
return null;
|
|
22054
|
+
return { ordinal: m[1] ? Number(m[1]) : null, weekday: m[2] };
|
|
22055
|
+
}
|
|
22033
22056
|
var recurrenceRuleParams = {
|
|
22034
22057
|
timezone: string2().min(1).default("UTC"),
|
|
22035
22058
|
interval: number2().int().positive().default(1),
|
|
22036
22059
|
mode: recurrenceModeSchema,
|
|
22037
22060
|
timeOfDay: string2().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "time-of-day must be HH:MM (24h)").nullish(),
|
|
22038
22061
|
freq: recurrenceFreqSchema.optional(),
|
|
22039
|
-
byDay: array(
|
|
22062
|
+
byDay: array(byDaySchema).optional(),
|
|
22040
22063
|
until: exports_coerce.date().optional(),
|
|
22041
22064
|
count: number2().int().positive().optional(),
|
|
22042
22065
|
unit: recurrenceUnitSchema.optional(),
|
|
@@ -22046,6 +22069,14 @@ function refineRecurrenceRule(v, ctx) {
|
|
|
22046
22069
|
if (v.mode === "rigid" && !v.freq) {
|
|
22047
22070
|
ctx.addIssue({ code: "custom", path: ["freq"], message: "rigid rules require a freq" });
|
|
22048
22071
|
}
|
|
22072
|
+
const ordinal = v.byDay?.findIndex((code) => parseByDay(code)?.ordinal != null) ?? -1;
|
|
22073
|
+
if (ordinal >= 0 && v.freq !== "monthly" && v.freq !== "yearly") {
|
|
22074
|
+
ctx.addIssue({
|
|
22075
|
+
code: "custom",
|
|
22076
|
+
path: ["byDay", ordinal],
|
|
22077
|
+
message: "an ordinal weekday (1SU, -1FR) needs a monthly or yearly freq"
|
|
22078
|
+
});
|
|
22079
|
+
}
|
|
22049
22080
|
if (v.mode === "flexible") {
|
|
22050
22081
|
if (!v.unit) {
|
|
22051
22082
|
ctx.addIssue({ code: "custom", path: ["unit"], message: "flexible rules require a unit" });
|
|
@@ -22437,6 +22468,8 @@ var collectionEntries = pgTable("collection_entries", {
|
|
|
22437
22468
|
fileIds: text("file_ids").array().notNull().default([]),
|
|
22438
22469
|
minutes: integer2("minutes"),
|
|
22439
22470
|
ops: jsonb("ops").$type().notNull().default([]),
|
|
22471
|
+
chapter: text("chapter"),
|
|
22472
|
+
linkedNoteId: text("linked_note_id").references(() => nodes.id, { onDelete: "set null" }),
|
|
22440
22473
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
22441
22474
|
}, (t) => [
|
|
22442
22475
|
index("collection_entries_item_occurred_idx").on(t.itemId, t.occurredOn),
|
|
@@ -22548,8 +22581,11 @@ var collectionEntrySchema = createSelectSchema(collectionEntries, {
|
|
|
22548
22581
|
taskId: string2().nullable().default(null),
|
|
22549
22582
|
fileIds: array(string2()).default([]),
|
|
22550
22583
|
minutes: number2().int().nullable().default(null),
|
|
22551
|
-
ops: array(unknown()).default([])
|
|
22584
|
+
ops: array(unknown()).default([]),
|
|
22585
|
+
chapter: string2().nullable().default(null),
|
|
22586
|
+
linkedNoteId: string2().nullable().default(null)
|
|
22552
22587
|
});
|
|
22588
|
+
var entryChapterSchema = string2().trim().min(1).max(60);
|
|
22553
22589
|
var entryCostSchema = number2().min(0).max(1e7).multipleOf(0.01);
|
|
22554
22590
|
var entryFileIdsSchema = array(ulidSchema).max(20);
|
|
22555
22591
|
var createCollectionEntryInputSchema = createInsertSchema(collectionEntries, {
|
|
@@ -22561,7 +22597,9 @@ var createCollectionEntryInputSchema = createInsertSchema(collectionEntries, {
|
|
|
22561
22597
|
cost: entryCostSchema.nullish(),
|
|
22562
22598
|
contactId: ulidSchema.nullish(),
|
|
22563
22599
|
taskId: ulidSchema.nullish(),
|
|
22564
|
-
fileIds: entryFileIdsSchema.optional()
|
|
22600
|
+
fileIds: entryFileIdsSchema.optional(),
|
|
22601
|
+
chapter: entryChapterSchema.nullish(),
|
|
22602
|
+
linkedNoteId: ulidSchema.nullish()
|
|
22565
22603
|
}).omit({ tenantId: true, itemId: true, createdAt: true, minutes: true, ops: true });
|
|
22566
22604
|
var updateCollectionEntryInputSchema = object({
|
|
22567
22605
|
occurredOn: string2().regex(/^\d{4}-\d{2}-\d{2}$/, "must be an ISO date (YYYY-MM-DD)").optional(),
|
|
@@ -22570,7 +22608,9 @@ var updateCollectionEntryInputSchema = object({
|
|
|
22570
22608
|
kind: collectionEntryKindSchema.optional(),
|
|
22571
22609
|
cost: entryCostSchema.nullish(),
|
|
22572
22610
|
contactId: ulidSchema.nullish(),
|
|
22573
|
-
fileIds: entryFileIdsSchema.optional()
|
|
22611
|
+
fileIds: entryFileIdsSchema.optional(),
|
|
22612
|
+
chapter: entryChapterSchema.nullish(),
|
|
22613
|
+
linkedNoteId: ulidSchema.nullish()
|
|
22574
22614
|
}).strict();
|
|
22575
22615
|
var collectionItemViewSchema = collectionItemSchema.extend({
|
|
22576
22616
|
title: string2(),
|
|
@@ -22643,6 +22683,7 @@ var collectionLogInputSchema = object({
|
|
|
22643
22683
|
status: string2().trim().min(1).max(40).optional(),
|
|
22644
22684
|
rating: collectionRatingSchema.nullish(),
|
|
22645
22685
|
note: string2().max(2000).nullish(),
|
|
22686
|
+
chapter: entryChapterSchema.nullish(),
|
|
22646
22687
|
said: string2().trim().min(1).max(500).optional(),
|
|
22647
22688
|
occurredOn: string2().regex(/^\d{4}-\d{2}-\d{2}$/, "must be an ISO date (YYYY-MM-DD)").optional()
|
|
22648
22689
|
}).strict();
|
|
@@ -25566,13 +25607,19 @@ var moneyGoalLineSchema = createSelectSchema(moneyGoalLines, {
|
|
|
25566
25607
|
createdAt: exports_coerce.date(),
|
|
25567
25608
|
updatedAt: exports_coerce.date()
|
|
25568
25609
|
});
|
|
25610
|
+
var MONEY_GOAL_SAVED_FROM = ["account", "account_share", "contributions"];
|
|
25611
|
+
var moneyGoalSavedFromSchema = _enum2(MONEY_GOAL_SAVED_FROM);
|
|
25569
25612
|
var moneyGoalViewSchema = moneyGoalSchema.extend({
|
|
25570
25613
|
title: string2(),
|
|
25571
25614
|
visibility: _enum2(NODE_VISIBILITY_VALUES),
|
|
25572
25615
|
accountTitle: string2().nullable(),
|
|
25573
25616
|
accountBalance: amount.nullable(),
|
|
25617
|
+
accountBalanceKnown: boolean2().nullable(),
|
|
25574
25618
|
projectTitle: string2().nullable(),
|
|
25575
25619
|
saved: amount,
|
|
25620
|
+
savedFrom: moneyGoalSavedFromSchema,
|
|
25621
|
+
remaining: amount,
|
|
25622
|
+
monthlyNeed: amount.nullable(),
|
|
25576
25623
|
linesPlanned: amount,
|
|
25577
25624
|
linesPaid: amount
|
|
25578
25625
|
});
|
|
@@ -31268,6 +31315,140 @@ var journalExportSchema = object({
|
|
|
31268
31315
|
nextSince: exports_coerce.date()
|
|
31269
31316
|
});
|
|
31270
31317
|
|
|
31318
|
+
// ../../packages/schema/src/contracts/journal-private.ts
|
|
31319
|
+
var JOURNAL_PRIVATE_TERMS = {
|
|
31320
|
+
alcohol: [
|
|
31321
|
+
"alcohol*",
|
|
31322
|
+
"drunk*",
|
|
31323
|
+
"drink*",
|
|
31324
|
+
"beer*",
|
|
31325
|
+
"wine*",
|
|
31326
|
+
"whisky",
|
|
31327
|
+
"whiskey",
|
|
31328
|
+
"vodka*",
|
|
31329
|
+
"gin",
|
|
31330
|
+
"rum",
|
|
31331
|
+
"tequila*",
|
|
31332
|
+
"cocktail*",
|
|
31333
|
+
"hangover*",
|
|
31334
|
+
"hungover",
|
|
31335
|
+
"tipsy",
|
|
31336
|
+
"booze*",
|
|
31337
|
+
"sober*",
|
|
31338
|
+
"sobriety",
|
|
31339
|
+
"bier*",
|
|
31340
|
+
"wijn*",
|
|
31341
|
+
"drank*",
|
|
31342
|
+
"dronken*",
|
|
31343
|
+
"borrel*",
|
|
31344
|
+
"pint",
|
|
31345
|
+
"pints",
|
|
31346
|
+
"pintje*",
|
|
31347
|
+
"kater*",
|
|
31348
|
+
"zuipen",
|
|
31349
|
+
"gezopen"
|
|
31350
|
+
],
|
|
31351
|
+
smoking: [
|
|
31352
|
+
"smok*",
|
|
31353
|
+
"cigar*",
|
|
31354
|
+
"tobacco",
|
|
31355
|
+
"nicotine",
|
|
31356
|
+
"vape*",
|
|
31357
|
+
"vaping",
|
|
31358
|
+
"roken",
|
|
31359
|
+
"rook",
|
|
31360
|
+
"rookte*",
|
|
31361
|
+
"gerookt",
|
|
31362
|
+
"roker*",
|
|
31363
|
+
"sigaar*",
|
|
31364
|
+
"sigaret*",
|
|
31365
|
+
"tabak*",
|
|
31366
|
+
"peuk*",
|
|
31367
|
+
"vapen"
|
|
31368
|
+
],
|
|
31369
|
+
cannabis: [
|
|
31370
|
+
"cannabis",
|
|
31371
|
+
"weed",
|
|
31372
|
+
"hash",
|
|
31373
|
+
"hashish",
|
|
31374
|
+
"marijuana",
|
|
31375
|
+
"joint",
|
|
31376
|
+
"joints",
|
|
31377
|
+
"jointje*",
|
|
31378
|
+
"stoned",
|
|
31379
|
+
"thc",
|
|
31380
|
+
"cbd",
|
|
31381
|
+
"edibles",
|
|
31382
|
+
"wiet*",
|
|
31383
|
+
"hasj*",
|
|
31384
|
+
"blowen",
|
|
31385
|
+
"blowt",
|
|
31386
|
+
"geblowd"
|
|
31387
|
+
],
|
|
31388
|
+
drugs: [
|
|
31389
|
+
"drug*",
|
|
31390
|
+
"cocaine",
|
|
31391
|
+
"coca\xEFne",
|
|
31392
|
+
"coke",
|
|
31393
|
+
"mdma",
|
|
31394
|
+
"ecstasy",
|
|
31395
|
+
"xtc",
|
|
31396
|
+
"ketamine",
|
|
31397
|
+
"lsd",
|
|
31398
|
+
"shrooms",
|
|
31399
|
+
"paddo*",
|
|
31400
|
+
"amphetamine*",
|
|
31401
|
+
"heroin*",
|
|
31402
|
+
"hero\xEFne",
|
|
31403
|
+
"opioid*"
|
|
31404
|
+
],
|
|
31405
|
+
relapse: [
|
|
31406
|
+
"relaps*",
|
|
31407
|
+
"craving*",
|
|
31408
|
+
"rehab",
|
|
31409
|
+
"aa meeting*",
|
|
31410
|
+
"terugval*",
|
|
31411
|
+
"hervallen",
|
|
31412
|
+
"afkick*",
|
|
31413
|
+
"ontwenning*"
|
|
31414
|
+
],
|
|
31415
|
+
fertility: [
|
|
31416
|
+
"fertil*",
|
|
31417
|
+
"ivf",
|
|
31418
|
+
"iui",
|
|
31419
|
+
"ovulat*",
|
|
31420
|
+
"conceiv*",
|
|
31421
|
+
"conception",
|
|
31422
|
+
"pregnan*",
|
|
31423
|
+
"sperm*",
|
|
31424
|
+
"embryo*",
|
|
31425
|
+
"miscarr*",
|
|
31426
|
+
"ttc",
|
|
31427
|
+
"vruchtbaar*",
|
|
31428
|
+
"ovulatie",
|
|
31429
|
+
"eisprong",
|
|
31430
|
+
"zwanger*",
|
|
31431
|
+
"sperma*",
|
|
31432
|
+
"miskraam",
|
|
31433
|
+
"kinderwens"
|
|
31434
|
+
]
|
|
31435
|
+
};
|
|
31436
|
+
var LETTER = "[\\p{L}\\p{N}_]";
|
|
31437
|
+
var PRIVATE = new RegExp(`(?<!${LETTER})(?:${Object.values(JOURNAL_PRIVATE_TERMS).flat().map((term) => {
|
|
31438
|
+
const stem = term.replace(/\*$/, "").replace(/\s+/g, "\\s+");
|
|
31439
|
+
return term.endsWith("*") ? `${stem}${LETTER}*` : stem;
|
|
31440
|
+
}).join("|")})(?!${LETTER})`, "iu");
|
|
31441
|
+
function isPrivateLine(line) {
|
|
31442
|
+
return PRIVATE.test(line);
|
|
31443
|
+
}
|
|
31444
|
+
function withoutPrivateLines(text) {
|
|
31445
|
+
const kept = text.split(/\r?\n/).filter((line) => !isPrivateLine(line)).join(`
|
|
31446
|
+
`).replace(/\n{3,}/g, `
|
|
31447
|
+
|
|
31448
|
+
`).trim();
|
|
31449
|
+
return kept || null;
|
|
31450
|
+
}
|
|
31451
|
+
|
|
31271
31452
|
// ../../packages/schema/src/contracts/journal-import.ts
|
|
31272
31453
|
var journalImportBatchSchema = string2().regex(/^\d{4}-(0[1-9]|1[0-2])$/, "a batch is a month (YYYY-MM)");
|
|
31273
31454
|
var journalImportEntrySchema = _enum2(["summary", "retro", "log"]);
|
|
@@ -31278,6 +31459,7 @@ var journalImportFoundSchema = object({
|
|
|
31278
31459
|
documents: number2().int().nonnegative().optional(),
|
|
31279
31460
|
habits: number2().int().nonnegative().optional()
|
|
31280
31461
|
}).strict();
|
|
31462
|
+
var keptLine = (max) => string2().trim().min(1).max(max).transform((v) => isPrivateLine(v) ? null : v).nullish();
|
|
31281
31463
|
var JOURNAL_IMPORT_MAX_ITEMS = 400;
|
|
31282
31464
|
var journalImportItemInputSchema = object({
|
|
31283
31465
|
day: journalDateSchema,
|
|
@@ -31285,12 +31467,12 @@ var journalImportItemInputSchema = object({
|
|
|
31285
31467
|
sourceHash: string2().trim().min(1).max(128),
|
|
31286
31468
|
entry: journalImportEntrySchema.default("log"),
|
|
31287
31469
|
hasCheck: boolean2().default(false),
|
|
31288
|
-
title:
|
|
31289
|
-
body: string2().trim().min(1).max(20000).nullish(),
|
|
31470
|
+
title: keptLine(200),
|
|
31471
|
+
body: string2().trim().min(1).max(20000).transform(withoutPrivateLines).nullish(),
|
|
31290
31472
|
rating: number2().min(1).max(10).nullish(),
|
|
31291
|
-
ratingQuestion:
|
|
31473
|
+
ratingQuestion: keptLine(80),
|
|
31292
31474
|
sleep: number2().min(0).max(24).nullish(),
|
|
31293
|
-
highlight:
|
|
31475
|
+
highlight: keptLine(200),
|
|
31294
31476
|
milestoneCandidate: boolean2().default(false),
|
|
31295
31477
|
health: boolean2().default(false),
|
|
31296
31478
|
found: journalImportFoundSchema.default({})
|
|
@@ -31356,8 +31538,25 @@ var journalImportAnswerInputSchema = object({
|
|
|
31356
31538
|
rating: number2().min(1).max(10).nullable().optional(),
|
|
31357
31539
|
skip: boolean2().optional()
|
|
31358
31540
|
}).strict().refine((v) => Object.keys(v).length > 0, { message: "nothing to answer" });
|
|
31541
|
+
var journalImportRatingAnswerSchema = object({ day: journalDateSchema, rating: number2().min(1).max(10) }).strict();
|
|
31542
|
+
var journalImportApproveInputSchema = object({
|
|
31543
|
+
batch: journalImportBatchSchema.optional(),
|
|
31544
|
+
all: literal(true).optional(),
|
|
31545
|
+
ratings: array(journalImportRatingAnswerSchema).max(JOURNAL_IMPORT_MAX_ITEMS).default([])
|
|
31546
|
+
}).strict().refine((v) => v.batch !== undefined !== (v.all === true), {
|
|
31547
|
+
message: "approve one batch or all: send batch or all, not both"
|
|
31548
|
+
}).refine((v) => new Set(v.ratings.map((r) => r.day)).size === v.ratings.length, {
|
|
31549
|
+
message: "one rating per day",
|
|
31550
|
+
path: ["ratings"]
|
|
31551
|
+
});
|
|
31552
|
+
var journalImportOpenDaySchema = object({
|
|
31553
|
+
day: journalDateSchema,
|
|
31554
|
+
ratingQuestion: string2()
|
|
31555
|
+
});
|
|
31359
31556
|
var journalImportApproveSchema = object({
|
|
31360
31557
|
imported: number2().int(),
|
|
31558
|
+
batches: array(journalImportBatchSchema).default([]),
|
|
31559
|
+
open: array(journalImportOpenDaySchema).default([]),
|
|
31361
31560
|
overview: journalImportOverviewSchema
|
|
31362
31561
|
});
|
|
31363
31562
|
|
|
@@ -31745,12 +31944,25 @@ var journalOperations = defineOperations({
|
|
|
31745
31944
|
scopes: ["journal:write"],
|
|
31746
31945
|
tag: "Journal",
|
|
31747
31946
|
summary: "Stage daily notes for the backfill",
|
|
31748
|
-
description: "Stage parsed daily notes (`do journal import push <dir>`): one item per day (at most 400 a request) with its source file and hash, what becomes its entry (`summary`, `retro` or `log`), a one-liner, the summary's text (health lines left out), a clear `rating` or a `ratingQuestion` (\"~5/10\"), a highlight, whether it may be a milestone or had health lines, and the links counted. Nothing is written into the Journal
|
|
31947
|
+
description: "Stage parsed daily notes (`do journal import push <dir>`): one item per day (at most 400 a request) with its source file and hash, what becomes its entry (`summary`, `retro` or `log`), a one-liner, the summary's text (health lines left out), a clear `rating` or a `ratingQuestion` (\"~5/10\"), a highlight, whether it may be a milestone or had health lines, and the links counted. A line about drinking, smoking, cannabis or other drugs, relapse or fertility is stripped from the title, text, highlight and rating question here, whatever parsed the day. Nothing is written into the Journal until a batch is approved (`journal_import_approve`, or the member's Approve in the app). A day still waiting is replaced; one already imported is left alone (`alreadyImported`). Staged days stand behind the Journal's wall.",
|
|
31749
31948
|
body: journalImportUploadInputSchema,
|
|
31750
31949
|
status: 200,
|
|
31751
31950
|
response: { description: "How the upload went.", view: "JournalImportUpload" },
|
|
31752
31951
|
errors: { 400: true }
|
|
31753
31952
|
},
|
|
31953
|
+
approveImport: {
|
|
31954
|
+
method: "post",
|
|
31955
|
+
path: "/journal/imports/approve",
|
|
31956
|
+
scopes: ["journal:write"],
|
|
31957
|
+
tag: "Journal",
|
|
31958
|
+
summary: "Approve staged days into the Journal",
|
|
31959
|
+
description: 'Write staged days into the Journal (`do journal import approve <YYYY-MM|--all>`): one batch (`batch`, a month `YYYY-MM`) or every staged batch (`all: true`), never both. `ratings` answers the rating questions inline, one per day (`[{"day":"2026-10-03","rating":7}]`, 1 to 10). A day whose question has no answer stays staged and comes back in `open` with its question; the rest are written as imports (one-liner, summary, highlight, the `rating` and `sleep` Metrics), and a day the Journal already has keeps what\'s there and only gains a missing rating or sleep. An agent\'s approve goes through the same writes as `journal_write_day`: it shows in the member\'s change list with Undo, and past the bulk limit it waits for the member\'s confirmation (428). Lines about drinking, smoking, drugs, relapse or fertility are never written. 400 when a rating names a day that isn\'t waiting in the batch, or nothing is ready and nothing is open.',
|
|
31960
|
+
body: journalImportApproveInputSchema,
|
|
31961
|
+
status: 200,
|
|
31962
|
+
response: { description: "How the approve went.", view: "JournalImportApprove" },
|
|
31963
|
+
errors: { 400: true },
|
|
31964
|
+
operationId: "journal_import_approve"
|
|
31965
|
+
},
|
|
31754
31966
|
importOverview: {
|
|
31755
31967
|
method: "get",
|
|
31756
31968
|
path: "/journal/imports",
|
|
@@ -33674,7 +33886,7 @@ var moneyOperations = defineOperations({
|
|
|
33674
33886
|
scopes: ["finance:read"],
|
|
33675
33887
|
tag: "Money",
|
|
33676
33888
|
summary: "List money goals",
|
|
33677
|
-
description: "The goals this key may see (the household's, and its member's Only me ones), soonest target first: `targetAmount`, `targetOn`, `monthlyPlan`, the pot's account and Project by name, `saved` (`savedBefore` plus the transactions counted toward it) and the lines' `linesPlanned`/`linesPaid`.",
|
|
33889
|
+
description: "The goals this key may see (the household's, and its member's Only me ones), soonest target first: `targetAmount`, `targetOn`, `monthlyPlan`, the pot's account and Project by name, `saved` and where it comes from (`savedFrom`: `account`, the balance of the account it's alone in; `account_share`, its own capped by what a shared account has left, soonest target first; `contributions`, `savedBefore` plus the transactions counted toward it, also when the account's balance is unknown, `accountBalanceKnown: false`), `remaining`, `monthlyNeed` (to `targetOn`) and the lines' `linesPlanned`/`linesPaid`.",
|
|
33678
33890
|
status: 200,
|
|
33679
33891
|
response: { description: "The goals." }
|
|
33680
33892
|
},
|
|
@@ -34814,7 +35026,8 @@ var planUnplaceInputSchema = object({ taskId: ulidSchema }).strict();
|
|
|
34814
35026
|
var planListQuerySchema = object({
|
|
34815
35027
|
kind: planKindSchema.optional(),
|
|
34816
35028
|
status: planStatusSchema.optional(),
|
|
34817
|
-
startsOn: civilDaySchema.optional()
|
|
35029
|
+
startsOn: civilDaySchema.optional(),
|
|
35030
|
+
taskId: ulidSchema.optional()
|
|
34818
35031
|
});
|
|
34819
35032
|
var planTaskSchema = object({
|
|
34820
35033
|
id: ulidSchema,
|
|
@@ -35034,6 +35247,7 @@ var automationListItemSchema = automationViewSchema.extend({
|
|
|
35034
35247
|
madeBy: object({ userId: string2(), name: string2(), you: boolean2() }).nullable(),
|
|
35035
35248
|
lastRun: automationRunSummarySchema.nullable()
|
|
35036
35249
|
});
|
|
35250
|
+
var automationRunNowSchema = object({ queued: literal(true) });
|
|
35037
35251
|
|
|
35038
35252
|
// ../../packages/schema/src/db/shopping.ts
|
|
35039
35253
|
var shoppingLists = pgTable("shopping_lists", {
|
|
@@ -38787,6 +39001,18 @@ var WEEKDAY_ALT = "monday|tuesday|wednesday|thursday|friday|saturday|sunday|mon|
|
|
|
38787
39001
|
var EVERY_N_RE = /\bevery\s+(\d+)\s+(day|days|week|weeks|month|months|year|years)\b/i;
|
|
38788
39002
|
var EVERY_WEEKDAYS_RE = /\bevery\s+(?:weekday|weekdays|work\s?day|work\s?days)\b/i;
|
|
38789
39003
|
var EVERY_WEEKDAY_RE = new RegExp(`\\bevery\\s+(${WEEKDAY_ALT})\\b`, "i");
|
|
39004
|
+
var EVERY_NTH_WEEKDAY_RE = new RegExp(`\\bevery\\s+(first|1st|second|2nd|third|3rd|fourth|4th|last)\\s+(${WEEKDAY_ALT}|sun)(?:\\s+of\\s+(?:the|each|every)\\s+month)?\\b`, "i");
|
|
39005
|
+
var NTH = {
|
|
39006
|
+
first: "1",
|
|
39007
|
+
"1st": "1",
|
|
39008
|
+
second: "2",
|
|
39009
|
+
"2nd": "2",
|
|
39010
|
+
third: "3",
|
|
39011
|
+
"3rd": "3",
|
|
39012
|
+
fourth: "4",
|
|
39013
|
+
"4th": "4",
|
|
39014
|
+
last: "-1"
|
|
39015
|
+
};
|
|
38790
39016
|
var EVERY_UNIT_RE = /\bevery\s+(day|week|month|year)\b/i;
|
|
38791
39017
|
var ADVERB_RE = /\b(daily|weekly|monthly|yearly|annually)\b/i;
|
|
38792
39018
|
var RELATIVE_RE = /\b(today|tonight|tomorrow|tmrw|tmr|yesterday)\b/i;
|
|
@@ -38918,6 +39144,12 @@ function takeEstimate(scan, take) {
|
|
|
38918
39144
|
return;
|
|
38919
39145
|
}
|
|
38920
39146
|
function takeRecurrence(scan, take) {
|
|
39147
|
+
const nth = scan.match(EVERY_NTH_WEEKDAY_RE);
|
|
39148
|
+
if (nth && take(nth, "recurrence")) {
|
|
39149
|
+
const ordinal = NTH[(nth[1] ?? "").toLowerCase()] ?? "1";
|
|
39150
|
+
const byDay = `${ordinal}${weekdayByName(nth[2]).code}`;
|
|
39151
|
+
return { mode: "rigid", freq: "monthly", interval: 1, byDay: [byDay] };
|
|
39152
|
+
}
|
|
38921
39153
|
const everyN = scan.match(EVERY_N_RE);
|
|
38922
39154
|
if (everyN && take(everyN, "recurrence")) {
|
|
38923
39155
|
const [, nStr = "1", unitRaw = "day"] = everyN;
|
|
@@ -39053,8 +39285,28 @@ function firstOccurrence(rule, now) {
|
|
|
39053
39285
|
});
|
|
39054
39286
|
return addDays(today, Math.min(...deltas));
|
|
39055
39287
|
}
|
|
39288
|
+
const nth = rule.freq === "monthly" ? rule.byDay?.map(parseByDay) : undefined;
|
|
39289
|
+
if (nth?.length) {
|
|
39290
|
+
const days = [0, 1].flatMap((ahead) => nth.flatMap((p) => {
|
|
39291
|
+
if (!p)
|
|
39292
|
+
return [];
|
|
39293
|
+
const dow = WEEKDAYS.find((w) => w.code === p.weekday)?.dow ?? 0;
|
|
39294
|
+
const day = nthWeekdayOfMonth(today.getFullYear(), today.getMonth() + ahead, dow, p.ordinal ?? 1);
|
|
39295
|
+
return day >= today ? [day] : [];
|
|
39296
|
+
}));
|
|
39297
|
+
if (days.length > 0)
|
|
39298
|
+
return new Date(Math.min(...days.map((d) => d.getTime())));
|
|
39299
|
+
}
|
|
39056
39300
|
return today;
|
|
39057
39301
|
}
|
|
39302
|
+
function nthWeekdayOfMonth(year, month, dow, ordinal) {
|
|
39303
|
+
if (ordinal < 0) {
|
|
39304
|
+
const last = new Date(year, month + 1, 0);
|
|
39305
|
+
return addDays(last, -((last.getDay() - dow + 7) % 7));
|
|
39306
|
+
}
|
|
39307
|
+
const first = new Date(year, month, 1);
|
|
39308
|
+
return addDays(first, (dow - first.getDay() + 7) % 7 + (ordinal - 1) * 7);
|
|
39309
|
+
}
|
|
39058
39310
|
function addMonths(d, months) {
|
|
39059
39311
|
const next = new Date(d);
|
|
39060
39312
|
next.setMonth(next.getMonth() + months);
|
|
@@ -39125,6 +39377,7 @@ var bookFacetSchema = object({
|
|
|
39125
39377
|
coverSources: coverSourcesSchema.nullish(),
|
|
39126
39378
|
pageCount: number2().int().min(1).max(1e5).nullish(),
|
|
39127
39379
|
currentPage: number2().int().min(0).max(1e5).nullish(),
|
|
39380
|
+
chapter: string2().trim().min(1).max(60).nullish(),
|
|
39128
39381
|
isbn: string2().regex(/^[0-9Xx][0-9Xx\s-]{8,20}$/, "must be an ISBN").nullish()
|
|
39129
39382
|
}).strict();
|
|
39130
39383
|
var filmFacetSchema = object({
|
|
@@ -40342,7 +40595,17 @@ function renderJournalImportScan(data, mode) {
|
|
|
40342
40595
|
}
|
|
40343
40596
|
out.push(` milestone candidates ${count((i) => Boolean(i.milestoneCandidate))}`);
|
|
40344
40597
|
out.push(` health lines (left for Health) ${count((i) => Boolean(i.health))}`);
|
|
40345
|
-
out.push("", d.dryRun ? paint(mode, "dim", "Dry run: nothing uploaded.") : `Staged ${d.staged ?? 0}${d.alreadyImported ? ` (${d.alreadyImported} already in the Journal, left as they are)` : ""}. Approve each month in the app: Journal \u203A Import.`);
|
|
40598
|
+
out.push("", d.dryRun ? paint(mode, "dim", "Dry run: nothing uploaded.") : `Staged ${d.staged ?? 0}${d.alreadyImported ? ` (${d.alreadyImported} already in the Journal, left as they are)` : ""}. Approve each month with do journal import approve <YYYY-MM>, or in the app: Journal \u203A Import.`);
|
|
40599
|
+
return out.join(`
|
|
40600
|
+
`);
|
|
40601
|
+
}
|
|
40602
|
+
function renderJournalImportApprove(data, mode) {
|
|
40603
|
+
const d = data;
|
|
40604
|
+
const batches = d.batches?.length ? paint(mode, "dim", ` (${d.batches.join(", ")})`) : "";
|
|
40605
|
+
const out = [`Imported ${d.imported ?? 0} days into the Journal${batches}`];
|
|
40606
|
+
if (d.open?.length) {
|
|
40607
|
+
out.push("", "Still asking for a rating, left staged (answer with --rating YYYY-MM-DD=N):", ...d.open.map((o) => ` ${o.day} \u201C${o.ratingQuestion}\u201D`));
|
|
40608
|
+
}
|
|
40346
40609
|
return out.join(`
|
|
40347
40610
|
`);
|
|
40348
40611
|
}
|
|
@@ -41471,11 +41734,16 @@ var commands2 = [
|
|
|
41471
41734
|
],
|
|
41472
41735
|
flags: [
|
|
41473
41736
|
{ name: "unit", type: "string", description: "For a weight: g (default) or kg." },
|
|
41737
|
+
{
|
|
41738
|
+
name: "note",
|
|
41739
|
+
type: "string",
|
|
41740
|
+
description: 'For a weight: a short note drawn under its date ("vet check done").'
|
|
41741
|
+
},
|
|
41474
41742
|
{ name: "at", type: "string", description: "When (ISO 8601); default now." }
|
|
41475
41743
|
],
|
|
41476
41744
|
scopes: [...animalOperations.log.scopes],
|
|
41477
41745
|
returns: "AnimalTimelineEntry",
|
|
41478
|
-
notes: `${routeOf(animalOperations.log)} (\`animal_log\`). \`do animals log <hens> eggs 2\`, \`do animals log <dog> weight 37.8 --unit kg\`, \`do animals log <hen> note "eating again"\`. Eggs are the day's count: logging again that day replaces it.`
|
|
41746
|
+
notes: `${routeOf(animalOperations.log)} (\`animal_log\`). \`do animals log <hens> eggs 2\`, \`do animals log <dog> weight 37.8 --unit kg\`, \`do animals log <tortoise> weight 1409 --note "vet check done"\`, \`do animals log <hen> note "eating again"\`. Eggs are the day's count: logging again that day replaces it.`
|
|
41479
41747
|
},
|
|
41480
41748
|
run: async ({ args, flags, requireClient }) => {
|
|
41481
41749
|
const id = requireArg(args, 0, "animal id");
|
|
@@ -41485,8 +41753,11 @@ var commands2 = [
|
|
|
41485
41753
|
throw usageError("animals log needs a value (a number, or the note's text)");
|
|
41486
41754
|
const at = flagString(flags, "at");
|
|
41487
41755
|
const unit = flagString(flags, "unit");
|
|
41756
|
+
const note = flagString(flags, "note");
|
|
41757
|
+
if (note && kind !== "weight")
|
|
41758
|
+
throw usageError("--note goes with a weight");
|
|
41488
41759
|
const n = Number(raw);
|
|
41489
|
-
const fact = kind === "note" ? { kind, text: raw } : kind === "eggs" ? { kind, count: n } : kind === "weight" ? { kind, value: n, ...unit ? { unit } : {} } : { kind, value: n };
|
|
41760
|
+
const fact = kind === "note" ? { kind, text: raw } : kind === "eggs" ? { kind, count: n } : kind === "weight" ? { kind, value: n, ...unit ? { unit } : {}, ...note ? { note } : {} } : { kind, value: n };
|
|
41490
41761
|
if (kind !== "note" && Number.isNaN(n))
|
|
41491
41762
|
throw usageError("the value must be a number");
|
|
41492
41763
|
const body = parseInput(animalLogInputSchema, { ...fact, ...at ? { occurredAt: at } : {} });
|
|
@@ -42414,6 +42685,20 @@ var commands7 = [
|
|
|
42414
42685
|
];
|
|
42415
42686
|
|
|
42416
42687
|
// src/commands/collection.ts
|
|
42688
|
+
var CHAPTER_FLAG = {
|
|
42689
|
+
name: "chapter",
|
|
42690
|
+
type: "string",
|
|
42691
|
+
description: 'Where in the work: "9" (a chapter), "chapter 9", "book 9 and on".'
|
|
42692
|
+
};
|
|
42693
|
+
var LINKED_NOTE_FLAG = {
|
|
42694
|
+
name: "linked-note",
|
|
42695
|
+
type: "string",
|
|
42696
|
+
description: "Note ULID this Entry is tied to."
|
|
42697
|
+
};
|
|
42698
|
+
var whereFlags = (flags) => ({
|
|
42699
|
+
...flagString(flags, "chapter") ? { chapter: flagString(flags, "chapter") } : {},
|
|
42700
|
+
...flagString(flags, "linked-note") ? { linkedNoteId: flagString(flags, "linked-note") } : {}
|
|
42701
|
+
});
|
|
42417
42702
|
var commands8 = [
|
|
42418
42703
|
{
|
|
42419
42704
|
spec: {
|
|
@@ -42537,6 +42822,53 @@ var commands8 = [
|
|
|
42537
42822
|
};
|
|
42538
42823
|
}
|
|
42539
42824
|
},
|
|
42825
|
+
{
|
|
42826
|
+
spec: {
|
|
42827
|
+
path: ["collection", "reading"],
|
|
42828
|
+
summary: "Say where you are in a book: its chapter and/or page.",
|
|
42829
|
+
args: [{ name: "id", required: true, description: "Item ULID (a book)." }],
|
|
42830
|
+
flags: [
|
|
42831
|
+
{
|
|
42832
|
+
name: "chapter",
|
|
42833
|
+
type: "string",
|
|
42834
|
+
description: 'Where you are: "9" (a chapter), "chapter 9", "book 9".'
|
|
42835
|
+
},
|
|
42836
|
+
{ name: "page", type: "string", description: "The page you're on." },
|
|
42837
|
+
{ name: "clear-chapter", type: "boolean", description: "Forget the chapter." }
|
|
42838
|
+
],
|
|
42839
|
+
scopes: [...collectionOperations.update.scopes],
|
|
42840
|
+
returns: "CollectionItemView",
|
|
42841
|
+
notes: `Reads the book's facet and patches it back with the new place (${routeOf(collectionOperations.update)}). A first page moves a book you want to doing (Progress); a chapter alone moves nothing.`
|
|
42842
|
+
},
|
|
42843
|
+
run: async ({ args, flags, requireClient }) => {
|
|
42844
|
+
const id = requireArg(args, 0, "item id");
|
|
42845
|
+
const chapter = flagString(flags, "chapter");
|
|
42846
|
+
const clear = flagBool(flags, "clear-chapter");
|
|
42847
|
+
const page = flagString(flags, "page");
|
|
42848
|
+
if (page !== undefined && !/^\d+$/.test(page)) {
|
|
42849
|
+
throw usageError("--page must be a whole number, e.g. 142");
|
|
42850
|
+
}
|
|
42851
|
+
if (chapter === undefined && page === undefined && !clear) {
|
|
42852
|
+
throw usageError('collection reading needs --chapter or --page, e.g. --chapter "9"');
|
|
42853
|
+
}
|
|
42854
|
+
const client = requireClient();
|
|
42855
|
+
const item = await client.get(pathOf(collectionOperations.get, { id }));
|
|
42856
|
+
const { chapter: kept, ...rest } = item.facet ?? {};
|
|
42857
|
+
const facet = {
|
|
42858
|
+
...rest,
|
|
42859
|
+
...clear || kept === undefined ? {} : { chapter: kept }
|
|
42860
|
+
};
|
|
42861
|
+
if (chapter !== undefined)
|
|
42862
|
+
facet.chapter = chapter;
|
|
42863
|
+
if (page !== undefined)
|
|
42864
|
+
facet.currentPage = Number(page);
|
|
42865
|
+
const body = parseInput(updateCollectionItemInputSchema, { facet });
|
|
42866
|
+
return {
|
|
42867
|
+
data: await client.patch(pathOf(collectionOperations.update, { id }), body),
|
|
42868
|
+
render: renderRecord
|
|
42869
|
+
};
|
|
42870
|
+
}
|
|
42871
|
+
},
|
|
42540
42872
|
{
|
|
42541
42873
|
spec: {
|
|
42542
42874
|
path: ["collection", "add-entry"],
|
|
@@ -42556,7 +42888,9 @@ var commands8 = [
|
|
|
42556
42888
|
description: "What happened: use (default) \xB7 service \xB7 repair \xB7 fault \xB7 lent \xB7 returned \xB7 valued \xB7 moved."
|
|
42557
42889
|
},
|
|
42558
42890
|
{ name: "cost", type: "string", description: "What it cost (a sweep, a repair)." },
|
|
42559
|
-
{ name: "contact", type: "string", description: "Contact ULID of who did it." }
|
|
42891
|
+
{ name: "contact", type: "string", description: "Contact ULID of who did it." },
|
|
42892
|
+
CHAPTER_FLAG,
|
|
42893
|
+
LINKED_NOTE_FLAG
|
|
42560
42894
|
],
|
|
42561
42895
|
scopes: [...collectionOperations.addEntry.scopes],
|
|
42562
42896
|
returns: "CollectionItemDetail"
|
|
@@ -42578,7 +42912,8 @@ var commands8 = [
|
|
|
42578
42912
|
...flagString(flags, "note") ? { note: flagString(flags, "note") } : {},
|
|
42579
42913
|
...flagString(flags, "kind") ? { kind: flagString(flags, "kind") } : {},
|
|
42580
42914
|
...cost !== undefined ? { cost: Number(cost) } : {},
|
|
42581
|
-
...flagString(flags, "contact") ? { contactId: flagString(flags, "contact") } : {}
|
|
42915
|
+
...flagString(flags, "contact") ? { contactId: flagString(flags, "contact") } : {},
|
|
42916
|
+
...whereFlags(flags)
|
|
42582
42917
|
});
|
|
42583
42918
|
return {
|
|
42584
42919
|
data: await requireClient().post(pathOf(collectionOperations.addEntry, { id }), body),
|
|
@@ -42667,7 +43002,8 @@ var commands8 = [
|
|
|
42667
43002
|
name: "on",
|
|
42668
43003
|
type: "string",
|
|
42669
43004
|
description: "Day it occurred (YYYY-MM-DD); defaults to today."
|
|
42670
|
-
}
|
|
43005
|
+
},
|
|
43006
|
+
CHAPTER_FLAG
|
|
42671
43007
|
],
|
|
42672
43008
|
scopes: [...collectionOperations.log.scopes],
|
|
42673
43009
|
returns: "CollectionLogResult",
|
|
@@ -42688,7 +43024,8 @@ var commands8 = [
|
|
|
42688
43024
|
...flagString(flags, "status") ? { status: flagString(flags, "status") } : {},
|
|
42689
43025
|
...rating !== undefined ? { rating: Number(rating) } : {},
|
|
42690
43026
|
...flagString(flags, "note") ? { note: flagString(flags, "note") } : {},
|
|
42691
|
-
...flagString(flags, "on") ? { occurredOn: flagString(flags, "on") } : {}
|
|
43027
|
+
...flagString(flags, "on") ? { occurredOn: flagString(flags, "on") } : {},
|
|
43028
|
+
...flagString(flags, "chapter") ? { chapter: flagString(flags, "chapter") } : {}
|
|
42692
43029
|
});
|
|
42693
43030
|
return {
|
|
42694
43031
|
data: await requireClient().post(pathOf(collectionOperations.log), body),
|
|
@@ -42699,7 +43036,7 @@ var commands8 = [
|
|
|
42699
43036
|
{
|
|
42700
43037
|
spec: {
|
|
42701
43038
|
path: ["collection", "edit-entry"],
|
|
42702
|
-
summary: "Fix an Entry's day, rating or note.",
|
|
43039
|
+
summary: "Fix an Entry's day, rating, note, chapter or linked note.",
|
|
42703
43040
|
args: [
|
|
42704
43041
|
{ name: "id", required: true, description: "Item ULID." },
|
|
42705
43042
|
{ name: "entryId", required: true, description: "Entry ULID." }
|
|
@@ -42707,7 +43044,9 @@ var commands8 = [
|
|
|
42707
43044
|
flags: [
|
|
42708
43045
|
{ name: "rating", type: "string", description: "New rating 0.5\u20135." },
|
|
42709
43046
|
{ name: "note", type: "string", description: "New note." },
|
|
42710
|
-
{ name: "on", type: "string", description: "New day (YYYY-MM-DD)." }
|
|
43047
|
+
{ name: "on", type: "string", description: "New day (YYYY-MM-DD)." },
|
|
43048
|
+
CHAPTER_FLAG,
|
|
43049
|
+
LINKED_NOTE_FLAG
|
|
42711
43050
|
],
|
|
42712
43051
|
scopes: [...collectionOperations.editEntry.scopes],
|
|
42713
43052
|
returns: "CollectionItemDetail"
|
|
@@ -42722,7 +43061,8 @@ var commands8 = [
|
|
|
42722
43061
|
const body = parseInput(updateCollectionEntryInputSchema, {
|
|
42723
43062
|
...rating !== undefined ? { rating: Number(rating) } : {},
|
|
42724
43063
|
...flagString(flags, "note") ? { note: flagString(flags, "note") } : {},
|
|
42725
|
-
...flagString(flags, "on") ? { occurredOn: flagString(flags, "on") } : {}
|
|
43064
|
+
...flagString(flags, "on") ? { occurredOn: flagString(flags, "on") } : {},
|
|
43065
|
+
...whereFlags(flags)
|
|
42726
43066
|
});
|
|
42727
43067
|
return {
|
|
42728
43068
|
data: await requireClient().patch(pathOf(collectionOperations.editEntry, { id, entryId }), body),
|
|
@@ -46791,7 +47131,8 @@ import { createHash } from "crypto";
|
|
|
46791
47131
|
import { readdirSync, readFileSync as readFileSync7 } from "fs";
|
|
46792
47132
|
import { join as join2 } from "path";
|
|
46793
47133
|
var DAY_FILE = /^(\d{4}-\d{2}-\d{2})\.md$/;
|
|
46794
|
-
var
|
|
47134
|
+
var SCORE = "(?:10|[1-9])";
|
|
47135
|
+
var RATING = new RegExp(`(?:\\p{L}+\\s*\\()?(?<![\\p{L}\\p{N}_./~-])(~)?\\s*(${SCORE}(?:\\.\\d)?)(?:\\s*[-\u2013]\\s*(${SCORE}))?\\s*/\\s*10(?![\\p{L}\\p{N}_/-]|[.,]\\d)\\)?`, "u");
|
|
46795
47136
|
var HEALTH = /\b(symptoms?|stomach|nausea|headaches?|migraine|doctor|GP|medication|meds|pills?|blood|fever|pain|clinic|hospital)\b/i;
|
|
46796
47137
|
var NOISE = /\b(PR monitor|GitHub daily|deploy(?:ment)? watch\w*|cron|heartbeat|sales prospects?)\b/i;
|
|
46797
47138
|
var MILESTONE = /\b(first|went live|is live|win:|moved in|arrived|wedding|engaged)\b/i;
|
|
@@ -46860,8 +47201,8 @@ function parseDailyNote(path, day, text) {
|
|
|
46860
47201
|
const ratingLine = all.find((l) => /rating/i.test(l) && RATING.test(l)) ?? all.find((l) => RATING.test(l));
|
|
46861
47202
|
const match = ratingLine ? RATING.exec(ratingLine) : null;
|
|
46862
47203
|
if (match) {
|
|
46863
|
-
const [whole, approx, low, high] = match;
|
|
46864
|
-
if (approx || high)
|
|
47204
|
+
const [whole, approx, low = "", high] = match;
|
|
47205
|
+
if (approx || high || low.includes("."))
|
|
46865
47206
|
ratingQuestion = whole.trim();
|
|
46866
47207
|
else
|
|
46867
47208
|
rating = Number(low);
|
|
@@ -46883,7 +47224,7 @@ function parseDailyNote(path, day, text) {
|
|
|
46883
47224
|
inCheck = false;
|
|
46884
47225
|
if (line === ratingLine || /how was (the|your) day/i.test(line))
|
|
46885
47226
|
continue;
|
|
46886
|
-
if (HEALTH.test(line) || NOISE.test(line))
|
|
47227
|
+
if (HEALTH.test(line) || NOISE.test(line) || isPrivateLine(line))
|
|
46887
47228
|
continue;
|
|
46888
47229
|
kept.push(line);
|
|
46889
47230
|
}
|
|
@@ -46893,8 +47234,13 @@ function parseDailyNote(path, day, text) {
|
|
|
46893
47234
|
`).trim() || null;
|
|
46894
47235
|
const firstProse = (body ?? "").split(/\n\s*\n/).map((p) => plain(p)).find((p) => p.length > 0);
|
|
46895
47236
|
title = firstProse ? firstSentence(firstProse.replace(/^The day\s*/i, "")) : null;
|
|
47237
|
+
} else {
|
|
47238
|
+
body = all.filter((l) => !/^#\s/.test(l) && l !== ratingLine && !/how was (the|your) day/i.test(l) && !HEALTH.test(l) && !NOISE.test(l) && !isPrivateLine(l)).join(`
|
|
47239
|
+
`).replace(/\n{3,}/g, `
|
|
47240
|
+
|
|
47241
|
+
`).trim() || null;
|
|
46896
47242
|
}
|
|
46897
|
-
const milestoneLine = all.find((l) => MILESTONE.test(l) && !HEALTH.test(l) && !NOISE.test(l) && plain(l).length > 0);
|
|
47243
|
+
const milestoneLine = all.find((l) => MILESTONE.test(l) && !HEALTH.test(l) && !NOISE.test(l) && !isPrivateLine(l) && plain(l).length > 0);
|
|
46898
47244
|
const highlight = milestoneLine ? firstSentence(plain(milestoneLine), 80) : null;
|
|
46899
47245
|
const sleep = sleepOf(all);
|
|
46900
47246
|
const prs = (text.match(PR_REF) ?? []).length;
|
|
@@ -47275,7 +47621,7 @@ var commands24 = [
|
|
|
47275
47621
|
description: "How the days name their folder (default memory/daily)."
|
|
47276
47622
|
}
|
|
47277
47623
|
],
|
|
47278
|
-
notes: ", 400 days a request. The day's '## Day summary' becomes its entry; a clear 'n/10' rating is kept, '~5/10' or '3-4/10' becomes a question; health and automation lines never reach the text. Nothing is written into the Journal until
|
|
47624
|
+
notes: ", 400 days a request. The day's '## Day summary' becomes its entry; a clear 'n/10' rating (n 1 to 10, standing on its own) is kept, '~5/10' or '3-4/10' becomes a question; health and automation lines never reach the text, and lines about drinking, smoking, drugs, relapse or fertility never reach the title, text or highlight (the API strips them too). A day without a summary gets its kept lines as its text. Nothing is written into the Journal until a month is approved (`do journal import approve`, or the member in the app). Re-running replaces what's still waiting.",
|
|
47279
47625
|
run: async ({ args, flags, requireClient }) => {
|
|
47280
47626
|
const dir = requireArg(args, 0, "dir");
|
|
47281
47627
|
const items = scanDailyNotes(dir, flagString(flags, "label") ?? "memory/daily");
|
|
@@ -47300,6 +47646,42 @@ var commands24 = [
|
|
|
47300
47646
|
};
|
|
47301
47647
|
}
|
|
47302
47648
|
}),
|
|
47649
|
+
defineCommand({
|
|
47650
|
+
operation: journalOperations.approveImport,
|
|
47651
|
+
path: ["journal", "import", "approve"],
|
|
47652
|
+
summary: "Approve staged days into the Journal: one month's batch, or --all; --rating answers a fuzzy rating.",
|
|
47653
|
+
args: [{ name: "batch", required: false, description: "The month to approve, YYYY-MM." }],
|
|
47654
|
+
flags: [
|
|
47655
|
+
{ name: "all", type: "boolean", description: "Approve every staged batch." },
|
|
47656
|
+
{
|
|
47657
|
+
name: "rating",
|
|
47658
|
+
type: "string",
|
|
47659
|
+
description: "Answer a day's rating question, YYYY-MM-DD=N (1 to 10); repeat per day."
|
|
47660
|
+
}
|
|
47661
|
+
],
|
|
47662
|
+
notes: ". A day whose rating question has no --rating stays staged and is listed as open; the rest are written as imports. An agent's approve shows in the member's change list with Undo. Lines about drinking, smoking, drugs, relapse or fertility are never written.",
|
|
47663
|
+
run: async ({ args, flags, requireClient }) => {
|
|
47664
|
+
const batch = args[0];
|
|
47665
|
+
const all = flagBool(flags, "all");
|
|
47666
|
+
if (batch === undefined === !all) {
|
|
47667
|
+
throw usageError("approve one month (YYYY-MM) or --all");
|
|
47668
|
+
}
|
|
47669
|
+
const ratings = flagStrings(flags, "rating").map((answer) => {
|
|
47670
|
+
const m = /^(\d{4}-\d{2}-\d{2})=(\d+(?:\.\d+)?)$/.exec(answer.trim());
|
|
47671
|
+
if (!m)
|
|
47672
|
+
throw usageError(`--rating takes YYYY-MM-DD=N, got "${answer}"`);
|
|
47673
|
+
return { day: m[1], rating: Number(m[2]) };
|
|
47674
|
+
});
|
|
47675
|
+
const input = parseInput(journalImportApproveInputSchema, {
|
|
47676
|
+
...all ? { all: true } : { batch },
|
|
47677
|
+
ratings
|
|
47678
|
+
});
|
|
47679
|
+
return {
|
|
47680
|
+
data: await requireClient().post(pathOf(journalOperations.approveImport), input),
|
|
47681
|
+
render: renderJournalImportApprove
|
|
47682
|
+
};
|
|
47683
|
+
}
|
|
47684
|
+
}),
|
|
47303
47685
|
defineCommand({
|
|
47304
47686
|
operation: journalOperations.importOverview,
|
|
47305
47687
|
path: ["journal", "import", "status"],
|
|
@@ -48186,6 +48568,47 @@ var commands30 = [
|
|
|
48186
48568
|
// src/commands/money.ts
|
|
48187
48569
|
import { readFileSync as readFileSync8 } from "fs";
|
|
48188
48570
|
import { basename as basename5 } from "path";
|
|
48571
|
+
|
|
48572
|
+
// ../../packages/domain/src/money.ts
|
|
48573
|
+
var toNumber = (cents) => Number(cents) / 100;
|
|
48574
|
+
function formatMoney(amount, {
|
|
48575
|
+
currency = "EUR",
|
|
48576
|
+
signed = false,
|
|
48577
|
+
whole = false,
|
|
48578
|
+
cents: always = false,
|
|
48579
|
+
locale
|
|
48580
|
+
} = {}) {
|
|
48581
|
+
const exact = typeof amount === "bigint" ? amount : amountToCents(amount);
|
|
48582
|
+
const cents = whole ? roundToEuro(exact) : exact;
|
|
48583
|
+
const abs = cents < 0n ? -cents : cents;
|
|
48584
|
+
const text = new Intl.NumberFormat(locale ?? "en-IE", {
|
|
48585
|
+
style: "currency",
|
|
48586
|
+
currency,
|
|
48587
|
+
minimumFractionDigits: always || abs % 100n !== 0n ? 2 : 0,
|
|
48588
|
+
maximumFractionDigits: 2
|
|
48589
|
+
}).format(toNumber(abs));
|
|
48590
|
+
if (cents < 0n)
|
|
48591
|
+
return `\u2212${text}`;
|
|
48592
|
+
return signed && cents > 0n ? `+${text}` : text;
|
|
48593
|
+
}
|
|
48594
|
+
function roundToEuro(cents) {
|
|
48595
|
+
const abs = cents < 0n ? -cents : cents;
|
|
48596
|
+
const rounded = (abs + 50n) / 100n * 100n;
|
|
48597
|
+
return cents < 0n ? -rounded : rounded;
|
|
48598
|
+
}
|
|
48599
|
+
function goalSavedSource(goal) {
|
|
48600
|
+
const account = goal.accountTitle ?? "the account";
|
|
48601
|
+
if (goal.savedFrom === "account")
|
|
48602
|
+
return `all of ${account}`;
|
|
48603
|
+
if (goal.savedFrom === "account_share")
|
|
48604
|
+
return `its share of ${account}`;
|
|
48605
|
+
if (goal.accountTitle && goal.accountBalanceKnown === false) {
|
|
48606
|
+
return `counted toward it; the balance of ${goal.accountTitle} is unknown`;
|
|
48607
|
+
}
|
|
48608
|
+
return "counted toward it";
|
|
48609
|
+
}
|
|
48610
|
+
|
|
48611
|
+
// src/commands/money.ts
|
|
48189
48612
|
function renderDedupe(data) {
|
|
48190
48613
|
const r = data;
|
|
48191
48614
|
const lines = r.pairs.map((p) => `${p.bookedOn} ${p.amount.padStart(10)} ${p.description ?? p.counterpartyName ?? ""} keep ${p.keepId} twin ${p.twinId} (${p.twinBookedOn})${p.ambiguous ? " ambiguous: sort by hand" : ""}`);
|
|
@@ -48194,6 +48617,38 @@ function renderDedupe(data) {
|
|
|
48194
48617
|
return lines.join(`
|
|
48195
48618
|
`);
|
|
48196
48619
|
}
|
|
48620
|
+
function renderGoals(data) {
|
|
48621
|
+
const goals = Array.isArray(data) ? data : [];
|
|
48622
|
+
if (goals.length === 0)
|
|
48623
|
+
return "(no goals yet)";
|
|
48624
|
+
return goals.map((g) => {
|
|
48625
|
+
const whole = (a) => formatMoney(a, { whole: true });
|
|
48626
|
+
const parts = [
|
|
48627
|
+
`${whole(g.saved)} of ${whole(g.targetAmount)} (${goalSavedSource(g)})`,
|
|
48628
|
+
Number(g.remaining) > 0 ? `${whole(g.remaining)} to go` : "reached",
|
|
48629
|
+
g.monthlyNeed && g.targetOn ? `${formatMoney(g.monthlyNeed)} a month by ${g.targetOn}` : null
|
|
48630
|
+
];
|
|
48631
|
+
return `${g.title} ${parts.filter(Boolean).join(" \xB7 ")} ${g.id}`;
|
|
48632
|
+
}).join(`
|
|
48633
|
+
`);
|
|
48634
|
+
}
|
|
48635
|
+
function syncLine(r) {
|
|
48636
|
+
const when = r.startedAt ? `${String(r.startedAt).slice(0, 16).replace("T", " ")} ` : "";
|
|
48637
|
+
const what = r.accountTitle ? `${r.accountTitle} ` : "";
|
|
48638
|
+
const counts = `fetched ${r.fetched ?? 0} \xB7 imported ${r.imported ?? 0} \xB7 matched ${r.matched ?? 0} \xB7 duplicates ${r.duplicates ?? 0}`;
|
|
48639
|
+
return `${when}${what}${r.outcome ?? "?"} ${counts}${r.error ? `
|
|
48640
|
+
${r.error}` : ""}`;
|
|
48641
|
+
}
|
|
48642
|
+
function renderSyncLog(data) {
|
|
48643
|
+
const rows = Array.isArray(data) ? data : data.data ?? [];
|
|
48644
|
+
return rows.length === 0 ? "(no syncs yet)" : rows.map(syncLine).join(`
|
|
48645
|
+
`);
|
|
48646
|
+
}
|
|
48647
|
+
function renderSyncResults(data) {
|
|
48648
|
+
const { results } = data;
|
|
48649
|
+
return results.length === 0 ? "(no live bank links)" : results.map(syncLine).join(`
|
|
48650
|
+
`);
|
|
48651
|
+
}
|
|
48197
48652
|
function partyFlag(flags) {
|
|
48198
48653
|
const userId = flagString(flags, "with-user");
|
|
48199
48654
|
const contactId = flagString(flags, "with-contact");
|
|
@@ -48330,7 +48785,7 @@ var commands31 = [
|
|
|
48330
48785
|
});
|
|
48331
48786
|
return {
|
|
48332
48787
|
data: await requireClient().post(pathOf(moneyOperations.syncConnections), body),
|
|
48333
|
-
render:
|
|
48788
|
+
render: renderSyncResults
|
|
48334
48789
|
};
|
|
48335
48790
|
}
|
|
48336
48791
|
}),
|
|
@@ -48367,7 +48822,7 @@ var commands31 = [
|
|
|
48367
48822
|
args: [{ name: "id", required: true, description: "Bank link (connection) ULID." }],
|
|
48368
48823
|
flags: [],
|
|
48369
48824
|
returns: "MoneyConnectionSync[]",
|
|
48370
|
-
run: readOperation(moneyOperations.listConnectionSyncs,
|
|
48825
|
+
run: readOperation(moneyOperations.listConnectionSyncs, renderSyncLog, { id: "id" })
|
|
48371
48826
|
}),
|
|
48372
48827
|
defineCommand({
|
|
48373
48828
|
operation: moneyOperations.listCategories,
|
|
@@ -49115,7 +49570,8 @@ var commands31 = [
|
|
|
49115
49570
|
args: [],
|
|
49116
49571
|
flags: [],
|
|
49117
49572
|
returns: "MoneyGoalView[]",
|
|
49118
|
-
|
|
49573
|
+
notes: ". `saved` reads `savedFrom`: `account` (the only goal in its account: the balance), `account_share` (several share one: each its own, capped by what's left, soonest target first) or `contributions` (savedBefore plus what's counted toward it; also when the account's balance is unknown, `accountBalanceKnown: false`). `remaining` is the target less saved; `monthlyNeed` spreads it over the months to `targetOn`.",
|
|
49574
|
+
run: readOperation(moneyOperations.listGoals, renderGoals)
|
|
49119
49575
|
}),
|
|
49120
49576
|
defineCommand({
|
|
49121
49577
|
operation: moneyOperations.getGoal,
|
|
@@ -52147,7 +52603,7 @@ var commands49 = [
|
|
|
52147
52603
|
"#tag": "Adds a tag. Repeatable: '#home #urgent'.",
|
|
52148
52604
|
"!": "Stars the task \u2014 the 1\u20132 things that matter (aliases: !star/!s/!flag/!f). Drives Today's focus and the starred filter.",
|
|
52149
52605
|
"~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`).",
|
|
52150
|
-
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.",
|
|
52606
|
+
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' / 'every first sunday' / 'every last friday' attaches a recurrence.",
|
|
52151
52607
|
"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.",
|
|
52152
52608
|
estimate: "How long it takes (durationMinutes): '30m', '45 min', '1h', '1.5h', '1h30', '2 hours', 'for 20 mins'. Not 'in 2 hours'."
|
|
52153
52609
|
}
|
package/package.json
CHANGED