zinkee 0.1.44 → 0.1.46

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 (94) hide show
  1. package/README.md +4 -0
  2. package/dist/chunk-MHEVRMDX.js +1230 -0
  3. package/dist/chunk-MHEVRMDX.js.map +1 -0
  4. package/{src/utils/examples.ts → dist/examples-67U5IRBS.js} +866 -1141
  5. package/dist/examples-67U5IRBS.js.map +1 -0
  6. package/dist/index.js +1145 -8456
  7. package/dist/index.js.map +1 -1
  8. package/package.json +6 -3
  9. package/.github/workflows/npm-publish.yml +0 -77
  10. package/.github/workflows/pr-checks.yml +0 -36
  11. package/AGENTS.md +0 -110
  12. package/docs/cli-contract.md +0 -41
  13. package/docs/npm-release.md +0 -64
  14. package/docs/superpowers/plans/2026-03-24-zinkee-cli-implementation.md +0 -837
  15. package/docs/superpowers/plans/2026-03-25-cli-backend-error-contract.md +0 -503
  16. package/docs/superpowers/plans/2026-03-26-display-freeform-create.md +0 -389
  17. package/docs/superpowers/specs/2026-03-24-zinkee-cli-backend-blockers.md +0 -172
  18. package/docs/superpowers/specs/2026-03-24-zinkee-cli-design.md +0 -1576
  19. package/docs/superpowers/specs/2026-03-24-zinkee-cli-e2e-checklist.md +0 -215
  20. package/docs/superpowers/specs/2026-03-24-zinkee-cli-e2e-design.md +0 -492
  21. package/docs/superpowers/specs/2026-03-24-zinkee-cli-e2e-status.md +0 -307
  22. package/docs/superpowers/specs/2026-07-30-cli-pr-checks-design.md +0 -37
  23. package/src/api/automations.ts +0 -404
  24. package/src/api/comments.ts +0 -51
  25. package/src/api/displays.ts +0 -337
  26. package/src/api/document-templates.ts +0 -110
  27. package/src/api/files.ts +0 -50
  28. package/src/api/formulas.test.ts +0 -65
  29. package/src/api/formulas.ts +0 -67
  30. package/src/api/logs.ts +0 -34
  31. package/src/api/navigation.ts +0 -70
  32. package/src/api/records.ts +0 -184
  33. package/src/api/schemas.ts +0 -148
  34. package/src/api/teamspace.ts +0 -110
  35. package/src/cli-examples.ts +0 -130
  36. package/src/cli-runner.ts +0 -95
  37. package/src/client.test.ts +0 -189
  38. package/src/client.ts +0 -269
  39. package/src/command-registry.ts +0 -882
  40. package/src/commands/automations.test.ts +0 -1030
  41. package/src/commands/automations.ts +0 -2102
  42. package/src/commands/comments.test.ts +0 -214
  43. package/src/commands/comments.ts +0 -303
  44. package/src/commands/config.test.ts +0 -81
  45. package/src/commands/config.ts +0 -150
  46. package/src/commands/displays.test.ts +0 -1105
  47. package/src/commands/displays.ts +0 -1442
  48. package/src/commands/document-templates.test.ts +0 -569
  49. package/src/commands/document-templates.ts +0 -563
  50. package/src/commands/files.test.ts +0 -284
  51. package/src/commands/files.ts +0 -280
  52. package/src/commands/formulas.test.ts +0 -194
  53. package/src/commands/formulas.ts +0 -243
  54. package/src/commands/logs.test.ts +0 -123
  55. package/src/commands/logs.ts +0 -159
  56. package/src/commands/navigation.test.ts +0 -211
  57. package/src/commands/navigation.ts +0 -348
  58. package/src/commands/profiles.test.ts +0 -191
  59. package/src/commands/profiles.ts +0 -303
  60. package/src/commands/records.test.ts +0 -860
  61. package/src/commands/records.ts +0 -883
  62. package/src/commands/schemas.test.ts +0 -1252
  63. package/src/commands/schemas.ts +0 -890
  64. package/src/commands/teamspace.test.ts +0 -229
  65. package/src/commands/teamspace.ts +0 -546
  66. package/src/completion/engine.test.ts +0 -138
  67. package/src/completion/engine.ts +0 -168
  68. package/src/completion/install.test.ts +0 -179
  69. package/src/completion/install.ts +0 -260
  70. package/src/completion/runtime.ts +0 -150
  71. package/src/completion/scripts.ts +0 -91
  72. package/src/config.test.ts +0 -362
  73. package/src/config.ts +0 -294
  74. package/src/index.test.ts +0 -217
  75. package/src/index.ts +0 -8
  76. package/src/parsers/expressions.test.ts +0 -95
  77. package/src/parsers/expressions.ts +0 -128
  78. package/src/parsers/kv.test.ts +0 -35
  79. package/src/parsers/kv.ts +0 -49
  80. package/src/parsers/selectors.test.ts +0 -23
  81. package/src/parsers/selectors.ts +0 -29
  82. package/src/program.ts +0 -64
  83. package/src/runtime-context.ts +0 -103
  84. package/src/types.ts +0 -76
  85. package/src/utils/argv-rewrite.test.ts +0 -149
  86. package/src/utils/argv-rewrite.ts +0 -269
  87. package/src/utils/errors.test.ts +0 -85
  88. package/src/utils/errors.ts +0 -191
  89. package/src/utils/examples.test.ts +0 -374
  90. package/src/utils/output.test.ts +0 -65
  91. package/src/utils/output.ts +0 -154
  92. package/src/utils/schema-fields.ts +0 -1016
  93. package/tsconfig.json +0 -20
  94. package/tsup.config.ts +0 -13
