@enyo-energy/energy-app-sdk 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -367,6 +367,7 @@ Energy Apps use a granular permissions system to control access to system resour
367
367
  - **`Savings`**: Publish and read back day-scoped savings reports
368
368
  - **`EpexSpotPrices`**: Read EPEX SPOT day-ahead wholesale market prices
369
369
  - **`GridFeeRegister`** / **`GridFeeUse`**: Publish / resolve dynamic grid fees (time-variable network charges)
370
+ - **`CommandLog`**: Record and read back the commands apps issued to appliances
370
371
 
371
372
  #### Site & Identity Permissions
372
373
 
@@ -1500,6 +1501,50 @@ Notes worth respecting when producing a report:
1500
1501
 
1501
1502
  Requires the `Savings` permission.
1502
1503
 
1504
+ #### `useCommandLog(): EnergyAppCommandLog`
1505
+
1506
+ Record which register, configuration key or message your app wrote to an appliance — and read those
1507
+ writes back. This is the answer to *"why did the device do that?"*: an appliance's state shows what it
1508
+ is doing now, not that you wrote holding register 40023 four seconds ago because the energy manager cut
1509
+ available power, nor that the write was rejected.
1510
+
1511
+ ```typescript
1512
+ const commandLog = energyApp.useCommandLog();
1513
+
1514
+ await commandLog.logCommand({
1515
+ applianceId,
1516
+ protocol: EnyoCommandProtocolEnum.Modbus,
1517
+ operation: 'write-single-register',
1518
+ target: '40023',
1519
+ previousValue: String(before),
1520
+ newValue: String(limitW),
1521
+ outcome: EnyoCommandOutcomeEnum.Success,
1522
+ reason: 'energy manager reduced available power',
1523
+ });
1524
+
1525
+ // Everything we failed to write to this appliance today.
1526
+ const failures = await commandLog.getCommands({
1527
+ applianceId,
1528
+ outcome: EnyoCommandOutcomeEnum.Failed,
1529
+ fromIso: startOfDay,
1530
+ });
1531
+ ```
1532
+
1533
+ - **Writes, not reads.** Polling a register every two seconds does not belong here; recording reads
1534
+ would bury the writes the log exists for.
1535
+ - **Log the attempt, not only the success.** `Failed`, `TimedOut` and `Rejected` entries are the ones
1536
+ someone goes looking for. `Rejected` is separate from `Failed` on purpose — a device that answered and
1537
+ refused is a different diagnosis from one that could not be reached.
1538
+ - **Fill `reason`.** *What* was written can be reconstructed from the device; *why* cannot.
1539
+ - **Never gate the command on the log.** Log after the write is attempted; a failure to log must not
1540
+ surface as a failure to control the appliance.
1541
+ - **Append-only.** No update, no delete — a log the app can rewrite cannot explain an incident.
1542
+ Retention is the host's.
1543
+ - **Reads span every app** unless you filter by `packageName`: two packages fighting over the same
1544
+ register are invisible from one app's own entries.
1545
+
1546
+ Requires the `CommandLog` permission.
1547
+
1503
1548
  ### Operational Utilities
1504
1549
 
1505
1550
  #### `useOnboardingV2(): EnergyAppOnboardingV2`
@@ -62,4 +62,5 @@ var EnergyAppPermissionTypeEnum;
62
62
  EnergyAppPermissionTypeEnum["FirmwareRegistry"] = "FirmwareRegistry";
63
63
  EnergyAppPermissionTypeEnum["GridFeeRegister"] = "GridFeeRegister";
64
64
  EnergyAppPermissionTypeEnum["GridFeeUse"] = "GridFeeUse";
65
+ EnergyAppPermissionTypeEnum["CommandLog"] = "CommandLog";
65
66
  })(EnergyAppPermissionTypeEnum || (exports.EnergyAppPermissionTypeEnum = EnergyAppPermissionTypeEnum = {}));
@@ -57,7 +57,8 @@ export declare enum EnergyAppPermissionTypeEnum {
57
57
  EpexSpotPrices = "EpexSpotPrices",
58
58
  FirmwareRegistry = "FirmwareRegistry",
59
59
  GridFeeRegister = "GridFeeRegister",
60
- GridFeeUse = "GridFeeUse"
60
+ GridFeeUse = "GridFeeUse",
61
+ CommandLog = "CommandLog"
61
62
  }
62
63
  /**
63
64
  * String union of every permission name, derived from
@@ -465,6 +465,22 @@ class EnergyApp {
465
465
  useGridFee() {
466
466
  return this.energyAppSdk.useGridFee();
467
467
  }
468
+ /**
469
+ * Gets the Command Log API for recording the writes this app issued to an
470
+ * appliance — which Modbus register, which OCPP configuration key or
471
+ * message, with what it was before and whether the device took it.
472
+ *
473
+ * An appliance's state shows what it is doing; the command log shows what
474
+ * was done to it and why, which is what an incident investigation actually
475
+ * needs. Entries are append-only, and reads span every app so that two
476
+ * packages fighting over the same register are visible.
477
+ * @returns The Command Log API instance
478
+ * @throws {EnergyAppPermissionNotGrantedError} If the `CommandLog`
479
+ * permission is not granted.
480
+ */
481
+ useCommandLog() {
482
+ return this.energyAppSdk.useCommandLog();
483
+ }
468
484
  /**
469
485
  * Gets the current SDK version.
470
486
  * @returns The semantic version string of the SDK
@@ -46,6 +46,7 @@ import { EnergyAppSavings } from "./packages/energy-app-savings.cjs";
46
46
  import { EnergyAppDeviceTest } from "./packages/energy-app-device-test.cjs";
47
47
  import { EnergyAppEpexSpotPrice } from "./packages/energy-app-epex-spot-price.cjs";
48
48
  import { EnergyAppGridFee } from "./packages/energy-app-grid-fee.cjs";
49
+ import { EnergyAppCommandLog } from "./packages/energy-app-command-log.cjs";
49
50
  import { UseFetchOptions } from "./types/enyo-fetch.cjs";
50
51
  /**
51
52
  * Concrete implementation of {@link EnyoEnergyAppSdk} that delegates every call
@@ -387,6 +388,20 @@ export declare class EnergyApp implements EnyoEnergyAppSdk {
387
388
  * `GridFeeUse` permission required by the called method is not granted.
388
389
  */
