@roarkanalytics/cli 0.1.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.
Files changed (99) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +225 -0
  3. package/bin.d.ts +2 -0
  4. package/bin.js +14 -0
  5. package/commands.d.ts +5 -0
  6. package/commands.js +3553 -0
  7. package/completions.d.ts +1 -0
  8. package/completions.js +13 -0
  9. package/index.d.ts +6 -0
  10. package/index.js +26 -0
  11. package/man/roark-agent-create.1 +99 -0
  12. package/man/roark-agent-endpoint-create.1 +108 -0
  13. package/man/roark-agent-endpoint-get.1 +93 -0
  14. package/man/roark-agent-endpoint-list.1 +102 -0
  15. package/man/roark-agent-endpoint-update.1 +103 -0
  16. package/man/roark-agent-get.1 +93 -0
  17. package/man/roark-agent-list.1 +99 -0
  18. package/man/roark-agent-update.1 +100 -0
  19. package/man/roark-call-create.1 +150 -0
  20. package/man/roark-call-get.1 +93 -0
  21. package/man/roark-call-list.1 +111 -0
  22. package/man/roark-call-metric-list.1 +100 -0
  23. package/man/roark-call-sentiment-run-list.1 +93 -0
  24. package/man/roark-call-transcript-get.1 +97 -0
  25. package/man/roark-config-apply.1 +96 -0
  26. package/man/roark-config-diff.1 +96 -0
  27. package/man/roark-customer-flow-create.1 +97 -0
  28. package/man/roark-customer-flow-delete.1 +93 -0
  29. package/man/roark-customer-flow-edge-case-create.1 +112 -0
  30. package/man/roark-customer-flow-edge-case-delete.1 +96 -0
  31. package/man/roark-customer-flow-edge-case-promote.1 +96 -0
  32. package/man/roark-customer-flow-edge-case-update.1 +118 -0
  33. package/man/roark-customer-flow-get.1 +93 -0
  34. package/man/roark-customer-flow-graph-replace.1 +100 -0
  35. package/man/roark-customer-flow-happy-path-update.1 +115 -0
  36. package/man/roark-customer-flow-list.1 +105 -0
  37. package/man/roark-customer-flow-update.1 +109 -0
  38. package/man/roark-health.1 +89 -0
  39. package/man/roark-http-request-definition-create.1 +108 -0
  40. package/man/roark-http-request-definition-get.1 +93 -0
  41. package/man/roark-http-request-definition-list.1 +96 -0
  42. package/man/roark-http-request-definition-update.1 +109 -0
  43. package/man/roark-metric-collection-job-create.1 +99 -0
  44. package/man/roark-metric-collection-job-get.1 +93 -0
  45. package/man/roark-metric-collection-job-list.1 +99 -0
  46. package/man/roark-metric-definition-create.1 +100 -0
  47. package/man/roark-metric-definition-list.1 +89 -0
  48. package/man/roark-metric-policy-create.1 +105 -0
  49. package/man/roark-metric-policy-delete.1 +93 -0
  50. package/man/roark-metric-policy-get.1 +93 -0
  51. package/man/roark-metric-policy-list.1 +99 -0
  52. package/man/roark-metric-policy-update.1 +106 -0
  53. package/man/roark-simulation-environment-get.1 +93 -0
  54. package/man/roark-simulation-environment-list.1 +96 -0
  55. package/man/roark-simulation-job-get.1 +93 -0
  56. package/man/roark-simulation-job-lookup.1 +96 -0
  57. package/man/roark-simulation-persona-create.1 +156 -0
  58. package/man/roark-simulation-persona-get.1 +93 -0
  59. package/man/roark-simulation-persona-list.1 +99 -0
  60. package/man/roark-simulation-persona-update.1 +160 -0
  61. package/man/roark-simulation-plan-create.1 +138 -0
  62. package/man/roark-simulation-plan-delete.1 +93 -0
  63. package/man/roark-simulation-plan-get.1 +93 -0
  64. package/man/roark-simulation-plan-job-get.1 +93 -0
  65. package/man/roark-simulation-plan-job-list.1 +108 -0
  66. package/man/roark-simulation-plan-job-start.1 +97 -0
  67. package/man/roark-simulation-plan-list.1 +102 -0
  68. package/man/roark-simulation-plan-update.1 +142 -0
  69. package/man/roark-simulation-run.1 +97 -0
  70. package/man/roark-webhook-create.1 +102 -0
  71. package/man/roark-webhook-delete.1 +93 -0
  72. package/man/roark-webhook-get.1 +93 -0
  73. package/man/roark-webhook-list.1 +96 -0
  74. package/man/roark.1 +287 -0
  75. package/package.json +102 -0
  76. package/runtime/commands/api.d.ts +26 -0
  77. package/runtime/commands/api.js +85 -0
  78. package/runtime/commands/auth.d.ts +9 -0
  79. package/runtime/commands/auth.js +111 -0
  80. package/runtime/commands/completion.d.ts +9 -0
  81. package/runtime/commands/completion.js +42 -0
  82. package/runtime/commands/config.d.ts +15 -0
  83. package/runtime/commands/config.js +125 -0
  84. package/runtime/config.d.ts +65 -0
  85. package/runtime/config.js +163 -0
  86. package/runtime/confirm.d.ts +16 -0
  87. package/runtime/confirm.js +34 -0
  88. package/runtime/errors.d.ts +45 -0
  89. package/runtime/errors.js +91 -0
  90. package/runtime/input.d.ts +52 -0
  91. package/runtime/input.js +211 -0
  92. package/runtime/output.d.ts +41 -0
  93. package/runtime/output.js +90 -0
  94. package/runtime/program.d.ts +45 -0
  95. package/runtime/program.js +305 -0
  96. package/runtime/types.d.ts +53 -0
  97. package/runtime/types.js +10 -0
  98. package/version.d.ts +1 -0
  99. package/version.js +8 -0
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ /**
3
+ * Error rendering and exit codes.
4
+ *
5
+ * Exit codes are the only thing a script can branch on, so they distinguish the
6
+ * cases a caller would actually handle differently: a bad invocation, a missing
7
+ * credential, and a missing resource are not all "1".
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.reportError = exports.exitCodeFor = exports.AuthRequiredError = exports.UsageError = exports.EXIT = void 0;
11
+ const sdk_1 = require("@roarkanalytics/sdk");
12
+ const output_1 = require("./output");
13
+ exports.EXIT = {
14
+ ok: 0,
15
+ /** The request was made and the API rejected it. */
16
+ apiError: 1,
17
+ /** The command line was wrong: unknown flag, missing argument, bad value. */
18
+ usage: 2,
19
+ /** No credential, or the credential was refused. */
20
+ auth: 3,
21
+ /** The addressed resource does not exist. */
22
+ notFound: 4,
23
+ /** The request never completed: DNS, TLS, timeout, or an aborted run. */
24
+ connection: 5,
25
+ };
26
+ /** Thrown for anything wrong with the command line itself. */
27
+ class UsageError extends Error {
28
+ name = 'UsageError';
29
+ }
30
+ exports.UsageError = UsageError;
31
+ /**
32
+ * No credential was found at all. Distinct from a rejected one, but a script
33
+ * handles both the same way, so both exit 3.
34
+ */
35
+ class AuthRequiredError extends Error {
36
+ name = 'AuthRequiredError';
37
+ }
38
+ exports.AuthRequiredError = AuthRequiredError;
39
+ const exitCodeFor = (error) => {
40
+ if (error instanceof AuthRequiredError)
41
+ return exports.EXIT.auth;
42
+ if (error instanceof UsageError)
43
+ return exports.EXIT.usage;
44
+ if (error instanceof sdk_1.AuthenticationError || error instanceof sdk_1.PermissionDeniedError) {
45
+ return exports.EXIT.auth;
46
+ }
47
+ if (error instanceof sdk_1.NotFoundError)
48
+ return exports.EXIT.notFound;
49
+ if (error instanceof sdk_1.APIConnectionError ||
50
+ error instanceof sdk_1.APIConnectionTimeoutError ||
51
+ error instanceof sdk_1.APIUserAbortError) {
52
+ return exports.EXIT.connection;
53
+ }
54
+ if (error instanceof sdk_1.APIError)
55
+ return exports.EXIT.apiError;
56
+ return exports.EXIT.apiError;
57
+ };
58
+ exports.exitCodeFor = exitCodeFor;
59
+ /**
60
+ * Errors go to stderr so that `roark ... > out.json` leaves `out.json` holding
61
+ * only real output, and a failed run leaves it empty rather than half-written.
62
+ */
63
+ const reportError = (error, options) => {
64
+ const stderr = process.stderr;
65
+ const label = (0, output_1.paint)('error:', 'red', options.color);
66
+ if (error instanceof UsageError || error instanceof AuthRequiredError) {
67
+ (0, output_1.write)(`${label} ${error.message}`, stderr);
68
+ return;
69
+ }
70
+ if (error instanceof sdk_1.APIError) {
71
+ const status = error.status === undefined ? 'request failed' : `HTTP ${error.status}`;
72
+ const body = error.error;
73
+ const hasBody = body !== undefined && body !== null;
74
+ // The SDK's `message` already embeds the raw response body, so printing both
75
+ // shows the same JSON twice: once unformatted, once formatted.
76
+ (0, output_1.write)(hasBody ? `${label} ${status}` : `${label} ${status}: ${error.message}`, stderr);
77
+ if (hasBody) {
78
+ (0, output_1.write)((0, output_1.render)(body, { format: options.format, color: options.color, stream: stderr }), stderr);
79
+ }
80
+ if (error instanceof sdk_1.AuthenticationError) {
81
+ (0, output_1.write)((0, output_1.paint)(`\nRun \`${options.binaryName} auth login\` or set ROARK_API_BEARER_TOKEN.`, 'dim', options.color), stderr);
82
+ }
83
+ return;
84
+ }
85
+ if (error instanceof Error) {
86
+ (0, output_1.write)(`${label} ${error.message}`, stderr);
87
+ return;
88
+ }
89
+ (0, output_1.write)(`${label} ${String(error)}`, stderr);
90
+ };
91
+ exports.reportError = reportError;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Turning a command line into SDK arguments.
3
+ *
4
+ * Three inputs can supply the request body, in increasing precedence: piped
5
+ * stdin, `--data`, then individual flags. That ordering is what makes
6
+ * `cat call.json | roark call create --agent-id other` a useful edit of a saved
7
+ * payload rather than an ambiguous merge.
8
+ */
9
+ import type { CliCommand, CliFlag } from './types';
10
+ export type Params = Record<string, unknown>;
11
+ /** `--data @payload.json` reads a file; anything else is the JSON itself. */
12
+ export declare const readData: (value: string) => unknown;
13
+ export declare const readStdin: () => string;
14
+ /**
15
+ * Only consume stdin when there is actually something to read from.
16
+ *
17
+ * "Not a TTY" is not the same question. A descriptor can be closed, or opened
18
+ * non-blocking, or be `/dev/null`, and none of those are a TTY: reading them
19
+ * either fails outright or returns nothing. Under a CI runner it is the first,
20
+ * so `roark agent update <id> --name x` - a command with no pipe in sight -
21
+ * failed with "Could not read stdin".
22
+ *
23
+ * A pipe (`echo … | roark`) is a FIFO and a redirect (`roark < body.json`) is a
24
+ * regular file. Those two are what this is for, so those two are what it asks
25
+ * about, and anything it cannot stat is not a pipe.
26
+ */
27
+ export declare const stdinIsPiped: (fd?: number) => boolean;
28
+ export declare const coerceFlag: (raw: string | string[] | boolean, flag: CliFlag) => unknown;
29
+ /** Writes `['metadata','source']` into `{ metadata: { source } }`. */
30
+ export declare const setPath: (target: Params, path: string[], value: unknown) => void;
31
+ export interface BuildArgsInput {
32
+ command: CliCommand;
33
+ /** Positional values in the order the command declares them. */
34
+ positionals: string[];
35
+ /** Commander's parsed options, keyed by the flag's camelCase option name. */
36
+ options: Record<string, unknown>;
37
+ /** Already-parsed `--data` payload, if any. */
38
+ data?: unknown;
39
+ /** Already-read stdin payload, if any. */
40
+ stdin?: unknown;
41
+ }
42
+ /** Commander stores `--outbound-dial-type` under `outboundDialType`. */
43
+ export declare const optionKey: (flagName: string) => string;
44
+ /**
45
+ * Assembles the argument list for the SDK method.
46
+ *
47
+ * The SDK takes the *last* path parameter positionally and folds any earlier
48
+ * ones into the params object under their wire names, so the CLI's uniform
49
+ * "every path param is a positional" is undone here rather than pushed onto the
50
+ * user. See `customerFlowEdgeCase.update(edgeCaseID, { flowId, ...body })`.
51
+ */
52
+ export declare const buildArgs: (input: BuildArgsInput) => unknown[];
@@ -0,0 +1,211 @@
1
+ "use strict";
2
+ /**
3
+ * Turning a command line into SDK arguments.
4
+ *
5
+ * Three inputs can supply the request body, in increasing precedence: piped
6
+ * stdin, `--data`, then individual flags. That ordering is what makes
7
+ * `cat call.json | roark call create --agent-id other` a useful edit of a saved
8
+ * payload rather than an ambiguous merge.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.buildArgs = exports.optionKey = exports.setPath = exports.coerceFlag = exports.stdinIsPiped = exports.readStdin = exports.readData = void 0;
12
+ const node_fs_1 = require("node:fs");
13
+ const errors_1 = require("./errors");
14
+ const parseJson = (raw, source) => {
15
+ try {
16
+ return JSON.parse(raw);
17
+ }
18
+ catch (cause) {
19
+ throw new errors_1.UsageError(`${source} is not valid JSON: ${cause.message}`);
20
+ }
21
+ };
22
+ /** `--data @payload.json` reads a file; anything else is the JSON itself. */
23
+ const readData = (value) => {
24
+ if (value.startsWith('@')) {
25
+ const path = value.slice(1);
26
+ if (path === '-')
27
+ return parseJson((0, exports.readStdin)(), 'stdin');
28
+ try {
29
+ return parseJson((0, node_fs_1.readFileSync)(path, 'utf8'), path);
30
+ }
31
+ catch (cause) {
32
+ if (cause instanceof errors_1.UsageError)
33
+ throw cause;
34
+ throw new errors_1.UsageError(`Could not read ${path}: ${cause.message}`);
35
+ }
36
+ }
37
+ return parseJson(value, '--data');
38
+ };
39
+ exports.readData = readData;
40
+ const readStdin = () => {
41
+ try {
42
+ return (0, node_fs_1.readFileSync)(0, 'utf8');
43
+ }
44
+ catch (cause) {
45
+ throw new errors_1.UsageError(`Could not read stdin: ${cause.message}`);
46
+ }
47
+ };
48
+ exports.readStdin = readStdin;
49
+ /**
50
+ * Only consume stdin when there is actually something to read from.
51
+ *
52
+ * "Not a TTY" is not the same question. A descriptor can be closed, or opened
53
+ * non-blocking, or be `/dev/null`, and none of those are a TTY: reading them
54
+ * either fails outright or returns nothing. Under a CI runner it is the first,
55
+ * so `roark agent update <id> --name x` - a command with no pipe in sight -
56
+ * failed with "Could not read stdin".
57
+ *
58
+ * A pipe (`echo … | roark`) is a FIFO and a redirect (`roark < body.json`) is a
59
+ * regular file. Those two are what this is for, so those two are what it asks
60
+ * about, and anything it cannot stat is not a pipe.
61
+ */
62
+ const stdinIsPiped = (fd = 0) => {
63
+ if (fd === 0 && process.stdin.isTTY === true)
64
+ return false;
65
+ try {
66
+ const stats = (0, node_fs_1.fstatSync)(fd);
67
+ return stats.isFIFO() || stats.isFile();
68
+ }
69
+ catch {
70
+ return false;
71
+ }
72
+ };
73
+ exports.stdinIsPiped = stdinIsPiped;
74
+ const coerceScalar = (raw, kind, flag) => {
75
+ switch (kind) {
76
+ case 'integer':
77
+ case 'number': {
78
+ const parsed = Number(raw);
79
+ if (!Number.isFinite(parsed))
80
+ throw new errors_1.UsageError(`--${flag} expects a number, got '${raw}'`);
81
+ if (kind === 'integer' && !Number.isInteger(parsed)) {
82
+ throw new errors_1.UsageError(`--${flag} expects a whole number, got '${raw}'`);
83
+ }
84
+ return parsed;
85
+ }
86
+ case 'boolean':
87
+ if (raw === 'true' || raw === '')
88
+ return true;
89
+ if (raw === 'false')
90
+ return false;
91
+ throw new errors_1.UsageError(`--${flag} expects true or false, got '${raw}'`);
92
+ case 'object':
93
+ return parseJson(raw, `--${flag}`);
94
+ case 'array':
95
+ // A single `--tag a,b` is a convenience over repeating the flag; JSON is
96
+ // there for arrays of anything that is not a bare string.
97
+ return raw.trimStart().startsWith('[') ? parseJson(raw, `--${flag}`) : raw.split(',');
98
+ default:
99
+ return raw;
100
+ }
101
+ };
102
+ const coerceFlag = (raw, flag) => {
103
+ if (typeof raw === 'boolean')
104
+ return raw;
105
+ if (Array.isArray(raw)) {
106
+ const values = raw.map((entry) => coerceScalar(entry, 'string', flag.name));
107
+ assertEnum(values, flag);
108
+ return values;
109
+ }
110
+ const value = coerceScalar(raw, flag.valueKind, flag.name);
111
+ assertEnum(Array.isArray(value) ? value : [value], flag);
112
+ return value;
113
+ };
114
+ exports.coerceFlag = coerceFlag;
115
+ /**
116
+ * Checked here rather than left to the API so a typo costs no round trip and the
117
+ * message can list what was actually allowed.
118
+ */
119
+ const assertEnum = (values, flag) => {
120
+ if (!flag.enumValues || flag.enumValues.length === 0)
121
+ return;
122
+ for (const value of values) {
123
+ if (typeof value !== 'string')
124
+ continue;
125
+ if (!flag.enumValues.includes(value)) {
126
+ throw new errors_1.UsageError(`--${flag.name} expects one of ${flag.enumValues.join(', ')}, got '${value}'`);
127
+ }
128
+ }
129
+ };
130
+ /** Writes `['metadata','source']` into `{ metadata: { source } }`. */
131
+ const setPath = (target, path, value) => {
132
+ let cursor = target;
133
+ for (let index = 0; index < path.length - 1; index += 1) {
134
+ const key = path[index];
135
+ const existing = cursor[key];
136
+ if (typeof existing !== 'object' || existing === null || Array.isArray(existing)) {
137
+ cursor[key] = {};
138
+ }
139
+ cursor = cursor[key];
140
+ }
141
+ cursor[path.at(-1)] = value;
142
+ };
143
+ exports.setPath = setPath;
144
+ /** Commander stores `--outbound-dial-type` under `outboundDialType`. */
145
+ const optionKey = (flagName) => flagName
146
+ .split(/[.-]/)
147
+ .map((part, index) => (index === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)))
148
+ .join('');
149
+ exports.optionKey = optionKey;
150
+ const asObject = (value, source) => {
151
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
152
+ throw new errors_1.UsageError(`${source} must be a JSON object`);
153
+ }
154
+ return { ...value };
155
+ };
156
+ /**
157
+ * Assembles the argument list for the SDK method.
158
+ *
159
+ * The SDK takes the *last* path parameter positionally and folds any earlier
160
+ * ones into the params object under their wire names, so the CLI's uniform
161
+ * "every path param is a positional" is undone here rather than pushed onto the
162
+ * user. See `customerFlowEdgeCase.update(edgeCaseID, { flowId, ...body })`.
163
+ */
164
+ const buildArgs = (input) => {
165
+ const { command, positionals, options } = input;
166
+ if (positionals.length !== command.positionals.length) {
167
+ const expected = command.positionals.map((entry) => `<${entry.name}>`).join(' ');
168
+ throw new errors_1.UsageError(`expected ${command.positionals.length} argument(s): ${expected}`);
169
+ }
170
+ const params = {
171
+ ...(input.stdin === undefined ? {} : asObject(input.stdin, 'stdin')),
172
+ ...(input.data === undefined ? {} : asObject(input.data, '--data')),
173
+ };
174
+ // Path params other than the last one travel inside the params object.
175
+ command.positionals.slice(0, -1).forEach((entry, index) => {
176
+ params[entry.paramKey] = positionals[index];
177
+ });
178
+ for (const flag of command.flags) {
179
+ const raw = options[(0, exports.optionKey)(flag.name)];
180
+ if (raw === undefined)
181
+ continue;
182
+ (0, exports.setPath)(params, flag.path, (0, exports.coerceFlag)(raw, flag));
183
+ }
184
+ for (const flag of command.flags) {
185
+ if (!flag.required)
186
+ continue;
187
+ if (readPath(params, flag.path) === undefined) {
188
+ throw new errors_1.UsageError(`--${flag.name} is required`);
189
+ }
190
+ }
191
+ const args = [];
192
+ if (command.positionals.length > 0)
193
+ args.push(positionals.at(-1));
194
+ const hasParams = Object.keys(params).length > 0;
195
+ if (command.hasParams && (hasParams || !command.paramsAllOptional))
196
+ args.push(params);
197
+ else if (hasParams && !command.hasParams) {
198
+ throw new errors_1.UsageError(`${command.commandPath.join(' ')} takes no parameters`);
199
+ }
200
+ return args;
201
+ };
202
+ exports.buildArgs = buildArgs;
203
+ const readPath = (target, path) => {
204
+ let cursor = target;
205
+ for (const key of path) {
206
+ if (typeof cursor !== 'object' || cursor === null)
207
+ return undefined;
208
+ cursor = cursor[key];
209
+ }
210
+ return cursor;
211
+ };
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Output formatting and colour.
3
+ *
4
+ * The rule throughout is clig.dev's: a TTY gets something a human reads, a pipe
5
+ * gets something a program parses. Nothing here decides that per command, so
6
+ * `roark call list | jq` and `roark call list` differ only in presentation.
7
+ */
8
+ export type OutputFormat = 'auto' | 'json' | 'jsonl' | 'plain';
9
+ export declare const OUTPUT_FORMATS: OutputFormat[];
10
+ export declare const isTty: (stream?: NodeJS.WriteStream) => boolean;
11
+ /**
12
+ * Colour is off unless we are certain it will be read by a terminal. `NO_COLOR`
13
+ * is honoured with any value, per the no-color.org convention.
14
+ */
15
+ export declare const supportsColor: (stream?: NodeJS.WriteStream) => boolean;
16
+ declare const CODES: {
17
+ readonly reset: "\u001B[0m";
18
+ readonly dim: "\u001B[2m";
19
+ readonly bold: "\u001B[1m";
20
+ readonly red: "\u001B[31m";
21
+ readonly green: "\u001B[32m";
22
+ readonly yellow: "\u001B[33m";
23
+ readonly blue: "\u001B[34m";
24
+ readonly cyan: "\u001B[36m";
25
+ };
26
+ export type Style = keyof Omit<typeof CODES, 'reset'>;
27
+ export declare const paint: (text: string, style: Style, enabled: boolean) => string;
28
+ /**
29
+ * `auto` is JSON either way. What the TTY changes is presentation - indentation
30
+ * and colour - not the format, so a command's output stays parseable no matter
31
+ * where it runs and a script never has to pass a flag to get stable output.
32
+ */
33
+ export declare const resolveFormat: (requested: OutputFormat) => Exclude<OutputFormat, "auto">;
34
+ export interface RenderOptions {
35
+ format: OutputFormat;
36
+ color: boolean;
37
+ stream?: NodeJS.WriteStream;
38
+ }
39
+ export declare const render: (value: unknown, options: RenderOptions) => string;
40
+ export declare const write: (text: string, stream?: NodeJS.WriteStream) => void;
41
+ export {};
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ /**
3
+ * Output formatting and colour.
4
+ *
5
+ * The rule throughout is clig.dev's: a TTY gets something a human reads, a pipe
6
+ * gets something a program parses. Nothing here decides that per command, so
7
+ * `roark call list | jq` and `roark call list` differ only in presentation.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.write = exports.render = exports.resolveFormat = exports.paint = exports.supportsColor = exports.isTty = exports.OUTPUT_FORMATS = void 0;
11
+ exports.OUTPUT_FORMATS = ['auto', 'json', 'jsonl', 'plain'];
12
+ const isTty = (stream = process.stdout) => stream.isTTY === true;
13
+ exports.isTty = isTty;
14
+ /**
15
+ * Colour is off unless we are certain it will be read by a terminal. `NO_COLOR`
16
+ * is honoured with any value, per the no-color.org convention.
17
+ */
18
+ const supportsColor = (stream = process.stdout) => {
19
+ if (process.env['NO_COLOR'] !== undefined)
20
+ return false;
21
+ if (process.env['TERM'] === 'dumb')
22
+ return false;
23
+ return (0, exports.isTty)(stream);
24
+ };
25
+ exports.supportsColor = supportsColor;
26
+ const CODES = {
27
+ reset: '\u001b[0m',
28
+ dim: '\u001b[2m',
29
+ bold: '\u001b[1m',
30
+ red: '\u001b[31m',
31
+ green: '\u001b[32m',
32
+ yellow: '\u001b[33m',
33
+ blue: '\u001b[34m',
34
+ cyan: '\u001b[36m',
35
+ };
36
+ const paint = (text, style, enabled) => enabled ? `${CODES[style]}${text}${CODES.reset}` : text;
37
+ exports.paint = paint;
38
+ /** JSON with the structure picked out by colour: keys, strings, numbers, literals. */
39
+ const colorizeJson = (json) => json.replace(/("(\\.|[^"\\])*"\s*:)|("(\\.|[^"\\])*")|(\b(?:true|false|null)\b)|(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)/g, (match, key, _k2, str, _s2, literal) => {
40
+ if (key !== undefined)
41
+ return `${CODES.blue}${key}${CODES.reset}`;
42
+ if (str !== undefined)
43
+ return `${CODES.green}${str}${CODES.reset}`;
44
+ if (literal !== undefined)
45
+ return `${CODES.yellow}${literal}${CODES.reset}`;
46
+ return `${CODES.cyan}${match}${CODES.reset}`;
47
+ });
48
+ /**
49
+ * `plain` prints scalars bare so `$(roark ... --format plain)` is directly
50
+ * usable in a shell; anything with structure still has to be JSON.
51
+ */
52
+ const plain = (value) => {
53
+ if (value === null || value === undefined)
54
+ return '';
55
+ if (typeof value === 'string')
56
+ return value;
57
+ if (typeof value === 'number' || typeof value === 'boolean')
58
+ return String(value);
59
+ if (Array.isArray(value))
60
+ return value.map(plain).join('\n');
61
+ return JSON.stringify(value);
62
+ };
63
+ /**
64
+ * `auto` is JSON either way. What the TTY changes is presentation - indentation
65
+ * and colour - not the format, so a command's output stays parseable no matter
66
+ * where it runs and a script never has to pass a flag to get stable output.
67
+ */
68
+ const resolveFormat = (requested) => requested === 'auto' ? 'json' : requested;
69
+ exports.resolveFormat = resolveFormat;
70
+ const render = (value, options) => {
71
+ const format = (0, exports.resolveFormat)(options.format);
72
+ if (format === 'plain')
73
+ return plain(value);
74
+ if (format === 'jsonl') {
75
+ const rows = Array.isArray(value) ? value : [value];
76
+ return rows.map((row) => JSON.stringify(row)).join('\n');
77
+ }
78
+ // `json` is pretty-printed when a human will read it and compact when a
79
+ // program will: indentation is noise to `jq` and structure to a person.
80
+ const humanReadable = options.color || (0, exports.isTty)(options.stream);
81
+ const json = humanReadable ? JSON.stringify(value, null, 2) : JSON.stringify(value);
82
+ return options.color ? colorizeJson(json) : json;
83
+ };
84
+ exports.render = render;
85
+ const write = (text, stream = process.stdout) => {
86
+ if (text.length === 0)
87
+ return;
88
+ stream.write(text.endsWith('\n') ? text : `${text}\n`);
89
+ };
90
+ exports.write = write;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Builds the whole command tree from the generated table.
3
+ *
4
+ * Nothing here names an endpoint. Every command, positional and flag comes from
5
+ * `commands.ts`, so a new API operation reaches the CLI as a data change.
6
+ */
7
+ import { Command } from 'commander';
8
+ import { type ResolvedConfig } from './config';
9
+ import { type OutputFormat } from './output';
10
+ import type { CliCommand } from './types';
11
+ export interface ProgramOptions {
12
+ binaryName: string;
13
+ version: string;
14
+ description: string;
15
+ commands: readonly CliCommand[];
16
+ /** Help text for the command groups, keyed by space-joined path. */
17
+ groups: Readonly<Record<string, string>>;
18
+ completions: Readonly<Record<string, string>>;
19
+ }
20
+ interface GlobalOptions {
21
+ baseUrl?: string;
22
+ timeout?: string;
23
+ maxRetries?: string;
24
+ token?: string;
25
+ format?: OutputFormat;
26
+ json?: boolean;
27
+ color?: boolean;
28
+ yes?: boolean;
29
+ input?: boolean;
30
+ quiet?: boolean;
31
+ data?: string;
32
+ allowProjectBaseUrl?: boolean;
33
+ }
34
+ /**
35
+ * Refuses the one combination that would post a secret to a host the caller did
36
+ * not choose. Nothing is sent first: this throws before the client exists.
37
+ */
38
+ export declare const assertBaseUrlIsTrusted: (options: GlobalOptions, resolved: ResolvedConfig) => void;
39
+ export declare const resolveOutput: (options: GlobalOptions) => {
40
+ format: OutputFormat;
41
+ color: boolean;
42
+ };
43
+ export declare const createProgram: (options: ProgramOptions) => Command;
44
+ export declare const run: (options: ProgramOptions, argv: readonly string[]) => Promise<number>;
45
+ export {};