@greatwait/cli 0.1.0-alpha.3 → 0.1.0-alpha.4

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/dist/cli.d.ts CHANGED
@@ -9,6 +9,15 @@ import { type CliRuntime } from "./runtime.js";
9
9
  * lets a test construct one. `main.ts` is the only file that knows there is a
10
10
  * process at all.
11
11
  */
12
- declare const USAGE = "greatwait \u2014 the Greatwait command line\n\n greatwait login [--api-url <origin>] [--api-key] [--no-browser]\n greatwait logout [--api-url <origin>]\n greatwait whoami\n greatwait doctor\n greatwait context [list | use <origin>]\n greatwait journey\n\n greatwait destinations create --url <https receiver>\n greatwait destinations disable <destination>\n greatwait schedules create --endpoint <destination> (--in 30s | --at <instant>) [--data <json>]\n greatwait schedules read <schedule>\n greatwait occurrences wait <schedule> <occurrence>\n greatwait attempts list <schedule> <occurrence>\n\n --json machine-readable output, versioned\n --help this text\n\nRun `greatwait journey` for the published first-value sequence.";
12
+ /**
13
+ * The overview, rendered from the catalog rather than kept beside it.
14
+ *
15
+ * **It was a string literal, and by the end of this story it would have been
16
+ * wrong.** Thirty-odd commands maintained in a template beside thirty-odd
17
+ * declarations is a list that drifts on the first rename, and the drift is
18
+ * invisible: nothing executes a usage string. Generating it means a command
19
+ * that exists appears here, and one that does not, cannot.
20
+ */
21
+ declare function usage(programName: string): string;
13
22
  export declare function run(runtime: CliRuntime): Promise<number>;
14
- export { USAGE };
23
+ export { usage };
package/dist/cli.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import { GreatwaitClientError } from "@greatwait/api-client";
2
2
  import { parseArguments } from "./args.js";
3
+ import { COMMAND_CATALOG, commandHelp, overview } from "./catalog.js";
3
4
  import { COMMANDS } from "./commands.js";
4
5
  import { UnsafeConfigError } from "./config.js";
5
6
  import { EXIT_CLASSES, classifyStatus } from "./exit.js";
6
7
  import { renderFailure, renderResult } from "./output.js";
8
+ import { remediationFor } from "./remediation.js";
7
9
  import { CliError, InterruptedError } from "./runtime.js";
8
10
  /**
9
11
  * One run of `greatwait`, from argv to an exit code.
@@ -15,26 +17,25 @@ import { CliError, InterruptedError } from "./runtime.js";
15
17
  * lets a test construct one. `main.ts` is the only file that knows there is a
16
18
  * process at all.
17
19
  */
18
- const USAGE = `greatwait — the Greatwait command line
19
-
20
- greatwait login [--api-url <origin>] [--api-key] [--no-browser]
21
- greatwait logout [--api-url <origin>]
22
- greatwait whoami
23
- greatwait doctor
24
- greatwait context [list | use <origin>]
25
- greatwait journey
20
+ /**
21
+ * The overview, rendered from the catalog rather than kept beside it.
22
+ *
23
+ * **It was a string literal, and by the end of this story it would have been
24
+ * wrong.** Thirty-odd commands maintained in a template beside thirty-odd
25
+ * declarations is a list that drifts on the first rename, and the drift is
26
+ * invisible: nothing executes a usage string. Generating it means a command
27
+ * that exists appears here, and one that does not, cannot.
28
+ */
29
+ function usage(programName) {
30
+ return `${programName} — the Greatwait command line
26
31
 
27
- greatwait destinations create --url <https receiver>
28
- greatwait destinations disable <destination>
29
- greatwait schedules create --endpoint <destination> (--in 30s | --at <instant>) [--data <json>]
30
- greatwait schedules read <schedule>
31
- greatwait occurrences wait <schedule> <occurrence>
32
- greatwait attempts list <schedule> <occurrence>
32
+ ${overview()}
33
33
 
34
34
  --json machine-readable output, versioned
35
- --help this text
35
+ --help this text, or the help for a command that precedes it
36
36
 
37
- Run \`greatwait journey\` for the published first-value sequence.`;
37
+ Run \`${programName} journey\` for the published first-value sequence.`;
38
+ }
38
39
  /**
39
40
  * Which command a set of leading words names.
40
41
  *
@@ -71,7 +72,7 @@ function problemOf(error) {
71
72
  * them, and a client that replaced those with its own wording would be
72
73
  * maintaining a second copy of every refusal the service can produce.
73
74
  */
