securewithtrace 0.49.0 → 0.51.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 (2) hide show
  1. package/dist/index.js +1659 -248
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6283,7 +6283,7 @@ var $ZodBase64 = /* @__PURE__ */ $constructor("$ZodBase64", (inst, def) => {
6283
6283
  function isValidBase64URL(data) {
6284
6284
  if (!base64url.test(data))
6285
6285
  return false;
6286
- const base644 = data.replace(/[-_]/g, (c83) => c83 === "-" ? "+" : "/");
6286
+ const base644 = data.replace(/[-_]/g, (c84) => c84 === "-" ? "+" : "/");
6287
6287
  const padded = base644.padEnd(Math.ceil(base644.length / 4) * 4, "=");
6288
6288
  return isValidBase64(padded);
6289
6289
  }
@@ -16327,9 +16327,9 @@ var ZodDate = /* @__PURE__ */ $constructor("ZodDate", (inst, def) => {
16327
16327
  inst._zod.processJSONSchema = (ctx, json2, params3) => dateProcessor(inst, ctx, json2, params3);
16328
16328
  inst.min = (value, params3) => inst.check(_gte(value, params3));
16329
16329
  inst.max = (value, params3) => inst.check(_lte(value, params3));
16330
- const c83 = inst._zod.bag;
16331
- inst.minDate = c83.minimum ? new Date(c83.minimum) : null;
16332
- inst.maxDate = c83.maximum ? new Date(c83.maximum) : null;
16330
+ const c84 = inst._zod.bag;
16331
+ inst.minDate = c84.minimum ? new Date(c84.minimum) : null;
16332
+ inst.maxDate = c84.maximum ? new Date(c84.maximum) : null;
16333
16333
  });
16334
16334
  function date3(params3) {
16335
16335
  return _date(ZodDate, params3);
@@ -18329,6 +18329,8 @@ var STAFF_NOTIFICATION_TOOLSET_VERSIONS = /* @__PURE__ */ new Set([
18329
18329
  // API_COVERAGE_TOOLSET_VERSION and its predecessor, written out because they are declared below.
18330
18330
  "api-coverage-tools-v1",
18331
18331
  "api-coverage-tools-v2",
18332
+ // SETUP_TOOLSET_VERSION, written out for the same reason.
18333
+ "setup-tools-v1",
18332
18334
  ...STAFF_NOTIFICATION_OOB_PROFILE_SUCCESSIONS.flatMap(({ successor, predecessor }) => [
18333
18335
  predecessor.toolsetVersion,
18334
18336
  successor.toolsetVersion
@@ -18654,6 +18656,21 @@ var PENTEST_CUSTOMER_INTAKE_QUESTIONS = [
18654
18656
  }
18655
18657
  ];
18656
18658
  var PENTEST_CUSTOMER_INTAKE_QUESTION_COUNT = PENTEST_CUSTOMER_INTAKE_QUESTIONS.length;
18659
+ function isIntakeAnswerPresent(value) {
18660
+ if (value === void 0 || value === null) return false;
18661
+ if (typeof value === "string") return value.trim().length > 0;
18662
+ if (Array.isArray(value)) return value.length > 0;
18663
+ return true;
18664
+ }
18665
+ function countAnsweredIntakeQuestions(answers) {
18666
+ if (!answers) return 0;
18667
+ let answered = 0;
18668
+ for (const question of PENTEST_CUSTOMER_INTAKE_QUESTIONS) {
18669
+ const keys = question.kind === "multiSelect" ? [question.key, question.notesKey] : [question.key];
18670
+ if (keys.some((key) => isIntakeAnswerPresent(answers[key]))) answered += 1;
18671
+ }
18672
+ return answered;
18673
+ }
18657
18674
  var INTAKE_ISSUE_CLASS_LABELS = {
18658
18675
  AUTHN_BYPASS: "Authn bypass",
18659
18676
  AUTHZ_IDOR: "Broken access control",
@@ -21924,23 +21941,23 @@ var ZodEffects = class extends ZodType2 {
21924
21941
  }
21925
21942
  if (effect.type === "transform") {
21926
21943
  if (ctx.common.async === false) {
21927
- const base2 = this._def.schema._parseSync({
21944
+ const base3 = this._def.schema._parseSync({
21928
21945
  data: ctx.data,
21929
21946
  path: ctx.path,
21930
21947
  parent: ctx
21931
21948
  });
21932
- if (!isValid(base2))
21933
- return base2;
21934
- const result = effect.transform(base2.value, checkCtx);
21949
+ if (!isValid(base3))
21950
+ return base3;
21951
+ const result = effect.transform(base3.value, checkCtx);
21935
21952
  if (result instanceof Promise) {
21936
21953
  throw new Error(`Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.`);
21937
21954
  }
21938
21955
  return { status: status.value, value: result };
21939
21956
  } else {
21940
- return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((base2) => {
21941
- if (!isValid(base2))
21942
- return base2;
21943
- return Promise.resolve(effect.transform(base2.value, checkCtx)).then((result) => ({ status: status.value, value: result }));
21957
+ return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((base3) => {
21958
+ if (!isValid(base3))
21959
+ return base3;
21960
+ return Promise.resolve(effect.transform(base3.value, checkCtx)).then((result) => ({ status: status.value, value: result }));
21944
21961
  });
21945
21962
  }
21946
21963
  }
@@ -22598,8 +22615,8 @@ var initContract = () => {
22598
22615
  };
22599
22616
  };
22600
22617
  var insertParamsIntoPath = ({ path: path3, params: params3 }) => {
22601
- const pathParams2 = params3;
22602
- return path3.replace(/\/?:([^/?]+)\??/g, (matched, p) => pathParams2[p] ? `${matched.startsWith("/") ? "/" : ""}${pathParams2[p]}` : "");
22618
+ const pathParams3 = params3;
22619
+ return path3.replace(/\/?:([^/?]+)\??/g, (matched, p) => pathParams3[p] ? `${matched.startsWith("/") ? "/" : ""}${pathParams3[p]}` : "");
22603
22620
  };
22604
22621
  var convertQueryParamsToUrlString = (query, json2 = false) => {
22605
22622
  const queryString = json2 ? encodeQueryParamsJson(query) : encodeQueryParams(query);
@@ -23172,7 +23189,8 @@ var AgentSessionTypeValues = [
23172
23189
  "ASSESSMENT",
23173
23190
  "COORDINATOR",
23174
23191
  "REMEDIATION",
23175
- "API_COVERAGE"
23192
+ "API_COVERAGE",
23193
+ "SETUP"
23176
23194
  ];
23177
23195
  var AgentSessionType = external_exports.enum(AgentSessionTypeValues);
23178
23196
  var AgentSessionStatusValues = [
@@ -23227,6 +23245,7 @@ var MAX_AGENT_SESSION_MAX_USD = 1500;
23227
23245
  var MAX_AGENT_SESSION_MAX_WALL_CLOCK_MIN = 24 * 60;
23228
23246
  var MAX_AGENT_SESSION_FOCUS_CHARS = 8e3;
23229
23247
  var MAX_AGENT_SESSION_FOCUS_BRIEF_CHARS = MAX_AGENT_SESSION_FOCUS_CHARS * 3;
23248
+ var MAX_SETUP_NOTES_CHARS = 8e3;
23230
23249
  var OperatorSessionPurpose = external_exports.enum(OPERATOR_PURPOSES);
23231
23250
  var CreateAgentSessionCommonSchema = external_exports.object({
23232
23251
  provider: AgentSessionProvider.optional(),
@@ -23337,7 +23356,16 @@ var CreateAgentSessionRequestSchema = external_exports.discriminatedUnion("type"
23337
23356
  CreateAgentSessionCommonSchema.extend({
23338
23357
  type: external_exports.literal("API_COVERAGE"),
23339
23358
  focus: external_exports.string().min(1).max(MAX_AGENT_SESSION_FOCUS_CHARS).optional()
23340
- })
23359
+ }),
23360
+ // SETUP: sets up a PoC engagement's application, environment, exclusions and tenant
23361
+ // accounts from the setup notes. Strict, so a client still sending the removed `focus`,
23362
+ // `tenants` or `shareLink` gets a 400 instead of a launch that silently lost its input.
23363
+ CreateAgentSessionCommonSchema.extend({
23364
+ type: external_exports.literal("SETUP"),
23365
+ /** What the customer sent and what staff want: environments, rules, logins, share links.
23366
+ * Stored encrypted and never returned by any read route. */
23367
+ notes: external_exports.string().trim().min(1).max(MAX_SETUP_NOTES_CHARS).optional()
23368
+ }).strict()
23341
23369
  ]);
23342
23370
  var SendAgentSessionMessageRequestSchema = external_exports.object({
23343
23371
  clientRequestId: external_exports.uuid().optional(),
@@ -23516,6 +23544,9 @@ var OperatorPentestContextSchema = external_exports.object({
23516
23544
  customerPriorityCategories: external_exports.array(external_exports.string()),
23517
23545
  /** Staff scope notes; defaulted so an older API's envelope still parses. */
23518
23546
  scopeNotes: external_exports.string().nullable().default(null),
23547
+ /** The rendered staff-notes block (heading and never-quote line included), or null when the
23548
+ * engagement has none or this session's type never receives them. Defaulted like `scopeNotes`. */
23549
+ staffNotes: external_exports.string().nullable().default(null),
23519
23550
  /** The customer's intake answers, rendered as child briefs carry them. */
23520
23551
  customerIntake: external_exports.string().nullable().default(null),
23521
23552
  status: PentestStatus,
@@ -27462,7 +27493,17 @@ var CustomerPentestFindingRepositorySchema = PentestFindingRepositorySchema.omit
27462
27493
  repositoryId: true
27463
27494
  });
27464
27495
  var CustomerFindingPendingRetestSchema = external_exports.discriminatedUnion("state", [
27465
- external_exports.object({ state: external_exports.literal("QUEUED"), since: external_exports.coerce.date() }),
27496
+ external_exports.object({
27497
+ state: external_exports.literal("QUEUED"),
27498
+ since: external_exports.coerce.date(),
27499
+ /**
27500
+ * Present only while the retest waits on a completed ticket's delay: when
27501
+ * it becomes due. A field on QUEUED rather than its own state because prod
27502
+ * promotes web after the API and web parses these responses, so a new
27503
+ * variant would fail the parse in the web still serving.
27504
+ */
27505
+ startsAt: external_exports.coerce.date().optional()
27506
+ }),
27466
27507
  external_exports.object({ state: external_exports.literal("RUNNING"), since: external_exports.coerce.date() }),
27467
27508
  /**
27468
27509
  * A mobile-only engagement whose finding has no usable build newer than its
@@ -28442,6 +28483,9 @@ var PentestResponseSchema = external_exports.object({
28442
28483
  /** Customer threat-model context — free text. Used by hypothesis-gen prompt. */
28443
28484
  customerConcernsContext: external_exports.string().nullable(),
28444
28485
  customerPriorityCategories: external_exports.array(CustomerPriorityCategory),
28486
+ /** Staff-only session guidance. Staff response only. `.optional()` for the same
28487
+ * rolling-deploy reason as `purchaseCapUsd`: the panel row reads Unavailable without it. */
28488
+ staffNotes: external_exports.string().nullable().optional(),
28445
28489
  hypothesisBudgetCapUsd: external_exports.number(),
28446
28490
  validationBudgetCapUsd: external_exports.number(),
28447
28491
  perHypothesisBudgetCapUsd: external_exports.number(),
@@ -28667,6 +28711,8 @@ var CreatePentestRequestSchema = external_exports.object({
28667
28711
  perHypothesisBudgetCapUsd: external_exports.number().min(0).max(1e5).optional(),
28668
28712
  purchaseCapUsd: external_exports.number().min(0).max(1e5).optional()
28669
28713
  });
28714
+ var STAFF_NOTES_MAX_CHARS = 1e4;
28715
+ var STAFF_NOTES_BRIEF_MAX_CHARS = STAFF_NOTES_MAX_CHARS * 3;
28670
28716
  var UpdatePentestRequestSchema = external_exports.object({
28671
28717
  title: external_exports.string().min(1).max(500).optional(),
28672
28718
  description: external_exports.string().max(1e4).nullable().optional(),
@@ -28688,6 +28734,8 @@ var UpdatePentestRequestSchema = external_exports.object({
28688
28734
  testType: PentestTestType.optional(),
28689
28735
  customerConcernsContext: external_exports.string().max(2e4).nullable().optional(),
28690
28736
  customerPriorityCategories: external_exports.array(CustomerPriorityCategory).max(20).optional(),
28737
+ /** Staff-only guidance for this engagement's agent sessions; `null` clears. */
28738
+ staffNotes: external_exports.string().max(STAFF_NOTES_MAX_CHARS).nullable().optional(),
28691
28739
  /** Report-cover engagement overrides. `null` clears (reverts to auto-derivation),
28692
28740
  * `undefined` leaves unchanged. Empty SUT is rejected — use `null` to clear. */
28693
28741
  systemUnderTest: external_exports.string().trim().min(1).max(200).nullable().optional(),
@@ -28816,13 +28864,13 @@ var StorageStateSchema = external_exports.object({
28816
28864
  } catch {
28817
28865
  return;
28818
28866
  }
28819
- s.cookies.forEach((c83, i2) => {
28820
- const d = c83.domain.replace(/^\./, "").toLowerCase();
28867
+ s.cookies.forEach((c84, i2) => {
28868
+ const d = c84.domain.replace(/^\./, "").toLowerCase();
28821
28869
  if (d !== host && !host.endsWith(`.${d}`)) {
28822
28870
  ctx.addIssue({
28823
28871
  code: "custom",
28824
28872
  path: ["cookies", i2, "domain"],
28825
- message: `cookie domain "${c83.domain}" is not the capture origin host "${host}" or a parent domain of it`
28873
+ message: `cookie domain "${c84.domain}" is not the capture origin host "${host}" or a parent domain of it`
28826
28874
  });
28827
28875
  }
28828
28876
  });
@@ -30156,7 +30204,27 @@ var AgentSessionCreateTestEmailResponseSchema = external_exports.object({
30156
30204
  */
30157
30205
  reused: external_exports.boolean()
30158
30206
  });
30159
- var AgentSessionSaveTestAccountRequestSchema = external_exports.object({
30207
+ var ProvidedCredentialType = external_exports.enum(CredentialTypeValues).extract(["USERNAME_PASSWORD", "API_KEY", "BEARER_TOKEN"]);
30208
+ var AgentSessionSaveProvidedAccountRequestSchema = external_exports.object({
30209
+ runVersion: external_exports.number().int().nonnegative(),
30210
+ provided: external_exports.literal(true),
30211
+ /**
30212
+ * `customer`: the customer handed it over (a share link). `created`: the agent made it, such as an
30213
+ * API key from an admin account it signed up; it is labelled like any account the agent mints, so
30214
+ * a black-box engagement does not read it as customer-supplied.
30215
+ */
30216
+ source: external_exports.enum(["customer", "created"]),
30217
+ label: external_exports.string().min(1).max(200),
30218
+ credentialType: ProvidedCredentialType,
30219
+ /** USERNAME_PASSWORD only. */
30220
+ username: external_exports.string().min(1).max(320).optional(),
30221
+ /** The password, key or token. */
30222
+ secret: external_exports.string().min(1).max(4096),
30223
+ /** API_KEY only: the header that carries the key. */
30224
+ headerName: external_exports.string().min(1).max(100).optional(),
30225
+ targetUrl: external_exports.string().min(1).max(2048).optional()
30226
+ });
30227
+ var AgentSessionSaveMintedAccountRequestSchema = external_exports.strictObject({
30160
30228
  runVersion: external_exports.number().int().nonnegative(),
30161
30229
  /** The `label` returned by `create_test_email`. */
30162
30230
  label: external_exports.string().min(1).max(200),
@@ -30184,11 +30252,18 @@ var AgentSessionSaveTestAccountRequestSchema = external_exports.object({
30184
30252
  */
30185
30253
  targetUrl: external_exports.string().min(1).max(2048).optional()
30186
30254
  });
30255
+ var AgentSessionSaveTestAccountRequestSchema = external_exports.union([
30256
+ AgentSessionSaveProvidedAccountRequestSchema,
30257
+ AgentSessionSaveMintedAccountRequestSchema
30258
+ ]);
30187
30259
  var AgentSessionSaveTestAccountResponseSchema = external_exports.object({
30188
30260
  /** Echoed so the agent names the account the same way afterwards. */
30189
30261
  label: external_exports.string().max(200),
30190
- /** `USERNAME_PASSWORD` when a password was supplied, else `MAGIC_AUTH`. */
30191
- credentialType: external_exports.enum(["USERNAME_PASSWORD", "MAGIC_AUTH"]),
30262
+ /**
30263
+ * Minted accounts: `USERNAME_PASSWORD` when a password was supplied, else `MAGIC_AUTH`.
30264
+ * Provided logins: the type the agent named.
30265
+ */
30266
+ credentialType: external_exports.enum(["USERNAME_PASSWORD", "MAGIC_AUTH", "API_KEY", "BEARER_TOKEN"]),
30192
30267
  /**
30193
30268
  * The environment the credential landed on. Authoritative, not an echo: it is
30194
30269
  * how the agent learns that its `targetUrl` matched nothing and the server
@@ -30322,6 +30397,46 @@ var RenewCloudCredentialsResponseSchema = external_exports.object({
30322
30397
  /** The most recently verified ACTIVE resource, whether or not its mint succeeded. Null when none. */
30323
30398
  gcpPrimaryResourceId: GcpPrefixedResourceIdSchema.nullable().optional()
30324
30399
  });
30400
+ var GetSessionCredentialsRequestSchema = external_exports.object({
30401
+ runVersion: external_exports.number().int().nonnegative()
30402
+ });
30403
+ var ParsedWebCredentialSchema = external_exports.union([
30404
+ external_exports.strictObject({
30405
+ reference: external_exports.literal("SESSION_STATE"),
30406
+ credentialId: external_exports.uuid(),
30407
+ environmentId: external_exports.uuid(),
30408
+ origin: external_exports.string()
30409
+ }),
30410
+ external_exports.strictObject({
30411
+ reference: external_exports.literal("SSH_PRIVATE_KEY"),
30412
+ credentialId: external_exports.uuid(),
30413
+ environmentId: external_exports.uuid(),
30414
+ host: external_exports.string(),
30415
+ port: external_exports.number().int(),
30416
+ username: external_exports.string()
30417
+ }),
30418
+ external_exports.strictObject({ username: external_exports.string(), password: external_exports.string() }),
30419
+ external_exports.strictObject({ secretValue: external_exports.string() }),
30420
+ external_exports.strictObject({ loginEmail: external_exports.string() })
30421
+ ]);
30422
+ var WebCredentialEntrySchema = external_exports.object({
30423
+ source: external_exports.literal("customer"),
30424
+ label: external_exports.string(),
30425
+ credentialType: external_exports.enum(CredentialTypeValues),
30426
+ parsed: ParsedWebCredentialSchema,
30427
+ authConfig: external_exports.record(external_exports.string(), external_exports.unknown()).nullable(),
30428
+ mfa: external_exports.object({ kind: external_exports.string() }).optional(),
30429
+ passkeys: external_exports.number().int().positive().optional()
30430
+ });
30431
+ var SessionCredentialsResponseSchema = external_exports.object({
30432
+ /** Empty when the type gets no web credentials or none resolved. */
30433
+ webCredentials: external_exports.array(WebCredentialEntrySchema),
30434
+ vercel: external_exports.object({ token: external_exports.string(), teamId: external_exports.string().nullable() }).nullable(),
30435
+ browserAllowedBaseUrls: external_exports.array(external_exports.string()),
30436
+ browserExclusions: external_exports.array(external_exports.string()),
30437
+ /** API_COVERAGE only; null otherwise. Range checks stay with the host's rate parser. */
30438
+ requestRate: external_exports.object({ perSec: external_exports.number(), burst: external_exports.number() }).nullable()
30439
+ });
30325
30440
  var ConvexReconRequestSchema = external_exports.object({
30326
30441
  runVersion: external_exports.number().int().nonnegative()
30327
30442
  });
@@ -30579,7 +30694,7 @@ var ParkAgentSessionRequestSchema = external_exports.object({
30579
30694
  * idle-lingered out (the host's only natural-completion signal); INTERRUPTED =
30580
30695
  * SIGTERM / stream-close / api-outage give-up / wall-clock. Meaningful only when
30581
30696
  * outcome === 'IDLE'. OPTIONAL for rolling-deploy tolerance: an old host omits
30582
- * it, and the api treats absent as non-natural (no VALIDATION auto-advance).
30697
+ * it, and the api treats absent as non-natural (the validation run stays RUNNING).
30583
30698
  */
30584
30699
  idleReason: external_exports.enum(["COMPLETED", "INTERRUPTED"]).optional(),
30585
30700
  waitingReason: AgentSessionWaitingReason.optional(),
@@ -31460,7 +31575,7 @@ var agentSessionInternalContract = c4.router({
31460
31575
  201: RecordValidationResultResponseSchema,
31461
31576
  400: ErrorResponseSchema,
31462
31577
  401: ErrorResponseSchema,
31463
- // runVersion/status guard, or pentest not in VALIDATION_RUNNING.
31578
+ // runVersion/status guard, an ended engagement, or no validation run for the session.
31464
31579
  409: ErrorResponseSchema,
31465
31580
  500: ErrorResponseSchema
31466
31581
  }
@@ -31912,6 +32027,21 @@ var agentSessionInternalContract = c4.router({
31912
32027
  500: ErrorResponseSchema
31913
32028
  }
31914
32029
  },
32030
+ // The session's credentials, fetched by the host at boot (AgentSessionService.getSessionCredentials).
32031
+ getSessionCredentials: {
32032
+ method: "POST",
32033
+ path: "/internal/v1/agent-sessions/:agentSessionId/session-credentials",
32034
+ pathParams: AgentSessionInternalParamsSchema,
32035
+ body: GetSessionCredentialsRequestSchema,
32036
+ responses: {
32037
+ 200: SessionCredentialsResponseSchema,
32038
+ 400: ErrorResponseSchema,
32039
+ 401: ErrorResponseSchema,
32040
+ 403: ErrorResponseSchema,
32041
+ 409: ErrorResponseSchema,
32042
+ 500: ErrorResponseSchema
32043
+ }
32044
+ },
31915
32045
  // get_convex_deployments (Convex integration PR 3): the read-only broker. Every
31916
32046
  // connected Convex deployment for the run's OWN org, each with its function
31917
32047
  // inventory. No request identifier at all — see the schema block above. No 404:
@@ -36719,6 +36849,7 @@ var LinearIntegrationStatusValues = [
36719
36849
  "DISCONNECTED"
36720
36850
  ];
36721
36851
  var linearEntityId2 = external_exports.string().min(1).max(64);
36852
+ var LINEAR_RETEST_DELAY_MINUTES_OPTIONS = [0, 60, 180, 720, 1440, 4320];
36722
36853
  var LinearIntegrationSummarySchema = external_exports.object({
36723
36854
  linearIntegrationId: external_exports.uuid(),
36724
36855
  status: external_exports.enum(LinearIntegrationStatusValues),
@@ -36728,6 +36859,12 @@ var LinearIntegrationSummarySchema = external_exports.object({
36728
36859
  defaultTeamId: external_exports.string().nullable(),
36729
36860
  defaultProjectId: external_exports.string().nullable(),
36730
36861
  defaultLabelIds: external_exports.array(external_exports.string()),
36862
+ /**
36863
+ * Optional on the wire only: prod promotes web after the API, so a web bundle
36864
+ * can read an API that predates the field. Consumers default it. Not limited
36865
+ * to the options so retiring one never makes a stored value unparseable.
36866
+ */
36867
+ retestDelayMinutes: external_exports.number().int().nonnegative().optional(),
36731
36868
  connectedAt: external_exports.iso.datetime(),
36732
36869
  connectedByName: external_exports.string().nullable(),
36733
36870
  /** Non-null → disconnect committed but Linear-side revocation didn't confirm; UI shows the confirm-removal banner. */
@@ -36759,6 +36896,9 @@ var UpdateLinearDefaultsRequestSchema = external_exports.object({
36759
36896
  defaultProjectId: linearEntityId2.nullable().optional(),
36760
36897
  defaultLabelIds: external_exports.array(linearEntityId2).max(10).optional()
36761
36898
  });
36899
+ var UpdateLinearRetestDelayRequestSchema = external_exports.object({
36900
+ retestDelayMinutes: external_exports.literal(LINEAR_RETEST_DELAY_MINUTES_OPTIONS)
36901
+ });
36762
36902
  var LinearTeamSchema = external_exports.object({
36763
36903
  id: external_exports.string(),
36764
36904
  name: external_exports.string(),
@@ -36845,6 +36985,27 @@ var linearIntegrationContract = c26.router({
36845
36985
  503: ErrorResponseSchema
36846
36986
  }
36847
36987
  },
36988
+ /**
36989
+ * Separate from the defaults PATCH, which revalidates the saved team against
36990
+ * Linear on every save: this setting is ours alone and must save while Linear
36991
+ * is unreachable or the team was archived.
36992
+ */
36993
+ updateLinearRetestDelay: {
36994
+ method: "PATCH",
36995
+ path: "/organizations/:organizationId/integrations/linear/retest-delay",
36996
+ pathParams: OrgIdParamsSchema,
36997
+ body: UpdateLinearRetestDelayRequestSchema,
36998
+ responses: {
36999
+ 200: LinearIntegrationResponseSchema,
37000
+ 400: ErrorResponseSchema,
37001
+ 401: ErrorResponseSchema,
37002
+ 403: ErrorResponseSchema,
37003
+ 404: ErrorResponseSchema,
37004
+ 409: ErrorResponseSchema,
37005
+ 500: ErrorResponseSchema,
37006
+ 503: ErrorResponseSchema
37007
+ }
37008
+ },
36848
37009
  getLinearMetadata: {
36849
37010
  method: "GET",
36850
37011
  path: "/organizations/:organizationId/integrations/linear/metadata",
@@ -41541,6 +41702,23 @@ var pentestFindingContract = c45.router({
41541
41702
  500: ErrorResponseSchema
41542
41703
  }
41543
41704
  },
41705
+ // Customer says the fix is already live for a finding whose retest waits on its
41706
+ // ticket's delay: the retest becomes due now. No status change.
41707
+ startScheduledRetest: {
41708
+ method: "POST",
41709
+ path: "/organizations/:organizationId/customer-pentests/:pentestId/findings/:pentestFindingId/start-retest",
41710
+ pathParams: CustomerPentestFindingIdParamsSchema,
41711
+ body: c45.noBody(),
41712
+ responses: {
41713
+ 200: CustomerPentestFindingResponseSchema,
41714
+ 401: ErrorResponseSchema,
41715
+ 403: ErrorResponseSchema,
41716
+ 404: ErrorResponseSchema,
41717
+ // RETEST_NOT_SCHEDULED: not REMEDIATED with a retest still waiting.
41718
+ 409: ErrorResponseSchema,
41719
+ 500: ErrorResponseSchema
41720
+ }
41721
+ },
41544
41722
  // Customer accepts the finding → ACCEPTED_WITH_CONTROLS without a dispute or a
41545
41723
  // staff decision. Human principals here; an API key sends the same transition
41546
41724
  // through `automationStatusUpdate` with status ACCEPTED_WITH_CONTROLS.
@@ -41935,6 +42113,57 @@ var PentestIntakeDocumentTypeValues = [
41935
42113
  "OTHER"
41936
42114
  ];
41937
42115
  var PentestIntakeDocumentTypeSchema = external_exports.enum(PentestIntakeDocumentTypeValues);
42116
+ var PENTEST_INTAKE_DOCUMENTS_QUESTION = "Do you have any relevant documents to share?";
42117
+ var PENTEST_INTAKE_DOCUMENT_TYPE_LABELS = {
42118
+ PREVIOUS_PENTEST: "Previous pentest report",
42119
+ ARCHITECTURE_DIAGRAM: "Architecture diagram",
42120
+ THREAT_MODEL: "Threat model",
42121
+ OTHER: "Other"
42122
+ };
42123
+ function guessPentestIntakeDocumentType(fileName) {
42124
+ const name = fileName.toLowerCase();
42125
+ if (/threat[\s_-]*model|stride|attack[\s_-]*tree|dfd/.test(name)) return "THREAT_MODEL";
42126
+ if (/pen[\s_-]*test|pentest|penetration|security[\s_-]*assessment|vapt|findings/.test(name)) {
42127
+ return "PREVIOUS_PENTEST";
42128
+ }
42129
+ if (/arch|diagram|topology|infra|system[\s_-]*design|c4|\.drawio/.test(name)) {
42130
+ return "ARCHITECTURE_DIAGRAM";
42131
+ }
42132
+ return "OTHER";
42133
+ }
42134
+ var INTAKE_DOCUMENT_MIME_BY_EXTENSION = {
42135
+ pdf: "application/pdf",
42136
+ doc: "application/msword",
42137
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
42138
+ xls: "application/vnd.ms-excel",
42139
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
42140
+ ppt: "application/vnd.ms-powerpoint",
42141
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
42142
+ txt: "text/plain",
42143
+ md: "text/markdown",
42144
+ csv: "text/csv",
42145
+ json: "application/json",
42146
+ xml: "application/xml",
42147
+ // text/yaml rather than application/yaml: readers that only accept text/* still take it.
42148
+ yaml: "text/yaml",
42149
+ yml: "text/yaml",
42150
+ html: "text/html",
42151
+ htm: "text/html",
42152
+ png: "image/png",
42153
+ jpg: "image/jpeg",
42154
+ jpeg: "image/jpeg",
42155
+ gif: "image/gif",
42156
+ svg: "image/svg+xml",
42157
+ webp: "image/webp",
42158
+ zip: "application/zip",
42159
+ drawio: "application/xml",
42160
+ vsdx: "application/vnd.ms-visio.drawing"
42161
+ };
42162
+ function pentestIntakeDocumentMimeType(fileName) {
42163
+ const dot = fileName.lastIndexOf(".");
42164
+ const extension = dot === -1 ? "" : fileName.slice(dot + 1).toLowerCase();
42165
+ return INTAKE_DOCUMENT_MIME_BY_EXTENSION[extension] ?? "application/octet-stream";
42166
+ }
41938
42167
  var MAX_PENTEST_INTAKE_DOCUMENT_BYTES = 50 * 1024 * 1024;
41939
42168
  var PentestIntakeDocumentIdParamsSchema = CustomerPentestIdParamsSchema.extend({
41940
42169
  documentId: external_exports.uuid()
@@ -46227,16 +46456,16 @@ var COVERAGE_CLASSES = [
46227
46456
  },
46228
46457
  { taxonomy: "RECON", id: "RECON-15", name: "Deployment-platform config", shortLabel: "Deploy" }
46229
46458
  ];
46230
- var BY_ID = new Map(COVERAGE_CLASSES.map((c83) => [c83.id, c83]));
46459
+ var BY_ID = new Map(COVERAGE_CLASSES.map((c84) => [c84.id, c84]));
46231
46460
  function idsWhere(keep) {
46232
- return [OTHER_CLASS_ID, ...COVERAGE_CLASSES.filter(keep).map((c83) => c83.id)];
46461
+ return [OTHER_CLASS_ID, ...COVERAGE_CLASSES.filter(keep).map((c84) => c84.id)];
46233
46462
  }
46234
- var HUNTING_CLASS_IDS = idsWhere((c83) => c83.taxonomy !== "RECON");
46235
- var RECON_CLASS_IDS = idsWhere((c83) => c83.taxonomy === "RECON");
46463
+ var HUNTING_CLASS_IDS = idsWhere((c84) => c84.taxonomy !== "RECON");
46464
+ var RECON_CLASS_IDS = idsWhere((c84) => c84.taxonomy === "RECON");
46236
46465
  var HUNTING_ID_SET = new Set(HUNTING_CLASS_IDS);
46237
46466
  var RECON_ID_SET = new Set(RECON_CLASS_IDS);
46238
46467
  var TAXONOMY_ORDER = new Map(CoverageTaxonomyValues.map((t2, i2) => [t2, i2]));
46239
- var CLASS_ORDER = new Map(COVERAGE_CLASSES.map((c83, i2) => [c83.id, i2]));
46468
+ var CLASS_ORDER = new Map(COVERAGE_CLASSES.map((c84, i2) => [c84.id, i2]));
46240
46469
 
46241
46470
  // ../../packages/api-contracts/src/contracts/v1/api-coverage-internal.ts
46242
46471
  var c80 = initContract();
@@ -46309,6 +46538,135 @@ var apiCoverageInternalContract = c80.router({
46309
46538
  }
46310
46539
  });
46311
46540
 
46541
+ // ../../packages/api-contracts/src/schemas/api/v1/setup-session-internal.api.ts
46542
+ var RunVersionSchema2 = external_exports.number().int().nonnegative();
46543
+ var SetupTargetType = external_exports.enum(ApplicationTypeValues).extract(["WEB", "API_SERVICE"]);
46544
+ var ReasonSchema = external_exports.string().min(1).max(500);
46545
+ var SetupVerificationStatusValues = [
46546
+ "UNVERIFIED",
46547
+ "PENDING",
46548
+ "VERIFIED",
46549
+ "FAILED",
46550
+ "INCONCLUSIVE"
46551
+ ];
46552
+ var SetupVerificationStatus = external_exports.enum(SetupVerificationStatusValues);
46553
+ var SetupAddTargetRequestSchema = external_exports.object({
46554
+ runVersion: RunVersionSchema2,
46555
+ reason: ReasonSchema,
46556
+ /** The application's name in Trace. An existing org application with this name is reused. */
46557
+ name: external_exports.string().min(1).max(200),
46558
+ type: SetupTargetType,
46559
+ /** The environment's login or base URL. */
46560
+ url: external_exports.url({ protocol: /^https?$/ }).max(2048),
46561
+ environmentName: external_exports.string().min(1).max(100),
46562
+ isProduction: external_exports.boolean()
46563
+ });
46564
+ var SetupAddTargetResponseSchema = external_exports.object({
46565
+ applicationName: external_exports.string(),
46566
+ environmentName: external_exports.string(),
46567
+ created: external_exports.object({
46568
+ application: external_exports.boolean(),
46569
+ environment: external_exports.boolean(),
46570
+ binding: external_exports.boolean(),
46571
+ selection: external_exports.boolean()
46572
+ }),
46573
+ /** The URL's registrable domain differs from the org website's: list it in the summary. */
46574
+ offHomepageDomain: external_exports.boolean()
46575
+ });
46576
+ var SetupAddExclusionRequestSchema = external_exports.object({
46577
+ runVersion: RunVersionSchema2,
46578
+ reason: ReasonSchema,
46579
+ expression: external_exports.string().min(1).max(500)
46580
+ });
46581
+ var SetupAddExclusionResponseSchema = external_exports.object({
46582
+ /** false when the same exclusion was already on the engagement. */
46583
+ created: external_exports.boolean()
46584
+ });
46585
+ var SetupUpdateEngagementRequestSchema = external_exports.object({
46586
+ runVersion: RunVersionSchema2,
46587
+ reason: ReasonSchema,
46588
+ testType: PentestTestType.optional(),
46589
+ // The same ranges the staff engagement form enforces: a rate outside them turns API coverage's
46590
+ // sender off at launch. There is no description field: it is shown to the customer. Not
46591
+ // nullable: setup only adds, so it can set a limit but never clear one.
46592
+ requestRatePerSec: external_exports.number().min(REQUEST_RATE_PER_SEC_RANGE.min).max(REQUEST_RATE_PER_SEC_RANGE.max).optional(),
46593
+ requestBurst: external_exports.number().int().min(REQUEST_BURST_RANGE.min).max(REQUEST_BURST_RANGE.max).optional()
46594
+ });
46595
+ var SetupVerifyRequestSchema = external_exports.object({
46596
+ runVersion: RunVersionSchema2,
46597
+ action: external_exports.enum(["start", "status"])
46598
+ });
46599
+ var SetupVerifyResponseSchema = external_exports.object({
46600
+ applications: external_exports.array(
46601
+ external_exports.object({
46602
+ applicationName: external_exports.string(),
46603
+ /** What `start` did for this application; null on `status`. */
46604
+ launch: external_exports.enum(["STARTED", "IN_FLIGHT", "NOTHING_TO_CHECK"]).nullable(),
46605
+ environments: external_exports.array(external_exports.object({ name: external_exports.string(), status: SetupVerificationStatus })),
46606
+ credentials: external_exports.array(
46607
+ external_exports.object({
46608
+ label: external_exports.string(),
46609
+ environmentName: external_exports.string(),
46610
+ status: SetupVerificationStatus
46611
+ })
46612
+ )
46613
+ })
46614
+ )
46615
+ });
46616
+ var SetupNotesRequestSchema = external_exports.object({ runVersion: RunVersionSchema2 });
46617
+ var SetupNotesResponseSchema = external_exports.object({ notes: external_exports.string().nullable() });
46618
+
46619
+ // ../../packages/api-contracts/src/contracts/v1/setup-session-internal.ts
46620
+ var c81 = initContract();
46621
+ var pathParams2 = external_exports.strictObject({ agentSessionId: external_exports.uuid() });
46622
+ var errors6 = {
46623
+ 400: ErrorResponseSchema,
46624
+ 401: ErrorResponseSchema,
46625
+ 403: ErrorResponseSchema,
46626
+ 404: ErrorResponseSchema,
46627
+ 409: ErrorResponseSchema,
46628
+ 500: ErrorResponseSchema,
46629
+ 503: ErrorResponseSchema
46630
+ };
46631
+ var base2 = "/internal/v1/agent-sessions/:agentSessionId/setup";
46632
+ var setupSessionInternalContract = c81.router({
46633
+ addSetupTarget: {
46634
+ method: "POST",
46635
+ path: `${base2}/target`,
46636
+ pathParams: pathParams2,
46637
+ body: SetupAddTargetRequestSchema,
46638
+ responses: { 200: SetupAddTargetResponseSchema, ...errors6 }
46639
+ },
46640
+ addSetupExclusion: {
46641
+ method: "POST",
46642
+ path: `${base2}/exclusion`,
46643
+ pathParams: pathParams2,
46644
+ body: SetupAddExclusionRequestSchema,
46645
+ responses: { 200: SetupAddExclusionResponseSchema, ...errors6 }
46646
+ },
46647
+ updateSetupEngagement: {
46648
+ method: "POST",
46649
+ path: `${base2}/engagement`,
46650
+ pathParams: pathParams2,
46651
+ body: SetupUpdateEngagementRequestSchema,
46652
+ responses: { 200: OperatorPentestContextSchema, ...errors6 }
46653
+ },
46654
+ verifySetup: {
46655
+ method: "POST",
46656
+ path: `${base2}/verify`,
46657
+ pathParams: pathParams2,
46658
+ body: SetupVerifyRequestSchema,
46659
+ responses: { 200: SetupVerifyResponseSchema, ...errors6 }
46660
+ },
46661
+ getSetupNotes: {
46662
+ method: "POST",
46663
+ path: `${base2}/notes`,
46664
+ pathParams: pathParams2,
46665
+ body: SetupNotesRequestSchema,
46666
+ responses: { 200: SetupNotesResponseSchema, ...errors6 }
46667
+ }
46668
+ });
46669
+
46312
46670
  // ../../packages/api-contracts/src/schemas/api/v1/premise-check.api.ts
46313
46671
  var PREMISE_VERDICTS = ["supported", "contradicted", "cant_tell"];
46314
46672
  var EXTRACTED_PREMISE_KINDS = [
@@ -46398,8 +46756,8 @@ var ListReviewPremiseChecksResponseSchema = external_exports.object({
46398
46756
  });
46399
46757
 
46400
46758
  // ../../packages/api-contracts/src/contracts/v1/premise-check-internal.ts
46401
- var c81 = initContract();
46402
- var premiseCheckInternalContract = c81.router({
46759
+ var c82 = initContract();
46760
+ var premiseCheckInternalContract = c82.router({
46403
46761
  listReviewPremiseChecks: {
46404
46762
  method: "POST",
46405
46763
  path: "/internal/v1/agent-sessions/:agentSessionId/premise-checks/list",
@@ -48261,11 +48619,11 @@ function buildCloneTokenFailureWarning(cloneUrl, failure) {
48261
48619
  return `Trace returned an unexpected response while generating clone credentials for ${cloneUrl}${quotedMessage ? ` ${quotedMessage}` : ""}. ${fallbackSuffix}`;
48262
48620
  }
48263
48621
  }
48264
- function withOptionalMessage(base2, message) {
48622
+ function withOptionalMessage(base3, message) {
48265
48623
  if (typeof message === "string") {
48266
- return { ...base2, message };
48624
+ return { ...base3, message };
48267
48625
  }
48268
- return base2;
48626
+ return base3;
48269
48627
  }
48270
48628
  async function listOrganizationsSafe() {
48271
48629
  const result = await authenticatedRequestSafe(
@@ -49083,6 +49441,13 @@ function parseOutput(value) {
49083
49441
  console.error(colorError(`Invalid --output '${value}'. Use table, json, or tsv.`));
49084
49442
  process.exit(1);
49085
49443
  }
49444
+ function parseTextOutput(value) {
49445
+ if (value === "text" || value === "json") {
49446
+ return value;
49447
+ }
49448
+ console.error(colorError(`Invalid --output '${sanitizeDisplay(value)}'. Use text or json.`));
49449
+ process.exit(1);
49450
+ }
49086
49451
  var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
49087
49452
  function isUuid(value) {
49088
49453
  return UUID_PATTERN.test(value);
@@ -49316,12 +49681,12 @@ function buildApplicationsTable(apps) {
49316
49681
  repos: String(a2.repositories.length),
49317
49682
  url: a2.applicationUrl ? sanitizeDisplay(a2.applicationUrl) : "\u2014"
49318
49683
  }));
49319
- const idWidth = Math.max(2, ...cells.map((c83) => c83.id.length));
49320
- const nameWidth = Math.max(4, ...cells.map((c83) => c83.name.length));
49321
- const typeWidth = Math.max(4, ...cells.map((c83) => c83.type.length));
49684
+ const idWidth = Math.max(2, ...cells.map((c84) => c84.id.length));
49685
+ const nameWidth = Math.max(4, ...cells.map((c84) => c84.name.length));
49686
+ const typeWidth = Math.max(4, ...cells.map((c84) => c84.type.length));
49322
49687
  const header = `${"ID".padEnd(idWidth)} ${"NAME".padEnd(nameWidth)} ${"TYPE".padEnd(typeWidth)} REPOS URL`;
49323
49688
  const rows = cells.map(
49324
- (c83) => `${c83.id.padEnd(idWidth)} ${c83.name.padEnd(nameWidth)} ${c83.type.padEnd(typeWidth)} ${c83.repos.padStart(5)} ${c83.url}`
49689
+ (c84) => `${c84.id.padEnd(idWidth)} ${c84.name.padEnd(nameWidth)} ${c84.type.padEnd(typeWidth)} ${c84.repos.padStart(5)} ${c84.url}`
49325
49690
  );
49326
49691
  return { header, rows };
49327
49692
  }
@@ -49341,12 +49706,12 @@ function buildApplicationRepositoriesTable(links) {
49341
49706
  path: l2.repositoryPath ? sanitizeDisplay(l2.repositoryPath) : "\u2014",
49342
49707
  status: formatStatus(l2.repositoryStatus)
49343
49708
  }));
49344
- const nameWidth = Math.max(10, ...cells.map((c83) => c83.name.length));
49345
- const roleWidth = Math.max(4, ...cells.map((c83) => c83.role.length));
49346
- const pathWidth = Math.max(4, ...cells.map((c83) => c83.path.length));
49709
+ const nameWidth = Math.max(10, ...cells.map((c84) => c84.name.length));
49710
+ const roleWidth = Math.max(4, ...cells.map((c84) => c84.role.length));
49711
+ const pathWidth = Math.max(4, ...cells.map((c84) => c84.path.length));
49347
49712
  const header = `${"REPOSITORY".padEnd(nameWidth)} SCAN ${"ROLE".padEnd(roleWidth)} ${"PATH".padEnd(pathWidth)} STATUS`;
49348
49713
  const rows = cells.map(
49349
- (c83) => `${c83.name.padEnd(nameWidth)} ${c83.scan.padEnd(4)} ${c83.role.padEnd(roleWidth)} ${c83.path.padEnd(pathWidth)} ${c83.status}`
49714
+ (c84) => `${c84.name.padEnd(nameWidth)} ${c84.scan.padEnd(4)} ${c84.role.padEnd(roleWidth)} ${c84.path.padEnd(pathWidth)} ${c84.status}`
49350
49715
  );
49351
49716
  return { header, rows };
49352
49717
  }
@@ -50131,8 +50496,8 @@ function wrapTextWithPrefix(e, r2, o2, t2 = o2, s = o2, n3) {
50131
50496
  hard: true,
50132
50497
  trim: false
50133
50498
  }).split(`
50134
- `).map((c83, i2, m2) => {
50135
- const d = n3 ? n3(c83, i2) : c83;
50499
+ `).map((c84, i2, m2) => {
50500
+ const d = n3 ? n3(c84, i2) : c84;
50136
50501
  return i2 === 0 ? `${t2}${d}` : i2 === m2.length - 1 ? `${s}${d}` : `${o2}${d}`;
50137
50502
  }).join(`
50138
50503
  `);
@@ -50630,14 +50995,14 @@ var limitOptions = ({
50630
50995
  Math.min(l2 - a2 + 3, e.length - a2),
50631
50996
  0
50632
50997
  ));
50633
- let d = a2 < e.length && f > 0, c83 = a2 < e.length && f + a2 < e.length;
50998
+ let d = a2 < e.length && f > 0, c84 = a2 < e.length && f + a2 < e.length;
50634
50999
  const W = Math.min(
50635
51000
  f + a2,
50636
51001
  e.length
50637
51002
  ), s = [];
50638
51003
  let g = 0;
50639
- d && g++, c83 && g++;
50640
- const T = f + (d ? 1 : 0), y = W - (c83 ? 1 : 0);
51004
+ d && g++, c84 && g++;
51005
+ const T = f + (d ? 1 : 0), y = W - (c84 ? 1 : 0);
50641
51006
  for (let t2 = T; t2 < y; t2++) {
50642
51007
  const n3 = e[t2], o2 = n3 ? w(n3, t2 === l2) : "", h2 = wrapAnsi(o2, i2, {
50643
51008
  hard: true,
@@ -50658,14 +51023,14 @@ var limitOptions = ({
50658
51023
  u4,
50659
51024
  true
50660
51025
  );
50661
- d ? ({ lineCount: o2, removals: t2 } = L(), o2 > u4 && (c83 || (u4 -= 1), { lineCount: o2, removals: n3 } = E())) : (c83 || (u4 -= 1), { lineCount: o2, removals: n3 } = E(), o2 > u4 && (u4 -= 1, { lineCount: o2, removals: t2 } = L())), t2 > 0 && (d = true, s.splice(0, t2)), n3 > 0 && (c83 = true, s.splice(s.length - n3, n3));
51026
+ d ? ({ lineCount: o2, removals: t2 } = L(), o2 > u4 && (c84 || (u4 -= 1), { lineCount: o2, removals: n3 } = E())) : (c84 || (u4 -= 1), { lineCount: o2, removals: n3 } = E(), o2 > u4 && (u4 -= 1, { lineCount: o2, removals: t2 } = L())), t2 > 0 && (d = true, s.splice(0, t2)), n3 > 0 && (c84 = true, s.splice(s.length - n3, n3));
50662
51027
  }
50663
51028
  const x = [];
50664
51029
  d && x.push(M);
50665
51030
  for (const t2 of s)
50666
51031
  for (const n3 of t2)
50667
51032
  x.push(n3);
50668
- return c83 && x.push(M), x;
51033
+ return c84 && x.push(M), x;
50669
51034
  };
50670
51035
  var confirm = (i2) => {
50671
51036
  const a2 = i2.active ?? "Yes", s = i2.inactive ?? "No";
@@ -50684,15 +51049,15 @@ var confirm = (i2) => {
50684
51049
  u4
50685
51050
  ), o2 = `${e ? `${styleText2("gray", S_BAR)}
50686
51051
  ` : ""}${f}
50687
- `, c83 = this.value ? a2 : s;
51052
+ `, c84 = this.value ? a2 : s;
50688
51053
  switch (this.state) {
50689
51054
  case "submit": {
50690
51055
  const r2 = e ? `${styleText2("gray", S_BAR)} ` : "";
50691
- return `${o2}${r2}${styleText2("dim", c83)}`;
51056
+ return `${o2}${r2}${styleText2("dim", c84)}`;
50692
51057
  }
50693
51058
  case "cancel": {
50694
51059
  const r2 = e ? `${styleText2("gray", S_BAR)} ` : "";
50695
- return `${o2}${r2}${styleText2(["strikethrough", "dim"], c83)}${e ? `
51060
+ return `${o2}${r2}${styleText2(["strikethrough", "dim"], c84)}${e ? `
50696
51061
  ${styleText2("gray", S_BAR)}` : ""}`;
50697
51062
  }
50698
51063
  default: {
@@ -50840,13 +51205,13 @@ var multiline = (e) => new h({
50840
51205
  `, m2 = e.placeholder && e.placeholder.length > 0 ? (
50841
51206
  // biome-ignore lint/style/noNonNullAssertion: guarded by placeholder.length > 0
50842
51207
  styleText2("inverse", e.placeholder[0]) + styleText2("dim", e.placeholder.slice(1))
50843
- ) : styleText2(["inverse", "hidden"], "_"), a2 = this.userInput ? this.userInputWithCursor : m2, l2 = this.value ?? "", c83 = e.showSubmit ? `
51208
+ ) : styleText2(["inverse", "hidden"], "_"), a2 = this.userInput ? this.userInputWithCursor : m2, l2 = this.value ?? "", c84 = e.showSubmit ? `
50844
51209
  ${styleText2(this.focused === "submit" ? "cyan" : "dim", "[ submit ]")}` : "";
50845
51210
  switch (this.state) {
50846
51211
  case "error": {
50847
51212
  const n3 = `${styleText2("yellow", S_BAR)} `, r2 = i2 ? wrapTextWithPrefix(e.output, a2, n3, void 0) : a2, u4 = styleText2("yellow", S_BAR_END);
50848
51213
  return `${o2}${r2}
50849
- ${u4} ${styleText2("yellow", this.error)}${c83}
51214
+ ${u4} ${styleText2("yellow", this.error)}${c84}
50850
51215
  `;
50851
51216
  }
50852
51217
  case "submit": {
@@ -50874,7 +51239,7 @@ ${u4} ${styleText2("yellow", this.error)}${c83}
50874
51239
  default: {
50875
51240
  const n3 = i2 ? `${styleText2("cyan", S_BAR)} ` : "", r2 = i2 ? styleText2("cyan", S_BAR_END) : "", u4 = i2 ? wrapTextWithPrefix(e.output, a2, n3) : a2;
50876
51241
  return `${o2}${u4}
50877
- ${r2}${c83}
51242
+ ${r2}${c84}
50878
51243
  `;
50879
51244
  }
50880
51245
  }
@@ -50889,7 +51254,7 @@ var password = (r2) => new u$1({
50889
51254
  render() {
50890
51255
  const e = r2.withGuide ?? settings.withGuide, o2 = `${e ? `${styleText2("gray", S_BAR)}
50891
51256
  ` : ""}${symbol2(this.state)} ${r2.message}
50892
- `, c83 = this.userInputWithCursor, i2 = this.masked;
51257
+ `, c84 = this.userInputWithCursor, i2 = this.masked;
50893
51258
  switch (this.state) {
50894
51259
  case "error": {
50895
51260
  const s = e ? `${styleText2("yellow", S_BAR)} ` : "", n3 = e ? `${styleText2("yellow", S_BAR_END)} ` : "", l2 = i2 ?? "";
@@ -50909,7 +51274,7 @@ ${styleText2("gray", S_BAR)}` : ""}`;
50909
51274
  }
50910
51275
  default: {
50911
51276
  const s = e ? `${styleText2("cyan", S_BAR)} ` : "", n3 = e ? styleText2("cyan", S_BAR_END) : "";
50912
- return `${o2}${s}${c83}
51277
+ return `${o2}${s}${c84}
50913
51278
  ${n3}
50914
51279
  `;
50915
51280
  }
@@ -50925,7 +51290,7 @@ var SELECT_INSTRUCTIONS = [
50925
51290
  `${styleText2("dim", "\u2191/\u2193")} to navigate`,
50926
51291
  `${styleText2("dim", "Enter:")} confirm`
50927
51292
  ];
50928
- var c82 = (t2, o2) => t2.includes(`
51293
+ var c83 = (t2, o2) => t2.includes(`
50929
51294
  `) ? t2.split(`
50930
51295
  `).map((d) => o2(d)).join(`
50931
51296
  `) : o2(t2);
@@ -50936,15 +51301,15 @@ var select = (t2) => {
50936
51301
  const s = n3.label ?? String(n3.value);
50937
51302
  switch (m2) {
50938
51303
  case "disabled":
50939
- return `${styleText2("gray", S_RADIO_INACTIVE)} ${c82(s, (i2) => styleText2("gray", i2))}${n3.hint ? ` ${styleText2("dim", `(${n3.hint ?? "disabled"})`)}` : ""}`;
51304
+ return `${styleText2("gray", S_RADIO_INACTIVE)} ${c83(s, (i2) => styleText2("gray", i2))}${n3.hint ? ` ${styleText2("dim", `(${n3.hint ?? "disabled"})`)}` : ""}`;
50940
51305
  case "selected":
50941
- return `${c82(s, (i2) => styleText2("dim", i2))}`;
51306
+ return `${c83(s, (i2) => styleText2("dim", i2))}`;
50942
51307
  case "active":
50943
51308
  return `${styleText2("green", S_RADIO_ACTIVE)} ${s}${n3.hint ? ` ${styleText2("dim", `(${n3.hint})`)}` : ""}`;
50944
51309
  case "cancelled":
50945
- return `${c82(s, (i2) => styleText2(["strikethrough", "dim"], i2))}`;
51310
+ return `${c83(s, (i2) => styleText2(["strikethrough", "dim"], i2))}`;
50946
51311
  default:
50947
- return `${styleText2("dim", S_RADIO_INACTIVE)} ${c82(s, (i2) => styleText2("dim", i2))}`;
51312
+ return `${styleText2("dim", S_RADIO_INACTIVE)} ${c83(s, (i2) => styleText2("dim", i2))}`;
50948
51313
  }
50949
51314
  }, d = t2.showInstructions ?? true;
50950
51315
  return new n$1({
@@ -51817,7 +52182,7 @@ function buildAuthStatusInfo(credential, refreshToken, selectedOrganizationId) {
51817
52182
  };
51818
52183
  }
51819
52184
  const token = credential.token;
51820
- const base2 = {
52185
+ const base3 = {
51821
52186
  loggedIn: true,
51822
52187
  authMethod: "user",
51823
52188
  expired: false,
@@ -51828,20 +52193,20 @@ function buildAuthStatusInfo(credential, refreshToken, selectedOrganizationId) {
51828
52193
  const parts = token.split(".");
51829
52194
  const payload = parts[1];
51830
52195
  if (!payload) {
51831
- return base2;
52196
+ return base3;
51832
52197
  }
51833
52198
  const decoded = JSON.parse(Buffer.from(payload, "base64url").toString("utf-8"));
51834
52199
  const expiry = decoded.exp ? new Date(decoded.exp * 1e3) : null;
51835
52200
  const isExpired = expiry ? expiry < /* @__PURE__ */ new Date() : false;
51836
52201
  return {
51837
- ...base2,
52202
+ ...base3,
51838
52203
  ...decoded.sub ? { userId: decoded.sub } : {},
51839
52204
  ...decoded.org_id ? { orgId: decoded.org_id } : {},
51840
52205
  ...expiry ? { expires: expiry.toISOString() } : {},
51841
52206
  expired: isExpired
51842
52207
  };
51843
52208
  } catch {
51844
- return base2;
52209
+ return base3;
51845
52210
  }
51846
52211
  }
51847
52212
  function createStatusCommand() {
@@ -52441,7 +52806,7 @@ function extractCommandTree(cmd) {
52441
52806
  description: o2.description,
52442
52807
  takesValue: o2.required || o2.optional
52443
52808
  }));
52444
- const subcommands = cmd.commands.filter((c83) => !c83._hidden).map((c83) => extractCommandTree(c83));
52809
+ const subcommands = cmd.commands.filter((c84) => !c84._hidden).map((c84) => extractCommandTree(c84));
52445
52810
  return {
52446
52811
  name: cmd.name(),
52447
52812
  description: cmd.description(),
@@ -52507,7 +52872,7 @@ function generateBashCompletion(tree, binName) {
52507
52872
  }
52508
52873
  function collectBashCases(tree, prefix) {
52509
52874
  const cases = /* @__PURE__ */ new Map();
52510
- const subcommandNames = tree.subcommands.map((c83) => c83.name);
52875
+ const subcommandNames = tree.subcommands.map((c84) => c84.name);
52511
52876
  const optionNames = tree.options.flatMap((o2) => o2.short ? [o2.long, o2.short] : [o2.long]);
52512
52877
  const extraOptions = prefix === "" ? ["--help", "--version"] : ["--help"];
52513
52878
  const allOptions = [.../* @__PURE__ */ new Set([...optionNames, ...extraOptions])];
@@ -52922,9 +53287,9 @@ function buildInboxesTable(inboxes) {
52922
53287
  address: sanitizeDisplay(inbox.address),
52923
53288
  label: sanitizeDisplay(inbox.label)
52924
53289
  }));
52925
- const addressWidth = Math.max(7, ...cells.map((c83) => c83.address.length));
53290
+ const addressWidth = Math.max(7, ...cells.map((c84) => c84.address.length));
52926
53291
  const header = `${"ADDRESS".padEnd(addressWidth)} LABEL`;
52927
- const rows = cells.map((c83) => `${c83.address.padEnd(addressWidth)} ${c83.label}`);
53292
+ const rows = cells.map((c84) => `${c84.address.padEnd(addressWidth)} ${c84.label}`);
52928
53293
  return { header, rows };
52929
53294
  }
52930
53295
  function formatInboxQuota(quota) {
@@ -52948,14 +53313,14 @@ function buildInboxEmailsTable(messages) {
52948
53313
  subject: sanitizeDisplay(message.subject ?? ""),
52949
53314
  unread: message.unread ? "yes" : "no"
52950
53315
  }));
52951
- const idWidth = Math.max(2, ...cells.map((c83) => c83.id.length));
52952
- const receivedWidth = Math.max(8, ...cells.map((c83) => c83.received.length));
52953
- const fromWidth = Math.max(4, ...cells.map((c83) => c83.from.length));
52954
- const toWidth = Math.max(2, ...cells.map((c83) => c83.to.length));
52955
- const subjectWidth = Math.max(7, ...cells.map((c83) => c83.subject.length));
53316
+ const idWidth = Math.max(2, ...cells.map((c84) => c84.id.length));
53317
+ const receivedWidth = Math.max(8, ...cells.map((c84) => c84.received.length));
53318
+ const fromWidth = Math.max(4, ...cells.map((c84) => c84.from.length));
53319
+ const toWidth = Math.max(2, ...cells.map((c84) => c84.to.length));
53320
+ const subjectWidth = Math.max(7, ...cells.map((c84) => c84.subject.length));
52956
53321
  const header = `${"ID".padEnd(idWidth)} ${"RECEIVED".padEnd(receivedWidth)} ${"FROM".padEnd(fromWidth)} ${"TO".padEnd(toWidth)} ${"SUBJECT".padEnd(subjectWidth)} UNREAD`;
52957
53322
  const rows = cells.map(
52958
- (c83) => `${c83.id.padEnd(idWidth)} ${c83.received.padEnd(receivedWidth)} ${c83.from.padEnd(fromWidth)} ${c83.to.padEnd(toWidth)} ${c83.subject.padEnd(subjectWidth)} ${c83.unread}`
53323
+ (c84) => `${c84.id.padEnd(idWidth)} ${c84.received.padEnd(receivedWidth)} ${c84.from.padEnd(fromWidth)} ${c84.to.padEnd(toWidth)} ${c84.subject.padEnd(subjectWidth)} ${c84.unread}`
52959
53324
  );
52960
53325
  return { header, rows };
52961
53326
  }
@@ -53698,6 +54063,13 @@ function mfaDisplay(mfa) {
53698
54063
  if (mfa.algorithm !== "SHA1") parts.push(mfa.algorithm);
53699
54064
  return parts.join(" \xB7 ");
53700
54065
  }
54066
+ function sourceDisplay(place) {
54067
+ return `${sanitizeDisplay(place.applicationName)} / ${sanitizeDisplay(place.environmentName)}`;
54068
+ }
54069
+ function linkedIntoDisplay(linkedInto) {
54070
+ if (!linkedInto || linkedInto.length === 0) return "(none)";
54071
+ return linkedInto.map(sourceDisplay).join(", ");
54072
+ }
53701
54073
  function renderCredentialDetail(cred) {
53702
54074
  const pad = 13;
53703
54075
  return [
@@ -53707,6 +54079,7 @@ function renderCredentialDetail(cred) {
53707
54079
  `${"Environment:".padEnd(pad)} ${cred.environmentId}`,
53708
54080
  `${"Auth config:".padEnd(pad)} ${authConfigDisplay(cred.authConfig)}`,
53709
54081
  `${"MFA:".padEnd(pad)} ${mfaDisplay(cred.mfa)}`,
54082
+ `${"Linked into:".padEnd(pad)} ${linkedIntoDisplay(cred.linkedInto)}`,
53710
54083
  `${"Created:".padEnd(pad)} ${formatDate(cred.createdAt)}`
53711
54084
  ].join("\n");
53712
54085
  }
@@ -53730,16 +54103,16 @@ function printCredentialsTsv(creds) {
53730
54103
  }
53731
54104
  }
53732
54105
  function buildCredentialsTable(creds) {
53733
- const cells = creds.map((c83) => ({
53734
- id: c83.credentialId,
53735
- label: sanitizeDisplay(c83.label),
53736
- type: c83.credentialType
54106
+ const cells = creds.map((c84) => ({
54107
+ id: c84.credentialId,
54108
+ label: sanitizeDisplay(c84.label),
54109
+ type: c84.credentialType
53737
54110
  }));
53738
- const idWidth = Math.max(2, ...cells.map((c83) => c83.id.length));
53739
- const labelWidth = Math.max(5, ...cells.map((c83) => c83.label.length));
54111
+ const idWidth = Math.max(2, ...cells.map((c84) => c84.id.length));
54112
+ const labelWidth = Math.max(5, ...cells.map((c84) => c84.label.length));
53740
54113
  const header = `${"ID".padEnd(idWidth)} ${"LABEL".padEnd(labelWidth)} TYPE`;
53741
54114
  const rows = cells.map(
53742
- (c83) => `${c83.id.padEnd(idWidth)} ${c83.label.padEnd(labelWidth)} ${c83.type}`
54115
+ (c84) => `${c84.id.padEnd(idWidth)} ${c84.label.padEnd(labelWidth)} ${c84.type}`
53743
54116
  );
53744
54117
  return { header, rows };
53745
54118
  }
@@ -54174,6 +54547,512 @@ async function runDelete3(credentialId, options) {
54174
54547
  }
54175
54548
  console.log(colorSuccess(`Deleted credential ${credentialId}`));
54176
54549
  }
54550
+ function failLinkOp(op, status, body, notFoundLabel) {
54551
+ const { message, errorCode } = readErrorFields(body);
54552
+ if (status === UNAUTHORIZED_STATUS2) {
54553
+ exitAuthFailed();
54554
+ }
54555
+ if (status === 403) {
54556
+ const verb = op.startsWith("un") ? "unlink" : "link";
54557
+ console.error(
54558
+ colorError(
54559
+ `Only an organization admin or Trace staff can ${verb} credentials, and API keys can't.`
54560
+ )
54561
+ );
54562
+ if (message !== void 0) console.error(message);
54563
+ process.exit(1);
54564
+ }
54565
+ if (status === 404) {
54566
+ console.error(colorError(message ?? `Not found: ${notFoundLabel}.`));
54567
+ process.exit(1);
54568
+ }
54569
+ if (status === 400) {
54570
+ console.error(colorError(message ?? "Invalid request (status 400)."));
54571
+ process.exit(1);
54572
+ }
54573
+ if (status === 409) {
54574
+ if (errorCode === "CREDENTIAL_LINK_SELF") {
54575
+ console.error(
54576
+ colorError(message ?? "That already belongs to this environment; nothing to link.")
54577
+ );
54578
+ } else if (errorCode === "CREDENTIAL_ALREADY_LINKED") {
54579
+ console.error(colorError(message ?? "That is already linked into this environment."));
54580
+ } else if (errorCode === "CREDENTIAL_LABEL_TAKEN") {
54581
+ console.error(
54582
+ colorError(
54583
+ message ?? "This environment already shows a credential with that label. Rename one of them first."
54584
+ )
54585
+ );
54586
+ } else if (errorCode === "CREDENTIAL_IN_USE") {
54587
+ console.error(
54588
+ colorError(message ?? "You can't unlink while a pentest using this environment is running.")
54589
+ );
54590
+ } else {
54591
+ console.error(colorError(message ?? "Conflict (status 409)."));
54592
+ }
54593
+ process.exit(1);
54594
+ }
54595
+ console.error(colorError(`Failed to ${op} (status ${status})${message ? `: ${message}` : "."}`));
54596
+ process.exit(1);
54597
+ }
54598
+ function requireLinkId(credentialLinkId) {
54599
+ if (!isUuid(credentialLinkId)) {
54600
+ console.error(colorError(`Invalid credential link id '${credentialLinkId}'. Expected a UUID.`));
54601
+ process.exit(1);
54602
+ }
54603
+ }
54604
+ function renderCredentialLinkDetail(link) {
54605
+ const pad = 13;
54606
+ return [
54607
+ `${"Link ID:".padEnd(pad)} ${link.credentialLinkId}`,
54608
+ `${"Credential:".padEnd(pad)} ${link.credential.credentialId}`,
54609
+ `${"Label:".padEnd(pad)} ${sanitizeDisplay(link.credential.label)}`,
54610
+ `${"Type:".padEnd(pad)} ${link.credential.credentialType}`,
54611
+ `${"Source:".padEnd(pad)} ${sourceDisplay(link.source)} (${link.source.environmentId})`,
54612
+ `${"Linked into:".padEnd(pad)} ${link.environmentId}`,
54613
+ `${"MFA:".padEnd(pad)} ${mfaDisplay(link.credential.mfa)}`,
54614
+ `${"Linked:".padEnd(pad)} ${formatDate(link.createdAt)}`
54615
+ ].join("\n");
54616
+ }
54617
+ function printCredentialLinksTsv(links) {
54618
+ console.log(
54619
+ [
54620
+ "credentialLinkId",
54621
+ "credentialId",
54622
+ "label",
54623
+ "credentialType",
54624
+ "sourceApplication",
54625
+ "sourceEnvironment",
54626
+ "sourceEnvironmentId",
54627
+ "environmentCredentialSourceId",
54628
+ "mfa",
54629
+ "createdAt"
54630
+ ].join(" ")
54631
+ );
54632
+ for (const link of links) {
54633
+ console.log(
54634
+ [
54635
+ tsvSafe(link.credentialLinkId),
54636
+ tsvSafe(link.credential.credentialId),
54637
+ tsvSafe(sanitizeDisplay(link.credential.label)),
54638
+ tsvSafe(link.credential.credentialType),
54639
+ tsvSafe(sanitizeDisplay(link.source.applicationName)),
54640
+ tsvSafe(sanitizeDisplay(link.source.environmentName)),
54641
+ tsvSafe(link.source.environmentId),
54642
+ tsvSafe(link.environmentCredentialSourceId ?? ""),
54643
+ tsvSafe(link.credential.mfa ? link.credential.mfa.kind : ""),
54644
+ tsvSafe(toIso(link.createdAt))
54645
+ ].join(" ")
54646
+ );
54647
+ }
54648
+ }
54649
+ function buildCredentialLinksTable(links) {
54650
+ const cells = links.map((link) => ({
54651
+ id: link.credentialLinkId,
54652
+ label: sanitizeDisplay(link.credential.label),
54653
+ type: link.credential.credentialType,
54654
+ source: sourceDisplay(link.source),
54655
+ via: link.environmentCredentialSourceId ? "link-all" : ""
54656
+ }));
54657
+ const idWidth = Math.max("LINK ID".length, ...cells.map((c84) => c84.id.length));
54658
+ const labelWidth = Math.max("LABEL".length, ...cells.map((c84) => c84.label.length));
54659
+ const typeWidth = Math.max("TYPE".length, ...cells.map((c84) => c84.type.length));
54660
+ const sourceWidth = Math.max("SOURCE".length, ...cells.map((c84) => c84.source.length));
54661
+ const header = `${"LINK ID".padEnd(idWidth)} ${"LABEL".padEnd(labelWidth)} ${"TYPE".padEnd(typeWidth)} ${"SOURCE".padEnd(sourceWidth)} VIA`;
54662
+ const rows = cells.map(
54663
+ (c84) => `${c84.id.padEnd(idWidth)} ${c84.label.padEnd(labelWidth)} ${c84.type.padEnd(typeWidth)} ${c84.source.padEnd(sourceWidth)} ${c84.via}`.trimEnd()
54664
+ );
54665
+ return { header, rows };
54666
+ }
54667
+ function outputCredentialLink(link, output) {
54668
+ if (output === "json") {
54669
+ printJson(link);
54670
+ return;
54671
+ }
54672
+ if (output === "tsv") {
54673
+ printCredentialLinksTsv([link]);
54674
+ return;
54675
+ }
54676
+ console.log(renderCredentialLinkDetail(link));
54677
+ }
54678
+ function printLinkableCredentialsTsv(creds) {
54679
+ console.log(
54680
+ [
54681
+ "credentialId",
54682
+ "label",
54683
+ "credentialType",
54684
+ "application",
54685
+ "environment",
54686
+ "environmentId",
54687
+ "hasMfa",
54688
+ "alreadyLinked"
54689
+ ].join(" ")
54690
+ );
54691
+ for (const cred of creds) {
54692
+ console.log(
54693
+ [
54694
+ tsvSafe(cred.credentialId),
54695
+ tsvSafe(sanitizeDisplay(cred.label)),
54696
+ tsvSafe(cred.credentialType),
54697
+ tsvSafe(sanitizeDisplay(cred.application.name)),
54698
+ tsvSafe(sanitizeDisplay(cred.environment.name)),
54699
+ tsvSafe(cred.environment.environmentId),
54700
+ tsvSafe(String(cred.hasMfa)),
54701
+ tsvSafe(String(cred.alreadyLinked))
54702
+ ].join(" ")
54703
+ );
54704
+ }
54705
+ }
54706
+ function buildLinkableCredentialsTable(creds) {
54707
+ const cells = creds.map((cred) => ({
54708
+ id: cred.credentialId,
54709
+ label: sanitizeDisplay(cred.label),
54710
+ type: cred.credentialType,
54711
+ source: sourceDisplay({
54712
+ applicationName: cred.application.name,
54713
+ environmentName: cred.environment.name
54714
+ }),
54715
+ linked: cred.alreadyLinked ? "linked" : ""
54716
+ }));
54717
+ const idWidth = Math.max("ID".length, ...cells.map((c84) => c84.id.length));
54718
+ const labelWidth = Math.max("LABEL".length, ...cells.map((c84) => c84.label.length));
54719
+ const typeWidth = Math.max("TYPE".length, ...cells.map((c84) => c84.type.length));
54720
+ const sourceWidth = Math.max("SOURCE".length, ...cells.map((c84) => c84.source.length));
54721
+ const header = `${"ID".padEnd(idWidth)} ${"LABEL".padEnd(labelWidth)} ${"TYPE".padEnd(typeWidth)} ${"SOURCE".padEnd(sourceWidth)} STATUS`;
54722
+ const rows = cells.map(
54723
+ (c84) => `${c84.id.padEnd(idWidth)} ${c84.label.padEnd(labelWidth)} ${c84.type.padEnd(typeWidth)} ${c84.source.padEnd(sourceWidth)} ${c84.linked}`.trimEnd()
54724
+ );
54725
+ return { header, rows };
54726
+ }
54727
+ async function runLink(credentialId, options) {
54728
+ const output = parseOutput(options.output);
54729
+ requireEnvId(options.env);
54730
+ requireCredId(credentialId);
54731
+ const organizationId = await resolveOrganizationId();
54732
+ const result = await authenticatedRequest(
54733
+ (client) => client.credentialLink.createCredentialLink({
54734
+ params: { organizationId, environmentId: options.env },
54735
+ body: { credentialId }
54736
+ })
54737
+ );
54738
+ if (result.status !== CREATED_STATUS) {
54739
+ failLinkOp("link", result.status, result.body, credentialId);
54740
+ }
54741
+ if (output === "table") {
54742
+ console.log(
54743
+ colorSuccess(
54744
+ `Linked credential ${sanitizeDisplay(result.body.credential.label)} from ${sourceDisplay(result.body.source)}`
54745
+ )
54746
+ );
54747
+ }
54748
+ outputCredentialLink(result.body, output);
54749
+ }
54750
+ async function runUnlink(credentialLinkId, options) {
54751
+ requireEnvId(options.env);
54752
+ requireLinkId(credentialLinkId);
54753
+ const confirmed = await confirmAction(
54754
+ `Unlink credential link ${credentialLinkId} (the source credential is kept)?`,
54755
+ { skip: options.yes, verb: "unlink" }
54756
+ );
54757
+ if (!confirmed) {
54758
+ console.log("Aborted.");
54759
+ return;
54760
+ }
54761
+ const organizationId = await resolveOrganizationId();
54762
+ const result = await authenticatedRequest(
54763
+ (client) => client.credentialLink.deleteCredentialLink({
54764
+ params: { organizationId, environmentId: options.env, credentialLinkId }
54765
+ })
54766
+ );
54767
+ if (result.status !== NO_CONTENT_STATUS) {
54768
+ if (result.status === 409 && readErrorFields(result.body).errorCode === "CREDENTIAL_LINKED_BY_RULE") {
54769
+ await failLinkedByRule(organizationId, options.env, result.body);
54770
+ }
54771
+ failLinkOp("unlink", result.status, result.body, credentialLinkId);
54772
+ }
54773
+ console.log(colorSuccess(`Unlinked credential link ${credentialLinkId}`));
54774
+ }
54775
+ function coveringRuleId(body) {
54776
+ if (typeof body !== "object" || body === null || !("details" in body)) return null;
54777
+ const { details } = body;
54778
+ if (typeof details !== "object" || details === null) return null;
54779
+ if (!("environmentCredentialSourceId" in details)) return null;
54780
+ const id = details.environmentCredentialSourceId;
54781
+ return typeof id === "string" && isUuid(id) ? id : null;
54782
+ }
54783
+ async function failLinkedByRule(organizationId, environmentId, body) {
54784
+ const { message } = readErrorFields(body);
54785
+ console.error(
54786
+ colorError(
54787
+ message ?? "All of the environment this credential comes from is linked here, so it stays."
54788
+ )
54789
+ );
54790
+ const ruleId = coveringRuleId(body);
54791
+ const rules = ruleId === null ? null : await fetchCredentialSources(organizationId, environmentId);
54792
+ const rule = rules?.find((r2) => r2.environmentCredentialSourceId === ruleId);
54793
+ console.error(
54794
+ rule ? `Unlink all of ${sourceDisplay(rule.source)} with: tracecli credential unlink-all ${rule.source.environmentId} --env ${environmentId}` : `See which environment with: tracecli credential links --env ${environmentId}`
54795
+ );
54796
+ process.exit(1);
54797
+ }
54798
+ async function fetchCredentialSources(organizationId, environmentId) {
54799
+ const result = await authenticatedRequestSafe(
54800
+ (client) => client.credentialLink.listCredentialSources({
54801
+ params: { organizationId, environmentId }
54802
+ })
54803
+ );
54804
+ if (result === null) return null;
54805
+ if (result.status === SUCCESS_STATUS) return result.body;
54806
+ if (result.status === 404) return [];
54807
+ return null;
54808
+ }
54809
+ function credentialSourceLines(rules) {
54810
+ return rules.map(
54811
+ (rule) => `All of ${sourceDisplay(rule.source)} is linked here, including credentials added there later.`
54812
+ );
54813
+ }
54814
+ async function runLinks(options) {
54815
+ const output = parseOutput(options.output);
54816
+ requireEnvId(options.env);
54817
+ const organizationId = await resolveOrganizationId();
54818
+ const result = await authenticatedRequest(
54819
+ (client) => client.credentialLink.listCredentialLinks({
54820
+ params: { organizationId, environmentId: options.env }
54821
+ })
54822
+ );
54823
+ if (result.status !== SUCCESS_STATUS) {
54824
+ failReadOp3("list credential links", result.status, result.body, options.env);
54825
+ }
54826
+ const links = result.body;
54827
+ if (output === "json") {
54828
+ printJson(links);
54829
+ return;
54830
+ }
54831
+ if (output === "tsv") {
54832
+ printCredentialLinksTsv(links);
54833
+ return;
54834
+ }
54835
+ const rules = await fetchCredentialSources(organizationId, options.env);
54836
+ if (rules === null) {
54837
+ console.error(
54838
+ colorError("Could not read which environments are linked whole; showing links only.")
54839
+ );
54840
+ }
54841
+ const ruleLines = credentialSourceLines(rules ?? []);
54842
+ if (links.length === 0) {
54843
+ if (ruleLines.length > 0) {
54844
+ for (const line of ruleLines) console.log(line);
54845
+ console.log("No credentials from there yet.");
54846
+ return;
54847
+ }
54848
+ console.log(
54849
+ `No linked credentials. See what you can link with: tracecli credential linkable --env ${options.env}`
54850
+ );
54851
+ return;
54852
+ }
54853
+ for (const line of ruleLines) console.log(line);
54854
+ if (ruleLines.length > 0) console.log("");
54855
+ const { header, rows } = buildCredentialLinksTable(links);
54856
+ await runInteractivePager({
54857
+ items: links,
54858
+ header,
54859
+ renderItem: (_link, i2) => [rows[i2] ?? ""],
54860
+ disabled: options.pager === false
54861
+ });
54862
+ }
54863
+ async function runLinkable(options) {
54864
+ const output = parseOutput(options.output);
54865
+ requireEnvId(options.env);
54866
+ const organizationId = await resolveOrganizationId();
54867
+ const result = await authenticatedRequest(
54868
+ (client) => client.credentialLink.listLinkableCredentials({
54869
+ params: { organizationId, environmentId: options.env }
54870
+ })
54871
+ );
54872
+ if (result.status !== SUCCESS_STATUS) {
54873
+ failReadOp3("list linkable credentials", result.status, result.body, options.env);
54874
+ }
54875
+ const creds = result.body;
54876
+ if (output === "json") {
54877
+ printJson(creds);
54878
+ return;
54879
+ }
54880
+ if (output === "tsv") {
54881
+ printLinkableCredentialsTsv(creds);
54882
+ return;
54883
+ }
54884
+ if (creds.length === 0) {
54885
+ console.log("No credentials elsewhere in this organization to link.");
54886
+ return;
54887
+ }
54888
+ const { header, rows } = buildLinkableCredentialsTable(creds);
54889
+ await runInteractivePager({
54890
+ items: creds,
54891
+ header,
54892
+ renderItem: (_cred, i2) => [rows[i2] ?? ""],
54893
+ disabled: options.pager === false
54894
+ });
54895
+ }
54896
+ function printLinkableEnvironmentsTsv(envs) {
54897
+ console.log(
54898
+ ["environmentId", "application", "environment", "credentialCount", "alreadyLinked"].join(" ")
54899
+ );
54900
+ for (const env2 of envs) {
54901
+ console.log(
54902
+ [
54903
+ tsvSafe(env2.environmentId),
54904
+ tsvSafe(sanitizeDisplay(env2.application.name)),
54905
+ tsvSafe(sanitizeDisplay(env2.name)),
54906
+ tsvSafe(String(env2.credentialCount)),
54907
+ tsvSafe(String(env2.alreadyLinked))
54908
+ ].join(" ")
54909
+ );
54910
+ }
54911
+ }
54912
+ function buildLinkableEnvironmentsTable(envs) {
54913
+ const cells = envs.map((env2) => ({
54914
+ id: env2.environmentId,
54915
+ name: sourceDisplay({ applicationName: env2.application.name, environmentName: env2.name }),
54916
+ count: String(env2.credentialCount),
54917
+ linked: env2.alreadyLinked ? "linked" : ""
54918
+ }));
54919
+ const idWidth = Math.max("ENVIRONMENT ID".length, ...cells.map((c84) => c84.id.length));
54920
+ const nameWidth = Math.max("ENVIRONMENT".length, ...cells.map((c84) => c84.name.length));
54921
+ const countWidth = "CREDENTIALS".length;
54922
+ const header = `${"ENVIRONMENT ID".padEnd(idWidth)} ${"ENVIRONMENT".padEnd(nameWidth)} ${"CREDENTIALS".padEnd(countWidth)} STATUS`;
54923
+ const rows = cells.map(
54924
+ (c84) => `${c84.id.padEnd(idWidth)} ${c84.name.padEnd(nameWidth)} ${c84.count.padEnd(countWidth)} ${c84.linked}`.trimEnd()
54925
+ );
54926
+ return { header, rows };
54927
+ }
54928
+ async function runLinkableEnvironments(options) {
54929
+ const output = parseOutput(options.output);
54930
+ requireEnvId(options.env);
54931
+ const organizationId = await resolveOrganizationId();
54932
+ const result = await authenticatedRequest(
54933
+ (client) => client.credentialLink.listLinkableEnvironments({
54934
+ params: { organizationId, environmentId: options.env }
54935
+ })
54936
+ );
54937
+ if (result.status !== SUCCESS_STATUS) {
54938
+ failReadOp3("list linkable environments", result.status, result.body, options.env);
54939
+ }
54940
+ const envs = result.body;
54941
+ if (output === "json") {
54942
+ printJson(envs);
54943
+ return;
54944
+ }
54945
+ if (output === "tsv") {
54946
+ printLinkableEnvironmentsTsv(envs);
54947
+ return;
54948
+ }
54949
+ if (envs.length === 0) {
54950
+ console.log("No other environments in this organization to link.");
54951
+ return;
54952
+ }
54953
+ const { header, rows } = buildLinkableEnvironmentsTable(envs);
54954
+ await runInteractivePager({
54955
+ items: envs,
54956
+ header,
54957
+ renderItem: (_env, i2) => [rows[i2] ?? ""],
54958
+ disabled: options.pager === false
54959
+ });
54960
+ }
54961
+ function requireSourceEnvId(sourceEnvironmentId) {
54962
+ if (!isUuid(sourceEnvironmentId)) {
54963
+ console.error(
54964
+ colorError(
54965
+ `Invalid source environment id '${sourceEnvironmentId}'. Expected an environment UUID.`
54966
+ )
54967
+ );
54968
+ process.exit(1);
54969
+ }
54970
+ }
54971
+ async function runLinkAll(sourceEnvironmentId, options) {
54972
+ const output = parseOutput(options.output);
54973
+ requireEnvId(options.env);
54974
+ requireSourceEnvId(sourceEnvironmentId);
54975
+ const organizationId = await resolveOrganizationId();
54976
+ const result = await authenticatedRequest(
54977
+ (client) => client.credentialLink.createCredentialSource({
54978
+ params: { organizationId, environmentId: options.env },
54979
+ body: { sourceEnvironmentId }
54980
+ })
54981
+ );
54982
+ if (result.status !== CREATED_STATUS) {
54983
+ failLinkOp("link all", result.status, result.body, sourceEnvironmentId);
54984
+ }
54985
+ const rule = result.body;
54986
+ if (output === "json") {
54987
+ printJson(rule);
54988
+ return;
54989
+ }
54990
+ if (output === "tsv") {
54991
+ printCredentialLinksTsv(rule.links);
54992
+ return;
54993
+ }
54994
+ const pad = 13;
54995
+ console.log(
54996
+ colorSuccess(
54997
+ `Linked all of ${sourceDisplay(rule.source)}. Credentials added there later show up here too.`
54998
+ )
54999
+ );
55000
+ console.log(
55001
+ [
55002
+ `${"Source:".padEnd(pad)} ${sourceDisplay(rule.source)} (${rule.source.environmentId})`,
55003
+ `${"Linked into:".padEnd(pad)} ${rule.environmentId}`,
55004
+ `${"New links:".padEnd(pad)} ${rule.links.length}`
55005
+ ].join("\n")
55006
+ );
55007
+ }
55008
+ async function runUnlinkAll(sourceEnvironmentId, options) {
55009
+ requireEnvId(options.env);
55010
+ requireSourceEnvId(sourceEnvironmentId);
55011
+ const organizationId = await resolveOrganizationId();
55012
+ const listed = await authenticatedRequest(
55013
+ (client) => client.credentialLink.listCredentialSources({
55014
+ params: { organizationId, environmentId: options.env }
55015
+ })
55016
+ );
55017
+ if (listed.status !== SUCCESS_STATUS) {
55018
+ failReadOp3("look up the link-all", listed.status, listed.body, options.env);
55019
+ }
55020
+ const wanted = sourceEnvironmentId.toLowerCase();
55021
+ const rule = listed.body.find((r2) => r2.source.environmentId.toLowerCase() === wanted);
55022
+ if (!rule) {
55023
+ console.error(
55024
+ colorError(
55025
+ `Nothing from ${sourceEnvironmentId} is linked whole into this environment. See what is with: tracecli credential linkable --env ${options.env} --environments`
55026
+ )
55027
+ );
55028
+ process.exit(1);
55029
+ }
55030
+ const confirmed = await confirmAction(
55031
+ `Unlink all of ${sourceDisplay(rule.source)} from this environment (credentials linked on their own stay)?`,
55032
+ { skip: options.yes, verb: "unlink" }
55033
+ );
55034
+ if (!confirmed) {
55035
+ console.log("Aborted.");
55036
+ return;
55037
+ }
55038
+ const result = await authenticatedRequest(
55039
+ (client) => client.credentialLink.deleteCredentialSource({
55040
+ params: {
55041
+ organizationId,
55042
+ environmentId: options.env,
55043
+ environmentCredentialSourceId: rule.environmentCredentialSourceId
55044
+ }
55045
+ })
55046
+ );
55047
+ if (result.status !== NO_CONTENT_STATUS) {
55048
+ failLinkOp("unlink all", result.status, result.body, rule.environmentCredentialSourceId);
55049
+ }
55050
+ console.log(
55051
+ colorSuccess(
55052
+ `Unlinked all of ${sourceDisplay(rule.source)}. Credentials linked on their own stay.`
55053
+ )
55054
+ );
55055
+ }
54177
55056
  function addSecretInputOptions(command) {
54178
55057
  return command.option("--secret-stdin", "Read the secret value from stdin").option("--secret-file <path>", "Read the secret value from a file").option("--secret-env <name>", "Read the secret value from an env var").option("--username-stdin", "Read the username from stdin").option("--username-file <path>", "Read the username from a file").option("--username-env <name>", "Read the username from an env var").option("--password-stdin", "Read the password from stdin").option("--password-file <path>", "Read the password from a file").option("--password-env <name>", "Read the password from an env var");
54179
55058
  }
@@ -54284,6 +55163,89 @@ left \u2014 if the current window is nearly over, the API waits for the next one
54284
55163
  await runMfaCode(credentialId, options);
54285
55164
  });
54286
55165
  }
55166
+ function createLinkCommand() {
55167
+ return new Command("link").description("Link another application's credential into an environment").argument("<credentialId>", "UUID of the credential to link (owned by another environment)").requiredOption("--env <environmentId>", "Environment UUID to link it into").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").addHelpText(
55168
+ "after",
55169
+ `
55170
+ A link holds no secret: the linked environment sees the source credential as it
55171
+ is now, so a rotation, MFA change, or relabel on the source shows up here too.
55172
+ Edit the credential where it lives (its own --env). The label must be free in
55173
+ the target environment, and the credential must not already belong to it.
55174
+
55175
+ Find candidates with: tracecli credential linkable --env <envId>
55176
+ To link every credential of an environment, now and as they are added, use
55177
+ link-all instead.
55178
+
55179
+ Examples:
55180
+ tracecli credential linkable --env <envId>
55181
+ tracecli credential link <credentialId> --env <envId>`
55182
+ ).action(async (credentialId, options) => {
55183
+ await runLink(credentialId, options);
55184
+ });
55185
+ }
55186
+ function createUnlinkCommand() {
55187
+ return new Command("unlink").description("Remove a credential link from an environment (the source credential is kept)").argument("<credentialLinkId>", "Credential link UUID (the LINK ID from `credential links`)").requiredOption("--env <environmentId>", "Environment UUID the credential is linked into").option("-y, --yes", "Skip the confirmation prompt (required in non-interactive shells)").addHelpText(
55188
+ "after",
55189
+ `
55190
+ Blocked while a pentest using this environment is running, like deleting one of
55191
+ the environment's own credentials. A credential whose whole environment is
55192
+ linked here (VIA link-all in \`credential links\`) stays until that is removed
55193
+ with \`credential unlink-all\`.`
55194
+ ).action(async (credentialLinkId, options) => {
55195
+ await runUnlink(credentialLinkId, options);
55196
+ });
55197
+ }
55198
+ function createLinksCommand() {
55199
+ return new Command("links").description(
55200
+ "List the credentials linked into an environment from elsewhere (secrets are never shown)"
55201
+ ).requiredOption("--env <environmentId>", "Environment UUID").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").option("--no-pager", "Disable the interactive pager for table output").action(async (options) => {
55202
+ await runLinks(options);
55203
+ });
55204
+ }
55205
+ function createLinkableCommand() {
55206
+ return new Command("linkable").description("List credentials elsewhere in the organization that an environment could link").requiredOption("--env <environmentId>", "Environment UUID to link into").option("--environments", "List whole environments to link-all instead of credentials").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").option("--no-pager", "Disable the interactive pager for table output").addHelpText(
55207
+ "after",
55208
+ `
55209
+ Rows already linked into this environment are marked "linked" (table) or
55210
+ alreadyLinked=true (tsv). With --environments, "linked" means all of that
55211
+ environment is linked here already.`
55212
+ ).action(async (options) => {
55213
+ if (options.environments) {
55214
+ await runLinkableEnvironments(options);
55215
+ return;
55216
+ }
55217
+ await runLinkable(options);
55218
+ });
55219
+ }
55220
+ function createLinkAllCommand() {
55221
+ return new Command("link-all").description("Link every credential of another environment, including ones added there later").argument("<sourceEnvironmentId>", "UUID of the environment whose credentials to link").requiredOption("--env <environmentId>", "Environment UUID to link them into").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").addHelpText(
55222
+ "after",
55223
+ `
55224
+ Each credential shows here as a link, like \`credential link\`, and a credential
55225
+ added to the source later shows up here too. Edit them where they live. Every
55226
+ label must be free in this environment. Credentials you already linked on their
55227
+ own stay as they are.
55228
+
55229
+ Find environments with: tracecli credential linkable --env <envId> --environments
55230
+
55231
+ Examples:
55232
+ tracecli credential link-all <sourceEnvId> --env <envId>
55233
+ tracecli credential unlink-all <sourceEnvId> --env <envId>`
55234
+ ).action(async (sourceEnvironmentId, options) => {
55235
+ await runLinkAll(sourceEnvironmentId, options);
55236
+ });
55237
+ }
55238
+ function createUnlinkAllCommand() {
55239
+ return new Command("unlink-all").description("Stop linking every credential of another environment into this one").argument("<sourceEnvironmentId>", "UUID of the environment passed to link-all").requiredOption("--env <environmentId>", "Environment UUID they are linked into").option("-y, --yes", "Skip the confirmation prompt (required in non-interactive shells)").addHelpText(
55240
+ "after",
55241
+ `
55242
+ Removes the links link-all made; the credentials stay in their own environment.
55243
+ Credentials you linked on their own stay linked. Blocked while a pentest using
55244
+ this environment is running.`
55245
+ ).action(async (sourceEnvironmentId, options) => {
55246
+ await runUnlinkAll(sourceEnvironmentId, options);
55247
+ });
55248
+ }
54287
55249
  function createCredentialCommand() {
54288
55250
  const credential = new Command("credential").alias("cred").description("Manage environment credentials");
54289
55251
  credential.addCommand(createCreateCommand3());
@@ -54294,6 +55256,12 @@ function createCredentialCommand() {
54294
55256
  credential.addCommand(createSetMfaCommand());
54295
55257
  credential.addCommand(createClearMfaCommand());
54296
55258
  credential.addCommand(createMfaCodeCommand());
55259
+ credential.addCommand(createLinkCommand());
55260
+ credential.addCommand(createUnlinkCommand());
55261
+ credential.addCommand(createLinkAllCommand());
55262
+ credential.addCommand(createUnlinkAllCommand());
55263
+ credential.addCommand(createLinksCommand());
55264
+ credential.addCommand(createLinkableCommand());
54297
55265
  credential.addCommand(createInboxCommand(), { hidden: true });
54298
55266
  return credential;
54299
55267
  }
@@ -54509,14 +55477,14 @@ function buildEnvironmentTable(envs, leading) {
54509
55477
  url: e.primaryUrl != null ? sanitizeDisplay(e.primaryUrl) : "\u2014"
54510
55478
  }));
54511
55479
  const leadingWidths = leading.map(
54512
- (column, i2) => Math.max(column.header.length, ...cells.map((c83) => c83.leading[i2]?.length ?? 0))
55480
+ (column, i2) => Math.max(column.header.length, ...cells.map((c84) => c84.leading[i2]?.length ?? 0))
54513
55481
  );
54514
- const nameWidth = Math.max(4, ...cells.map((c83) => c83.name.length));
55482
+ const nameWidth = Math.max(4, ...cells.map((c84) => c84.name.length));
54515
55483
  const padLeading = (values) => values.map((v, i2) => v.padEnd(leadingWidths[i2] ?? 0)).join(" ");
54516
55484
  return {
54517
- header: `${padLeading(leading.map((c83) => c83.header))} ${"NAME".padEnd(nameWidth)} PROD URL`,
55485
+ header: `${padLeading(leading.map((c84) => c84.header))} ${"NAME".padEnd(nameWidth)} PROD URL`,
54518
55486
  rows: cells.map(
54519
- (c83) => `${padLeading(c83.leading)} ${c83.name.padEnd(nameWidth)} ${c83.prod.padEnd(4)} ${c83.url}`
55487
+ (c84) => `${padLeading(c84.leading)} ${c84.name.padEnd(nameWidth)} ${c84.prod.padEnd(4)} ${c84.url}`
54520
55488
  )
54521
55489
  };
54522
55490
  }
@@ -54898,7 +55866,6 @@ var CLI_VERSION = packageMetadata.version;
54898
55866
  import { readFileSync as readFileSync7 } from "fs";
54899
55867
 
54900
55868
  // src/pentest/evidence.ts
54901
- import { readFileSync as readFileSync5, statSync as statSync2 } from "fs";
54902
55869
  import { basename as basename2 } from "path";
54903
55870
 
54904
55871
  // src/pentest/resolve.ts
@@ -54973,6 +55940,67 @@ Run \`tracecli pentest list\` to see available pentests.`
54973
55940
  return pentest.pentestId;
54974
55941
  }
54975
55942
 
55943
+ // src/pentest/upload-file.ts
55944
+ import { readFileSync as readFileSync5, statSync as statSync2 } from "fs";
55945
+ function formatBytes2(bytes) {
55946
+ if (bytes < 1024) return `${bytes} B`;
55947
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
55948
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
55949
+ }
55950
+ function statUploadFile(path3, limit) {
55951
+ let sizeBytes;
55952
+ try {
55953
+ const stats = statSync2(path3);
55954
+ if (!stats.isFile()) {
55955
+ console.error(colorError(`--file '${sanitizeDisplay(path3)}' is not a regular file.`));
55956
+ process.exit(1);
55957
+ }
55958
+ sizeBytes = stats.size;
55959
+ } catch {
55960
+ console.error(colorError(`Cannot read --file '${sanitizeDisplay(path3)}'.`));
55961
+ process.exit(1);
55962
+ }
55963
+ if (sizeBytes === 0) {
55964
+ console.error(colorError(`--file '${sanitizeDisplay(path3)}' is empty.`));
55965
+ process.exit(1);
55966
+ }
55967
+ if (sizeBytes > limit.bytes) {
55968
+ console.error(
55969
+ colorError(
55970
+ `File is ${formatBytes2(sizeBytes)}; the limit for one ${limit.noun} is ${formatBytes2(limit.bytes)}.`
55971
+ )
55972
+ );
55973
+ process.exit(1);
55974
+ }
55975
+ return sizeBytes;
55976
+ }
55977
+ async function putFileToPresignedUrl(uploadUrl, path3, options = {}) {
55978
+ let put;
55979
+ try {
55980
+ put = await fetch(uploadUrl, {
55981
+ method: "PUT",
55982
+ body: new Uint8Array(readFileSync5(path3)),
55983
+ ...options.contentType === void 0 ? {} : { headers: { "Content-Type": options.contentType } }
55984
+ });
55985
+ } catch (error48) {
55986
+ const reason = error48 instanceof Error ? error48.message : "unknown error";
55987
+ console.error(
55988
+ colorError(
55989
+ `Upload to storage failed: could not reach storage (${sanitizeDisplay(reason)}). Check the connection and re-run the command.`
55990
+ )
55991
+ );
55992
+ process.exit(1);
55993
+ }
55994
+ if (!put.ok) {
55995
+ console.error(
55996
+ colorError(
55997
+ `Upload to storage failed (status ${put.status}). The upload link is short-lived; re-run the command.`
55998
+ )
55999
+ );
56000
+ process.exit(1);
56001
+ }
56002
+ }
56003
+
54976
56004
  // src/pentest/evidence.ts
54977
56005
  var SECTION_LIST = ValidationEvidenceSectionValues.join(", ");
54978
56006
  var KIND_LIST = PentestExploitArtifactKindValues.join(", ");
@@ -54982,11 +56010,6 @@ function isSection(value) {
54982
56010
  function isKind(value) {
54983
56011
  return PentestExploitArtifactKindValues.includes(value);
54984
56012
  }
54985
- function formatBytes2(bytes) {
54986
- if (bytes < 1024) return `${bytes} B`;
54987
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
54988
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
54989
- }
54990
56013
  async function resolveFindingId(organizationId, pentestId, identifier) {
54991
56014
  if (isUuid(identifier)) {
54992
56015
  return identifier;
@@ -55033,30 +56056,10 @@ async function runAttachEvidence(options) {
55033
56056
  console.error(`Valid kinds: ${KIND_LIST}`);
55034
56057
  process.exit(1);
55035
56058
  }
55036
- let sizeBytes;
55037
- try {
55038
- const stats = statSync2(options.file);
55039
- if (!stats.isFile()) {
55040
- console.error(colorError(`--file '${sanitizeDisplay(options.file)}' is not a regular file.`));
55041
- process.exit(1);
55042
- }
55043
- sizeBytes = stats.size;
55044
- } catch {
55045
- console.error(colorError(`Cannot read --file '${sanitizeDisplay(options.file)}'.`));
55046
- process.exit(1);
55047
- }
55048
- if (sizeBytes === 0) {
55049
- console.error(colorError(`--file '${sanitizeDisplay(options.file)}' is empty.`));
55050
- process.exit(1);
55051
- }
55052
- if (sizeBytes > MAX_STAFF_EVIDENCE_UPLOAD_BYTES) {
55053
- console.error(
55054
- colorError(
55055
- `File is ${formatBytes2(sizeBytes)}; the limit for one exhibit is ${formatBytes2(MAX_STAFF_EVIDENCE_UPLOAD_BYTES)}.`
55056
- )
55057
- );
55058
- process.exit(1);
55059
- }
56059
+ const sizeBytes = statUploadFile(options.file, {
56060
+ bytes: MAX_STAFF_EVIDENCE_UPLOAD_BYTES,
56061
+ noun: "exhibit"
56062
+ });
55060
56063
  const organizationId = await resolveOrganizationId();
55061
56064
  const pentestId = await resolvePentestId(organizationId, options.pentest);
55062
56065
  const pentestFindingId = await resolveFindingId(organizationId, pentestId, options.finding);
@@ -55078,16 +56081,7 @@ async function runAttachEvidence(options) {
55078
56081
  );
55079
56082
  process.exit(1);
55080
56083
  }
55081
- const body = readFileSync5(options.file);
55082
- const put = await fetch(slot.body.uploadUrl, { method: "PUT", body: new Uint8Array(body) });
55083
- if (!put.ok) {
55084
- console.error(
55085
- colorError(
55086
- `Upload to storage failed (status ${put.status}). The upload link is short-lived \u2014 re-run the command.`
55087
- )
55088
- );
55089
- process.exit(1);
55090
- }
56084
+ await putFileToPresignedUrl(slot.body.uploadUrl, options.file);
55091
56085
  const attached = await authenticatedRequest(
55092
56086
  (client) => client.pentestFinding.attachFindingEvidence({
55093
56087
  params: params3,
@@ -55896,8 +56890,8 @@ function requiredExtensionForKind(kind) {
55896
56890
  return ".zip";
55897
56891
  }
55898
56892
  function defaultDisplayName(originalFilename) {
55899
- const base2 = originalFilename.replace(/\.(zip|apk|ipa)$/i, "").trim();
55900
- return base2.slice(0, 200) || "source";
56893
+ const base3 = originalFilename.replace(/\.(zip|apk|ipa)$/i, "").trim();
56894
+ return base3.slice(0, 200) || "source";
55901
56895
  }
55902
56896
  function formatArchiveBytes(bytes) {
55903
56897
  if (bytes < 1024) return `${bytes} B`;
@@ -56812,6 +57806,258 @@ function createAttestationCommand() {
56812
57806
  return attestation;
56813
57807
  }
56814
57808
 
57809
+ // src/pentest/documents.ts
57810
+ import { basename as basename5 } from "path";
57811
+
57812
+ // src/pentest/intake-errors.ts
57813
+ var WRITE_FORBIDDEN_MESSAGE = {
57814
+ questionnaire: "Questionnaire writes require an organization admin (or Trace staff) signed in with a browser session. API keys are rejected.",
57815
+ "questionnaire documents": "Questionnaire document writes require an organization admin (or Trace staff) signed in with a browser session. API keys are rejected."
57816
+ };
57817
+ function failIntakeRequest(status, body, options) {
57818
+ const { message } = readErrorFields(body);
57819
+ if (status === UNAUTHORIZED_STATUS2) {
57820
+ exitAuthFailed();
57821
+ }
57822
+ if (status === 403) {
57823
+ console.error(
57824
+ colorError(
57825
+ options.action === "write" ? WRITE_FORBIDDEN_MESSAGE[options.subject] : message ?? `You do not have permission to view this ${options.subject}.`
57826
+ )
57827
+ );
57828
+ process.exit(1);
57829
+ }
57830
+ if (status === 404) {
57831
+ console.error(
57832
+ colorError(
57833
+ options.notFound === void 0 ? message ?? "Not found." : `${options.notFound.what} not found: ${sanitizeDisplay(options.notFound.label)}.`
57834
+ )
57835
+ );
57836
+ process.exit(1);
57837
+ }
57838
+ if (status === 400) {
57839
+ console.error(colorError(message ?? `Invalid ${options.subject} request (status 400).`));
57840
+ process.exit(1);
57841
+ }
57842
+ if (status === 409) {
57843
+ console.error(colorError(message ?? QUESTIONNAIRE_LOCKED_MESSAGE));
57844
+ process.exit(1);
57845
+ }
57846
+ console.error(
57847
+ colorError(
57848
+ `Failed to ${options.action === "write" ? "save" : "load"} ${options.subject} (status ${status})${message ? `: ${message}` : "."}`
57849
+ )
57850
+ );
57851
+ process.exit(1);
57852
+ }
57853
+
57854
+ // src/pentest/documents.ts
57855
+ var TYPE_LIST = PentestIntakeDocumentTypeValues.join(", ");
57856
+ function isDocumentType(value) {
57857
+ return PentestIntakeDocumentTypeValues.includes(value);
57858
+ }
57859
+ function renderDocumentLines(doc) {
57860
+ const uploader = doc.uploadedByName ? ` by ${sanitizeDisplay(doc.uploadedByName)}` : "";
57861
+ return [
57862
+ ` ${sanitizeDisplay(doc.fileName)}`,
57863
+ ` ${PENTEST_INTAKE_DOCUMENT_TYPE_LABELS[doc.documentType]} | ${formatBytes2(doc.fileSizeBytes)} | uploaded${uploader} on ${formatDate(doc.createdAt)}`,
57864
+ ` id ${doc.documentId}`
57865
+ ];
57866
+ }
57867
+ var TSV_HEADER = "documentId documentType fileName fileType fileSizeBytes uploadedByName createdAt";
57868
+ async function runList5(options) {
57869
+ const output = parseOutput(options.output);
57870
+ const organizationId = await resolveOrganizationId();
57871
+ const pentestId = await resolvePentestId(organizationId, options.pentest);
57872
+ const result = await authenticatedRequest(
57873
+ (client) => client.pentestIntakeDocument.listDocuments({ params: { organizationId, pentestId } })
57874
+ );
57875
+ if (result.status !== SUCCESS_STATUS) {
57876
+ failIntakeRequest(result.status, result.body, {
57877
+ subject: "questionnaire documents",
57878
+ action: "read",
57879
+ notFound: { what: "Pentest", label: options.pentest }
57880
+ });
57881
+ }
57882
+ const { editable, documents } = result.body;
57883
+ if (output === "json") {
57884
+ printJson({ editable, documents });
57885
+ return;
57886
+ }
57887
+ if (output === "tsv") {
57888
+ console.log(TSV_HEADER);
57889
+ for (const doc of documents) {
57890
+ console.log(
57891
+ [
57892
+ doc.documentId,
57893
+ doc.documentType,
57894
+ tsvSafe(sanitizeDisplay(doc.fileName)),
57895
+ tsvSafe(sanitizeDisplay(doc.fileType)),
57896
+ String(doc.fileSizeBytes),
57897
+ tsvSafe(doc.uploadedByName === null ? null : sanitizeDisplay(doc.uploadedByName)),
57898
+ toIso(doc.createdAt)
57899
+ ].join(" ")
57900
+ );
57901
+ }
57902
+ return;
57903
+ }
57904
+ console.log(`Editable: ${editable ? "yes" : "no"}`);
57905
+ if (documents.length === 0) {
57906
+ console.log("No documents attached.");
57907
+ return;
57908
+ }
57909
+ console.log(`${documents.length} document${documents.length === 1 ? "" : "s"}:`);
57910
+ for (const doc of documents) {
57911
+ for (const line of renderDocumentLines(doc)) {
57912
+ console.log(line);
57913
+ }
57914
+ }
57915
+ }
57916
+ async function runAttach(options) {
57917
+ const output = parseTextOutput(options.output);
57918
+ if (options.type !== void 0 && !isDocumentType(options.type)) {
57919
+ console.error(colorError(`Invalid --type '${sanitizeDisplay(options.type)}'.`));
57920
+ console.error(`Valid types: ${TYPE_LIST}`);
57921
+ process.exit(1);
57922
+ }
57923
+ const sizeBytes = statUploadFile(options.file, {
57924
+ bytes: MAX_PENTEST_INTAKE_DOCUMENT_BYTES,
57925
+ noun: "document"
57926
+ });
57927
+ const fileName = basename5(options.file);
57928
+ const documentType = options.type !== void 0 && isDocumentType(options.type) ? options.type : guessPentestIntakeDocumentType(fileName);
57929
+ const organizationId = await resolveOrganizationId();
57930
+ const pentestId = await resolvePentestId(organizationId, options.pentest);
57931
+ const params3 = { organizationId, pentestId };
57932
+ const slot = await authenticatedRequest(
57933
+ (client) => client.pentestIntakeDocument.createUpload({
57934
+ params: params3,
57935
+ body: { fileName, fileSizeBytes: sizeBytes }
57936
+ })
57937
+ );
57938
+ if (slot.status !== CREATED_STATUS) {
57939
+ failIntakeRequest(slot.status, slot.body, {
57940
+ subject: "questionnaire documents",
57941
+ action: "write",
57942
+ notFound: { what: "Pentest", label: options.pentest }
57943
+ });
57944
+ }
57945
+ const fileType = pentestIntakeDocumentMimeType(fileName);
57946
+ await putFileToPresignedUrl(slot.body.uploadUrl, options.file, { contentType: fileType });
57947
+ const attached = await authenticatedRequest(
57948
+ (client) => client.pentestIntakeDocument.attachDocument({
57949
+ params: params3,
57950
+ body: {
57951
+ uploadKey: slot.body.uploadKey,
57952
+ documentType,
57953
+ fileName,
57954
+ fileType
57955
+ }
57956
+ })
57957
+ );
57958
+ if (attached.status !== CREATED_STATUS) {
57959
+ failIntakeRequest(attached.status, attached.body, {
57960
+ subject: "questionnaire documents",
57961
+ action: "write",
57962
+ notFound: { what: "Pentest", label: options.pentest }
57963
+ });
57964
+ }
57965
+ if (output === "json") {
57966
+ printJson(attached.body);
57967
+ return;
57968
+ }
57969
+ console.log(
57970
+ colorSuccess(
57971
+ `Attached ${sanitizeDisplay(attached.body.fileName)} (${PENTEST_INTAKE_DOCUMENT_TYPE_LABELS[attached.body.documentType]}, ${formatBytes2(attached.body.fileSizeBytes)})`
57972
+ )
57973
+ );
57974
+ console.log(` id ${attached.body.documentId}`);
57975
+ }
57976
+ async function runRemove(documentId, options) {
57977
+ if (!isUuid(documentId)) {
57978
+ console.error(
57979
+ colorError(
57980
+ `Invalid document ID '${sanitizeDisplay(documentId)}'. Pass the UUID from \`tracecli pentest documents list\`.`
57981
+ )
57982
+ );
57983
+ process.exit(1);
57984
+ }
57985
+ const organizationId = await resolveOrganizationId();
57986
+ const pentestId = await resolvePentestId(organizationId, options.pentest);
57987
+ const confirmed = await confirmDeletion(`document ${documentId}`, options.yes === true);
57988
+ if (!confirmed) {
57989
+ console.log("Cancelled.");
57990
+ return;
57991
+ }
57992
+ const result = await authenticatedRequest(
57993
+ (client) => client.pentestIntakeDocument.deleteDocument({
57994
+ params: { organizationId, pentestId, documentId }
57995
+ })
57996
+ );
57997
+ if (result.status !== NO_CONTENT_STATUS) {
57998
+ failIntakeRequest(result.status, result.body, {
57999
+ subject: "questionnaire documents",
58000
+ action: "write",
58001
+ notFound: { what: "Document", label: documentId }
58002
+ });
58003
+ }
58004
+ console.log(colorSuccess(`Removed document ${documentId}.`));
58005
+ }
58006
+ function createListCommand6() {
58007
+ return new Command("list").description("List documents attached to the intake questionnaire").requiredOption("--pentest <id>", "Pentest display ID (e.g. PT-3) or UUID").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").addHelpText(
58008
+ "after",
58009
+ `
58010
+ Examples:
58011
+ tracecli pentest documents list --pentest PT-1
58012
+ tracecli pentest documents list --pentest PT-1 --output json`
58013
+ ).action(async (options) => {
58014
+ await runList5(options);
58015
+ });
58016
+ }
58017
+ function createAttachCommand() {
58018
+ return new Command("attach").description(
58019
+ "Attach a document (prior report, architecture diagram, threat model) to the questionnaire"
58020
+ ).requiredOption("--pentest <id>", "Pentest display ID (e.g. PT-3) or UUID").requiredOption("--file <path>", "Path to the document").option(
58021
+ "--type <type>",
58022
+ `Document type (${TYPE_LIST}); guessed from the file name when omitted`
58023
+ ).option("-o, --output <format>", "Output format: text, json (default: text)", "text").addHelpText(
58024
+ "after",
58025
+ `
58026
+ Answers the questionnaire's "documents to share" question. Same edit window as
58027
+ the answers: allowed until testing starts. Every documents command needs a
58028
+ browser session from \`tracecli auth login\`; API keys are rejected. Attach and
58029
+ remove also need an organization admin (or Trace staff).
58030
+ Up to ${formatBytes2(MAX_PENTEST_INTAKE_DOCUMENT_BYTES)} per file.
58031
+
58032
+ Examples:
58033
+ tracecli pentest documents attach --pentest PT-1 --file ./threat-model.pdf
58034
+ tracecli pentest documents attach --pentest PT-1 --file ./q3-report.pdf --type PREVIOUS_PENTEST
58035
+ tracecli pentest documents attach --pentest PT-1 --file ./arch.png --output json`
58036
+ ).action(async (options) => {
58037
+ await runAttach(options);
58038
+ });
58039
+ }
58040
+ function createRemoveCommand() {
58041
+ return new Command("remove").description("Remove a document from the intake questionnaire").argument("<documentId>", "Document UUID (from `documents list`)").requiredOption("--pentest <id>", "Pentest display ID (e.g. PT-3) or UUID").option("-y, --yes", "Skip the confirmation prompt").addHelpText(
58042
+ "after",
58043
+ `
58044
+ Examples:
58045
+ tracecli pentest documents remove 3f1c... --pentest PT-1
58046
+ tracecli pentest documents remove 3f1c... --pentest PT-1 --yes`
58047
+ ).action(async (documentId, options) => {
58048
+ await runRemove(documentId, options);
58049
+ });
58050
+ }
58051
+ function createDocumentsCommand() {
58052
+ const documents = new Command("documents").description(
58053
+ "Documents attached to the pentest intake questionnaire"
58054
+ );
58055
+ documents.addCommand(createListCommand6());
58056
+ documents.addCommand(createAttachCommand());
58057
+ documents.addCommand(createRemoveCommand());
58058
+ return documents;
58059
+ }
58060
+
56815
58061
  // src/pentest/get-finding.ts
56816
58062
  var OUTPUT_FORMATS2 = ["text", "json"];
56817
58063
  function isOutputFormat2(value) {
@@ -57261,7 +58507,10 @@ function parseQuestionnaireAnswersFile(raw, sourceLabel = "file") {
57261
58507
  error: `${sourceLabel} must contain the answers object directly, not an { answers } wrapper. The CLI wraps it for the API.`
57262
58508
  };
57263
58509
  }
57264
- const result = PentestCustomerIntakeAnswersSchema.safeParse(parsed);
58510
+ const withoutNulls = Object.fromEntries(
58511
+ Object.entries(parsed).filter(([, value]) => value !== null)
58512
+ );
58513
+ const result = PentestCustomerIntakeAnswersSchema.safeParse(withoutNulls);
57265
58514
  if (!result.success) {
57266
58515
  const issue2 = result.error.issues[0];
57267
58516
  const path3 = issue2?.path.length ? issue2.path.join(".") : "answers";
@@ -57286,6 +58535,12 @@ function applyMultiSelectIntakeAnswer(current, key, selected) {
57286
58535
  [key]: Array.from(new Set(selected)).sort()
57287
58536
  };
57288
58537
  }
58538
+ function mergeIntakeAnswers(base3, patch) {
58539
+ return normalizePentestCustomerIntakeAnswers({
58540
+ ...base3 ?? {},
58541
+ ...patch
58542
+ });
58543
+ }
57289
58544
  function formatIssueClasses(values) {
57290
58545
  return values.map((v) => INTAKE_ISSUE_CLASS_LABELS[v]).join(", ");
57291
58546
  }
@@ -57317,22 +58572,113 @@ function formatAnswerValue(key, answers) {
57317
58572
  }
57318
58573
  }
57319
58574
  }
58575
+ var NOT_ANSWERED = "(not answered)";
57320
58576
  function formatQuestionnaireShow(input) {
57321
58577
  const submitted = input.submitted === true;
57322
58578
  const editableLine = input.editable ? "yes" : input.lockedReason === "ENGAGEMENT_ENDED" ? "no (engagement has ended)" : input.lockedReason === "TESTING_STARTED" ? "no (testing has started)" : "no";
57323
- const lines = [`Editable: ${editableLine}`, `Submitted: ${submitted ? "yes" : "no"}`];
57324
- if (input.answers === null || Object.keys(input.answers).length === 0) {
57325
- lines.push("Answers: (none)");
57326
- return lines.join("\n");
57327
- }
57328
- lines.push("Answers:");
58579
+ const answers = input.answers ?? {};
58580
+ const lines = [
58581
+ `Editable: ${editableLine}`,
58582
+ `Submitted: ${submitted ? "yes" : "no"}`,
58583
+ `Answers: ${countAnsweredIntakeQuestions(answers)} of ${PENTEST_CUSTOMER_INTAKE_QUESTION_COUNT} questions filled`
58584
+ ];
57329
58585
  for (const key of PENTEST_CUSTOMER_INTAKE_ANSWER_KEYS) {
57330
- const value = formatAnswerValue(key, input.answers);
57331
- if (value === null) {
57332
- continue;
58586
+ const value = formatAnswerValue(key, answers) ?? NOT_ANSWERED;
58587
+ lines.push(`${PENTEST_CUSTOMER_INTAKE_LABELS[key]} (${key}): ${value}`);
58588
+ }
58589
+ return lines.join("\n");
58590
+ }
58591
+ function buildQuestionnaireExport(answers) {
58592
+ const n3 = normalizePentestCustomerIntakeAnswers(answers ?? {});
58593
+ const text = (value) => value === void 0 || value.trim() === "" ? null : value;
58594
+ const list = (value) => value === void 0 || value.length === 0 ? null : [...value];
58595
+ return {
58596
+ issueClasses: list(n3.issueClasses),
58597
+ issueClassesNotes: text(n3.issueClassesNotes),
58598
+ focusAreas: text(n3.focusAreas),
58599
+ rolesAndPermissions: text(n3.rolesAndPermissions),
58600
+ authenticationSetup: text(n3.authenticationSetup),
58601
+ tenantIsolationExpectation: text(n3.tenantIsolationExpectation),
58602
+ surfaces: list(n3.surfaces),
58603
+ surfacesNotes: text(n3.surfacesNotes),
58604
+ thirdParties: text(n3.thirdParties),
58605
+ sensitiveData: text(n3.sensitiveData),
58606
+ neverTouch: text(n3.neverTouch),
58607
+ knownRisks: text(n3.knownRisks),
58608
+ tribalKnowledge: text(n3.tribalKnowledge)
58609
+ };
58610
+ }
58611
+ function buildQuestionnaireRecordJson(input) {
58612
+ const toIsoString = (value) => typeof value === "string" ? value : value.toISOString();
58613
+ return {
58614
+ pentestId: input.pentestId,
58615
+ editable: input.editable,
58616
+ lockedReason: input.lockedReason ?? null,
58617
+ submitted: input.submittedAt != null,
58618
+ submittedAt: input.submittedAt == null ? null : toIsoString(input.submittedAt),
58619
+ updatedAt: toIsoString(input.updatedAt),
58620
+ answers: buildQuestionnaireExport(input.answers)
58621
+ };
58622
+ }
58623
+ var DOCUMENTS_ATTACH_COMMAND = "tracecli pentest documents attach --pentest <pentestId> --file <path>";
58624
+ function buildQuestionnaireCatalog() {
58625
+ const questions = PENTEST_CUSTOMER_INTAKE_QUESTIONS.map(
58626
+ (entry, index) => {
58627
+ if (entry.kind === "text") {
58628
+ return {
58629
+ number: index + 1,
58630
+ key: entry.key,
58631
+ kind: "text",
58632
+ question: entry.question,
58633
+ helper: entry.helper ?? null,
58634
+ options: null,
58635
+ notes: null
58636
+ };
58637
+ }
58638
+ return {
58639
+ number: index + 1,
58640
+ key: entry.key,
58641
+ kind: "multiSelect",
58642
+ question: entry.question,
58643
+ helper: entry.helper ?? null,
58644
+ options: entry.key === "issueClasses" ? issueClassSelectOptions() : surfaceSelectOptions(),
58645
+ notes: { key: entry.notesKey, question: entry.notesQuestion }
58646
+ };
58647
+ }
58648
+ );
58649
+ return {
58650
+ questions,
58651
+ documents: {
58652
+ question: PENTEST_INTAKE_DOCUMENTS_QUESTION,
58653
+ command: DOCUMENTS_ATTACH_COMMAND,
58654
+ types: PentestIntakeDocumentTypeValues.map((value) => ({
58655
+ value,
58656
+ label: PENTEST_INTAKE_DOCUMENT_TYPE_LABELS[value]
58657
+ }))
58658
+ }
58659
+ };
58660
+ }
58661
+ function formatQuestionnaireCatalog(catalog) {
58662
+ const lines = [];
58663
+ for (const entry of catalog.questions) {
58664
+ lines.push(`${entry.number}. ${entry.question}`);
58665
+ if (entry.helper !== null) {
58666
+ lines.push(` ${entry.helper}`);
58667
+ }
58668
+ lines.push(` key: ${entry.key} (${entry.kind === "text" ? "text" : "multi-select"})`);
58669
+ if (entry.options !== null) {
58670
+ lines.push(` options: ${entry.options.map((o2) => `${o2.value} (${o2.label})`).join(", ")}`);
58671
+ }
58672
+ if (entry.notes !== null) {
58673
+ lines.push(` notes key: ${entry.notes.key} (text): ${entry.notes.question}`);
57333
58674
  }
57334
- lines.push(`${PENTEST_CUSTOMER_INTAKE_LABELS[key]}: ${value}`);
58675
+ lines.push("");
57335
58676
  }
58677
+ lines.push(`${catalog.questions.length + 1}. ${catalog.documents.question}`);
58678
+ lines.push(` Files, not an answers key. Attach with: ${catalog.documents.command}`);
58679
+ lines.push(
58680
+ ` types: ${catalog.documents.types.map((t2) => `${t2.value} (${t2.label})`).join(", ")}`
58681
+ );
57336
58682
  return lines.join("\n");
57337
58683
  }
57338
58684
  function issueClassSelectOptions() {
@@ -57431,7 +58777,7 @@ function formatFindingSeverity(severity) {
57431
58777
  return severity;
57432
58778
  }
57433
58779
  }
57434
- function createListCommand6() {
58780
+ function createListCommand7() {
57435
58781
  return new Command("list").description("List pentests for your organization").option(
57436
58782
  "--status <status>",
57437
58783
  "Filter by status (SCOPING, IN_PROGRESS, REMEDIATION, CLOSED)",
@@ -57789,7 +59135,8 @@ function buildUpdatePentestBody(input) {
57789
59135
  ...input.title !== void 0 ? { title: input.title } : {},
57790
59136
  ...input.description !== void 0 ? { description: input.description } : {},
57791
59137
  ...input.customerConcernsContext !== void 0 ? { customerConcernsContext: input.customerConcernsContext } : {},
57792
- ...input.customerPriorityCategories !== void 0 ? { customerPriorityCategories: input.customerPriorityCategories } : {}
59138
+ ...input.customerPriorityCategories !== void 0 ? { customerPriorityCategories: input.customerPriorityCategories } : {},
59139
+ ...input.staffNotes !== void 0 ? { staffNotes: input.staffNotes } : {}
57793
59140
  };
57794
59141
  }
57795
59142
  function isEmptyPentestUpdate(body) {
@@ -57891,12 +59238,15 @@ function printPentestUpdateDetail(pt) {
57891
59238
  `${"Description:".padEnd(pad)} ${pt.description ? sanitizeDisplay(truncateForDisplay(pt.description)) : "\u2014"}`,
57892
59239
  `${"Priorities:".padEnd(pad)} ${sanitizeDisplay(formatPriorities(pt.customerPriorityCategories))}`,
57893
59240
  `${"Concerns:".padEnd(pad)} ${pt.customerConcernsContext ? sanitizeDisplay(truncateForDisplay(pt.customerConcernsContext)) : "\u2014"}`,
59241
+ `${"Staff notes:".padEnd(pad)} ${pt.staffNotes ? sanitizeDisplay(truncateForDisplay(pt.staffNotes)) : "\u2014"}`,
57894
59242
  `${"Updated:".padEnd(pad)} ${toIso(pt.updatedAt)}`
57895
59243
  ].join("\n")
57896
59244
  );
57897
59245
  }
57898
59246
  function printPentestUpdateTsv(pt) {
57899
- console.log("displayId title status phase description priorities concerns updatedAt");
59247
+ console.log(
59248
+ "displayId title status phase description priorities concerns staffNotes updatedAt"
59249
+ );
57900
59250
  console.log(
57901
59251
  [
57902
59252
  `PT-${pt.displayId}`,
@@ -57906,6 +59256,7 @@ function printPentestUpdateTsv(pt) {
57906
59256
  tsvSafe2(sanitizeDisplay(pt.description ?? "")),
57907
59257
  tsvSafe2(sanitizeDisplay(pt.customerPriorityCategories.join(","))),
57908
59258
  tsvSafe2(sanitizeDisplay(pt.customerConcernsContext ?? "")),
59259
+ tsvSafe2(sanitizeDisplay(pt.staffNotes ?? "")),
57909
59260
  toIso(pt.updatedAt)
57910
59261
  ].join(" ")
57911
59262
  );
@@ -57942,16 +59293,26 @@ async function runUpdate5(pentestIdentifier, options) {
57942
59293
  console.error(colorError(priorities.error));
57943
59294
  process.exit(1);
57944
59295
  }
59296
+ const staffNotes = resolveClearableOption(
59297
+ options.staffNotes,
59298
+ options.clearStaffNotes,
59299
+ "staff-notes"
59300
+ );
59301
+ if ("error" in staffNotes) {
59302
+ console.error(colorError(staffNotes.error));
59303
+ process.exit(1);
59304
+ }
57945
59305
  const body = buildUpdatePentestBody({
57946
59306
  title: options.title,
57947
59307
  description: description.value,
57948
59308
  customerConcernsContext: concerns.value,
57949
- customerPriorityCategories: priorities.value
59309
+ customerPriorityCategories: priorities.value,
59310
+ staffNotes: staffNotes.value
57950
59311
  });
57951
59312
  if (isEmptyPentestUpdate(body)) {
57952
59313
  console.error(
57953
59314
  colorError(
57954
- "Nothing to update. Pass at least one of --title, --description, --clear-description, --concerns, --clear-concerns, --priority, --clear-priorities."
59315
+ "Nothing to update. Pass at least one of --title, --description, --clear-description, --concerns, --clear-concerns, --priority, --clear-priorities, --staff-notes, --clear-staff-notes."
57955
59316
  )
57956
59317
  );
57957
59318
  process.exit(1);
@@ -57964,19 +59325,30 @@ async function runUpdate5(pentestIdentifier, options) {
57964
59325
  if (result.status !== SUCCESS_STATUS) {
57965
59326
  failPentestUpdate(result.status, result.body, pentestIdentifier);
57966
59327
  }
59328
+ if (staffNotesIgnoredByApi(body, result.body)) {
59329
+ console.error(
59330
+ colorError(
59331
+ "This API does not support staff notes yet, so they were not saved. Any other fields you passed were saved."
59332
+ )
59333
+ );
59334
+ process.exit(1);
59335
+ }
57967
59336
  if (output === "table") {
57968
59337
  console.log(colorSuccess(`Updated pentest PT-${result.body.displayId}`));
57969
59338
  }
57970
59339
  outputPentestUpdate(result.body, output);
57971
59340
  }
59341
+ function staffNotesIgnoredByApi(body, response) {
59342
+ return body.staffNotes !== void 0 && !("staffNotes" in response);
59343
+ }
57972
59344
  function createUpdateCommand4() {
57973
59345
  return new Command("update").description(
57974
- "Update pentest details and agent brief (staff-only: title, description, priorities, concerns)"
59346
+ "Update pentest details and agent brief (staff-only: title, description, priorities, concerns, staff notes)"
57975
59347
  ).argument("<pentestId>", "Pentest display ID (e.g. PT-3) or UUID").option("--title <title>", "Engagement title").option("--description <text>", "Engagement description").option("--clear-description", "Clear the engagement description").option("--concerns <text>", "Agent-brief concerns (threat-model context)").option("--clear-concerns", "Clear agent-brief concerns").option(
57976
59348
  "--priority <category>",
57977
59349
  `Agent-brief priority category (repeatable). One of: ${PRIORITY_LIST}`,
57978
59350
  collectPriority
57979
- ).option("--clear-priorities", "Clear all agent-brief priority categories").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").addHelpText(
59351
+ ).option("--clear-priorities", "Clear all agent-brief priority categories").option("--staff-notes <text>", "Guidance for the agent sessions on this pentest").option("--clear-staff-notes", "Clear the staff notes").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").addHelpText(
57980
59352
  "after",
57981
59353
  `
57982
59354
  Staff-only. Requires a browser session from \`tracecli auth login\` with a Trace
@@ -57989,51 +59361,12 @@ Examples:
57989
59361
  tracecli pentest update PT-3 --priority AUTHN_BYPASS --priority AUTHZ_IDOR
57990
59362
  tracecli pentest update PT-3 --concerns "Worried about IDOR on invoice IDs"
57991
59363
  tracecli pentest update PT-3 --clear-concerns --clear-priorities
59364
+ tracecli pentest update PT-3 --staff-notes "Staging is behind a Cloudflare challenge; use headed agent-browser"
57992
59365
  tracecli pentest update PT-3 --title "Acme Q3" -o json`
57993
59366
  ).action(async (pentestId, options) => {
57994
59367
  await runUpdate5(pentestId, options);
57995
59368
  });
57996
59369
  }
57997
- function failQuestionnaire(status, body, action, notFoundLabel) {
57998
- const { message } = readErrorFields(body);
57999
- if (status === UNAUTHORIZED_STATUS2) {
58000
- exitAuthFailed();
58001
- }
58002
- if (status === 403) {
58003
- if (action === "write") {
58004
- console.error(
58005
- colorError(
58006
- "Questionnaire writes require an organization admin (or Trace staff) signed in with a browser session. API keys are rejected."
58007
- )
58008
- );
58009
- } else {
58010
- console.error(
58011
- colorError(message ?? "You do not have permission to view this questionnaire.")
58012
- );
58013
- }
58014
- process.exit(1);
58015
- }
58016
- if (status === 404) {
58017
- console.error(
58018
- colorError(notFoundLabel ? `Pentest not found: ${notFoundLabel}.` : message ?? "Not found.")
58019
- );
58020
- process.exit(1);
58021
- }
58022
- if (status === 400) {
58023
- console.error(colorError(message ?? "Invalid questionnaire answers (status 400)."));
58024
- process.exit(1);
58025
- }
58026
- if (status === 409) {
58027
- console.error(colorError(message ?? QUESTIONNAIRE_LOCKED_MESSAGE));
58028
- process.exit(1);
58029
- }
58030
- console.error(
58031
- colorError(
58032
- `Failed to ${action === "write" ? "save" : "load"} questionnaire (status ${status})${message ? `: ${message}` : "."}`
58033
- )
58034
- );
58035
- process.exit(1);
58036
- }
58037
59370
  async function fetchQuestionnaire(organizationId, pentestId, notFoundLabel) {
58038
59371
  const result = await authenticatedRequest(
58039
59372
  (client) => client.pentest.getCustomerPentestQuestionnaire({
@@ -58041,7 +59374,11 @@ async function fetchQuestionnaire(organizationId, pentestId, notFoundLabel) {
58041
59374
  })
58042
59375
  );
58043
59376
  if (result.status !== SUCCESS_STATUS) {
58044
- failQuestionnaire(result.status, result.body, "read", notFoundLabel);
59377
+ failIntakeRequest(result.status, result.body, {
59378
+ subject: "questionnaire",
59379
+ action: "read",
59380
+ notFound: { what: "Pentest", label: notFoundLabel }
59381
+ });
58045
59382
  }
58046
59383
  return toQuestionnaireGetBody(result.body);
58047
59384
  }
@@ -58054,7 +59391,11 @@ async function putQuestionnaire(organizationId, pentestId, answers, notFoundLabe
58054
59391
  })
58055
59392
  );
58056
59393
  if (result.status !== SUCCESS_STATUS) {
58057
- failQuestionnaire(result.status, result.body, "write", notFoundLabel);
59394
+ failIntakeRequest(result.status, result.body, {
59395
+ subject: "questionnaire",
59396
+ action: "write",
59397
+ notFound: { what: "Pentest", label: notFoundLabel }
59398
+ });
58058
59399
  }
58059
59400
  return toQuestionnaireGetBody(result.body);
58060
59401
  }
@@ -58066,7 +59407,11 @@ async function submitQuestionnaire(organizationId, pentestId, answers, notFoundL
58066
59407
  })
58067
59408
  );
58068
59409
  if (result.status !== SUCCESS_STATUS) {
58069
- failQuestionnaire(result.status, result.body, "write", notFoundLabel);
59410
+ failIntakeRequest(result.status, result.body, {
59411
+ subject: "questionnaire",
59412
+ action: "write",
59413
+ notFound: { what: "Pentest", label: notFoundLabel }
59414
+ });
58070
59415
  }
58071
59416
  return toQuestionnaireGetBody(result.body);
58072
59417
  }
@@ -58149,35 +59494,85 @@ async function promptQuestionnaireAnswers(existing) {
58149
59494
  }
58150
59495
  return working;
58151
59496
  }
58152
- async function runQuestionnaire(pentestIdentifier, options) {
58153
- const show = options.show === true;
58154
- const filePath = options.file;
58155
- const submit = options.submit === true;
58156
- if (show && filePath !== void 0) {
58157
- console.error(colorError("Pass only one of --show / --file."));
59497
+ function resolveQuestionnaireMode(options) {
59498
+ const exclusive = [
59499
+ { flag: "--show", set: options.show === true },
59500
+ { flag: "--questions", set: options.questions === true },
59501
+ { flag: "--export", set: options.export === true },
59502
+ { flag: "--file", set: options.file !== void 0 }
59503
+ ];
59504
+ const chosen = exclusive.filter((entry) => entry.set);
59505
+ if (chosen.length > 1) {
59506
+ console.error(colorError(`Pass only one of ${chosen.map((c84) => c84.flag).join(" / ")}.`));
59507
+ process.exit(1);
59508
+ }
59509
+ const only = chosen[0];
59510
+ if (only !== void 0 && only.flag !== "--file" && options.submit === true) {
59511
+ console.error(colorError(`Pass only one of ${only.flag} / --submit.`));
58158
59512
  process.exit(1);
58159
59513
  }
58160
- if (show && submit) {
58161
- console.error(colorError("Pass only one of --show / --submit."));
59514
+ if (options.merge === true && options.file === void 0) {
59515
+ console.error(colorError("--merge needs --file <path>."));
58162
59516
  process.exit(1);
58163
59517
  }
59518
+ if (options.show) return { kind: "show" };
59519
+ if (options.questions) return { kind: "questions" };
59520
+ if (options.export) return { kind: "export" };
59521
+ if (options.file !== void 0) {
59522
+ return { kind: "file", path: options.file, merge: options.merge === true };
59523
+ }
59524
+ if (options.submit) return { kind: "submit" };
59525
+ return { kind: "interactive" };
59526
+ }
59527
+ function printQuestionnaireRecord(body, output, successLine) {
59528
+ if (output === "json") {
59529
+ printJson(buildQuestionnaireRecordJson(body));
59530
+ return;
59531
+ }
59532
+ if (successLine !== void 0) {
59533
+ console.log(colorSuccess(successLine));
59534
+ }
59535
+ console.log(formatQuestionnaireShow(questionnaireShowInput(body)));
59536
+ }
59537
+ function submittedLine(record2) {
59538
+ return record2.editable ? "Submitted questionnaire. You can edit until testing starts." : "Submitted questionnaire.";
59539
+ }
59540
+ async function runQuestionnaire(pentestIdentifier, options) {
59541
+ const mode = resolveQuestionnaireMode(options);
59542
+ const output = parseTextOutput(options.output);
59543
+ const submit = options.submit === true;
59544
+ if (mode.kind === "questions") {
59545
+ const catalog = buildQuestionnaireCatalog();
59546
+ if (output === "json") {
59547
+ printJson(catalog);
59548
+ } else {
59549
+ console.log(formatQuestionnaireCatalog(catalog));
59550
+ }
59551
+ return;
59552
+ }
58164
59553
  const isInteractiveTty = Boolean(process.stdin.isTTY && process.stdout.isTTY);
58165
- if (!show && filePath === void 0 && !submit && !isInteractiveTty) {
59554
+ if (mode.kind === "interactive" && !isInteractiveTty) {
58166
59555
  console.error(
58167
59556
  colorError(
58168
- "Interactive questionnaire requires a TTY. Use --show to print answers, --file <path> to upload an answers JSON object, or --submit to mark the current answers done."
59557
+ "Interactive questionnaire requires a TTY. Without one: --questions lists every question with its answer key, --export prints the current answers as JSON (blanks as null), --file <path> --merge uploads only the keys the file sets, --show prints the saved state, --submit marks it done. --file without --merge replaces every saved answer, so keys missing or null in the file are cleared."
58169
59558
  )
58170
59559
  );
58171
59560
  process.exit(2);
58172
59561
  }
58173
59562
  const organizationId = await resolveOrganizationId();
58174
59563
  const pentestId = await resolvePentestId(organizationId, pentestIdentifier);
58175
- if (show) {
59564
+ if (mode.kind === "show") {
58176
59565
  const current2 = await fetchQuestionnaire(organizationId, pentestId, pentestIdentifier);
58177
- console.log(formatQuestionnaireShow(questionnaireShowInput(current2)));
59566
+ printQuestionnaireRecord(current2, output);
58178
59567
  return;
58179
59568
  }
58180
- if (filePath !== void 0) {
59569
+ if (mode.kind === "export") {
59570
+ const current2 = await fetchQuestionnaire(organizationId, pentestId, pentestIdentifier);
59571
+ printJson(buildQuestionnaireExport(current2.answers));
59572
+ return;
59573
+ }
59574
+ if (mode.kind === "file") {
59575
+ const filePath = mode.path;
58181
59576
  let raw;
58182
59577
  try {
58183
59578
  raw = readFileSync7(filePath, "utf8");
@@ -58190,28 +59585,27 @@ async function runQuestionnaire(pentestIdentifier, options) {
58190
59585
  console.error(colorError(parsed.error));
58191
59586
  process.exit(1);
58192
59587
  }
58193
- const saved2 = submit ? await submitQuestionnaire(organizationId, pentestId, parsed.answers, pentestIdentifier) : await putQuestionnaire(organizationId, pentestId, parsed.answers, pentestIdentifier);
58194
- console.log(
58195
- colorSuccess(
58196
- submit ? "Submitted questionnaire. You can edit until testing starts." : "Saved questionnaire answers."
58197
- )
59588
+ let answers = parsed.answers;
59589
+ if (mode.merge) {
59590
+ const current2 = await fetchQuestionnaire(organizationId, pentestId, pentestIdentifier);
59591
+ answers = mergeIntakeAnswers(current2.answers, parsed.answers);
59592
+ }
59593
+ const saved2 = submit ? await submitQuestionnaire(organizationId, pentestId, answers, pentestIdentifier) : await putQuestionnaire(organizationId, pentestId, answers, pentestIdentifier);
59594
+ printQuestionnaireRecord(
59595
+ saved2,
59596
+ output,
59597
+ submit ? submittedLine(saved2) : "Saved questionnaire answers."
58198
59598
  );
58199
- console.log(formatQuestionnaireShow(questionnaireShowInput(saved2)));
58200
59599
  return;
58201
59600
  }
58202
- if (submit) {
59601
+ if (mode.kind === "submit") {
58203
59602
  const submitted = await submitQuestionnaire(
58204
59603
  organizationId,
58205
59604
  pentestId,
58206
59605
  void 0,
58207
59606
  pentestIdentifier
58208
59607
  );
58209
- console.log(
58210
- colorSuccess(
58211
- submitted.editable ? "Submitted questionnaire. You can edit until testing starts." : "Submitted questionnaire."
58212
- )
58213
- );
58214
- console.log(formatQuestionnaireShow(questionnaireShowInput(submitted)));
59608
+ printQuestionnaireRecord(submitted, output, submittedLine(submitted));
58215
59609
  return;
58216
59610
  }
58217
59611
  const current = await fetchQuestionnaire(organizationId, pentestId, pentestIdentifier);
@@ -58225,32 +59619,48 @@ async function runQuestionnaire(pentestIdentifier, options) {
58225
59619
  }
58226
59620
  const merged = await promptQuestionnaireAnswers(current.answers);
58227
59621
  const saved = await putQuestionnaire(organizationId, pentestId, merged, pentestIdentifier);
58228
- console.log(colorSuccess("Saved questionnaire answers."));
58229
- console.log(formatQuestionnaireShow(questionnaireShowInput(saved)));
59622
+ printQuestionnaireRecord(saved, output, "Saved questionnaire answers.");
58230
59623
  }
58231
59624
  function createQuestionnaireCommand() {
58232
- return new Command("questionnaire").description("View or fill the customer pentest intake questionnaire").argument("<pentestId>", "Pentest display ID (e.g. PT-3) or UUID").option("--show", "Print current answers using shared labels (GET)").option(
59625
+ return new Command("questionnaire").description("View or fill the customer pentest intake questionnaire").argument("<pentestId>", "Pentest display ID (e.g. PT-3) or UUID").option("--show", "Print every question with its answer key and current answer (GET)").option(
59626
+ "--questions",
59627
+ "Print the question catalog: keys, kinds, option values (no auth needed)"
59628
+ ).option("--export", "Print the current answers as JSON, every key present, blanks as null").option(
58233
59629
  "--file <path>",
58234
- "Upload answers from a JSON file (answers object only \u2014 not { answers }; whole-object replace)"
58235
- ).option("--submit", "Mark the questionnaire as done (still editable until testing starts)").addHelpText(
59630
+ "Upload answers from a JSON file (the answers object itself). Replaces every saved answer: keys missing or null in the file are cleared unless you add --merge"
59631
+ ).option(
59632
+ "--merge",
59633
+ "With --file: change only the keys the file sets; keep every other saved answer"
59634
+ ).option("--submit", "Mark the questionnaire as done (still editable until testing starts)").option("-o, --output <format>", "Output format: text, json (default: text)", "text").addHelpText(
58236
59635
  "after",
58237
59636
  `
58238
- Optional scoping questionnaire. Editable until testing starts. Reads accept a
58239
- browser session or API key;
58240
- writes require an organization admin (or Trace staff) signed in via \`tracecli
58241
- auth login\` \u2014 API keys are rejected.
59637
+ Optional scoping questionnaire, editable until testing starts.
59638
+ Every mode but --questions needs a browser session from \`tracecli auth login\`;
59639
+ API keys are rejected. Writes (--file, --submit, the TTY fill) also need an
59640
+ organization admin (or Trace staff).
58242
59641
 
58243
59642
  Modes:
58244
- --show GET and print key: value with shared customer-facing labels
58245
- --file <path> PUT the file's answers object (CLI wraps { answers } for the API)
59643
+ --questions The question catalog with answer keys and option values
59644
+ --show GET and print every question with its key and current answer
59645
+ --export GET and print the answers object as JSON (all keys, null when blank)
59646
+ --file <path> PUT the file's answers object, replacing every saved answer;
59647
+ add --merge to change only the keys the file sets
58246
59648
  --submit Mark as done (combine with --file or TTY fill, or alone)
58247
59649
  (TTY default) load-merge-write: GET, prompt each question, confirm, PUT
58248
59650
 
58249
- Non-interactive shells must pass --show, --file, or --submit (exit 2 otherwise).
59651
+ Every mode takes -o json. Non-interactive shells must pass one of the flags
59652
+ above (exit 2 otherwise).
59653
+
59654
+ Scripted fill (what a coding agent runs):
59655
+ tracecli pentest questionnaire PT-1 --questions
59656
+ tracecli pentest questionnaire PT-1 --export > answers.json
59657
+ # fill in the null values the code can answer, then
59658
+ tracecli pentest questionnaire PT-1 --file answers.json --merge
59659
+ tracecli pentest documents attach --pentest PT-1 --file ./threat-model.pdf
58250
59660
 
58251
59661
  Examples:
58252
59662
  tracecli pentest questionnaire PT-1 --show
58253
- tracecli pentest questionnaire PT-1 --file answers.json
59663
+ tracecli pentest questionnaire PT-1 --show -o json
58254
59664
  tracecli pentest questionnaire PT-1 --file answers.json --submit
58255
59665
  tracecli pentest questionnaire PT-1 --submit
58256
59666
  tracecli pentest questionnaire PT-1`
@@ -58260,11 +59670,12 @@ Examples:
58260
59670
  }
58261
59671
  function createPentestCommand() {
58262
59672
  const pentest = new Command("pentest").description("Manage pentests");
58263
- pentest.addCommand(createListCommand6());
59673
+ pentest.addCommand(createListCommand7());
58264
59674
  pentest.addCommand(createUpdateCommand4());
58265
59675
  pentest.addCommand(createCreatePentestCommand(), { hidden: true });
58266
59676
  pentest.addCommand(createVisibilityCommand(), { hidden: true });
58267
59677
  pentest.addCommand(createQuestionnaireCommand());
59678
+ pentest.addCommand(createDocumentsCommand());
58268
59679
  pentest.addCommand(createFindingsCommand());
58269
59680
  pentest.addCommand(createHypothesesCommand(), { hidden: true });
58270
59681
  pentest.addCommand(createReportCommand());
@@ -58901,7 +60312,7 @@ Examples:
58901
60312
  // src/upload/download.ts
58902
60313
  import { createHash as createHash2 } from "crypto";
58903
60314
  import { open as open3, rename as rename2, rm as rm2, stat as stat2 } from "fs/promises";
58904
- import { basename as basename5, join as join8, resolve as resolve2 } from "path";
60315
+ import { basename as basename6, join as join8, resolve as resolve2 } from "path";
58905
60316
  var CodeUploadDownloadError = class extends Error {
58906
60317
  constructor(message) {
58907
60318
  super(message);
@@ -58911,7 +60322,7 @@ var CodeUploadDownloadError = class extends Error {
58911
60322
  function safeDownloadFilename(upload) {
58912
60323
  const extension = requiredExtensionForKind(upload.artifactKind);
58913
60324
  const fallback = `app${extension}`;
58914
- const stripped = Array.from(basename5(upload.originalFilename.replace(/\\/g, "/")), (char) => {
60325
+ const stripped = Array.from(basename6(upload.originalFilename.replace(/\\/g, "/")), (char) => {
58915
60326
  const code = char.codePointAt(0) ?? 0;
58916
60327
  return code <= 31 || code === 127 ? "_" : char;
58917
60328
  }).join("").trim();
@@ -59326,13 +60737,13 @@ function buildUploadsTable(uploads) {
59326
60737
  size: formatArchiveBytes(upload.sizeBytes),
59327
60738
  id: upload.codeUploadId
59328
60739
  }));
59329
- const nameWidth = Math.max(4, ...cells.map((c83) => c83.name.length));
59330
- const kindWidth = Math.max(4, ...cells.map((c83) => c83.kind.length));
59331
- const statusWidth = Math.max(6, ...cells.map((c83) => c83.status.length));
59332
- const sizeWidth = Math.max(4, ...cells.map((c83) => c83.size.length));
60740
+ const nameWidth = Math.max(4, ...cells.map((c84) => c84.name.length));
60741
+ const kindWidth = Math.max(4, ...cells.map((c84) => c84.kind.length));
60742
+ const statusWidth = Math.max(6, ...cells.map((c84) => c84.status.length));
60743
+ const sizeWidth = Math.max(4, ...cells.map((c84) => c84.size.length));
59333
60744
  const header = `${"NAME".padEnd(nameWidth)} ${"KIND".padEnd(kindWidth)} ${"STATUS".padEnd(statusWidth)} ${"SIZE".padEnd(sizeWidth)} ID`;
59334
60745
  const rows = cells.map(
59335
- (c83) => `${c83.name.padEnd(nameWidth)} ${c83.kind.padEnd(kindWidth)} ${c83.status.padEnd(statusWidth)} ${c83.size.padEnd(sizeWidth)} ${c83.id}`
60746
+ (c84) => `${c84.name.padEnd(nameWidth)} ${c84.kind.padEnd(kindWidth)} ${c84.status.padEnd(statusWidth)} ${c84.size.padEnd(sizeWidth)} ${c84.id}`
59336
60747
  );
59337
60748
  return { header, rows };
59338
60749
  }
@@ -59686,7 +61097,7 @@ async function runResume(codeUploadIdArg, filePath, options, runtime) {
59686
61097
  }
59687
61098
  outputUpload(upload, output);
59688
61099
  }
59689
- async function runList5(options) {
61100
+ async function runList6(options) {
59690
61101
  const output = parseOutput(options.output);
59691
61102
  if (options.web) {
59692
61103
  const slug = await resolveOrganizationSlug();
@@ -59819,7 +61230,7 @@ async function runDelete5(codeUploadIdArg, options) {
59819
61230
  }
59820
61231
  console.log(colorSuccess(`Deleted upload ${codeUploadId}`));
59821
61232
  }
59822
- async function runLink(options) {
61233
+ async function runLink2(options) {
59823
61234
  const output = parseOutput(options.output);
59824
61235
  const applicationId = requireUuid(options.app, "--app");
59825
61236
  const codeUploadId = requireUuid(options.upload, "--upload");
@@ -59901,9 +61312,9 @@ Examples:
59901
61312
  await runBuild(file2, options, runtime);
59902
61313
  });
59903
61314
  }
59904
- function createListCommand7() {
61315
+ function createListCommand8() {
59905
61316
  return new Command("list").description("List source and build uploads in the active organization").option("--kind <kind>", "Filter: source (code ZIP), apk, or ipa").option("--app <applicationId>", "Filter build uploads for one application").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").option("--no-pager", "Disable the interactive pager for table output").option("-w, --web", "Open the source uploads page in a browser").action(async (options) => {
59906
- await runList5(options);
61317
+ await runList6(options);
59907
61318
  });
59908
61319
  }
59909
61320
  function createGetCommand4() {
@@ -59951,7 +61362,7 @@ function createDeleteCommand5() {
59951
61362
  await runDelete5(codeUploadId, options);
59952
61363
  });
59953
61364
  }
59954
- function createLinkCommand() {
61365
+ function createLinkCommand2() {
59955
61366
  return new Command("link").description("Link a ready source upload to an application").requiredOption("--app <applicationId>", "Application UUID").requiredOption("--upload <codeUploadId>", "Upload UUID (must be a ready source ZIP)").option("--path <repositoryPath>", "Path within the archive").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").addHelpText(
59956
61367
  "after",
59957
61368
  `
@@ -59961,7 +61372,7 @@ APK and IPA builds attach to an application with \`upload build --app\`.
59961
61372
  Examples:
59962
61373
  tracecli upload link --app <applicationId> --upload <codeUploadId>`
59963
61374
  ).action(async (options) => {
59964
- await runLink(options);
61375
+ await runLink2(options);
59965
61376
  });
59966
61377
  }
59967
61378
  function createUploadCommand(runtime = {}) {
@@ -59969,13 +61380,13 @@ function createUploadCommand(runtime = {}) {
59969
61380
  const upload = new Command("upload").alias("code-upload").description("Upload source archives and mobile builds for pentest; download builds back");
59970
61381
  upload.addCommand(createCreateCommand5(resolved));
59971
61382
  upload.addCommand(createBuildCommand(resolved));
59972
- upload.addCommand(createListCommand7());
61383
+ upload.addCommand(createListCommand8());
59973
61384
  upload.addCommand(createGetCommand4());
59974
61385
  upload.addCommand(createDownloadCommand(resolved));
59975
61386
  upload.addCommand(createResumeCommand(resolved));
59976
61387
  upload.addCommand(createWaitCommand(resolved));
59977
61388
  upload.addCommand(createDeleteCommand5());
59978
- upload.addCommand(createLinkCommand());
61389
+ upload.addCommand(createLinkCommand2());
59979
61390
  return upload;
59980
61391
  }
59981
61392
 
@@ -60214,7 +61625,7 @@ Run \`tracecli vulnerabilities list\` to see available vulnerabilities.`
60214
61625
  printVulnerabilityDetail(result.body);
60215
61626
  });
60216
61627
  }
60217
- function createListCommand8() {
61628
+ function createListCommand9() {
60218
61629
  return new Command("list").description("List vulnerabilities for your organization").option("--repo <repo>", "Filter by repository name or owner/repo").option(
60219
61630
  "--severity <severity>",
60220
61631
  "Filter by severity (CRITICAL, HIGH, MEDIUM, LOW)",
@@ -60348,7 +61759,7 @@ Examples:
60348
61759
  function createVulnerabilitiesCommand() {
60349
61760
  const vulnerabilities = new Command("vulnerabilities").description("Manage vulnerabilities");
60350
61761
  vulnerabilities.addCommand(createGetCommand5());
60351
- vulnerabilities.addCommand(createListCommand8());
61762
+ vulnerabilities.addCommand(createListCommand9());
60352
61763
  return vulnerabilities;
60353
61764
  }
60354
61765