@robbeverhelst/do 1.314.0 → 1.314.1

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.
Files changed (2) hide show
  1. package/dist/do.js +1216 -251
  2. package/package.json +1 -1
package/dist/do.js CHANGED
@@ -27859,6 +27859,64 @@ var moneyBankSyncResultSchema = object({
27859
27859
  }).extend({ accountTitle: string2() }))
27860
27860
  });
27861
27861
 
27862
+ // ../../packages/schema/src/contracts/money-year.ts
27863
+ var amount6 = string2();
27864
+ var moneyYearQuerySchema = object({
27865
+ year: exports_coerce.number().int().min(2000).max(2100).optional(),
27866
+ scope: moneyBudgetScopeSchema.optional()
27867
+ });
27868
+ var moneyYearMonthSchema = object({
27869
+ month: moneyMonthSchema,
27870
+ income: amount6,
27871
+ spent: amount6
27872
+ });
27873
+ var moneyYearCategorySchema = object({
27874
+ categoryId: ulidSchema.nullable(),
27875
+ name: string2(),
27876
+ group: _enum2(["fixed", "flexible", "income"]).nullable(),
27877
+ amount: amount6,
27878
+ lastYear: amount6
27879
+ });
27880
+ var MONEY_YEAR_STREAM_CHANGES = ["new", "stopped", "up", "down", "same"];
27881
+ var moneyYearStreamChangeSchema = _enum2(MONEY_YEAR_STREAM_CHANGES);
27882
+ var moneyYearStreamSchema = object({
27883
+ id: ulidSchema,
27884
+ title: string2(),
27885
+ cadence: moneyCadenceSchema,
27886
+ visibility: _enum2(NODE_VISIBILITY_VALUES),
27887
+ thisYear: amount6,
27888
+ lastYear: amount6,
27889
+ change: moneyYearStreamChangeSchema
27890
+ });
27891
+ var moneyYearGoalSchema = object({
27892
+ id: ulidSchema,
27893
+ title: string2(),
27894
+ visibility: _enum2(NODE_VISIBILITY_VALUES),
27895
+ status: moneyGoalStatusSchema,
27896
+ projectTitle: string2().nullable(),
27897
+ thisYear: amount6,
27898
+ saved: amount6,
27899
+ target: amount6
27900
+ });
27901
+ var moneyYearTotalsSchema = object({
27902
+ income: amount6,
27903
+ spent: amount6,
27904
+ saved: amount6
27905
+ });
27906
+ var moneyYearSchema = object({
27907
+ year: number2().int(),
27908
+ scope: moneyBudgetScopeSchema,
27909
+ through: moneyMonthSchema,
27910
+ firstYear: number2().int().nullable(),
27911
+ totals: moneyYearTotalsSchema,
27912
+ lastYear: moneyYearTotalsSchema.nullable(),
27913
+ months: array(moneyYearMonthSchema),
27914
+ spending: array(moneyYearCategorySchema),
27915
+ income: array(moneyYearCategorySchema),
27916
+ recurring: array(moneyYearStreamSchema),
27917
+ goals: array(moneyYearGoalSchema)
27918
+ });
27919
+
27862
27920
  // ../../packages/schema/src/contracts/family-tree.ts
27863
27921
  var familyTreeRefSchema = string2().regex(/^[A-Za-z0-9_-]{1,40}$/, "expected a tree reference");
27864
27922
  var familyTreeSideSchema = _enum2(["father", "mother"]);
@@ -31541,6 +31599,66 @@ var dayOperations = defineOperations({
31541
31599
  }
31542
31600
  });
31543
31601
 
31602
+ // ../../packages/schema/src/operations/files.ts
31603
+ var fileOperations = defineOperations({
31604
+ list: {
31605
+ method: "get",
31606
+ path: "/files",
31607
+ scopes: ["files:read"],
31608
+ tag: "Files",
31609
+ summary: "List a node's files",
31610
+ description: "A node's files, newest first (`?nodeId=`): name, type, size, sha256, source, pages, who uploaded it, whether it's sealed and whether it has text. Never the bytes.",
31611
+ query: [{ name: "nodeId", description: "The owner node's ULID." }],
31612
+ querySchema: object({ nodeId: ulidSchema }),
31613
+ status: 200,
31614
+ response: { description: "The node's files.", list: true },
31615
+ errors: {
31616
+ 400: true,
31617
+ 404: "No such file, or its owner node isn't visible to the key's member."
31618
+ }
31619
+ },
31620
+ get: {
31621
+ method: "get",
31622
+ path: "/files/{id}",
31623
+ scopes: ["files:read"],
31624
+ tag: "Files",
31625
+ summary: "Get a file",
31626
+ description: "One file with its `textExtract`.",
31627
+ pathParams: { id: "The file's ULID." },
31628
+ status: 200,
31629
+ response: { description: "The file." },
31630
+ errors: { 404: "No such file, or its owner node isn't visible to the key's member." }
31631
+ },
31632
+ viewUrl: {
31633
+ method: "post",
31634
+ path: "/files/{id}/view-url",
31635
+ scopes: ["files:read"],
31636
+ tag: "Files",
31637
+ summary: "Get a file's view link",
31638
+ description: "A short-lived signed link (`path`, on this API's origin, and `expiresAt`, ten minutes on) to the file's inline view, for a browser that holds no key: an `<iframe>` or an `<img>`. Minted behind the download's check; opening it checks again as this key, so a revoked key or a dropped `files:read` ends it.",
31639
+ pathParams: { id: "The file's ULID." },
31640
+ status: 200,
31641
+ response: { description: "The link." },
31642
+ errors: { 404: "No such file, or its owner node isn't visible to the key's member." },
31643
+ operationId: "get_file_view_link"
31644
+ },
31645
+ remove: {
31646
+ method: "delete",
31647
+ path: "/files/{id}",
31648
+ scopes: ["files:write"],
31649
+ tag: "Files",
31650
+ summary: "Delete a file",
31651
+ description: "Delete a file from a node the key's member may edit.",
31652
+ pathParams: { id: "The file's ULID." },
31653
+ status: 204,
31654
+ response: { description: "Deleted." },
31655
+ errors: {
31656
+ 403: "The key's member can see the node but may not change it, or the key has no member.",
31657
+ 404: "No such file, or its owner node isn't visible to the key's member."
31658
+ }
31659
+ }
31660
+ });
31661
+
31544
31662
  // ../../packages/schema/src/operations/food-log.ts
31545
31663
  var NO_MEMBER = "The key holds no health scope by name, or has no member behind it.";
31546
31664
  var HIDDEN = "No such meal, or it isn't the key's member's.";
