@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 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 value
46
+ ## First callback
48
47
 
49
- `greatwait journey` prints the published first-value sequence — the same nine
50
- steps the website and the quickstarts walk — with the command for each one.
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 destinations create --url https://your-receiver.example.com/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.1.0-alpha.7",
11
- "@greatwait/contracts": "0.1.0-alpha.7"
10
+ "@greatwait/api-client": "0.2.0",
11
+ "@greatwait/contracts": "0.2.0"
12
12
  },
13
- "description": "The GreatWait command line: sign in, create a destination and a one-shot schedule, and wait for the callback, over the public API.",
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.1.0-alpha.7"
51
+ "version": "0.2.0"
52
52
  }