@robbeverhelst/do 1.333.0 → 1.335.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 +1292 -85
- 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.335.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();
|
|
@@ -22874,6 +22915,12 @@ var moneyAccountKind = pgEnum("money_account_kind", [
|
|
|
22874
22915
|
"pension",
|
|
22875
22916
|
"asset_manual"
|
|
22876
22917
|
]);
|
|
22918
|
+
var moneyPensionSubtype = pgEnum("money_pension_subtype", [
|
|
22919
|
+
"group_insurance",
|
|
22920
|
+
"pension_savings_fund",
|
|
22921
|
+
"pension_savings_insurance",
|
|
22922
|
+
"vapz"
|
|
22923
|
+
]);
|
|
22877
22924
|
var moneyAccountHolder = pgEnum("money_account_holder", ["me", "joint", "other"]);
|
|
22878
22925
|
var moneyAccountEntity = pgEnum("money_account_entity", ["personal", "business"]);
|
|
22879
22926
|
var moneyImportSource = pgEnum("money_import_source", [
|
|
@@ -22925,6 +22972,8 @@ var moneyTaxTag = pgEnum("money_tax_tag", [
|
|
|
22925
22972
|
var moneyAccounts = pgTable("money_accounts", {
|
|
22926
22973
|
...leafColumns(),
|
|
22927
22974
|
kind: moneyAccountKind("kind").notNull().default("current"),
|
|
22975
|
+
pensionSubtype: moneyPensionSubtype("pension_subtype"),
|
|
22976
|
+
lockedUntil: date5("locked_until"),
|
|
22928
22977
|
holder: moneyAccountHolder("holder").notNull().default("me"),
|
|
22929
22978
|
entity: moneyAccountEntity("entity").notNull().default("personal"),
|
|
22930
22979
|
institutionContactId: text("institution_contact_id").references(() => contacts.id, {
|
|
@@ -25161,6 +25210,7 @@ var fileViewLinkSchema = object({
|
|
|
25161
25210
|
|
|
25162
25211
|
// ../../packages/schema/src/contracts/money.ts
|
|
25163
25212
|
var moneyAccountKindSchema = _enum2(moneyAccountKind.enumValues);
|
|
25213
|
+
var moneyPensionSubtypeSchema = _enum2(moneyPensionSubtype.enumValues);
|
|
25164
25214
|
var moneyAccountHolderSchema = _enum2(moneyAccountHolder.enumValues);
|
|
25165
25215
|
var moneyAccountEntitySchema = _enum2(moneyAccountEntity.enumValues);
|
|
25166
25216
|
var moneyImportSourceSchema = _enum2(moneyImportSource.enumValues);
|
|
@@ -25256,6 +25306,8 @@ var createMoneyAccountInputSchema = object({
|
|
|
25256
25306
|
id: ulidSchema,
|
|
25257
25307
|
title: string2().trim().min(1).max(120),
|
|
25258
25308
|
kind: moneyAccountKindSchema.optional(),
|
|
25309
|
+
pensionSubtype: moneyPensionSubtypeSchema.nullish(),
|
|
25310
|
+
lockedUntil: keyDateDaySchema.nullish(),
|
|
25259
25311
|
holder: moneyAccountHolderSchema.optional(),
|
|
25260
25312
|
institutionContactId: ulidSchema.nullish(),
|
|
25261
25313
|
iban: string2().trim().transform(compactIban).pipe(string2().regex(/^[A-Z]{2}\d{2}[A-Z0-9]{8,30}$/, "expected an IBAN")).nullish(),
|
|
@@ -25269,7 +25321,9 @@ var moneyBalanceAtSchema = union([keyDateDaySchema, exports_iso.datetime({ offse
|
|
|
25269
25321
|
var updateMoneyAccountInputSchema = createMoneyAccountInputSchema.omit({ id: true, currency: true }).partial().extend({
|
|
25270
25322
|
balance: moneyAmountSchema.optional(),
|
|
25271
25323
|
balanceAt: moneyBalanceAtSchema.optional()
|
|
25272
|
-
}).strict().refine((u) => u.
|
|
25324
|
+
}).strict().refine((u) => Object.values(u).some((v) => v !== undefined), {
|
|
25325
|
+
message: "nothing to change: give a field to set (or a balance with its balanceAt)"
|
|
25326
|
+
}).refine((u) => u.balance === undefined === (u.balanceAt === undefined), {
|
|
25273
25327
|
message: "a balance and the day it's from go together",
|
|
25274
25328
|
path: ["balanceAt"]
|
|
25275
25329
|
});
|
|
@@ -25566,13 +25620,19 @@ var moneyGoalLineSchema = createSelectSchema(moneyGoalLines, {
|
|
|
25566
25620
|
createdAt: exports_coerce.date(),
|
|
25567
25621
|
updatedAt: exports_coerce.date()
|
|
25568
25622
|
});
|
|
25623
|
+
var MONEY_GOAL_SAVED_FROM = ["account", "account_share", "contributions"];
|
|
25624
|
+
var moneyGoalSavedFromSchema = _enum2(MONEY_GOAL_SAVED_FROM);
|
|
25569
25625
|
var moneyGoalViewSchema = moneyGoalSchema.extend({
|
|
25570
25626
|
title: string2(),
|
|
25571
25627
|
visibility: _enum2(NODE_VISIBILITY_VALUES),
|
|
25572
25628
|
accountTitle: string2().nullable(),
|
|
25573
25629
|
accountBalance: amount.nullable(),
|
|
25630
|
+
accountBalanceKnown: boolean2().nullable(),
|
|
25574
25631
|
projectTitle: string2().nullable(),
|
|
25575
25632
|
saved: amount,
|
|
25633
|
+
savedFrom: moneyGoalSavedFromSchema,
|
|
25634
|
+
remaining: amount,
|
|
25635
|
+
monthlyNeed: amount.nullable(),
|
|
25576
25636
|
linesPlanned: amount,
|
|
25577
25637
|
linesPaid: amount
|
|
25578
25638
|
});
|
|
@@ -26511,6 +26571,16 @@ var familyTreeUnionSchema = object({
|
|
|
26511
26571
|
partners: array(familyTreeRefSchema),
|
|
26512
26572
|
children: array(familyTreeRefSchema)
|
|
26513
26573
|
});
|
|
26574
|
+
var FAMILY_TREE_MAX_GENERATIONS = 50;
|
|
26575
|
+
var familyTreeAncestorSchema = object({
|
|
26576
|
+
n: number2().int().min(1),
|
|
26577
|
+
generation: number2().int().min(0),
|
|
26578
|
+
ref: familyTreeRefSchema,
|
|
26579
|
+
repeatOf: number2().int().nullable()
|
|
26580
|
+
});
|
|
26581
|
+
function ahnentafelGeneration(n) {
|
|
26582
|
+
return Math.floor(Math.log2(n));
|
|
26583
|
+
}
|
|
26514
26584
|
var familyTreePersonViewSchema = object({
|
|
26515
26585
|
ref: familyTreeRefSchema,
|
|
26516
26586
|
name: string2(),
|
|
@@ -26568,11 +26638,24 @@ var familyTreeViewSchema = object({
|
|
|
26568
26638
|
birthdaysOffered: array(familyTreeRefSchema),
|
|
26569
26639
|
branch: familyTreeBranchViewSchema.nullable(),
|
|
26570
26640
|
matches: array(familyTreeMatchSchema),
|
|
26641
|
+
edits: object({ count: number2().int(), skipped: number2().int() }),
|
|
26571
26642
|
updatedAt: exports_coerce.date()
|
|
26572
26643
|
});
|
|
26573
26644
|
var familyTreeResponseSchema = object({ tree: familyTreeViewSchema.nullable() });
|
|
26574
26645
|
var sourceNameSchema = string2().trim().min(1).max(80);
|
|
26575
26646
|
var sourceEmailSchema = string2().trim().email().max(200);
|
|
26647
|
+
var familyTreeAncestorsQuerySchema = object({
|
|
26648
|
+
ref: familyTreeRefSchema.optional(),
|
|
26649
|
+
depth: exports_coerce.number().int().min(1).max(FAMILY_TREE_MAX_GENERATIONS).optional()
|
|
26650
|
+
});
|
|
26651
|
+
var familyTreeAncestorsSchema = object({
|
|
26652
|
+
rootRef: familyTreeRefSchema,
|
|
26653
|
+
depth: number2().int().nullable(),
|
|
26654
|
+
generations: number2().int(),
|
|
26655
|
+
deeper: boolean2(),
|
|
26656
|
+
ancestors: array(familyTreeAncestorSchema),
|
|
26657
|
+
people: array(familyTreePersonViewSchema)
|
|
26658
|
+
});
|
|
26576
26659
|
var familyTreeQuerySchema = object({ side: familyTreeSideSchema.optional() });
|
|
26577
26660
|
var familyTreeUploadFieldsSchema = object({
|
|
26578
26661
|
sourceName: preprocess((v) => v === "" ? undefined : v, sourceNameSchema.optional()),
|
|
@@ -26609,7 +26692,118 @@ var familyTreeMatchDecisionResultSchema = object({
|
|
|
26609
26692
|
matches: array(familyTreeMatchViewSchema)
|
|
26610
26693
|
});
|
|
26611
26694
|
|
|
26695
|
+
// ../../packages/schema/src/contracts/family-tree-edits.ts
|
|
26696
|
+
var familyTreeDateSchema = string2().trim().regex(/^\d{4}(-\d{2}-\d{2})?$/, "expected YYYY or YYYY-MM-DD").refine((v) => {
|
|
26697
|
+
if (v.length === 4)
|
|
26698
|
+
return true;
|
|
26699
|
+
const d = new Date(`${v}T00:00:00Z`);
|
|
26700
|
+
return !Number.isNaN(d.getTime()) && d.toISOString().slice(0, 10) === v;
|
|
26701
|
+
}, "not a real day");
|
|
26702
|
+
var nameSchema = string2().trim().min(1).max(120);
|
|
26703
|
+
var partSchema = string2().trim().min(1).max(80).nullish();
|
|
26704
|
+
var placeSchema = string2().trim().min(1).max(200).nullish();
|
|
26705
|
+
var sexSchema = _enum2(["M", "F"]).nullish();
|
|
26706
|
+
var personFieldsShape = {
|
|
26707
|
+
given: partSchema,
|
|
26708
|
+
surname: partSchema,
|
|
26709
|
+
sex: sexSchema,
|
|
26710
|
+
birthDate: familyTreeDateSchema.nullish(),
|
|
26711
|
+
birthPlace: placeSchema,
|
|
26712
|
+
deathDate: familyTreeDateSchema.nullish(),
|
|
26713
|
+
deathPlace: placeSchema
|
|
26714
|
+
};
|
|
26715
|
+
var addFamilyTreePersonInputSchema = object({ name: nameSchema, ...personFieldsShape }).strict();
|
|
26716
|
+
var editFamilyTreePersonInputSchema = object({ name: nameSchema.optional(), ...personFieldsShape }).strict().refine((v) => Object.values(v).some((x) => x !== undefined), "nothing to change");
|
|
26717
|
+
var factTypeSchema = string2().trim().min(1).max(40).transform((v) => v.toLowerCase());
|
|
26718
|
+
var factFieldsShape = {
|
|
26719
|
+
date: string2().trim().min(1).max(60).nullish(),
|
|
26720
|
+
place: placeSchema,
|
|
26721
|
+
value: string2().trim().min(1).max(2000).nullish()
|
|
26722
|
+
};
|
|
26723
|
+
var addFamilyTreeFactInputSchema = object({ type: factTypeSchema, ...factFieldsShape }).strict();
|
|
26724
|
+
var editFamilyTreeFactInputSchema = object({ type: factTypeSchema.optional(), ...factFieldsShape }).strict().refine((v) => Object.values(v).some((x) => x !== undefined), "nothing to change");
|
|
26725
|
+
var familyTreeFactIdSchema = string2().regex(/^[A-Za-z0-9_-]{1,40}$/, "expected a fact id");
|
|
26726
|
+
var familyTreeLinkInputSchema = discriminatedUnion("kind", [
|
|
26727
|
+
object({ kind: literal("parent"), parent: familyTreeRefSchema, child: familyTreeRefSchema }).strict(),
|
|
26728
|
+
object({ kind: literal("partner"), a: familyTreeRefSchema, b: familyTreeRefSchema }).strict()
|
|
26729
|
+
]);
|
|
26730
|
+
var familyTreeFactSchema = object({
|
|
26731
|
+
id: familyTreeFactIdSchema,
|
|
26732
|
+
type: string2(),
|
|
26733
|
+
date: string2().nullable(),
|
|
26734
|
+
place: string2().nullable(),
|
|
26735
|
+
value: string2().nullable()
|
|
26736
|
+
});
|
|
26737
|
+
var relativeSchema = object({ ref: familyTreeRefSchema, name: string2() });
|
|
26738
|
+
var familyTreePersonDetailSchema = object({
|
|
26739
|
+
ref: familyTreeRefSchema,
|
|
26740
|
+
name: string2(),
|
|
26741
|
+
given: string2().nullable(),
|
|
26742
|
+
surname: string2().nullable(),
|
|
26743
|
+
sex: _enum2(["M", "F"]).nullable(),
|
|
26744
|
+
birthDate: string2().nullable(),
|
|
26745
|
+
birthYear: number2().int().nullable(),
|
|
26746
|
+
birthPlace: string2().nullable(),
|
|
26747
|
+
deathDate: string2().nullable(),
|
|
26748
|
+
deathYear: number2().int().nullable(),
|
|
26749
|
+
deathPlace: string2().nullable(),
|
|
26750
|
+
living: boolean2(),
|
|
26751
|
+
facts: array(familyTreeFactSchema),
|
|
26752
|
+
parents: array(relativeSchema),
|
|
26753
|
+
partners: array(relativeSchema),
|
|
26754
|
+
children: array(relativeSchema),
|
|
26755
|
+
added: boolean2(),
|
|
26756
|
+
contact: object({ id: ulidSchema, title: string2() }).nullable()
|
|
26757
|
+
});
|
|
26758
|
+
var familyTreeEditKindSchema = _enum2([
|
|
26759
|
+
"person_add",
|
|
26760
|
+
"person_edit",
|
|
26761
|
+
"person_remove",
|
|
26762
|
+
"fact_add",
|
|
26763
|
+
"fact_edit",
|
|
26764
|
+
"fact_remove",
|
|
26765
|
+
"link_parent",
|
|
26766
|
+
"link_partner",
|
|
26767
|
+
"unlink_parent",
|
|
26768
|
+
"unlink_partner"
|
|
26769
|
+
]);
|
|
26770
|
+
var familyTreeEditViewSchema = object({
|
|
26771
|
+
id: ulidSchema,
|
|
26772
|
+
kind: familyTreeEditKindSchema,
|
|
26773
|
+
line: string2(),
|
|
26774
|
+
at: exports_coerce.date(),
|
|
26775
|
+
applied: boolean2(),
|
|
26776
|
+
reason: string2().nullable()
|
|
26777
|
+
});
|
|
26778
|
+
var familyTreeEditsSchema = object({ edits: array(familyTreeEditViewSchema) });
|
|
26779
|
+
var familyTreeChangeSchema = object({
|
|
26780
|
+
ref: familyTreeRefSchema,
|
|
26781
|
+
op: _enum2(["created", "updated", "deleted"]),
|
|
26782
|
+
name: string2(),
|
|
26783
|
+
fields: array(string2())
|
|
26784
|
+
});
|
|
26785
|
+
var familyTreeEditReceiptSchema = object({
|
|
26786
|
+
edit: familyTreeEditViewSchema,
|
|
26787
|
+
changes: array(familyTreeChangeSchema),
|
|
26788
|
+
person: familyTreePersonDetailSchema.nullable()
|
|
26789
|
+
});
|
|
26790
|
+
var familyTreeUndoResultSchema = object({
|
|
26791
|
+
undone: ulidSchema,
|
|
26792
|
+
changes: array(familyTreeChangeSchema),
|
|
26793
|
+
skipped: array(familyTreeEditViewSchema)
|
|
26794
|
+
});
|
|
26795
|
+
|
|
26612
26796
|
// ../../packages/schema/src/operations/family-tree.ts
|
|
26797
|
+
var REF = {
|
|
26798
|
+
ref: { description: "The person's ref in the tree (`I12`, or `P\u2026` for one added in do)." }
|
|
26799
|
+
};
|
|
26800
|
+
var REF_SCHEMA = { ref: familyTreeRefSchema };
|
|
26801
|
+
var FACT = {
|
|
26802
|
+
...REF,
|
|
26803
|
+
factId: { description: "The fact's id (`OCCU-1` from the GEDCOM, `f-\u2026` for one added in do)." }
|
|
26804
|
+
};
|
|
26805
|
+
var FACT_SCHEMA = { ref: familyTreeRefSchema, factId: familyTreeFactIdSchema };
|
|
26806
|
+
var RECEIPT = "Returns the receipt `{ edit, changes, person }`: `edit.id` is what `POST /family-tree/edits/{id}/undo` takes, `changes` lists each person the edit changed (created \xB7 updated \xB7 deleted, with the fields). The edit is kept as an overlay over the import, so a new GEDCOM keeps it.";
|
|
26613
26807
|
var NOT_IN_STAR = "A sensitive scope (ADR 0028): never part of `*`, and only the tree of the member who minted the key.";
|
|
26614
26808
|
var familyTreeOperations = defineOperations({
|
|
26615
26809
|
import: {
|
|
@@ -26646,6 +26840,31 @@ var familyTreeOperations = defineOperations({
|
|
|
26646
26840
|
response: { description: "`{ tree }`." },
|
|
26647
26841
|
errors: { 400: true, 403: "The key doesn't hold `people:family-tree`, or is no member's." }
|
|
26648
26842
|
},
|
|
26843
|
+
ancestors: {
|
|
26844
|
+
method: "get",
|
|
26845
|
+
path: "/family-tree/ancestors",
|
|
26846
|
+
scopes: ["people:family-tree"],
|
|
26847
|
+
tag: "People",
|
|
26848
|
+
summary: "The direct line back",
|
|
26849
|
+
description: "A pedigree: every ancestor of `ref` (the member's own place in the tree when left out) as far as `depth` generations (1\u201350; all of them when left out). Each slot has its Ahnentafel number `n` (the person is 1, a father 2n, a mother 2n+1) and `generation`; someone who is an ancestor along two lines appears at each number, `repeatOf` naming the first. `people` holds each person once; `deeper` says the tree goes further back than `depth`. A 400 before the member said who they are and with no `ref`.",
|
|
26850
|
+
query: [
|
|
26851
|
+
{
|
|
26852
|
+
name: "ref",
|
|
26853
|
+
description: "Whose ancestors (default: the member's own place in the tree)."
|
|
26854
|
+
},
|
|
26855
|
+
{
|
|
26856
|
+
name: "depth",
|
|
26857
|
+
type: "integer",
|
|
26858
|
+
minimum: 1,
|
|
26859
|
+
maximum: 50,
|
|
26860
|
+
description: "How many generations back (default: all)."
|
|
26861
|
+
}
|
|
26862
|
+
],
|
|
26863
|
+
querySchema: familyTreeAncestorsQuerySchema,
|
|
26864
|
+
status: 200,
|
|
26865
|
+
response: { description: "`{ rootRef, depth, generations, deeper, ancestors, people }`." },
|
|
26866
|
+
errors: { 400: true, 404: true }
|
|
26867
|
+
},
|
|
26649
26868
|
people: {
|
|
26650
26869
|
method: "get",
|
|
26651
26870
|
path: "/family-tree/people",
|
|
@@ -26697,6 +26916,150 @@ var familyTreeOperations = defineOperations({
|
|
|
26697
26916
|
status: 200,
|
|
26698
26917
|
response: { description: "`{ id, did, matches }`." },
|
|
26699
26918
|
errors: { 400: true, 404: true }
|
|
26919
|
+
},
|
|
26920
|
+
person: {
|
|
26921
|
+
method: "get",
|
|
26922
|
+
path: "/family-tree/people/{ref}",
|
|
26923
|
+
scopes: ["people:family-tree"],
|
|
26924
|
+
tag: "People",
|
|
26925
|
+
summary: "One person in the tree",
|
|
26926
|
+
description: "Their name, sex, birth and death (date and place), facts (each with the `id` an edit takes), parents, partners and children, whether the member added them, and the Contact they're linked to.",
|
|
26927
|
+
pathParams: REF,
|
|
26928
|
+
pathParamSchemas: REF_SCHEMA,
|
|
26929
|
+
status: 200,
|
|
26930
|
+
response: { description: "The person." },
|
|
26931
|
+
errors: { 400: true, 404: true }
|
|
26932
|
+
},
|
|
26933
|
+
addPerson: {
|
|
26934
|
+
method: "post",
|
|
26935
|
+
path: "/family-tree/people",
|
|
26936
|
+
scopes: ["people:family-tree"],
|
|
26937
|
+
tag: "People",
|
|
26938
|
+
summary: "Add a person to the tree",
|
|
26939
|
+
description: `\`{ name, given?, surname?, sex? (M \xB7 F), birthDate?, birthPlace?, deathDate?, deathPlace? }\`; dates are \`YYYY\` or \`YYYY-MM-DD\`, and a death date or place means not living. They get a new ref (\`P\u2026\`); link them to parents or a partner next. ${RECEIPT}`,
|
|
26940
|
+
body: addFamilyTreePersonInputSchema,
|
|
26941
|
+
status: 201,
|
|
26942
|
+
response: { description: "The receipt." },
|
|
26943
|
+
errors: { 400: true, 404: true }
|
|
26944
|
+
},
|
|
26945
|
+
editPerson: {
|
|
26946
|
+
method: "patch",
|
|
26947
|
+
path: "/family-tree/people/{ref}",
|
|
26948
|
+
scopes: ["people:family-tree"],
|
|
26949
|
+
tag: "People",
|
|
26950
|
+
summary: "Change a person in the tree",
|
|
26951
|
+
description: `Any of the add's fields; \`null\` clears one. A change to the death decides again whether they're living. ${RECEIPT}`,
|
|
26952
|
+
pathParams: REF,
|
|
26953
|
+
pathParamSchemas: REF_SCHEMA,
|
|
26954
|
+
body: editFamilyTreePersonInputSchema,
|
|
26955
|
+
status: 200,
|
|
26956
|
+
response: { description: "The receipt." },
|
|
26957
|
+
errors: { 400: true, 404: true }
|
|
26958
|
+
},
|
|
26959
|
+
removePerson: {
|
|
26960
|
+
method: "delete",
|
|
26961
|
+
path: "/family-tree/people/{ref}",
|
|
26962
|
+
scopes: ["people:family-tree"],
|
|
26963
|
+
tag: "People",
|
|
26964
|
+
summary: "Take a person out of the tree",
|
|
26965
|
+
description: `Out of every family they're in; their link to a Contact comes back with an Undo. ${RECEIPT}`,
|
|
26966
|
+
pathParams: REF,
|
|
26967
|
+
pathParamSchemas: REF_SCHEMA,
|
|
26968
|
+
status: 200,
|
|
26969
|
+
response: { description: "The receipt (`person`: null)." },
|
|
26970
|
+
errors: { 400: true, 404: true }
|
|
26971
|
+
},
|
|
26972
|
+
addFact: {
|
|
26973
|
+
method: "post",
|
|
26974
|
+
path: "/family-tree/people/{ref}/facts",
|
|
26975
|
+
scopes: ["people:family-tree"],
|
|
26976
|
+
tag: "People",
|
|
26977
|
+
summary: "Add a fact to a person",
|
|
26978
|
+
description: `\`{ type, date?, place?, value? }\`: an occupation, a residence, a baptism, a note (the type is a word of the member's own). The date is as written (\`ABT 1720\`). ${RECEIPT}`,
|
|
26979
|
+
pathParams: REF,
|
|
26980
|
+
pathParamSchemas: REF_SCHEMA,
|
|
26981
|
+
body: addFamilyTreeFactInputSchema,
|
|
26982
|
+
status: 201,
|
|
26983
|
+
response: { description: "The receipt." },
|
|
26984
|
+
errors: { 400: true, 404: true }
|
|
26985
|
+
},
|
|
26986
|
+
editFact: {
|
|
26987
|
+
method: "patch",
|
|
26988
|
+
path: "/family-tree/people/{ref}/facts/{factId}",
|
|
26989
|
+
scopes: ["people:family-tree"],
|
|
26990
|
+
tag: "People",
|
|
26991
|
+
summary: "Change a fact",
|
|
26992
|
+
description: `Any of \`type\`, \`date\`, \`place\`, \`value\`; \`null\` clears one. ${RECEIPT}`,
|
|
26993
|
+
pathParams: FACT,
|
|
26994
|
+
pathParamSchemas: FACT_SCHEMA,
|
|
26995
|
+
body: editFamilyTreeFactInputSchema,
|
|
26996
|
+
status: 200,
|
|
26997
|
+
response: { description: "The receipt." },
|
|
26998
|
+
errors: { 400: true, 404: true }
|
|
26999
|
+
},
|
|
27000
|
+
removeFact: {
|
|
27001
|
+
method: "delete",
|
|
27002
|
+
path: "/family-tree/people/{ref}/facts/{factId}",
|
|
27003
|
+
scopes: ["people:family-tree"],
|
|
27004
|
+
tag: "People",
|
|
27005
|
+
summary: "Remove a fact",
|
|
27006
|
+
description: RECEIPT,
|
|
27007
|
+
pathParams: FACT,
|
|
27008
|
+
pathParamSchemas: FACT_SCHEMA,
|
|
27009
|
+
status: 200,
|
|
27010
|
+
response: { description: "The receipt." },
|
|
27011
|
+
errors: { 400: true, 404: true }
|
|
27012
|
+
},
|
|
27013
|
+
link: {
|
|
27014
|
+
method: "post",
|
|
27015
|
+
path: "/family-tree/links",
|
|
27016
|
+
scopes: ["people:family-tree"],
|
|
27017
|
+
tag: "People",
|
|
27018
|
+
summary: "Link parent and child, or partners",
|
|
27019
|
+
description: `\`{ kind: "parent", parent, child }\` (a child has at most two parents: a third is a 409) or \`{ kind: "partner", a, b }\`. ${RECEIPT}`,
|
|
27020
|
+
body: familyTreeLinkInputSchema,
|
|
27021
|
+
status: 200,
|
|
27022
|
+
response: { description: "The receipt." },
|
|
27023
|
+
errors: { 400: true, 404: true, 409: true },
|
|
27024
|
+
operationId: "family_tree_link"
|
|
27025
|
+
},
|
|
27026
|
+
unlink: {
|
|
27027
|
+
method: "post",
|
|
27028
|
+
path: "/family-tree/unlink",
|
|
27029
|
+
scopes: ["people:family-tree"],
|
|
27030
|
+
tag: "People",
|
|
27031
|
+
summary: "Unlink parent and child, or partners",
|
|
27032
|
+
description: `The link's body. A child with siblings keeps them with the other parent; partners with children together are a 409 (unlink a parent first). ${RECEIPT}`,
|
|
27033
|
+
body: familyTreeLinkInputSchema,
|
|
27034
|
+
status: 200,
|
|
27035
|
+
response: { description: "The receipt." },
|
|
27036
|
+
errors: { 400: true, 404: true, 409: true },
|
|
27037
|
+
operationId: "family_tree_unlink"
|
|
27038
|
+
},
|
|
27039
|
+
edits: {
|
|
27040
|
+
method: "get",
|
|
27041
|
+
path: "/family-tree/edits",
|
|
27042
|
+
scopes: ["people:family-tree"],
|
|
27043
|
+
tag: "People",
|
|
27044
|
+
summary: "The member's edits to the tree",
|
|
27045
|
+
description: "Oldest first, each `{ id, kind, line, at, applied, reason }`. The edits are an overlay replayed over every import: one the current file has no room for (its person or fact isn't in it, or it no longer fits) is `applied: false` with the `reason`, kept, and applies again when a later file brings its person back.",
|
|
27046
|
+
status: 200,
|
|
27047
|
+
response: { description: "`{ edits }`." },
|
|
27048
|
+
errors: { 404: true }
|
|
27049
|
+
},
|
|
27050
|
+
undo: {
|
|
27051
|
+
method: "post",
|
|
27052
|
+
path: "/family-tree/edits/{id}/undo",
|
|
27053
|
+
scopes: ["people:family-tree"],
|
|
27054
|
+
tag: "People",
|
|
27055
|
+
summary: "Undo an edit to the tree",
|
|
27056
|
+
description: "The receipt's Undo: the edit leaves the overlay and the tree is replayed without it. Returns `{ undone, changes, skipped }`; `skipped` lists the edits that leaned on it (a fact on a person it added) and no longer apply. A POST, so it doesn't wait on a confirmation the way a delete does.",
|
|
27057
|
+
pathParams: { id: "The edit's id (a receipt's `edit.id`)." },
|
|
27058
|
+
pathParamSchemas: { id: ulidSchema },
|
|
27059
|
+
status: 200,
|
|
27060
|
+
response: { description: "`{ undone, changes, skipped }`." },
|
|
27061
|
+
errors: { 400: true, 404: true },
|
|
27062
|
+
operationId: "family_tree_undo"
|
|
26700
27063
|
}
|
|
26701
27064
|
});
|
|
26702
27065
|
|
|
@@ -27893,7 +28256,7 @@ var foodPatternsSchema = object({
|
|
|
27893
28256
|
// ../../packages/schema/src/operations/food-log.ts
|
|
27894
28257
|
var NO_MEMBER = "The key holds no health scope by name, or has no member behind it.";
|
|
27895
28258
|
var HIDDEN2 = "No such meal, or it isn't the key's member's.";
|
|
27896
|
-
var
|
|
28259
|
+
var RECEIPT2 = "The meal with its `receipt`: a headline with the kcal range, a confidence word (close \xB7 estimate \xB7 rough), a line per item with its source, and the Undo / Edit / Save as my meal calls.";
|
|
27897
28260
|
var ITEM = "Each item: `label` and one of `foodId` (from `GET /api/v1/foods/search`), `productId` or `recipeId`, with `grams`, `quantity` + `unit` (`2 boterham`, `1 el`) or `servings`; optional `gramsLow`/`gramsHigh` and `matchConfidence`. With no reference, an `estimate` `{kcal, proteinG, carbsG, fatG}` (a labelled guess). The numbers are computed: table \xD7 grams.";
|
|
27898
28261
|
var meal = { id: "The meal's ULID." };
|
|
27899
28262
|
var mealId = { id: ulidSchema };
|
|
@@ -27923,7 +28286,7 @@ var foodLogOperations = defineOperations({
|
|
|
27923
28286
|
description: `Log what the member ate in one call: \`id\`, \`title\`, \`eatenAt\`, \`mealType\` (breakfast \xB7 lunch \xB7 dinner \xB7 snack \xB7 drink), optional \`portionNote\`, \`place\`, \`flags\`, \`capture\`, \`photoFileId\`, and \`items\`. ${ITEM} Always private to the member.`,
|
|
27924
28287
|
body: createMealLogInputSchema,
|
|
27925
28288
|
status: 201,
|
|
27926
|
-
response: { description:
|
|
28289
|
+
response: { description: RECEIPT2 },
|
|
27927
28290
|
errors: { 400: true, 403: NO_MEMBER },
|
|
27928
28291
|
operationId: "food_log"
|
|
27929
28292
|
},
|
|
@@ -27951,7 +28314,7 @@ var foodLogOperations = defineOperations({
|
|
|
27951
28314
|
description: `Correct a meal: \`replace\` (\`[{itemId, item}]\`), \`remove\` (item ids) and \`add\` (items) add rows, never edit one; the meal's own fields update. ${ITEM}`,
|
|
27952
28315
|
body: correctMealLogInputSchema,
|
|
27953
28316
|
status: 200,
|
|
27954
|
-
response: { description:
|
|
28317
|
+
response: { description: RECEIPT2 },
|
|
27955
28318
|
errors: { 400: true, 404: HIDDEN2 },
|
|
27956
28319
|
operationId: "food_correct"
|
|
27957
28320
|
},
|
|
@@ -27978,7 +28341,7 @@ var foodLogOperations = defineOperations({
|
|
|
27978
28341
|
summary: "Undo a meal's last correction",
|
|
27979
28342
|
description: "Take back the latest correction: the items it replaced or removed are current again.",
|
|
27980
28343
|
status: 200,
|
|
27981
|
-
response: { description:
|
|
28344
|
+
response: { description: RECEIPT2 },
|
|
27982
28345
|
errors: { 400: true, 404: HIDDEN2 }
|
|
27983
28346
|
},
|
|
27984
28347
|
saveMeal: {
|
|
@@ -31268,6 +31631,140 @@ var journalExportSchema = object({
|
|
|
31268
31631
|
nextSince: exports_coerce.date()
|
|
31269
31632
|
});
|
|
31270
31633
|
|
|
31634
|
+
// ../../packages/schema/src/contracts/journal-private.ts
|
|
31635
|
+
var JOURNAL_PRIVATE_TERMS = {
|
|
31636
|
+
alcohol: [
|
|
31637
|
+
"alcohol*",
|
|
31638
|
+
"drunk*",
|
|
31639
|
+
"drink*",
|
|
31640
|
+
"beer*",
|
|
31641
|
+
"wine*",
|
|
31642
|
+
"whisky",
|
|
31643
|
+
"whiskey",
|
|
31644
|
+
"vodka*",
|
|
31645
|
+
"gin",
|
|
31646
|
+
"rum",
|
|
31647
|
+
"tequila*",
|
|
31648
|
+
"cocktail*",
|
|
31649
|
+
"hangover*",
|
|
31650
|
+
"hungover",
|
|
31651
|
+
"tipsy",
|
|
31652
|
+
"booze*",
|
|
31653
|
+
"sober*",
|
|
31654
|
+
"sobriety",
|
|
31655
|
+
"bier*",
|
|
31656
|
+
"wijn*",
|
|
31657
|
+
"drank*",
|
|
31658
|
+
"dronken*",
|
|
31659
|
+
"borrel*",
|
|
31660
|
+
"pint",
|
|
31661
|
+
"pints",
|
|
31662
|
+
"pintje*",
|
|
31663
|
+
"kater*",
|
|
31664
|
+
"zuipen",
|
|
31665
|
+
"gezopen"
|
|
31666
|
+
],
|
|
31667
|
+
smoking: [
|
|
31668
|
+
"smok*",
|
|
31669
|
+
"cigar*",
|
|
31670
|
+
"tobacco",
|
|
31671
|
+
"nicotine",
|
|
31672
|
+
"vape*",
|
|
31673
|
+
"vaping",
|
|
31674
|
+
"roken",
|
|
31675
|
+
"rook",
|
|
31676
|
+
"rookte*",
|
|
31677
|
+
"gerookt",
|
|
31678
|
+
"roker*",
|
|
31679
|
+
"sigaar*",
|
|
31680
|
+
"sigaret*",
|
|
31681
|
+
"tabak*",
|
|
31682
|
+
"peuk*",
|
|
31683
|
+
"vapen"
|
|
31684
|
+
],
|
|
31685
|
+
cannabis: [
|
|
31686
|
+
"cannabis",
|
|
31687
|
+
"weed",
|
|
31688
|
+
"hash",
|
|
31689
|
+
"hashish",
|
|
31690
|
+
"marijuana",
|
|
31691
|
+
"joint",
|
|
31692
|
+
"joints",
|
|
31693
|
+
"jointje*",
|
|
31694
|
+
"stoned",
|
|
31695
|
+
"thc",
|
|
31696
|
+
"cbd",
|
|
31697
|
+
"edibles",
|
|
31698
|
+
"wiet*",
|
|
31699
|
+
"hasj*",
|
|
31700
|
+
"blowen",
|
|
31701
|
+
"blowt",
|
|
31702
|
+
"geblowd"
|
|
31703
|
+
],
|
|
31704
|
+
drugs: [
|
|
31705
|
+
"drug*",
|
|
31706
|
+
"cocaine",
|
|
31707
|
+
"coca\xEFne",
|
|
31708
|
+
"coke",
|
|
31709
|
+
"mdma",
|
|
31710
|
+
"ecstasy",
|
|
31711
|
+
"xtc",
|
|
31712
|
+
"ketamine",
|
|
31713
|
+
"lsd",
|
|
31714
|
+
"shrooms",
|
|
31715
|
+
"paddo*",
|
|
31716
|
+
"amphetamine*",
|
|
31717
|
+
"heroin*",
|
|
31718
|
+
"hero\xEFne",
|
|
31719
|
+
"opioid*"
|
|
31720
|
+
],
|
|
31721
|
+
relapse: [
|
|
31722
|
+
"relaps*",
|
|
31723
|
+
"craving*",
|
|
31724
|
+
"rehab",
|
|
31725
|
+
"aa meeting*",
|
|
31726
|
+
"terugval*",
|
|
31727
|
+
"hervallen",
|
|
31728
|
+
"afkick*",
|
|
31729
|
+
"ontwenning*"
|
|
31730
|
+
],
|
|
31731
|
+
fertility: [
|
|
31732
|
+
"fertil*",
|
|
31733
|
+
"ivf",
|
|
31734
|
+
"iui",
|
|
31735
|
+
"ovulat*",
|
|
31736
|
+
"conceiv*",
|
|
31737
|
+
"conception",
|
|
31738
|
+
"pregnan*",
|
|
31739
|
+
"sperm*",
|
|
31740
|
+
"embryo*",
|
|
31741
|
+
"miscarr*",
|
|
31742
|
+
"ttc",
|
|
31743
|
+
"vruchtbaar*",
|
|
31744
|
+
"ovulatie",
|
|
31745
|
+
"eisprong",
|
|
31746
|
+
"zwanger*",
|
|
31747
|
+
"sperma*",
|
|
31748
|
+
"miskraam",
|
|
31749
|
+
"kinderwens"
|
|
31750
|
+
]
|
|
31751
|
+
};
|
|
31752
|
+
var LETTER = "[\\p{L}\\p{N}_]";
|
|
31753
|
+
var PRIVATE = new RegExp(`(?<!${LETTER})(?:${Object.values(JOURNAL_PRIVATE_TERMS).flat().map((term) => {
|
|
31754
|
+
const stem = term.replace(/\*$/, "").replace(/\s+/g, "\\s+");
|
|
31755
|
+
return term.endsWith("*") ? `${stem}${LETTER}*` : stem;
|
|
31756
|
+
}).join("|")})(?!${LETTER})`, "iu");
|
|
31757
|
+
function isPrivateLine(line) {
|
|
31758
|
+
return PRIVATE.test(line);
|
|
31759
|
+
}
|
|
31760
|
+
function withoutPrivateLines(text) {
|
|
31761
|
+
const kept = text.split(/\r?\n/).filter((line) => !isPrivateLine(line)).join(`
|
|
31762
|
+
`).replace(/\n{3,}/g, `
|
|
31763
|
+
|
|
31764
|
+
`).trim();
|
|
31765
|
+
return kept || null;
|
|
31766
|
+
}
|
|
31767
|
+
|
|
31271
31768
|
// ../../packages/schema/src/contracts/journal-import.ts
|
|
31272
31769
|
var journalImportBatchSchema = string2().regex(/^\d{4}-(0[1-9]|1[0-2])$/, "a batch is a month (YYYY-MM)");
|
|
31273
31770
|
var journalImportEntrySchema = _enum2(["summary", "retro", "log"]);
|
|
@@ -31278,6 +31775,7 @@ var journalImportFoundSchema = object({
|
|
|
31278
31775
|
documents: number2().int().nonnegative().optional(),
|
|
31279
31776
|
habits: number2().int().nonnegative().optional()
|
|
31280
31777
|
}).strict();
|
|
31778
|
+
var keptLine = (max) => string2().trim().min(1).max(max).transform((v) => isPrivateLine(v) ? null : v).nullish();
|
|
31281
31779
|
var JOURNAL_IMPORT_MAX_ITEMS = 400;
|
|
31282
31780
|
var journalImportItemInputSchema = object({
|
|
31283
31781
|
day: journalDateSchema,
|
|
@@ -31285,12 +31783,12 @@ var journalImportItemInputSchema = object({
|
|
|
31285
31783
|
sourceHash: string2().trim().min(1).max(128),
|
|
31286
31784
|
entry: journalImportEntrySchema.default("log"),
|
|
31287
31785
|
hasCheck: boolean2().default(false),
|
|
31288
|
-
title:
|
|
31289
|
-
body: string2().trim().min(1).max(20000).nullish(),
|
|
31786
|
+
title: keptLine(200),
|
|
31787
|
+
body: string2().trim().min(1).max(20000).transform(withoutPrivateLines).nullish(),
|
|
31290
31788
|
rating: number2().min(1).max(10).nullish(),
|
|
31291
|
-
ratingQuestion:
|
|
31789
|
+
ratingQuestion: keptLine(80),
|
|
31292
31790
|
sleep: number2().min(0).max(24).nullish(),
|
|
31293
|
-
highlight:
|
|
31791
|
+
highlight: keptLine(200),
|
|
31294
31792
|
milestoneCandidate: boolean2().default(false),
|
|
31295
31793
|
health: boolean2().default(false),
|
|
31296
31794
|
found: journalImportFoundSchema.default({})
|
|
@@ -31356,8 +31854,25 @@ var journalImportAnswerInputSchema = object({
|
|
|
31356
31854
|
rating: number2().min(1).max(10).nullable().optional(),
|
|
31357
31855
|
skip: boolean2().optional()
|
|
31358
31856
|
}).strict().refine((v) => Object.keys(v).length > 0, { message: "nothing to answer" });
|
|
31857
|
+
var journalImportRatingAnswerSchema = object({ day: journalDateSchema, rating: number2().min(1).max(10) }).strict();
|
|
31858
|
+
var journalImportApproveInputSchema = object({
|
|
31859
|
+
batch: journalImportBatchSchema.optional(),
|
|
31860
|
+
all: literal(true).optional(),
|
|
31861
|
+
ratings: array(journalImportRatingAnswerSchema).max(JOURNAL_IMPORT_MAX_ITEMS).default([])
|
|
31862
|
+
}).strict().refine((v) => v.batch !== undefined !== (v.all === true), {
|
|
31863
|
+
message: "approve one batch or all: send batch or all, not both"
|
|
31864
|
+
}).refine((v) => new Set(v.ratings.map((r) => r.day)).size === v.ratings.length, {
|
|
31865
|
+
message: "one rating per day",
|
|
31866
|
+
path: ["ratings"]
|
|
31867
|
+
});
|
|
31868
|
+
var journalImportOpenDaySchema = object({
|
|
31869
|
+
day: journalDateSchema,
|
|
31870
|
+
ratingQuestion: string2()
|
|
31871
|
+
});
|
|
31359
31872
|
var journalImportApproveSchema = object({
|
|
31360
31873
|
imported: number2().int(),
|
|
31874
|
+
batches: array(journalImportBatchSchema).default([]),
|
|
31875
|
+
open: array(journalImportOpenDaySchema).default([]),
|
|
31361
31876
|
overview: journalImportOverviewSchema
|
|
31362
31877
|
});
|
|
31363
31878
|
|
|
@@ -31745,12 +32260,25 @@ var journalOperations = defineOperations({
|
|
|
31745
32260
|
scopes: ["journal:write"],
|
|
31746
32261
|
tag: "Journal",
|
|
31747
32262
|
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
|
|
32263
|
+
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
32264
|
body: journalImportUploadInputSchema,
|
|
31750
32265
|
status: 200,
|
|
31751
32266
|
response: { description: "How the upload went.", view: "JournalImportUpload" },
|
|
31752
32267
|
errors: { 400: true }
|
|
31753
32268
|
},
|
|
32269
|
+
approveImport: {
|
|
32270
|
+
method: "post",
|
|
32271
|
+
path: "/journal/imports/approve",
|
|
32272
|
+
scopes: ["journal:write"],
|
|
32273
|
+
tag: "Journal",
|
|
32274
|
+
summary: "Approve staged days into the Journal",
|
|
32275
|
+
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.',
|
|
32276
|
+
body: journalImportApproveInputSchema,
|
|
32277
|
+
status: 200,
|
|
32278
|
+
response: { description: "How the approve went.", view: "JournalImportApprove" },
|
|
32279
|
+
errors: { 400: true },
|
|
32280
|
+
operationId: "journal_import_approve"
|
|
32281
|
+
},
|
|
31754
32282
|
importOverview: {
|
|
31755
32283
|
method: "get",
|
|
31756
32284
|
path: "/journal/imports",
|
|
@@ -32826,7 +33354,10 @@ var moneyNetWorthAccountSchema = object({
|
|
|
32826
33354
|
currency: string2(),
|
|
32827
33355
|
value: amount5,
|
|
32828
33356
|
source: moneySnapshotSourceSchema,
|
|
32829
|
-
month: moneyMonthSchema
|
|
33357
|
+
month: moneyMonthSchema,
|
|
33358
|
+
pensionSubtype: moneyPensionSubtypeSchema.nullable(),
|
|
33359
|
+
lockedUntil: string2().nullable(),
|
|
33360
|
+
locked: boolean2()
|
|
32830
33361
|
});
|
|
32831
33362
|
var moneyNetWorthUnknownSchema = moneyNetWorthAccountSchema.pick({
|
|
32832
33363
|
accountId: true,
|
|
@@ -32841,6 +33372,8 @@ var moneyNetWorthSchema = object({
|
|
|
32841
33372
|
total: amount5,
|
|
32842
33373
|
have: amount5,
|
|
32843
33374
|
owe: amount5,
|
|
33375
|
+
liquid: amount5,
|
|
33376
|
+
locked: amount5,
|
|
32844
33377
|
points: array(moneyNetWorthPointSchema),
|
|
32845
33378
|
accounts: array(moneyNetWorthAccountSchema),
|
|
32846
33379
|
complete: boolean2(),
|
|
@@ -33051,7 +33584,7 @@ var moneyOperations = defineOperations({
|
|
|
33051
33584
|
scopes: ["finance:write"],
|
|
33052
33585
|
tag: "Money",
|
|
33053
33586
|
summary: "Open a money account",
|
|
33054
|
-
description: 'Open an account: `id`, `title`, optional `kind` (current \xB7 savings \xB7 credit_card \xB7 cash \xB7 voucher \xB7 loan \xB7 investment \xB7 pension \xB7 asset_manual), `holder`, `iban` (stored only masked and hashed), `currency`, `openingBalance` (a decimal string), `includeInBudget
|
|
33587
|
+
description: 'Open an account: `id`, `title`, optional `kind` (current \xB7 savings \xB7 credit_card \xB7 cash \xB7 voucher \xB7 loan \xB7 investment \xB7 pension \xB7 asset_manual), the `pensionSubtype` of a pension (group_insurance \xB7 pension_savings_fund \xB7 pension_savings_insurance \xB7 vapz; on another kind a 400), `lockedUntil` (YYYY-MM-DD, any kind), `holder`, `iban` (stored only masked and hashed), `currency`, `openingBalance` (a decimal string), `includeInBudget` (always off for a pension, which never counts toward the budget), `includeInNetWorth` and `visibility`. An account is the household\'s (`visibility: space`) or "Only me" (`owner`, the default for a new one); another member never sees an Only me account or anything on it.',
|
|
33055
33588
|
body: createMoneyAccountInputSchema,
|
|
33056
33589
|
status: 201,
|
|
33057
33590
|
response: { description: "The account." },
|
|
@@ -33126,7 +33659,7 @@ var moneyOperations = defineOperations({
|
|
|
33126
33659
|
scopes: ["finance:write"],
|
|
33127
33660
|
tag: "Money",
|
|
33128
33661
|
summary: "Update a money account",
|
|
33129
|
-
description: "Rename it, change its kind, IBAN, opening balance, what it counts towards, or who sees it. `balance` with `balanceAt` (a day, read as its end, or a moment) states what it held then: the anchor net worth counts from, back and forward through the ledger, for a bank whose export has no running balance. The two go together; a day ahead, or an account a bank link keeps, is a 400.",
|
|
33662
|
+
description: "Rename it, change its kind (an investment account moved to `pension` keeps its balance and ledger; moved off `pension`, its `pensionSubtype` is cleared), its `pensionSubtype` or `lockedUntil` (null clears), IBAN, opening balance, what it counts towards, or who sees it. `balance` with `balanceAt` (a day, read as its end, or a moment) states what it held then: the anchor net worth counts from, back and forward through the ledger, for a bank whose export has no running balance. The two go together; a day ahead, or an account a bank link keeps, is a 400, and so is a body that sets nothing.",
|
|
33130
33663
|
pathParams: { id: "The account's ULID." },
|
|
33131
33664
|
pathParamSchemas: { id: ulidSchema },
|
|
33132
33665
|
body: updateMoneyAccountInputSchema,
|
|
@@ -33646,7 +34179,7 @@ var moneyOperations = defineOperations({
|
|
|
33646
34179
|
scopes: ["finance:read"],
|
|
33647
34180
|
tag: "Money",
|
|
33648
34181
|
summary: "Get net worth and its monthly line",
|
|
33649
|
-
description: "Net worth for the accounts that count in it (`includeInNetWorth`): `total`, `have` and `owe` this month, `points` (one per month, oldest first, the current month last; `total` null before any account had a value) and `accounts` (biggest first, each with its `value`, `source` \u2014 balance, ledger or manual \u2014 and the `month` that value is from). A loan or a credit card always counts as owed. This month follows each account's balance, carried forward by what was booked after `balanceAt`; the months before an account was first read are worked back from its ledger; a value set by hand carries on until the next one. An account whose ledger came from a bank but has no balance yet (an export with no running balance) is never counted as 0: it is listed under `unknown` and left out of the total, and `complete` is false. `scope`: `all` (default), `space` or `owner`; `months` 2\u201360 (12 by default).",
|
|
34182
|
+
description: "Net worth for the accounts that count in it (`includeInNetWorth`): `total`, `have` and `owe` this month, `liquid` and `locked` (they add up to `total`; locked is a pension, or an account whose `lockedUntil` day is ahead, never a debt), `points` (one per month, oldest first, the current month last; `total` null before any account had a value) and `accounts` (biggest first, each with its `value`, `source` \u2014 balance, ledger or manual \u2014 and the `month` that value is from). A loan or a credit card always counts as owed. This month follows each account's balance, carried forward by what was booked after `balanceAt`; the months before an account was first read are worked back from its ledger; a value set by hand carries on until the next one. An account whose ledger came from a bank but has no balance yet (an export with no running balance) is never counted as 0: it is listed under `unknown` and left out of the total, and `complete` is false. `scope`: `all` (default), `space` or `owner`; `months` 2\u201360 (12 by default).",
|
|
33650
34183
|
query: [
|
|
33651
34184
|
{ name: "scope", description: "all, space or owner." },
|
|
33652
34185
|
{ name: "months", description: "How many months the line covers (2\u201360, default 12)." }
|
|
@@ -33674,7 +34207,7 @@ var moneyOperations = defineOperations({
|
|
|
33674
34207
|
scopes: ["finance:read"],
|
|
33675
34208
|
tag: "Money",
|
|
33676
34209
|
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`.",
|
|
34210
|
+
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
34211
|
status: 200,
|
|
33679
34212
|
response: { description: "The goals." }
|
|
33680
34213
|
},
|
|
@@ -34354,12 +34887,12 @@ var periodOperations = defineOperations({
|
|
|
34354
34887
|
var placePointSchema = tuple([number2().finite(), number2().finite()]);
|
|
34355
34888
|
var placeOutlineSchema = array(placePointSchema).min(3).max(200);
|
|
34356
34889
|
var placeKindSchema = _enum2(placeKindValues);
|
|
34357
|
-
var
|
|
34890
|
+
var placeSchema2 = createSelectSchema(places, {
|
|
34358
34891
|
outline: placeOutlineSchema.nullable(),
|
|
34359
34892
|
kind: string2().default("room"),
|
|
34360
34893
|
gardenFeatureId: string2().nullable().default(null)
|
|
34361
34894
|
});
|
|
34362
|
-
var placeViewSchema =
|
|
34895
|
+
var placeViewSchema = placeSchema2.extend({
|
|
34363
34896
|
title: string2(),
|
|
34364
34897
|
parentId: string2().nullable().default(null),
|
|
34365
34898
|
createdAt: exports_coerce.date(),
|
|
@@ -34814,7 +35347,8 @@ var planUnplaceInputSchema = object({ taskId: ulidSchema }).strict();
|
|
|
34814
35347
|
var planListQuerySchema = object({
|
|
34815
35348
|
kind: planKindSchema.optional(),
|
|
34816
35349
|
status: planStatusSchema.optional(),
|
|
34817
|
-
startsOn: civilDaySchema.optional()
|
|
35350
|
+
startsOn: civilDaySchema.optional(),
|
|
35351
|
+
taskId: ulidSchema.optional()
|
|
34818
35352
|
});
|
|
34819
35353
|
var planTaskSchema = object({
|
|
34820
35354
|
id: ulidSchema,
|
|
@@ -35034,6 +35568,7 @@ var automationListItemSchema = automationViewSchema.extend({
|
|
|
35034
35568
|
madeBy: object({ userId: string2(), name: string2(), you: boolean2() }).nullable(),
|
|
35035
35569
|
lastRun: automationRunSummarySchema.nullable()
|
|
35036
35570
|
});
|
|
35571
|
+
var automationRunNowSchema = object({ queued: literal(true) });
|
|
35037
35572
|
|
|
35038
35573
|
// ../../packages/schema/src/db/shopping.ts
|
|
35039
35574
|
var shoppingLists = pgTable("shopping_lists", {
|
|
@@ -35795,7 +36330,8 @@ var projectOperations = defineOperations({
|
|
|
35795
36330
|
var occurrenceViewSchema = occurrenceSchema.extend({
|
|
35796
36331
|
title: string2(),
|
|
35797
36332
|
templateKind: _enum2(["task", "habit", "routine"]),
|
|
35798
|
-
measurementType: habitMeasurementTypeSchema.nullable()
|
|
36333
|
+
measurementType: habitMeasurementTypeSchema.nullable(),
|
|
36334
|
+
allDay: boolean2().optional()
|
|
35799
36335
|
});
|
|
35800
36336
|
var todayResponseSchema = object({
|
|
35801
36337
|
from: exports_coerce.date(),
|
|
@@ -38787,6 +39323,18 @@ var WEEKDAY_ALT = "monday|tuesday|wednesday|thursday|friday|saturday|sunday|mon|
|
|
|
38787
39323
|
var EVERY_N_RE = /\bevery\s+(\d+)\s+(day|days|week|weeks|month|months|year|years)\b/i;
|
|
38788
39324
|
var EVERY_WEEKDAYS_RE = /\bevery\s+(?:weekday|weekdays|work\s?day|work\s?days)\b/i;
|
|
38789
39325
|
var EVERY_WEEKDAY_RE = new RegExp(`\\bevery\\s+(${WEEKDAY_ALT})\\b`, "i");
|
|
39326
|
+
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");
|
|
39327
|
+
var NTH = {
|
|
39328
|
+
first: "1",
|
|
39329
|
+
"1st": "1",
|
|
39330
|
+
second: "2",
|
|
39331
|
+
"2nd": "2",
|
|
39332
|
+
third: "3",
|
|
39333
|
+
"3rd": "3",
|
|
39334
|
+
fourth: "4",
|
|
39335
|
+
"4th": "4",
|
|
39336
|
+
last: "-1"
|
|
39337
|
+
};
|
|
38790
39338
|
var EVERY_UNIT_RE = /\bevery\s+(day|week|month|year)\b/i;
|
|
38791
39339
|
var ADVERB_RE = /\b(daily|weekly|monthly|yearly|annually)\b/i;
|
|
38792
39340
|
var RELATIVE_RE = /\b(today|tonight|tomorrow|tmrw|tmr|yesterday)\b/i;
|
|
@@ -38918,6 +39466,12 @@ function takeEstimate(scan, take) {
|
|
|
38918
39466
|
return;
|
|
38919
39467
|
}
|
|
38920
39468
|
function takeRecurrence(scan, take) {
|
|
39469
|
+
const nth = scan.match(EVERY_NTH_WEEKDAY_RE);
|
|
39470
|
+
if (nth && take(nth, "recurrence")) {
|
|
39471
|
+
const ordinal = NTH[(nth[1] ?? "").toLowerCase()] ?? "1";
|
|
39472
|
+
const byDay = `${ordinal}${weekdayByName(nth[2]).code}`;
|
|
39473
|
+
return { mode: "rigid", freq: "monthly", interval: 1, byDay: [byDay] };
|
|
39474
|
+
}
|
|
38921
39475
|
const everyN = scan.match(EVERY_N_RE);
|
|
38922
39476
|
if (everyN && take(everyN, "recurrence")) {
|
|
38923
39477
|
const [, nStr = "1", unitRaw = "day"] = everyN;
|
|
@@ -39053,8 +39607,28 @@ function firstOccurrence(rule, now) {
|
|
|
39053
39607
|
});
|
|
39054
39608
|
return addDays(today, Math.min(...deltas));
|
|
39055
39609
|
}
|
|
39610
|
+
const nth = rule.freq === "monthly" ? rule.byDay?.map(parseByDay) : undefined;
|
|
39611
|
+
if (nth?.length) {
|
|
39612
|
+
const days = [0, 1].flatMap((ahead) => nth.flatMap((p) => {
|
|
39613
|
+
if (!p)
|
|
39614
|
+
return [];
|
|
39615
|
+
const dow = WEEKDAYS.find((w) => w.code === p.weekday)?.dow ?? 0;
|
|
39616
|
+
const day = nthWeekdayOfMonth(today.getFullYear(), today.getMonth() + ahead, dow, p.ordinal ?? 1);
|
|
39617
|
+
return day >= today ? [day] : [];
|
|
39618
|
+
}));
|
|
39619
|
+
if (days.length > 0)
|
|
39620
|
+
return new Date(Math.min(...days.map((d) => d.getTime())));
|
|
39621
|
+
}
|
|
39056
39622
|
return today;
|
|
39057
39623
|
}
|
|
39624
|
+
function nthWeekdayOfMonth(year, month, dow, ordinal) {
|
|
39625
|
+
if (ordinal < 0) {
|
|
39626
|
+
const last = new Date(year, month + 1, 0);
|
|
39627
|
+
return addDays(last, -((last.getDay() - dow + 7) % 7));
|
|
39628
|
+
}
|
|
39629
|
+
const first = new Date(year, month, 1);
|
|
39630
|
+
return addDays(first, (dow - first.getDay() + 7) % 7 + (ordinal - 1) * 7);
|
|
39631
|
+
}
|
|
39058
39632
|
function addMonths(d, months) {
|
|
39059
39633
|
const next = new Date(d);
|
|
39060
39634
|
next.setMonth(next.getMonth() + months);
|
|
@@ -39125,6 +39699,7 @@ var bookFacetSchema = object({
|
|
|
39125
39699
|
coverSources: coverSourcesSchema.nullish(),
|
|
39126
39700
|
pageCount: number2().int().min(1).max(1e5).nullish(),
|
|
39127
39701
|
currentPage: number2().int().min(0).max(1e5).nullish(),
|
|
39702
|
+
chapter: string2().trim().min(1).max(60).nullish(),
|
|
39128
39703
|
isbn: string2().regex(/^[0-9Xx][0-9Xx\s-]{8,20}$/, "must be an ISBN").nullish()
|
|
39129
39704
|
}).strict();
|
|
39130
39705
|
var filmFacetSchema = object({
|
|
@@ -40342,7 +40917,17 @@ function renderJournalImportScan(data, mode) {
|
|
|
40342
40917
|
}
|
|
40343
40918
|
out.push(` milestone candidates ${count((i) => Boolean(i.milestoneCandidate))}`);
|
|
40344
40919
|
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.`);
|
|
40920
|
+
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.`);
|
|
40921
|
+
return out.join(`
|
|
40922
|
+
`);
|
|
40923
|
+
}
|
|
40924
|
+
function renderJournalImportApprove(data, mode) {
|
|
40925
|
+
const d = data;
|
|
40926
|
+
const batches = d.batches?.length ? paint(mode, "dim", ` (${d.batches.join(", ")})`) : "";
|
|
40927
|
+
const out = [`Imported ${d.imported ?? 0} days into the Journal${batches}`];
|
|
40928
|
+
if (d.open?.length) {
|
|
40929
|
+
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`));
|
|
40930
|
+
}
|
|
40346
40931
|
return out.join(`
|
|
40347
40932
|
`);
|
|
40348
40933
|
}
|
|
@@ -41471,11 +42056,16 @@ var commands2 = [
|
|
|
41471
42056
|
],
|
|
41472
42057
|
flags: [
|
|
41473
42058
|
{ name: "unit", type: "string", description: "For a weight: g (default) or kg." },
|
|
42059
|
+
{
|
|
42060
|
+
name: "note",
|
|
42061
|
+
type: "string",
|
|
42062
|
+
description: 'For a weight: a short note drawn under its date ("vet check done").'
|
|
42063
|
+
},
|
|
41474
42064
|
{ name: "at", type: "string", description: "When (ISO 8601); default now." }
|
|
41475
42065
|
],
|
|
41476
42066
|
scopes: [...animalOperations.log.scopes],
|
|
41477
42067
|
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.`
|
|
42068
|
+
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
42069
|
},
|
|
41480
42070
|
run: async ({ args, flags, requireClient }) => {
|
|
41481
42071
|
const id = requireArg(args, 0, "animal id");
|
|
@@ -41485,8 +42075,11 @@ var commands2 = [
|
|
|
41485
42075
|
throw usageError("animals log needs a value (a number, or the note's text)");
|
|
41486
42076
|
const at = flagString(flags, "at");
|
|
41487
42077
|
const unit = flagString(flags, "unit");
|
|
42078
|
+
const note = flagString(flags, "note");
|
|
42079
|
+
if (note && kind !== "weight")
|
|
42080
|
+
throw usageError("--note goes with a weight");
|
|
41488
42081
|
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 };
|
|
42082
|
+
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
42083
|
if (kind !== "note" && Number.isNaN(n))
|
|
41491
42084
|
throw usageError("the value must be a number");
|
|
41492
42085
|
const body = parseInput(animalLogInputSchema, { ...fact, ...at ? { occurredAt: at } : {} });
|
|
@@ -42414,6 +43007,20 @@ var commands7 = [
|
|
|
42414
43007
|
];
|
|
42415
43008
|
|
|
42416
43009
|
// src/commands/collection.ts
|
|
43010
|
+
var CHAPTER_FLAG = {
|
|
43011
|
+
name: "chapter",
|
|
43012
|
+
type: "string",
|
|
43013
|
+
description: 'Where in the work: "9" (a chapter), "chapter 9", "book 9 and on".'
|
|
43014
|
+
};
|
|
43015
|
+
var LINKED_NOTE_FLAG = {
|
|
43016
|
+
name: "linked-note",
|
|
43017
|
+
type: "string",
|
|
43018
|
+
description: "Note ULID this Entry is tied to."
|
|
43019
|
+
};
|
|
43020
|
+
var whereFlags = (flags) => ({
|
|
43021
|
+
...flagString(flags, "chapter") ? { chapter: flagString(flags, "chapter") } : {},
|
|
43022
|
+
...flagString(flags, "linked-note") ? { linkedNoteId: flagString(flags, "linked-note") } : {}
|
|
43023
|
+
});
|
|
42417
43024
|
var commands8 = [
|
|
42418
43025
|
{
|
|
42419
43026
|
spec: {
|
|
@@ -42537,6 +43144,53 @@ var commands8 = [
|
|
|
42537
43144
|
};
|
|
42538
43145
|
}
|
|
42539
43146
|
},
|
|
43147
|
+
{
|
|
43148
|
+
spec: {
|
|
43149
|
+
path: ["collection", "reading"],
|
|
43150
|
+
summary: "Say where you are in a book: its chapter and/or page.",
|
|
43151
|
+
args: [{ name: "id", required: true, description: "Item ULID (a book)." }],
|
|
43152
|
+
flags: [
|
|
43153
|
+
{
|
|
43154
|
+
name: "chapter",
|
|
43155
|
+
type: "string",
|
|
43156
|
+
description: 'Where you are: "9" (a chapter), "chapter 9", "book 9".'
|
|
43157
|
+
},
|
|
43158
|
+
{ name: "page", type: "string", description: "The page you're on." },
|
|
43159
|
+
{ name: "clear-chapter", type: "boolean", description: "Forget the chapter." }
|
|
43160
|
+
],
|
|
43161
|
+
scopes: [...collectionOperations.update.scopes],
|
|
43162
|
+
returns: "CollectionItemView",
|
|
43163
|
+
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.`
|
|
43164
|
+
},
|
|
43165
|
+
run: async ({ args, flags, requireClient }) => {
|
|
43166
|
+
const id = requireArg(args, 0, "item id");
|
|
43167
|
+
const chapter = flagString(flags, "chapter");
|
|
43168
|
+
const clear = flagBool(flags, "clear-chapter");
|
|
43169
|
+
const page = flagString(flags, "page");
|
|
43170
|
+
if (page !== undefined && !/^\d+$/.test(page)) {
|
|
43171
|
+
throw usageError("--page must be a whole number, e.g. 142");
|
|
43172
|
+
}
|
|
43173
|
+
if (chapter === undefined && page === undefined && !clear) {
|
|
43174
|
+
throw usageError('collection reading needs --chapter or --page, e.g. --chapter "9"');
|
|
43175
|
+
}
|
|
43176
|
+
const client = requireClient();
|
|
43177
|
+
const item = await client.get(pathOf(collectionOperations.get, { id }));
|
|
43178
|
+
const { chapter: kept, ...rest } = item.facet ?? {};
|
|
43179
|
+
const facet = {
|
|
43180
|
+
...rest,
|
|
43181
|
+
...clear || kept === undefined ? {} : { chapter: kept }
|
|
43182
|
+
};
|
|
43183
|
+
if (chapter !== undefined)
|
|
43184
|
+
facet.chapter = chapter;
|
|
43185
|
+
if (page !== undefined)
|
|
43186
|
+
facet.currentPage = Number(page);
|
|
43187
|
+
const body = parseInput(updateCollectionItemInputSchema, { facet });
|
|
43188
|
+
return {
|
|
43189
|
+
data: await client.patch(pathOf(collectionOperations.update, { id }), body),
|
|
43190
|
+
render: renderRecord
|
|
43191
|
+
};
|
|
43192
|
+
}
|
|
43193
|
+
},
|
|
42540
43194
|
{
|
|
42541
43195
|
spec: {
|
|
42542
43196
|
path: ["collection", "add-entry"],
|
|
@@ -42556,7 +43210,9 @@ var commands8 = [
|
|
|
42556
43210
|
description: "What happened: use (default) \xB7 service \xB7 repair \xB7 fault \xB7 lent \xB7 returned \xB7 valued \xB7 moved."
|
|
42557
43211
|
},
|
|
42558
43212
|
{ name: "cost", type: "string", description: "What it cost (a sweep, a repair)." },
|
|
42559
|
-
{ name: "contact", type: "string", description: "Contact ULID of who did it." }
|
|
43213
|
+
{ name: "contact", type: "string", description: "Contact ULID of who did it." },
|
|
43214
|
+
CHAPTER_FLAG,
|
|
43215
|
+
LINKED_NOTE_FLAG
|
|
42560
43216
|
],
|
|
42561
43217
|
scopes: [...collectionOperations.addEntry.scopes],
|
|
42562
43218
|
returns: "CollectionItemDetail"
|
|
@@ -42578,7 +43234,8 @@ var commands8 = [
|
|
|
42578
43234
|
...flagString(flags, "note") ? { note: flagString(flags, "note") } : {},
|
|
42579
43235
|
...flagString(flags, "kind") ? { kind: flagString(flags, "kind") } : {},
|
|
42580
43236
|
...cost !== undefined ? { cost: Number(cost) } : {},
|
|
42581
|
-
...flagString(flags, "contact") ? { contactId: flagString(flags, "contact") } : {}
|
|
43237
|
+
...flagString(flags, "contact") ? { contactId: flagString(flags, "contact") } : {},
|
|
43238
|
+
...whereFlags(flags)
|
|
42582
43239
|
});
|
|
42583
43240
|
return {
|
|
42584
43241
|
data: await requireClient().post(pathOf(collectionOperations.addEntry, { id }), body),
|
|
@@ -42667,7 +43324,8 @@ var commands8 = [
|
|
|
42667
43324
|
name: "on",
|
|
42668
43325
|
type: "string",
|
|
42669
43326
|
description: "Day it occurred (YYYY-MM-DD); defaults to today."
|
|
42670
|
-
}
|
|
43327
|
+
},
|
|
43328
|
+
CHAPTER_FLAG
|
|
42671
43329
|
],
|
|
42672
43330
|
scopes: [...collectionOperations.log.scopes],
|
|
42673
43331
|
returns: "CollectionLogResult",
|
|
@@ -42688,7 +43346,8 @@ var commands8 = [
|
|
|
42688
43346
|
...flagString(flags, "status") ? { status: flagString(flags, "status") } : {},
|
|
42689
43347
|
...rating !== undefined ? { rating: Number(rating) } : {},
|
|
42690
43348
|
...flagString(flags, "note") ? { note: flagString(flags, "note") } : {},
|
|
42691
|
-
...flagString(flags, "on") ? { occurredOn: flagString(flags, "on") } : {}
|
|
43349
|
+
...flagString(flags, "on") ? { occurredOn: flagString(flags, "on") } : {},
|
|
43350
|
+
...flagString(flags, "chapter") ? { chapter: flagString(flags, "chapter") } : {}
|
|
42692
43351
|
});
|
|
42693
43352
|
return {
|
|
42694
43353
|
data: await requireClient().post(pathOf(collectionOperations.log), body),
|
|
@@ -42699,7 +43358,7 @@ var commands8 = [
|
|
|
42699
43358
|
{
|
|
42700
43359
|
spec: {
|
|
42701
43360
|
path: ["collection", "edit-entry"],
|
|
42702
|
-
summary: "Fix an Entry's day, rating or note.",
|
|
43361
|
+
summary: "Fix an Entry's day, rating, note, chapter or linked note.",
|
|
42703
43362
|
args: [
|
|
42704
43363
|
{ name: "id", required: true, description: "Item ULID." },
|
|
42705
43364
|
{ name: "entryId", required: true, description: "Entry ULID." }
|
|
@@ -42707,7 +43366,9 @@ var commands8 = [
|
|
|
42707
43366
|
flags: [
|
|
42708
43367
|
{ name: "rating", type: "string", description: "New rating 0.5\u20135." },
|
|
42709
43368
|
{ name: "note", type: "string", description: "New note." },
|
|
42710
|
-
{ name: "on", type: "string", description: "New day (YYYY-MM-DD)." }
|
|
43369
|
+
{ name: "on", type: "string", description: "New day (YYYY-MM-DD)." },
|
|
43370
|
+
CHAPTER_FLAG,
|
|
43371
|
+
LINKED_NOTE_FLAG
|
|
42711
43372
|
],
|
|
42712
43373
|
scopes: [...collectionOperations.editEntry.scopes],
|
|
42713
43374
|
returns: "CollectionItemDetail"
|
|
@@ -42722,7 +43383,8 @@ var commands8 = [
|
|
|
42722
43383
|
const body = parseInput(updateCollectionEntryInputSchema, {
|
|
42723
43384
|
...rating !== undefined ? { rating: Number(rating) } : {},
|
|
42724
43385
|
...flagString(flags, "note") ? { note: flagString(flags, "note") } : {},
|
|
42725
|
-
...flagString(flags, "on") ? { occurredOn: flagString(flags, "on") } : {}
|
|
43386
|
+
...flagString(flags, "on") ? { occurredOn: flagString(flags, "on") } : {},
|
|
43387
|
+
...whereFlags(flags)
|
|
42726
43388
|
});
|
|
42727
43389
|
return {
|
|
42728
43390
|
data: await requireClient().patch(pathOf(collectionOperations.editEntry, { id, entryId }), body),
|
|
@@ -46791,7 +47453,8 @@ import { createHash } from "crypto";
|
|
|
46791
47453
|
import { readdirSync, readFileSync as readFileSync7 } from "fs";
|
|
46792
47454
|
import { join as join2 } from "path";
|
|
46793
47455
|
var DAY_FILE = /^(\d{4}-\d{2}-\d{2})\.md$/;
|
|
46794
|
-
var
|
|
47456
|
+
var SCORE = "(?:10|[1-9])";
|
|
47457
|
+
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
47458
|
var HEALTH = /\b(symptoms?|stomach|nausea|headaches?|migraine|doctor|GP|medication|meds|pills?|blood|fever|pain|clinic|hospital)\b/i;
|
|
46796
47459
|
var NOISE = /\b(PR monitor|GitHub daily|deploy(?:ment)? watch\w*|cron|heartbeat|sales prospects?)\b/i;
|
|
46797
47460
|
var MILESTONE = /\b(first|went live|is live|win:|moved in|arrived|wedding|engaged)\b/i;
|
|
@@ -46860,8 +47523,8 @@ function parseDailyNote(path, day, text) {
|
|
|
46860
47523
|
const ratingLine = all.find((l) => /rating/i.test(l) && RATING.test(l)) ?? all.find((l) => RATING.test(l));
|
|
46861
47524
|
const match = ratingLine ? RATING.exec(ratingLine) : null;
|
|
46862
47525
|
if (match) {
|
|
46863
|
-
const [whole, approx, low, high] = match;
|
|
46864
|
-
if (approx || high)
|
|
47526
|
+
const [whole, approx, low = "", high] = match;
|
|
47527
|
+
if (approx || high || low.includes("."))
|
|
46865
47528
|
ratingQuestion = whole.trim();
|
|
46866
47529
|
else
|
|
46867
47530
|
rating = Number(low);
|
|
@@ -46883,7 +47546,7 @@ function parseDailyNote(path, day, text) {
|
|
|
46883
47546
|
inCheck = false;
|
|
46884
47547
|
if (line === ratingLine || /how was (the|your) day/i.test(line))
|
|
46885
47548
|
continue;
|
|
46886
|
-
if (HEALTH.test(line) || NOISE.test(line))
|
|
47549
|
+
if (HEALTH.test(line) || NOISE.test(line) || isPrivateLine(line))
|
|
46887
47550
|
continue;
|
|
46888
47551
|
kept.push(line);
|
|
46889
47552
|
}
|
|
@@ -46893,8 +47556,13 @@ function parseDailyNote(path, day, text) {
|
|
|
46893
47556
|
`).trim() || null;
|
|
46894
47557
|
const firstProse = (body ?? "").split(/\n\s*\n/).map((p) => plain(p)).find((p) => p.length > 0);
|
|
46895
47558
|
title = firstProse ? firstSentence(firstProse.replace(/^The day\s*/i, "")) : null;
|
|
47559
|
+
} else {
|
|
47560
|
+
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(`
|
|
47561
|
+
`).replace(/\n{3,}/g, `
|
|
47562
|
+
|
|
47563
|
+
`).trim() || null;
|
|
46896
47564
|
}
|
|
46897
|
-
const milestoneLine = all.find((l) => MILESTONE.test(l) && !HEALTH.test(l) && !NOISE.test(l) && plain(l).length > 0);
|
|
47565
|
+
const milestoneLine = all.find((l) => MILESTONE.test(l) && !HEALTH.test(l) && !NOISE.test(l) && !isPrivateLine(l) && plain(l).length > 0);
|
|
46898
47566
|
const highlight = milestoneLine ? firstSentence(plain(milestoneLine), 80) : null;
|
|
46899
47567
|
const sleep = sleepOf(all);
|
|
46900
47568
|
const prs = (text.match(PR_REF) ?? []).length;
|
|
@@ -47275,7 +47943,7 @@ var commands24 = [
|
|
|
47275
47943
|
description: "How the days name their folder (default memory/daily)."
|
|
47276
47944
|
}
|
|
47277
47945
|
],
|
|
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
|
|
47946
|
+
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
47947
|
run: async ({ args, flags, requireClient }) => {
|
|
47280
47948
|
const dir = requireArg(args, 0, "dir");
|
|
47281
47949
|
const items = scanDailyNotes(dir, flagString(flags, "label") ?? "memory/daily");
|
|
@@ -47300,6 +47968,42 @@ var commands24 = [
|
|
|
47300
47968
|
};
|
|
47301
47969
|
}
|
|
47302
47970
|
}),
|
|
47971
|
+
defineCommand({
|
|
47972
|
+
operation: journalOperations.approveImport,
|
|
47973
|
+
path: ["journal", "import", "approve"],
|
|
47974
|
+
summary: "Approve staged days into the Journal: one month's batch, or --all; --rating answers a fuzzy rating.",
|
|
47975
|
+
args: [{ name: "batch", required: false, description: "The month to approve, YYYY-MM." }],
|
|
47976
|
+
flags: [
|
|
47977
|
+
{ name: "all", type: "boolean", description: "Approve every staged batch." },
|
|
47978
|
+
{
|
|
47979
|
+
name: "rating",
|
|
47980
|
+
type: "string",
|
|
47981
|
+
description: "Answer a day's rating question, YYYY-MM-DD=N (1 to 10); repeat per day."
|
|
47982
|
+
}
|
|
47983
|
+
],
|
|
47984
|
+
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.",
|
|
47985
|
+
run: async ({ args, flags, requireClient }) => {
|
|
47986
|
+
const batch = args[0];
|
|
47987
|
+
const all = flagBool(flags, "all");
|
|
47988
|
+
if (batch === undefined === !all) {
|
|
47989
|
+
throw usageError("approve one month (YYYY-MM) or --all");
|
|
47990
|
+
}
|
|
47991
|
+
const ratings = flagStrings(flags, "rating").map((answer) => {
|
|
47992
|
+
const m = /^(\d{4}-\d{2}-\d{2})=(\d+(?:\.\d+)?)$/.exec(answer.trim());
|
|
47993
|
+
if (!m)
|
|
47994
|
+
throw usageError(`--rating takes YYYY-MM-DD=N, got "${answer}"`);
|
|
47995
|
+
return { day: m[1], rating: Number(m[2]) };
|
|
47996
|
+
});
|
|
47997
|
+
const input = parseInput(journalImportApproveInputSchema, {
|
|
47998
|
+
...all ? { all: true } : { batch },
|
|
47999
|
+
ratings
|
|
48000
|
+
});
|
|
48001
|
+
return {
|
|
48002
|
+
data: await requireClient().post(pathOf(journalOperations.approveImport), input),
|
|
48003
|
+
render: renderJournalImportApprove
|
|
48004
|
+
};
|
|
48005
|
+
}
|
|
48006
|
+
}),
|
|
47303
48007
|
defineCommand({
|
|
47304
48008
|
operation: journalOperations.importOverview,
|
|
47305
48009
|
path: ["journal", "import", "status"],
|
|
@@ -48186,6 +48890,118 @@ var commands30 = [
|
|
|
48186
48890
|
// src/commands/money.ts
|
|
48187
48891
|
import { readFileSync as readFileSync8 } from "fs";
|
|
48188
48892
|
import { basename as basename5 } from "path";
|
|
48893
|
+
|
|
48894
|
+
// ../../packages/domain/src/money.ts
|
|
48895
|
+
var toNumber = (cents) => Number(cents) / 100;
|
|
48896
|
+
function formatMoney(amount, {
|
|
48897
|
+
currency = "EUR",
|
|
48898
|
+
signed = false,
|
|
48899
|
+
whole = false,
|
|
48900
|
+
cents: always = false,
|
|
48901
|
+
locale
|
|
48902
|
+
} = {}) {
|
|
48903
|
+
const exact = typeof amount === "bigint" ? amount : amountToCents(amount);
|
|
48904
|
+
const cents = whole ? roundToEuro(exact) : exact;
|
|
48905
|
+
const abs = cents < 0n ? -cents : cents;
|
|
48906
|
+
const text = new Intl.NumberFormat(locale ?? "en-IE", {
|
|
48907
|
+
style: "currency",
|
|
48908
|
+
currency,
|
|
48909
|
+
minimumFractionDigits: always || abs % 100n !== 0n ? 2 : 0,
|
|
48910
|
+
maximumFractionDigits: 2
|
|
48911
|
+
}).format(toNumber(abs));
|
|
48912
|
+
if (cents < 0n)
|
|
48913
|
+
return `\u2212${text}`;
|
|
48914
|
+
return signed && cents > 0n ? `+${text}` : text;
|
|
48915
|
+
}
|
|
48916
|
+
function roundToEuro(cents) {
|
|
48917
|
+
const abs = cents < 0n ? -cents : cents;
|
|
48918
|
+
const rounded = (abs + 50n) / 100n * 100n;
|
|
48919
|
+
return cents < 0n ? -rounded : rounded;
|
|
48920
|
+
}
|
|
48921
|
+
function goalSavedSource(goal) {
|
|
48922
|
+
const account = goal.accountTitle ?? "the account";
|
|
48923
|
+
if (goal.savedFrom === "account")
|
|
48924
|
+
return `all of ${account}`;
|
|
48925
|
+
if (goal.savedFrom === "account_share")
|
|
48926
|
+
return `its share of ${account}`;
|
|
48927
|
+
if (goal.accountTitle && goal.accountBalanceKnown === false) {
|
|
48928
|
+
return `counted toward it; the balance of ${goal.accountTitle} is unknown`;
|
|
48929
|
+
}
|
|
48930
|
+
return "counted toward it";
|
|
48931
|
+
}
|
|
48932
|
+
|
|
48933
|
+
// src/global-flags.ts
|
|
48934
|
+
var GLOBAL_FLAGS = [
|
|
48935
|
+
{ name: "json", type: "boolean", description: "Emit JSON (automatic when stdout is piped)." },
|
|
48936
|
+
{ name: "quiet", type: "boolean", description: "Suppress human output (errors still report)." },
|
|
48937
|
+
{ name: "verbose", type: "boolean", description: "Extra diagnostics on stderr." },
|
|
48938
|
+
{
|
|
48939
|
+
name: "api-key",
|
|
48940
|
+
type: "string",
|
|
48941
|
+
description: "API key; overrides DO_API_KEY and the config file."
|
|
48942
|
+
},
|
|
48943
|
+
{
|
|
48944
|
+
name: "url",
|
|
48945
|
+
type: "string",
|
|
48946
|
+
description: "API origin; overrides DO_API_URL and the config file."
|
|
48947
|
+
},
|
|
48948
|
+
{ name: "help", type: "boolean", description: "Show help for the command." },
|
|
48949
|
+
{ name: "version", type: "boolean", description: "Print the CLI version." },
|
|
48950
|
+
{
|
|
48951
|
+
name: "data",
|
|
48952
|
+
type: "string",
|
|
48953
|
+
description: `JSON object body for create/update (e.g. --data '{"title":"Buy milk"}').`
|
|
48954
|
+
},
|
|
48955
|
+
{
|
|
48956
|
+
name: "allow-destructive",
|
|
48957
|
+
type: "boolean",
|
|
48958
|
+
description: "Permit a destructive verb (delete/revoke/remove-member). Off by default \u2014 a guardrail for autonomous agents."
|
|
48959
|
+
},
|
|
48960
|
+
{
|
|
48961
|
+
name: "dry-run",
|
|
48962
|
+
type: "boolean",
|
|
48963
|
+
description: "Preview a create/update/delete/archive (prints before/after) without writing \u2014 for agent-driven cleanup."
|
|
48964
|
+
}
|
|
48965
|
+
];
|
|
48966
|
+
|
|
48967
|
+
// src/commands/money.ts
|
|
48968
|
+
function refuseUnknownFlags(flags, own) {
|
|
48969
|
+
const known = new Set([...own, ...GLOBAL_FLAGS.map((f) => f.name)]);
|
|
48970
|
+
const unknown = Object.keys(flags).filter((name) => !known.has(name));
|
|
48971
|
+
if (unknown.length > 0) {
|
|
48972
|
+
throw usageError(`unknown flag ${unknown.map((n) => `--${n}`).join(", ")} (takes ${own.map((n) => `--${n}`).join(", ")}; see \`do help\`)`);
|
|
48973
|
+
}
|
|
48974
|
+
}
|
|
48975
|
+
var ACCOUNT_KIND_FLAGS = [
|
|
48976
|
+
{
|
|
48977
|
+
name: "kind",
|
|
48978
|
+
type: "string",
|
|
48979
|
+
description: "current \xB7 savings \xB7 credit_card \xB7 cash \xB7 voucher \xB7 loan \xB7 investment \xB7 pension \xB7 asset_manual."
|
|
48980
|
+
},
|
|
48981
|
+
{
|
|
48982
|
+
name: "subtype",
|
|
48983
|
+
type: "string",
|
|
48984
|
+
description: "A pension's kind: group_insurance \xB7 pension_savings_fund \xB7 pension_savings_insurance \xB7 vapz (`none` clears it)."
|
|
48985
|
+
},
|
|
48986
|
+
{
|
|
48987
|
+
name: "locked-until",
|
|
48988
|
+
type: "string",
|
|
48989
|
+
description: "YYYY-MM-DD until which it can't be taken out (`none` clears it)."
|
|
48990
|
+
}
|
|
48991
|
+
];
|
|
48992
|
+
function accountKindFields(flags) {
|
|
48993
|
+
const out = {};
|
|
48994
|
+
const kind = flagString(flags, "kind");
|
|
48995
|
+
const subtype = flagString(flags, "subtype");
|
|
48996
|
+
const lockedUntil = flagString(flags, "locked-until");
|
|
48997
|
+
if (kind !== undefined)
|
|
48998
|
+
out.kind = kind;
|
|
48999
|
+
if (subtype !== undefined)
|
|
49000
|
+
out.pensionSubtype = subtype === "none" ? null : subtype;
|
|
49001
|
+
if (lockedUntil !== undefined)
|
|
49002
|
+
out.lockedUntil = lockedUntil === "none" ? null : lockedUntil;
|
|
49003
|
+
return out;
|
|
49004
|
+
}
|
|
48189
49005
|
function renderDedupe(data) {
|
|
48190
49006
|
const r = data;
|
|
48191
49007
|
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 +49010,38 @@ function renderDedupe(data) {
|
|
|
48194
49010
|
return lines.join(`
|
|
48195
49011
|
`);
|
|
48196
49012
|
}
|
|
49013
|
+
function renderGoals(data) {
|
|
49014
|
+
const goals = Array.isArray(data) ? data : [];
|
|
49015
|
+
if (goals.length === 0)
|
|
49016
|
+
return "(no goals yet)";
|
|
49017
|
+
return goals.map((g) => {
|
|
49018
|
+
const whole = (a) => formatMoney(a, { whole: true });
|
|
49019
|
+
const parts = [
|
|
49020
|
+
`${whole(g.saved)} of ${whole(g.targetAmount)} (${goalSavedSource(g)})`,
|
|
49021
|
+
Number(g.remaining) > 0 ? `${whole(g.remaining)} to go` : "reached",
|
|
49022
|
+
g.monthlyNeed && g.targetOn ? `${formatMoney(g.monthlyNeed)} a month by ${g.targetOn}` : null
|
|
49023
|
+
];
|
|
49024
|
+
return `${g.title} ${parts.filter(Boolean).join(" \xB7 ")} ${g.id}`;
|
|
49025
|
+
}).join(`
|
|
49026
|
+
`);
|
|
49027
|
+
}
|
|
49028
|
+
function syncLine(r) {
|
|
49029
|
+
const when = r.startedAt ? `${String(r.startedAt).slice(0, 16).replace("T", " ")} ` : "";
|
|
49030
|
+
const what = r.accountTitle ? `${r.accountTitle} ` : "";
|
|
49031
|
+
const counts = `fetched ${r.fetched ?? 0} \xB7 imported ${r.imported ?? 0} \xB7 matched ${r.matched ?? 0} \xB7 duplicates ${r.duplicates ?? 0}`;
|
|
49032
|
+
return `${when}${what}${r.outcome ?? "?"} ${counts}${r.error ? `
|
|
49033
|
+
${r.error}` : ""}`;
|
|
49034
|
+
}
|
|
49035
|
+
function renderSyncLog(data) {
|
|
49036
|
+
const rows = Array.isArray(data) ? data : data.data ?? [];
|
|
49037
|
+
return rows.length === 0 ? "(no syncs yet)" : rows.map(syncLine).join(`
|
|
49038
|
+
`);
|
|
49039
|
+
}
|
|
49040
|
+
function renderSyncResults(data) {
|
|
49041
|
+
const { results } = data;
|
|
49042
|
+
return results.length === 0 ? "(no live bank links)" : results.map(syncLine).join(`
|
|
49043
|
+
`);
|
|
49044
|
+
}
|
|
48197
49045
|
function partyFlag(flags) {
|
|
48198
49046
|
const userId = flagString(flags, "with-user");
|
|
48199
49047
|
const contactId = flagString(flags, "with-contact");
|
|
@@ -48237,13 +49085,17 @@ var commands31 = [
|
|
|
48237
49085
|
path: ["money", "account-add"],
|
|
48238
49086
|
summary: "Open a money account (Only me unless --data says visibility: space).",
|
|
48239
49087
|
args: [],
|
|
48240
|
-
flags: [
|
|
49088
|
+
flags: [
|
|
49089
|
+
{ name: "data", type: "string", description: "CreateMoneyAccountInput JSON." },
|
|
49090
|
+
...ACCOUNT_KIND_FLAGS
|
|
49091
|
+
],
|
|
48241
49092
|
returns: "MoneyAccountView",
|
|
48242
|
-
notes: `. \`do money account-add --data '{"title":"Joint","kind":"current","holder":"joint","visibility":"space"}'\`. An IBAN is stored only masked and hashed.`,
|
|
49093
|
+
notes: `. \`do money account-add --data '{"title":"Joint","kind":"current","holder":"joint","visibility":"space"}'\`. An IBAN is stored only masked and hashed. A pension: \`--kind pension --subtype group_insurance --locked-until 2055-01-01\` (a subtype on another kind is a 400).`,
|
|
48243
49094
|
run: async ({ flags, requireClient }) => {
|
|
48244
49095
|
const body = parseInput(createMoneyAccountInputSchema, {
|
|
48245
49096
|
id: ulid3(),
|
|
48246
|
-
...readDataFlag(flags)
|
|
49097
|
+
...readDataFlag(flags),
|
|
49098
|
+
...accountKindFields(flags)
|
|
48247
49099
|
});
|
|
48248
49100
|
return {
|
|
48249
49101
|
data: await requireClient().post(pathOf(moneyOperations.createAccount), body),
|
|
@@ -48258,6 +49110,7 @@ var commands31 = [
|
|
|
48258
49110
|
args: [{ name: "id", required: true, description: "Account ULID." }],
|
|
48259
49111
|
flags: [
|
|
48260
49112
|
{ name: "data", type: "string", description: "UpdateMoneyAccountInput JSON." },
|
|
49113
|
+
...ACCOUNT_KIND_FLAGS,
|
|
48261
49114
|
{
|
|
48262
49115
|
name: "balance",
|
|
48263
49116
|
type: "string",
|
|
@@ -48270,8 +49123,14 @@ var commands31 = [
|
|
|
48270
49123
|
}
|
|
48271
49124
|
],
|
|
48272
49125
|
returns: "MoneyAccountView",
|
|
48273
|
-
notes: ". `--balance 1234.56 --balance-at 2026-10-27` anchors an account whose bank export has no running balance (ING's): net worth counts from it, back and forward through the ledger. A negative one rides as `--balance=-20`. A day ahead is refused, and so is an account a bank link keeps. `--data` takes the rest (title,
|
|
49126
|
+
notes: ". `--balance 1234.56 --balance-at 2026-10-27` anchors an account whose bank export has no running balance (ING's): net worth counts from it, back and forward through the ledger. A negative one rides as `--balance=-20`. A day ahead is refused, and so is an account a bank link keeps. `--kind pension --subtype vapz --locked-until 2055-01-01` refiles it (an investment account moved to pension keeps its balance and ledger; moved off pension, the subtype is cleared). `--data` takes the rest (title, iban, includeInNetWorth, \u2026). A flag it doesn't take, or nothing to change, is refused before anything is sent.",
|
|
48274
49127
|
run: async ({ args, flags, io, requireClient }) => {
|
|
49128
|
+
refuseUnknownFlags(flags, [
|
|
49129
|
+
"data",
|
|
49130
|
+
...ACCOUNT_KIND_FLAGS.map((f) => f.name),
|
|
49131
|
+
"balance",
|
|
49132
|
+
"balance-at"
|
|
49133
|
+
]);
|
|
48275
49134
|
const id = requireArg(args, 0, "account id");
|
|
48276
49135
|
const balance = flagString(flags, "balance");
|
|
48277
49136
|
const balanceAt = flagString(flags, "balance-at");
|
|
@@ -48280,6 +49139,7 @@ var commands31 = [
|
|
|
48280
49139
|
}
|
|
48281
49140
|
const body = parseInput(updateMoneyAccountInputSchema, {
|
|
48282
49141
|
...readDataFlag(flags),
|
|
49142
|
+
...accountKindFields(flags),
|
|
48283
49143
|
...balance !== undefined ? { balance, balanceAt: balanceAt ?? isoDay(io.now()) } : {}
|
|
48284
49144
|
});
|
|
48285
49145
|
return {
|
|
@@ -48330,7 +49190,7 @@ var commands31 = [
|
|
|
48330
49190
|
});
|
|
48331
49191
|
return {
|
|
48332
49192
|
data: await requireClient().post(pathOf(moneyOperations.syncConnections), body),
|
|
48333
|
-
render:
|
|
49193
|
+
render: renderSyncResults
|
|
48334
49194
|
};
|
|
48335
49195
|
}
|
|
48336
49196
|
}),
|
|
@@ -48367,7 +49227,7 @@ var commands31 = [
|
|
|
48367
49227
|
args: [{ name: "id", required: true, description: "Bank link (connection) ULID." }],
|
|
48368
49228
|
flags: [],
|
|
48369
49229
|
returns: "MoneyConnectionSync[]",
|
|
48370
|
-
run: readOperation(moneyOperations.listConnectionSyncs,
|
|
49230
|
+
run: readOperation(moneyOperations.listConnectionSyncs, renderSyncLog, { id: "id" })
|
|
48371
49231
|
}),
|
|
48372
49232
|
defineCommand({
|
|
48373
49233
|
operation: moneyOperations.listCategories,
|
|
@@ -49076,7 +49936,7 @@ var commands31 = [
|
|
|
49076
49936
|
}
|
|
49077
49937
|
],
|
|
49078
49938
|
returns: "MoneyNetWorth",
|
|
49079
|
-
notes: ". total, have and owe this month; points per month (oldest first, total null before any value); accounts biggest first with value, source (balance \xB7 ledger \xB7 manual) and the month it's from. A loan or a card always counts as owed. `complete: false` when an account counts but its balance isn't known (a bank export with no running balance): it's listed under `unknown`, left out of the total, never counted as 0; `do money account-update <id> --balance \u2026 --balance-at \u2026` states it.",
|
|
49939
|
+
notes: ". total, have and owe this month, and liquid beside locked (a pension, or an account locked until a day ahead; each account says `locked`, `lockedUntil` and a pension's `pensionSubtype`); points per month (oldest first, total null before any value); accounts biggest first with value, source (balance \xB7 ledger \xB7 manual) and the month it's from. A loan or a card always counts as owed. `complete: false` when an account counts but its balance isn't known (a bank export with no running balance): it's listed under `unknown`, left out of the total, never counted as 0; `do money account-update <id> --balance \u2026 --balance-at \u2026` states it.",
|
|
49080
49940
|
run: async ({ flags, requireClient }) => ({
|
|
49081
49941
|
data: await requireClient().get(pathOf(moneyOperations.netWorthRead), {
|
|
49082
49942
|
query: { scope: flagString(flags, "scope"), months: flagString(flags, "months") }
|
|
@@ -49115,7 +49975,8 @@ var commands31 = [
|
|
|
49115
49975
|
args: [],
|
|
49116
49976
|
flags: [],
|
|
49117
49977
|
returns: "MoneyGoalView[]",
|
|
49118
|
-
|
|
49978
|
+
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`.",
|
|
49979
|
+
run: readOperation(moneyOperations.listGoals, renderGoals)
|
|
49119
49980
|
}),
|
|
49120
49981
|
defineCommand({
|
|
49121
49982
|
operation: moneyOperations.getGoal,
|
|
@@ -49660,6 +50521,139 @@ var commands33 = [
|
|
|
49660
50521
|
// src/commands/people.ts
|
|
49661
50522
|
import { readFileSync as readFileSync9 } from "fs";
|
|
49662
50523
|
import { basename as basename6 } from "path";
|
|
50524
|
+
|
|
50525
|
+
// ../../packages/domain/src/family-tree.ts
|
|
50526
|
+
function lifeYears(p) {
|
|
50527
|
+
if (p.birthYear && p.deathYear)
|
|
50528
|
+
return `${p.birthYear}\u2013${p.deathYear}`;
|
|
50529
|
+
if (p.deathYear)
|
|
50530
|
+
return `\u2013${p.deathYear}`;
|
|
50531
|
+
return p.birthYear ? String(p.birthYear) : "";
|
|
50532
|
+
}
|
|
50533
|
+
function greats(generation) {
|
|
50534
|
+
if (generation < 3)
|
|
50535
|
+
return "";
|
|
50536
|
+
const n = generation - 2;
|
|
50537
|
+
return n === 1 ? "great-" : `${n}\xD7 great-`;
|
|
50538
|
+
}
|
|
50539
|
+
function generationLabel(generation) {
|
|
50540
|
+
if (generation === 0)
|
|
50541
|
+
return "You";
|
|
50542
|
+
if (generation === 1)
|
|
50543
|
+
return "Parents";
|
|
50544
|
+
const word = `${greats(generation)}grandparents`;
|
|
50545
|
+
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
50546
|
+
}
|
|
50547
|
+
function ancestorTitle(n) {
|
|
50548
|
+
const generation = ahnentafelGeneration(n);
|
|
50549
|
+
if (generation === 0)
|
|
50550
|
+
return "You";
|
|
50551
|
+
const male = n % 2 === 0;
|
|
50552
|
+
if (generation === 1)
|
|
50553
|
+
return male ? "Father" : "Mother";
|
|
50554
|
+
const word = `${greats(generation)}grand${male ? "father" : "mother"}`;
|
|
50555
|
+
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
50556
|
+
}
|
|
50557
|
+
function pedigreeOutline(view) {
|
|
50558
|
+
const people = new Map(view.people.map((p) => [p.ref, p]));
|
|
50559
|
+
const slots = new Map(view.ancestors.map((a) => [a.n, a]));
|
|
50560
|
+
const rows = [];
|
|
50561
|
+
const visit = (n) => {
|
|
50562
|
+
const slot = slots.get(n);
|
|
50563
|
+
const person = slot ? people.get(slot.ref) : undefined;
|
|
50564
|
+
if (!slot || !person)
|
|
50565
|
+
return;
|
|
50566
|
+
const parents = [2 * n, 2 * n + 1].filter((m) => slots.has(m));
|
|
50567
|
+
rows.push({
|
|
50568
|
+
n,
|
|
50569
|
+
generation: slot.generation,
|
|
50570
|
+
title: ancestorTitle(n),
|
|
50571
|
+
person,
|
|
50572
|
+
repeatOf: slot.repeatOf,
|
|
50573
|
+
parents
|
|
50574
|
+
});
|
|
50575
|
+
for (const m of parents)
|
|
50576
|
+
visit(m);
|
|
50577
|
+
};
|
|
50578
|
+
visit(1);
|
|
50579
|
+
return rows;
|
|
50580
|
+
}
|
|
50581
|
+
function pedigreeText(view) {
|
|
50582
|
+
const lines = pedigreeOutline(view).map((r) => {
|
|
50583
|
+
const years = lifeYears(r.person);
|
|
50584
|
+
const name = years ? `${r.person.name} (${years})` : r.person.name;
|
|
50585
|
+
const again = r.repeatOf ? ` \xB7 also #${r.repeatOf}, line shown there` : "";
|
|
50586
|
+
return `${" ".repeat(r.generation)}${r.title} \xB7 ${name} \xB7 ${r.person.ref}${again}`;
|
|
50587
|
+
});
|
|
50588
|
+
const deepest = generationLabel(view.generations).toLowerCase();
|
|
50589
|
+
const more = view.deeper ? " The tree goes further back: ask for a bigger depth." : "";
|
|
50590
|
+
return [...lines, "", `${view.generations} generations back (to the ${deepest}).${more}`].join(`
|
|
50591
|
+
`);
|
|
50592
|
+
}
|
|
50593
|
+
|
|
50594
|
+
// src/commands/people.ts
|
|
50595
|
+
var EDIT_NOTE = "Answers with a receipt: edit.id (for `do people tree undo`), the changes person by person, and the person after. Edits are kept over a new import.";
|
|
50596
|
+
var PERSON_FLAGS = [
|
|
50597
|
+
{ name: "given", type: "string", description: "First name(s)." },
|
|
50598
|
+
{ name: "surname", type: "string", description: "Surname." },
|
|
50599
|
+
{ name: "sex", type: "string", description: "M or F." },
|
|
50600
|
+
{ name: "born", type: "string", description: "Birth date: YYYY or YYYY-MM-DD." },
|
|
50601
|
+
{ name: "born-place", type: "string", description: "Where they were born." },
|
|
50602
|
+
{ name: "died", type: "string", description: "Death date: YYYY or YYYY-MM-DD." },
|
|
50603
|
+
{ name: "died-place", type: "string", description: "Where they died." }
|
|
50604
|
+
];
|
|
50605
|
+
var PERSON_FLAG_FIELDS = {
|
|
50606
|
+
given: "given",
|
|
50607
|
+
surname: "surname",
|
|
50608
|
+
sex: "sex",
|
|
50609
|
+
born: "birthDate",
|
|
50610
|
+
"born-place": "birthPlace",
|
|
50611
|
+
died: "deathDate",
|
|
50612
|
+
"died-place": "deathPlace"
|
|
50613
|
+
};
|
|
50614
|
+
var FACT_FLAGS = [
|
|
50615
|
+
{ name: "date", type: "string", description: "The date as written (ABT 1720, 1801-03-02)." },
|
|
50616
|
+
{ name: "place", type: "string", description: "Where." },
|
|
50617
|
+
{ name: "value", type: "string", description: "The value or note (farmer, a line of text)." }
|
|
50618
|
+
];
|
|
50619
|
+
var CLEAR_FLAG = {
|
|
50620
|
+
name: "clear",
|
|
50621
|
+
type: "string",
|
|
50622
|
+
description: "Fields to clear, comma-separated."
|
|
50623
|
+
};
|
|
50624
|
+
var FACT_ARGS = [
|
|
50625
|
+
{ name: "ref", required: true, description: "Their ref in the tree." },
|
|
50626
|
+
{ name: "factId", required: true, description: "The fact's id (OCCU-1, f-\u2026)." }
|
|
50627
|
+
];
|
|
50628
|
+
var LINK_ARGS = [
|
|
50629
|
+
{ name: "kind", required: true, description: "parent, child or partner." },
|
|
50630
|
+
{ name: "ref", required: true, description: "The person's ref." },
|
|
50631
|
+
{ name: "other", required: true, description: "Their parent's, child's or partner's ref." }
|
|
50632
|
+
];
|
|
50633
|
+
var pick2 = (flags, fields) => Object.fromEntries(Object.entries(fields).flatMap(([flag, field]) => {
|
|
50634
|
+
const v = flagString(flags, flag);
|
|
50635
|
+
return v === undefined ? [] : [[field, v]];
|
|
50636
|
+
}));
|
|
50637
|
+
var personFields = (flags) => pick2(flags, { name: "name", ...PERSON_FLAG_FIELDS });
|
|
50638
|
+
var factFields = (flags) => pick2(flags, { date: "date", place: "place", value: "value" });
|
|
50639
|
+
var clears = (flags) => Object.fromEntries((flagString(flags, "clear") ?? "").split(",").map((f) => f.trim()).filter(Boolean).map((f) => [f, null]));
|
|
50640
|
+
var factParams = (args) => ({
|
|
50641
|
+
ref: parseInput(familyTreeRefSchema, requireArg(args, 0, "ref")),
|
|
50642
|
+
factId: parseInput(familyTreeFactIdSchema, requireArg(args, 1, "fact id"))
|
|
50643
|
+
});
|
|
50644
|
+
function linkBody(args) {
|
|
50645
|
+
const kind = requireArg(args, 0, "kind");
|
|
50646
|
+
const ref = requireArg(args, 1, "ref");
|
|
50647
|
+
const other = requireArg(args, 2, "other");
|
|
50648
|
+
if (kind === "parent")
|
|
50649
|
+
return parseInput(familyTreeLinkInputSchema, { kind, parent: other, child: ref });
|
|
50650
|
+
if (kind === "child") {
|
|
50651
|
+
return parseInput(familyTreeLinkInputSchema, { kind: "parent", parent: ref, child: other });
|
|
50652
|
+
}
|
|
50653
|
+
if (kind === "partner")
|
|
50654
|
+
return parseInput(familyTreeLinkInputSchema, { kind, a: ref, b: other });
|
|
50655
|
+
throw usageError("the kind is parent, child or partner");
|
|
50656
|
+
}
|
|
49663
50657
|
var commands34 = [
|
|
49664
50658
|
{
|
|
49665
50659
|
spec: {
|
|
@@ -50015,6 +51009,34 @@ var commands34 = [
|
|
|
50015
51009
|
};
|
|
50016
51010
|
}
|
|
50017
51011
|
},
|
|
51012
|
+
{
|
|
51013
|
+
spec: {
|
|
51014
|
+
path: ["people", "tree", "ancestors"],
|
|
51015
|
+
summary: "Your direct line back, as a text pedigree: as far as --depth, or all of it.",
|
|
51016
|
+
args: [],
|
|
51017
|
+
flags: [
|
|
51018
|
+
{
|
|
51019
|
+
name: "depth",
|
|
51020
|
+
type: "string",
|
|
51021
|
+
description: "How many generations back (1\u201350; default all)."
|
|
51022
|
+
},
|
|
51023
|
+
{ name: "ref", type: "string", description: "Whose ancestors (default: you in the tree)." }
|
|
51024
|
+
],
|
|
51025
|
+
scopes: [...familyTreeOperations.ancestors.scopes],
|
|
51026
|
+
returns: "FamilyTreeAncestors",
|
|
51027
|
+
notes: "Each line is who they are to you, the name, the years and their ref, indented by generation; father's line before mother's. --json gives the slots by Ahnentafel number (you 1, a father 2n, a mother 2n+1)."
|
|
51028
|
+
},
|
|
51029
|
+
run: async ({ flags, requireClient }) => {
|
|
51030
|
+
const depth = flagString(flags, "depth");
|
|
51031
|
+
const ref = flagString(flags, "ref");
|
|
51032
|
+
const query = { ...depth ? { depth } : {}, ...ref ? { ref } : {} };
|
|
51033
|
+
parseInput(familyTreeAncestorsQuerySchema, query);
|
|
51034
|
+
return {
|
|
51035
|
+
data: await requireClient().get(pathOf(familyTreeOperations.ancestors), { query }),
|
|
51036
|
+
render: (data) => pedigreeText(data)
|
|
51037
|
+
};
|
|
51038
|
+
}
|
|
51039
|
+
},
|
|
50018
51040
|
{
|
|
50019
51041
|
spec: {
|
|
50020
51042
|
path: ["people", "tree", "list"],
|
|
@@ -50099,6 +51121,223 @@ var commands34 = [
|
|
|
50099
51121
|
render: renderRecord
|
|
50100
51122
|
};
|
|
50101
51123
|
}
|
|
51124
|
+
},
|
|
51125
|
+
{
|
|
51126
|
+
spec: {
|
|
51127
|
+
path: ["people", "tree", "person"],
|
|
51128
|
+
summary: "One person in your tree: facts (with their ids), parents, partners, children.",
|
|
51129
|
+
args: [{ name: "ref", required: true, description: "Their ref in the tree (I12)." }],
|
|
51130
|
+
flags: [],
|
|
51131
|
+
scopes: [...familyTreeOperations.person.scopes],
|
|
51132
|
+
returns: "FamilyTreePersonDetail"
|
|
51133
|
+
},
|
|
51134
|
+
run: async ({ args, requireClient }) => {
|
|
51135
|
+
const ref = parseInput(familyTreeRefSchema, requireArg(args, 0, "ref"));
|
|
51136
|
+
return {
|
|
51137
|
+
data: await requireClient().get(pathOf(familyTreeOperations.person, { ref })),
|
|
51138
|
+
render: renderRecord
|
|
51139
|
+
};
|
|
51140
|
+
}
|
|
51141
|
+
},
|
|
51142
|
+
{
|
|
51143
|
+
spec: {
|
|
51144
|
+
path: ["people", "tree", "person", "add"],
|
|
51145
|
+
summary: "Add someone your tree is missing.",
|
|
51146
|
+
args: [{ name: "name", required: true, description: "Their full name." }],
|
|
51147
|
+
flags: PERSON_FLAGS,
|
|
51148
|
+
scopes: [...familyTreeOperations.addPerson.scopes],
|
|
51149
|
+
returns: "FamilyTreeEditReceipt",
|
|
51150
|
+
notes: `${EDIT_NOTE} They get a new ref (P\u2026): link them next (\`do people tree link parent <child> <parent>\`).`
|
|
51151
|
+
},
|
|
51152
|
+
run: async ({ args, flags, requireClient }) => {
|
|
51153
|
+
const body = parseInput(addFamilyTreePersonInputSchema, {
|
|
51154
|
+
...personFields(flags),
|
|
51155
|
+
name: args.join(" ").trim() || requireArg(args, 0, "name")
|
|
51156
|
+
});
|
|
51157
|
+
return {
|
|
51158
|
+
data: await requireClient().post(pathOf(familyTreeOperations.addPerson), body),
|
|
51159
|
+
render: renderRecord
|
|
51160
|
+
};
|
|
51161
|
+
}
|
|
51162
|
+
},
|
|
51163
|
+
{
|
|
51164
|
+
spec: {
|
|
51165
|
+
path: ["people", "tree", "person", "edit"],
|
|
51166
|
+
summary: "Change a person in your tree: name, sex, birth, death.",
|
|
51167
|
+
args: [{ name: "ref", required: true, description: "Their ref in the tree." }],
|
|
51168
|
+
flags: [
|
|
51169
|
+
{ name: "name", type: "string", description: "Their full name." },
|
|
51170
|
+
...PERSON_FLAGS,
|
|
51171
|
+
CLEAR_FLAG
|
|
51172
|
+
],
|
|
51173
|
+
scopes: [...familyTreeOperations.editPerson.scopes],
|
|
51174
|
+
returns: "FamilyTreeEditReceipt",
|
|
51175
|
+
notes: `${EDIT_NOTE} --clear takes field names (birthPlace,deathDate,\u2026).`
|
|
51176
|
+
},
|
|
51177
|
+
run: async ({ args, flags, requireClient }) => {
|
|
51178
|
+
const ref = parseInput(familyTreeRefSchema, requireArg(args, 0, "ref"));
|
|
51179
|
+
const body = parseInput(editFamilyTreePersonInputSchema, {
|
|
51180
|
+
...personFields(flags),
|
|
51181
|
+
...clears(flags),
|
|
51182
|
+
...readDataFlag(flags)
|
|
51183
|
+
});
|
|
51184
|
+
return {
|
|
51185
|
+
data: await requireClient().patch(pathOf(familyTreeOperations.editPerson, { ref }), body),
|
|
51186
|
+
render: renderRecord
|
|
51187
|
+
};
|
|
51188
|
+
}
|
|
51189
|
+
},
|
|
51190
|
+
{
|
|
51191
|
+
spec: {
|
|
51192
|
+
path: ["people", "tree", "person", "remove"],
|
|
51193
|
+
summary: "Take a person out of your tree (and out of their families).",
|
|
51194
|
+
args: [{ name: "ref", required: true, description: "Their ref in the tree." }],
|
|
51195
|
+
flags: [],
|
|
51196
|
+
scopes: [...familyTreeOperations.removePerson.scopes],
|
|
51197
|
+
returns: "FamilyTreeEditReceipt",
|
|
51198
|
+
notes: `${EDIT_NOTE} An agent's key waits for the member's yes, as every delete does.`
|
|
51199
|
+
},
|
|
51200
|
+
run: async ({ args, requireClient }) => {
|
|
51201
|
+
const ref = parseInput(familyTreeRefSchema, requireArg(args, 0, "ref"));
|
|
51202
|
+
return {
|
|
51203
|
+
data: await requireClient().delete(pathOf(familyTreeOperations.removePerson, { ref })),
|
|
51204
|
+
render: renderRecord
|
|
51205
|
+
};
|
|
51206
|
+
}
|
|
51207
|
+
},
|
|
51208
|
+
{
|
|
51209
|
+
spec: {
|
|
51210
|
+
path: ["people", "tree", "fact", "add"],
|
|
51211
|
+
summary: "Add a fact to a person: an occupation, a residence, a note.",
|
|
51212
|
+
args: [
|
|
51213
|
+
{ name: "ref", required: true, description: "Their ref in the tree." },
|
|
51214
|
+
{
|
|
51215
|
+
name: "type",
|
|
51216
|
+
required: true,
|
|
51217
|
+
description: "What kind (occupation, residence, note, \u2026)."
|
|
51218
|
+
}
|
|
51219
|
+
],
|
|
51220
|
+
flags: FACT_FLAGS,
|
|
51221
|
+
scopes: [...familyTreeOperations.addFact.scopes],
|
|
51222
|
+
returns: "FamilyTreeEditReceipt",
|
|
51223
|
+
notes: EDIT_NOTE
|
|
51224
|
+
},
|
|
51225
|
+
run: async ({ args, flags, requireClient }) => {
|
|
51226
|
+
const ref = parseInput(familyTreeRefSchema, requireArg(args, 0, "ref"));
|
|
51227
|
+
const body = parseInput(addFamilyTreeFactInputSchema, {
|
|
51228
|
+
type: requireArg(args, 1, "type"),
|
|
51229
|
+
...factFields(flags)
|
|
51230
|
+
});
|
|
51231
|
+
return {
|
|
51232
|
+
data: await requireClient().post(pathOf(familyTreeOperations.addFact, { ref }), body),
|
|
51233
|
+
render: renderRecord
|
|
51234
|
+
};
|
|
51235
|
+
}
|
|
51236
|
+
},
|
|
51237
|
+
{
|
|
51238
|
+
spec: {
|
|
51239
|
+
path: ["people", "tree", "fact", "edit"],
|
|
51240
|
+
summary: "Change one of a person's facts.",
|
|
51241
|
+
args: FACT_ARGS,
|
|
51242
|
+
flags: [
|
|
51243
|
+
{ name: "type", type: "string", description: "What kind of fact." },
|
|
51244
|
+
...FACT_FLAGS,
|
|
51245
|
+
CLEAR_FLAG
|
|
51246
|
+
],
|
|
51247
|
+
scopes: [...familyTreeOperations.editFact.scopes],
|
|
51248
|
+
returns: "FamilyTreeEditReceipt",
|
|
51249
|
+
notes: `${EDIT_NOTE} --clear takes field names (date,place,value).`
|
|
51250
|
+
},
|
|
51251
|
+
run: async ({ args, flags, requireClient }) => {
|
|
51252
|
+
const params = factParams(args);
|
|
51253
|
+
const type = flagString(flags, "type");
|
|
51254
|
+
const body = parseInput(editFamilyTreeFactInputSchema, {
|
|
51255
|
+
...type ? { type } : {},
|
|
51256
|
+
...factFields(flags),
|
|
51257
|
+
...clears(flags)
|
|
51258
|
+
});
|
|
51259
|
+
return {
|
|
51260
|
+
data: await requireClient().patch(pathOf(familyTreeOperations.editFact, params), body),
|
|
51261
|
+
render: renderRecord
|
|
51262
|
+
};
|
|
51263
|
+
}
|
|
51264
|
+
},
|
|
51265
|
+
{
|
|
51266
|
+
spec: {
|
|
51267
|
+
path: ["people", "tree", "fact", "remove"],
|
|
51268
|
+
summary: "Remove one of a person's facts.",
|
|
51269
|
+
args: FACT_ARGS,
|
|
51270
|
+
flags: [],
|
|
51271
|
+
scopes: [...familyTreeOperations.removeFact.scopes],
|
|
51272
|
+
returns: "FamilyTreeEditReceipt",
|
|
51273
|
+
notes: `${EDIT_NOTE} An agent's key waits for the member's yes, as every delete does.`
|
|
51274
|
+
},
|
|
51275
|
+
run: async ({ args, requireClient }) => {
|
|
51276
|
+
return {
|
|
51277
|
+
data: await requireClient().delete(pathOf(familyTreeOperations.removeFact, factParams(args))),
|
|
51278
|
+
render: renderRecord
|
|
51279
|
+
};
|
|
51280
|
+
}
|
|
51281
|
+
},
|
|
51282
|
+
{
|
|
51283
|
+
spec: {
|
|
51284
|
+
path: ["people", "tree", "link"],
|
|
51285
|
+
summary: "Link two people: give someone a parent or a child, or a partner.",
|
|
51286
|
+
args: LINK_ARGS,
|
|
51287
|
+
flags: [],
|
|
51288
|
+
scopes: [...familyTreeOperations.link.scopes],
|
|
51289
|
+
returns: "FamilyTreeEditReceipt",
|
|
51290
|
+
notes: `${EDIT_NOTE} A child has at most two parents.`
|
|
51291
|
+
},
|
|
51292
|
+
run: async ({ args, requireClient }) => ({
|
|
51293
|
+
data: await requireClient().post(pathOf(familyTreeOperations.link), linkBody(args)),
|
|
51294
|
+
render: renderRecord
|
|
51295
|
+
})
|
|
51296
|
+
},
|
|
51297
|
+
{
|
|
51298
|
+
spec: {
|
|
51299
|
+
path: ["people", "tree", "unlink"],
|
|
51300
|
+
summary: "Undo a parent, child or partner link.",
|
|
51301
|
+
args: LINK_ARGS,
|
|
51302
|
+
flags: [],
|
|
51303
|
+
scopes: [...familyTreeOperations.unlink.scopes],
|
|
51304
|
+
returns: "FamilyTreeEditReceipt",
|
|
51305
|
+
notes: `${EDIT_NOTE} Partners with children together: unlink a parent first.`
|
|
51306
|
+
},
|
|
51307
|
+
run: async ({ args, requireClient }) => ({
|
|
51308
|
+
data: await requireClient().post(pathOf(familyTreeOperations.unlink), linkBody(args)),
|
|
51309
|
+
render: renderRecord
|
|
51310
|
+
})
|
|
51311
|
+
},
|
|
51312
|
+
{
|
|
51313
|
+
spec: {
|
|
51314
|
+
path: ["people", "tree", "edits"],
|
|
51315
|
+
summary: "Your edits to the tree, oldest first; the ones the current file has no room for are flagged.",
|
|
51316
|
+
args: [],
|
|
51317
|
+
flags: [],
|
|
51318
|
+
scopes: [...familyTreeOperations.edits.scopes],
|
|
51319
|
+
returns: "FamilyTreeEdits",
|
|
51320
|
+
notes: "Edits are kept over every new import: one whose person isn't in the new file is applied: false with its reason, and applies again when a later file has them."
|
|
51321
|
+
},
|
|
51322
|
+
run: async ({ requireClient }) => ({
|
|
51323
|
+
data: await requireClient().get(pathOf(familyTreeOperations.edits)),
|
|
51324
|
+
render: renderRecord
|
|
51325
|
+
})
|
|
51326
|
+
},
|
|
51327
|
+
{
|
|
51328
|
+
spec: {
|
|
51329
|
+
path: ["people", "tree", "undo"],
|
|
51330
|
+
summary: "Undo one edit to the tree, by the id its receipt gave.",
|
|
51331
|
+
args: [{ name: "editId", required: true, description: "The receipt's edit.id." }],
|
|
51332
|
+
flags: [],
|
|
51333
|
+
scopes: [...familyTreeOperations.undo.scopes],
|
|
51334
|
+
returns: "FamilyTreeUndoResult",
|
|
51335
|
+
notes: "skipped lists the edits that leaned on it (a fact on a person it added)."
|
|
51336
|
+
},
|
|
51337
|
+
run: async ({ args, requireClient }) => ({
|
|
51338
|
+
data: await requireClient().post(pathOf(familyTreeOperations.undo, { id: requireArg(args, 0, "edit id") }), {}),
|
|
51339
|
+
render: renderRecord
|
|
51340
|
+
})
|
|
50102
51341
|
}
|
|
50103
51342
|
];
|
|
50104
51343
|
|
|
@@ -52147,7 +53386,7 @@ var commands49 = [
|
|
|
52147
53386
|
"#tag": "Adds a tag. Repeatable: '#home #urgent'.",
|
|
52148
53387
|
"!": "Stars the task \u2014 the 1\u20132 things that matter (aliases: !star/!s/!flag/!f). Drives Today's focus and the starred filter.",
|
|
52149
53388
|
"~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.",
|
|
53389
|
+
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
53390
|
"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
53391
|
estimate: "How long it takes (durationMinutes): '30m', '45 min', '1h', '1.5h', '1h30', '2 hours', 'for 20 mins'. Not 'in 2 hours'."
|
|
52153
53392
|
}
|
|
@@ -52942,38 +54181,6 @@ var FEATURE_COMMANDS = [
|
|
|
52942
54181
|
// src/registry.ts
|
|
52943
54182
|
var COMMANDS = FEATURE_COMMANDS;
|
|
52944
54183
|
var COMMAND_SPECS = COMMANDS.map((c) => c.spec);
|
|
52945
|
-
var GLOBAL_FLAGS = [
|
|
52946
|
-
{ name: "json", type: "boolean", description: "Emit JSON (automatic when stdout is piped)." },
|
|
52947
|
-
{ name: "quiet", type: "boolean", description: "Suppress human output (errors still report)." },
|
|
52948
|
-
{ name: "verbose", type: "boolean", description: "Extra diagnostics on stderr." },
|
|
52949
|
-
{
|
|
52950
|
-
name: "api-key",
|
|
52951
|
-
type: "string",
|
|
52952
|
-
description: "API key; overrides DO_API_KEY and the config file."
|
|
52953
|
-
},
|
|
52954
|
-
{
|
|
52955
|
-
name: "url",
|
|
52956
|
-
type: "string",
|
|
52957
|
-
description: "API origin; overrides DO_API_URL and the config file."
|
|
52958
|
-
},
|
|
52959
|
-
{ name: "help", type: "boolean", description: "Show help for the command." },
|
|
52960
|
-
{ name: "version", type: "boolean", description: "Print the CLI version." },
|
|
52961
|
-
{
|
|
52962
|
-
name: "data",
|
|
52963
|
-
type: "string",
|
|
52964
|
-
description: `JSON object body for create/update (e.g. --data '{"title":"Buy milk"}').`
|
|
52965
|
-
},
|
|
52966
|
-
{
|
|
52967
|
-
name: "allow-destructive",
|
|
52968
|
-
type: "boolean",
|
|
52969
|
-
description: "Permit a destructive verb (delete/revoke/remove-member). Off by default \u2014 a guardrail for autonomous agents."
|
|
52970
|
-
},
|
|
52971
|
-
{
|
|
52972
|
-
name: "dry-run",
|
|
52973
|
-
type: "boolean",
|
|
52974
|
-
description: "Preview a create/update/delete/archive (prints before/after) without writing \u2014 for agent-driven cleanup."
|
|
52975
|
-
}
|
|
52976
|
-
];
|
|
52977
54184
|
function matchCommand(positionals) {
|
|
52978
54185
|
for (const len of [4, 3, 2, 1]) {
|
|
52979
54186
|
const candidate = positionals.slice(0, len);
|