389
390
  useGridFee(): EnergyAppGridFee;
391
+ /**
392
+ * Gets the Command Log API for recording the writes this app issued to an
393
+ * appliance — which Modbus register, which OCPP configuration key or
394
+ * message, with what it was before and whether the device took it.
395
+ *
396
+ * An appliance's state shows what it is doing; the command log shows what
397
+ * was done to it and why, which is what an incident investigation actually
398
+ * needs. Entries are append-only, and reads span every app so that two
399
+ * packages fighting over the same register are visible.
400
+ * @returns The Command Log API instance
401
+ * @throws {EnergyAppPermissionNotGrantedError} If the `CommandLog`
402
+ * permission is not granted.
403
+ */
404
+ useCommandLog(): EnergyAppCommandLog;
390
405
  /**
391
406
  * Gets the current SDK version.
392
407
  * @returns The semantic version string of the SDK
@@ -45,6 +45,7 @@ import { EnergyAppSavings } from "./packages/energy-app-savings.cjs";
45
45
  import { EnergyAppDeviceTest } from "./packages/energy-app-device-test.cjs";
46
46
  import { EnergyAppEpexSpotPrice } from "./packages/energy-app-epex-spot-price.cjs";
47
47
  import { EnergyAppGridFee } from "./packages/energy-app-grid-fee.cjs";
48
+ import { EnergyAppCommandLog } from "./packages/energy-app-command-log.cjs";
48
49
  import { UseFetchOptions } from "./types/enyo-fetch.cjs";
49
50
  export declare enum EnergyAppStateEnum {
50
51
  Launching = "launching",
@@ -166,4 +167,6 @@ export interface EnyoEnergyAppSdk {
166
167
  useEpexSpotPrices: () => EnergyAppEpexSpotPrice;
167
168
  /** Get the Grid Fee API for publishing time-dependent network charges and resolving them to a 15-minute series */
168
169
  useGridFee: () => EnergyAppGridFee;
170
+ /** Get the Command Log API for recording which register, configuration key or message this app wrote to an appliance, and reading those commands back */
171
+ useCommandLog: () => EnergyAppCommandLog;
169
172
  }
@@ -147,6 +147,8 @@ __exportStar(require("./packages/energy-app-device-test.cjs"), exports);
147
147
  __exportStar(require("./implementations/device-test/device-test-validators.cjs"), exports);
148
148
  __exportStar(require("./types/enyo-savings.cjs"), exports);
149
149
  __exportStar(require("./packages/energy-app-savings.cjs"), exports);
150
+ __exportStar(require("./types/enyo-command-log.cjs"), exports);
151
+ __exportStar(require("./packages/energy-app-command-log.cjs"), exports);
150
152
  __exportStar(require("./types/enyo-epex-spot-price.cjs"), exports);
151
153
  __exportStar(require("./packages/energy-app-epex-spot-price.cjs"), exports);
152
154
  __exportStar(require("./types/enyo-automation.cjs"), exports);
@@ -131,6 +131,8 @@ export * from './packages/energy-app-device-test.cjs';
131
131
  export * from './implementations/device-test/device-test-validators.cjs';
132
132
  export * from './types/enyo-savings.cjs';
133
133
  export * from './packages/energy-app-savings.cjs';
134
+ export * from './types/enyo-command-log.cjs';
135
+ export * from './packages/energy-app-command-log.cjs';
134
136
  export * from './types/enyo-epex-spot-price.cjs';
135
137
  export * from './packages/energy-app-epex-spot-price.cjs';
