zinkee 0.1.4 → 0.1.8

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.
@@ -590,6 +590,94 @@ describe("schemas commands", () => {
590
590
  });
591
591
  });
592
592
 
593
+ it("normalizes schedule field shorthand payloads into the backend shape", async () => {
594
+ const configPath = createApiConfig();
595
+ const output = createStdoutBuffer();
596
+ const fetchMock = createFetchMock();
597
+ mockSchemaLookup(fetchMock);
598
+ fetchMock.mockResolvedValueOnce(
599
+ jsonResponse({
600
+ id: "field-5",
601
+ type: "ScheduleField",
602
+ slug: "monthly-recurrence",
603
+ label: "Monthly Recurrence",
604
+ }),
605
+ );
606
+ const program = buildProgram({ configPath, stdout: output.stdout });
607
+
608
+ await program.parseAsync([
609
+ "node",
610
+ "zinkee",
611
+ "--json",
612
+ "schemas",
613
+ "fields",
614
+ "create",
615
+ "contacts",
616
+ "--raw",
617
+ '{"type":"schedule","slug":"monthly-recurrence","label":"Monthly Recurrence","referenceField":"c52efe3f-2825-4c60-a7e3-34dd4c1ce043","beginningPeriodField":"d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f90","endingPeriodField":"d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f90","frequency":"monthly"}',
618
+ ]);
619
+
620
+ expect(JSON.parse(String(fetchMock.mock.calls[1]?.[1]?.body))).toEqual({
621
+ type: "ScheduleField",
622
+ slug: "monthly-recurrence",
623
+ label: "Monthly Recurrence",
624
+ config: {
625
+ referenceField: "c52efe3f-2825-4c60-a7e3-34dd4c1ce043",
626
+ beginningPeriodField: "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f90",
627
+ endingPeriodField: "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f90",
628
+ frequency: "monthly",
629
+ },
630
+ });
631
+ });
632
+
633
+ it("rejects schedule fields with invalid frequency before hitting the backend", async () => {
634
+ const configPath = createApiConfig();
635
+ const output = createStdoutBuffer();
636
+ const fetchMock = createFetchMock();
637
+ const program = buildProgram({ configPath, stdout: output.stdout });
638
+
639
+ await expect(
640
+ program.parseAsync([
641
+ "node",
642
+ "zinkee",
643
+ "schemas",
644
+ "fields",
645
+ "create",
646
+ "contacts",
647
+ "--raw",
648
+ '{"type":"ScheduleField","slug":"weekly","label":"Weekly","config":{"referenceField":"c52efe3f-2825-4c60-a7e3-34dd4c1ce043","beginningPeriodField":"d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f90","endingPeriodField":"d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f90","frequency":"weekly"}}',
649
+ ]),
650
+ ).rejects.toThrowError(
651
+ 'Schedule "frequency" must be "monthly" or "daily" (received "weekly").',
652
+ );
653
+
654
+ expect(fetchMock).not.toHaveBeenCalled();
655
+ });
656
+
657
+ it("rejects schedule fields missing required config fields", async () => {
658
+ const configPath = createApiConfig();
659
+ const output = createStdoutBuffer();
660
+ const fetchMock = createFetchMock();
661
+ const program = buildProgram({ configPath, stdout: output.stdout });
662
+
663
+ await expect(
664
+ program.parseAsync([
665
+ "node",
666
+ "zinkee",
667
+ "schemas",
668
+ "fields",
669
+ "create",
670
+ "contacts",
671
+ "--raw",
672
+ '{"type":"ScheduleField","slug":"recurrence","label":"Recurrence","config":{"frequency":"daily"}}',
673
+ ]),
674
+ ).rejects.toThrowError(
675
+ /Schedule fields require "referenceField", "beginningPeriodField", "endingPeriodField" and "frequency"/,
676
+ );
677
+
678
+ expect(fetchMock).not.toHaveBeenCalled();
679
+ });
680
+
593
681
  it("rejects unsupported field types before making network requests", async () => {
594
682
  const configPath = createApiConfig();
595
683
  const output = createStdoutBuffer();
@@ -612,7 +700,7 @@ describe("schemas commands", () => {
612
700
  "Bad",
613
701
  ]),
614
702
  ).rejects.toThrowError(
615
- 'Unsupported field type "InvalidField". Valid types: text, number, date, option, file, reference, formula, member, autoincremental.',
703
+ 'Unsupported field type "InvalidField". Valid types: text, number, date, option, file, reference, formula, member, autoincremental, schedule.',
616
704
  );
617
705
 
618
706
  expect(fetchMock).not.toHaveBeenCalled();
@@ -629,13 +717,12 @@ describe("schemas commands", () => {
629
717
  expect(help).toContain("member");
630
718
  expect(help).toContain("autoincremental");
631
719
  expect(help).toContain("Raw payload shapes by type:");
632
- expect(help).toContain('"type":"number","slug":"price-eur"');
720
+ expect(help).toContain('"type":"NumberField","slug":"quantity"');
633
721
  expect(help).toContain('"defaultValue":"0"');
634
- expect(help).toContain('"symbolEnabled":true');
635
722
  expect(help).toContain('- member: Workspace member field.');
636
- expect(help).toContain('"type":"member","slug":"owner","label":"Owner"');
723
+ expect(help).toContain('"type":"MemberField","slug":"owner","label":"Owner"');
637
724
  expect(help).toContain('- option: Dropdown/select field with options.');
638
- expect(help).toContain('"type":"option","slug":"status","label":"Status"');
725
+ expect(help).toContain('"type":"OptionField","slug":"status","label":"Status"');
639
726
  expect(help).toContain('Use "zinkee --example schemas fields create --type <type>"');
640
727
  });
641
728
 
@@ -692,7 +779,7 @@ describe("schemas commands", () => {
692
779
  '{"type":"option","slug":"status","label":"Status","options":[{"id":1,"name":"Open","color":"default"}]}',
693
780
  ]),
694
781
  ).rejects.toThrowError(
695
- 'Invalid payload for field type "option". Expected raw shape: {"type":"option","slug":"status","label":"Status","config":{"options":[{"id":1,"name":"Open","color":"default"},{"id":2,"name":"Closed","color":"success"}]}}',
782
+ 'Invalid payload for field type "option". Expected raw shape: {"type":"OptionField","slug":"status","label":"Status","config":{"options":[{"id":1,"name":"Open","color":"default"},{"id":2,"name":"Closed","color":"success"}]}}',
696
783
  );
