zinkee 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/AGENTS.md +46 -0
  2. package/README.md +395 -0
  3. package/dist/index.d.ts +13 -0
  4. package/dist/index.js +7142 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +32 -0
  7. package/src/api/automations.ts +403 -0
  8. package/src/api/comments.ts +51 -0
  9. package/src/api/displays.ts +276 -0
  10. package/src/api/files.ts +50 -0
  11. package/src/api/navigation.ts +71 -0
  12. package/src/api/records.ts +167 -0
  13. package/src/api/schemas.ts +130 -0
  14. package/src/api/teamspace.ts +110 -0
  15. package/src/cli-examples.ts +175 -0
  16. package/src/cli-runner.ts +73 -0
  17. package/src/client.test.ts +128 -0
  18. package/src/client.ts +243 -0
  19. package/src/command-registry.ts +749 -0
  20. package/src/commands/automations.test.ts +728 -0
  21. package/src/commands/automations.ts +2013 -0
  22. package/src/commands/comments.test.ts +214 -0
  23. package/src/commands/comments.ts +303 -0
  24. package/src/commands/config.test.ts +81 -0
  25. package/src/commands/config.ts +150 -0
  26. package/src/commands/displays.test.ts +266 -0
  27. package/src/commands/displays.ts +755 -0
  28. package/src/commands/files.test.ts +284 -0
  29. package/src/commands/files.ts +280 -0
  30. package/src/commands/navigation.test.ts +214 -0
  31. package/src/commands/navigation.ts +348 -0
  32. package/src/commands/profiles.test.ts +82 -0
  33. package/src/commands/profiles.ts +281 -0
  34. package/src/commands/records.test.ts +367 -0
  35. package/src/commands/records.ts +726 -0
  36. package/src/commands/schemas.test.ts +748 -0
  37. package/src/commands/schemas.ts +746 -0
  38. package/src/commands/teamspace.test.ts +229 -0
  39. package/src/commands/teamspace.ts +540 -0
  40. package/src/config.test.ts +165 -0
  41. package/src/config.ts +248 -0
  42. package/src/index.test.ts +140 -0
  43. package/src/index.ts +8 -0
  44. package/src/parsers/expressions.test.ts +50 -0
  45. package/src/parsers/expressions.ts +111 -0
  46. package/src/parsers/kv.test.ts +35 -0
  47. package/src/parsers/kv.ts +49 -0
  48. package/src/parsers/selectors.test.ts +23 -0
  49. package/src/parsers/selectors.ts +29 -0
  50. package/src/program.ts +56 -0
  51. package/src/runtime-context.ts +18 -0
  52. package/src/types.ts +71 -0
  53. package/src/utils/errors.test.ts +48 -0
  54. package/src/utils/errors.ts +183 -0
  55. package/src/utils/examples.test.ts +67 -0
  56. package/src/utils/examples.ts +855 -0
  57. package/src/utils/output.test.ts +39 -0
  58. package/src/utils/output.ts +124 -0
  59. package/src/utils/schema-fields.ts +529 -0
  60. package/tsconfig.json +20 -0
  61. package/tsup.config.ts +13 -0