136
138
  export * from './types/enyo-automation.cjs';
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,108 @@
1
+ import { EnyoCommandLogEntry, EnyoCommandLogEntryInput, EnyoCommandLogFilter } from "../types/enyo-command-log.cjs";
2
+ /**
3
+ * Interface for recording and reading back the commands an energy app issued to
4
+ * an appliance.
5
+ *
6
+ * This is the answer to "why did the device do that?". An appliance's own state
7
+ * shows what it is doing now; it does not show that this app wrote holding
8
+ * register 40023 four seconds ago because the energy manager cut the available
9
+ * power, nor that the write was rejected. Without a record of the writes, that
10
+ * has to be reconstructed from log files that may already have rotated away.
11
+ *
12
+ * The unit is a **write, not a read**. Polling a register every two seconds does
13
+ * not belong here — an entry is a command that changed, or tried to change,
14
+ * something on the device. Recording reads would bury the writes.
15
+ *
16
+ * Entries are append-only: there is no update and no delete. A log an app can
17
+ * rewrite cannot be trusted to explain an incident, and retention is the host's
18
+ * to enforce.
19
+ *
20
+ * Access to this API requires the `CommandLog` permission
21
+ * ({@link EnergyAppPermissionType}); {@link EnergyApp.useCommandLog} throws when
22
+ * it has not been granted.
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * const commandLog = energyApp.useCommandLog();
27
+ *
28
+ * const before = await modbus.readHoldingRegisters(40023, 1);
29
+ * try {
30
+ * await modbus.writeSingleRegister(40023, limitW);
31
+ * await commandLog.logCommand({
32
+ * applianceId,
33
+ * protocol: EnyoCommandProtocolEnum.Modbus,
34
+ * operation: 'write-single-register',
35
+ * target: '40023',
36
+ * previousValue: String(before.readUInt16BE(0)),
37
+ * newValue: String(limitW),
38
+ * outcome: EnyoCommandOutcomeEnum.Success,
39
+ * reason: 'energy manager reduced available power',
40
+ * });
41
+ * } catch (error) {
42
+ * await commandLog.logCommand({
43
+ * applianceId,
44
+ * protocol: EnyoCommandProtocolEnum.Modbus,
45
+ * operation: 'write-single-register',
46
+ * target: '40023',
47
+ * newValue: String(limitW),
48
+ * outcome: EnyoCommandOutcomeEnum.Failed,
49
+ * errorMessage: String(error),
50
+ * reason: 'energy manager reduced available power',
51
+ * });
52
+ * }
53
+ * ```
54
+ */
55
+ export interface EnergyAppCommandLog {
56
+ /**
57
+ * Appends one command to the log.
58
+ *
59
+ * Log the attempt, not only the success: a command that timed out or was
60
+ * rejected is the entry someone will actually go looking for, and an app
61
+ * that only records its successes produces a log where nothing ever went
62
+ * wrong. Set {@link EnyoCommandLogEntry.outcome} accordingly and put the
63
+ * detail in `errorMessage`.
64
+ *
65
+ * Fill {@link EnyoCommandLogEntry.reason} whenever the app knows it. What was
66
+ * written can be reconstructed from the device; why it was written cannot.
67
+ *
68
+ * This call is for auditing and must never gate the command itself — log
69
+ * after the write has been attempted, and do not let a failure to log
70
+ * surface as a failure to control the appliance.
71
+ *
72
+ * Requires the `CommandLog` permission.
73
+ *
74
+ * @param entry - The command to record. `id`, `packageName` and (unless
75
+ * supplied) `timestampIso` are assigned by the host.
76
+ * @returns Promise resolving to the stored entry, including its assigned id
77
+ * @throws {EnergyAppPermissionNotGrantedError} If the `CommandLog` permission
78
+ * is not granted.
79
+ */
80
+ logCommand(entry: EnyoCommandLogEntryInput): Promise<EnyoCommandLogEntry>;
81
+ /**
82
+ * Reads recorded commands back, newest first.
83
+ *
84
+ * Returns entries from **every** app, not just this one, unless
85
+ * {@link EnyoCommandLogFilter.packageName} narrows it. That is deliberate:
86
+ * the failure this log exists to explain is often two apps writing the same
87
+ * register, which is invisible from either app's own entries.
88
+ *
89
+ * Requires the `CommandLog` permission.
90
+ *
91
+ * @param filter - Optional narrowing by appliance, protocol, outcome, time
92
+ * range or package. Fields combine with AND.
93
+ * @returns Promise resolving to the matching entries, newest first
94
+ * @throws {EnergyAppPermissionNotGrantedError} If the `CommandLog` permission
95
+ * is not granted.
96
+ *
97
+ * @example
98
+ * ```typescript
99
+ * // Everything we failed to write to this appliance today.
100
+ * const failures = await commandLog.getCommands({
101
+ * applianceId,
102
+ * outcome: EnyoCommandOutcomeEnum.Failed,
103
+ * fromIso: startOfDay,
104
+ * });
105
+ * ```
106
+ */
107
+ getCommands(filter?: EnyoCommandLogFilter): Promise<EnyoCommandLogEntry[]>;
108
+ }
@@ -65,4 +65,27 @@ export interface EnyoBatteryApplianceMetadata {
65
65
  * (0–100). The EMS should not charge the battery above this value.
66
66
  */
67
67
  maxSoC?: number;
68
+ /**
69
+ * Observed round-trip efficiency as a percentage (0–100): of the energy put
70
+ * into the battery, how much comes back out again.
71
+ *
72
+ * A **calculated** value, not a nameplate figure — derived from measured
73
+ * charge and discharge energy over completed cycles. It therefore drifts
74
+ * with cell age, temperature and depth of discharge, and is absent until
75
+ * enough cycles have been observed to compute it. Consumers should treat a
76
+ * missing value as "not yet known" rather than substituting a datasheet
77
+ * number.
78
+ *
79
+ * This is what decides whether a charge/discharge cycle is worth running at
80
+ * all: arbitrage pays only when the price spread exceeds the energy lost to
81
+ * the round trip, so an optimizer that assumes 100% will schedule cycles
82
+ * that lose money.
83
+ *
84
+ * Measure it at the same boundary the battery reports its power and meter
85
+ * values at. An AC-to-AC round trip includes the inverter's conversion
86
+ * losses in both directions and typically lands several points below the
87
+ * DC-to-DC figure for the same pack — quoting one where the other is
88
+ * expected silently misprices every cycle.
89
+ */
90
+ roundTripEfficiencyPercent?: number;
68
91
  }
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EnyoCommandOutcomeEnum = exports.EnyoCommandProtocolEnum = void 0;
4
+ /**
5
+ * The protocol a logged command was issued over.
6
+ *
7
+ * Names the transport, not the appliance: the same wallbox may be written to
8
+ * over Modbus TCP for power limits and over OCPP for configuration keys, and an
9
+ * entry is only interpretable once you know which.
10
+ */
11
+ var EnyoCommandProtocolEnum;
12
+ (function (EnyoCommandProtocolEnum) {
13
+ /** Modbus TCP */
14
+ EnyoCommandProtocolEnum["Modbus"] = "modbus";
15
+ /** Modbus RTU (serial) */
16
+ EnyoCommandProtocolEnum["ModbusRtu"] = "modbus-rtu";
17
+ /** OCPP — a configuration change or a call sent to a charge point */
18
+ EnyoCommandProtocolEnum["Ocpp"] = "ocpp";
19
+ /** EEBUS SHIP/SPINE */
20
+ EnyoCommandProtocolEnum["Eebus"] = "eebus";
21
+ /** MQTT publish */
22
+ EnyoCommandProtocolEnum["Mqtt"] = "mqtt";
23
+ /** HTTP request against a vendor API or local endpoint */
24
+ EnyoCommandProtocolEnum["Http"] = "http";
25
+ /** Bluetooth Low Energy GATT write */
26
+ EnyoCommandProtocolEnum["Bluetooth"] = "bluetooth";
27
+ /** UDP datagram */
28
+ EnyoCommandProtocolEnum["Udp"] = "udp";
29
+ /** Anything else — describe it in {@link EnyoCommandLogEntry.operation} */
30
+ EnyoCommandProtocolEnum["Other"] = "other";
31
+ })(EnyoCommandProtocolEnum || (exports.EnyoCommandProtocolEnum = EnyoCommandProtocolEnum = {}));
32
+ /**
33
+ * How a logged command ended.
34
+ *
35
+ * {@link Rejected} is deliberately separate from {@link Failed}: a device that
36
+ * answered and refused is a very different diagnosis from one that could not be
37
+ * reached, and collapsing the two is what makes a log unable to answer "did it
38
+ * hear us?".
39
+ */
40
+ var EnyoCommandOutcomeEnum;
41
+ (function (EnyoCommandOutcomeEnum) {
42
+ /** The device accepted the command. */
43
+ EnyoCommandOutcomeEnum["Success"] = "success";
44
+ /** The command could not be delivered or the device errored. */
45
+ EnyoCommandOutcomeEnum["Failed"] = "failed";
46
+ /** No answer arrived within the app's deadline. */
47
+ EnyoCommandOutcomeEnum["TimedOut"] = "timed-out";
48
+ /** The device answered and refused the command. */
49
+ EnyoCommandOutcomeEnum["Rejected"] = "rejected";
50
+ })(EnyoCommandOutcomeEnum || (exports.EnyoCommandOutcomeEnum = EnyoCommandOutcomeEnum = {}));
@@ -0,0 +1,151 @@
1
+ /**
2
+ * The protocol a logged command was issued over.
3
+ *
4
+ * Names the transport, not the appliance: the same wallbox may be written to
5
+ * over Modbus TCP for power limits and over OCPP for configuration keys, and an
6
+ * entry is only interpretable once you know which.
7
+ */
8
+ export declare enum EnyoCommandProtocolEnum {
9
+ /** Modbus TCP */
10
+ Modbus = "modbus",
11
+ /** Modbus RTU (serial) */
12
+ ModbusRtu = "modbus-rtu",
13
+ /** OCPP — a configuration change or a call sent to a charge point */
14
+ Ocpp = "ocpp",
15
+ /** EEBUS SHIP/SPINE */
16
+ Eebus = "eebus",
17
+ /** MQTT publish */
18
+ Mqtt = "mqtt",
19
+ /** HTTP request against a vendor API or local endpoint */
20
+ Http = "http",
21
+ /** Bluetooth Low Energy GATT write */
22
+ Bluetooth = "bluetooth",
23
+ /** UDP datagram */
24
+ Udp = "udp",
25
+ /** Anything else — describe it in {@link EnyoCommandLogEntry.operation} */
26
+ Other = "other"
27
+ }
28
+ /**
29
+ * How a logged command ended.
30
+ *
31
+ * {@link Rejected} is deliberately separate from {@link Failed}: a device that
32
+ * answered and refused is a very different diagnosis from one that could not be
33
+ * reached, and collapsing the two is what makes a log unable to answer "did it
34
+ * hear us?".
35
+ */
36
+ export declare enum EnyoCommandOutcomeEnum {
37
+ /** The device accepted the command. */
38
+ Success = "success",
39
+ /** The command could not be delivered or the device errored. */
40
+ Failed = "failed",
41
+ /** No answer arrived within the app's deadline. */
42
+ TimedOut = "timed-out",
43
+ /** The device answered and refused the command. */
44
+ Rejected = "rejected"
45
+ }
46
+ /**
47
+ * One recorded command an energy app issued to an appliance.
48
+ *
49
+ * The unit of the log is a **write**, not a read: which register was set, which
50
+ * OCPP configuration key was changed, which message was executed — with what it
51
+ * was before, what it became, and whether the device took it.
52
+ */
53
+ export interface EnyoCommandLogEntry {
54
+ /** Unique identifier of this entry. Assigned by the host. */
55
+ id: string;
56
+ /** The appliance the command was issued to. */
57
+ applianceId: string;
58
+ /** When the command was issued, in ISO format. */
59
+ timestampIso: string;
60
+ /** The package that issued it. Set by the host, not by the caller. */
61
+ packageName: string;
62
+ /** Which protocol carried the command. */
63
+ protocol: EnyoCommandProtocolEnum;
64
+ /**
65
+ * What was done, in the protocol's own vocabulary — `'write-holding-registers'`,
66
+ * `'ChangeConfiguration'`, `'SetChargingProfile'`, `'gatt-write'`.
67
+ *
68
+ * Use the name the protocol uses rather than a prose description, so entries
69
+ * from one protocol group and compare.
70
+ */
71
+ operation: string;
72
+ /**
73
+ * What was addressed: a register address (`'40023'`), an OCPP configuration
74
+ * key (`'MeterValueSampleInterval'`), an MQTT topic, a GATT characteristic
75
+ * UUID. Omit for commands that address nothing in particular.
76
+ */
77
+ target?: string;
78
+ /**
79
+ * The value before the write, rendered as a string. Omit when it was not
80
+ * read back first — do not substitute a value the app merely believed was
81
+ * there, or the log will explain changes that never happened.
82
+ */
83
+ previousValue?: string;
84
+ /** The value written, rendered as a string. */
85
+ newValue?: string;
86
+ /** How the command ended. */
87
+ outcome: EnyoCommandOutcomeEnum;
88
+ /**
89
+ * Why the app issued the command — `'energy manager reduced available power'`,
90
+ * `'user changed charge mode'`.
91
+ *
92
+ * The single most useful field when reading the log back: the *what* can be
93
+ * reconstructed from the device, the *why* cannot.
94
+ */
95
+ reason?: string;
96
+ /**
97
+ * The error or rejection detail, for any outcome other than
98
+ * {@link EnyoCommandOutcomeEnum.Success}.
99
+ */
100
+ errorMessage?: string;
101
+ /** How long the command took, in milliseconds. */
102
+ durationMs?: number;
103
+ /**
104
+ * Anything the flat fields cannot carry — a decoded payload, the full
105
+ * register block written, the OCPP response object.
106
+ *
107
+ * Values above carry strings so entries from different protocols share one
108
+ * shape and can be rendered in one table; structured data belongs here.
109
+ */
110
+ details?: Record<string, unknown>;
111
+ }
112
+ /**
113
+ * What an app passes to {@link EnergyAppCommandLog.logCommand}.
114
+ *
115
+ * `id` and `packageName` are assigned by the host. `timestampIso` may be
116
+ * supplied when the command was issued earlier than it is being logged —
117
+ * a batch flushed after a reconnect — and otherwise defaults to the moment the
118
+ * host handles the call.
119
+ */
120
+ export type EnyoCommandLogEntryInput = Omit<EnyoCommandLogEntry, 'id' | 'packageName' | 'timestampIso'> & {
121
+ timestampIso?: string;
122
+ };
123
+ /**
124
+ * Narrows which entries {@link EnergyAppCommandLog.getCommands} returns.
125
+ *
126
+ * Every field is optional and they combine with AND. With no filter at all, the
127
+ * most recent entries across every appliance are returned.
128
+ */
129
+ export interface EnyoCommandLogFilter {
130
+ /** Only commands issued to this appliance. */
131
+ applianceId?: string;
132
+ /** Only commands carried over this protocol. */
133
+ protocol?: EnyoCommandProtocolEnum;
134
+ /** Only commands that ended this way — e.g. every failure in the last day. */
135
+ outcome?: EnyoCommandOutcomeEnum;
136
+ /** Only commands issued at or after this ISO timestamp (inclusive). */
137
+ fromIso?: string;
138
+ /** Only commands issued before this ISO timestamp (exclusive). */
139
+ untilIso?: string;
140
+ /**
141
+ * Only commands issued by this package. Omit to see every app's commands —
142
+ * a device misbehaving because two apps are writing the same register is
143
+ * invisible from one app's own entries.
144
+ */
145
+ packageName?: string;
146
+ /**
147
+ * Maximum number of entries to return, newest first. The host applies its
148
+ * own upper bound when this is omitted or larger than that bound.
149
+ */
150
+ limit?: number;
151
+ }
@@ -9,7 +9,7 @@ exports.getSdkVersion = getSdkVersion;
9
9
  /**
10
10
  * Current version of the enyo Energy App SDK.
11
11
  */