74
- function classify(error) {
75
+ function classify(error, programName) {
75
76
  if (error instanceof InterruptedError) {
76
77
  return {
77
78
  exit: "interrupted",
@@ -103,13 +104,13 @@ function classify(error) {
103
104
  human: problem === undefined
104
105
  ? `The service answered ${String(error.status)}.`
105
106
  : `${problem.title} (${problem.code})`,
106
- nextMove: problem?.code === "authentication_required"
107
- ? "Sign in: greatwait login"
108
- : problem?.code === "scope_missing"
109
- ? "This credential does not carry the permission this call needs. Create one that does, rather than widening this one."
110
- : error.retryable
111
- ? "The identical request is safe to repeat."
112
- : undefined,
107
+ // **Every published code, not the two that were spelled out here.** This
108
+ // was a two-branch conditional naming `authentication_required` and
109
+ // `scope_missing`, so a `credential_revoked` — the same 401, and the one
110
+ // that means sign in again — arrived with the right exit number and no
111
+ // idea what to do about it. The record it now reads is exhaustive by
112
+ // type, so the next code the contract publishes cannot arrive silent.
113
+ nextMove: remediationFor(problem, error.retryable, programName),
113
114
  problem,
114
115
  };
115
116
  }
@@ -125,17 +126,27 @@ function classify(error) {
125
126
  }
126
127
  export async function run(runtime) {
127
128
  let json = false;
128
- let command = "greatwait";
129
+ let command = runtime.programName;
129
130
  try {
130
- const args = parseArguments(runtime.argv, COMMAND_WORDS);
131
+ const args = parseArguments(runtime.argv, COMMAND_WORDS, runtime.programName);
131
132
  json = args.booleans.has("--json");
133
+ const selected = select(args.command);
132
134
  if (args.booleans.has("--help") || args.command.length === 0) {
133
- runtime.stdout(`${USAGE}\n`);
135
+ // **The help for the command that was named, where one was.** `greatwait
136
+ // schedules replace --help` printing the whole overview is the answer to
137
+ // a question nobody asked: somebody who has typed two words wants to know
138
+ // what those two words take, and the alternative is reading a thirty-line
139
+ // list to find the same line they started from.
140
+ const declaration = selected === undefined
141
+ ? undefined
142
+ : COMMAND_CATALOG.find((candidate) => candidate.words === selected.key);
143
+ runtime.stdout(`${declaration === undefined
144
+ ? usage(runtime.programName)
145
+ : commandHelp(declaration, runtime.programName)}\n`);
134
146
  return EXIT_CLASSES.ok;
135
147
  }
136
- const selected = select(args.command);
137
148
  if (selected === undefined) {
138
- throw new CliError(`unknown command ${args.command.join(" ")}`, "usage", "Run greatwait --help for the list.");
149
+ throw new CliError(`unknown command ${args.command.join(" ")}`, "usage", `Run ${runtime.programName} --help for the list.`);
139
150
  }
140
151
  command = selected.key;
141
152
  const result = await COMMANDS[selected.key](runtime, { ...args, command: selected.rest });
@@ -156,7 +167,7 @@ export async function run(runtime) {
156
167
  return EXIT_CLASSES[result.exit ?? "ok"];
157
168
  }
158
169
  catch (error) {
159
- const classified = classify(error);
170
+ const classified = classify(error, runtime.programName);
160
171
  // **A `--json` failure goes to standard output, and a plain one to standard
161
172
  // error.** Found by the staging browser suite, which could not parse a
162
173
  // refusal because Node had written a deprecation warning to the same stream
@@ -188,4 +199,4 @@ export async function run(runtime) {
188
199
  return EXIT_CLASSES[classified.exit];
189
200
  }
190
201
  }
191
- export { USAGE };
202
+ export { usage };
@@ -2,6 +2,16 @@ import { type ParsedArguments } from "./args.js";
2
2
  import { type StoredCredential } from "./config.js";
3
3
  import { type CommandResult } from "./output.js";
4
4
  import { type CliRuntime } from "./runtime.js";
5
+ /**
6
+ * The commands, each bounded and each useful on its own.
7
+ *
8
+ * **There is no `greatwait demo`.** The project specification excludes one by
9
+ * name, and the reason is that a single command whose success proves everything
10
+ * teaches nothing: somebody who ran it has watched a callback arrive and still
11
+ * cannot make a second one. Every command here is a step somebody would take
12
+ * again on their own work, and together they are the published first-value
13
+ * journey — which `greatwait journey` renders rather than restates.
14
+ */
5
15
  type Command = (runtime: CliRuntime, args: ParsedArguments) => Promise<CommandResult>;
6
16
  interface CredentialContextView {
7
17
  readonly environmentKind: string;
@@ -9,6 +19,28 @@ interface CredentialContextView {
9
19
  readonly project: string;
10
20
  readonly scopes: readonly string[];
11
21
  }
12
- /** Every command, keyed by the words that select it. */
22
+ /**
23
+ * Every implementation, keyed by the words that select it.
24
+ *
25
+ * Not the dispatch table: that is built from the catalog below, so a command
26
+ * cannot be documented without existing and cannot exist without being
27
+ * documented. This half is the functions, and `packages/cli/src/catalog.ts` is
28
+ * what says they are the right set.
29
+ */
30
+ declare const IMPLEMENTATIONS: Readonly<Record<string, Command>>;
31
+ /**
32
+ * The dispatch table, assembled from the catalog.
33
+ *
34
+ * **A declaration with no function raises here, at import.** The alternative
35
+ * was a test that compared two lists, which is a check the published reference
36
+ * page can still contradict: the page is generated from the catalog, so a
37
+ * command documented and never implemented would ship as a promise the
38
+ * executable does not keep. Raising means the binary does not start.
39
+ *
40
+ * The other direction is a test rather than a raise — an implementation nobody
41
+ * declared is unreachable rather than dangerous, and `commands.test.ts` names
42
+ * it.
43
+ */
13
44
  export declare const COMMANDS: Readonly<Record<string, Command>>;
45
+ export { IMPLEMENTATIONS };
14
46
  export type { Command, CredentialContextView, StoredCredential };