@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.
@@ -0,0 +1,1161 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ apiRequest,
4
+ coerceBodyFlag,
5
+ fetchList,
6
+ formatErrorPayload,
7
+ getBaseUrl,
8
+ getDefaultOutputFormat,
9
+ mergeBody,
10
+ printList,
11
+ printOutput,
12
+ printStructuredError
13
+ } from "./chunk-CXNVJZ4C.js";
14
+ import {
15
+ resolveToken
16
+ } from "./chunk-GZ4DXDQM.js";
17
+
18
+ // src/build-program.ts
19
+ import { createRequire } from "module";
20
+ import { Command } from "commander";
21
+
22
+ // src/lib/command-discovery.ts
23
+ function discoverCommands(program, version) {
24
+ return {
25
+ cli: "pilfer",
26
+ version,
27
+ outputFormats: ["json", "table", "csv", "jsonl"],
28
+ auth: {
29
+ tokenSources: ["--token", "PILFER_API_TOKEN", "credentials.json"],
30
+ baseUrlSources: ["--base-url", "--api", "PILFER_API_BASE_URL", "config", "default"]
31
+ },
32
+ commands: program.commands.map(discoverCommand)
33
+ };
34
+ }
35
+ function discoverCommand(command) {
36
+ const commandWithInternals = command;
37
+ return {
38
+ name: command.name(),
39
+ aliases: command.aliases(),
40
+ description: command.description(),
41
+ usage: command.usage(),
42
+ arguments: (commandWithInternals.registeredArguments ?? []).map((arg) => ({
43
+ name: arg.name(),
44
+ required: arg.required,
45
+ variadic: arg.variadic
46
+ })),
47
+ options: command.options.map((option) => ({
48
+ flags: option.flags,
49
+ description: option.description,
50
+ required: option.required,
51
+ optional: option.optional
52
+ })),
53
+ commands: command.commands.map(discoverCommand)
54
+ };
55
+ }
56
+ function buildResourceSchema(payload) {
57
+ const resources = payload.commands.filter(
58
+ (command) => command.commands.some(
59
+ (subcommand) => ["list", "get", "create", "update", "delete"].includes(subcommand.name)
60
+ )
61
+ ).map((command) => ({
62
+ name: command.name,
63
+ actions: command.commands.map((subcommand) => ({
64
+ name: subcommand.name,
65
+ arguments: subcommand.arguments,
66
+ options: subcommand.options
67
+ }))
68
+ }));
69
+ return {
70
+ cli: payload.cli,
71
+ version: payload.version,
72
+ outputFormats: payload.outputFormats,
73
+ auth: payload.auth,
74
+ resources
75
+ };
76
+ }
77
+
78
+ // src/lib/reports.ts
79
+ var PRIORITY_ORDER = ["urgent", "high", "medium", "low", "none"];
80
+ var ACTIVE_SCOUT_STATUSES = ["New", "In Progress", "In Review", "Shortlist", "Approved"];
81
+ function buildShortlistRows(scouts, nowMs = Date.now()) {
82
+ return scouts.filter(isRecord).filter((scout) => scout.status === "Shortlist").map((scout) => scoutToQueueRow(scout, nowMs)).sort(
83
+ (a, b) => comparePriority(a.priority, b.priority) || b.estimatedCost - a.estimatedCost || b.ageDays - a.ageDays
84
+ );
85
+ }
86
+ function buildPipelineGroups(scouts, nowMs = Date.now()) {
87
+ const rows = scouts.filter(isRecord).map((scout) => scoutToQueueRow(scout, nowMs));
88
+ return ACTIVE_SCOUT_STATUSES.map((status) => {
89
+ const statusRows = rows.filter((row) => row.status === status);
90
+ const totalAge = statusRows.reduce((sum2, row) => sum2 + row.ageDays, 0);
91
+ const sortedByAge = [...statusRows].sort((a, b) => b.ageDays - a.ageDays);
92
+ return {
93
+ status,
94
+ count: statusRows.length,
95
+ totalEstimatedCost: sum(statusRows, "estimatedCost"),
96
+ averageAgeDays: statusRows.length > 0 ? Math.round(totalAge / statusRows.length) : 0,
97
+ oldestAgeDays: sortedByAge[0]?.ageDays ?? 0,
98
+ ...sortedByAge[0] ? { oldestScoutName: sortedByAge[0].name } : {}
99
+ };
100
+ });
101
+ }
102
+ function buildScoutingReport(scouts, sourcesByScout = {}, nowMs = Date.now()) {
103
+ const scoutRows = scouts.filter(isRecord).map((scout) => {
104
+ const id = stringValue(scout._id ?? scout.id);
105
+ return {
106
+ ...scout,
107
+ sources: sourcesByScout[id] ?? scout.sources
108
+ };
109
+ });
110
+ const queueRows = scoutRows.map((scout) => scoutToQueueRow(scout, nowMs));
111
+ const activeRows = queueRows.filter((row) => ACTIVE_SCOUT_STATUSES.includes(row.status));
112
+ const shortlistRows = buildShortlistRows(scoutRows, nowMs);
113
+ const statusCounts = queueRows.reduce((acc, row) => {
114
+ acc[row.status] = (acc[row.status] ?? 0) + 1;
115
+ return acc;
116
+ }, {});
117
+ return {
118
+ totalScouts: queueRows.length,
119
+ activeScouts: activeRows.length,
120
+ activeEstimatedCost: sum(activeRows, "estimatedCost"),
121
+ shortlist: {
122
+ count: shortlistRows.length,
123
+ totalEstimatedCost: sum(shortlistRows, "estimatedCost"),
124
+ ...shortlistRows[0] ? { largestScout: shortlistRows[0] } : {}
125
+ },
126
+ pipeline: buildPipelineGroups(scoutRows, nowMs),
127
+ missingSourceScouts: activeRows.filter((row) => (row.sourceCount ?? 0) === 0).sort(
128
+ (a, b) => comparePriority(a.priority, b.priority) || b.estimatedCost - a.estimatedCost || b.ageDays - a.ageDays
129
+ ),
130
+ statusCounts
131
+ };
132
+ }
133
+ function buildAssetReport(assets, scope = {}) {
134
+ const scoped = applyScope(assets.filter(isRecord), scope);
135
+ const byCategory = /* @__PURE__ */ new Map();
136
+ for (const asset of scoped.rows) {
137
+ const category = categoryName(asset);
138
+ const current = byCategory.get(category) ?? { category, count: 0, totalCost: 0 };
139
+ current.count += 1;
140
+ current.totalCost += numberValue(asset.cost);
141
+ byCategory.set(category, current);
142
+ }
143
+ return {
144
+ totalAssets: scoped.rows.length,
145
+ totalCost: scoped.rows.reduce((total, asset) => total + numberValue(asset.cost), 0),
146
+ byCategory: Array.from(byCategory.values()).sort(
147
+ (a, b) => b.totalCost - a.totalCost || b.count - a.count
148
+ ),
149
+ ...scoped.warning ? { scopeWarning: scoped.warning } : {}
150
+ };
151
+ }
152
+ function buildBalanceReport(assets, scouts, scope = {}, nowMs = Date.now()) {
153
+ const scopedScouts = applyScope(scouts.filter(isRecord), scope);
154
+ const activeScouts = scopedScouts.rows.map((scout) => scoutToQueueRow(scout, nowMs)).filter((row) => ACTIVE_SCOUT_STATUSES.includes(row.status));
155
+ const shortlistRows = activeScouts.filter((row) => row.status === "Shortlist");
156
+ const assetReport = buildAssetReport(assets, scope);
157
+ const gapSignals = [];
158
+ if (activeScouts.length === 0) {
159
+ gapSignals.push({ kind: "no_active_scouts", message: "No active scouts in scope" });
160
+ }
161
+ if (assetReport.totalAssets === 0) {
162
+ gapSignals.push({ kind: "no_assets", message: "No assets in scope" });
163
+ }
164
+ if (assetReport.totalCost > 0 && activeScouts.length === 0) {
165
+ gapSignals.push({
166
+ kind: "maintenance_only",
167
+ message: "Scope has assets but no active scouting pipeline",
168
+ value: assetReport.totalCost
169
+ });
170
+ }
171
+ if (activeScouts.length > 0 && assetReport.totalAssets === 0) {
172
+ gapSignals.push({
173
+ kind: "scouting_without_assets",
174
+ message: "Scope has scouting pipeline but no owned assets yet",
175
+ value: sum(activeScouts, "estimatedCost")
176
+ });
177
+ }
178
+ const scopeWarning = scopedScouts.warning ?? assetReport.scopeWarning;
179
+ return {
180
+ scope,
181
+ assets: assetReport,
182
+ scouting: {
183
+ activeScouts: activeScouts.length,
184
+ activeEstimatedCost: sum(activeScouts, "estimatedCost"),
185
+ shortlistCount: shortlistRows.length,
186
+ shortlistEstimatedCost: sum(shortlistRows, "estimatedCost")
187
+ },
188
+ gapSignals,
189
+ ...scopeWarning ? { scopeWarning } : {}
190
+ };
191
+ }
192
+ function scoutToQueueRow(scout, nowMs = Date.now()) {
193
+ const sources = Array.isArray(scout.sources) ? scout.sources.filter(isRecord) : [];
194
+ return {
195
+ _id: stringValue(scout._id ?? scout.id),
196
+ name: stringValue(scout.name, "Untitled scout"),
197
+ status: stringValue(scout.status, "unknown"),
198
+ priority: stringValue(scout.priority, "none"),
199
+ estimatedCost: numberValue(scout.estimatedCost),
200
+ ageDays: ageDays(scout._creationTime, nowMs),
201
+ ...sources.length > 0 ? {
202
+ sourceCount: sources.length,
203
+ hasStarredSource: sources.some((source) => source.starred === true)
204
+ } : {}
205
+ };
206
+ }
207
+ function comparePriority(a, b) {
208
+ const aIndex = PRIORITY_ORDER.indexOf(a);
209
+ const bIndex = PRIORITY_ORDER.indexOf(b);
210
+ return (aIndex === -1 ? PRIORITY_ORDER.length : aIndex) - (bIndex === -1 ? PRIORITY_ORDER.length : bIndex);
211
+ }
212
+ function ageDays(creationTime, nowMs) {
213
+ const created = numberValue(creationTime);
214
+ if (created <= 0) return 0;
215
+ return Math.max(0, Math.floor((nowMs - created) / 864e5));
216
+ }
217
+ function sum(rows, field) {
218
+ return rows.reduce((total, row) => total + row[field], 0);
219
+ }
220
+ function applyScope(rows, scope) {
221
+ const key = scope.projectId ? "projectId" : scope.spaceId ? "spaceId" : null;
222
+ const value = scope.projectId ?? scope.spaceId;
223
+ if (!key || !value) return { rows };
224
+ const rowsWithScopeData = rows.filter((row) => hasScopeValue(row, key, value));
225
+ const anyHasScopeShape = rows.some((row) => hasScopeShape(row, key));
226
+ if (!anyHasScopeShape) {
227
+ return {
228
+ rows,
229
+ warning: `${key} filter requested, but REST payload has no relation data; totals are unfiltered`
230
+ };
231
+ }
232
+ return { rows: rowsWithScopeData };
233
+ }
234
+ function hasScopeShape(row, key) {
235
+ if (typeof row[key] === "string") return true;
236
+ const relationKey = key === "projectId" ? "projects" : "spaces";
237
+ return Array.isArray(row[relationKey]);
238
+ }
239
+ function hasScopeValue(row, key, value) {
240
+ if (row[key] === value) return true;
241
+ const relationKey = key === "projectId" ? "projects" : "spaces";
242
+ const relations = row[relationKey];
243
+ if (!Array.isArray(relations)) return false;
244
+ return relations.filter(isRecord).some((relation) => relation[key] === value || relation._id === value);
245
+ }
246
+ function categoryName(row) {
247
+ if (isRecord(row.category) && typeof row.category.name === "string") return row.category.name;
248
+ if (typeof row.categoryId === "string") return row.categoryId;
249
+ return "Uncategorized";
250
+ }
251
+ function numberValue(value) {
252
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
253
+ }
254
+ function stringValue(value, fallback = "") {
255
+ return typeof value === "string" && value.trim() ? value : fallback;
256
+ }
257
+ function isRecord(value) {
258
+ return typeof value === "object" && value !== null && !Array.isArray(value);
259
+ }
260
+
261
+ // src/lib/resource-resolver.ts
262
+ async function resolveResourceByName(options) {
263
+ const res = await options.request({
264
+ method: "GET",
265
+ path: options.resource,
266
+ token: options.token,
267
+ baseUrl: options.baseUrl
268
+ });
269
+ if (!res.ok) {
270
+ return {
271
+ ok: false,
272
+ status: res.status || 500,
273
+ message: res.error ?? "Request failed"
274
+ };
275
+ }
276
+ const rows = (res.data?.data ?? []).filter(
277
+ isRecord2
278
+ );
279
+ const expected = options.name.trim().toLowerCase();
280
+ const matches = rows.filter((row) => {
281
+ const raw = row[options.field];
282
+ return typeof raw === "string" && raw.trim().toLowerCase() === expected;
283
+ });
284
+ if (matches.length === 0) {
285
+ return {
286
+ ok: false,
287
+ status: 404,
288
+ message: `No ${options.resource} found where ${options.field} is ${JSON.stringify(options.name)}`
289
+ };
290
+ }
291
+ if (matches.length > 1) {
292
+ const candidates = matches.map((row) => ({
293
+ _id: row._id ?? row.id,
294
+ [options.field]: row[options.field]
295
+ }));
296
+ return {
297
+ ok: false,
298
+ status: 409,
299
+ message: `Multiple ${options.resource} matched ${options.field}=${JSON.stringify(options.name)}`,
300
+ candidates
301
+ };
302
+ }
303
+ return { ok: true, item: matches[0] };
304
+ }
305
+ function isRecord2(value) {
306
+ return typeof value === "object" && value !== null && !Array.isArray(value);
307
+ }
308
+
309
+ // src/lib/source-body.ts
310
+ function sourceBodyFromFlags(opts, isUpdate, exitWithError) {
311
+ const bodyFromFlags = {};
312
+ const fieldSpecs = [
313
+ { option: "url", field: "url", flag: "--url", kind: "string" },
314
+ { option: "name", field: "name", flag: "--name", kind: "string" },
315
+ { option: "title", field: "name", flag: "--title", kind: "string" },
316
+ { option: "description", field: "description", flag: "--description", kind: "string" },
317
+ { option: "price", field: "price", flag: "--price", kind: "number" },
318
+ { option: "image", field: "image", flag: "--image", kind: "string" },
319
+ { option: "imageUrl", field: "image", flag: "--image-url", kind: "string" },
320
+ { option: "address", field: "address", flag: "--address", kind: "string" },
321
+ { option: "sourceType", field: "sourceType", flag: "--source-type", kind: "string" },
322
+ { option: "condition", field: "condition", flag: "--condition", kind: "string" },
323
+ { option: "notes", field: "notes", flag: "--notes", kind: "string" },
324
+ { option: "starred", field: "starred", flag: "--starred", kind: "boolean" }
325
+ ];
326
+ for (const spec of fieldSpecs) {
327
+ const value = opts[spec.option];
328
+ const parsed = coerceBodyFlag(
329
+ value,
330
+ {
331
+ field: spec.field,
332
+ flag: spec.flag,
333
+ kind: spec.kind
334
+ },
335
+ exitWithError
336
+ );
337
+ if (parsed !== void 0) bodyFromFlags[spec.field] = parsed;
338
+ }
339
+ if (!isUpdate && Object.keys(bodyFromFlags).length === 0 && !opts.body) {
340
+ exitWithError(400, "Provide source fields or --body JSON");
341
+ }
342
+ return mergeBody(bodyFromFlags, opts.body);
343
+ }
344
+
345
+ // src/lib/workflows.ts
346
+ async function purchaseScout(options) {
347
+ const scoutRes = await options.request({
348
+ method: "GET",
349
+ path: `scouts/${encodeURIComponent(options.scoutId)}`,
350
+ token: options.token,
351
+ baseUrl: options.baseUrl
352
+ });
353
+ if (!scoutRes.ok) return fail(scoutRes);
354
+ const scout = unwrapRecord(scoutRes.data);
355
+ if (!scout) {
356
+ return { ok: false, status: 404, error: "Scout not found" };
357
+ }
358
+ const assetBody = buildAssetBodyFromScout(scout, options);
359
+ if (options.dryRun) {
360
+ return {
361
+ ok: true,
362
+ status: 200,
363
+ data: { scout, assetBody, dryRun: true }
364
+ };
365
+ }
366
+ const assetRes = await options.request({
367
+ method: "POST",
368
+ path: "assets",
369
+ token: options.token,
370
+ baseUrl: options.baseUrl,
371
+ body: assetBody
372
+ });
373
+ if (!assetRes.ok) return fail(assetRes);
374
+ const updateRes = await options.request({
375
+ method: "PATCH",
376
+ path: `scouts/${encodeURIComponent(options.scoutId)}`,
377
+ token: options.token,
378
+ baseUrl: options.baseUrl,
379
+ body: { status: "Purchased" }
380
+ });
381
+ if (!updateRes.ok) {
382
+ return {
383
+ ok: false,
384
+ status: updateRes.status || 500,
385
+ error: updateRes.error ?? "Asset created but scout status update failed",
386
+ data: {
387
+ scout,
388
+ assetBody,
389
+ asset: unwrapData(assetRes.data)
390
+ }
391
+ };
392
+ }
393
+ return {
394
+ ok: true,
395
+ status: 200,
396
+ data: {
397
+ scout,
398
+ assetBody,
399
+ asset: unwrapData(assetRes.data),
400
+ scoutUpdate: unwrapData(updateRes.data)
401
+ }
402
+ };
403
+ }
404
+ function buildAssetBodyFromScout(scout, options) {
405
+ const body = {
406
+ name: options.assetName ?? scout.name ?? "Purchased scout",
407
+ scoutId: options.scoutId,
408
+ cost: options.cost ?? numericValue(scout.estimatedCost) ?? numericValue(scout.potentialCost) ?? 0,
409
+ acquisitionDate: options.acquisitionDate ?? Date.now()
410
+ };
411
+ for (const field of ["organizationId", "categoryId", "heroImage"]) {
412
+ if (typeof scout[field] === "string") body[field] = scout[field];
413
+ }
414
+ return body;
415
+ }
416
+ function numericValue(value) {
417
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
418
+ }
419
+ function unwrapData(data) {
420
+ if (isRecord3(data) && "data" in data) return data.data;
421
+ return data;
422
+ }
423
+ function unwrapRecord(data) {
424
+ const unwrapped = unwrapData(data);
425
+ return isRecord3(unwrapped) ? unwrapped : null;
426
+ }
427
+ function fail(res) {
428
+ return {
429
+ ok: false,
430
+ status: res.status || 500,
431
+ error: res.error ?? "Request failed"
432
+ };
433
+ }
434
+ function isRecord3(value) {
435
+ return typeof value === "object" && value !== null && !Array.isArray(value);
436
+ }
437
+
438
+ // src/build-program.ts
439
+ var _require = createRequire(import.meta.url);
440
+ var { version: CLI_VERSION } = _require("../package.json");
441
+ var rootOpts = {};
442
+ function syncRootOptsFromCommand(cmd) {
443
+ const maybeCommand = cmd;
444
+ if (!maybeCommand || typeof maybeCommand !== "object") return;
445
+ if (typeof maybeCommand.opts !== "function") return;
446
+ let root = maybeCommand;
447
+ while (root.parent) root = root.parent;
448
+ if (typeof root.opts !== "function") return;
449
+ rootOpts = root.opts();
450
+ }
451
+ var DUMMY_HELPERS = {
452
+ getOutputFormat: () => "json",
453
+ requireToken: () => null,
454
+ exitWithError: () => {
455
+ throw new Error("Docs generator: action should not run");
456
+ },
457
+ getBaseUrl: () => "https://api.getpilfer.com",
458
+ syncRootOptsFromCommand: () => {
459
+ }
460
+ };
461
+ function addTopLevelCommands(program, helpers) {
462
+ const { getOutputFormat, requireToken, exitWithError } = helpers;
463
+ function getRequiredToken() {
464
+ const token = requireToken();
465
+ if (!token) exitWithError(401, "Missing or invalid token. Run: pilfer login");
466
+ return token;
467
+ }
468
+ function getOutputFormatFromProgram(_p) {
469
+ const raw = rootOpts.output;
470
+ if (raw === "json" || raw === "table" || raw === "csv") return raw;
471
+ return getDefaultOutputFormat();
472
+ }
473
+ function exitWithErrorHandler(program2, status, message) {
474
+ const format = getOutputFormatFromProgram(program2);
475
+ const payload = formatErrorPayload(status, message);
476
+ printStructuredError(format, payload);
477
+ process.exit(status === 401 || status === 403 ? 1 : 2);
478
+ }
479
+ program.action(() => {
480
+ console.log(`
481
+ Welcome to the Pilfer CLI - manage your Pilfer data from the terminal.
482
+
483
+ Get started
484
+ ----------
485
+ Log in: pilfer login
486
+ Verify: pilfer whoami
487
+ Overview: pilfer status
488
+
489
+ Resources (list | get | create | update | delete)
490
+ ------------------------------------------------
491
+ organizations projects spaces scouts
492
+ sources assets categories labels views
493
+
494
+ Examples
495
+ --------
496
+ pilfer organizations list
497
+ pilfer projects create --name "Studio upgrade"
498
+ pilfer scouts list --status Shortlist
499
+ pilfer sources list --scout-id scout_123
500
+
501
+ Help
502
+ ----
503
+ pilfer --help
504
+ pilfer <resource> --help
505
+ `);
506
+ });
507
+ program.command("resolve <resource>").description("Resolve one resource id by an exact field match").requiredOption("--name <value>", "Value to resolve").option("--field <field>", "Field to match", "name").option("--id-only", "Print only the resolved id").action(async (resource, opts) => {
508
+ const token = getRequiredToken();
509
+ const result = await resolveResourceByName({
510
+ resource,
511
+ field: opts.field,
512
+ name: opts.name,
513
+ token,
514
+ baseUrl: getBaseUrl(rootOpts.baseUrl ?? rootOpts.api),
515
+ request: apiRequest
516
+ });
517
+ if (!result.ok) {
518
+ const suffix = result.candidates && result.candidates.length > 0 ? ` Candidates: ${JSON.stringify(result.candidates)}` : "";
519
+ exitWithError(result.status, `${result.message}${suffix}`);
520
+ return;
521
+ }
522
+ if (opts.idOnly) {
523
+ const id = result.item._id ?? result.item.id;
524
+ if (typeof id !== "string") exitWithError(404, "Resolved item has no id");
525
+ console.log(id);
526
+ return;
527
+ }
528
+ printOutput(getOutputFormat(), result.item, false);
529
+ });
530
+ program.command("commands").description("Print machine-readable command metadata").option("--json", "Print JSON metadata").action(() => {
531
+ const payload = discoverCommands(program, CLI_VERSION);
532
+ console.log(JSON.stringify(payload));
533
+ });
534
+ program.command("schema").description("Print machine-readable resource schema").option("--json", "Print JSON schema").action(() => {
535
+ const payload = discoverCommands(program, CLI_VERSION);
536
+ console.log(JSON.stringify(buildResourceSchema(payload)));
537
+ });
538
+ program.command("whoami").description("Verify auth and show current user and token scopes").action(async () => {
539
+ const token = getRequiredToken();
540
+ const res = await apiRequest({
541
+ method: "GET",
542
+ path: "me",
543
+ token,
544
+ baseUrl: rootOpts.baseUrl
545
+ });
546
+ if (!res.ok) exitWithError(res.status || 401, res.error ?? "Unauthorized");
547
+ const data = res.data?.data ?? res.data;
548
+ printOutput(getOutputFormat(), data ?? {}, false);
549
+ });
550
+ program.command("login [token]").description("Store API token (prompt or pass token as argument)").action(async (tokenArg) => {
551
+ const { saveToken } = await import("./auth-VFGSMTC6.js");
552
+ let token = tokenArg?.trim();
553
+ if (!token) {
554
+ const readline = await import("readline");
555
+ const rl = readline.createInterface({
556
+ input: process.stdin,
557
+ output: process.stderr
558
+ });
559
+ token = await new Promise((resolve) => {
560
+ rl.question("API token: ", (answer) => {
561
+ rl.close();
562
+ resolve(answer?.trim() ?? "");
563
+ });
564
+ });
565
+ }
566
+ if (!token) {
567
+ console.error("No token provided.");
568
+ process.exit(1);
569
+ }
570
+ const res = await apiRequest({
571
+ method: "GET",
572
+ path: "me",
573
+ token,
574
+ baseUrl: rootOpts.baseUrl
575
+ });
576
+ if (!res.ok) {
577
+ console.error("Invalid token or API error:", res.error ?? res.status);
578
+ process.exit(1);
579
+ }
580
+ saveToken(token);
581
+ console.log("Logged in successfully.");
582
+ });
583
+ const config = program.command("config").description("Persist and inspect CLI config");
584
+ config.command("show").description("Show stored CLI config").action(async () => {
585
+ const { getCredentialsPath, resolveStoredBaseUrl } = await import("./auth-VFGSMTC6.js");
586
+ const token = resolveToken(rootOpts.token ?? void 0);
587
+ const baseUrl = getBaseUrl(rootOpts.baseUrl ?? rootOpts.api);
588
+ const storedBaseUrl = resolveStoredBaseUrl();
589
+ printOutput(
590
+ getOutputFormat(),
591
+ {
592
+ configPath: getCredentialsPath(),
593
+ tokenConfigured: Boolean(token),
594
+ baseUrl,
595
+ baseUrlSource: rootOpts.baseUrl ?? rootOpts.api ? "flag" : process.env.PILFER_API_BASE_URL ? "env" : storedBaseUrl ? "config" : "default"
596
+ },
597
+ false
598
+ );
599
+ });
600
+ config.command("set-base-url <url>").alias("set-api").description("Persist default API base URL").action(async (url) => {
601
+ const { saveBaseUrl } = await import("./auth-VFGSMTC6.js");
602
+ const normalized = url.trim().replace(/\/$/, "");
603
+ if (!normalized) {
604
+ console.error("No base URL provided.");
605
+ process.exit(1);
606
+ }
607
+ saveBaseUrl(normalized);
608
+ console.log(`Saved API base URL: ${normalized}`);
609
+ });
610
+ config.command("unset-base-url").alias("unset-api").description("Remove persisted API base URL").action(async () => {
611
+ const { clearBaseUrl } = await import("./auth-VFGSMTC6.js");
612
+ clearBaseUrl();
613
+ console.log("Cleared stored API base URL.");
614
+ });
615
+ program.command("logout").description("Remove stored API token").action(async () => {
616
+ const { clearCredentials } = await import("./auth-VFGSMTC6.js");
617
+ clearCredentials();
618
+ console.log("Logged out.");
619
+ });
620
+ program.command("setup-agent").description("Print instructions for configuring Pilfer CLI for AI agents").action(() => {
621
+ console.log(`
622
+ To use the Pilfer CLI from an AI agent or script:
623
+
624
+ 1. Set your API token:
625
+ export PILFER_API_TOKEN="plf_..."
626
+
627
+ 2. Optional: override API base URL for local or early deployments:
628
+ export PILFER_API_BASE_URL="https://api.getpilfer.com"
629
+
630
+ 3. Verify auth:
631
+ pilfer whoami
632
+
633
+ 4. Commands use the pattern: pilfer <resource> list|get|create|update|delete
634
+ When stdout is not a TTY, output defaults to JSON.
635
+ `);
636
+ });
637
+ program.command("status").description("Situational summary: organizations, projects, spaces, scouts, assets").action(async () => {
638
+ const token = getRequiredToken();
639
+ const baseUrl = rootOpts.baseUrl;
640
+ const [orgsRes, projectsRes, spacesRes, scoutsRes, assetsRes] = await Promise.all([
641
+ apiRequest({
642
+ method: "GET",
643
+ path: "organizations",
644
+ token,
645
+ baseUrl
646
+ }),
647
+ apiRequest({
648
+ method: "GET",
649
+ path: "projects",
650
+ token,
651
+ baseUrl
652
+ }),
653
+ apiRequest({
654
+ method: "GET",
655
+ path: "spaces",
656
+ token,
657
+ baseUrl
658
+ }),
659
+ apiRequest({
660
+ method: "GET",
661
+ path: "scouts",
662
+ token,
663
+ baseUrl
664
+ }),
665
+ apiRequest({
666
+ method: "GET",
667
+ path: "assets",
668
+ token,
669
+ baseUrl
670
+ })
671
+ ]);
672
+ const unwrap = (res) => res.ok && res.data ? res.data.data ?? [] : [];
673
+ const scouts = unwrap(scoutsRes);
674
+ const scoutStatuses = scouts.reduce((acc, scout) => {
675
+ const status = scout.status ?? "unknown";
676
+ acc[status] = (acc[status] ?? 0) + 1;
677
+ return acc;
678
+ }, {});
679
+ printOutput(
680
+ getOutputFormat(),
681
+ {
682
+ organizations: unwrap(orgsRes).length,
683
+ projects: unwrap(projectsRes).length,
684
+ spaces: unwrap(spacesRes).length,
685
+ scouts: scouts.length,
686
+ assets: unwrap(assetsRes).length,
687
+ scoutsByStatus: scoutStatuses
688
+ },
689
+ false
690
+ );
691
+ });
692
+ program.exitOverride((err) => {
693
+ if (err.code === "commander.helpDisplayed" || err.code === "commander.version") {
694
+ throw err;
695
+ }
696
+ exitWithErrorHandler(program, 2, err.message);
697
+ });
698
+ }
699
+ async function registerGeneratedCommands(program, helpers) {
700
+ const mod = await import("./generated-7QDIZ5V5.js").catch(() => null);
701
+ if (mod?.register) mod.register(program, helpers);
702
+ }
703
+ function registerWorkflowCommands(program, helpers) {
704
+ const { getOutputFormat, requireToken, exitWithError, getBaseUrl: getBaseUrl2 } = helpers;
705
+ const scouts = program.commands.find((command) => command.name() === "scouts");
706
+ const reports = program.command("reports").description("Agent-oriented reports");
707
+ function getRequiredToken() {
708
+ const token = requireToken();
709
+ if (!token) exitWithError(401, "Missing or invalid token. Run: pilfer login");
710
+ return token;
711
+ }
712
+ registerRelationCommands(program, helpers, getRequiredToken);
713
+ reports.command("assets").description("Summarize asset value and category concentration").option("--project-id <id>", "Project scope").option("--space-id <id>", "Space scope").hook("preAction", (thisCommand) => {
714
+ helpers.syncRootOptsFromCommand(thisCommand);
715
+ }).action(async (opts) => {
716
+ const token = getRequiredToken();
717
+ const baseUrl = getBaseUrl2();
718
+ const assetsRes = await fetchList({
719
+ path: "assets",
720
+ token,
721
+ baseUrl,
722
+ query: {},
723
+ all: true,
724
+ request: apiRequest
725
+ });
726
+ if (!assetsRes.ok) {
727
+ exitWithError(assetsRes.status || 500, assetsRes.error ?? "Request failed");
728
+ }
729
+ printOutput(
730
+ getOutputFormat(),
731
+ buildAssetReport(assetsRes.data?.data ?? [], scopeFromOptions(opts)),
732
+ false
733
+ );
734
+ });
735
+ reports.command("balance").description("Compare owned asset value with active scouting pipeline").option("--project-id <id>", "Project scope").option("--space-id <id>", "Space scope").hook("preAction", (thisCommand) => {
736
+ helpers.syncRootOptsFromCommand(thisCommand);
737
+ }).action(async (opts) => {
738
+ const token = getRequiredToken();
739
+ const baseUrl = getBaseUrl2();
740
+ const [assetsRes, scoutsRes] = await Promise.all([
741
+ fetchList({
742
+ path: "assets",
743
+ token,
744
+ baseUrl,
745
+ query: {},
746
+ all: true,
747
+ request: apiRequest
748
+ }),
749
+ fetchList({
750
+ path: "scouts",
751
+ token,
752
+ baseUrl,
753
+ query: {},
754
+ all: true,
755
+ request: apiRequest
756
+ })
757
+ ]);
758
+ if (!assetsRes.ok) {
759
+ exitWithError(assetsRes.status || 500, assetsRes.error ?? "Request failed");
760
+ }
761
+ if (!scoutsRes.ok) {
762
+ exitWithError(scoutsRes.status || 500, scoutsRes.error ?? "Request failed");
763
+ }
764
+ printOutput(
765
+ getOutputFormat(),
766
+ buildBalanceReport(
767
+ assetsRes.data?.data ?? [],
768
+ scoutsRes.data?.data ?? [],
769
+ scopeFromOptions(opts)
770
+ ),
771
+ false
772
+ );
773
+ });
774
+ reports.command("scouting").description("Summarize scouting pipeline, shortlist pressure, and source gaps").option("--skip-sources", "Skip per-scout source checks").hook("preAction", (thisCommand) => {
775
+ helpers.syncRootOptsFromCommand(thisCommand);
776
+ }).action(async (opts) => {
777
+ const token = getRequiredToken();
778
+ const baseUrl = getBaseUrl2();
779
+ const scoutsRes = await fetchList({
780
+ path: "scouts",
781
+ token,
782
+ baseUrl,
783
+ query: {},
784
+ all: true,
785
+ request: apiRequest
786
+ });
787
+ if (!scoutsRes.ok) {
788
+ exitWithError(scoutsRes.status || 500, scoutsRes.error ?? "Request failed");
789
+ }
790
+ const scoutsData = scoutsRes.data?.data ?? [];
791
+ const sourcesByScout = opts.skipSources ? {} : await fetchSourcesByScout(scoutsData, token, baseUrl);
792
+ printOutput(
793
+ getOutputFormat(),
794
+ buildScoutingReport(scoutsData, sourcesByScout),
795
+ false
796
+ );
797
+ });
798
+ if (!scouts) return;
799
+ scouts.command("shortlist").description("Show shortlisted scouts in purchase priority order").option("--limit <n>", "Max rows").hook("preAction", (thisCommand) => {
800
+ helpers.syncRootOptsFromCommand(thisCommand);
801
+ }).action(async (opts) => {
802
+ const token = getRequiredToken();
803
+ const res = await fetchList({
804
+ path: "scouts",
805
+ token,
806
+ baseUrl: getBaseUrl2(),
807
+ query: {},
808
+ all: true,
809
+ request: apiRequest
810
+ });
811
+ if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
812
+ const rows = buildShortlistRows(res.data?.data ?? []);
813
+ const limit = parseOptionalNumber(opts.limit, "--limit", exitWithError);
814
+ printOutput(
815
+ getOutputFormat(),
816
+ typeof limit === "number" ? rows.slice(0, limit) : rows,
817
+ false
818
+ );
819
+ });
820
+ scouts.command("pipeline").description("Summarize active scout pipeline by status").hook("preAction", (thisCommand) => {
821
+ helpers.syncRootOptsFromCommand(thisCommand);
822
+ }).action(async () => {
823
+ const token = getRequiredToken();
824
+ const res = await fetchList({
825
+ path: "scouts",
826
+ token,
827
+ baseUrl: getBaseUrl2(),
828
+ query: {},
829
+ all: true,
830
+ request: apiRequest
831
+ });
832
+ if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
833
+ printOutput(getOutputFormat(), buildPipelineGroups(res.data?.data ?? []), false);
834
+ });
835
+ scouts.command("purchase <id>").description("Create an asset from a purchased scout and mark the scout Purchased").option("--name <value>", "Asset name override").option("--cost <value>", "Actual purchase cost").option("--acquisition-date <value>", "Acquisition timestamp in milliseconds").option("--dry-run", "Print proposed asset body without writing").hook("preAction", (thisCommand) => {
836
+ helpers.syncRootOptsFromCommand(thisCommand);
837
+ }).action(
838
+ async (id, opts) => {
839
+ const token = getRequiredToken();
840
+ const cost = parseOptionalNumber(opts.cost, "--cost", exitWithError);
841
+ const acquisitionDate = parseOptionalNumber(
842
+ opts.acquisitionDate,
843
+ "--acquisition-date",
844
+ exitWithError
845
+ );
846
+ const res = await purchaseScout({
847
+ scoutId: id,
848
+ token,
849
+ baseUrl: getBaseUrl2(),
850
+ assetName: opts.name,
851
+ cost,
852
+ acquisitionDate,
853
+ dryRun: Boolean(opts.dryRun),
854
+ request: apiRequest
855
+ });
856
+ if (!res.ok) {
857
+ exitWithError(res.status || 500, res.error ?? "Purchase workflow failed");
858
+ }
859
+ printOutput(getOutputFormat(), res.data ?? {}, false);
860
+ }
861
+ );
862
+ }
863
+ function registerRelationCommands(program, helpers, getRequiredToken) {
864
+ const { getOutputFormat, exitWithError, getBaseUrl: getBaseUrl2 } = helpers;
865
+ const configs = {
866
+ scouts: { relations: ["projects", "spaces", "labels"], relate: true },
867
+ assets: { relations: ["projects", "spaces", "labels"] },
868
+ projects: { relations: ["spaces", "scouts", "assets"] },
869
+ spaces: { relations: ["scouts", "assets"] }
870
+ };
871
+ const singularToPlural = {
872
+ project: "projects",
873
+ projects: "projects",
874
+ space: "spaces",
875
+ spaces: "spaces",
876
+ label: "labels",
877
+ labels: "labels",
878
+ scout: "scouts",
879
+ scouts: "scouts",
880
+ asset: "assets",
881
+ assets: "assets"
882
+ };
883
+ function relationFor(resource, raw) {
884
+ const relation = singularToPlural[raw];
885
+ if (!relation || !configs[resource]?.relations.includes(relation)) {
886
+ exitWithError(400, `Invalid relation for ${resource}: ${raw}`);
887
+ }
888
+ return relation;
889
+ }
890
+ function idsFromOption(raw) {
891
+ if (!raw) return [];
892
+ return [...new Set(raw.split(",").map((id) => id.trim()).filter(Boolean))];
893
+ }
894
+ for (const [resource, config] of Object.entries(configs)) {
895
+ const cmd = program.commands.find((command) => command.name() === resource);
896
+ if (!cmd) continue;
897
+ cmd.command("relations <id>").description(`List ${resource} relations`).hook("preAction", (thisCommand) => {
898
+ helpers.syncRootOptsFromCommand(thisCommand);
899
+ }).action(async (id) => {
900
+ const token = getRequiredToken();
901
+ const res = await apiRequest({
902
+ method: "GET",
903
+ path: `${resource}/${encodeURIComponent(id)}/relations`,
904
+ token,
905
+ baseUrl: getBaseUrl2()
906
+ });
907
+ if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
908
+ printOutput(getOutputFormat(), res.data?.data ?? res.data ?? {}, false);
909
+ });
910
+ cmd.command("link <relation> <id> <targetId>").description(`Add a ${resource} relation`).hook("preAction", (thisCommand) => {
911
+ helpers.syncRootOptsFromCommand(thisCommand);
912
+ }).action(async (rawRelation, id, targetId) => {
913
+ const token = getRequiredToken();
914
+ const relation = relationFor(resource, rawRelation);
915
+ const res = await apiRequest({
916
+ method: "POST",
917
+ path: `${resource}/${encodeURIComponent(id)}/${relation}/${encodeURIComponent(targetId)}`,
918
+ token,
919
+ baseUrl: getBaseUrl2()
920
+ });
921
+ if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
922
+ printOutput(getOutputFormat(), res.data?.data ?? res.data ?? {}, false);
923
+ });
924
+ cmd.command("unlink <relation> <id> <targetId>").description(`Remove a ${resource} relation`).hook("preAction", (thisCommand) => {
925
+ helpers.syncRootOptsFromCommand(thisCommand);
926
+ }).action(async (rawRelation, id, targetId) => {
927
+ const token = getRequiredToken();
928
+ const relation = relationFor(resource, rawRelation);
929
+ const res = await apiRequest({
930
+ method: "DELETE",
931
+ path: `${resource}/${encodeURIComponent(id)}/${relation}/${encodeURIComponent(targetId)}`,
932
+ token,
933
+ baseUrl: getBaseUrl2()
934
+ });
935
+ if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
936
+ printOutput(getOutputFormat(), res.data?.data ?? res.data ?? {}, false);
937
+ });
938
+ for (const relation of config.relations) {
939
+ cmd.command(`set-${relation} <id>`).description(`Replace ${resource} ${relation} links`).requiredOption("--ids <ids>", "Comma-separated target ids").hook("preAction", (thisCommand) => {
940
+ helpers.syncRootOptsFromCommand(thisCommand);
941
+ }).action(async (id, opts) => {
942
+ const token = getRequiredToken();
943
+ const res = await apiRequest({
944
+ method: "PUT",
945
+ path: `${resource}/${encodeURIComponent(id)}/${relation}`,
946
+ token,
947
+ baseUrl: getBaseUrl2(),
948
+ body: { ids: idsFromOption(opts.ids) }
949
+ });
950
+ if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
951
+ printOutput(getOutputFormat(), res.data?.data ?? res.data ?? {}, false);
952
+ });
953
+ }
954
+ if (resource === "scouts" && config.relate) {
955
+ cmd.command("relate <id> <relatedId>").description("Add a typed scout relationship").requiredOption("--type <type>", "parent_of|sub_scout_of|blocking|blocked_by|related_to|duplicate_of").hook("preAction", (thisCommand) => {
956
+ helpers.syncRootOptsFromCommand(thisCommand);
957
+ }).action(async (id, relatedId, opts) => {
958
+ const token = getRequiredToken();
959
+ const res = await apiRequest({
960
+ method: "POST",
961
+ path: `scouts/${encodeURIComponent(id)}/relationships`,
962
+ token,
963
+ baseUrl: getBaseUrl2(),
964
+ body: { relatedScoutId: relatedId, type: opts.type }
965
+ });
966
+ if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
967
+ printOutput(getOutputFormat(), res.data?.data ?? res.data ?? {}, false);
968
+ });
969
+ cmd.command("unrelate <id> <linkId>").description("Remove a typed scout relationship by link id").hook("preAction", (thisCommand) => {
970
+ helpers.syncRootOptsFromCommand(thisCommand);
971
+ }).action(async (id, linkId) => {
972
+ const token = getRequiredToken();
973
+ const res = await apiRequest({
974
+ method: "DELETE",
975
+ path: `scouts/${encodeURIComponent(id)}/relationships/${encodeURIComponent(linkId)}`,
976
+ token,
977
+ baseUrl: getBaseUrl2()
978
+ });
979
+ if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
980
+ printOutput(getOutputFormat(), res.data?.data ?? res.data ?? {}, false);
981
+ });
982
+ }
983
+ }
984
+ }
985
+ function scopeFromOptions(opts) {
986
+ return {
987
+ ...opts.projectId ? { projectId: opts.projectId } : {},
988
+ ...opts.spaceId ? { spaceId: opts.spaceId } : {}
989
+ };
990
+ }
991
+ async function fetchSourcesByScout(scouts, token, baseUrl) {
992
+ const rows = scouts.filter(isRecord4);
993
+ const entries = await Promise.all(
994
+ rows.map(async (scout) => {
995
+ const id = typeof scout._id === "string" ? scout._id : void 0;
996
+ if (!id) return null;
997
+ const res = await apiRequest({
998
+ method: "GET",
999
+ path: `scouts/${encodeURIComponent(id)}/sources`,
1000
+ token,
1001
+ baseUrl
1002
+ });
1003
+ return [id, res.ok ? res.data?.data ?? [] : []];
1004
+ })
1005
+ );
1006
+ return Object.fromEntries(entries.filter((entry) => entry !== null));
1007
+ }
1008
+ function isRecord4(value) {
1009
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1010
+ }
1011
+ function parseOptionalNumber(value, flag, exitWithError) {
1012
+ if (value == null || value === "") return void 0;
1013
+ const parsed = Number(value);
1014
+ if (!Number.isFinite(parsed)) {
1015
+ exitWithError(400, `${flag} expected number, got ${JSON.stringify(value)}`);
1016
+ }
1017
+ return parsed;
1018
+ }
1019
+ function registerSourceCommands(program, helpers) {
1020
+ const { getOutputFormat, requireToken, exitWithError, getBaseUrl: getBaseUrl2 } = helpers;
1021
+ function getRequiredToken() {
1022
+ const token = requireToken();
1023
+ if (!token) exitWithError(401, "Missing or invalid token. Run: pilfer login");
1024
+ return token;
1025
+ }
1026
+ const sources = program.command("sources").description("Manage scout sources");
1027
+ sources.command("list").description("List sources for a scout").requiredOption("--scout-id <id>", "Scout id").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) => {
1028
+ helpers.syncRootOptsFromCommand(thisCommand);
1029
+ }).action(async (opts) => {
1030
+ const token = getRequiredToken();
1031
+ const res = await fetchList({
1032
+ path: `scouts/${encodeURIComponent(opts.scoutId)}/sources`,
1033
+ token,
1034
+ baseUrl: getBaseUrl2(),
1035
+ query: { limit: opts.limit, offset: opts.offset },
1036
+ all: Boolean(opts.all),
1037
+ request: apiRequest
1038
+ });
1039
+ if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
1040
+ const data = res.data?.data ?? [];
1041
+ printList({
1042
+ format: getOutputFormat(),
1043
+ data,
1044
+ fields: opts.fields,
1045
+ where: opts.where,
1046
+ jsonl: Boolean(opts.jsonl)
1047
+ });
1048
+ });
1049
+ sources.command("get <sourceId>").description("Get one source by id").requiredOption("--scout-id <id>", "Scout id").hook("preAction", (thisCommand) => {
1050
+ helpers.syncRootOptsFromCommand(thisCommand);
1051
+ }).action(async (sourceId, opts) => {
1052
+ const token = getRequiredToken();
1053
+ const res = await apiRequest({
1054
+ method: "GET",
1055
+ path: `scouts/${encodeURIComponent(opts.scoutId)}/sources/${encodeURIComponent(
1056
+ sourceId
1057
+ )}`,
1058
+ token,
1059
+ baseUrl: getBaseUrl2()
1060
+ });
1061
+ if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
1062
+ const data = res.data?.data ?? res.data;
1063
+ printOutput(getOutputFormat(), data ?? {}, false);
1064
+ });
1065
+ sources.command("create").description("Create a source for a scout").requiredOption("--scout-id <id>", "Scout id").option("--url <value>", "url").option("--name <value>", "name").option("--title <value>", "deprecated alias for --name").option("--description <value>", "description").option("--price <value>", "price").option("--image <value>", "image").option("--image-url <value>", "deprecated alias for --image").option("--address <value>", "address").option("--source-type <value>", "sourceType").option("--condition <value>", "condition").option("--notes <value>", "notes").option("--starred <value>", "starred").option("--body <json>", "Request body JSON (merged with flags)").hook("preAction", (thisCommand) => {
1066
+ helpers.syncRootOptsFromCommand(thisCommand);
1067
+ }).action(async (opts) => {
1068
+ const token = getRequiredToken();
1069
+ const res = await apiRequest({
1070
+ method: "POST",
1071
+ path: `scouts/${encodeURIComponent(opts.scoutId)}/sources`,
1072
+ token,
1073
+ baseUrl: getBaseUrl2(),
1074
+ body: sourceBodyFromFlags(opts, false, exitWithError)
1075
+ });
1076
+ if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
1077
+ const data = res.data?.data ?? res.data;
1078
+ printOutput(getOutputFormat(), data ?? {}, false);
1079
+ });
1080
+ sources.command("update <sourceId>").description("Update a source").requiredOption("--scout-id <id>", "Scout id").option("--url <value>", "url").option("--name <value>", "name").option("--title <value>", "deprecated alias for --name").option("--description <value>", "description").option("--price <value>", "price").option("--image <value>", "image").option("--image-url <value>", "deprecated alias for --image").option("--address <value>", "address").option("--source-type <value>", "sourceType").option("--condition <value>", "condition").option("--notes <value>", "notes").option("--starred <value>", "starred").option("--body <json>", "Request body JSON (merged with flags)").hook("preAction", (thisCommand) => {
1081
+ helpers.syncRootOptsFromCommand(thisCommand);
1082
+ }).action(async (sourceId, opts) => {
1083
+ const token = getRequiredToken();
1084
+ const res = await apiRequest({
1085
+ method: "PATCH",
1086
+ path: `scouts/${encodeURIComponent(opts.scoutId)}/sources/${encodeURIComponent(
1087
+ sourceId
1088
+ )}`,
1089
+ token,
1090
+ baseUrl: getBaseUrl2(),
1091
+ body: sourceBodyFromFlags(opts, true, exitWithError)
1092
+ });
1093
+ if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
1094
+ const data = res.data?.data ?? res.data;
1095
+ printOutput(getOutputFormat(), data ?? {}, false);
1096
+ });
1097
+ sources.command("delete <sourceId>").description("Delete a source").requiredOption("--scout-id <id>", "Scout id").hook("preAction", (thisCommand) => {
1098
+ helpers.syncRootOptsFromCommand(thisCommand);
1099
+ }).action(async (sourceId, opts) => {
1100
+ const token = getRequiredToken();
1101
+ const res = await apiRequest({
1102
+ method: "DELETE",
1103
+ path: `scouts/${encodeURIComponent(opts.scoutId)}/sources/${encodeURIComponent(
1104
+ sourceId
1105
+ )}`,
1106
+ token,
1107
+ baseUrl: getBaseUrl2()
1108
+ });
1109
+ if (!res.ok) exitWithError(res.status || 500, res.error ?? "Request failed");
1110
+ if (getOutputFormat() === "json") {
1111
+ console.log(JSON.stringify({ data: null, deleted: true }));
1112
+ }
1113
+ });
1114
+ }
1115
+ async function createProgram(helpers) {
1116
+ const program = new Command();
1117
+ program.name("pilfer").description("Pilfer REST API CLI").version(CLI_VERSION).option("-t, --token <token>", "API token (overrides env and config file)").option("-o, --output <format>", "Output format: json | table | csv").option("--base-url <url>", "API base URL (default: https://api.getpilfer.com)").option("--api <url>", "Alias for --base-url").hook("preAction", (thisCommand) => {
1118
+ syncRootOptsFromCommand(thisCommand);
1119
+ const opts = thisCommand.opts();
1120
+ if (opts.output == null || opts.output === "") {
1121
+ thisCommand.setOptionValue("output", getDefaultOutputFormat());
1122
+ }
1123
+ });
1124
+ const resolvedHelpers = helpers ?? DUMMY_HELPERS;
1125
+ addTopLevelCommands(program, resolvedHelpers);
1126
+ await registerGeneratedCommands(program, resolvedHelpers);
1127
+ registerWorkflowCommands(program, resolvedHelpers);
1128
+ registerSourceCommands(program, resolvedHelpers);
1129
+ return program;
1130
+ }
1131
+ async function createProgramForCLI() {
1132
+ const realHelpers = {
1133
+ getOutputFormat: () => {
1134
+ const raw = rootOpts.output;
1135
+ if (raw === "json" || raw === "table" || raw === "csv") return raw;
1136
+ return getDefaultOutputFormat();
1137
+ },
1138
+ requireToken: () => resolveToken(rootOpts.token ?? void 0),
1139
+ exitWithError: (status, message) => {
1140
+ const raw = rootOpts.output;
1141
+ const format = raw === "json" || raw === "table" || raw === "csv" ? raw : getDefaultOutputFormat();
1142
+ const payload = formatErrorPayload(status, message);
1143
+ printStructuredError(format, payload);
1144
+ process.exit(status === 401 || status === 403 ? 1 : 2);
1145
+ },
1146
+ getBaseUrl: () => getBaseUrl(rootOpts.baseUrl ?? rootOpts.api),
1147
+ syncRootOptsFromCommand
1148
+ };
1149
+ return createProgram(realHelpers);
1150
+ }
1151
+ async function getProgramForDocs() {
1152
+ return createProgram();
1153
+ }
1154
+
1155
+ export {
1156
+ syncRootOptsFromCommand,
1157
+ createProgram,
1158
+ createProgramForCLI,
1159
+ getProgramForDocs
1160
+ };
1161
+ //# sourceMappingURL=chunk-NFFHTNJE.js.map