12
- exports.SDK_VERSION = '1.5.0';
12
+ exports.SDK_VERSION = '1.6.0';
13
13
  /**
14
14
  * Gets the current SDK version.
15
15
  * @returns The semantic version string of the SDK
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * Current version of the enyo Energy App SDK.
7
7
  */
8
- export declare const SDK_VERSION = "1.5.0";
8
+ export declare const SDK_VERSION = "1.6.0";
9
9
  /**
10
10
  * Gets the current SDK version.
11
11
  * @returns The semantic version string of the SDK
@@ -57,7 +57,8 @@ export declare enum EnergyAppPermissionTypeEnum {
57
57
  EpexSpotPrices = "EpexSpotPrices",
58
58
  FirmwareRegistry = "FirmwareRegistry",
59
59
  GridFeeRegister = "GridFeeRegister",
60
- GridFeeUse = "GridFeeUse"
60
+ GridFeeUse = "GridFeeUse",
61
+ CommandLog = "CommandLog"
61
62
  }
62
63
  /**
63
64
  * String union of every permission name, derived from
@@ -59,4 +59,5 @@ export var EnergyAppPermissionTypeEnum;
59
59
  EnergyAppPermissionTypeEnum["FirmwareRegistry"] = "FirmwareRegistry";
60
60
  EnergyAppPermissionTypeEnum["GridFeeRegister"] = "GridFeeRegister";
61
61
  EnergyAppPermissionTypeEnum["GridFeeUse"] = "GridFeeUse";
62
+ EnergyAppPermissionTypeEnum["CommandLog"] = "CommandLog";
62
63
  })(EnergyAppPermissionTypeEnum || (EnergyAppPermissionTypeEnum = {}));
@@ -46,6 +46,7 @@ import { EnergyAppSavings } from "./packages/energy-app-savings.js";
46
46
  import { EnergyAppDeviceTest } from "./packages/energy-app-device-test.js";
47
47
  import { EnergyAppEpexSpotPrice } from "./packages/energy-app-epex-spot-price.js";
48
48
  import { EnergyAppGridFee } from "./packages/energy-app-grid-fee.js";
49
+ import { EnergyAppCommandLog } from "./packages/energy-app-command-log.js";
49
50
  import { UseFetchOptions } from "./types/enyo-fetch.js";
50
51
  /**
51
52
  * Concrete implementation of {@link EnyoEnergyAppSdk} that delegates every call
@@ -387,6 +388,20 @@ export declare class EnergyApp implements EnyoEnergyAppSdk {
387
388
  * `GridFeeUse` permission required by the called method is not granted.
388
389
  */