@@ -1,2102 +0,0 @@
1
- import { readFileSync } from "node:fs";
2
- import type { Writable } from "node:stream";
3
-
4
- import { Command, InvalidArgumentError } from "commander";
5
-
6
- import {
7
- AutomationsApi,
8
- type ApiAutomation,
9
- type ApiAutomationAction,
10
- type ApiAutomationConnection,
11
- type ApiAutomationFolder,
12
- type ApiAutomationPlugin,
13
- type CreateAutomationConnectionInput,
14
- type CreateAutomationInput,
15
- type MutationAutomationActionInput,
16
- type SetAutomationFlowInput,
17
- type SetAutomationTriggerInput,
18
- type SetAutomationWebhookInput,
19
- type UpdateAutomationConnectionInput,
20
- type UpdateAutomationInput,
21
- } from "../api/automations.js";
22
- import { ApiClient } from "../client.js";
23
- import { defaultConfigPath, readRuntimeConfig } from "../config.js";
24
- import { parseJsonAssignment, parseKeyValue } from "../parsers/kv.js";
25
- import {
26
- normalizeUuidOrSlug,
27
- type NormalizedUuidOrSlug,
28
- } from "../parsers/selectors.js";
29
- import { resolveRuntimeContext } from "../runtime-context.js";
30
- import type { JsonObject, JsonValue, RuntimeOverrides } from "../types.js";
31
- import { CliError } from "../utils/errors.js";
32
- import { buildJsonSuccess, folderColumns, FOLDER_TABLE_KEYS, printTable } from "../utils/output.js";
33
-
34
- interface CommandIo {
35
- write(chunk: string): unknown;
36
- }
37
-
38
- export interface RegisterAutomationsCommandsOptions {
39
- configPath?: string;
40
- stdout?: CommandIo;
41
- }
42
-
43
- interface GlobalOptions extends RuntimeOverrides {}
44
-
45
- interface FolderMutationCommandOptions {
46
- name?: string;
47
- }
48
-
49
- interface FolderMoveCommandOptions {
50
- to?: NormalizedUuidOrSlug;
51
- }
52
-
53
- interface AutomationListCommandOptions {
54
- folder?: NormalizedUuidOrSlug;
55
- status?: string;
56
- triggerType?: string;
57
- actionType?: string;
58
- plugin?: string;
59
- search?: string;
60
- }
61
-
62
- interface TriggerPayloadCommandOptions {
63
- triggerType?: string;
64
- cron?: string;
65
- triggerSchema?: string;
66
- triggerField?: string[];
67
- condition?: string[];
68
- conditionJson?: string[];
69
- webhookEndpointId?: string;
70
- raw?: string;
71
- rawFile?: string;
72
- }
73
-
74
- interface AutomationCreateCommandOptions extends TriggerPayloadCommandOptions {
75
- name?: string;
76
- description?: string;
77
- folder?: NormalizedUuidOrSlug;
78
- }
79
-
80
- interface AutomationUpdateCommandOptions {
81
- name?: string;
82
- description?: string;
83
- folder?: NormalizedUuidOrSlug;
84
- raw?: string;
85
- rawFile?: string;
86
- }
87
-
88
- interface ActionMutationCommandOptions {
89
- type?: string;
90
- name?: string;
91
- targetSchema?: string;
92
- map?: string[];
93
- mapJson?: string[];
94
- condition?: string[];
95
- conditionJson?: string[];
96
- useTriggerRecord?: boolean;
97
- method?: string;
98
- url?: string;
99
- header?: string[];
100
- body?: string;
101
- bodyFile?: string;
102
- contentType?: string;
103
- timeoutMs?: number;
104
- var?: string[];
105
- authMode?: string;
106
- connection?: string;
107
- plugin?: string;
108
- arg?: string[];
109
- argJson?: string[];
110
- to?: string;
111
- subject?: string;
112
- message?: string;
113
- documentTemplate?: string;
114
- targetFileField?: string;
115
- raw?: string;
116
- rawFile?: string;
117
- }
118
-
119
- interface FlowSetCommandOptions {
120
- entry?: string[];
121
- transition?: string[];
122
- raw?: string;
123
- rawFile?: string;
124
- }
125
-
126
- interface WebhookSetCommandOptions {
127
- active?: boolean;
128
- idempotencyKeyPath?: string;
129
- eventTypePath?: string;
130
- allowedEventType?: string[];
131
- fieldMapping?: string[];
132
- fieldMappingFile?: string[];
133
- raw?: string;
134
- rawFile?: string;
135
- }
136
-
137
- interface ConnectionCreateCommandOptions {
138
- name?: string;
139
- type?: string;
140
- scope?: string;
141
- ownerMember?: string;
142
- config?: string;
143
- configFile?: string;
144
- secret?: string[];
145
- active?: boolean;
146
- raw?: string;
147
- rawFile?: string;
148
- }
149
-
150
- interface ConnectionUpdateCommandOptions {
151
- set?: string[];
152
- unset?: string[];
153
- raw?: string;
154
- rawFile?: string;
155
- }
156
-
157
- type ConditionSpec = JsonObject & {
158
- field: string;
159
- comparator: string;
160
- values: JsonValue[];
161
- };
162
-
163
- type TransitionSpec = JsonObject & {
164
- fromActionId: string;
165
- toActionId: string;
166
- };
167
-
168
- function getCommandIo(stdout: CommandIo | undefined): CommandIo {
169
- return stdout ?? (process.stdout as Writable);
170
- }
171
-
172
- function writeLine(stdout: CommandIo, value: string): void {
173
- stdout.write(`${value}\n`);
174
- }
175
-
176
- function getGlobalOptions(command: Command): GlobalOptions {
177
- return command.optsWithGlobals<GlobalOptions>();
178
- }
179
-
180
- function emitJson(
181
- stdout: CommandIo,
182
- commandName: string,
183
- data: unknown,
184
- configPath: string,
185
- ): void {
186
- writeLine(
187
- stdout,
188
- JSON.stringify(buildJsonSuccess(data, { command: commandName, configPath })),
189
- );
190
- }
191
-
192
- function toConfigurationError(filePath: string, error: unknown): never {
193
- if (error instanceof CliError) {
194
- throw error;
195
- }
196
-
197
- if (
198
- typeof error === "object" &&
199
- error !== null &&
200
- "code" in error &&
201
- error.code === "ENOENT"
202
- ) {
203
- throw new CliError("configuration_error", `Config file not found at ${filePath}.`);
204
- }
205
-
206
- if (error instanceof Error) {
207
- throw new CliError("configuration_error", error.message);
208
- }
209
-
210
- throw error;
211
- }
212
-
213
- function createAutomationsApi(
214
- command: Command,
215
- filePath: string,
216
- ): { api: AutomationsApi; json: boolean; readOnly: boolean } {
217
- try {
218
- const config = readRuntimeConfig(filePath);
219
- const runtime = resolveRuntimeContext(config, getGlobalOptions(command));
220
-
221
- if (!runtime.baseUrl) {
222
- throw new CliError(
223
- "configuration_error",
224
- `Resolved profile "${runtime.profile}" is missing a base URL.`,
225
- );
226
- }
227
-
228
- return {
229
- api: new AutomationsApi(
230
- new ApiClient({
231
- baseUrl: runtime.baseUrl,
232
- apiKey: runtime.apiKey,
233
- }),
234
- ),
235
- json: runtime.json,
236
- readOnly: runtime.readOnly,
237
- };
238
- } catch (error) {
239
- toConfigurationError(filePath, error);
240
- }
241
- }
242
-
243
- function ensureWritable(
244
- commandName: string,
245
- readOnly: boolean,
246
- commandMode: "write" | "destructive",
247
- ): void {
248
- if (readOnly) {
249
- throw new CliError(
250
- "command_not_allowed_in_read_only_mode",
251
- `Command "${commandName}" is not allowed in read-only mode.`,
252
- {
253
- commandMode,
254
- executionMode: "read-only",
255
- },
256
- );
257
- }
258
- }
259
-
260
- function parseUuidOrSlugSelector(input: string): NormalizedUuidOrSlug {
261
- try {
262
- return normalizeUuidOrSlug(input);
263
- } catch (error) {
264
- const message = error instanceof Error ? error.message : "Invalid selector.";
265
- throw new InvalidArgumentError(message);
266
- }
267
- }
268
-
269
- function collectValues(value: string, previous: string[] = []): string[] {
270
- return [...previous, value];
271
- }
272
-
273
- function parseNonNegativeInteger(input: string): number {
274
- if (!/^\d+$/.test(input.trim())) {
275
- throw new InvalidArgumentError(`Expected a non-negative integer, received "${input}".`);
276
- }
277
-
278
- return Number(input);
279
- }
280
-
281
- function isJsonObject(value: unknown): value is JsonObject {
282
- return typeof value === "object" && value !== null && !Array.isArray(value);
283
- }
284
-
285
- function isEmptyObject(value: object): boolean {
286
- return Object.keys(value).length === 0;
287
- }
288
-
289
- function readRawSetPayload(raw: JsonObject, label: string): JsonObject {
290
- const setValue = raw.set;
291
-
292
- if (setValue === undefined) {
293
- return {};
294
- }
295
-
296
- if (!isJsonObject(setValue)) {
297
- throw new CliError(
298
- "invalid_cli_usage",
299
- `Expected ${label} field "set" to be a JSON object.`,
300
- );
301
- }
302
-
303
- return { ...setValue };
304
- }
305
-
306
- function readRawUnsetPayload(raw: JsonObject, label: string): string[] {
307
- const unsetValue = raw.unset;
308
-
309
- if (unsetValue === undefined) {
310
- return [];
311
- }
312
-
313
- if (!Array.isArray(unsetValue) || !unsetValue.every((value) => typeof value === "string")) {
314
- throw new CliError(
315
- "invalid_cli_usage",
316
- `Expected ${label} field "unset" to be an array of strings.`,
317
- );
318
- }
319
-
320
- return [...unsetValue];
321
- }
322
-
323
- function splitRawPatchPayload(
324
- raw: JsonObject,
325
- label: string,
326
- ): { set: JsonObject; unset: string[] } {
327
- const set: JsonObject = { ...raw };
328
- delete set.set;
329
- delete set.unset;
330
-
331
- return {
332
- set: {
333
- ...set,
334
- ...readRawSetPayload(raw, label),
335
- },
336
- unset: readRawUnsetPayload(raw, label),
337
- };
338
- }
339
-
340
- function setNestedPath(target: JsonObject, path: readonly string[], value: JsonValue): void {
341
- const [head, ...tail] = path;
342
-
343
- if (!head) {
344
- return;
345
- }
346
-
347
- if (tail.length === 0) {
348
- target[head] = value;
349
- return;
350
- }
351
-
352
- const next = isJsonObject(target[head]) ? { ...target[head] } : {};
353
- target[head] = next;
354
- setNestedPath(next, tail, value);
355
- }
356
-
357
- function hasSetPath(target: JsonObject, path: string): boolean {
358
- if (Object.hasOwn(target, path)) {
359
- return true;
360
- }
361
-
362
- const segments = path.split(".").filter(Boolean);
363
-
364
- if (segments.length <= 1) {
365
- return Object.hasOwn(target, path);
366
- }
367
-
368
- let current: unknown = target;
369
-
370
- for (const segment of segments) {
371
- if (!isJsonObject(current) || !Object.hasOwn(current, segment)) {
372
- return false;
373
- }
374
-
375
- current = current[segment];
376
- }
377
-
378
- return true;
379
- }
380
-
381
- function parseJsonValue(input: string, sourceLabel: string): JsonValue {
382
- try {
383
- return JSON.parse(input) as JsonValue;
384
- } catch (error) {
385
- const message = error instanceof Error ? error.message : "Invalid JSON.";
386
- throw new CliError(
387
- "invalid_cli_usage",
388
- `Failed to parse JSON from ${sourceLabel}: ${message}`,
389
- );
390
- }
391
- }
392
-
393
- function parseJsonObject(input: string, sourceLabel: string): JsonObject {
394
- const parsed = parseJsonValue(input, sourceLabel);
395
-
396
- if (!isJsonObject(parsed)) {
397
- throw new CliError(
398
- "invalid_cli_usage",
399
- `Expected ${sourceLabel} to contain a JSON object.`,
400
- );
401
- }
402
-
403
- return parsed;
404
- }
405
-
406
- function readTextFile(filePath: string, label: string): string {
407
- try {
408
- return readFileSync(filePath, "utf8");
409
- } catch (error) {
410
- const message = error instanceof Error ? error.message : "Unable to read file.";
411
- throw new CliError(
412
- "invalid_cli_usage",
413
- `Failed to read ${label} from ${filePath}: ${message}`,
414
- );
415
- }
416
- }
417
-
418
- function readJsonValueOption(
419
- raw: string | undefined,
420
- rawFile: string | undefined,
421
- rawLabel: string,
422
- rawFileLabel: string,
423
- ): JsonValue | undefined {
424
- if (raw && rawFile) {
425
- throw new CliError(
426
- "invalid_cli_usage",
427
- `Provide either "${rawLabel}" or "${rawFileLabel}", not both.`,
428
- );
429
- }
430
-
431
- if (raw) {
432
- return parseJsonValue(raw, rawLabel);
433
- }
434
-
435
- if (rawFile) {
436
- return parseJsonValue(readTextFile(rawFile, rawFileLabel), `${rawFileLabel} ${rawFile}`);
437
- }
438
-
439
- return undefined;
440
- }
441
-
442
- function readJsonObjectOption(
443
- raw: string | undefined,
444
- rawFile: string | undefined,
445
- rawLabel: string,
446
- rawFileLabel: string,
447
- ): JsonObject {
448
- return (
449
- readJsonValueOption(raw, rawFile, rawLabel, rawFileLabel) ?? {}
450
- ) as JsonObject;
451
- }
452
-
453
- const NO_VALUE_COMPARATORS = new Set([
454
- "chg",
455
- "changes",
456
- "ie",
457
- "is_empty",
458
- "ine",
459
- "is_not_empty",
460
- ]);
461
-
462
- function isNoValueComparator(comparator: string): boolean {
463
- return NO_VALUE_COMPARATORS.has(comparator.toLowerCase());
464
- }
465
-
466
- function conditionFormatError(input: string): CliError {
467
- return new CliError(
468
- "invalid_cli_usage",
469
- `Expected condition in the form field:comparator:value, or field:comparator for comparators without value (chg, ie, ine), received "${input}".`,
470
- );
471
- }
472
-
473
- function parseCondition(input: string): ConditionSpec {
474
- const firstSeparator = input.indexOf(":");
475
- const secondSeparator = input.indexOf(":", firstSeparator + 1);
476
-
477
- if (firstSeparator <= 0) {
478
- throw conditionFormatError(input);
479
- }
480
-
481
- if (secondSeparator === -1) {
482
- const field = input.slice(0, firstSeparator).trim();
483
- const comparator = input.slice(firstSeparator + 1).trim();
484
-
485
- if (!field || !comparator || !isNoValueComparator(comparator)) {
486
- throw conditionFormatError(input);
487
- }
488
-
489
- return { field, comparator, values: [] };
490
- }
491
-
492
- if (secondSeparator <= firstSeparator + 1) {
493
- throw conditionFormatError(input);
494
- }
495
-
496
- const field = input.slice(0, firstSeparator).trim();
497
- const comparator = input.slice(firstSeparator + 1, secondSeparator).trim();
498
- const value = input.slice(secondSeparator + 1).trim();
499
-
500
- if (!field || !comparator || !value) {
501
- throw conditionFormatError(input);
502
- }
503
-
504
- return {
505
- field,
506
- comparator,
507
- values: value
508
- .split(",")
509
- .map((entry) => entry.trim())
510
- .filter(Boolean),
511
- };
512
- }
513
-
514
- function parseJsonCondition(input: string, label: string): ConditionSpec {
515
- const parsed = parseJsonObject(input, label);
516
- const field = typeof parsed.field === "string" ? parsed.field.trim() : "";
517
- const comparator = typeof parsed.comparator === "string" ? parsed.comparator.trim() : "";
518
- const values = parsed.values;
519
-
520
- if (!field || !comparator || !Array.isArray(values)) {
521
- throw new CliError(
522
- "invalid_cli_usage",
523
- `Expected ${label} to contain {"field","comparator","values"}.`,
524
- );
525
- }
526
-
527
- return {
528
- ...parsed,
529
- field,
530
- comparator,
531
- values: [...values],
532
- };
533
- }
534
-
535
- function parseTransition(input: string): TransitionSpec {
536
- const separatorIndex = input.indexOf(":");
537
-
538
- if (separatorIndex <= 0 || separatorIndex === input.length - 1) {
539
- throw new CliError(
540
- "invalid_cli_usage",
541
- `Expected transition in the form from:to, received "${input}".`,
542
- );
543
- }
544
-
545
- const fromActionId = input.slice(0, separatorIndex).trim();
546
- const toActionId = input.slice(separatorIndex + 1).trim();
547
-
548
- if (!fromActionId || !toActionId) {
549
- throw new CliError(
550
- "invalid_cli_usage",
551
- `Expected transition in the form from:to, received "${input}".`,
552
- );
553
- }
554
-
555
- return { fromActionId, toActionId };
556
- }
557
-
558
- function parseStringAssignments(values: readonly string[] | undefined): Record<string, string> {
559
- const result: Record<string, string> = {};
560
-
561
- for (const value of values ?? []) {
562
- const assignment = parseKeyValue(value);
563
- result[assignment.key] = assignment.value;
564
- }
565
-
566
- return result;
567
- }
568
-
569
- function parseJsonAssignments(values: readonly string[] | undefined): JsonObject {
570
- const result: JsonObject = {};
571
-
572
- for (const value of values ?? []) {
573
- const assignment = parseJsonAssignment<JsonValue>(value);
574
- result[assignment.key] = assignment.value;
575
- }
576
-
577
- return result;
578
- }
579
-
580
- function mergeMappings(
581
- stringAssignments: readonly string[] | undefined,
582
- jsonAssignments: readonly string[] | undefined,
583
- ): JsonObject {
584
- return {
585
- ...parseStringAssignments(stringAssignments),
586
- ...parseJsonAssignments(jsonAssignments),
587
- };
588
- }
589
-
590
- const AUTOMATION_SOURCE_TYPES = new Set(["TRIGGER", "PREVIOUS_ACTION"]);
591
-
592
- type AutomationSourceType = "TRIGGER" | "PREVIOUS_ACTION";
593
-
594
- type AutomationValueSource = JsonObject & {
595
- type: AutomationSourceType;
596
- id: string;
597
- };
598
-
599
- type AutomationResolvedValue = JsonObject & {
600
- rawValue: JsonValue | null;
601
- source: AutomationValueSource | null;
602
- };
603
-
604
- function isAutomationValueSource(value: unknown): value is AutomationValueSource {
605
- return (
606
- isJsonObject(value) &&
607
- typeof value.type === "string" &&
608
- AUTOMATION_SOURCE_TYPES.has(value.type) &&
609
- typeof value.id === "string" &&
610
- value.id.trim().length > 0
611
- );
612
- }
613
-
614
- function looksLikeAutomationResolvedValue(value: unknown): value is JsonObject {
615
- if (!isJsonObject(value)) {
616
- return false;
617
- }
618
-
619
- if ("rawValue" in value) {
620
- return true;
621
- }
622
-
623
- if (!Object.hasOwn(value, "source")) {
624
- return false;
625
- }
626
-
627
- return value.source === null || isAutomationValueSource(value.source);
628
- }
629
-
630
- function normalizeAutomationResolvedValue(
631
- value: JsonValue,
632
- label: string,
633
- options: { allowSource: boolean },
634
- ): AutomationResolvedValue {
635
- if (!looksLikeAutomationResolvedValue(value)) {
636
- return {
637
- rawValue: value,
638
- source: null,
639
- };
640
- }
641
-
642
- const rawValue = Object.hasOwn(value, "rawValue") ? (value.rawValue as JsonValue | null) : undefined;
643
- const sourceValue = Object.hasOwn(value, "source") ? value.source : undefined;
644
- const hasRawValue = rawValue !== undefined && rawValue !== null;
645
- const hasSource = sourceValue !== undefined && sourceValue !== null;
646
-
647
- if (hasRawValue === hasSource) {
648
- throw new CliError(
649
- "invalid_cli_usage",
650
- `Expected ${label} to define exactly one of "rawValue" or "source".`,
651
- );
652
- }
653
-
654
- if (hasSource) {
655
- if (!options.allowSource) {
656
- throw new CliError(
657
- "invalid_cli_usage",
658
- `Expected ${label} to use only literal "rawValue" values.`,
659
- );
660
- }
661
-
662
- if (!isAutomationValueSource(sourceValue)) {
663
- throw new CliError(
664
- "invalid_cli_usage",
665
- `Expected ${label} "source" to contain a valid automation source reference.`,
666
- );
667
- }
668
-
669
- return {
670
- rawValue: null,
671
- source: {
672
- type: sourceValue.type,
673
- id: sourceValue.id,
674
- },
675
- };
676
- }
677
-
678
- return {
679
- rawValue: rawValue ?? null,
680
- source: null,
681
- };
682
- }
683
-
684
- function normalizeAutomationFieldMapping(mapping: JsonObject, label: string): JsonObject {
685
- const normalized: JsonObject = {};
686
-
687
- for (const [field, value] of Object.entries(mapping)) {
688
- normalized[field] = normalizeAutomationResolvedValue(
689
- value as JsonValue,
690
- `${label} "${field}"`,
691
- { allowSource: true },
692
- );
693
- }
694
-
695
- return normalized;
696
- }
697
-
698
- function normalizeAutomationConditions(
699
- conditions: readonly unknown[],
700
- label: string,
701
- options: { allowSource: boolean },
702
- ): ConditionSpec[] {
703
- return conditions.map((condition, index) => {
704
- if (!isJsonObject(condition)) {
705
- throw new CliError(
706
- "invalid_cli_usage",
707
- `Expected ${label} ${index + 1} to be a JSON object.`,
708
- );
709
- }
710
-
711
- const field = typeof condition.field === "string" ? condition.field.trim() : "";
712
- const comparator = typeof condition.comparator === "string" ? condition.comparator.trim() : "";
713
- const values = condition.values;
714
-
715
- const allowsEmptyValues = isNoValueComparator(comparator);
716
- if (!field || !comparator || !Array.isArray(values) || (values.length === 0 && !allowsEmptyValues)) {
717
- throw new CliError(
718
- "invalid_cli_usage",
719
- `Expected ${label} ${index + 1} to contain "field", "comparator", and a non-empty "values" array (comparators without value, e.g. chg/ie/ine, accept an empty array).`,
720
- );
721
- }
722
-
723
- return {
724
- ...condition,
725
- field,
726
- comparator,
727
- values: values.map((value, valueIndex) =>
728
- normalizeAutomationResolvedValue(
729
- value as JsonValue,
730
- `${label} ${index + 1} value ${valueIndex + 1}`,
731
- options,
732
- ),
733
- ),
734
- };
735
- });
736
- }
737
-
738
- function readConditions(
739
- literalConditions: readonly string[] | undefined,
740
- jsonConditions: readonly string[] | undefined,
741
- label: string,
742
- ): ConditionSpec[] {
743
- return [
744
- ...(literalConditions ?? []).map(parseCondition),
745
- ...(jsonConditions ?? []).map((value, index) =>
746
- parseJsonCondition(value, `${label} ${index + 1}`),
747
- ),
748
- ];
749
- }
750
-
751
- function resolveFolder(
752
- folders: readonly ApiAutomationFolder[],
753
- selector: NormalizedUuidOrSlug,
754
- ): ApiAutomationFolder {
755
- if (selector.kind === "uuid") {
756
- const match = folders.find((folder) => folder.id === selector.value);
757
-
758
- if (match) {
759
- return match;
760
- }
761
- }
762
-
763
- const matches = folders.filter((folder) => folder.name === selector.value);
764
-
765
- if (matches.length === 1) {
766
- return matches[0] as ApiAutomationFolder;
767
- }
768
-
769
- if (matches.length > 1) {
770
- throw new CliError(
771
- "invalid_cli_usage",
772
- `Folder "${selector.value}" is ambiguous. Found ${matches.length} matches.`,
773
- );
774
- }
775
-
776
- throw new CliError("invalid_cli_usage", `Folder "${selector.value}" was not found.`);
777
- }
778
-
779
- async function resolveFolderSelector(
780
- api: AutomationsApi,
781
- selector: NormalizedUuidOrSlug | undefined,
782
- ): Promise<NormalizedUuidOrSlug | undefined> {
783
- if (!selector) {
784
- return undefined;
785
- }
786
-
787
- if (selector.kind === "uuid") {
788
- return selector;
789
- }
790
-
791
- const folders = await api.listFolders();
792
- const resolved = resolveFolder(folders, selector);
793
-
794
- if (typeof resolved.id !== "string" || resolved.id.length === 0) {
795
- throw new CliError(
796
- "invalid_cli_usage",
797
- `Folder "${selector.value}" is missing an id in the API response.`,
798
- );
799
- }
800
-
801
- return { kind: "uuid", value: resolved.id };
802
- }
803
-
804
- function toTableRows(data: unknown): Record<string, unknown>[] {
805
- if (Array.isArray(data)) {
806
- return data.map((entry) => (isJsonObject(entry) ? entry : { value: entry }));
807
- }
808
-
809
- if (isJsonObject(data)) {
810
- return [data];
811
- }
812
-
813
- return [{ value: data }];
814
- }
815
-
816
- function emitTable(stdout: CommandIo, data: unknown): void {
817
- printTable(toTableRows(data), undefined, stdout);
818
- }
819
-
820
- function folderRows(folders: readonly ApiAutomationFolder[]): Record<string, unknown>[] {
821
- return folders.map((folder) => ({
822
- id: folder.id ?? "",
823
- name: folder.name ?? "",
824
- automationCount: folder.automationCount ?? "",
825
- }));
826
- }
827
-
828
- function automationRows(automations: readonly ApiAutomation[]): Record<string, unknown>[] {
829
- return automations.map((automation) => ({
830
- id: automation.id ?? "",
831
- name: automation.name ?? "",
832
- status: automation.status ?? "",
833
- triggerType:
834
- isJsonObject(automation.trigger) && typeof automation.trigger.type === "string"
835
- ? automation.trigger.type
836
- : "",
837
- actionCount: Array.isArray(automation.actions) ? automation.actions.length : "",
838
- ...folderColumns(automation.folder),
839
- }));
840
- }
841
-
842
- function actionRows(actions: readonly ApiAutomationAction[]): Record<string, unknown>[] {
843
- return actions.map((action) => ({
844
- id: action.id ?? "",
845
- name: action.name ?? "",
846
- type: action.type ?? "",
847
- }));
848
- }
849
-
850
- function pluginRows(plugins: readonly ApiAutomationPlugin[]): Record<string, unknown>[] {
851
- return plugins.map((plugin) => ({
852
- id: plugin.id ?? "",
853
- name: plugin.name ?? "",
854
- version: plugin.version ?? "",
855
- }));
856
- }
857
-
858
- function connectionRows(
859
- connections: readonly ApiAutomationConnection[],
860
- ): Record<string, unknown>[] {
861
- return connections.map((connection) => ({
862
- id: connection.id ?? "",
863
- name: connection.name ?? "",
864
- type: connection.type ?? "",
865
- scope: connection.scope ?? "",
866
- active: connection.active ?? "",
867
- }));
868
- }
869
-
870
- function buildFolderMutationInput(
871
- options: FolderMutationCommandOptions,
872
- requireName: boolean,
873
- ): Record<string, unknown> {
874
- const input: Record<string, unknown> = {};
875
-
876
- if (options.name !== undefined) {
877
- input.name = options.name;
878
- }
879
-
880
- if (requireName && typeof input.name !== "string") {
881
- throw new CliError("invalid_cli_usage", 'Provide folder name via "--name".');
882
- }
883
-
884
- if (!requireName && isEmptyObject(input)) {
885
- throw new CliError("invalid_cli_usage", 'Provide folder updates via "--name".');
886
- }
887
-
888
- return input;
889
- }
890
-
891
- function buildTriggerInput(
892
- options: TriggerPayloadCommandOptions,
893
- mode: "optional" | "required",
894
- ): SetAutomationTriggerInput | undefined {
895
- const input = {
896
- ...readJsonObjectOption(options.raw, options.rawFile, "--raw", "--raw-file"),
897
- } as Record<string, unknown>;
898
-
899
- const config = isJsonObject(input.config) ? { ...input.config } : {};
900
-
901
- if (options.triggerType !== undefined) {
902
- input.type = options.triggerType;
903
- }
904
-
905
- if (options.cron !== undefined) {
906
- config.cronExpression = options.cron;
907
- }
908
-
909
- if (options.triggerSchema !== undefined) {
910
- config.schemaId = options.triggerSchema;
911
- }
912
-
913
- if ((options.triggerField?.length ?? 0) > 0) {
914
- config.fieldIds = [...(options.triggerField ?? [])];
915
- }
916
-
917
- const conditions = readConditions(options.condition, options.conditionJson, "--condition-json");
918
- if (conditions.length > 0) {
919
- config.conditions = conditions;
920
- }
921
-
922
- if (options.webhookEndpointId !== undefined) {
923
- config.webhookEndpointId = options.webhookEndpointId;
924
- }
925
-
926
- if (!isEmptyObject(config)) {
927
- if (Array.isArray(config.conditions)) {
928
- config.conditions = normalizeAutomationConditions(
929
- config.conditions,
930
- "trigger condition",
931
- { allowSource: false },
932
- );
933
- }
934
-
935
- input.config = config;
936
- }
937
-
938
- if (isEmptyObject(input)) {
939
- if (mode === "optional") {
940
- return undefined;
941
- }
942
-
943
- throw new CliError(
944
- "invalid_cli_usage",
945
- 'Provide trigger input via "--trigger-type", trigger flags, "--raw", or "--raw-file".',
946
- );
947
- }
948
-
949
- return input as SetAutomationTriggerInput;
950
- }
951
-
952
- async function buildAutomationCreateInput(
953
- api: AutomationsApi,
954
- options: AutomationCreateCommandOptions,
955
- ): Promise<CreateAutomationInput> {
956
- const input = {
957
- ...readJsonObjectOption(options.raw, options.rawFile, "--raw", "--raw-file"),
958
- } as Record<string, unknown>;
959
-
960
- if (options.name !== undefined) {
961
- input.name = options.name;
962
- }
963
-
964
- if (options.description !== undefined) {
965
- input.description = options.description;
966
- }
967
-
968
- const folder = await resolveFolderSelector(api, options.folder);
969
- if (folder) {
970
- input.folderId = folder.value;
971
- }
972
-
973
- const trigger = buildTriggerInput(options, "optional");
974
- if (trigger) {
975
- input.trigger = trigger;
976
- }
977
-
978
- if (typeof input.name !== "string" || input.name.trim().length === 0) {
979
- throw new CliError("invalid_cli_usage", 'Provide automation name via "--name".');
980
- }
981
-
982
- return input as CreateAutomationInput;
983
- }
984
-
985
- async function buildAutomationUpdateInput(
986
- api: AutomationsApi,
987
- options: AutomationUpdateCommandOptions,
988
- ): Promise<UpdateAutomationInput> {
989
- const raw = readJsonObjectOption(options.raw, options.rawFile, "--raw", "--raw-file");
990
- const { set: rawSet, unset: rawUnset } = splitRawPatchPayload(raw, "raw automation update payload");
991
- const setPayload: JsonObject = { ...rawSet };
992
-
993
- if (options.name !== undefined) {
994
- setPayload.name = options.name;
995
- }
996
-
997
- if (options.description !== undefined) {
998
- setPayload.description = options.description;
999
- }
1000
-
1001
- const folder = await resolveFolderSelector(api, options.folder);
1002
- if (folder) {
1003
- setPayload.folderId = folder.value;
1004
- }
1005
-
1006
- const unsetPayload = rawUnset.filter((path) => !hasSetPath(setPayload, path));
1007
- const input: UpdateAutomationInput = {};
1008
-
1009
- if (!isEmptyObject(setPayload)) {
1010
- input.set = setPayload;
1011
- }
1012
-
1013
- if (unsetPayload.length > 0) {
1014
- input.unset = [...new Set(unsetPayload)];
1015
- }
1016
-
1017
- if (isEmptyObject(input as object)) {
1018
- throw new CliError(
1019
- "invalid_cli_usage",
1020
- 'Provide automation updates via "--name", "--description", "--folder", "--raw", or "--raw-file".',
1021
- );
1022
- }
1023
-
1024
- return input as UpdateAutomationInput;
1025
- }
1026
-
1027
- function buildActionInput(
1028
- options: ActionMutationCommandOptions,
1029
- mode: "create" | "update",
1030
- ): MutationAutomationActionInput {
1031
- const input = {
1032
- ...readJsonObjectOption(options.raw, options.rawFile, "--raw", "--raw-file"),
1033
- } as Record<string, unknown>;
1034
-
1035
- const config = isJsonObject(input.config) ? { ...input.config } : {};
1036
- const mappings = mergeMappings(options.map, options.mapJson);
1037
- const headers = parseStringAssignments(options.header);
1038
- const variables = parseStringAssignments(options.var);
1039
- const args = mergeMappings(options.arg, options.argJson);
1040
- const body = readJsonValueOption(options.body, options.bodyFile, "--body", "--body-file");
1041
-
1042
- if (options.type !== undefined) {
1043
- input.type = options.type;
1044
- }
1045
-
1046
- if (options.name !== undefined) {
1047
- input.name = options.name;
1048
- }
1049
-
1050
- if (options.targetSchema !== undefined) {
1051
- config.targetSchemaId = options.targetSchema;
1052
- }
1053
-
1054
- if (options.to !== undefined) {
1055
- mappings.recipients = options.to;
1056
- }
1057
-
1058
- if (options.subject !== undefined) {
1059
- mappings.subject = options.subject;
1060
- }
1061
-
1062
- if (options.message !== undefined) {
1063
- mappings.body = options.message;
1064
- }
1065
-
1066
- if (!isEmptyObject(mappings)) {
1067
- config.fieldMapping = mappings;
1068
- }
1069
-
1070
- const conditions = readConditions(options.condition, options.conditionJson, "--condition-json");
1071
- if (conditions.length > 0) {
1072
- config.conditions = conditions;
1073
- }
1074
-
1075
- if (options.useTriggerRecord === true) {
1076
- config.useTriggerRecord = true;
1077
- }
1078
-
1079
- if (options.method !== undefined) {
1080
- config.method = options.method.toUpperCase();
1081
- }
1082
-
1083
- if (options.url !== undefined) {
1084
- config.url = options.url;
1085
- }
1086
-
1087
- if (!isEmptyObject(headers)) {
1088
- config.headers = headers;
1089
- }
1090
-
1091
- if (body !== undefined) {
1092
- config.body = body;
1093
- }
1094
-
1095
- if (options.contentType !== undefined) {
1096
- config.contentType = options.contentType;
1097
- }
1098
-
1099
- if (options.timeoutMs !== undefined) {
1100
- config.timeoutMs = options.timeoutMs;
1101
- }
1102
-
1103
- if (!isEmptyObject(variables)) {
1104
- config.variables = variables;
1105
- }
1106
-
1107
- if (options.authMode !== undefined) {
1108
- config.authMode = options.authMode;
1109
- }
1110
-
1111
- if (options.connection !== undefined) {
1112
- config.connectionId = options.connection;
1113
- }
1114
-
1115
- if (options.plugin !== undefined) {
1116
- config.pluginId = options.plugin;
1117
- }
1118
-
1119
- if (!isEmptyObject(args)) {
1120
- config.pluginArgs = args;
1121
- }
1122
-
1123
- if (options.documentTemplate !== undefined) {
1124
- config.documentTemplateId = options.documentTemplate;
1125
- }
1126
-
1127
- if (options.targetFileField !== undefined) {
1128
- config.targetFileFieldId = options.targetFileField;
1129
- }
1130
-
1131
- if (!isEmptyObject(config)) {
1132
- if (isJsonObject(config.fieldMapping)) {
1133
- config.fieldMapping = normalizeAutomationFieldMapping(
1134
- config.fieldMapping,
1135
- "field mapping",
1136
- );
1137
- }
1138
-
1139
- if (Array.isArray(config.conditions)) {
1140
- config.conditions = normalizeAutomationConditions(
1141
- config.conditions,
1142
- "action condition",
1143
- { allowSource: true },
1144
- );
1145
- }
1146
-
1147
- if (config.useTriggerRecord === true) {
1148
- delete config.conditions;
1149
- }
1150
-
1151
- input.config = config;
1152
- }
1153
-
1154
- if (mode === "create" && typeof input.type !== "string") {
1155
- throw new CliError("invalid_cli_usage", 'Provide action type via "--type" or "--raw".');
1156
- }
1157
-
1158
- if (isEmptyObject(input)) {
1159
- throw new CliError(
1160
- "invalid_cli_usage",
1161
- 'Provide action input via flags, "--raw", or "--raw-file".',
1162
- );
1163
- }
1164
-
1165
- return input as MutationAutomationActionInput;
1166
- }
1167
-
1168
- function buildFlowInput(options: FlowSetCommandOptions): SetAutomationFlowInput {
1169
- const input = {
1170
- ...readJsonObjectOption(options.raw, options.rawFile, "--raw", "--raw-file"),
1171
- } as Record<string, unknown>;
1172
-
1173
- if ((options.entry?.length ?? 0) > 0) {
1174
- input.entryActionIds = [...(options.entry ?? [])];
1175
- }
1176
-
1177
- if ((options.transition?.length ?? 0) > 0) {
1178
- input.transitions = (options.transition ?? []).map(parseTransition);
1179
- }
1180
-
1181
- if (isEmptyObject(input)) {
1182
- throw new CliError(
1183
- "invalid_cli_usage",
1184
- 'Provide flow input via "--entry", "--transition", "--raw", or "--raw-file".',
1185
- );
1186
- }
1187
-
1188
- return input as SetAutomationFlowInput;
1189
- }
1190
-
1191
- function readWebhookFieldMappings(options: WebhookSetCommandOptions): JsonValue[] {
1192
- const mappings: JsonValue[] = [];
1193
-
1194
- for (const value of options.fieldMapping ?? []) {
1195
- mappings.push(parseJsonValue(value, "--field-mapping"));
1196
- }
1197
-
1198
- for (const filePath of options.fieldMappingFile ?? []) {
1199
- mappings.push(
1200
- parseJsonValue(
1201
- readTextFile(filePath, "--field-mapping-file"),
1202
- `--field-mapping-file ${filePath}`,
1203
- ),
1204
- );
1205
- }
1206
-
1207
- return mappings;
1208
- }
1209
-
1210
- function buildWebhookInput(options: WebhookSetCommandOptions): SetAutomationWebhookInput {
1211
- const input = {
1212
- ...readJsonObjectOption(options.raw, options.rawFile, "--raw", "--raw-file"),
1213
- } as Record<string, unknown>;
1214
-
1215
- if (options.active === true) {
1216
- input.active = true;
1217
- }
1218
-
1219
- if (options.idempotencyKeyPath !== undefined) {
1220
- input.idempotencyKeyJsonPath = options.idempotencyKeyPath;
1221
- }
1222
-
1223
- if (options.eventTypePath !== undefined) {
1224
- input.eventTypeJsonPath = options.eventTypePath;
1225
- }
1226
-
1227
- if ((options.allowedEventType?.length ?? 0) > 0) {
1228
- input.allowedEventTypes = [...(options.allowedEventType ?? [])];
1229
- }
1230
-
1231
- const fieldMappings = readWebhookFieldMappings(options);
1232
- if (fieldMappings.length > 0) {
1233
- input.fieldMappings = fieldMappings;
1234
- }
1235
-
1236
- if (isEmptyObject(input)) {
1237
- throw new CliError(
1238
- "invalid_cli_usage",
1239
- 'Provide webhook input via flags, "--raw", or "--raw-file".',
1240
- );
1241
- }
1242
-
1243
- return input as SetAutomationWebhookInput;
1244
- }
1245
-
1246
- function buildConnectionCreateInput(
1247
- options: ConnectionCreateCommandOptions,
1248
- ): CreateAutomationConnectionInput {
1249
- const input = {
1250
- ...readJsonObjectOption(options.raw, options.rawFile, "--raw", "--raw-file"),
1251
- } as Record<string, unknown>;
1252
-
1253
- if (options.name !== undefined) {
1254
- input.name = options.name;
1255
- }
1256
-
1257
- if (options.type !== undefined) {
1258
- input.type = options.type;
1259
- }
1260
-
1261
- if (options.scope !== undefined) {
1262
- input.scope = options.scope;
1263
- }
1264
-
1265
- if (options.ownerMember !== undefined) {
1266
- input.ownerMemberId = options.ownerMember;
1267
- }
1268
-
1269
- const config = readJsonValueOption(options.config, options.configFile, "--config", "--config-file");
1270
- if (config !== undefined) {
1271
- if (!isJsonObject(config)) {
1272
- throw new CliError(
1273
- "invalid_cli_usage",
1274
- 'Expected "--config" or "--config-file" to contain a JSON object.',
1275
- );
1276
- }
1277
-
1278
- input.config = config;
1279
- }
1280
-
1281
- const secrets = parseStringAssignments(options.secret);
1282
- if (!isEmptyObject(secrets)) {
1283
- input.secret = secrets;
1284
- }
1285
-
1286
- if (options.active === true) {
1287
- input.active = true;
1288
- }
1289
-
1290
- if (typeof input.name !== "string") {
1291
- throw new CliError("invalid_cli_usage", 'Provide connection name via "--name".');
1292
- }
1293
-
1294
- if (typeof input.type !== "string") {
1295
- throw new CliError("invalid_cli_usage", 'Provide connection type via "--type".');
1296
- }
1297
-
1298
- if (typeof input.scope !== "string") {
1299
- throw new CliError("invalid_cli_usage", 'Provide connection scope via "--scope".');
1300
- }
1301
-
1302
- return input as CreateAutomationConnectionInput;
1303
- }
1304
-
1305
- function buildConnectionUpdateInput(
1306
- options: ConnectionUpdateCommandOptions,
1307
- ): UpdateAutomationConnectionInput {
1308
- const input = {
1309
- ...readJsonObjectOption(options.raw, options.rawFile, "--raw", "--raw-file"),
1310
- } as UpdateAutomationConnectionInput;
1311
- const setPayload = readRawSetPayload(input as JsonObject, "raw connection update payload");
1312
-
1313
- for (const value of options.set ?? []) {
1314
- const assignment = parseKeyValue(value);
1315
-
1316
- if (assignment.key.startsWith("config.")) {
1317
- setNestedPath(setPayload, assignment.key.split(".").filter(Boolean), assignment.value);
1318
- continue;
1319
- }
1320
-
1321
- setPayload[assignment.key] = assignment.value;
1322
- }
1323
-
1324
- if (!isEmptyObject(setPayload)) {
1325
- input.set = setPayload;
1326
- }
1327
-
1328
- const unsetPayload = [
1329
- ...readRawUnsetPayload(input as JsonObject, "raw connection update payload"),
1330
- ...(options.unset ?? []),
1331
- ].filter((path) => !hasSetPath(setPayload, path));
1332
-
1333
- if (unsetPayload.length > 0) {
1334
- input.unset = [...new Set(unsetPayload)];
1335
- } else {
1336
- delete input.unset;
1337
- }
1338
-
1339
- if (isEmptyObject(input as object)) {
1340
- throw new CliError(
1341
- "invalid_cli_usage",
1342
- 'Provide connection updates via "--set", "--unset", "--raw", or "--raw-file".',
1343
- );
1344
- }
1345
-
1346
- return input as UpdateAutomationConnectionInput;
1347
- }
1348
-
1349
- function addTriggerPayloadOptions(command: Command): Command {
1350
- return command
1351
- .option("--trigger-type <type>", "trigger type")
1352
- .option("--cron <expression>", "cron expression for scheduled triggers")
1353
- .option("--trigger-schema <uuid>", "schema id for record-based triggers")
1354
- .option(
1355
- "--trigger-field <uuid>",
1356
- "field id for record_changed triggers",
1357
- collectValues,
1358
- [],
1359
- )
1360
- .option(
1361
- "--condition <field:comparator:value>",
1362
- "trigger condition (use field:comparator for comparators without value: chg, ie, ine)",
1363
- collectValues,
1364
- [],
1365
- )
1366
- .option(
1367
- "--condition-json <json>",
1368
- "trigger condition as JSON",
1369
- collectValues,
1370
- [],
1371
- )
1372
- .option("--webhook-endpoint-id <uuid>", "webhook endpoint id")
1373
- .option("--raw <json>", "raw trigger JSON payload")
1374
- .option("--raw-file <path>", "path to raw trigger JSON payload");
1375
- }
1376
-
1377
- const ACTION_TYPES_HELP = `
1378
- Action types and their flags:
1379
- create_record --target-schema, --map, --map-json
1380
- update_record --target-schema, --use-trigger-record, --map,
1381
- --condition, --condition-json
1382
- search_records --target-schema, --condition, --condition-json
1383
- send_message --to, --subject, --message
1384
- (--map / --map-json also accepted for extra keys)
1385
- http_request --method, --url, --header, --body, --content-type,
1386
- --timeout-ms, --var, --auth-mode, --connection
1387
- execute_plugin --plugin, --arg, --arg-json
1388
- generate_document --document-template, --target-schema, --target-file-field
1389
- `;
1390
-
1391
- function addActionPayloadOptions(command: Command): Command {
1392
- return command
1393
- .option(
1394
- "--type <type>",
1395
- "action type (create_record|update_record|search_records|send_message|http_request|execute_plugin|generate_document)",
1396
- )
1397
- .option("--name <name>", "action name")
1398
- .option("--target-schema <uuid>", "target schema id")
1399
- .option("--map <field=value>", "field mapping", collectValues, [])
1400
- .option("--map-json <field=json>", "JSON field mapping", collectValues, [])
1401
- .option(
1402
- "--condition <field:comparator:value>",
1403
- "action condition (use field:comparator for comparators without value: ie, ine)",
1404
- collectValues,
1405
- [],
1406
- )
1407
- .option("--condition-json <json>", "action condition as JSON", collectValues, [])
1408
- .option("--use-trigger-record", "use the triggering record")
1409
- .option("--method <method>", "HTTP method")
1410
- .option("--url <url>", "HTTP target URL")
1411
- .option("--header <key=value>", "HTTP header", collectValues, [])
1412
- .option("--body <json>", "HTTP request body as JSON")
1413
- .option("--body-file <path>", "path to HTTP request body JSON")
1414
- .option("--content-type <type>", "HTTP content type")
1415
- .option(
1416
- "--timeout-ms <n>",
1417
- "HTTP timeout in milliseconds",
1418
- parseNonNegativeInteger,
1419
- )
1420
- .option("--var <key=value>", "HTTP variable", collectValues, [])
1421
- .option("--auth-mode <mode>", "HTTP auth mode")
1422
- .option("--connection <uuid>", "connection id for HTTP auth")
1423
- .option("--plugin <id>", "plugin id for execute_plugin")
1424
- .option("--arg <key=value>", "plugin argument", collectValues, [])
1425
- .option("--arg-json <key=json>", "plugin argument as JSON", collectValues, [])
1426
- .option("--to <recipient>", "send_message recipient (maps to fieldMapping.recipients)")
1427
- .option("--subject <text>", "send_message subject")
1428
- .option("--message <text>", "send_message body text (maps to fieldMapping.body)")
1429
- .option("--document-template <uuid>", "generate_document template id")
1430
- .option("--target-file-field <uuid>", "generate_document target FileField id")
1431
- .option("--raw <json>", "raw action JSON payload")
1432
- .option("--raw-file <path>", "path to raw action JSON payload")
1433
- .addHelpText("after", ACTION_TYPES_HELP);
1434
- }
1435
-
1436
- export function registerAutomationsCommands(
1437
- program: Command,
1438
- options: RegisterAutomationsCommandsOptions = {},
1439
- ): void {
1440
- const filePath = options.configPath ?? defaultConfigPath;
1441
- const stdout = getCommandIo(options.stdout);
1442
- const automations = program
1443
- .command("automations")
1444
- .description("Manage automations, triggers, actions, and connections");
1445
-
1446
- const folders = automations
1447
- .command("folders")
1448
- .description("Manage automation folders (flat: a folder only has a name, no order or properties)");
1449
-
1450
- folders
1451
- .command("list")
1452
- .description("List automation folders (non-root folders plus the root bucket, whose name is null)")
1453
- .action(async function action(this: Command) {
1454
- const { api, json } = createAutomationsApi(this, filePath);
1455
- const data = await api.listFolders();
1456
-
1457
- if (json) {
1458
- emitJson(stdout, "automations folders list", data, filePath);
1459
- return;
1460
- }
1461
-
1462
- printTable(folderRows(data), ["id", "name", "automationCount"], stdout);
1463
- });
1464
-
1465
- folders
1466
- .command("create")
1467
- .description("Create an automation folder")
1468
- .requiredOption("--name <name>", "folder name")
1469
- .action(async function action(this: Command, commandOptions: FolderMutationCommandOptions) {
1470
- const { api, json, readOnly } = createAutomationsApi(this, filePath);
1471
- ensureWritable("automations folders create", readOnly, "write");
1472
- const data = await api.createFolder(buildFolderMutationInput(commandOptions, true));
1473
-
1474
- if (json) {
1475
- emitJson(stdout, "automations folders create", data, filePath);
1476
- return;
1477
- }
1478
-
1479
- writeLine(stdout, `Created automation folder "${String(data.name ?? commandOptions.name)}".`);
1480
- });
1481
-
1482
- folders
1483
- .command("update")
1484
- .description("Update an automation folder")
1485
- .argument("<folder>", "folder UUID or exact name", parseUuidOrSlugSelector)
1486
- .option("--name <name>", "folder name")
1487
- .action(async function action(
1488
- this: Command,
1489
- folder: NormalizedUuidOrSlug,
1490
- commandOptions: FolderMutationCommandOptions,
1491
- ) {
1492
- const { api, json, readOnly } = createAutomationsApi(this, filePath);
1493
- ensureWritable("automations folders update", readOnly, "write");
1494
- const resolvedFolder = await resolveFolderSelector(api, folder);
1495
-
1496
- if (!resolvedFolder) {
1497
- throw new CliError("invalid_cli_usage", `Folder "${folder.value}" was not found.`);
1498
- }
1499
-
1500
- const data = await api.updateFolder(
1501
- resolvedFolder,
1502
- buildFolderMutationInput(commandOptions, false),
1503
- );
1504
-
1505
- if (json) {
1506
- emitJson(stdout, "automations folders update", data, filePath);
1507
- return;
1508
- }
1509
-
1510
- writeLine(stdout, `Updated automation folder "${String(data.name ?? folder.value)}".`);
1511
- });
1512
-
1513
- folders
1514
- .command("delete")
1515
- .description("Delete an automation folder")
1516
- .argument("<folder>", "folder UUID or exact name", parseUuidOrSlugSelector)
1517
- .action(async function action(this: Command, folder: NormalizedUuidOrSlug) {
1518
- const { api, json, readOnly } = createAutomationsApi(this, filePath);
1519
- ensureWritable("automations folders delete", readOnly, "destructive");
1520
- const resolvedFolder = await resolveFolderSelector(api, folder);
1521
-
1522
- if (!resolvedFolder) {
1523
- throw new CliError("invalid_cli_usage", `Folder "${folder.value}" was not found.`);
1524
- }
1525
-
1526
- const data = await api.deleteFolder(resolvedFolder);
1527
-
1528
- if (json) {
1529
- emitJson(stdout, "automations folders delete", data, filePath);
1530
- return;
1531
- }
1532
-
1533
- writeLine(stdout, `Deleted automation folder "${folder.value}".`);
1534
- });
1535
-
1536
- folders
1537
- .command("move")
1538
- .description("Move an automation into a folder")
1539
- .argument("<automation>", "automation UUID", parseUuidOrSlugSelector)
1540
- .requiredOption(
1541
- "--to <folder>",
1542
- "target folder UUID or exact name (use the root folder to move it out of any folder)",
1543
- parseUuidOrSlugSelector,
1544
- )
1545
- .action(async function action(
1546
- this: Command,
1547
- automation: NormalizedUuidOrSlug,
1548
- commandOptions: FolderMoveCommandOptions,
1549
- ) {
1550
- const { api, json, readOnly } = createAutomationsApi(this, filePath);
1551
- ensureWritable("automations folders move", readOnly, "write");
1552
-
1553
- if (!commandOptions.to) {
1554
- throw new CliError("invalid_cli_usage", 'Provide target folder via "--to".');
1555
- }
1556
-
1557
- const targetFolder = await resolveFolderSelector(api, commandOptions.to);
1558
-
1559
- if (!targetFolder) {
1560
- throw new CliError(
1561
- "invalid_cli_usage",
1562
- `Folder "${commandOptions.to.value}" was not found.`,
1563
- );
1564
- }
1565
-
1566
- const data = await api.moveAutomation({
1567
- automationId: automation.value,
1568
- targetFolderId: targetFolder.value,
1569
- });
1570
-
1571
- if (json) {
1572
- emitJson(stdout, "automations folders move", data, filePath);
1573
- return;
1574
- }
1575
-
1576
- writeLine(stdout, `Moved automation "${automation.value}" to folder "${targetFolder.value}".`);
1577
- });
1578
-
1579
- automations
1580
- .command("list")
1581
- .description("List automations")
1582
- .option("--folder <folder>", "filter by folder UUID or exact name", parseUuidOrSlugSelector)
1583
- .option("--status <status>", "filter by status")
1584
- .option("--trigger-type <type>", "filter by trigger type")
1585
- .option("--action-type <type>", "filter by action type")
1586
- .option("--plugin <id>", "filter by plugin id")
1587
- .option("--search <text>", "search text")
1588
- .action(async function action(this: Command, commandOptions: AutomationListCommandOptions) {
1589
- const { api, json } = createAutomationsApi(this, filePath);
1590
- const folder = await resolveFolderSelector(api, commandOptions.folder);
1591
- const data = await api.listAutomations({
1592
- folderId: folder?.value,
1593
- status: commandOptions.status,
1594
- triggerType: commandOptions.triggerType,
1595
- actionType: commandOptions.actionType,
1596
- pluginId: commandOptions.plugin,
1597
- search: commandOptions.search,
1598
- });
1599
-
1600
- if (json) {
1601
- emitJson(stdout, "automations list", data, filePath);
1602
- return;
1603
- }
1604
-
1605
- printTable(
1606
- automationRows(data),
1607
- ["id", "name", "status", "triggerType", "actionCount", ...FOLDER_TABLE_KEYS],
1608
- stdout,
1609
- );
1610
- });
1611
-
1612
- automations
1613
- .command("get")
1614
- .description("Get an automation")
1615
- .argument("<automation>", "automation UUID", parseUuidOrSlugSelector)
1616
- .action(async function action(this: Command, automation: NormalizedUuidOrSlug) {
1617
- const { api, json } = createAutomationsApi(this, filePath);
1618
- const data = await api.getAutomation(automation);
1619
-
1620
- if (json) {
1621
- emitJson(stdout, "automations get", data, filePath);
1622
- return;
1623
- }
1624
-
1625
- emitTable(stdout, data);
1626
- });
1627
-
1628
- addTriggerPayloadOptions(
1629
- automations
1630
- .command("create")
1631
- .description("Create an automation")
1632
- .requiredOption("--name <name>", "automation name")
1633
- .option("--description <text>", "automation description")
1634
- .option("--folder <folder>", "folder UUID or exact name", parseUuidOrSlugSelector),
1635
- ).action(async function action(
1636
- this: Command,
1637
- commandOptions: AutomationCreateCommandOptions,
1638
- ) {
1639
- const { api, json, readOnly } = createAutomationsApi(this, filePath);
1640
- ensureWritable("automations create", readOnly, "write");
1641
- const data = await api.createAutomation(await buildAutomationCreateInput(api, commandOptions));
1642
-
1643
- if (json) {
1644
- emitJson(stdout, "automations create", data, filePath);
1645
- return;
1646
- }
1647
-
1648
- writeLine(stdout, `Created automation "${String(data.name ?? commandOptions.name)}".`);
1649
- });
1650
-
1651
- automations
1652
- .command("update")
1653
- .description("Update an automation")
1654
- .argument("<automation>", "automation UUID", parseUuidOrSlugSelector)
1655
- .option("--name <name>", "automation name")
1656
- .option("--description <text>", "automation description")
1657
- .option("--folder <folder>", "folder UUID or exact name", parseUuidOrSlugSelector)
1658
- .option("--raw <json>", "raw automation JSON payload")
1659
- .option("--raw-file <path>", "path to raw automation JSON payload")
1660
- .action(async function action(
1661
- this: Command,
1662
- automation: NormalizedUuidOrSlug,
1663
- commandOptions: AutomationUpdateCommandOptions,
1664
- ) {
1665
- const { api, json, readOnly } = createAutomationsApi(this, filePath);
1666
- ensureWritable("automations update", readOnly, "write");
1667
- const data = await api.updateAutomation(
1668
- automation,
1669
- await buildAutomationUpdateInput(api, commandOptions),
1670
- );
1671
-
1672
- if (json) {
1673
- emitJson(stdout, "automations update", data, filePath);
1674
- return;
1675
- }
1676
-
1677
- writeLine(stdout, `Updated automation "${automation.value}".`);
1678
- });
1679
-
1680
- automations
1681
- .command("delete")
1682
- .description("Delete an automation")
1683
- .argument("<automation>", "automation UUID", parseUuidOrSlugSelector)
1684
- .action(async function action(this: Command, automation: NormalizedUuidOrSlug) {
1685
- const { api, json, readOnly } = createAutomationsApi(this, filePath);
1686
- ensureWritable("automations delete", readOnly, "destructive");
1687
- const data = await api.deleteAutomation(automation);
1688
-
1689
- if (json) {
1690
- emitJson(stdout, "automations delete", data, filePath);
1691
- return;
1692
- }
1693
-
1694
- writeLine(stdout, `Deleted automation "${automation.value}".`);
1695
- });
1696
-
1697
- automations
1698
- .command("activate")
1699
- .description("Activate an automation")
1700
- .argument("<automation>", "automation UUID", parseUuidOrSlugSelector)
1701
- .action(async function action(this: Command, automation: NormalizedUuidOrSlug) {
1702
- const { api, json, readOnly } = createAutomationsApi(this, filePath);
1703
- ensureWritable("automations activate", readOnly, "write");
1704
- const data = await api.activateAutomation(automation);
1705
-
1706
- if (json) {
1707
- emitJson(stdout, "automations activate", data, filePath);
1708
- return;
1709
- }
1710
-
1711
- writeLine(stdout, `Activated automation "${automation.value}".`);
1712
- });
1713
-
1714
- automations
1715
- .command("deactivate")
1716
- .description("Deactivate an automation")
1717
- .argument("<automation>", "automation UUID", parseUuidOrSlugSelector)
1718
- .action(async function action(this: Command, automation: NormalizedUuidOrSlug) {
1719
- const { api, json, readOnly } = createAutomationsApi(this, filePath);
1720
- ensureWritable("automations deactivate", readOnly, "write");
1721
- const data = await api.deactivateAutomation(automation);
1722
-
1723
- if (json) {
1724
- emitJson(stdout, "automations deactivate", data, filePath);
1725
- return;
1726
- }
1727
-
1728
- writeLine(stdout, `Deactivated automation "${automation.value}".`);
1729
- });
1730
-
1731
- const trigger = automations.command("trigger").description("Manage automation triggers");
1732
-
1733
- trigger
1734
- .command("get")
1735
- .description("Get the trigger for an automation")
1736
- .argument("<automation>", "automation UUID", parseUuidOrSlugSelector)
1737
- .action(async function action(this: Command, automation: NormalizedUuidOrSlug) {
1738
- const { api, json } = createAutomationsApi(this, filePath);
1739
- const data = await api.getTrigger(automation);
1740
-
1741
- if (json) {
1742
- emitJson(stdout, "automations trigger get", data, filePath);
1743
- return;
1744
- }
1745
-
1746
- emitTable(stdout, data);
1747
- });
1748
-
1749
- addTriggerPayloadOptions(
1750
- trigger
1751
- .command("set")
1752
- .description("Set the trigger for an automation")
1753
- .argument("<automation>", "automation UUID", parseUuidOrSlugSelector),
1754
- ).action(async function action(
1755
- this: Command,
1756
- automation: NormalizedUuidOrSlug,
1757
- commandOptions: TriggerPayloadCommandOptions,
1758
- ) {
1759
- const { api, json, readOnly } = createAutomationsApi(this, filePath);
1760
- ensureWritable("automations trigger set", readOnly, "write");
1761
- const data = await api.setTrigger(
1762
- automation,
1763
- buildTriggerInput(commandOptions, "required") as SetAutomationTriggerInput,
1764
- );
1765
-
1766
- if (json) {
1767
- emitJson(stdout, "automations trigger set", data, filePath);
1768
- return;
1769
- }
1770
-
1771
- writeLine(stdout, `Updated trigger for automation "${automation.value}".`);
1772
- });
1773
-
1774
- const actions = automations.command("actions").description("Manage automation actions");
1775
-
1776
- actions
1777
- .command("list")
1778
- .description("List automation actions")
1779
- .argument("<automation>", "automation UUID", parseUuidOrSlugSelector)
1780
- .action(async function action(this: Command, automation: NormalizedUuidOrSlug) {
1781
- const { api, json } = createAutomationsApi(this, filePath);
1782
- const data = await api.listActions(automation);
1783
-
1784
- if (json) {
1785
- emitJson(stdout, "automations actions list", data, filePath);
1786
- return;
1787
- }
1788
-
1789
- printTable(actionRows(data), ["id", "name", "type"], stdout);
1790
- });
1791
-
1792
- addActionPayloadOptions(
1793
- actions
1794
- .command("add")
1795
- .description("Add an action to an automation")
1796
- .argument("<automation>", "automation UUID", parseUuidOrSlugSelector),
1797
- ).action(async function action(
1798
- this: Command,
1799
- automation: NormalizedUuidOrSlug,
1800
- commandOptions: ActionMutationCommandOptions,
1801
- ) {
1802
- const { api, json, readOnly } = createAutomationsApi(this, filePath);
1803
- ensureWritable("automations actions add", readOnly, "write");
1804
- const data = await api.addAction(automation, buildActionInput(commandOptions, "create"));
1805
-
1806
- if (json) {
1807
- emitJson(stdout, "automations actions add", data, filePath);
1808
- return;
1809
- }
1810
-
1811
- writeLine(stdout, `Added action to automation "${automation.value}".`);
1812
- });
1813
-
1814
- addActionPayloadOptions(
1815
- actions
1816
- .command("update")
1817
- .description("Update an automation action")
1818
- .argument("<automation>", "automation UUID", parseUuidOrSlugSelector)
1819
- .argument("<action>", "action UUID"),
1820
- ).action(async function action(
1821
- this: Command,
1822
- automation: NormalizedUuidOrSlug,
1823
- actionId: string,
1824
- commandOptions: ActionMutationCommandOptions,
1825
- ) {
1826
- const { api, json, readOnly } = createAutomationsApi(this, filePath);
1827
- ensureWritable("automations actions update", readOnly, "write");
1828
- const data = await api.updateAction(automation, actionId, buildActionInput(commandOptions, "update"));
1829
-
1830
- if (json) {
1831
- emitJson(stdout, "automations actions update", data, filePath);
1832
- return;
1833
- }
1834
-
1835
- writeLine(stdout, `Updated action "${actionId}" on automation "${automation.value}".`);
1836
- });
1837
-
1838
- actions
1839
- .command("delete")
1840
- .description("Delete an automation action")
1841
- .argument("<automation>", "automation UUID", parseUuidOrSlugSelector)
1842
- .argument("<action>", "action UUID")
1843
- .action(async function action(
1844
- this: Command,
1845
- automation: NormalizedUuidOrSlug,
1846
- actionId: string,
1847
- ) {
1848
- const { api, json, readOnly } = createAutomationsApi(this, filePath);
1849
- ensureWritable("automations actions delete", readOnly, "destructive");
1850
- const data = await api.deleteAction(automation, actionId);
1851
-
1852
- if (json) {
1853
- emitJson(stdout, "automations actions delete", data, filePath);
1854
- return;
1855
- }
1856
-
1857
- writeLine(stdout, `Deleted action "${actionId}" from automation "${automation.value}".`);
1858
- });
1859
-
1860
- const flow = automations.command("flow").description("Manage automation flow wiring");
1861
-
1862
- flow
1863
- .command("get")
1864
- .description("Get the flow for an automation")
1865
- .argument("<automation>", "automation UUID", parseUuidOrSlugSelector)
1866
- .action(async function action(this: Command, automation: NormalizedUuidOrSlug) {
1867
- const { api, json } = createAutomationsApi(this, filePath);
1868
- const data = await api.getFlow(automation);
1869
-
1870
- if (json) {
1871
- emitJson(stdout, "automations flow get", data, filePath);
1872
- return;
1873
- }
1874
-
1875
- emitTable(stdout, data);
1876
- });
1877
-
1878
- flow
1879
- .command("set")
1880
- .description("Set the flow for an automation")
1881
- .argument("<automation>", "automation UUID", parseUuidOrSlugSelector)
1882
- .option("--entry <actionId>", "entry action id", collectValues, [])
1883
- .option("--transition <from:to>", "transition", collectValues, [])
1884
- .option("--raw <json>", "raw flow JSON payload")
1885
- .option("--raw-file <path>", "path to raw flow JSON payload")
1886
- .action(async function action(
1887
- this: Command,
1888
- automation: NormalizedUuidOrSlug,
1889
- commandOptions: FlowSetCommandOptions,
1890
- ) {
1891
- const { api, json, readOnly } = createAutomationsApi(this, filePath);
1892
- ensureWritable("automations flow set", readOnly, "write");
1893
- const data = await api.setFlow(automation, buildFlowInput(commandOptions));
1894
-
1895
- if (json) {
1896
- emitJson(stdout, "automations flow set", data, filePath);
1897
- return;
1898
- }
1899
-
1900
- writeLine(stdout, `Updated flow for automation "${automation.value}".`);
1901
- });
1902
-
1903
- const webhook = automations.command("webhook").description("Manage automation webhook config");
1904
-
1905
- webhook
1906
- .command("get")
1907
- .description("Get webhook configuration for an automation")
1908
- .argument("<automation>", "automation UUID", parseUuidOrSlugSelector)
1909
- .action(async function action(this: Command, automation: NormalizedUuidOrSlug) {
1910
- const { api, json } = createAutomationsApi(this, filePath);
1911
- const data = await api.getWebhook(automation);
1912
-
1913
- if (json) {
1914
- emitJson(stdout, "automations webhook get", data, filePath);
1915
- return;
1916
- }
1917
-
1918
- emitTable(stdout, data);
1919
- });
1920
-
1921
- webhook
1922
- .command("set")
1923
- .description("Set webhook configuration for an automation")
1924
- .argument("<automation>", "automation UUID", parseUuidOrSlugSelector)
1925
- .option("--active", "activate webhook processing")
1926
- .option("--idempotency-key-path <jsonpath>", "idempotency key JSONPath")
1927
- .option("--event-type-path <jsonpath>", "event type JSONPath")
1928
- .option("--allowed-event-type <value>", "allowed event type", collectValues, [])
1929
- .option("--field-mapping <json>", "field mapping JSON", collectValues, [])
1930
- .option("--field-mapping-file <path>", "path to field mapping JSON", collectValues, [])
1931
- .option("--raw <json>", "raw webhook JSON payload")
1932
- .option("--raw-file <path>", "path to raw webhook JSON payload")
1933
- .action(async function action(
1934
- this: Command,
1935
- automation: NormalizedUuidOrSlug,
1936
- commandOptions: WebhookSetCommandOptions,
1937
- ) {
1938
- const { api, json, readOnly } = createAutomationsApi(this, filePath);
1939
- ensureWritable("automations webhook set", readOnly, "write");
1940
- const data = await api.setWebhook(automation, buildWebhookInput(commandOptions));
1941
-
1942
- if (json) {
1943
- emitJson(stdout, "automations webhook set", data, filePath);
1944
- return;
1945
- }
1946
-
1947
- writeLine(stdout, `Updated webhook for automation "${automation.value}".`);
1948
- });
1949
-
1950
- webhook
1951
- .command("delete")
1952
- .description("Delete webhook configuration for an automation")
1953
- .argument("<automation>", "automation UUID", parseUuidOrSlugSelector)
1954
- .action(async function action(this: Command, automation: NormalizedUuidOrSlug) {
1955
- const { api, json, readOnly } = createAutomationsApi(this, filePath);
1956
- ensureWritable("automations webhook delete", readOnly, "destructive");
1957
- const data = await api.deleteWebhook(automation);
1958
-
1959
- if (json) {
1960
- emitJson(stdout, "automations webhook delete", data, filePath);
1961
- return;
1962
- }
1963
-
1964
- writeLine(stdout, `Deleted webhook for automation "${automation.value}".`);
1965
- });
1966
-
1967
- const plugins = automations.command("plugins").description("Inspect automation plugins");
1968
-
1969
- plugins
1970
- .command("list")
1971
- .description("List automation plugins")
1972
- .action(async function action(this: Command) {
1973
- const { api, json } = createAutomationsApi(this, filePath);
1974
- const data = await api.listPlugins();
1975
-
1976
- if (json) {
1977
- emitJson(stdout, "automations plugins list", data, filePath);
1978
- return;
1979
- }
1980
-
1981
- printTable(pluginRows(data), ["id", "name", "version"], stdout);
1982
- });
1983
-
1984
- plugins
1985
- .command("get")
1986
- .description("Get an automation plugin")
1987
- .argument("<plugin>", "plugin id")
1988
- .action(async function action(this: Command, pluginId: string) {
1989
- const { api, json } = createAutomationsApi(this, filePath);
1990
- const data = await api.getPlugin(pluginId);
1991
-
1992
- if (json) {
1993
- emitJson(stdout, "automations plugins get", data, filePath);
1994
- return;
1995
- }
1996
-
1997
- emitTable(stdout, data);
1998
- });
1999
-
2000
- const connections = automations
2001
- .command("connections")
2002
- .description("Manage automation connections");
2003
-
2004
- connections
2005
- .command("list")
2006
- .description("List automation connections")
2007
- .action(async function action(this: Command) {
2008
- const { api, json } = createAutomationsApi(this, filePath);
2009
- const data = await api.listConnections();
2010
-
2011
- if (json) {
2012
- emitJson(stdout, "automations connections list", data, filePath);
2013
- return;
2014
- }
2015
-
2016
- printTable(connectionRows(data), ["id", "name", "type", "scope", "active"], stdout);
2017
- });
2018
-
2019
- connections
2020
- .command("get")
2021
- .description("Get an automation connection")
2022
- .argument("<connection>", "connection UUID", parseUuidOrSlugSelector)
2023
- .action(async function action(this: Command, connection: NormalizedUuidOrSlug) {
2024
- const { api, json } = createAutomationsApi(this, filePath);
2025
- const data = await api.getConnection(connection);
2026
-
2027
- if (json) {
2028
- emitJson(stdout, "automations connections get", data, filePath);
2029
- return;
2030
- }
2031
-
2032
- emitTable(stdout, data);
2033
- });
2034
-
2035
- connections
2036
- .command("create")
2037
- .description("Create an automation connection")
2038
- .requiredOption("--name <name>", "connection name")
2039
- .requiredOption("--type <type>", "connection type")
2040
- .requiredOption("--scope <scope>", "connection scope")
2041
- .option("--owner-member <uuid>", "owner member id")
2042
- .option("--config <json>", "connection config JSON object")
2043
- .option("--config-file <path>", "path to connection config JSON object")
2044
- .option("--secret <key=value>", "connection secret", collectValues, [])
2045
- .option("--active", "activate connection")
2046
- .option("--raw <json>", "raw connection JSON payload")
2047
- .option("--raw-file <path>", "path to raw connection JSON payload")
2048
- .action(async function action(this: Command, commandOptions: ConnectionCreateCommandOptions) {
2049
- const { api, json, readOnly } = createAutomationsApi(this, filePath);
2050
- ensureWritable("automations connections create", readOnly, "write");
2051
- const data = await api.createConnection(buildConnectionCreateInput(commandOptions));
2052
-
2053
- if (json) {
2054
- emitJson(stdout, "automations connections create", data, filePath);
2055
- return;
2056
- }
2057
-
2058
- writeLine(stdout, `Created connection "${String(data.name ?? commandOptions.name)}".`);
2059
- });
2060
-
2061
- connections
2062
- .command("update")
2063
- .description("Update an automation connection")
2064
- .argument("<connection>", "connection UUID", parseUuidOrSlugSelector)
2065
- .option("--set <path=value>", "set a path to a string value", collectValues, [])
2066
- .option("--unset <path>", "unset a path", collectValues, [])
2067
- .option("--raw <json>", "raw connection patch JSON payload")
2068
- .option("--raw-file <path>", "path to raw connection patch JSON payload")
2069
- .action(async function action(
2070
- this: Command,
2071
- connection: NormalizedUuidOrSlug,
2072
- commandOptions: ConnectionUpdateCommandOptions,
2073
- ) {
2074
- const { api, json, readOnly } = createAutomationsApi(this, filePath);
2075
- ensureWritable("automations connections update", readOnly, "write");
2076
- const data = await api.updateConnection(connection, buildConnectionUpdateInput(commandOptions));
2077
-
2078
- if (json) {
2079
- emitJson(stdout, "automations connections update", data, filePath);
2080
- return;
2081
- }
2082
-
2083
- writeLine(stdout, `Updated connection "${connection.value}".`);
2084
- });
2085
-
2086
- connections
2087
- .command("delete")
2088
- .description("Delete an automation connection")
2089
- .argument("<connection>", "connection UUID", parseUuidOrSlugSelector)
2090
- .action(async function action(this: Command, connection: NormalizedUuidOrSlug) {
2091
- const { api, json, readOnly } = createAutomationsApi(this, filePath);
2092
- ensureWritable("automations connections delete", readOnly, "destructive");
2093
- const data = await api.deleteConnection(connection);
2094
-
2095
- if (json) {
2096
- emitJson(stdout, "automations connections delete", data, filePath);
2097
- return;
2098
- }
2099
-
2100
- writeLine(stdout, `Deleted connection "${connection.value}".`);
2101
- });
2102
- }