@epilot/cli 0.1.73 → 0.1.75

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
@@ -29,7 +29,7 @@ npm install -g @epilot/cli
29
29
 
30
30
  <!-- usage-help -->
31
31
  ```
32
- epilot v0.1.73 — CLI for epilot APIs
32
+ epilot v0.1.75 — CLI for epilot APIs
33
33
 
34
34
  USAGE
35
35
  epilot <api> <operationId> [params...] [flags]
@@ -26,6 +26,10 @@
26
26
  "name": "O365 Outlook Connection",
27
27
  "description": "Microsoft Office 365 Outlook integration endpoints. These endpoints enable organizations to:\n- Initiate OAuth connections with Microsoft 365\n- Connect Outlook shared mailboxes\n- Manage connection status and tokens\n- Disconnect integrations when needed\n\nThe integration supports multiple Azure AD tenants and handles token refresh automatically.\n"
28
28
  },
29
+ {
30
+ "name": "Custom SMTP",
31
+ "description": "Custom SMTP connection management. Lets organizations configure their own outbound\nSMTP server (e.g. Mailgun, SendGrid, a self-hosted Postfix) for sending emails\nfrom epilot via their existing provider. Credentials are encrypted at rest using\na customer-managed KMS key. Only outbound sending is supported — replies do not\ncome back into epilot.\n"
32
+ },
29
33
  {
30
34
  "name": "Channels",
31
35
  "description": "Channel integration endpoints. Channels represent external communication providers\n(e.g., Microsoft Teams, WhatsApp) that can be connected to an epilot organization.\n\nAll channel endpoints live under `/v2/channels/{channel}`.\n"
@@ -610,6 +614,81 @@
610
614
  }
611
615
  }
612
616
  },
617
+ "/v2/outlook/calendar/admin-consent-status": {
618
+ "get": {
619
+ "operationId": "getCalendarAdminConsentStatus",
620
+ "summary": "getCalendarAdminConsentStatus",
621
+ "description": "Reports whether the caller's organization can connect personal Outlook\ncalendars.\n\nConnecting a personal calendar requires the tenant admin to have\nconsented to epilot's Microsoft 365 app. That consent is tenant-wide,\none-time, and granted for the full scope set (mail + calendar), so the\norg-level Outlook connection set up during onboarding is the gate.\n\nWhen consent is missing the response includes `admin_consent_url` for\nthe admin to open, and the UI should ask the user to involve their IT\ninstead of starting an OAuth flow that would fail.\n",
622
+ "tags": [
623
+ "O365 Outlook Connection"
624
+ ],
625
+ "responses": {
626
+ "200": {
627
+ "description": "Admin consent status for the organization",
628
+ "content": {
629
+ "application/json": {
630
+ "schema": {
631
+ "$ref": "#/components/schemas/CalendarAdminConsentStatus"
632
+ }
633
+ }
634
+ }
635
+ },
636
+ "401": {
637
+ "description": "Unauthorized"
638
+ },
639
+ "500": {
640
+ "description": "Internal server error"
641
+ }
642
+ }
643
+ }
644
+ },
645
+ "/v2/outlook/calendar/me": {
646
+ "get": {
647
+ "operationId": "getMyCalendarConnection",
648
+ "summary": "getMyCalendarConnection",
649
+ "description": "Returns the calling user's personal Outlook calendar connection,\nor null when the user hasn't connected yet.\n\nPer-user calendar connections are distinct from the org-level\nshared-mailbox connection (`/v2/outlook/connection/status`): each\nuser connects their own delegated token because reading user A's\ncalendar requires user A's consent.\n",
650
+ "tags": [
651
+ "O365 Outlook Connection"
652
+ ],
653
+ "responses": {
654
+ "200": {
655
+ "description": "Calendar connection (or null if absent)",
656
+ "content": {
657
+ "application/json": {
658
+ "schema": {
659
+ "$ref": "#/components/schemas/MyCalendarConnectionResponse"
660
+ }
661
+ }
662
+ }
663
+ },
664
+ "401": {
665
+ "description": "Unauthorized"
666
+ },
667
+ "500": {
668
+ "description": "Internal server error"
669
+ }
670
+ }
671
+ },
672
+ "delete": {
673
+ "operationId": "disconnectMyCalendar",
674
+ "summary": "disconnectMyCalendar",
675
+ "description": "Removes the calling user's personal calendar connection.",
676
+ "tags": [
677
+ "O365 Outlook Connection"
678
+ ],
679
+ "responses": {
680
+ "204": {
681
+ "description": "Disconnected"
682
+ },
683
+ "404": {
684
+ "description": "No connection to disconnect"
685
+ },
686
+ "500": {
687
+ "description": "Internal server error"
688
+ }
689
+ }
690
+ }
691
+ },
613
692
  "/v2/outlook/connection/disconnect": {
614
693
  "post": {
615
694
  "operationId": "disconnectOutlook",
@@ -817,6 +896,28 @@
817
896
  "description": "Shared inbox ID to associate with the mailbox. Defaults to the default shared inbox.",
818
897
  "default": "default"
819
898
  },
899
+ "name": {
900
+ "type": "string",
901
+ "description": "Sender name to use for the Outlook mailbox email address."
902
+ },
903
+ "user_ids": {
904
+ "type": "array",
905
+ "items": {
906
+ "type": "string"
907
+ },
908
+ "description": "User IDs for which this email address should be available by default."
909
+ },
910
+ "group_ids": {
911
+ "type": "array",
912
+ "items": {
913
+ "type": "string"
914
+ },
915
+ "description": "Group IDs for which this email address should be available by default."
916
+ },
917
+ "default_signature_id": {
918
+ "type": "string",
919
+ "description": "Default signature ID to use for this Outlook mailbox email address."
920
+ },
820
921
  "mailboxSyncTimeframe": {
821
922
  "type": "string",
822
923
  "enum": [
@@ -973,7 +1074,11 @@
973
1074
  ],
974
1075
  "properties": {
975
1076
  "timeframe": {
976
- "$ref": "#/components/schemas/MailboxSyncTimeframePeriods",
1077
+ "allOf": [
1078
+ {
1079
+ "$ref": "#/components/schemas/MailboxSyncTimeframePeriods"
1080
+ }
1081
+ ],
977
1082
  "description": "Sync period:\n- 5m: last 5 minutes (quick sync)\n- 1w: 1 week\n- 2w: 2 weeks\n- 1m: 1 month\n"
978
1083
  }
979
1084
  }
@@ -1160,40 +1265,428 @@
1160
1265
  "summary": "getConnectedOutlookEmails",
1161
1266
  "description": "Returns all Outlook email addresses connected to the organization.\n",
1162
1267
  "tags": [
1163
- "O365 Outlook Connection"
1268
+ "O365 Outlook Connection"
1269
+ ],
1270
+ "responses": {
1271
+ "200": {
1272
+ "description": "Outlook emails retrieved successfully",
1273
+ "content": {
1274
+ "application/json": {
1275
+ "schema": {
1276
+ "type": "object",
1277
+ "required": [
1278
+ "outlook_emails",
1279
+ "count"
1280
+ ],
1281
+ "properties": {
1282
+ "outlook_emails": {
1283
+ "type": "array",
1284
+ "items": {
1285
+ "$ref": "#/components/schemas/ConnectedOutlookEmail"
1286
+ }
1287
+ },
1288
+ "count": {
1289
+ "type": "integer",
1290
+ "description": "Number of Outlook emails"
1291
+ }
1292
+ }
1293
+ }
1294
+ }
1295
+ }
1296
+ },
1297
+ "400": {
1298
+ "description": "Bad request - missing organization ID"
1299
+ },
1300
+ "500": {
1301
+ "description": "Internal server error"
1302
+ }
1303
+ }
1304
+ }
1305
+ },
1306
+ "/v2/smtp/connections": {
1307
+ "get": {
1308
+ "operationId": "listSmtpConnections",
1309
+ "summary": "listSmtpConnections",
1310
+ "description": "Returns all custom SMTP connections configured for the organization.\nPasswords are never returned.\n",
1311
+ "tags": [
1312
+ "Custom SMTP"
1313
+ ],
1314
+ "responses": {
1315
+ "200": {
1316
+ "description": "SMTP connections listed",
1317
+ "content": {
1318
+ "application/json": {
1319
+ "schema": {
1320
+ "type": "object",
1321
+ "required": [
1322
+ "connections",
1323
+ "has_connections"
1324
+ ],
1325
+ "properties": {
1326
+ "connections": {
1327
+ "type": "array",
1328
+ "items": {
1329
+ "$ref": "#/components/schemas/SmtpConnection"
1330
+ }
1331
+ },
1332
+ "has_connections": {
1333
+ "type": "boolean"
1334
+ }
1335
+ }
1336
+ }
1337
+ }
1338
+ }
1339
+ },
1340
+ "403": {
1341
+ "$ref": "#/components/responses/Forbidden"
1342
+ },
1343
+ "500": {
1344
+ "$ref": "#/components/responses/InternalServerError"
1345
+ }
1346
+ }
1347
+ },
1348
+ "post": {
1349
+ "operationId": "createSmtpConnection",
1350
+ "summary": "createSmtpConnection",
1351
+ "description": "Creates a new custom SMTP connection. Runs a live verify against the SMTP server\nbefore persisting; on failure the request is rejected and nothing is saved.\nThe password is encrypted via KMS before being written to DynamoDB.\n",
1352
+ "tags": [
1353
+ "Custom SMTP"
1354
+ ],
1355
+ "requestBody": {
1356
+ "required": true,
1357
+ "content": {
1358
+ "application/json": {
1359
+ "schema": {
1360
+ "$ref": "#/components/schemas/CreateSmtpConnectionRequest"
1361
+ }
1362
+ }
1363
+ }
1364
+ },
1365
+ "responses": {
1366
+ "201": {
1367
+ "description": "SMTP connection created",
1368
+ "content": {
1369
+ "application/json": {
1370
+ "schema": {
1371
+ "$ref": "#/components/schemas/SmtpConnection"
1372
+ }
1373
+ }
1374
+ }
1375
+ },
1376
+ "400": {
1377
+ "description": "Validation error, SMTP test failed, or refused for security reasons\n(private IP, plaintext on non-localhost host, invalid TLS).\n"
1378
+ },
1379
+ "403": {
1380
+ "$ref": "#/components/responses/Forbidden"
1381
+ },
1382
+ "409": {
1383
+ "$ref": "#/components/responses/Conflict"
1384
+ },
1385
+ "500": {
1386
+ "$ref": "#/components/responses/InternalServerError"
1387
+ }
1388
+ }
1389
+ }
1390
+ },
1391
+ "/v2/smtp/connections/{connectionId}": {
1392
+ "parameters": [
1393
+ {
1394
+ "name": "connectionId",
1395
+ "in": "path",
1396
+ "required": true,
1397
+ "schema": {
1398
+ "type": "string"
1399
+ }
1400
+ }
1401
+ ],
1402
+ "get": {
1403
+ "operationId": "getSmtpConnection",
1404
+ "summary": "getSmtpConnection",
1405
+ "description": "Returns a single custom SMTP connection by id. The password is never returned.",
1406
+ "tags": [
1407
+ "Custom SMTP"
1408
+ ],
1409
+ "responses": {
1410
+ "200": {
1411
+ "description": "SMTP connection retrieved",
1412
+ "content": {
1413
+ "application/json": {
1414
+ "schema": {
1415
+ "$ref": "#/components/schemas/SmtpConnection"
1416
+ }
1417
+ }
1418
+ }
1419
+ },
1420
+ "403": {
1421
+ "$ref": "#/components/responses/Forbidden"
1422
+ },
1423
+ "404": {
1424
+ "$ref": "#/components/responses/NotFound"
1425
+ }
1426
+ }
1427
+ },
1428
+ "put": {
1429
+ "operationId": "updateSmtpConnection",
1430
+ "summary": "updateSmtpConnection",
1431
+ "description": "Partial update; omitted fields keep their existing values. The merged\nconfiguration is verified against the SMTP server before persisting.\n",
1432
+ "tags": [
1433
+ "Custom SMTP"
1434
+ ],
1435
+ "requestBody": {
1436
+ "required": true,
1437
+ "content": {
1438
+ "application/json": {
1439
+ "schema": {
1440
+ "$ref": "#/components/schemas/UpdateSmtpConnectionRequest"
1441
+ }
1442
+ }
1443
+ }
1444
+ },
1445
+ "responses": {
1446
+ "200": {
1447
+ "description": "SMTP connection updated",
1448
+ "content": {
1449
+ "application/json": {
1450
+ "schema": {
1451
+ "$ref": "#/components/schemas/SmtpConnection"
1452
+ }
1453
+ }
1454
+ }
1455
+ },
1456
+ "400": {
1457
+ "description": "Validation error or SMTP test failed"
1458
+ },
1459
+ "403": {
1460
+ "$ref": "#/components/responses/Forbidden"
1461
+ },
1462
+ "404": {
1463
+ "$ref": "#/components/responses/NotFound"
1464
+ },
1465
+ "409": {
1466
+ "$ref": "#/components/responses/Conflict"
1467
+ }
1468
+ }
1469
+ },
1470
+ "delete": {
1471
+ "operationId": "deleteSmtpConnection",
1472
+ "summary": "deleteSmtpConnection",
1473
+ "description": "Deletes a custom SMTP connection. Messages already sent are unaffected.",
1474
+ "tags": [
1475
+ "Custom SMTP"
1476
+ ],
1477
+ "responses": {
1478
+ "200": {
1479
+ "description": "SMTP connection deleted",
1480
+ "content": {
1481
+ "application/json": {
1482
+ "schema": {
1483
+ "type": "object",
1484
+ "properties": {
1485
+ "success": {
1486
+ "type": "boolean"
1487
+ },
1488
+ "connection_id": {
1489
+ "type": "string"
1490
+ }
1491
+ }
1492
+ }
1493
+ }
1494
+ }
1495
+ },
1496
+ "403": {
1497
+ "$ref": "#/components/responses/Forbidden"
1498
+ },
1499
+ "404": {
1500
+ "$ref": "#/components/responses/NotFound"
1501
+ }
1502
+ }
1503
+ }
1504
+ },
1505
+ "/v2/smtp/connections/{connectionId}/test": {
1506
+ "parameters": [
1507
+ {
1508
+ "name": "connectionId",
1509
+ "in": "path",
1510
+ "required": true,
1511
+ "schema": {
1512
+ "type": "string"
1513
+ }
1514
+ }
1515
+ ],
1516
+ "post": {
1517
+ "operationId": "testSmtpConnection",
1518
+ "summary": "testSmtpConnection",
1519
+ "description": "Re-runs a live SMTP verify against the saved configuration (EHLO + AUTH + NOOP + QUIT)\nand updates `last_test_status` / `last_tested_at` on the connection.\n",
1520
+ "tags": [
1521
+ "Custom SMTP"
1522
+ ],
1523
+ "responses": {
1524
+ "200": {
1525
+ "description": "SMTP test executed",
1526
+ "content": {
1527
+ "application/json": {
1528
+ "schema": {
1529
+ "$ref": "#/components/schemas/SmtpTestResult"
1530
+ }
1531
+ }
1532
+ }
1533
+ },
1534
+ "403": {
1535
+ "$ref": "#/components/responses/Forbidden"
1536
+ },
1537
+ "404": {
1538
+ "$ref": "#/components/responses/NotFound"
1539
+ }
1540
+ }
1541
+ }
1542
+ },
1543
+ "/v2/smtp/senders": {
1544
+ "get": {
1545
+ "operationId": "listSmtpSenders",
1546
+ "summary": "listSmtpSenders",
1547
+ "description": "Returns every address registered to send through a custom SMTP connection.",
1548
+ "tags": [
1549
+ "Custom SMTP"
1550
+ ],
1551
+ "responses": {
1552
+ "200": {
1553
+ "description": "SMTP senders listed",
1554
+ "content": {
1555
+ "application/json": {
1556
+ "schema": {
1557
+ "type": "object",
1558
+ "required": [
1559
+ "senders"
1560
+ ],
1561
+ "properties": {
1562
+ "senders": {
1563
+ "type": "array",
1564
+ "items": {
1565
+ "$ref": "#/components/schemas/SmtpSender"
1566
+ }
1567
+ }
1568
+ }
1569
+ }
1570
+ }
1571
+ }
1572
+ },
1573
+ "403": {
1574
+ "$ref": "#/components/responses/Forbidden"
1575
+ },
1576
+ "500": {
1577
+ "$ref": "#/components/responses/InternalServerError"
1578
+ }
1579
+ }
1580
+ },
1581
+ "post": {
1582
+ "operationId": "connectSmtpSender",
1583
+ "summary": "connectSmtpSender",
1584
+ "description": "Registers an address as a sender on a custom SMTP connection:\n 1. Creates the email address so it can be picked in the composer\n 2. Binds it to the connection, so outgoing mail from it is routed there\nThe SMTP server still has the final say on which From addresses it accepts.\n",
1585
+ "tags": [
1586
+ "Custom SMTP"
1587
+ ],
1588
+ "requestBody": {
1589
+ "required": true,
1590
+ "content": {
1591
+ "application/json": {
1592
+ "schema": {
1593
+ "$ref": "#/components/schemas/ConnectSmtpSenderRequest"
1594
+ }
1595
+ }
1596
+ }
1597
+ },
1598
+ "responses": {
1599
+ "201": {
1600
+ "description": "Sender connected",
1601
+ "content": {
1602
+ "application/json": {
1603
+ "schema": {
1604
+ "type": "object",
1605
+ "required": [
1606
+ "email_address",
1607
+ "sender"
1608
+ ],
1609
+ "properties": {
1610
+ "email_address": {
1611
+ "$ref": "#/components/schemas/EmailAddressResponse"
1612
+ },
1613
+ "sender": {
1614
+ "$ref": "#/components/schemas/SmtpSender"
1615
+ }
1616
+ }
1617
+ }
1618
+ }
1619
+ }
1620
+ },
1621
+ "400": {
1622
+ "description": "Validation error"
1623
+ },
1624
+ "403": {
1625
+ "$ref": "#/components/responses/Forbidden"
1626
+ },
1627
+ "404": {
1628
+ "description": "The referenced SMTP connection does not exist"
1629
+ },
1630
+ "409": {
1631
+ "description": "The address is already registered, here or in another organization"
1632
+ },
1633
+ "500": {
1634
+ "$ref": "#/components/responses/InternalServerError"
1635
+ }
1636
+ }
1637
+ }
1638
+ },
1639
+ "/v2/smtp/senders/{email}": {
1640
+ "parameters": [
1641
+ {
1642
+ "name": "email",
1643
+ "in": "path",
1644
+ "required": true,
1645
+ "schema": {
1646
+ "type": "string",
1647
+ "format": "email"
1648
+ }
1649
+ }
1650
+ ],
1651
+ "delete": {
1652
+ "operationId": "disconnectSmtpSender",
1653
+ "summary": "disconnectSmtpSender",
1654
+ "description": "Removes a sender address: deletes the email address and its binding to the SMTP\nconnection. The connection itself and messages already sent are unaffected.\n",
1655
+ "tags": [
1656
+ "Custom SMTP"
1164
1657
  ],
1165
1658
  "responses": {
1166
1659
  "200": {
1167
- "description": "Outlook emails retrieved successfully",
1660
+ "description": "Sender disconnected",
1168
1661
  "content": {
1169
1662
  "application/json": {
1170
1663
  "schema": {
1171
1664
  "type": "object",
1172
1665
  "required": [
1173
- "outlook_emails",
1174
- "count"
1666
+ "success",
1667
+ "email"
1175
1668
  ],
1176
1669
  "properties": {
1177
- "outlook_emails": {
1178
- "type": "array",
1179
- "items": {
1180
- "$ref": "#/components/schemas/ConnectedOutlookEmail"
1181
- }
1670
+ "success": {
1671
+ "type": "boolean"
1182
1672
  },
1183
- "count": {
1184
- "type": "integer",
1185
- "description": "Number of Outlook emails"
1673
+ "email": {
1674
+ "type": "string",
1675
+ "format": "email"
1186
1676
  }
1187
1677
  }
1188
1678
  }
1189
1679
  }
1190
1680
  }
1191
1681
  },
1192
- "400": {
1193
- "description": "Bad request - missing organization ID"
1682
+ "403": {
1683
+ "$ref": "#/components/responses/Forbidden"
1684
+ },
1685
+ "404": {
1686
+ "$ref": "#/components/responses/NotFound"
1194
1687
  },
1195
1688
  "500": {
1196
- "description": "Internal server error"
1689
+ "$ref": "#/components/responses/InternalServerError"
1197
1690
  }
1198
1691
  }
1199
1692
  }
@@ -1287,6 +1780,14 @@
1287
1780
  "schema": {
1288
1781
  "type": "string"
1289
1782
  }
1783
+ },
1784
+ {
1785
+ "name": "clientdata",
1786
+ "in": "query",
1787
+ "required": false,
1788
+ "schema": {
1789
+ "type": "string"
1790
+ }
1290
1791
  }
1291
1792
  ],
1292
1793
  "responses": {
@@ -2078,6 +2579,12 @@
2078
2579
  "calendar": {
2079
2580
  "type": "boolean",
2080
2581
  "description": "Request 'calendar' specific scopes."
2582
+ },
2583
+ "return_to": {
2584
+ "type": "string",
2585
+ "maxLength": 512,
2586
+ "pattern": "^/[^/\\\\].*$",
2587
+ "description": "Optional same-origin frontend path to redirect to after the OAuth\nround-trip. Must start with a single \"/\" — protocol-relative\nand absolute URLs are rejected.\n"
2081
2588
  }
2082
2589
  }
2083
2590
  },
@@ -2644,6 +3151,89 @@
2644
3151
  }
2645
3152
  }
2646
3153
  },
3154
+ "CalendarAdminConsentStatus": {
3155
+ "type": "object",
3156
+ "required": [
3157
+ "admin_consented"
3158
+ ],
3159
+ "properties": {
3160
+ "admin_consented": {
3161
+ "type": "boolean",
3162
+ "description": "True when the tenant admin has consented to epilot's Microsoft 365\napp, so users in this organization can connect personal calendars.\n"
3163
+ },
3164
+ "admin_consent_url": {
3165
+ "type": "string",
3166
+ "format": "uri",
3167
+ "description": "Microsoft admin-consent URL for an administrator to open. Present\nonly when `admin_consented` is false.\n"
3168
+ }
3169
+ }
3170
+ },
3171
+ "MyCalendarConnectionResponse": {
3172
+ "type": "object",
3173
+ "description": "Per-user calendar connection metadata, or null when the caller has\nnot connected their personal calendar yet.\n",
3174
+ "properties": {
3175
+ "connection": {
3176
+ "allOf": [
3177
+ {
3178
+ "$ref": "#/components/schemas/CalendarConnectionInfo"
3179
+ }
3180
+ ],
3181
+ "nullable": true
3182
+ }
3183
+ }
3184
+ },
3185
+ "CalendarConnectionInfo": {
3186
+ "type": "object",
3187
+ "required": [
3188
+ "user_id",
3189
+ "tenant_id",
3190
+ "status"
3191
+ ],
3192
+ "properties": {
3193
+ "user_id": {
3194
+ "type": "string",
3195
+ "description": "Azure AD Object ID of the connected user"
3196
+ },
3197
+ "tenant_id": {
3198
+ "type": "string",
3199
+ "description": "Microsoft Azure AD tenant ID"
3200
+ },
3201
+ "status": {
3202
+ "type": "string",
3203
+ "enum": [
3204
+ "connected",
3205
+ "expired"
3206
+ ],
3207
+ "description": "Token health for this calendar connection"
3208
+ },
3209
+ "scopes": {
3210
+ "type": "array",
3211
+ "items": {
3212
+ "type": "string"
3213
+ },
3214
+ "description": "Granted permission scopes on the user's token"
3215
+ },
3216
+ "connected_by_display_name": {
3217
+ "type": "string"
3218
+ },
3219
+ "connected_by_email": {
3220
+ "type": "string",
3221
+ "format": "email"
3222
+ },
3223
+ "connected_at": {
3224
+ "type": "string",
3225
+ "format": "date-time"
3226
+ },
3227
+ "updated_at": {
3228
+ "type": "string",
3229
+ "format": "date-time"
3230
+ },
3231
+ "expires_at": {
3232
+ "type": "string",
3233
+ "format": "date-time"
3234
+ }
3235
+ }
3236
+ },
2647
3237
  "OutlookConnectionStatus": {
2648
3238
  "type": "object",
2649
3239
  "required": [
@@ -2726,6 +3316,253 @@
2726
3316
  }
2727
3317
  }
2728
3318
  },
3319
+ "SmtpConnection": {
3320
+ "type": "object",
3321
+ "required": [
3322
+ "connection_id",
3323
+ "smtp_host",
3324
+ "smtp_port",
3325
+ "smtp_secure",
3326
+ "smtp_username"
3327
+ ],
3328
+ "properties": {
3329
+ "connection_id": {
3330
+ "type": "string",
3331
+ "description": "Stable identifier for this SMTP connection"
3332
+ },
3333
+ "smtp_host": {
3334
+ "type": "string",
3335
+ "description": "Hostname of the SMTP server (e.g. smtp.mailgun.org)"
3336
+ },
3337
+ "smtp_port": {
3338
+ "type": "integer",
3339
+ "description": "SMTP port (typically 587 for STARTTLS or 465 for TLS)"
3340
+ },
3341
+ "smtp_secure": {
3342
+ "type": "string",
3343
+ "enum": [
3344
+ "tls",
3345
+ "starttls"
3346
+ ],
3347
+ "description": "Connection security mode. Plaintext SMTP is not offered: it would put the\nSMTP password on the wire in the clear.\n- tls: Implicit TLS (port 465)\n- starttls: STARTTLS upgrade (port 587)\n"
3348
+ },
3349
+ "smtp_username": {
3350
+ "type": "string",
3351
+ "description": "SMTP auth username"
3352
+ },
3353
+ "smtp_password": {
3354
+ "type": "string",
3355
+ "description": "Only returned when the stored value is exactly one `{{ env.your_key }}`\nreference, which names a secret rather than being one — that lets the\nsettings UI edit the reference without dropping it. A literal password, or a\npartly templated one, is omitted entirely: submit an update without this\nfield to keep the stored value.\n"
3356
+ },
3357
+ "connected_by_display_name": {
3358
+ "type": "string"
3359
+ },
3360
+ "connected_by_email": {
3361
+ "type": "string",
3362
+ "format": "email"
3363
+ },
3364
+ "connected_by_user_id": {
3365
+ "type": "string"
3366
+ },
3367
+ "connected_at": {
3368
+ "type": "string",
3369
+ "format": "date-time"
3370
+ },
3371
+ "updated_at": {
3372
+ "type": "string",
3373
+ "format": "date-time"
3374
+ },
3375
+ "last_tested_at": {
3376
+ "type": "string",
3377
+ "format": "date-time"
3378
+ },
3379
+ "last_test_status": {
3380
+ "type": "string",
3381
+ "enum": [
3382
+ "ok",
3383
+ "auth_failed",
3384
+ "tls_failed",
3385
+ "host_unreachable",
3386
+ "timeout",
3387
+ "secret_missing",
3388
+ "unknown"
3389
+ ]
3390
+ },
3391
+ "last_test_error": {
3392
+ "type": "string",
3393
+ "description": "Latest test error message, if any"
3394
+ }
3395
+ }
3396
+ },
3397
+ "CreateSmtpConnectionRequest": {
3398
+ "type": "object",
3399
+ "required": [
3400
+ "smtp_host",
3401
+ "smtp_port",
3402
+ "smtp_secure",
3403
+ "smtp_username",
3404
+ "smtp_password"
3405
+ ],
3406
+ "properties": {
3407
+ "smtp_host": {
3408
+ "type": "string"
3409
+ },
3410
+ "smtp_port": {
3411
+ "type": "integer",
3412
+ "minimum": 1,
3413
+ "maximum": 65535
3414
+ },
3415
+ "smtp_secure": {
3416
+ "type": "string",
3417
+ "enum": [
3418
+ "tls",
3419
+ "starttls"
3420
+ ]
3421
+ },
3422
+ "smtp_username": {
3423
+ "type": "string"
3424
+ },
3425
+ "smtp_password": {
3426
+ "type": "string",
3427
+ "description": "May contain `{{ env.your_key }}` templates referencing the organization's\nenvironment variables (including secrets), resolved at send time. Stored as\nentered, so the settings UI edits the template rather than a resolved value.\n",
3428
+ "example": "{{ env.smtp_password }}"
3429
+ }
3430
+ }
3431
+ },
3432
+ "UpdateSmtpConnectionRequest": {
3433
+ "type": "object",
3434
+ "description": "Partial update; omitted fields keep their existing values.",
3435
+ "properties": {
3436
+ "smtp_host": {
3437
+ "type": "string"
3438
+ },
3439
+ "smtp_port": {
3440
+ "type": "integer",
3441
+ "minimum": 1,
3442
+ "maximum": 65535
3443
+ },
3444
+ "smtp_secure": {
3445
+ "type": "string",
3446
+ "enum": [
3447
+ "tls",
3448
+ "starttls"
3449
+ ]
3450
+ },
3451
+ "smtp_username": {
3452
+ "type": "string"
3453
+ },
3454
+ "smtp_password": {
3455
+ "type": "string"
3456
+ }
3457
+ }
3458
+ },
3459
+ "SmtpSender": {
3460
+ "type": "object",
3461
+ "description": "An address allowed to send through a custom SMTP connection. Mirrors an Outlook\nmailbox: the address, its display name and its assignees live on the shared\nemail-address record, while this row binds it to a set of SMTP credentials.\n",
3462
+ "required": [
3463
+ "email",
3464
+ "connection_id"
3465
+ ],
3466
+ "properties": {
3467
+ "email": {
3468
+ "type": "string",
3469
+ "format": "email",
3470
+ "description": "The sender address. Stored lower-cased."
3471
+ },
3472
+ "connection_id": {
3473
+ "type": "string",
3474
+ "description": "SMTP connection this address sends through"
3475
+ },
3476
+ "reply_to_email": {
3477
+ "type": "string",
3478
+ "description": "Default Reply-To for messages from this address. Custom SMTP is send-only, so\npoint this at a mailbox someone actually monitors. May contain a\n`{{ env.your_key }}` template, so this is not constrained to `format: email`.\nA per-message Reply-To still wins.\n"
3479
+ },
3480
+ "connected_at": {
3481
+ "type": "string",
3482
+ "format": "date-time"
3483
+ },
3484
+ "connected_by_user_id": {
3485
+ "type": "string"
3486
+ }
3487
+ }
3488
+ },
3489
+ "ConnectSmtpSenderRequest": {
3490
+ "type": "object",
3491
+ "required": [
3492
+ "email",
3493
+ "connection_id"
3494
+ ],
3495
+ "properties": {
3496
+ "email": {
3497
+ "type": "string",
3498
+ "format": "email",
3499
+ "description": "Address to send from. Must be one the SMTP server accepts as a From."
3500
+ },
3501
+ "connection_id": {
3502
+ "type": "string",
3503
+ "description": "SMTP connection to send through"
3504
+ },
3505
+ "name": {
3506
+ "type": "string",
3507
+ "description": "Sender name shown on outgoing messages."
3508
+ },
3509
+ "reply_to_email": {
3510
+ "type": "string",
3511
+ "description": "Email address, or a `{{ env.your_key }}` template resolving to one."
3512
+ },
3513
+ "shared_inbox_id": {
3514
+ "type": "string",
3515
+ "description": "Shared inbox to associate with the address. Defaults to the default shared inbox.",
3516
+ "default": "default"
3517
+ },
3518
+ "user_ids": {
3519
+ "type": "array",
3520
+ "items": {
3521
+ "type": "string"
3522
+ },
3523
+ "description": "User IDs for which this address should be available by default."
3524
+ },
3525
+ "group_ids": {
3526
+ "type": "array",
3527
+ "items": {
3528
+ "type": "string"
3529
+ },
3530
+ "description": "Group IDs for which this address should be available by default."
3531
+ },
3532
+ "default_signature_id": {
3533
+ "type": "string",
3534
+ "description": "Default signature ID to use for this address."
3535
+ }
3536
+ }
3537
+ },
3538
+ "SmtpTestResult": {
3539
+ "type": "object",
3540
+ "required": [
3541
+ "status",
3542
+ "tested_at"
3543
+ ],
3544
+ "properties": {
3545
+ "status": {
3546
+ "type": "string",
3547
+ "enum": [
3548
+ "ok",
3549
+ "auth_failed",
3550
+ "tls_failed",
3551
+ "host_unreachable",
3552
+ "timeout",
3553
+ "secret_missing",
3554
+ "unknown"
3555
+ ]
3556
+ },
3557
+ "error": {
3558
+ "type": "string"
3559
+ },
3560
+ "tested_at": {
3561
+ "type": "string",
3562
+ "format": "date-time"
3563
+ }
3564
+ }
3565
+ },
2729
3566
  "SignatureSetting": {
2730
3567
  "type": "string",
2731
3568
  "description": "Setting that allows to add a signature.",
@@ -2745,6 +2745,26 @@
2745
2745
  "SearchIDParams": {
2746
2746
  "type": "object",
2747
2747
  "properties": {
2748
+ "inbox_id": {
2749
+ "oneOf": [
2750
+ {
2751
+ "type": "string",
2752
+ "description": "Inbox ID",
2753
+ "example": "3f34ce73-089c-4d45-a5ee-c161234e41c3"
2754
+ },
2755
+ {
2756
+ "type": "array",
2757
+ "items": {
2758
+ "type": "string"
2759
+ },
2760
+ "description": "Inbox IDs",
2761
+ "example": [
2762
+ "3f34ce73-089c-4d45-a5ee-c161234e41c3",
2763
+ "3f34ce73-089c-4d45-a5ee-c161234e41c4"
2764
+ ]
2765
+ }
2766
+ ]
2767
+ },
2748
2768
  "q": {
2749
2769
  "description": "Lucene query syntax supported with ElasticSearch",
2750
2770
  "type": "string",
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.0.3",
3
3
  "info": {
4
4
  "title": "Metering API",
5
- "version": "1.1.0",
5
+ "version": "1.2.0",
6
6
  "description": "The Metering API manages smart meter data, meter counters, and meter readings for epilot customers and administrators.\n\nIt supports two audiences:\n- **ECP (End Customer Portal)**: Portal users can view their meters, counters, and submit readings via the customer portal.\n- **ECP Admin**: Internal epilot users and ERP integrations can create, update, and bulk-manage meter readings.\n\nKey capabilities:\n- Retrieve meters and counters associated with a customer or contract\n- Submit individual or bulk meter readings (with optional validation skip)\n- Batch upsert/delete readings using the v2 endpoint\n- Query historical readings by date interval with cumulative or relative consumption modes\n- Retrieve allowed reading ranges to guide end customers entering readings\n"
7
7
  },
8
8
  "tags": [
@@ -104,7 +104,7 @@
104
104
  "properties": {
105
105
  "last_reading": {
106
106
  "type": "string",
107
- "example": "2022-10-10T00:00:00.000Z",
107
+ "example": "2022-10-10",
108
108
  "description": "The timestamp of the last reading"
109
109
  },
110
110
  "current_consumption": {
@@ -791,6 +791,101 @@
791
791
  }
792
792
  }
793
793
  },
794
+ "/v2/metering/readings/prune": {
795
+ "post": {
796
+ "operationId": "pruneMeterReadings",
797
+ "summary": "pruneMeterReadings",
798
+ "description": "Deletes every reading of a meter whose `external_id` is NOT in the provided keep list — in a single request.\n\nThe prune scope can optionally be narrowed to a single counter (`counter_id`) and/or a reading `source` (e.g. `ERP`).\nReplaces the client-side pattern of paginating the full reading history and issuing chunked batch deletes.\n\nReadings without an `external_id`:\n- when a `source` filter is provided, they are **deleted** — they cannot be referenced by any keep list\n- when no `source` filter is provided, they are **kept** (conservative default)\n\nDeletions reuse the same internal path as `batchWriteMeterReadings` with `operation: delete`: the same\nper-reading lifecycle events are emitted, and providing `activity_id` suppresses the per-reading delete\nactivities and attaches the given activity to the affected meter and counters instead. Delete operations\nnever create manual-intervention tickets; `create_ticket` is accepted for call-site parity with\n`batchWriteMeterReadings`.\n\n`keep_external_ids` is limited to 10000 entries.\n",
799
+ "tags": [
800
+ "ECP Admin"
801
+ ],
802
+ "security": [
803
+ {
804
+ "EpilotAuth": []
805
+ }
806
+ ],
807
+ "parameters": [
808
+ {
809
+ "in": "query",
810
+ "name": "async",
811
+ "description": "Don't wait for the deletions to become visible in GetReadings API. Useful for large prunes",
812
+ "required": false,
813
+ "schema": {
814
+ "type": "boolean",
815
+ "default": true
816
+ }
817
+ },
818
+ {
819
+ "$ref": "#/components/parameters/ActivityIdQueryParam"
820
+ },
821
+ {
822
+ "$ref": "#/components/parameters/CreateTicketQueryParam"
823
+ },
824
+ {
825
+ "in": "query",
826
+ "name": "dry_run",
827
+ "description": "When true, computes and returns the deleted/kept counts without deleting anything.",
828
+ "required": false,
829
+ "schema": {
830
+ "type": "boolean",
831
+ "default": false
832
+ }
833
+ }
834
+ ],
835
+ "requestBody": {
836
+ "description": "Prune scope and keep list.",
837
+ "required": true,
838
+ "content": {
839
+ "application/json": {
840
+ "schema": {
841
+ "$ref": "#/components/schemas/PruneMeterReadingsPayload"
842
+ }
843
+ }
844
+ }
845
+ },
846
+ "responses": {
847
+ "200": {
848
+ "description": "Meter readings pruned successfully.",
849
+ "content": {
850
+ "application/json": {
851
+ "schema": {
852
+ "type": "object",
853
+ "properties": {
854
+ "data": {
855
+ "type": "object",
856
+ "properties": {
857
+ "deleted_count": {
858
+ "type": "integer",
859
+ "description": "Number of readings deleted (or that would be deleted when `dry_run=true`).",
860
+ "example": 42
861
+ },
862
+ "kept_count": {
863
+ "type": "integer",
864
+ "description": "Number of readings in scope that were kept.",
865
+ "example": 12
866
+ }
867
+ }
868
+ }
869
+ }
870
+ }
871
+ }
872
+ }
873
+ },
874
+ "400": {
875
+ "$ref": "#/components/responses/InvalidRequest"
876
+ },
877
+ "401": {
878
+ "$ref": "#/components/responses/Unauthorized"
879
+ },
880
+ "403": {
881
+ "$ref": "#/components/responses/Forbidden"
882
+ },
883
+ "500": {
884
+ "$ref": "#/components/responses/InternalServerError"
885
+ }
886
+ }
887
+ }
888
+ },
794
889
  "/v1/metering/reading/submission": {
795
890
  "post": {
796
891
  "operationId": "createMeterReadingFromSubmission",
@@ -2035,7 +2130,7 @@
2035
2130
  },
2036
2131
  "calibration_date": {
2037
2132
  "type": "string",
2038
- "example": "2022-10-10T00:00:00.000Z",
2133
+ "example": "2022-10-10",
2039
2134
  "description": "The calibration date of the meter"
2040
2135
  },
2041
2136
  "contract": {
@@ -2151,7 +2246,7 @@
2151
2246
  "timestamp": {
2152
2247
  "type": "string",
2153
2248
  "description": "If the value is not provided, the system will be set with the time the request is processed.",
2154
- "example": "2022-10-10T00:00:00.000Z",
2249
+ "example": "2022-10-10",
2155
2250
  "format": "date-time"
2156
2251
  },
2157
2252
  "source": {
@@ -2353,6 +2448,38 @@
2353
2448
  "propertyName": "operation"
2354
2449
  }
2355
2450
  },
2451
+ "PruneMeterReadingsPayload": {
2452
+ "type": "object",
2453
+ "required": [
2454
+ "meter_id",
2455
+ "keep_external_ids"
2456
+ ],
2457
+ "properties": {
2458
+ "meter_id": {
2459
+ "$ref": "#/components/schemas/EntityId",
2460
+ "description": "The ID of the meter whose readings are pruned"
2461
+ },
2462
+ "counter_id": {
2463
+ "$ref": "#/components/schemas/EntityId",
2464
+ "description": "Optionally narrows the prune scope to a single counter of the meter"
2465
+ },
2466
+ "source": {
2467
+ "$ref": "#/components/schemas/Source",
2468
+ "description": "Optionally only prune readings with this source (e.g. `ERP`)"
2469
+ },
2470
+ "keep_external_ids": {
2471
+ "type": "array",
2472
+ "items": {
2473
+ "type": "string"
2474
+ },
2475
+ "description": "Readings whose `external_id` is contained in this list are kept; every other reading in scope is deleted.\nAn empty array deletes all readings in scope (subject to the no-external-id rule described on the endpoint).\nLimited to 10000 entries.\n",
2476
+ "example": [
2477
+ "erp-reading-1",
2478
+ "erp-reading-2"
2479
+ ]
2480
+ }
2481
+ }
2482
+ },
2356
2483
  "UpdateMeterReading": {
2357
2484
  "type": "object",
2358
2485
  "required": [
@@ -2387,7 +2514,7 @@
2387
2514
  "timestamp": {
2388
2515
  "type": "string",
2389
2516
  "description": "If the value is not provided, the system will be set with the time the request is processed.",
2390
- "example": "2022-10-10T00:00:00.000Z",
2517
+ "example": "2022-10-10",
2391
2518
  "format": "date-time"
2392
2519
  },
2393
2520
  "source": {
@@ -2466,7 +2593,7 @@
2466
2593
  },
2467
2594
  "forecast_as_of": {
2468
2595
  "type": "string",
2469
- "example": "2022-12-10T00:00:00.000Z",
2596
+ "example": "2022-12-10",
2470
2597
  "description": "The date as of which the forecast reading value is applicable"
2471
2598
  },
2472
2599
  "current_consumption": {
@@ -2476,7 +2603,7 @@
2476
2603
  },
2477
2604
  "last_reading": {
2478
2605
  "type": "string",
2479
- "example": "2022-10-10T00:00:00.000Z",
2606
+ "example": "2022-10-10",
2480
2607
  "description": "The timestamp of the last reading"
2481
2608
  },
2482
2609
  "conversion_factor": {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  API_LIST
4
- } from "../chunk-UI4BIEZG.js";
4
+ } from "../chunk-ILDXCHBW.js";
5
5
 
6
6
  // bin/epilot.ts
7
7
  import { runMain } from "citty";
@@ -11,7 +11,7 @@ import { defineCommand } from "citty";
11
11
  var main = defineCommand({
12
12
  meta: {
13
13
  name: "epilot",
14
- version: "0.1.73",
14
+ version: "0.1.75",
15
15
  description: "CLI for epilot APIs"
16
16
  },
17
17
  args: {
@@ -30,8 +30,8 @@ var main = defineCommand({
30
30
  auth: () => import("../auth-4HG7B2GC.js").then((m) => m.default),
31
31
  profile: () => import("../profile-OZJL5ZPT.js").then((m) => m.default),
32
32
  config: () => import("../config-DGZIMLZK.js").then((m) => m.default),
33
- completion: () => import("../completion-JJWJGP6Q.js").then((m) => m.default),
34
- upgrade: () => import("../upgrade-ZCU6UKYR.js").then((m) => m.default),
33
+ completion: () => import("../completion-RJBGSMR2.js").then((m) => m.default),
34
+ upgrade: () => import("../upgrade-IYLXBGRO.js").then((m) => m.default),
35
35
  "access-token": () => import("../access-token-WWE6BDJH.js").then((m) => m.default),
36
36
  address: () => import("../address-EH3C4CVB.js").then((m) => m.default),
37
37
  "address-suggestions": () => import("../address-suggestions-RRSLOBFW.js").then((m) => m.default),
@@ -134,13 +134,13 @@ process.stderr.on("error", (err) => {
134
134
  if (err.code === "EPIPE") process.exit(0);
135
135
  throw err;
136
136
  });
137
- var VERSION = true ? "0.1.73" : (await null).default.version;
137
+ var VERSION = true ? "0.1.75" : (await null).default.version;
138
138
  var reorderedArgv = hoistFlagsAfterSubcommand(process.argv.slice(2));
139
139
  process.argv = [process.argv[0], process.argv[1], ...reorderedArgv];
140
140
  var args = process.argv.slice(2);
141
141
  var completionsIdx = args.indexOf("--_completions");
142
142
  if (completionsIdx >= 0) {
143
- const { handleCompletions } = await import("../completion-JJWJGP6Q.js");
143
+ const { handleCompletions } = await import("../completion-RJBGSMR2.js");
144
144
  handleCompletions(args[completionsIdx + 1], args[completionsIdx + 2]);
145
145
  process.exit(0);
146
146
  }
@@ -518,7 +518,7 @@ var API_LIST = [
518
518
  kebabName: "email-settings",
519
519
  title: "Messaging Settings API",
520
520
  serverUrl: "https://email-settings.sls.epilot.io",
521
- operationCount: 36,
521
+ operationCount: 48,
522
522
  operationIds: [
523
523
  "provisionEpilotEmailAddress",
524
524
  "setEmailAddressPrimary",
@@ -535,6 +535,9 @@ var API_LIST = [
535
535
  "listInboxBuckets",
536
536
  "connectOutlook",
537
537
  "getOutlookConnectionStatus",
538
+ "getCalendarAdminConsentStatus",
539
+ "getMyCalendarConnection",
540
+ "disconnectMyCalendar",
538
541
  "disconnectOutlook",
539
542
  "connectMsTeams",
540
543
  "disconnectMsTeams",
@@ -545,6 +548,15 @@ var API_LIST = [
545
548
  "getMailboxSyncStatus",
546
549
  "retryMailboxSync",
547
550
  "getConnectedOutlookEmails",
551
+ "listSmtpConnections",
552
+ "createSmtpConnection",
553
+ "getSmtpConnection",
554
+ "updateSmtpConnection",
555
+ "deleteSmtpConnection",
556
+ "testSmtpConnection",
557
+ "listSmtpSenders",
558
+ "connectSmtpSender",
559
+ "disconnectSmtpSender",
548
560
  "outlookOAuthCallback",
549
561
  "getSettings",
550
562
  "addSetting",
@@ -953,7 +965,7 @@ var API_LIST = [
953
965
  kebabName: "metering",
954
966
  title: "Metering API",
955
967
  serverUrl: "https://metering.sls.epilot.io",
956
- operationCount: 20,
968
+ operationCount: 21,
957
969
  operationIds: [
958
970
  "getCustomerMeters",
959
971
  "getMetersByContractId",
@@ -965,6 +977,7 @@ var API_LIST = [
965
977
  "createMeterReadings",
966
978
  "createPortalMeterReadings",
967
979
  "batchWriteMeterReadings",
980
+ "pruneMeterReadings",
968
981
  "createMeterReadingFromSubmission",
969
982
  "getAllowedReadingForMeter",
970
983
  "createReadingWithMeter",
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  API_LIST
4
- } from "./chunk-UI4BIEZG.js";
4
+ } from "./chunk-ILDXCHBW.js";
5
5
  import {
6
6
  DIM,
7
7
  GREEN,
@@ -72,7 +72,7 @@ ${GREEN}${BOLD}Upgraded to @epilot/cli@${latest}${RESET}
72
72
  }
73
73
  });
74
74
  var getCurrentVersion = () => {
75
- if (true) return "0.1.73";
75
+ if (true) return "0.1.75";
76
76
  try {
77
77
  const output = execSync("npm ls -g @epilot/cli --depth=0 --json 2>/dev/null", {
78
78
  encoding: "utf-8",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epilot/cli",
3
- "version": "0.1.73",
3
+ "version": "0.1.75",
4
4
  "description": "CLI for epilot APIs",
5
5
  "type": "module",
6
6
  "bin": {