389
390
  useGridFee(): EnergyAppGridFee;
391
+ /**
392
+ * Gets the Command Log API for recording the writes this app issued to an
393
+ * appliance — which Modbus register, which OCPP configuration key or
394
+ * message, with what it was before and whether the device took it.
395
+ *
396
+ * An appliance's state shows what it is doing; the command log shows what
397
+ * was done to it and why, which is what an incident investigation actually
398
+ * needs. Entries are append-only, and reads span every app so that two
399
+ * packages fighting over the same register are visible.
400
+ * @returns The Command Log API instance
401
+ * @throws {EnergyAppPermissionNotGrantedError} If the `CommandLog`
402
+ * permission is not granted.
403
+ */
404
+ useCommandLog(): EnergyAppCommandLog;
390
405
  /**
391
406
  * Gets the current SDK version.
392
407
  * @returns The semantic version string of the SDK
@@ -462,6 +462,22 @@ export class EnergyApp {
462
462
  useGridFee() {
463
463
  return this.energyAppSdk.useGridFee();
464
464
  }
465
+ /**
466
+ * Gets the Command Log API for recording the writes this app issued to an
467
+ * appliance — which Modbus register, which OCPP configuration key or
468
+ * message, with what it was before and whether the device took it.
469
+ *
470
+ * An appliance's state shows what it is doing; the command log shows what
471
+ * was done to it and why, which is what an incident investigation actually
472
+ * needs. Entries are append-only, and reads span every app so that two
473
+ * packages fighting over the same register are visible.
474
+ * @returns The Command Log API instance
475
+ * @throws {EnergyAppPermissionNotGrantedError} If the `CommandLog`
476
+ * permission is not granted.
477
+ */
478
+ useCommandLog() {
479
+ return this.energyAppSdk.useCommandLog();
480
+ }
465
481
  /**
466
482
  * Gets the current SDK version.
467
483
  * @returns The semantic version string of the SDK
@@ -45,6 +45,7 @@ import { EnergyAppSavings } from "./packages/energy-app-savings.js";
45
45
  import { EnergyAppDeviceTest } from "./packages/energy-app-device-test.js";
46
46
  import { EnergyAppEpexSpotPrice } from "./packages/energy-app-epex-spot-price.js";
47
47
  import { EnergyAppGridFee } from "./packages/energy-app-grid-fee.js";
48
+ import { EnergyAppCommandLog } from "./packages/energy-app-command-log.js";
48
49
  import { UseFetchOptions } from "./types/enyo-fetch.js";
49
50
  export declare enum EnergyAppStateEnum {
50
51
  Launching = "launching",
@@ -166,4 +167,6 @@ export interface EnyoEnergyAppSdk {
166
167
  useEpexSpotPrices: () => EnergyAppEpexSpotPrice;
167
168
  /** Get the Grid Fee API for publishing time-dependent network charges and resolving them to a 15-minute series */
