deepline 0.3.293 → 0.3.295

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/dist/bundling-sources/sdk/src/compat.ts +35 -7
  2. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  3. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +269 -267
  4. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +3 -4
  5. package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +14 -1
  6. package/dist/cli/auth-EDTRERZP.mjs +31 -0
  7. package/dist/cli/auth-TDHKB277.js +31 -0
  8. package/dist/cli/billing-ELIDPILY.js +1749 -0
  9. package/dist/cli/billing-EXGXJVAU.mjs +1749 -0
  10. package/dist/cli/chunk-2S5Y3TZP.js +5688 -0
  11. package/dist/cli/chunk-3KC45UVF.js +150 -0
  12. package/dist/cli/chunk-3XX5TEA4.js +15 -0
  13. package/dist/cli/chunk-4MBOJQPL.mjs +0 -0
  14. package/dist/cli/chunk-4NEFYBF7.js +2240 -0
  15. package/dist/cli/chunk-5JLQPVNX.js +3056 -0
  16. package/dist/cli/chunk-5KDKFHZI.js +1269 -0
  17. package/dist/cli/chunk-6A3TJUWC.js +76 -0
  18. package/dist/cli/chunk-6O5YZOVN.mjs +890 -0
  19. package/dist/cli/chunk-7P5AA3SN.js +344 -0
  20. package/dist/cli/chunk-AYHLUHE5.js +52 -0
  21. package/dist/cli/chunk-CBVIJSHF.mjs +560 -0
  22. package/dist/cli/chunk-CFVVCN4A.js +735 -0
  23. package/dist/cli/chunk-COGTRVWW.js +30 -0
  24. package/dist/cli/chunk-D4AZUZO4.js +190 -0
  25. package/dist/cli/chunk-DLIRSHV6.mjs +28 -0
  26. package/dist/cli/chunk-EACVZI6N.mjs +1995 -0
  27. package/dist/cli/chunk-EDWJY6T4.mjs +3056 -0
  28. package/dist/cli/chunk-EXMLR72R.mjs +0 -0
  29. package/dist/cli/chunk-F35VOIQL.js +890 -0
  30. package/dist/cli/chunk-GAMFK2ZP.mjs +148 -0
  31. package/dist/cli/chunk-GHRQQPW2.mjs +2240 -0
  32. package/dist/cli/chunk-GONRLVV3.js +3440 -0
  33. package/dist/cli/chunk-GVUJJJ5G.mjs +76 -0
  34. package/dist/cli/chunk-H7HIDGZ7.mjs +150 -0
  35. package/dist/cli/chunk-HUZXWOSK.mjs +503 -0
  36. package/dist/cli/chunk-I5YZLGMG.js +1 -0
  37. package/dist/cli/chunk-ITOFSXIU.mjs +15 -0
  38. package/dist/cli/chunk-J5NA2TTX.js +503 -0
  39. package/dist/cli/chunk-JCS5TPNY.mjs +3440 -0
  40. package/dist/cli/chunk-JNNOCMQY.js +47 -0
  41. package/dist/cli/chunk-KRD6H754.mjs +30 -0
  42. package/dist/cli/chunk-LA46MMTL.js +560 -0
  43. package/dist/cli/chunk-LDWT7RJK.mjs +190 -0
  44. package/dist/cli/chunk-M2V26OR5.js +499 -0
  45. package/dist/cli/chunk-MIMASXO4.mjs +389 -0
  46. package/dist/cli/chunk-MROPCKW2.mjs +190 -0
  47. package/dist/cli/chunk-NXDRDJ4K.js +389 -0
  48. package/dist/cli/chunk-NXGCO4JB.mjs +344 -0
  49. package/dist/cli/chunk-OG64RBAC.js +6 -0
  50. package/dist/cli/chunk-OQYEC4GL.mjs +34 -0
  51. package/dist/cli/chunk-OWJZUE3Y.mjs +103 -0
  52. package/dist/cli/chunk-SDK3ZA2C.js +103 -0
  53. package/dist/cli/chunk-SFLARO4Y.mjs +47 -0
  54. package/dist/cli/chunk-SVIIIYIQ.js +923 -0
  55. package/dist/cli/chunk-TAC3JIFE.mjs +354 -0
  56. package/dist/cli/chunk-U3PI7HBM.js +34 -0
  57. package/dist/cli/chunk-UFS752TU.mjs +52 -0
  58. package/dist/cli/chunk-UI37DQE7.mjs +499 -0
  59. package/dist/cli/chunk-UNEMDKXN.js +1 -0
  60. package/dist/cli/chunk-WB6Z2Q44.mjs +1622 -0
  61. package/dist/cli/chunk-WJYPBHTE.js +148 -0
  62. package/dist/cli/chunk-WOHBA5PV.js +354 -0
  63. package/dist/cli/chunk-WQICC2B3.js +190 -0
  64. package/dist/cli/chunk-WZYKRD7T.mjs +6 -0
  65. package/dist/cli/chunk-XL2FO6CW.mjs +5688 -0
  66. package/dist/cli/chunk-XNYIZMNQ.js +28 -0
  67. package/dist/cli/chunk-XRK2TZT5.js +1995 -0
  68. package/dist/cli/chunk-YJYPL5OK.mjs +1269 -0
  69. package/dist/cli/chunk-YWNDYCXR.mjs +735 -0
  70. package/dist/cli/chunk-YWWAF7KZ.mjs +923 -0
  71. package/dist/cli/chunk-ZDO3TO3M.js +1622 -0
  72. package/dist/cli/client-4JVOYVC5.js +20 -0
  73. package/dist/cli/client-EEN2IJN2.mjs +20 -0
  74. package/dist/cli/csv-BOHBEP2I.mjs +585 -0
  75. package/dist/cli/csv-S5NYRUEX.js +585 -0
  76. package/dist/cli/db-CLODWCOB.mjs +486 -0
  77. package/dist/cli/db-F6PKFOOV.js +486 -0
  78. package/dist/cli/executions-5TQAKLJU.js +28 -0
  79. package/dist/cli/executions-MAKW6QOY.mjs +28 -0
  80. package/dist/cli/feedback-GV4QJFEN.js +107 -0
  81. package/dist/cli/feedback-XY24TW7H.mjs +107 -0
  82. package/dist/cli/getting-started.js +18 -76
  83. package/dist/cli/getting-started.mjs +10 -37
  84. package/dist/cli/git-6S2C22T5.js +23 -0
  85. package/dist/cli/git-OZLBD5HH.mjs +23 -0
  86. package/dist/cli/index.js +291 -54294
  87. package/dist/cli/index.mjs +209 -54352
  88. package/dist/cli/legacy-noops-LLCGDPQE.js +157 -0
  89. package/dist/cli/legacy-noops-TYH3TW2D.mjs +157 -0
  90. package/dist/cli/monitors-CABMB4DW.js +3251 -0
  91. package/dist/cli/monitors-TTD55HMK.mjs +3251 -0
  92. package/dist/cli/org-MSF3RG76.js +849 -0
  93. package/dist/cli/org-SFJ77MWY.mjs +849 -0
  94. package/dist/cli/play-NXHJSCKL.mjs +18439 -0
  95. package/dist/cli/play-OKSFNNO4.js +18439 -0
  96. package/dist/cli/providers-QC2ERERL.js +95 -0
  97. package/dist/cli/providers-TIIAZEUA.mjs +95 -0
  98. package/dist/cli/proxy-env-EBANZHPV.js +161 -0
  99. package/dist/cli/proxy-env-M4ZHXHRL.mjs +161 -0
  100. package/dist/cli/quickstart-ICANHLMI.js +69 -0
  101. package/dist/cli/quickstart-KWUOP3FI.mjs +69 -0
  102. package/dist/cli/search-U5H2FDOO.mjs +18 -0
  103. package/dist/cli/search-XQRQOLWS.js +18 -0
  104. package/dist/cli/secrets-3J6YTWFA.mjs +400 -0
  105. package/dist/cli/secrets-YNMNZNOM.js +400 -0
  106. package/dist/cli/sessions-CLEES4L6.mjs +15 -0
  107. package/dist/cli/sessions-J6VR47FG.js +15 -0
  108. package/dist/cli/settings-EEKZMTY4.js +846 -0
  109. package/dist/cli/settings-UVLOQZRH.mjs +846 -0
  110. package/dist/cli/setup-DLMDXPGJ.mjs +1257 -0
  111. package/dist/cli/setup-HC4Q7QM4.js +1257 -0
  112. package/dist/cli/skills-33PHFBSB.mjs +28 -0
  113. package/dist/cli/skills-LFRY3KPX.js +28 -0
  114. package/dist/cli/tools-3COEPKML.mjs +2955 -0
  115. package/dist/cli/tools-QXEVCN45.js +2955 -0
  116. package/dist/cli/update-LSAC4FF5.mjs +28 -0
  117. package/dist/cli/update-NXHSPYXA.js +28 -0
  118. package/dist/cli/workflow-7S44OECF.mjs +1936 -0
  119. package/dist/cli/workflow-QPZWYSZE.js +1936 -0
  120. package/dist/helpers.js +6 -32
  121. package/dist/helpers.mjs +2 -0
  122. package/dist/index.js +1972 -12825
  123. package/dist/index.mjs +1910 -12672
  124. package/dist/install-integrity.json +110 -0
  125. package/dist/monitor-fleet-contract.js +31 -730
  126. package/dist/monitor-fleet-contract.mjs +18 -667
  127. package/dist/monitor-input-contract.js +10 -390
  128. package/dist/monitor-input-contract.mjs +7 -348
  129. package/dist/plays/bundle-play-file.mjs +13 -5178
  130. package/dist/plays/csv-data-input.js +5 -3132
  131. package/dist/plays/csv-data-input.mjs +5 -3105
  132. package/dist/plays/run-request.js +56 -664
  133. package/dist/plays/run-request.mjs +30 -588
  134. package/dist/release.d.mts +1 -1
  135. package/dist/release.d.ts +1 -1
  136. package/dist/release.js +5 -212
  137. package/dist/release.mjs +5 -185
  138. package/package.json +1 -1