@@ -33091,98 +33209,885 @@ var memberOperations = defineOperations({
33091
33209
  summary: "List members",
33092
33210
  description: "The Space roster.",
33093
33211
  status: 200,
33094
- response: { description: "The members." }
33212
+ response: { description: "The members." }
33213
+ },
33214
+ setRole: {
33215
+ method: "patch",
33216
+ path: "/members/{id}/role",
33217
+ scopes: ["members:write"],
33218
+ tag: "Collaboration",
33219
+ summary: "Set a member's role",
33220
+ description: "Change a member's role; fires `member.role.updated`.",
33221
+ body: setMemberRoleInputSchema,
33222
+ status: 200,
33223
+ response: { description: "The updated member." },
33224
+ errors: { 400: true, 404: true },
33225
+ webhook: "member.role.updated"
33226
+ },
33227
+ remove: {
33228
+ method: "delete",
33229
+ path: "/members/{id}",
33230
+ scopes: ["members:write"],
33231
+ tag: "Collaboration",
33232
+ summary: "Remove a member",
33233
+ description: "Remove a member from the Space; fires `member.removed`.",
33234
+ status: 204,
33235
+ response: { description: "Removed." },
33236
+ errors: { 404: true },
33237
+ webhook: "member.removed"
33238
+ }
33239
+ });
33240
+
33241
+ // ../../packages/schema/src/operations/metrics.ts
33242
+ var NO_MEMBER2 = "The key acts for no member.";
33243
+ var metricOperations = defineOperations({
33244
+ list: {
33245
+ method: "get",
33246
+ path: "/metrics",
33247
+ scopes: ["habits:read"],
33248
+ tag: "Habits",
33249
+ summary: "List metrics",
33250
+ description: "Weight (kg), sleep (h), mood (1\u20135), the day rating, energy, bedtime, resting heart rate (bpm) and HRV (ms) as trend lines: one point per day in the member's timezone (that day's last value, hand-logged or from Google Fit or Google Health), oldest first, plus the latest. No target, no streak. The key must act for a member. Resting heart rate and HRV are health data: without `health:read` held by name their lines come back empty.",
33251
+ query: [
33252
+ { name: "days", description: "How far back, 1\u2013730 days (default 90).", type: "integer" }
33253
+ ],
33254
+ querySchema: metricsQuerySchema,
33255
+ status: 200,
33256
+ response: { description: "Every Metric with its points." },
33257
+ errors: { 403: NO_MEMBER2 }
33258
+ },
33259
+ sessions: {
33260
+ method: "get",
33261
+ path: "/metrics/sessions",
33262
+ scopes: ["habits:read"],
33263
+ tag: "Habits",
33264
+ summary: "List workout sessions",
33265
+ description: "The member's workouts from their connections (Google Fit workouts, Strava activities), newest first: when, the sport in words, its name and minutes. Read only; they come in through sync.",
33266
+ query: [
33267
+ { name: "days", description: "How far back, 1\u2013365 days (default 14).", type: "integer" }
33268
+ ],
33269
+ querySchema: sessionsQuerySchema,
33270
+ status: 200,
33271
+ response: { description: "The sessions." },
33272
+ errors: { 403: NO_MEMBER2 }
33273
+ },
33274
+ import: {
33275
+ method: "post",
33276
+ path: "/metrics/import",
33277
+ scopes: ["habits:write"],
33278
+ tag: "Habits",
33279
+ summary: "Import metrics from Health Connect",
33280
+ description: "A batch the Android app read off the phone's Health Connect: `weight` (kg), `sleep` (h) and `bedtime` (minutes after 18:00), at most 500, each with its Health Connect `recordId`. Each lands once on the member's line with source `health_connect`; a record already imported, out of its Metric's range or in the future is skipped, not refused. Returns `{ imported, skipped }`.",
33281
+ body: importMetricsInputSchema,
33282
+ status: 201,
33283
+ response: { description: "How many points landed and how many were skipped." },
33284
+ errors: { 400: true, 403: NO_MEMBER2 }
33285
+ },
33286
+ log: {
33287
+ method: "post",
33288
+ path: "/metrics/{key}",
33289
+ pathParams: {
33290
+ key: {
33291
+ description: "`weight` (kg), `sleep` (h), `mood` (1\u20135), `rating` (the day, 1\u201310), `energy` (1\u201310), `bedtime` (minutes after 18:00: 01:00 = 420), `resting_hr` (bpm) or `hrv` (ms). The last two are health data: logging one needs `health:write` too.",
33292
+ enum: metricKeySchema.options
33293
+ }
33294
+ },
33295
+ pathParamSchemas: { key: metricKeySchema },
33296
+ scopes: ["habits:write"],
33297
+ tag: "Habits",
33298
+ summary: "Log a metric",
33299
+ description: 'Log one value ("weighed 72.4"; "slept 7.5 hours"; "mood 4"; "today was a 7" \u2192 `rating`; "bed 01:00" \u2192 `bedtime` 420); `occurredAt` back-dates it, `source` says `manual` (default) or `device`. A bedtime lands on the night\'s wake day. Logging again the same day replaces that day\'s point. 400 outside the Metric\'s range. Returns the Metric\'s refreshed line.',
33300
+ body: logMetricInputSchema,
33301
+ status: 201,
33302
+ response: { description: "The Metric." },
33303
+ errors: { 400: true, 403: NO_MEMBER2 }
33304
+ }
33305
+ });
33306
+
33307
+ // ../../packages/schema/src/operations/money.ts
33308
+ var moneyOperations = defineOperations({
33309
+ listAccounts: {
33310
+ method: "get",
33311
+ path: "/money/accounts",
33312
+ scopes: ["finance:read"],
33313
+ tag: "Money",
33314
+ summary: "List money accounts",
33315
+ description: "The accounts the key may see: the household's and, for a member's key, that member's Only me ones. Each with its kind, masked IBAN, currency, opening and last reported balance. 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.",
33316
+ status: 200,
33317
+ response: { description: "The accounts." }
33318
+ },
33319
+ createAccount: {
33320
+ method: "post",
33321
+ path: "/money/accounts",
33322
+ scopes: ["finance:write"],
33323
+ tag: "Money",
33324
+ summary: "Open a money account",
33325
+ 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`, `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.',
33326
+ body: createMoneyAccountInputSchema,
33327
+ status: 201,
33328
+ response: { description: "The account." },
33329
+ errors: { 400: true }
33330
+ },
33331
+ listConnections: {
33332
+ method: "get",
33333
+ path: "/money/connections",
33334
+ scopes: ["finance:read"],
33335
+ tag: "Money",
33336
+ summary: "List bank connections",
33337
+ description: "The bank links (Enable Banking) feeding the accounts the key may see: the bank, the account it feeds, its status (active \xB7 failing \xB7 expired \xB7 revoked), when the bank's consent ends, the last sync and its error, and `reconnectSoon` once the consent ends within 14 days. No credential or provider account id is ever returned. 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.",
33338
+ status: 200,
33339
+ response: { description: "The connections." }
33340
+ },
33341
+ syncConnections: {
33342
+ method: "post",
33343
+ path: "/money/connections/sync",
33344
+ scopes: ["finance:write"],
33345
+ tag: "Money",
33346
+ summary: "Sync bank connections now",
33347
+ 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, amount and counterparty or reference, is matched instead of 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.",
33348
+ body: moneyBankSyncInputSchema,
33349
+ status: 200,
33350
+ response: { description: "What each sync did." },
33351
+ errors: {
33352
+ 400: true,
33353
+ 404: `No such account or transaction, or it is another member's "Only me" one.`
33354
+ }
33355
+ },
33356
+ listConnectionSyncs: {
33357
+ method: "get",
33358
+ path: "/money/connections/{id}/syncs",
33359
+ scopes: ["finance:read"],
33360
+ tag: "Money",
33361
+ summary: "List a bank connection's sync log",
33362
+ description: "The link's last 30 sync runs, newest first: when, outcome, fetched, imported, matched, duplicates and the error.",
33363
+ pathParams: { id: "The bank link's ULID." },
33364
+ pathParamSchemas: { id: ulidSchema },
33365
+ status: 200,
33366
+ response: { description: "The sync log." },
33367
+ errors: { 404: `No such account or transaction, or it is another member's "Only me" one.` }
33368
+ },
33369
+ getAccount: {
33370
+ method: "get",
33371
+ path: "/money/accounts/{id}",
33372
+ scopes: ["finance:read"],
33373
+ tag: "Money",
33374
+ summary: "Get a money account",
33375
+ description: "One account.",
33376
+ pathParams: { id: "The account's ULID." },
33377
+ pathParamSchemas: { id: ulidSchema },
33378
+ status: 200,
33379
+ response: { description: "The account." },
33380
+ errors: { 404: `No such account or transaction, or it is another member's "Only me" one.` }
33381
+ },
33382
+ updateAccount: {
33383
+ method: "patch",
33384
+ path: "/money/accounts/{id}",
33385
+ scopes: ["finance:write"],
33386
+ tag: "Money",
33387
+ summary: "Update a money account",
33388
+ description: "Rename it, change its kind, IBAN, opening balance, what it counts towards, or who sees it.",
33389
+ pathParams: { id: "The account's ULID." },
33390
+ pathParamSchemas: { id: ulidSchema },
33391
+ body: updateMoneyAccountInputSchema,
33392
+ status: 200,
33393
+ response: { description: "The account." },
33394
+ errors: {
33395
+ 400: true,
33396
+ 404: `No such account or transaction, or it is another member's "Only me" one.`
33397
+ }
33398
+ },
33399
+ removeAccount: {
33400
+ method: "delete",
33401
+ path: "/money/accounts/{id}",
33402
+ scopes: ["finance:write"],
33403
+ tag: "Money",
33404
+ summary: "Close a money account",
33405
+ description: "The account goes to Trash; its transactions stay with it for a restore.",
33406
+ pathParams: { id: "The account's ULID." },
33407
+ pathParamSchemas: { id: ulidSchema },
33408
+ status: 204,
33409
+ response: { description: "Closed." },
33410
+ errors: { 404: `No such account or transaction, or it is another member's "Only me" one.` }
33411
+ },
33412
+ listCategories: {
33413
+ method: "get",
33414
+ path: "/money/categories",
33415
+ scopes: ["finance:read"],
33416
+ tag: "Money",
33417
+ summary: "List money categories",
33418
+ description: "The household's categories (two levels; group fixed \xB7 flexible \xB7 saving \xB7 income \xB7 transfer). A Space's first read makes the starting list.",
33419
+ status: 200,
33420
+ response: { description: "The categories." }
33421
+ },
33422
+ createCategory: {
33423
+ method: "post",
33424
+ path: "/money/categories",
33425
+ scopes: ["finance:write"],
33426
+ tag: "Money",
33427
+ summary: "Add a money category",
33428
+ description: "`id`, `name`, optional `parentId` (one level up at most), `group`, `icon`, `sortOrder`.",
33429
+ body: createMoneyCategoryInputSchema,
33430
+ status: 201,
33431
+ response: { description: "The category." },
33432
+ errors: { 400: true, 409: "That name is already there." }
33433
+ },
33434
+ listTransactions: {
33435
+ method: "get",
33436
+ path: "/money/transactions",
33437
+ scopes: ["finance:read"],
33438
+ tag: "Money",
33439
+ summary: "List money transactions",
33440
+ description: "The ledger, newest first, paged by `cursor`: each with its signed `amount` (a decimal string), day, payee, the bank's text, category, status (unreviewed \xB7 reviewed), kind, `businessShare` (0\u2013100), `links` (what it points at across do, by name) and `recurring` (the stream its payee belongs to). Filters: `accountId`, `status`, `categoryId`, `from`/`to` (days), `q` (payee or text), `business=true` (a business share above 0), `businessStatus`, `taxTag`.",
33441
+ query: [
33442
+ { name: "accountId", description: "Only this account." },
33443
+ { name: "status", description: "unreviewed or reviewed." },
33444
+ { name: "categoryId", description: "Only this category." },
33445
+ { name: "from", description: "First day, YYYY-MM-DD." },
33446
+ { name: "to", description: "Last day, YYYY-MM-DD." },
33447
+ { name: "q", description: "Payee or bank text contains." },
33448
+ { name: "business", description: "`true`: only rows with a business share." },
33449
+ { name: "businessStatus", description: "none, asked, draft_pushed, posted or declined." },
33450
+ { name: "taxTag", description: "Only this tax tag." },
33451
+ { name: "limit", description: "Page size (max 100).", type: "integer" },
33452
+ { name: "cursor", description: "The previous page's nextCursor." }
33453
+ ],
33454
+ querySchema: moneyTransactionsQuerySchema,
33455
+ status: 200,
33456
+ response: { description: "A page: `{ data, nextCursor }`." },
33457
+ errors: { 400: true }
33458
+ },
33459
+ createTransaction: {
33460
+ method: "post",
33461
+ path: "/money/transactions",
33462
+ scopes: ["finance:write"],
33463
+ tag: "Money",
33464
+ summary: "Book a money transaction",
33465
+ description: "Book one by hand (cash, or a payment the bank hasn't shown yet): `id`, `accountId`, `bookedOn`, `amount` (negative is money out), optional `counterpartyName`, `note`, `categoryId`, `contactId`, `kind`.",
33466
+ body: createMoneyTransactionInputSchema,
33467
+ status: 201,
33468
+ response: { description: "The transaction." },
33469
+ errors: {
33470
+ 400: true,
33471
+ 404: `No such account or transaction, or it is another member's "Only me" one.`
33472
+ }
33473
+ },
33474
+ getTransaction: {
33475
+ method: "get",
33476
+ path: "/money/transactions/{id}",
33477
+ scopes: ["finance:read"],
33478
+ tag: "Money",
33479
+ summary: "Get a money transaction",
33480
+ description: "One transaction with its account's and category's names.",
33481
+ pathParams: { id: "The transaction's ULID." },
33482
+ pathParamSchemas: { id: ulidSchema },
33483
+ status: 200,
33484
+ response: { description: "The transaction." },
33485
+ errors: { 404: `No such account or transaction, or it is another member's "Only me" one.` }
33486
+ },
33487
+ sortTransaction: {
33488
+ method: "patch",
33489
+ path: "/money/transactions/{id}",
33490
+ scopes: ["finance:write"],
33491
+ tag: "Money",
33492
+ summary: "Sort a money transaction",
33493
+ description: "Sort one: `categoryId`, `contactId`, `kind`, `note`, `status`, `businessShare` (0\u2013100), `businessNote`, `businessStatus` (none \xB7 asked \xB7 declined), `taxTag` (`null` clears). The ledger facts (day, amount, account, the bank's text) never change. Answers with the same receipt as `/sort`.",
33494
+ pathParams: { id: "The transaction's ULID." },
33495
+ pathParamSchemas: { id: ulidSchema },
33496
+ body: sortMoneyTransactionInputSchema,
33497
+ status: 200,
33498
+ response: { description: "The receipt." },
33499
+ errors: {
33500
+ 400: true,
33501
+ 404: `No such account or transaction, or it is another member's "Only me" one.`
33502
+ }
33503
+ },
33504
+ listSplits: {
33505
+ method: "get",
33506
+ path: "/money/transactions/{id}/splits",
33507
+ scopes: ["finance:read"],
33508
+ tag: "Money",
33509
+ summary: "List a money transaction's splits",
33510
+ description: "Its parts (empty when it isn't split), each with its signed `amount`, category, and who owes it (`owedBy`: `{ userId }` or `{ contactId }`, with `owedAmount`).",
33511
+ pathParams: { id: "The transaction's ULID." },
33512
+ pathParamSchemas: { id: ulidSchema },
33513
+ status: 200,
33514
+ response: { description: "The parts." },
33515
+ errors: { 404: `No such account or transaction, or it is another member's "Only me" one.` }
33516
+ },
33517
+ setSplits: {
33518
+ method: "put",
33519
+ path: "/money/transactions/{id}/splits",
33520
+ scopes: ["finance:write"],
33521
+ tag: "Money",
33522
+ summary: "Split a money transaction",
33523
+ description: "Replace its parts: `splits: [{ amount, categoryId?, owedBy?, owedAmount?, note? }]` (up to 20), adding up to the transaction, each with its sign; `[]` unsplits it. A part someone owes is owed to the member splitting it; that member (another member, never yourself) then sees the part and its note on their settle-up, never the transaction. Answers a receipt whose `undo` is the old parts.",
33524
+ pathParams: { id: "The transaction's ULID." },
33525
+ pathParamSchemas: { id: ulidSchema },
33526
+ body: setMoneySplitsInputSchema,
33527
+ status: 200,
33528
+ response: { description: "The receipt." },
33529
+ errors: {
33530
+ 400: true,
33531
+ 404: `No such account or transaction, or it is another member's "Only me" one.`
33532
+ }
33533
+ },
33534
+ share: {
33535
+ method: "post",
33536
+ path: "/money/transactions/{id}/share",
33537
+ scopes: ["finance:write"],
33538
+ tag: "Money",
33539
+ summary: "Share a money transaction",
33540
+ description: "`money_split`: `with` (`{ userId }` or `{ contactId }`) owes `percent` of it (50 by default) or exactly `owedAmount`; optional `note`. An unsplit transaction becomes one shared part; one already in several parts answers 409 (split it by hand).",
33541
+ pathParams: { id: "The transaction's ULID." },
33542
+ pathParamSchemas: { id: ulidSchema },
33543
+ body: shareMoneyTransactionInputSchema,
33544
+ status: 200,
33545
+ response: { description: "The receipt." },
33546
+ errors: {
33547
+ 400: true,
33548
+ 404: `No such account or transaction, or it is another member's "Only me" one.`,
33549
+ 409: "It's split in several parts."
33550
+ },
33551
+ operationId: "money_split"
33552
+ },
33553
+ settleUp: {
33554
+ method: "get",
33555
+ path: "/money/settle-up",
33556
+ scopes: ["finance:read"],
33557
+ tag: "Money",
33558
+ summary: "Get the money settle-up",
33559
+ description: "Per person (a member's `userId` or a Contact with its `name`): the `balance` (positive: they owe you; negative: you owe them) and the lines behind it, newest first (parts owed and settlements, each signed the same way; `transactionId` only when you may open it). Needs a member behind the key.",
33560
+ status: 200,
33561
+ response: { description: "`{ balances }`." },
33562
+ errors: { 403: "No member behind the key, or no finance scope." },
33563
+ operationId: "money_settle_up"
33564
+ },
33565
+ settle: {
33566
+ method: "post",
33567
+ path: "/money/settlements",
33568
+ scopes: ["finance:write"],
33569
+ tag: "Money",
33570
+ summary: "Record a money settlement",
33571
+ description: "Money paid back: `id`, `with`, `direction` (`they_paid` you or `you_paid` them), `amount`, optional `settledOn` (default today), `transactionId` (the transfer) and `note`. Only the members on its sides see it.",
33572
+ body: createMoneySettlementInputSchema,
33573
+ status: 201,
33574
+ response: { description: "The settlement." },
33575
+ errors: {
33576
+ 400: true,
33577
+ 404: `No such account or transaction, or it is another member's "Only me" one.`
33578
+ }
33579
+ },
33580
+ removeSettlement: {
33581
+ method: "delete",
33582
+ path: "/money/settlements/{id}",
33583
+ scopes: ["finance:write"],
33584
+ tag: "Money",
33585
+ summary: "Remove a money settlement",
33586
+ description: "Take it back (either side may).",
33587
+ pathParams: { id: "The settlement's ULID." },
33588
+ pathParamSchemas: { id: ulidSchema },
33589
+ status: 204,
33590
+ response: { description: "Removed." },
33591
+ errors: { 404: `No such account or transaction, or it is another member's "Only me" one.` }
33592
+ },
33593
+ taxEvidence: {
33594
+ method: "get",
33595
+ path: "/money/tax-evidence",
33596
+ scopes: ["finance:read"],
33597
+ tag: "Money",
33598
+ summary: "Get the money tax evidence",
33599
+ description: "A year's tax-tagged payments (`year`, optionally one `tag`: pension_saving \xB7 long_term_saving \xB7 gift_281_71 \xB7 childcare \xB7 service_cheques \xB7 home_energy_investment \xB7 business_cost), grouped by tag in that order, oldest first, with each group's signed `total` and each payment's `evidence`: the nodes linked as evidence and the files on them (an empty list when none is linked yet).",
33600
+ query: [
33601
+ { name: "year", description: "The year, YYYY.", type: "integer", required: true },
33602
+ { name: "tag", description: "Only this tax tag." }
33603
+ ],
33604
+ querySchema: moneyTaxEvidenceQuerySchema,
33605
+ status: 200,
33606
+ response: { description: "`{ year, groups }`." },
33607
+ errors: { 400: true },
33608
+ operationId: "money_tax_evidence"
33609
+ },
33610
+ pushOdooDraft: {
33611
+ method: "post",
33612
+ path: "/money/transactions/{id}/odoo-draft",
33613
+ scopes: ["finance:write"],
33614
+ tag: "Money",
33615
+ summary: "Push a money transaction to Odoo",
33616
+ description: "`money_push_odoo`: the business share goes to Odoo as a **draft** vendor bill (never posted from do), for the share only (30% of \u20AC120 is \u20AC36), dated the day it was paid, with the calculation note (`note` overrides the transaction's `businessNote`) and the files on its `evidence` links attached. Idempotent on the bill's ref `do:tx:<id>`: a second push answers the same draft with `alreadyThere`. The receipt has the draft's `odooRef`, `openUrl` and a calm `line`; undo is `DELETE` on the same path. 400 without a business share or for money in; 403 while the deployment's push is off (`MONEY_ODOO_PUSH` and its Space list); 409 once the bill was booked in Odoo; 502 when Odoo doesn't answer.",
33617
+ pathParams: { id: "The transaction's ULID." },
33618
+ pathParamSchemas: { id: ulidSchema },
33619
+ body: moneyOdooPushInputSchema,
33620
+ bodyOptional: true,
33621
+ status: 200,
33622
+ response: { description: "The receipt." },
33623
+ errors: {
33624
+ 400: true,
33625
+ 403: "The Odoo push is off for this Space.",
33626
+ 404: `No such account or transaction, or it is another member's "Only me" one.`,
33627
+ 409: "Its bill is booked in Odoo already.",
33628
+ 502: "Odoo didn't answer."
33629
+ },
33630
+ operationId: "money_push_odoo"
33631
+ },
33632
+ undoOdooDraft: {
33633
+ method: "delete",
33634
+ path: "/money/transactions/{id}/odoo-draft",
33635
+ scopes: ["finance:write"],
33636
+ tag: "Money",
33637
+ summary: "Take a money transaction's draft back from Odoo",
33638
+ description: "Undo a push: deletes the draft bill in Odoo while it is still a draft, and the transaction is ready again (`businessStatus: none`). A bill booked in Odoo meanwhile stays: the answer is 409 and the transaction says `posted`.",
33639
+ pathParams: { id: "The transaction's ULID." },
33640
+ pathParamSchemas: { id: ulidSchema },
33641
+ status: 200,
33642
+ response: { description: "`{ transactionId, line }`." },
33643
+ errors: {
33644
+ 403: "The Odoo push is off for this Space.",
33645
+ 404: `No such account or transaction, or it is another member's "Only me" one.`,
33646
+ 409: "No draft to take back, or it was booked.",
33647
+ 502: "Odoo didn't answer."
33648
+ },
33649
+ operationId: "money_push_odoo_undo"
33650
+ },
33651
+ review: {
33652
+ method: "get",
33653
+ path: "/money/review",
33654
+ scopes: ["finance:read"],
33655
+ tag: "Money",
33656
+ summary: "Review money transactions",
33657
+ description: "The review queue (`money_review`): `count` waiting and the oldest first (`limit`, default 50; `accountId`), each with a `suggestion` \u2014 the category its payee's sorted transactions went to most, and how many \u2014 when it has none yet.",
33658
+ query: [
33659
+ { name: "accountId", description: "Only this account." },
33660
+ { name: "limit", description: "How many (max 200).", type: "integer" }
33661
+ ],
33662
+ querySchema: moneyReviewQuerySchema,
33663
+ status: 200,
33664
+ response: { description: "`{ count, items }`." },
33665
+ errors: { 400: true },
33666
+ operationId: "money_review"
33667
+ },
33668
+ sort: {
33669
+ method: "post",
33670
+ path: "/money/sort",
33671
+ scopes: ["finance:write"],
33672
+ tag: "Money",
33673
+ summary: "Sort money transactions",
33674
+ description: "Sort a batch in one call (`money_sort`): `items: [{ id, categoryId?, contactId?, kind?, note?, status? }]` (1\u2013200). A member's sort confirms each (`reviewed`); an agent's leaves it `unreviewed` for the member's nod. Answers with a receipt: a calm `line` per transaction, `before`/`after`, `undo` (post it to `/sort/undo` to take it all back) and `ruleProposals` (a payee sorted into the same category three times by hand, with no rule yet: post one to `/rules` to keep it).",
33675
+ body: moneySortInputSchema,
33676
+ status: 200,
33677
+ response: { description: "The receipt." },
33678
+ errors: {
33679
+ 400: true,
33680
+ 404: `No such account or transaction, or it is another member's "Only me" one.`
33681
+ },
33682
+ operationId: "money_sort"
33683
+ },
33684
+ undoSort: {
33685
+ method: "post",
33686
+ path: "/money/sort/undo",
33687
+ scopes: ["finance:write"],
33688
+ tag: "Money",
33689
+ summary: "Undo a money sort",
33690
+ description: "Put each transaction back the way a sort's receipt recorded it (its `undo`). Answers with a receipt of its own.",
33691
+ body: moneySortUndoInputSchema,
33692
+ status: 200,
33693
+ response: { description: "The receipt." },
33694
+ errors: {
33695
+ 400: true,
33696
+ 404: `No such account or transaction, or it is another member's "Only me" one.`
33697
+ },
33698
+ operationId: "money_sort_undo"
33699
+ },
33700
+ listRules: {
33701
+ method: "get",
33702
+ path: "/money/rules",
33703
+ scopes: ["finance:read"],
33704
+ tag: "Money",
33705
+ summary: "List money rules",
33706
+ description: "The sorting rules the key may see, in the order they run (`priority`), with their hits. A rule on an Only me account is its member's own.",
33707
+ status: 200,
33708
+ response: { description: "The rules." }
33709
+ },
33710
+ ruleSuggestions: {
33711
+ method: "get",
33712
+ path: "/money/rules/suggestions",
33713
+ scopes: ["finance:read"],
33714
+ tag: "Money",
33715
+ summary: "Suggest money rules",
33716
+ description: 'The rules worth making now: each payee sorted into the same category by hand three times or more this past year that no rule covers, most-sorted first (at most three): `name`, `payee`, `categoryName`, `conditions`, `actions`, `count`, `createdFromTransactionId`, and `goal` ({ nodeId, title } those payments were mostly counted toward, or null). Accepting one is a POST /money/rules with its fields, plus `actions.link: { nodeId, kind: "for_goal" }` to count the next ones toward the goal.',
33717
+ status: 200,
33718
+ response: { description: "The suggestions." }
33719
+ },
33720
+ createRule: {
33721
+ method: "post",
33722
+ path: "/money/rules",
33723
+ scopes: ["finance:write"],
33724
+ tag: "Money",
33725
+ summary: "Add a money rule",
33726
+ description: "`id`, `name`, `conditions` (all must hold: `counterpartyContains`, `descriptionContains`, `counterpartyIbanHash`, `structuredRefPrefix`, `direction` in/out, `amountMin`/`amountMax` on the absolute amount) and `actions` (`categoryId`, `contactId`, `kind`, `markReviewed`, `link: { nodeId, kind }`), optional `accountId`, `priority`, `createdFromTransactionId`, `visibility`. It runs over what's still unreviewed right away; a hand sort is never overwritten.",
33727
+ body: createMoneyRuleInputSchema,
33728
+ status: 201,
33729
+ response: { description: "The rule, with its first hits." },
33730
+ errors: {
33731
+ 400: true,
33732
+ 404: `No such account or transaction, or it is another member's "Only me" one.`
33733
+ }
33734
+ },
33735
+ applyRules: {
33736
+ method: "post",
33737
+ path: "/money/rules/apply",
33738
+ scopes: ["finance:write"],
33739
+ tag: "Money",
33740
+ summary: "Apply money rules",
33741
+ description: "Run the rules, then the transfer matching between own accounts, over what's still unreviewed (optionally one `accountId`). Answers `{ sorted, transfers }`.",
33742
+ body: applyMoneyRulesInputSchema,
33743
+ bodyOptional: true,
33744
+ status: 200,
33745
+ response: { description: "The counts." },
33746
+ errors: { 404: `No such account or transaction, or it is another member's "Only me" one.` }
33747
+ },
33748
+ getRule: {
33749
+ method: "get",
33750
+ path: "/money/rules/{id}",
33751
+ scopes: ["finance:read"],
33752
+ tag: "Money",
33753
+ summary: "Get a money rule",
33754
+ description: "One rule.",
33755
+ pathParams: { id: "The rule's ULID." },
33756
+ pathParamSchemas: { id: ulidSchema },
33757
+ status: 200,
33758
+ response: { description: "The rule." },
33759
+ errors: { 404: `No such account or transaction, or it is another member's "Only me" one.` }
33760
+ },
33761
+ updateRule: {
33762
+ method: "patch",
33763
+ path: "/money/rules/{id}",
33764
+ scopes: ["finance:write"],
33765
+ tag: "Money",
33766
+ summary: "Update a money rule",
33767
+ description: "Rename it, change what it matches or does, its priority, or switch it off.",
33768
+ pathParams: { id: "The rule's ULID." },
33769
+ pathParamSchemas: { id: ulidSchema },
33770
+ body: updateMoneyRuleInputSchema,
33771
+ status: 200,
33772
+ response: { description: "The rule." },
33773
+ errors: {
33774
+ 400: true,
33775
+ 404: `No such account or transaction, or it is another member's "Only me" one.`
33776
+ }
33777
+ },
33778
+ removeRule: {
33779
+ method: "delete",
33780
+ path: "/money/rules/{id}",
33781
+ scopes: ["finance:write"],
33782
+ tag: "Money",
33783
+ summary: "Remove a money rule",
33784
+ description: "What it already sorted stays sorted.",
33785
+ pathParams: { id: "The rule's ULID." },
33786
+ pathParamSchemas: { id: ulidSchema },
33787
+ status: 204,
33788
+ response: { description: "Removed." },
33789
+ errors: { 404: `No such account or transaction, or it is another member's "Only me" one.` }
33790
+ },
33791
+ budgetMonth: {
33792
+ method: "get",
33793
+ path: "/money/budget",
33794
+ scopes: ["finance:read"],
33795
+ tag: "Money",
33796
+ summary: "Get the money plan for a month",
33797
+ description: "A month's plan next to what happened (plan vs record): `rows` per category (fixed, then flexible, then saving) with `planned` (the plan lines the scope covers, listed in `lines`), `spent` so far (spending less refunds; for a saving category, what moved toward it) and `lastMonth`; a `summary` with `incomeIn`, `plannedOut`, `spent`, `unsorted` and `notYetPlanned`. Categories show when planned, or spent on this month or last. `scope`: `all` (default), `space` (the household's accounts and plan) or `owner` (yours). Nothing is judged: a category ahead of its plan is just ahead.",
33798
+ query: [
33799
+ { name: "month", description: "The month, YYYY-MM.", required: true },
33800
+ { name: "scope", description: "all, space or owner." }
33801
+ ],
33802
+ querySchema: moneyBudgetQuerySchema,
33803
+ status: 200,
33804
+ response: { description: "The month." },
33805
+ errors: { 400: true }
33806
+ },
33807
+ setBudgetLine: {
33808
+ method: "put",
33809
+ path: "/money/budget/lines",
33810
+ scopes: ["finance:write"],
33811
+ tag: "Money",
33812
+ summary: "Set a money plan line",
33813
+ description: "Plan a category for a month: `month`, `categoryId` (a fixed, flexible or saving one), `planned` (never negative), optional `note`. The household's line, or `mine: true` for your own (only you see it). Setting it again replaces the amount (and the note, when given). `madeVia: agent` marks a line the agent proposed.",
33814
+ body: setMoneyBudgetLineInputSchema,
33815
+ status: 200,
33816
+ response: { description: "The line." },
33817
+ errors: { 400: true, 404: true }
33818
+ },
33819
+ removeBudgetLine: {
33820
+ method: "delete",
33821
+ path: "/money/budget/lines/{id}",
33822
+ scopes: ["finance:write"],
33823
+ tag: "Money",
33824
+ summary: "Remove a money plan line",
33825
+ description: "Take a category out of a month's plan.",
33826
+ pathParams: { id: "The plan line's ULID." },
33827
+ pathParamSchemas: { id: ulidSchema },
33828
+ status: 204,
33829
+ response: { description: "Removed." },
33830
+ errors: { 404: true }
33831
+ },
33832
+ draftBudget: {
33833
+ method: "post",
33834
+ path: "/money/budget/draft",
33835
+ scopes: ["finance:write"],
33836
+ tag: "Money",
33837
+ summary: "Draft a money plan from history",
33838
+ description: "Draft `month`'s household plan from the `months` before it (3 by default): per fixed or flexible category spent on, the average month rounded up (fixed to the euro, flexible to ten). Categories the month already plans are kept. Answers `{ lines: [{ id, categoryId, name, planned }], kept, months }`; taking the lines out (DELETE /money/budget/lines/{id}) is the undo.",
33839
+ body: draftMoneyBudgetInputSchema,
33840
+ status: 200,
33841
+ response: { description: "The drafted lines." },
33842
+ errors: { 400: true }
33843
+ },
33844
+ copyBudget: {
33845
+ method: "post",
33846
+ path: "/money/budget/copy",
33847
+ scopes: ["finance:write"],
33848
+ tag: "Money",
33849
+ summary: "Copy a money plan to another month",
33850
+ description: "Copy `from` month's plan into `to`: each line stays the household's or yours; a line `to` already has is kept. Answers `{ copied, kept }`.",
33851
+ body: copyMoneyBudgetInputSchema,
33852
+ status: 200,
33853
+ response: { description: "The counts." },
33854
+ errors: { 400: true }
33855
+ },
33856
+ year: {
33857
+ method: "get",
33858
+ path: "/money/year",
33859
+ scopes: ["finance:read"],
33860
+ tag: "Money",
33861
+ summary: "Get the year in money",
33862
+ description: "A calendar year of the ledger, like Admin's yearly review: `totals` (`income`, `spent` \u2014 fixed and flexible spending and what isn't sorted, refunds netted, transfers left out \u2014 and `saved`, what moved toward saving categories) and `lastYear` (the same months a year earlier; null when nothing was booked then); `months` January to December; `spending` and `income` by category, biggest first, each with `lastYear`; `recurring`: each tracked stream's `thisYear` and `lastYear` with `change` (new, stopped, up, down or same), the biggest change first; `goals`: what went toward each this year (`thisYear`), `saved` and `target`. A year under way counts to this month (`through`); `firstYear` is the oldest year the ledger reaches. Read over the accounts counted in the budget; another member's Only me account, stream or goal never adds to it. `year` 2000\u20132100 (this one by default); `scope`: `all` (default), `space` or `owner`.",
33863
+ query: [
33864
+ { name: "year", description: "The calendar year (default this one)." },
33865
+ { name: "scope", description: "all, space or owner." }
33866
+ ],
33867
+ querySchema: moneyYearQuerySchema,
33868
+ status: 200,
33869
+ response: { description: "The year." },
33870
+ errors: { 400: true }
33871
+ },
33872
+ netWorthRead: {
33873
+ method: "get",
33874
+ path: "/money/net-worth",
33875
+ scopes: ["finance:read"],
33876
+ tag: "Money",
33877
+ summary: "Get net worth and its monthly line",
33878
+ 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; 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. `scope`: `all` (default), `space` or `owner`; `months` 2\u201360 (12 by default).",
33879
+ query: [
33880
+ { name: "scope", description: "all, space or owner." },
33881
+ { name: "months", description: "How many months the line covers (2\u201360, default 12)." }
33882
+ ],
33883
+ querySchema: moneyNetWorthQuerySchema,
33884
+ status: 200,
33885
+ response: { description: "Net worth." },
33886
+ errors: { 400: true }
33887
+ },
33888
+ setNetWorthValue: {
33889
+ method: "put",
33890
+ path: "/money/net-worth/values",
33891
+ scopes: ["finance:write"],
33892
+ tag: "Money",
33893
+ summary: "Set an account's value for a month",
33894
+ description: "Value an account by hand for a month (a house's estimate, a car, a pension statement): `accountId`, `value` (a decimal string), optional `month` (YYYY-MM, this month by default; a month ahead is a 400). Again for the same month replaces it. This month's value also becomes the account's balance, unless a bank link keeps that. Answers the snapshot `{ id, accountId, month, value, source }`.",
33895
+ body: setMoneyNetWorthValueInputSchema,
33896
+ status: 200,
33897
+ response: { description: "The snapshot." },
33898
+ errors: { 400: true, 404: true }
33899
+ },
33900
+ listGoals: {
33901
+ method: "get",
33902
+ path: "/money/goals",
33903
+ scopes: ["finance:read"],
33904
+ tag: "Money",
33905
+ summary: "List money goals",
33906
+ 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`.",
33907
+ status: 200,
33908
+ response: { description: "The goals." }
33909
+ },
33910
+ createGoal: {
33911
+ method: "post",
33912
+ path: "/money/goals",
33913
+ scopes: ["finance:write"],
33914
+ tag: "Money",
33915
+ summary: "Set a money goal",
33916
+ description: "`id`, `title`, `targetAmount`, optional `targetOn`, `accountId` (where the pot sits), `monthlyPlan`, `savedBefore`, `projectId` (a Project), `startedOn`. Only me unless `visibility: space`.",
33917
+ body: createMoneyGoalInputSchema,
33918
+ status: 201,
33919
+ response: { description: "The goal." },
33920
+ errors: { 400: true, 404: true }
33921
+ },
33922
+ getGoal: {
33923
+ method: "get",
33924
+ path: "/money/goals/{id}",
33925
+ scopes: ["finance:read"],
33926
+ tag: "Money",
33927
+ summary: "Get a money goal",
33928
+ description: "One goal with its `lines` and the `contributions` counted toward it, newest first.",
33929
+ pathParams: { id: "The goal's ULID." },
33930
+ pathParamSchemas: { id: ulidSchema },
33931
+ status: 200,
33932
+ response: { description: "The goal." },
33933
+ errors: { 404: true }
33934
+ },
33935
+ updateGoal: {
33936
+ method: "patch",
33937
+ path: "/money/goals/{id}",
33938
+ scopes: ["finance:write"],
33939
+ tag: "Money",
33940
+ summary: "Update a money goal",
33941
+ description: "Change any field, or `status` (active \xB7 reached \xB7 paused).",
33942
+ pathParams: { id: "The goal's ULID." },
33943
+ pathParamSchemas: { id: ulidSchema },
33944
+ body: updateMoneyGoalInputSchema,
33945
+ status: 200,
33946
+ response: { description: "The goal." },
33947
+ errors: { 400: true, 404: true }
33095
33948
  },
33096
- setRole: {
33949
+ removeGoal: {
33950
+ method: "delete",
33951
+ path: "/money/goals/{id}",
33952
+ scopes: ["finance:write"],
33953
+ tag: "Money",
33954
+ summary: "Remove a money goal",
33955
+ description: "Let it go (to Trash); the money and the transactions stay.",
33956
+ pathParams: { id: "The goal's ULID." },
33957
+ pathParamSchemas: { id: ulidSchema },
33958
+ status: 204,
33959
+ response: { description: "Removed." },
33960
+ errors: { 404: true }
33961
+ },
33962
+ addGoalLine: {
33963
+ method: "post",
33964
+ path: "/money/goals/{id}/lines",
33965
+ scopes: ["finance:write"],
33966
+ tag: "Money",
33967
+ summary: "Add a money goal line",
33968
+ description: "What the goal pays for: `id`, `title`, `planned`, optional `paid`, `note`, `dueOn`, `sortOrder`.",
33969
+ pathParams: { id: "The goal's ULID." },
33970
+ pathParamSchemas: { id: ulidSchema },
33971
+ body: createMoneyGoalLineInputSchema,
33972
+ status: 201,
33973
+ response: { description: "The line." },
33974
+ errors: { 400: true, 404: true }
33975
+ },
33976
+ updateGoalLine: {
33097
33977
  method: "patch",
33098
- path: "/members/{id}/role",
33099
- scopes: ["members:write"],
33100
- tag: "Collaboration",
33101
- summary: "Set a member's role",
33102
- description: "Change a member's role; fires `member.role.updated`.",
33103
- body: setMemberRoleInputSchema,
33978
+ path: "/money/goals/lines/{lineId}",
33979
+ scopes: ["finance:write"],
33980
+ tag: "Money",
33981
+ summary: "Update a money goal line",
33982
+ description: "Change what's planned or paid, the note or the day it's due.",
33983
+ pathParams: { lineId: "The line's ULID." },
33984
+ pathParamSchemas: { lineId: ulidSchema },
33985
+ body: updateMoneyGoalLineInputSchema,
33104
33986
  status: 200,
33105
- response: { description: "The updated member." },
33106
- errors: { 400: true, 404: true },
33107
- webhook: "member.role.updated"
33987
+ response: { description: "The line." },
33988
+ errors: { 400: true, 404: true }
33108
33989
  },
33109
- remove: {
33990
+ removeGoalLine: {
33110
33991
  method: "delete",
33111
- path: "/members/{id}",
33112
- scopes: ["members:write"],
33113
- tag: "Collaboration",
33114
- summary: "Remove a member",
33115
- description: "Remove a member from the Space; fires `member.removed`.",
33992
+ path: "/money/goals/lines/{lineId}",
33993
+ scopes: ["finance:write"],
33994
+ tag: "Money",
33995
+ summary: "Remove a money goal line",
33996
+ description: "Take a line off the goal.",
33997
+ pathParams: { lineId: "The line's ULID." },
33998
+ pathParamSchemas: { lineId: ulidSchema },
33116
33999
  status: 204,
33117
34000
  response: { description: "Removed." },
33118
- errors: { 404: true },
33119
- webhook: "member.removed"
33120
- }
33121
- });
33122
-
33123
- // ../../packages/schema/src/operations/metrics.ts
33124
- var NO_MEMBER2 = "The key acts for no member.";
33125
- var metricOperations = defineOperations({
33126
- list: {
33127
- method: "get",
33128
- path: "/metrics",
33129
- scopes: ["habits:read"],
33130
- tag: "Habits",
33131
- summary: "List metrics",
33132
- description: "Weight (kg), sleep (h), mood (1\u20135), the day rating, energy, bedtime, resting heart rate (bpm) and HRV (ms) as trend lines: one point per day in the member's timezone (that day's last value, hand-logged or from Google Fit or Google Health), oldest first, plus the latest. No target, no streak. The key must act for a member. Resting heart rate and HRV are health data: without `health:read` held by name their lines come back empty.",
33133
- query: [
33134
- { name: "days", description: "How far back, 1\u2013730 days (default 90).", type: "integer" }
33135
- ],
33136
- querySchema: metricsQuerySchema,
34001
+ errors: { 404: true }
34002
+ },
34003
+ addGoalContribution: {
34004
+ method: "post",
34005
+ path: "/money/goals/{id}/contributions",
34006
+ scopes: ["finance:write"],
34007
+ tag: "Money",
34008
+ summary: "Count a transaction toward a money goal",
34009
+ description: "`transactionId`: money moved into the pot now counts as saved (a `for_goal` link). Again is a no-op.",
34010
+ pathParams: { id: "The goal's ULID." },
34011
+ pathParamSchemas: { id: ulidSchema },
34012
+ body: moneyGoalContributionInputSchema,
33137
34013
  status: 200,
33138
- response: { description: "Every Metric with its points." },
33139
- errors: { 403: NO_MEMBER2 }
34014
+ response: { description: "The goal." },
34015
+ errors: { 400: true, 404: true }
33140
34016
  },
33141
- sessions: {
34017
+ removeGoalContribution: {
34018
+ method: "delete",
34019
+ path: "/money/goals/{id}/contributions/{transactionId}",
34020
+ scopes: ["finance:write"],
34021
+ tag: "Money",
34022
+ summary: "Stop counting a transaction toward a money goal",
34023
+ description: "The transaction stays; it just no longer counts as saved for this goal.",
34024
+ pathParams: { id: "The goal's ULID.", transactionId: "The transaction's ULID." },
34025
+ pathParamSchemas: { id: ulidSchema, transactionId: ulidSchema },
34026
+ status: 200,
34027
+ response: { description: "The goal." },
34028
+ errors: { 404: true }
34029
+ },
34030
+ recurringOverview: {
33142
34031
  method: "get",
33143
- path: "/metrics/sessions",
33144
- scopes: ["habits:read"],
33145
- tag: "Habits",
33146
- summary: "List workout sessions",
33147
- description: "The member's workouts from their connections (Google Fit workouts, Strava activities), newest first: when, the sport in words, its name and minutes. Read only; they come in through sync.",
33148
- query: [
33149
- { name: "days", description: "How far back, 1\u2013365 days (default 14).", type: "integer" }
33150
- ],
33151
- querySchema: sessionsQuerySchema,
34032
+ path: "/money/recurring",
34033
+ scopes: ["finance:read"],
34034
+ tag: "Money",
34035
+ summary: "List recurring money streams",
34036
+ description: "Recurring payments and subscriptions: `streams` (active and paused, soonest next first; each with `yearly`, the provider, category and paper by name, the paper's soonest `keyDate` \u2014 cancel-by, promo ends \u2014 and `lastPaid` from the ledger), `found` (patterns over the whole ledger, across accounts, that aren't tracked \u2014 grouped by counterparty IBAN, name or the bank line's payee and split by amount: payee, `kind` (subscription, or standing_order for money moved to an own account), cadence, amount, next day, whose account), `changes` (a stream whose latest payment differs by more than 5%) and `summary` (fixed costs per year: all, household, yours, the business share).",
33152
34037
  status: 200,
33153
- response: { description: "The sessions." },
33154
- errors: { 403: NO_MEMBER2 }
34038
+ response: { description: "The overview." }
33155
34039
  },
33156
- import: {
34040
+ createRecurring: {
33157
34041
  method: "post",
33158
- path: "/metrics/import",
33159
- scopes: ["habits:write"],
33160
- tag: "Habits",
33161
- summary: "Import metrics from Health Connect",
33162
- description: "A batch the Android app read off the phone's Health Connect: `weight` (kg), `sleep` (h) and `bedtime` (minutes after 18:00), at most 500, each with its Health Connect `recordId`. Each lands once on the member's line with source `health_connect`; a record already imported, out of its Metric's range or in the future is skipped, not refused. Returns `{ imported, skipped }`.",
33163
- body: importMetricsInputSchema,
34042
+ path: "/money/recurring",
34043
+ scopes: ["finance:write"],
34044
+ tag: "Money",
34045
+ summary: "Track a recurring money stream",
34046
+ description: "`id`, `title`, `expectedAmount`, optional `cadence` (weekly \xB7 monthly \xB7 quarterly \xB7 yearly), `nextOn`, `matchText` (the payee, to recognise its payments), `contactId`, `categoryId`, `documentId` (its Admin paper), `businessShare` (0\u2013100), `pick` (keep \xB7 negotiate \xB7 compare \xB7 drop), `detectedBy`. `status: ignored` calls a found pattern a one-off. Only me unless `visibility: space`.",
34047
+ body: createMoneyRecurringInputSchema,
33164
34048
  status: 201,
33165
- response: { description: "How many points landed and how many were skipped." },
33166
- errors: { 400: true, 403: NO_MEMBER2 }
34049
+ response: { description: "The stream." },
34050
+ errors: { 400: true, 404: true }
33167
34051
  },
33168
- log: {
33169
- method: "post",
33170
- path: "/metrics/{key}",
33171
- pathParams: {
33172
- key: {
33173
- description: "`weight` (kg), `sleep` (h), `mood` (1\u20135), `rating` (the day, 1\u201310), `energy` (1\u201310), `bedtime` (minutes after 18:00: 01:00 = 420), `resting_hr` (bpm) or `hrv` (ms). The last two are health data: logging one needs `health:write` too.",
33174
- enum: metricKeySchema.options
33175
- }
33176
- },
33177
- pathParamSchemas: { key: metricKeySchema },
33178
- scopes: ["habits:write"],
33179
- tag: "Habits",
33180
- summary: "Log a metric",
33181
- description: 'Log one value ("weighed 72.4"; "slept 7.5 hours"; "mood 4"; "today was a 7" \u2192 `rating`; "bed 01:00" \u2192 `bedtime` 420); `occurredAt` back-dates it, `source` says `manual` (default) or `device`. A bedtime lands on the night\'s wake day. Logging again the same day replaces that day\'s point. 400 outside the Metric\'s range. Returns the Metric\'s refreshed line.',
33182
- body: logMetricInputSchema,
33183
- status: 201,
33184
- response: { description: "The Metric." },
33185
- errors: { 400: true, 403: NO_MEMBER2 }
34052
+ getRecurring: {
34053
+ method: "get",
34054
+ path: "/money/recurring/{id}",
34055
+ scopes: ["finance:read"],
34056
+ tag: "Money",
34057
+ summary: "Get a recurring money stream",
34058
+ description: "One stream, with its paper's soonest key date.",
34059
+ pathParams: { id: "The stream's ULID." },
34060
+ pathParamSchemas: { id: ulidSchema },
34061
+ status: 200,
34062
+ response: { description: "The stream." },
34063
+ errors: { 404: true }
34064
+ },
34065
+ updateRecurring: {
34066
+ method: "patch",
34067
+ path: "/money/recurring/{id}",
34068
+ scopes: ["finance:write"],
34069
+ tag: "Money",
34070
+ summary: "Update a recurring money stream",
34071
+ description: "Change any field: the amount, the next day, the paper, the pick, or `status` (active \xB7 paused \xB7 cancelled \xB7 ignored).",
34072
+ pathParams: { id: "The stream's ULID." },
34073
+ pathParamSchemas: { id: ulidSchema },
34074
+ body: updateMoneyRecurringInputSchema,
34075
+ status: 200,
34076
+ response: { description: "The stream." },
34077
+ errors: { 400: true, 404: true }
34078
+ },
34079
+ removeRecurring: {
34080
+ method: "delete",
34081
+ path: "/money/recurring/{id}",
34082
+ scopes: ["finance:write"],
34083
+ tag: "Money",
34084
+ summary: "Remove a recurring money stream",
34085
+ description: "Stop tracking it (to Trash); its payments stay.",
34086
+ pathParams: { id: "The stream's ULID." },
34087
+ pathParamSchemas: { id: ulidSchema },
34088
+ status: 204,
34089
+ response: { description: "Removed." },
34090
+ errors: { 404: true }
33186
34091
  }
33187
34092
  });
33188
34093
 
@@ -35766,7 +36671,7 @@ function parseCapture2(input, now) {
35766
36671
 
35767
36672
  // src/meta.ts
35768
36673
  var CLI_NAME = "do";
35769
- var VERSION = "1.314.0";
36674
+ var VERSION = "1.314.1";
35770
36675
  var API_PREFIX = "/api/v1";
35771
36676
  var DEFAULT_API_URL = "http://localhost:3001";
35772
36677
 
@@ -40436,9 +41341,9 @@ var COMMAND_SPECS = [
40436
41341
  summary: "List a node's files, newest first (never their bytes).",
40437
41342
  args: [],
40438
41343
  flags: [{ name: "node", type: "string", description: "The owner node's ULID (required)." }],
40439
- scopes: ["files:read"],
41344
+ scopes: [...fileOperations.list.scopes],
40440
41345
  returns: "FileView",
40441
- notes: "GET /api/v1/files?nodeId=. A node the key's member can't see is not found (exit 4)."
41346
+ notes: `${routeOf(fileOperations.list)}?nodeId=. A node the key's member can't see is not found (exit 4).`
40442
41347
  },
40443
41348
  {
40444
41349
  path: ["files", "get"],
@@ -40507,9 +41412,9 @@ var COMMAND_SPECS = [
40507
41412
  summary: "Get a short-lived link that shows a file in a browser (a PDF, a picture).",
40508
41413
  args: [{ name: "id", required: true, description: "File ULID." }],
40509
41414
  flags: [],
40510
- scopes: ["files:read"],
41415
+ scopes: [...fileOperations.viewUrl.scopes],
40511
41416
  returns: "FileViewUrl",
40512
- notes: "POST /api/v1/files/{id}/view-url. Prints the URL (--json: id, url, expiresAt). It works for ten minutes, opens the file inline, and only while this key could still download it."
41417
+ notes: `${routeOf(fileOperations.viewUrl)}. Prints the URL (--json: id, url, expiresAt). It works for ten minutes, opens the file inline, and only while this key could still download it.`
40513
41418
  },
40514
41419
  {
40515
41420
  path: ["files", "delete"],
@@ -41702,36 +42607,36 @@ var COMMAND_SPECS = [
41702
42607
  summary: "List the money accounts you may see: the household's and your Only me ones.",
41703
42608
  args: [],
41704
42609
  flags: [],
41705
- scopes: ["finance:read"],
42610
+ scopes: [...moneyOperations.listAccounts.scopes],
41706
42611
  returns: "MoneyAccountView[]",
41707
- notes: "GET /api/v1/money/accounts. Money is a sensitive group: `*` never covers finance:*."
42612
+ notes: `${routeOf(moneyOperations.listAccounts)}. Money is a sensitive group: \`*\` never covers finance:*.`
41708
42613
  },
41709
42614
  {
41710
42615
  path: ["money", "account"],
41711
42616
  summary: "Show one money account.",
41712
42617
  args: [{ name: "id", required: true, description: "Account ULID." }],
41713
42618
  flags: [],
41714
- scopes: ["finance:read"],
42619
+ scopes: [...moneyOperations.getAccount.scopes],
41715
42620
  returns: "MoneyAccountView",
41716
- notes: "GET /api/v1/money/accounts/{id}."
42621
+ notes: `${routeOf(moneyOperations.getAccount)}.`
41717
42622
  },
41718
42623
  {
41719
42624
  path: ["money", "account-add"],
41720
42625
  summary: "Open a money account (Only me unless --data says visibility: space).",
41721
42626
  args: [],
41722
42627
  flags: [{ name: "data", type: "string", description: "CreateMoneyAccountInput JSON." }],
41723
- scopes: ["finance:write"],
42628
+ scopes: [...moneyOperations.createAccount.scopes],
41724
42629
  returns: "MoneyAccountView",
41725
- notes: 'POST /api/v1/money/accounts. `do money account-add --data \'{"title":"Joint","kind":"current","holder":"joint","visibility":"space"}\'`. An IBAN is stored only masked and hashed.'
42630
+ notes: `${routeOf(moneyOperations.createAccount)}. \`do money account-add --data '{"title":"Joint","kind":"current","holder":"joint","visibility":"space"}'\`. An IBAN is stored only masked and hashed.`
41726
42631
  },
41727
42632
  {
41728
42633
  path: ["money", "account-update"],
41729
42634
  summary: "Edit a money account.",
41730
42635
  args: [{ name: "id", required: true, description: "Account ULID." }],
41731
42636
  flags: [{ name: "data", type: "string", description: "UpdateMoneyAccountInput JSON." }],
41732
- scopes: ["finance:write"],
42637
+ scopes: [...moneyOperations.updateAccount.scopes],
41733
42638
  returns: "MoneyAccountView",
41734
- notes: "PATCH /api/v1/money/accounts/{id}."
42639
+ notes: `${routeOf(moneyOperations.updateAccount)}.`
41735
42640
  },
41736
42641
  {
41737
42642
  path: ["money", "account-close"],
@@ -41740,18 +42645,18 @@ var COMMAND_SPECS = [
41740
42645
  flags: [
41741
42646
  { name: "allow-destructive", type: "boolean", description: "Required to actually close." }
41742
42647
  ],
41743
- scopes: ["finance:write"],
42648
+ scopes: [...moneyOperations.removeAccount.scopes],
41744
42649
  returns: "{ ok, closed }",
41745
- notes: "DELETE /api/v1/money/accounts/{id}."
42650
+ notes: `${routeOf(moneyOperations.removeAccount)}.`
41746
42651
  },
41747
42652
  {
41748
42653
  path: ["money", "connections"],
41749
42654
  summary: "List the bank links feeding your money accounts: bank, account, consent end, last sync.",
41750
42655
  args: [],
41751
42656
  flags: [],
41752
- scopes: ["finance:read"],
42657
+ scopes: [...moneyOperations.listConnections.scopes],
41753
42658
  returns: "MoneyConnectionView[]",
41754
- notes: "GET /api/v1/money/connections. `reconnectSoon` is true when the bank's consent ends within 14 days (or did). No credential or provider id is ever returned."
42659
+ notes: `${routeOf(moneyOperations.listConnections)}. \`reconnectSoon\` is true when the bank's consent ends within 14 days (or did). No credential or provider id is ever returned.`
41755
42660
  },
41756
42661
  {
41757
42662
  path: ["money", "sync"],
@@ -41760,36 +42665,36 @@ var COMMAND_SPECS = [
41760
42665
  flags: [
41761
42666
  { name: "account", type: "string", description: "Only the link feeding this account ULID." }
41762
42667
  ],
41763
- scopes: ["finance:write"],
42668
+ scopes: [...moneyOperations.syncConnections.scopes],
41764
42669
  returns: "MoneyBankSyncResult",
41765
- notes: "POST /api/v1/money/connections/sync. Rows go through the CSV import's pipeline, so nothing already imported is booked twice. Each result has outcome, imported, matched, duplicates and the error."
42670
+ notes: `${routeOf(moneyOperations.syncConnections)}. Rows go through the CSV import's pipeline, so nothing already imported is booked twice. Each result has outcome, imported, matched, duplicates and the error.`
41766
42671
  },
41767
42672
  {
41768
42673
  path: ["money", "connection-syncs"],
41769
42674
  summary: "Show a bank link's sync log, newest first.",
41770
42675
  args: [{ name: "id", required: true, description: "Bank link (connection) ULID." }],
41771
42676
  flags: [],
41772
- scopes: ["finance:read"],
42677
+ scopes: [...moneyOperations.listConnectionSyncs.scopes],
41773
42678
  returns: "MoneyConnectionSync[]",
41774
- notes: "GET /api/v1/money/connections/{id}/syncs."
42679
+ notes: `${routeOf(moneyOperations.listConnectionSyncs)}.`
41775
42680
  },
41776
42681
  {
41777
42682
  path: ["money", "categories"],
41778
42683
  summary: "List the household's money categories (the starting list arrives on first use).",
41779
42684
  args: [],
41780
42685
  flags: [],
41781
- scopes: ["finance:read"],
42686
+ scopes: [...moneyOperations.listCategories.scopes],
41782
42687
  returns: "MoneyCategory[]",
41783
- notes: "GET /api/v1/money/categories."
42688
+ notes: `${routeOf(moneyOperations.listCategories)}.`
41784
42689
  },
41785
42690
  {
41786
42691
  path: ["money", "category-add"],
41787
42692
  summary: "Add a money category.",
41788
42693
  args: [],
41789
42694
  flags: [{ name: "data", type: "string", description: "CreateMoneyCategoryInput JSON." }],
41790
- scopes: ["finance:write"],
42695
+ scopes: [...moneyOperations.createCategory.scopes],
41791
42696
  returns: "MoneyCategory",
41792
- notes: "POST /api/v1/money/categories. Two levels at most."
42697
+ notes: `${routeOf(moneyOperations.createCategory)}. Two levels at most.`
41793
42698
  },
41794
42699
  {
41795
42700
  path: ["money", "transactions"],
@@ -41812,27 +42717,27 @@ var COMMAND_SPECS = [
41812
42717
  { name: "limit", type: "string", description: "Page size (max 100)." },
41813
42718
  { name: "cursor", type: "string", description: "The previous page's nextCursor." }
41814
42719
  ],
41815
- scopes: ["finance:read"],
42720
+ scopes: [...moneyOperations.listTransactions.scopes],
41816
42721
  returns: "{ data: MoneyTransactionView[], nextCursor }",
41817
- notes: "GET /api/v1/money/transactions. Amounts are signed decimal strings. `--business` keeps rows with a business share; `--business-status` and `--tax-tag` narrow further."
42722
+ notes: `${routeOf(moneyOperations.listTransactions)}. Amounts are signed decimal strings. \`--business\` keeps rows with a business share; \`--business-status\` and \`--tax-tag\` narrow further.`
41818
42723
  },
41819
42724
  {
41820
42725
  path: ["money", "transaction"],
41821
42726
  summary: "Show one money transaction.",
41822
42727
  args: [{ name: "id", required: true, description: "Transaction ULID." }],
41823
42728
  flags: [],
41824
- scopes: ["finance:read"],
42729
+ scopes: [...moneyOperations.getTransaction.scopes],
41825
42730
  returns: "MoneyTransactionView",
41826
- notes: "GET /api/v1/money/transactions/{id}."
42731
+ notes: `${routeOf(moneyOperations.getTransaction)}.`
41827
42732
  },
41828
42733
  {
41829
42734
  path: ["money", "book"],
41830
42735
  summary: "Book a transaction by hand (cash, or one the bank hasn't shown yet).",
41831
42736
  args: [],
41832
42737
  flags: [{ name: "data", type: "string", description: "CreateMoneyTransactionInput JSON." }],
41833
- scopes: ["finance:write"],
42738
+ scopes: [...moneyOperations.createTransaction.scopes],
41834
42739
  returns: "MoneyTransactionView",
41835
- notes: 'POST /api/v1/money/transactions. `do money book --data \'{"accountId":"\u2026","bookedOn":"2026-10-18","amount":"-20","counterpartyName":"Fries stand"}\'`.'
42740
+ notes: `${routeOf(moneyOperations.createTransaction)}. \`do money book --data '{"accountId":"\u2026","bookedOn":"2026-10-18","amount":"-20","counterpartyName":"Fries stand"}'\`.`
41836
42741
  },
41837
42742
  {
41838
42743
  path: ["money", "review"],
@@ -41842,9 +42747,9 @@ var COMMAND_SPECS = [
41842
42747
  { name: "account", type: "string", description: "Only this account (ULID)." },
41843
42748
  { name: "limit", type: "string", description: "How many (default 50, max 200)." }
41844
42749
  ],
41845
- scopes: ["finance:read"],
42750
+ scopes: [...moneyOperations.review.scopes],
41846
42751
  returns: "MoneyReview",
41847
- notes: "GET /api/v1/money/review (`money_review`)."
42752
+ notes: `${routeOf(moneyOperations.review)} (\`money_review\`).`
41848
42753
  },
41849
42754
  {
41850
42755
  path: ["money", "sort"],
@@ -41874,36 +42779,36 @@ var COMMAND_SPECS = [
41874
42779
  },
41875
42780
  { name: "data", type: "string", description: "MoneySortInput JSON: { items: [...] }." }
41876
42781
  ],
41877
- scopes: ["finance:write"],
42782
+ scopes: [...moneyOperations.sort.scopes],
41878
42783
  returns: "MoneySortReceipt",
41879
- notes: "POST /api/v1/money/sort (`money_sort`). A member's sort confirms it; an agent's leaves it for the member. The receipt's `undo` goes to `do money sort-undo --data`; `ruleProposals` to `do money rule-add --data`. A share on an `asked` row answers it: above 0 is business, 0 is not. A row whose bill is in Odoo keeps its share until the draft is taken back."
42784
+ notes: `${routeOf(moneyOperations.sort)} (\`money_sort\`). A member's sort confirms it; an agent's leaves it for the member. The receipt's \`undo\` goes to \`do money sort-undo --data\`; \`ruleProposals\` to \`do money rule-add --data\`. A share on an \`asked\` row answers it: above 0 is business, 0 is not. A row whose bill is in Odoo keeps its share until the draft is taken back.`
41880
42785
  },
41881
42786
  {
41882
42787
  path: ["money", "sort-undo"],
41883
42788
  summary: "Take a sort back, from its receipt's undo.",
41884
42789
  args: [],
41885
42790
  flags: [{ name: "data", type: "string", description: "The receipt's `undo` JSON." }],
41886
- scopes: ["finance:write"],
42791
+ scopes: [...moneyOperations.undoSort.scopes],
41887
42792
  returns: "MoneySortReceipt",
41888
- notes: "POST /api/v1/money/sort/undo."
42793
+ notes: `${routeOf(moneyOperations.undoSort)}.`
41889
42794
  },
41890
42795
  {
41891
42796
  path: ["money", "splits"],
41892
42797
  summary: "Show a transaction's parts (empty when it isn't split).",
41893
42798
  args: [{ name: "id", required: true, description: "Transaction ULID." }],
41894
42799
  flags: [],
41895
- scopes: ["finance:read"],
42800
+ scopes: [...moneyOperations.listSplits.scopes],
41896
42801
  returns: "MoneySplitView[]",
41897
- notes: "GET /api/v1/money/transactions/{id}/splits."
42802
+ notes: `${routeOf(moneyOperations.listSplits)}.`
41898
42803
  },
41899
42804
  {
41900
42805
  path: ["money", "split"],
41901
42806
  summary: `Split a transaction into parts (or --data '{"splits":[]}' to unsplit it).`,
41902
42807
  args: [{ name: "id", required: true, description: "Transaction ULID." }],
41903
42808
  flags: [{ name: "data", type: "string", description: "SetMoneySplitsInput JSON." }],
41904
- scopes: ["finance:write"],
42809
+ scopes: [...moneyOperations.setSplits.scopes],
41905
42810
  returns: "MoneySplitsReceipt",
41906
- notes: 'PUT /api/v1/money/transactions/{id}/splits. The parts add up to the transaction, each with its sign; a part may be owed by a member or a Contact: `{"splits":[{"amount":"-70","categoryId":"\u2026"},{"amount":"-30","owedBy":{"contactId":"\u2026"},"owedAmount":"15"}]}`. The receipt\'s `undo` goes back to `do money split <id> --data`.'
42811
+ notes: `${routeOf(moneyOperations.setSplits)}. The parts add up to the transaction, each with its sign; a part may be owed by a member or a Contact: \`{"splits":[{"amount":"-70","categoryId":"\u2026"},{"amount":"-30","owedBy":{"contactId":"\u2026"},"owedAmount":"15"}]}\`. The receipt's \`undo\` goes back to \`do money split <id> --data\`.`
41907
42812
  },
41908
42813
  {
41909
42814
  path: ["money", "share"],
@@ -41924,18 +42829,18 @@ var COMMAND_SPECS = [
41924
42829
  },
41925
42830
  { name: "note", type: "string", description: "What it was, shown to whoever owes it." }
41926
42831
  ],
41927
- scopes: ["finance:write"],
42832
+ scopes: [...moneyOperations.share.scopes],
41928
42833
  returns: "MoneySplitsReceipt",
41929
- notes: "POST /api/v1/money/transactions/{id}/share (`money_split`). The member who owes a part sees that part and its note, never the transaction."
42834
+ notes: `${routeOf(moneyOperations.share)} (\`money_split\`). The member who owes a part sees that part and its note, never the transaction.`
41930
42835
  },
41931
42836
  {
41932
42837
  path: ["money", "settle-up"],
41933
42838
  summary: "Who owes you and whom you owe, per person, with the lines behind it.",
41934
42839
  args: [],
41935
42840
  flags: [],
41936
- scopes: ["finance:read"],
42841
+ scopes: [...moneyOperations.settleUp.scopes],
41937
42842
  returns: "MoneySettleUp",
41938
- notes: "GET /api/v1/money/settle-up. A positive balance is owed to you, a negative one by you. Needs a member behind the key."
42843
+ notes: `${routeOf(moneyOperations.settleUp)}. A positive balance is owed to you, a negative one by you. Needs a member behind the key.`
41939
42844
  },
41940
42845
  {
41941
42846
  path: ["money", "settle"],
@@ -41955,9 +42860,9 @@ var COMMAND_SPECS = [
41955
42860
  { name: "transaction", type: "string", description: "The transfer that carried it (ULID)." },
41956
42861
  { name: "note", type: "string", description: "A note." }
41957
42862
  ],
41958
- scopes: ["finance:write"],
42863
+ scopes: [...moneyOperations.settle.scopes],
41959
42864
  returns: "MoneySettlementView",
41960
- notes: "POST /api/v1/money/settlements. Undo: `do money settlement-remove <id>`."
42865
+ notes: `${routeOf(moneyOperations.settle)}. Undo: \`do money settlement-remove <id>\`.`
41961
42866
  },
41962
42867
  {
41963
42868
  path: ["money", "settlement-remove"],
@@ -41966,9 +42871,9 @@ var COMMAND_SPECS = [
41966
42871
  flags: [
41967
42872
  { name: "allow-destructive", type: "boolean", description: "Required to actually remove." }
41968
42873
  ],
41969
- scopes: ["finance:write"],
42874
+ scopes: [...moneyOperations.removeSettlement.scopes],
41970
42875
  returns: "{ ok, removed }",
41971
- notes: "DELETE /api/v1/money/settlements/{id}."
42876
+ notes: `${routeOf(moneyOperations.removeSettlement)}.`
41972
42877
  },
41973
42878
  {
41974
42879
  path: ["money", "tax-evidence"],
@@ -41978,72 +42883,72 @@ var COMMAND_SPECS = [
41978
42883
  { name: "year", type: "string", description: "The year (YYYY)." },
41979
42884
  { name: "tag", type: "string", description: "Only this tax tag." }
41980
42885
  ],
41981
- scopes: ["finance:read"],
42886
+ scopes: [...moneyOperations.taxEvidence.scopes],
41982
42887
  returns: "MoneyTaxEvidence",
41983
- notes: "GET /api/v1/money/tax-evidence?year=. Per tag, the year's payments oldest first, their signed total, and each one's evidence (linked nodes and their files)."
42888
+ notes: `${routeOf(moneyOperations.taxEvidence)}?year=. Per tag, the year's payments oldest first, their signed total, and each one's evidence (linked nodes and their files).`
41984
42889
  },
41985
42890
  {
41986
42891
  path: ["money", "odoo-push"],
41987
42892
  summary: "Push a transaction's business share to Odoo as a draft vendor bill.",
41988
42893
  args: [{ name: "id", required: true, description: "Transaction ULID." }],
41989
42894
  flags: [{ name: "note", type: "string", description: "The note on the bill." }],
41990
- scopes: ["finance:write"],
42895
+ scopes: [...moneyOperations.pushOdooDraft.scopes],
41991
42896
  returns: "MoneyOdooReceipt",
41992
- notes: "POST /api/v1/money/transactions/{id}/odoo-draft (`money_push_odoo`). Draft only, never posted; the papers linked as evidence are attached. Idempotent on the bill's ref `do:tx:<id>`. Off (403) until the deployment sets MONEY_ODOO_PUSH and lists the Space. Undo: `do money odoo-undo <id>`."
42897
+ notes: `${routeOf(moneyOperations.pushOdooDraft)} (\`money_push_odoo\`). Draft only, never posted; the papers linked as evidence are attached. Idempotent on the bill's ref \`do:tx:<id>\`. Off (403) until the deployment sets MONEY_ODOO_PUSH and lists the Space. Undo: \`do money odoo-undo <id>\`.`
41993
42898
  },
41994
42899
  {
41995
42900
  path: ["money", "odoo-undo"],
41996
42901
  summary: "Take a draft bill back from Odoo while it is still a draft.",
41997
42902
  args: [{ name: "id", required: true, description: "Transaction ULID." }],
41998
42903
  flags: [],
41999
- scopes: ["finance:write"],
42904
+ scopes: [...moneyOperations.undoOdooDraft.scopes],
42000
42905
  returns: "MoneyOdooUndoResult",
42001
- notes: "DELETE /api/v1/money/transactions/{id}/odoo-draft. A bill posted in Odoo meanwhile can't be taken back (409); the transaction then says `posted`."
42906
+ notes: `${routeOf(moneyOperations.undoOdooDraft)}. A bill posted in Odoo meanwhile can't be taken back (409); the transaction then says \`posted\`.`
42002
42907
  },
42003
42908
  {
42004
42909
  path: ["money", "rules"],
42005
42910
  summary: "List the sorting rules, in the order they run.",
42006
42911
  args: [],
42007
42912
  flags: [],
42008
- scopes: ["finance:read"],
42913
+ scopes: [...moneyOperations.listRules.scopes],
42009
42914
  returns: "MoneyRuleView[]",
42010
- notes: "GET /api/v1/money/rules."
42915
+ notes: `${routeOf(moneyOperations.listRules)}.`
42011
42916
  },
42012
42917
  {
42013
42918
  path: ["money", "rule-suggestions"],
42014
42919
  summary: "The rules worth making now: payees sorted the same way by hand, with their goal.",
42015
42920
  args: [],
42016
42921
  flags: [],
42017
- scopes: ["finance:read"],
42922
+ scopes: [...moneyOperations.ruleSuggestions.scopes],
42018
42923
  returns: "MoneyRuleSuggestion[]",
42019
- notes: "GET /api/v1/money/rules/suggestions. Accept one with `do money rule-add --data` (its name, conditions and actions; add actions.link to its goal to count the next ones there)."
42924
+ notes: `${routeOf(moneyOperations.ruleSuggestions)}. Accept one with \`do money rule-add --data\` (its name, conditions and actions; add actions.link to its goal to count the next ones there).`
42020
42925
  },
42021
42926
  {
42022
42927
  path: ["money", "rule"],
42023
42928
  summary: "Show one sorting rule.",
42024
42929
  args: [{ name: "id", required: true, description: "Rule ULID." }],
42025
42930
  flags: [],
42026
- scopes: ["finance:read"],
42931
+ scopes: [...moneyOperations.getRule.scopes],
42027
42932
  returns: "MoneyRuleView",
42028
- notes: "GET /api/v1/money/rules/{id}."
42933
+ notes: `${routeOf(moneyOperations.getRule)}.`
42029
42934
  },
42030
42935
  {
42031
42936
  path: ["money", "rule-add"],
42032
42937
  summary: "Add a sorting rule; it runs over what's waiting right away.",
42033
42938
  args: [],
42034
42939
  flags: [{ name: "data", type: "string", description: "CreateMoneyRuleInput JSON." }],
42035
- scopes: ["finance:write"],
42940
+ scopes: [...moneyOperations.createRule.scopes],
42036
42941
  returns: "MoneyRuleView",
42037
- notes: 'POST /api/v1/money/rules. `do money rule-add --data \'{"name":"Supermarket","conditions":{"counterpartyContains":"supermarkt"},"actions":{"categoryId":"\u2026","markReviewed":true}}\'`. A hand sort is never overwritten.'
42942
+ notes: `${routeOf(moneyOperations.createRule)}. \`do money rule-add --data '{"name":"Supermarket","conditions":{"counterpartyContains":"supermarkt"},"actions":{"categoryId":"\u2026","markReviewed":true}}'\`. A hand sort is never overwritten.`
42038
42943
  },
42039
42944
  {
42040
42945
  path: ["money", "rule-update"],
42041
42946
  summary: "Edit a sorting rule (or switch it off).",
42042
42947
  args: [{ name: "id", required: true, description: "Rule ULID." }],
42043
42948
  flags: [{ name: "data", type: "string", description: "UpdateMoneyRuleInput JSON." }],
42044
- scopes: ["finance:write"],
42949
+ scopes: [...moneyOperations.updateRule.scopes],
42045
42950
  returns: "MoneyRuleView",
42046
- notes: "PATCH /api/v1/money/rules/{id}."
42951
+ notes: `${routeOf(moneyOperations.updateRule)}.`
42047
42952
  },
42048
42953
  {
42049
42954
  path: ["money", "rule-remove"],
@@ -42052,18 +42957,18 @@ var COMMAND_SPECS = [
42052
42957
  flags: [
42053
42958
  { name: "allow-destructive", type: "boolean", description: "Required to actually remove." }
42054
42959
  ],
42055
- scopes: ["finance:write"],
42960
+ scopes: [...moneyOperations.removeRule.scopes],
42056
42961
  returns: "{ ok, removed }",
42057
- notes: "DELETE /api/v1/money/rules/{id}."
42962
+ notes: `${routeOf(moneyOperations.removeRule)}.`
42058
42963
  },
42059
42964
  {
42060
42965
  path: ["money", "rules-apply"],
42061
42966
  summary: "Run the rules and the transfer matching again over what's waiting.",
42062
42967
  args: [],
42063
42968
  flags: [{ name: "account", type: "string", description: "Only this account (ULID)." }],
42064
- scopes: ["finance:write"],
42969
+ scopes: [...moneyOperations.applyRules.scopes],
42065
42970
  returns: "{ sorted, transfers }",
42066
- notes: "POST /api/v1/money/rules/apply."
42971
+ notes: `${routeOf(moneyOperations.applyRules)}.`
42067
42972
  },
42068
42973
  {
42069
42974
  path: ["money", "import"],
@@ -42086,9 +42991,9 @@ var COMMAND_SPECS = [
42086
42991
  { name: "month", type: "string", description: "The month, YYYY-MM (default this month)." },
42087
42992
  { name: "scope", type: "string", description: "all (default) | space | owner." }
42088
42993
  ],
42089
- scopes: ["finance:read"],
42994
+ scopes: [...moneyOperations.budgetMonth.scopes],
42090
42995
  returns: "MoneyBudgetMonth",
42091
- notes: "GET /api/v1/money/budget. Rows (fixed, flexible, saving) with planned, spent so far and lastMonth; a summary with incomeIn, plannedOut, spent, unsorted and notYetPlanned."
42996
+ notes: `${routeOf(moneyOperations.budgetMonth)}. Rows (fixed, flexible, saving) with planned, spent so far and lastMonth; a summary with incomeIn, plannedOut, spent, unsorted and notYetPlanned.`
42092
42997
  },
42093
42998
  {
42094
42999
  path: ["money", "budget-set"],
@@ -42102,9 +43007,9 @@ var COMMAND_SPECS = [
42102
43007
  { name: "mine", type: "boolean", description: "Your own line (only you see it)." },
42103
43008
  { name: "data", type: "string", description: "SetMoneyBudgetLineInput JSON instead." }
42104
43009
  ],
42105
- scopes: ["finance:write"],
43010
+ scopes: [...moneyOperations.setBudgetLine.scopes],
42106
43011
  returns: "MoneyBudgetLine",
42107
- notes: "PUT /api/v1/money/budget/lines. Only fixed, flexible and saving categories are planned. `do money budget-set --category <id> --planned 400`."
43012
+ notes: `${routeOf(moneyOperations.setBudgetLine)}. Only fixed, flexible and saving categories are planned. \`do money budget-set --category <id> --planned 400\`.`
42108
43013
  },
42109
43014
  {
42110
43015
  path: ["money", "budget-remove"],
@@ -42113,9 +43018,9 @@ var COMMAND_SPECS = [
42113
43018
  flags: [
42114
43019
  { name: "allow-destructive", type: "boolean", description: "Required to actually remove." }
42115
43020
  ],
42116
- scopes: ["finance:write"],
43021
+ scopes: [...moneyOperations.removeBudgetLine.scopes],
42117
43022
  returns: "{ ok, removed }",
42118
- notes: "DELETE /api/v1/money/budget/lines/{id}."
43023
+ notes: `${routeOf(moneyOperations.removeBudgetLine)}.`
42119
43024
  },
42120
43025
  {
42121
43026
  path: ["money", "budget-draft"],
@@ -42125,9 +43030,9 @@ var COMMAND_SPECS = [
42125
43030
  { name: "month", type: "string", description: "The month to plan, YYYY-MM." },
42126
43031
  { name: "months", type: "string", description: "How many months back to read (default 3)." }
42127
43032
  ],
42128
- scopes: ["finance:write"],
43033
+ scopes: [...moneyOperations.draftBudget.scopes],
42129
43034
  returns: "{ lines, kept, months }",
42130
- notes: "POST /api/v1/money/budget/draft. Undo by taking the lines out: `do money budget-remove <id>`."
43035
+ notes: `${routeOf(moneyOperations.draftBudget)}. Undo by taking the lines out: \`do money budget-remove <id>\`.`
42131
43036
  },
42132
43037
  {
42133
43038
  path: ["money", "budget-copy"],
@@ -42137,9 +43042,9 @@ var COMMAND_SPECS = [
42137
43042
  { name: "from", type: "string", description: "The month to copy, YYYY-MM." },
42138
43043
  { name: "to", type: "string", description: "The month to fill, YYYY-MM." }
42139
43044
  ],
42140
- scopes: ["finance:write"],
43045
+ scopes: [...moneyOperations.copyBudget.scopes],
42141
43046
  returns: "{ copied, kept }",
42142
- notes: "POST /api/v1/money/budget/copy."
43047
+ notes: `${routeOf(moneyOperations.copyBudget)}.`
42143
43048
  },
42144
43049
  {
42145
43050
  path: ["money", "year"],
@@ -42149,9 +43054,9 @@ var COMMAND_SPECS = [
42149
43054
  { name: "year", type: "string", description: "The calendar year (default this one)." },
42150
43055
  { name: "scope", type: "string", description: "all (default) | space | owner." }
42151
43056
  ],
42152
- scopes: ["finance:read"],
43057
+ scopes: [...moneyOperations.year.scopes],
42153
43058
  returns: "MoneyYear",
42154
- notes: "GET /api/v1/money/year. totals (income, spent, saved) and lastYear (the same months a year earlier, null with nothing booked then); months Jan\u2013Dec; spending and income by category, biggest first, each with lastYear; recurring streams with thisYear, lastYear and change (new \xB7 stopped \xB7 up \xB7 down \xB7 same); goals with what went toward them this year. A year under way counts to this month (through)."
43059
+ notes: `${routeOf(moneyOperations.year)}. totals (income, spent, saved) and lastYear (the same months a year earlier, null with nothing booked then); months Jan\u2013Dec; spending and income by category, biggest first, each with lastYear; recurring streams with thisYear, lastYear and change (new \xB7 stopped \xB7 up \xB7 down \xB7 same); goals with what went toward them this year. A year under way counts to this month (through).`
42155
43060
  },
42156
43061
  {
42157
43062
  path: ["money", "net-worth"],
@@ -42165,9 +43070,9 @@ var COMMAND_SPECS = [
42165
43070
  description: "How many months the line covers (2\u201360, default 12)."
42166
43071
  }
42167
43072
  ],
42168
- scopes: ["finance:read"],
43073
+ scopes: [...moneyOperations.netWorthRead.scopes],
42169
43074
  returns: "MoneyNetWorth",
42170
- notes: "GET /api/v1/money/net-worth. 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."
43075
+ notes: `${routeOf(moneyOperations.netWorthRead)}. 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.`
42171
43076
  },
42172
43077
  {
42173
43078
  path: ["money", "net-worth-set"],
@@ -42178,45 +43083,45 @@ var COMMAND_SPECS = [
42178
43083
  { name: "month", type: "string", description: "The month, YYYY-MM (default this month)." },
42179
43084
  { name: "data", type: "string", description: "SetMoneyNetWorthValueInput JSON instead." }
42180
43085
  ],
42181
- scopes: ["finance:write"],
43086
+ scopes: [...moneyOperations.setNetWorthValue.scopes],
42182
43087
  returns: "MoneySnapshot",
42183
- notes: "PUT /api/v1/money/net-worth/values. A month ahead is refused. This month's value also becomes the account's balance unless a bank link keeps it. `do money net-worth-set <accountId> --value 310000`."
43088
+ notes: `${routeOf(moneyOperations.setNetWorthValue)}. A month ahead is refused. This month's value also becomes the account's balance unless a bank link keeps it. \`do money net-worth-set <accountId> --value 310000\`.`
42184
43089
  },
42185
43090
  {
42186
43091
  path: ["money", "goals"],
42187
43092
  summary: "List money goals: what's saved toward each, soonest first.",
42188
43093
  args: [],
42189
43094
  flags: [],
42190
- scopes: ["finance:read"],
43095
+ scopes: [...moneyOperations.listGoals.scopes],
42191
43096
  returns: "MoneyGoalView[]",
42192
- notes: "GET /api/v1/money/goals."
43097
+ notes: `${routeOf(moneyOperations.listGoals)}.`
42193
43098
  },
42194
43099
  {
42195
43100
  path: ["money", "goal"],
42196
43101
  summary: "Show one money goal with its lines and what went into it.",
42197
43102
  args: [{ name: "id", required: true, description: "Goal ULID." }],
42198
43103
  flags: [],
42199
- scopes: ["finance:read"],
43104
+ scopes: [...moneyOperations.getGoal.scopes],
42200
43105
  returns: "MoneyGoalDetail",
42201
- notes: "GET /api/v1/money/goals/{id}."
43106
+ notes: `${routeOf(moneyOperations.getGoal)}.`
42202
43107
  },
42203
43108
  {
42204
43109
  path: ["money", "goal-add"],
42205
43110
  summary: "Set a money goal (Only me unless --data says visibility: space).",
42206
43111
  args: [],
42207
43112
  flags: [{ name: "data", type: "string", description: "CreateMoneyGoalInput JSON." }],
42208
- scopes: ["finance:write"],
43113
+ scopes: [...moneyOperations.createGoal.scopes],
42209
43114
  returns: "MoneyGoalView",
42210
- notes: 'POST /api/v1/money/goals. `do money goal-add --data \'{"title":"Garden shed","targetAmount":"3000","targetOn":"2027-04-01","monthlyPlan":"200","visibility":"space"}\'`.'
43115
+ notes: `${routeOf(moneyOperations.createGoal)}. \`do money goal-add --data '{"title":"Garden shed","targetAmount":"3000","targetOn":"2027-04-01","monthlyPlan":"200","visibility":"space"}'\`.`
42211
43116
  },
42212
43117
  {
42213
43118
  path: ["money", "goal-update"],
42214
43119
  summary: "Edit a money goal, or mark it reached or paused.",
42215
43120
  args: [{ name: "id", required: true, description: "Goal ULID." }],
42216
43121
  flags: [{ name: "data", type: "string", description: "UpdateMoneyGoalInput JSON." }],
42217
- scopes: ["finance:write"],
43122
+ scopes: [...moneyOperations.updateGoal.scopes],
42218
43123
  returns: "MoneyGoalView",
42219
- notes: "PATCH /api/v1/money/goals/{id}."
43124
+ notes: `${routeOf(moneyOperations.updateGoal)}.`
42220
43125
  },
42221
43126
  {
42222
43127
  path: ["money", "goal-remove"],
@@ -42225,27 +43130,27 @@ var COMMAND_SPECS = [
42225
43130
  flags: [
42226
43131
  { name: "allow-destructive", type: "boolean", description: "Required to actually remove." }
42227
43132
  ],
42228
- scopes: ["finance:write"],
43133
+ scopes: [...moneyOperations.removeGoal.scopes],
42229
43134
  returns: "{ ok, removed }",
42230
- notes: "DELETE /api/v1/money/goals/{id}. The money and its transactions stay."
43135
+ notes: `${routeOf(moneyOperations.removeGoal)}. The money and its transactions stay.`
42231
43136
  },
42232
43137
  {
42233
43138
  path: ["money", "goal-line-add"],
42234
43139
  summary: "Add a line of what a goal pays for (planned, paid).",
42235
43140
  args: [{ name: "id", required: true, description: "Goal ULID." }],
42236
43141
  flags: [{ name: "data", type: "string", description: "CreateMoneyGoalLineInput JSON." }],
42237
- scopes: ["finance:write"],
43142
+ scopes: [...moneyOperations.addGoalLine.scopes],
42238
43143
  returns: "MoneyGoalLine",
42239
- notes: 'POST /api/v1/money/goals/{id}/lines. `do money goal-line-add <goal-id> --data \'{"title":"Venue","planned":"5200","paid":"1500"}\'`.'
43144
+ notes: `${routeOf(moneyOperations.addGoalLine)}. \`do money goal-line-add <goal-id> --data '{"title":"Venue","planned":"5200","paid":"1500"}'\`.`
42240
43145
  },
42241
43146
  {
42242
43147
  path: ["money", "goal-line-update"],
42243
43148
  summary: "Edit a goal's line (what's planned or paid, its paper in Admin via documentId).",
42244
43149
  args: [{ name: "id", required: true, description: "Line ULID." }],
42245
43150
  flags: [{ name: "data", type: "string", description: "UpdateMoneyGoalLineInput JSON." }],
42246
- scopes: ["finance:write"],
43151
+ scopes: [...moneyOperations.updateGoalLine.scopes],
42247
43152
  returns: "MoneyGoalLine",
42248
- notes: "PATCH /api/v1/money/goals/lines/{lineId}."
43153
+ notes: `${routeOf(moneyOperations.updateGoalLine)}.`
42249
43154
  },
42250
43155
  {
42251
43156
  path: ["money", "goal-line-remove"],
@@ -42254,9 +43159,9 @@ var COMMAND_SPECS = [
42254
43159
  flags: [
42255
43160
  { name: "allow-destructive", type: "boolean", description: "Required to actually remove." }
42256
43161
  ],
42257
- scopes: ["finance:write"],
43162
+ scopes: [...moneyOperations.removeGoalLine.scopes],
42258
43163
  returns: "{ ok, removed }",
42259
- notes: "DELETE /api/v1/money/goals/lines/{lineId}."
43164
+ notes: `${routeOf(moneyOperations.removeGoalLine)}.`
42260
43165
  },
42261
43166
  {
42262
43167
  path: ["money", "goal-count"],
@@ -42266,9 +43171,9 @@ var COMMAND_SPECS = [
42266
43171
  { name: "transaction", required: true, description: "Transaction ULID." }
42267
43172
  ],
42268
43173
  flags: [],
42269
- scopes: ["finance:write"],
43174
+ scopes: [...moneyOperations.addGoalContribution.scopes],
42270
43175
  returns: "MoneyGoalView",
42271
- notes: "POST /api/v1/money/goals/{id}/contributions."
43176
+ notes: `${routeOf(moneyOperations.addGoalContribution)}.`
42272
43177
  },
42273
43178
  {
42274
43179
  path: ["money", "goal-uncount"],
@@ -42278,36 +43183,36 @@ var COMMAND_SPECS = [
42278
43183
  { name: "transaction", required: true, description: "Transaction ULID." }
42279
43184
  ],
42280
43185
  flags: [],
42281
- scopes: ["finance:write"],
43186
+ scopes: [...moneyOperations.removeGoalContribution.scopes],
42282
43187
  returns: "MoneyGoalView",
42283
- notes: "DELETE /api/v1/money/goals/{id}/contributions/{transactionId}."
43188
+ notes: `${routeOf(moneyOperations.removeGoalContribution)}.`
42284
43189
  },
42285
43190
  {
42286
43191
  path: ["money", "recurring"],
42287
43192
  summary: "Recurring payments: the streams, what the ledger shows untracked, price changes, the year's fixed costs.",
42288
43193
  args: [],
42289
43194
  flags: [],
42290
- scopes: ["finance:read"],
43195
+ scopes: [...moneyOperations.recurringOverview.scopes],
42291
43196
  returns: "MoneyRecurringOverview",
42292
- notes: "GET /api/v1/money/recurring. `found` is read over the whole ledger, across accounts (grouped by counterparty IBAN, name or the bank line's payee, split by amount); each has `kind`: subscription, or standing_order for money moved to an own account."
43197
+ notes: `${routeOf(moneyOperations.recurringOverview)}. \`found\` is read over the whole ledger, across accounts (grouped by counterparty IBAN, name or the bank line's payee, split by amount); each has \`kind\`: subscription, or standing_order for money moved to an own account.`
42293
43198
  },
42294
43199
  {
42295
43200
  path: ["money", "recurring-add"],
42296
43201
  summary: "Track a recurring payment (Only me unless --data says visibility: space).",
42297
43202
  args: [],
42298
43203
  flags: [{ name: "data", type: "string", description: "CreateMoneyRecurringInput JSON." }],
42299
- scopes: ["finance:write"],
43204
+ scopes: [...moneyOperations.createRecurring.scopes],
42300
43205
  returns: "MoneyRecurringView",
42301
- notes: 'POST /api/v1/money/recurring. `do money recurring-add --data \'{"title":"Energy","cadence":"monthly","expectedAmount":"148","matchText":"Bright Energy","visibility":"space"}\'`. `"status":"ignored"` calls a found pattern a one-off.'
43206
+ notes: `${routeOf(moneyOperations.createRecurring)}. \`do money recurring-add --data '{"title":"Energy","cadence":"monthly","expectedAmount":"148","matchText":"Bright Energy","visibility":"space"}'\`. \`"status":"ignored"\` calls a found pattern a one-off.`
42302
43207
  },
42303
43208
  {
42304
43209
  path: ["money", "recurring-update"],
42305
43210
  summary: "Edit a recurring payment (amount, next day, paper, pick, status).",
42306
43211
  args: [{ name: "id", required: true, description: "Stream ULID." }],
42307
43212
  flags: [{ name: "data", type: "string", description: "UpdateMoneyRecurringInput JSON." }],
42308
- scopes: ["finance:write"],
43213
+ scopes: [...moneyOperations.updateRecurring.scopes],
42309
43214
  returns: "MoneyRecurringView",
42310
- notes: "PATCH /api/v1/money/recurring/{id}."
43215
+ notes: `${routeOf(moneyOperations.updateRecurring)}.`
42311
43216
  },
42312
43217
  {
42313
43218
  path: ["money", "recurring-remove"],
@@ -42316,18 +43221,18 @@ var COMMAND_SPECS = [
42316
43221
  flags: [
42317
43222
  { name: "allow-destructive", type: "boolean", description: "Required to actually remove." }
42318
43223
  ],
42319
- scopes: ["finance:write"],
43224
+ scopes: [...moneyOperations.removeRecurring.scopes],
42320
43225
  returns: "{ ok, removed }",
42321
- notes: "DELETE /api/v1/money/recurring/{id}. Its payments stay."
43226
+ notes: `${routeOf(moneyOperations.removeRecurring)}. Its payments stay.`
42322
43227
  },
42323
43228
  {
42324
43229
  path: ["money", "stream"],
42325
43230
  summary: "Show one recurring payment, with its paper's soonest key date.",
42326
43231
  args: [{ name: "id", required: true, description: "Stream ULID." }],
42327
43232
  flags: [],
42328
- scopes: ["finance:read"],
43233
+ scopes: [...moneyOperations.getRecurring.scopes],
42329
43234
  returns: "MoneyRecurringView",
42330
- notes: "GET /api/v1/money/recurring/{id}."
43235
+ notes: `${routeOf(moneyOperations.getRecurring)}.`
42331
43236
  },
42332
43237
  {
42333
43238
  path: ["animals", "overview"],
@@ -45219,14 +46124,17 @@ var HANDLERS = {
45219
46124
  return { data: { ok: true, unlinked: node }, render: renderRecord };
45220
46125
  },
45221
46126
  "files list": async ({ flags, requireClient }) => ({
45222
- data: await requireClient().get("/files", {
46127
+ data: await requireClient().get(pathOf(fileOperations.list), {
45223
46128
  query: { nodeId: requireFlag(flags, "node", "files list needs --node <node id>") }
45224
46129
  }),
45225
46130
  render: renderList
45226
46131
  }),
45227
46132
  "files get": async ({ args, requireClient }) => {
45228
46133
  const id = requireArg(args, 0, "file id");
45229
- return { data: await requireClient().get(`/files/${id}`), render: renderRecord };
46134
+ return {
46135
+ data: await requireClient().get(pathOf(fileOperations.get, { id })),
46136
+ render: renderRecord
46137
+ };
45230
46138
  },
45231
46139
  "files upload": async ({ args, flags, requireClient }) => {
45232
46140
  const path = requireArg(args, 0, "path");
@@ -45274,14 +46182,14 @@ var HANDLERS = {
45274
46182
  "files view-url": async ({ args, requireClient }) => {
45275
46183
  const id = requireArg(args, 0, "file id");
45276
46184
  const client = requireClient();
45277
- const link = await client.post(`/files/${id}/view-url`, {});
46185
+ const link = await client.post(pathOf(fileOperations.viewUrl, { id }), {});
45278
46186
  const url = new URL(link.path, client.url("/")).toString();
45279
46187
  return { data: { id, url, expiresAt: link.expiresAt }, render: () => url };
45280
46188
  },
45281
46189
  "files delete": async ({ args, flags, requireClient }) => {
45282
46190
  const id = requireArg(args, 0, "file id");
45283
46191
  assertDestructiveAllowed(flags, `delete file ${id}`);
45284
- await requireClient().delete(`/files/${id}`);
46192
+ await requireClient().delete(pathOf(fileOperations.remove, { id }));
45285
46193
  return { data: { ok: true, deleted: id }, render: renderRecord };
45286
46194
  },
45287
46195
  "foods search": async ({ args, flags, requireClient }) => {
@@ -46662,33 +47570,39 @@ var HANDLERS = {
46662
47570
  };
46663
47571
  },
46664
47572
  "money accounts": async ({ requireClient }) => ({
46665
- data: await requireClient().get("/money/accounts"),
47573
+ data: await requireClient().get(pathOf(moneyOperations.listAccounts)),
46666
47574
  render: renderList
46667
47575
  }),
46668
47576
  "money account": async ({ args, requireClient }) => {
46669
47577
  const id = requireArg(args, 0, "account id");
46670
- return { data: await requireClient().get(`/money/accounts/${id}`), render: renderRecord };
47578
+ return {
47579
+ data: await requireClient().get(pathOf(moneyOperations.getAccount, { id })),
47580
+ render: renderRecord
47581
+ };
46671
47582
  },
46672
47583
  "money account-add": async ({ flags, requireClient }) => {
46673
47584
  const body = parseInput(createMoneyAccountInputSchema, { id: ulid3(), ...readDataFlag(flags) });
46674
- return { data: await requireClient().post("/money/accounts", body), render: renderRecord };
47585
+ return {
47586
+ data: await requireClient().post(pathOf(moneyOperations.createAccount), body),
47587
+ render: renderRecord
47588
+ };
46675
47589
  },
46676
47590
  "money account-update": async ({ args, flags, requireClient }) => {
46677
47591
  const id = requireArg(args, 0, "account id");
46678
47592
  const body = parseInput(updateMoneyAccountInputSchema, readDataFlag(flags));
46679
47593
  return {
46680
- data: await requireClient().patch(`/money/accounts/${id}`, body),
47594
+ data: await requireClient().patch(pathOf(moneyOperations.updateAccount, { id }), body),
46681
47595
  render: renderRecord
46682
47596
  };
46683
47597
  },
46684
47598
  "money account-close": async ({ args, flags, requireClient }) => {
46685
47599
  const id = requireArg(args, 0, "account id");
46686
47600
  assertDestructiveAllowed(flags, `close account ${id}`);
46687
- await requireClient().delete(`/money/accounts/${id}`);
47601
+ await requireClient().delete(pathOf(moneyOperations.removeAccount, { id }));
46688
47602
  return { data: { ok: true, closed: id }, render: renderRecord };
46689
47603
  },
46690
47604
  "money connections": async ({ requireClient }) => ({
46691
- data: await requireClient().get("/money/connections"),
47605
+ data: await requireClient().get(pathOf(moneyOperations.listConnections)),
46692
47606
  render: renderList
46693
47607
  }),
46694
47608
  "money sync": async ({ flags, requireClient }) => {
@@ -46696,27 +47610,30 @@ var HANDLERS = {
46696
47610
  ...typeof flags.account === "string" ? { accountId: flags.account } : {}
46697
47611
  });
46698
47612
  return {
46699
- data: await requireClient().post("/money/connections/sync", body),
47613
+ data: await requireClient().post(pathOf(moneyOperations.syncConnections), body),
46700
47614
  render: renderRecord
46701
47615
  };
46702
47616
  },
46703
47617
  "money connection-syncs": async ({ args, requireClient }) => {
46704
47618
  const id = requireArg(args, 0, "id");
46705
47619
  return {
46706
- data: await requireClient().get(`/money/connections/${id}/syncs`),
47620
+ data: await requireClient().get(pathOf(moneyOperations.listConnectionSyncs, { id })),
46707
47621
  render: renderList
46708
47622
  };
46709
47623
  },
46710
47624
  "money categories": async ({ requireClient }) => ({
46711
- data: await requireClient().get("/money/categories"),
47625
+ data: await requireClient().get(pathOf(moneyOperations.listCategories)),
46712
47626
  render: renderList
46713
47627
  }),
46714
47628
  "money category-add": async ({ flags, requireClient }) => {
46715
47629
  const body = parseInput(createMoneyCategoryInputSchema, { id: ulid3(), ...readDataFlag(flags) });
46716
- return { data: await requireClient().post("/money/categories", body), render: renderRecord };
47630
+ return {
47631
+ data: await requireClient().post(pathOf(moneyOperations.createCategory), body),
47632
+ render: renderRecord
47633
+ };
46717
47634
  },
46718
47635
  "money transactions": async ({ flags, requireClient }) => ({
46719
- data: await requireClient().get("/money/transactions", {
47636
+ data: await requireClient().get(pathOf(moneyOperations.listTransactions), {
46720
47637
  query: {
46721
47638
  accountId: flagString(flags, "account"),
46722
47639
  status: flagString(flags, "status"),
@@ -46735,17 +47652,23 @@ var HANDLERS = {
46735
47652
  }),
46736
47653
  "money transaction": async ({ args, requireClient }) => {
46737
47654
  const id = requireArg(args, 0, "transaction id");
46738
- return { data: await requireClient().get(`/money/transactions/${id}`), render: renderRecord };
47655
+ return {
47656
+ data: await requireClient().get(pathOf(moneyOperations.getTransaction, { id })),
47657
+ render: renderRecord
47658
+ };
46739
47659
  },
46740
47660
  "money book": async ({ flags, requireClient }) => {
46741
47661
  const body = parseInput(createMoneyTransactionInputSchema, {
46742
47662
  id: ulid3(),
46743
47663
  ...readDataFlag(flags)
46744
47664
  });
46745
- return { data: await requireClient().post("/money/transactions", body), render: renderRecord };
47665
+ return {
47666
+ data: await requireClient().post(pathOf(moneyOperations.createTransaction), body),
47667
+ render: renderRecord
47668
+ };
46746
47669
  },
46747
47670
  "money review": async ({ flags, requireClient }) => ({
46748
- data: await requireClient().get("/money/review", {
47671
+ data: await requireClient().get(pathOf(moneyOperations.review), {
46749
47672
  query: { accountId: flagString(flags, "account"), limit: flagString(flags, "limit") }
46750
47673
  }),
46751
47674
  render: renderRecord
@@ -46767,16 +47690,22 @@ var HANDLERS = {
46767
47690
  }
46768
47691
  ]
46769
47692
  });
46770
- return { data: await requireClient().post("/money/sort", body), render: renderRecord };
47693
+ return {
47694
+ data: await requireClient().post(pathOf(moneyOperations.sort), body),
47695
+ render: renderRecord
47696
+ };
46771
47697
  },
46772
47698
  "money sort-undo": async ({ flags, requireClient }) => {
46773
47699
  const body = parseInput(moneySortUndoInputSchema, readDataFlag(flags));
46774
- return { data: await requireClient().post("/money/sort/undo", body), render: renderRecord };
47700
+ return {
47701
+ data: await requireClient().post(pathOf(moneyOperations.undoSort), body),
47702
+ render: renderRecord
47703
+ };
46775
47704
  },
46776
47705
  "money splits": async ({ args, requireClient }) => {
46777
47706
  const id = requireArg(args, 0, "transaction id");
46778
47707
  return {
46779
- data: await requireClient().get(`/money/transactions/${id}/splits`),
47708
+ data: await requireClient().get(pathOf(moneyOperations.listSplits, { id })),
46780
47709
  render: renderList
46781
47710
  };
46782
47711
  },
@@ -46784,7 +47713,7 @@ var HANDLERS = {
46784
47713
  const id = requireArg(args, 0, "transaction id");
46785
47714
  const body = parseInput(setMoneySplitsInputSchema, readDataFlag(flags));
46786
47715
  return {
46787
- data: await requireClient().put(`/money/transactions/${id}/splits`, body),
47716
+ data: await requireClient().put(pathOf(moneyOperations.setSplits, { id }), body),
46788
47717
  render: renderRecord
46789
47718
  };
46790
47719
  },
@@ -46798,12 +47727,12 @@ var HANDLERS = {
46798
47727
  note: flagString(flags, "note")
46799
47728
  });
46800
47729
  return {
46801
- data: await requireClient().post(`/money/transactions/${id}/share`, body),
47730
+ data: await requireClient().post(pathOf(moneyOperations.share, { id }), body),
46802
47731
  render: renderRecord
46803
47732
  };
46804
47733
  },
46805
47734
  "money settle-up": async ({ requireClient }) => ({
46806
- data: await requireClient().get("/money/settle-up"),
47735
+ data: await requireClient().get(pathOf(moneyOperations.settleUp)),
46807
47736
  render: renderRecord
46808
47737
  }),
46809
47738
  "money settle": async ({ flags, requireClient }) => {
@@ -46820,18 +47749,21 @@ var HANDLERS = {
46820
47749
  transactionId: flagString(flags, "transaction"),
46821
47750
  note: flagString(flags, "note")
46822
47751
  });
46823
- return { data: await requireClient().post("/money/settlements", body), render: renderRecord };
47752
+ return {
47753
+ data: await requireClient().post(pathOf(moneyOperations.settle), body),
47754
+ render: renderRecord
47755
+ };
46824
47756
  },
46825
47757
  "money settlement-remove": async ({ args, flags, requireClient }) => {
46826
47758
  const id = requireArg(args, 0, "settlement id");
46827
47759
  assertDestructiveAllowed(flags, `remove settlement ${id}`);
46828
- await requireClient().delete(`/money/settlements/${id}`);
47760
+ await requireClient().delete(pathOf(moneyOperations.removeSettlement, { id }));
46829
47761
  return { data: { ok: true, removed: id }, render: renderRecord };
46830
47762
  },
46831
47763
  "money tax-evidence": async ({ flags, requireClient }) => {
46832
47764
  const year = requireFlag(flags, "year", "money tax-evidence needs --year <YYYY>");
46833
47765
  return {
46834
- data: await requireClient().get("/money/tax-evidence", {
47766
+ data: await requireClient().get(pathOf(moneyOperations.taxEvidence), {
46835
47767
  query: { year, tag: flagString(flags, "tag") }
46836
47768
  }),
46837
47769
  render: renderRecord
@@ -46841,48 +47773,57 @@ var HANDLERS = {
46841
47773
  const id = requireArg(args, 0, "transaction id");
46842
47774
  const body = parseInput(moneyOdooPushInputSchema, { note: flagString(flags, "note") });
46843
47775
  return {
46844
- data: await requireClient().post(`/money/transactions/${id}/odoo-draft`, body),
47776
+ data: await requireClient().post(pathOf(moneyOperations.pushOdooDraft, { id }), body),
46845
47777
  render: renderRecord
46846
47778
  };
46847
47779
  },
46848
47780
  "money odoo-undo": async ({ args, requireClient }) => {
46849
47781
  const id = requireArg(args, 0, "transaction id");
46850
47782
  return {
46851
- data: await requireClient().delete(`/money/transactions/${id}/odoo-draft`),
47783
+ data: await requireClient().delete(pathOf(moneyOperations.undoOdooDraft, { id })),
46852
47784
  render: renderRecord
46853
47785
  };
46854
47786
  },
46855
47787
  "money rules": async ({ requireClient }) => ({
46856
- data: await requireClient().get("/money/rules"),
47788
+ data: await requireClient().get(pathOf(moneyOperations.listRules)),
46857
47789
  render: renderList
46858
47790
  }),
46859
47791
  "money rule-suggestions": async ({ requireClient }) => ({
46860
- data: await requireClient().get("/money/rules/suggestions"),
47792
+ data: await requireClient().get(pathOf(moneyOperations.ruleSuggestions)),
46861
47793
  render: renderList
46862
47794
  }),
46863
47795
  "money rule": async ({ args, requireClient }) => {
46864
47796
  const id = requireArg(args, 0, "rule id");
46865
- return { data: await requireClient().get(`/money/rules/${id}`), render: renderRecord };
47797
+ return {
47798
+ data: await requireClient().get(pathOf(moneyOperations.getRule, { id })),
47799
+ render: renderRecord
47800
+ };
46866
47801
  },
46867
47802
  "money rule-add": async ({ flags, requireClient }) => {
46868
47803
  const body = parseInput(createMoneyRuleInputSchema, { id: ulid3(), ...readDataFlag(flags) });
46869
- return { data: await requireClient().post("/money/rules", body), render: renderRecord };
47804
+ return {
47805
+ data: await requireClient().post(pathOf(moneyOperations.createRule), body),
47806
+ render: renderRecord
47807
+ };
46870
47808
  },
46871
47809
  "money rule-update": async ({ args, flags, requireClient }) => {
46872
47810
  const id = requireArg(args, 0, "rule id");
46873
47811
  const body = parseInput(updateMoneyRuleInputSchema, readDataFlag(flags));
46874
- return { data: await requireClient().patch(`/money/rules/${id}`, body), render: renderRecord };
47812
+ return {
47813
+ data: await requireClient().patch(pathOf(moneyOperations.updateRule, { id }), body),
47814
+ render: renderRecord
47815
+ };
46875
47816
  },
46876
47817
  "money rule-remove": async ({ args, flags, requireClient }) => {
46877
47818
  const id = requireArg(args, 0, "rule id");
46878
47819
  assertDestructiveAllowed(flags, `remove rule ${id}`);
46879
- await requireClient().delete(`/money/rules/${id}`);
47820
+ await requireClient().delete(pathOf(moneyOperations.removeRule, { id }));
46880
47821
  return { data: { ok: true, removed: id }, render: renderRecord };
46881
47822
  },
46882
47823
  "money rules-apply": async ({ flags, requireClient }) => {
46883
47824
  const account = flagString(flags, "account");
46884
47825
  return {
46885
- data: await requireClient().post("/money/rules/apply", account ? { accountId: account } : {}),
47826
+ data: await requireClient().post(pathOf(moneyOperations.applyRules), account ? { accountId: account } : {}),
46886
47827
  render: renderRecord
46887
47828
  };
46888
47829
  },
@@ -46906,13 +47847,13 @@ var HANDLERS = {
46906
47847
  return { data: await requireClient().upload(route, form), render: renderRecord };
46907
47848
  },
46908
47849
  "money year": async ({ flags, requireClient }) => ({
46909
- data: await requireClient().get("/money/year", {
47850
+ data: await requireClient().get(pathOf(moneyOperations.year), {
46910
47851
  query: { year: flagString(flags, "year"), scope: flagString(flags, "scope") }
46911
47852
  }),
46912
47853
  render: renderRecord
46913
47854
  }),
46914
47855
  "money budget": async ({ flags, io, requireClient }) => ({
46915
- data: await requireClient().get("/money/budget", {
47856
+ data: await requireClient().get(pathOf(moneyOperations.budgetMonth), {
46916
47857
  query: {
46917
47858
  month: flagString(flags, "month") ?? localMonth(io.now()),
46918
47859
  scope: flagString(flags, "scope")
@@ -46928,12 +47869,15 @@ var HANDLERS = {
46928
47869
  note: flagString(flags, "note"),
46929
47870
  mine: flagBool(flags, "mine") || undefined
46930
47871
  });
46931
- return { data: await requireClient().put("/money/budget/lines", body), render: renderRecord };
47872
+ return {
47873
+ data: await requireClient().put(pathOf(moneyOperations.setBudgetLine), body),
47874
+ render: renderRecord
47875
+ };
46932
47876
  },
46933
47877
  "money budget-remove": async ({ args, flags, requireClient }) => {
46934
47878
  const id = requireArg(args, 0, "plan line id");
46935
47879
  assertDestructiveAllowed(flags, `remove plan line ${id}`);
46936
- await requireClient().delete(`/money/budget/lines/${id}`);
47880
+ await requireClient().delete(pathOf(moneyOperations.removeBudgetLine, { id }));
46937
47881
  return { data: { ok: true, removed: id }, render: renderRecord };
46938
47882
  },
46939
47883
  "money budget-draft": async ({ flags, requireClient }) => {
@@ -46942,17 +47886,23 @@ var HANDLERS = {
46942
47886
  month: requireFlag(flags, "month", "money budget-draft needs --month <YYYY-MM>"),
46943
47887
  ...months ? { months: Number(months) } : {}
46944
47888
  });
46945
- return { data: await requireClient().post("/money/budget/draft", body), render: renderRecord };
47889
+ return {
47890
+ data: await requireClient().post(pathOf(moneyOperations.draftBudget), body),
47891
+ render: renderRecord
47892
+ };
46946
47893
  },
46947
47894
  "money budget-copy": async ({ flags, requireClient }) => {
46948
47895
  const body = parseInput(copyMoneyBudgetInputSchema, {
46949
47896
  from: requireFlag(flags, "from", "money budget-copy needs --from <YYYY-MM>"),
46950
47897
  to: requireFlag(flags, "to", "money budget-copy needs --to <YYYY-MM>")
46951
47898
  });
46952
- return { data: await requireClient().post("/money/budget/copy", body), render: renderRecord };
47899
+ return {
47900
+ data: await requireClient().post(pathOf(moneyOperations.copyBudget), body),
47901
+ render: renderRecord
47902
+ };
46953
47903
  },
46954
47904
  "money net-worth": async ({ flags, requireClient }) => ({
46955
- data: await requireClient().get("/money/net-worth", {
47905
+ data: await requireClient().get(pathOf(moneyOperations.netWorthRead), {
46956
47906
  query: { scope: flagString(flags, "scope"), months: flagString(flags, "months") }
46957
47907
  }),
46958
47908
  render: renderRecord
@@ -46964,38 +47914,47 @@ var HANDLERS = {
46964
47914
  month: flagString(flags, "month")
46965
47915
  });
46966
47916
  return {
46967
- data: await requireClient().put("/money/net-worth/values", body),
47917
+ data: await requireClient().put(pathOf(moneyOperations.setNetWorthValue), body),
46968
47918
  render: renderRecord
46969
47919
  };
46970
47920
  },
46971
47921
  "money goals": async ({ requireClient }) => ({
46972
- data: await requireClient().get("/money/goals"),
47922
+ data: await requireClient().get(pathOf(moneyOperations.listGoals)),
46973
47923
  render: renderList
46974
47924
  }),
46975
47925
  "money goal": async ({ args, requireClient }) => {
46976
47926
  const id = requireArg(args, 0, "goal id");
46977
- return { data: await requireClient().get(`/money/goals/${id}`), render: renderRecord };
47927
+ return {
47928
+ data: await requireClient().get(pathOf(moneyOperations.getGoal, { id })),
47929
+ render: renderRecord
47930
+ };
46978
47931
  },
46979
47932
  "money goal-add": async ({ flags, requireClient }) => {
46980
47933
  const body = parseInput(createMoneyGoalInputSchema, { id: ulid3(), ...readDataFlag(flags) });
46981
- return { data: await requireClient().post("/money/goals", body), render: renderRecord };
47934
+ return {
47935
+ data: await requireClient().post(pathOf(moneyOperations.createGoal), body),
47936
+ render: renderRecord
47937
+ };
46982
47938
  },
46983
47939
  "money goal-update": async ({ args, flags, requireClient }) => {
46984
47940
  const id = requireArg(args, 0, "goal id");
46985
47941
  const body = parseInput(updateMoneyGoalInputSchema, readDataFlag(flags));
46986
- return { data: await requireClient().patch(`/money/goals/${id}`, body), render: renderRecord };
47942
+ return {
47943
+ data: await requireClient().patch(pathOf(moneyOperations.updateGoal, { id }), body),
47944
+ render: renderRecord
47945
+ };
46987
47946
  },
46988
47947
  "money goal-remove": async ({ args, flags, requireClient }) => {
46989
47948
  const id = requireArg(args, 0, "goal id");
46990
47949
  assertDestructiveAllowed(flags, `remove goal ${id}`);
46991
- await requireClient().delete(`/money/goals/${id}`);
47950
+ await requireClient().delete(pathOf(moneyOperations.removeGoal, { id }));
46992
47951
  return { data: { ok: true, removed: id }, render: renderRecord };
46993
47952
  },
46994
47953
  "money goal-line-add": async ({ args, flags, requireClient }) => {
46995
47954
  const id = requireArg(args, 0, "goal id");
46996
47955
  const body = parseInput(createMoneyGoalLineInputSchema, { id: ulid3(), ...readDataFlag(flags) });
46997
47956
  return {
46998
- data: await requireClient().post(`/money/goals/${id}/lines`, body),
47957
+ data: await requireClient().post(pathOf(moneyOperations.addGoalLine, { id }), body),
46999
47958
  render: renderRecord
47000
47959
  };
47001
47960
  },
@@ -47003,14 +47962,14 @@ var HANDLERS = {
47003
47962
  const id = requireArg(args, 0, "line id");
47004
47963
  const body = parseInput(updateMoneyGoalLineInputSchema, readDataFlag(flags));
47005
47964
  return {
47006
- data: await requireClient().patch(`/money/goals/lines/${id}`, body),
47965
+ data: await requireClient().patch(pathOf(moneyOperations.updateGoalLine, { lineId: id }), body),
47007
47966
  render: renderRecord
47008
47967
  };
47009
47968
  },
47010
47969
  "money goal-line-remove": async ({ args, flags, requireClient }) => {
47011
47970
  const id = requireArg(args, 0, "line id");
47012
47971
  assertDestructiveAllowed(flags, `remove goal line ${id}`);
47013
- await requireClient().delete(`/money/goals/lines/${id}`);
47972
+ await requireClient().delete(pathOf(moneyOperations.removeGoalLine, { lineId: id }));
47014
47973
  return { data: { ok: true, removed: id }, render: renderRecord };
47015
47974
  },
47016
47975
  "money goal-count": async ({ args, requireClient }) => {
@@ -47019,7 +47978,7 @@ var HANDLERS = {
47019
47978
  transactionId: requireArg(args, 1, "transaction id")
47020
47979
  });
47021
47980
  return {
47022
- data: await requireClient().post(`/money/goals/${id}/contributions`, body),
47981
+ data: await requireClient().post(pathOf(moneyOperations.addGoalContribution, { id }), body),
47023
47982
  render: renderRecord
47024
47983
  };
47025
47984
  },
@@ -47027,37 +47986,43 @@ var HANDLERS = {
47027
47986
  const id = requireArg(args, 0, "goal id");
47028
47987
  const tx = requireArg(args, 1, "transaction id");
47029
47988
  return {
47030
- data: await requireClient().delete(`/money/goals/${id}/contributions/${tx}`),
47989
+ data: await requireClient().delete(pathOf(moneyOperations.removeGoalContribution, { id, transactionId: tx })),
47031
47990
  render: renderRecord
47032
47991
  };
47033
47992
  },
47034
47993
  "money recurring": async ({ requireClient }) => ({
47035
- data: await requireClient().get("/money/recurring"),
47994
+ data: await requireClient().get(pathOf(moneyOperations.recurringOverview)),
47036
47995
  render: renderRecord
47037
47996
  }),
47038
47997
  "money stream": async ({ args, requireClient }) => {
47039
47998
  const id = requireArg(args, 0, "stream id");
47040
- return { data: await requireClient().get(`/money/recurring/${id}`), render: renderRecord };
47999
+ return {
48000
+ data: await requireClient().get(pathOf(moneyOperations.getRecurring, { id })),
48001
+ render: renderRecord
48002
+ };
47041
48003
  },
47042
48004
  "money recurring-add": async ({ flags, requireClient }) => {
47043
48005
  const body = parseInput(createMoneyRecurringInputSchema, {
47044
48006
  id: ulid3(),
47045
48007
  ...readDataFlag(flags)
47046
48008
  });
47047
- return { data: await requireClient().post("/money/recurring", body), render: renderRecord };
48009
+ return {
48010
+ data: await requireClient().post(pathOf(moneyOperations.createRecurring), body),
48011
+ render: renderRecord
48012
+ };
47048
48013
  },
47049
48014
  "money recurring-update": async ({ args, flags, requireClient }) => {
47050
48015
  const id = requireArg(args, 0, "stream id");
47051
48016
  const body = parseInput(updateMoneyRecurringInputSchema, readDataFlag(flags));
47052
48017
  return {
47053
- data: await requireClient().patch(`/money/recurring/${id}`, body),
48018
+ data: await requireClient().patch(pathOf(moneyOperations.updateRecurring, { id }), body),
47054
48019
  render: renderRecord
47055
48020
  };
47056
48021
  },
47057
48022
  "money recurring-remove": async ({ args, flags, requireClient }) => {
47058
48023
  const id = requireArg(args, 0, "stream id");
47059
48024
  assertDestructiveAllowed(flags, `stop tracking ${id}`);
47060
- await requireClient().delete(`/money/recurring/${id}`);
48025
+ await requireClient().delete(pathOf(moneyOperations.removeRecurring, { id }));
47061
48026
  return { data: { ok: true, removed: id }, render: renderRecord };
47062
48027
  },
47063
48028
  "animals overview": async ({ requireClient }) => ({