@chainpatrol/cli 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/{breakdown-5DJMXRK4.js → breakdown-2HAZ2MX5.js} +1 -1
  3. package/dist/{check-5P3EXVSN.js → check-5LJI7N5I.js} +1 -1
  4. package/dist/{chunk-OFUIYMPD.js → chunk-322AVMUH.js} +4 -0
  5. package/dist/{chunk-MM5OAY2U.js → chunk-55B34HJK.js} +1 -1
  6. package/dist/{chunk-PEX2A3IL.js → chunk-FPCN4SNK.js} +1 -1
  7. package/dist/{chunk-6VTWWGNV.js → chunk-P5DJRDMV.js} +11 -1
  8. package/dist/cli.js +114 -41
  9. package/dist/{completions-ILC76OX5.js → completions-FEUVEC5N.js} +1 -1
  10. package/dist/{configs-update-TAYT4JTU.js → configs-update-N7ZIWWKQ.js} +1 -1
  11. package/dist/{create-UXRULUR7.js → create-4ZJ7DXHX.js} +1 -1
  12. package/dist/{drift-TCZELVHO.js → drift-BGWNH6QE.js} +1 -1
  13. package/dist/{found-NCP6H2SD.js → found-QOARB5G2.js} +1 -1
  14. package/dist/{get-SM5KGJVA.js → get-ON5JOCV7.js} +1 -1
  15. package/dist/{healthcheck-5YEZCTXC.js → healthcheck-34LQTSVZ.js} +1 -1
  16. package/dist/{list-2RHEVI2T.js → list-6CAKS43I.js} +1 -1
  17. package/dist/{list-WQBXC3QS.js → list-AG6LWNMN.js} +1 -1
  18. package/dist/{list-W6JZUNS2.js → list-DJFIKDY6.js} +1 -1
  19. package/dist/{list-MPSB2DYU.js → list-H6QWWXOU.js} +1 -1
  20. package/dist/{list-4YBWU2JZ.js → list-IVFZGLKN.js} +1 -1
  21. package/dist/{list-23C72EH4.js → list-JG5ARDTR.js} +2 -2
  22. package/dist/{list-BAPTZNTM.js → list-MHOHKFXT.js} +1 -1
  23. package/dist/{list-TA4HYD66.js → list-PAJBOVN2.js} +1 -1
  24. package/dist/{list-QLMFMYU6.js → list-SJFJT4V3.js} +1 -1
  25. package/dist/{list-SCA73PLA.js → list-UPY6PHY4.js} +1 -1
  26. package/dist/{list-GEJQ6N2Y.js → list-UXYGISAK.js} +1 -1
  27. package/dist/{list-QJYRXPXM.js → list-W6CF25ZS.js} +1 -1
  28. package/dist/{list-json-NJ5IFRDE.js → list-json-ZR3XOQPJ.js} +1 -1
  29. package/dist/{organization-N6RMWKJM.js → organization-YAID36GZ.js} +1 -1
  30. package/dist/review-7A7WV73T.js +270 -0
  31. package/dist/{run-RRG7D4SN.js → run-AIYPHWFJ.js} +1 -1
  32. package/dist/{run-KOZXEYTL.js → run-LZZOV4HG.js} +1 -1
  33. package/dist/{run-TUADYLU7.js → run-YP4O5VXZ.js} +2 -2
  34. package/dist/{search-M6EKPO4D.js → search-OGGKNPUJ.js} +1 -1
  35. package/dist/{search-Y37B2JVP.js → search-W5QM2IOC.js} +1 -1
  36. package/dist/{setup-skill-RRTDEOR4.js → setup-skill-2KCAXU2U.js} +2 -2
  37. package/dist/{snapshot-RFGSAHO7.js → snapshot-TLSOPK7C.js} +1 -1
  38. package/dist/{summary-XCN3DRYP.js → summary-XA3PVJIW.js} +1 -1
  39. package/dist/{validate-OYTNB2ZM.js → validate-WB5367QN.js} +1 -1
  40. package/dist/{whoami-5WCW3FOZ.js → whoami-YE3V6I65.js} +1 -1
  41. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # @chainpatrol/cli
2
2
 
3
+ ## 1.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 9b15056: Adds `chainpatrol proposals review` for actioning proposals from the terminal.
8
+
9
+ ```
10
+ chainpatrol proposals review 4321 --asset-id 987 --decision approve --label "General Phishing"
11
+ chainpatrol proposals review 4321 --asset-id 987 --decision reject --reject-reason irrelevant
12
+ chainpatrol proposals review 4321 --asset-id 987 --decision watchlist --watchlist-reason DEAD
13
+ ```
14
+
15
+ Take both IDs from `proposals[].id` and `proposals[].asset.id` in
16
+ `reports list --needs-customer-review --json`. `--asset-id` is required and must
17
+ match the proposal's current asset.
18
+
19
+ A review cannot be undone, so the command prints the decision it is about to
20
+ submit and waits for `yes`. `--yes` skips the prompt and is required with
21
+ `--json`, `--no-input`, or a non-TTY stdin. `--dry-run` prints the payload and
22
+ submits nothing.
23
+
24
+ `--label` is required when approving, plus `--brand-id` for the labels that name
25
+ an impersonation target; `--reject-reason` when rejecting; `--watchlist-reason`
26
+ when watchlisting; and `--note` when that reason is `OTHER`. Decisions and
27
+ reasons are case-insensitive.
28
+
29
+ ### Patch Changes
30
+
31
+ - Updated dependencies [a253313]
32
+ - Updated dependencies [108f9f8]
33
+ - @chainpatrol/sdk@1.6.0
34
+
3
35
  ## 1.5.0
4
36
 
5
37
  ### Minor Changes
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-VFT3TD3E.js";
8
8
  import {
9
9
  createApiClient
10
- } from "./chunk-OFUIYMPD.js";
10
+ } from "./chunk-322AVMUH.js";
11
11
  import "./chunk-TFCNKBRC.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-U73SABXK.js";
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-OFUIYMPD.js";
11
+ } from "./chunk-322AVMUH.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-U73SABXK.js";
14
14
 
@@ -26,6 +26,8 @@ import {
26
26
  LIVENESS_FILTER_VALUES,
27
27
  LIVENESS_STATUSES,
28
28
  ORGANIZATION_METRICS_INCLUDE_FIELDS,
29
+ PROPOSAL_REJECT_REASONS,
30
+ PROPOSAL_WATCHLIST_REASONS,
29
31
  REPORT_REVIEW_DECISIONS,
30
32
  REPORT_STATUSES,
31
33
  TAKEDOWN_SORT_KEYS,
@@ -56,6 +58,8 @@ export {
56
58
  LIVENESS_FILTER_VALUES,
57
59
  LIVENESS_STATUSES,
58
60
  ORGANIZATION_METRICS_INCLUDE_FIELDS,
61
+ PROPOSAL_REJECT_REASONS,
62
+ PROPOSAL_WATCHLIST_REASONS,
59
63
  REPORT_REVIEW_DECISIONS,
60
64
  REPORT_STATUSES,
61
65
  TAKEDOWN_SORT_KEYS,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  installCompletions,
3
3
  uninstallCompletions
4
- } from "./chunk-6VTWWGNV.js";
4
+ } from "./chunk-P5DJRDMV.js";
5
5
 
