@getpilfer/cli 0.1.1 → 0.1.2

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-AQXEFAD4.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").hook("preAction", (thisCommand) => {
92
180
  syncRootOptsFromCommand(thisCommand);
93
181
  }).action(async (opts) => {
94
182
  const token = requireToken();
@@ -99,10 +187,10 @@ 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
+ const res = await fetchList({ path: "projects", token, baseUrl: getBaseUrl(), query, all: Boolean(opts.all), request: apiRequest });
103
191
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
104
192
  const data = res.data?.data ?? [];
105
- printOutput(getOutputFormat(), data, false);
193
+ printList({ format: getOutputFormat(), data, fields: opts.fields, where: opts.where, jsonl: Boolean(opts.jsonl) });
106
194
  });
107
195
  projectsCmd.command("get <id>").description("Get one projects by id").hook("preAction", (thisCommand) => {
108
196
  syncRootOptsFromCommand(thisCommand);
@@ -131,20 +219,69 @@ function register(program, helpers) {
131
219
  if (parsedPriority !== void 0) bodyFromFlags.priority = parsedPriority;
132
220
  const body = mergeBody(bodyFromFlags, opts.body);
133
221
  if (opts.ifNotExists) {
222
+ const existing = await findExistingResource({
223
+ resource: "projects",
224
+ token,
225
+ baseUrl: getBaseUrl(),
226
+ body,
227
+ identityFields: ["name", "organizationId"],
228
+ queryFields: ["organizationId"],
229
+ request: apiRequest
230
+ });
231
+ if (existing) {
232
+ printOutput(getOutputFormat(), existing, false);
233
+ return;
234
+ }
134
235
  }
135
236
  const res = await apiRequest({ method: "POST", path: "projects", token, baseUrl: getBaseUrl(), body });
136
237
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
137
238
  const data = res.data?.data ?? res.data;
138
239
  printOutput(getOutputFormat(), data ?? {}, false);
139
240
  });
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) => {
241
+ 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
242
  syncRootOptsFromCommand(thisCommand);
142
243
  }).action(async (id, opts) => {
143
244
  const token = requireToken();
144
245
  if (!token) exitWithError(401, "Missing or invalid token");
145
- const ids = parseIds(opts.ids);
246
+ const inputRecords = await parseBulkInput(opts.input);
247
+ if (inputRecords.length > 0) {
248
+ const ids2 = inputRecords.map(getBulkRecordId).filter((value) => value !== null);
249
+ if (ids2.length !== inputRecords.length) exitWithError(400, "Each --input record must include id or _id");
250
+ validateBulkIds(ids2, "projects");
251
+ if (opts.dryRun) {
252
+ printOutput(getOutputFormat(), { data: ids2.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids2.length, succeeded: ids2.length, failed: 0 } }, false);
253
+ return;
254
+ }
255
+ const { data: data2, summary } = await runBulk({
256
+ items: inputRecords,
257
+ runOne: async (record) => {
258
+ const itemId = getBulkRecordId(record);
259
+ const recordBody = { ...record };
260
+ delete recordBody.id;
261
+ delete recordBody._id;
262
+ const bodyFromFlags2 = {};
263
+ const parsedName2 = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
264
+ if (parsedName2 !== void 0) bodyFromFlags2.name = parsedName2;
265
+ const parsedDescription2 = coerceBodyFlag(opts.description, { field: "description", flag: "--description", kind: "string" }, exitWithError);
266
+ if (parsedDescription2 !== void 0) bodyFromFlags2.description = parsedDescription2;
267
+ const parsedPriority2 = coerceBodyFlag(opts.priority, { field: "priority", flag: "--priority", kind: "string" }, exitWithError);
268
+ if (parsedPriority2 !== void 0) bodyFromFlags2.priority = parsedPriority2;
269
+ const body2 = { ...recordBody, ...mergeBody(bodyFromFlags2, opts.body) };
270
+ const res2 = await apiRequest({ method: "PATCH", path: "projects/" + encodeURIComponent(itemId), token, baseUrl: getBaseUrl(), body: body2 });
271
+ return { ok: res2.ok, status: res2.status || 0, data: res2.data, error: res2.error };
272
+ },
273
+ toResult: (record, ok, status, _data, err) => ok ? { _id: getBulkRecordId(record), status: "updated" } : { _id: getBulkRecordId(record) ?? void 0, error: true, statusCode: status, message: err ?? "Failed" }
274
+ });
275
+ printOutput(getOutputFormat(), { data: data2, summary }, false);
276
+ return;
277
+ }
278
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
146
279
  if (ids.length > 0) {
147
280
  validateBulkIds(ids, "projects");
281
+ if (opts.dryRun) {
282
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
283
+ return;
284
+ }
148
285
  const { data: data2, summary } = await runBulk({
149
286
  items: ids,
150
287
  runOne: async (itemId) => {
@@ -164,6 +301,7 @@ function register(program, helpers) {
164
301
  printOutput(getOutputFormat(), { data: data2, summary }, false);
165
302
  return;
166
303
  }
304
+ if (!id) exitWithError(400, "Provide id, --ids, --ids-file, or --input");
167
305
  const bodyFromFlags = {};
168
306
  const parsedName = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
169
307
  if (parsedName !== void 0) bodyFromFlags.name = parsedName;
@@ -177,14 +315,18 @@ function register(program, helpers) {
177
315
  const data = res.data?.data ?? res.data;
178
316
  printOutput(getOutputFormat(), data ?? {}, false);
179
317
  });
180
- projectsCmd.command("delete <id>").description("Delete projects").option("--ids <ids>", "Comma-separated IDs for bulk delete (max 25)").hook("preAction", (thisCommand) => {
318
+ 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
319
  syncRootOptsFromCommand(thisCommand);
182
320
  }).action(async (id, opts) => {
183
321
  const token = requireToken();
184
322
  if (!token) exitWithError(401, "Missing or invalid token");
185
- const ids = parseIds(opts.ids);
323
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
186
324
  if (ids.length > 0) {
187
325
  validateBulkIds(ids, "projects");
326
+ if (opts.dryRun) {
327
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_delete" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
328
+ return;
329
+ }
188
330
  const { data, summary } = await runBulk({
189
331
  items: ids,
190
332
  runOne: async (itemId) => {
@@ -196,12 +338,17 @@ function register(program, helpers) {
196
338
  printOutput(getOutputFormat(), { data, summary }, false);
197
339
  return;
198
340
  }
341
+ if (!id) exitWithError(400, "Provide id, --ids, or --ids-file");
342
+ if (opts.dryRun) {
343
+ printOutput(getOutputFormat(), { _id: id, status: "would_delete" }, false);
344
+ return;
345
+ }
199
346
  const res = await apiRequest({ method: "DELETE", path: "projects/" + encodeURIComponent(id), token, baseUrl: getBaseUrl() });
200
347
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
201
348
  if (getOutputFormat() === "json") console.log(JSON.stringify({ data: null, deleted: true }));
202
349
  });
203
350
  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) => {
351
+ 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").hook("preAction", (thisCommand) => {
205
352
  syncRootOptsFromCommand(thisCommand);
206
353
  }).action(async (opts) => {
207
354
  const token = requireToken();
@@ -215,10 +362,10 @@ function register(program, helpers) {
215
362
  if (opts.projectId != null) {
216
363
  query.projectId = coerceQueryFlag(opts.projectId, { field: "projectId", flag: "--project-id", kind: "string" }, exitWithError);
217
364
  }
218
- const res = await apiRequest({ method: "GET", path: "spaces", token, baseUrl: getBaseUrl(), query });
365
+ const res = await fetchList({ path: "spaces", token, baseUrl: getBaseUrl(), query, all: Boolean(opts.all), request: apiRequest });
219
366
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
220
367
  const data = res.data?.data ?? [];
221
- printOutput(getOutputFormat(), data, false);
368
+ printList({ format: getOutputFormat(), data, fields: opts.fields, where: opts.where, jsonl: Boolean(opts.jsonl) });
222
369
  });
223
370
  spacesCmd.command("get <id>").description("Get one spaces by id").hook("preAction", (thisCommand) => {
224
371
  syncRootOptsFromCommand(thisCommand);
@@ -253,20 +400,75 @@ function register(program, helpers) {
253
400
  if (parsedCurrentStrength !== void 0) bodyFromFlags.currentStrength = parsedCurrentStrength;
254
401
  const body = mergeBody(bodyFromFlags, opts.body);
255
402
  if (opts.ifNotExists) {
403
+ const existing = await findExistingResource({
404
+ resource: "spaces",
405
+ token,
406
+ baseUrl: getBaseUrl(),
407
+ body,
408
+ identityFields: ["name", "organizationId", "projectId"],
409
+ queryFields: ["organizationId", "projectId"],
410
+ request: apiRequest
411
+ });
412
+ if (existing) {
413
+ printOutput(getOutputFormat(), existing, false);
414
+ return;
415
+ }
256
416
  }
257
417
  const res = await apiRequest({ method: "POST", path: "spaces", token, baseUrl: getBaseUrl(), body });
258
418
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
259
419
  const data = res.data?.data ?? res.data;
260
420
  printOutput(getOutputFormat(), data ?? {}, false);
261
421
  });
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) => {
422
+ 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
423
  syncRootOptsFromCommand(thisCommand);
264
424
  }).action(async (id, opts) => {
265
425
  const token = requireToken();
266
426
  if (!token) exitWithError(401, "Missing or invalid token");
267
- const ids = parseIds(opts.ids);
427
+ const inputRecords = await parseBulkInput(opts.input);
428
+ if (inputRecords.length > 0) {
429
+ const ids2 = inputRecords.map(getBulkRecordId).filter((value) => value !== null);
430
+ if (ids2.length !== inputRecords.length) exitWithError(400, "Each --input record must include id or _id");
431
+ validateBulkIds(ids2, "spaces");
432
+ if (opts.dryRun) {
433
+ printOutput(getOutputFormat(), { data: ids2.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids2.length, succeeded: ids2.length, failed: 0 } }, false);
434
+ return;
435
+ }
436
+ const { data: data2, summary } = await runBulk({
437
+ items: inputRecords,
438
+ runOne: async (record) => {
439
+ const itemId = getBulkRecordId(record);
440
+ const recordBody = { ...record };
441
+ delete recordBody.id;
442
+ delete recordBody._id;
443
+ const bodyFromFlags2 = {};
444
+ const parsedName2 = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
445
+ if (parsedName2 !== void 0) bodyFromFlags2.name = parsedName2;
446
+ const parsedDescription2 = coerceBodyFlag(opts.description, { field: "description", flag: "--description", kind: "string" }, exitWithError);
447
+ if (parsedDescription2 !== void 0) bodyFromFlags2.description = parsedDescription2;
448
+ const parsedProjectId2 = coerceBodyFlag(opts.projectId, { field: "projectId", flag: "--project-id", kind: "string", allowNull: true }, exitWithError);
449
+ if (parsedProjectId2 !== void 0) bodyFromFlags2.projectId = parsedProjectId2;
450
+ const parsedStatus2 = coerceBodyFlag(opts.status, { field: "status", flag: "--status", kind: "string" }, exitWithError);
451
+ if (parsedStatus2 !== void 0) bodyFromFlags2.status = parsedStatus2;
452
+ const parsedPriority2 = coerceBodyFlag(opts.priority, { field: "priority", flag: "--priority", kind: "string" }, exitWithError);
453
+ if (parsedPriority2 !== void 0) bodyFromFlags2.priority = parsedPriority2;
454
+ const parsedCurrentStrength2 = coerceBodyFlag(opts.currentStrength, { field: "currentStrength", flag: "--current-strength", kind: "number" }, exitWithError);
455
+ if (parsedCurrentStrength2 !== void 0) bodyFromFlags2.currentStrength = parsedCurrentStrength2;
456
+ const body2 = { ...recordBody, ...mergeBody(bodyFromFlags2, opts.body) };
457
+ const res2 = await apiRequest({ method: "PATCH", path: "spaces/" + encodeURIComponent(itemId), token, baseUrl: getBaseUrl(), body: body2 });
458
+ return { ok: res2.ok, status: res2.status || 0, data: res2.data, error: res2.error };
459
+ },
460
+ toResult: (record, ok, status, _data, err) => ok ? { _id: getBulkRecordId(record), status: "updated" } : { _id: getBulkRecordId(record) ?? void 0, error: true, statusCode: status, message: err ?? "Failed" }
461
+ });
462
+ printOutput(getOutputFormat(), { data: data2, summary }, false);
463
+ return;
464
+ }
465
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
268
466
  if (ids.length > 0) {
269
467
  validateBulkIds(ids, "spaces");
468
+ if (opts.dryRun) {
469
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
470
+ return;
471
+ }
270
472
  const { data: data2, summary } = await runBulk({
271
473
  items: ids,
272
474
  runOne: async (itemId) => {
@@ -292,6 +494,7 @@ function register(program, helpers) {
292
494
  printOutput(getOutputFormat(), { data: data2, summary }, false);
293
495
  return;
294
496
  }
497
+ if (!id) exitWithError(400, "Provide id, --ids, --ids-file, or --input");
295
498
  const bodyFromFlags = {};
296
499
  const parsedName = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
297
500
  if (parsedName !== void 0) bodyFromFlags.name = parsedName;
@@ -311,14 +514,18 @@ function register(program, helpers) {
311
514
  const data = res.data?.data ?? res.data;
312
515
  printOutput(getOutputFormat(), data ?? {}, false);
313
516
  });
314
- spacesCmd.command("delete <id>").description("Delete spaces").option("--ids <ids>", "Comma-separated IDs for bulk delete (max 25)").hook("preAction", (thisCommand) => {
517
+ 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
518
  syncRootOptsFromCommand(thisCommand);
316
519
  }).action(async (id, opts) => {
317
520
  const token = requireToken();
318
521
  if (!token) exitWithError(401, "Missing or invalid token");
319
- const ids = parseIds(opts.ids);
522
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
320
523
  if (ids.length > 0) {
321
524
  validateBulkIds(ids, "spaces");
525
+ if (opts.dryRun) {
526
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_delete" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
527
+ return;
528
+ }
322
529
  const { data, summary } = await runBulk({
323
530
  items: ids,
324
531
  runOne: async (itemId) => {
@@ -330,12 +537,17 @@ function register(program, helpers) {
330
537
  printOutput(getOutputFormat(), { data, summary }, false);
331
538
  return;
332
539
  }
540
+ if (!id) exitWithError(400, "Provide id, --ids, or --ids-file");
541
+ if (opts.dryRun) {
542
+ printOutput(getOutputFormat(), { _id: id, status: "would_delete" }, false);
543
+ return;
544
+ }
333
545
  const res = await apiRequest({ method: "DELETE", path: "spaces/" + encodeURIComponent(id), token, baseUrl: getBaseUrl() });
334
546
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
335
547
  if (getOutputFormat() === "json") console.log(JSON.stringify({ data: null, deleted: true }));
336
548
  });
337
549
  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) => {
550
+ 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").hook("preAction", (thisCommand) => {
339
551
  syncRootOptsFromCommand(thisCommand);
340
552
  }).action(async (opts) => {
341
553
  const token = requireToken();
@@ -349,10 +561,10 @@ function register(program, helpers) {
349
561
  if (opts.categoryId != null) {
350
562
  query.categoryId = coerceQueryFlag(opts.categoryId, { field: "categoryId", flag: "--category-id", kind: "string" }, exitWithError);
351
563
  }
352
- const res = await apiRequest({ method: "GET", path: "scouts", token, baseUrl: getBaseUrl(), query });
564
+ const res = await fetchList({ path: "scouts", token, baseUrl: getBaseUrl(), query, all: Boolean(opts.all), request: apiRequest });
353
565
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
354
566
  const data = res.data?.data ?? [];
355
- printOutput(getOutputFormat(), data, false);
567
+ printList({ format: getOutputFormat(), data, fields: opts.fields, where: opts.where, jsonl: Boolean(opts.jsonl) });
356
568
  });
357
569
  scoutsCmd.command("get <id>").description("Get one scouts by id").hook("preAction", (thisCommand) => {
358
570
  syncRootOptsFromCommand(thisCommand);
@@ -413,20 +625,99 @@ function register(program, helpers) {
413
625
  if (parsedActivationPotential !== void 0) bodyFromFlags.activationPotential = parsedActivationPotential;
414
626
  const body = mergeBody(bodyFromFlags, opts.body);
415
627
  if (opts.ifNotExists) {
628
+ const existing = await findExistingResource({
629
+ resource: "scouts",
630
+ token,
631
+ baseUrl: getBaseUrl(),
632
+ body,
633
+ identityFields: ["name", "organizationId"],
634
+ queryFields: ["organizationId", "categoryId"],
635
+ request: apiRequest
636
+ });
637
+ if (existing) {
638
+ printOutput(getOutputFormat(), existing, false);
639
+ return;
640
+ }
416
641
  }
417
642
  const res = await apiRequest({ method: "POST", path: "scouts", token, baseUrl: getBaseUrl(), body });
418
643
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
419
644
  const data = res.data?.data ?? res.data;
420
645
  printOutput(getOutputFormat(), data ?? {}, false);
421
646
  });
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) => {
647
+ 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
648
  syncRootOptsFromCommand(thisCommand);
424
649
  }).action(async (id, opts) => {
425
650
  const token = requireToken();
426
651
  if (!token) exitWithError(401, "Missing or invalid token");
427
- const ids = parseIds(opts.ids);
652
+ const inputRecords = await parseBulkInput(opts.input);
653
+ if (inputRecords.length > 0) {
654
+ const ids2 = inputRecords.map(getBulkRecordId).filter((value) => value !== null);
655
+ if (ids2.length !== inputRecords.length) exitWithError(400, "Each --input record must include id or _id");
656
+ validateBulkIds(ids2, "scouts");
657
+ if (opts.dryRun) {
658
+ printOutput(getOutputFormat(), { data: ids2.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids2.length, succeeded: ids2.length, failed: 0 } }, false);
659
+ return;
660
+ }
661
+ const { data: data2, summary } = await runBulk({
662
+ items: inputRecords,
663
+ runOne: async (record) => {
664
+ const itemId = getBulkRecordId(record);
665
+ const recordBody = { ...record };
666
+ delete recordBody.id;
667
+ delete recordBody._id;
668
+ const bodyFromFlags2 = {};
669
+ const parsedName2 = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
670
+ if (parsedName2 !== void 0) bodyFromFlags2.name = parsedName2;
671
+ const parsedDescription2 = coerceBodyFlag(opts.description, { field: "description", flag: "--description", kind: "string" }, exitWithError);
672
+ if (parsedDescription2 !== void 0) bodyFromFlags2.description = parsedDescription2;
673
+ const parsedCategoryId2 = coerceBodyFlag(opts.categoryId, { field: "categoryId", flag: "--category-id", kind: "string", allowNull: true }, exitWithError);
674
+ if (parsedCategoryId2 !== void 0) bodyFromFlags2.categoryId = parsedCategoryId2;
675
+ const parsedUrl2 = coerceBodyFlag(opts.url, { field: "url", flag: "--url", kind: "string" }, exitWithError);
676
+ if (parsedUrl2 !== void 0) bodyFromFlags2.url = parsedUrl2;
677
+ const parsedHeroImage2 = coerceBodyFlag(opts.heroImage, { field: "heroImage", flag: "--hero-image", kind: "string" }, exitWithError);
678
+ if (parsedHeroImage2 !== void 0) bodyFromFlags2.heroImage = parsedHeroImage2;
679
+ const parsedPriority2 = coerceBodyFlag(opts.priority, { field: "priority", flag: "--priority", kind: "string" }, exitWithError);
680
+ if (parsedPriority2 !== void 0) bodyFromFlags2.priority = parsedPriority2;
681
+ const parsedStatus2 = coerceBodyFlag(opts.status, { field: "status", flag: "--status", kind: "string" }, exitWithError);
682
+ if (parsedStatus2 !== void 0) bodyFromFlags2.status = parsedStatus2;
683
+ const parsedEstimatedCost2 = coerceBodyFlag(opts.estimatedCost, { field: "estimatedCost", flag: "--estimated-cost", kind: "number" }, exitWithError);
684
+ if (parsedEstimatedCost2 !== void 0) bodyFromFlags2.estimatedCost = parsedEstimatedCost2;
685
+ const parsedPotentialCost2 = coerceBodyFlag(opts.potentialCost, { field: "potentialCost", flag: "--potential-cost", kind: "number" }, exitWithError);
686
+ if (parsedPotentialCost2 !== void 0) bodyFromFlags2.potentialCost = parsedPotentialCost2;
687
+ const parsedNeed2 = coerceBodyFlag(opts.need, { field: "need", flag: "--need", kind: "number" }, exitWithError);
688
+ if (parsedNeed2 !== void 0) bodyFromFlags2.need = parsedNeed2;
689
+ const parsedUrgency2 = coerceBodyFlag(opts.urgency, { field: "urgency", flag: "--urgency", kind: "number" }, exitWithError);
690
+ if (parsedUrgency2 !== void 0) bodyFromFlags2.urgency = parsedUrgency2;
691
+ const parsedUse2 = coerceBodyFlag(opts.use, { field: "use", flag: "--use", kind: "number" }, exitWithError);
692
+ if (parsedUse2 !== void 0) bodyFromFlags2.use = parsedUse2;
693
+ const parsedLongevity2 = coerceBodyFlag(opts.longevity, { field: "longevity", flag: "--longevity", kind: "number" }, exitWithError);
694
+ if (parsedLongevity2 !== void 0) bodyFromFlags2.longevity = parsedLongevity2;
695
+ const parsedRoi2 = coerceBodyFlag(opts.roi, { field: "roi", flag: "--roi", kind: "number" }, exitWithError);
696
+ if (parsedRoi2 !== void 0) bodyFromFlags2.roi = parsedRoi2;
697
+ const parsedSystemFit2 = coerceBodyFlag(opts.systemFit, { field: "systemFit", flag: "--system-fit", kind: "number" }, exitWithError);
698
+ if (parsedSystemFit2 !== void 0) bodyFromFlags2.systemFit = parsedSystemFit2;
699
+ const parsedEmotionalPull2 = coerceBodyFlag(opts.emotionalPull, { field: "emotionalPull", flag: "--emotional-pull", kind: "number" }, exitWithError);
700
+ if (parsedEmotionalPull2 !== void 0) bodyFromFlags2.emotionalPull = parsedEmotionalPull2;
701
+ const parsedInfrastructureFit2 = coerceBodyFlag(opts.infrastructureFit, { field: "infrastructureFit", flag: "--infrastructure-fit", kind: "number" }, exitWithError);
702
+ if (parsedInfrastructureFit2 !== void 0) bodyFromFlags2.infrastructureFit = parsedInfrastructureFit2;
703
+ const parsedActivationPotential2 = coerceBodyFlag(opts.activationPotential, { field: "activationPotential", flag: "--activation-potential", kind: "number" }, exitWithError);
704
+ if (parsedActivationPotential2 !== void 0) bodyFromFlags2.activationPotential = parsedActivationPotential2;
705
+ const body2 = { ...recordBody, ...mergeBody(bodyFromFlags2, opts.body) };
706
+ const res2 = await apiRequest({ method: "PATCH", path: "scouts/" + encodeURIComponent(itemId), token, baseUrl: getBaseUrl(), body: body2 });
707
+ return { ok: res2.ok, status: res2.status || 0, data: res2.data, error: res2.error };
708
+ },
709
+ toResult: (record, ok, status, _data, err) => ok ? { _id: getBulkRecordId(record), status: "updated" } : { _id: getBulkRecordId(record) ?? void 0, error: true, statusCode: status, message: err ?? "Failed" }
710
+ });
711
+ printOutput(getOutputFormat(), { data: data2, summary }, false);
712
+ return;
713
+ }
714
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
428
715
  if (ids.length > 0) {
429
716
  validateBulkIds(ids, "scouts");
717
+ if (opts.dryRun) {
718
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
719
+ return;
720
+ }
430
721
  const { data: data2, summary } = await runBulk({
431
722
  items: ids,
432
723
  runOne: async (itemId) => {
@@ -476,6 +767,7 @@ function register(program, helpers) {
476
767
  printOutput(getOutputFormat(), { data: data2, summary }, false);
477
768
  return;
478
769
  }
770
+ if (!id) exitWithError(400, "Provide id, --ids, --ids-file, or --input");
479
771
  const bodyFromFlags = {};
480
772
  const parsedName = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
481
773
  if (parsedName !== void 0) bodyFromFlags.name = parsedName;
@@ -519,14 +811,18 @@ function register(program, helpers) {
519
811
  const data = res.data?.data ?? res.data;
520
812
  printOutput(getOutputFormat(), data ?? {}, false);
521
813
  });
522
- scoutsCmd.command("delete <id>").description("Delete scouts").option("--ids <ids>", "Comma-separated IDs for bulk delete (max 25)").hook("preAction", (thisCommand) => {
814
+ 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
815
  syncRootOptsFromCommand(thisCommand);
524
816
  }).action(async (id, opts) => {
525
817
  const token = requireToken();
526
818
  if (!token) exitWithError(401, "Missing or invalid token");
527
- const ids = parseIds(opts.ids);
819
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
528
820
  if (ids.length > 0) {
529
821
  validateBulkIds(ids, "scouts");
822
+ if (opts.dryRun) {
823
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_delete" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
824
+ return;
825
+ }
530
826
  const { data, summary } = await runBulk({
531
827
  items: ids,
532
828
  runOne: async (itemId) => {
@@ -538,12 +834,17 @@ function register(program, helpers) {
538
834
  printOutput(getOutputFormat(), { data, summary }, false);
539
835
  return;
540
836
  }
837
+ if (!id) exitWithError(400, "Provide id, --ids, or --ids-file");
838
+ if (opts.dryRun) {
839
+ printOutput(getOutputFormat(), { _id: id, status: "would_delete" }, false);
840
+ return;
841
+ }
541
842
  const res = await apiRequest({ method: "DELETE", path: "scouts/" + encodeURIComponent(id), token, baseUrl: getBaseUrl() });
542
843
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
543
844
  if (getOutputFormat() === "json") console.log(JSON.stringify({ data: null, deleted: true }));
544
845
  });
545
846
  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) => {
847
+ 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").hook("preAction", (thisCommand) => {
547
848
  syncRootOptsFromCommand(thisCommand);
548
849
  }).action(async (opts) => {
549
850
  const token = requireToken();
@@ -557,10 +858,10 @@ function register(program, helpers) {
557
858
  if (opts.scoutId != null) {
558
859
  query.scoutId = coerceQueryFlag(opts.scoutId, { field: "scoutId", flag: "--scout-id", kind: "string" }, exitWithError);
559
860
  }
560
- const res = await apiRequest({ method: "GET", path: "assets", token, baseUrl: getBaseUrl(), query });
861
+ const res = await fetchList({ path: "assets", token, baseUrl: getBaseUrl(), query, all: Boolean(opts.all), request: apiRequest });
561
862
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
562
863
  const data = res.data?.data ?? [];
563
- printOutput(getOutputFormat(), data, false);
864
+ printList({ format: getOutputFormat(), data, fields: opts.fields, where: opts.where, jsonl: Boolean(opts.jsonl) });
564
865
  });
565
866
  assetsCmd.command("get <id>").description("Get one assets by id").hook("preAction", (thisCommand) => {
566
867
  syncRootOptsFromCommand(thisCommand);
@@ -607,20 +908,87 @@ function register(program, helpers) {
607
908
  if (parsedCost !== void 0) bodyFromFlags.cost = parsedCost;
608
909
  const body = mergeBody(bodyFromFlags, opts.body);
609
910
  if (opts.ifNotExists) {
911
+ const existing = await findExistingResource({
912
+ resource: "assets",
913
+ token,
914
+ baseUrl: getBaseUrl(),
915
+ body,
916
+ identityFields: ["name", "organizationId"],
917
+ queryFields: ["organizationId", "scoutId"],
918
+ request: apiRequest
919
+ });
920
+ if (existing) {
921
+ printOutput(getOutputFormat(), existing, false);
922
+ return;
923
+ }
610
924
  }
611
925
  const res = await apiRequest({ method: "POST", path: "assets", token, baseUrl: getBaseUrl(), body });
612
926
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
613
927
  const data = res.data?.data ?? res.data;
614
928
  printOutput(getOutputFormat(), data ?? {}, false);
615
929
  });
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) => {
930
+ 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
931
  syncRootOptsFromCommand(thisCommand);
618
932
  }).action(async (id, opts) => {
619
933
  const token = requireToken();
620
934
  if (!token) exitWithError(401, "Missing or invalid token");
621
- const ids = parseIds(opts.ids);
935
+ const inputRecords = await parseBulkInput(opts.input);
936
+ if (inputRecords.length > 0) {
937
+ const ids2 = inputRecords.map(getBulkRecordId).filter((value) => value !== null);
938
+ if (ids2.length !== inputRecords.length) exitWithError(400, "Each --input record must include id or _id");
939
+ validateBulkIds(ids2, "assets");
940
+ if (opts.dryRun) {
941
+ printOutput(getOutputFormat(), { data: ids2.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids2.length, succeeded: ids2.length, failed: 0 } }, false);
942
+ return;
943
+ }
944
+ const { data: data2, summary } = await runBulk({
945
+ items: inputRecords,
946
+ runOne: async (record) => {
947
+ const itemId = getBulkRecordId(record);
948
+ const recordBody = { ...record };
949
+ delete recordBody.id;
950
+ delete recordBody._id;
951
+ const bodyFromFlags2 = {};
952
+ const parsedName2 = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
953
+ if (parsedName2 !== void 0) bodyFromFlags2.name = parsedName2;
954
+ const parsedDescription2 = coerceBodyFlag(opts.description, { field: "description", flag: "--description", kind: "string" }, exitWithError);
955
+ if (parsedDescription2 !== void 0) bodyFromFlags2.description = parsedDescription2;
956
+ const parsedScoutId2 = coerceBodyFlag(opts.scoutId, { field: "scoutId", flag: "--scout-id", kind: "string", allowNull: true }, exitWithError);
957
+ if (parsedScoutId2 !== void 0) bodyFromFlags2.scoutId = parsedScoutId2;
958
+ const parsedCategoryId2 = coerceBodyFlag(opts.categoryId, { field: "categoryId", flag: "--category-id", kind: "string", allowNull: true }, exitWithError);
959
+ if (parsedCategoryId2 !== void 0) bodyFromFlags2.categoryId = parsedCategoryId2;
960
+ const parsedHeroImage2 = coerceBodyFlag(opts.heroImage, { field: "heroImage", flag: "--hero-image", kind: "string" }, exitWithError);
961
+ if (parsedHeroImage2 !== void 0) bodyFromFlags2.heroImage = parsedHeroImage2;
962
+ const parsedCondition2 = coerceBodyFlag(opts.condition, { field: "condition", flag: "--condition", kind: "number" }, exitWithError);
963
+ if (parsedCondition2 !== void 0) bodyFromFlags2.condition = parsedCondition2;
964
+ const parsedSatisfaction2 = coerceBodyFlag(opts.satisfaction, { field: "satisfaction", flag: "--satisfaction", kind: "number" }, exitWithError);
965
+ if (parsedSatisfaction2 !== void 0) bodyFromFlags2.satisfaction = parsedSatisfaction2;
966
+ const parsedUsage2 = coerceBodyFlag(opts.usage, { field: "usage", flag: "--usage", kind: "number" }, exitWithError);
967
+ if (parsedUsage2 !== void 0) bodyFromFlags2.usage = parsedUsage2;
968
+ const parsedFriction2 = coerceBodyFlag(opts.friction, { field: "friction", flag: "--friction", kind: "number" }, exitWithError);
969
+ if (parsedFriction2 !== void 0) bodyFromFlags2.friction = parsedFriction2;
970
+ const parsedLifetimeValue2 = coerceBodyFlag(opts.lifetimeValue, { field: "lifetimeValue", flag: "--lifetime-value", kind: "number" }, exitWithError);
971
+ if (parsedLifetimeValue2 !== void 0) bodyFromFlags2.lifetimeValue = parsedLifetimeValue2;
972
+ const parsedAcquisitionDate2 = coerceBodyFlag(opts.acquisitionDate, { field: "acquisitionDate", flag: "--acquisition-date", kind: "number" }, exitWithError);
973
+ if (parsedAcquisitionDate2 !== void 0) bodyFromFlags2.acquisitionDate = parsedAcquisitionDate2;
974
+ const parsedCost2 = coerceBodyFlag(opts.cost, { field: "cost", flag: "--cost", kind: "number" }, exitWithError);
975
+ if (parsedCost2 !== void 0) bodyFromFlags2.cost = parsedCost2;
976
+ const body2 = { ...recordBody, ...mergeBody(bodyFromFlags2, opts.body) };
977
+ const res2 = await apiRequest({ method: "PATCH", path: "assets/" + encodeURIComponent(itemId), token, baseUrl: getBaseUrl(), body: body2 });
978
+ return { ok: res2.ok, status: res2.status || 0, data: res2.data, error: res2.error };
979
+ },
980
+ toResult: (record, ok, status, _data, err) => ok ? { _id: getBulkRecordId(record), status: "updated" } : { _id: getBulkRecordId(record) ?? void 0, error: true, statusCode: status, message: err ?? "Failed" }
981
+ });
982
+ printOutput(getOutputFormat(), { data: data2, summary }, false);
983
+ return;
984
+ }
985
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
622
986
  if (ids.length > 0) {
623
987
  validateBulkIds(ids, "assets");
988
+ if (opts.dryRun) {
989
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
990
+ return;
991
+ }
624
992
  const { data: data2, summary } = await runBulk({
625
993
  items: ids,
626
994
  runOne: async (itemId) => {
@@ -658,6 +1026,7 @@ function register(program, helpers) {
658
1026
  printOutput(getOutputFormat(), { data: data2, summary }, false);
659
1027
  return;
660
1028
  }
1029
+ if (!id) exitWithError(400, "Provide id, --ids, --ids-file, or --input");
661
1030
  const bodyFromFlags = {};
662
1031
  const parsedName = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
663
1032
  if (parsedName !== void 0) bodyFromFlags.name = parsedName;
@@ -689,14 +1058,18 @@ function register(program, helpers) {
689
1058
  const data = res.data?.data ?? res.data;
690
1059
  printOutput(getOutputFormat(), data ?? {}, false);
691
1060
  });
692
- assetsCmd.command("delete <id>").description("Delete assets").option("--ids <ids>", "Comma-separated IDs for bulk delete (max 25)").hook("preAction", (thisCommand) => {
1061
+ 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
1062
  syncRootOptsFromCommand(thisCommand);
694
1063
  }).action(async (id, opts) => {
695
1064
  const token = requireToken();
696
1065
  if (!token) exitWithError(401, "Missing or invalid token");
697
- const ids = parseIds(opts.ids);
1066
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
698
1067
  if (ids.length > 0) {
699
1068
  validateBulkIds(ids, "assets");
1069
+ if (opts.dryRun) {
1070
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_delete" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
1071
+ return;
1072
+ }
700
1073
  const { data, summary } = await runBulk({
701
1074
  items: ids,
702
1075
  runOne: async (itemId) => {
@@ -708,12 +1081,17 @@ function register(program, helpers) {
708
1081
  printOutput(getOutputFormat(), { data, summary }, false);
709
1082
  return;
710
1083
  }
1084
+ if (!id) exitWithError(400, "Provide id, --ids, or --ids-file");
1085
+ if (opts.dryRun) {
1086
+ printOutput(getOutputFormat(), { _id: id, status: "would_delete" }, false);
1087
+ return;
1088
+ }
711
1089
  const res = await apiRequest({ method: "DELETE", path: "assets/" + encodeURIComponent(id), token, baseUrl: getBaseUrl() });
712
1090
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
713
1091
  if (getOutputFormat() === "json") console.log(JSON.stringify({ data: null, deleted: true }));
714
1092
  });
715
1093
  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) => {
1094
+ 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
1095
  syncRootOptsFromCommand(thisCommand);
718
1096
  }).action(async (opts) => {
719
1097
  const token = requireToken();
@@ -724,10 +1102,10 @@ function register(program, helpers) {
724
1102
  if (opts.organizationId != null) {
725
1103
  query.organizationId = coerceQueryFlag(opts.organizationId, { field: "organizationId", flag: "--organization-id", kind: "string" }, exitWithError);
726
1104
  }
727
- const res = await apiRequest({ method: "GET", path: "categories", token, baseUrl: getBaseUrl(), query });
1105
+ const res = await fetchList({ path: "categories", token, baseUrl: getBaseUrl(), query, all: Boolean(opts.all), request: apiRequest });
728
1106
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
729
1107
  const data = res.data?.data ?? [];
730
- printOutput(getOutputFormat(), data, false);
1108
+ printList({ format: getOutputFormat(), data, fields: opts.fields, where: opts.where, jsonl: Boolean(opts.jsonl) });
731
1109
  });
732
1110
  categoriesCmd.command("get <id>").description("Get one categories by id").hook("preAction", (thisCommand) => {
733
1111
  syncRootOptsFromCommand(thisCommand);
@@ -754,20 +1132,67 @@ function register(program, helpers) {
754
1132
  if (parsedDefaultBucket !== void 0) bodyFromFlags.defaultBucket = parsedDefaultBucket;
755
1133
  const body = mergeBody(bodyFromFlags, opts.body);
756
1134
  if (opts.ifNotExists) {
1135
+ const existing = await findExistingResource({
1136
+ resource: "categories",
1137
+ token,
1138
+ baseUrl: getBaseUrl(),
1139
+ body,
1140
+ identityFields: ["name", "organizationId"],
1141
+ queryFields: ["organizationId"],
1142
+ request: apiRequest
1143
+ });
1144
+ if (existing) {
1145
+ printOutput(getOutputFormat(), existing, false);
1146
+ return;
1147
+ }
757
1148
  }
758
1149
  const res = await apiRequest({ method: "POST", path: "categories", token, baseUrl: getBaseUrl(), body });
759
1150
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
760
1151
  const data = res.data?.data ?? res.data;
761
1152
  printOutput(getOutputFormat(), data ?? {}, false);
762
1153
  });
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) => {
1154
+ 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
1155
  syncRootOptsFromCommand(thisCommand);
765
1156
  }).action(async (id, opts) => {
766
1157
  const token = requireToken();
767
1158
  if (!token) exitWithError(401, "Missing or invalid token");
768
- const ids = parseIds(opts.ids);
1159
+ const inputRecords = await parseBulkInput(opts.input);
1160
+ if (inputRecords.length > 0) {
1161
+ const ids2 = inputRecords.map(getBulkRecordId).filter((value) => value !== null);
1162
+ if (ids2.length !== inputRecords.length) exitWithError(400, "Each --input record must include id or _id");
1163
+ validateBulkIds(ids2, "categories");
1164
+ if (opts.dryRun) {
1165
+ printOutput(getOutputFormat(), { data: ids2.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids2.length, succeeded: ids2.length, failed: 0 } }, false);
1166
+ return;
1167
+ }
1168
+ const { data: data2, summary } = await runBulk({
1169
+ items: inputRecords,
1170
+ runOne: async (record) => {
1171
+ const itemId = getBulkRecordId(record);
1172
+ const recordBody = { ...record };
1173
+ delete recordBody.id;
1174
+ delete recordBody._id;
1175
+ const bodyFromFlags2 = {};
1176
+ const parsedName2 = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
1177
+ if (parsedName2 !== void 0) bodyFromFlags2.name = parsedName2;
1178
+ const parsedDefaultBucket2 = coerceBodyFlag(opts.defaultBucket, { field: "defaultBucket", flag: "--default-bucket", kind: "string" }, exitWithError);
1179
+ if (parsedDefaultBucket2 !== void 0) bodyFromFlags2.defaultBucket = parsedDefaultBucket2;
1180
+ const body2 = { ...recordBody, ...mergeBody(bodyFromFlags2, opts.body) };
1181
+ const res2 = await apiRequest({ method: "PATCH", path: "categories/" + encodeURIComponent(itemId), token, baseUrl: getBaseUrl(), body: body2 });
1182
+ return { ok: res2.ok, status: res2.status || 0, data: res2.data, error: res2.error };
1183
+ },
1184
+ toResult: (record, ok, status, _data, err) => ok ? { _id: getBulkRecordId(record), status: "updated" } : { _id: getBulkRecordId(record) ?? void 0, error: true, statusCode: status, message: err ?? "Failed" }
1185
+ });
1186
+ printOutput(getOutputFormat(), { data: data2, summary }, false);
1187
+ return;
1188
+ }
1189
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
769
1190
  if (ids.length > 0) {
770
1191
  validateBulkIds(ids, "categories");
1192
+ if (opts.dryRun) {
1193
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
1194
+ return;
1195
+ }
771
1196
  const { data: data2, summary } = await runBulk({
772
1197
  items: ids,
773
1198
  runOne: async (itemId) => {
@@ -785,6 +1210,7 @@ function register(program, helpers) {
785
1210
  printOutput(getOutputFormat(), { data: data2, summary }, false);
786
1211
  return;
787
1212
  }
1213
+ if (!id) exitWithError(400, "Provide id, --ids, --ids-file, or --input");
788
1214
  const bodyFromFlags = {};
789
1215
  const parsedName = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
790
1216
  if (parsedName !== void 0) bodyFromFlags.name = parsedName;
@@ -796,14 +1222,18 @@ function register(program, helpers) {
796
1222
  const data = res.data?.data ?? res.data;
797
1223
  printOutput(getOutputFormat(), data ?? {}, false);
798
1224
  });
799
- categoriesCmd.command("delete <id>").description("Delete categories").option("--ids <ids>", "Comma-separated IDs for bulk delete (max 25)").hook("preAction", (thisCommand) => {
1225
+ 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
1226
  syncRootOptsFromCommand(thisCommand);
801
1227
  }).action(async (id, opts) => {
802
1228
  const token = requireToken();
803
1229
  if (!token) exitWithError(401, "Missing or invalid token");
804
- const ids = parseIds(opts.ids);
1230
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
805
1231
  if (ids.length > 0) {
806
1232
  validateBulkIds(ids, "categories");
1233
+ if (opts.dryRun) {
1234
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_delete" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
1235
+ return;
1236
+ }
807
1237
  const { data, summary } = await runBulk({
808
1238
  items: ids,
809
1239
  runOne: async (itemId) => {
@@ -815,12 +1245,17 @@ function register(program, helpers) {
815
1245
  printOutput(getOutputFormat(), { data, summary }, false);
816
1246
  return;
817
1247
  }
1248
+ if (!id) exitWithError(400, "Provide id, --ids, or --ids-file");
1249
+ if (opts.dryRun) {
1250
+ printOutput(getOutputFormat(), { _id: id, status: "would_delete" }, false);
1251
+ return;
1252
+ }
818
1253
  const res = await apiRequest({ method: "DELETE", path: "categories/" + encodeURIComponent(id), token, baseUrl: getBaseUrl() });
819
1254
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
820
1255
  if (getOutputFormat() === "json") console.log(JSON.stringify({ data: null, deleted: true }));
821
1256
  });
822
1257
  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) => {
1258
+ 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
1259
  syncRootOptsFromCommand(thisCommand);
825
1260
  }).action(async (opts) => {
826
1261
  const token = requireToken();
@@ -831,10 +1266,10 @@ function register(program, helpers) {
831
1266
  if (opts.organizationId != null) {
832
1267
  query.organizationId = coerceQueryFlag(opts.organizationId, { field: "organizationId", flag: "--organization-id", kind: "string" }, exitWithError);
833
1268
  }
834
- const res = await apiRequest({ method: "GET", path: "labels", token, baseUrl: getBaseUrl(), query });
1269
+ const res = await fetchList({ path: "labels", token, baseUrl: getBaseUrl(), query, all: Boolean(opts.all), request: apiRequest });
835
1270
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
836
1271
  const data = res.data?.data ?? [];
837
- printOutput(getOutputFormat(), data, false);
1272
+ printList({ format: getOutputFormat(), data, fields: opts.fields, where: opts.where, jsonl: Boolean(opts.jsonl) });
838
1273
  });
839
1274
  labelsCmd.command("get <id>").description("Get one labels by id").hook("preAction", (thisCommand) => {
840
1275
  syncRootOptsFromCommand(thisCommand);
@@ -861,20 +1296,67 @@ function register(program, helpers) {
861
1296
  if (parsedColour !== void 0) bodyFromFlags.colour = parsedColour;
862
1297
  const body = mergeBody(bodyFromFlags, opts.body);
863
1298
  if (opts.ifNotExists) {
1299
+ const existing = await findExistingResource({
1300
+ resource: "labels",
1301
+ token,
1302
+ baseUrl: getBaseUrl(),
1303
+ body,
1304
+ identityFields: ["name", "organizationId"],
1305
+ queryFields: ["organizationId"],
1306
+ request: apiRequest
1307
+ });
1308
+ if (existing) {
1309
+ printOutput(getOutputFormat(), existing, false);
1310
+ return;
1311
+ }
864
1312
  }
865
1313
  const res = await apiRequest({ method: "POST", path: "labels", token, baseUrl: getBaseUrl(), body });
866
1314
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
867
1315
  const data = res.data?.data ?? res.data;
868
1316
  printOutput(getOutputFormat(), data ?? {}, false);
869
1317
  });
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) => {
1318
+ 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
1319
  syncRootOptsFromCommand(thisCommand);
872
1320
  }).action(async (id, opts) => {
873
1321
  const token = requireToken();
874
1322
  if (!token) exitWithError(401, "Missing or invalid token");
875
- const ids = parseIds(opts.ids);
1323
+ const inputRecords = await parseBulkInput(opts.input);
1324
+ if (inputRecords.length > 0) {
1325
+ const ids2 = inputRecords.map(getBulkRecordId).filter((value) => value !== null);
1326
+ if (ids2.length !== inputRecords.length) exitWithError(400, "Each --input record must include id or _id");
1327
+ validateBulkIds(ids2, "labels");
1328
+ if (opts.dryRun) {
1329
+ printOutput(getOutputFormat(), { data: ids2.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids2.length, succeeded: ids2.length, failed: 0 } }, false);
1330
+ return;
1331
+ }
1332
+ const { data: data2, summary } = await runBulk({
1333
+ items: inputRecords,
1334
+ runOne: async (record) => {
1335
+ const itemId = getBulkRecordId(record);
1336
+ const recordBody = { ...record };
1337
+ delete recordBody.id;
1338
+ delete recordBody._id;
1339
+ const bodyFromFlags2 = {};
1340
+ const parsedName2 = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
1341
+ if (parsedName2 !== void 0) bodyFromFlags2.name = parsedName2;
1342
+ const parsedColour2 = coerceBodyFlag(opts.colour, { field: "colour", flag: "--colour", kind: "string" }, exitWithError);
1343
+ if (parsedColour2 !== void 0) bodyFromFlags2.colour = parsedColour2;
1344
+ const body2 = { ...recordBody, ...mergeBody(bodyFromFlags2, opts.body) };
1345
+ const res2 = await apiRequest({ method: "PATCH", path: "labels/" + encodeURIComponent(itemId), token, baseUrl: getBaseUrl(), body: body2 });
1346
+ return { ok: res2.ok, status: res2.status || 0, data: res2.data, error: res2.error };
1347
+ },
1348
+ toResult: (record, ok, status, _data, err) => ok ? { _id: getBulkRecordId(record), status: "updated" } : { _id: getBulkRecordId(record) ?? void 0, error: true, statusCode: status, message: err ?? "Failed" }
1349
+ });
1350
+ printOutput(getOutputFormat(), { data: data2, summary }, false);
1351
+ return;
1352
+ }
1353
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
876
1354
  if (ids.length > 0) {
877
1355
  validateBulkIds(ids, "labels");
1356
+ if (opts.dryRun) {
1357
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
1358
+ return;
1359
+ }
878
1360
  const { data: data2, summary } = await runBulk({
879
1361
  items: ids,
880
1362
  runOne: async (itemId) => {
@@ -892,6 +1374,7 @@ function register(program, helpers) {
892
1374
  printOutput(getOutputFormat(), { data: data2, summary }, false);
893
1375
  return;
894
1376
  }
1377
+ if (!id) exitWithError(400, "Provide id, --ids, --ids-file, or --input");
895
1378
  const bodyFromFlags = {};
896
1379
  const parsedName = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
897
1380
  if (parsedName !== void 0) bodyFromFlags.name = parsedName;
@@ -903,14 +1386,18 @@ function register(program, helpers) {
903
1386
  const data = res.data?.data ?? res.data;
904
1387
  printOutput(getOutputFormat(), data ?? {}, false);
905
1388
  });
906
- labelsCmd.command("delete <id>").description("Delete labels").option("--ids <ids>", "Comma-separated IDs for bulk delete (max 25)").hook("preAction", (thisCommand) => {
1389
+ 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
1390
  syncRootOptsFromCommand(thisCommand);
908
1391
  }).action(async (id, opts) => {
909
1392
  const token = requireToken();
910
1393
  if (!token) exitWithError(401, "Missing or invalid token");
911
- const ids = parseIds(opts.ids);
1394
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
912
1395
  if (ids.length > 0) {
913
1396
  validateBulkIds(ids, "labels");
1397
+ if (opts.dryRun) {
1398
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_delete" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
1399
+ return;
1400
+ }
914
1401
  const { data, summary } = await runBulk({
915
1402
  items: ids,
916
1403
  runOne: async (itemId) => {
@@ -922,12 +1409,17 @@ function register(program, helpers) {
922
1409
  printOutput(getOutputFormat(), { data, summary }, false);
923
1410
  return;
924
1411
  }
1412
+ if (!id) exitWithError(400, "Provide id, --ids, or --ids-file");
1413
+ if (opts.dryRun) {
1414
+ printOutput(getOutputFormat(), { _id: id, status: "would_delete" }, false);
1415
+ return;
1416
+ }
925
1417
  const res = await apiRequest({ method: "DELETE", path: "labels/" + encodeURIComponent(id), token, baseUrl: getBaseUrl() });
926
1418
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
927
1419
  if (getOutputFormat() === "json") console.log(JSON.stringify({ data: null, deleted: true }));
928
1420
  });
929
1421
  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) => {
1422
+ 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
1423
  syncRootOptsFromCommand(thisCommand);
932
1424
  }).action(async (opts) => {
933
1425
  const token = requireToken();
@@ -944,10 +1436,10 @@ function register(program, helpers) {
944
1436
  if (opts.entityType != null) {
945
1437
  query.entityType = coerceQueryFlag(opts.entityType, { field: "entityType", flag: "--entity-type", kind: "string" }, exitWithError);
946
1438
  }
947
- const res = await apiRequest({ method: "GET", path: "views", token, baseUrl: getBaseUrl(), query });
1439
+ const res = await fetchList({ path: "views", token, baseUrl: getBaseUrl(), query, all: Boolean(opts.all), request: apiRequest });
948
1440
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
949
1441
  const data = res.data?.data ?? [];
950
- printOutput(getOutputFormat(), data, false);
1442
+ printList({ format: getOutputFormat(), data, fields: opts.fields, where: opts.where, jsonl: Boolean(opts.jsonl) });
951
1443
  });
952
1444
  viewsCmd.command("get <id>").description("Get one views by id").hook("preAction", (thisCommand) => {
953
1445
  syncRootOptsFromCommand(thisCommand);
@@ -980,20 +1472,71 @@ function register(program, helpers) {
980
1472
  if (parsedIsDefault !== void 0) bodyFromFlags.isDefault = parsedIsDefault;
981
1473
  const body = mergeBody(bodyFromFlags, opts.body);
982
1474
  if (opts.ifNotExists) {
1475
+ const existing = await findExistingResource({
1476
+ resource: "views",
1477
+ token,
1478
+ baseUrl: getBaseUrl(),
1479
+ body,
1480
+ identityFields: ["name", "entityType", "organizationId", "projectId"],
1481
+ queryFields: ["organizationId", "projectId", "entityType"],
1482
+ request: apiRequest
1483
+ });
1484
+ if (existing) {
1485
+ printOutput(getOutputFormat(), existing, false);
1486
+ return;
1487
+ }
983
1488
  }
984
1489
  const res = await apiRequest({ method: "POST", path: "views", token, baseUrl: getBaseUrl(), body });
985
1490
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
986
1491
  const data = res.data?.data ?? res.data;
987
1492
  printOutput(getOutputFormat(), data ?? {}, false);
988
1493
  });
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) => {
1494
+ 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
1495
  syncRootOptsFromCommand(thisCommand);
991
1496
  }).action(async (id, opts) => {
992
1497
  const token = requireToken();
993
1498
  if (!token) exitWithError(401, "Missing or invalid token");
994
- const ids = parseIds(opts.ids);
1499
+ const inputRecords = await parseBulkInput(opts.input);
1500
+ if (inputRecords.length > 0) {
1501
+ const ids2 = inputRecords.map(getBulkRecordId).filter((value) => value !== null);
1502
+ if (ids2.length !== inputRecords.length) exitWithError(400, "Each --input record must include id or _id");
1503
+ validateBulkIds(ids2, "views");
1504
+ if (opts.dryRun) {
1505
+ printOutput(getOutputFormat(), { data: ids2.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids2.length, succeeded: ids2.length, failed: 0 } }, false);
1506
+ return;
1507
+ }
1508
+ const { data: data2, summary } = await runBulk({
1509
+ items: inputRecords,
1510
+ runOne: async (record) => {
1511
+ const itemId = getBulkRecordId(record);
1512
+ const recordBody = { ...record };
1513
+ delete recordBody.id;
1514
+ delete recordBody._id;
1515
+ const bodyFromFlags2 = {};
1516
+ const parsedName2 = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
1517
+ if (parsedName2 !== void 0) bodyFromFlags2.name = parsedName2;
1518
+ const parsedProjectId2 = coerceBodyFlag(opts.projectId, { field: "projectId", flag: "--project-id", kind: "string" }, exitWithError);
1519
+ if (parsedProjectId2 !== void 0) bodyFromFlags2.projectId = parsedProjectId2;
1520
+ const parsedConfig2 = coerceBodyFlag(opts.config, { field: "config", flag: "--config", kind: "string" }, exitWithError);
1521
+ if (parsedConfig2 !== void 0) bodyFromFlags2.config = parsedConfig2;
1522
+ const parsedIsDefault2 = coerceBodyFlag(opts.isDefault, { field: "isDefault", flag: "--is-default", kind: "boolean" }, exitWithError);
1523
+ if (parsedIsDefault2 !== void 0) bodyFromFlags2.isDefault = parsedIsDefault2;
1524
+ const body2 = { ...recordBody, ...mergeBody(bodyFromFlags2, opts.body) };
1525
+ const res2 = await apiRequest({ method: "PATCH", path: "views/" + encodeURIComponent(itemId), token, baseUrl: getBaseUrl(), body: body2 });
1526
+ return { ok: res2.ok, status: res2.status || 0, data: res2.data, error: res2.error };
1527
+ },
1528
+ toResult: (record, ok, status, _data, err) => ok ? { _id: getBulkRecordId(record), status: "updated" } : { _id: getBulkRecordId(record) ?? void 0, error: true, statusCode: status, message: err ?? "Failed" }
1529
+ });
1530
+ printOutput(getOutputFormat(), { data: data2, summary }, false);
1531
+ return;
1532
+ }
1533
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
995
1534
  if (ids.length > 0) {
996
1535
  validateBulkIds(ids, "views");
1536
+ if (opts.dryRun) {
1537
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_update" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
1538
+ return;
1539
+ }
997
1540
  const { data: data2, summary } = await runBulk({
998
1541
  items: ids,
999
1542
  runOne: async (itemId) => {
@@ -1015,6 +1558,7 @@ function register(program, helpers) {
1015
1558
  printOutput(getOutputFormat(), { data: data2, summary }, false);
1016
1559
  return;
1017
1560
  }
1561
+ if (!id) exitWithError(400, "Provide id, --ids, --ids-file, or --input");
1018
1562
  const bodyFromFlags = {};
1019
1563
  const parsedName = coerceBodyFlag(opts.name, { field: "name", flag: "--name", kind: "string" }, exitWithError);
1020
1564
  if (parsedName !== void 0) bodyFromFlags.name = parsedName;
@@ -1030,14 +1574,18 @@ function register(program, helpers) {
1030
1574
  const data = res.data?.data ?? res.data;
1031
1575
  printOutput(getOutputFormat(), data ?? {}, false);
1032
1576
  });
1033
- viewsCmd.command("delete <id>").description("Delete views").option("--ids <ids>", "Comma-separated IDs for bulk delete (max 25)").hook("preAction", (thisCommand) => {
1577
+ 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
1578
  syncRootOptsFromCommand(thisCommand);
1035
1579
  }).action(async (id, opts) => {
1036
1580
  const token = requireToken();
1037
1581
  if (!token) exitWithError(401, "Missing or invalid token");
1038
- const ids = parseIds(opts.ids);
1582
+ const ids = await parseIdsInput(opts.ids, opts.idsFile);
1039
1583
  if (ids.length > 0) {
1040
1584
  validateBulkIds(ids, "views");
1585
+ if (opts.dryRun) {
1586
+ printOutput(getOutputFormat(), { data: ids.map((_id) => ({ _id, status: "would_delete" })), summary: { total: ids.length, succeeded: ids.length, failed: 0 } }, false);
1587
+ return;
1588
+ }
1041
1589
  const { data, summary } = await runBulk({
1042
1590
  items: ids,
1043
1591
  runOne: async (itemId) => {
@@ -1049,12 +1597,17 @@ function register(program, helpers) {
1049
1597
  printOutput(getOutputFormat(), { data, summary }, false);
1050
1598
  return;
1051
1599
  }
1600
+ if (!id) exitWithError(400, "Provide id, --ids, or --ids-file");
1601
+ if (opts.dryRun) {
1602
+ printOutput(getOutputFormat(), { _id: id, status: "would_delete" }, false);
1603
+ return;
1604
+ }
1052
1605
  const res = await apiRequest({ method: "DELETE", path: "views/" + encodeURIComponent(id), token, baseUrl: getBaseUrl() });
1053
1606
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
1054
1607
  if (getOutputFormat() === "json") console.log(JSON.stringify({ data: null, deleted: true }));
1055
1608
  });
1056
1609
  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) => {
1610
+ 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
1611
  syncRootOptsFromCommand(thisCommand);
1059
1612
  }).action(async (opts) => {
1060
1613
  const token = requireToken();
@@ -1068,10 +1621,10 @@ function register(program, helpers) {
1068
1621
  if (opts.entityId != null) {
1069
1622
  query.entityId = coerceQueryFlag(opts.entityId, { field: "entityId", flag: "--entity-id", kind: "string" }, exitWithError);
1070
1623
  }
1071
- const res = await apiRequest({ method: "GET", path: "comments", token, baseUrl: getBaseUrl(), query });
1624
+ const res = await fetchList({ path: "comments", token, baseUrl: getBaseUrl(), query, all: Boolean(opts.all), request: apiRequest });
1072
1625
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
1073
1626
  const data = res.data?.data ?? [];
1074
- printOutput(getOutputFormat(), data, false);
1627
+ printList({ format: getOutputFormat(), data, fields: opts.fields, where: opts.where, jsonl: Boolean(opts.jsonl) });
1075
1628
  });
1076
1629
  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
1630
  syncRootOptsFromCommand(thisCommand);
@@ -1089,6 +1642,19 @@ function register(program, helpers) {
1089
1642
  if (parsedParentCommentId !== void 0) bodyFromFlags.parentCommentId = parsedParentCommentId;
1090
1643
  const body = mergeBody(bodyFromFlags, opts.body);
1091
1644
  if (opts.ifNotExists) {
1645
+ const existing = await findExistingResource({
1646
+ resource: "comments",
1647
+ token,
1648
+ baseUrl: getBaseUrl(),
1649
+ body,
1650
+ identityFields: ["entityType", "entityId"],
1651
+ queryFields: ["entityType", "entityId"],
1652
+ request: apiRequest
1653
+ });
1654
+ if (existing) {
1655
+ printOutput(getOutputFormat(), existing, false);
1656
+ return;
1657
+ }
1092
1658
  }
1093
1659
  const res = await apiRequest({ method: "POST", path: "comments", token, baseUrl: getBaseUrl(), body });
1094
1660
  if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
@@ -1099,4 +1665,4 @@ function register(program, helpers) {
1099
1665
  export {
1100
1666
  register
1101
1667
  };
1102
- //# sourceMappingURL=generated-IFLUY7PB.js.map
1668
+ //# sourceMappingURL=generated-UG3O6FVC.js.map