697
784
  });
698
785
 
package/src/index.test.ts CHANGED
@@ -85,12 +85,12 @@ describe("project bootstrap", () => {
85
85
  );
86
86
 
87
87
  expect(output).toContain('zinkee --json schemas fields create contacts --raw');
88
- expect(output).toContain('"type":"option"');
89
- expect(output).not.toContain('"type":"text"');
90
- expect(output).toContain("Create a option field");
88
+ expect(output).toContain('"type":"OptionField"');
89
+ expect(output).not.toContain('"type":"TextField"');
90
+ expect(output).toContain("Option field (select dropdown)");
91
91
  });
92
92
 
93
- it("shows both number creation examples, including Moneda formatting", async () => {
93
+ it("shows every backend number variant, including currency formatting", async () => {
94
94
  let output = "";
95
95
 
96
96
  await showExamples(
@@ -105,9 +105,14 @@ describe("project bootstrap", () => {
105
105
  { type: "number" },
106
106
  );
107
107
 
108
- expect(output).toContain('"slug":"amount"');
108
+ expect(output).toContain('"slug":"quantity"');
109
+ expect(output).toContain('"slug":"amount-eur"');
110
+ expect(output).toContain('"slug":"amount-usd"');
109
111
  expect(output).toContain('"symbolEnabled":true');
112
+ expect(output).toContain('"symbolPosition":"before"');
110
113
  expect(output).toContain('"symbolPosition":"right"');
114
+ expect(output).toContain('"format":"percentage"');
115
+ expect(output).toContain('"durationFormat":"HH:MM"');
111
116
  });
112
117
 
113
118
  it("shows automation examples with descriptions and equivalent payloads", async () => {
@@ -120,7 +125,7 @@ describe("project bootstrap", () => {
120
125
  },
121
126
  });
122
127
 
123
- expect(output).toContain("Create a scheduled automation with a six-field cron expression");
128
+ expect(output).toContain("Create a scheduled automation");
124
129
  expect(output).toContain("Equivalent JSON payload:");
125
130
  expect(output).toContain('"type": "scheduled"');
126
131
  expect(output).toContain('"cronExpression": "0 15 10 * * 1"');
@@ -3,48 +3,93 @@ import { describe, expect, it } from "vitest";
3
3
  import { parseExpression } from "./expressions.js";
4
4
 
5
5
  describe("parseExpression", () => {
6
- it("parses space-separated equality filters", () => {
7
- expect(parseExpression("status eq active")).toEqual({
8
- field: "status",
9
- comparator: "eq",
10
- value: "active",
11
- raw: "status eq active",
6
+ describe("scalar comparators", () => {
7
+ it.each([
8
+ ["eq", "status eq active", "status", "active"],
9
+ ["neq", "status neq archived", "status", "archived"],
10
+ ["gt", "amount gt 1000", "amount", "1000"],
11
+ ["gte", "amount gte 500", "amount", "500"],
12
+ ["lt", "amount lt 100", "amount", "100"],
13
+ ["lte", "due-date lte 2026-12-31T23:59:59Z", "due-date", "2026-12-31T23:59:59Z"],
14
+ ["contains", "contact-name contains Lovelace", "contact-name", "Lovelace"],
15
+ ])("parses %s expressions", (comparator, input, field, value) => {
16
+ expect(parseExpression(input)).toEqual({
17
+ field,
18
+ comparator,
19
+ value,
20
+ raw: input,
21
+ });
22
+ });
23
+
24
+ it("preserves spaces inside contains values", () => {
25
+ expect(parseExpression("email contains @acme.com")).toEqual({
26
+ field: "email",
27
+ comparator: "contains",
28
+ value: "@acme.com",
29
+ raw: "email contains @acme.com",
30
+ });
12
31
  });
13
32
  });
14
33
 
15
- it("parses contains filters with spaces in the value", () => {
16
- expect(parseExpression("email contains @acme.com")).toEqual({
17
- field: "email",
18
- comparator: "contains",
19
- value: "@acme.com",
20
- raw: "email contains @acme.com",
34
+ describe("unary comparators", () => {
35
+ it.each([
36
+ ["is_empty", "due-date is_empty", "due-date"],
37
+ ["is_not_empty", "contact-email is_not_empty", "contact-email"],
38
+ ])("parses %s expressions without a value", (comparator, input, field) => {
39
+ expect(parseExpression(input)).toEqual({
40
+ field,
41
+ comparator,
42
+ value: null,
43
+ raw: input,
44
+ });
21
45
  });
22
46
  });
23
47
 
24
- it("parses unary null checks", () => {
25
- expect(parseExpression("closedAt is-null")).toEqual({
26
- field: "closedAt",
27
- comparator: "is-null",
28
- value: null,
29
- raw: "closedAt is-null",
48
+ describe("list comparators", () => {
49
+ it("parses in expressions with a comma-separated value", () => {
50
+ expect(parseExpression("ownerId in a,b,c")).toEqual({
51
+ field: "ownerId",
52
+ comparator: "in",
53
+ value: ["a", "b", "c"],
54
+ raw: "ownerId in a,b,c",
55
+ });
30
56
  });
31
57
  });
32
58
 
33
- it("parses repeated value lists", () => {
34
- expect(parseExpression("ownerId in a,b,c")).toEqual({
35
- field: "ownerId",
36
- comparator: "in",
37
- value: ["a", "b", "c"],
38
- raw: "ownerId in a,b,c",
59
+ describe("delimiters", () => {
60
+ it("parses colon-delimited expressions", () => {
61
+ expect(parseExpression("amount:gt:1000")).toEqual({
62
+ field: "amount",
63
+ comparator: "gt",
64
+ value: "1000",
65
+ raw: "amount:gt:1000",
66
+ });
39
67
  });
40
68
  });
41
69
 
42
- it("parses colon-delimited condition expressions", () => {
43
- expect(parseExpression("amount:gt:1000")).toEqual({
44
- field: "amount",
45
- comparator: "gt",
46
- value: "1000",
47
- raw: "amount:gt:1000",
70
+ describe("errors", () => {
71
+ it("rejects unknown comparators", () => {
72
+ expect(() => parseExpression("status ne archived")).toThrow(
73
+ 'Unsupported comparator "ne" in expression "status ne archived".',
74
+ );
75
+ });
76
+
77
+ it("rejects scalar comparators without a value", () => {
78
+ expect(() => parseExpression("status eq")).toThrow(
79
+ 'Comparator "eq" requires a value.',
80
+ );
81
+ });
82
+
83
+ it("rejects unary comparators that carry a value", () => {
84
+ expect(() => parseExpression("due-date is_empty 2026-01-01")).toThrow(
85
+ 'Comparator "is_empty" does not accept a value.',
86
+ );
87
+ });
88
+
89
+ it("rejects in expressions with no values", () => {
90
+ expect(() => parseExpression("ownerId in")).toThrow(
91
+ 'Comparator "in" requires at least one value.',
92
+ );
48
93
  });
49
94
  });
50
95
  });
@@ -1,4 +1,14 @@
1
- export type ExpressionComparator = "eq" | "contains" | "gt" | "is-null" | "in";
1
+ export type ExpressionComparator =
2
+ | "eq"
3
+ | "neq"
4
+ | "gt"
5
+ | "gte"
6
+ | "lt"
7
+ | "lte"
8
+ | "contains"
9
+ | "in"
10
+ | "is_empty"
11
+ | "is_not_empty";
2
12
 
3
13
  export type ExpressionValue = string | readonly string[] | null;
4
14
 
@@ -11,12 +21,19 @@ export interface ParsedExpression {
11
21
 
12
22
  const supportedComparators = new Set<ExpressionComparator>([
13
23
  "eq",
14
- "contains",
24
+ "neq",
15
25
  "gt",
16
- "is-null",
26
+ "gte",
27
+ "lt",
28
+ "lte",
29
+ "contains",
17
30
  "in",
31
+ "is_empty",
32
+ "is_not_empty",
18
33
  ]);
19
34
 
35
+ const noValueComparators = new Set<ExpressionComparator>(["is_empty", "is_not_empty"]);
36
+
20
37
  function parseColonExpression(input: string): [string, string, string] {
21
38
  const parts = input.split(":");
22
39
 
@@ -60,7 +77,7 @@ function parseSpaceExpression(input: string): [string, string, string] {
60
77
  }
61
78
 
62
79
  function normalizeValue(comparator: ExpressionComparator, rawValue: string): ExpressionValue {
63
- if (comparator === "is-null") {
80
+ if (noValueComparators.has(comparator)) {
64
81
  if (rawValue.length > 0) {
65
82
  throw new Error(`Comparator "${comparator}" does not accept a value.`);
66
83
  }
package/src/program.ts CHANGED
@@ -7,6 +7,7 @@ import { registerAutomationsCommands } from "./commands/automations.js";
7
7
  import { registerCommentsCommands } from "./commands/comments.js";
8
8
  import { registerDisplaysCommands } from "./commands/displays.js";
9
9
  import { registerFilesCommands } from "./commands/files.js";
10
+ import { registerLogsCommands } from "./commands/logs.js";
10
11
  import { registerNavigationCommands } from "./commands/navigation.js";
11
12
  import { registerProfilesCommands } from "./commands/profiles.js";
12
13
  import { registerRecordsCommands } from "./commands/records.js";
@@ -50,6 +51,7 @@ export function buildProgram(options: BuildProgramOptions = {}): Command {
50
51
  registerSchemasCommands(program, options);
51
52
  registerRecordsCommands(program, options);
52
53
  registerCommentsCommands(program, options);
54
+ registerLogsCommands(program, options);
53
55
  registerTeamspaceCommands(program, options);
54
56
 
55
57
  return program;
@@ -21,18 +21,54 @@ describe("getCommandExamples", () => {
21
21
  const examples = getCommandExamples("schemas fields create", { type: "member" });
22
22
 
23
23
  expect(examples).toHaveLength(1);
24
- expect(examples[0]?.command).toContain('"type":"member"');
25
- expect(examples[0]?.description).toContain("member field");
24
+ expect(examples[0]?.command).toContain('"type":"MemberField"');
25
+ expect(examples[0]?.description).toBe("Member field");
26
26
  });
27
27
 
28
- it("returns both plain and currency-style examples for number fields", () => {
29
- const examples = getCommandExamples("schemas fields create", { type: "number" });
28
+ it("returns monthly and daily variants for schedule fields", () => {
29
+ const examples = getCommandExamples("schemas fields create", { type: "schedule" });
30
30
 
31
31
  expect(examples).toHaveLength(2);
32
- expect(examples[0]?.command).toContain('"slug":"amount"');
33
- expect(examples[1]?.command).toContain('"symbolEnabled":true');
34
- expect(examples[1]?.command).toContain('"symbolValue":" €"');
35
- expect(examples[1]?.description).toContain("Moneda");
32
+ expect(examples.map((example) => example.description)).toEqual([
33
+ "Schedule field (monthly)",
34
+ "Schedule field (daily)",
35
+ ]);
36
+ expect(examples[0]?.command).toContain('"type":"ScheduleField"');
37
+ expect(examples[0]?.command).toContain('"frequency":"monthly"');
38
+ expect(examples[1]?.command).toContain('"frequency":"daily"');
39
+ });
40
+
41
+ it("covers every backend number field variant", () => {
42
+ const examples = getCommandExamples("schemas fields create", { type: "number" });
43
+
44
+ expect(examples).toHaveLength(6);
45
+
46
+ const descriptions = examples.map((example) => example.description);
47
+ expect(descriptions).toEqual([
48
+ "Number field (plain integer)",
49
+ "Number field (decimal with thousands)",
50
+ "Number field (currency € prefix)",
51
+ "Number field (currency $ suffix)",
52
+ "Number field (percentage)",
53
+ "Number field (duration HH:MM)",
54
+ ]);
55
+
56
+ expect(examples[0]?.command).toContain('"slug":"quantity"');
57
+ expect(examples[0]?.command).toContain('"format":"number"');
58
+
59
+ const eurCurrency = examples[2]?.command ?? "";
60
+ expect(eurCurrency).toContain('"format":"currency"');
61
+ expect(eurCurrency).toContain('"symbolEnabled":true');
62
+ expect(eurCurrency).toContain('"symbolPosition":"before"');
63
+ expect(eurCurrency).toContain('"symbolValue":"€"');
64
+
65
+ const usdCurrency = examples[3]?.command ?? "";
66
+ expect(usdCurrency).toContain('"symbolPosition":"right"');
67
+ expect(usdCurrency).toContain('"symbolValue":" $"');
68
+
69
+ expect(examples[4]?.command).toContain('"format":"percentage"');
70
+ expect(examples[5]?.command).toContain('"format":"duration"');
71
+ expect(examples[5]?.command).toContain('"durationFormat":"HH:MM"');
36
72
  });
37
73
 
38
74
  it("returns canonical automation creation examples", () => {
@@ -64,4 +100,154 @@ describe("getCommandExamples", () => {
64
100
  expect(actionExamples.some((example) => example.raw?.includes('"rawValue"'))).toBe(true);
65
101
  expect(webhookExamples[0]?.raw).toContain("idempotencyKeyJsonPath");
66
102
  });
103
+
104
+ it("covers every records query operator documented by the backend", () => {
105
+ const examples = getCommandExamples("records query");
106
+ const backendOperators = [
107
+ "eq",
108
+ "neq",
109
+ "gt",
110
+ "gte",
111
+ "lt",
112
+ "lte",
113
+ "contains",
114
+ "in",
115
+ "is_empty",
116
+ "is_not_empty",
117
+ ];
118
+
119
+ for (const op of backendOperators) {
120
+ expect(
121
+ examples.some((example) => example.raw?.includes(`"op": "${op}"`)),
122
+ `expected an example with op "${op}"`,
123
+ ).toBe(true);
124
+ }
125
+ });
126
+
127
+ it("offers both a minimal and a multi-type records create example", () => {
128
+ const examples = getCommandExamples("records create");
129
+
130
+ expect(examples).toHaveLength(2);
131
+ expect(examples[0]?.raw).toContain('"contact-name"');
132
+ expect(examples[1]?.raw).toContain('"customer-ref"');
133
+ expect(examples[1]?.raw).toContain('"owner"');
134
+ });
135
+
136
+ it("provides full/minimal/folder variants for schemas create", () => {
137
+ const examples = getCommandExamples("schemas create");
138
+
139
+ expect(examples).toHaveLength(3);
140
+ expect(examples.some((example) => example.raw?.includes('"folderId"'))).toBe(true);
141
+ expect(examples.every((example) => example.command.startsWith("zinkee --json schemas create"))).toBe(
142
+ true,
143
+ );
144
+ });
145
+
146
+ it("covers all widget types in displays widgets create", () => {
147
+ const examples = getCommandExamples("displays widgets create");
148
+ const rawPayloads = examples.map((example) => example.raw ?? "");
149
+
150
+ expect(examples.length).toBeGreaterThanOrEqual(7);
151
+ for (const widgetType of ["TABLE_OR_SUBTABLE", "DETAIL", "KPI", "CHART", "TIMELINE", "KANBAN", "HIERARCHY"]) {
152
+ expect(rawPayloads.some((raw) => raw.includes(`"type": "${widgetType}"`))).toBe(true);
153
+ }
154
+ });
155
+
156
+ it("covers REFERENCE, MEMBER and DATE variants in displays variables create", () => {
157
+ const examples = getCommandExamples("displays variables create");
158
+ const rawPayloads = examples.map((example) => example.raw ?? "");
159
+
160
+ expect(examples.length).toBeGreaterThanOrEqual(6);
161
+ for (const variableType of ["REFERENCE", "MEMBER", "DATE"]) {
162
+ expect(rawPayloads.some((raw) => raw.includes(`"type": "${variableType}"`))).toBe(true);
163
+ }
164
+ expect(rawPayloads.some((raw) => raw.includes('"dynamicOption": "thisMonth"'))).toBe(true);
165
+ });
166
+
167
+ it("covers SCHEMA, WORKFLOW and DISPLAY variants in navigation resources move", () => {
168
+ const examples = getCommandExamples("navigation resources move");
169
+ const rawPayloads = examples.map((example) => example.raw ?? "");
170
+
171
+ expect(examples.length).toBeGreaterThanOrEqual(5);
172
+ for (const resourceType of ["SCHEMA", "WORKFLOW", "DISPLAY", "SCHEDULE"]) {
173
+ expect(rawPayloads.some((raw) => raw.includes(`"resourceType": "${resourceType}"`))).toBe(true);
174
+ }
175
+ });
176
+
177
+ it("covers slug and UUID variants in teamspace resources publish", () => {
178
+ const examples = getCommandExamples("teamspace resources publish");
179
+
180
+ expect(examples.length).toBeGreaterThanOrEqual(4);
181
+ expect(examples.some((example) => example.command.includes("customer-records"))).toBe(true);
182
+ expect(examples.some((example) => example.command.includes("2eb8bdcf-6d9c-4fbb-9018-7fa8180f4c5e"))).toBe(
183
+ true,
184
+ );
185
+ });
186
+
187
+ it("enriches displays list with the documented response body", () => {
188
+ const examples = getCommandExamples("displays list");
189
+
190
+ expect(examples).toHaveLength(1);
191
+ expect(examples[0]?.response).toContain('"layoutTemplate": "widget_row_tabs"');
192
+ expect(examples[0]?.response).toContain('"layoutTemplate": "freeform"');
193
+ expect(examples[0]?.response).toContain('"layoutTemplate": "grid"');
194
+ });
195
+
196
+ it("attaches a response body to schemas create and teamspace resources publish", () => {
197
+ const schemaExamples = getCommandExamples("schemas create");
198
+ const publishExamples = getCommandExamples("teamspace resources publish");
199
+
200
+ expect(schemaExamples[0]?.response).toContain('"schemaId"');
201
+ expect(publishExamples[0]?.response).toContain('"targetFolder"');
202
+ expect(publishExamples[0]?.response).toContain('"previousFolder"');
203
+ });
204
+
205
+ it("attaches a rich response body to displays get", () => {
206
+ const examples = getCommandExamples("displays get");
207
+
208
+ expect(examples).toHaveLength(1);
209
+ expect(examples[0]?.response).toContain('"widgets"');
210
+ expect(examples[0]?.response).toContain('"variables"');
211
+ expect(examples[0]?.response).toContain('"crossWidgetFilter"');
212
+ expect(examples[0]?.response).toContain('"navigationTargets"');
213
+ });
214
+
215
+ it("attaches responses to automations reads and mutations", () => {
216
+ expect(getCommandExamples("automations list")[0]?.response).toContain('"triggerType"');
217
+ expect(getCommandExamples("automations get")[0]?.response).toContain('"trigger"');
218
+ expect(getCommandExamples("automations create")[0]?.response).toContain('"automationId"');
219
+ expect(getCommandExamples("automations create")[0]?.response).toContain('"webhookEndpoint"');
220
+ expect(getCommandExamples("automations webhook set")[0]?.response).toContain('"endpointUrl"');
221
+ expect(getCommandExamples("automations folders create")[0]?.response).toContain('"automationCount": 0');
222
+ expect(getCommandExamples("automations connections create")[0]?.response).toContain(
223
+ '"ownerMemberId"',
224
+ );
225
+ });
226
+
227
+ it("covers every profiles and config subcommand with an example that emits JSON", () => {
228
+ const profileAndConfigCommands = [
229
+ "profiles list",
230
+ "profiles get",
231
+ "profiles use",
232
+ "profiles add",
233
+ "profiles update",
234
+ "profiles remove",
235
+ "config path",
236
+ "config show",
237
+ "config validate",
238
+ ];
239
+
240
+ for (const commandName of profileAndConfigCommands) {
241
+ const examples = getCommandExamples(commandName);
242
+ expect(examples.length, `${commandName} has no examples`).toBeGreaterThan(0);
243
+ for (const example of examples) {
244
+ expect(example.command.startsWith("zinkee "), `${commandName}: ${example.command}`).toBe(true);
245
+ expect(example.response, `${commandName} missing response`).toBeTruthy();
246
+ }
247
+ }
248
+
249
+ expect(getCommandExamples("profiles list")[0]?.response).toContain('"apiKey": "********cdef"');
250
+ expect(getCommandExamples("config show")[0]?.response).toContain('"defaultProfile": "prod"');
251
+ expect(getCommandExamples("config validate")[0]?.response).toContain('"valid": true');
252
+ });
67
253
  });