@greatwait/cli 0.1.0-alpha.7 → 0.2.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 +48 -8
- package/dist/catalog.js +71 -0
- package/dist/operate.js +94 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -6,7 +6,6 @@ against a receiver you control.
|
|
|
6
6
|
```sh
|
|
7
7
|
npm install --global @greatwait/cli
|
|
8
8
|
greatwait login --api-url https://api.greatwait.dev
|
|
9
|
-
greatwait journey
|
|
10
9
|
```
|
|
11
10
|
|
|
12
11
|
## What it is
|
|
@@ -44,18 +43,59 @@ directory, readable by you and nobody else. A staging credential is never
|
|
|
44
43
|
presented to production, because the two are separate entries rather than one
|
|
45
44
|
current context.
|
|
46
45
|
|
|
47
|
-
## First
|
|
46
|
+
## First callback
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
Use a public HTTPS receiver you control. Once signed in, schedule directly to
|
|
49
|
+
its URL and wait for the first delivery:
|
|
51
50
|
|
|
52
51
|
```sh
|
|
53
|
-
greatwait
|
|
54
|
-
greatwait schedules create --endpoint ep_… --in 30s --data '{"greeting":"first value"}'
|
|
55
|
-
greatwait occurrences wait sch_… occ_…
|
|
56
|
-
greatwait attempts list sch_… occ_…
|
|
52
|
+
greatwait schedules create --url https://your-receiver.example.com/greatwait --in 30s --data '{"hello":"world"}' --wait
|
|
57
53
|
```
|
|
58
54
|
|
|
55
|
+
GreatWait uses your project's destination at that URL or creates one. The
|
|
56
|
+
answer includes the schedule and occurrence ids; copy them into the read:
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
greatwait attempts list <schedule> <occurrence>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Look for `state: succeeded` and the status your receiver returned. `--wait`
|
|
63
|
+
stops after three minutes by default; a settled failure is also an outcome,
|
|
64
|
+
so inspect the attempt. If it times out, use
|
|
65
|
+
`greatwait occurrences wait <schedule> <occurrence>` and read the attempts
|
|
66
|
+
again. The timeout does not cancel the scheduled call.
|
|
67
|
+
|
|
68
|
+
For a signed receiver, first add your URL in
|
|
69
|
+
[Settings → Endpoints](https://app.greatwait.dev/settings/endpoints), reveal its
|
|
70
|
+
signing secret, and configure the receiver. The direct-URL schedule reuses that
|
|
71
|
+
destination. If the schedule already created it, reveal the secret and configure
|
|
72
|
+
the receiver before its due time; use a longer `--in` if needed. Direct-URL
|
|
73
|
+
scheduling never prints the signing secret. Verify the signature over the exact request bytes
|
|
74
|
+
before parsing, as described in the
|
|
75
|
+
[CLI quickstart](https://docs.greatwait.dev/guides/cli).
|
|
76
|
+
|
|
77
|
+
## Clean up
|
|
78
|
+
|
|
79
|
+
Archive the finished schedule. Read it to find its destination id:
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
greatwait schedules read <schedule>
|
|
83
|
+
greatwait schedules archive <schedule>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
If this walkthrough created the destination and no other schedule uses it,
|
|
87
|
+
disable it:
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
greatwait destinations disable <destination> --yes
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
If you stop before delivery, cancel the schedule with
|
|
94
|
+
`greatwait schedules cancel <schedule> --yes` first. Revoke a walkthrough API
|
|
95
|
+
key in the dashboard, or use `greatwait credentials revoke <credential> --yes`
|
|
96
|
+
while signed in through OAuth. Finish with `greatwait logout` to revoke the CLI
|
|
97
|
+
grant and remove local credentials.
|
|
98
|
+
|
|
59
99
|
## Output and exit codes
|
|
60
100
|
|
|
61
101
|
`--json` is a versioned machine shape, not a serialization of the human text.
|
package/dist/catalog.js
CHANGED
|
@@ -255,6 +255,77 @@ export const MEMBER_COMMANDS = [
|
|
|
255
255
|
},
|
|
256
256
|
];
|
|
257
257
|
export const COMMAND_CATALOG = [
|
|
258
|
+
{
|
|
259
|
+
group: "Planning",
|
|
260
|
+
words: "notifications show",
|
|
261
|
+
summary: "Show this project and environment's delivery notification settings.",
|
|
262
|
+
operations: ["projectnotifications.read"],
|
|
263
|
+
positionals: [
|
|
264
|
+
{
|
|
265
|
+
name: "<project>",
|
|
266
|
+
summary: "The project identifier in this credential's authority.",
|
|
267
|
+
},
|
|
268
|
+
],
|
|
269
|
+
options: [API_URL],
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
group: "Planning",
|
|
273
|
+
words: "notifications update",
|
|
274
|
+
summary: "Change failure email and failed-event webhook preferences at the revision you read.",
|
|
275
|
+
confirms: true,
|
|
276
|
+
operations: ["projectnotifications.update"],
|
|
277
|
+
positionals: [
|
|
278
|
+
{
|
|
279
|
+
name: "<project>",
|
|
280
|
+
summary: "The project identifier in this credential's authority.",
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
options: [
|
|
284
|
+
API_URL,
|
|
285
|
+
{
|
|
286
|
+
flag: "--settings",
|
|
287
|
+
value: "<json|@file|->",
|
|
288
|
+
summary: "JSON expectedRevision and email/statusWebhook settings; null statusWebhook disables it. No signing secret is accepted.",
|
|
289
|
+
},
|
|
290
|
+
IDEMPOTENCY,
|
|
291
|
+
CONFIRM,
|
|
292
|
+
],
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
group: "Planning",
|
|
296
|
+
words: "notifications signing-key",
|
|
297
|
+
summary: "Mint or rotate a failed-event webhook signing secret, shown once.",
|
|
298
|
+
confirms: true,
|
|
299
|
+
operations: ["projectnotifications.rotateSigningKey"],
|
|
300
|
+
positionals: [
|
|
301
|
+
{
|
|
302
|
+
name: "<project>",
|
|
303
|
+
summary: "The project identifier in this credential's authority.",
|
|
304
|
+
},
|
|
305
|
+
],
|
|
306
|
+
options: [
|
|
307
|
+
API_URL,
|
|
308
|
+
{
|
|
309
|
+
flag: "--expected-revision",
|
|
310
|
+
value: "<revision>",
|
|
311
|
+
summary: "The current revision from notifications show; a stale retry is refused.",
|
|
312
|
+
},
|
|
313
|
+
CONFIRM,
|
|
314
|
+
],
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
group: "Planning",
|
|
318
|
+
words: "notifications deliveries",
|
|
319
|
+
summary: "List failed-event webhook deliveries and retries.",
|
|
320
|
+
operations: ["statusdeliveries.list"],
|
|
321
|
+
positionals: [
|
|
322
|
+
{
|
|
323
|
+
name: "<project>",
|
|
324
|
+
summary: "The project identifier in this credential's authority.",
|
|
325
|
+
},
|
|
326
|
+
],
|
|
327
|
+
options: [API_URL, ...PAGE_OPTIONS],
|
|
328
|
+
},
|
|
258
329
|
/* --- Session ---------------------------------------------------------- */
|
|
259
330
|
{
|
|
260
331
|
group: "Session",
|
package/dist/operate.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { writeFile } from "node:fs/promises";
|
|
2
|
-
import { autoPauseAfterFailuresSchema, httpRequestSchema, sanitizedDiagnosticText, diagnosticBodyText, responseBodySentence, requestCaptureSentence, attemptStatusGuidance, endpointHeaderChangesSchema, billingAutoTopUpApplyRequestSchema, billingAutoTopUpPreviewRequestSchema, billingAutoTopUpPreviewSchema, billingCeilingUpdateRequestSchema, billingFundingQuoteRequestSchema, billingFundingQuoteSchema, billingNotificationSettingsUpdateRequestSchema, billingRefundRequestCreateSchema, billingRefundResolutionRequestSchema, projectBillingQuoteSchema, projectBillingQuoteRequestSchema, } from "@greatwait/contracts";
|
|
2
|
+
import { projectNotificationSettingsUpdateRequestSchema, statusSigningKeyRequestSchema, autoPauseAfterFailuresSchema, httpRequestSchema, sanitizedDiagnosticText, diagnosticBodyText, responseBodySentence, requestCaptureSentence, attemptStatusGuidance, endpointHeaderChangesSchema, billingAutoTopUpApplyRequestSchema, billingAutoTopUpPreviewRequestSchema, billingAutoTopUpPreviewSchema, billingCeilingUpdateRequestSchema, billingFundingQuoteRequestSchema, billingFundingQuoteSchema, billingNotificationSettingsUpdateRequestSchema, billingRefundRequestCreateSchema, billingRefundResolutionRequestSchema, projectBillingQuoteSchema, projectBillingQuoteRequestSchema, } from "@greatwait/contracts";
|
|
3
3
|
import { parseDuration } from "./args.js";
|
|
4
4
|
import { confirmChange } from "./confirm.js";
|
|
5
5
|
import { redactSecret } from "./output.js";
|
|
@@ -1621,11 +1621,104 @@ const sinksDelivery = async (runtime, args) => {
|
|
|
1621
1621
|
].join("\n"),
|
|
1622
1622
|
};
|
|
1623
1623
|
};
|
|
1624
|
+
function notificationPreferenceResult(settings) {
|
|
1625
|
+
const webhook = settings.statusWebhook;
|
|
1626
|
+
return {
|
|
1627
|
+
data: settings,
|
|
1628
|
+
human: [
|
|
1629
|
+
`Project ${settings.project}`,
|
|
1630
|
+
`Revision ${String(settings.revision)}`,
|
|
1631
|
+
`Email ${settings.email.enabled ? "enabled" : "disabled"}; ${settings.email.scheduleKinds}`,
|
|
1632
|
+
`Recipients ${settings.email.recipients.kind === "admins" ? "organization admins" : settings.email.recipients.memberIds.join(", ")}`,
|
|
1633
|
+
`Webhook ${webhook === undefined ? "disabled" : webhook.url}`,
|
|
1634
|
+
...(webhook === undefined
|
|
1635
|
+
? []
|
|
1636
|
+
: [
|
|
1637
|
+
`Events ${webhook.events.join(", ")}`,
|
|
1638
|
+
`Signing key ${webhook.signingKey?.id ?? "not provisioned"}`,
|
|
1639
|
+
]),
|
|
1640
|
+
].join("\n"),
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1624
1643
|
/* -------------------------------------------------------------------------- */
|
|
1625
1644
|
/* Dispatch */
|
|
1626
1645
|
/* -------------------------------------------------------------------------- */
|
|
1627
1646
|
/** The operate-and-diagnose commands, keyed by the words that select them. */
|
|
1628
1647
|
export const OPERATE_COMMANDS = {
|
|
1648
|
+
"notifications show": async (runtime, args) => {
|
|
1649
|
+
const [project] = positionals(args, ["<project>"], `${runtime.programName} notifications show <project>`);
|
|
1650
|
+
const active = await session(runtime, args);
|
|
1651
|
+
const settings = parseResponse("projectnotifications.read", await active.client.request("projectnotifications_read", project));
|
|
1652
|
+
return notificationPreferenceResult(settings);
|
|
1653
|
+
},
|
|
1654
|
+
"notifications update": async (runtime, args) => {
|
|
1655
|
+
const [project] = positionals(args, ["<project>"], `${runtime.programName} notifications update <project> --settings <json|@file|->`);
|
|
1656
|
+
const parsed = projectNotificationSettingsUpdateRequestSchema.safeParse(await readJsonOption(runtime, "--settings", args.options["--settings"]));
|
|
1657
|
+
if (!parsed.success)
|
|
1658
|
+
throw new CliError("--settings must contain expectedRevision and valid email/statusWebhook preferences", "usage");
|
|
1659
|
+
const confirmation = await confirmChange(runtime, args, {
|
|
1660
|
+
action: `Update delivery notifications for ${project}.`,
|
|
1661
|
+
effect: [
|
|
1662
|
+
"Changes failure email recipients and/or failed-event webhook destination.",
|
|
1663
|
+
"Failed-event hooks are included and free, including retries.",
|
|
1664
|
+
"The signing secret is never returned by this settings change.",
|
|
1665
|
+
],
|
|
1666
|
+
});
|
|
1667
|
+
const active = await session(runtime, args);
|
|
1668
|
+
const key = idempotencyKey(runtime, args);
|
|
1669
|
+
runtime.stderr(`Retry key ${key}; retain it if the response is lost.\n`);
|
|
1670
|
+
const settings = parseResponse("projectnotifications.update", await active.client.request("projectnotifications_update", project, parsed.data, { "Idempotency-Key": key }));
|
|
1671
|
+
return {
|
|
1672
|
+
...notificationPreferenceResult(settings),
|
|
1673
|
+
data: { settings, confirmation, idempotencyKey: key },
|
|
1674
|
+
};
|
|
1675
|
+
},
|
|
1676
|
+
"notifications signing-key": async (runtime, args) => {
|
|
1677
|
+
const [project] = positionals(args, ["<project>"], `${runtime.programName} notifications signing-key <project> --expected-revision <revision>`);
|
|
1678
|
+
const request = statusSigningKeyRequestSchema.safeParse({
|
|
1679
|
+
expectedRevision: expectedRevision(args),
|
|
1680
|
+
});
|
|
1681
|
+
if (!request.success)
|
|
1682
|
+
throw new CliError("--expected-revision must be a positive whole number", "usage");
|
|
1683
|
+
const confirmation = await confirmChange(runtime, args, {
|
|
1684
|
+
action: `Mint or rotate the status signing key for ${project}.`,
|
|
1685
|
+
effect: [
|
|
1686
|
+
"The previous key is replaced; queued notifications using that old key are suppressed.",
|
|
1687
|
+
"The secret is shown once in this response and cannot be read back. A lost response needs a fresh revision read and a new rotation.",
|
|
1688
|
+
],
|
|
1689
|
+
});
|
|
1690
|
+
const active = await session(runtime, args);
|
|
1691
|
+
const result = parseResponse("projectnotifications.rotateSigningKey", await active.client.request("projectnotifications_rotateSigningKey", project, request.data));
|
|
1692
|
+
return {
|
|
1693
|
+
data: { ...result, confirmation },
|
|
1694
|
+
human: [
|
|
1695
|
+
`Signing key ${result.keyId}`,
|
|
1696
|
+
`Secret ${result.secret}`,
|
|
1697
|
+
`Revision ${String(result.settings.revision)}`,
|
|
1698
|
+
"Save this secret now. It will not be returned again.",
|
|
1699
|
+
].join("\n"),
|
|
1700
|
+
};
|
|
1701
|
+
},
|
|
1702
|
+
"notifications deliveries": async (runtime, args) => {
|
|
1703
|
+
const [project] = positionals(args, ["<project>"], `${runtime.programName} notifications deliveries <project>`);
|
|
1704
|
+
const active = await session(runtime, args);
|
|
1705
|
+
return listing(runtime, args, {
|
|
1706
|
+
columns: ["Event", "State", "Calls", "Delivery"],
|
|
1707
|
+
command: `${runtime.programName} notifications deliveries ${project}`,
|
|
1708
|
+
empty: "No status notifications have been queued.",
|
|
1709
|
+
read: async (request) => {
|
|
1710
|
+
const result = parseResponse("statusdeliveries.list", await active.client.request("statusdeliveries_list", project, request));
|
|
1711
|
+
return {
|
|
1712
|
+
data: result.data,
|
|
1713
|
+
freshness: { kind: "authoritative" },
|
|
1714
|
+
...(result.nextCursor === undefined
|
|
1715
|
+
? {}
|
|
1716
|
+
: { nextCursor: result.nextCursor }),
|
|
1717
|
+
};
|
|
1718
|
+
},
|
|
1719
|
+
row: (row) => [row.event, row.state, String(row.attempts), row.id],
|
|
1720
|
+
});
|
|
1721
|
+
},
|
|
1629
1722
|
"limits show": limitsShow,
|
|
1630
1723
|
"attempts read": attemptsRead,
|
|
1631
1724
|
billing,
|
package/package.json
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
"url": "https://github.com/flowxo/greatwait/issues"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@greatwait/api-client": "0.
|
|
11
|
-
"@greatwait/contracts": "0.
|
|
10
|
+
"@greatwait/api-client": "0.2.0",
|
|
11
|
+
"@greatwait/contracts": "0.2.0"
|
|
12
12
|
},
|
|
13
|
-
"description": "The GreatWait command line: sign in,
|
|
13
|
+
"description": "The GreatWait command line: sign in, schedule a call to a URL, and read its delivery outcome over the public API.",
|
|
14
14
|
"engines": {
|
|
15
15
|
"node": ">=22.13.0"
|
|
16
16
|
},
|
|
@@ -48,5 +48,5 @@
|
|
|
48
48
|
},
|
|
49
49
|
"sideEffects": false,
|
|
50
50
|
"type": "module",
|
|
51
|
-
"version": "0.
|
|
51
|
+
"version": "0.2.0"
|
|
52
52
|
}
|