@getpeppr/cli 0.4.4 → 0.4.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.
package/README.md CHANGED
@@ -211,7 +211,7 @@ The input file must be a JSON object matching the getpeppr `InvoiceInput` type.
211
211
  "buyerReference": "PO-2026-042",
212
212
  "from": {
213
213
  "name": "Dupont & Fils SPRL",
214
- "peppolId": "0208:0123456789",
214
+ "peppolId": "0208:0685660237",
215
215
  "street": "Avenue Louise 54",
216
216
  "city": "Bruxelles",
217
217
  "postalCode": "1050",
package/dist/index.js CHANGED
@@ -884,7 +884,7 @@ function validateParty(party, path) {
884
884
  errors.push(error(`${path}.name`, "Business name is required", "BR-06"));
885
885
  }
886
886
  if (party.peppolId === void 0 || party.peppolId === null || party.peppolId === "") {
887
- errors.push(error(`${path}.peppolId`, "Peppol participant ID is required", void 0, 'Format: "scheme:id", e.g. "0208:0123456789" for Belgian companies'));
887
+ errors.push(error(`${path}.peppolId`, "Peppol participant ID is required", void 0, 'Format: "scheme:id", e.g. "0208:0685660237" for Belgian companies'));
888
888
  } else if (!assertString(party.peppolId, `${path}.peppolId`, errors)) {
889
889
  } else if (!party.peppolId.includes(":")) {
890
890
  errors.push(error(`${path}.peppolId`, `Invalid Peppol ID format: "${party.peppolId}"`, void 0, 'Must be "scheme:id" format. Common schemes: 0208 (Belgium), 0009 (France SIRET), 0204 (Germany Leitweg)'));
@@ -986,7 +986,7 @@ function validateInvoice(input) {
986
986
  }
987
987
  const ppId = input.payeeParty.peppolId;
988
988
  if (ppId === void 0 || ppId === null || ppId === "") {
989
- errors.push(error("payeeParty.peppolId", "Payee party Peppol ID is required", void 0, 'Format: "scheme:id", e.g. "0208:0123456789"'));
989
+ errors.push(error("payeeParty.peppolId", "Payee party Peppol ID is required", void 0, 'Format: "scheme:id", e.g. "0208:0685660237"'));
990
990
  } else if (!assertString(ppId, "payeeParty.peppolId", errors)) {
991
991
  } else if (!ppId.includes(":")) {
992
992
  errors.push(error("payeeParty.peppolId", `Invalid Peppol ID format: "${ppId}"`, void 0, 'Must be "scheme:id" format'));
@@ -1059,7 +1059,7 @@ function validateInvoice(input) {
1059
1059
  }
1060
1060
 
1061
1061
  // ../sdk/dist/version.js
1062
- var SDK_VERSION = "1.5.1";
1062
+ var SDK_VERSION = "1.9.0";
1063
1063
 
1064
1064
  // ../sdk/dist/core/client.js
1065
1065
  function findHeaderCaseInsensitive(headers, name) {
@@ -1286,12 +1286,7 @@ var GetpepprAdapter = class {
1286
1286
  const invoices = result.invoices ?? result.data ?? [];
1287
1287
  const meta = result.meta;
1288
1288
  return {
1289
- data: invoices.map((inv) => ({
1290
- id: String(inv.id ?? ""),
1291
- number: String(inv.number ?? ""),
1292
- status: mapStatus(String(inv.state ?? inv.status ?? "submitted")),
1293
- createdAt: inv.created_at ? String(inv.created_at) : void 0
1294
- })),
1289
+ data: invoices.map(parseInvoiceSummary),
1295
1290
  meta: {
1296
1291
  totalCount: Number(meta?.total_count ?? invoices.length),
1297
1292
  offset: Number(meta?.offset ?? options?.offset ?? 0),
@@ -1306,7 +1301,8 @@ var GetpepprAdapter = class {
1306
1301
  return parseSendResult(result);
1307
1302
  }
1308
1303
  async lookupDirectory(scheme, id) {
1309
- return this.request("GET", `/directory/${scheme}/${id}`);
1304
+ const result = await this.request("GET", `/directory/${scheme}/${id}`);
1305
+ return parseDirectoryEntry(result);
1310
1306
  }
1311
1307
  async searchDirectory(params) {
1312
1308
  const query = new URLSearchParams(params).toString();
@@ -1490,6 +1486,58 @@ var GetpepprAdapter = class {
1490
1486
  async deleteContact(id) {
1491
1487
  await this.request("DELETE", `/contacts/${id}`);
1492
1488
  }
1489
+ async createLegalEntity(input, options) {
1490
+ const headers = {};
1491
+ if (options?.idempotencyKey)
1492
+ headers["Idempotency-Key"] = options.idempotencyKey;
1493
+ const result = await this.request("POST", "/legal-entities", input, headers);
1494
+ return parseLegalEntity(result);
1495
+ }
1496
+ async getLegalEntity(id) {
1497
+ const result = await this.request("GET", `/legal-entities/${id}`);
1498
+ return parseLegalEntity(result);
1499
+ }
1500
+ async listLegalEntities(options) {
1501
+ const params = new URLSearchParams();
1502
+ if (options?.limit != null)
1503
+ params.set("limit", String(options.limit));
1504
+ if (options?.offset != null)
1505
+ params.set("offset", String(options.offset));
1506
+ const query = params.toString() ? `?${params.toString()}` : "";
1507
+ const result = await this.request("GET", `/legal-entities${query}`);
1508
+ const rows = result.data ?? [];
1509
+ const pagination = result.pagination;
1510
+ return {
1511
+ data: rows.map(parseLegalEntity),
1512
+ meta: {
1513
+ totalCount: Number(pagination?.total_count ?? rows.length),
1514
+ offset: Number(pagination?.offset ?? options?.offset ?? 0),
1515
+ limit: Number(pagination?.limit ?? options?.limit ?? 50),
1516
+ hasMore: Boolean(pagination?.has_more ?? false),
1517
+ truncated: false
1518
+ }
1519
+ };
1520
+ }
1521
+ async archiveLegalEntity(id) {
1522
+ const result = await this.request("DELETE", `/legal-entities/${id}`);
1523
+ return {
1524
+ id: String(result.id ?? id),
1525
+ externalId: result.externalId != null ? String(result.externalId) : null,
1526
+ status: "archived"
1527
+ };
1528
+ }
1529
+ async requestLegalEntityAttestation(id, input, options) {
1530
+ const headers = {};
1531
+ if (options?.idempotencyKey)
1532
+ headers["Idempotency-Key"] = options.idempotencyKey;
1533
+ const result = await this.request("POST", `/legal-entities/${id}/attestation`, input, headers);
1534
+ return {
1535
+ id: String(result.id ?? id),
1536
+ externalId: result.externalId != null ? String(result.externalId) : null,
1537
+ status: String(result.status ?? ""),
1538
+ expiresAt: String(result.expiresAt ?? "")
1539
+ };
1540
+ }
1493
1541
  async listBankAccounts(options) {
1494
1542
  const params = new URLSearchParams();
1495
1543
  if (options?.limit != null)
@@ -1595,6 +1643,40 @@ function parseSendResult(result) {
1595
1643
  warnings: Array.isArray(result.warnings) ? result.warnings : void 0
1596
1644
  };
1597
1645
  }
1646
+ function parseDirectoryEntry(result) {
1647
+ const participant = isRecord(result.participant) ? result.participant : result;
1648
+ const scheme = participant.scheme == null ? void 0 : String(participant.scheme);
1649
+ const id = participant.id == null ? void 0 : String(participant.id);
1650
+ const peppolId = participant.peppolId == null ? formatPeppolId(scheme, id) : String(participant.peppolId);
1651
+ return {
1652
+ name: String(participant.name ?? ""),
1653
+ peppolId,
1654
+ country: String(participant.country ?? ""),
1655
+ capabilities: Array.isArray(participant.capabilities) ? participant.capabilities.map(String) : [],
1656
+ registrationDate: participant.registrationDate == null ? void 0 : String(participant.registrationDate),
1657
+ vatNumber: participant.vatNumber == null ? void 0 : String(participant.vatNumber),
1658
+ additionalIds: Array.isArray(participant.additionalIds) ? participant.additionalIds.filter(isRecord).map((entry) => ({
1659
+ scheme: String(entry.scheme ?? ""),
1660
+ value: String(entry.value ?? "")
1661
+ })) : void 0,
1662
+ contactInfo: isRecord(participant.contactInfo) ? {
1663
+ name: participant.contactInfo.name == null ? void 0 : String(participant.contactInfo.name),
1664
+ email: participant.contactInfo.email == null ? void 0 : String(participant.contactInfo.email),
1665
+ phone: participant.contactInfo.phone == null ? void 0 : String(participant.contactInfo.phone)
1666
+ } : void 0,
1667
+ website: participant.website == null ? void 0 : String(participant.website)
1668
+ };
1669
+ }
1670
+ function formatPeppolId(scheme, id) {
1671
+ if (!id)
1672
+ return scheme ? `${scheme}:` : "";
1673
+ if (id.includes(":"))
1674
+ return id;
1675
+ return scheme ? `${scheme}:${id}` : id;
1676
+ }
1677
+ function isRecord(value) {
1678
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1679
+ }
1598
1680
  function parseContact(raw) {
1599
1681
  const contact = {
1600
1682
  id: String(raw.id ?? ""),
@@ -1632,6 +1714,44 @@ function parseContact(raw) {
1632
1714
  contact.directoryLastChecked = String(raw.directoryLastChecked);
1633
1715
  return contact;
1634
1716
  }
1717
+ function parseLegalEntity(raw) {
1718
+ const idObj = raw.identifier;
1719
+ const le = {
1720
+ id: String(raw.id ?? ""),
1721
+ externalId: raw.externalId != null ? String(raw.externalId) : null,
1722
+ companyName: raw.companyName != null ? String(raw.companyName) : null,
1723
+ country: raw.country != null ? String(raw.country) : null,
1724
+ identifier: idObj && idObj.scheme != null && idObj.value != null ? { scheme: String(idObj.scheme), value: String(idObj.value) } : null,
1725
+ status: String(raw.status ?? "pending"),
1726
+ environment: String(raw.environment ?? ""),
1727
+ createdAt: String(raw.createdAt ?? "")
1728
+ };
1729
+ if (raw.verificationDetail != null) {
1730
+ le.verificationDetail = raw.verificationDetail;
1731
+ }
1732
+ return le;
1733
+ }
1734
+ function parseInvoiceSummary(raw) {
1735
+ const summary = {
1736
+ id: String(raw.id ?? ""),
1737
+ number: String(raw.invoiceNumber ?? raw.number ?? ""),
1738
+ status: mapStatus(String(raw.state ?? raw.status ?? "submitted"))
1739
+ };
1740
+ if (raw.createdAt != null)
1741
+ summary.createdAt = String(raw.createdAt);
1742
+ if (typeof raw.isCreditNote === "boolean")
1743
+ summary.isCreditNote = raw.isCreditNote;
1744
+ if (raw.recipientName != null)
1745
+ summary.recipientName = String(raw.recipientName);
1746
+ if (raw.totalAmount != null && Number.isFinite(Number(raw.totalAmount))) {
1747
+ summary.totalAmount = Number(raw.totalAmount);
1748
+ }
1749
+ if (raw.currency != null)
1750
+ summary.currency = String(raw.currency);
1751
+ if (raw.environment != null)
1752
+ summary.environment = String(raw.environment);
1753
+ return summary;
1754
+ }
1635
1755
  function parseBankAccount(raw) {
1636
1756
  const account = {
1637
1757
  id: String(raw.id ?? ""),
@@ -1710,6 +1830,19 @@ var PeppolApiError = class extends PeppolError {
1710
1830
  this.name = "PeppolApiError";
1711
1831
  this.retryAfterMs = retryAfterMs;
1712
1832
  }
1833
+ /**
1834
+ * The gateway's machine-readable error code, parsed from the JSON response body
1835
+ * (e.g. "le_cap_exceeded", "identifier_immutable", "legal_entity_locked", "forbidden").
1836
+ * Returns undefined when the body is not JSON or carries no string `code`.
1837
+ */
1838
+ get code() {
1839
+ try {
1840
+ const parsed = JSON.parse(this.responseBody);
1841
+ return typeof parsed?.code === "string" ? parsed.code : void 0;
1842
+ } catch {
1843
+ return void 0;
1844
+ }
1845
+ }
1713
1846
  };
1714
1847
  var Peppol = class {
1715
1848
  adapter;
@@ -1720,6 +1853,7 @@ var Peppol = class {
1720
1853
  contacts;
1721
1854
  bankAccounts;
1722
1855
  transports;
1856
+ legalEntities;
1723
1857
  constructor(config) {
1724
1858
  if (!config.apiKey) {
1725
1859
  throw new PeppolError("API key is required. Sign up at https://console.getpeppr.dev to get your sandbox key.");
@@ -1732,6 +1866,7 @@ var Peppol = class {
1732
1866
  this.contacts = new ContactOperations(this.adapter);
1733
1867
  this.bankAccounts = new BankAccountOperations(this.adapter);
1734
1868
  this.transports = new TransportOperations(this.adapter);
1869
+ this.legalEntities = new LegalEntityOperations(this.adapter);
1735
1870
  }
1736
1871
  /**
1737
1872
  * Validate an invoice without sending it.
@@ -1819,8 +1954,8 @@ ${validation.errors.map((e) => ` - ${e.field}: ${e.message}${e.suggestion ? ` (
1819
1954
  * ```ts
1820
1955
  * const result = await peppol.invoices.send({
1821
1956
  * number: "INV-001",
1822
- * from: { name: "My Company", peppolId: "0208:0123456789", country: "BE" },
1823
- * to: { name: "Client Co", peppolId: "0208:0987654321", country: "BE" },
1957
+ * from: { name: "My Company", peppolId: "0208:0685660237", country: "BE" },
1958
+ * to: { name: "Client Co", peppolId: "0208:0685660237", country: "BE" },
1824
1959
  * lines: [
1825
1960
  * { description: "Consulting", quantity: 10, unitPrice: 150, vatRate: 21 }
1826
1961
  * ]
@@ -1882,7 +2017,7 @@ ${validation.errors.map((e) => ` - ${e.field}: ${e.message}${e.suggestion ? ` (
1882
2017
  * ```ts
1883
2018
  * const result = await peppol.invoices.validateServer({
1884
2019
  * number: "INV-001",
1885
- * to: { name: "Acme", peppolId: "0208:0123456789", country: "BE" },
2020
+ * to: { name: "Acme", peppolId: "0208:0685660237", country: "BE" },
1886
2021
  * lines: [{ description: "Item", quantity: 1, unitPrice: 100, vatRate: 21 }]
1887
2022
  * });
1888
2023
  * console.log(result.valid, result.schematron.errors);
@@ -2079,14 +2214,14 @@ var DirectoryOperations = class {
2079
2214
  *
2080
2215
  * @example
2081
2216
  * ```ts
2082
- * const entry = await peppol.directory.lookup("0208:0123456789");
2217
+ * const entry = await peppol.directory.lookup("0208:0685660237");
2083
2218
  * console.log(entry.name, entry.capabilities);
2084
2219
  * ```
2085
2220
  */
2086
2221
  async lookup(peppolId) {
2087
2222
  const colonIndex = peppolId.indexOf(":");
2088
2223
  if (colonIndex === -1) {
2089
- throw new PeppolError('Invalid Peppol ID format. Expected "scheme:id" (e.g., "0208:0123456789")');
2224
+ throw new PeppolError('Invalid Peppol ID format. Expected "scheme:id" (e.g., "0208:0685660237")');
2090
2225
  }
2091
2226
  const scheme = peppolId.slice(0, colonIndex);
2092
2227
  const id = peppolId.slice(colonIndex + 1);
@@ -2131,7 +2266,7 @@ var DirectoryOperations = class {
2131
2266
  *
2132
2267
  * @example
2133
2268
  * ```ts
2134
- * const result = await peppol.directory.searchByVat("BE0123456789");
2269
+ * const result = await peppol.directory.searchByVat("BE0685660237");
2135
2270
  * ```
2136
2271
  */
2137
2272
  async searchByVat(vatNumber) {
@@ -2207,8 +2342,8 @@ var ContactOperations = class {
2207
2342
  * @example
2208
2343
  * ```ts
2209
2344
  * const contact = await peppol.contacts.create({
2210
- * name: "Acme Corp",
2211
- * peppolId: "0208:0123456789",
2345
+ * name: "ACMEDIA",
2346
+ * peppolId: "0208:0685660237",
2212
2347
  * country: "BE",
2213
2348
  * isClient: true,
2214
2349
  * });
@@ -2253,6 +2388,74 @@ var ContactOperations = class {
2253
2388
  return paginate((offset, limit) => this.adapter.listContacts({ ...options, offset, limit }), options);
2254
2389
  }
2255
2390
  };
2391
+ var LegalEntityOperations = class {
2392
+ adapter;
2393
+ constructor(adapter) {
2394
+ this.adapter = adapter;
2395
+ }
2396
+ /**
2397
+ * Create a sub-tenant Legal Entity for one of your customers.
2398
+ *
2399
+ * Idempotent on `externalId`: repeated calls with the same `externalId` return
2400
+ * the existing entity (HTTP 200) instead of creating a duplicate. Transient 5xx
2401
+ * failures are NOT auto-retried unless you pass `options.idempotencyKey`.
2402
+ *
2403
+ * @example
2404
+ * ```ts
2405
+ * const le = await peppol.legalEntities.create({
2406
+ * externalId: "tenant-42",
2407
+ * companyName: "Acme Health AB",
2408
+ * country: "SE",
2409
+ * address: { line1: "Storgatan 1", city: "Stockholm", zip: "11122" },
2410
+ * identifier: { scheme: "0007", value: "5560000001" },
2411
+ * }, { idempotencyKey: "tenant-42-create" });
2412
+ * ```
2413
+ */
2414
+ async create(input, options) {
2415
+ return this.adapter.createLegalEntity(input, options);
2416
+ }
2417
+ /**
2418
+ * Fetch a single sub-tenant Legal Entity by id. For production entities the
2419
+ * `status` reflects the attestation lifecycle (awaiting_authz → attested → active).
2420
+ */
2421
+ async get(id) {
2422
+ return this.adapter.getLegalEntity(id);
2423
+ }
2424
+ /** List your sub-tenant Legal Entities, newest first. */
2425
+ async list(options) {
2426
+ return this.adapter.listLegalEntities(options);
2427
+ }
2428
+ /**
2429
+ * Async iterator over all sub-tenant Legal Entities, handling pagination.
2430
+ *
2431
+ * @example
2432
+ * ```ts
2433
+ * for await (const le of peppol.legalEntities.listAll()) console.log(le.id, le.status);
2434
+ * ```
2435
+ */
2436
+ listAll(options) {
2437
+ return paginate((offset, limit) => this.adapter.listLegalEntities({ ...options, offset, limit }), options);
2438
+ }
2439
+ /** Archive (soft-delete) a sub-tenant Legal Entity. The id stays resolvable for audit. */
2440
+ async archive(id) {
2441
+ return this.adapter.archiveLegalEntity(id);
2442
+ }
2443
+ /**
2444
+ * Request a sub-tenant attestation (production only). Emails the co-branded
2445
+ * confirmation link to the sub-tenant contact and returns the pending status.
2446
+ *
2447
+ * Transient failures are NOT auto-retried unless you pass `options.idempotencyKey`;
2448
+ * re-issuing mints a fresh token, so a retried call is safe.
2449
+ *
2450
+ * @example
2451
+ * ```ts
2452
+ * await peppol.legalEntities.requestAttestation(le.id, { contactEmail: "owner@acme.example" });
2453
+ * ```
2454
+ */
2455
+ async requestAttestation(id, input, options) {
2456
+ return this.adapter.requestLegalEntityAttestation(id, input, options);
2457
+ }
2458
+ };
2256
2459
  var BankAccountOperations = class {
2257
2460
  adapter;
2258
2461
  constructor(adapter) {
@@ -2789,7 +2992,7 @@ var INVOICE_TEMPLATE = {
2789
2992
  buyerReference: "PO-2026-042",
2790
2993
  from: {
2791
2994
  name: "Dupont & Fils SPRL",
2792
- peppolId: "0208:0123456789",
2995
+ peppolId: "0208:0685660237",
2793
2996
  street: "Avenue Louise 54",
2794
2997
  city: "Bruxelles",
2795
2998
  postalCode: "1050",
@@ -2831,7 +3034,7 @@ var CREDIT_NOTE_TEMPLATE = {
2831
3034
  invoiceReference: "INV-2026-001",
2832
3035
  from: {
2833
3036
  name: "Dupont & Fils SPRL",
2834
- peppolId: "0208:0123456789",
3037
+ peppolId: "0208:0685660237",
2835
3038
  street: "Avenue Louise 54",
2836
3039
  city: "Bruxelles",
2837
3040
  postalCode: "1050",
@@ -2882,6 +3085,11 @@ function registerInitCommand(program2) {
2882
3085
  1. Edit the file with your invoice data
2883
3086
  2. Validate: getpeppr validate ${filename}
2884
3087
  3. Convert to XML: getpeppr convert ${filename}
3088
+ 4. Send: getpeppr send ${filename}
3089
+
3090
+ ${pc2.dim("Sandbox note:")} this template includes VAT. To send it on a sandbox
3091
+ account, first register your VAT in Settings \u2192 Peppol Identity, or set each
3092
+ line's "vatCategory" to "O" (outside the scope of VAT) for a no-VAT test send.
2885
3093
  `);
2886
3094
  process.exit(0);
2887
3095
  }