@debugbundle/cli 0.1.4 → 0.1.6

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 (3) hide show
  1. package/README.md +1 -0
  2. package/dist/main.cjs +211 -49
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -21,6 +21,7 @@ debugbundle setup --non-interactive
21
21
  debugbundle doctor --privacy
22
22
  debugbundle verify local
23
23
  debugbundle verify cloud --project-id <id> --trigger-5xx
24
+ debugbundle verify cloud --project-id <id> --trigger-4xx 403
24
25
  debugbundle process
25
26
  debugbundle incidents
26
27
  debugbundle explain <incident-id> --source cloud
package/dist/main.cjs CHANGED
@@ -14485,6 +14485,31 @@ var CaptureProbeEventsValues = ["buffer_only", "standalone_when_activated"];
14485
14485
  var CaptureProbeEventsSchema = external_exports.enum(CaptureProbeEventsValues);
14486
14486
  var RequestSignalClassificationValues = ["incident_signal", "context_signal"];
14487
14487
  var RequestSignalClassificationSchema = external_exports.enum(RequestSignalClassificationValues);
14488
+ var RECOMMENDED_IMMEDIATE_CLIENT_ERROR_STATUSES = [401, 403, 409, 422];
14489
+ var ImmediateClientErrorStatusSchema = external_exports.number().int().min(400).max(499);
14490
+ function normalizeImmediateClientErrorStatuses(statuses) {
14491
+ return Array.from(new Set(statuses)).sort((left, right) => left - right);
14492
+ }
14493
+ var ImmediateClientErrorStatusesSchema = external_exports.array(ImmediateClientErrorStatusSchema).max(12).transform((statuses) => normalizeImmediateClientErrorStatuses(statuses));
14494
+ var ResolvedCapturePolicySchema = external_exports.object({
14495
+ preset: CapturePresetSchema,
14496
+ capture_logs: CaptureLogsSchema,
14497
+ capture_request_events: CaptureRequestEventsSchema,
14498
+ capture_breadcrumbs: CaptureBreadcrumbsSchema,
14499
+ capture_probe_events: CaptureProbeEventsSchema,
14500
+ immediate_client_error_statuses: ImmediateClientErrorStatusesSchema
14501
+ });
14502
+ var CapturePolicyOverridesSchema = external_exports.object({
14503
+ capture_logs: CaptureLogsSchema.nullable(),
14504
+ capture_request_events: CaptureRequestEventsSchema.nullable(),
14505
+ capture_breadcrumbs: CaptureBreadcrumbsSchema.nullable(),
14506
+ capture_probe_events: CaptureProbeEventsSchema.nullable(),
14507
+ immediate_client_error_statuses: ImmediateClientErrorStatusesSchema.nullable()
14508
+ });
14509
+ var CapturePolicyResponseSchema = external_exports.object({
14510
+ policy: ResolvedCapturePolicySchema,
14511
+ overrides: CapturePolicyOverridesSchema
14512
+ });
14488
14513
  var CapturePolicySchema = external_exports.object({
14489
14514
  project_id: external_exports.string().uuid(),
14490
14515
  preset: CapturePresetSchema,
@@ -14492,6 +14517,7 @@ var CapturePolicySchema = external_exports.object({
14492
14517
  capture_request_events: CaptureRequestEventsSchema.nullable(),
14493
14518
  capture_breadcrumbs: CaptureBreadcrumbsSchema.nullable(),
14494
14519
  capture_probe_events: CaptureProbeEventsSchema.nullable(),
14520
+ immediate_client_error_statuses: ImmediateClientErrorStatusesSchema.nullable(),
14495
14521
  updated_at: external_exports.string().datetime()
14496
14522
  });
14497
14523
  var CapturePolicyUpdateSchema = external_exports.object({
@@ -14499,21 +14525,48 @@ var CapturePolicyUpdateSchema = external_exports.object({
14499
14525
  capture_logs: CaptureLogsSchema.nullable().optional(),
14500
14526
  capture_request_events: CaptureRequestEventsSchema.nullable().optional(),
14501
14527
  capture_breadcrumbs: CaptureBreadcrumbsSchema.nullable().optional(),
14502
- capture_probe_events: CaptureProbeEventsSchema.nullable().optional()
14528
+ capture_probe_events: CaptureProbeEventsSchema.nullable().optional(),
14529
+ immediate_client_error_statuses: ImmediateClientErrorStatusesSchema.nullable().optional()
14503
14530
  });
14531
+ var PRESET_DEFAULTS = {
14532
+ minimal: {
14533
+ capture_logs: "error",
14534
+ capture_request_events: "failures_only",
14535
+ capture_breadcrumbs: "local_only",
14536
+ capture_probe_events: "buffer_only",
14537
+ immediate_client_error_statuses: []
14538
+ },
14539
+ balanced: {
14540
+ capture_logs: "warning",
14541
+ capture_request_events: "failures_only",
14542
+ capture_breadcrumbs: "exception_only",
14543
+ capture_probe_events: "buffer_only",
14544
+ immediate_client_error_statuses: []
14545
+ },
14546
+ investigative: {
14547
+ capture_logs: "info",
14548
+ capture_request_events: "all",
14549
+ capture_breadcrumbs: "standalone",
14550
+ capture_probe_events: "standalone_when_activated",
14551
+ immediate_client_error_statuses: [...RECOMMENDED_IMMEDIATE_CLIENT_ERROR_STATUSES]
14552
+ }
14553
+ };
14504
14554
  var BALANCED_IMMEDIATE_REQUEST_STATUSES = /* @__PURE__ */ new Set([408, 423, 424, 425, 429]);
14505
14555
  var INVESTIGATIVE_IMMEDIATE_REQUEST_STATUSES = /* @__PURE__ */ new Set([...BALANCED_IMMEDIATE_REQUEST_STATUSES, 409]);
14506
14556
  var BALANCED_STANDARD_ANOMALY_STATUSES = /* @__PURE__ */ new Set([401, 403, 404, 409, 422]);
14507
14557
  var BALANCED_HIGH_VOLUME_ANOMALY_STATUSES = /* @__PURE__ */ new Set([400, 410]);
14508
14558
  var INVESTIGATIVE_ANOMALY_STATUSES = /* @__PURE__ */ new Set([...BALANCED_STANDARD_ANOMALY_STATUSES, ...BALANCED_HIGH_VOLUME_ANOMALY_STATUSES]);
14509
14559
  function classifyRequestStatus(input) {
14510
- const { responseStatus, capturePreset } = input;
14560
+ const { responseStatus, capturePreset, immediateClientErrorStatuses = [] } = input;
14511
14561
  if (responseStatus === null || !Number.isFinite(responseStatus)) {
14512
14562
  return "context_signal";
14513
14563
  }
14514
14564
  if (responseStatus >= 500) {
14515
14565
  return "incident_signal";
14516
14566
  }
14567
+ if (immediateClientErrorStatuses.includes(responseStatus)) {
14568
+ return "incident_signal";
14569
+ }
14517
14570
  if (capturePreset === "investigative") {
14518
14571
  return INVESTIGATIVE_IMMEDIATE_REQUEST_STATUSES.has(responseStatus) ? "incident_signal" : "context_signal";
14519
14572
  }
@@ -16565,7 +16618,7 @@ function getRequestResponseStatus(payload) {
16565
16618
  const status = payload?.["response_status"];
16566
16619
  return typeof status === "number" && Number.isFinite(status) ? status : null;
16567
16620
  }
16568
- function classifyEvent(eventType, logLevel, probeActivationId, payload, capturePreset = "minimal") {
16621
+ function classifyEvent(eventType, logLevel, probeActivationId, payload, capturePreset = "minimal", immediateClientErrorStatuses = []) {
16569
16622
  switch (eventType) {
16570
16623
  case "backend_exception":
16571
16624
  case "frontend_exception":
@@ -16577,7 +16630,7 @@ function classifyEvent(eventType, logLevel, probeActivationId, payload, captureP
16577
16630
  return "context_signal";
16578
16631
  case "request_event": {
16579
16632
  const responseStatus = getRequestResponseStatus(payload);
16580
- return classifyRequestStatus({ responseStatus, capturePreset });
16633
+ return classifyRequestStatus({ responseStatus, capturePreset, immediateClientErrorStatuses });
16581
16634
  }
16582
16635
  case "frontend_breadcrumb":
16583
16636
  case "deploy_metadata":
@@ -16680,6 +16733,13 @@ var STORAGE_SCHEMA_MIGRATIONS = [
16680
16733
  ON slack_destinations (organization_id, is_active, created_at)
16681
16734
  `
16682
16735
  ]
16736
+ }),
16737
+ defineStorageSchemaMigration({
16738
+ id: "202605140001_add_capture_policy_immediate_client_error_statuses",
16739
+ description: "Add nullable immediate client error status overrides to capture policies.",
16740
+ statements: [
16741
+ "ALTER TABLE capture_policies ADD COLUMN IF NOT EXISTS immediate_client_error_statuses jsonb"
16742
+ ]
16683
16743
  })
16684
16744
  ];
16685
16745
 
@@ -24530,7 +24590,7 @@ function formatResult(input, exitCode, checks, errors, incidentId) {
24530
24590
  };
24531
24591
  }
24532
24592
  function buildCloudSuggestedActions(status, incidentId, mode = "passive_recent_incident") {
24533
- if (status === "healthy" && incidentId !== void 0 && mode === "active_5xx") {
24593
+ if (status === "healthy" && incidentId !== void 0 && (mode === "active_5xx" || mode === "active_4xx")) {
24534
24594
  return [
24535
24595
  `Run debugbundle inspect ${incidentId} --source cloud to inspect why the incident fired.`,
24536
24596
  `Run debugbundle bundle ${incidentId} --source cloud to fetch the generated debug bundle.`
@@ -24600,11 +24660,11 @@ function localFailureStepName(checks) {
24600
24660
  function cloudVerificationRunId(now) {
24601
24661
  return now.toISOString().replace(/[-:.TZ]/g, "").slice(0, 14);
24602
24662
  }
24603
- function requestFailureReason() {
24663
+ function requestFailureReason(responseStatus) {
24604
24664
  const incidentReason = deriveIncidentReasonFromSignal({
24605
24665
  event_type: "request_event",
24606
24666
  event_class: "incident_signal",
24607
- response_status: 503
24667
+ response_status: responseStatus
24608
24668
  });
24609
24669
  if (incidentReason === null) {
24610
24670
  throw new Error("request_failure_reason_unavailable");
@@ -24613,6 +24673,9 @@ function requestFailureReason() {
24613
24673
  }
24614
24674
  function buildCloudVerificationEvent(input) {
24615
24675
  const runId = cloudVerificationRunId(input.now);
24676
+ const is5xxVerification = input.responseStatus >= 500;
24677
+ const routeTemplate = is5xxVerification ? "/debugbundle/verify/cloud" : `/debugbundle/verify/cloud/client-error/${input.responseStatus}`;
24678
+ const verificationLabel = is5xxVerification ? "true" : `client-error-${input.responseStatus}`;
24616
24679
  return createEventEnvelope({
24617
24680
  event_type: "request_event",
24618
24681
  sdk_name: "debugbundle-cli",
@@ -24626,28 +24689,39 @@ function buildCloudVerificationEvent(input) {
24626
24689
  occurred_at: input.now.toISOString(),
24627
24690
  payload: {
24628
24691
  method: "GET",
24629
- path: "/debugbundle/verify/cloud",
24630
- route_template: "/debugbundle/verify/cloud",
24692
+ path: routeTemplate,
24693
+ route_template: routeTemplate,
24631
24694
  query: {
24632
24695
  debugbundle_verification: true,
24633
- run_id: runId
24696
+ run_id: runId,
24697
+ synthetic_status: input.responseStatus
24634
24698
  },
24635
24699
  headers: {
24636
- "x-debugbundle-verification": "true"
24700
+ "x-debugbundle-verification": verificationLabel
24637
24701
  },
24638
- response_status: 503,
24702
+ response_status: input.responseStatus,
24639
24703
  duration_ms: 37,
24640
24704
  response_headers: {
24641
- "x-debugbundle-verification": "true"
24705
+ "x-debugbundle-verification": verificationLabel
24642
24706
  },
24643
24707
  response_body: {
24644
- error: "debugbundle_cloud_verification",
24708
+ error: is5xxVerification ? "debugbundle_cloud_verification" : "debugbundle_cloud_client_error_verification",
24645
24709
  synthetic: true,
24646
- run_id: runId
24710
+ run_id: runId,
24711
+ response_status: input.responseStatus
24647
24712
  }
24648
24713
  }
24649
24714
  });
24650
24715
  }
24716
+ function validateActiveCloudVerificationInput(input) {
24717
+ if (input.trigger5xx === true && input.trigger4xxStatus !== void 0) {
24718
+ return "Choose either --trigger-5xx or --trigger-4xx, not both.";
24719
+ }
24720
+ if (input.trigger4xxStatus !== void 0 && (input.trigger4xxStatus < 400 || input.trigger4xxStatus > 499)) {
24721
+ return "--trigger-4xx must be an integer status between 400 and 499.";
24722
+ }
24723
+ return null;
24724
+ }
24651
24725
  async function sendEventsToApi2(input, dependencies = {}) {
24652
24726
  const fetchImpl = dependencies.fetchImpl ?? fetch;
24653
24727
  const baseUrl = input.baseUrl.endsWith("/") ? input.baseUrl.slice(0, -1) : input.baseUrl;
@@ -24839,6 +24913,15 @@ async function verifyCloudCommand(input, dependencies = {}) {
24839
24913
  const checks = [];
24840
24914
  const environment = input.environment ?? "production";
24841
24915
  const maxAgeMinutes = input.maxAgeMinutes ?? 15;
24916
+ const activeInputError = validateActiveCloudVerificationInput(input);
24917
+ if (activeInputError !== null) {
24918
+ checks.push({
24919
+ name: "trigger-input",
24920
+ status: "error",
24921
+ message: activeInputError
24922
+ });
24923
+ return formatCloudResult(input, 4, checks, [activeInputError]);
24924
+ }
24842
24925
  const readAuthState = dependencies.readAuthState ?? readCliAuthState;
24843
24926
  let authState;
24844
24927
  try {
@@ -24871,7 +24954,7 @@ async function verifyCloudCommand(input, dependencies = {}) {
24871
24954
  requestInput,
24872
24955
  dependencies.fetchImpl === void 0 ? {} : { fetchImpl: dependencies.fetchImpl }
24873
24956
  ));
24874
- if (input.trigger5xx === true) {
24957
+ if (input.trigger5xx === true || input.trigger4xxStatus !== void 0) {
24875
24958
  const verificationStartedAt = now();
24876
24959
  const runId = cloudVerificationRunId(verificationStartedAt);
24877
24960
  const serviceName = input.service ?? `debugbundle-verify-cloud-${runId}`;
@@ -24879,16 +24962,20 @@ async function verifyCloudCommand(input, dependencies = {}) {
24879
24962
  const pollAttempts = dependencies.pollAttempts ?? 6;
24880
24963
  const pollIntervalMs = dependencies.pollIntervalMs ?? 2e3;
24881
24964
  const sleep = dependencies.sleep ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
24965
+ const responseStatus = input.trigger4xxStatus ?? 503;
24966
+ const activeMode = input.trigger4xxStatus !== void 0 ? "active_4xx" : "active_5xx";
24967
+ const activeCheckName = input.trigger4xxStatus !== void 0 ? "active-4xx-event" : "active-5xx-event";
24968
+ const statusLabel = input.trigger4xxStatus !== void 0 ? `${responseStatus}` : "5xx";
24882
24969
  const verification = {
24883
- mode: "active_5xx",
24970
+ mode: activeMode,
24884
24971
  bundle_status: "unknown",
24885
- classification_reason: requestFailureReason()
24972
+ classification_reason: requestFailureReason(responseStatus)
24886
24973
  };
24887
24974
  const errors = [];
24888
24975
  let exitCode = 0;
24889
24976
  let tokenId = null;
24890
24977
  let incidentId;
24891
- let activeStep = "active-5xx-event";
24978
+ let activeStep = activeCheckName;
24892
24979
  try {
24893
24980
  const token = await createProjectToken({
24894
24981
  bearerToken: authState.bearer_token,
@@ -24902,7 +24989,8 @@ async function verifyCloudCommand(input, dependencies = {}) {
24902
24989
  const event = buildCloudVerificationEvent({
24903
24990
  now: verificationStartedAt,
24904
24991
  serviceName,
24905
- environment
24992
+ environment,
24993
+ responseStatus
24906
24994
  });
24907
24995
  const ingestion = await sendEvents({
24908
24996
  baseUrl: authState.base_url,
@@ -24911,12 +24999,12 @@ async function verifyCloudCommand(input, dependencies = {}) {
24911
24999
  });
24912
25000
  verification.accepted_event_count = ingestion.accepted;
24913
25001
  if (ingestion.accepted < 1 || ingestion.rejected > 0 || ingestion.errors.length > 0) {
24914
- throw new Error(`Synthetic 5xx ingestion was not fully accepted: accepted=${ingestion.accepted}, rejected=${ingestion.rejected}.`);
25002
+ throw new Error(`Synthetic ${statusLabel} ingestion was not fully accepted: accepted=${ingestion.accepted}, rejected=${ingestion.rejected}.`);
24915
25003
  }
24916
25004
  checks.push({
24917
- name: "active-5xx-event",
25005
+ name: activeCheckName,
24918
25006
  status: "ok",
24919
- message: "Sent synthetic 5xx request_event through cloud ingestion."
25007
+ message: `Sent synthetic ${statusLabel} request_event through cloud ingestion.`
24920
25008
  });
24921
25009
  activeStep = "incident-retrieval";
24922
25010
  for (let attempt = 1; attempt <= pollAttempts; attempt += 1) {
@@ -24934,7 +25022,7 @@ async function verifyCloudCommand(input, dependencies = {}) {
24934
25022
  if (candidate !== void 0) {
24935
25023
  incidentId = candidate.incident_id;
24936
25024
  verification.incident_id = candidate.incident_id;
24937
- verification.classification_reason = candidate.incident_reason ?? requestFailureReason();
25025
+ verification.classification_reason = candidate.incident_reason ?? requestFailureReason(responseStatus);
24938
25026
  break;
24939
25027
  }
24940
25028
  if (attempt < pollAttempts) {
@@ -24942,12 +25030,12 @@ async function verifyCloudCommand(input, dependencies = {}) {
24942
25030
  }
24943
25031
  }
24944
25032
  if (incidentId === void 0) {
24945
- throw new Error("Synthetic 5xx request was accepted but no matching cloud incident was visible yet.");
25033
+ throw new Error(`Synthetic ${statusLabel} request was accepted but no matching cloud incident was visible yet.`);
24946
25034
  }
24947
25035
  checks.push({
24948
25036
  name: "incident-retrieval",
24949
25037
  status: "ok",
24950
- message: `Retrieved cloud incident ${incidentId} for the synthetic 5xx request.`
25038
+ message: `Retrieved cloud incident ${incidentId} for the synthetic ${statusLabel} request.`
24951
25039
  });
24952
25040
  activeStep = "bundle-status";
24953
25041
  const bundle = await getBundle({
@@ -25213,7 +25301,7 @@ var CLI_USAGE_LINES = [
25213
25301
  " debugbundle clean [--events] [--bundles] [--all] [--older-than <Nd>] [--json]",
25214
25302
  " debugbundle validate [--fix] [--json]",
25215
25303
  " debugbundle verify local [--json]",
25216
- " debugbundle verify cloud --project-id <id> [--trigger-5xx] [--service <name>] [--environment <name>] [--max-age-minutes <n>] [--auth-file <path>] [--json]",
25304
+ " debugbundle verify cloud --project-id <id> [--trigger-5xx | --trigger-4xx <400-499>] [--service <name>] [--environment <name>] [--max-age-minutes <n>] [--auth-file <path>] [--json]",
25217
25305
  " debugbundle smoke --project-id <id> [--service <name>] [--environment <name>] [--max-age-minutes <n>] [--auth-file <path>] [--json]",
25218
25306
  " debugbundle login [--base-url <url>] [--auth-file <path>] [--json]",
25219
25307
  " debugbundle login <member-token> [--base-url <url>] [--auth-file <path>] [--json]",
@@ -25275,7 +25363,7 @@ var CLI_USAGE_LINES = [
25275
25363
  " debugbundle weekly-report update <channel-id> [--day-of-week <day>] [--hour-of-day <0-23>] [--timezone <iana>] [--config-json <json>] [--is-enabled <true|false>] [--auth-file <path>] [--json]",
25276
25364
  " debugbundle weekly-report delete <channel-id> [--auth-file <path>] [--json]",
25277
25365
  " debugbundle capture-policy get --project <id> [--auth-file <path>] [--json]",
25278
- " debugbundle capture-policy set --project <id> [--preset <minimal|balanced|investigative>] [--override <key=value>] [--auth-file <path>] [--json]",
25366
+ " debugbundle capture-policy set --project <id> [--preset <minimal|balanced|investigative>] [--override <key=value>] [--client-error-incidents <preset-default|none|recommended|custom>] [--client-error-statuses <400,401,...>] [--auth-file <path>] [--json]",
25279
25367
  " debugbundle probe activate <project-id> --label-pattern <pattern> [--service <name>] [--environment <name>] [--ttl-seconds <n>] [--trigger-ttl-seconds <n>] [--auth-file <path>] [--json]",
25280
25368
  " debugbundle probe list <project-id> [--auth-file <path>] [--json]",
25281
25369
  " debugbundle probe deactivate <project-id> <activation-id> [--auth-file <path>] [--json]",
@@ -25942,16 +26030,6 @@ async function deleteAlertWithAuthCommand(input, dependencies) {
25942
26030
  }
25943
26031
 
25944
26032
  // src/capture-policy-commands.ts
25945
- var ResolvedCapturePolicySchema = external_exports.object({
25946
- preset: CapturePresetSchema,
25947
- capture_logs: CaptureLogsSchema,
25948
- capture_request_events: CaptureRequestEventsSchema,
25949
- capture_breadcrumbs: CaptureBreadcrumbsSchema,
25950
- capture_probe_events: CaptureProbeEventsSchema
25951
- });
25952
- var CapturePolicyResponseSchema = external_exports.object({
25953
- policy: ResolvedCapturePolicySchema
25954
- });
25955
26033
  var CapturePolicyApiError = class extends Error {
25956
26034
  status;
25957
26035
  constructor(status, message) {
@@ -25981,7 +26059,7 @@ function createCapturePolicyApi(httpClient) {
25981
26059
  if (!parsed.success) {
25982
26060
  throw new CapturePolicyApiError(500, "Invalid capture policy response.");
25983
26061
  }
25984
- return parsed.data.policy;
26062
+ return parsed.data;
25985
26063
  },
25986
26064
  async updateCapturePolicy(input) {
25987
26065
  const response = await httpClient.request({
@@ -25997,7 +26075,7 @@ function createCapturePolicyApi(httpClient) {
25997
26075
  if (!parsed.success) {
25998
26076
  throw new CapturePolicyApiError(500, "Invalid capture policy response.");
25999
26077
  }
26000
- return parsed.data.policy;
26078
+ return parsed.data;
26001
26079
  }
26002
26080
  };
26003
26081
  }
@@ -26016,24 +26094,48 @@ function mapErrorToExitCode5(error) {
26016
26094
  }
26017
26095
  return 1;
26018
26096
  }
26019
- function formatPolicy(policy) {
26097
+ function statusesEqual(left, right) {
26098
+ if (left.length !== right.length) {
26099
+ return false;
26100
+ }
26101
+ return left.every((value, index) => value === right[index]);
26102
+ }
26103
+ function formatStatusList(statuses) {
26104
+ return statuses.length === 0 ? "none" : statuses.join(", ");
26105
+ }
26106
+ function formatClientErrorIncidents(response) {
26107
+ const rawOverride = response.overrides.immediate_client_error_statuses;
26108
+ if (rawOverride === null) {
26109
+ return `preset default (${formatStatusList(response.policy.immediate_client_error_statuses)})`;
26110
+ }
26111
+ if (rawOverride.length === 0) {
26112
+ return "none (explicit)";
26113
+ }
26114
+ if (statusesEqual(rawOverride, RECOMMENDED_IMMEDIATE_CLIENT_ERROR_STATUSES)) {
26115
+ return `recommended (${formatStatusList(rawOverride)})`;
26116
+ }
26117
+ return `custom (${formatStatusList(rawOverride)})`;
26118
+ }
26119
+ function formatPolicy(response) {
26120
+ const policy = response.policy;
26020
26121
  return [
26021
26122
  `preset: ${policy.preset}`,
26022
26123
  `capture_logs: ${policy.capture_logs}`,
26023
26124
  `capture_request_events: ${policy.capture_request_events}`,
26024
26125
  `capture_breadcrumbs: ${policy.capture_breadcrumbs}`,
26025
- `capture_probe_events: ${policy.capture_probe_events}`
26126
+ `capture_probe_events: ${policy.capture_probe_events}`,
26127
+ `client_error_incidents: ${formatClientErrorIncidents(response)}`
26026
26128
  ].join("\n");
26027
26129
  }
26028
26130
  async function getCapturePolicyCommand(input, api) {
26029
26131
  try {
26030
- const policy = await api.getCapturePolicy({
26132
+ const response = await api.getCapturePolicy({
26031
26133
  bearerToken: input.bearerToken,
26032
26134
  projectId: input.projectId
26033
26135
  });
26034
26136
  return {
26035
26137
  exitCode: 0,
26036
- output: input.json ? JSON.stringify({ policy }) : formatPolicy(policy)
26138
+ output: input.json ? JSON.stringify(response) : formatPolicy(response)
26037
26139
  };
26038
26140
  } catch (error) {
26039
26141
  return {
@@ -26051,15 +26153,15 @@ async function setCapturePolicyCommand(input, api) {
26051
26153
  output: "Invalid capture policy update."
26052
26154
  };
26053
26155
  }
26054
- const policy = await api.updateCapturePolicy({
26156
+ const response = await api.updateCapturePolicy({
26055
26157
  bearerToken: input.bearerToken,
26056
26158
  projectId: input.projectId,
26057
26159
  update: parsedUpdate.data
26058
26160
  });
26059
26161
  return {
26060
26162
  exitCode: 0,
26061
- output: input.json ? JSON.stringify({ policy }) : `Capture policy updated.
26062
- ${formatPolicy(policy)}`
26163
+ output: input.json ? JSON.stringify(response) : `Capture policy updated.
26164
+ ${formatPolicy(response)}`
26063
26165
  };
26064
26166
  } catch (error) {
26065
26167
  return {
@@ -28568,6 +28670,28 @@ async function handleTokenCommand(parsedArgv, dependencies) {
28568
28670
  throw new CliInputError("Unknown token command.");
28569
28671
  }
28570
28672
  async function handleCapturePolicyCommand(parsedArgv, dependencies) {
28673
+ function parseClientErrorStatusesOption(value) {
28674
+ const parts = value.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
28675
+ if (parts.length === 0) {
28676
+ throw new CliInputError("Invalid value for --client-error-statuses.");
28677
+ }
28678
+ const statuses = [];
28679
+ for (const part of parts) {
28680
+ if (!/^\d+$/.test(part)) {
28681
+ throw new CliInputError("Invalid value for --client-error-statuses.");
28682
+ }
28683
+ const status = Number(part);
28684
+ if (!Number.isInteger(status) || status < 400 || status > 499) {
28685
+ throw new CliInputError("Invalid value for --client-error-statuses.");
28686
+ }
28687
+ statuses.push(status);
28688
+ }
28689
+ const normalized = Array.from(new Set(statuses)).sort((left, right) => left - right);
28690
+ if (normalized.length > 12) {
28691
+ throw new CliInputError("Invalid value for --client-error-statuses.");
28692
+ }
28693
+ return normalized;
28694
+ }
28571
28695
  const action = requirePositional(parsedArgv, 1, "action");
28572
28696
  if (action === "get") {
28573
28697
  expectNoUnknownOptions(parsedArgv, ["auth-file", "json", "project"]);
@@ -28581,7 +28705,15 @@ async function handleCapturePolicyCommand(parsedArgv, dependencies) {
28581
28705
  );
28582
28706
  }
28583
28707
  if (action === "set") {
28584
- expectNoUnknownOptions(parsedArgv, ["auth-file", "json", "project", "preset", "override"]);
28708
+ expectNoUnknownOptions(parsedArgv, [
28709
+ "auth-file",
28710
+ "json",
28711
+ "project",
28712
+ "preset",
28713
+ "override",
28714
+ "client-error-incidents",
28715
+ "client-error-statuses"
28716
+ ]);
28585
28717
  ensureNoExtraPositionals(parsedArgv, 2);
28586
28718
  const projectId = readStringOption(parsedArgv, "project");
28587
28719
  if (projectId === void 0) {
@@ -28605,6 +28737,32 @@ async function handleCapturePolicyCommand(parsedArgv, dependencies) {
28605
28737
  }
28606
28738
  update[key] = rawValue === "null" ? null : rawValue;
28607
28739
  }
28740
+ const clientErrorIncidents = readStringOption(parsedArgv, "client-error-incidents");
28741
+ const clientErrorStatuses = readStringOption(parsedArgv, "client-error-statuses");
28742
+ if (clientErrorStatuses !== void 0 && clientErrorIncidents !== "custom") {
28743
+ throw new CliInputError("Use --client-error-statuses only with --client-error-incidents custom.");
28744
+ }
28745
+ if (clientErrorIncidents !== void 0) {
28746
+ switch (clientErrorIncidents) {
28747
+ case "preset-default":
28748
+ update.immediate_client_error_statuses = null;
28749
+ break;
28750
+ case "none":
28751
+ update.immediate_client_error_statuses = [];
28752
+ break;
28753
+ case "recommended":
28754
+ update.immediate_client_error_statuses = [...RECOMMENDED_IMMEDIATE_CLIENT_ERROR_STATUSES];
28755
+ break;
28756
+ case "custom":
28757
+ if (clientErrorStatuses === void 0) {
28758
+ throw new CliInputError("Missing required option --client-error-statuses.");
28759
+ }
28760
+ update.immediate_client_error_statuses = parseClientErrorStatusesOption(clientErrorStatuses);
28761
+ break;
28762
+ default:
28763
+ throw new CliInputError("Invalid value for --client-error-incidents.");
28764
+ }
28765
+ }
28608
28766
  if (Object.keys(update).length === 0) {
28609
28767
  throw new CliInputError("At least one capture policy field must be provided.");
28610
28768
  }
@@ -29389,7 +29547,7 @@ ${formatUsage()}`
29389
29547
  return await (dependencies.verifyLocalCommand ?? verifyLocalCommand)(readBooleanOption(parsedArgv, "json") === true ? { json: true } : {});
29390
29548
  }
29391
29549
  if (subcommand === "cloud") {
29392
- expectNoUnknownOptions(parsedArgv, ["auth-file", "json", "project-id", "service", "environment", "max-age-minutes", "trigger-5xx"]);
29550
+ expectNoUnknownOptions(parsedArgv, ["auth-file", "json", "project-id", "service", "environment", "max-age-minutes", "trigger-5xx", "trigger-4xx"]);
29393
29551
  ensureNoExtraPositionals(parsedArgv, 2);
29394
29552
  const projectId = readStringOption(parsedArgv, "project-id");
29395
29553
  if (projectId === void 0) {
@@ -29407,6 +29565,10 @@ ${formatUsage()}`
29407
29565
  if (readBooleanOption(parsedArgv, "trigger-5xx") === true) {
29408
29566
  input.trigger5xx = true;
29409
29567
  }
29568
+ const trigger4xxStatus = readIntegerOption(parsedArgv, "trigger-4xx");
29569
+ if (trigger4xxStatus !== void 0) {
29570
+ input.trigger4xxStatus = trigger4xxStatus;
29571
+ }
29410
29572
  return await (dependencies.verifyCloudCommand ?? verifyCloudCommand)(input);
29411
29573
  }
29412
29574
  throw new CliInputError("Unknown verify command.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@debugbundle/cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "private": false,
5
5
  "description": "Command-line interface for DebugBundle",
6
6
  "license": "AGPL-3.0-only",