@zksecurity/zkao-sdk 0.1.0 → 0.5.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.
package/dist/index.d.ts CHANGED
@@ -73,6 +73,50 @@ interface paths {
73
73
  patch?: never;
74
74
  trace?: never;
75
75
  };
76
+ "/projects/{projectId}/repositories/{repositoryId}/audit-areas": {
77
+ parameters: {
78
+ query?: never;
79
+ header?: never;
80
+ path?: never;
81
+ cookie?: never;
82
+ };
83
+ /**
84
+ * List a repository's audit areas
85
+ * @description Requires scope: `read`. Audit areas are the named subsystems a scan can be scoped to (`auditAreaKeys` on launch). Each scan that maps the repository keeps the list current; sizes come from one branch's map, so an area that map no longer names is returned without one.
86
+ */
87
+ get: operations["listAuditAreas"];
88
+ put?: never;
89
+ /**
90
+ * Add a custom audit area
91
+ * @description Requires scope: `guidance:write`. An area is a name and a description, never a file list: a scan resolves it against the code at the commit it runs on. The `key` is derived from the name and disambiguated on collision.
92
+ */
93
+ post: operations["createAuditArea"];
94
+ delete?: never;
95
+ options?: never;
96
+ head?: never;
97
+ patch?: never;
98
+ trace?: never;
99
+ };
100
+ "/projects/{projectId}/repositories/{repositoryId}/audit-areas/{areaKey}": {
101
+ parameters: {
102
+ query?: never;
103
+ header?: never;
104
+ path?: never;
105
+ cookie?: never;
106
+ };
107
+ get?: never;
108
+ put?: never;
109
+ post?: never;
110
+ /**
111
+ * Delete a custom audit area
112
+ * @description Requires scope: `guidance:write`. Only an area with `source: custom` can be deleted; a discovered one is the map's own account of the repository and is refused with `409`. Scans that already ran keep the scope they ran with.
113
+ */
114
+ delete: operations["deleteAuditArea"];
115
+ options?: never;
116
+ head?: never;
117
+ patch?: never;
118
+ trace?: never;
119
+ };
76
120
  "/projects/{projectId}/scans": {
77
121
  parameters: {
78
122
  query?: never;
@@ -91,7 +135,12 @@ interface paths {
91
135
  * @description Requires scope: `scans:launch`. Reserves `creditBudget` credits from the
92
136
  * project balance. The repo must be in the token's allowlist (when set) and
93
137
  * ACTIVE. Discover valid `presetRef` / `optInFlowIds` via the discovery
94
- * endpoints. Per-token spend limits and the project plan are enforced.
138
+ * endpoints. Per-token spend limits are enforced, and the project must have
139
+ * enough prepaid credits to cover the budget (otherwise `402`).
140
+ *
141
+ * A repository added moments ago may still be analyzing, in which case
142
+ * this returns `409 repository_initializing`. Wait for its `readiness` to
143
+ * be `ready` in `GET /repositories` and launch again.
95
144
  */
96
145
  post: operations["launchScan"];
97
146
  delete?: never;
@@ -329,7 +378,7 @@ interface paths {
329
378
  };
330
379
  /**
331
380
  * List opt-in flows available at launch
332
- * @description Requires scope: `read`. Pass the chosen ids as `optInFlowIds` when launching.
381
+ * @description Requires scope: `read`. Pass the chosen ids as `optInFlowIds` when launching. A flow marked `rotating` may run without being asked for.
333
382
  */
334
383
  get: operations["listOptionalFlows"];
335
384
  put?: never;
@@ -340,13 +389,111 @@ interface paths {
340
389
  patch?: never;
341
390
  trace?: never;
342
391
  };
392
+ "/projects/{projectId}/billing/balance": {
393
+ parameters: {
394
+ query?: never;
395
+ header?: never;
396
+ path?: never;
397
+ cookie?: never;
398
+ };
399
+ /**
400
+ * Current prepaid credit balance
401
+ * @description Requires scope: `read`. All amounts are in credits.
402
+ */
403
+ get: operations["getBillingBalance"];
404
+ put?: never;
405
+ post?: never;
406
+ delete?: never;
407
+ options?: never;
408
+ head?: never;
409
+ patch?: never;
410
+ trace?: never;
411
+ };
412
+ "/projects/{projectId}/billing/usage": {
413
+ parameters: {
414
+ query?: never;
415
+ header?: never;
416
+ path?: never;
417
+ cookie?: never;
418
+ };
419
+ /**
420
+ * Credit usage events
421
+ * @description Requires scope: `read`. Signed credit delta per ledger event. Defaults to the last 30 days; newest first; max 1000 rows.
422
+ */
423
+ get: operations["getBillingUsage"];
424
+ put?: never;
425
+ post?: never;
426
+ delete?: never;
427
+ options?: never;
428
+ head?: never;
429
+ patch?: never;
430
+ trace?: never;
431
+ };
432
+ "/projects/{projectId}/billing/summary": {
433
+ parameters: {
434
+ query?: never;
435
+ header?: never;
436
+ path?: never;
437
+ cookie?: never;
438
+ };
439
+ /**
440
+ * Monthly spend and purchase summary
441
+ * @description Requires scope: `read`. Per-month net scan spend and net purchases, in credits, newest first.
442
+ */
443
+ get: operations["getBillingSummary"];
444
+ put?: never;
445
+ post?: never;
446
+ delete?: never;
447
+ options?: never;
448
+ head?: never;
449
+ patch?: never;
450
+ trace?: never;
451
+ };
343
452
  }
344
453
  interface components {
345
454
  schemas: {
455
+ BillingBalance: {
456
+ /** @description Total prepaid credit balance. */
457
+ balanceCredits: number;
458
+ /** @description Balance minus credits reserved by in-flight scans. */
459
+ availableCredits: number;
460
+ /** @description Credits held by queued/running scans. */
461
+ reservedCredits: number;
462
+ };
463
+ /** @enum {string} */
464
+ UsageEventType: "SCAN_DEDUCTION" | "SCAN_REFUND" | "PURCHASE" | "STRIPE_REFUND" | "ADMIN_ADJUSTMENT";
465
+ UsageEvent: {
466
+ id: string;
467
+ /** Format: date-time */
468
+ at: string;
469
+ type: components["schemas"]["UsageEventType"];
470
+ /** @description Signed credit delta: negative = spent/clawed back, positive = added/refunded. */
471
+ credits: number;
472
+ scanId: string | null;
473
+ description: string;
474
+ };
475
+ BillingUsage: {
476
+ /** Format: date-time */
477
+ from: string;
478
+ /** Format: date-time */
479
+ to: string;
480
+ events: components["schemas"]["UsageEvent"][];
481
+ };
482
+ UsageMonthSummary: {
483
+ /** @description YYYY-MM (UTC). */
484
+ month: string;
485
+ /** @description Net scan spend (deductions less refunds), in credits. */
486
+ spentCredits: number;
487
+ /** @description Net purchases (purchases less clawbacks), in credits. */
488
+ purchasedCredits: number;
489
+ };
490
+ BillingSummary: {
491
+ months: components["schemas"]["UsageMonthSummary"][];
492
+ };
346
493
  Error: {
347
494
  error: {
348
495
  /** @enum {string} */
349
- code: "unauthorized" | "forbidden" | "not_found" | "bad_request" | "conflict" | "plan_inactive" | "rate_limited" | "internal";
496
+ code: "unauthorized" | "forbidden" | "not_found" | "bad_request" | "conflict" | "insufficient_credits" | "repository_initializing" | "rate_limited" | "internal";
350
497
  message: string;
351
498
  };
352
499
  };
@@ -366,6 +513,11 @@ interface components {
366
513
  defaultBranch?: string | null;
367
514
  /** @description ACTIVE | MARKED_FOR_DELETION */
368
515
  status: string;
516
+ /**
517
+ * @description Whether a scan can start on this repository. A newly added repository is `analyzing` until a one-time pass over it finishes; launching a scan meanwhile fails with `repository_initializing`. Poll this endpoint rather than retrying the launch blind.
518
+ * @enum {string}
519
+ */
520
+ readiness: "ready" | "analyzing";
369
521
  };
370
522
  Scan: {
371
523
  id: string;
@@ -380,6 +532,17 @@ interface components {
380
532
  startedAt?: string | null;
381
533
  /** Format: date-time */
382
534
  completedAt?: string | null;
535
+ /** @description Phase progress while the scan is in flight. Null once the scan reaches a terminal state, and null before it has been dispatched (no phases exist yet). */
536
+ progress?: components["schemas"]["ScanProgress"] | null;
537
+ };
538
+ /** @description How far a running scan has got through its phases. Deliberately not a time estimate: a phase's duration moves with the guidance it was given, the repository, and the model that ran it. */
539
+ ScanProgress: {
540
+ /** @description Phases that reached a terminal state (completed, failed, or skipped). */
541
+ phasesCompleted: number;
542
+ /** @description Phases this scan will run, fixed when it was dispatched. */
543
+ phasesTotal: number;
544
+ /** @description Weighted completion. Each phase counts for its share of the scan budget, so this does not simply equal phasesCompleted / phasesTotal. */
545
+ percent: number;
383
546
  };
384
547
  ScanDetail: components["schemas"]["Scan"] & {
385
548
  branch?: string | null;
@@ -457,6 +620,8 @@ interface components {
457
620
  flowId: string;
458
621
  title: string;
459
622
  description: string;
623
+ /** @description True when this flow belongs to a group the preset rotates through. Some of the group runs on every scan without being asked for, and which members depends on the repository. Naming it in `optInFlowIds` adds it whether or not the rotation picked it, and raises the scan's minimum budget. */
624
+ rotating: boolean;
460
625
  };
461
626
  LaunchScanRequest: {
462
627
  repositoryId: string;
@@ -469,8 +634,10 @@ interface components {
469
634
  /** @description Pin a specific commit SHA (7-40 hex chars). */
470
635
  commitHash?: string | null;
471
636
  commitMessage?: string | null;
472
- /** @description Optional-flow ids from /optional-flows. */
637
+ /** @description Optional-flow ids from /optional-flows, added to the scan on top of whatever a rotating group already contributes. */
473
638
  optInFlowIds?: string[];
639
+ /** @description Area keys from /audit-areas to scope this scan to. Omit or send an empty array to scan the whole repository. A key the repository's current map no longer names fails the launch rather than being dropped, so a scan budgeted for one subsystem never silently runs against everything. */
640
+ auditAreaKeys?: string[];
474
641
  /** @description Guidance for this scan only, replacing the repository's configured guidance layer (it is still layered over any committed zkao.md). Omit the field to inherit the repository's guidance; send null to scan with no guidance layer. */
475
642
  guidance?: string | null;
476
643
  };
@@ -501,6 +668,44 @@ interface components {
501
668
  /** @description True when the content already matched (no revision recorded). */
502
669
  unchanged: boolean;
503
670
  };
671
+ AuditArea: {
672
+ /** @description Stable slug, unique per repository. Pass it in `auditAreaKeys` when launching a scan. */
673
+ key: string;
674
+ name: string;
675
+ description: string | null;
676
+ /**
677
+ * @description `discovered` was named by a scan's map of the repository; `custom` was added through this API or the app.
678
+ * @enum {string}
679
+ */
680
+ source: "discovered" | "custom";
681
+ /** @description Whether the branch's latest map still names this area. */
682
+ inLatestMap: boolean;
683
+ /** @description Files this area covers at that map. Null when the map does not name it. */
684
+ files: number | null;
685
+ /** @description Lines this area covers at that map. Null when the map does not name it. */
686
+ lines: number | null;
687
+ };
688
+ AuditAreaList: {
689
+ repositoryId: string;
690
+ /** @description Branch the sizes were read from. */
691
+ branch: string | null;
692
+ /** @description Whether that branch has a stored map at all. */
693
+ mapped: boolean;
694
+ totalFiles: number | null;
695
+ totalLines: number | null;
696
+ /** @description The map's own audit order first, then the areas it no longer names. */
697
+ areas: components["schemas"]["AuditArea"][];
698
+ };
699
+ CreateAuditAreaRequest: {
700
+ name: string;
701
+ /** @description What this part of the code does. */
702
+ description?: string | null;
703
+ };
704
+ DeleteAuditAreaResult: {
705
+ repositoryId: string;
706
+ key: string;
707
+ deleted: boolean;
708
+ };
504
709
  CancelScanResult: {
505
710
  scanId: string;
506
711
  /** @description Terminal status after cancellation (always CANCELLED). */
@@ -532,6 +737,14 @@ interface components {
532
737
  findingId: string;
533
738
  resolutionStatus: components["schemas"]["ResolutionStatus"];
534
739
  };
740
+ /**
741
+ * @description Short code for why a finding is being closed, chosen from the set offered for the `resolutionStatus` it is sent with; a code belonging to a different status is rejected with 400. Ignored for the open statuses (`NOT_STARTED`, `IN_PROGRESS`) and when `note` is also given. It is not stored as a field: it is recorded as a comment on the finding stating what was chosen, which is returned by the notes endpoints.
742
+ *
743
+ * Codes per status. `RESOLVED`: fixed_in_code, fixed_upstream, code_removed. `MITIGATED`: compensating_control, limited_exposure, monitored. `FALSE_POSITIVE`: not_reachable, guarded_elsewhere, intended_behavior, misread_code, bad_assumption. `WONT_FIX`: risk_accepted, out_of_scope, not_worth_fixing, code_being_removed. `DUPLICATE`: duplicate_of_finding, same_root_cause.
744
+ * @example not_reachable
745
+ * @enum {string}
746
+ */
747
+ ResolutionReason: "fixed_in_code" | "fixed_upstream" | "code_removed" | "compensating_control" | "limited_exposure" | "monitored" | "not_reachable" | "guarded_elsewhere" | "intended_behavior" | "misread_code" | "bad_assumption" | "risk_accepted" | "out_of_scope" | "not_worth_fixing" | "code_being_removed" | "duplicate_of_finding" | "same_root_cause";
535
748
  PublishFindingRequest: {
536
749
  /** @description A note id on the finding to pin as the published note. */
537
750
  noteId?: string;
@@ -569,6 +782,24 @@ interface components {
569
782
  "application/json": components["schemas"]["Error"];
570
783
  };
571
784
  };
785
+ /** @description The project does not have enough prepaid credits to cover the scan budget. Top up credits and retry. (code `insufficient_credits`) */
786
+ InsufficientCredits: {
787
+ headers: {
788
+ [name: string]: unknown;
789
+ };
790
+ content: {
791
+ "application/json": components["schemas"]["Error"];
792
+ };
793
+ };
794
+ /** @description The repository is still being analyzed and cannot be scanned yet. The same request succeeds once `GET /repositories` reports its `readiness` as `ready`. (code `repository_initializing`) */
795
+ RepositoryInitializing: {
796
+ headers: {
797
+ [name: string]: unknown;
798
+ };
799
+ content: {
800
+ "application/json": components["schemas"]["Error"];
801
+ };
802
+ };
572
803
  /** @description The token lacks the required scope */
573
804
  Forbidden: {
574
805
  headers: {
@@ -706,6 +937,97 @@ interface operations {
706
937
  409: components["responses"]["Conflict"];
707
938
  };
708
939
  };
940
+ listAuditAreas: {
941
+ parameters: {
942
+ query?: {
943
+ /** @description Branch whose map the sizes come from. Defaults to the repository's default branch. */
944
+ branch?: string;
945
+ };
946
+ header?: never;
947
+ path: {
948
+ projectId: components["parameters"]["ProjectId"];
949
+ repositoryId: components["parameters"]["RepositoryId"];
950
+ };
951
+ cookie?: never;
952
+ };
953
+ requestBody?: never;
954
+ responses: {
955
+ /** @description OK */
956
+ 200: {
957
+ headers: {
958
+ [name: string]: unknown;
959
+ };
960
+ content: {
961
+ "application/json": components["schemas"]["AuditAreaList"];
962
+ };
963
+ };
964
+ 401: components["responses"]["Unauthorized"];
965
+ 403: components["responses"]["Forbidden"];
966
+ 404: components["responses"]["NotFound"];
967
+ };
968
+ };
969
+ createAuditArea: {
970
+ parameters: {
971
+ query?: never;
972
+ header?: never;
973
+ path: {
974
+ projectId: components["parameters"]["ProjectId"];
975
+ repositoryId: components["parameters"]["RepositoryId"];
976
+ };
977
+ cookie?: never;
978
+ };
979
+ requestBody: {
980
+ content: {
981
+ "application/json": components["schemas"]["CreateAuditAreaRequest"];
982
+ };
983
+ };
984
+ responses: {
985
+ /** @description Created */
986
+ 201: {
987
+ headers: {
988
+ [name: string]: unknown;
989
+ };
990
+ content: {
991
+ "application/json": {
992
+ area: components["schemas"]["AuditArea"];
993
+ };
994
+ };
995
+ };
996
+ 400: components["responses"]["BadRequest"];
997
+ 401: components["responses"]["Unauthorized"];
998
+ 403: components["responses"]["Forbidden"];
999
+ 404: components["responses"]["NotFound"];
1000
+ 409: components["responses"]["Conflict"];
1001
+ };
1002
+ };
1003
+ deleteAuditArea: {
1004
+ parameters: {
1005
+ query?: never;
1006
+ header?: never;
1007
+ path: {
1008
+ projectId: components["parameters"]["ProjectId"];
1009
+ repositoryId: components["parameters"]["RepositoryId"];
1010
+ areaKey: string;
1011
+ };
1012
+ cookie?: never;
1013
+ };
1014
+ requestBody?: never;
1015
+ responses: {
1016
+ /** @description OK */
1017
+ 200: {
1018
+ headers: {
1019
+ [name: string]: unknown;
1020
+ };
1021
+ content: {
1022
+ "application/json": components["schemas"]["DeleteAuditAreaResult"];
1023
+ };
1024
+ };
1025
+ 401: components["responses"]["Unauthorized"];
1026
+ 403: components["responses"]["Forbidden"];
1027
+ 404: components["responses"]["NotFound"];
1028
+ 409: components["responses"]["Conflict"];
1029
+ };
1030
+ };
709
1031
  listScans: {
710
1032
  parameters: {
711
1033
  query?: {
@@ -760,8 +1082,10 @@ interface operations {
760
1082
  };
761
1083
  400: components["responses"]["BadRequest"];
762
1084
  401: components["responses"]["Unauthorized"];
1085
+ 402: components["responses"]["InsufficientCredits"];
763
1086
  403: components["responses"]["Forbidden"];
764
1087
  404: components["responses"]["NotFound"];
1088
+ 409: components["responses"]["RepositoryInitializing"];
765
1089
  429: components["responses"]["RateLimited"];
766
1090
  };
767
1091
  };
@@ -1000,6 +1324,7 @@ interface operations {
1000
1324
  "application/json": {
1001
1325
  resolutionStatus: components["schemas"]["ResolutionStatus"];
1002
1326
  note?: components["schemas"]["ChangeNote"];
1327
+ reason?: components["schemas"]["ResolutionReason"];
1003
1328
  };
1004
1329
  };
1005
1330
  };
@@ -1135,6 +1460,88 @@ interface operations {
1135
1460
  404: components["responses"]["NotFound"];
1136
1461
  };
1137
1462
  };
1463
+ getBillingBalance: {
1464
+ parameters: {
1465
+ query?: never;
1466
+ header?: never;
1467
+ path: {
1468
+ projectId: components["parameters"]["ProjectId"];
1469
+ };
1470
+ cookie?: never;
1471
+ };
1472
+ requestBody?: never;
1473
+ responses: {
1474
+ /** @description OK */
1475
+ 200: {
1476
+ headers: {
1477
+ [name: string]: unknown;
1478
+ };
1479
+ content: {
1480
+ "application/json": components["schemas"]["BillingBalance"];
1481
+ };
1482
+ };
1483
+ 401: components["responses"]["Unauthorized"];
1484
+ 403: components["responses"]["Forbidden"];
1485
+ 404: components["responses"]["NotFound"];
1486
+ };
1487
+ };
1488
+ getBillingUsage: {
1489
+ parameters: {
1490
+ query?: {
1491
+ from?: string;
1492
+ to?: string;
1493
+ limit?: number;
1494
+ };
1495
+ header?: never;
1496
+ path: {
1497
+ projectId: components["parameters"]["ProjectId"];
1498
+ };
1499
+ cookie?: never;
1500
+ };
1501
+ requestBody?: never;
1502
+ responses: {
1503
+ /** @description OK */
1504
+ 200: {
1505
+ headers: {
1506
+ [name: string]: unknown;
1507
+ };
1508
+ content: {
1509
+ "application/json": components["schemas"]["BillingUsage"];
1510
+ };
1511
+ };
1512
+ 400: components["responses"]["BadRequest"];
1513
+ 401: components["responses"]["Unauthorized"];
1514
+ 403: components["responses"]["Forbidden"];
1515
+ 404: components["responses"]["NotFound"];
1516
+ };
1517
+ };
1518
+ getBillingSummary: {
1519
+ parameters: {
1520
+ query?: {
1521
+ months?: number;
1522
+ };
1523
+ header?: never;
1524
+ path: {
1525
+ projectId: components["parameters"]["ProjectId"];
1526
+ };
1527
+ cookie?: never;
1528
+ };
1529
+ requestBody?: never;
1530
+ responses: {
1531
+ /** @description OK */
1532
+ 200: {
1533
+ headers: {
1534
+ [name: string]: unknown;
1535
+ };
1536
+ content: {
1537
+ "application/json": components["schemas"]["BillingSummary"];
1538
+ };
1539
+ };
1540
+ 401: components["responses"]["Unauthorized"];
1541
+ 403: components["responses"]["Forbidden"];
1542
+ 404: components["responses"]["NotFound"];
1543
+ };
1544
+ };
1138
1545
  }
1139
1546
 
1140
1547
  type Schemas = components["schemas"];
@@ -1152,11 +1559,21 @@ type CancelScanResult = Schemas["CancelScanResult"];
1152
1559
  type ChangeNote = Schemas["ChangeNote"];
1153
1560
  type Severity = Schemas["Severity"];
1154
1561
  type ResolutionStatus = Schemas["ResolutionStatus"];
1562
+ type ResolutionReason = Schemas["ResolutionReason"];
1155
1563
  type TriageStatus = Schemas["TriageStatus"];
1156
1564
  type ScanStatus = Schemas["ScanStatus"];
1157
1565
  type PublishArtifactResult = Schemas["PublishArtifactResult"];
1158
1566
  type RepositoryGuidance = Schemas["RepositoryGuidance"];
1567
+ type AuditArea = Schemas["AuditArea"];
1568
+ type AuditAreaList = Schemas["AuditAreaList"];
1569
+ type DeleteAuditAreaResult = Schemas["DeleteAuditAreaResult"];
1159
1570
  type SetGuidanceResult = Schemas["SetGuidanceResult"];
1571
+ type BillingBalance = Schemas["BillingBalance"];
1572
+ type BillingUsage = Schemas["BillingUsage"];
1573
+ type UsageEvent = Schemas["UsageEvent"];
1574
+ type UsageEventType = Schemas["UsageEventType"];
1575
+ type BillingSummary = Schemas["BillingSummary"];
1576
+ type UsageMonthSummary = Schemas["UsageMonthSummary"];
1160
1577
  type Paginated<T> = {
1161
1578
  items: T[];
1162
1579
  page: number;
@@ -1180,6 +1597,16 @@ type ZkaoClientOptions = {
1180
1597
  /** Terminal scan statuses: polling can stop once a scan reaches one of these. */
1181
1598
  declare const TERMINAL_SCAN_STATUSES: readonly ScanStatus[];
1182
1599
  declare function isTerminalScanStatus(status: ScanStatus): boolean;
1600
+ type WaitForRepositoryReadyOptions = {
1601
+ /** First poll delay, in ms (default 5000). Grows with backoff up to maxIntervalMs. */
1602
+ intervalMs?: number;
1603
+ /** Cap on the poll delay, in ms (default 30000). */
1604
+ maxIntervalMs?: number;
1605
+ /** Give up after this long, in ms (default 30m). Throws on timeout. */
1606
+ timeoutMs?: number;
1607
+ /** Abort the wait (e.g. on Ctrl-C). Rejects with the abort reason. */
1608
+ signal?: AbortSignal;
1609
+ };
1183
1610
  type WaitForScanOptions = {
1184
1611
  /** First poll delay, in ms (default 5000). Grows with backoff up to maxIntervalMs. */
1185
1612
  intervalMs?: number;
@@ -1208,6 +1635,15 @@ declare class ZkaoClient {
1208
1635
  constructor(options: ZkaoClientOptions);
1209
1636
  private get path();
1210
1637
  listRepositories(): Promise<Repository[]>;
1638
+ /**
1639
+ * Block until a repository is ready to scan, then return it.
1640
+ *
1641
+ * A repository added moments ago is still being analyzed, and launching a
1642
+ * scan against it fails with `repository_initializing`. Call this between
1643
+ * adding a repository and launching its first scan rather than retrying the
1644
+ * launch and swallowing the error.
1645
+ */
1646
+ waitForRepositoryReady(repositoryId: string, opts?: WaitForRepositoryReadyOptions): Promise<Repository>;
1211
1647
  /** Read a repository's configured guidance (requires the `read` scope). */
1212
1648
  getRepositoryGuidance(repositoryId: string): Promise<RepositoryGuidance>;
1213
1649
  /**
@@ -1220,6 +1656,30 @@ declare class ZkaoClient {
1220
1656
  setRepositoryGuidance(repositoryId: string, content: string | null, opts?: {
1221
1657
  expectedContent?: string | null;
1222
1658
  }): Promise<SetGuidanceResult>;
1659
+ /**
1660
+ * List a repository's audit areas (requires the `read` scope).
1661
+ *
1662
+ * An area is a named subsystem a scan can be scoped to: pass its `key` in
1663
+ * `auditAreaKeys` when launching. Sizes come from one branch's map, so an
1664
+ * area that map no longer names is returned without one. Defaults to the
1665
+ * repository's default branch.
1666
+ */
1667
+ listAuditAreas(repositoryId: string, opts?: {
1668
+ branch?: string;
1669
+ }): Promise<AuditAreaList>;
1670
+ /**
1671
+ * Add a custom audit area (requires the `guidance:write` scope).
1672
+ *
1673
+ * An area is a name and a description, never a file list: a scan resolves it
1674
+ * against the code at the commit it runs on. The returned `key` is derived
1675
+ * from the name.
1676
+ */
1677
+ createAuditArea(repositoryId: string, name: string, description?: string | null): Promise<AuditArea>;
1678
+ /**
1679
+ * Delete a custom audit area (requires the `guidance:write` scope). An area
1680
+ * a scan's map named is refused with a `409`.
1681
+ */
1682
+ deleteAuditArea(repositoryId: string, areaKey: string): Promise<DeleteAuditAreaResult>;
1223
1683
  listScans(opts?: {
1224
1684
  page?: number;
1225
1685
  limit?: number;
@@ -1263,7 +1723,19 @@ declare class ZkaoClient {
1263
1723
  userSeverity?: Severity | null;
1264
1724
  effectiveSeverity: Severity;
1265
1725
  }>;
1266
- setFindingResolution(findingId: string, resolutionStatus: ResolutionStatus, note?: ChangeNote): Promise<{
1726
+ /**
1727
+ * Change a finding's resolution status (requires the `findings:write` scope).
1728
+ *
1729
+ * `note` attaches free text in the same call. `reason` picks a catalog code
1730
+ * instead, recorded as a generated comment; the codes offered depend on the
1731
+ * status, and a code from another status is rejected with 400. A `note` takes
1732
+ * precedence: pass one or the other. Both are ignored for the open statuses
1733
+ * (`NOT_STARTED`, `IN_PROGRESS`).
1734
+ */
1735
+ setFindingResolution(findingId: string, resolutionStatus: ResolutionStatus, opts?: {
1736
+ note?: ChangeNote;
1737
+ reason?: ResolutionReason;
1738
+ }): Promise<{
1267
1739
  findingId: string;
1268
1740
  resolutionStatus: ResolutionStatus;
1269
1741
  }>;
@@ -1273,6 +1745,31 @@ declare class ZkaoClient {
1273
1745
  }): Promise<PublishArtifactResult>;
1274
1746
  listScanPresets(): Promise<ScanPreset[]>;
1275
1747
  listOptionalFlows(): Promise<OptionalFlow[]>;
1748
+ /**
1749
+ * Get the project's prepaid credit balance (requires the `read` scope). zkao
1750
+ * is pay-as-you-go: `balanceCredits` is the total, `reservedCredits` is held
1751
+ * by active scans, and `availableCredits` is what remains for new scans.
1752
+ */
1753
+ getBillingBalance(): Promise<BillingBalance>;
1754
+ /**
1755
+ * List credit-ledger movements for reconciliation (requires the `read`
1756
+ * scope). `credits` on each event is signed (negative = spent). Defaults to
1757
+ * the last 30 days when `from`/`to` are omitted; the range actually used is
1758
+ * echoed back on the response.
1759
+ */
1760
+ getBillingUsage(opts?: {
1761
+ from?: string;
1762
+ to?: string;
1763
+ limit?: number;
1764
+ }): Promise<BillingUsage>;
1765
+ /**
1766
+ * Per-calendar-month (UTC) totals of net credits spent on scans and net
1767
+ * credits purchased (requires the `read` scope). Quiet months are returned as
1768
+ * zeros; newest month first.
1769
+ */
1770
+ getBillingSummary(opts?: {
1771
+ months?: number;
1772
+ }): Promise<UsageMonthSummary[]>;
1276
1773
  }
1277
1774
 
1278
- export { type CancelScanResult, type ChangeNote, DEFAULT_BASE_URL, type Finding, type FindingDetail, type FindingNote, type LaunchScanRequest, type LaunchScanResult, type OptionalFlow, type Paginated, type PublishArtifactResult, type Repository, type ResolutionStatus, type Scan, type ScanDetail, type ScanPreset, type ScanStatus, type Severity, TERMINAL_SCAN_STATUSES, type TriageStatus, type WaitForScanOptions, ZkaoApiError, ZkaoClient, type ZkaoClientOptions, type components, isTerminalScanStatus, normalizeBaseUrl, type paths, resolveBaseUrlFromEnv };
1775
+ export { type BillingBalance, type BillingSummary, type BillingUsage, type CancelScanResult, type ChangeNote, DEFAULT_BASE_URL, type Finding, type FindingDetail, type FindingNote, type LaunchScanRequest, type LaunchScanResult, type OptionalFlow, type Paginated, type PublishArtifactResult, type Repository, type RepositoryGuidance, type ResolutionReason, type ResolutionStatus, type Scan, type ScanDetail, type ScanPreset, type ScanStatus, type SetGuidanceResult, type Severity, TERMINAL_SCAN_STATUSES, type TriageStatus, type UsageEvent, type UsageEventType, type UsageMonthSummary, type WaitForRepositoryReadyOptions, type WaitForScanOptions, ZkaoApiError, ZkaoClient, type ZkaoClientOptions, type components, isTerminalScanStatus, normalizeBaseUrl, type paths, resolveBaseUrlFromEnv };
package/dist/index.js CHANGED
@@ -120,6 +120,55 @@ var ZkaoClient = class {
120
120
  });
121
121
  return unwrap(res).repositories;
122
122
  }
123
+ /**
124
+ * Block until a repository is ready to scan, then return it.
125
+ *
126
+ * A repository added moments ago is still being analyzed, and launching a
127
+ * scan against it fails with `repository_initializing`. Call this between
128
+ * adding a repository and launching its first scan rather than retrying the
129
+ * launch and swallowing the error.
130
+ */
131
+ async waitForRepositoryReady(repositoryId, opts = {}) {
132
+ const intervalMs = opts.intervalMs ?? 5e3;
133
+ const maxIntervalMs = opts.maxIntervalMs ?? 3e4;
134
+ const timeoutMs = opts.timeoutMs ?? 30 * 60 * 1e3;
135
+ const deadline = Date.now() + timeoutMs;
136
+ for (let attempt = 0; ; attempt++) {
137
+ throwIfAborted(opts.signal);
138
+ const res = await this.http.GET("/projects/{projectId}/repositories", {
139
+ params: { path: this.path },
140
+ signal: opts.signal
141
+ });
142
+ const retryAfterMs = parseRetryAfterMs(
143
+ res.response.headers.get("retry-after")
144
+ );
145
+ if (res.response.status !== 429) {
146
+ const repository = unwrap(res).repositories.find(
147
+ (candidate) => candidate.id === repositoryId
148
+ );
149
+ if (!repository) {
150
+ throw new Error(
151
+ `Repository ${repositoryId} is not in this project (or this token's allowlist)`
152
+ );
153
+ }
154
+ if (repository.readiness === "ready") {
155
+ return repository;
156
+ }
157
+ }
158
+ const backoffMs = Math.min(
159
+ maxIntervalMs,
160
+ Math.round(intervalMs * 1.5 ** attempt)
161
+ );
162
+ const baseMs = retryAfterMs ?? backoffMs;
163
+ const delayMs = baseMs + Math.round(Math.random() * baseMs * 0.2);
164
+ if (Date.now() + delayMs > deadline) {
165
+ throw new Error(
166
+ `Timed out waiting for repository ${repositoryId} to be ready after ${timeoutMs}ms`
167
+ );
168
+ }
169
+ await sleep(delayMs, opts.signal);
170
+ }
171
+ }
123
172
  /** Read a repository's configured guidance (requires the `read` scope). */
124
173
  async getRepositoryGuidance(repositoryId) {
125
174
  const res = await this.http.GET(
@@ -145,6 +194,55 @@ var ZkaoClient = class {
145
194
  );
146
195
  return unwrap(res);
147
196
  }
197
+ // --- Audit areas --------------------------------------------------------
198
+ /**
199
+ * List a repository's audit areas (requires the `read` scope).
200
+ *
201
+ * An area is a named subsystem a scan can be scoped to: pass its `key` in
202
+ * `auditAreaKeys` when launching. Sizes come from one branch's map, so an
203
+ * area that map no longer names is returned without one. Defaults to the
204
+ * repository's default branch.
205
+ */
206
+ async listAuditAreas(repositoryId, opts = {}) {
207
+ const res = await this.http.GET(
208
+ "/projects/{projectId}/repositories/{repositoryId}/audit-areas",
209
+ {
210
+ params: {
211
+ path: { ...this.path, repositoryId },
212
+ query: opts.branch === void 0 ? {} : { branch: opts.branch }
213
+ }
214
+ }
215
+ );
216
+ return unwrap(res);
217
+ }
218
+ /**
219
+ * Add a custom audit area (requires the `guidance:write` scope).
220
+ *
221
+ * An area is a name and a description, never a file list: a scan resolves it
222
+ * against the code at the commit it runs on. The returned `key` is derived
223
+ * from the name.
224
+ */
225
+ async createAuditArea(repositoryId, name, description) {
226
+ const res = await this.http.POST(
227
+ "/projects/{projectId}/repositories/{repositoryId}/audit-areas",
228
+ {
229
+ params: { path: { ...this.path, repositoryId } },
230
+ body: { name, description: description ?? null }
231
+ }
232
+ );
233
+ return unwrap(res).area;
234
+ }
235
+ /**
236
+ * Delete a custom audit area (requires the `guidance:write` scope). An area
237
+ * a scan's map named is refused with a `409`.
238
+ */
239
+ async deleteAuditArea(repositoryId, areaKey) {
240
+ const res = await this.http.DELETE(
241
+ "/projects/{projectId}/repositories/{repositoryId}/audit-areas/{areaKey}",
242
+ { params: { path: { ...this.path, repositoryId, areaKey } } }
243
+ );
244
+ return unwrap(res);
245
+ }
148
246
  // --- Scans --------------------------------------------------------------
149
247
  async listScans(opts = {}) {
150
248
  const res = await this.http.GET("/projects/{projectId}/scans", {
@@ -263,10 +361,19 @@ var ZkaoClient = class {
263
361
  });
264
362
  return unwrap(res);
265
363
  }
266
- async setFindingResolution(findingId, resolutionStatus, note) {
364
+ /**
365
+ * Change a finding's resolution status (requires the `findings:write` scope).
366
+ *
367
+ * `note` attaches free text in the same call. `reason` picks a catalog code
368
+ * instead, recorded as a generated comment; the codes offered depend on the
369
+ * status, and a code from another status is rejected with 400. A `note` takes
370
+ * precedence: pass one or the other. Both are ignored for the open statuses
371
+ * (`NOT_STARTED`, `IN_PROGRESS`).
372
+ */
373
+ async setFindingResolution(findingId, resolutionStatus, opts = {}) {
267
374
  const res = await this.http.PATCH("/projects/{projectId}/findings/{findingId}/resolution", {
268
375
  params: { path: { ...this.path, findingId } },
269
- body: { resolutionStatus, note }
376
+ body: { resolutionStatus, note: opts.note, reason: opts.reason }
270
377
  });
271
378
  return unwrap(res);
272
379
  }
@@ -290,6 +397,44 @@ var ZkaoClient = class {
290
397
  });
291
398
  return unwrap(res).optionalFlows;
292
399
  }
400
+ // --- Billing ------------------------------------------------------------
401
+ /**
402
+ * Get the project's prepaid credit balance (requires the `read` scope). zkao
403
+ * is pay-as-you-go: `balanceCredits` is the total, `reservedCredits` is held
404
+ * by active scans, and `availableCredits` is what remains for new scans.
405
+ */
406
+ async getBillingBalance() {
407
+ const res = await this.http.GET("/projects/{projectId}/billing/balance", {
408
+ params: { path: this.path }
409
+ });
410
+ return unwrap(res);
411
+ }
412
+ /**
413
+ * List credit-ledger movements for reconciliation (requires the `read`
414
+ * scope). `credits` on each event is signed (negative = spent). Defaults to
415
+ * the last 30 days when `from`/`to` are omitted; the range actually used is
416
+ * echoed back on the response.
417
+ */
418
+ async getBillingUsage(opts = {}) {
419
+ const res = await this.http.GET("/projects/{projectId}/billing/usage", {
420
+ params: {
421
+ path: this.path,
422
+ query: { from: opts.from, to: opts.to, limit: opts.limit }
423
+ }
424
+ });
425
+ return unwrap(res);
426
+ }
427
+ /**
428
+ * Per-calendar-month (UTC) totals of net credits spent on scans and net
429
+ * credits purchased (requires the `read` scope). Quiet months are returned as
430
+ * zeros; newest month first.
431
+ */
432
+ async getBillingSummary(opts = {}) {
433
+ const res = await this.http.GET("/projects/{projectId}/billing/summary", {
434
+ params: { path: this.path, query: { months: opts.months } }
435
+ });
436
+ return unwrap(res).months;
437
+ }
293
438
  };
294
439
  function throwIfAborted(signal) {
295
440
  if (signal?.aborted) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zksecurity/zkao-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.5.0",
4
4
  "description": "TypeScript SDK for the zkao Project API",
5
5
  "license": "MIT",
6
6
  "author": "zkSecurity",
@@ -29,7 +29,11 @@
29
29
  "import": "./dist/index.js"
30
30
  }
31
31
  },
32
- "files": ["dist", "LICENSE", "README.md"],
32
+ "files": [
33
+ "dist",
34
+ "LICENSE",
35
+ "README.md"
36
+ ],
33
37
  "engines": {
34
38
  "node": ">=18"
35
39
  },