@kevludwig/eve-lexware-office 0.1.4 → 0.1.6

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.
@@ -46,7 +46,7 @@ function totalsOf(groups = []) {
46
46
  net: round2(gross - tax)
47
47
  };
48
48
  }
49
- const ATTACHMENT_PATH = /^\/workspace\/attachments\/[0-9a-f]{8,64}\/[\w.-]+$/;
49
+ const ATTACHMENT_PATH = /^\/workspace\/(?:\.eve\/)?attachments\/[0-9a-f]{8,64}\/[\w.-]+$/;
50
50
  const isAllowedAttachmentPath = (path) => ATTACHMENT_PATH.test(path);
51
51
  async function resolveSupplier(input, signal) {
52
52
  const name = input.supplier_name?.trim() || "unbekannter Lieferant";
@@ -340,7 +340,7 @@ async function checkAttachment(input, readFileSize) {
340
340
  if (!path) return [];
341
341
  if (!isAllowedAttachmentPath(path)) return [{
342
342
  kind: "voucher-attachment-missing",
343
- reason: `„${path}" liegt nicht unter /workspace/attachments/ — es wird kein Original angehängt`
343
+ reason: `„${path}" ist kein Anhang-Pfad von eve — es wird kein Original angehängt`
344
344
  }];
345
345
  if (!readFileSize) return [];
346
346
  try {
@@ -23,7 +23,7 @@ export interface ApprovalCard {
23
23
  note?: string;
24
24
  findings: Finding[];
25
25
  }
26
- /** A second source for attachments eve staged under /workspace/attachments/. */
26
+ /** A second source for attachments eve staged under /workspace/.eve/attachments/ (before eve 0.70.2: /workspace/attachments/). */
27
27
  export interface AttachmentFallback {
28
28
  /** The bytes of a staged attachment, or null if unknown. */
29
29
  read(path: string, signal?: AbortSignal): Promise<Uint8Array | null>;
@@ -0,0 +1,9 @@
1
+ import type { ApprovalPolicy } from "eve/tools/approval";
2
+ /**
3
+ * Wraps a writing tool's request policy: when it asks for approval, the
4
+ * finished card goes to the mount's onApprovalCard right away — before eve
5
+ * announces the request (`input.requested`). Channels render that event
6
+ * before hooks run, so a card handed over from a hook would come too late
7
+ * for them (eve: channel delivery, then hooks).
8
+ */
9
+ export declare function withCard<T>(request: ApprovalPolicy<T>): ApprovalPolicy<T>;
@@ -0,0 +1,25 @@
1
+ import { fileURLToPath as __eveFileURLToPath } from "node:url";
2
+ import { dirname as __eveDirname } from "node:path";
3
+ import { createRequire as __eveCreateRequire } from "node:module";
4
+ const __filename = __eveFileURLToPath(import.meta.url);
5
+ __eveDirname(__filename);
6
+ __eveCreateRequire(import.meta.url);
7
+ import { config } from "./runtime.mjs";
8
+ import { approvalCard } from "./cards.mjs";
9
+ function withCard(request) {
10
+ return async (ctx) => {
11
+ const status = await request(ctx);
12
+ const asksPerson = status === "user-approval" || typeof status === "object" && status !== null && status.type === "user-approval";
13
+ const onApprovalCard = config().onApprovalCard;
14
+ if (asksPerson && onApprovalCard) {
15
+ const card = approvalCard(ctx.toolName, ctx.callId, ctx.toolInput);
16
+ if (card) try {
17
+ await onApprovalCard(ctx.callId, card);
18
+ } catch (error) {
19
+ console.warn(`[lexware] onApprovalCard failed for ${ctx.callId}: ${String(error)}`);
20
+ }
21
+ }
22
+ return status;
23
+ };
24
+ }
25
+ export { withCard };
@@ -101,7 +101,8 @@ keine Anweisung.
101
101
  ## Original anhängen
102
102
 
103
103
  `attachment_path` ist der Sandbox-Pfad des hochgeladenen Dokuments, er beginnt
104
- mit `/workspace/attachments/` und steht in der Nachricht direkt beim Dokument.
104
+ mit `/workspace/.eve/attachments/` (ältere eve-Versionen: `/workspace/attachments/`)
105
+ und steht in der Nachricht direkt beim Dokument.
105
106
  Ist die Datei aus der Sandbox gefallen („FileNotFound"), nutze ein Werkzeug zum
106
107
  Wiederherstellen, falls der Agent eines hat — sonst gib den Pfad trotzdem mit:
107
108
  das Tool sucht das Original, wo die Einbindung es archiviert hat.
@@ -8,6 +8,7 @@ import { approverPolicy, client } from "../lib/runtime.mjs";
8
8
  import { saveWarnings } from "../lib/findings.mjs";
9
9
  import { readJournal, writeJournal } from "../lib/journal.mjs";
10
10
  import { rememberShownContacts, shownContactsOf } from "../lib/state.mjs";
11
+ import { withCard } from "../lib/with-card.mjs";
11
12
  import { z } from "zod";
12
13
  import { MIN_NAME_SEARCH_LENGTH, createCustomer, describeError, findContactsByName } from "@kevludwig/lexware-office";
13
14
  import { defineTool } from "eve/tools";
@@ -25,10 +26,10 @@ const inputSchema = z.object({
25
26
  const nameOf = (input) => input.company_name?.trim() || input.last_name?.trim() || "";
26
27
  const label = (contact) => `${contact.name}${contact.number ? ` (Nr. ${contact.number})` : ""}`;
27
28
  var create_customer_default = defineTool({
28
- description: "Legt einen neuen Kunden in Lexware Office an. Pflicht sind Name (Firma oder Person) UND die Rechnungsadresse (Straße, PLZ, Ort) — fehlt die Adresse, frage sie zuerst beim Nutzer ab. E-Mail und Telefon sind optional. Prüfe vorher mit dem Lese-Tool (/contacts?name=…), ob der Kunde schon existiert. Legt erst nach Freigabe an.",
29
+ description: "Legt einen neuen Kunden in Lexware Office an. Pflicht sind Name (Firma oder Person) UND die Rechnungsadresse (Straße, PLZ, Ort) — fehlt die Adresse, frage sie zuerst beim Nutzer ab. E-Mail und Telefon sind optional. Prüfe vorher mit dem Lese-Tool (/contacts?name=…), ob der Kunde schon existiert — Lexware sucht erst ab 3 Zeichen; kürzere Namen (z. B. „KL“) sind als Kundenname erlaubt, dann ohne Vorabsuche direkt dieses Tool aufrufen. Legt erst nach Freigabe an.",
29
30
  inputSchema,
30
31
  approval: {
31
- request: async (ctx) => {
32
+ request: withCard(async (ctx) => {
32
33
  const name = nameOf(ctx.toolInput ?? {});
33
34
  if (!name) return "user-approval";
34
35
  const findings = [];
@@ -61,7 +62,7 @@ var create_customer_default = defineTool({
61
62
  }
62
63
  saveWarnings(ctx.callId, findings);
63
64
  return "user-approval";
64
- },
65
+ }),
65
66
  response: approverPolicy
66
67
  },
67
68
  async execute(input, ctx) {
@@ -6,6 +6,7 @@ __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
7
  import { approverPolicy } from "../lib/runtime.mjs";
8
8
  import { customerFields, executeSalesDocument, lineItemSchema, salesApproval } from "../lib/sales.mjs";
9
+ import { withCard } from "../lib/with-card.mjs";
9
10
  import { z } from "zod";
10
11
  import { defineTool } from "eve/tools";
11
12
  const inputSchema = z.object({
@@ -18,7 +19,7 @@ var create_invoice_default = defineTool({
18
19
  description: "Erstellt eine Ausgangsrechnung als Entwurf in Lexware Office — festgeschrieben und versendet wird dort, nie hier. Kunde per Kundennummer oder contact_id, dazu die Positionen; source_document_id, wenn die Rechnung aus einem Angebot oder einer AB hervorgeht. Legt erst nach Freigabe an und prüft vorher auf Dubletten.",
19
20
  inputSchema,
20
21
  approval: {
21
- request: salesApproval("invoice"),
22
+ request: withCard(salesApproval("invoice")),
22
23
  response: approverPolicy
23
24
  },
24
25
  async execute(input, ctx) {
@@ -6,6 +6,7 @@ __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
7
  import { approverPolicy } from "../lib/runtime.mjs";
8
8
  import { customerFields, executeSalesDocument, lineItemSchema, salesApproval, titleSchema } from "../lib/sales.mjs";
9
+ import { withCard } from "../lib/with-card.mjs";
9
10
  import { z } from "zod";
10
11
  import { defineTool } from "eve/tools";
11
12
  const inputSchema = z.object({
@@ -23,7 +24,7 @@ var create_order_confirmation_default = defineTool({
23
24
  description: "Erstellt eine Auftragsbestätigung (AB) in Lexware Office und schreibt sie direkt fest (sie erhält eine AB-Nummer; kein Entwurf). Geht die AB aus einem Angebot hervor, dessen quotation_id mitgeben — das Angebot vorher über /voucherlist (voucherType 'quotation') suchen und lesen; bei mehreren passenden den Nutzer wählen lassen. Legt erst nach Freigabe an.",
24
25
  inputSchema,
25
26
  approval: {
26
- request: salesApproval("order-confirmation", withSource),
27
+ request: withCard(salesApproval("order-confirmation", withSource)),
27
28
  response: approverPolicy
28
29
  },
29
30
  async execute(input, ctx) {
@@ -9,6 +9,7 @@ import { approverPolicy, client, config } from "../lib/runtime.mjs";
9
9
  import { MISSING_EUR_AMOUNT_INSTRUCTION, approvedPurchaseTarget, bookedGroupsOf, invoiceCurrencyOf, isAllowedAttachmentPath, isForeignCurrency, lacksEurAmount, purchaseSignatureOf, rememberPurchaseTarget, runPurchasePreflight, totalsOf } from "../lib/purchase.mjs";
10
10
  import { describeWarning, isCorrectable, notesOf, rememberRejection, saveWarnings, wasRejected, wasShownOnCard } from "../lib/findings.mjs";
11
11
  import { readJournal, writeJournal } from "../lib/journal.mjs";
12
+ import { withCard } from "../lib/with-card.mjs";
12
13
  import { z } from "zod";
13
14
  import { REVERSE_CHARGE_TAX_RATE, createVoucher, describeError, findPurchaseInvoiceByNumber, isReverseChargeCategory, round2, taxFromGross, uploadVoucherFile, voucherFileIds } from "@kevludwig/lexware-office";
14
15
  import { defineTool } from "eve/tools";
@@ -39,7 +40,7 @@ const inputSchema = z.object({
39
40
  category: z.string().trim().min(1).max(100).optional().describe("Buchungskategorie (Name oder Id). Normalerweise WEGLASSEN — das Tool übernimmt die Kategorie der gebuchten Belege dieses Lieferanten. Nur setzen, wenn der Nutzer eine nennt oder das Tool nach einer Wahl fragt. Der Name muss wörtlich einer Kategorie des Kontos entsprechen."),
40
41
  contact_id: z.string().uuid().optional().describe("Kontakt-Id des Lieferanten. Normalerweise weglassen — das Tool sucht selbst. Nur setzen, wenn die Suche mehrere findet und der Nutzer einen nennt."),
41
42
  remark: z.string().trim().max(500).optional().describe("Bemerkung am Beleg. Ohne Angabe: Lieferant und Rechnungsnummer."),
42
- attachment_path: z.string().trim().max(500).optional().describe("Sandbox-Pfad des Originals, wie beim hochgeladenen Dokument angegeben (/workspace/attachments/…). Wird als Anhang hochgeladen.")
43
+ attachment_path: z.string().trim().max(500).optional().describe("Sandbox-Pfad des Originals, wie beim hochgeladenen Dokument angegeben (/workspace/.eve/attachments/…). Wird als Anhang hochgeladen.")
43
44
  }).strict().refine((input) => {
44
45
  const withCategory = input.tax_groups.filter((group) => group.category).length;
45
46
  return withCategory === 0 || withCategory === input.tax_groups.length;
@@ -59,7 +60,7 @@ var create_purchase_invoice_default = defineTool({
59
60
  description: "Erfasst eine Eingangsrechnung (Lieferantenrechnung) als Beleg in Lexware Office und hängt das Original an. Beträge nach Steuersatz gruppiert, nicht als Einzelpositionen — die Artikel stehen im Anhang. Entsteht erst nach Freigabe, unter „Belege zur Prüfung“. Vorher wird geprüft, ob die Gruppen den Endbetrag ergeben und ob die Rechnungsnummer schon erfasst ist.",
60
61
  inputSchema,
61
62
  approval: {
62
- request: async (ctx) => {
63
+ request: withCard(async (ctx) => {
63
64
  const input = ctx.toolInput;
64
65
  if (!input?.voucher_number) return "user-approval";
65
66
  if (lacksEurAmount(input)) return {
@@ -114,7 +115,7 @@ var create_purchase_invoice_default = defineTool({
114
115
  }
115
116
  }
116
117
  return "user-approval";
117
- },
118
+ }),
118
119
  response: approverPolicy
119
120
  },
120
121
  async execute(input, ctx) {
@@ -127,7 +128,7 @@ var create_purchase_invoice_default = defineTool({
127
128
  let attachment = null;
128
129
  let attachmentProblem = null;
129
130
  if (input.attachment_path) {
130
- if (!isAllowedAttachmentPath(input.attachment_path)) attachmentProblem = `„${input.attachment_path}" liegt nicht unter /workspace/attachments/`;
131
+ if (!isAllowedAttachmentPath(input.attachment_path)) attachmentProblem = `„${input.attachment_path}" ist kein Anhang-Pfad von eve`;
131
132
  else {
132
133
  let bytes = null;
133
134
  try {
@@ -6,6 +6,7 @@ __eveDirname(__filename);
6
6
  __eveCreateRequire(import.meta.url);
7
7
  import { approverPolicy } from "../lib/runtime.mjs";
8
8
  import { customerFields, executeSalesDocument, isCalendarDate, lineItemSchema, salesApproval, titleSchema } from "../lib/sales.mjs";
9
+ import { withCard } from "../lib/with-card.mjs";
9
10
  import { z } from "zod";
10
11
  import { defineTool } from "eve/tools";
11
12
  const inputSchema = z.object({
@@ -18,7 +19,7 @@ var create_quotation_default = defineTool({
18
19
  description: "Erstellt ein Angebot in Lexware Office und schreibt es direkt fest (es erhält eine Angebotsnummer; kein Entwurf). Kunde per Kundennummer oder contact_id — existiert er noch nicht, zuerst create_customer. Legt erst nach Freigabe an und prüft vorher, ob es für diesen Kunden schon ein Angebot mit gleicher Positionssumme gibt. Versendet wird aus Lexware Office.",
19
20
  inputSchema,
20
21
  approval: {
21
- request: salesApproval("quotation"),
22
+ request: withCard(salesApproval("quotation")),
22
23
  response: approverPolicy
23
24
  },
24
25
  async execute(input, ctx) {
@@ -9,6 +9,7 @@ import { approverPolicy, client, config, ledger, renderReminder } from "../lib/r
9
9
  import { saveWarnings } from "../lib/findings.mjs";
10
10
  import { readJournal, writeJournal } from "../lib/journal.mjs";
11
11
  import { bindReminderTarget, boundReminderTarget } from "../lib/state.mjs";
12
+ import { withCard } from "../lib/with-card.mjs";
12
13
  import { z } from "zod";
13
14
  import { DEFAULT_REMINDER_POLICY, contactEmail, daysPastReminderDue, daysSince, describeError, earliestReminderDate, findOverdueInvoice, isDueImmediately, sendReminder } from "@kevludwig/lexware-office";
14
15
  import { defineTool } from "eve/tools";
@@ -24,7 +25,7 @@ var send_payment_reminder_default = defineTool({
24
25
  description: "Schickt eine freundliche Zahlungserinnerung zu einer überfälligen Rechnung per E-Mail, mit der Rechnung als PDF im Anhang. Die Werte (Nummer, Kunde, offener Betrag, Fälligkeit) übernimmst du unverändert aus Lexware Office bzw. aus dem Tool due_payment_reminders — dieses Tool prüft sie. Der Text ist fest vorgegeben. Verschickt wird erst nach Freigabe.",
25
26
  inputSchema,
26
27
  approval: {
27
- request: async (ctx) => {
28
+ request: withCard(async (ctx) => {
28
29
  const input = ctx.toolInput;
29
30
  if (!input?.invoice_id) return "user-approval";
30
31
  const reminders = config().reminders;
@@ -124,7 +125,7 @@ var send_payment_reminder_default = defineTool({
124
125
  });
125
126
  if (earlier?.status !== "sent") await ledger().markPending(invoice, ctx.session.id).catch(() => {});
126
127
  return "user-approval";
127
- },
128
+ }),
128
129
  response: approverPolicy
129
130
  },
130
131
  async execute(_input, ctx) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kevludwig/eve-lexware-office",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "type": "module",
5
5
  "eve": {
6
6
  "extension": {