@velaro/cli 1.4.81 → 1.4.83
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/bin/velaro.js
CHANGED
|
@@ -93,6 +93,7 @@ import { routingCommand } from '../lib/commands/routing.js';
|
|
|
93
93
|
import { diagnosticsCommand } from '../lib/commands/diagnostics.js';
|
|
94
94
|
import { aiSpendCommand } from '../lib/commands/ai-spend.js';
|
|
95
95
|
import { statusPageCommand } from '../lib/commands/status-page.js';
|
|
96
|
+
import { statusNotifyCommand } from '../lib/commands/status-notify.js';
|
|
96
97
|
import { emailInboxCommand } from '../lib/commands/email-inbox.js';
|
|
97
98
|
import { callbackCommand } from '../lib/commands/callback.js';
|
|
98
99
|
import { caseCommand } from '../lib/commands/case.js';
|
|
@@ -105,6 +106,7 @@ import { featureDiscoveryCommand } from '../lib/commands/feature-discovery.js';
|
|
|
105
106
|
import { tourPolicyCommand } from '../lib/commands/tour-policy.js';
|
|
106
107
|
import { scraperCommand } from '../lib/commands/scraper.js';
|
|
107
108
|
import { campaignsCommand } from '../lib/commands/campaigns.js';
|
|
109
|
+
import { senderDomainHealthCommand } from '../lib/commands/sender-domain-health.js';
|
|
108
110
|
import { contactsCommand } from '../lib/commands/contacts.js';
|
|
109
111
|
import { contactListCommand } from '../lib/commands/contact-list.js';
|
|
110
112
|
import { startUpdateCheck } from '../lib/update-check.js';
|
|
@@ -211,6 +213,7 @@ await yargs(hideBin(process.argv))
|
|
|
211
213
|
.command(diagnosticsCommand)
|
|
212
214
|
.command(aiSpendCommand)
|
|
213
215
|
.command(statusPageCommand)
|
|
216
|
+
.command(statusNotifyCommand)
|
|
214
217
|
.command(emailInboxCommand)
|
|
215
218
|
.command(callbackCommand)
|
|
216
219
|
.command(caseCommand)
|
|
@@ -223,6 +226,7 @@ await yargs(hideBin(process.argv))
|
|
|
223
226
|
.command({ command: 'scraper', desc: 'Velaro staff: scraper / upload / chunk usage for any site', builder: scraperCommand, handler: () => {} })
|
|
224
227
|
.command({ command: 'tour-policy', desc: 'Velaro staff: guided-tour auto-open policy (versioned, rollback-able)', builder: tourPolicyCommand, handler: () => {} })
|
|
225
228
|
.command(campaignsCommand)
|
|
229
|
+
.command(senderDomainHealthCommand)
|
|
226
230
|
.command(contactsCommand)
|
|
227
231
|
.command(contactListCommand)
|
|
228
232
|
.demandCommand(1, 'Specify a command. Run "velaro --help" for options.')
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* velaro sender-domain-health - registration expiry, DNS and email-provider state for your sending domains
|
|
3
|
+
*
|
|
4
|
+
* velaro sender-domain-health show Health + plain-language history
|
|
5
|
+
* velaro sender-domain-health settings View or change warning days / grace days
|
|
6
|
+
*/
|
|
7
|
+
import { request } from '../api.js';
|
|
8
|
+
import { runCommand } from '../run.js';
|
|
9
|
+
|
|
10
|
+
async function showHandler() {
|
|
11
|
+
const r = await request('GET', '/SenderDomains/Health');
|
|
12
|
+
const domains = r.domains ?? [];
|
|
13
|
+
if (domains.length === 0) { console.log('No sender domains.'); return; }
|
|
14
|
+
for (const d of domains) {
|
|
15
|
+
const exp = d.registrationExpiresOn ? `${d.registrationExpiresOn.slice(0, 10)} (${d.daysToExpiry} days)` : 'unknown';
|
|
16
|
+
console.log(`${d.domainName} [${d.healthStatus}] status=${d.status} registration expires: ${exp}`);
|
|
17
|
+
if (d.healthDetail) console.log(` ${d.healthDetail}`);
|
|
18
|
+
}
|
|
19
|
+
const open = r.notices ?? [];
|
|
20
|
+
if (open.length) {
|
|
21
|
+
console.log('\nAction needed:');
|
|
22
|
+
for (const n of open) console.log(` - ${n.domainName}: ${n.message}\n What to do: ${n.remedy}`);
|
|
23
|
+
}
|
|
24
|
+
console.log(`\nWarnings start at ${r.settings.thresholds.join('/')} days; sending pauses ${r.settings.graceDays} days after expiry${r.settings.usingAccountDefault ? ' (account default)' : ''}.`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function settingsHandler(argv) {
|
|
28
|
+
if (argv.reset) { console.log(JSON.stringify(await request('PUT', '/SenderDomains/Health/Settings', { reset: true }))); return; }
|
|
29
|
+
if (argv.thresholds === undefined && argv.graceDays === undefined) {
|
|
30
|
+
const r = await request('GET', '/SenderDomains/Health');
|
|
31
|
+
console.log(JSON.stringify(r.settings, null, 2));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
console.log(JSON.stringify(await request('PUT', '/SenderDomains/Health/Settings', { thresholds: argv.thresholds, graceDays: argv.graceDays })));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function dismissHandler(argv) {
|
|
38
|
+
console.log(JSON.stringify(await request('POST', `/SenderDomains/Health/Events/${argv.id}/Dismiss`, {})));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const senderDomainHealthCommand = {
|
|
42
|
+
command: 'sender-domain-health <subcommand>',
|
|
43
|
+
describe: 'Sender domain health: registration expiry, DNS, email-provider state',
|
|
44
|
+
builder: y => y
|
|
45
|
+
.command({ command: 'show', describe: 'Health and history for your sender domains', handler: runCommand(showHandler) })
|
|
46
|
+
.command({
|
|
47
|
+
command: 'settings', describe: 'View or change warning days and grace days',
|
|
48
|
+
builder: yy => yy
|
|
49
|
+
.option('thresholds', { type: 'string', describe: 'Comma-separated days before expiry, e.g. 60,30,14,7,3,1' })
|
|
50
|
+
.option('grace-days', { type: 'number', describe: 'Days after expiry before sending pauses (0-90)' })
|
|
51
|
+
.option('reset', { type: 'boolean', describe: 'Go back to the account default' }),
|
|
52
|
+
handler: runCommand(settingsHandler),
|
|
53
|
+
})
|
|
54
|
+
.command({ command: 'dismiss <id>', describe: 'Dismiss a notice (do not remind me)', handler: runCommand(dismissHandler) })
|
|
55
|
+
.demandCommand(1, 'Specify a subcommand: show | settings | dismiss')
|
|
56
|
+
.strict(),
|
|
57
|
+
handler: () => {},
|
|
58
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { messagingRequest } from '../api.js';
|
|
2
|
+
|
|
3
|
+
// Staff "Notify subscribers" for the public status page (velaro-messaging StatusNotificationsController).
|
|
4
|
+
// Only major incidents and scheduled maintenance are ever emailed. `send` is guarded: it re-runs the preview,
|
|
5
|
+
// refuses unless --confirm-count equals the live recipient count, and needs an --idempotency-key.
|
|
6
|
+
|
|
7
|
+
function buildRequest(argv) {
|
|
8
|
+
const body = {};
|
|
9
|
+
if (argv.incident !== undefined) body.incidentId = argv.incident;
|
|
10
|
+
if (argv.title) {
|
|
11
|
+
body.announcement = {
|
|
12
|
+
title: argv.title,
|
|
13
|
+
impact: argv.impact ?? null,
|
|
14
|
+
components: argv.components ? String(argv.components).split(',').map((s) => s.trim()).filter(Boolean) : null,
|
|
15
|
+
windowStart: argv.windowStart ?? null,
|
|
16
|
+
windowEnd: argv.windowEnd ?? null,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
if (argv.subject) body.subject = argv.subject;
|
|
20
|
+
if (argv.body) body.body = argv.body;
|
|
21
|
+
return body;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const draftOptions = (y) => y
|
|
25
|
+
.option('incident', { type: 'number', describe: 'Status incident id (use this OR --title)' })
|
|
26
|
+
.option('title', { type: 'string', describe: 'Announcement title (no incident)' })
|
|
27
|
+
.option('impact', { type: 'string' })
|
|
28
|
+
.option('components', { type: 'string', describe: 'CSV of component names' })
|
|
29
|
+
.option('window-start', { type: 'string', describe: 'ISO timestamp' })
|
|
30
|
+
.option('window-end', { type: 'string', describe: 'ISO timestamp' })
|
|
31
|
+
.option('subject', { type: 'string', describe: 'Edited subject (default: generated)' })
|
|
32
|
+
.option('body', { type: 'string', describe: 'Edited plain-text body (default: generated)' });
|
|
33
|
+
|
|
34
|
+
const out = (data) => console.log(JSON.stringify(data, null, 2));
|
|
35
|
+
|
|
36
|
+
const settingsCommand = {
|
|
37
|
+
command: 'settings',
|
|
38
|
+
describe: 'Show send switch, recipient cap and subscriber counts',
|
|
39
|
+
builder: (y) => y,
|
|
40
|
+
handler: async () => out(await messagingRequest('GET', '/StatusNotifications/settings')),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const subscribersCommand = {
|
|
44
|
+
command: 'subscribers',
|
|
45
|
+
describe: 'Confirmed / pending / unsubscribed subscriber counts',
|
|
46
|
+
builder: (y) => y,
|
|
47
|
+
handler: async () => {
|
|
48
|
+
const s = await messagingRequest('GET', '/StatusNotifications/settings');
|
|
49
|
+
console.log(`confirmed=${s.confirmedSubscribers} pending=${s.pendingSubscribers} unsubscribed=${s.unsubscribed}`);
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const previewCommand = {
|
|
54
|
+
command: 'preview',
|
|
55
|
+
describe: 'Draft the notice and show the live recipient count. Sends nothing.',
|
|
56
|
+
builder: draftOptions,
|
|
57
|
+
handler: async (argv) => out(await messagingRequest('POST', '/StatusNotifications/preview', buildRequest(argv))),
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const testSendCommand = {
|
|
61
|
+
command: 'test-send',
|
|
62
|
+
describe: 'Send the draft to the signed-in staff member only',
|
|
63
|
+
builder: draftOptions,
|
|
64
|
+
handler: async (argv) => out(await messagingRequest('POST', '/StatusNotifications/test-send', buildRequest(argv))),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const sendCommand = {
|
|
68
|
+
command: 'send',
|
|
69
|
+
describe: 'Send the notice to ALL confirmed subscribers. Requires --confirm-count (must equal the preview recipient count) and --idempotency-key.',
|
|
70
|
+
builder: (y) => draftOptions(y)
|
|
71
|
+
.option('confirm-count', { type: 'number', demandOption: true, describe: 'Recipient count from `status-notify preview`' })
|
|
72
|
+
.option('idempotency-key', { type: 'string', demandOption: true, describe: 'One key per announcement; retries never double-send' }),
|
|
73
|
+
handler: async (argv) => {
|
|
74
|
+
const req = buildRequest(argv);
|
|
75
|
+
const preview = await messagingRequest('POST', '/StatusNotifications/preview', req);
|
|
76
|
+
if (preview.recipientCount !== argv.confirmCount) {
|
|
77
|
+
console.error(`Refusing to send: --confirm-count ${argv.confirmCount} does not match live recipient count ${preview.recipientCount}. Re-run preview.`);
|
|
78
|
+
process.exitCode = 1;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
out(await messagingRequest('POST', '/StatusNotifications/send', { ...req, expectedRecipientCount: argv.confirmCount, idempotencyKey: argv.idempotencyKey }));
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const auditCommand = {
|
|
86
|
+
command: 'audit',
|
|
87
|
+
describe: 'Recent send audit rows',
|
|
88
|
+
builder: (y) => y.option('take', { type: 'number', default: 50 }),
|
|
89
|
+
handler: async (argv) => out(await messagingRequest('GET', `/StatusNotifications/audit?take=${argv.take}`)),
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const statusNotifyCommand = {
|
|
93
|
+
command: 'status-notify <subcommand>',
|
|
94
|
+
describe: 'Status page email alerts: settings, preview, test-send, send, audit, subscribers (staff only)',
|
|
95
|
+
builder: (y) => y.command(settingsCommand).command(subscribersCommand).command(previewCommand).command(testSendCommand).command(sendCommand).command(auditCommand)
|
|
96
|
+
.demandCommand(1, 'Specify a subcommand: settings, subscribers, preview, test-send, send, audit').strict(),
|
|
97
|
+
handler: () => {},
|
|
98
|
+
};
|
package/package.json
CHANGED