@getpilfer/cli 0.1.1 → 0.1.3

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.
@@ -3,12 +3,55 @@ import {
3
3
  apiRequest,
4
4
  coerceBodyFlag,
5
5
  coerceQueryFlag,
6
+ fetchList,
6
7
  mergeBody,
8
+ printList,
7
9
  printOutput
8
- } from "./chunk-QH2MMA3M.js";
10
+ } from "./chunk-CXNVJZ4C.js";
9
11
  import "./chunk-GZ4DXDQM.js";
10
12
 
13
+ // src/lib/idempotent-create.ts
14
+ async function findExistingResource(options) {
15
+ const identity = options.identityFields.filter(
16
+ (field) => isComparable(options.body[field])
17
+ );
18
+ if (identity.length === 0) return null;
19
+ const query = {};
20
+ for (const field of identity) {
21
+ if (!options.queryFields.includes(field)) continue;
22
+ const value = options.body[field];
23
+ if (typeof value === "string" || typeof value === "number") {
24
+ query[field] = value;
25
+ }
26
+ }
27
+ const res = await options.request({
28
+ method: "GET",
29
+ path: options.resource,
30
+ token: options.token,
31
+ baseUrl: options.baseUrl,
32
+ query
33
+ });
34
+ if (!res.ok) return null;
35
+ const rows = (res.data?.data ?? []).filter(
36
+ isRecord
37
+ );
38
+ return rows.find((row) => identity.every((field) => equalValue(row[field], options.body[field]))) ?? null;
39
+ }
40
+ function isComparable(value) {
41
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
42
+ }
43
+ function equalValue(a, b) {
44
+ if (typeof a === "string" && typeof b === "string") {
45
+ return a.trim().toLowerCase() === b.trim().toLowerCase();
46
+ }
47
+ return a === b;
48
+ }
49
+ function isRecord(value) {
50
+ return typeof value === "object" && value !== null && !Array.isArray(value);
51
+ }
52
+
11
53
  // src/lib/bulk.ts
54
+ import { readFile } from "fs/promises";
12
55
  var MAX_IDS = 25;
13
56
  var CONCURRENCY = 5;
14
57
  var MAX_RETRIES = 3;
@@ -16,6 +59,33 @@ function parseIds(idsFlag) {
16
59
  if (!idsFlag?.trim()) return [];
17
60
  return idsFlag.split(",").map((s) => s.trim()).filter(Boolean);
18
61
  }