168
169
  useGridFee: () => EnergyAppGridFee;
170
+ /** Get the Command Log API for recording which register, configuration key or message this app wrote to an appliance, and reading those commands back */
171
+ useCommandLog: () => EnergyAppCommandLog;
169
172
  }
package/dist/index.d.ts CHANGED
@@ -131,6 +131,8 @@ export * from './packages/energy-app-device-test.js';
131
131
  export * from './implementations/device-test/device-test-validators.js';
132
132
  export * from './types/enyo-savings.js';
133
133
  export * from './packages/energy-app-savings.js';
134
+ export * from './types/enyo-command-log.js';
135
+ export * from './packages/energy-app-command-log.js';
134
136
  export * from './types/enyo-epex-spot-price.js';
135
137
  export * from './packages/energy-app-epex-spot-price.js';
136
138
  export * from './types/enyo-automation.js';
package/dist/index.js CHANGED
@@ -131,6 +131,8 @@ export * from './packages/energy-app-device-test.js';
131
131
  export * from './implementations/device-test/device-test-validators.js';
132
132
  export * from './types/enyo-savings.js';
133
133
  export * from './packages/energy-app-savings.js';
134
+ export * from './types/enyo-command-log.js';
135
+ export * from './packages/energy-app-command-log.js';
134
136
  export * from './types/enyo-epex-spot-price.js';
135
137
  export * from './packages/energy-app-epex-spot-price.js';
136
138
  export * from './types/enyo-automation.js';