package/src/client.ts ADDED
@@ -0,0 +1,243 @@
1
+ export interface ApiClientOptions {
2
+ baseUrl: string;
3
+ apiKey?: string;
4
+ timeoutMs?: number;
5
+ fetch?: typeof fetch;
6
+ }
7
+
8
+ export type ApiClientErrorCode =
9
+ | "network_error"
10
+ | "http_error"
11
+ | "timeout"
12
+ | "invalid_json";
13
+
14
+ export interface ApiClientErrorDetails {
15
+ url: string;
16
+ method: string;
17
+ status?: number;
18
+ responseBody?: unknown;
19
+ responseText?: string;
20
+ cause?: unknown;
21
+ }
22
+
23
+ export class ApiClientError extends Error {
24
+ override name = "ApiClientError";
25
+
26
+ readonly code: ApiClientErrorCode;
27
+
28
+ readonly url: string;
29
+
30
+ readonly method: string;
31
+
32
+ readonly status?: number;
33
+
34
+ readonly responseBody?: unknown;
35
+
36
+ readonly responseText?: string;
37
+
38
+ override readonly cause?: unknown;
39
+
40
+ constructor(
41
+ code: ApiClientErrorCode,
42
+ message: string,
43
+ details: ApiClientErrorDetails,
44
+ ) {
45
+ super(message);
46
+ this.code = code;
47
+ this.url = details.url;
48
+ this.method = details.method;
49
+ this.status = details.status;
50
+ this.responseBody = details.responseBody;
51
+ this.responseText = details.responseText;
52
+ this.cause = details.cause;
53
+ }
54
+ }
55
+
56
+ export interface ApiRequestOptions extends RequestInit {
57
+ timeoutMs?: number;
58
+ }
59
+
60
+ function isJsonContentType(contentType: string | null): boolean {
61
+ return contentType ? /(^|\/|\+)json\b/i.test(contentType) : false;
62
+ }
63
+
64
+ function looksLikeJson(text: string): boolean {
65
+ const trimmed = text.trim();
66
+
67
+ return (
68
+ trimmed.startsWith("{") ||
69
+ trimmed.startsWith("[") ||
70
+ trimmed === "null" ||
71
+ trimmed === "true" ||
72
+ trimmed === "false" ||
73
+ /^-?(0|[1-9]\d*)(\.\d+)?([eE][+-]?\d+)?$/.test(trimmed) ||
74
+ trimmed.startsWith('"')
75
+ );
76
+ }
77
+
78
+ function parseJsonBody<T>(text: string, url: string, method: string): T {
79
+ const trimmed = text.trim();
80
+
81
+ if (trimmed.length === 0) {
82
+ return undefined as T;
83
+ }
84
+
85
+ try {
86
+ return JSON.parse(trimmed) as T;
87
+ } catch (cause) {
88
+ throw new ApiClientError("invalid_json", `Failed to parse JSON response from ${method} ${url}.`, {
89
+ url,
90
+ method,
91
+ responseText: text,
92
+ cause,
93
+ });
94
+ }
95
+ }
96
+
97
+ function parseErrorBody(text: string, contentType: string | null): unknown {
98
+ if (text.trim().length === 0) {
99
+ return undefined;
100
+ }
101
+
102
+ if (!isJsonContentType(contentType) && !looksLikeJson(text)) {
103
+ return text;
104
+ }
105
+
106
+ try {
107
+ return JSON.parse(text);
108
+ } catch {
109
+ return text;
110
+ }
111
+ }
112
+
113
+ export class ApiClient {
114
+ private readonly fetchImpl: typeof fetch;
115
+
116
+ constructor(private readonly options: ApiClientOptions) {
117
+ this.fetchImpl = options.fetch ?? fetch;
118
+ }
119
+
120
+ async getJson<T>(path: string, init: ApiRequestOptions = {}): Promise<T> {
121
+ return this.requestJson<T>(path, { ...init, method: "GET" });
122
+ }
123
+
124
+ async requestJson<T>(path: string, init: ApiRequestOptions = {}): Promise<T> {
125
+ const response = await this.request(path, init, "application/json");
126
+
127
+ if (!response.ok) {
128
+ const text = await response.text();
129
+ throw this.buildHttpError(response, text, init, "requestJson");
130
+ }
131
+
132
+ const text = await response.text();
133
+ return parseJsonBody<T>(text, response.url, init.method ?? "GET");
134
+ }
135
+
136
+ async requestBinary(path: string, init: ApiRequestOptions = {}): Promise<ArrayBuffer> {
137
+ const response = await this.request(path, init);
138
+
139
+ if (!response.ok) {
140
+ const text = await response.text();
141
+ throw this.buildHttpError(response, text, init, "requestBinary");
142
+ }
143
+
144
+ return response.arrayBuffer();
145
+ }
146
+
147
+ private buildUrl(path: string): string {
148
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
149
+
150
+ return new URL(normalizedPath, this.options.baseUrl).toString();
151
+ }
152
+
153
+ private async request(
154
+ path: string,
155
+ init: ApiRequestOptions,
156
+ accept?: string,
157
+ ): Promise<Response> {
158
+ const url = this.buildUrl(path);
159
+ const method = init.method ?? "GET";
160
+ const headers = new Headers(init.headers);
161
+
162
+ if (accept && !headers.has("accept")) {
163
+ headers.set("accept", accept);
164
+ }
165
+
166
+ if (this.options.apiKey && !headers.has("authorization")) {
167
+ headers.set("authorization", `Bearer ${this.options.apiKey}`);
168
+ }
169
+
170
+ const controller = new AbortController();
171
+ const timeoutMs = init.timeoutMs ?? this.options.timeoutMs;
172
+ let timedOut = false;
173
+ let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
174
+
175
+ const abortListener = (): void => {
176
+ controller.abort();
177
+ };
178
+
179
+ if (init.signal) {
180
+ if (init.signal.aborted) {
181
+ controller.abort();
182
+ } else {
183
+ init.signal.addEventListener("abort", abortListener, { once: true });
184
+ }
185
+ }
186
+
187
+ if (timeoutMs !== undefined) {
188
+ timeoutHandle = setTimeout(() => {
189
+ timedOut = true;
190
+ controller.abort();
191
+ }, timeoutMs);
192
+ }
193
+
194
+ try {
195
+ return await this.fetchImpl(url, {
196
+ ...init,
197
+ headers,
198
+ signal: controller.signal,
199
+ });
200
+ } catch (cause) {
201
+ if (timedOut) {
202
+ throw new ApiClientError("timeout", `Request timed out after ${timeoutMs}ms.`, {
203
+ url,
204
+ method,
205
+ cause,
206
+ });
207
+ }
208
+
209
+ throw new ApiClientError("network_error", `Request failed for ${method} ${url}.`, {
210
+ url,
211
+ method,
212
+ cause,
213
+ });
214
+ } finally {
215
+ if (timeoutHandle !== undefined) {
216
+ clearTimeout(timeoutHandle);
217
+ }
218
+
219
+ if (init.signal) {
220
+ init.signal.removeEventListener("abort", abortListener);
221
+ }
222
+ }
223
+ }
224
+
225
+ private buildHttpError(
226
+ response: Response,
227
+ text: string,
228
+ init: ApiRequestOptions,
229
+ operation: string,
230
+ ): ApiClientError {
231
+ const method = init.method ?? "GET";
232
+ const responseBody = parseErrorBody(text, response.headers.get("content-type"));
233
+
234
+ return new ApiClientError("http_error", `Request failed with status ${response.status} for ${method} ${response.url}.`, {
235
+ url: response.url,
236
+ method,
237
+ status: response.status,
238
+ responseBody,
239
+ responseText: responseBody === text ? text : undefined,
240
+ cause: new Error(operation),
241
+ });
242
+ }
243
+ }