6
6
  // src/commands/setup-skill.ts
7
7
  import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, readFileSync as readFileSync3, rmSync } from "fs";
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-OFUIYMPD.js";
11
+ } from "./chunk-322AVMUH.js";
12
12
  import {
13
13
  DateTime
14
14
  } from "./chunk-TFCNKBRC.js";
@@ -22,6 +22,7 @@ _chainpatrol() {
22
22
  'detections:Validate, run, list, update detection configs and results'
23
23
  'metrics:Query organization metrics'
24
24
  'reports:Create, list, search reports from terminal'
25
+ 'proposals:Review proposals waiting on your organization'
25
26
  'queues:Snapshot operations queues'
26
27
  'takedowns:List individual takedown records'
27
28
  'threats:List confirmed threats blocked by ChainPatrol'
@@ -106,6 +107,11 @@ _chainpatrol() {
106
107
  )
107
108
  _describe 'subcommand' reports_subcommands
108
109
  ;;
110
+ proposals)
111
+ local -a proposals_subcommands
112
+ proposals_subcommands=('review:Approve, reject, or watchlist a proposal')
113
+ _describe 'subcommand' proposals_subcommands
114
+ ;;
109
115
  queues)
110
116
  local -a queues_subcommands
111
117
  queues_subcommands=('snapshot:Show queue snapshot')
@@ -154,7 +160,7 @@ var BASH_COMPLETION = `_chainpatrol() {
154
160
  COMPREPLY=()
155
161
  cur="\${COMP_WORDS[COMP_CWORD]}"
156
162
  prev="\${COMP_WORDS[COMP_CWORD-1]}"
157
- commands="login logout whoami asset configs detections metrics reports queues takedowns threats orgs presets setup uninstall completions help"
163
+ commands="login logout whoami asset configs detections metrics reports proposals queues takedowns threats orgs presets setup uninstall completions help"
158
164
 
159
165
  case "\${prev}" in
160
166
  chainpatrol)
@@ -181,6 +187,10 @@ var BASH_COMPLETION = `_chainpatrol() {
181
187
  COMPREPLY=( $(compgen -W "create list search" -- "\${cur}") )
182
188
  return 0
183
189
  ;;
190
+ proposals)
191
+ COMPREPLY=( $(compgen -W "review" -- "\${cur}") )
192
+ return 0
193
+ ;;
184
194
  queues)
185
195
  COMPREPLY=( $(compgen -W "snapshot" -- "\${cur}") )
186
196
  return 0
package/dist/cli.js CHANGED
@@ -13,8 +13,8 @@ import {
13
13
  getCliVersion,
14
14
  isSkillInstalled,
15
15
  readInstalledSkillVersion
16
- } from "./chunk-MM5OAY2U.js";
17
- import "./chunk-6VTWWGNV.js";
16
+ } from "./chunk-55B34HJK.js";
17
+ import "./chunk-P5DJRDMV.js";
18
18
  import {
19
19
  DateTime
20
20
  } from "./chunk-TFCNKBRC.js";
@@ -426,6 +426,39 @@ var HELP = {
426
426
  "chainpatrol reports list --org acme --needs-customer-review"
427
427
  ]
428
428
  },