@@ -0,0 +1,108 @@
1
+ import { EnyoCommandLogEntry, EnyoCommandLogEntryInput, EnyoCommandLogFilter } from "../types/enyo-command-log.js";
2
+ /**
3
+ * Interface for recording and reading back the commands an energy app issued to
4
+ * an appliance.
5
+ *
6
+ * This is the answer to "why did the device do that?". An appliance's own state
7
+ * shows what it is doing now; it does not show that this app wrote holding
8
+ * register 40023 four seconds ago because the energy manager cut the available
9
+ * power, nor that the write was rejected. Without a record of the writes, that
10
+ * has to be reconstructed from log files that may already have rotated away.
11
+ *
12
+ * The unit is a **write, not a read**. Polling a register every two seconds does
13
+ * not belong here — an entry is a command that changed, or tried to change,
14
+ * something on the device. Recording reads would bury the writes.
15
+ *
16
+ * Entries are append-only: there is no update and no delete. A log an app can
17
+ * rewrite cannot be trusted to explain an incident, and retention is the host's
18
+ * to enforce.
19
+ *
20
+ * Access to this API requires the `CommandLog` permission
21
+ * ({@link EnergyAppPermissionType}); {@link EnergyApp.useCommandLog} throws when
22
+ * it has not been granted.
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * const commandLog = energyApp.useCommandLog();
27
+ *
28
+ * const before = await modbus.readHoldingRegisters(40023, 1);
29
+ * try {
30
+ * await modbus.writeSingleRegister(40023, limitW);
31
+ * await commandLog.logCommand({
32
+ * applianceId,
33
+ * protocol: EnyoCommandProtocolEnum.Modbus,
34
+ * operation: 'write-single-register',
35
+ * target: '40023',
36
+ * previousValue: String(before.readUInt16BE(0)),
37
+ * newValue: String(limitW),
38
+ * outcome: EnyoCommandOutcomeEnum.Success,
39
+ * reason: 'energy manager reduced available power',
40
+ * });
41
+ * } catch (error) {
42
+ * await commandLog.logCommand({
43
+ * applianceId,
44
+ * protocol: EnyoCommandProtocolEnum.Modbus,
45
+ * operation: 'write-single-register',
46
+ * target: '40023',
47
+ * newValue: String(limitW),
48
+ * outcome: EnyoCommandOutcomeEnum.Failed,
49
+ * errorMessage: String(error),
50
+ * reason: 'energy manager reduced available power',
51
+ * });
52
+ * }
53
+ * ```
54
+ */
55
+ export interface EnergyAppCommandLog {
56
+ /**
57
+ * Appends one command to the log.
58
+ *
59
+ * Log the attempt, not only the success: a command that timed out or was
60
+ * rejected is the entry someone will actually go looking for, and an app
61
+ * that only records its successes produces a log where nothing ever went
62
+ * wrong. Set {@link EnyoCommandLogEntry.outcome} accordingly and put the
63
+ * detail in `errorMessage`.
64
+ *
65
+ * Fill {@link EnyoCommandLogEntry.reason} whenever the app knows it. What was
66
+ * written can be reconstructed from the device; why it was written cannot.
67
+ *
68
+ * This call is for auditing and must never gate the command itself — log
69
+ * after the write has been attempted, and do not let a failure to log
70
+ * surface as a failure to control the appliance.
71
+ *
72
+ * Requires the `CommandLog` permission.
73
+ *
74
+ * @param entry - The command to record. `id`, `packageName` and (unless
75
+ * supplied) `timestampIso` are assigned by the host.
76
+ * @returns Promise resolving to the stored entry, including its assigned id
77
+ * @throws {EnergyAppPermissionNotGrantedError} If the `CommandLog` permission
78
+ * is not granted.
79
+ */
80
+ logCommand(entry: EnyoCommandLogEntryInput): Promise<EnyoCommandLogEntry>;
81
+ /**
82
+ * Reads recorded commands back, newest first.
83
+ *
84
+ * Returns entries from **every** app, not just this one, unless
85
+ * {@link EnyoCommandLogFilter.packageName} narrows it. That is deliberate:
86
+ * the failure this log exists to explain is often two apps writing the same
87
+ * register, which is invisible from either app's own entries.
88
+ *
89
+ * Requires the `CommandLog` permission.
90
+ *
91
+ * @param filter - Optional narrowing by appliance, protocol, outcome, time
92
+ * range or package. Fields combine with AND.
93
+ * @returns Promise resolving to the matching entries, newest first
94
+ * @throws {EnergyAppPermissionNotGrantedError} If the `CommandLog` permission
95
+ * is not granted.
96
+ *
97
+ * @example
98
+ * ```typescript
99
+ * // Everything we failed to write to this appliance today.
100
+ * const failures = await commandLog.getCommands({
101
+ * applianceId,
102
+ * outcome: EnyoCommandOutcomeEnum.Failed,
103
+ * fromIso: startOfDay,
104
+ * });
105
+ * ```
106
+ */
107
+ getCommands(filter?: EnyoCommandLogFilter): Promise<EnyoCommandLogEntry[]>;
108
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -65,4 +65,27 @@ export interface EnyoBatteryApplianceMetadata {
65
65
  * (0–100). The EMS should not charge the battery above this value.
66
66
  */
67
67
  maxSoC?: number;
68
+ /**
69
+ * Observed round-trip efficiency as a percentage (0–100): of the energy put
70
+ * into the battery, how much comes back out again.
71
+ *
72
+ * A **calculated** value, not a nameplate figure — derived from measured
73
+ * charge and discharge energy over completed cycles. It therefore drifts
74
+ * with cell age, temperature and depth of discharge, and is absent until
75
+ * enough cycles have been observed to compute it. Consumers should treat a
76
+ * missing value as "not yet known" rather than substituting a datasheet
77
+ * number.
78
+ *
79
+ * This is what decides whether a charge/discharge cycle is worth running at
80
+ * all: arbitrage pays only when the price spread exceeds the energy lost to
81
+ * the round trip, so an optimizer that assumes 100% will schedule cycles
82
+ * that lose money.
83
+ *
84
+ * Measure it at the same boundary the battery reports its power and meter
85
+ * values at. An AC-to-AC round trip includes the inverter's conversion
86
+ * losses in both directions and typically lands several points below the
87
+ * DC-to-DC figure for the same pack — quoting one where the other is
88
+ * expected silently misprices every cycle.
89
+ */
90
+ roundTripEfficiencyPercent?: number;
68
91
  }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * The protocol a logged command was issued over.
3
+ *
4
+ * Names the transport, not the appliance: the same wallbox may be written to
5
+ * over Modbus TCP for power limits and over OCPP for configuration keys, and an
6
+ * entry is only interpretable once you know which.
7
+ */
8
+ export declare enum EnyoCommandProtocolEnum {
9
+ /** Modbus TCP */
10
+ Modbus = "modbus",
11
+ /** Modbus RTU (serial) */
12
+ ModbusRtu = "modbus-rtu",
13
+ /** OCPP — a configuration change or a call sent to a charge point */
14
+ Ocpp = "ocpp",
15
+ /** EEBUS SHIP/SPINE */
16
+ Eebus = "eebus",
17
+ /** MQTT publish */
18
+ Mqtt = "mqtt",
19
+ /** HTTP request against a vendor API or local endpoint */
20
+ Http = "http",
21
+ /** Bluetooth Low Energy GATT write */
22
+ Bluetooth = "bluetooth",
23
+ /** UDP datagram */
24
+ Udp = "udp",
25
+ /** Anything else — describe it in {@link EnyoCommandLogEntry.operation} */
26
+ Other = "other"
27
+ }
28
+ /**
29
+ * How a logged command ended.
30
+ *
31
+ * {@link Rejected} is deliberately separate from {@link Failed}: a device that
32
+ * answered and refused is a very different diagnosis from one that could not be
33
+ * reached, and collapsing the two is what makes a log unable to answer "did it
34
+ * hear us?".
35
+ */
36
+ export declare enum EnyoCommandOutcomeEnum {
37
+ /** The device accepted the command. */
38
+ Success = "success",
39
+ /** The command could not be delivered or the device errored. */
40
+ Failed = "failed",
41
+ /** No answer arrived within the app's deadline. */
42
+ TimedOut = "timed-out",
43
+ /** The device answered and refused the command. */
44
+ Rejected = "rejected"
45
+ }
46
+ /**
47
+ * One recorded command an energy app issued to an appliance.
48
+ *
49
+ * The unit of the log is a **write**, not a read: which register was set, which
50
+ * OCPP configuration key was changed, which message was executed — with what it
51
+ * was before, what it became, and whether the device took it.
52
+ */
53
+ export interface EnyoCommandLogEntry {
54
+ /** Unique identifier of this entry. Assigned by the host. */
55
+ id: string;
56
+ /** The appliance the command was issued to. */
57
+ applianceId: string;
58
+ /** When the command was issued, in ISO format. */
59
+ timestampIso: string;
60
+ /** The package that issued it. Set by the host, not by the caller. */
61
+ packageName: string;
62
+ /** Which protocol carried the command. */
63
+ protocol: EnyoCommandProtocolEnum;
64
+ /**
65
+ * What was done, in the protocol's own vocabulary — `'write-holding-registers'`,
66
+ * `'ChangeConfiguration'`, `'SetChargingProfile'`, `'gatt-write'`.
67
+ *
68
+ * Use the name the protocol uses rather than a prose description, so entries
69
+ * from one protocol group and compare.
70
+ */
71
+ operation: string;
72
+ /**
73
+ * What was addressed: a register address (`'40023'`), an OCPP configuration
74
+ * key (`'MeterValueSampleInterval'`), an MQTT topic, a GATT characteristic
75
+ * UUID. Omit for commands that address nothing in particular.
76
+ */
77
+ target?: string;
78
+ /**
79
+ * The value before the write, rendered as a string. Omit when it was not
80
+ * read back first — do not substitute a value the app merely believed was
81
+ * there, or the log will explain changes that never happened.
82
+ */
83
+ previousValue?: string;
84
+ /** The value written, rendered as a string. */
85
+ newValue?: string;
86
+ /** How the command ended. */
87
+ outcome: EnyoCommandOutcomeEnum;
88
+ /**
89
+ * Why the app issued the command — `'energy manager reduced available power'`,
90
+ * `'user changed charge mode'`.
91
+ *
92
+ * The single most useful field when reading the log back: the *what* can be
93
+ * reconstructed from the device, the *why* cannot.
94
+ */
95
+ reason?: string;
96
+ /**
97
+ * The error or rejection detail, for any outcome other than
98
+ * {@link EnyoCommandOutcomeEnum.Success}.
99
+ */
100
+ errorMessage?: string;
101
+ /** How long the command took, in milliseconds. */
102
+ durationMs?: number;
103
+ /**
104
+ * Anything the flat fields cannot carry — a decoded payload, the full
105
+ * register block written, the OCPP response object.
106
+ *
107
+ * Values above carry strings so entries from different protocols share one
108
+ * shape and can be rendered in one table; structured data belongs here.
109
+ */
110
+ details?: Record<string, unknown>;
111
+ }
112
+ /**
113
+ * What an app passes to {@link EnergyAppCommandLog.logCommand}.
114
+ *
115
+ * `id` and `packageName` are assigned by the host. `timestampIso` may be
116
+ * supplied when the command was issued earlier than it is being logged —
117
+ * a batch flushed after a reconnect — and otherwise defaults to the moment the
118
+ * host handles the call.
119
+ */
120
+ export type EnyoCommandLogEntryInput = Omit<EnyoCommandLogEntry, 'id' | 'packageName' | 'timestampIso'> & {
121
+ timestampIso?: string;
122
+ };
123
+ /**
124
+ * Narrows which entries {@link EnergyAppCommandLog.getCommands} returns.
125
+ *
126
+ * Every field is optional and they combine with AND. With no filter at all, the
127
+ * most recent entries across every appliance are returned.
128
+ */
129
+ export interface EnyoCommandLogFilter {
130
+ /** Only commands issued to this appliance. */
131
+ applianceId?: string;
132
+ /** Only commands carried over this protocol. */
133
+ protocol?: EnyoCommandProtocolEnum;
134
+ /** Only commands that ended this way — e.g. every failure in the last day. */
135
+ outcome?: EnyoCommandOutcomeEnum;
136
+ /** Only commands issued at or after this ISO timestamp (inclusive). */
137
+ fromIso?: string;
138
+ /** Only commands issued before this ISO timestamp (exclusive). */
139
+ untilIso?: string;
140
+ /**
141
+ * Only commands issued by this package. Omit to see every app's commands —
142
+ * a device misbehaving because two apps are writing the same register is
143
+ * invisible from one app's own entries.
144
+ */
145
+ packageName?: string;
146
+ /**
147
+ * Maximum number of entries to return, newest first. The host applies its
148
+ * own upper bound when this is omitted or larger than that bound.
149
+ */
150
+ limit?: number;
151
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The protocol a logged command was issued over.
3
+ *
4
+ * Names the transport, not the appliance: the same wallbox may be written to
5
+ * over Modbus TCP for power limits and over OCPP for configuration keys, and an
6
+ * entry is only interpretable once you know which.
7
+ */
8
+ export var EnyoCommandProtocolEnum;
9
+ (function (EnyoCommandProtocolEnum) {
10
+ /** Modbus TCP */
11
+ EnyoCommandProtocolEnum["Modbus"] = "modbus";
12
+ /** Modbus RTU (serial) */
13
+ EnyoCommandProtocolEnum["ModbusRtu"] = "modbus-rtu";
14
+ /** OCPP — a configuration change or a call sent to a charge point */
15
+ EnyoCommandProtocolEnum["Ocpp"] = "ocpp";
16
+ /** EEBUS SHIP/SPINE */
17
+ EnyoCommandProtocolEnum["Eebus"] = "eebus";
18
+ /** MQTT publish */
19
+ EnyoCommandProtocolEnum["Mqtt"] = "mqtt";
20
+ /** HTTP request against a vendor API or local endpoint */
21
+ EnyoCommandProtocolEnum["Http"] = "http";
22
+ /** Bluetooth Low Energy GATT write */
23
+ EnyoCommandProtocolEnum["Bluetooth"] = "bluetooth";
24
+ /** UDP datagram */
25
+ EnyoCommandProtocolEnum["Udp"] = "udp";
26
+ /** Anything else — describe it in {@link EnyoCommandLogEntry.operation} */
27
+ EnyoCommandProtocolEnum["Other"] = "other";
28
+ })(EnyoCommandProtocolEnum || (EnyoCommandProtocolEnum = {}));
29
+ /**
30
+ * How a logged command ended.
31
+ *
32
+ * {@link Rejected} is deliberately separate from {@link Failed}: a device that
33
+ * answered and refused is a very different diagnosis from one that could not be
34
+ * reached, and collapsing the two is what makes a log unable to answer "did it
35
+ * hear us?".
36
+ */
37
+ export var EnyoCommandOutcomeEnum;
38
+ (function (EnyoCommandOutcomeEnum) {
39
+ /** The device accepted the command. */
40
+ EnyoCommandOutcomeEnum["Success"] = "success";
41
+ /** The command could not be delivered or the device errored. */
42
+ EnyoCommandOutcomeEnum["Failed"] = "failed";
43
+ /** No answer arrived within the app's deadline. */
44
+ EnyoCommandOutcomeEnum["TimedOut"] = "timed-out";
45
+ /** The device answered and refused the command. */
46
+ EnyoCommandOutcomeEnum["Rejected"] = "rejected";
47
+ })(EnyoCommandOutcomeEnum || (EnyoCommandOutcomeEnum = {}));
package/dist/version.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * Current version of the enyo Energy App SDK.
7
7
  */
8
- export declare const SDK_VERSION = "1.5.0";
8
+ export declare const SDK_VERSION = "1.6.0";
9
9
  /**
10
10
  * Gets the current SDK version.
11
11
  * @returns The semantic version string of the SDK
package/dist/version.js CHANGED
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * Current version of the enyo Energy App SDK.
7
7
  */
8
- export const SDK_VERSION = '1.5.0';
8
+ export const SDK_VERSION = '1.6.0';
9
9
  /**
10
10
  * Gets the current SDK version.
11
11
  * @returns The semantic version string of the SDK
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enyo-energy/energy-app-sdk",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "enyo Energy App SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",