@@ -0,0 +1,846 @@
1
+ import {
2
+ DeeplineClient,
3
+ PRODUCT_NOTIFICATION_EVENT_CATALOG,
4
+ resolveLegacySlackNotification
5
+ } from "./chunk-XL2FO6CW.mjs";
6
+ import "./chunk-CBVIJSHF.mjs";
7
+ import {
8
+ readCliUpdatePreferences,
9
+ runUpdateCommand,
10
+ setCliAutoUpdateEnabled
11
+ } from "./chunk-YJYPL5OK.mjs";
12
+ import "./chunk-OQYEC4GL.mjs";
13
+ import "./chunk-GHRQQPW2.mjs";
14
+ import "./chunk-GAMFK2ZP.mjs";
15
+ import {
16
+ openInBrowser,
17
+ printCommandEnvelope
18
+ } from "./chunk-6O5YZOVN.mjs";
19
+ import "./chunk-EACVZI6N.mjs";
20
+ import "./chunk-DLIRSHV6.mjs";
21
+ import "./chunk-LDWT7RJK.mjs";
22
+ import "./chunk-GVUJJJ5G.mjs";
23
+ import "./chunk-NXGCO4JB.mjs";
24
+ import "./chunk-WZYKRD7T.mjs";
25
+ import "./chunk-KRD6H754.mjs";
26
+
27
+ // src/cli/commands/settings.ts
28
+ import { readFileSync } from "fs";
29
+ var eventHelp = PRODUCT_NOTIFICATION_EVENT_CATALOG.map(
30
+ (event) => ` ${event.id.padEnd(28)} ${event.description}`
31
+ ).join("\n");
32
+ function slackDestination(settings) {
33
+ const destinations = settings.destinations.map(
34
+ (entry) => ({
35
+ ...entry,
36
+ kind: typeof entry.kind === "string" ? entry.kind : "",
37
+ name: typeof entry.name === "string" ? entry.name : "",
38
+ archivedAt: typeof entry.archivedAt === "number" ? entry.archivedAt : void 0
39
+ })
40
+ );
41
+ return resolveLegacySlackNotification(destinations);
42
+ }
43
+ function eventLines(settings, destinationId) {
44
+ const enabled = new Map(
45
+ settings.subscriptions.filter(
46
+ (entry) => !destinationId || entry.destinationId === destinationId
47
+ ).map((entry) => [entry.eventType, entry.enabled])
48
+ );
49
+ return settings.catalog.map(
50
+ (event) => `${event.id}: ${enabled.get(event.id) === true ? "enabled" : "disabled"} \u2014 ${event.description}`
51
+ );
52
+ }
53
+ function requireCatalogEvent(settings, eventType) {
54
+ const event = settings.catalog.find((entry) => entry.id === eventType);
55
+ if (!event) {
56
+ throw new Error(
57
+ `Unknown notification event "${eventType}". Run deepline settings notifications subscriptions list.`
58
+ );
59
+ }
60
+ return event;
61
+ }
62
+ async function printSettings(options) {
63
+ const settings = await new DeeplineClient().getNotificationSettings();
64
+ const slack = slackDestination(settings);
65
+ const destinationId = typeof slack?._id === "string" ? slack._id : void 0;
66
+ printCommandEnvelope(
67
+ {
68
+ settings,
69
+ render: {
70
+ sections: [
71
+ {
72
+ title: "Slack destination",
73
+ lines: slack ? [
74
+ `status: ${String(slack.status ?? "unknown")}`,
75
+ `${slack.targetKind === "member" ? "member" : "channel"}: ${slack.targetKind === "member" ? "@" : "#"}${String(slack.channelName ?? slack.channelId ?? "unknown")}`,
76
+ ...slack.lastErrorMessage ? [`issue: ${String(slack.lastErrorMessage)}`] : []
77
+ ] : ["Not configured."]
78
+ },
79
+ {
80
+ title: "subscriptions",
81
+ lines: eventLines(settings, destinationId)
82
+ },
83
+ {
84
+ title: "dead letter queue",
85
+ lines: [
86
+ `${settings.dlq.count}${settings.dlq.capped ? "+" : ""} deliveries require review`
87
+ ]
88
+ }
89
+ ],
90
+ actions: slack ? [
91
+ {
92
+ label: "Validate Slack",
93
+ command: "deepline settings notifications test slack"
94
+ }
95
+ ] : [
96
+ {
97
+ label: "Connect Slack",
98
+ command: "deepline settings notifications connect slack --open"
99
+ }
100
+ ]
101
+ }
102
+ },
103
+ { json: options.json }
104
+ );
105
+ }
106
+ function registerSettingsCommands(program) {
107
+ const settings = program.command("settings").description("Manage Deepline settings.");
108
+ const autoupdate = settings.command("autoupdate").description("Configure automatic CLI updates on this machine.").addHelpText(
109
+ "after",
110
+ `
111
+ Examples:
112
+ deepline settings autoupdate
113
+ deepline settings autoupdate on
114
+ deepline settings autoupdate off
115
+ deepline settings autoupdate pin <version>
116
+
117
+ Automatic updates are enabled by default. Turning them on clears an existing
118
+ version pin. Pinning installs and holds one exact pin-aware CLI version.
119
+ `
120
+ ).option("--json", "Emit JSON output").action((options) => {
121
+ const preferences = readCliUpdatePreferences();
122
+ printCommandEnvelope(
123
+ {
124
+ autoUpdateEnabled: preferences.autoUpdateEnabled,
125
+ pinnedVersion: preferences.pinnedVersion,
126
+ render: {
127
+ sections: [
128
+ {
129
+ title: "automatic CLI updates",
130
+ lines: [
131
+ `status: ${preferences.autoUpdateEnabled && !preferences.pinnedVersion ? "on" : "off"}`,
132
+ `pinned version: ${preferences.pinnedVersion ?? "none"}`
133
+ ]
134
+ }
135
+ ]
136
+ }
137
+ },
138
+ { json: options.json }
139
+ );
140
+ });
141
+ for (const enabled of [true, false]) {
142
+ autoupdate.command(enabled ? "on" : "off").description(
143
+ enabled ? "Enable automatic CLI updates and clear any version pin." : "Disable automatic CLI updates."
144
+ ).option("--json", "Emit JSON output").action((options) => {
145
+ const preferences = setCliAutoUpdateEnabled(enabled);
146
+ printCommandEnvelope(
147
+ {
148
+ ok: true,
149
+ autoUpdateEnabled: preferences.autoUpdateEnabled,
150
+ pinnedVersion: preferences.pinnedVersion,
151
+ render: {
152
+ sections: [
153
+ {
154
+ title: "automatic CLI updates",
155
+ lines: [
156
+ enabled ? "enabled; any version pin was cleared" : "disabled"
157
+ ]
158
+ }
159
+ ]
160
+ }
161
+ },
162
+ { json: options.json }
163
+ );
164
+ });
165
+ }
166
+ autoupdate.command("pin").description("Install and hold an exact pin-aware CLI version.").argument("<version>", "Exact semantic version from a pin-aware release").option("--json", "Emit JSON output").action(async (version, options) => {
167
+ process.exitCode = await runUpdateCommand({
168
+ pin: version,
169
+ json: options.json,
170
+ executeWithJson: true
171
+ });
172
+ });
173
+ const notifications = settings.command("notifications").description(
174
+ "Configure Deepline product notifications and delivery safety."
175
+ ).addHelpText(
176
+ "after",
177
+ `
178
+ Workflow:
179
+ 1. deepline settings notifications connect slack --open
180
+ 2. deepline settings notifications channels --query pipeline
181
+ 3. deepline settings notifications set slack --channel '#pipeline-alerts'
182
+ 4. deepline settings notifications test slack
183
+ 5. deepline settings notifications subscriptions list
184
+
185
+ Slack is the delivery integration. This command group only manages product
186
+ notifications; there is no general integrations CLI.
187
+ `
188
+ );
189
+ notifications.command("get").description("Show destination, subscriptions, and DLQ health.").option("--json", "Emit JSON output").action(printSettings);
190
+ notifications.command("connect").description("Start the OAuth connection required by a destination.").argument("<destination>", "Destination kind (currently slack)").option("--open", "Open the Slack authorization URL in a browser").option("--dry-run", "Describe the mutation without starting OAuth").option("--json", "Emit JSON output").action(
191
+ async (destination, options) => {
192
+ if (destination !== "slack")
193
+ throw new Error("Only slack is supported.");
194
+ if (options.dryRun) {
195
+ printCommandEnvelope(
196
+ { dryRun: true, destination, action: "start_oauth" },
197
+ { json: options.json }
198
+ );
199
+ return;
200
+ }
201
+ const result = await new DeeplineClient().connectNotificationSlack();
202
+ const browser = options.open ? openInBrowser(result.redirect_url, { deduplicate: false }) : "not_requested";
203
+ printCommandEnvelope(
204
+ {
205
+ ...result,
206
+ browser,
207
+ render: {
208
+ sections: [
209
+ {
210
+ title: "Slack authorization",
211
+ lines: [result.redirect_url, `browser: ${browser}`]
212
+ }
213
+ ],
214
+ actions: [
215
+ {
216
+ label: "After authorization",
217
+ command: "deepline settings notifications set slack --channel '#channel'"
218
+ }
219
+ ]
220
+ }
221
+ },
222
+ { json: options.json }
223
+ );
224
+ }
225
+ );
226
+ notifications.command("channels").description("List Slack channels visible to the connected Deepline app.").option("--query <text>", "Filter channels by name").option("--json", "Emit JSON output").action(async (options) => {
227
+ const result = await new DeeplineClient().listNotificationSlackChannels(
228
+ options.query
229
+ );
230
+ printCommandEnvelope(
231
+ {
232
+ ...result,
233
+ render: {
234
+ sections: [
235
+ {
236
+ title: "Slack channels",
237
+ lines: result.channels.length ? result.channels.map(
238
+ (channel) => `#${channel.name} (${channel.id})${channel.isPrivate ? " private" : ""}`
239
+ ) : ["No visible channels matched."]
240
+ }
241
+ ]
242
+ }
243
+ },
244
+ { json: options.json }
245
+ );
246
+ });
247
+ notifications.command("set").description("Set a product-notification destination.").argument("<destination>", "Destination kind (currently slack)").option("--channel <channel>", "Slack channel name or ID").option("--member-id <id>", "Slack member ID, for example U0123456789").option("--dry-run", "Validate intent without changing settings").option("--json", "Emit JSON output").action(
248
+ async (destination, options) => {
249
+ if (destination !== "slack")
250
+ throw new Error("Only slack is supported.");
251
+ if (Boolean(options.channel) === Boolean(options.memberId)) {
252
+ throw new Error("Provide exactly one of --channel or --member-id.");
253
+ }
254
+ const target = options.memberId ? { memberId: options.memberId } : options.channel;
255
+ if (options.dryRun) {
256
+ printCommandEnvelope(
257
+ { dryRun: true, destination, target },
258
+ { json: options.json }
259
+ );
260
+ return;
261
+ }
262
+ const result = await new DeeplineClient().setNotificationSlack(target);
263
+ printCommandEnvelope(
264
+ {
265
+ ok: true,
266
+ result,
267
+ render: {
268
+ sections: [
269
+ {
270
+ title: "Slack destination saved",
271
+ lines: [options.memberId ?? options.channel]
272
+ }
273
+ ],
274
+ actions: [
275
+ {
276
+ label: "Validate delivery",
277
+ command: "deepline settings notifications test slack"
278
+ }
279
+ ]
280
+ }
281
+ },
282
+ { json: options.json }
283
+ );
284
+ }
285
+ );
286
+ notifications.command("test").description("Send a validation ping to a destination.").argument("<destination>", "Destination kind (currently slack)").option("--dry-run", "Describe the external send without sending").option("--json", "Emit JSON output").action(async (destination, options) => {
287
+ if (destination !== "slack") throw new Error("Only slack is supported.");
288
+ if (options.dryRun) {
289
+ printCommandEnvelope(
290
+ { dryRun: true, destination, action: "send_test_ping" },
291
+ { json: options.json }
292
+ );
293
+ return;
294
+ }
295
+ const result = await new DeeplineClient().testNotificationSlack();
296
+ printCommandEnvelope(
297
+ {
298
+ ...result,
299
+ render: {
300
+ sections: [{ title: "Slack validation", lines: [result.message] }]
301
+ }
302
+ },
303
+ { json: options.json }
304
+ );
305
+ });
306
+ notifications.command("disable").description("Disable a product-notification destination.").argument("<destination>", "Destination kind (currently slack)").option("--dry-run", "Describe the mutation without changing settings").option("--json", "Emit JSON output").action(async (destination, options) => {
307
+ if (destination !== "slack") throw new Error("Only slack is supported.");
308
+ if (options.dryRun) {
309
+ printCommandEnvelope(
310
+ { dryRun: true, destination, action: "disable" },
311
+ { json: options.json }
312
+ );
313
+ return;
314
+ }
315
+ const result = await new DeeplineClient().disableNotificationSlack();
316
+ printCommandEnvelope(
317
+ {
318
+ ok: true,
319
+ result,
320
+ render: {
321
+ sections: [{ title: "Slack destination", lines: ["disabled"] }]
322
+ }
323
+ },
324
+ { json: options.json }
325
+ );
326
+ });
327
+ const subscriptions = notifications.command("subscriptions").description("List and configure supported notification events.").addHelpText(
328
+ "after",
329
+ `
330
+ Supported events (shared product catalog):
331
+ ${eventHelp}
332
+ `
333
+ );
334
+ subscriptions.command("list").description("List every supported event and its current state.").option("--json", "Emit JSON output").action(async (options) => {
335
+ const current = await new DeeplineClient().getNotificationSettings();
336
+ const destination = slackDestination(current);
337
+ const destinationId = typeof destination?._id === "string" ? destination._id : void 0;
338
+ printCommandEnvelope(
339
+ {
340
+ catalog: current.catalog,
341
+ subscriptions: current.subscriptions,
342
+ render: {
343
+ sections: [
344
+ {
345
+ title: "subscriptions",
346
+ lines: eventLines(current, destinationId)
347
+ }
348
+ ]
349
+ }
350
+ },
351
+ { json: options.json }
352
+ );
353
+ });
354
+ subscriptions.command("describe").description("Describe one event from the shared product catalog.").argument("<event>", "Event ID; run subscriptions list to enumerate").option("--json", "Emit JSON output").action(async (eventType, options) => {
355
+ const current = await new DeeplineClient().getNotificationSettings();
356
+ const event = requireCatalogEvent(current, eventType);
357
+ const destination = slackDestination(current);
358
+ const destinationId = typeof destination?._id === "string" ? destination._id : void 0;
359
+ const subscription = current.subscriptions.find(
360
+ (entry) => entry.eventType === event.id && (!destinationId || entry.destinationId === destinationId)
361
+ );
362
+ printCommandEnvelope(
363
+ {
364
+ event,
365
+ enabled: subscription?.enabled === true,
366
+ render: {
367
+ sections: [
368
+ {
369
+ title: event.id,
370
+ lines: [
371
+ event.description,
372
+ `enabled: ${subscription?.enabled === true}`
373
+ ]
374
+ }
375
+ ]
376
+ }
377
+ },
378
+ { json: options.json }
379
+ );
380
+ });
381
+ for (const enabled of [true, false]) {
382
+ subscriptions.command(enabled ? "enable" : "disable").description(`${enabled ? "Enable" : "Disable"} one supported event.`).argument("<event>", "Event ID; run subscriptions list to enumerate").option("--dry-run", "Validate intent without changing settings").option("--json", "Emit JSON output").action(async (eventType, options) => {
383
+ const client = new DeeplineClient();
384
+ const current = await client.getNotificationSettings();
385
+ const event = requireCatalogEvent(current, eventType);
386
+ if (options.dryRun) {
387
+ printCommandEnvelope(
388
+ { dryRun: true, event, enabled },
389
+ { json: options.json }
390
+ );
391
+ return;
392
+ }
393
+ const result = await client.setNotificationSubscriptions(
394
+ [event.id],
395
+ enabled
396
+ );
397
+ printCommandEnvelope(
398
+ {
399
+ ok: true,
400
+ event: event.id,
401
+ enabled,
402
+ result,
403
+ render: {
404
+ sections: [
405
+ {
406
+ title: "subscription updated",
407
+ lines: [`${event.id}: ${enabled ? "enabled" : "disabled"}`]
408
+ }
409
+ ]
410
+ }
411
+ },
412
+ { json: options.json }
413
+ );
414
+ });
415
+ }
416
+ const dlq = notifications.command("dlq").description("Inspect and explicitly resolve inert failed deliveries.");
417
+ dlq.command("list").option("--limit <count>", "Maximum deliveries", "25").option("--json", "Emit JSON output").action(async (options) => {
418
+ const result = await new DeeplineClient().listNotificationDlq(
419
+ Number(options.limit)
420
+ );
421
+ const deliveries = result.deliveries ?? [];
422
+ printCommandEnvelope(
423
+ {
424
+ ...result,
425
+ render: {
426
+ sections: [
427
+ {
428
+ title: "dead letter queue",
429
+ lines: deliveries.length ? deliveries.map(
430
+ (entry) => `${String(entry._id)}: ${String(entry.lastErrorCode ?? "delivery_failed")}`
431
+ ) : ["empty"]
432
+ }
433
+ ]
434
+ }
435
+ },
436
+ { json: options.json }
437
+ );
438
+ });
439
+ dlq.command("get").argument("<delivery-id>").option("--json", "Emit JSON output").action(async (deliveryId, options) => {
440
+ const result = await new DeeplineClient().getNotificationDlqDelivery(
441
+ deliveryId
442
+ );
443
+ printCommandEnvelope(result, {
444
+ json: options.json
445
+ });
446
+ });
447
+ for (const action of ["retry", "archive"]) {
448
+ dlq.command(action).description(
449
+ `${action === "retry" ? "Replay once from attempt zero" : "Archive"} one delivery.`
450
+ ).argument("<delivery-id>").option("--dry-run", "Describe the mutation without changing state").option("--json", "Emit JSON output").action(async (deliveryId, options) => {
451
+ if (options.dryRun) {
452
+ printCommandEnvelope(
453
+ { dryRun: true, action, deliveryId },
454
+ { json: options.json }
455
+ );
456
+ return;
457
+ }
458
+ const result = await new DeeplineClient().updateNotificationDlqDelivery(
459
+ deliveryId,
460
+ action
461
+ );
462
+ printCommandEnvelope(
463
+ { ok: true, action, deliveryId, result },
464
+ { json: options.json }
465
+ );
466
+ });
467
+ }
468
+ }
469
+ function notificationByName(settings, reference) {
470
+ const notification = (settings.notifications ?? []).find(
471
+ (entry) => entry.id === reference || entry.name === reference
472
+ );
473
+ if (!notification) {
474
+ throw new Error(
475
+ `No notification named "${reference}". Run deepline notifications list.`
476
+ );
477
+ }
478
+ return notification;
479
+ }
480
+ function parseSlackTarget(value) {
481
+ const match = /^slack:(.+)$/i.exec(value.trim());
482
+ if (!match?.[1]) {
483
+ throw new Error(
484
+ "Use --to slack:#channel or --to slack:member:U0123456789."
485
+ );
486
+ }
487
+ const target = match[1].trim();
488
+ const member = /^member:([UW][A-Z0-9]{8,})$/i.exec(target);
489
+ if (member?.[1]) return { memberId: member[1].toUpperCase() };
490
+ if (target.toLowerCase().startsWith("member:")) {
491
+ throw new Error(
492
+ "Slack member IDs must look like slack:member:U0123456789."
493
+ );
494
+ }
495
+ return { channel: target };
496
+ }
497
+ function displaySlackTarget(target) {
498
+ return `${target.kind === "member" ? "@" : "#"}${target.name}`;
499
+ }
500
+ function collectEvent(value, previous = []) {
501
+ return [...previous, value];
502
+ }
503
+ function collectNotificationTarget(value, previous = []) {
504
+ return [...previous, value];
505
+ }
506
+ async function readNotificationConfigurationChange(source, read = (path, encoding) => readFileSync(path, encoding)) {
507
+ const label = source === "-" ? "stdin" : source;
508
+ let text;
509
+ try {
510
+ text = await read(source === "-" ? 0 : source, "utf8");
511
+ } catch (error) {
512
+ throw new Error(
513
+ `Could not read notification configuration from ${label}: ${error instanceof Error ? error.message : String(error)}`
514
+ );
515
+ }
516
+ let parsed;
517
+ try {
518
+ parsed = JSON.parse(text);
519
+ } catch (error) {
520
+ throw new Error(
521
+ `Notification configuration from ${label} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
522
+ );
523
+ }
524
+ if (typeof parsed.expectedWorkspaceVersion !== "number" || !Number.isInteger(parsed.expectedWorkspaceVersion) || !parsed.change || typeof parsed.change !== "object") {
525
+ throw new Error(
526
+ `Notification configuration from ${label} must contain an integer expectedWorkspaceVersion and a change object.`
527
+ );
528
+ }
529
+ return {
530
+ expectedWorkspaceVersion: parsed.expectedWorkspaceVersion,
531
+ change: parsed.change
532
+ };
533
+ }
534
+ function registerNotificationCommands(program) {
535
+ const notifications = program.command("notifications").description("Configure notification groups, destinations, and rules.").addHelpText(
536
+ "after",
537
+ `
538
+ Examples:
539
+ deepline notifications add oncall --to email:oncall@example.com --to slack:#oncall
540
+ deepline notifications events
541
+ deepline notifications slack channels --search pipeline
542
+ deepline notifications add pipeline-watchdog --to slack:#pipeline-alerts --for play.cron.failed
543
+ deepline notifications add owner-alerts --to slack:member:U0123456789 --for play.cron.failed
544
+ deepline notifications test pipeline-watchdog
545
+
546
+ Each named notification is a group. Repeat --to to give it email and Slack
547
+ destinations. Add rules from the dashboard or notification configuration. The
548
+ existing one-Slack-target form with --for remains supported and creates the
549
+ equivalent compatibility group and rules. Slack connections are managed in
550
+ Dashboard \u2192 Integrations.
551
+ `
552
+ );
553
+ const configuration = notifications.command("configuration").description("Read or atomically update notification groups and rules.").addHelpText(
554
+ "after",
555
+ `
556
+ Examples:
557
+ deepline notifications configuration get --json
558
+ deepline notifications configuration apply change.json --dry-run --json
559
+ jq '{expectedWorkspaceVersion: .configuration.workspaceVersion, change: {kind: "delete_group", groupKey: "oncall"}}' current.json | deepline notifications configuration apply - --json
560
+ `
561
+ );
562
+ configuration.command("get").description("Show the effective group-based notification configuration.").option("--json", "Emit JSON output").action(async (options) => {
563
+ const result = await new DeeplineClient().getNotificationConfiguration();
564
+ printCommandEnvelope(
565
+ {
566
+ ...result,
567
+ render: {
568
+ sections: [
569
+ {
570
+ title: "notification groups",
571
+ lines: result.configuration.groups.length ? result.configuration.groups.map(
572
+ (group) => `${group.name}: ${group.enabled ? "active" : "paused"}`
573
+ ) : ["No notification groups are configured."]
574
+ }
575
+ ]
576
+ }
577
+ },
578
+ { json: options.json }
579
+ );
580
+ });
581
+ configuration.command("apply").argument(
582
+ "<file>",
583
+ "JSON file, or - for stdin, containing expectedWorkspaceVersion and one desired change"
584
+ ).description(
585
+ "Atomically apply a configuration draft exported by an agent or UI."
586
+ ).option("--dry-run", "Validate and display the file without saving it").option("--json", "Emit JSON output").action(async (file, options) => {
587
+ const input = await readNotificationConfigurationChange(file);
588
+ if (options.dryRun) {
589
+ printCommandEnvelope(
590
+ { dryRun: true, ...input },
591
+ { json: options.json }
592
+ );
593
+ return;
594
+ }
595
+ const result = await new DeeplineClient().changeNotificationConfiguration(
596
+ input.expectedWorkspaceVersion,
597
+ input.change
598
+ );
599
+ printCommandEnvelope(result, { json: options.json });
600
+ });
601
+ notifications.command("events").description("List the Play events available to a notification.").option("--json", "Emit JSON output").action(async (options) => {
602
+ const settings = await new DeeplineClient().getNotifications();
603
+ printCommandEnvelope(
604
+ {
605
+ events: settings.catalog,
606
+ render: {
607
+ sections: [
608
+ {
609
+ title: "available events",
610
+ lines: settings.catalog.length ? settings.catalog.map(
611
+ (event) => `${event.id}: ${event.description}`
612
+ ) : ["No notification events are available."]
613
+ }
614
+ ]
615
+ }
616
+ },
617
+ { json: options.json }
618
+ );
619
+ });
620
+ notifications.command("list").description("List named notifications and their selected Play events.").option("--json", "Emit JSON output").option("--compact", "Emit only names, state, target, and event IDs").action(async (options) => {
621
+ const settings = await new DeeplineClient().getNotifications();
622
+ const rules = settings.notifications ?? [];
623
+ printCommandEnvelope(
624
+ {
625
+ notifications: options.compact ? rules.map((rule) => ({
626
+ id: rule.id,
627
+ name: rule.name,
628
+ enabled: rule.enabled,
629
+ target: displaySlackTarget(rule.target),
630
+ eventTypes: rule.eventTypes
631
+ })) : rules,
632
+ count: rules.length,
633
+ render: {
634
+ sections: [
635
+ {
636
+ title: "notifications",
637
+ lines: rules.length ? rules.map(
638
+ (rule) => `${rule.name}: ${rule.enabled ? "on" : "paused"} \u2192 ${displaySlackTarget(rule.target)} (${rule.eventTypes.join(", ") || "no events"})`
639
+ ) : [
640
+ "None yet. Add one with deepline notifications add <name> --to slack:#channel --for play.cron.failed."
641
+ ]
642
+ }
643
+ ]
644
+ }
645
+ },
646
+ { json: options.json }
647
+ );
648
+ });
649
+ notifications.command("get").argument("<name>", "Notification name or ID").description("Show one named notification.").option("--json", "Emit JSON output").action(async (name, options) => {
650
+ const rule = notificationByName(
651
+ await new DeeplineClient().getNotifications(),
652
+ name
653
+ );
654
+ printCommandEnvelope({ notification: rule }, { json: options.json });
655
+ });
656
+ notifications.command("add").argument("<name>", "Group name, for example oncall").requiredOption(
657
+ "--to <target>",
658
+ "Target; repeat email:name@example.com, slack:#channel, or slack:member:U0123456789",
659
+ collectNotificationTarget,
660
+ []
661
+ ).option(
662
+ "--for <event>",
663
+ "Legacy one-Slack-target event ID; repeat for more events",
664
+ collectEvent,
665
+ []
666
+ ).option("--dry-run", "Describe the notification without saving it").option("--json", "Emit JSON output").action(
667
+ async (name, options) => {
668
+ const result = await new DeeplineClient().createNotificationGroup({
669
+ name,
670
+ targets: options.to,
671
+ legacyEvents: options.for,
672
+ dryRun: options.dryRun
673
+ });
674
+ if (result.mode === "dry_run") {
675
+ printCommandEnvelope(
676
+ result.compatibilityMode ? {
677
+ dryRun: true,
678
+ name: result.name,
679
+ target: options.to[0],
680
+ eventTypes: result.legacyEvents
681
+ } : {
682
+ dryRun: true,
683
+ group: result.name,
684
+ targets: result.targets,
685
+ compatibilityMode: false
686
+ },
687
+ { json: options.json }
688
+ );
689
+ return;
690
+ }
691
+ if (result.mode === "legacy") {
692
+ printCommandEnvelope(
693
+ {
694
+ notification: result.notification,
695
+ render: {
696
+ sections: [
697
+ {
698
+ title: "notification saved",
699
+ lines: [name]
700
+ }
701
+ ],
702
+ actions: [
703
+ {
704
+ label: "Send a test",
705
+ command: `deepline notifications test ${name}`
706
+ }
707
+ ]
708
+ }
709
+ },
710
+ { json: options.json }
711
+ );
712
+ return;
713
+ }
714
+ printCommandEnvelope(
715
+ {
716
+ ...result,
717
+ render: {
718
+ sections: [
719
+ {
720
+ title: "notification group saved",
721
+ lines: [
722
+ result.group.name,
723
+ `${result.destinations.length} destination${result.destinations.length === 1 ? "" : "s"}`,
724
+ "0 rules"
725
+ ]
726
+ }
727
+ ],
728
+ actions: [
729
+ {
730
+ label: "View configuration",
731
+ command: "deepline notifications configuration get"
732
+ }
733
+ ]
734
+ }
735
+ },
736
+ { json: options.json }
737
+ );
738
+ }
739
+ );
740
+ notifications.command("edit").argument("<name>", "Notification name or ID").option(
741
+ "--to <target>",
742
+ "New target, for example slack:#pipeline-alerts or slack:member:U0123456789"
743
+ ).option(
744
+ "--for <event>",
745
+ "Replace selected events; repeat for more events. Run notifications events to list them",
746
+ collectEvent,
747
+ []
748
+ ).option("--dry-run", "Describe the change without saving it").option("--json", "Emit JSON output").action(
749
+ async (name, options) => {
750
+ const client = new DeeplineClient();
751
+ const rule = notificationByName(await client.getNotifications(), name);
752
+ const target = options.to ? parseSlackTarget(options.to) : rule.target.kind === "member" ? { memberId: rule.target.id } : { channel: rule.target.name };
753
+ const eventTypes = options.for.length ? options.for : rule.eventTypes;
754
+ if (options.dryRun) {
755
+ printCommandEnvelope(
756
+ { dryRun: true, notification: rule.id, target, eventTypes },
757
+ { json: options.json }
758
+ );
759
+ return;
760
+ }
761
+ const result = await client.updateNotification(rule.id, {
762
+ name: rule.name,
763
+ ...target,
764
+ eventTypes
765
+ });
766
+ printCommandEnvelope({ notification: result }, { json: options.json });
767
+ }
768
+ );
769
+ const slack = notifications.command("slack").description("Select a channel from the connected Slack integration.");
770
+ slack.command("channels").option("--search <text>", "Filter channel names").option("--json", "Emit JSON output").action(async (options) => {
771
+ const result = await new DeeplineClient().listNotificationChannels(
772
+ options.search
773
+ );
774
+ printCommandEnvelope(
775
+ {
776
+ ...result,
777
+ render: {
778
+ sections: [
779
+ {
780
+ title: "Slack channels",
781
+ lines: result.channels.length ? result.channels.map((channel) => `#${channel.name}`) : ["No visible channels matched."]
782
+ }
783
+ ]
784
+ }
785
+ },
786
+ { json: options.json }
787
+ );
788
+ });
789
+ for (const [command, enabled] of [
790
+ ["pause", false],
791
+ ["resume", true]
792
+ ]) {
793
+ notifications.command(command).argument("<name>", "Notification name or ID").option("--dry-run", "Describe the change without saving it").option("--json", "Emit JSON output").action(async (name, options) => {
794
+ const client = new DeeplineClient();
795
+ const rule = notificationByName(await client.getNotifications(), name);
796
+ if (options.dryRun) {
797
+ printCommandEnvelope(
798
+ { dryRun: true, notification: rule.id, enabled },
799
+ { json: options.json }
800
+ );
801
+ return;
802
+ }
803
+ const result = await client.updateNotification(rule.id, { enabled });
804
+ printCommandEnvelope({ notification: result }, { json: options.json });
805
+ });
806
+ }
807
+ notifications.command("test").argument("<name>", "Notification name or ID").option("--dry-run", "Describe the external send without sending").option("--json", "Emit JSON output").action(async (name, options) => {
808
+ const client = new DeeplineClient();
809
+ const rule = notificationByName(await client.getNotifications(), name);
810
+ if (options.dryRun) {
811
+ printCommandEnvelope(
812
+ { dryRun: true, notification: rule.id, action: "send_test" },
813
+ { json: options.json }
814
+ );
815
+ return;
816
+ }
817
+ const result = await client.testNotification(rule.id);
818
+ printCommandEnvelope(
819
+ { ...result, notification: rule.name },
820
+ { json: options.json }
821
+ );
822
+ });
823
+ notifications.command("delete").argument("<name>", "Notification name or ID").description(
824
+ "Archive one notification without touching its provider integration."
825
+ ).option("--dry-run", "Describe the deletion without changing state").option("--json", "Emit JSON output").action(async (name, options) => {
826
+ const client = new DeeplineClient();
827
+ const rule = notificationByName(await client.getNotifications(), name);
828
+ if (options.dryRun) {
829
+ printCommandEnvelope(
830
+ { dryRun: true, notification: rule.id, action: "archive" },
831
+ { json: options.json }
832
+ );
833
+ return;
834
+ }
835
+ const result = await client.deleteNotification(rule.id);
836
+ printCommandEnvelope(
837
+ { ...result, notification: rule.name },
838
+ { json: options.json }
839
+ );
840
+ });
841
+ }
842
+ export {
843
+ readNotificationConfigurationChange,
844
+ registerNotificationCommands,
845
+ registerSettingsCommands
846
+ };