62
+ async function parseIdsInput(idsFlag, idsFile) {
63
+ const inline = idsFlag?.trim() === "-" ? await readStdin() : idsFlag;
64
+ const fromFile = idsFile ? await readTextSource(idsFile) : "";
65
+ return [...parseIds(inline), ...parseIdsText(fromFile)];
66
+ }
67
+ async function parseBulkInput(input) {
68
+ if (!input?.trim()) return [];
69
+ const text = await readTextSource(input);
70
+ if (!text.trim()) return [];
71
+ if (text.trim().startsWith("[") || text.trim().startsWith("{")) {
72
+ try {
73
+ const parsed = JSON.parse(text);
74
+ if (Array.isArray(parsed)) return parsed.filter(isRecord2);
75
+ if (isRecord2(parsed) && Array.isArray(parsed.data)) {
76
+ return parsed.data.filter(isRecord2);
77
+ }
78
+ if (isRecord2(parsed)) return [parsed];
79
+ } catch {
80
+ }
81
+ }
82
+ return text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => JSON.parse(line)).filter(isRecord2);
83
+ }
84
+ function getBulkRecordId(record) {
85
+ if (typeof record._id === "string" && record._id.trim()) return record._id.trim();
86
+ if (typeof record.id === "string" && record.id.trim()) return record.id.trim();
87
+ return null;
88
+ }
19
89
  function validateBulkIds(ids, resourceLabel) {
20
90
  if (ids.length > MAX_IDS) {
21
91
  throw new Error(
@@ -29,17 +99,17 @@ async function runBulk(options) {
29
99
  const results = [];
30
100
  let succeeded = 0;
31
101
  let failed = 0;
32
- async function runWithRetry(id) {
102
+ async function runWithRetry(item) {
33
103
  let lastRes = null;
34
104
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
35
- const res = await runOne(id);
105
+ const res = await runOne(item);
36
106
  lastRes = res;
37
107
  if (res.status !== 429) break;
38
108
  const retryAfter = 2 ** attempt;
39
109
  await new Promise((r2) => setTimeout(r2, retryAfter * 1e3));
40
110
  }
41
111
  const r = lastRes;
42
- const result = toResult(id, r.ok, r.status, r.data, r.error);
112
+ const result = toResult(item, r.ok, r.status, r.data, r.error);
43
113
  if (r.ok) succeeded++;
44
114
  else failed++;
45
115
  return result;
@@ -47,8 +117,8 @@ async function runBulk(options) {
47
117
  const queue = [...items];
48
118
  const workers = Array.from({ length: concurrency }, async () => {
49
119
  while (queue.length > 0) {
50
- const id = queue.shift();
51
- const result = await runWithRetry(id);
120
+ const item = queue.shift();
121
+ const result = await runWithRetry(item);
52
122
  results.push(result);
53
123
  }
54
124
  });
@@ -58,12 +128,30 @@ async function runBulk(options) {
58
128
  summary: { total: items.length, succeeded, failed }
59
129
  };
60
130
  }
131
+ function parseIdsText(text) {
132
+ if (!text?.trim()) return [];
133
+ return text.split(/[\s,]+/).map((s) => s.trim()).filter(Boolean);
134
+ }
135
+ async function readTextSource(source) {
136
+ if (source === "-") return readStdin();
137
+ return readFile(source, "utf-8");
138
+ }
139
+ async function readStdin() {
140
+ const chunks = [];
141
+ for await (const chunk of process.stdin) {
142
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
143
+ }
144
+ return Buffer.concat(chunks).toString("utf-8");
145
+ }
146
+ function isRecord2(value) {
147
+ return typeof value === "object" && value !== null && !Array.isArray(value);
148
+ }
61
149
 
62
150
  // src/commands/generated/index.ts
63
151
  function register(program, helpers) {
64
152
  const { getOutputFormat, requireToken, exitWithError, getBaseUrl, syncRootOptsFromCommand } = helpers;
65
153
  const organizationsCmd = program.command("organizations").description("organizations resource");
66
- organizationsCmd.command("list").description("List organizations").option("--limit <n>", "Max results").option("--offset <n>", "Skip n results").hook("preAction", (thisCommand) => {
154
+ organizationsCmd.command("list").description("List organizations").option("--limit <n>", "Max results").option("--offset <n>", "Skip n results").option("--all", "Fetch all pages").option("--fields <fields>", "Comma-separated fields to print").option("--where <field=value>", "Client-side exact filters, comma-separated").option("--jsonl", "Print one compact JSON object per row").hook("preAction", (thisCommand) => {
67
155
  syncRootOptsFromCommand(thisCommand);
68
156
  }).action(async (opts) => {
69
157
  const token = requireToken();
@@ -71,10 +159,10 @@ function register(program, helpers) {
71
159
  const query = {};
72
160
  if (opts.limit != null) query.limit = opts.limit;
73
161
  if (opts.offset != null) query.offset = opts.offset;
74
- const res = await apiRequest({ method: "GET", path: "organizations", token, baseUrl: getBaseUrl(), query });
162
+ const res = await fetchList({ path: "organizations", token, baseUrl: getBaseUrl(), query, all: Boolean(opts.all), request: apiRequest });
75
163
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
76
164
  const data = res.data?.data ?? [];
77
- printOutput(getOutputFormat(), data, false);
165
+ printList({ format: getOutputFormat(), data, fields: opts.fields, where: opts.where, jsonl: Boolean(opts.jsonl) });
78
166
  });
79
167
  organizationsCmd.command("get <id>").description("Get one organizations by id").hook("preAction", (thisCommand) => {
80
168
  syncRootOptsFromCommand(thisCommand);
@@ -88,7 +176,7 @@ function register(program, helpers) {
88
176
  printOutput(getOutputFormat(), data ?? {}, false);
89
177
  });
90
178
  const projectsCmd = program.command("projects").description("projects resource");
91
- projectsCmd.command("list").description("List projects").option("--limit <n>", "Max results").option("--offset <n>", "Skip n results").option("--organization-id <value>", "Filter by organizationId").hook("preAction", (thisCommand) => {
179
+ projectsCmd.command("list").description("List projects").option("--limit <n>", "Max results").option("--offset <n>", "Skip n results").option("--all", "Fetch all pages").option("--fields <fields>", "Comma-separated fields to print").option("--where <field=value>", "Client-side exact filters, comma-separated").option("--jsonl", "Print one compact JSON object per row").option("--organization-id <value>", "Filter by organizationId").option("--include <value>", "Filter by include").hook("preAction", (thisCommand) => {
92
180
  syncRootOptsFromCommand(thisCommand);
93
181
  }).action(async (opts) => {
94
182
  const token = requireToken();
@@ -99,17 +187,23 @@ function register(program, helpers) {
99
187
  if (opts.organizationId != null) {
100
188
  query.organizationId = coerceQueryFlag(opts.organizationId, { field: "organizationId", flag: "--organization-id", kind: "string" }, exitWithError);
101
189
  }
102
- const res = await apiRequest({ method: "GET", path: "projects", token, baseUrl: getBaseUrl(), query });
190
+ if (opts.include != null) {
191
+ query.include = coerceQueryFlag(opts.include, { field: "include", flag: "--include", kind: "string" }, exitWithError);
192
+ }
193
+ const res = await fetchList({ path: "projects", token, baseUrl: getBaseUrl(), query, all: Boolean(opts.all), request: apiRequest });
103
194
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
104
195
  const data = res.data?.data ?? [];
105
- printOutput(getOutputFormat(), data, false);
196
+ printList({ format: getOutputFormat(), data, fields: opts.fields, where: opts.where, jsonl: Boolean(opts.jsonl) });
106
197
  });
107
- projectsCmd.command("get <id>").description("Get one projects by id").hook("preAction", (thisCommand) => {
198
+ projectsCmd.command("get <id>").description("Get one projects by id").option("--include <value>", "Include/filter by include").hook("preAction", (thisCommand) => {
108
199
  syncRootOptsFromCommand(thisCommand);
109
200
  }).action(async (id, opts) => {
110
201
  const token = requireToken();
111
202
  if (!token) exitWithError(401, "Missing or invalid token");
112
203
  const query = {};
204
+ if (opts.include != null) {
205
+ query.include = coerceQueryFlag(opts.include, { field: "include", flag: "--include", kind: "string" }, exitWithError);
206
+ }
113
207
  const res = await apiRequest({ method: "GET", path: "projects/" + encodeURIComponent(id), token, baseUrl: getBaseUrl(), query });
114
208
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
115
209
  const data = res.data?.data ?? res.data;
@@ -131,20 +225,69 @@ function register(program, helpers) {
131
225
  if (parsedPriority !== void 0) bodyFromFlags.priority = parsedPriority;
132
226
  const body = mergeBody(bodyFromFlags, opts.body);
133
227
  if (opts.ifNotExists) {
228
+ const existing = await findExistingResource({
229
+ resource: "projects",
230
+ token,
231
+ baseUrl: getBaseUrl(),
232
+ body,
233
+ identityFields: ["name", "organizationId"],
234
+ queryFields: ["organizationId", "include"],
235
+ request: apiRequest
236
+ });
237
+ if (existing) {
238
+ printOutput(getOutputFormat(), existing, false);
239
+ return;
240
+ }
134
241
  }
135
242
  const res = await apiRequest({ method: "POST", path: "projects", token, baseUrl: getBaseUrl(), body });
136
243
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
137
244
  const data = res.data?.data ?? res.data;
138
245
  printOutput(getOutputFormat(), data ?? {}, false);
139
246
  });
140
- projectsCmd.command("update <id>").description("Update projects").option("--body <json>", "Request body JSON (merged with flags)").option("--ids <ids>", "Comma-separated IDs for bulk update (max 25)").option("--name <value>", "name").option("--description <value>", "description").option("--priority <value>", "priority").hook("preAction", (thisCommand) => {
247
+ projectsCmd.command("update [id]").description("Update projects").option("--body <json>", "Request body JSON (merged with flags)").option("--ids <ids>", "Comma-separated IDs for bulk update (max 25)").option("--ids-file <path>", "File containing IDs for bulk update").option("--input <path>", "JSON/JSONL records for bulk update; use - for stdin").option("--dry-run", "Preview bulk update without sending requests").option("--name <value>", "name").option("--description <value>", "description").option("--priority <value>", "priority").hook("preAction", (thisCommand) => {
141
248
  syncRootOptsFromCommand(thisCommand);
142
249
  }).action(async (id, opts) => {
143
250
  const token = requireToken();
144
251
  if (!token) exitWithError(401, "Missing or invalid token");
145
- const ids = parseIds(opts.ids);
252
+ const inputRecords = await parseBulkInput(opts.input);
253
+ if (inputRecords.length > 0) {
254
+ const ids2 = inputRecords.map(getBulkRecordId).filter((value) => value !== null);
255
+ if (ids2.length !== inputRecords.length) exitWithError(400, "Each --input record must include id or _id");
256
+ validateBulkIds(ids2, "projects");
257
+ if (opts.dryRun) {
258
+ printOutput(getOutputFormat(), { data: ids2.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids2.length, succeeded: ids2.length, failed: 0 } }, false);
259
+ return;
260
+ }
261
+ const { data: data2, summary } = await runBulk({
262
+ items: inputRecords,
263
+ runOne: async (record) => {
264
+ const itemId = getBulkRecordId(record);
265
+ const recordBody = { ...record };
266
+ delete recordBody.id;
267
+ delete recordBody._id;
268
+ const bodyFromFlags2 = {};
269
+ const parsedName2 = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
270
+ if (parsedName2 !== void 0) bodyFromFlags2.name = parsedName2;
271
+ const parsedDescription2 = coerceBodyFlag(opts.description, { field: "description", flag: "--description", kind: "string" }, exitWithError);
272
+ if (parsedDescription2 !== void 0) bodyFromFlags2.description = parsedDescription2;
273
+ const parsedPriority2 = coerceBodyFlag(opts.priority, { field: "priority", flag: "--priority", kind: "string" }, exitWithError);
274
+ if (parsedPriority2 !== void 0) bodyFromFlags2.priority = parsedPriority2;
275
+ const body2 = { ...recordBody, ...mergeBody(bodyFromFlags2, opts.body) };
276
+ const res2 = await apiRequest({ method: "PATCH", path: "projects/" + encodeURIComponent(itemId), token, baseUrl: getBaseUrl(), body: body2 });
277
+ return { ok: res2.ok, status: res2.status || 0, data: res2.data, error: res2.error };
278
+ },
279
+ toResult: (record, ok, status, _data, err) => ok ? { _id: getBulkRecordId(record), status: "updated" } : { _id: getBulkRecordId(record) ?? void 0, error: true, statusCode: status, message: err ?? "Failed" }
280
+ });
281
+ printOutput(getOutputFormat(), { data: data2, summary }, false);
282
+ return;
283
+ }
284
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
146
285
  if (ids.length > 0) {
147
286
  validateBulkIds(ids, "projects");
287
+ if (opts.dryRun) {
288
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
289
+ return;
290
+ }
148
291
  const { data: data2, summary } = await runBulk({
149
292
  items: ids,
150
293
  runOne: async (itemId) => {
@@ -164,6 +307,7 @@ function register(program, helpers) {
164
307
  printOutput(getOutputFormat(), { data: data2, summary }, false);
165
308
  return;
166
309
  }
310
+ if (!id) exitWithError(400, "Provide id, --ids, --ids-file, or --input");
167
311
  const bodyFromFlags = {};
168
312
  const parsedName = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
169
313
  if (parsedName !== void 0) bodyFromFlags.name = parsedName;
@@ -177,14 +321,18 @@ function register(program, helpers) {
177
321
  const data = res.data?.data ?? res.data;
178
322
  printOutput(getOutputFormat(), data ?? {}, false);
179
323
  });
180
- projectsCmd.command("delete <id>").description("Delete projects").option("--ids <ids>", "Comma-separated IDs for bulk delete (max 25)").hook("preAction", (thisCommand) => {
324
+ projectsCmd.command("delete [id]").description("Delete projects").option("--ids <ids>", "Comma-separated IDs for bulk delete (max 25)").option("--ids-file <path>", "File containing IDs for bulk delete").option("--dry-run", "Preview delete without sending requests").option("--yes", "Confirm destructive bulk delete").hook("preAction", (thisCommand) => {
181
325
  syncRootOptsFromCommand(thisCommand);
182
326
  }).action(async (id, opts) => {
183
327
  const token = requireToken();
184
328
  if (!token) exitWithError(401, "Missing or invalid token");
185
- const ids = parseIds(opts.ids);
329
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
186
330
  if (ids.length > 0) {
187
331
  validateBulkIds(ids, "projects");
332
+ if (opts.dryRun) {
333
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_delete" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
334
+ return;
335
+ }
188
336
  const { data, summary } = await runBulk({
189
337
  items: ids,
190
338
  runOne: async (itemId) => {
@@ -196,12 +344,17 @@ function register(program, helpers) {
196
344
  printOutput(getOutputFormat(), { data, summary }, false);
197
345
  return;
198
346
  }
347
+ if (!id) exitWithError(400, "Provide id, --ids, or --ids-file");
348
+ if (opts.dryRun) {
349
+ printOutput(getOutputFormat(), { _id: id, status: "would_delete" }, false);
350
+ return;
351
+ }
199
352
  const res = await apiRequest({ method: "DELETE", path: "projects/" + encodeURIComponent(id), token, baseUrl: getBaseUrl() });
200
353
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
201
354
  if (getOutputFormat() === "json") console.log(JSON.stringify({ data: null, deleted: true }));
202
355
  });
203
356
  const spacesCmd = program.command("spaces").description("spaces resource");
204
- spacesCmd.command("list").description("List spaces").option("--limit <n>", "Max results").option("--offset <n>", "Skip n results").option("--organization-id <value>", "Filter by organizationId").option("--project-id <value>", "Filter by projectId").hook("preAction", (thisCommand) => {
357
+ spacesCmd.command("list").description("List spaces").option("--limit <n>", "Max results").option("--offset <n>", "Skip n results").option("--all", "Fetch all pages").option("--fields <fields>", "Comma-separated fields to print").option("--where <field=value>", "Client-side exact filters, comma-separated").option("--jsonl", "Print one compact JSON object per row").option("--organization-id <value>", "Filter by organizationId").option("--project-id <value>", "Filter by projectId").option("--include <value>", "Filter by include").hook("preAction", (thisCommand) => {
205
358
  syncRootOptsFromCommand(thisCommand);
206
359
  }).action(async (opts) => {
207
360
  const token = requireToken();
@@ -215,17 +368,23 @@ function register(program, helpers) {
215
368
  if (opts.projectId != null) {
216
369
  query.projectId = coerceQueryFlag(opts.projectId, { field: "projectId", flag: "--project-id", kind: "string" }, exitWithError);
217
370
  }
218
- const res = await apiRequest({ method: "GET", path: "spaces", token, baseUrl: getBaseUrl(), query });
371
+ if (opts.include != null) {
372
+ query.include = coerceQueryFlag(opts.include, { field: "include", flag: "--include", kind: "string" }, exitWithError);
373
+ }
374
+ const res = await fetchList({ path: "spaces", token, baseUrl: getBaseUrl(), query, all: Boolean(opts.all), request: apiRequest });
219
375
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
220
376
  const data = res.data?.data ?? [];
221
- printOutput(getOutputFormat(), data, false);
377
+ printList({ format: getOutputFormat(), data, fields: opts.fields, where: opts.where, jsonl: Boolean(opts.jsonl) });
222
378
  });
223
- spacesCmd.command("get <id>").description("Get one spaces by id").hook("preAction", (thisCommand) => {
379
+ spacesCmd.command("get <id>").description("Get one spaces by id").option("--include <value>", "Include/filter by include").hook("preAction", (thisCommand) => {
224
380
  syncRootOptsFromCommand(thisCommand);
225
381
  }).action(async (id, opts) => {
226
382
  const token = requireToken();
227
383
  if (!token) exitWithError(401, "Missing or invalid token");
228
384
  const query = {};
385
+ if (opts.include != null) {
386
+ query.include = coerceQueryFlag(opts.include, { field: "include", flag: "--include", kind: "string" }, exitWithError);
387
+ }
229
388
  const res = await apiRequest({ method: "GET", path: "spaces/" + encodeURIComponent(id), token, baseUrl: getBaseUrl(), query });
230
389
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
231
390
  const data = res.data?.data ?? res.data;
@@ -253,20 +412,75 @@ function register(program, helpers) {
253
412
  if (parsedCurrentStrength !== void 0) bodyFromFlags.currentStrength = parsedCurrentStrength;
254
413
  const body = mergeBody(bodyFromFlags, opts.body);
255
414
  if (opts.ifNotExists) {
415
+ const existing = await findExistingResource({
416
+ resource: "spaces",
417
+ token,
418
+ baseUrl: getBaseUrl(),
419
+ body,
420
+ identityFields: ["name", "organizationId", "projectId"],
421
+ queryFields: ["organizationId", "projectId", "include"],
422
+ request: apiRequest
423
+ });
424
+ if (existing) {
425
+ printOutput(getOutputFormat(), existing, false);
426
+ return;
427
+ }
256
428
  }
257
429
  const res = await apiRequest({ method: "POST", path: "spaces", token, baseUrl: getBaseUrl(), body });
258
430
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
259
431
  const data = res.data?.data ?? res.data;
260
432
  printOutput(getOutputFormat(), data ?? {}, false);
261
433
  });
262
- spacesCmd.command("update <id>").description("Update spaces").option("--body <json>", "Request body JSON (merged with flags)").option("--ids <ids>", "Comma-separated IDs for bulk update (max 25)").option("--name <value>", "name").option("--description <value>", "description").option("--project-id <value>", "projectId").option("--status <value>", "status").option("--priority <value>", "priority").option("--current-strength <value>", "currentStrength").hook("preAction", (thisCommand) => {
434
+ spacesCmd.command("update [id]").description("Update spaces").option("--body <json>", "Request body JSON (merged with flags)").option("--ids <ids>", "Comma-separated IDs for bulk update (max 25)").option("--ids-file <path>", "File containing IDs for bulk update").option("--input <path>", "JSON/JSONL records for bulk update; use - for stdin").option("--dry-run", "Preview bulk update without sending requests").option("--name <value>", "name").option("--description <value>", "description").option("--project-id <value>", "projectId").option("--status <value>", "status").option("--priority <value>", "priority").option("--current-strength <value>", "currentStrength").hook("preAction", (thisCommand) => {
263
435
  syncRootOptsFromCommand(thisCommand);
264
436
  }).action(async (id, opts) => {
265
437
  const token = requireToken();
266
438
  if (!token) exitWithError(401, "Missing or invalid token");
267
- const ids = parseIds(opts.ids);
439
+ const inputRecords = await parseBulkInput(opts.input);
440
+ if (inputRecords.length > 0) {
441
+ const ids2 = inputRecords.map(getBulkRecordId).filter((value) => value !== null);
442
+ if (ids2.length !== inputRecords.length) exitWithError(400, "Each --input record must include id or _id");
443
+ validateBulkIds(ids2, "spaces");
444
+ if (opts.dryRun) {
445
+ printOutput(getOutputFormat(), { data: ids2.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids2.length, succeeded: ids2.length, failed: 0 } }, false);
446
+ return;
447
+ }
448
+ const { data: data2, summary } = await runBulk({
449
+ items: inputRecords,
450
+ runOne: async (record) => {
451
+ const itemId = getBulkRecordId(record);
452
+ const recordBody = { ...record };
453
+ delete recordBody.id;
454
+ delete recordBody._id;
455
+ const bodyFromFlags2 = {};
456
+ const parsedName2 = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
457
+ if (parsedName2 !== void 0) bodyFromFlags2.name = parsedName2;
458
+ const parsedDescription2 = coerceBodyFlag(opts.description, { field: "description", flag: "--description", kind: "string" }, exitWithError);
459
+ if (parsedDescription2 !== void 0) bodyFromFlags2.description = parsedDescription2;
460
+ const parsedProjectId2 = coerceBodyFlag(opts.projectId, { field: "projectId", flag: "--project-id", kind: "string", allowNull: true }, exitWithError);
461
+ if (parsedProjectId2 !== void 0) bodyFromFlags2.projectId = parsedProjectId2;
462
+ const parsedStatus2 = coerceBodyFlag(opts.status, { field: "status", flag: "--status", kind: "string" }, exitWithError);
463
+ if (parsedStatus2 !== void 0) bodyFromFlags2.status = parsedStatus2;
464
+ const parsedPriority2 = coerceBodyFlag(opts.priority, { field: "priority", flag: "--priority", kind: "string" }, exitWithError);
465
+ if (parsedPriority2 !== void 0) bodyFromFlags2.priority = parsedPriority2;
466
+ const parsedCurrentStrength2 = coerceBodyFlag(opts.currentStrength, { field: "currentStrength", flag: "--current-strength", kind: "number" }, exitWithError);
467
+ if (parsedCurrentStrength2 !== void 0) bodyFromFlags2.currentStrength = parsedCurrentStrength2;
468
+ const body2 = { ...recordBody, ...mergeBody(bodyFromFlags2, opts.body) };
469
+ const res2 = await apiRequest({ method: "PATCH", path: "spaces/" + encodeURIComponent(itemId), token, baseUrl: getBaseUrl(), body: body2 });
470
+ return { ok: res2.ok, status: res2.status || 0, data: res2.data, error: res2.error };
471
+ },
472
+ toResult: (record, ok, status, _data, err) => ok ? { _id: getBulkRecordId(record), status: "updated" } : { _id: getBulkRecordId(record) ?? void 0, error: true, statusCode: status, message: err ?? "Failed" }
473
+ });
474
+ printOutput(getOutputFormat(), { data: data2, summary }, false);
475
+ return;
476
+ }
477
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
268
478
  if (ids.length > 0) {
269
479
  validateBulkIds(ids, "spaces");
480
+ if (opts.dryRun) {
481
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
482
+ return;
483
+ }
270
484
  const { data: data2, summary } = await runBulk({
271
485
  items: ids,
272
486
  runOne: async (itemId) => {
@@ -292,6 +506,7 @@ function register(program, helpers) {
292
506
  printOutput(getOutputFormat(), { data: data2, summary }, false);
293
507
  return;
294
508
  }
509
+ if (!id) exitWithError(400, "Provide id, --ids, --ids-file, or --input");
295
510
  const bodyFromFlags = {};
296
511
  const parsedName = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
297
512
  if (parsedName !== void 0) bodyFromFlags.name = parsedName;
@@ -311,14 +526,18 @@ function register(program, helpers) {
311
526
  const data = res.data?.data ?? res.data;
312
527
  printOutput(getOutputFormat(), data ?? {}, false);
313
528
  });
314
- spacesCmd.command("delete <id>").description("Delete spaces").option("--ids <ids>", "Comma-separated IDs for bulk delete (max 25)").hook("preAction", (thisCommand) => {
529
+ spacesCmd.command("delete [id]").description("Delete spaces").option("--ids <ids>", "Comma-separated IDs for bulk delete (max 25)").option("--ids-file <path>", "File containing IDs for bulk delete").option("--dry-run", "Preview delete without sending requests").option("--yes", "Confirm destructive bulk delete").hook("preAction", (thisCommand) => {
315
530
  syncRootOptsFromCommand(thisCommand);
316
531
  }).action(async (id, opts) => {
317
532
  const token = requireToken();
318
533
  if (!token) exitWithError(401, "Missing or invalid token");
319
- const ids = parseIds(opts.ids);
534
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
320
535
  if (ids.length > 0) {
321
536
  validateBulkIds(ids, "spaces");
537
+ if (opts.dryRun) {
538
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_delete" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
539
+ return;
540
+ }
322
541
  const { data, summary } = await runBulk({
323
542
  items: ids,
324
543
  runOne: async (itemId) => {
@@ -330,12 +549,17 @@ function register(program, helpers) {
330
549
  printOutput(getOutputFormat(), { data, summary }, false);
331
550
  return;
332
551
  }
552
+ if (!id) exitWithError(400, "Provide id, --ids, or --ids-file");
553
+ if (opts.dryRun) {
554
+ printOutput(getOutputFormat(), { _id: id, status: "would_delete" }, false);
555
+ return;
556
+ }
333
557
  const res = await apiRequest({ method: "DELETE", path: "spaces/" + encodeURIComponent(id), token, baseUrl: getBaseUrl() });
334
558
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
335
559
  if (getOutputFormat() === "json") console.log(JSON.stringify({ data: null, deleted: true }));
336
560
  });
337
561
  const scoutsCmd = program.command("scouts").description("scouts resource");
338
- scoutsCmd.command("list").description("List scouts").option("--limit <n>", "Max results").option("--offset <n>", "Skip n results").option("--organization-id <value>", "Filter by organizationId").option("--category-id <value>", "Filter by categoryId").hook("preAction", (thisCommand) => {
562
+ scoutsCmd.command("list").description("List scouts").option("--limit <n>", "Max results").option("--offset <n>", "Skip n results").option("--all", "Fetch all pages").option("--fields <fields>", "Comma-separated fields to print").option("--where <field=value>", "Client-side exact filters, comma-separated").option("--jsonl", "Print one compact JSON object per row").option("--organization-id <value>", "Filter by organizationId").option("--category-id <value>", "Filter by categoryId").option("--include <value>", "Filter by include").hook("preAction", (thisCommand) => {
339
563
  syncRootOptsFromCommand(thisCommand);
340
564
  }).action(async (opts) => {
341
565
  const token = requireToken();
@@ -349,17 +573,23 @@ function register(program, helpers) {
349
573
  if (opts.categoryId != null) {
350
574
  query.categoryId = coerceQueryFlag(opts.categoryId, { field: "categoryId", flag: "--category-id", kind: "string" }, exitWithError);
351
575
  }
352
- const res = await apiRequest({ method: "GET", path: "scouts", token, baseUrl: getBaseUrl(), query });
576
+ if (opts.include != null) {
577
+ query.include = coerceQueryFlag(opts.include, { field: "include", flag: "--include", kind: "string" }, exitWithError);
578
+ }
579
+ const res = await fetchList({ path: "scouts", token, baseUrl: getBaseUrl(), query, all: Boolean(opts.all), request: apiRequest });
353
580
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
354
581
  const data = res.data?.data ?? [];
355
- printOutput(getOutputFormat(), data, false);
582
+ printList({ format: getOutputFormat(), data, fields: opts.fields, where: opts.where, jsonl: Boolean(opts.jsonl) });
356
583
  });
357
- scoutsCmd.command("get <id>").description("Get one scouts by id").hook("preAction", (thisCommand) => {
584
+ scoutsCmd.command("get <id>").description("Get one scouts by id").option("--include <value>", "Include/filter by include").hook("preAction", (thisCommand) => {
358
585
  syncRootOptsFromCommand(thisCommand);
359
586
  }).action(async (id, opts) => {
360
587
  const token = requireToken();
361
588
  if (!token) exitWithError(401, "Missing or invalid token");
362
589
  const query = {};
590
+ if (opts.include != null) {
591
+ query.include = coerceQueryFlag(opts.include, { field: "include", flag: "--include", kind: "string" }, exitWithError);
592
+ }
363
593
  const res = await apiRequest({ method: "GET", path: "scouts/" + encodeURIComponent(id), token, baseUrl: getBaseUrl(), query });
364
594
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
365
595
  const data = res.data?.data ?? res.data;
@@ -413,20 +643,99 @@ function register(program, helpers) {
413
643
  if (parsedActivationPotential !== void 0) bodyFromFlags.activationPotential = parsedActivationPotential;
414
644
  const body = mergeBody(bodyFromFlags, opts.body);
415
645
  if (opts.ifNotExists) {
646
+ const existing = await findExistingResource({
647
+ resource: "scouts",
648
+ token,
649
+ baseUrl: getBaseUrl(),
650
+ body,
651
+ identityFields: ["name", "organizationId"],
652
+ queryFields: ["organizationId", "categoryId", "include"],
653
+ request: apiRequest
654
+ });
655
+ if (existing) {
656
+ printOutput(getOutputFormat(), existing, false);
657
+ return;
658
+ }
416
659
  }
417
660
  const res = await apiRequest({ method: "POST", path: "scouts", token, baseUrl: getBaseUrl(), body });
418
661
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
419
662
  const data = res.data?.data ?? res.data;
420
663
  printOutput(getOutputFormat(), data ?? {}, false);
421
664
  });
422
- scoutsCmd.command("update <id>").description("Update scouts").option("--body <json>", "Request body JSON (merged with flags)").option("--ids <ids>", "Comma-separated IDs for bulk update (max 25)").option("--name <value>", "name").option("--description <value>", "description").option("--category-id <value>", "categoryId").option("--url <value>", "url").option("--hero-image <value>", "heroImage").option("--priority <value>", "priority").option("--status <value>", "status").option("--estimated-cost <value>", "estimatedCost").option("--potential-cost <value>", "potentialCost").option("--need <value>", "need").option("--urgency <value>", "urgency").option("--use <value>", "use").option("--longevity <value>", "longevity").option("--roi <value>", "roi").option("--system-fit <value>", "systemFit").option("--emotional-pull <value>", "emotionalPull").option("--infrastructure-fit <value>", "infrastructureFit").option("--activation-potential <value>", "activationPotential").hook("preAction", (thisCommand) => {
665
+ scoutsCmd.command("update [id]").description("Update scouts").option("--body <json>", "Request body JSON (merged with flags)").option("--ids <ids>", "Comma-separated IDs for bulk update (max 25)").option("--ids-file <path>", "File containing IDs for bulk update").option("--input <path>", "JSON/JSONL records for bulk update; use - for stdin").option("--dry-run", "Preview bulk update without sending requests").option("--name <value>", "name").option("--description <value>", "description").option("--category-id <value>", "categoryId").option("--url <value>", "url").option("--hero-image <value>", "heroImage").option("--priority <value>", "priority").option("--status <value>", "status").option("--estimated-cost <value>", "estimatedCost").option("--potential-cost <value>", "potentialCost").option("--need <value>", "need").option("--urgency <value>", "urgency").option("--use <value>", "use").option("--longevity <value>", "longevity").option("--roi <value>", "roi").option("--system-fit <value>", "systemFit").option("--emotional-pull <value>", "emotionalPull").option("--infrastructure-fit <value>", "infrastructureFit").option("--activation-potential <value>", "activationPotential").hook("preAction", (thisCommand) => {
423
666
  syncRootOptsFromCommand(thisCommand);
424
667
  }).action(async (id, opts) => {
425
668
  const token = requireToken();
426
669
  if (!token) exitWithError(401, "Missing or invalid token");
427
- const ids = parseIds(opts.ids);
670
+ const inputRecords = await parseBulkInput(opts.input);
671
+ if (inputRecords.length > 0) {
672
+ const ids2 = inputRecords.map(getBulkRecordId).filter((value) => value !== null);
673
+ if (ids2.length !== inputRecords.length) exitWithError(400, "Each --input record must include id or _id");
674
+ validateBulkIds(ids2, "scouts");
675
+ if (opts.dryRun) {
676
+ printOutput(getOutputFormat(), { data: ids2.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids2.length, succeeded: ids2.length, failed: 0 } }, false);
677
+ return;
678
+ }
679
+ const { data: data2, summary } = await runBulk({
680
+ items: inputRecords,
681
+ runOne: async (record) => {
682
+ const itemId = getBulkRecordId(record);
683
+ const recordBody = { ...record };
684
+ delete recordBody.id;
685
+ delete recordBody._id;
686
+ const bodyFromFlags2 = {};
687
+ const parsedName2 = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
688
+ if (parsedName2 !== void 0) bodyFromFlags2.name = parsedName2;
689
+ const parsedDescription2 = coerceBodyFlag(opts.description, { field: "description", flag: "--description", kind: "string" }, exitWithError);
690
+ if (parsedDescription2 !== void 0) bodyFromFlags2.description = parsedDescription2;
691
+ const parsedCategoryId2 = coerceBodyFlag(opts.categoryId, { field: "categoryId", flag: "--category-id", kind: "string", allowNull: true }, exitWithError);
692
+ if (parsedCategoryId2 !== void 0) bodyFromFlags2.categoryId = parsedCategoryId2;
693
+ const parsedUrl2 = coerceBodyFlag(opts.url, { field: "url", flag: "--url", kind: "string" }, exitWithError);
694
+ if (parsedUrl2 !== void 0) bodyFromFlags2.url = parsedUrl2;
695
+ const parsedHeroImage2 = coerceBodyFlag(opts.heroImage, { field: "heroImage", flag: "--hero-image", kind: "string" }, exitWithError);
696
+ if (parsedHeroImage2 !== void 0) bodyFromFlags2.heroImage = parsedHeroImage2;
697
+ const parsedPriority2 = coerceBodyFlag(opts.priority, { field: "priority", flag: "--priority", kind: "string" }, exitWithError);
698
+ if (parsedPriority2 !== void 0) bodyFromFlags2.priority = parsedPriority2;
699
+ const parsedStatus2 = coerceBodyFlag(opts.status, { field: "status", flag: "--status", kind: "string" }, exitWithError);
700
+ if (parsedStatus2 !== void 0) bodyFromFlags2.status = parsedStatus2;
701
+ const parsedEstimatedCost2 = coerceBodyFlag(opts.estimatedCost, { field: "estimatedCost", flag: "--estimated-cost", kind: "number" }, exitWithError);
702
+ if (parsedEstimatedCost2 !== void 0) bodyFromFlags2.estimatedCost = parsedEstimatedCost2;
703
+ const parsedPotentialCost2 = coerceBodyFlag(opts.potentialCost, { field: "potentialCost", flag: "--potential-cost", kind: "number" }, exitWithError);
704
+ if (parsedPotentialCost2 !== void 0) bodyFromFlags2.potentialCost = parsedPotentialCost2;
705
+ const parsedNeed2 = coerceBodyFlag(opts.need, { field: "need", flag: "--need", kind: "number" }, exitWithError);
706
+ if (parsedNeed2 !== void 0) bodyFromFlags2.need = parsedNeed2;
707
+ const parsedUrgency2 = coerceBodyFlag(opts.urgency, { field: "urgency", flag: "--urgency", kind: "number" }, exitWithError);
708
+ if (parsedUrgency2 !== void 0) bodyFromFlags2.urgency = parsedUrgency2;
709
+ const parsedUse2 = coerceBodyFlag(opts.use, { field: "use", flag: "--use", kind: "number" }, exitWithError);
710
+ if (parsedUse2 !== void 0) bodyFromFlags2.use = parsedUse2;
711
+ const parsedLongevity2 = coerceBodyFlag(opts.longevity, { field: "longevity", flag: "--longevity", kind: "number" }, exitWithError);
712
+ if (parsedLongevity2 !== void 0) bodyFromFlags2.longevity = parsedLongevity2;
713
+ const parsedRoi2 = coerceBodyFlag(opts.roi, { field: "roi", flag: "--roi", kind: "number" }, exitWithError);
714
+ if (parsedRoi2 !== void 0) bodyFromFlags2.roi = parsedRoi2;
715
+ const parsedSystemFit2 = coerceBodyFlag(opts.systemFit, { field: "systemFit", flag: "--system-fit", kind: "number" }, exitWithError);
716
+ if (parsedSystemFit2 !== void 0) bodyFromFlags2.systemFit = parsedSystemFit2;
717
+ const parsedEmotionalPull2 = coerceBodyFlag(opts.emotionalPull, { field: "emotionalPull", flag: "--emotional-pull", kind: "number" }, exitWithError);
718
+ if (parsedEmotionalPull2 !== void 0) bodyFromFlags2.emotionalPull = parsedEmotionalPull2;
719
+ const parsedInfrastructureFit2 = coerceBodyFlag(opts.infrastructureFit, { field: "infrastructureFit", flag: "--infrastructure-fit", kind: "number" }, exitWithError);
720
+ if (parsedInfrastructureFit2 !== void 0) bodyFromFlags2.infrastructureFit = parsedInfrastructureFit2;
721
+ const parsedActivationPotential2 = coerceBodyFlag(opts.activationPotential, { field: "activationPotential", flag: "--activation-potential", kind: "number" }, exitWithError);
722
+ if (parsedActivationPotential2 !== void 0) bodyFromFlags2.activationPotential = parsedActivationPotential2;
723
+ const body2 = { ...recordBody, ...mergeBody(bodyFromFlags2, opts.body) };
724
+ const res2 = await apiRequest({ method: "PATCH", path: "scouts/" + encodeURIComponent(itemId), token, baseUrl: getBaseUrl(), body: body2 });
725
+ return { ok: res2.ok, status: res2.status || 0, data: res2.data, error: res2.error };
726
+ },
727
+ toResult: (record, ok, status, _data, err) => ok ? { _id: getBulkRecordId(record), status: "updated" } : { _id: getBulkRecordId(record) ?? void 0, error: true, statusCode: status, message: err ?? "Failed" }
728
+ });
729
+ printOutput(getOutputFormat(), { data: data2, summary }, false);
730
+ return;
731
+ }
732
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
428
733
  if (ids.length > 0) {
429
734
  validateBulkIds(ids, "scouts");
735
+ if (opts.dryRun) {
736
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
737
+ return;
738
+ }
430
739
  const { data: data2, summary } = await runBulk({
431
740
  items: ids,
432
741
  runOne: async (itemId) => {
@@ -476,6 +785,7 @@ function register(program, helpers) {
476
785
  printOutput(getOutputFormat(), { data: data2, summary }, false);
477
786
  return;
478
787
  }
788
+ if (!id) exitWithError(400, "Provide id, --ids, --ids-file, or --input");
479
789
  const bodyFromFlags = {};
480
790
  const parsedName = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
481
791
  if (parsedName !== void 0) bodyFromFlags.name = parsedName;
@@ -519,14 +829,18 @@ function register(program, helpers) {
519
829
  const data = res.data?.data ?? res.data;
520
830
  printOutput(getOutputFormat(), data ?? {}, false);
521
831
  });
522
- scoutsCmd.command("delete <id>").description("Delete scouts").option("--ids <ids>", "Comma-separated IDs for bulk delete (max 25)").hook("preAction", (thisCommand) => {
832
+ scoutsCmd.command("delete [id]").description("Delete scouts").option("--ids <ids>", "Comma-separated IDs for bulk delete (max 25)").option("--ids-file <path>", "File containing IDs for bulk delete").option("--dry-run", "Preview delete without sending requests").option("--yes", "Confirm destructive bulk delete").hook("preAction", (thisCommand) => {
523
833
  syncRootOptsFromCommand(thisCommand);
524
834
  }).action(async (id, opts) => {
525
835
  const token = requireToken();
526
836
  if (!token) exitWithError(401, "Missing or invalid token");
527
- const ids = parseIds(opts.ids);
837
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
528
838
  if (ids.length > 0) {
529
839
  validateBulkIds(ids, "scouts");
840
+ if (opts.dryRun) {
841
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_delete" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
842
+ return;
843
+ }
530
844
  const { data, summary } = await runBulk({
531
845
  items: ids,
532
846
  runOne: async (itemId) => {
@@ -538,12 +852,17 @@ function register(program, helpers) {
538
852
  printOutput(getOutputFormat(), { data, summary }, false);
539
853
  return;
540
854
  }
855
+ if (!id) exitWithError(400, "Provide id, --ids, or --ids-file");
856
+ if (opts.dryRun) {
857
+ printOutput(getOutputFormat(), { _id: id, status: "would_delete" }, false);
858
+ return;
859
+ }
541
860
  const res = await apiRequest({ method: "DELETE", path: "scouts/" + encodeURIComponent(id), token, baseUrl: getBaseUrl() });
542
861
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
543
862
  if (getOutputFormat() === "json") console.log(JSON.stringify({ data: null, deleted: true }));
544
863
  });
545
864
  const assetsCmd = program.command("assets").description("assets resource");
546
- assetsCmd.command("list").description("List assets").option("--limit <n>", "Max results").option("--offset <n>", "Skip n results").option("--organization-id <value>", "Filter by organizationId").option("--scout-id <value>", "Filter by scoutId").hook("preAction", (thisCommand) => {
865
+ assetsCmd.command("list").description("List assets").option("--limit <n>", "Max results").option("--offset <n>", "Skip n results").option("--all", "Fetch all pages").option("--fields <fields>", "Comma-separated fields to print").option("--where <field=value>", "Client-side exact filters, comma-separated").option("--jsonl", "Print one compact JSON object per row").option("--organization-id <value>", "Filter by organizationId").option("--scout-id <value>", "Filter by scoutId").option("--include <value>", "Filter by include").hook("preAction", (thisCommand) => {
547
866
  syncRootOptsFromCommand(thisCommand);
548
867
  }).action(async (opts) => {
549
868
  const token = requireToken();
@@ -557,17 +876,23 @@ function register(program, helpers) {
557
876
  if (opts.scoutId != null) {
558
877
  query.scoutId = coerceQueryFlag(opts.scoutId, { field: "scoutId", flag: "--scout-id", kind: "string" }, exitWithError);
559
878
  }
560
- const res = await apiRequest({ method: "GET", path: "assets", token, baseUrl: getBaseUrl(), query });
879
+ if (opts.include != null) {
880
+ query.include = coerceQueryFlag(opts.include, { field: "include", flag: "--include", kind: "string" }, exitWithError);
881
+ }
882
+ const res = await fetchList({ path: "assets", token, baseUrl: getBaseUrl(), query, all: Boolean(opts.all), request: apiRequest });
561
883
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
562
884
  const data = res.data?.data ?? [];
563
- printOutput(getOutputFormat(), data, false);
885
+ printList({ format: getOutputFormat(), data, fields: opts.fields, where: opts.where, jsonl: Boolean(opts.jsonl) });
564
886
  });
565
- assetsCmd.command("get <id>").description("Get one assets by id").hook("preAction", (thisCommand) => {
887
+ assetsCmd.command("get <id>").description("Get one assets by id").option("--include <value>", "Include/filter by include").hook("preAction", (thisCommand) => {
566
888
  syncRootOptsFromCommand(thisCommand);
567
889
  }).action(async (id, opts) => {
568
890
  const token = requireToken();
569
891
  if (!token) exitWithError(401, "Missing or invalid token");
570
892
  const query = {};
893
+ if (opts.include != null) {
894
+ query.include = coerceQueryFlag(opts.include, { field: "include", flag: "--include", kind: "string" }, exitWithError);
895
+ }
571
896
  const res = await apiRequest({ method: "GET", path: "assets/" + encodeURIComponent(id), token, baseUrl: getBaseUrl(), query });
572
897
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
573
898
  const data = res.data?.data ?? res.data;
@@ -607,20 +932,87 @@ function register(program, helpers) {
607
932
  if (parsedCost !== void 0) bodyFromFlags.cost = parsedCost;
608
933
  const body = mergeBody(bodyFromFlags, opts.body);
609
934
  if (opts.ifNotExists) {
935
+ const existing = await findExistingResource({
936
+ resource: "assets",
937
+ token,
938
+ baseUrl: getBaseUrl(),
939
+ body,
940
+ identityFields: ["name", "organizationId"],
941
+ queryFields: ["organizationId", "scoutId", "include"],
942
+ request: apiRequest
943
+ });
944
+ if (existing) {
945
+ printOutput(getOutputFormat(), existing, false);
946
+ return;
947
+ }
610
948
  }
611
949
  const res = await apiRequest({ method: "POST", path: "assets", token, baseUrl: getBaseUrl(), body });
612
950
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
613
951
  const data = res.data?.data ?? res.data;
614
952
  printOutput(getOutputFormat(), data ?? {}, false);
615
953
  });
616
- assetsCmd.command("update <id>").description("Update assets").option("--body <json>", "Request body JSON (merged with flags)").option("--ids <ids>", "Comma-separated IDs for bulk update (max 25)").option("--name <value>", "name").option("--description <value>", "description").option("--scout-id <value>", "scoutId").option("--category-id <value>", "categoryId").option("--hero-image <value>", "heroImage").option("--condition <value>", "condition").option("--satisfaction <value>", "satisfaction").option("--usage <value>", "usage").option("--friction <value>", "friction").option("--lifetime-value <value>", "lifetimeValue").option("--acquisition-date <value>", "acquisitionDate").option("--cost <value>", "cost").hook("preAction", (thisCommand) => {
954
+ assetsCmd.command("update [id]").description("Update assets").option("--body <json>", "Request body JSON (merged with flags)").option("--ids <ids>", "Comma-separated IDs for bulk update (max 25)").option("--ids-file <path>", "File containing IDs for bulk update").option("--input <path>", "JSON/JSONL records for bulk update; use - for stdin").option("--dry-run", "Preview bulk update without sending requests").option("--name <value>", "name").option("--description <value>", "description").option("--scout-id <value>", "scoutId").option("--category-id <value>", "categoryId").option("--hero-image <value>", "heroImage").option("--condition <value>", "condition").option("--satisfaction <value>", "satisfaction").option("--usage <value>", "usage").option("--friction <value>", "friction").option("--lifetime-value <value>", "lifetimeValue").option("--acquisition-date <value>", "acquisitionDate").option("--cost <value>", "cost").hook("preAction", (thisCommand) => {
617
955
  syncRootOptsFromCommand(thisCommand);
618
956
  }).action(async (id, opts) => {
619
957
  const token = requireToken();
620
958
  if (!token) exitWithError(401, "Missing or invalid token");
621
- const ids = parseIds(opts.ids);
959
+ const inputRecords = await parseBulkInput(opts.input);
960
+ if (inputRecords.length > 0) {
961
+ const ids2 = inputRecords.map(getBulkRecordId).filter((value) => value !== null);
962
+ if (ids2.length !== inputRecords.length) exitWithError(400, "Each --input record must include id or _id");
963
+ validateBulkIds(ids2, "assets");
964
+ if (opts.dryRun) {
965
+ printOutput(getOutputFormat(), { data: ids2.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids2.length, succeeded: ids2.length, failed: 0 } }, false);
966
+ return;
967
+ }
968
+ const { data: data2, summary } = await runBulk({
969
+ items: inputRecords,
970
+ runOne: async (record) => {
971
+ const itemId = getBulkRecordId(record);
972
+ const recordBody = { ...record };
973
+ delete recordBody.id;
974
+ delete recordBody._id;
975
+ const bodyFromFlags2 = {};
976
+ const parsedName2 = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
977
+ if (parsedName2 !== void 0) bodyFromFlags2.name = parsedName2;
978
+ const parsedDescription2 = coerceBodyFlag(opts.description, { field: "description", flag: "--description", kind: "string" }, exitWithError);
979
+ if (parsedDescription2 !== void 0) bodyFromFlags2.description = parsedDescription2;
980
+ const parsedScoutId2 = coerceBodyFlag(opts.scoutId, { field: "scoutId", flag: "--scout-id", kind: "string", allowNull: true }, exitWithError);
981
+ if (parsedScoutId2 !== void 0) bodyFromFlags2.scoutId = parsedScoutId2;
982
+ const parsedCategoryId2 = coerceBodyFlag(opts.categoryId, { field: "categoryId", flag: "--category-id", kind: "string", allowNull: true }, exitWithError);
983
+ if (parsedCategoryId2 !== void 0) bodyFromFlags2.categoryId = parsedCategoryId2;
984
+ const parsedHeroImage2 = coerceBodyFlag(opts.heroImage, { field: "heroImage", flag: "--hero-image", kind: "string" }, exitWithError);
985
+ if (parsedHeroImage2 !== void 0) bodyFromFlags2.heroImage = parsedHeroImage2;
986
+ const parsedCondition2 = coerceBodyFlag(opts.condition, { field: "condition", flag: "--condition", kind: "number" }, exitWithError);
987
+ if (parsedCondition2 !== void 0) bodyFromFlags2.condition = parsedCondition2;
988
+ const parsedSatisfaction2 = coerceBodyFlag(opts.satisfaction, { field: "satisfaction", flag: "--satisfaction", kind: "number" }, exitWithError);
989
+ if (parsedSatisfaction2 !== void 0) bodyFromFlags2.satisfaction = parsedSatisfaction2;
990
+ const parsedUsage2 = coerceBodyFlag(opts.usage, { field: "usage", flag: "--usage", kind: "number" }, exitWithError);
991
+ if (parsedUsage2 !== void 0) bodyFromFlags2.usage = parsedUsage2;
992
+ const parsedFriction2 = coerceBodyFlag(opts.friction, { field: "friction", flag: "--friction", kind: "number" }, exitWithError);
993
+ if (parsedFriction2 !== void 0) bodyFromFlags2.friction = parsedFriction2;
994
+ const parsedLifetimeValue2 = coerceBodyFlag(opts.lifetimeValue, { field: "lifetimeValue", flag: "--lifetime-value", kind: "number" }, exitWithError);
995
+ if (parsedLifetimeValue2 !== void 0) bodyFromFlags2.lifetimeValue = parsedLifetimeValue2;
996
+ const parsedAcquisitionDate2 = coerceBodyFlag(opts.acquisitionDate, { field: "acquisitionDate", flag: "--acquisition-date", kind: "number" }, exitWithError);
997
+ if (parsedAcquisitionDate2 !== void 0) bodyFromFlags2.acquisitionDate = parsedAcquisitionDate2;
998
+ const parsedCost2 = coerceBodyFlag(opts.cost, { field: "cost", flag: "--cost", kind: "number" }, exitWithError);
999
+ if (parsedCost2 !== void 0) bodyFromFlags2.cost = parsedCost2;
1000
+ const body2 = { ...recordBody, ...mergeBody(bodyFromFlags2, opts.body) };
1001
+ const res2 = await apiRequest({ method: "PATCH", path: "assets/" + encodeURIComponent(itemId), token, baseUrl: getBaseUrl(), body: body2 });
1002
+ return { ok: res2.ok, status: res2.status || 0, data: res2.data, error: res2.error };
1003
+ },
1004
+ toResult: (record, ok, status, _data, err) => ok ? { _id: getBulkRecordId(record), status: "updated" } : { _id: getBulkRecordId(record) ?? void 0, error: true, statusCode: status, message: err ?? "Failed" }
1005
+ });
1006
+ printOutput(getOutputFormat(), { data: data2, summary }, false);
1007
+ return;
1008
+ }
1009
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
622
1010
  if (ids.length > 0) {
623
1011
  validateBulkIds(ids, "assets");
1012
+ if (opts.dryRun) {
1013
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
1014
+ return;
1015
+ }
624
1016
  const { data: data2, summary } = await runBulk({
625
1017
  items: ids,
626
1018
  runOne: async (itemId) => {
@@ -658,6 +1050,7 @@ function register(program, helpers) {
658
1050
  printOutput(getOutputFormat(), { data: data2, summary }, false);
659
1051
  return;
660
1052
  }
1053
+ if (!id) exitWithError(400, "Provide id, --ids, --ids-file, or --input");
661
1054
  const bodyFromFlags = {};
662
1055
  const parsedName = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
663
1056
  if (parsedName !== void 0) bodyFromFlags.name = parsedName;
@@ -689,14 +1082,18 @@ function register(program, helpers) {
689
1082
  const data = res.data?.data ?? res.data;
690
1083
  printOutput(getOutputFormat(), data ?? {}, false);
691
1084
  });
692
- assetsCmd.command("delete <id>").description("Delete assets").option("--ids <ids>", "Comma-separated IDs for bulk delete (max 25)").hook("preAction", (thisCommand) => {
1085
+ assetsCmd.command("delete [id]").description("Delete assets").option("--ids <ids>", "Comma-separated IDs for bulk delete (max 25)").option("--ids-file <path>", "File containing IDs for bulk delete").option("--dry-run", "Preview delete without sending requests").option("--yes", "Confirm destructive bulk delete").hook("preAction", (thisCommand) => {
693
1086
  syncRootOptsFromCommand(thisCommand);
694
1087
  }).action(async (id, opts) => {
695
1088
  const token = requireToken();
696
1089
  if (!token) exitWithError(401, "Missing or invalid token");
697
- const ids = parseIds(opts.ids);
1090
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
698
1091
  if (ids.length > 0) {
699
1092
  validateBulkIds(ids, "assets");
1093
+ if (opts.dryRun) {
1094
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_delete" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
1095
+ return;
1096
+ }
700
1097
  const { data, summary } = await runBulk({
701
1098
  items: ids,
702
1099
  runOne: async (itemId) => {
@@ -708,12 +1105,17 @@ function register(program, helpers) {
708
1105
  printOutput(getOutputFormat(), { data, summary }, false);
709
1106
  return;
710
1107
  }
1108
+ if (!id) exitWithError(400, "Provide id, --ids, or --ids-file");
1109
+ if (opts.dryRun) {
1110
+ printOutput(getOutputFormat(), { _id: id, status: "would_delete" }, false);
1111
+ return;
1112
+ }
711
1113
  const res = await apiRequest({ method: "DELETE", path: "assets/" + encodeURIComponent(id), token, baseUrl: getBaseUrl() });
712
1114
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
713
1115
  if (getOutputFormat() === "json") console.log(JSON.stringify({ data: null, deleted: true }));
714
1116
  });
715
1117
  const categoriesCmd = program.command("categories").description("categories resource");
716
- categoriesCmd.command("list").description("List categories").option("--limit <n>", "Max results").option("--offset <n>", "Skip n results").option("--organization-id <value>", "Filter by organizationId").hook("preAction", (thisCommand) => {
1118
+ categoriesCmd.command("list").description("List categories").option("--limit <n>", "Max results").option("--offset <n>", "Skip n results").option("--all", "Fetch all pages").option("--fields <fields>", "Comma-separated fields to print").option("--where <field=value>", "Client-side exact filters, comma-separated").option("--jsonl", "Print one compact JSON object per row").option("--organization-id <value>", "Filter by organizationId").hook("preAction", (thisCommand) => {
717
1119
  syncRootOptsFromCommand(thisCommand);
718
1120
  }).action(async (opts) => {
719
1121
  const token = requireToken();
@@ -724,10 +1126,10 @@ function register(program, helpers) {
724
1126
  if (opts.organizationId != null) {
725
1127
  query.organizationId = coerceQueryFlag(opts.organizationId, { field: "organizationId", flag: "--organization-id", kind: "string" }, exitWithError);
726
1128
  }
727
- const res = await apiRequest({ method: "GET", path: "categories", token, baseUrl: getBaseUrl(), query });
1129
+ const res = await fetchList({ path: "categories", token, baseUrl: getBaseUrl(), query, all: Boolean(opts.all), request: apiRequest });
728
1130
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
729
1131
  const data = res.data?.data ?? [];
730
- printOutput(getOutputFormat(), data, false);
1132
+ printList({ format: getOutputFormat(), data, fields: opts.fields, where: opts.where, jsonl: Boolean(opts.jsonl) });
731
1133
  });
732
1134
  categoriesCmd.command("get <id>").description("Get one categories by id").hook("preAction", (thisCommand) => {
733
1135
  syncRootOptsFromCommand(thisCommand);
@@ -754,20 +1156,67 @@ function register(program, helpers) {
754
1156
  if (parsedDefaultBucket !== void 0) bodyFromFlags.defaultBucket = parsedDefaultBucket;
755
1157
  const body = mergeBody(bodyFromFlags, opts.body);
756
1158
  if (opts.ifNotExists) {
1159
+ const existing = await findExistingResource({
1160
+ resource: "categories",
1161
+ token,
1162
+ baseUrl: getBaseUrl(),
1163
+ body,
1164
+ identityFields: ["name", "organizationId"],
1165
+ queryFields: ["organizationId"],
1166
+ request: apiRequest
1167
+ });
1168
+ if (existing) {
1169
+ printOutput(getOutputFormat(), existing, false);
1170
+ return;
1171
+ }
757
1172
  }
758
1173
  const res = await apiRequest({ method: "POST", path: "categories", token, baseUrl: getBaseUrl(), body });
759
1174
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
760
1175
  const data = res.data?.data ?? res.data;
761
1176
  printOutput(getOutputFormat(), data ?? {}, false);
762
1177
  });
763
- categoriesCmd.command("update <id>").description("Update categories").option("--body <json>", "Request body JSON (merged with flags)").option("--ids <ids>", "Comma-separated IDs for bulk update (max 25)").option("--name <value>", "name").option("--default-bucket <value>", "defaultBucket").hook("preAction", (thisCommand) => {
1178
+ categoriesCmd.command("update [id]").description("Update categories").option("--body <json>", "Request body JSON (merged with flags)").option("--ids <ids>", "Comma-separated IDs for bulk update (max 25)").option("--ids-file <path>", "File containing IDs for bulk update").option("--input <path>", "JSON/JSONL records for bulk update; use - for stdin").option("--dry-run", "Preview bulk update without sending requests").option("--name <value>", "name").option("--default-bucket <value>", "defaultBucket").hook("preAction", (thisCommand) => {
764
1179
  syncRootOptsFromCommand(thisCommand);
765
1180
  }).action(async (id, opts) => {
766
1181
  const token = requireToken();
767
1182
  if (!token) exitWithError(401, "Missing or invalid token");
768
- const ids = parseIds(opts.ids);
1183
+ const inputRecords = await parseBulkInput(opts.input);
1184
+ if (inputRecords.length > 0) {
1185
+ const ids2 = inputRecords.map(getBulkRecordId).filter((value) => value !== null);
1186
+ if (ids2.length !== inputRecords.length) exitWithError(400, "Each --input record must include id or _id");
1187
+ validateBulkIds(ids2, "categories");
1188
+ if (opts.dryRun) {
1189
+ printOutput(getOutputFormat(), { data: ids2.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids2.length, succeeded: ids2.length, failed: 0 } }, false);
1190
+ return;
1191
+ }
1192
+ const { data: data2, summary } = await runBulk({
1193
+ items: inputRecords,
1194
+ runOne: async (record) => {
1195
+ const itemId = getBulkRecordId(record);
1196
+ const recordBody = { ...record };
1197
+ delete recordBody.id;
1198
+ delete recordBody._id;
1199
+ const bodyFromFlags2 = {};
1200
+ const parsedName2 = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
1201
+ if (parsedName2 !== void 0) bodyFromFlags2.name = parsedName2;
1202
+ const parsedDefaultBucket2 = coerceBodyFlag(opts.defaultBucket, { field: "defaultBucket", flag: "--default-bucket", kind: "string" }, exitWithError);
1203
+ if (parsedDefaultBucket2 !== void 0) bodyFromFlags2.defaultBucket = parsedDefaultBucket2;
1204
+ const body2 = { ...recordBody, ...mergeBody(bodyFromFlags2, opts.body) };
1205
+ const res2 = await apiRequest({ method: "PATCH", path: "categories/" + encodeURIComponent(itemId), token, baseUrl: getBaseUrl(), body: body2 });
1206
+ return { ok: res2.ok, status: res2.status || 0, data: res2.data, error: res2.error };
1207
+ },
1208
+ toResult: (record, ok, status, _data, err) => ok ? { _id: getBulkRecordId(record), status: "updated" } : { _id: getBulkRecordId(record) ?? void 0, error: true, statusCode: status, message: err ?? "Failed" }
1209
+ });
1210
+ printOutput(getOutputFormat(), { data: data2, summary }, false);
1211
+ return;
1212
+ }
1213
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
769
1214
  if (ids.length > 0) {
770
1215
  validateBulkIds(ids, "categories");
1216
+ if (opts.dryRun) {
1217
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
1218
+ return;
1219
+ }
771
1220
  const { data: data2, summary } = await runBulk({
772
1221
  items: ids,
773
1222
  runOne: async (itemId) => {
@@ -785,6 +1234,7 @@ function register(program, helpers) {
785
1234
  printOutput(getOutputFormat(), { data: data2, summary }, false);
786
1235
  return;
787
1236
  }
1237
+ if (!id) exitWithError(400, "Provide id, --ids, --ids-file, or --input");
788
1238
  const bodyFromFlags = {};
789
1239
  const parsedName = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
790
1240
  if (parsedName !== void 0) bodyFromFlags.name = parsedName;
@@ -796,14 +1246,18 @@ function register(program, helpers) {
796
1246
  const data = res.data?.data ?? res.data;
797
1247
  printOutput(getOutputFormat(), data ?? {}, false);
798
1248
  });
799
- categoriesCmd.command("delete <id>").description("Delete categories").option("--ids <ids>", "Comma-separated IDs for bulk delete (max 25)").hook("preAction", (thisCommand) => {
1249
+ categoriesCmd.command("delete [id]").description("Delete categories").option("--ids <ids>", "Comma-separated IDs for bulk delete (max 25)").option("--ids-file <path>", "File containing IDs for bulk delete").option("--dry-run", "Preview delete without sending requests").option("--yes", "Confirm destructive bulk delete").hook("preAction", (thisCommand) => {
800
1250
  syncRootOptsFromCommand(thisCommand);
801
1251
  }).action(async (id, opts) => {
802
1252
  const token = requireToken();
803
1253
  if (!token) exitWithError(401, "Missing or invalid token");
804
- const ids = parseIds(opts.ids);
1254
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
805
1255
  if (ids.length > 0) {
806
1256
  validateBulkIds(ids, "categories");
1257
+ if (opts.dryRun) {
1258
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_delete" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
1259
+ return;
1260
+ }
807
1261
  const { data, summary } = await runBulk({
808
1262
  items: ids,
809
1263
  runOne: async (itemId) => {
@@ -815,12 +1269,17 @@ function register(program, helpers) {
815
1269
  printOutput(getOutputFormat(), { data, summary }, false);
816
1270
  return;
817
1271
  }
1272
+ if (!id) exitWithError(400, "Provide id, --ids, or --ids-file");
1273
+ if (opts.dryRun) {
1274
+ printOutput(getOutputFormat(), { _id: id, status: "would_delete" }, false);
1275
+ return;
1276
+ }
818
1277
  const res = await apiRequest({ method: "DELETE", path: "categories/" + encodeURIComponent(id), token, baseUrl: getBaseUrl() });
819
1278
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
820
1279
  if (getOutputFormat() === "json") console.log(JSON.stringify({ data: null, deleted: true }));
821
1280
  });
822
1281
  const labelsCmd = program.command("labels").description("labels resource");
823
- labelsCmd.command("list").description("List labels").option("--limit <n>", "Max results").option("--offset <n>", "Skip n results").option("--organization-id <value>", "Filter by organizationId").hook("preAction", (thisCommand) => {
1282
+ labelsCmd.command("list").description("List labels").option("--limit <n>", "Max results").option("--offset <n>", "Skip n results").option("--all", "Fetch all pages").option("--fields <fields>", "Comma-separated fields to print").option("--where <field=value>", "Client-side exact filters, comma-separated").option("--jsonl", "Print one compact JSON object per row").option("--organization-id <value>", "Filter by organizationId").hook("preAction", (thisCommand) => {
824
1283
  syncRootOptsFromCommand(thisCommand);
825
1284
  }).action(async (opts) => {
826
1285
  const token = requireToken();
@@ -831,10 +1290,10 @@ function register(program, helpers) {
831
1290
  if (opts.organizationId != null) {
832
1291
  query.organizationId = coerceQueryFlag(opts.organizationId, { field: "organizationId", flag: "--organization-id", kind: "string" }, exitWithError);
833
1292
  }
834
- const res = await apiRequest({ method: "GET", path: "labels", token, baseUrl: getBaseUrl(), query });
1293
+ const res = await fetchList({ path: "labels", token, baseUrl: getBaseUrl(), query, all: Boolean(opts.all), request: apiRequest });
835
1294
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
836
1295
  const data = res.data?.data ?? [];
837
- printOutput(getOutputFormat(), data, false);
1296
+ printList({ format: getOutputFormat(), data, fields: opts.fields, where: opts.where, jsonl: Boolean(opts.jsonl) });
838
1297
  });
839
1298
  labelsCmd.command("get <id>").description("Get one labels by id").hook("preAction", (thisCommand) => {
840
1299
  syncRootOptsFromCommand(thisCommand);
@@ -861,20 +1320,67 @@ function register(program, helpers) {
861
1320
  if (parsedColour !== void 0) bodyFromFlags.colour = parsedColour;
862
1321
  const body = mergeBody(bodyFromFlags, opts.body);
863
1322
  if (opts.ifNotExists) {
1323
+ const existing = await findExistingResource({
1324
+ resource: "labels",
1325
+ token,
1326
+ baseUrl: getBaseUrl(),
1327
+ body,
1328
+ identityFields: ["name", "organizationId"],
1329
+ queryFields: ["organizationId"],
1330
+ request: apiRequest
1331
+ });
1332
+ if (existing) {
1333
+ printOutput(getOutputFormat(), existing, false);
1334
+ return;
1335
+ }
864
1336
  }
865
1337
  const res = await apiRequest({ method: "POST", path: "labels", token, baseUrl: getBaseUrl(), body });
866
1338
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
867
1339
  const data = res.data?.data ?? res.data;
868
1340
  printOutput(getOutputFormat(), data ?? {}, false);
869
1341
  });
870
- labelsCmd.command("update <id>").description("Update labels").option("--body <json>", "Request body JSON (merged with flags)").option("--ids <ids>", "Comma-separated IDs for bulk update (max 25)").option("--name <value>", "name").option("--colour <value>", "colour").hook("preAction", (thisCommand) => {
1342
+ labelsCmd.command("update [id]").description("Update labels").option("--body <json>", "Request body JSON (merged with flags)").option("--ids <ids>", "Comma-separated IDs for bulk update (max 25)").option("--ids-file <path>", "File containing IDs for bulk update").option("--input <path>", "JSON/JSONL records for bulk update; use - for stdin").option("--dry-run", "Preview bulk update without sending requests").option("--name <value>", "name").option("--colour <value>", "colour").hook("preAction", (thisCommand) => {
871
1343
  syncRootOptsFromCommand(thisCommand);
872
1344
  }).action(async (id, opts) => {
873
1345
  const token = requireToken();
874
1346
  if (!token) exitWithError(401, "Missing or invalid token");
875
- const ids = parseIds(opts.ids);
1347
+ const inputRecords = await parseBulkInput(opts.input);
1348
+ if (inputRecords.length > 0) {
1349
+ const ids2 = inputRecords.map(getBulkRecordId).filter((value) => value !== null);
1350
+ if (ids2.length !== inputRecords.length) exitWithError(400, "Each --input record must include id or _id");
1351
+ validateBulkIds(ids2, "labels");
1352
+ if (opts.dryRun) {
1353
+ printOutput(getOutputFormat(), { data: ids2.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids2.length, succeeded: ids2.length, failed: 0 } }, false);
1354
+ return;
1355
+ }
1356
+ const { data: data2, summary } = await runBulk({
1357
+ items: inputRecords,
1358
+ runOne: async (record) => {
1359
+ const itemId = getBulkRecordId(record);
1360
+ const recordBody = { ...record };
1361
+ delete recordBody.id;
1362
+ delete recordBody._id;
1363
+ const bodyFromFlags2 = {};
1364
+ const parsedName2 = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
1365
+ if (parsedName2 !== void 0) bodyFromFlags2.name = parsedName2;
1366
+ const parsedColour2 = coerceBodyFlag(opts.colour, { field: "colour", flag: "--colour", kind: "string" }, exitWithError);
1367
+ if (parsedColour2 !== void 0) bodyFromFlags2.colour = parsedColour2;
1368
+ const body2 = { ...recordBody, ...mergeBody(bodyFromFlags2, opts.body) };
1369
+ const res2 = await apiRequest({ method: "PATCH", path: "labels/" + encodeURIComponent(itemId), token, baseUrl: getBaseUrl(), body: body2 });
1370
+ return { ok: res2.ok, status: res2.status || 0, data: res2.data, error: res2.error };
1371
+ },
1372
+ toResult: (record, ok, status, _data, err) => ok ? { _id: getBulkRecordId(record), status: "updated" } : { _id: getBulkRecordId(record) ?? void 0, error: true, statusCode: status, message: err ?? "Failed" }
1373
+ });
1374
+ printOutput(getOutputFormat(), { data: data2, summary }, false);
1375
+ return;
1376
+ }
1377
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
876
1378
  if (ids.length > 0) {
877
1379
  validateBulkIds(ids, "labels");
1380
+ if (opts.dryRun) {
1381
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
1382
+ return;
1383
+ }
878
1384
  const { data: data2, summary } = await runBulk({
879
1385
  items: ids,
880
1386
  runOne: async (itemId) => {
@@ -892,6 +1398,7 @@ function register(program, helpers) {
892
1398
  printOutput(getOutputFormat(), { data: data2, summary }, false);
893
1399
  return;
894
1400
  }
1401
+ if (!id) exitWithError(400, "Provide id, --ids, --ids-file, or --input");
895
1402
  const bodyFromFlags = {};
896
1403
  const parsedName = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
897
1404
  if (parsedName !== void 0) bodyFromFlags.name = parsedName;
@@ -903,14 +1410,18 @@ function register(program, helpers) {
903
1410
  const data = res.data?.data ?? res.data;
904
1411
  printOutput(getOutputFormat(), data ?? {}, false);
905
1412
  });
906
- labelsCmd.command("delete <id>").description("Delete labels").option("--ids <ids>", "Comma-separated IDs for bulk delete (max 25)").hook("preAction", (thisCommand) => {
1413
+ labelsCmd.command("delete [id]").description("Delete labels").option("--ids <ids>", "Comma-separated IDs for bulk delete (max 25)").option("--ids-file <path>", "File containing IDs for bulk delete").option("--dry-run", "Preview delete without sending requests").option("--yes", "Confirm destructive bulk delete").hook("preAction", (thisCommand) => {
907
1414
  syncRootOptsFromCommand(thisCommand);
908
1415
  }).action(async (id, opts) => {
909
1416
  const token = requireToken();
910
1417
  if (!token) exitWithError(401, "Missing or invalid token");
911
- const ids = parseIds(opts.ids);
1418
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
912
1419
  if (ids.length > 0) {
913
1420
  validateBulkIds(ids, "labels");
1421
+ if (opts.dryRun) {
1422
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_delete" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
1423
+ return;
1424
+ }
914
1425
  const { data, summary } = await runBulk({
915
1426
  items: ids,
916
1427
  runOne: async (itemId) => {
@@ -922,12 +1433,17 @@ function register(program, helpers) {
922
1433
  printOutput(getOutputFormat(), { data, summary }, false);
923
1434
  return;
924
1435
  }
1436
+ if (!id) exitWithError(400, "Provide id, --ids, or --ids-file");
1437
+ if (opts.dryRun) {
1438
+ printOutput(getOutputFormat(), { _id: id, status: "would_delete" }, false);
1439
+ return;
1440
+ }
925
1441
  const res = await apiRequest({ method: "DELETE", path: "labels/" + encodeURIComponent(id), token, baseUrl: getBaseUrl() });
926
1442
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
927
1443
  if (getOutputFormat() === "json") console.log(JSON.stringify({ data: null, deleted: true }));
928
1444
  });
929
1445
  const viewsCmd = program.command("views").description("views resource");
930
- viewsCmd.command("list").description("List views").option("--limit <n>", "Max results").option("--offset <n>", "Skip n results").option("--organization-id <value>", "Filter by organizationId").option("--project-id <value>", "Filter by projectId").option("--entity-type <value>", "Filter by entityType").hook("preAction", (thisCommand) => {
1446
+ viewsCmd.command("list").description("List views").option("--limit <n>", "Max results").option("--offset <n>", "Skip n results").option("--all", "Fetch all pages").option("--fields <fields>", "Comma-separated fields to print").option("--where <field=value>", "Client-side exact filters, comma-separated").option("--jsonl", "Print one compact JSON object per row").option("--organization-id <value>", "Filter by organizationId").option("--project-id <value>", "Filter by projectId").option("--entity-type <value>", "Filter by entityType").hook("preAction", (thisCommand) => {
931
1447
  syncRootOptsFromCommand(thisCommand);
932
1448
  }).action(async (opts) => {
933
1449
  const token = requireToken();
@@ -944,10 +1460,10 @@ function register(program, helpers) {
944
1460
  if (opts.entityType != null) {
945
1461
  query.entityType = coerceQueryFlag(opts.entityType, { field: "entityType", flag: "--entity-type", kind: "string" }, exitWithError);
946
1462
  }
947
- const res = await apiRequest({ method: "GET", path: "views", token, baseUrl: getBaseUrl(), query });
1463
+ const res = await fetchList({ path: "views", token, baseUrl: getBaseUrl(), query, all: Boolean(opts.all), request: apiRequest });
948
1464
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
949
1465
  const data = res.data?.data ?? [];
950
- printOutput(getOutputFormat(), data, false);
1466
+ printList({ format: getOutputFormat(), data, fields: opts.fields, where: opts.where, jsonl: Boolean(opts.jsonl) });
951
1467
  });
952
1468
  viewsCmd.command("get <id>").description("Get one views by id").hook("preAction", (thisCommand) => {
953
1469
  syncRootOptsFromCommand(thisCommand);
@@ -980,20 +1496,71 @@ function register(program, helpers) {
980
1496
  if (parsedIsDefault !== void 0) bodyFromFlags.isDefault = parsedIsDefault;
981
1497
  const body = mergeBody(bodyFromFlags, opts.body);
982
1498
  if (opts.ifNotExists) {
1499
+ const existing = await findExistingResource({
1500
+ resource: "views",
1501
+ token,
1502
+ baseUrl: getBaseUrl(),
1503
+ body,
1504
+ identityFields: ["name", "entityType", "organizationId", "projectId"],
1505
+ queryFields: ["organizationId", "projectId", "entityType"],
1506
+ request: apiRequest
1507
+ });
1508
+ if (existing) {
1509
+ printOutput(getOutputFormat(), existing, false);
1510
+ return;
1511
+ }
983
1512
  }
984
1513
  const res = await apiRequest({ method: "POST", path: "views", token, baseUrl: getBaseUrl(), body });
985
1514
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
986
1515
  const data = res.data?.data ?? res.data;
987
1516
  printOutput(getOutputFormat(), data ?? {}, false);
988
1517
  });
989
- viewsCmd.command("update <id>").description("Update views").option("--body <json>", "Request body JSON (merged with flags)").option("--ids <ids>", "Comma-separated IDs for bulk update (max 25)").option("--name <value>", "name").option("--project-id <value>", "projectId").option("--config <value>", "config").option("--is-default <value>", "isDefault").hook("preAction", (thisCommand) => {
1518
+ viewsCmd.command("update [id]").description("Update views").option("--body <json>", "Request body JSON (merged with flags)").option("--ids <ids>", "Comma-separated IDs for bulk update (max 25)").option("--ids-file <path>", "File containing IDs for bulk update").option("--input <path>", "JSON/JSONL records for bulk update; use - for stdin").option("--dry-run", "Preview bulk update without sending requests").option("--name <value>", "name").option("--project-id <value>", "projectId").option("--config <value>", "config").option("--is-default <value>", "isDefault").hook("preAction", (thisCommand) => {
990
1519
  syncRootOptsFromCommand(thisCommand);
991
1520
  }).action(async (id, opts) => {
992
1521
  const token = requireToken();
993
1522
  if (!token) exitWithError(401, "Missing or invalid token");
994
- const ids = parseIds(opts.ids);
1523
+ const inputRecords = await parseBulkInput(opts.input);
1524
+ if (inputRecords.length > 0) {
1525
+ const ids2 = inputRecords.map(getBulkRecordId).filter((value) => value !== null);
1526
+ if (ids2.length !== inputRecords.length) exitWithError(400, "Each --input record must include id or _id");
1527
+ validateBulkIds(ids2, "views");
1528
+ if (opts.dryRun) {
1529
+ printOutput(getOutputFormat(), { data: ids2.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids2.length, succeeded: ids2.length, failed: 0 } }, false);
1530
+ return;
1531
+ }
1532
+ const { data: data2, summary } = await runBulk({
1533
+ items: inputRecords,
1534
+ runOne: async (record) => {
1535
+ const itemId = getBulkRecordId(record);
1536
+ const recordBody = { ...record };
1537
+ delete recordBody.id;
1538
+ delete recordBody._id;
1539
+ const bodyFromFlags2 = {};
1540
+ const parsedName2 = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
1541
+ if (parsedName2 !== void 0) bodyFromFlags2.name = parsedName2;
1542
+ const parsedProjectId2 = coerceBodyFlag(opts.projectId, { field: "projectId", flag: "--project-id", kind: "string" }, exitWithError);
1543
+ if (parsedProjectId2 !== void 0) bodyFromFlags2.projectId = parsedProjectId2;
1544
+ const parsedConfig2 = coerceBodyFlag(opts.config, { field: "config", flag: "--config", kind: "string" }, exitWithError);
1545
+ if (parsedConfig2 !== void 0) bodyFromFlags2.config = parsedConfig2;
1546
+ const parsedIsDefault2 = coerceBodyFlag(opts.isDefault, { field: "isDefault", flag: "--is-default", kind: "boolean" }, exitWithError);
1547
+ if (parsedIsDefault2 !== void 0) bodyFromFlags2.isDefault = parsedIsDefault2;
1548
+ const body2 = { ...recordBody, ...mergeBody(bodyFromFlags2, opts.body) };
1549
+ const res2 = await apiRequest({ method: "PATCH", path: "views/" + encodeURIComponent(itemId), token, baseUrl: getBaseUrl(), body: body2 });
1550
+ return { ok: res2.ok, status: res2.status || 0, data: res2.data, error: res2.error };
1551
+ },
1552
+ toResult: (record, ok, status, _data, err) => ok ? { _id: getBulkRecordId(record), status: "updated" } : { _id: getBulkRecordId(record) ?? void 0, error: true, statusCode: status, message: err ?? "Failed" }
1553
+ });
1554
+ printOutput(getOutputFormat(), { data: data2, summary }, false);
1555
+ return;
1556
+ }
1557
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
995
1558
  if (ids.length > 0) {
996
1559
  validateBulkIds(ids, "views");
1560
+ if (opts.dryRun) {
1561
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
1562
+ return;
1563
+ }
997
1564
  const { data: data2, summary } = await runBulk({
998
1565
  items: ids,
999
1566
  runOne: async (itemId) => {
@@ -1015,6 +1582,7 @@ function register(program, helpers) {
1015
1582
  printOutput(getOutputFormat(), { data: data2, summary }, false);
1016
1583
  return;
1017
1584
  }
1585
+ if (!id) exitWithError(400, "Provide id, --ids, --ids-file, or --input");
1018
1586
  const bodyFromFlags = {};
1019
1587
  const parsedName = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
1020
1588
  if (parsedName !== void 0) bodyFromFlags.name = parsedName;
@@ -1030,14 +1598,18 @@ function register(program, helpers) {
1030
1598
  const data = res.data?.data ?? res.data;
1031
1599
  printOutput(getOutputFormat(), data ?? {}, false);
1032
1600
  });
1033
- viewsCmd.command("delete <id>").description("Delete views").option("--ids <ids>", "Comma-separated IDs for bulk delete (max 25)").hook("preAction", (thisCommand) => {
1601
+ viewsCmd.command("delete [id]").description("Delete views").option("--ids <ids>", "Comma-separated IDs for bulk delete (max 25)").option("--ids-file <path>", "File containing IDs for bulk delete").option("--dry-run", "Preview delete without sending requests").option("--yes", "Confirm destructive bulk delete").hook("preAction", (thisCommand) => {
1034
1602
  syncRootOptsFromCommand(thisCommand);
1035
1603
  }).action(async (id, opts) => {
1036
1604
  const token = requireToken();
1037
1605
  if (!token) exitWithError(401, "Missing or invalid token");
1038
- const ids = parseIds(opts.ids);
1606
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
1039
1607
  if (ids.length > 0) {
1040
1608
  validateBulkIds(ids, "views");
1609
+ if (opts.dryRun) {
1610
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_delete" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
1611
+ return;
1612
+ }
1041
1613
  const { data, summary } = await runBulk({
1042
1614
  items: ids,
1043
1615
  runOne: async (itemId) => {
@@ -1049,12 +1621,17 @@ function register(program, helpers) {
1049
1621
  printOutput(getOutputFormat(), { data, summary }, false);
1050
1622
  return;
1051
1623
  }
1624
+ if (!id) exitWithError(400, "Provide id, --ids, or --ids-file");
1625
+ if (opts.dryRun) {
1626
+ printOutput(getOutputFormat(), { _id: id, status: "would_delete" }, false);
1627
+ return;
1628
+ }
1052
1629
  const res = await apiRequest({ method: "DELETE", path: "views/" + encodeURIComponent(id), token, baseUrl: getBaseUrl() });
1053
1630
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
1054
1631
  if (getOutputFormat() === "json") console.log(JSON.stringify({ data: null, deleted: true }));
1055
1632
  });
1056
1633
  const commentsCmd = program.command("comments").description("comments resource");
1057
- commentsCmd.command("list").description("List comments").option("--limit <n>", "Max results").option("--offset <n>", "Skip n results").option("--entity-type <value>", "Filter by entityType").option("--entity-id <value>", "Filter by entityId").hook("preAction", (thisCommand) => {
1634
+ commentsCmd.command("list").description("List comments").option("--limit <n>", "Max results").option("--offset <n>", "Skip n results").option("--all", "Fetch all pages").option("--fields <fields>", "Comma-separated fields to print").option("--where <field=value>", "Client-side exact filters, comma-separated").option("--jsonl", "Print one compact JSON object per row").option("--entity-type <value>", "Filter by entityType").option("--entity-id <value>", "Filter by entityId").hook("preAction", (thisCommand) => {
1058
1635
  syncRootOptsFromCommand(thisCommand);
1059
1636
  }).action(async (opts) => {
1060
1637
  const token = requireToken();
@@ -1068,10 +1645,10 @@ function register(program, helpers) {
1068
1645
  if (opts.entityId != null) {
1069
1646
  query.entityId = coerceQueryFlag(opts.entityId, { field: "entityId", flag: "--entity-id", kind: "string" }, exitWithError);
1070
1647
  }
1071
- const res = await apiRequest({ method: "GET", path: "comments", token, baseUrl: getBaseUrl(), query });
1648
+ const res = await fetchList({ path: "comments", token, baseUrl: getBaseUrl(), query, all: Boolean(opts.all), request: apiRequest });
1072
1649
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
1073
1650
  const data = res.data?.data ?? [];
1074
- printOutput(getOutputFormat(), data, false);
1651
+ printList({ format: getOutputFormat(), data, fields: opts.fields, where: opts.where, jsonl: Boolean(opts.jsonl) });
1075
1652
  });
1076
1653
  commentsCmd.command("create").description("Create comments").option("--body <json>", "Request body JSON (merged with flags)").option("--entity-type <value>", "entityType").option("--entity-id <value>", "entityId").option("--text <value>", "body").option("--parent-comment-id <value>", "parentCommentId").option("--if-not-exists", "Return existing if match found").hook("preAction", (thisCommand) => {
1077
1654
  syncRootOptsFromCommand(thisCommand);
@@ -1089,6 +1666,19 @@ function register(program, helpers) {
1089
1666
  if (parsedParentCommentId !== void 0) bodyFromFlags.parentCommentId = parsedParentCommentId;
1090
1667
  const body = mergeBody(bodyFromFlags, opts.body);
1091
1668
  if (opts.ifNotExists) {
1669
+ const existing = await findExistingResource({
1670
+ resource: "comments",
1671
+ token,
1672
+ baseUrl: getBaseUrl(),
1673
+ body,
1674
+ identityFields: ["entityType", "entityId"],
1675
+ queryFields: ["entityType", "entityId"],
1676
+ request: apiRequest
1677
+ });
1678
+ if (existing) {
1679
+ printOutput(getOutputFormat(), existing, false);
1680
+ return;
1681
+ }
1092
1682
  }
1093
1683
  const res = await apiRequest({ method: "POST", path: "comments", token, baseUrl: getBaseUrl(), body });
1094
1684
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
@@ -1099,4 +1689,4 @@ function register(program, helpers) {
1099
1689
  export {
1100
1690
  register
1101
1691
  };
1102
- //# sourceMappingURL=generated-IFLUY7PB.js.map
1692
+ //# sourceMappingURL=generated-7QDIZ5V5.js.map