@robbeverhelst/do 1.331.0 → 1.333.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 +698 -73
- package/package.json +1 -1
package/dist/do.js
CHANGED
|
@@ -14410,7 +14410,7 @@ var configError = (message) => new CliError("config", message, EXIT.CONFIG);
|
|
|
14410
14410
|
|
|
14411
14411
|
// src/meta.ts
|
|
14412
14412
|
var CLI_NAME = "do";
|
|
14413
|
-
var VERSION = "1.
|
|
14413
|
+
var VERSION = "1.333.0";
|
|
14414
14414
|
var API_PREFIX = "/api/v1";
|
|
14415
14415
|
var DEFAULT_API_URL = "http://localhost:3001";
|
|
14416
14416
|
|
|
@@ -17969,7 +17969,8 @@ var nodeGrants = pgTable("node_grants", {
|
|
|
17969
17969
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
17970
17970
|
}, (t) => [
|
|
17971
17971
|
unique("node_grants_node_subject_uniq").on(t.tenantId, t.nodeId, t.subjectType, t.subjectId),
|
|
17972
|
-
index("node_grants_tenant_node_idx").on(t.tenantId, t.nodeId)
|
|
17972
|
+
index("node_grants_tenant_node_idx").on(t.tenantId, t.nodeId),
|
|
17973
|
+
index("node_grants_node_idx").on(t.nodeId)
|
|
17973
17974
|
]);
|
|
17974
17975
|
var shareLinks = pgTable("share_links", {
|
|
17975
17976
|
id: text("id").primaryKey(),
|
|
@@ -17982,7 +17983,10 @@ var shareLinks = pgTable("share_links", {
|
|
|
17982
17983
|
revokedAt: timestamp("revoked_at", { withTimezone: true }),
|
|
17983
17984
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
17984
17985
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
17985
|
-
}, (t) => [
|
|
17986
|
+
}, (t) => [
|
|
17987
|
+
index("share_links_tenant_node_idx").on(t.tenantId, t.nodeId),
|
|
17988
|
+
index("share_links_node_idx").on(t.nodeId)
|
|
17989
|
+
]);
|
|
17986
17990
|
var shareLinkGuests = pgTable("share_link_guests", {
|
|
17987
17991
|
id: text("id").primaryKey(),
|
|
17988
17992
|
tenantId: text("tenant_id").notNull().references(() => tenants.id),
|
|
@@ -18006,7 +18010,8 @@ var comments = pgTable("comments", {
|
|
|
18006
18010
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
18007
18011
|
}, (t) => [
|
|
18008
18012
|
index("comments_tenant_node_created_idx").on(t.tenantId, t.nodeId, t.createdAt),
|
|
18009
|
-
index("comments_parent_idx").on(t.parentId)
|
|
18013
|
+
index("comments_parent_idx").on(t.parentId),
|
|
18014
|
+
index("comments_node_idx").on(t.nodeId)
|
|
18010
18015
|
]);
|
|
18011
18016
|
var commentMentions = pgTable("comment_mentions", {
|
|
18012
18017
|
id: text("id").primaryKey(),
|
|
@@ -18212,7 +18217,8 @@ var occurrences = pgTable("occurrences", {
|
|
|
18212
18217
|
unique("occurrences_template_date_uniq").on(t.templateId, t.occurrenceDate),
|
|
18213
18218
|
index("occurrences_status_date_idx").on(t.status, t.occurrenceDate),
|
|
18214
18219
|
index("occurrences_tenant_date_idx").on(t.tenantId, t.occurrenceDate),
|
|
18215
|
-
index("occurrences_rule_idx").on(t.ruleId)
|
|
18220
|
+
index("occurrences_rule_idx").on(t.ruleId),
|
|
18221
|
+
index("occurrences_tenant_done_completed_idx").on(t.tenantId, t.completedAt).where(sql`${t.status} = 'done'`)
|
|
18216
18222
|
]);
|
|
18217
18223
|
|
|
18218
18224
|
// ../../packages/schema/src/db/core.ts
|
|
@@ -18349,6 +18355,7 @@ var userSettings = pgTable("user_settings", {
|
|
|
18349
18355
|
foodDiaryMode: boolean4("food_diary_mode").notNull().default(false),
|
|
18350
18356
|
calendarZoom: integer2("calendar_zoom"),
|
|
18351
18357
|
lifeBlockSharing: jsonb("life_block_sharing").$type(),
|
|
18358
|
+
recentSearches: jsonb("recent_searches").$type().notNull().default([]),
|
|
18352
18359
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
18353
18360
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
18354
18361
|
}, (t) => [unique("user_settings_tenant_user_uniq").on(t.tenantId, t.userId)]);
|
|
@@ -19806,6 +19813,39 @@ var agentOperations = defineOperations({
|
|
|
19806
19813
|
}
|
|
19807
19814
|
});
|
|
19808
19815
|
|
|
19816
|
+
// ../../packages/schema/src/db/files.ts
|
|
19817
|
+
var bytea = customType({
|
|
19818
|
+
dataType() {
|
|
19819
|
+
return "bytea";
|
|
19820
|
+
}
|
|
19821
|
+
});
|
|
19822
|
+
var files = pgTable("files", {
|
|
19823
|
+
id: text("id").primaryKey(),
|
|
19824
|
+
tenantId: text("tenant_id").notNull().references(() => tenants.id),
|
|
19825
|
+
ownerNodeId: text("owner_node_id").references(() => nodes.id, { onDelete: "cascade" }),
|
|
19826
|
+
uploadedByUserId: text("uploaded_by_user_id").references(() => users.id, {
|
|
19827
|
+
onDelete: "set null"
|
|
19828
|
+
}),
|
|
19829
|
+
uploadedByAgentId: text("uploaded_by_agent_id").references(() => agents.id),
|
|
19830
|
+
kind: text("kind").notNull(),
|
|
19831
|
+
name: text("name").notNull(),
|
|
19832
|
+
mime: text("mime").notNull(),
|
|
19833
|
+
size: integer2("size").notNull(),
|
|
19834
|
+
sha256: text("sha256").notNull(),
|
|
19835
|
+
data: bytea("data").notNull(),
|
|
19836
|
+
enc: text("enc"),
|
|
19837
|
+
textExtract: text("text_extract"),
|
|
19838
|
+
pages: integer2("pages"),
|
|
19839
|
+
source: text("source").notNull().default("upload"),
|
|
19840
|
+
receivedAt: timestamp("received_at", { withTimezone: true }),
|
|
19841
|
+
durationMs: integer2("duration_ms"),
|
|
19842
|
+
transcript: text("transcript"),
|
|
19843
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
19844
|
+
}, (t) => [
|
|
19845
|
+
index("files_tenant_idx").on(t.tenantId, t.createdAt),
|
|
19846
|
+
index("files_owner_idx").on(t.ownerNodeId, t.createdAt)
|
|
19847
|
+
]);
|
|
19848
|
+
|
|
19809
19849
|
// ../../packages/schema/src/db/periods.ts
|
|
19810
19850
|
var periodKind = pgEnum("period_kind", [
|
|
19811
19851
|
"hibernation",
|
|
@@ -19916,9 +19956,29 @@ var animalLostCards = pgTable("animal_lost_cards", {
|
|
|
19916
19956
|
phone: text("phone"),
|
|
19917
19957
|
showChipped: boolean4("show_chipped").notNull().default(false),
|
|
19918
19958
|
note: text("note"),
|
|
19959
|
+
description: text("description"),
|
|
19960
|
+
answersTo: text("answers_to"),
|
|
19961
|
+
missingSince: timestamp("missing_since", { withTimezone: true }),
|
|
19962
|
+
missingWhere: text("missing_where"),
|
|
19919
19963
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
19920
19964
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
19921
19965
|
}, (t) => [index("animal_lost_cards_tenant_idx").on(t.tenantId)]);
|
|
19966
|
+
var animalLostReportFrom = pgEnum("animal_lost_report_from", ["owner", "finder"]);
|
|
19967
|
+
var animalLostReports = pgTable("animal_lost_reports", {
|
|
19968
|
+
id: text("id").primaryKey(),
|
|
19969
|
+
tenantId: text("tenant_id").notNull().references(() => tenants.id),
|
|
19970
|
+
animalId: text("animal_id").notNull().references(() => animals.id, { onDelete: "cascade" }),
|
|
19971
|
+
from: animalLostReportFrom("from").notNull(),
|
|
19972
|
+
at: timestamp("at", { withTimezone: true }).notNull(),
|
|
19973
|
+
title: text("title"),
|
|
19974
|
+
note: text("note"),
|
|
19975
|
+
place: text("place"),
|
|
19976
|
+
lat: doublePrecision("lat"),
|
|
19977
|
+
lng: doublePrecision("lng"),
|
|
19978
|
+
phone: text("phone"),
|
|
19979
|
+
photoFileId: text("photo_file_id"),
|
|
19980
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
19981
|
+
}, (t) => [index("animal_lost_reports_animal_idx").on(t.tenantId, t.animalId, t.at)]);
|
|
19922
19982
|
var animalHandoverLinks = pgTable("animal_handover_links", {
|
|
19923
19983
|
id: text("id").primaryKey(),
|
|
19924
19984
|
tenantId: text("tenant_id").notNull().references(() => tenants.id),
|
|
@@ -19931,6 +19991,25 @@ var animalHandoverLinks = pgTable("animal_handover_links", {
|
|
|
19931
19991
|
createdByUserId: text("created_by_user_id").references(() => users.id),
|
|
19932
19992
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
19933
19993
|
}, (t) => [index("animal_handover_links_period_idx").on(t.tenantId, t.periodId)]);
|
|
19994
|
+
var animalHandoverTicks = pgTable("animal_handover_ticks", {
|
|
19995
|
+
id: text("id").primaryKey(),
|
|
19996
|
+
tenantId: text("tenant_id").notNull().references(() => tenants.id),
|
|
19997
|
+
periodId: text("period_id").notNull().references(() => periods.id, { onDelete: "cascade" }),
|
|
19998
|
+
linkId: text("link_id").notNull().references(() => animalHandoverLinks.id, { onDelete: "cascade" }),
|
|
19999
|
+
habitId: text("habit_id").notNull().references(() => habits.id),
|
|
20000
|
+
habitEntryId: text("habit_entry_id").notNull().unique().references(() => habitEntries.id, { onDelete: "cascade" }),
|
|
20001
|
+
day: date5("day", { mode: "string" }).notNull(),
|
|
20002
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
20003
|
+
}, (t) => [unique("animal_handover_ticks_habit_day_uniq").on(t.periodId, t.habitId, t.day)]);
|
|
20004
|
+
var animalHandoverNotes = pgTable("animal_handover_notes", {
|
|
20005
|
+
id: text("id").primaryKey(),
|
|
20006
|
+
tenantId: text("tenant_id").notNull().references(() => tenants.id),
|
|
20007
|
+
periodId: text("period_id").notNull().references(() => periods.id, { onDelete: "cascade" }),
|
|
20008
|
+
linkId: text("link_id").notNull().references(() => animalHandoverLinks.id, { onDelete: "cascade" }),
|
|
20009
|
+
body: text("body").notNull(),
|
|
20010
|
+
photoFileId: text("photo_file_id").references(() => files.id, { onDelete: "set null" }),
|
|
20011
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
20012
|
+
}, (t) => [index("animal_handover_notes_period_idx").on(t.tenantId, t.periodId)]);
|
|
19934
20013
|
|
|
19935
20014
|
// ../../packages/schema/src/db/documents.ts
|
|
19936
20015
|
var documentKind = pgEnum("document_kind", [
|
|
@@ -20590,12 +20669,23 @@ var animalHolidayModeSchema = object({
|
|
|
20590
20669
|
dailyPhoto: boolean2(),
|
|
20591
20670
|
feedReorder: boolean2()
|
|
20592
20671
|
});
|
|
20672
|
+
var animalHandoverWhereSchema = object({
|
|
20673
|
+
label: string2().trim().min(1).max(40),
|
|
20674
|
+
value: string2().trim().min(1).max(200)
|
|
20675
|
+
});
|
|
20676
|
+
var animalHandoverDayNoteSchema = object({
|
|
20677
|
+
day: string2().regex(/^\d{4}-\d{2}-\d{2}$/, "A day, like 2026-12-26"),
|
|
20678
|
+
title: string2().trim().min(1).max(80),
|
|
20679
|
+
lines: array(string2().trim().min(1).max(120)).max(2).default([])
|
|
20680
|
+
});
|
|
20593
20681
|
var animalHandoverRuleSchema = object({
|
|
20594
20682
|
sitter: string2().trim().min(1).max(120).optional(),
|
|
20595
20683
|
houseNote: string2().trim().min(1).max(300).optional(),
|
|
20596
20684
|
holidayMode: animalHolidayModeSchema.partial().optional(),
|
|
20597
20685
|
share: object({ tokenHash: string2(), until: string2() }).optional(),
|
|
20598
|
-
agentNote: string2().trim().min(1).max(500).optional()
|
|
20686
|
+
agentNote: string2().trim().min(1).max(500).optional(),
|
|
20687
|
+
where: array(animalHandoverWhereSchema).max(12).optional(),
|
|
20688
|
+
dayNotes: array(animalHandoverDayNoteSchema).max(31).optional()
|
|
20599
20689
|
}).passthrough();
|
|
20600
20690
|
var animalHandoverQuerySchema = object({ periodId: ulidSchema.optional() });
|
|
20601
20691
|
var animalHandoverTripSchema = object({
|
|
@@ -20650,22 +20740,122 @@ var animalHandoverShareSchema = object({
|
|
|
20650
20740
|
path: string2(),
|
|
20651
20741
|
until: string2()
|
|
20652
20742
|
});
|
|
20743
|
+
var animalSitterItemSchema = object({
|
|
20744
|
+
habitId: ulidSchema,
|
|
20745
|
+
title: string2(),
|
|
20746
|
+
timeOfDay: string2().nullable(),
|
|
20747
|
+
how: string2().nullable(),
|
|
20748
|
+
done: boolean2(),
|
|
20749
|
+
bySitter: boolean2()
|
|
20750
|
+
});
|
|
20751
|
+
var animalSitterDaySchema = object({
|
|
20752
|
+
day: string2(),
|
|
20753
|
+
items: array(animalSitterItemSchema),
|
|
20754
|
+
notes: array(animalHandoverDayNoteSchema.omit({ day: true }))
|
|
20755
|
+
});
|
|
20756
|
+
var animalSitterContactSchema = object({
|
|
20757
|
+
id: ulidSchema,
|
|
20758
|
+
title: string2(),
|
|
20759
|
+
kind: _enum2(["vet", "member"]),
|
|
20760
|
+
label: string2().nullable(),
|
|
20761
|
+
masked: string2()
|
|
20762
|
+
});
|
|
20763
|
+
var animalSitterNoteSchema = object({
|
|
20764
|
+
id: ulidSchema,
|
|
20765
|
+
body: string2(),
|
|
20766
|
+
photos: number2().int(),
|
|
20767
|
+
sentAt: exports_coerce.date(),
|
|
20768
|
+
seenBy: string2().nullable()
|
|
20769
|
+
});
|
|
20770
|
+
var animalSitterViewSchema = object({
|
|
20771
|
+
today: string2(),
|
|
20772
|
+
trip: object({
|
|
20773
|
+
title: string2(),
|
|
20774
|
+
from: string2(),
|
|
20775
|
+
to: string2(),
|
|
20776
|
+
backAt: string2().nullable(),
|
|
20777
|
+
until: string2()
|
|
20778
|
+
}),
|
|
20779
|
+
owners: array(string2()),
|
|
20780
|
+
sharedBy: string2().nullable(),
|
|
20781
|
+
days: array(animalSitterDaySchema),
|
|
20782
|
+
animals: array(animalHandoverAnimalSchema),
|
|
20783
|
+
where: array(animalHandoverWhereSchema),
|
|
20784
|
+
contacts: array(animalSitterContactSchema),
|
|
20785
|
+
notes: array(animalSitterNoteSchema)
|
|
20786
|
+
});
|
|
20787
|
+
var animalSitterTickInputSchema = object({ habitId: ulidSchema, done: boolean2() }).strict();
|
|
20788
|
+
var animalSitterNoteInputSchema = object({
|
|
20789
|
+
id: ulidSchema,
|
|
20790
|
+
body: string2().trim().max(1000).default("")
|
|
20791
|
+
}).strict();
|
|
20792
|
+
var animalSitterPhoneSchema = object({ phone: string2() });
|
|
20793
|
+
var lostCardPhoneSchema = string2().trim().regex(/^\+?[0-9][0-9 ()./-]{4,30}$/, "A phone number: digits, spaces, + ( ) . / -");
|
|
20653
20794
|
var animalLostCardInputSchema = object({
|
|
20654
20795
|
photoFileId: ulidSchema.nullish(),
|
|
20655
|
-
phone:
|
|
20796
|
+
phone: lostCardPhoneSchema.nullish(),
|
|
20656
20797
|
showChipped: boolean2().optional(),
|
|
20657
|
-
note: string2().trim().min(1).max(200).nullish()
|
|
20798
|
+
note: string2().trim().min(1).max(200).nullish(),
|
|
20799
|
+
description: string2().trim().min(1).max(400).nullish(),
|
|
20800
|
+
answersTo: string2().trim().min(1).max(120).nullish(),
|
|
20801
|
+
missingSince: exports_coerce.date().nullish(),
|
|
20802
|
+
missingWhere: string2().trim().min(1).max(200).nullish()
|
|
20658
20803
|
}).strict();
|
|
20659
20804
|
var animalLostCardPhotoSchema = object({ id: ulidSchema, name: string2() });
|
|
20805
|
+
var animalLostUpdateSchema = object({
|
|
20806
|
+
id: ulidSchema,
|
|
20807
|
+
at: exports_coerce.date(),
|
|
20808
|
+
title: string2(),
|
|
20809
|
+
note: string2().nullable(),
|
|
20810
|
+
place: string2().nullable()
|
|
20811
|
+
});
|
|
20660
20812
|
var animalLostCardPublicSchema = object({
|
|
20661
20813
|
name: string2(),
|
|
20662
20814
|
speciesKey: string2().nullable(),
|
|
20663
20815
|
kind: string2().nullable(),
|
|
20664
20816
|
missing: boolean2(),
|
|
20665
20817
|
hasPhoto: boolean2(),
|
|
20666
|
-
|
|
20818
|
+
phoneMasked: string2().nullable(),
|
|
20667
20819
|
chipped: boolean2(),
|
|
20668
|
-
note: string2().nullable()
|
|
20820
|
+
note: string2().nullable(),
|
|
20821
|
+
description: string2().nullable(),
|
|
20822
|
+
answersTo: string2().nullable(),
|
|
20823
|
+
missingSince: exports_coerce.date().nullable(),
|
|
20824
|
+
missingWhere: string2().nullable(),
|
|
20825
|
+
pronoun: _enum2(["he", "she", "they"]),
|
|
20826
|
+
sharedBy: string2().nullable(),
|
|
20827
|
+
owners: array(string2()),
|
|
20828
|
+
vet: string2().nullable(),
|
|
20829
|
+
updates: array(animalLostUpdateSchema)
|
|
20830
|
+
});
|
|
20831
|
+
var animalLostCardCallSchema = object({ phone: string2() });
|
|
20832
|
+
var blankless = (v) => typeof v === "string" && v.trim() === "" ? undefined : v;
|
|
20833
|
+
var animalLostFoundInputSchema = object({
|
|
20834
|
+
where: string2().trim().min(1).max(300),
|
|
20835
|
+
lat: preprocess(blankless, exports_coerce.number().min(-90).max(90).optional()),
|
|
20836
|
+
lng: preprocess(blankless, exports_coerce.number().min(-180).max(180).optional()),
|
|
20837
|
+
phone: preprocess(blankless, lostCardPhoneSchema.optional())
|
|
20838
|
+
}).strict().refine((v) => v.lat === undefined === (v.lng === undefined), {
|
|
20839
|
+
message: "Send both lat and lng, or neither."
|
|
20840
|
+
});
|
|
20841
|
+
var animalLostFoundSentSchema = object({ to: array(string2()) });
|
|
20842
|
+
var animalLostUpdateInputSchema = object({
|
|
20843
|
+
at: exports_coerce.date(),
|
|
20844
|
+
title: string2().trim().min(1).max(120),
|
|
20845
|
+
note: string2().trim().min(1).max(200).nullish(),
|
|
20846
|
+
place: string2().trim().min(1).max(200).nullish()
|
|
20847
|
+
}).strict();
|
|
20848
|
+
var animalLostReportSchema = object({
|
|
20849
|
+
id: ulidSchema,
|
|
20850
|
+
from: _enum2(["owner", "finder"]),
|
|
20851
|
+
at: exports_coerce.date(),
|
|
20852
|
+
title: string2().nullable(),
|
|
20853
|
+
note: string2().nullable(),
|
|
20854
|
+
place: string2().nullable(),
|
|
20855
|
+
lat: number2().nullable(),
|
|
20856
|
+
lng: number2().nullable(),
|
|
20857
|
+
phone: string2().nullable(),
|
|
20858
|
+
photoFileId: ulidSchema.nullable()
|
|
20669
20859
|
});
|
|
20670
20860
|
var animalLostCardStateSchema = object({
|
|
20671
20861
|
card: object({
|
|
@@ -20674,7 +20864,12 @@ var animalLostCardStateSchema = object({
|
|
|
20674
20864
|
phone: string2().nullable(),
|
|
20675
20865
|
showChipped: boolean2(),
|
|
20676
20866
|
note: string2().nullable(),
|
|
20867
|
+
description: string2().nullable(),
|
|
20868
|
+
answersTo: string2().nullable(),
|
|
20869
|
+
missingSince: exports_coerce.date().nullable(),
|
|
20870
|
+
missingWhere: string2().nullable(),
|
|
20677
20871
|
since: exports_coerce.date(),
|
|
20872
|
+
reports: array(animalLostReportSchema),
|
|
20678
20873
|
preview: animalLostCardPublicSchema
|
|
20679
20874
|
}).nullable(),
|
|
20680
20875
|
photos: array(animalLostCardPhotoSchema),
|
|
@@ -20925,7 +21120,7 @@ var animalOperations = defineOperations({
|
|
|
20925
21120
|
scopes: READ,
|
|
20926
21121
|
tag: "Animals",
|
|
20927
21122
|
summary: "An animal's lost-pet card",
|
|
20928
|
-
description: "`card` is null while it's off; else its `path` (`/lost/<token>`, the page its QR code opens, no account), what it shows (`photoFileId`, `phone`, `showChipped`, `note`) and `preview`, the finder's page. `photos` are the pictures on the animal it may show; `chipOnFile` whether it can say chipped.",
|
|
21123
|
+
description: "`card` is null while it's off; else its `path` (`/lost/<token>`, the page its QR code opens, no account), what it shows (`photoFileId`, `phone`, `showChipped`, `note`, `description`, `answersTo`, `missingSince`, `missingWhere`), `reports` (the household's updates and finders' reports, with a finder's number) and `preview`, the finder's page (its number masked). `photos` are the pictures on the animal it may show; `chipOnFile` whether it can say chipped.",
|
|
20929
21124
|
status: 200,
|
|
20930
21125
|
response: { description: "The card's state." },
|
|
20931
21126
|
errors: { 404: true }
|
|
@@ -21617,6 +21812,8 @@ var taskOwnerSchema = actorRefSchema.extend({ desk: string2().nullable() });
|
|
|
21617
21812
|
var taskFilingSuggestionSchema = object({
|
|
21618
21813
|
areaId: ulidSchema.nullable(),
|
|
21619
21814
|
area: string2().nullable(),
|
|
21815
|
+
projectId: ulidSchema.nullable().default(null),
|
|
21816
|
+
project: string2().nullable().default(null),
|
|
21620
21817
|
owner: taskOwnerSchema.nullable(),
|
|
21621
21818
|
scheduledAt: exports_coerce.date().nullable(),
|
|
21622
21819
|
by: actorRefSchema
|
|
@@ -23027,6 +23224,8 @@ var moneyBankPendingSchema = object({
|
|
|
23027
23224
|
pendingId: ulidSchema,
|
|
23028
23225
|
bank: string2(),
|
|
23029
23226
|
consentValidUntil: exports_coerce.date(),
|
|
23227
|
+
createdAt: exports_coerce.date(),
|
|
23228
|
+
expiresAt: exports_coerce.date(),
|
|
23030
23229
|
accounts: array(object({
|
|
23031
23230
|
index: number2().int().min(0),
|
|
23032
23231
|
name: string2().nullable(),
|
|
@@ -23062,6 +23261,33 @@ var moneyBankSyncResultSchema = object({
|
|
|
23062
23261
|
error: true
|
|
23063
23262
|
}).extend({ accountTitle: string2() }))
|
|
23064
23263
|
});
|
|
23264
|
+
var moneyDedupeInputSchema = object({ accountId: ulidSchema, commit: boolean2().optional() }).strict();
|
|
23265
|
+
var moneyDedupePairSchema = object({
|
|
23266
|
+
keepId: string2(),
|
|
23267
|
+
twinId: string2(),
|
|
23268
|
+
bookedOn: string2(),
|
|
23269
|
+
twinBookedOn: string2(),
|
|
23270
|
+
amount: string2(),
|
|
23271
|
+
description: string2().nullable(),
|
|
23272
|
+
counterpartyName: string2().nullable(),
|
|
23273
|
+
ambiguous: boolean2()
|
|
23274
|
+
});
|
|
23275
|
+
var moneyDedupeChangesSchema = object({
|
|
23276
|
+
merged: number2().int(),
|
|
23277
|
+
links: number2().int(),
|
|
23278
|
+
splits: number2().int(),
|
|
23279
|
+
settlements: number2().int(),
|
|
23280
|
+
transfers: number2().int(),
|
|
23281
|
+
rules: number2().int(),
|
|
23282
|
+
notes: number2().int()
|
|
23283
|
+
});
|
|
23284
|
+
var moneyDedupeResultSchema = object({
|
|
23285
|
+
accountId: string2(),
|
|
23286
|
+
committed: boolean2(),
|
|
23287
|
+
pairs: array(moneyDedupePairSchema),
|
|
23288
|
+
counts: object({ pairs: number2().int(), ambiguous: number2().int() }),
|
|
23289
|
+
changed: moneyDedupeChangesSchema
|
|
23290
|
+
});
|
|
23065
23291
|
|
|
23066
23292
|
// ../../packages/schema/src/operations/bank-link.ts
|
|
23067
23293
|
var CONNECT = ["connections:write"];
|
|
@@ -23144,6 +23370,18 @@ var bankLinkOperations = defineOperations({
|
|
|
23144
23370
|
response: { description: "The bank accounts to map." },
|
|
23145
23371
|
errors: { 400: true, 403: true, 409: true }
|
|
23146
23372
|
},
|
|
23373
|
+
pendings: {
|
|
23374
|
+
method: "get",
|
|
23375
|
+
path: "/money/bank-link/pending",
|
|
23376
|
+
scopes: CONNECT,
|
|
23377
|
+
afterScopes: AFTER,
|
|
23378
|
+
tag: "Money",
|
|
23379
|
+
summary: "List the bank links waiting to be mapped",
|
|
23380
|
+
description: "Every authorisation the key's member brought back from a bank and hasn't mapped yet, newest first, while it lasts (a day): each as the pending answers it, with `createdAt` and `expiresAt`. For when the page that finished it is gone; map one with link.",
|
|
23381
|
+
status: 200,
|
|
23382
|
+
response: { description: "The bank links waiting to be mapped." },
|
|
23383
|
+
errors: { 403: true }
|
|
23384
|
+
},
|
|
23147
23385
|
pending: {
|
|
23148
23386
|
method: "get",
|
|
23149
23387
|
path: "/money/bank-link/pending/{id}",
|
|
@@ -23580,7 +23818,8 @@ var calendarEventLinks = pgTable("calendar_event_links", {
|
|
|
23580
23818
|
}, (t) => [
|
|
23581
23819
|
unique("calendar_event_links_connection_node_uniq").on(t.connectionId, t.nodeId, t.purpose),
|
|
23582
23820
|
unique("calendar_event_links_connection_event_uniq").on(t.connectionId, t.externalEventId),
|
|
23583
|
-
index("calendar_event_links_tenant_connection_idx").on(t.tenantId, t.connectionId)
|
|
23821
|
+
index("calendar_event_links_tenant_connection_idx").on(t.tenantId, t.connectionId),
|
|
23822
|
+
index("calendar_event_links_node_idx").on(t.nodeId)
|
|
23584
23823
|
]);
|
|
23585
23824
|
var eventResponseStatus = pgEnum("event_response_status", [
|
|
23586
23825
|
"none",
|
|
@@ -24856,39 +25095,6 @@ var doccleSyncResultSchema = object({
|
|
|
24856
25095
|
failure: doccleFailureSchema.nullable()
|
|
24857
25096
|
});
|
|
24858
25097
|
|
|
24859
|
-
// ../../packages/schema/src/db/files.ts
|
|
24860
|
-
var bytea = customType({
|
|
24861
|
-
dataType() {
|
|
24862
|
-
return "bytea";
|
|
24863
|
-
}
|
|
24864
|
-
});
|
|
24865
|
-
var files = pgTable("files", {
|
|
24866
|
-
id: text("id").primaryKey(),
|
|
24867
|
-
tenantId: text("tenant_id").notNull().references(() => tenants.id),
|
|
24868
|
-
ownerNodeId: text("owner_node_id").references(() => nodes.id, { onDelete: "cascade" }),
|
|
24869
|
-
uploadedByUserId: text("uploaded_by_user_id").references(() => users.id, {
|
|
24870
|
-
onDelete: "set null"
|
|
24871
|
-
}),
|
|
24872
|
-
uploadedByAgentId: text("uploaded_by_agent_id").references(() => agents.id),
|
|
24873
|
-
kind: text("kind").notNull(),
|
|
24874
|
-
name: text("name").notNull(),
|
|
24875
|
-
mime: text("mime").notNull(),
|
|
24876
|
-
size: integer2("size").notNull(),
|
|
24877
|
-
sha256: text("sha256").notNull(),
|
|
24878
|
-
data: bytea("data").notNull(),
|
|
24879
|
-
enc: text("enc"),
|
|
24880
|
-
textExtract: text("text_extract"),
|
|
24881
|
-
pages: integer2("pages"),
|
|
24882
|
-
source: text("source").notNull().default("upload"),
|
|
24883
|
-
receivedAt: timestamp("received_at", { withTimezone: true }),
|
|
24884
|
-
durationMs: integer2("duration_ms"),
|
|
24885
|
-
transcript: text("transcript"),
|
|
24886
|
-
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
24887
|
-
}, (t) => [
|
|
24888
|
-
index("files_tenant_idx").on(t.tenantId, t.createdAt),
|
|
24889
|
-
index("files_owner_idx").on(t.ownerNodeId, t.createdAt)
|
|
24890
|
-
]);
|
|
24891
|
-
|
|
24892
25098
|
// ../../packages/schema/src/contracts/files.ts
|
|
24893
25099
|
var FILE_TEXT_MAX = 1e6;
|
|
24894
25100
|
var FILE_SOURCES = [
|
|
@@ -24998,7 +25204,8 @@ var moneyAccountSchema = createSelectSchema(moneyAccounts, {
|
|
|
24998
25204
|
});
|
|
24999
25205
|
var moneyAccountViewSchema = moneyAccountSchema.omit({ ibanHash: true }).extend({
|
|
25000
25206
|
title: string2(),
|
|
25001
|
-
visibility: _enum2(NODE_VISIBILITY_VALUES)
|
|
25207
|
+
visibility: _enum2(NODE_VISIBILITY_VALUES),
|
|
25208
|
+
balanceKnown: boolean2()
|
|
25002
25209
|
});
|
|
25003
25210
|
var moneyCategorySchema = createSelectSchema(moneyCategories, {
|
|
25004
25211
|
archivedAt: exports_coerce.date().nullable(),
|
|
@@ -25058,7 +25265,14 @@ var createMoneyAccountInputSchema = object({
|
|
|
25058
25265
|
includeInNetWorth: boolean2().optional(),
|
|
25059
25266
|
visibility: _enum2(NODE_VISIBILITY_VALUES).optional()
|
|
25060
25267
|
}).strict();
|
|
25061
|
-
var
|
|
25268
|
+
var moneyBalanceAtSchema = union([keyDateDaySchema, exports_iso.datetime({ offset: true })]);
|
|
25269
|
+
var updateMoneyAccountInputSchema = createMoneyAccountInputSchema.omit({ id: true, currency: true }).partial().extend({
|
|
25270
|
+
balance: moneyAmountSchema.optional(),
|
|
25271
|
+
balanceAt: moneyBalanceAtSchema.optional()
|
|
25272
|
+
}).strict().refine((u) => u.balance === undefined === (u.balanceAt === undefined), {
|
|
25273
|
+
message: "a balance and the day it's from go together",
|
|
25274
|
+
path: ["balanceAt"]
|
|
25275
|
+
});
|
|
25062
25276
|
var createMoneyCategoryInputSchema = object({
|
|
25063
25277
|
id: ulidSchema,
|
|
25064
25278
|
name: string2().trim().min(1).max(60),
|
|
@@ -25091,6 +25305,7 @@ var sortMoneyTransactionInputSchema = object({
|
|
|
25091
25305
|
taxTag: moneyTaxTagSchema.nullish(),
|
|
25092
25306
|
orderItem: string2().trim().min(1).max(200).nullish()
|
|
25093
25307
|
}).strict();
|
|
25308
|
+
var moneyTransactionLinkInputSchema = object({ nodeId: ulidSchema, kind: moneyLinkKindSchema.default("about") }).strict();
|
|
25094
25309
|
var createMoneyRuleInputSchema = object({
|
|
25095
25310
|
id: ulidSchema,
|
|
25096
25311
|
name: string2().trim().min(1).max(120),
|
|
@@ -25152,8 +25367,13 @@ var moneyTransactionPageSchema = object({
|
|
|
25152
25367
|
var MONEY_IMPORT_MAX_BYTES = 5 * 1024 * 1024;
|
|
25153
25368
|
var moneyImportFieldsSchema = object({
|
|
25154
25369
|
accountId: ulidSchema,
|
|
25155
|
-
since: keyDateDaySchema.optional()
|
|
25156
|
-
|
|
25370
|
+
since: keyDateDaySchema.optional(),
|
|
25371
|
+
balance: moneyAmountSchema.optional(),
|
|
25372
|
+
balanceAt: moneyBalanceAtSchema.optional()
|
|
25373
|
+
}).strict().refine((f) => f.balance === undefined === (f.balanceAt === undefined), {
|
|
25374
|
+
message: "a balance and the day it's from go together",
|
|
25375
|
+
path: ["balanceAt"]
|
|
25376
|
+
});
|
|
25157
25377
|
var moneyImportOutcomeSchema = _enum2([
|
|
25158
25378
|
"new",
|
|
25159
25379
|
"duplicate",
|
|
@@ -26857,6 +27077,8 @@ var healthShares = pgTable("health_shares", {
|
|
|
26857
27077
|
episodeId: text("episode_id"),
|
|
26858
27078
|
sections: text("sections").array().notNull().default(sql`'{}'::text[]`),
|
|
26859
27079
|
snapshot: jsonb("snapshot").notNull(),
|
|
27080
|
+
note: text("note"),
|
|
27081
|
+
attachments: jsonb("attachments").notNull().default(sql`'[]'::jsonb`),
|
|
26860
27082
|
tokenHash: text("token_hash").notNull().unique(),
|
|
26861
27083
|
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
|
|
26862
27084
|
revokedAt: timestamp("revoked_at", { withTimezone: true }),
|
|
@@ -31969,6 +32191,98 @@ var keyOperations = defineOperations({
|
|
|
31969
32191
|
}
|
|
31970
32192
|
});
|
|
31971
32193
|
|
|
32194
|
+
// ../../packages/schema/src/contracts/money-kraken.ts
|
|
32195
|
+
var KRAKEN_SECRET = /^[A-Za-z0-9+/]+={0,2}$/;
|
|
32196
|
+
var moneyKrakenKeyInputSchema = object({
|
|
32197
|
+
apiKey: string2().trim().min(16).max(200).regex(/^\S+$/, "The API key has no spaces."),
|
|
32198
|
+
apiSecret: string2().trim().min(40).max(200).regex(KRAKEN_SECRET, { message: "Paste the private key as Kraken shows it (base64)." }),
|
|
32199
|
+
accountId: ulidSchema.optional()
|
|
32200
|
+
}).strict();
|
|
32201
|
+
var moneyHoldingSchema = object({
|
|
32202
|
+
asset: string2(),
|
|
32203
|
+
amount: string2(),
|
|
32204
|
+
priceEur: string2().nullable(),
|
|
32205
|
+
valueEur: string2().nullable(),
|
|
32206
|
+
share: number2().nullable()
|
|
32207
|
+
});
|
|
32208
|
+
var moneyHoldingsSchema = object({
|
|
32209
|
+
accountId: ulidSchema,
|
|
32210
|
+
at: exports_coerce.date(),
|
|
32211
|
+
totalEur: string2(),
|
|
32212
|
+
dustEur: string2(),
|
|
32213
|
+
holdings: array(moneyHoldingSchema)
|
|
32214
|
+
});
|
|
32215
|
+
var moneyAccountHoldingsSchema = object({ holdings: moneyHoldingsSchema.nullable() });
|
|
32216
|
+
var moneyKrakenLinkStatusSchema = object({
|
|
32217
|
+
configured: boolean2(),
|
|
32218
|
+
link: object({
|
|
32219
|
+
id: ulidSchema,
|
|
32220
|
+
accountId: ulidSchema,
|
|
32221
|
+
accountTitle: string2(),
|
|
32222
|
+
status: _enum2(moneyConnectionStatus.enumValues),
|
|
32223
|
+
lastSyncAt: exports_coerce.date().nullable(),
|
|
32224
|
+
lastError: string2().nullable()
|
|
32225
|
+
}).nullable(),
|
|
32226
|
+
holdings: moneyHoldingsSchema.nullable()
|
|
32227
|
+
});
|
|
32228
|
+
|
|
32229
|
+
// ../../packages/schema/src/operations/kraken-link.ts
|
|
32230
|
+
var CONNECT2 = ["connections:write"];
|
|
32231
|
+
var AFTER2 = "`*` and `private:*` never grant `connections:write`: a key holds it by name, and a guest's key never does.";
|
|
32232
|
+
var krakenLinkOperations = defineOperations({
|
|
32233
|
+
status: {
|
|
32234
|
+
method: "get",
|
|
32235
|
+
path: "/money/kraken-link",
|
|
32236
|
+
scopes: CONNECT2,
|
|
32237
|
+
afterScopes: AFTER2,
|
|
32238
|
+
tag: "Money",
|
|
32239
|
+
summary: "Get the Kraken link",
|
|
32240
|
+
description: "Whether the key's member has a Kraken key set (`configured`), the investment account it feeds with how its last sync went (`link`: `status` `active` or `failing`, `lastSyncAt`, `lastError`), and the `holdings` that sync saw: per asset its `amount`, EUR `priceEur` and `valueEur` and `share`, with `totalEur` and `at`; holdings worth under \u20AC0.50 are left out of the list (`dustEur` sums them), never out of the total. The key is never returned.",
|
|
32241
|
+
status: 200,
|
|
32242
|
+
response: { description: "The Kraken link." },
|
|
32243
|
+
errors: { 403: true }
|
|
32244
|
+
},
|
|
32245
|
+
set: {
|
|
32246
|
+
method: "put",
|
|
32247
|
+
path: "/money/kraken-link",
|
|
32248
|
+
scopes: CONNECT2,
|
|
32249
|
+
afterScopes: AFTER2,
|
|
32250
|
+
tag: "Money",
|
|
32251
|
+
summary: "Set the Kraken key",
|
|
32252
|
+
description: "Keep the key's member's Kraken `apiKey` and `apiSecret` (the private key), sealed, then sync. Make the key at Kraken with only Query Funds ticked. The key is checked, never echoed: it must read the balance and must not see open orders (a key that can may do more than read, and is refused); the answer is the link, without it. `accountId` picks the investment account it feeds; left out, the link keeps its account, or a first set makes one named Kraken. 400 for a key Kraken refuses, a key that isn't read-only, or an account that isn't an EUR investment account.",
|
|
32253
|
+
body: moneyKrakenKeyInputSchema,
|
|
32254
|
+
status: 200,
|
|
32255
|
+
response: { description: "The Kraken link, without the key." },
|
|
32256
|
+
errors: { 400: true, 403: true, 404: true, 503: true },
|
|
32257
|
+
throttle: "strict",
|
|
32258
|
+
mcp: false
|
|
32259
|
+
},
|
|
32260
|
+
sync: {
|
|
32261
|
+
method: "post",
|
|
32262
|
+
path: "/money/kraken-link/sync",
|
|
32263
|
+
scopes: CONNECT2,
|
|
32264
|
+
afterScopes: AFTER2,
|
|
32265
|
+
tag: "Money",
|
|
32266
|
+
summary: "Sync Kraken now",
|
|
32267
|
+
description: "Read the holdings from Kraken now, value them in EUR and set the account's balance, as the daily sync does. A failure keeps the last holdings and marks the link `failing` with its reason. Answers the link. 400 before a key is set.",
|
|
32268
|
+
status: 200,
|
|
32269
|
+
response: { description: "The Kraken link after the sync." },
|
|
32270
|
+
errors: { 400: true, 403: true }
|
|
32271
|
+
},
|
|
32272
|
+
remove: {
|
|
32273
|
+
method: "delete",
|
|
32274
|
+
path: "/money/kraken-link",
|
|
32275
|
+
scopes: CONNECT2,
|
|
32276
|
+
afterScopes: AFTER2,
|
|
32277
|
+
tag: "Money",
|
|
32278
|
+
summary: "Remove the Kraken link",
|
|
32279
|
+
description: "Forget the key's member's Kraken key and stop syncing. The account stays, with its last balance and holdings.",
|
|
32280
|
+
status: 200,
|
|
32281
|
+
response: { description: "The Kraken link, not configured." },
|
|
32282
|
+
errors: { 403: true }
|
|
32283
|
+
}
|
|
32284
|
+
});
|
|
32285
|
+
|
|
31972
32286
|
// ../../packages/schema/src/db/mail.ts
|
|
31973
32287
|
var mailCampaignStatus = pgEnum("mail_campaign_status", [
|
|
31974
32288
|
"draft",
|
|
@@ -32514,6 +32828,13 @@ var moneyNetWorthAccountSchema = object({
|
|
|
32514
32828
|
source: moneySnapshotSourceSchema,
|
|
32515
32829
|
month: moneyMonthSchema
|
|
32516
32830
|
});
|
|
32831
|
+
var moneyNetWorthUnknownSchema = moneyNetWorthAccountSchema.pick({
|
|
32832
|
+
accountId: true,
|
|
32833
|
+
title: true,
|
|
32834
|
+
kind: true,
|
|
32835
|
+
visibility: true,
|
|
32836
|
+
currency: true
|
|
32837
|
+
});
|
|
32517
32838
|
var moneyNetWorthSchema = object({
|
|
32518
32839
|
scope: moneyBudgetScopeSchema,
|
|
32519
32840
|
month: moneyMonthSchema,
|
|
@@ -32521,7 +32842,9 @@ var moneyNetWorthSchema = object({
|
|
|
32521
32842
|
have: amount5,
|
|
32522
32843
|
owe: amount5,
|
|
32523
32844
|
points: array(moneyNetWorthPointSchema),
|
|
32524
|
-
accounts: array(moneyNetWorthAccountSchema)
|
|
32845
|
+
accounts: array(moneyNetWorthAccountSchema),
|
|
32846
|
+
complete: boolean2(),
|
|
32847
|
+
unknown: array(moneyNetWorthUnknownSchema)
|
|
32525
32848
|
});
|
|
32526
32849
|
var setMoneyNetWorthValueInputSchema = object({
|
|
32527
32850
|
accountId: ulidSchema,
|
|
@@ -32703,7 +33026,7 @@ var moneyOperations = defineOperations({
|
|
|
32703
33026
|
scopes: ["finance:write"],
|
|
32704
33027
|
tag: "Money",
|
|
32705
33028
|
summary: "Import a statement",
|
|
32706
|
-
description: "Book a statement's new lines as unreviewed transactions (the same upload as the preview). Re-importing a file, or an overlapping one, books nothing twice. An account without an IBAN takes the file's; the newest line's balance becomes the account's.",
|
|
33029
|
+
description: "Book a statement's new lines as unreviewed transactions (the same upload as the preview). Re-importing a file, or an overlapping one, books nothing twice. An account without an IBAN takes the file's; the newest line's balance becomes the account's. For a bank whose export has no running balance, `balance` + `balanceAt` (a day, read as its end, or a moment; together, never a day ahead) state what the account holds and anchor it there, as an account update does; a preview ignores them.",
|
|
32707
33030
|
body: moneyImportFieldsSchema,
|
|
32708
33031
|
multipart: STATEMENT,
|
|
32709
33032
|
status: 201,
|
|
@@ -32750,7 +33073,7 @@ var moneyOperations = defineOperations({
|
|
|
32750
33073
|
scopes: ["finance:write"],
|
|
32751
33074
|
tag: "Money",
|
|
32752
33075
|
summary: "Sync bank connections now",
|
|
32753
|
-
description: "Sync every live bank link the key may see, or with `accountId` the one feeding that account, the way the daily run does: booked transactions go through the CSV import's pipeline (the bank's entry id, else the CSV row of the same day
|
|
33076
|
+
description: "Sync every live bank link the key may see, or with `accountId` the one feeding that account, the way the daily run does: booked transactions go through the CSV import's pipeline (the bank's entry id, else the CSV row of the same amount booked within a day \u2014 the most alike when several fit \u2014 is matched: it adopts the bank's entry id instead of the row being booked twice), the rules and transfer matching run, and the balance is updated. Per link: outcome (ok \xB7 failed), fetched, imported, matched, duplicates and the error in plain words. 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.",
|
|
32754
33077
|
body: moneyBankSyncInputSchema,
|
|
32755
33078
|
status: 200,
|
|
32756
33079
|
response: { description: "What each sync did." },
|
|
@@ -32772,6 +33095,18 @@ var moneyOperations = defineOperations({
|
|
|
32772
33095
|
response: { description: "The sync log." },
|
|
32773
33096
|
errors: { 404: `No such account or transaction, or it is another member's "Only me" one.` }
|
|
32774
33097
|
},
|
|
33098
|
+
dedupe: {
|
|
33099
|
+
method: "post",
|
|
33100
|
+
path: "/money/dedupe",
|
|
33101
|
+
scopes: ["finance:write"],
|
|
33102
|
+
tag: "Money",
|
|
33103
|
+
summary: "Fold a bank link's twins into the statement's rows",
|
|
33104
|
+
description: "Find, on one `accountId`, the movements booked twice: a statement import's row (no bank entry id) and a bank link's row (with one), paired by the bank sync's own rule \u2014 the same amount, booked a day apart at most, the most alike when several fit (counterparty IBAN, structured reference, name, then the words), never one row twice. A preview by default: the pairs (`keepId` the statement's row, `twinId` the bank's, their days, amount, description) and their counts, writing nothing. With `commit: true` each pair is folded: the statement's row keeps its category, review status, notes, links, splits and transfer, takes the bank's entry id and the details it lacked; whatever pointed at the twin (links, splits, settlements, its transfer peer, a rule made from it, its note) moves to the kept row, and the twin is deleted. `changed` counts what moved. A pair marked `ambiguous` (another row fit just as well) is listed but never committed. Running it again changes nothing.",
|
|
33105
|
+
body: moneyDedupeInputSchema,
|
|
33106
|
+
status: 200,
|
|
33107
|
+
response: { description: "The pairs, their counts and what changed." },
|
|
33108
|
+
errors: { 400: true, 404: NO_ACCOUNT }
|
|
33109
|
+
},
|
|
32775
33110
|
getAccount: {
|
|
32776
33111
|
method: "get",
|
|
32777
33112
|
path: "/money/accounts/{id}",
|
|
@@ -32791,7 +33126,7 @@ var moneyOperations = defineOperations({
|
|
|
32791
33126
|
scopes: ["finance:write"],
|
|
32792
33127
|
tag: "Money",
|
|
32793
33128
|
summary: "Update a money account",
|
|
32794
|
-
description: "Rename it, change its kind, IBAN, opening balance, what it counts towards, or who sees it.",
|
|
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.",
|
|
32795
33130
|
pathParams: { id: "The account's ULID." },
|
|
32796
33131
|
pathParamSchemas: { id: ulidSchema },
|
|
32797
33132
|
body: updateMoneyAccountInputSchema,
|
|
@@ -32907,6 +33242,36 @@ var moneyOperations = defineOperations({
|
|
|
32907
33242
|
404: `No such account or transaction, or it is another member's "Only me" one.`
|
|
32908
33243
|
}
|
|
32909
33244
|
},
|
|
33245
|
+
linkTransaction: {
|
|
33246
|
+
method: "post",
|
|
33247
|
+
path: "/money/transactions/{id}/links",
|
|
33248
|
+
scopes: ["finance:write"],
|
|
33249
|
+
tag: "Money",
|
|
33250
|
+
summary: "Link a money transaction to something across do",
|
|
33251
|
+
description: "`nodeId` (a paper, a goal, an asset, an animal, a Project) and `kind`: `evidence` for a paper that proves it, `for_goal` to count it toward a goal, `paid_as` for what it paid, else `about` (the default). Again is a no-op. Answers the transaction with its `links`.",
|
|
33252
|
+
pathParams: { id: "The transaction's ULID." },
|
|
33253
|
+
pathParamSchemas: { id: ulidSchema },
|
|
33254
|
+
body: moneyTransactionLinkInputSchema,
|
|
33255
|
+
status: 200,
|
|
33256
|
+
response: { description: "The transaction." },
|
|
33257
|
+
errors: {
|
|
33258
|
+
400: true,
|
|
33259
|
+
404: `No such transaction or node, or it is another member's "Only me" one.`
|
|
33260
|
+
}
|
|
33261
|
+
},
|
|
33262
|
+
unlinkTransaction: {
|
|
33263
|
+
method: "delete",
|
|
33264
|
+
path: "/money/transactions/{id}/links/{nodeId}",
|
|
33265
|
+
scopes: ["finance:write"],
|
|
33266
|
+
tag: "Money",
|
|
33267
|
+
summary: "Unlink a money transaction from something across do",
|
|
33268
|
+
description: "Takes every link from the transaction to `nodeId` off; both stay. Undo is the link again (`POST \u2026/links` with the same `nodeId` and `kind`). Answers the transaction with its `links`.",
|
|
33269
|
+
pathParams: { id: "The transaction's ULID.", nodeId: "The linked node's ULID." },
|
|
33270
|
+
pathParamSchemas: { id: ulidSchema, nodeId: ulidSchema },
|
|
33271
|
+
status: 200,
|
|
33272
|
+
response: { description: "The transaction." },
|
|
33273
|
+
errors: { 404: "The transaction isn't linked to that node, or isn't yours to see." }
|
|
33274
|
+
},
|
|
32910
33275
|
listSplits: {
|
|
32911
33276
|
method: "get",
|
|
32912
33277
|
path: "/money/transactions/{id}/splits",
|
|
@@ -33281,7 +33646,7 @@ var moneyOperations = defineOperations({
|
|
|
33281
33646
|
scopes: ["finance:read"],
|
|
33282
33647
|
tag: "Money",
|
|
33283
33648
|
summary: "Get net worth and its monthly line",
|
|
33284
|
-
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
|
|
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).",
|
|
33285
33650
|
query: [
|
|
33286
33651
|
{ name: "scope", description: "all, space or owner." },
|
|
33287
33652
|
{ name: "months", description: "How many months the line covers (2\u201360, default 12)." }
|
|
@@ -38776,7 +39141,12 @@ var gameFacetSchema = object({
|
|
|
38776
39141
|
coverUrl,
|
|
38777
39142
|
coverSources: coverSourcesSchema.nullish(),
|
|
38778
39143
|
completion: number2().int().min(0).max(100).nullish(),
|
|
38779
|
-
steamAppId: number2().int().positive().max(99999999).nullish()
|
|
39144
|
+
steamAppId: number2().int().positive().max(99999999).nullish(),
|
|
39145
|
+
format: _enum2(["physical", "digital"]).nullish(),
|
|
39146
|
+
store: string2().min(1).max(60).nullish(),
|
|
39147
|
+
studio: string2().min(1).max(120).nullish(),
|
|
39148
|
+
year: number2().int().min(1950).max(2100).nullish(),
|
|
39149
|
+
playedWith: string2().min(1).max(120).nullish()
|
|
38780
39150
|
}).strict();
|
|
38781
39151
|
var musicFacetSchema = object({
|
|
38782
39152
|
artist: string2().min(1).max(200).nullish(),
|
|
@@ -39092,7 +39462,7 @@ var STATUS_MARK = {
|
|
|
39092
39462
|
};
|
|
39093
39463
|
function suggestionText(s) {
|
|
39094
39464
|
const parts = [
|
|
39095
|
-
s.area,
|
|
39465
|
+
[s.area, s.project].filter(Boolean).join(" \u203A ") || null,
|
|
39096
39466
|
s.owner ? `owner ${ownerText(s.owner)}` : null,
|
|
39097
39467
|
s.scheduledAt ? shortDate(s.scheduledAt) : null
|
|
39098
39468
|
].filter(Boolean);
|
|
@@ -42559,12 +42929,18 @@ var CONNECTION_COMMAND_SPECS = [
|
|
|
42559
42929
|
},
|
|
42560
42930
|
{
|
|
42561
42931
|
path: ["money", "bank-link", "pending"],
|
|
42562
|
-
summary: "
|
|
42563
|
-
args: [
|
|
42932
|
+
summary: "The bank authorisations waiting to be mapped, or one by its id: bank accounts and suggestions.",
|
|
42933
|
+
args: [
|
|
42934
|
+
{
|
|
42935
|
+
name: "id",
|
|
42936
|
+
required: false,
|
|
42937
|
+
description: "The pendingId from `complete`; without it, every one still waiting."
|
|
42938
|
+
}
|
|
42939
|
+
],
|
|
42564
42940
|
flags: [],
|
|
42565
42941
|
scopes: scopesOf(bankLinkOperations.pending),
|
|
42566
|
-
returns: "MoneyBankPending",
|
|
42567
|
-
notes: `${routeOf(bankLinkOperations.pending)}.`
|
|
42942
|
+
returns: "MoneyBankPending (one) or MoneyBankPending[] (all)",
|
|
42943
|
+
notes: `${routeOf(bankLinkOperations.pending)}; without an id ${routeOf(bankLinkOperations.pendings)}, newest first, each with createdAt and expiresAt (a day after). Finds an authorisation whose browser tab is gone, before it runs out.`
|
|
42568
42944
|
},
|
|
42569
42945
|
{
|
|
42570
42946
|
path: ["money", "bank-link", "link"],
|
|
@@ -42582,6 +42958,53 @@ var CONNECTION_COMMAND_SPECS = [
|
|
|
42582
42958
|
returns: "MoneyConnectionView[]",
|
|
42583
42959
|
notes: `${routeOf(bankLinkOperations.link)}. Each do account is fed by one bank account.`
|
|
42584
42960
|
},
|
|
42961
|
+
{
|
|
42962
|
+
path: ["money", "kraken-link", "status"],
|
|
42963
|
+
summary: "Your Kraken link: the account it feeds, how its last sync went, and the holdings it saw.",
|
|
42964
|
+
args: [],
|
|
42965
|
+
flags: [],
|
|
42966
|
+
scopes: scopesOf(krakenLinkOperations.status),
|
|
42967
|
+
returns: "MoneyKrakenLinkStatus",
|
|
42968
|
+
notes: `${routeOf(krakenLinkOperations.status)}. The key is never returned.`
|
|
42969
|
+
},
|
|
42970
|
+
{
|
|
42971
|
+
path: ["money", "kraken-link", "set"],
|
|
42972
|
+
summary: "Give do your read-only Kraken API key, read from a file, and sync.",
|
|
42973
|
+
args: [],
|
|
42974
|
+
flags: [
|
|
42975
|
+
{
|
|
42976
|
+
name: "key-file",
|
|
42977
|
+
type: "string",
|
|
42978
|
+
description: "Path to a file holding the API key and its private key (two lines, or JSON with apiKey and apiSecret), or - to read it from stdin."
|
|
42979
|
+
},
|
|
42980
|
+
{
|
|
42981
|
+
name: "account",
|
|
42982
|
+
type: "string",
|
|
42983
|
+
description: "The EUR investment account it feeds. Left out: the link's account, or a new one named Kraken."
|
|
42984
|
+
}
|
|
42985
|
+
],
|
|
42986
|
+
scopes: scopesOf(krakenLinkOperations.set),
|
|
42987
|
+
returns: "MoneyKrakenLinkStatus",
|
|
42988
|
+
notes: `${routeOf(krakenLinkOperations.set)}. Make the key at Kraken with only Query Funds ticked: a key that can see open orders is refused. The key and secret are read from the file and sealed; they are never a flag, never printed and never read back. A new Kraken account is "Only me", so making it needs finance:write on the key too (or pass --account). Replaces a key set before.`
|
|
42989
|
+
},
|
|
42990
|
+
{
|
|
42991
|
+
path: ["money", "kraken-link", "sync"],
|
|
42992
|
+
summary: "Read your Kraken holdings now and update the account's balance.",
|
|
42993
|
+
args: [],
|
|
42994
|
+
flags: [],
|
|
42995
|
+
scopes: scopesOf(krakenLinkOperations.sync),
|
|
42996
|
+
returns: "MoneyKrakenLinkStatus",
|
|
42997
|
+
notes: `${routeOf(krakenLinkOperations.sync)}. A failure keeps the last holdings and shows on the link as failing, with its reason.`
|
|
42998
|
+
},
|
|
42999
|
+
{
|
|
43000
|
+
path: ["money", "kraken-link", "remove"],
|
|
43001
|
+
summary: "Forget your Kraken key. The account keeps its last balance and holdings.",
|
|
43002
|
+
args: [],
|
|
43003
|
+
flags: [DESTRUCTIVE],
|
|
43004
|
+
scopes: scopesOf(krakenLinkOperations.remove),
|
|
43005
|
+
returns: "MoneyKrakenLinkStatus",
|
|
43006
|
+
notes: `${routeOf(krakenLinkOperations.remove)}. Gated behind --allow-destructive.`
|
|
43007
|
+
},
|
|
42585
43008
|
{
|
|
42586
43009
|
path: ["doccle", "connect"],
|
|
42587
43010
|
summary: "Give do your Doccle login so the nightly import can run. do checks it with Doccle first and keeps it sealed.",
|
|
@@ -42688,8 +43111,14 @@ var CONNECTION_HANDLERS = {
|
|
|
42688
43111
|
},
|
|
42689
43112
|
"money bank-link pending": async ({ args, requireClient }) => {
|
|
42690
43113
|
const id = args[0];
|
|
42691
|
-
if (!id)
|
|
42692
|
-
|
|
43114
|
+
if (!id) {
|
|
43115
|
+
return {
|
|
43116
|
+
data: await guarded([], () => requireClient().get(pathOf(bankLinkOperations.pendings))),
|
|
43117
|
+
render: (data, mode) => Array.isArray(data) ? data.length === 0 ? "No bank links waiting to be mapped." : data.map((p) => renderPending(p, mode)).join(`
|
|
43118
|
+
|
|
43119
|
+
`) : renderRecord(data, mode)
|
|
43120
|
+
};
|
|
43121
|
+
}
|
|
42693
43122
|
return {
|
|
42694
43123
|
data: await guarded([], () => requireClient().get(pathOf(bankLinkOperations.pending, { id }))),
|
|
42695
43124
|
render: renderPending
|
|
@@ -42704,6 +43133,32 @@ var CONNECTION_HANDLERS = {
|
|
|
42704
43133
|
`) || "Linked."
|
|
42705
43134
|
};
|
|
42706
43135
|
},
|
|
43136
|
+
"money kraken-link status": async ({ requireClient }) => ({
|
|
43137
|
+
data: await guarded([], () => requireClient().get(pathOf(krakenLinkOperations.status))),
|
|
43138
|
+
render: renderKraken
|
|
43139
|
+
}),
|
|
43140
|
+
"money kraken-link set": async ({ io, flags, requireClient }) => {
|
|
43141
|
+
refuseSecretFlags(flags, ["key", "api-key", "secret", "api-secret", "private-key"], "--key-file <path>");
|
|
43142
|
+
const text = await readSecret(io, flags, "key-file", "the Kraken key", "all");
|
|
43143
|
+
const { apiKey, apiSecret } = parseKrakenKey(text);
|
|
43144
|
+
const account = flagString(flags, "account")?.trim();
|
|
43145
|
+
const body = { apiKey, apiSecret, ...account ? { accountId: account } : {} };
|
|
43146
|
+
return {
|
|
43147
|
+
data: await guarded([apiKey, apiSecret], () => requireClient().put(pathOf(krakenLinkOperations.set), body)),
|
|
43148
|
+
render: renderKraken
|
|
43149
|
+
};
|
|
43150
|
+
},
|
|
43151
|
+
"money kraken-link sync": async ({ requireClient }) => ({
|
|
43152
|
+
data: await guarded([], () => requireClient().post(pathOf(krakenLinkOperations.sync), {})),
|
|
43153
|
+
render: renderKraken
|
|
43154
|
+
}),
|
|
43155
|
+
"money kraken-link remove": async ({ flags, requireClient }) => {
|
|
43156
|
+
assertDestructive(flags, "forget the Kraken key");
|
|
43157
|
+
return {
|
|
43158
|
+
data: await guarded([], () => requireClient().delete(pathOf(krakenLinkOperations.remove))),
|
|
43159
|
+
render: renderKraken
|
|
43160
|
+
};
|
|
43161
|
+
},
|
|
42707
43162
|
"doccle connect": (ctx) => connectDoccle(ctx),
|
|
42708
43163
|
"steam link": async ({ io, flags, requireClient }) => {
|
|
42709
43164
|
refuseSecretFlags(flags, ["key", "steam-key", "web-api-key"], "--key-file <path>");
|
|
@@ -42880,7 +43335,8 @@ function renderPending(data, mode) {
|
|
|
42880
43335
|
const p = data;
|
|
42881
43336
|
if (!Array.isArray(p?.accounts))
|
|
42882
43337
|
return renderRecord(data, mode);
|
|
42883
|
-
const
|
|
43338
|
+
const until = p.expiresAt ? ` \xB7 map before ${p.expiresAt}` : "";
|
|
43339
|
+
const lines = [`${p.bank ?? "Bank"} \xB7 pending ${p.pendingId}${until}`];
|
|
42884
43340
|
for (const a of p.accounts) {
|
|
42885
43341
|
const label = [a.name, a.ibanMasked].filter(Boolean).join(" ") || "account";
|
|
42886
43342
|
const hint = a.suggestedAccountId ? ` \u2192 ${a.suggestedAccountId}` : "";
|
|
@@ -42889,6 +43345,48 @@ function renderPending(data, mode) {
|
|
|
42889
43345
|
return lines.join(`
|
|
42890
43346
|
`);
|
|
42891
43347
|
}
|
|
43348
|
+
function parseKrakenKey(text) {
|
|
43349
|
+
const hint = "the key file should hold the API key and its private key: two lines, or JSON with apiKey and apiSecret";
|
|
43350
|
+
if (text.startsWith("{")) {
|
|
43351
|
+
let parsed;
|
|
43352
|
+
try {
|
|
43353
|
+
parsed = JSON.parse(text);
|
|
43354
|
+
} catch {
|
|
43355
|
+
throw usageError(hint);
|
|
43356
|
+
}
|
|
43357
|
+
const o = parsed ?? {};
|
|
43358
|
+
const apiKey = o.apiKey ?? o.key;
|
|
43359
|
+
const apiSecret = o.apiSecret ?? o.secret ?? o.privateKey;
|
|
43360
|
+
if (typeof apiKey !== "string" || typeof apiSecret !== "string")
|
|
43361
|
+
throw usageError(hint);
|
|
43362
|
+
return { apiKey: apiKey.trim(), apiSecret: apiSecret.trim() };
|
|
43363
|
+
}
|
|
43364
|
+
const lines = text.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
|
|
43365
|
+
if (lines.length !== 2)
|
|
43366
|
+
throw usageError(hint);
|
|
43367
|
+
return { apiKey: lines[0], apiSecret: lines[1] };
|
|
43368
|
+
}
|
|
43369
|
+
function renderKraken(data, mode) {
|
|
43370
|
+
const k = data;
|
|
43371
|
+
if (typeof k?.configured !== "boolean")
|
|
43372
|
+
return renderRecord(data, mode);
|
|
43373
|
+
if (!k.link)
|
|
43374
|
+
return k.configured ? "Kraken key set, not linked yet." : "No Kraken key set.";
|
|
43375
|
+
const synced = k.link.lastSyncAt ? ` \xB7 synced ${k.link.lastSyncAt}` : " \xB7 not synced yet";
|
|
43376
|
+
const lines = [`Kraken \u2192 ${k.link.accountTitle} \xB7 ${k.link.status}${synced}`];
|
|
43377
|
+
if (k.link.lastError)
|
|
43378
|
+
lines.push(` ${k.link.lastError}`);
|
|
43379
|
+
if (k.holdings) {
|
|
43380
|
+
lines.push(`Holdings at ${k.holdings.at} \xB7 \u20AC ${k.holdings.totalEur}`);
|
|
43381
|
+
for (const h of k.holdings.holdings) {
|
|
43382
|
+
const value = h.valueEur === null ? "no EUR price" : `\u20AC ${h.valueEur}`;
|
|
43383
|
+
const share = h.share === null ? "" : ` \xB7 ${(h.share * 100).toFixed(1)}%`;
|
|
43384
|
+
lines.push(` ${h.asset.padEnd(6)} ${h.amount} ${value}${share}`);
|
|
43385
|
+
}
|
|
43386
|
+
}
|
|
43387
|
+
return lines.join(`
|
|
43388
|
+
`);
|
|
43389
|
+
}
|
|
42892
43390
|
var commands10 = pairCommands(CONNECTION_COMMAND_SPECS, CONNECTION_HANDLERS);
|
|
42893
43391
|
|
|
42894
43392
|
// src/commands/contacts.ts
|
|
@@ -47688,6 +48186,14 @@ var commands30 = [
|
|
|
47688
48186
|
// src/commands/money.ts
|
|
47689
48187
|
import { readFileSync as readFileSync8 } from "fs";
|
|
47690
48188
|
import { basename as basename5 } from "path";
|
|
48189
|
+
function renderDedupe(data) {
|
|
48190
|
+
const r = data;
|
|
48191
|
+
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" : ""}`);
|
|
48192
|
+
const c = r.changed;
|
|
48193
|
+
lines.push(`${r.counts.pairs} pairs, ${r.counts.ambiguous} ambiguous`, r.committed ? `merged ${c.merged} \xB7 links ${c.links} \xB7 splits ${c.splits} \xB7 settlements ${c.settlements} \xB7 transfers ${c.transfers} \xB7 rules ${c.rules} \xB7 notes ${c.notes}` : "preview: nothing changed (--commit folds the unambiguous pairs)");
|
|
48194
|
+
return lines.join(`
|
|
48195
|
+
`);
|
|
48196
|
+
}
|
|
47691
48197
|
function partyFlag(flags) {
|
|
47692
48198
|
const userId = flagString(flags, "with-user");
|
|
47693
48199
|
const contactId = flagString(flags, "with-contact");
|
|
@@ -47748,13 +48254,34 @@ var commands31 = [
|
|
|
47748
48254
|
defineCommand({
|
|
47749
48255
|
operation: moneyOperations.updateAccount,
|
|
47750
48256
|
path: ["money", "account-update"],
|
|
47751
|
-
summary: "Edit a money account.",
|
|
48257
|
+
summary: "Edit a money account, or state its balance (for an export with no balance column).",
|
|
47752
48258
|
args: [{ name: "id", required: true, description: "Account ULID." }],
|
|
47753
|
-
flags: [
|
|
48259
|
+
flags: [
|
|
48260
|
+
{ name: "data", type: "string", description: "UpdateMoneyAccountInput JSON." },
|
|
48261
|
+
{
|
|
48262
|
+
name: "balance",
|
|
48263
|
+
type: "string",
|
|
48264
|
+
description: "What the account holds, e.g. 1234.56 (sets balance)."
|
|
48265
|
+
},
|
|
48266
|
+
{
|
|
48267
|
+
name: "balance-at",
|
|
48268
|
+
type: "string",
|
|
48269
|
+
description: "The day (YYYY-MM-DD, its end) or moment that balance is from (default today)."
|
|
48270
|
+
}
|
|
48271
|
+
],
|
|
47754
48272
|
returns: "MoneyAccountView",
|
|
47755
|
-
|
|
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, kind, iban, includeInNetWorth, \u2026).",
|
|
48274
|
+
run: async ({ args, flags, io, requireClient }) => {
|
|
47756
48275
|
const id = requireArg(args, 0, "account id");
|
|
47757
|
-
const
|
|
48276
|
+
const balance = flagString(flags, "balance");
|
|
48277
|
+
const balanceAt = flagString(flags, "balance-at");
|
|
48278
|
+
if (balanceAt !== undefined && balance === undefined) {
|
|
48279
|
+
throw usageError("--balance-at goes with --balance");
|
|
48280
|
+
}
|
|
48281
|
+
const body = parseInput(updateMoneyAccountInputSchema, {
|
|
48282
|
+
...readDataFlag(flags),
|
|
48283
|
+
...balance !== undefined ? { balance, balanceAt: balanceAt ?? isoDay(io.now()) } : {}
|
|
48284
|
+
});
|
|
47758
48285
|
return {
|
|
47759
48286
|
data: await requireClient().patch(pathOf(moneyOperations.updateAccount, { id }), body),
|
|
47760
48287
|
render: renderRecord
|
|
@@ -47807,6 +48334,32 @@ var commands31 = [
|
|
|
47807
48334
|
};
|
|
47808
48335
|
}
|
|
47809
48336
|
}),
|
|
48337
|
+
defineCommand({
|
|
48338
|
+
operation: moneyOperations.dedupe,
|
|
48339
|
+
path: ["money", "dedupe"],
|
|
48340
|
+
summary: "Find movements booked twice on an account (a statement's row and a bank link's twin); --commit folds them.",
|
|
48341
|
+
args: [],
|
|
48342
|
+
flags: [
|
|
48343
|
+
{ name: "account", type: "string", description: "The account ULID (required)." },
|
|
48344
|
+
{
|
|
48345
|
+
name: "commit",
|
|
48346
|
+
type: "boolean",
|
|
48347
|
+
description: "Fold the pairs; without it, a preview that writes nothing."
|
|
48348
|
+
}
|
|
48349
|
+
],
|
|
48350
|
+
returns: "MoneyDedupeResult",
|
|
48351
|
+
notes: ". Pairs by the bank sync's rule: same amount, booked a day apart at most, the most alike when several fit. A commit keeps the statement's row (category, review status, notes, links, splits, transfer), gives it the bank's entry id, moves what pointed at the twin onto it and deletes the twin. An `ambiguous` pair is never committed. Running it twice changes nothing the second time.",
|
|
48352
|
+
run: async ({ flags, requireClient }) => {
|
|
48353
|
+
const body = parseInput(moneyDedupeInputSchema, {
|
|
48354
|
+
accountId: requireFlag(flags, "account", "say which account: --account <id>"),
|
|
48355
|
+
...flagBool(flags, "commit") ? { commit: true } : {}
|
|
48356
|
+
});
|
|
48357
|
+
return {
|
|
48358
|
+
data: await requireClient().post(pathOf(moneyOperations.dedupe), body),
|
|
48359
|
+
render: renderDedupe
|
|
48360
|
+
};
|
|
48361
|
+
}
|
|
48362
|
+
}),
|
|
47810
48363
|
defineCommand({
|
|
47811
48364
|
operation: moneyOperations.listConnectionSyncs,
|
|
47812
48365
|
path: ["money", "connection-syncs"],
|
|
@@ -47867,9 +48420,9 @@ var commands31 = [
|
|
|
47867
48420
|
{ name: "cursor", type: "string", description: "The previous page's nextCursor." }
|
|
47868
48421
|
],
|
|
47869
48422
|
returns: "{ data: MoneyTransactionView[], nextCursor }",
|
|
47870
|
-
notes: ". Amounts are signed decimal strings. `--business` keeps rows with a business share; `--business-status` and `--tax-tag` narrow further.",
|
|
48423
|
+
notes: ". Amounts are signed decimal strings. `--business` keeps rows with a business share; `--business-status` and `--tax-tag` narrow further. A page is 100 rows at most: while `nextCursor` isn't null, pass it back as `--cursor` for the next.",
|
|
47871
48424
|
run: async ({ flags, requireClient }) => ({
|
|
47872
|
-
data: await requireClient().
|
|
48425
|
+
data: await requireClient().getPage(pathOf(moneyOperations.listTransactions), {
|
|
47873
48426
|
query: {
|
|
47874
48427
|
accountId: flagString(flags, "account"),
|
|
47875
48428
|
status: flagString(flags, "status"),
|
|
@@ -48002,6 +48555,55 @@ var commands31 = [
|
|
|
48002
48555
|
};
|
|
48003
48556
|
}
|
|
48004
48557
|
}),
|
|
48558
|
+
defineCommand({
|
|
48559
|
+
operation: moneyOperations.linkTransaction,
|
|
48560
|
+
path: ["money", "link"],
|
|
48561
|
+
summary: "Link a transaction to something across do (a paper, a goal, an asset).",
|
|
48562
|
+
args: [
|
|
48563
|
+
{ name: "id", required: true, description: "Transaction ULID." },
|
|
48564
|
+
{ name: "node", required: true, description: "The node's ULID." }
|
|
48565
|
+
],
|
|
48566
|
+
flags: [
|
|
48567
|
+
{
|
|
48568
|
+
name: "kind",
|
|
48569
|
+
type: "string",
|
|
48570
|
+
description: "evidence | for_goal | about | paid_as (default about)."
|
|
48571
|
+
}
|
|
48572
|
+
],
|
|
48573
|
+
returns: "MoneyTransactionView",
|
|
48574
|
+
notes: " Again is a no-op; `do money unlink` takes it off.",
|
|
48575
|
+
run: async ({ args, flags, requireClient }) => {
|
|
48576
|
+
const id = requireArg(args, 0, "transaction id");
|
|
48577
|
+
const body = parseInput(moneyTransactionLinkInputSchema, {
|
|
48578
|
+
nodeId: requireArg(args, 1, "node id"),
|
|
48579
|
+
kind: flagString(flags, "kind")
|
|
48580
|
+
});
|
|
48581
|
+
return {
|
|
48582
|
+
data: await requireClient().post(pathOf(moneyOperations.linkTransaction, { id }), body),
|
|
48583
|
+
render: renderRecord
|
|
48584
|
+
};
|
|
48585
|
+
}
|
|
48586
|
+
}),
|
|
48587
|
+
defineCommand({
|
|
48588
|
+
operation: moneyOperations.unlinkTransaction,
|
|
48589
|
+
path: ["money", "unlink"],
|
|
48590
|
+
summary: "Unlink a transaction from a node across do (both stay).",
|
|
48591
|
+
args: [
|
|
48592
|
+
{ name: "id", required: true, description: "Transaction ULID." },
|
|
48593
|
+
{ name: "node", required: true, description: "The linked node's ULID." }
|
|
48594
|
+
],
|
|
48595
|
+
flags: [],
|
|
48596
|
+
returns: "MoneyTransactionView",
|
|
48597
|
+
notes: " Undo is `do money link` with the same node and kind.",
|
|
48598
|
+
run: async ({ args, requireClient }) => {
|
|
48599
|
+
const id = requireArg(args, 0, "transaction id");
|
|
48600
|
+
const nodeId = requireArg(args, 1, "node id");
|
|
48601
|
+
return {
|
|
48602
|
+
data: await requireClient().delete(pathOf(moneyOperations.unlinkTransaction, { id, nodeId })),
|
|
48603
|
+
render: renderRecord
|
|
48604
|
+
};
|
|
48605
|
+
}
|
|
48606
|
+
}),
|
|
48005
48607
|
defineCommand({
|
|
48006
48608
|
operation: moneyOperations.listSplits,
|
|
48007
48609
|
path: ["money", "splits"],
|
|
@@ -48284,12 +48886,22 @@ var commands31 = [
|
|
|
48284
48886
|
flags: [
|
|
48285
48887
|
{ name: "account", type: "string", description: "The account's ULID." },
|
|
48286
48888
|
{ name: "since", type: "string", description: "First day to take (default 2025-01-01)." },
|
|
48287
|
-
{ name: "commit", type: "boolean", description: "Book the new lines (else a preview)." }
|
|
48889
|
+
{ name: "commit", type: "boolean", description: "Book the new lines (else a preview)." },
|
|
48890
|
+
{
|
|
48891
|
+
name: "balance",
|
|
48892
|
+
type: "string",
|
|
48893
|
+
description: "What the account holds, for an export with no balance column (anchors it with --commit)."
|
|
48894
|
+
},
|
|
48895
|
+
{
|
|
48896
|
+
name: "balance-at",
|
|
48897
|
+
type: "string",
|
|
48898
|
+
description: "The day (YYYY-MM-DD, its end) or moment --balance is from (default today)."
|
|
48899
|
+
}
|
|
48288
48900
|
],
|
|
48289
48901
|
scopes: [...moneyOperations.previewImport.scopes, ...moneyOperations.commitImport.scopes],
|
|
48290
48902
|
returns: "MoneyImportPreview | MoneyImportResult",
|
|
48291
|
-
notes: `${routeOf(moneyOperations.previewImport)}, or ${routeOf(moneyOperations.commitImport)} with --commit (\`money_import_file\`). A KBC Touch export reads as is (\`;\`, comma decimals, UTF-8 or Windows-1252), and so does a CODA file. Each line is new, duplicate, before_window, other_account or other_currency; a line at exactly 0,00 (nothing moved, e.g. ING's empty SEPA lines) is left out and listed under \`skipped\`, counted as \`counts.nothing_moved\`, never a problem; re-importing books nothing twice. On a credit_card account the file is the card's statement: its CSV export (KBC's credit card CSV reads as is; a transaction cost is its own row) or the statement PDF's text (\`cardStatement\` says which, the total and the debit day); paying it off is a transfer, never spending twice.`,
|
|
48292
|
-
run: async ({ args, flags, requireClient }) => {
|
|
48903
|
+
notes: `${routeOf(moneyOperations.previewImport)}, or ${routeOf(moneyOperations.commitImport)} with --commit (\`money_import_file\`). A KBC Touch export reads as is (\`;\`, comma decimals, UTF-8 or Windows-1252), and so does a CODA file. Each line is new, duplicate, before_window, other_account or other_currency; a line at exactly 0,00 (nothing moved, e.g. ING's empty SEPA lines) is left out and listed under \`skipped\`, counted as \`counts.nothing_moved\`, never a problem; re-importing books nothing twice. On a credit_card account the file is the card's statement: its CSV export (KBC's credit card CSV reads as is; a transaction cost is its own row) or the statement PDF's text (\`cardStatement\` says which, the total and the debit day); paying it off is a transfer, never spending twice. A bank whose export has no running balance (ING's) leaves the account's balance unknown: \`--balance 1234.56 [--balance-at YYYY-MM-DD]\` with --commit states it, as \`do money account-update --balance\` does (a preview ignores it).`,
|
|
48904
|
+
run: async ({ args, flags, io, requireClient }) => {
|
|
48293
48905
|
const path = requireArg(args, 0, "path to the statement CSV");
|
|
48294
48906
|
const account = requireFlag(flags, "account", "money import needs --account <account id>");
|
|
48295
48907
|
let bytes;
|
|
@@ -48303,6 +48915,19 @@ var commands31 = [
|
|
|
48303
48915
|
const since = flagString(flags, "since");
|
|
48304
48916
|
if (since !== undefined)
|
|
48305
48917
|
form.append("since", since);
|
|
48918
|
+
const balance = flagString(flags, "balance");
|
|
48919
|
+
const balanceAt = flagString(flags, "balance-at");
|
|
48920
|
+
if (balanceAt !== undefined && balance === undefined) {
|
|
48921
|
+
throw usageError("--balance-at goes with --balance");
|
|
48922
|
+
}
|
|
48923
|
+
if (balance !== undefined) {
|
|
48924
|
+
const anchor = parseInput(updateMoneyAccountInputSchema, {
|
|
48925
|
+
balance,
|
|
48926
|
+
balanceAt: balanceAt ?? isoDay(io.now())
|
|
48927
|
+
});
|
|
48928
|
+
form.append("balance", anchor.balance);
|
|
48929
|
+
form.append("balanceAt", anchor.balanceAt);
|
|
48930
|
+
}
|
|
48306
48931
|
const name = basename5(path);
|
|
48307
48932
|
form.append("file", new File([Uint8Array.from(bytes)], name, { type: "text/csv" }), name);
|
|
48308
48933
|
const op = flagBool(flags, "commit") ? moneyOperations.commitImport : moneyOperations.previewImport;
|
|
@@ -48451,7 +49076,7 @@ var commands31 = [
|
|
|
48451
49076
|
}
|
|
48452
49077
|
],
|
|
48453
49078
|
returns: "MoneyNetWorth",
|
|
48454
|
-
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.",
|
|
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.",
|
|
48455
49080
|
run: async ({ flags, requireClient }) => ({
|
|
48456
49081
|
data: await requireClient().get(pathOf(moneyOperations.netWorthRead), {
|
|
48457
49082
|
query: { scope: flagString(flags, "scope"), months: flagString(flags, "months") }
|
package/package.json
CHANGED