@frihet/mcp-server 1.17.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +84 -59
  2. package/dist/agent-onboarding.d.ts +1 -1
  3. package/dist/agent-onboarding.js +3 -3
  4. package/dist/canary/mcp-harness.d.ts +1 -1
  5. package/dist/canary/mcp-harness.js +4 -1
  6. package/dist/capability-truth.js +20 -12
  7. package/dist/claude-candidate-contract.d.ts +482 -0
  8. package/dist/claude-candidate-contract.js +87 -0
  9. package/dist/claude-output-privacy.d.ts +17 -0
  10. package/dist/claude-output-privacy.js +240 -0
  11. package/dist/claude-profile.d.ts +18 -0
  12. package/dist/claude-profile.js +260 -0
  13. package/dist/client-interface.d.ts +26 -20
  14. package/dist/client.d.ts +65 -29
  15. package/dist/client.js +278 -167
  16. package/dist/demo-client.d.ts +27 -21
  17. package/dist/demo-client.js +31 -14
  18. package/dist/deposit-contract.d.ts +29 -0
  19. package/dist/deposit-contract.js +76 -0
  20. package/dist/fetch-no-redirect.d.ts +3 -0
  21. package/dist/fetch-no-redirect.js +16 -0
  22. package/dist/fiscal-aliases.d.ts +2 -2
  23. package/dist/fiscal-aliases.js +2 -2
  24. package/dist/fiscal-period.d.ts +26 -0
  25. package/dist/fiscal-period.js +31 -0
  26. package/dist/google-profile.d.ts +19 -0
  27. package/dist/google-profile.js +122 -0
  28. package/dist/index.js +31 -7
  29. package/dist/logger.d.ts +1 -1
  30. package/dist/logger.js +2 -2
  31. package/dist/muse-profile.d.ts +19 -0
  32. package/dist/muse-profile.js +122 -0
  33. package/dist/observability.js +3 -2
  34. package/dist/openai-profile.d.ts +2 -2
  35. package/dist/openai-profile.js +37 -11
  36. package/dist/openai-review-contract.d.ts +1 -1
  37. package/dist/openai-review-contract.js +4 -5
  38. package/dist/openai-review-oauth.d.ts +107 -7
  39. package/dist/openai-review-oauth.js +161 -17
  40. package/dist/prompts/register-all.js +32 -78
  41. package/dist/redaction.d.ts +5 -0
  42. package/dist/redaction.js +9 -0
  43. package/dist/resources/register-all.js +150 -54
  44. package/dist/retry-after.d.ts +19 -0
  45. package/dist/retry-after.js +84 -0
  46. package/dist/server-composition.d.ts +5 -2
  47. package/dist/server-composition.js +35 -5
  48. package/dist/tool-exposure.d.ts +6 -5
  49. package/dist/tool-exposure.js +15 -13
  50. package/dist/tools/audit_gl.js +8 -8
  51. package/dist/tools/backend-availability.d.ts +8 -29
  52. package/dist/tools/backend-availability.js +31 -30
  53. package/dist/tools/bank_rules.js +66 -15
  54. package/dist/tools/banking.js +5 -13
  55. package/dist/tools/clients.js +252 -6
  56. package/dist/tools/deposits.js +107 -97
  57. package/dist/tools/einvoice.d.ts +17 -3
  58. package/dist/tools/einvoice.js +59 -12
  59. package/dist/tools/fiscal.d.ts +3 -4
  60. package/dist/tools/fiscal.js +93 -60
  61. package/dist/tools/gestoria.js +15 -7
  62. package/dist/tools/hr.js +41 -11
  63. package/dist/tools/igic.d.ts +9 -8
  64. package/dist/tools/igic.js +34 -64
  65. package/dist/tools/impuesto_sociedades.d.ts +4 -5
  66. package/dist/tools/impuesto_sociedades.js +16 -37
  67. package/dist/tools/intelligence.js +164 -10
  68. package/dist/tools/invoices.js +109 -42
  69. package/dist/tools/kitchen.js +19 -9
  70. package/dist/tools/quotes.js +86 -25
  71. package/dist/tools/recurring.js +26 -25
  72. package/dist/tools/register-all.d.ts +2 -2
  73. package/dist/tools/register-all.js +9 -6
  74. package/dist/tools/search.d.ts +19 -0
  75. package/dist/tools/search.js +84 -0
  76. package/dist/tools/shared.d.ts +17 -37
  77. package/dist/tools/shared.js +96 -30
  78. package/dist/tools/team.js +8 -16
  79. package/dist/tools/time.js +4 -12
  80. package/dist/tools/vendors.js +240 -6
  81. package/dist/types.d.ts +3 -1
  82. package/dist/workflows/daily-operational-brief.d.ts +163 -0
  83. package/dist/workflows/daily-operational-brief.js +881 -0
  84. package/docs/agent-onboarding.json +15 -5
  85. package/package.json +26 -12
  86. package/dist/tools/onboarding.d.ts +0 -19
  87. package/dist/tools/onboarding.js +0 -64
