groundmail-cli 0.43.1 → 0.45.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 +10 -0
- package/dist/cli.js +314 -195
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -147,6 +147,16 @@ groundmail suppressions list --type category_opt_out --tenant cus_123
|
|
|
147
147
|
groundmail suppressions remove --type category_opt_out --email reader@example.com --category newsletter --tenant cus_123
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
+
## Updating
|
|
151
|
+
|
|
152
|
+
The CLI tells you when a newer version is out, at most once a day and never in CI or a pipe. To update:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
npm install -g groundmail-cli@latest
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Set `GROUNDMAIL_NO_UPDATE_NOTIFIER=1` to turn the notice off.
|
|
159
|
+
|
|
150
160
|
## Docs
|
|
151
161
|
|
|
152
162
|
- CLI: https://docs.groundmail.dev/cli
|
package/dist/cli.js
CHANGED
|
@@ -39,10 +39,10 @@ function idLike(token) {
|
|
|
39
39
|
}
|
|
40
40
|
function shortenReply(reply, max) {
|
|
41
41
|
if (reply.length <= LABEL_WIDTH) return reply;
|
|
42
|
-
const
|
|
42
|
+
const parts2 = reply.split(/(\s+)/);
|
|
43
43
|
const out = [];
|
|
44
44
|
let inRun = false;
|
|
45
|
-
for (const part of
|
|
45
|
+
for (const part of parts2) {
|
|
46
46
|
if (/^\s+$/.test(part)) {
|
|
47
47
|
if (!inRun) out.push(part);
|
|
48
48
|
continue;
|
|
@@ -2089,6 +2089,214 @@ async function runListKeysCommandWithClient(args, env = process.env) {
|
|
|
2089
2089
|
}
|
|
2090
2090
|
}
|
|
2091
2091
|
|
|
2092
|
+
// package.json
|
|
2093
|
+
var version = "0.45.0";
|
|
2094
|
+
|
|
2095
|
+
// src/help.ts
|
|
2096
|
+
var USAGE_LINES = [
|
|
2097
|
+
" groundmail login [--scope <scopes>] [--workspace <name>] sign in through the browser (OAuth; no key to copy)",
|
|
2098
|
+
" groundmail logout [--workspace <name>|--all] revokes the sign-in server-side",
|
|
2099
|
+
" groundmail whoami",
|
|
2100
|
+
" groundmail auth token [--scope <scopes>] print a short-lived access token (for agents and scripts)",
|
|
2101
|
+
" groundmail workspaces list",
|
|
2102
|
+
" groundmail workspaces use <name|id>",
|
|
2103
|
+
" groundmail domains add example.com [--tenant <tenant>]",
|
|
2104
|
+
" groundmail domains apply example.com --provider cloudflare",
|
|
2105
|
+
" groundmail domains apply example.com --provider vercel",
|
|
2106
|
+
" groundmail domains apply example.com --provider godaddy",
|
|
2107
|
+
" groundmail domains list [--tenant <tenant>]",
|
|
2108
|
+
" groundmail domains get example.com",
|
|
2109
|
+
" groundmail domains update example.com --inbound true",
|
|
2110
|
+
" groundmail domains verify example.com",
|
|
2111
|
+
" groundmail domains delete example.com",
|
|
2112
|
+
" groundmail dmarc status example.com [--json] DMARC monitoring: sources, pass rates, readiness",
|
|
2113
|
+
" groundmail dmarc enable example.com [--apply cloudflare|vercel|godaddy|netlify|digitalocean|dnsimple] report address and the record to publish",
|
|
2114
|
+
" groundmail dmarc digest example.com --on|--off weekly digest email",
|
|
2115
|
+
" groundmail dmarc disable example.com",
|
|
2116
|
+
" groundmail received-emails list [--domain inbound.example.com] [--in-reply-to-email msg_...]",
|
|
2117
|
+
" groundmail received-emails get inb_...",
|
|
2118
|
+
" groundmail test-inbox address [--prefix signup] a fresh address on your team's test domain (mail there is caught, never delivered)",
|
|
2119
|
+
" groundmail test-inbox wait --to <address> [--timeout 30] [--since <iso>] [--json] wait for an email; prints its code and links (exit 2 on timeout)",
|
|
2120
|
+
" groundmail test-inbox list [--to <address>] [--since <iso>] [--json]",
|
|
2121
|
+
" groundmail mailboxes create --instant [--owner-email you@example.com] no account or key needed",
|
|
2122
|
+
" groundmail mailboxes owner <mailbox> --owner-email you@example.com send your person a code",
|
|
2123
|
+
" groundmail mailboxes verify <mailbox> --code 482913 unlock sending",
|
|
2124
|
+
" groundmail mailboxes create [--local-part signup-bot] [--domain agents.example.com] [--display-name \u2026] [--external-id \u2026] [--tenant <tenant>] [--retention-days 7] [--spam-threshold 5] [--metadata JSON] [--forward-from <mailbox>] [--json] a real address your agent reads and sends from; no --domain: a hosted address, which can only reply; --forward-from: a hosted mailbox this one replaces",
|
|
2125
|
+
" groundmail mailboxes list [--tenant <tenant>] [--limit 25] [--cursor \u2026] [--json]",
|
|
2126
|
+
" groundmail mailboxes get <mailbox> [--json]",
|
|
2127
|
+
" groundmail mailboxes update <mailbox> --send-as support@example.com send as an address on a verified domain (none removes it)",
|
|
2128
|
+
" groundmail mailboxes delete <mailbox> --yes the mailbox and all its mail; a hosted address is never given out again",
|
|
2129
|
+
" groundmail mailboxes forward <mailbox> --to <new mailbox> --yes move a hosted address to a mailbox on your own domain, for 30 days; it can't send meanwhile",
|
|
2130
|
+
" groundmail mailboxes threads <mailbox> [--label \u2026] [--participant \u2026] [--unread] [--archived] [--since <iso>] [--include-held] [--limit 25] [--cursor \u2026] [--json]",
|
|
2131
|
+
" groundmail mailboxes read <mailbox> [mbm_\u2026|thd_\u2026] [--search \u2026] [--from \u2026] [--thread thd_\u2026] [--label \u2026] [--unread] [--since <iso>] [--include-held] [--limit 25] [--cursor \u2026] [--json] one message or thread; with no id, list or search messages",
|
|
2132
|
+
" groundmail mailboxes wait <mailbox> [--from \u2026] [--thread thd_\u2026] [--since <iso>] [--timeout 30] [--json] wait for the next message; prints its code and links (exit 2 on timeout)",
|
|
2133
|
+
" groundmail mailboxes send <mailbox> --to a@example.com,b@example.com [--cc \u2026] --subject \u2026 (--text \u2026 | --html \u2026) [--idempotency-key \u2026] [--json] email someone first, from a mailbox on your own domain",
|
|
2134
|
+
" groundmail mailboxes reply <mailbox> mbm_\u2026 (--text \u2026 | --html \u2026) [--reply-all] [--idempotency-key \u2026] [--json] reply in the thread",
|
|
2135
|
+
` groundmail emails send [--from you@domain.com | --sender billing] --to user@example.com --subject "Hello" --html "<p>Hi</p>" [--category transactional|product|newsletter] [--unsubscribe] [--tenant <tenant>] [--watch [--timeout 60] [--json]] --sender: a saved sender's handle; no --from or --sender: the sandbox sender (to your account email only)`,
|
|
2136
|
+
' groundmail emails send --to you@example.com --subject "Hello" --text "Hi" --watch watch it land: the receiving server, its reply and the time',
|
|
2137
|
+
" groundmail emails batch --file ./emails.json [--tenant <tenant>]",
|
|
2138
|
+
" groundmail emails list [--limit 25] [--cursor \u2026] [--test | --live] [--tenant <tenant>] [--stream transactional|product|newsletter]",
|
|
2139
|
+
" groundmail emails insights [--window 7d|30d]",
|
|
2140
|
+
" groundmail emails get --id msg_\u2026 [--watch [--timeout 60] [--json]] --watch: follow an email you already sent until it lands",
|
|
2141
|
+
" groundmail emails cancel --id msg_\u2026",
|
|
2142
|
+
" groundmail batches get <batch_id> an emails batch's status counts (the batch_id `emails batch` returned)",
|
|
2143
|
+
" groundmail batches cancel <batch_id> cancel the batch's emails that are still scheduled; safe to repeat",
|
|
2144
|
+
" groundmail emails resend --id msg_\u2026",
|
|
2145
|
+
` groundmail events send --name user.created --email user@example.com --data '{"name":"Ada"}'`,
|
|
2146
|
+
" groundmail automations list",
|
|
2147
|
+
" groundmail automations create --file ./welcome.json",
|
|
2148
|
+
` groundmail automations create --name Welcome --trigger user.created --steps '[{"type":"send_email","email":{"from":"you@yourdomain.com","to":"{{event.email}}","template":{"id":"welcome","variables":{"name":"{{event.data.name}}"}}}}]' each email step sends a published template`,
|
|
2149
|
+
" groundmail automations create --file ./otp-sms.json",
|
|
2150
|
+
" groundmail automations get --id <automation-id>",
|
|
2151
|
+
" groundmail automations update --id <automation-id> --file ./welcome.json",
|
|
2152
|
+
' groundmail automations update --id <automation-id> --folder Onboarding (--folder "" takes it out of any folder)',
|
|
2153
|
+
" groundmail automations activate --id <automation-id>",
|
|
2154
|
+
" groundmail automations pause --id <automation-id>",
|
|
2155
|
+
" groundmail automations delete --id <automation-id>",
|
|
2156
|
+
" groundmail webhooks list",
|
|
2157
|
+
" groundmail webhooks create --url https://yourapp.com/hooks [--tenants a,b] [--mailboxes mbx_a,mbx_b] --tenants: only these tenants' email events; --mailboxes: only these mailboxes' events",
|
|
2158
|
+
" groundmail webhooks get --id wh_\u2026",
|
|
2159
|
+
" groundmail webhooks update --id wh_\u2026 [--url \u2026] [--event email.sent] [--enabled true|false] [--tenants a,b|all] [--mailboxes mbx_a,mbx_b|all]",
|
|
2160
|
+
" groundmail webhooks delete --id <webhook-id>",
|
|
2161
|
+
" groundmail webhooks deliveries list --webhook <webhook-id> [--status failed] [--event email.delivered] [--email-id msg_\u2026] [--q \u2026] [--test true|false] [--limit 25] [--cursor \u2026]",
|
|
2162
|
+
" groundmail webhooks deliveries get --webhook <webhook-id> --id <delivery-id>",
|
|
2163
|
+
" groundmail webhooks deliveries resend --webhook <webhook-id> --id <delivery-id>",
|
|
2164
|
+
" groundmail validate user@example.com Groundmail validation: verdict, reasons, typo suggestion (exit 2 when invalid)",
|
|
2165
|
+
" groundmail burrowguard sites the forms BurrowGuard protects",
|
|
2166
|
+
' groundmail burrowguard sites create --name "Sign-up form" --hostname app.example.com [--hostname localhost] [--block-free-webmail] [--allow-data-center-ips] [--email-checks-only] [--watch-only] prints the secret once',
|
|
2167
|
+
" groundmail burrowguard sites update <id> --watch-only | --no-watch-only show what would be blocked without blocking anyone, or start blocking",
|
|
2168
|
+
" groundmail burrowguard sites roll-secret <id> --yes the old secret stops working at once",
|
|
2169
|
+
" groundmail burrowguard sites delete <id> --yes",
|
|
2170
|
+
" groundmail burrowguard checks [--site <id>] [--result passed|blocked|would_block|no_token] [--range 24h|7d|30d] [--limit 25]",
|
|
2171
|
+
" groundmail suppressions list [--tenant <tenant>]",
|
|
2172
|
+
" groundmail suppressions add --email user@example.com [--reason \u2026] [--tenant <tenant>]",
|
|
2173
|
+
" groundmail suppressions remove --id <id> | --email user@example.com [--tenant <tenant>]",
|
|
2174
|
+
" groundmail suppressions list --type category_opt_out [--category product|newsletter] [--tenant <tenant> | --include-tenants] one-click category unsubscribes",
|
|
2175
|
+
" groundmail suppressions remove --type category_opt_out --email user@example.com [--category newsletter] [--tenant <tenant>] re-subscribe to a category",
|
|
2176
|
+
" groundmail templates list [--status draft|published]",
|
|
2177
|
+
" groundmail templates starters",
|
|
2178
|
+
" groundmail templates from-starter password-reset",
|
|
2179
|
+
' groundmail templates create --name Welcome --subject "Hi" --html "<p>Hi</p>" [--category transactional|product] [--unsubscribe-enabled] [--folder Onboarding] [--sender billing]',
|
|
2180
|
+
" groundmail templates get --id tpl_\u2026",
|
|
2181
|
+
" groundmail templates update --id tpl_\u2026 [--name \u2026] [--subject \u2026] [--html \u2026] [--text \u2026] [--set-alias \u2026] [--category transactional|product] [--unsubscribe-enabled|--no-unsubscribe-enabled] [--folder \u2026] [--sender billing|none]",
|
|
2182
|
+
" groundmail templates publish --id tpl_\u2026",
|
|
2183
|
+
` groundmail templates push ./emails/welcome.tsx --alias welcome [--subject "Welcome"] [--props '{"name":"Ada"}'] [--publish]`,
|
|
2184
|
+
" groundmail templates versions --id tpl_\u2026",
|
|
2185
|
+
" groundmail templates restore --id tpl_\u2026 --version 2",
|
|
2186
|
+
" groundmail templates test-send --id tpl_\u2026 --to you@domain.com [--from you@domain.com | --sender billing] neither: the template's sender, else your default sender",
|
|
2187
|
+
' groundmail templates duplicate --id tpl_\u2026 [--name "Copy"]',
|
|
2188
|
+
" groundmail templates delete --id tpl_\u2026 refused while an automation step sends it (the error names them)",
|
|
2189
|
+
" groundmail tenants list [--status active|paused] [--limit 25] [--cursor \u2026] your customers, each with its own domains, suppressions and limits",
|
|
2190
|
+
` groundmail tenants create <external-id> [--name \u2026] [--default-from-domain \u2026] [--stream-policy all|transactional_only] [--daily-limit N] [--metadata '{"plan":"pro"}']`,
|
|
2191
|
+
" groundmail tenants get <tenant>",
|
|
2192
|
+
" groundmail tenants update <tenant> [--name \u2026] [--default-from-domain \u2026|none] [--stream-policy \u2026] [--daily-limit N|none] [--metadata JSON|null]",
|
|
2193
|
+
" groundmail tenants pause <tenant> [--reason \u2026] stops the tenant's new sends",
|
|
2194
|
+
" groundmail tenants resume <tenant>",
|
|
2195
|
+
" groundmail tenants delete <tenant> --yes",
|
|
2196
|
+
" groundmail tenants stats <tenant> [--days 7]",
|
|
2197
|
+
" groundmail tenants domains list <tenant>",
|
|
2198
|
+
" groundmail tenants domains add <tenant> example.com only this tenant can then send from it",
|
|
2199
|
+
" groundmail tenants domains remove <tenant> example.com gives the domain back to the account",
|
|
2200
|
+
" groundmail brand get your logo, colors and footer, which templates follow",
|
|
2201
|
+
' groundmail brand set [--logo-url <https url>|none] [--logo-align left|center] [--logo-width 120] [--color #2f6feb] [--company "Acme Inc"|none] [--address "\u2026"|none] [--website acme.com|none] [--settings-file brand.json] --color: buttons, links and the line across the top',
|
|
2202
|
+
" groundmail senders list the names and addresses your email goes out as",
|
|
2203
|
+
' groundmail senders create --name "Acme Billing" --address billing@mail.acme.com [--reply-to billing@acme.com] [--handle billing] [--default] address on a verified domain',
|
|
2204
|
+
" groundmail senders get <id|handle>",
|
|
2205
|
+
" groundmail senders update <id|handle> [--name \u2026] [--address \u2026] [--reply-to \u2026|none] [--handle \u2026] [--default|--no-default]",
|
|
2206
|
+
" groundmail senders delete <id|handle> --yes refused while an automation or template uses it",
|
|
2207
|
+
" groundmail keys list",
|
|
2208
|
+
" groundmail keys create [--name Default] [--scopes emails:send,domains:read] [--allowed-domains example.com] [--test] [--tenant <tenant>] [--mailboxes mbx_a,mbx_b] --test: a gm_test_ key, sends are simulated; --tenant: a key limited to one tenant; --mailboxes: a key for one agent, limited to its mailboxes",
|
|
2209
|
+
" groundmail keys delete --id <api-key-id>",
|
|
2210
|
+
" groundmail migrate sendgrid [--dry-run] [--only templates,suppressions,domains,webhooks] [--region eu] import from SendGrid (Beta)",
|
|
2211
|
+
" groundmail migrate postmark [--dry-run] [--only \u2026] import from Postmark (Beta)",
|
|
2212
|
+
" groundmail migrate mailgun [--dry-run] [--only \u2026] [--region eu] import from Mailgun (Beta)",
|
|
2213
|
+
" groundmail migrate resend [--dry-run] [--only \u2026] import from Resend (Beta; a Full access key)",
|
|
2214
|
+
" groundmail migrate ses --region us-east-1 [--dry-run] [--only \u2026] import one Amazon SES region (Beta)",
|
|
2215
|
+
" groundmail migrate list your recent imports",
|
|
2216
|
+
" groundmail migrate report <id> [--csv] an import report (JSON, or CSV to stdout)",
|
|
2217
|
+
" groundmail integrations twilio status",
|
|
2218
|
+
" groundmail integrations twilio connect --account-sid AC\u2026 --auth-token \u2026 --from +15551234567",
|
|
2219
|
+
" groundmail integrations twilio disconnect"
|
|
2220
|
+
];
|
|
2221
|
+
var FOOTER_LINES = [
|
|
2222
|
+
"Environment:",
|
|
2223
|
+
" GROUNDMAIL_API_KEY=gm_\u2026 optional (CI): wins over the `groundmail login` sign-in",
|
|
2224
|
+
" GROUNDMAIL_NO_KEYCHAIN=1 keep the refresh token in the config file (0600) instead of the OS keychain",
|
|
2225
|
+
" GROUNDMAIL_NO_UPDATE_NOTIFIER=1 turn off the update notice",
|
|
2226
|
+
" TWILIO_ACCOUNT_SID=AC\u2026 TWILIO_AUTH_TOKEN=\u2026 TWILIO_DEFAULT_FROM=+1\u2026 (integrations twilio connect)",
|
|
2227
|
+
" CLOUDFLARE_API_TOKEN=\u2026 (optional local apply; else uses dashboard Integrations)",
|
|
2228
|
+
" VERCEL_API_TOKEN=\u2026 VERCEL_TEAM_ID=\u2026 (optional local apply; else uses dashboard Integrations)",
|
|
2229
|
+
" GODADDY_PAT=\u2026 or GODADDY_API_KEY=\u2026 GODADDY_API_SECRET=\u2026 (domains apply --provider godaddy)",
|
|
2230
|
+
" SENDGRID_API_KEY / POSTMARK_SERVER_TOKEN (+ POSTMARK_ACCOUNT_TOKEN) / MAILGUN_API_KEY / RESEND_API_KEY (migrate; used once, never stored)",
|
|
2231
|
+
" AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY (+ AWS_SESSION_TOKEN, AWS_REGION) (migrate ses; used once, never stored)",
|
|
2232
|
+
"",
|
|
2233
|
+
"Scopes: full, emails:send, emails:read, domains:read, domains:write, webhooks:write, templates:write,",
|
|
2234
|
+
" suppressions:write, account:read, mailboxes:read, mailboxes:write, mailboxes:send",
|
|
2235
|
+
"",
|
|
2236
|
+
"<tenant>: the tenant's external id (your id for that customer) or its Groundmail id.",
|
|
2237
|
+
"<mailbox>: the mailbox's mbx_ id, its address or your external id.",
|
|
2238
|
+
"",
|
|
2239
|
+
"--watch exit codes: 0 delivered, 1 error, 3 bounced or failed, 4 still on its way at --timeout, 5 test send (never delivered).",
|
|
2240
|
+
"",
|
|
2241
|
+
"Add --help to any command for just its usage. --version prints the CLI version."
|
|
2242
|
+
];
|
|
2243
|
+
var CLI_VERSION = version;
|
|
2244
|
+
var ALIASES = {
|
|
2245
|
+
workspace: { "": "workspaces" },
|
|
2246
|
+
domains: { create: "add", remove: "delete" },
|
|
2247
|
+
events: { trigger: "send" },
|
|
2248
|
+
automations: { add: "create", remove: "delete" },
|
|
2249
|
+
suppressions: { create: "add", delete: "remove" },
|
|
2250
|
+
templates: { add: "create", remove: "delete" },
|
|
2251
|
+
tenants: { add: "create", remove: "delete" },
|
|
2252
|
+
mailboxes: { add: "create", remove: "delete" },
|
|
2253
|
+
senders: { add: "create", remove: "delete" },
|
|
2254
|
+
brand: { update: "set" }
|
|
2255
|
+
};
|
|
2256
|
+
function words(line) {
|
|
2257
|
+
return line.trim().split(/\s+/);
|
|
2258
|
+
}
|
|
2259
|
+
function fullUsage() {
|
|
2260
|
+
return ["Usage:", ...USAGE_LINES, "", ...FOOTER_LINES].join("\n");
|
|
2261
|
+
}
|
|
2262
|
+
function unknownCommandMessage(argv) {
|
|
2263
|
+
const typed = argv.filter((arg) => !arg.startsWith("-")).slice(0, 2).join(" ");
|
|
2264
|
+
return `Unknown command: groundmail ${typed}
|
|
2265
|
+
${outdatedHint()}`;
|
|
2266
|
+
}
|
|
2267
|
+
function outdatedHint() {
|
|
2268
|
+
return `You're on groundmail-cli ${CLI_VERSION}. If the docs show this, your version may be out of date: npm install -g groundmail-cli@latest`;
|
|
2269
|
+
}
|
|
2270
|
+
function usageFor(command, subcommand) {
|
|
2271
|
+
if (!command) return fullUsage();
|
|
2272
|
+
const name = ALIASES[command]?.[""] ?? command;
|
|
2273
|
+
const forCommand = USAGE_LINES.filter((line) => words(line)[1] === name);
|
|
2274
|
+
if (!forCommand.length) return null;
|
|
2275
|
+
const sub = subcommand && !subcommand.startsWith("-") ? ALIASES[name]?.[subcommand] ?? subcommand : void 0;
|
|
2276
|
+
const forSub = sub ? forCommand.filter((line) => words(line)[2] === sub) : [];
|
|
2277
|
+
return ["Usage:", ...forSub.length ? forSub : forCommand].join("\n");
|
|
2278
|
+
}
|
|
2279
|
+
function isHelpFlag(arg) {
|
|
2280
|
+
return arg === "--help" || arg === "-h";
|
|
2281
|
+
}
|
|
2282
|
+
function isVersionFlag(arg) {
|
|
2283
|
+
return arg === "--version" || arg === "-v" || arg === "-V";
|
|
2284
|
+
}
|
|
2285
|
+
function resolveMetaRequest(argv) {
|
|
2286
|
+
const [command, subcommand] = argv;
|
|
2287
|
+
if (isVersionFlag(command) || command === "version") return { kind: "version", text: CLI_VERSION };
|
|
2288
|
+
if (!command) return null;
|
|
2289
|
+
if (isHelpFlag(command)) return { kind: "help", text: fullUsage() };
|
|
2290
|
+
if (command === "help") {
|
|
2291
|
+
return { kind: "help", text: usageFor(argv[1], argv[2]) ?? fullUsage() };
|
|
2292
|
+
}
|
|
2293
|
+
if (argv.some(isHelpFlag)) {
|
|
2294
|
+
const sub = isHelpFlag(subcommand) ? void 0 : subcommand;
|
|
2295
|
+
return { kind: "help", text: usageFor(command, sub) ?? fullUsage() };
|
|
2296
|
+
}
|
|
2297
|
+
return null;
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2092
2300
|
// src/session/brand.ts
|
|
2093
2301
|
var GROUNDMAIL = {
|
|
2094
2302
|
bin: "groundmail",
|
|
@@ -2112,9 +2320,6 @@ function trimUrl(value) {
|
|
|
2112
2320
|
return (value ?? "").trim().replace(/\/+$/, "");
|
|
2113
2321
|
}
|
|
2114
2322
|
|
|
2115
|
-
// package.json
|
|
2116
|
-
var version = "0.43.1";
|
|
2117
|
-
|
|
2118
2323
|
// src/user-agent.ts
|
|
2119
2324
|
var AGENT_ENV = [
|
|
2120
2325
|
["claude-code", ["CLAUDECODE", "CLAUDE_CODE"]],
|
|
@@ -2993,7 +3198,23 @@ function readFlag17(args, flag) {
|
|
|
2993
3198
|
if (index === -1) return void 0;
|
|
2994
3199
|
return args[index + 1];
|
|
2995
3200
|
}
|
|
3201
|
+
var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
3202
|
+
"--api-key",
|
|
3203
|
+
"--base-url",
|
|
3204
|
+
"--name",
|
|
3205
|
+
"--scope",
|
|
3206
|
+
"--scopes",
|
|
3207
|
+
"--allowed-domains",
|
|
3208
|
+
"--test",
|
|
3209
|
+
"--tenant",
|
|
3210
|
+
"--mailboxes"
|
|
3211
|
+
]);
|
|
2996
3212
|
async function runCreateKeyCommandWithClient(args, env = process.env) {
|
|
3213
|
+
const unknown = args.find((arg) => arg.startsWith("--") && !KNOWN_FLAGS.has(arg.split("=")[0]));
|
|
3214
|
+
if (unknown) {
|
|
3215
|
+
return { exitCode: 1, error: `Unknown option ${unknown}. No key was created.
|
|
3216
|
+
${outdatedHint()}` };
|
|
3217
|
+
}
|
|
2997
3218
|
const apiKey = readFlag17(args, "--api-key") || env.GROUNDMAIL_API_KEY;
|
|
2998
3219
|
const baseUrl = readFlag17(args, "--base-url") || defaultApiUrl(env);
|
|
2999
3220
|
if (!apiKey) {
|
|
@@ -4941,6 +5162,7 @@ async function runTestInboxCommand(subcommand, args, env = process.env) {
|
|
|
4941
5162
|
|
|
4942
5163
|
// src/commands/mailboxes/mailboxes.ts
|
|
4943
5164
|
var VALUE_FLAGS6 = [
|
|
5165
|
+
"--send-as",
|
|
4944
5166
|
"--api-key",
|
|
4945
5167
|
"--base-url",
|
|
4946
5168
|
"--workspace",
|
|
@@ -5056,8 +5278,8 @@ function formatMessage(message) {
|
|
|
5056
5278
|
}
|
|
5057
5279
|
function formatThread(thread) {
|
|
5058
5280
|
const head = [`Thread: ${thread.id}`, `Subject: ${oneLine(thread.subject)}`, `With: ${oneLine(thread.participants.join(", "))}`];
|
|
5059
|
-
const
|
|
5060
|
-
return [UNTRUSTED_LINE, ...head, ...
|
|
5281
|
+
const parts2 = thread.messages.map((message) => [...messageHeader(message), "", plain(message.reply_text ?? message.visible_text) || "(no visible text)"].join("\n"));
|
|
5282
|
+
return [UNTRUSTED_LINE, ...head, ...parts2.flatMap((part) => ["", "---", part])].join("\n");
|
|
5061
5283
|
}
|
|
5062
5284
|
function threadRow(thread) {
|
|
5063
5285
|
const unread = thread.unread_count ? ` ${thread.unread_count} unread` : "";
|
|
@@ -5129,7 +5351,7 @@ async function createInstant(args) {
|
|
|
5129
5351
|
}
|
|
5130
5352
|
}
|
|
5131
5353
|
async function runMailboxesCommand(subcommand, argv, env = process.env) {
|
|
5132
|
-
if (!subcommand || !["create", "owner", "verify", "list", "get", "delete", "forward", "threads", "read", "wait", "send", "reply"].includes(subcommand)) {
|
|
5354
|
+
if (!subcommand || !["create", "owner", "verify", "list", "get", "update", "delete", "forward", "threads", "read", "wait", "send", "reply"].includes(subcommand)) {
|
|
5133
5355
|
return { exitCode: 1, error: USAGE3 };
|
|
5134
5356
|
}
|
|
5135
5357
|
const parsed = parseArgs(argv, { values: VALUE_FLAGS6, booleans: BOOLEAN_FLAGS });
|
|
@@ -5183,6 +5405,13 @@ async function runMailboxesCommand(subcommand, argv, env = process.env) {
|
|
|
5183
5405
|
print({ mailbox: found }, () => formatMailbox(found));
|
|
5184
5406
|
return { exitCode: 0 };
|
|
5185
5407
|
}
|
|
5408
|
+
if (subcommand === "update") {
|
|
5409
|
+
const value = args.values["--send-as"];
|
|
5410
|
+
if (value === void 0) return { exitCode: 1, error: "Nothing to change. Usage: groundmail mailboxes update <mailbox> --send-as support@example.com (or --send-as none)" };
|
|
5411
|
+
const updated = await client2.mailboxes.update(ref, { send_as: value === "none" ? null : value });
|
|
5412
|
+
print({ mailbox: updated }, () => `Sends as ${updated.send_as ?? updated.address}`);
|
|
5413
|
+
return { exitCode: 0 };
|
|
5414
|
+
}
|
|
5186
5415
|
if (subcommand === "delete") {
|
|
5187
5416
|
if (!on("--yes")) {
|
|
5188
5417
|
return { exitCode: 1, error: "This deletes the mailbox and all its mail. Add --yes to confirm." };
|
|
@@ -5289,199 +5518,81 @@ async function runMailboxesCommand(subcommand, argv, env = process.env) {
|
|
|
5289
5518
|
}
|
|
5290
5519
|
}
|
|
5291
5520
|
|
|
5292
|
-
// src/
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
" groundmail test-inbox wait --to <address> [--timeout 30] [--since <iso>] [--json] wait for an email; prints its code and links (exit 2 on timeout)",
|
|
5317
|
-
" groundmail test-inbox list [--to <address>] [--since <iso>] [--json]",
|
|
5318
|
-
" groundmail mailboxes create --instant [--owner-email you@example.com] no account or key needed",
|
|
5319
|
-
" groundmail mailboxes owner <mailbox> --owner-email you@example.com send your person a code",
|
|
5320
|
-
" groundmail mailboxes verify <mailbox> --code 482913 unlock sending",
|
|
5321
|
-
" groundmail mailboxes create [--local-part signup-bot] [--domain agents.example.com] [--display-name \u2026] [--external-id \u2026] [--tenant <tenant>] [--retention-days 7] [--spam-threshold 5] [--metadata JSON] [--forward-from <mailbox>] [--json] a real address your agent reads and sends from; no --domain: a hosted address, which can only reply; --forward-from: a hosted mailbox this one replaces",
|
|
5322
|
-
" groundmail mailboxes list [--tenant <tenant>] [--limit 25] [--cursor \u2026] [--json]",
|
|
5323
|
-
" groundmail mailboxes get <mailbox> [--json]",
|
|
5324
|
-
" groundmail mailboxes delete <mailbox> --yes the mailbox and all its mail; a hosted address is never given out again",
|
|
5325
|
-
" groundmail mailboxes forward <mailbox> --to <new mailbox> --yes move a hosted address to a mailbox on your own domain, for 30 days; it can't send meanwhile",
|
|
5326
|
-
" groundmail mailboxes threads <mailbox> [--label \u2026] [--participant \u2026] [--unread] [--archived] [--since <iso>] [--include-held] [--limit 25] [--cursor \u2026] [--json]",
|
|
5327
|
-
" groundmail mailboxes read <mailbox> [mbm_\u2026|thd_\u2026] [--search \u2026] [--from \u2026] [--thread thd_\u2026] [--label \u2026] [--unread] [--since <iso>] [--include-held] [--limit 25] [--cursor \u2026] [--json] one message or thread; with no id, list or search messages",
|
|
5328
|
-
" groundmail mailboxes wait <mailbox> [--from \u2026] [--thread thd_\u2026] [--since <iso>] [--timeout 30] [--json] wait for the next message; prints its code and links (exit 2 on timeout)",
|
|
5329
|
-
" groundmail mailboxes send <mailbox> --to a@example.com,b@example.com [--cc \u2026] --subject \u2026 (--text \u2026 | --html \u2026) [--idempotency-key \u2026] [--json] email someone first, from a mailbox on your own domain",
|
|
5330
|
-
" groundmail mailboxes reply <mailbox> mbm_\u2026 (--text \u2026 | --html \u2026) [--reply-all] [--idempotency-key \u2026] [--json] reply in the thread",
|
|
5331
|
-
` groundmail emails send [--from you@domain.com | --sender billing] --to user@example.com --subject "Hello" --html "<p>Hi</p>" [--category transactional|product|newsletter] [--unsubscribe] [--tenant <tenant>] [--watch [--timeout 60] [--json]] --sender: a saved sender's handle; no --from or --sender: the sandbox sender (to your account email only)`,
|
|
5332
|
-
' groundmail emails send --to you@example.com --subject "Hello" --text "Hi" --watch watch it land: the receiving server, its reply and the time',
|
|
5333
|
-
" groundmail emails batch --file ./emails.json [--tenant <tenant>]",
|
|
5334
|
-
" groundmail emails list [--limit 25] [--cursor \u2026] [--test | --live] [--tenant <tenant>] [--stream transactional|product|newsletter]",
|
|
5335
|
-
" groundmail emails insights [--window 7d|30d]",
|
|
5336
|
-
" groundmail emails get --id msg_\u2026 [--watch [--timeout 60] [--json]] --watch: follow an email you already sent until it lands",
|
|
5337
|
-
" groundmail emails cancel --id msg_\u2026",
|
|
5338
|
-
" groundmail batches get <batch_id> an emails batch's status counts (the batch_id `emails batch` returned)",
|
|
5339
|
-
" groundmail batches cancel <batch_id> cancel the batch's emails that are still scheduled; safe to repeat",
|
|
5340
|
-
" groundmail emails resend --id msg_\u2026",
|
|
5341
|
-
` groundmail events send --name user.created --email user@example.com --data '{"name":"Ada"}'`,
|
|
5342
|
-
" groundmail automations list",
|
|
5343
|
-
" groundmail automations create --file ./welcome.json",
|
|
5344
|
-
` groundmail automations create --name Welcome --trigger user.created --steps '[{"type":"send_email","email":{"from":"you@yourdomain.com","to":"{{event.email}}","template":{"id":"welcome","variables":{"name":"{{event.data.name}}"}}}}]' each email step sends a published template`,
|
|
5345
|
-
" groundmail automations create --file ./otp-sms.json",
|
|
5346
|
-
" groundmail automations get --id <automation-id>",
|
|
5347
|
-
" groundmail automations update --id <automation-id> --file ./welcome.json",
|
|
5348
|
-
' groundmail automations update --id <automation-id> --folder Onboarding (--folder "" takes it out of any folder)',
|
|
5349
|
-
" groundmail automations activate --id <automation-id>",
|
|
5350
|
-
" groundmail automations pause --id <automation-id>",
|
|
5351
|
-
" groundmail automations delete --id <automation-id>",
|
|
5352
|
-
" groundmail webhooks list",
|
|
5353
|
-
" groundmail webhooks create --url https://yourapp.com/hooks [--tenants a,b] [--mailboxes mbx_a,mbx_b] --tenants: only these tenants' email events; --mailboxes: only these mailboxes' events",
|
|
5354
|
-
" groundmail webhooks get --id wh_\u2026",
|
|
5355
|
-
" groundmail webhooks update --id wh_\u2026 [--url \u2026] [--event email.sent] [--enabled true|false] [--tenants a,b|all] [--mailboxes mbx_a,mbx_b|all]",
|
|
5356
|
-
" groundmail webhooks delete --id <webhook-id>",
|
|
5357
|
-
" groundmail webhooks deliveries list --webhook <webhook-id> [--status failed] [--event email.delivered] [--email-id msg_\u2026] [--q \u2026] [--test true|false] [--limit 25] [--cursor \u2026]",
|
|
5358
|
-
" groundmail webhooks deliveries get --webhook <webhook-id> --id <delivery-id>",
|
|
5359
|
-
" groundmail webhooks deliveries resend --webhook <webhook-id> --id <delivery-id>",
|
|
5360
|
-
" groundmail validate user@example.com Groundmail validation: verdict, reasons, typo suggestion (exit 2 when invalid)",
|
|
5361
|
-
" groundmail burrowguard sites the forms BurrowGuard protects",
|
|
5362
|
-
' groundmail burrowguard sites create --name "Sign-up form" --hostname app.example.com [--hostname localhost] [--block-free-webmail] [--allow-data-center-ips] [--email-checks-only] [--watch-only] prints the secret once',
|
|
5363
|
-
" groundmail burrowguard sites update <id> --watch-only | --no-watch-only show what would be blocked without blocking anyone, or start blocking",
|
|
5364
|
-
" groundmail burrowguard sites roll-secret <id> --yes the old secret stops working at once",
|
|
5365
|
-
" groundmail burrowguard sites delete <id> --yes",
|
|
5366
|
-
" groundmail burrowguard checks [--site <id>] [--result passed|blocked|would_block|no_token] [--range 24h|7d|30d] [--limit 25]",
|
|
5367
|
-
" groundmail suppressions list [--tenant <tenant>]",
|
|
5368
|
-
" groundmail suppressions add --email user@example.com [--reason \u2026] [--tenant <tenant>]",
|
|
5369
|
-
" groundmail suppressions remove --id <id> | --email user@example.com [--tenant <tenant>]",
|
|
5370
|
-
" groundmail suppressions list --type category_opt_out [--category product|newsletter] [--tenant <tenant> | --include-tenants] one-click category unsubscribes",
|
|
5371
|
-
" groundmail suppressions remove --type category_opt_out --email user@example.com [--category newsletter] [--tenant <tenant>] re-subscribe to a category",
|
|
5372
|
-
" groundmail templates list [--status draft|published]",
|
|
5373
|
-
" groundmail templates starters",
|
|
5374
|
-
" groundmail templates from-starter password-reset",
|
|
5375
|
-
' groundmail templates create --name Welcome --subject "Hi" --html "<p>Hi</p>" [--category transactional|product] [--unsubscribe-enabled] [--folder Onboarding] [--sender billing]',
|
|
5376
|
-
" groundmail templates get --id tpl_\u2026",
|
|
5377
|
-
" groundmail templates update --id tpl_\u2026 [--name \u2026] [--subject \u2026] [--html \u2026] [--text \u2026] [--set-alias \u2026] [--category transactional|product] [--unsubscribe-enabled|--no-unsubscribe-enabled] [--folder \u2026] [--sender billing|none]",
|
|
5378
|
-
" groundmail templates publish --id tpl_\u2026",
|
|
5379
|
-
` groundmail templates push ./emails/welcome.tsx --alias welcome [--subject "Welcome"] [--props '{"name":"Ada"}'] [--publish]`,
|
|
5380
|
-
" groundmail templates versions --id tpl_\u2026",
|
|
5381
|
-
" groundmail templates restore --id tpl_\u2026 --version 2",
|
|
5382
|
-
" groundmail templates test-send --id tpl_\u2026 --to you@domain.com [--from you@domain.com | --sender billing] neither: the template's sender, else your default sender",
|
|
5383
|
-
' groundmail templates duplicate --id tpl_\u2026 [--name "Copy"]',
|
|
5384
|
-
" groundmail templates delete --id tpl_\u2026 refused while an automation step sends it (the error names them)",
|
|
5385
|
-
" groundmail tenants list [--status active|paused] [--limit 25] [--cursor \u2026] your customers, each with its own domains, suppressions and limits",
|
|
5386
|
-
` groundmail tenants create <external-id> [--name \u2026] [--default-from-domain \u2026] [--stream-policy all|transactional_only] [--daily-limit N] [--metadata '{"plan":"pro"}']`,
|
|
5387
|
-
" groundmail tenants get <tenant>",
|
|
5388
|
-
" groundmail tenants update <tenant> [--name \u2026] [--default-from-domain \u2026|none] [--stream-policy \u2026] [--daily-limit N|none] [--metadata JSON|null]",
|
|
5389
|
-
" groundmail tenants pause <tenant> [--reason \u2026] stops the tenant's new sends",
|
|
5390
|
-
" groundmail tenants resume <tenant>",
|
|
5391
|
-
" groundmail tenants delete <tenant> --yes",
|
|
5392
|
-
" groundmail tenants stats <tenant> [--days 7]",
|
|
5393
|
-
" groundmail tenants domains list <tenant>",
|
|
5394
|
-
" groundmail tenants domains add <tenant> example.com only this tenant can then send from it",
|
|
5395
|
-
" groundmail tenants domains remove <tenant> example.com gives the domain back to the account",
|
|
5396
|
-
" groundmail brand get your logo, colors and footer, which templates follow",
|
|
5397
|
-
' groundmail brand set [--logo-url <https url>|none] [--logo-align left|center] [--logo-width 120] [--color #2f6feb] [--company "Acme Inc"|none] [--address "\u2026"|none] [--website acme.com|none] [--settings-file brand.json] --color: buttons, links and the line across the top',
|
|
5398
|
-
" groundmail senders list the names and addresses your email goes out as",
|
|
5399
|
-
' groundmail senders create --name "Acme Billing" --address billing@mail.acme.com [--reply-to billing@acme.com] [--handle billing] [--default] address on a verified domain',
|
|
5400
|
-
" groundmail senders get <id|handle>",
|
|
5401
|
-
" groundmail senders update <id|handle> [--name \u2026] [--address \u2026] [--reply-to \u2026|none] [--handle \u2026] [--default|--no-default]",
|
|
5402
|
-
" groundmail senders delete <id|handle> --yes refused while an automation or template uses it",
|
|
5403
|
-
" groundmail keys list",
|
|
5404
|
-
" groundmail keys create [--name Default] [--scopes emails:send,domains:read] [--allowed-domains example.com] [--test] [--tenant <tenant>] [--mailboxes mbx_a,mbx_b] --test: a gm_test_ key, sends are simulated; --tenant: a key limited to one tenant; --mailboxes: a key for one agent, limited to its mailboxes",
|
|
5405
|
-
" groundmail keys delete --id <api-key-id>",
|
|
5406
|
-
" groundmail migrate sendgrid [--dry-run] [--only templates,suppressions,domains,webhooks] [--region eu] import from SendGrid (Beta)",
|
|
5407
|
-
" groundmail migrate postmark [--dry-run] [--only \u2026] import from Postmark (Beta)",
|
|
5408
|
-
" groundmail migrate mailgun [--dry-run] [--only \u2026] [--region eu] import from Mailgun (Beta)",
|
|
5409
|
-
" groundmail migrate resend [--dry-run] [--only \u2026] import from Resend (Beta; a Full access key)",
|
|
5410
|
-
" groundmail migrate ses --region us-east-1 [--dry-run] [--only \u2026] import one Amazon SES region (Beta)",
|
|
5411
|
-
" groundmail migrate list your recent imports",
|
|
5412
|
-
" groundmail migrate report <id> [--csv] an import report (JSON, or CSV to stdout)",
|
|
5413
|
-
" groundmail integrations twilio status",
|
|
5414
|
-
" groundmail integrations twilio connect --account-sid AC\u2026 --auth-token \u2026 --from +15551234567",
|
|
5415
|
-
" groundmail integrations twilio disconnect"
|
|
5416
|
-
];
|
|
5417
|
-
var FOOTER_LINES = [
|
|
5418
|
-
"Environment:",
|
|
5419
|
-
" GROUNDMAIL_API_KEY=gm_\u2026 optional (CI): wins over the `groundmail login` sign-in",
|
|
5420
|
-
" GROUNDMAIL_NO_KEYCHAIN=1 keep the refresh token in the config file (0600) instead of the OS keychain",
|
|
5421
|
-
" TWILIO_ACCOUNT_SID=AC\u2026 TWILIO_AUTH_TOKEN=\u2026 TWILIO_DEFAULT_FROM=+1\u2026 (integrations twilio connect)",
|
|
5422
|
-
" CLOUDFLARE_API_TOKEN=\u2026 (optional local apply; else uses dashboard Integrations)",
|
|
5423
|
-
" VERCEL_API_TOKEN=\u2026 VERCEL_TEAM_ID=\u2026 (optional local apply; else uses dashboard Integrations)",
|
|
5424
|
-
" GODADDY_PAT=\u2026 or GODADDY_API_KEY=\u2026 GODADDY_API_SECRET=\u2026 (domains apply --provider godaddy)",
|
|
5425
|
-
" SENDGRID_API_KEY / POSTMARK_SERVER_TOKEN (+ POSTMARK_ACCOUNT_TOKEN) / MAILGUN_API_KEY / RESEND_API_KEY (migrate; used once, never stored)",
|
|
5426
|
-
" AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY (+ AWS_SESSION_TOKEN, AWS_REGION) (migrate ses; used once, never stored)",
|
|
5427
|
-
"",
|
|
5428
|
-
"Scopes: full, emails:send, emails:read, domains:read, domains:write, webhooks:write, templates:write,",
|
|
5429
|
-
" suppressions:write, account:read, mailboxes:read, mailboxes:write, mailboxes:send",
|
|
5430
|
-
"",
|
|
5431
|
-
"<tenant>: the tenant's external id (your id for that customer) or its Groundmail id.",
|
|
5432
|
-
"<mailbox>: the mailbox's mbx_ id, its address or your external id.",
|
|
5433
|
-
"",
|
|
5434
|
-
"--watch exit codes: 0 delivered, 1 error, 3 bounced or failed, 4 still on its way at --timeout, 5 test send (never delivered).",
|
|
5435
|
-
"",
|
|
5436
|
-
"Add --help to any command for just its usage. --version prints the CLI version."
|
|
5437
|
-
];
|
|
5438
|
-
var CLI_VERSION = version;
|
|
5439
|
-
var ALIASES = {
|
|
5440
|
-
workspace: { "": "workspaces" },
|
|
5441
|
-
domains: { create: "add", remove: "delete" },
|
|
5442
|
-
events: { trigger: "send" },
|
|
5443
|
-
automations: { add: "create", remove: "delete" },
|
|
5444
|
-
suppressions: { create: "add", delete: "remove" },
|
|
5445
|
-
templates: { add: "create", remove: "delete" },
|
|
5446
|
-
tenants: { add: "create", remove: "delete" },
|
|
5447
|
-
mailboxes: { add: "create", remove: "delete" },
|
|
5448
|
-
senders: { add: "create", remove: "delete" },
|
|
5449
|
-
brand: { update: "set" }
|
|
5450
|
-
};
|
|
5451
|
-
function words(line) {
|
|
5452
|
-
return line.trim().split(/\s+/);
|
|
5453
|
-
}
|
|
5454
|
-
function fullUsage() {
|
|
5455
|
-
return ["Usage:", ...USAGE_LINES, "", ...FOOTER_LINES].join("\n");
|
|
5521
|
+
// src/update-check.ts
|
|
5522
|
+
import { spawn as spawn2 } from "child_process";
|
|
5523
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
5524
|
+
import { join as join2 } from "path";
|
|
5525
|
+
var UPDATE_COMMAND = "npm install -g groundmail-cli@latest";
|
|
5526
|
+
var REFRESH_COMMAND = "__refresh-update-check";
|
|
5527
|
+
var REGISTRY_URL = "https://registry.npmjs.org/groundmail-cli/latest";
|
|
5528
|
+
var CHECK_EVERY_MS = 24 * 60 * 60 * 1e3;
|
|
5529
|
+
var REGISTRY_TIMEOUT_MS = 3e3;
|
|
5530
|
+
function cachePath(env) {
|
|
5531
|
+
return join2(configDir(env), "update-check.json");
|
|
5532
|
+
}
|
|
5533
|
+
function parts(version2) {
|
|
5534
|
+
const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version2.trim());
|
|
5535
|
+
return match ? match.slice(1).map(Number) : null;
|
|
5536
|
+
}
|
|
5537
|
+
function isNewerVersion(latest, current) {
|
|
5538
|
+
const next = parts(latest);
|
|
5539
|
+
const now = parts(current);
|
|
5540
|
+
if (!next || !now) return false;
|
|
5541
|
+
for (let index = 0; index < 3; index += 1) {
|
|
5542
|
+
if (next[index] !== now[index]) return next[index] > now[index];
|
|
5543
|
+
}
|
|
5544
|
+
return false;
|
|
5456
5545
|
}
|
|
5457
|
-
function
|
|
5458
|
-
|
|
5459
|
-
const name = ALIASES[command]?.[""] ?? command;
|
|
5460
|
-
const forCommand = USAGE_LINES.filter((line) => words(line)[1] === name);
|
|
5461
|
-
if (!forCommand.length) return null;
|
|
5462
|
-
const sub = subcommand && !subcommand.startsWith("-") ? ALIASES[name]?.[subcommand] ?? subcommand : void 0;
|
|
5463
|
-
const forSub = sub ? forCommand.filter((line) => words(line)[2] === sub) : [];
|
|
5464
|
-
return ["Usage:", ...forSub.length ? forSub : forCommand].join("\n");
|
|
5546
|
+
function updateNotice(latest, current = CLI_VERSION) {
|
|
5547
|
+
return `Update available: ${current} \u2192 ${latest}. Run: ${UPDATE_COMMAND}`;
|
|
5465
5548
|
}
|
|
5466
|
-
function
|
|
5467
|
-
|
|
5549
|
+
function updateCheckEnabled(env, stderrIsTty) {
|
|
5550
|
+
if (!stderrIsTty) return false;
|
|
5551
|
+
return !env.CI && !env.GROUNDMAIL_NO_UPDATE_NOTIFIER && !env.NO_UPDATE_NOTIFIER;
|
|
5468
5552
|
}
|
|
5469
|
-
function
|
|
5470
|
-
|
|
5553
|
+
function readUpdateCache(env) {
|
|
5554
|
+
try {
|
|
5555
|
+
const value = JSON.parse(readFileSync2(cachePath(env), "utf8"));
|
|
5556
|
+
if (typeof value.checkedAt !== "number") return null;
|
|
5557
|
+
return { checkedAt: value.checkedAt, latest: typeof value.latest === "string" ? value.latest : null };
|
|
5558
|
+
} catch {
|
|
5559
|
+
return null;
|
|
5560
|
+
}
|
|
5471
5561
|
}
|
|
5472
|
-
function
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5562
|
+
async function refreshUpdateCache(env = process.env, fetchImpl = fetch, now = Date.now()) {
|
|
5563
|
+
let latest = readUpdateCache(env)?.latest ?? null;
|
|
5564
|
+
try {
|
|
5565
|
+
const response = await fetchImpl(REGISTRY_URL, { signal: AbortSignal.timeout(REGISTRY_TIMEOUT_MS) });
|
|
5566
|
+
if (response.ok) {
|
|
5567
|
+
const body = await response.json();
|
|
5568
|
+
if (typeof body.version === "string") latest = body.version;
|
|
5569
|
+
}
|
|
5570
|
+
} catch {
|
|
5479
5571
|
}
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5572
|
+
try {
|
|
5573
|
+
mkdirSync2(configDir(env), { recursive: true });
|
|
5574
|
+
writeFileSync2(cachePath(env), JSON.stringify({ checkedAt: now, latest }));
|
|
5575
|
+
} catch {
|
|
5576
|
+
}
|
|
5577
|
+
}
|
|
5578
|
+
function startUpdateCheck(env = process.env, now = Date.now()) {
|
|
5579
|
+
try {
|
|
5580
|
+
if (!updateCheckEnabled(env, Boolean(process.stderr.isTTY))) return;
|
|
5581
|
+
const cache = readUpdateCache(env);
|
|
5582
|
+
if (!cache || now - cache.checkedAt >= CHECK_EVERY_MS) {
|
|
5583
|
+
const script = process.argv[1];
|
|
5584
|
+
if (script) {
|
|
5585
|
+
spawn2(process.execPath, [script, REFRESH_COMMAND], { detached: true, env, stdio: "ignore" }).unref();
|
|
5586
|
+
}
|
|
5587
|
+
}
|
|
5588
|
+
const latest = cache?.latest;
|
|
5589
|
+
if (latest && isNewerVersion(latest, CLI_VERSION)) {
|
|
5590
|
+
process.once("exit", () => process.stderr.write(`
|
|
5591
|
+
${updateNotice(latest)}
|
|
5592
|
+
`));
|
|
5593
|
+
}
|
|
5594
|
+
} catch {
|
|
5483
5595
|
}
|
|
5484
|
-
return null;
|
|
5485
5596
|
}
|
|
5486
5597
|
|
|
5487
5598
|
// src/cli.ts
|
|
@@ -5490,11 +5601,17 @@ function printUsage() {
|
|
|
5490
5601
|
}
|
|
5491
5602
|
async function finish(result) {
|
|
5492
5603
|
if (result.error) console.error(result.error);
|
|
5604
|
+
if (result.exitCode && result.error?.startsWith("Usage:")) console.error(outdatedHint());
|
|
5493
5605
|
process.exit(result.exitCode);
|
|
5494
5606
|
}
|
|
5495
5607
|
async function main() {
|
|
5496
5608
|
const [, , command, subcommand] = process.argv;
|
|
5497
5609
|
const args = process.argv.slice(3);
|
|
5610
|
+
if (command === REFRESH_COMMAND) {
|
|
5611
|
+
await refreshUpdateCache();
|
|
5612
|
+
process.exit(0);
|
|
5613
|
+
}
|
|
5614
|
+
startUpdateCheck();
|
|
5498
5615
|
const meta = resolveMetaRequest(process.argv.slice(2));
|
|
5499
5616
|
if (meta) {
|
|
5500
5617
|
console.log(meta.text);
|
|
@@ -5826,6 +5943,8 @@ async function main() {
|
|
|
5826
5943
|
}
|
|
5827
5944
|
}
|
|
5828
5945
|
printUsage();
|
|
5946
|
+
if (command) console.error(`
|
|
5947
|
+
${unknownCommandMessage(process.argv.slice(2))}`);
|
|
5829
5948
|
process.exit(1);
|
|
5830
5949
|
}
|
|
5831
5950
|
main().catch((error) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groundmail-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.0",
|
|
4
4
|
"description": "Groundmail CLI — send transactional email from the terminal",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"prepublishOnly": "npm run build"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"groundmail": "0.
|
|
41
|
+
"groundmail": "0.47.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"tsup": "^8.5.0",
|