@getpilfer/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/auth.ts
4
+ import { readFileSync, mkdirSync, writeFileSync, existsSync } from "fs";
5
+ import { join } from "path";
6
+ import { homedir } from "os";
7
+ var CONFIG_DIR = join(homedir(), ".config", "pilfer");
8
+ var CREDENTIALS_FILE = join(CONFIG_DIR, "credentials.json");
9
+ function readCredentials() {
10
+ try {
11
+ const raw = readFileSync(CREDENTIALS_FILE, "utf-8");
12
+ return JSON.parse(raw);
13
+ } catch {
14
+ return {};
15
+ }
16
+ }
17
+ function writeCredentials(data) {
18
+ mkdirSync(CONFIG_DIR, { recursive: true });
19
+ writeFileSync(
20
+ CREDENTIALS_FILE,
21
+ JSON.stringify(data, null, 0),
22
+ "utf-8"
23
+ );
24
+ }
25
+ function resolveToken(explicitToken) {
26
+ if (explicitToken?.trim()) return explicitToken.trim();
27
+ const env = process.env.PILFER_API_TOKEN;
28
+ if (env?.trim()) return env.trim();
29
+ const data = readCredentials();
30
+ if (data.token?.trim()) return data.token.trim();
31
+ return null;
32
+ }
33
+ function saveToken(token) {
34
+ const current = readCredentials();
35
+ writeCredentials({ ...current, token });
36
+ }
37
+ function resolveStoredBaseUrl() {
38
+ const data = readCredentials();
39
+ return data.baseUrl?.trim() ? data.baseUrl.trim() : null;
40
+ }
41
+ function saveBaseUrl(baseUrl) {
42
+ const current = readCredentials();
43
+ writeCredentials({ ...current, baseUrl });
44
+ }
45
+ function clearBaseUrl() {
46
+ const current = readCredentials();
47
+ if (!current.baseUrl) return;
48
+ const next = { ...current };
49
+ delete next.baseUrl;
50
+ writeCredentials(next);
51
+ }
52
+ function clearCredentials() {
53
+ try {
54
+ if (existsSync(CREDENTIALS_FILE)) {
55
+ const current = readCredentials();
56
+ const next = { ...current };
57
+ delete next.token;
58
+ writeCredentials(next);
59
+ }
60
+ } catch {
61
+ }
62
+ }
63
+ function getCredentialsPath() {
64
+ return CREDENTIALS_FILE;
65
+ }
66
+
67
+ export {
68
+ resolveToken,
69
+ saveToken,
70
+ resolveStoredBaseUrl,
71
+ saveBaseUrl,
72
+ clearBaseUrl,
73
+ clearCredentials,
74
+ getCredentialsPath
75
+ };
76
+ //# sourceMappingURL=chunk-GZ4DXDQM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/auth.ts"],"sourcesContent":["import { readFileSync, mkdirSync, writeFileSync, existsSync } from \"fs\";\nimport { join } from \"path\";\nimport { homedir } from \"os\";\n\nconst CONFIG_DIR = join(homedir(), \".config\", \"pilfer\");\nconst CREDENTIALS_FILE = join(CONFIG_DIR, \"credentials.json\");\n\nexport interface CredentialsFile {\n token?: string;\n baseUrl?: string;\n}\n\nfunction readCredentials(): CredentialsFile {\n try {\n const raw = readFileSync(CREDENTIALS_FILE, \"utf-8\");\n return JSON.parse(raw) as CredentialsFile;\n } catch {\n return {};\n }\n}\n\nfunction writeCredentials(data: CredentialsFile): void {\n mkdirSync(CONFIG_DIR, { recursive: true });\n writeFileSync(\n CREDENTIALS_FILE,\n JSON.stringify(data, null, 0),\n \"utf-8\",\n );\n}\n\n/**\n * Resolve API token in order: explicit token > PILFER_API_TOKEN > credentials file.\n */\nexport function resolveToken(explicitToken?: string): string | null {\n if (explicitToken?.trim()) return explicitToken.trim();\n const env = process.env.PILFER_API_TOKEN;\n if (env?.trim()) return env.trim();\n const data = readCredentials();\n if (data.token?.trim()) return data.token.trim();\n return null;\n}\n\n/**\n * Persist token to credentials file (for pilfer login).\n */\nexport function saveToken(token: string): void {\n const current = readCredentials();\n writeCredentials({ ...current, token });\n}\n\nexport function resolveStoredBaseUrl(): string | null {\n const data = readCredentials();\n return data.baseUrl?.trim() ? data.baseUrl.trim() : null;\n}\n\nexport function saveBaseUrl(baseUrl: string): void {\n const current = readCredentials();\n writeCredentials({ ...current, baseUrl });\n}\n\nexport function clearBaseUrl(): void {\n const current = readCredentials();\n if (!current.baseUrl) return;\n const next = { ...current };\n delete next.baseUrl;\n writeCredentials(next);\n}\n\n/**\n * Remove persisted API token (for pilfer logout) while keeping other config.\n */\nexport function clearCredentials(): void {\n try {\n if (existsSync(CREDENTIALS_FILE)) {\n const current = readCredentials();\n const next = { ...current };\n delete next.token;\n writeCredentials(next);\n }\n } catch {\n // ignore\n }\n}\n\nexport function getCredentialsPath(): string {\n return CREDENTIALS_FILE;\n}\n"],"mappings":";;;AAAA,SAAS,cAAc,WAAW,eAAe,kBAAkB;AACnE,SAAS,YAAY;AACrB,SAAS,eAAe;AAExB,IAAM,aAAa,KAAK,QAAQ,GAAG,WAAW,QAAQ;AACtD,IAAM,mBAAmB,KAAK,YAAY,kBAAkB;AAO5D,SAAS,kBAAmC;AAC1C,MAAI;AACF,UAAM,MAAM,aAAa,kBAAkB,OAAO;AAClD,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,SAAS,iBAAiB,MAA6B;AACrD,YAAU,YAAY,EAAE,WAAW,KAAK,CAAC;AACzC;AAAA,IACE;AAAA,IACA,KAAK,UAAU,MAAM,MAAM,CAAC;AAAA,IAC5B;AAAA,EACF;AACF;AAKO,SAAS,aAAa,eAAuC;AAClE,MAAI,eAAe,KAAK,EAAG,QAAO,cAAc,KAAK;AACrD,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,KAAK,KAAK,EAAG,QAAO,IAAI,KAAK;AACjC,QAAM,OAAO,gBAAgB;AAC7B,MAAI,KAAK,OAAO,KAAK,EAAG,QAAO,KAAK,MAAM,KAAK;AAC/C,SAAO;AACT;AAKO,SAAS,UAAU,OAAqB;AAC7C,QAAM,UAAU,gBAAgB;AAChC,mBAAiB,EAAE,GAAG,SAAS,MAAM,CAAC;AACxC;AAEO,SAAS,uBAAsC;AACpD,QAAM,OAAO,gBAAgB;AAC7B,SAAO,KAAK,SAAS,KAAK,IAAI,KAAK,QAAQ,KAAK,IAAI;AACtD;AAEO,SAAS,YAAY,SAAuB;AACjD,QAAM,UAAU,gBAAgB;AAChC,mBAAiB,EAAE,GAAG,SAAS,QAAQ,CAAC;AAC1C;AAEO,SAAS,eAAqB;AACnC,QAAM,UAAU,gBAAgB;AAChC,MAAI,CAAC,QAAQ,QAAS;AACtB,QAAM,OAAO,EAAE,GAAG,QAAQ;AAC1B,SAAO,KAAK;AACZ,mBAAiB,IAAI;AACvB;AAKO,SAAS,mBAAyB;AACvC,MAAI;AACF,QAAI,WAAW,gBAAgB,GAAG;AAChC,YAAM,UAAU,gBAAgB;AAChC,YAAM,OAAO,EAAE,GAAG,QAAQ;AAC1B,aAAO,KAAK;AACZ,uBAAiB,IAAI;AAAA,IACvB;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAEO,SAAS,qBAA6B;AAC3C,SAAO;AACT;","names":[]}
@@ -0,0 +1,281 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ resolveStoredBaseUrl
4
+ } from "./chunk-GZ4DXDQM.js";
5
+
6
+ // src/lib/client.ts
7
+ var DEFAULT_BASE_URL = "https://api.usepilfer.com";
8
+ function getBaseUrl(override) {
9
+ const env = process.env.PILFER_API_BASE_URL;
10
+ const stored = resolveStoredBaseUrl();
11
+ const base = override ?? env ?? stored ?? DEFAULT_BASE_URL;
12
+ return base.replace(/\/$/, "");
13
+ }
14
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([502, 503, 504]);
15
+ var GET_MAX_ATTEMPTS = 3;
16
+ var GET_RETRY_BASE_DELAY_MS = 150;
17
+ function shouldRetryRequest(method, response, attempt) {
18
+ if (method !== "GET") return false;
19
+ if (attempt >= GET_MAX_ATTEMPTS) return false;
20
+ if (response.status === 0) return true;
21
+ return RETRYABLE_STATUSES.has(response.status);
22
+ }
23
+ function getRetryDelayMs(attempt) {
24
+ const exp = GET_RETRY_BASE_DELAY_MS * Math.pow(2, Math.max(0, attempt - 1));
25
+ const jitter = Math.floor(Math.random() * 50);
26
+ return exp + jitter;
27
+ }
28
+ async function sleep(ms) {
29
+ await new Promise((resolve) => setTimeout(resolve, ms));
30
+ }
31
+ async function apiRequest(options) {
32
+ const base = getBaseUrl(options.baseUrl);
33
+ const url = new URL(`/api/v1/${options.path.replace(/^\//, "")}`, base);
34
+ if (options.query) {
35
+ for (const [k, v] of Object.entries(options.query)) {
36
+ if (v !== void 0 && v !== "") url.searchParams.set(k, String(v));
37
+ }
38
+ }
39
+ const headers = {
40
+ Authorization: `Bearer ${options.token ?? ""}`,
41
+ "Content-Type": "application/json"
42
+ };
43
+ const init = {
44
+ method: options.method,
45
+ headers
46
+ };
47
+ if (options.body !== void 0 && options.method !== "GET") {
48
+ init.body = JSON.stringify(options.body);
49
+ }
50
+ for (let attempt = 1; ; attempt++) {
51
+ const response = await doRequest(url.toString(), init);
52
+ if (!shouldRetryRequest(options.method, response, attempt)) {
53
+ return response;
54
+ }
55
+ await sleep(getRetryDelayMs(attempt));
56
+ }
57
+ }
58
+ async function doRequest(url, init) {
59
+ let res;
60
+ try {
61
+ res = await fetch(url, init);
62
+ } catch (err) {
63
+ const message = err instanceof Error ? err.message : String(err);
64
+ return { ok: false, status: 0, error: message };
65
+ }
66
+ let data;
67
+ const text = await res.text();
68
+ if (text) {
69
+ try {
70
+ data = JSON.parse(text);
71
+ } catch {
72
+ data = void 0;
73
+ }
74
+ }
75
+ return {
76
+ ok: res.ok,
77
+ status: res.status,
78
+ data,
79
+ error: !res.ok ? extractErrorMessage(data) : void 0,
80
+ headers: res.headers
81
+ };
82
+ }
83
+ function extractErrorMessage(data) {
84
+ if (data == null || typeof data !== "object") return void 0;
85
+ const body = data;
86
+ const baseMessage = typeof body.error === "string" ? body.error : typeof body.message === "string" ? body.message : void 0;
87
+ if (baseMessage == null) return void 0;
88
+ if (typeof body.requestId === "string" && body.requestId.trim() !== "") {
89
+ return `${baseMessage} (requestId: ${body.requestId})`;
90
+ }
91
+ return baseMessage;
92
+ }
93
+
94
+ // src/lib/flag-coercion.ts
95
+ function fail(exitWithError, message) {
96
+ return exitWithError(400, message);
97
+ }
98
+ function parseBoolean(raw, config, exitWithError) {
99
+ const value = raw.trim().toLowerCase();
100
+ if (["true", "1", "yes", "y", "on"].includes(value)) return true;
101
+ if (["false", "0", "no", "n", "off"].includes(value)) return false;
102
+ return fail(
103
+ exitWithError,
104
+ `Invalid value for ${config.flag} (${config.field}): expected boolean but got "${raw}"`
105
+ );
106
+ }
107
+ function parseNumber(raw, config, exitWithError) {
108
+ const trimmed = raw.trim();
109
+ const asNumber = Number(trimmed);
110
+ if (Number.isFinite(asNumber)) return asNumber;
111
+ if (config.allowDateTime) {
112
+ const asDate = Date.parse(trimmed);
113
+ if (Number.isFinite(asDate)) return asDate;
114
+ }
115
+ return fail(
116
+ exitWithError,
117
+ `Invalid value for ${config.flag} (${config.field}): expected number${config.allowDateTime ? " or ISO datetime" : ""} but got "${raw}"`
118
+ );
119
+ }
120
+ function coerceBodyFlag(raw, config, exitWithError) {
121
+ if (raw == null) return void 0;
122
+ const value = String(raw);
123
+ const trimmed = value.trim();
124
+ if (config.allowNull && trimmed.toLowerCase() === "null") {
125
+ return null;
126
+ }
127
+ if (config.kind === "string") return value;
128
+ if (config.kind === "boolean") return parseBoolean(value, config, exitWithError);
129
+ return parseNumber(value, config, exitWithError);
130
+ }
131
+ function coerceQueryFlag(raw, config, exitWithError) {
132
+ if (raw == null) {
133
+ return fail(
134
+ exitWithError,
135
+ `Missing value for ${config.flag} (${config.field})`
136
+ );
137
+ }
138
+ const value = String(raw);
139
+ if (config.kind === "string") return value;
140
+ if (config.kind === "boolean") {
141
+ return parseBoolean(value, config, exitWithError) ? "true" : "false";
142
+ }
143
+ return parseNumber(value, config, exitWithError);
144
+ }
145
+
146
+ // src/lib/merge-body.ts
147
+ function mergeBody(bodyFromFlags, bodyOption) {
148
+ let base = {};
149
+ if (bodyOption?.trim()) {
150
+ try {
151
+ base = JSON.parse(bodyOption);
152
+ } catch {
153
+ }
154
+ }
155
+ return { ...base, ...bodyFromFlags };
156
+ }
157
+
158
+ // src/lib/output.ts
159
+ function getDefaultOutputFormat() {
160
+ return process.stdout.isTTY ? "table" : "json";
161
+ }
162
+ function formatErrorPayload(status, message) {
163
+ const code = statusToCode(status);
164
+ return { error: true, status, code, message };
165
+ }
166
+ function statusToCode(status) {
167
+ if (status === 401) return "UNAUTHORIZED";
168
+ if (status === 403) return "FORBIDDEN";
169
+ if (status === 404) return "NOT_FOUND";
170
+ if (status === 400) return "BAD_REQUEST";
171
+ if (status === 429) return "TOO_MANY_REQUESTS";
172
+ if (status >= 500) return "SERVER_ERROR";
173
+ return "ERROR";
174
+ }
175
+ function printOutput(format, data, isError = false) {
176
+ if (format === "json") {
177
+ const out = Array.isArray(data) ? { data } : typeof data === "object" && data !== null && "data" in data ? data : { data };
178
+ console.log(JSON.stringify(out, null, 0));
179
+ return;
180
+ }
181
+ if (format === "table") {
182
+ printTable(data);
183
+ return;
184
+ }
185
+ if (format === "csv") {
186
+ printCsv(data);
187
+ return;
188
+ }
189
+ console.log(JSON.stringify(data));
190
+ }
191
+ function getRows(data) {
192
+ if (Array.isArray(data)) return data;
193
+ if (typeof data === "object" && data !== null && "data" in data) {
194
+ const d = data.data;
195
+ if (Array.isArray(d)) return d;
196
+ if (typeof d === "object" && d !== null) return [d];
197
+ }
198
+ if (typeof data === "object" && data !== null && !Array.isArray(data)) {
199
+ return [data];
200
+ }
201
+ return [];
202
+ }
203
+ function getColumns(rows) {
204
+ const set = /* @__PURE__ */ new Set();
205
+ for (const row of rows) {
206
+ for (const k of Object.keys(row)) set.add(k);
207
+ }
208
+ return [...set].sort((a, b) => {
209
+ const aIsId = a === "_id" || a === "id" || a.endsWith("Id");
210
+ const bIsId = b === "_id" || b === "id" || b.endsWith("Id");
211
+ if (aIsId !== bIsId) return aIsId ? 1 : -1;
212
+ return a.localeCompare(b);
213
+ });
214
+ }
215
+ function printTable(data) {
216
+ const rows = getRows(data);
217
+ if (rows.length === 0) {
218
+ console.log("(no data)");
219
+ return;
220
+ }
221
+ const cols = getColumns(rows);
222
+ const widths = cols.map((c) => Math.max(c.length, 8));
223
+ for (const row of rows) {
224
+ for (let i = 0; i < cols.length; i++) {
225
+ const col = cols[i];
226
+ const val = row[col];
227
+ const s = val === null || val === void 0 ? "" : String(val);
228
+ if (s.length > widths[i]) widths[i] = Math.min(s.length, 40);
229
+ }
230
+ }
231
+ const header = cols.map((c, i) => c.padEnd(widths[i])).join(" ");
232
+ console.log(header);
233
+ console.log(cols.map((_, i) => "-".repeat(widths[i])).join(" "));
234
+ for (const row of rows) {
235
+ const line = cols.map((c, i) => {
236
+ const val = row[c];
237
+ const s = val === null || val === void 0 ? "" : String(val);
238
+ return s.slice(0, widths[i]).padEnd(widths[i]);
239
+ }).join(" ");
240
+ console.log(line);
241
+ }
242
+ }
243
+ function printCsv(data) {
244
+ const rows = getRows(data);
245
+ if (rows.length === 0) {
246
+ console.log("");
247
+ return;
248
+ }
249
+ const cols = getColumns(rows);
250
+ const escape = (v) => {
251
+ const s = v === null || v === void 0 ? "" : String(v);
252
+ if (s.includes(",") || s.includes('"') || s.includes("\n")) {
253
+ return `"${s.replace(/"/g, '""')}"`;
254
+ }
255
+ return s;
256
+ };
257
+ console.log(cols.join(","));
258
+ for (const row of rows) {
259
+ console.log(cols.map((c) => escape(row[c])).join(","));
260
+ }
261
+ }
262
+ function printStructuredError(format, payload) {
263
+ if (format === "json") {
264
+ console.log(JSON.stringify(payload));
265
+ return;
266
+ }
267
+ console.error(`Error ${payload.status} (${payload.code}): ${payload.message}`);
268
+ }
269
+
270
+ export {
271
+ getBaseUrl,
272
+ apiRequest,
273
+ coerceBodyFlag,
274
+ coerceQueryFlag,
275
+ mergeBody,
276
+ getDefaultOutputFormat,
277
+ formatErrorPayload,
278
+ printOutput,
279
+ printStructuredError
280
+ };
281
+ //# sourceMappingURL=chunk-HJFKOMTC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/client.ts","../src/lib/flag-coercion.ts","../src/lib/merge-body.ts","../src/lib/output.ts"],"sourcesContent":["import { resolveStoredBaseUrl } from \"./auth.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.usepilfer.com\";\n\nexport function getBaseUrl(override?: string): string {\n const env = process.env.PILFER_API_BASE_URL;\n const stored = resolveStoredBaseUrl();\n const base = override ?? env ?? stored ?? DEFAULT_BASE_URL;\n return base.replace(/\\/$/, \"\");\n}\n\nexport interface RequestOptions {\n method: \"GET\" | \"POST\" | \"PATCH\" | \"DELETE\";\n path: string;\n token: string | null;\n baseUrl?: string;\n query?: Record<string, string | number | undefined>;\n body?: unknown;\n}\n\nexport interface ApiResponse<T = unknown> {\n ok: boolean;\n status: number;\n data?: T;\n error?: string;\n headers?: Headers;\n}\n\nconst RETRYABLE_STATUSES = new Set([502, 503, 504]);\nconst GET_MAX_ATTEMPTS = 3;\nconst GET_RETRY_BASE_DELAY_MS = 150;\n\nfunction shouldRetryRequest(method: RequestOptions[\"method\"], response: ApiResponse<unknown>, attempt: number): boolean {\n if (method !== \"GET\") return false;\n if (attempt >= GET_MAX_ATTEMPTS) return false;\n if (response.status === 0) return true;\n return RETRYABLE_STATUSES.has(response.status);\n}\n\nfunction getRetryDelayMs(attempt: number): number {\n const exp = GET_RETRY_BASE_DELAY_MS * Math.pow(2, Math.max(0, attempt - 1));\n const jitter = Math.floor(Math.random() * 50);\n return exp + jitter;\n}\n\nasync function sleep(ms: number): Promise<void> {\n await new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Call the Pilfer REST API. Path should be relative to /api/v1 (e.g. \"me\", \"projects\", \"projects/123\").\n */\nexport async function apiRequest<T = unknown>(\n options: RequestOptions,\n): Promise<ApiResponse<T>> {\n const base = getBaseUrl(options.baseUrl);\n const url = new URL(`/api/v1/${options.path.replace(/^\\//, \"\")}`, base);\n if (options.query) {\n for (const [k, v] of Object.entries(options.query)) {\n if (v !== undefined && v !== \"\") url.searchParams.set(k, String(v));\n }\n }\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${options.token ?? \"\"}`,\n \"Content-Type\": \"application/json\",\n };\n\n const init: RequestInit = {\n method: options.method,\n headers,\n };\n if (\n options.body !== undefined &&\n options.method !== \"GET\"\n ) {\n init.body = JSON.stringify(options.body);\n }\n\n for (let attempt = 1; ; attempt++) {\n const response = await doRequest<T>(url.toString(), init);\n if (!shouldRetryRequest(options.method, response, attempt)) {\n return response;\n }\n await sleep(getRetryDelayMs(attempt));\n }\n}\n\nasync function doRequest<T>(url: string, init: RequestInit): Promise<ApiResponse<T>> {\n let res: Response;\n try {\n res = await fetch(url, init);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { ok: false, status: 0, error: message };\n }\n\n let data: T | undefined;\n const text = await res.text();\n if (text) {\n try {\n data = JSON.parse(text) as T;\n } catch {\n data = undefined;\n }\n }\n\n return {\n ok: res.ok,\n status: res.status,\n data,\n error: !res.ok\n ? extractErrorMessage(data)\n : undefined,\n headers: res.headers,\n };\n}\n\nfunction extractErrorMessage(data: unknown): string | undefined {\n if (data == null || typeof data !== \"object\") return undefined;\n\n const body = data as {\n error?: unknown;\n message?: unknown;\n requestId?: unknown;\n };\n\n const baseMessage =\n typeof body.error === \"string\"\n ? body.error\n : typeof body.message === \"string\"\n ? body.message\n : undefined;\n if (baseMessage == null) return undefined;\n\n if (typeof body.requestId === \"string\" && body.requestId.trim() !== \"\") {\n return `${baseMessage} (requestId: ${body.requestId})`;\n }\n return baseMessage;\n}\n","type ExitWithError = (status: number, message: string) => never;\n\ntype PrimitiveKind = \"string\" | \"number\" | \"boolean\";\n\ninterface CoerceConfig {\n field: string;\n flag: string;\n kind: PrimitiveKind;\n allowNull?: boolean;\n allowDateTime?: boolean;\n}\n\nfunction fail(exitWithError: ExitWithError, message: string): never {\n return exitWithError(400, message);\n}\n\nfunction parseBoolean(\n raw: string,\n config: CoerceConfig,\n exitWithError: ExitWithError,\n): boolean {\n const value = raw.trim().toLowerCase();\n if ([\"true\", \"1\", \"yes\", \"y\", \"on\"].includes(value)) return true;\n if ([\"false\", \"0\", \"no\", \"n\", \"off\"].includes(value)) return false;\n return fail(\n exitWithError,\n `Invalid value for ${config.flag} (${config.field}): expected boolean but got \"${raw}\"`,\n );\n}\n\nfunction parseNumber(\n raw: string,\n config: CoerceConfig,\n exitWithError: ExitWithError,\n): number {\n const trimmed = raw.trim();\n const asNumber = Number(trimmed);\n if (Number.isFinite(asNumber)) return asNumber;\n\n if (config.allowDateTime) {\n const asDate = Date.parse(trimmed);\n if (Number.isFinite(asDate)) return asDate;\n }\n\n return fail(\n exitWithError,\n `Invalid value for ${config.flag} (${config.field}): expected number${config.allowDateTime ? \" or ISO datetime\" : \"\"} but got \"${raw}\"`,\n );\n}\n\nexport function coerceBodyFlag(\n raw: unknown,\n config: CoerceConfig,\n exitWithError: ExitWithError,\n): unknown {\n if (raw == null) return undefined;\n const value = String(raw);\n const trimmed = value.trim();\n\n if (config.allowNull && trimmed.toLowerCase() === \"null\") {\n return null;\n }\n\n if (config.kind === \"string\") return value;\n if (config.kind === \"boolean\") return parseBoolean(value, config, exitWithError);\n return parseNumber(value, config, exitWithError);\n}\n\nexport function coerceQueryFlag(\n raw: unknown,\n config: CoerceConfig,\n exitWithError: ExitWithError,\n): string | number {\n if (raw == null) {\n return fail(\n exitWithError,\n `Missing value for ${config.flag} (${config.field})`,\n );\n }\n\n const value = String(raw);\n if (config.kind === \"string\") return value;\n if (config.kind === \"boolean\") {\n return parseBoolean(value, config, exitWithError) ? \"true\" : \"false\";\n }\n return parseNumber(value, config, exitWithError);\n}\n","/**\n * Merge optional JSON body (from --body) with object built from flags.\n * Flags override keys in body when both are provided.\n */\nexport function mergeBody(\n bodyFromFlags: Record<string, unknown>,\n bodyOption: string | undefined,\n): Record<string, unknown> {\n let base: Record<string, unknown> = {};\n if (bodyOption?.trim()) {\n try {\n base = JSON.parse(bodyOption) as Record<string, unknown>;\n } catch {\n // ignore invalid JSON\n }\n }\n return { ...base, ...bodyFromFlags };\n}\n","export type OutputFormat = \"json\" | \"table\" | \"csv\";\n\nexport function getDefaultOutputFormat(): OutputFormat {\n return process.stdout.isTTY ? \"table\" : \"json\";\n}\n\nexport interface ErrorPayload {\n error: true;\n status: number;\n code: string;\n message: string;\n}\n\nexport function formatErrorPayload(status: number, message: string): ErrorPayload {\n const code = statusToCode(status);\n return { error: true, status, code, message };\n}\n\nfunction statusToCode(status: number): string {\n if (status === 401) return \"UNAUTHORIZED\";\n if (status === 403) return \"FORBIDDEN\";\n if (status === 404) return \"NOT_FOUND\";\n if (status === 400) return \"BAD_REQUEST\";\n if (status === 429) return \"TOO_MANY_REQUESTS\";\n if (status >= 500) return \"SERVER_ERROR\";\n return \"ERROR\";\n}\n\n/**\n * Print result to stdout according to format. For list responses, pass the data array.\n * For single resource, pass the data object.\n */\nexport function printOutput(\n format: OutputFormat,\n data: unknown,\n isError = false,\n): void {\n if (format === \"json\") {\n const out = Array.isArray(data)\n ? { data }\n : typeof data === \"object\" && data !== null && \"data\" in data\n ? data\n : { data };\n console.log(JSON.stringify(out, null, 0));\n return;\n }\n if (format === \"table\") {\n printTable(data);\n return;\n }\n if (format === \"csv\") {\n printCsv(data);\n return;\n }\n console.log(JSON.stringify(data));\n}\n\nfunction getRows(data: unknown): Record<string, unknown>[] {\n if (Array.isArray(data)) return data as Record<string, unknown>[];\n if (typeof data === \"object\" && data !== null && \"data\" in data) {\n const d = (data as { data: unknown }).data;\n if (Array.isArray(d)) return d as Record<string, unknown>[];\n if (typeof d === \"object\" && d !== null) return [d as Record<string, unknown>];\n }\n if (typeof data === \"object\" && data !== null && !Array.isArray(data)) {\n return [data as Record<string, unknown>];\n }\n return [];\n}\n\nfunction getColumns(rows: Record<string, unknown>[]): string[] {\n const set = new Set<string>();\n for (const row of rows) {\n for (const k of Object.keys(row)) set.add(k);\n }\n return [...set].sort((a, b) => {\n const aIsId = a === \"_id\" || a === \"id\" || a.endsWith(\"Id\");\n const bIsId = b === \"_id\" || b === \"id\" || b.endsWith(\"Id\");\n if (aIsId !== bIsId) return aIsId ? 1 : -1;\n return a.localeCompare(b);\n });\n}\n\nfunction printTable(data: unknown): void {\n const rows = getRows(data);\n if (rows.length === 0) {\n console.log(\"(no data)\");\n return;\n }\n const cols = getColumns(rows);\n const widths = cols.map((c) => Math.max(c.length, 8));\n for (const row of rows) {\n for (let i = 0; i < cols.length; i++) {\n const col = cols[i]!;\n const val = row[col];\n const s = val === null || val === undefined ? \"\" : String(val);\n if (s.length > widths[i]!) widths[i] = Math.min(s.length, 40);\n }\n }\n const header = cols.map((c, i) => c.padEnd(widths[i]!)).join(\" \");\n console.log(header);\n console.log(cols.map((_, i) => \"-\".repeat(widths[i]!)).join(\" \"));\n for (const row of rows) {\n const line = cols\n .map((c, i) => {\n const val = row[c];\n const s = val === null || val === undefined ? \"\" : String(val);\n return s.slice(0, widths[i]!).padEnd(widths[i]!);\n })\n .join(\" \");\n console.log(line);\n }\n}\n\nfunction printCsv(data: unknown): void {\n const rows = getRows(data);\n if (rows.length === 0) {\n console.log(\"\");\n return;\n }\n const cols = getColumns(rows);\n const escape = (v: unknown): string => {\n const s = v === null || v === undefined ? \"\" : String(v);\n if (s.includes(\",\") || s.includes('\"') || s.includes(\"\\n\")) {\n return `\"${s.replace(/\"/g, '\"\"')}\"`;\n }\n return s;\n };\n console.log(cols.join(\",\"));\n for (const row of rows) {\n console.log(cols.map((c) => escape(row[c])).join(\",\"));\n }\n}\n\nexport function printStructuredError(format: OutputFormat, payload: ErrorPayload): void {\n if (format === \"json\") {\n console.log(JSON.stringify(payload));\n return;\n }\n console.error(`Error ${payload.status} (${payload.code}): ${payload.message}`);\n}\n"],"mappings":";;;;;;AAEA,IAAM,mBAAmB;AAElB,SAAS,WAAW,UAA2B;AACpD,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,SAAS,qBAAqB;AACpC,QAAM,OAAO,YAAY,OAAO,UAAU;AAC1C,SAAO,KAAK,QAAQ,OAAO,EAAE;AAC/B;AAmBA,IAAM,qBAAqB,oBAAI,IAAI,CAAC,KAAK,KAAK,GAAG,CAAC;AAClD,IAAM,mBAAmB;AACzB,IAAM,0BAA0B;AAEhC,SAAS,mBAAmB,QAAkC,UAAgC,SAA0B;AACtH,MAAI,WAAW,MAAO,QAAO;AAC7B,MAAI,WAAW,iBAAkB,QAAO;AACxC,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,SAAO,mBAAmB,IAAI,SAAS,MAAM;AAC/C;AAEA,SAAS,gBAAgB,SAAyB;AAChD,QAAM,MAAM,0BAA0B,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,UAAU,CAAC,CAAC;AAC1E,QAAM,SAAS,KAAK,MAAM,KAAK,OAAO,IAAI,EAAE;AAC5C,SAAO,MAAM;AACf;AAEA,eAAe,MAAM,IAA2B;AAC9C,QAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACxD;AAKA,eAAsB,WACpB,SACyB;AACzB,QAAM,OAAO,WAAW,QAAQ,OAAO;AACvC,QAAM,MAAM,IAAI,IAAI,WAAW,QAAQ,KAAK,QAAQ,OAAO,EAAE,CAAC,IAAI,IAAI;AACtE,MAAI,QAAQ,OAAO;AACjB,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,QAAQ,KAAK,GAAG;AAClD,UAAI,MAAM,UAAa,MAAM,GAAI,KAAI,aAAa,IAAI,GAAG,OAAO,CAAC,CAAC;AAAA,IACpE;AAAA,EACF;AAEA,QAAM,UAAkC;AAAA,IACtC,eAAe,UAAU,QAAQ,SAAS,EAAE;AAAA,IAC5C,gBAAgB;AAAA,EAClB;AAEA,QAAM,OAAoB;AAAA,IACxB,QAAQ,QAAQ;AAAA,IAChB;AAAA,EACF;AACA,MACE,QAAQ,SAAS,UACjB,QAAQ,WAAW,OACnB;AACA,SAAK,OAAO,KAAK,UAAU,QAAQ,IAAI;AAAA,EACzC;AAEA,WAAS,UAAU,KAAK,WAAW;AACjC,UAAM,WAAW,MAAM,UAAa,IAAI,SAAS,GAAG,IAAI;AACxD,QAAI,CAAC,mBAAmB,QAAQ,QAAQ,UAAU,OAAO,GAAG;AAC1D,aAAO;AAAA,IACT;AACA,UAAM,MAAM,gBAAgB,OAAO,CAAC;AAAA,EACtC;AACF;AAEA,eAAe,UAAa,KAAa,MAA4C;AACnF,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,MAAM,KAAK,IAAI;AAAA,EAC7B,SAAS,KAAK;AACZ,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,WAAO,EAAE,IAAI,OAAO,QAAQ,GAAG,OAAO,QAAQ;AAAA,EAChD;AAEA,MAAI;AACJ,QAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,MAAI,MAAM;AACR,QAAI;AACF,aAAO,KAAK,MAAM,IAAI;AAAA,IACxB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,QAAQ,IAAI;AAAA,IACZ;AAAA,IACA,OAAO,CAAC,IAAI,KACR,oBAAoB,IAAI,IACxB;AAAA,IACJ,SAAS,IAAI;AAAA,EACf;AACF;AAEA,SAAS,oBAAoB,MAAmC;AAC9D,MAAI,QAAQ,QAAQ,OAAO,SAAS,SAAU,QAAO;AAErD,QAAM,OAAO;AAMb,QAAM,cACJ,OAAO,KAAK,UAAU,WAClB,KAAK,QACL,OAAO,KAAK,YAAY,WACtB,KAAK,UACL;AACR,MAAI,eAAe,KAAM,QAAO;AAEhC,MAAI,OAAO,KAAK,cAAc,YAAY,KAAK,UAAU,KAAK,MAAM,IAAI;AACtE,WAAO,GAAG,WAAW,gBAAgB,KAAK,SAAS;AAAA,EACrD;AACA,SAAO;AACT;;;AC/HA,SAAS,KAAK,eAA8B,SAAwB;AAClE,SAAO,cAAc,KAAK,OAAO;AACnC;AAEA,SAAS,aACP,KACA,QACA,eACS;AACT,QAAM,QAAQ,IAAI,KAAK,EAAE,YAAY;AACrC,MAAI,CAAC,QAAQ,KAAK,OAAO,KAAK,IAAI,EAAE,SAAS,KAAK,EAAG,QAAO;AAC5D,MAAI,CAAC,SAAS,KAAK,MAAM,KAAK,KAAK,EAAE,SAAS,KAAK,EAAG,QAAO;AAC7D,SAAO;AAAA,IACL;AAAA,IACA,qBAAqB,OAAO,IAAI,KAAK,OAAO,KAAK,gCAAgC,GAAG;AAAA,EACtF;AACF;AAEA,SAAS,YACP,KACA,QACA,eACQ;AACR,QAAM,UAAU,IAAI,KAAK;AACzB,QAAM,WAAW,OAAO,OAAO;AAC/B,MAAI,OAAO,SAAS,QAAQ,EAAG,QAAO;AAEtC,MAAI,OAAO,eAAe;AACxB,UAAM,SAAS,KAAK,MAAM,OAAO;AACjC,QAAI,OAAO,SAAS,MAAM,EAAG,QAAO;AAAA,EACtC;AAEA,SAAO;AAAA,IACL;AAAA,IACA,qBAAqB,OAAO,IAAI,KAAK,OAAO,KAAK,qBAAqB,OAAO,gBAAgB,qBAAqB,EAAE,aAAa,GAAG;AAAA,EACtI;AACF;AAEO,SAAS,eACd,KACA,QACA,eACS;AACT,MAAI,OAAO,KAAM,QAAO;AACxB,QAAM,QAAQ,OAAO,GAAG;AACxB,QAAM,UAAU,MAAM,KAAK;AAE3B,MAAI,OAAO,aAAa,QAAQ,YAAY,MAAM,QAAQ;AACxD,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,SAAS,SAAU,QAAO;AACrC,MAAI,OAAO,SAAS,UAAW,QAAO,aAAa,OAAO,QAAQ,aAAa;AAC/E,SAAO,YAAY,OAAO,QAAQ,aAAa;AACjD;AAEO,SAAS,gBACd,KACA,QACA,eACiB;AACjB,MAAI,OAAO,MAAM;AACf,WAAO;AAAA,MACL;AAAA,MACA,qBAAqB,OAAO,IAAI,KAAK,OAAO,KAAK;AAAA,IACnD;AAAA,EACF;AAEA,QAAM,QAAQ,OAAO,GAAG;AACxB,MAAI,OAAO,SAAS,SAAU,QAAO;AACrC,MAAI,OAAO,SAAS,WAAW;AAC7B,WAAO,aAAa,OAAO,QAAQ,aAAa,IAAI,SAAS;AAAA,EAC/D;AACA,SAAO,YAAY,OAAO,QAAQ,aAAa;AACjD;;;AClFO,SAAS,UACd,eACA,YACyB;AACzB,MAAI,OAAgC,CAAC;AACrC,MAAI,YAAY,KAAK,GAAG;AACtB,QAAI;AACF,aAAO,KAAK,MAAM,UAAU;AAAA,IAC9B,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO,EAAE,GAAG,MAAM,GAAG,cAAc;AACrC;;;ACfO,SAAS,yBAAuC;AACrD,SAAO,QAAQ,OAAO,QAAQ,UAAU;AAC1C;AASO,SAAS,mBAAmB,QAAgB,SAA+B;AAChF,QAAM,OAAO,aAAa,MAAM;AAChC,SAAO,EAAE,OAAO,MAAM,QAAQ,MAAM,QAAQ;AAC9C;AAEA,SAAS,aAAa,QAAwB;AAC5C,MAAI,WAAW,IAAK,QAAO;AAC3B,MAAI,WAAW,IAAK,QAAO;AAC3B,MAAI,WAAW,IAAK,QAAO;AAC3B,MAAI,WAAW,IAAK,QAAO;AAC3B,MAAI,WAAW,IAAK,QAAO;AAC3B,MAAI,UAAU,IAAK,QAAO;AAC1B,SAAO;AACT;AAMO,SAAS,YACd,QACA,MACA,UAAU,OACJ;AACN,MAAI,WAAW,QAAQ;AACrB,UAAM,MAAM,MAAM,QAAQ,IAAI,IAC1B,EAAE,KAAK,IACP,OAAO,SAAS,YAAY,SAAS,QAAQ,UAAU,OACrD,OACA,EAAE,KAAK;AACb,YAAQ,IAAI,KAAK,UAAU,KAAK,MAAM,CAAC,CAAC;AACxC;AAAA,EACF;AACA,MAAI,WAAW,SAAS;AACtB,eAAW,IAAI;AACf;AAAA,EACF;AACA,MAAI,WAAW,OAAO;AACpB,aAAS,IAAI;AACb;AAAA,EACF;AACA,UAAQ,IAAI,KAAK,UAAU,IAAI,CAAC;AAClC;AAEA,SAAS,QAAQ,MAA0C;AACzD,MAAI,MAAM,QAAQ,IAAI,EAAG,QAAO;AAChC,MAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,UAAU,MAAM;AAC/D,UAAM,IAAK,KAA2B;AACtC,QAAI,MAAM,QAAQ,CAAC,EAAG,QAAO;AAC7B,QAAI,OAAO,MAAM,YAAY,MAAM,KAAM,QAAO,CAAC,CAA4B;AAAA,EAC/E;AACA,MAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,CAAC,MAAM,QAAQ,IAAI,GAAG;AACrE,WAAO,CAAC,IAA+B;AAAA,EACzC;AACA,SAAO,CAAC;AACV;AAEA,SAAS,WAAW,MAA2C;AAC7D,QAAM,MAAM,oBAAI,IAAY;AAC5B,aAAW,OAAO,MAAM;AACtB,eAAW,KAAK,OAAO,KAAK,GAAG,EAAG,KAAI,IAAI,CAAC;AAAA,EAC7C;AACA,SAAO,CAAC,GAAG,GAAG,EAAE,KAAK,CAAC,GAAG,MAAM;AAC7B,UAAM,QAAQ,MAAM,SAAS,MAAM,QAAQ,EAAE,SAAS,IAAI;AAC1D,UAAM,QAAQ,MAAM,SAAS,MAAM,QAAQ,EAAE,SAAS,IAAI;AAC1D,QAAI,UAAU,MAAO,QAAO,QAAQ,IAAI;AACxC,WAAO,EAAE,cAAc,CAAC;AAAA,EAC1B,CAAC;AACH;AAEA,SAAS,WAAW,MAAqB;AACvC,QAAM,OAAO,QAAQ,IAAI;AACzB,MAAI,KAAK,WAAW,GAAG;AACrB,YAAQ,IAAI,WAAW;AACvB;AAAA,EACF;AACA,QAAM,OAAO,WAAW,IAAI;AAC5B,QAAM,SAAS,KAAK,IAAI,CAAC,MAAM,KAAK,IAAI,EAAE,QAAQ,CAAC,CAAC;AACpD,aAAW,OAAO,MAAM;AACtB,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,YAAM,MAAM,KAAK,CAAC;AAClB,YAAM,MAAM,IAAI,GAAG;AACnB,YAAM,IAAI,QAAQ,QAAQ,QAAQ,SAAY,KAAK,OAAO,GAAG;AAC7D,UAAI,EAAE,SAAS,OAAO,CAAC,EAAI,QAAO,CAAC,IAAI,KAAK,IAAI,EAAE,QAAQ,EAAE;AAAA,IAC9D;AAAA,EACF;AACA,QAAM,SAAS,KAAK,IAAI,CAAC,GAAG,MAAM,EAAE,OAAO,OAAO,CAAC,CAAE,CAAC,EAAE,KAAK,IAAI;AACjE,UAAQ,IAAI,MAAM;AAClB,UAAQ,IAAI,KAAK,IAAI,CAAC,GAAG,MAAM,IAAI,OAAO,OAAO,CAAC,CAAE,CAAC,EAAE,KAAK,IAAI,CAAC;AACjE,aAAW,OAAO,MAAM;AACtB,UAAM,OAAO,KACV,IAAI,CAAC,GAAG,MAAM;AACb,YAAM,MAAM,IAAI,CAAC;AACjB,YAAM,IAAI,QAAQ,QAAQ,QAAQ,SAAY,KAAK,OAAO,GAAG;AAC7D,aAAO,EAAE,MAAM,GAAG,OAAO,CAAC,CAAE,EAAE,OAAO,OAAO,CAAC,CAAE;AAAA,IACjD,CAAC,EACA,KAAK,IAAI;AACZ,YAAQ,IAAI,IAAI;AAAA,EAClB;AACF;AAEA,SAAS,SAAS,MAAqB;AACrC,QAAM,OAAO,QAAQ,IAAI;AACzB,MAAI,KAAK,WAAW,GAAG;AACrB,YAAQ,IAAI,EAAE;AACd;AAAA,EACF;AACA,QAAM,OAAO,WAAW,IAAI;AAC5B,QAAM,SAAS,CAAC,MAAuB;AACrC,UAAM,IAAI,MAAM,QAAQ,MAAM,SAAY,KAAK,OAAO,CAAC;AACvD,QAAI,EAAE,SAAS,GAAG,KAAK,EAAE,SAAS,GAAG,KAAK,EAAE,SAAS,IAAI,GAAG;AAC1D,aAAO,IAAI,EAAE,QAAQ,MAAM,IAAI,CAAC;AAAA,IAClC;AACA,WAAO;AAAA,EACT;AACA,UAAQ,IAAI,KAAK,KAAK,GAAG,CAAC;AAC1B,aAAW,OAAO,MAAM;AACtB,YAAQ,IAAI,KAAK,IAAI,CAAC,MAAM,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC;AAAA,EACvD;AACF;AAEO,SAAS,qBAAqB,QAAsB,SAA6B;AACtF,MAAI,WAAW,QAAQ;AACrB,YAAQ,IAAI,KAAK,UAAU,OAAO,CAAC;AACnC;AAAA,EACF;AACA,UAAQ,MAAM,SAAS,QAAQ,MAAM,KAAK,QAAQ,IAAI,MAAM,QAAQ,OAAO,EAAE;AAC/E;","names":[]}