@@ -13,6 +13,168 @@ const addressSchema = z
13
13
  })
14
14
  .optional()
15
15
  .describe("Vendor address / Direccion del proveedor");
16
+ /**
17
+ * Splits a concatenated / camelCase / snake_case / kebab-case search query into
18
+ * space-separated tokens (e.g. "CanariasBlue" -> "Canarias Blue",
19
+ * "SLCanarias" -> "SL Canarias", "Canarias_Blue" -> "Canarias Blue").
20
+ */
21
+ function splitCompoundQuery(q) {
22
+ return q
23
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
24
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
25
+ .replace(/[_-]+/g, " ")
26
+ .replace(/\s+/g, " ")
27
+ .trim();
28
+ }
29
+ const OPTIONAL_VENDOR_STRING_FIELDS = [
30
+ "email",
31
+ "phone",
32
+ "taxId",
33
+ "createdAt",
34
+ "updatedAt",
35
+ ];
36
+ /**
37
+ * Normalizes a vendor record returned by the Frihet API so it always satisfies
38
+ * `vendorItemOutput` without losing ERP-canonical data:
39
+ * - Strips `null` on optional string fields (`email`, `phone`, `taxId`,
40
+ * `createdAt`, `updatedAt`) so `z.string().optional()` never rejects legacy docs.
41
+ * - Backfills `taxId` from legacy `nif` / `cif` / `vatNumber` / `tax_id` when
42
+ * `taxId` is absent or null, preserving all original fields.
43
+ * - Converts legacy string `address` into `{ street }`.
44
+ * - Omits `null` or empty `address`.
45
+ * - Maps ERP canonical address keys (`province` -> `state`, `zip` -> `postalCode`,
46
+ * `countryCode` -> `country`).
47
+ */
48
+ function normalizeVendorRecord(vendorRecord) {
49
+ if (!vendorRecord || typeof vendorRecord !== "object" || Array.isArray(vendorRecord)) {
50
+ return vendorRecord;
51
+ }
52
+ const out = { ...vendorRecord };
53
+ for (const field of OPTIONAL_VENDOR_STRING_FIELDS) {
54
+ if (out[field] === null) {
55
+ delete out[field];
56
+ }
57
+ }
58
+ if (typeof out.taxId !== "string" || !out.taxId.trim()) {
59
+ for (const alias of ["nif", "cif", "vatNumber", "tax_id"]) {
60
+ const candidate = out[alias];
61
+ if (typeof candidate === "string" && candidate.trim()) {
62
+ out.taxId = candidate.trim();
63
+ break;
64
+ }
65
+ }
66
+ }
67
+ const rawAddress = out.address;
68
+ const rootStreet = typeof out.street === "string" && out.street.trim() ? out.street.trim() : undefined;
69
+ const rootCity = typeof out.city === "string" && out.city.trim() ? out.city.trim() : undefined;
70
+ const rootState = typeof out.state === "string" && out.state.trim()
71
+ ? out.state.trim()
72
+ : typeof out.province === "string" && out.province.trim()
73
+ ? out.province.trim()
74
+ : undefined;
75
+ const rootPostalCode = typeof out.postalCode === "string" && out.postalCode.trim()
76
+ ? out.postalCode.trim()
77
+ : typeof out.zip === "string" && out.zip.trim()
78
+ ? out.zip.trim()
79
+ : undefined;
80
+ const rootCountry = typeof out.country === "string" && out.country.trim()
81
+ ? out.country.trim()
82
+ : typeof out.countryCode === "string" && out.countryCode.trim()
83
+ ? out.countryCode.trim()
84
+ : undefined;
85
+ const buildFlatAddress = (streetOverride) => {
86
+ const built = {};
87
+ const st = streetOverride ?? rootStreet;
88
+ if (st !== undefined)
89
+ built.street = st;
90
+ if (rootCity !== undefined)
91
+ built.city = rootCity;
92
+ if (rootState !== undefined)
93
+ built.state = rootState;
94
+ if (rootPostalCode !== undefined)
95
+ built.postalCode = rootPostalCode;
96
+ if (rootCountry !== undefined)
97
+ built.country = rootCountry;
98
+ return Object.keys(built).length > 0 ? built : undefined;
99
+ };
100
+ if (rawAddress === undefined || rawAddress === null) {
101
+ if (rawAddress === null) {
102
+ delete out.address;
103
+ }
104
+ const flatAddr = buildFlatAddress();
105
+ if (flatAddr) {
106
+ out.address = flatAddr;
107
+ }
108
+ return out;
109
+ }
110
+ if (typeof rawAddress === "string") {
111
+ const trimmed = rawAddress.trim();
112
+ const flatAddr = buildFlatAddress(trimmed || undefined);
113
+ if (!flatAddr) {
114
+ delete out.address;
115
+ return out;
116
+ }
117
+ out.address = flatAddr;
118
+ return out;
119
+ }
120
+ if (typeof rawAddress === "object" && !Array.isArray(rawAddress)) {
121
+ const addrObj = rawAddress;
122
+ const normalizedAddress = {};
123
+ for (const [k, v] of Object.entries(addrObj)) {
124
+ if (v !== null && v !== undefined) {
125
+ normalizedAddress[k] = v;
126
+ }
127
+ }
128
+ const street = typeof addrObj.street === "string" && addrObj.street.trim()
129
+ ? addrObj.street.trim()
130
+ : rootStreet;
131
+ const city = typeof addrObj.city === "string" && addrObj.city.trim()
132
+ ? addrObj.city.trim()
133
+ : rootCity;
134
+ const state = typeof addrObj.state === "string" && addrObj.state.trim()
135
+ ? addrObj.state.trim()
136
+ : typeof addrObj.province === "string" && addrObj.province.trim()
137
+ ? addrObj.province.trim()
138
+ : rootState;
139
+ const postalCode = typeof addrObj.postalCode === "string" && addrObj.postalCode.trim()
140
+ ? addrObj.postalCode.trim()
141
+ : typeof addrObj.zip === "string" && addrObj.zip.trim()
142
+ ? addrObj.zip.trim()
143
+ : rootPostalCode;
144
+ const country = typeof addrObj.country === "string" && addrObj.country.trim()
145
+ ? addrObj.country.trim()
146
+ : typeof addrObj.countryCode === "string" && addrObj.countryCode.trim()
147
+ ? addrObj.countryCode.trim()
148
+ : rootCountry;
149
+ if (street !== undefined)
150
+ normalizedAddress.street = street;
151
+ else
152
+ delete normalizedAddress.street;
153
+ if (city !== undefined)
154
+ normalizedAddress.city = city;
155
+ else
156
+ delete normalizedAddress.city;
157
+ if (state !== undefined)
158
+ normalizedAddress.state = state;
159
+ else
160
+ delete normalizedAddress.state;
161
+ if (postalCode !== undefined)
162
+ normalizedAddress.postalCode = postalCode;
163
+ else
164
+ delete normalizedAddress.postalCode;
165
+ if (country !== undefined)
166
+ normalizedAddress.country = country;
167
+ else
168
+ delete normalizedAddress.country;
169
+ if (Object.keys(normalizedAddress).length === 0) {
170
+ delete out.address;
171
+ return out;
172
+ }
173
+ out.address = normalizedAddress;
174
+ return out;
175
+ }
176
+ return out;
177
+ }
16
178
  export function registerVendorTools(server, client) {
17
179
  // -- list_vendors --
18
180
  server.registerTool("list_vendors", {
@@ -31,10 +193,82 @@ export function registerVendorTools(server, client) {
31
193
  },
32
194
  outputSchema: paginatedOutput(vendorItemOutput, { projectable: true }),
33
195
  }, async ({ q, limit, offset, after, fields }) => withToolLogging("list_vendors", async () => {
34
- const result = await client.listVendors({ q, limit, offset, after, fields });
196
+ const trimmedQ = q?.trim();
197
+ const trimmedAfter = after?.trim();
198
+ // Limitation in Frihet-ERP `listResources` (publicApi.ts L8158-8268):
199
+ // When `search` (`q`) is provided, the backend slices results by `offset`/`limit`
200
+ // and returns before evaluating `cursorParam` (`after`). Protected profiles
201
+ // (Claude `claudeToolResult` and OpenAI `applyOpenAIProfile`) rebuild `content`
202
+ // from `structuredContent` while `paginatedOutput` is frozen, so fail closed
203
+ // (`isError: true` without `structuredContent`).
204
+ if (trimmedQ && trimmedAfter) {
205
+ return {
206
+ content: [{
207
+ type: "text",
208
+ text: "Error: text search ('q') cannot be combined with cursor pagination ('after') because the ERP backend slices search results by 'offset'/'limit' and ignores 'after'. Use 'offset' and 'limit' when searching with 'q', or omit 'q' when paginating with 'after'. / " +
209
+ "Error: la busqueda textual ('q') no se puede combinar con el cursor 'after' porque el backend pagina las busquedas mediante 'offset'/'limit' e ignora 'after'. Usa 'offset' y 'limit' con 'q', u omite 'q' al paginar con 'after'.",
210
+ }],
211
+ isError: true,
212
+ };
213
+ }
214
+ let result = await client.listVendors({ q, limit, offset, after, fields });
215
+ let usedFallback = false;
216
+ const splitQ = trimmedQ ? splitCompoundQuery(trimmedQ) : undefined;
217
+ const isSubsequentPage = (offset !== undefined && offset > 0) || Boolean(after);
218
+ const initialTruncated = result?.truncated === true;
219
+ const canFallback = splitQ !== undefined && splitQ !== trimmedQ && !after && !initialTruncated;
220
+ const hasZeroTotalMatches = Array.isArray(result?.data) &&
221
+ result.data.length === 0 &&
222
+ (isSubsequentPage ? result.total === 0 : result.total === 0 || result.total === undefined);
223
+ if (canFallback && hasZeroTotalMatches) {
224
+ try {
225
+ const fallbackResult = await client.listVendors({ q: splitQ, limit, offset, after, fields });
226
+ const fallbackTruncated = fallbackResult?.truncated === true;
227
+ if (fallbackTruncated ||
228
+ (fallbackResult?.data && (fallbackResult.data.length > 0 || (fallbackResult.total !== undefined && fallbackResult.total > 0)))) {
229
+ result = fallbackResult;
230
+ usedFallback = true;
231
+ }
232
+ }
233
+ catch {
234
+ // Preserve original response if fallback fails
235
+ }
236
+ }
237
+ const rawEnvelope = (result ?? {});
238
+ const isTruncated = rawEnvelope.truncated === true;
239
+ const normalizedData = Array.isArray(result?.data)
240
+ ? result.data.map((v) => (v && typeof v === "object" ? normalizeVendorRecord(v) : v))
241
+ : [];
242
+ // Conservative refusal when the ERP 500-document scan cap was saturated (`truncated: true`).
243
+ // Because `paginatedOutput` is a frozen schema contract and both Claude (`claudeToolResult`)
244
+ // and OpenAI (`applyOpenAIProfile`) overwrite `content` whenever `structuredContent` is
245
+ // present, returning `structuredContent` here would hide the truncation warning and present
246
+ // an incomplete 500-doc scan as exhaustive.
247
+ if (isTruncated) {
248
+ const partialCount = typeof result?.total === "number" ? result.total : normalizedData.length;
249
+ return {
250
+ content: [{
251
+ type: "text",
252
+ text: `Error: vendor search hit the ERP 500-document scan cap (truncated=true; ${partialCount} match(es) within the first 500 scanned records) and cannot be returned as an exhaustive result across protected profiles. Narrow 'q' or paginate without 'q' using 'after'. / ` +
253
+ `Error: la busqueda de proveedores alcanzo el limite de escaneo de 500 documentos del ERP (truncated=true; ${partialCount} coincidencia(s) en los primeros 500 registros) y no puede devolverse como resultado exhaustivo. Acota 'q' o pagina sin 'q' usando 'after'.`,
254
+ }],
255
+ isError: true,
256
+ };
257
+ }
258
+ const cleanEnvelope = {
259
+ data: normalizedData,
260
+ total: typeof result?.total === "number" ? result.total : normalizedData.length,
261
+ limit: typeof result?.limit === "number" ? result.limit : (limit ?? 20),
262
+ offset: typeof result?.offset === "number" ? result.offset : (offset ?? 0),
263
+ ...(typeof result?.nextCursor === "string" ? { nextCursor: result.nextCursor } : {}),
264
+ };
265
+ const notes = [];
266
+ if (usedFallback) {
267
+ notes.push(` (criterio aplicado: "${splitQ}")`);
268
+ }
35
269
  return {
36
- content: [listContent(formatPaginatedResponse("vendors", result))],
37
- structuredContent: result,
270
+ content: [listContent(formatPaginatedResponse("vendors", cleanEnvelope) + notes.join(""))],
271
+ structuredContent: cleanEnvelope,
38
272
  };
39
273
  }));
40
274
  // -- get_vendor --
@@ -48,7 +282,7 @@ export function registerVendorTools(server, client) {
48
282
  },
49
283
  outputSchema: vendorItemOutput,
50
284
  }, async ({ id }) => withToolLogging("get_vendor", async () => {
51
- const result = await client.getVendor(id);
285
+ const result = normalizeVendorRecord(await client.getVendor(id));
52
286
  return {
53
287
  content: [getContent(formatRecord("Vendor", result))],
54
288
  structuredContent: result,
@@ -72,7 +306,7 @@ export function registerVendorTools(server, client) {
72
306
  },
73
307
  outputSchema: vendorItemOutput,
74
308
  }, async (input) => withToolLogging("create_vendor", async () => {
75
- const result = await client.createVendor(input);
309
+ const result = normalizeVendorRecord(await client.createVendor(input));
76
310
  const hints = enrichResponse("vendors", "create", result);
77
311
  return {
78
312
  content: [mutateContent(formatRecord("Vendor created", result) + hints)],
@@ -96,7 +330,7 @@ export function registerVendorTools(server, client) {
96
330
  },
97
331
  outputSchema: vendorItemOutput,
98
332
  }, async ({ id, ...data }) => withToolLogging("update_vendor", async () => {
99
- const result = await client.updateVendor(id, data);
333
+ const result = normalizeVendorRecord(await client.updateVendor(id, data));
100
334
  return {
101
335
  content: [mutateContent(formatRecord("Vendor updated", result))],
102
336
  structuredContent: result,
package/dist/types.d.ts CHANGED
@@ -38,11 +38,13 @@ export interface Invoice {
38
38
  status?: string;
39
39
  notes?: string;
40
40
  taxRate?: number;
41
+ operationType?: "service" | "goods";
42
+ fiscalTreatment?: "not_subject_location" | "export_exempt";
41
43
  total?: number;
42
44
  createdAt?: string;
43
45
  updatedAt?: string;
44
46
  }
45
- export type CreateInvoiceInput = Pick<Invoice, "clientName" | "items"> & Partial<Pick<Invoice, "issueDate" | "dueDate" | "status" | "notes" | "taxRate">>;
47
+ export type CreateInvoiceInput = Pick<Invoice, "clientName" | "items"> & Partial<Pick<Invoice, "issueDate" | "dueDate" | "status" | "notes" | "taxRate" | "operationType" | "fiscalTreatment">>;
46
48
  export type UpdateInvoiceInput = Partial<CreateInvoiceInput>;
47
49
  export interface Expense {
48
50
  id: string;
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Daily Operational Brief workflow ("¿Qué debería revisar hoy en mi negocio y por qué?").
3
+ *
4
+ * Grounded strictly in read-only Frihet workspace tools:
5
+ * - get_business_context
6
+ * - list_invoices
7
+ * - list_quotes
8
+ * - list_expenses
9
+ * - list_clients
10
+ * - list_client_activities
11
+ *
12
+ * Enforces:
13
+ * 1. Explicit temporal scope (`unfiltered_operational_sample_as_of_date` vs `currentMonthActivity`)
14
+ * and per-resource coverage (inspected vs total records).
15
+ * 2. Strict separation between draft documents (non-issued) and real/issued activity.
16
+ * 3. Explicit amount provenance (`stored_field`, `derived_from_items`, `business_context_summary`,
17
+ * `unavailable`, or `mixed_currency_unconverted`).
18
+ * - Absent/corrupt amounts NEVER coerce to 0 (`amount: null`, `provenance: "unavailable"`).
19
+ * - Multi-currency totals NEVER sum across currencies without conversion (`totalsByCurrency`, `currency: "MIXED"`).
20
+ * - Derived line-item totals apply canonical per-line `discount`, document `discountRate`,
21
+ * `taxRate`, `equivalenceSurchargeRate`, and `irpfRate` over the discounted tax-exclusive base.
22
+ * 4. Honest unpaid invoice semantics: full nominal totals of unpaid invoices are labeled
23
+ * "importe nominal de facturas no marcadas como pagadas; saldo pendiente real no determinable
24
+ * sin desglose de cobros parciales ni conciliación bancaria" (`realOutstandingBalanceDeterminable: false`).
25
+ * 5. Record-backed prioritized review items and suggested next actions without auto-executing writes.
26
+ * 6. Explicit declaration of what cannot be determined ("knownUnknowns").
27
+ */
28
+ import type { IFrihetClient } from "../client-interface.js";
29
+ export type AmountProvenanceKind = "stored_field" | "derived_from_items" | "business_context_summary" | "unavailable" | "mixed_currency_unconverted";
30
+ export interface CurrencyBucketAmount {
31
+ amount: number;
32
+ currency: string;
33
+ provenance: "stored_field" | "derived_from_items";
34
+ documentCount: number;
35
+ }
36
+ export interface ProvenancedAmount {
37
+ amount: number | null;
38
+ currency: string;
39
+ provenance: AmountProvenanceKind;
40
+ fieldOrFormula: string;
41
+ totalsByCurrency?: Record<string, CurrencyBucketAmount>;
42
+ unavailableCount?: number;
43
+ }
44
+ export interface ResourceCoverage {
45
+ collection: "invoices" | "quotes" | "expenses" | "clients" | "client_activities";
46
+ sourceTool: string;
47
+ inspected: number;
48
+ total: number;
49
+ complete: boolean;
50
+ }
51
+ export interface BriefCoverageSummary {
52
+ asOfDate: string;
53
+ periodMonth: string;
54
+ referenceMonth: string;
55
+ temporalScope: "unfiltered_operational_sample_as_of_date";
56
+ temporalScopeNote: string;
57
+ isPartial: boolean;
58
+ resources: Record<"invoices" | "quotes" | "expenses" | "clients" | "client_activities", ResourceCoverage>;
59
+ }
60
+ export interface BriefDocumentSummary {
61
+ id: string;
62
+ documentNumber: string | null;
63
+ status: string;
64
+ counterpartyName: string;
65
+ issueDate: string | null;
66
+ dueDate: string | null;
67
+ amount: ProvenancedAmount;
68
+ }
69
+ export interface BriefPriorityItem {
70
+ priority: "high" | "medium" | "low";
71
+ category: "overdue_invoice" | "draft_invoice" | "draft_or_expiring_quote" | "expense_review" | "client_followup" | "plan_quota";
72
+ title: string;
73
+ why: string;
74
+ recordRefs: Array<{
75
+ type: "invoice" | "quote" | "expense" | "client" | "client_activity" | "plan_quota";
76
+ id: string;
77
+ label: string;
78
+ }>;
79
+ amount?: ProvenancedAmount;
80
+ suggestedNextAction: {
81
+ description: string;
82
+ recommendedTool: string;
83
+ requiresConfirmation: boolean;
84
+ autoExecuted: false;
85
+ };
86
+ }
87
+ export interface DailyOperationalBrief {
88
+ question: string;
89
+ coverage: BriefCoverageSummary;
90
+ activitySeparation: {
91
+ draftActivity: {
92
+ note: string;
93
+ invoiceDraftsCount: number;
94
+ invoiceDraftsTotal: ProvenancedAmount;
95
+ invoiceDrafts: BriefDocumentSummary[];
96
+ quoteDraftsCount: number;
97
+ quoteDraftsTotal: ProvenancedAmount;
98
+ quoteDrafts: BriefDocumentSummary[];
99
+ };
100
+ issuedActivity: {
101
+ note: string;
102
+ realOutstandingBalanceDeterminable: false;
103
+ unpaidNominalSemantics: "nominal_unpaid_invoice_totals_without_partial_payments";
104
+ issuedInvoicesCount: number;
105
+ issuedInvoicesByStatus: Record<string, number>;
106
+ issuedInvoicesTotal: ProvenancedAmount;
107
+ issuedInvoices: BriefDocumentSummary[];
108
+ paidInvoicesCount: number;
109
+ paidInvoicesTotal: ProvenancedAmount;
110
+ paidInvoices: BriefDocumentSummary[];
111
+ unpaidNominalInvoicesCount: number;
112
+ unpaidNominalTotal: ProvenancedAmount;
113
+ unpaidNominalTotalsByCurrency: Record<string, CurrencyBucketAmount>;
114
+ overdueNominalInvoicesCount: number;
115
+ overdueNominalTotal: ProvenancedAmount;
116
+ onTimeNominalPendingCount: number;
117
+ onTimeNominalPendingTotal: ProvenancedAmount;
118
+ outstandingInvoicesCount: number;
119
+ outstandingInvoicesTotal: ProvenancedAmount;
120
+ overdueInvoicesCount: number;
121
+ overdueInvoicesTotal: ProvenancedAmount;
122
+ onTimePendingCount: number;
123
+ onTimePendingTotal: ProvenancedAmount;
124
+ outstandingInvoices: BriefDocumentSummary[];
125
+ };
126
+ recordedExpenses: {
127
+ note: string;
128
+ expensesCount: number;
129
+ expensesTotal: ProvenancedAmount;
130
+ deductibleCount: number;
131
+ deductibleTotal: ProvenancedAmount;
132
+ nonDeductibleCount: number;
133
+ nonDeductibleTotal: ProvenancedAmount;
134
+ pendingReviewCount: number;
135
+ };
136
+ currentMonthActivity: {
137
+ referenceMonth: string;
138
+ note: string;
139
+ issuedInvoicesInMonthCount: number;
140
+ issuedInvoicesInMonthTotal: ProvenancedAmount;
141
+ invoiceDraftsInMonthCount: number;
142
+ invoiceDraftsInMonthTotal: ProvenancedAmount;
143
+ expensesInMonthCount: number;
144
+ expensesInMonthTotal: ProvenancedAmount;
145
+ };
146
+ };
147
+ priorities: BriefPriorityItem[];
148
+ knownUnknowns: string[];
149
+ mutationsExecuted: 0;
150
+ markdown: string;
151
+ }
152
+ export interface GenerateDailyBriefOptions {
153
+ asOfDate?: string;
154
+ pageLimit?: number;
155
+ maxActivityClients?: number;
156
+ }
157
+ export declare function formatProvenancedAmount(p: ProvenancedAmount): string;
158
+ export declare function resolveDocumentAmount(doc: Record<string, unknown>, defaultCurrency?: string): ProvenancedAmount;
159
+ export declare function sumProvenancedAmounts(amounts: ProvenancedAmount[], defaultCurrency?: string): ProvenancedAmount;
160
+ export declare function generateDailyOperationalBrief(client: Pick<IFrihetClient, "getBusinessContext" | "listInvoices" | "listQuotes" | "listExpenses" | "listClients" | "listClientActivities">, options?: GenerateDailyBriefOptions): Promise<DailyOperationalBrief>;
161
+ export declare function buildScenario1CompleteWorkspaceClient(callLog: string[]): Pick<IFrihetClient, "getBusinessContext" | "listInvoices" | "listQuotes" | "listExpenses" | "listClients" | "listClientActivities">;
162
+ export declare function buildScenario2PartialCoverageClient(callLog: string[]): Pick<IFrihetClient, "getBusinessContext" | "listInvoices" | "listQuotes" | "listExpenses" | "listClients" | "listClientActivities">;
163
+ //# sourceMappingURL=daily-operational-brief.d.ts.map