429
+ proposals: {
430
+ description: "Review the proposals waiting on your organization.",
431
+ usage: "chainpatrol proposals review <proposalId> [options]",
432
+ examples: [
433
+ "chainpatrol reports list --org acme --needs-customer-review",
434
+ 'chainpatrol proposals review 4321 --asset-id 987 --decision approve --label "General Phishing"',
435
+ "chainpatrol proposals review 4321 --asset-id 987 --decision reject --reject-reason irrelevant"
436
+ ]
437
+ },
438
+ "proposals review": {
439
+ description: "Approve, reject, or watchlist a proposal that is waiting on your organization's review \u2014 the same queue the Review page shows and `reports list --needs-customer-review` returns. Find the proposal and asset IDs in that listing's `proposals[].id` and `proposals[].asset.id`. A review cannot be undone over the API, so the command asks for confirmation unless you pass --yes, and it is capped at 10 reviews per organization per day across all of the org's API credentials.",
440
+ usage: "chainpatrol proposals review <proposalId> --asset-id <id> --decision <approve|reject|watchlist> [options]",
441
+ options: [
442
+ "--proposal-id <id> Proposal to review (alternative to the positional arg)",
443
+ "--asset-id <id> Asset the proposal targets; must match, or the API returns 409",
444
+ "--decision <d> approve|reject|watchlist (watchlist rejects and parks the asset)",
445
+ '--label <text> Block label, required when approving. Quote it: e.g. "General Phishing", "Brand Impersonation", "Employee Impersonation", "Fake Employee", "Targeting Org Users", "C2 Server", "False Positive"',
446
+ "--brand-id <id> Brand the asset impersonates; required for Brand/Employee Impersonation",
447
+ "--reject-reason <r> Required when rejecting: irrelevant|no_malicious_activity|insufficient_evidence|not_targeting_org|decayed",
448
+ "--watchlist-reason <r> Required when watchlisting: DEAD|PARKING|NOT_ENOUGH_EVIDENCE|OTHER",
449
+ "--note <text> Free-text detail; required when --watchlist-reason OTHER",
450
+ "--org <slug> Organization slug (optional for org-scoped API keys)",
451
+ "--yes Skip the confirmation prompt (required when not on a TTY)",
452
+ "--dry-run Preview the payload without submitting"
453
+ ],
454
+ examples: [
455
+ 'chainpatrol proposals review 4321 --asset-id 987 --decision approve --label "General Phishing" --yes',
456
+ 'chainpatrol proposals review 4321 --asset-id 987 --decision approve --label "Brand Impersonation" --brand-id 12 --yes',
457
+ "chainpatrol proposals review 4321 --asset-id 987 --decision reject --reject-reason no_malicious_activity --yes",
458
+ 'chainpatrol proposals review 4321 --asset-id 987 --decision watchlist --watchlist-reason OTHER --note "Registrar suspended it" --yes',
459
+ "chainpatrol proposals review 4321 --asset-id 987 --decision reject --reject-reason decayed --dry-run"
460
+ ]
461
+ },
429
462
  queues: {
430
463
  description: "Snapshot operations review/takedown queues.",
431
464
  usage: "chainpatrol queues snapshot [--org <slug>|--all]",
@@ -681,6 +714,7 @@ function getTopLevelHelp() {
681
714
  " healthchecks List and run organization healthchecks",
682
715
  " metrics Query organization metrics and breakdowns",
683
716
  " reports Create, list, and search reports from terminal",
717
+ " proposals Review proposals waiting on your organization",
684
718
  " queues Snapshot operations review/takedown queues",
685
719
  " takedowns List individual takedown records",
686
720
  " threats List confirmed threats blocked by ChainPatrol",
@@ -854,6 +888,7 @@ var COMMANDS = [
854
888
  "healthchecks",
855
889
  "metrics",
856
890
  "reports",
891
+ "proposals",
857
892
  "queues",
858
893
  "takedowns",
859
894
  "threats",
@@ -936,6 +971,15 @@ ${getTopLevelHelp()}
936
971
  search: { type: "string" },
937
972
  reportedByCustomer: { type: "boolean" },
938
973
  needsCustomerReview: { type: "boolean" },
974
+ proposalId: { type: "number" },
975
+ assetId: { type: "number" },
976
+ decision: { type: "string" },
977
+ label: { type: "string" },
978
+ brandId: { type: "number" },
979
+ rejectReason: { type: "string" },
980
+ watchlistReason: { type: "string" },
981
+ note: { type: "string" },
982
+ yes: { type: "boolean", default: false },
939
983
  attachmentUrl: { type: "string" },
940
984
  contactInfo: { type: "string" },
941
985
  externalSubmissionLink: { type: "string" },
@@ -1115,12 +1159,12 @@ function parseAttachmentUrls() {
1115
1159
  }
1116
1160
  async function handleConfigsList(org) {
1117
1161
  if (jsonMode) {
1118
- const { listConfigsJson } = await import("./list-json-NJ5IFRDE.js");
1162
+ const { listConfigsJson } = await import("./list-json-ZR3XOQPJ.js");
1119
1163
  await listConfigsJson({ org });
1120
1164
  return;
1121
1165
  }
1122
1166
  const { render } = await import("ink");
1123
- const { default: ConfigsList } = await import("./list-2RHEVI2T.js");
1167
+ const { default: ConfigsList } = await import("./list-6CAKS43I.js");
1124
1168
  const { default: React } = await import("react");
1125
1169
  render(React.createElement(ConfigsList, { org }));
1126
1170
  }
@@ -1159,7 +1203,7 @@ async function main() {
1159
1203
  return;
1160
1204
  }
1161
1205
  if (cliContext.dryRun) {
1162
- const dryRunSupported = command === "reports" && subcommand === "create" || command === "detections" && subcommand === "run" || command === "detections" && subcommand === "configs" && action === "run" || command === "detections" && subcommand === "configs" && action === "update";
1206
+ const dryRunSupported = command === "reports" && subcommand === "create" || command === "proposals" && subcommand === "review" || command === "detections" && subcommand === "run" || command === "detections" && subcommand === "configs" && action === "run" || command === "detections" && subcommand === "configs" && action === "update";
1163
1207
  if (!dryRunSupported) {
1164
1208
  throw new CliExitError(
1165
1209
  "--dry-run is only supported for mutation commands.",
@@ -1220,7 +1264,7 @@ async function main() {
1220
1264
  const positionals = cli.input.slice(2);
1221
1265
  const flagAssets = parseAssetInputs();
1222
1266
  const contents = [...positionals, ...flagAssets];
1223
- const { runAssetCheck } = await import("./check-5P3EXVSN.js");
1267
+ const { runAssetCheck } = await import("./check-5LJI7N5I.js");
1224
1268
  await runAssetCheck({
1225
1269
  contents,
1226
1270
  json: jsonMode,
@@ -1230,7 +1274,7 @@ async function main() {
1230
1274
  break;
1231
1275
  }
1232
1276
  if (subcommand === "list") {
1233
- const { runAssetList } = await import("./list-GEJQ6N2Y.js");
1277
+ const { runAssetList } = await import("./list-UXYGISAK.js");
1234
1278
  await runAssetList({
1235
1279
  type: cli.flags.type ?? cli.flags.assetType,
1236
1280
  status: cli.flags.assetStatus ?? cli.flags.status,
@@ -1255,7 +1299,7 @@ async function main() {
1255
1299
  }
1256
1300
  return n;
1257
1301
  });
1258
- const { runAssetSearch } = await import("./search-Y37B2JVP.js");
1302
+ const { runAssetSearch } = await import("./search-W5QM2IOC.js");
1259
1303
  await runAssetSearch({
1260
1304
  contents: contents.length > 0 ? contents : void 0,
1261
1305
  ids: ids.length > 0 ? ids : void 0,
@@ -1280,7 +1324,7 @@ async function main() {
1280
1324
  case "detections": {
1281
1325
  const org = await resolveOrg();
1282
1326
  if (subcommand === "healthcheck") {
1283
- const { runDetectionsHealthcheck } = await import("./healthcheck-5YEZCTXC.js");
1327
+ const { runDetectionsHealthcheck } = await import("./healthcheck-34LQTSVZ.js");
1284
1328
  await runDetectionsHealthcheck({
1285
1329
  org,
1286
1330
  source: cli.flags.source,
@@ -1295,7 +1339,7 @@ async function main() {
1295
1339
  break;
1296
1340
  }
1297
1341
  if (subcommand === "validate") {
1298
- const { runDetectionsValidate } = await import("./validate-OYTNB2ZM.js");
1342
+ const { runDetectionsValidate } = await import("./validate-WB5367QN.js");
1299
1343
  await runDetectionsValidate({
1300
1344
  org,
1301
1345
  source: cli.flags.source,
@@ -1310,7 +1354,7 @@ async function main() {
1310
1354
  break;
1311
1355
  }
1312
1356
  if (subcommand === "drift") {
1313
- const { runDetectionsDrift } = await import("./drift-TCZELVHO.js");
1357
+ const { runDetectionsDrift } = await import("./drift-BGWNH6QE.js");
1314
1358
  await runDetectionsDrift({
1315
1359
  org,
1316
1360
  source: cli.flags.source,
@@ -1324,7 +1368,7 @@ async function main() {
1324
1368
  break;
1325
1369
  }
1326
1370
  if (subcommand === "run") {
1327
- const { runDetectionsRun } = await import("./run-KOZXEYTL.js");
1371
+ const { runDetectionsRun } = await import("./run-LZZOV4HG.js");
1328
1372
  await runDetectionsRun({
1329
1373
  org,
1330
1374
  configId: cli.flags.configId,
@@ -1338,7 +1382,7 @@ async function main() {
1338
1382
  break;
1339
1383
  }
1340
1384
  if (subcommand === "list") {
1341
- const { runDetectionsList } = await import("./list-W6JZUNS2.js");
1385
+ const { runDetectionsList } = await import("./list-DJFIKDY6.js");
1342
1386
  await runDetectionsList({
1343
1387
  org,
1344
1388
  cursor: cli.flags.cursor,
@@ -1367,7 +1411,7 @@ async function main() {
1367
1411
  break;
1368
1412
  }
1369
1413
  if (action === "run") {
1370
- const { runDetectionsRun } = await import("./run-KOZXEYTL.js");
1414
+ const { runDetectionsRun } = await import("./run-LZZOV4HG.js");
1371
1415
  await runDetectionsRun({
1372
1416
  org,
1373
1417
  configId: cli.flags.configId,
@@ -1385,7 +1429,7 @@ async function main() {
1385
1429
  throw new Error("detections configs update requires --config-id");
1386
1430
  }
1387
1431
  const configPatch = getConfigPatchFromSetFlags();
1388
- const { runDetectionsConfigsUpdate } = await import("./configs-update-TAYT4JTU.js");
1432
+ const { runDetectionsConfigsUpdate } = await import("./configs-update-N7ZIWWKQ.js");
1389
1433
  await runDetectionsConfigsUpdate({
1390
1434
  org,
1391
1435
  configId: cli.flags.configId,
@@ -1421,7 +1465,7 @@ async function main() {
1421
1465
  const slugs = parseCsvList(cli.flags.slugs);
1422
1466
  const allMyOrgs = cli.flags.allMyOrgs;
1423
1467
  const org2 = slugs && slugs.length > 0 || allMyOrgs ? void 0 : await tryResolveOrg();
1424
- const { runMetricsOrganization } = await import("./organization-N6RMWKJM.js");
1468
+ const { runMetricsOrganization } = await import("./organization-YAID36GZ.js");
1425
1469
  await runMetricsOrganization({
1426
1470
  org: org2,
1427
1471
  slugs,
@@ -1440,7 +1484,7 @@ async function main() {
1440
1484
  }
1441
1485
  const org = await resolveOrg();
1442
1486
  if (subcommand === "summary") {
1443
- const { runMetricsSummary } = await import("./summary-XCN3DRYP.js");
1487
+ const { runMetricsSummary } = await import("./summary-XA3PVJIW.js");
1444
1488
  await runMetricsSummary({
1445
1489
  org,
1446
1490
  from: cli.flags.from,
@@ -1453,7 +1497,7 @@ async function main() {
1453
1497
  break;
1454
1498
  }
1455
1499
  if (subcommand === "found") {
1456
- const { runMetricsFound } = await import("./found-NCP6H2SD.js");
1500
+ const { runMetricsFound } = await import("./found-QOARB5G2.js");
1457
1501
  await runMetricsFound({
1458
1502
  org,
1459
1503
  from: cli.flags.from,
@@ -1470,7 +1514,7 @@ async function main() {
1470
1514
  if (!by || !["day", "type", "brand"].includes(by)) {
1471
1515
  throw new Error("metrics breakdown requires --by <day|type|brand>");
1472
1516
  }
1473
- const { runMetricsBreakdown } = await import("./breakdown-5DJMXRK4.js");
1517
+ const { runMetricsBreakdown } = await import("./breakdown-2HAZ2MX5.js");
1474
1518
  await runMetricsBreakdown({
1475
1519
  org,
1476
1520
  by,
@@ -1491,7 +1535,7 @@ async function main() {
1491
1535
  case "reports": {
1492
1536
  if (subcommand === "list") {
1493
1537
  const org = await resolveOrg();
1494
- const { runReportsList } = await import("./list-WQBXC3QS.js");
1538
+ const { runReportsList } = await import("./list-AG6LWNMN.js");
1495
1539
  await runReportsList({
1496
1540
  org,
1497
1541
  limit: cli.flags.limit,
@@ -1522,7 +1566,7 @@ async function main() {
1522
1566
  }
1523
1567
  if (subcommand === "create") {
1524
1568
  const org = await tryResolveOrg();
1525
- const { runReportsCreate } = await import("./create-UXRULUR7.js");
1569
+ const { runReportsCreate } = await import("./create-4ZJ7DXHX.js");
1526
1570
  await runReportsCreate({
1527
1571
  org,
1528
1572
  title: cli.flags.title,
@@ -1544,7 +1588,7 @@ async function main() {
1544
1588
  const positionals = cli.input.slice(2);
1545
1589
  const flagAssets = parseAssetInputs();
1546
1590
  const assetContents = [...positionals, ...flagAssets];
1547
- const { runReportsSearch } = await import("./search-M6EKPO4D.js");
1591
+ const { runReportsSearch } = await import("./search-OGGKNPUJ.js");
1548
1592
  await runReportsSearch({
1549
1593
  assetContents,
1550
1594
  org,
@@ -1560,9 +1604,38 @@ async function main() {
1560
1604
  subcommand ? `Unknown subcommand: reports ${subcommand}${hint ? `. Did you mean "reports ${hint}"?` : ""}` : "Usage: chainpatrol reports <create|list|search>"
1561
1605
  );
1562
1606
  }
1607
+ case "proposals": {
1608
+ if (subcommand === "review") {
1609
+ const org = await tryResolveOrg();
1610
+ const positionalProposalId = cli.input[2];
1611
+ const { runProposalsReview } = await import("./review-7A7WV73T.js");
1612
+ await runProposalsReview({
1613
+ org,
1614
+ proposalId: cli.flags.proposalId ?? (positionalProposalId ? Number(positionalProposalId) : void 0),
1615
+ assetId: cli.flags.assetId,
1616
+ decision: cli.flags.decision,
1617
+ label: cli.flags.label,
1618
+ brandId: cli.flags.brandId,
1619
+ rejectReason: cli.flags.rejectReason,
1620
+ watchlistReason: cli.flags.watchlistReason,
1621
+ note: cli.flags.note,
1622
+ yes: cli.flags.yes,
1623
+ noInput: cli.flags.noInput,
1624
+ json: jsonMode,
1625
+ outputFormat: cliContext.outputFormat,
1626
+ explain: cliContext.explain,
1627
+ dryRun: cliContext.dryRun
1628
+ });
1629
+ break;
1630
+ }
1631
+ const hint = subcommand ? suggest(subcommand, ["review"]) : null;
1632
+ throw new Error(
1633
+ subcommand ? `Unknown subcommand: proposals ${subcommand}${hint ? `. Did you mean "proposals ${hint}"?` : ""}` : "Usage: chainpatrol proposals review <proposalId> --asset-id <id> --decision <approve|reject|watchlist>"
1634
+ );
1635
+ }
1563
1636
  case "threats": {
1564
1637
  if (subcommand === "list") {
1565
- const { runThreatsList } = await import("./list-SCA73PLA.js");
1638
+ const { runThreatsList } = await import("./list-UPY6PHY4.js");
1566
1639
  await runThreatsList({
1567
1640
  query: cli.flags.query,
1568
1641
  from: cli.flags.from,
@@ -1585,7 +1658,7 @@ async function main() {
1585
1658
  case "takedowns": {
1586
1659
  if (subcommand === "list") {
1587
1660
  const org = await tryResolveOrg();
1588
- const { runTakedownsList } = await import("./list-TA4HYD66.js");
1661
+ const { runTakedownsList } = await import("./list-PAJBOVN2.js");
1589
1662
  await runTakedownsList({
1590
1663
  org,
1591
1664
  query: cli.flags.query,
@@ -1616,7 +1689,7 @@ async function main() {
1616
1689
  }
1617
1690
  case "queues": {
1618
1691
  if (subcommand === "snapshot") {
1619
- const { runQueuesSnapshot } = await import("./snapshot-RFGSAHO7.js");
1692
+ const { runQueuesSnapshot } = await import("./snapshot-TLSOPK7C.js");
1620
1693
  await runQueuesSnapshot({
1621
1694
  org: cli.flags.org,
1622
1695
  all: cli.flags.all,
@@ -1634,7 +1707,7 @@ async function main() {
1634
1707
  }
1635
1708
  case "orgs": {
1636
1709
  if (subcommand === "list") {
1637
- const { runOrgsList } = await import("./list-BAPTZNTM.js");
1710
+ const { runOrgsList } = await import("./list-MHOHKFXT.js");
1638
1711
  await runOrgsList({
1639
1712
  query: cli.flags.query,
1640
1713
  subscriptionStatus: cli.flags.subscriptionStatus,
@@ -1656,7 +1729,7 @@ async function main() {
1656
1729
  if (subcommand === "get") {
1657
1730
  const positionalSlug = action;
1658
1731
  const slug = positionalSlug ?? await tryResolveOrg();
1659
- const { runOrgsGet } = await import("./get-SM5KGJVA.js");
1732
+ const { runOrgsGet } = await import("./get-ON5JOCV7.js");
1660
1733
  await runOrgsGet({
1661
1734
  slug,
1662
1735
  json: jsonMode,
@@ -1666,7 +1739,7 @@ async function main() {
1666
1739
  }
1667
1740
  if (subcommand === "assets") {
1668
1741
  if (action === "list") {
1669
- const { runOrgsAssetsList } = await import("./list-MPSB2DYU.js");
1742
+ const { runOrgsAssetsList } = await import("./list-H6QWWXOU.js");
1670
1743
  await runOrgsAssetsList({
1671
1744
  type: cli.flags.type ?? cli.flags.assetType,
1672
1745
  groupId: cli.flags.groupId,
@@ -1682,7 +1755,7 @@ async function main() {
1682
1755
  }
1683
1756
  if (subcommand === "asset-groups") {
1684
1757
  if (action === "list") {
1685
- const { runOrgsAssetGroupsList } = await import("./list-QLMFMYU6.js");
1758
+ const { runOrgsAssetGroupsList } = await import("./list-SJFJT4V3.js");
1686
1759
  await runOrgsAssetGroupsList({
1687
1760
  json: jsonMode,
1688
1761
  outputFormat: cliContext.outputFormat
@@ -1699,12 +1772,12 @@ async function main() {
1699
1772
  case "brands": {
1700
1773
  if (subcommand === "list") {
1701
1774
  const rawType = cli.flags.type?.toUpperCase();
1702
- if (rawType && rawType !== "ORGANIZATION" && rawType !== "INDIVIDUAL") {
1775
+ if (rawType && rawType !== "ORGANIZATION" && rawType !== "INDIVIDUAL" && rawType !== "PRODUCT") {
1703
1776
  throw new Error(
1704
- "brands list --type must be ORGANIZATION or INDIVIDUAL (case-insensitive)."
1777
+ "brands list --type must be ORGANIZATION, INDIVIDUAL, or PRODUCT (case-insensitive)."
1705
1778
  );
1706
1779
  }
1707
- const { runBrandsList } = await import("./list-4YBWU2JZ.js");
1780
+ const { runBrandsList } = await import("./list-IVFZGLKN.js");
1708
1781
  await runBrandsList({
1709
1782
  type: rawType,
1710
1783
  json: jsonMode,
@@ -1714,11 +1787,11 @@ async function main() {
1714
1787
  }
1715
1788
  const hint = subcommand ? suggest(subcommand, ["list"]) : null;
1716
1789
  throw new Error(
1717
- subcommand ? `Unknown subcommand: brands ${subcommand}${hint ? `. Did you mean "brands ${hint}"?` : ""}` : "Usage: chainpatrol brands list [--type ORGANIZATION|INDIVIDUAL]"
1790
+ subcommand ? `Unknown subcommand: brands ${subcommand}${hint ? `. Did you mean "brands ${hint}"?` : ""}` : "Usage: chainpatrol brands list [--type ORGANIZATION|INDIVIDUAL|PRODUCT]"
1718
1791
  );
1719
1792
  }
1720
1793
  case "whoami": {
1721
- const { runWhoami } = await import("./whoami-5WCW3FOZ.js");
1794
+ const { runWhoami } = await import("./whoami-YE3V6I65.js");
1722
1795
  await runWhoami({
1723
1796
  json: jsonMode,
1724
1797
  outputFormat: cliContext.outputFormat
@@ -1727,7 +1800,7 @@ async function main() {
1727
1800
  }
1728
1801
  case "healthchecks": {
1729
1802
  if (subcommand === "list") {
1730
- const { runHealthchecksList } = await import("./list-QJYRXPXM.js");
1803
+ const { runHealthchecksList } = await import("./list-W6CF25ZS.js");
1731
1804
  await runHealthchecksList({
1732
1805
  json: jsonMode,
1733
1806
  outputFormat: cliContext.outputFormat
@@ -1741,7 +1814,7 @@ async function main() {
1741
1814
  thresholds.minResults = cli.flags.minResults;
1742
1815
  if (cli.flags.lookbackHours !== void 0)
1743
1816
  thresholds.lookbackHours = cli.flags.lookbackHours;
1744
- const { runHealthchecksRun } = await import("./run-RRG7D4SN.js");
1817
+ const { runHealthchecksRun } = await import("./run-AIYPHWFJ.js");
1745
1818
  await runHealthchecksRun({
1746
1819
  org,
1747
1820
  id: action,
@@ -1759,7 +1832,7 @@ async function main() {
1759
1832
  }
1760
1833
  case "presets": {
1761
1834
  if (subcommand === "list") {
1762
- const { runPresetsList } = await import("./list-23C72EH4.js");
1835
+ const { runPresetsList } = await import("./list-JG5ARDTR.js");
1763
1836
  await runPresetsList({ outputFormat: cliContext.outputFormat });
1764
1837
  break;
1765
1838
  }
@@ -1770,7 +1843,7 @@ async function main() {
1770
1843
  );
1771
1844
  }
1772
1845
  const org = await resolveOrg();
1773
- const { runPresetsRun } = await import("./run-TUADYLU7.js");
1846
+ const { runPresetsRun } = await import("./run-YP4O5VXZ.js");
1774
1847
  await runPresetsRun({
1775
1848
  presetId: action,
1776
1849
  org,
@@ -1787,17 +1860,17 @@ async function main() {
1787
1860
  case "setup":
1788
1861
  case "install":
1789
1862
  case "i": {
1790
- const { setupSkill } = await import("./setup-skill-RRTDEOR4.js");
1863
+ const { setupSkill } = await import("./setup-skill-2KCAXU2U.js");
1791
1864
  setupSkill({ json: jsonMode, cloud: cli.flags.cloud });
1792
1865
  break;
1793
1866
  }
1794
1867
  case "uninstall": {
1795
- const { uninstallSkill } = await import("./setup-skill-RRTDEOR4.js");
1868
+ const { uninstallSkill } = await import("./setup-skill-2KCAXU2U.js");
1796
1869
  uninstallSkill({ json: jsonMode });
1797
1870
  break;
1798
1871
  }
1799
1872
  case "completions": {
1800
- const { printCompletions } = await import("./completions-ILC76OX5.js");
1873
+ const { printCompletions } = await import("./completions-FEUVEC5N.js");
1801
1874
  printCompletions(subcommand);
1802
1875
  break;
1803
1876
  }
@@ -3,7 +3,7 @@ import {
3
3
  installCompletions,
4
4
  printCompletions,
5
5
  uninstallCompletions
6
- } from "./chunk-6VTWWGNV.js";
6
+ } from "./chunk-P5DJRDMV.js";
7
7
  export {
8
8
  getCompletionScript,
9
9
  installCompletions,
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-VFT3TD3E.js";
8
8
  import {
9
9
  createApiClient
10
- } from "./chunk-OFUIYMPD.js";
10
+ } from "./chunk-322AVMUH.js";
11
11
  import "./chunk-EGWK6SRQ.js";
12
12
  import "./chunk-U73SABXK.js";
13
13
 
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-OFUIYMPD.js";
11
+ } from "./chunk-322AVMUH.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-U73SABXK.js";
14
14
 
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-OFUIYMPD.js";
11
+ } from "./chunk-322AVMUH.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-U73SABXK.js";
14
14
 
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-VFT3TD3E.js";
8
8
  import {
9
9
  createApiClient
10
- } from "./chunk-OFUIYMPD.js";
10
+ } from "./chunk-322AVMUH.js";
11
11
  import "./chunk-TFCNKBRC.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-U73SABXK.js";
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-OFUIYMPD.js";
11
+ } from "./chunk-322AVMUH.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-U73SABXK.js";
14
14
 
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-OFUIYMPD.js";
11
+ } from "./chunk-322AVMUH.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-U73SABXK.js";
14
14
 
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-JCMWDZYY.js";
5
5
  import {
6
6
  createApiClient
7
- } from "./chunk-OFUIYMPD.js";
7
+ } from "./chunk-322AVMUH.js";
8
8
  import {
9
9
  AuthCorruptedError,
10
10
  AuthExpiredError,
@@ -10,7 +10,7 @@ import {
10
10
  REPORT_REVIEW_DECISIONS,
11
11
  REPORT_STATUSES,
12
12
  createApiClient
13
- } from "./chunk-OFUIYMPD.js";
13
+ } from "./chunk-322AVMUH.js";
14
14
  import "./chunk-EGWK6SRQ.js";
15
15
  import "./chunk-U73SABXK.js";
16
16
 
@@ -12,7 +12,7 @@ import {
12
12
  LIVENESS_FILTER_VALUES,
13
13
  WATCHLIST_FILTER_VALUES,
14
14
  createApiClient
15
- } from "./chunk-OFUIYMPD.js";
15
+ } from "./chunk-322AVMUH.js";
16
16
  import "./chunk-EGWK6SRQ.js";
17
17
  import "./chunk-U73SABXK.js";
18
18
 
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-OFUIYMPD.js";
11
+ } from "./chunk-322AVMUH.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-U73SABXK.js";
14
14
 
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-VFT3TD3E.js";
5
5
  import {
6
6
  createApiClient
7
- } from "./chunk-OFUIYMPD.js";
7
+ } from "./chunk-322AVMUH.js";
8
8
  import "./chunk-EGWK6SRQ.js";
9
9
  import "./chunk-U73SABXK.js";
10
10
 
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  PRESETS
3
- } from "./chunk-PEX2A3IL.js";
3
+ } from "./chunk-FPCN4SNK.js";
4
4
  import "./chunk-E2LAMILJ.js";
5
5
  import {
6
6
  printOutput,
7
7
  toCsvRows
8
8
  } from "./chunk-VFT3TD3E.js";
9
- import "./chunk-OFUIYMPD.js";
9
+ import "./chunk-322AVMUH.js";
10
10
  import "./chunk-TFCNKBRC.js";
11
11
  import "./chunk-EGWK6SRQ.js";
12
12
  import "./chunk-U73SABXK.js";
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-OFUIYMPD.js";
11
+ } from "./chunk-322AVMUH.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-U73SABXK.js";
14
14
 
@@ -11,7 +11,7 @@ import {
11
11
  TAKEDOWN_SORT_KEYS,
12
12
  TAKEDOWN_STATUSES,
13
13
  createApiClient
14
- } from "./chunk-OFUIYMPD.js";
14
+ } from "./chunk-322AVMUH.js";
15
15
  import "./chunk-EGWK6SRQ.js";
16
16
  import "./chunk-U73SABXK.js";
17
17
 
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-VFT3TD3E.js";
5
5
  import {
6
6
  createApiClient
7
- } from "./chunk-OFUIYMPD.js";
7
+ } from "./chunk-322AVMUH.js";
8
8
  import "./chunk-EGWK6SRQ.js";
9
9
  import "./chunk-U73SABXK.js";
10
10
 
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-OFUIYMPD.js";
11
+ } from "./chunk-322AVMUH.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-U73SABXK.js";
14
14
 
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-OFUIYMPD.js";
11
+ } from "./chunk-322AVMUH.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-U73SABXK.js";
14
14
 
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-VFT3TD3E.js";
5
5
  import {
6
6
  createApiClient
7
- } from "./chunk-OFUIYMPD.js";
7
+ } from "./chunk-322AVMUH.js";
8
8
  import "./chunk-EGWK6SRQ.js";
9
9
  import "./chunk-U73SABXK.js";
10
10
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createApiClient
3
- } from "./chunk-OFUIYMPD.js";
3
+ } from "./chunk-322AVMUH.js";
4
4
  import "./chunk-EGWK6SRQ.js";
5
5
  import "./chunk-U73SABXK.js";
6
6
 
@@ -8,7 +8,7 @@ import {
8
8
  import {
9
9
  ORGANIZATION_METRICS_INCLUDE_FIELDS,
10
10
  createApiClient
11
- } from "./chunk-OFUIYMPD.js";
11
+ } from "./chunk-322AVMUH.js";
12
12
  import "./chunk-TFCNKBRC.js";
13
13
  import "./chunk-EGWK6SRQ.js";
14
14
  import "./chunk-U73SABXK.js";
@@ -0,0 +1,270 @@
1
+ import {
2
+ CliExitError,
3
+ ExitCode
4
+ } from "./chunk-E2LAMILJ.js";
5
+ import {
6
+ printOutput,
7
+ toCsvRows
8
+ } from "./chunk-VFT3TD3E.js";
9
+ import {
10
+ PROPOSAL_REJECT_REASONS,
11
+ PROPOSAL_WATCHLIST_REASONS,
12
+ createApiClient
13
+ } from "./chunk-322AVMUH.js";
14
+ import "./chunk-EGWK6SRQ.js";
15
+ import "./chunk-U73SABXK.js";
16
+
17
+ // src/commands/proposals/review.ts
18
+ var BRAND_REQUIRED_LABELS = ["brand impersonation", "employee impersonation"];
19
+ var DECISIONS = ["APPROVE", "REJECT", "WATCHLIST"];
20
+ function parsePositiveId(value, flag) {
21
+ if (value === void 0) {
22
+ throw new CliExitError(`proposals review requires ${flag} <id>.`, ExitCode.USAGE);
23
+ }
24
+ if (!Number.isInteger(value) || value < 1) {
25
+ throw new CliExitError(`${flag} must be a positive integer.`, ExitCode.USAGE);
26
+ }
27
+ return value;
28
+ }
29
+ function parseDecision(value) {
30
+ if (!value) {
31
+ throw new CliExitError(
32
+ "proposals review requires --decision <approve|reject|watchlist>.",
33
+ ExitCode.USAGE
34
+ );
35
+ }
36
+ const normalized = value.trim().toUpperCase();
37
+ if (!DECISIONS.includes(normalized)) {
38
+ throw new CliExitError(
39
+ `Invalid --decision '${value}'. Valid: approve, reject, watchlist.`,
40
+ ExitCode.USAGE
41
+ );
42
+ }
43
+ return normalized;
44
+ }
45
+ function parseRejectReason(value) {
46
+ if (!value) {
47
+ throw new CliExitError(
48
+ `--reject-reason is required when --decision reject. Valid: ${PROPOSAL_REJECT_REASONS.join(
49
+ ", "
50
+ )}`,
51
+ ExitCode.USAGE
52
+ );
53
+ }
54
+ const normalized = value.trim().toLowerCase();
55
+ if (!PROPOSAL_REJECT_REASONS.includes(normalized)) {
56
+ throw new CliExitError(
57
+ `Invalid --reject-reason '${value}'. Valid: ${PROPOSAL_REJECT_REASONS.join(", ")}`,
58
+ ExitCode.USAGE
59
+ );
60
+ }
61
+ return normalized;
62
+ }
63
+ function parseWatchlistReason(value) {
64
+ if (!value) {
65
+ throw new CliExitError(
66
+ `--watchlist-reason is required when --decision watchlist. Valid: ${PROPOSAL_WATCHLIST_REASONS.join(
67
+ ", "
68
+ )}`,
69
+ ExitCode.USAGE
70
+ );
71
+ }
72
+ const normalized = value.trim().toUpperCase();
73
+ if (!PROPOSAL_WATCHLIST_REASONS.includes(normalized)) {
74
+ throw new CliExitError(
75
+ `Invalid --watchlist-reason '${value}'. Valid: ${PROPOSAL_WATCHLIST_REASONS.join(
76
+ ", "
77
+ )}`,
78
+ ExitCode.USAGE
79
+ );
80
+ }
81
+ return normalized;
82
+ }
83
+ function buildReviewInput(options) {
84
+ const proposalId = parsePositiveId(options.proposalId, "--proposal-id");
85
+ const assetId = parsePositiveId(options.assetId, "--asset-id");
86
+ const decision = parseDecision(options.decision);
87
+ const input = {
88
+ proposalId,
89
+ assetId,
90
+ slug: options.org,
91
+ decision
92
+ };
93
+ if (decision === "APPROVE") {
94
+ const label = options.label?.trim();
95
+ if (!label) {
96
+ throw new CliExitError(
97
+ "--label is required when --decision approve.",
98
+ ExitCode.USAGE
99
+ );
100
+ }
101
+ if (BRAND_REQUIRED_LABELS.includes(label.toLowerCase()) && options.brandId === void 0) {
102
+ throw new CliExitError(
103
+ `--brand-id is required when --label is '${label}'. List your brands with \`chainpatrol brands list\`.`,
104
+ ExitCode.USAGE
105
+ );
106
+ }
107
+ input.label = label;
108
+ if (options.brandId !== void 0) {
109
+ input.brandId = parsePositiveId(options.brandId, "--brand-id");
110
+ }
111
+ }
112
+ if (decision === "REJECT") {
113
+ input.rejectReason = parseRejectReason(options.rejectReason);
114
+ }
115
+ if (decision === "WATCHLIST") {
116
+ const watchlistReason = parseWatchlistReason(options.watchlistReason);
117
+ if (watchlistReason === "OTHER" && !options.note?.trim()) {
118
+ throw new CliExitError(
119
+ "--note is required when --watchlist-reason OTHER, since the note becomes the reason.",
120
+ ExitCode.USAGE
121
+ );
122
+ }
123
+ input.watchlistReason = watchlistReason;
124
+ }
125
+ const note = options.note?.trim();
126
+ if (note) {
127
+ input.note = note;
128
+ }
129
+ return input;
130
+ }
131
+ function describeReview(input) {
132
+ const lines = [
133
+ `Decision: ${input.decision}`,
134
+ `Proposal: #${input.proposalId}`,
135
+ `Asset: #${input.assetId}`,
136
+ `Organization: ${input.slug ?? "from credential context"}`
137
+ ];
138
+ if (input.label) {
139
+ lines.push(`Label: ${input.label}`);
140
+ }
141
+ if (input.brandId !== void 0) {
142
+ lines.push(`Brand ID: ${input.brandId}`);
143
+ }
144
+ if (input.rejectReason) {
145
+ lines.push(`Reject reason: ${input.rejectReason}`);
146
+ }
147
+ if (input.watchlistReason) {
148
+ lines.push(`Watchlist reason: ${input.watchlistReason}`);
149
+ }
150
+ if (input.note) {
151
+ lines.push(`Note: ${input.note}`);
152
+ }
153
+ return lines;
154
+ }
155
+ async function promptForConfirmation(summary) {
156
+ const { createInterface } = await import("readline/promises");
157
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
158
+ try {
159
+ const answer = await rl.question(`${summary}
160
+ Type "yes" to continue: `);
161
+ const normalized = answer.trim().toLowerCase();
162
+ return normalized === "y" || normalized === "yes";
163
+ } finally {
164
+ rl.close();
165
+ }
166
+ }
167
+ async function runProposalsReview(options) {
168
+ const input = buildReviewInput(options);
169
+ const outputFormat = options.outputFormat ?? (options.json ? "json" : "human");
170
+ const details = describeReview(input);
171
+ if (options.dryRun) {
172
+ printOutput({
173
+ outputFormat,
174
+ json: {
175
+ dryRun: true,
176
+ mutation: "organization.proposals.review",
177
+ payload: input,
178
+ explanation: options.explain ? "This command records a customer review on the proposal and, for APPROVE, blocks the asset." : void 0
179
+ },
180
+ markdown: ["# Dry Run: Proposal Review", "", ...details.map((d) => `- ${d}`)].join(
181
+ "\n"
182
+ ),
183
+ csv: toCsvRows([
184
+ {
185
+ proposalId: input.proposalId,
186
+ assetId: input.assetId,
187
+ decision: input.decision,
188
+ label: input.label ?? "",
189
+ rejectReason: input.rejectReason ?? "",
190
+ watchlistReason: input.watchlistReason ?? ""
191
+ }
192
+ ]),
193
+ human: () => {
194
+ console.log("Dry run only. No review was submitted.");
195
+ for (const line of details) {
196
+ console.log(line);
197
+ }
198
+ }
199
+ });
200
+ return;
201
+ }
202
+ if (!options.yes) {
203
+ const summary = [
204
+ "About to submit a review that cannot be undone over the API:",
205
+ ...details.map((line) => ` ${line}`)
206
+ ].join("\n");
207
+ if (options.confirm) {
208
+ if (!await options.confirm(summary)) {
209
+ console.log("Aborted. No review was submitted.");
210
+ return;
211
+ }
212
+ } else if (options.noInput || outputFormat !== "human" || !process.stdin.isTTY) {
213
+ throw new CliExitError(
214
+ "proposals review cannot be undone and needs confirmation. Re-run with --yes, or run it in an interactive terminal.",
215
+ ExitCode.USAGE
216
+ );
217
+ } else if (!await promptForConfirmation(summary)) {
218
+ console.log("Aborted. No review was submitted.");
219
+ return;
220
+ }
221
+ }
222
+ const client = options.apiClient ?? createApiClient();
223
+ const result = await client.reviewOrganizationProposal(input);
224
+ const row = {
225
+ proposalId: result.proposal.id,
226
+ reviewStatus: result.proposal.reviewStatus,
227
+ assetId: result.proposal.asset.id,
228
+ assetType: result.proposal.asset.type,
229
+ assetContent: result.proposal.asset.content,
230
+ assetStatus: result.proposal.asset.status,
231
+ reviewId: result.review.id,
232
+ decision: result.review.decision,
233
+ label: result.review.label,
234
+ watchlisted: result.watchlisted
235
+ };
236
+ printOutput({
237
+ outputFormat,
238
+ json: {
239
+ ...result,
240
+ explanation: options.explain ? "A WATCHLIST request is recorded as a REJECT review with the asset parked for re-evaluation." : void 0
241
+ },
242
+ markdown: [
243
+ "# Proposal Reviewed",
244
+ "",
245
+ `- Proposal: #${row.proposalId} (${row.reviewStatus})`,
246
+ `- Asset: #${row.assetId} ${row.assetType} ${row.assetContent} \u2192 ${row.assetStatus}`,
247
+ `- Review: #${row.reviewId} ${row.decision}${row.label ? ` (${row.label})` : ""}`,
248
+ `- Watchlisted: ${row.watchlisted ? "yes" : "no"}`
249
+ ].join("\n"),
250
+ csv: toCsvRows([row]),
251
+ human: () => {
252
+ console.log(`Reviewed proposal #${row.proposalId} as ${row.decision}`);
253
+ console.log(`Asset: ${row.assetType} ${row.assetContent} \u2192 ${row.assetStatus}`);
254
+ console.log(`Review: #${row.reviewId}${row.label ? ` label=${row.label}` : ""}`);
255
+ if (row.watchlisted) {
256
+ console.log("Asset parked on the watchlist for re-evaluation.");
257
+ }
258
+ if (options.explain) {
259
+ console.log(
260
+ "Reviews over the API are limited to 10 per organization per day and are shared across all of the org's API credentials."
261
+ );
262
+ }
263
+ }
264
+ });
265
+ }
266
+ export {
267
+ buildReviewInput,
268
+ describeReview,
269
+ runProposalsReview
270
+ };
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-OFUIYMPD.js";
11
+ } from "./chunk-322AVMUH.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-U73SABXK.js";
14
14
 
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-OFUIYMPD.js";
11
+ } from "./chunk-322AVMUH.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-U73SABXK.js";
14
14
 
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  getPresetDefinition,
3
3
  runPreset
4
- } from "./chunk-PEX2A3IL.js";
4
+ } from "./chunk-FPCN4SNK.js";
5
5
  import {
6
6
  CliExitError,
7
7
  ExitCode
8
8
  } from "./chunk-E2LAMILJ.js";
9
9
  import "./chunk-VFT3TD3E.js";
10
- import "./chunk-OFUIYMPD.js";
10
+ import "./chunk-322AVMUH.js";
11
11
  import "./chunk-TFCNKBRC.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-U73SABXK.js";
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-OFUIYMPD.js";
11
+ } from "./chunk-322AVMUH.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-U73SABXK.js";
14
14
 
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-OFUIYMPD.js";
11
+ } from "./chunk-322AVMUH.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-U73SABXK.js";
14
14
 
@@ -6,8 +6,8 @@ import {
6
6
  readInstalledSkillVersion,
7
7
  setupSkill,
8
8
  uninstallSkill
9
- } from "./chunk-MM5OAY2U.js";
10
- import "./chunk-6VTWWGNV.js";
9
+ } from "./chunk-55B34HJK.js";
10
+ import "./chunk-P5DJRDMV.js";
11
11
  export {
12
12
  getBundledSkillContent,
13
13
  getBundledSkillVersion,
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-OFUIYMPD.js";
11
+ } from "./chunk-322AVMUH.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-U73SABXK.js";
14
14
 
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-VFT3TD3E.js";
8
8
  import {
9
9
  createApiClient
10
- } from "./chunk-OFUIYMPD.js";
10
+ } from "./chunk-322AVMUH.js";
11
11
  import "./chunk-TFCNKBRC.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-U73SABXK.js";
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-VFT3TD3E.js";
9
9
  import {
10
10
  createApiClient
11
- } from "./chunk-OFUIYMPD.js";
11
+ } from "./chunk-322AVMUH.js";
12
12
  import "./chunk-EGWK6SRQ.js";
13
13
  import "./chunk-U73SABXK.js";
14
14
 
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-VFT3TD3E.js";
4
4
  import {
5
5
  createApiClient
6
- } from "./chunk-OFUIYMPD.js";
6
+ } from "./chunk-322AVMUH.js";
7
7
  import "./chunk-EGWK6SRQ.js";
8
8
  import "./chunk-U73SABXK.js";
9
9
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@chainpatrol/cli",
3
3
  "description": "The official ChainPatrol CLI — terminal interface for threat detection",
4
4
  "author": "Umar Ahmed <umar@chainpatrol.io>",
5
- "version": "1.5.0",
5
+ "version": "1.6.0",
6
6
  "license": "UNLICENSED",
7
7
  "homepage": "https://chainpatrol.com/docs/cli",
8
8
  "keywords": [
@@ -33,7 +33,7 @@
33
33
  "lint": "npx oxlint ."
34
34
  },
35
35
  "dependencies": {
36
- "@chainpatrol/sdk": "1.5.0",
36
+ "@chainpatrol/sdk": "1.6.0",
37
37
  "ink": "^7.0.1",
38
38
  "meow": "^14.1.0",
39
39
  "open": "^11.0.0",