securewithtrace 0.35.0 → 0.36.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 +287 -70
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -27177,10 +27177,11 @@ var CreateOrganizationResponseSchema = external_exports.object({
27177
27177
  name: external_exports.string()
27178
27178
  });
27179
27179
  var OnboardingItemValues = [
27180
+ "INVITE_MEMBERS",
27180
27181
  "CONNECT_CODE",
27181
27182
  "CONNECT_CLOUD",
27182
- "CREATE_APPLICATION",
27183
- "INVITE_MEMBERS"
27183
+ "FILL_QUESTIONNAIRE",
27184
+ "CREATE_APPLICATION"
27184
27185
  ];
27185
27186
  var OnboardingItemStatusValues = ["NOT_DONE", "DONE", "DISMISSED"];
27186
27187
  var OnboardingChecklistItemSchema = external_exports.object({
@@ -27189,6 +27190,7 @@ var OnboardingChecklistItemSchema = external_exports.object({
27189
27190
  });
27190
27191
  var OnboardingChecklistResponseSchema = external_exports.object({
27191
27192
  hasProvisionedPentest: external_exports.boolean(),
27193
+ provisionedPentestDisplayId: external_exports.number().int().nullable().optional(),
27192
27194
  items: external_exports.array(OnboardingChecklistItemSchema)
27193
27195
  });
27194
27196
  var OnboardingItemParamsSchema = external_exports.object({
@@ -35605,6 +35607,22 @@ import { join } from "path";
35605
35607
  var ACCESS_TOKEN_FILENAME = "token";
35606
35608
  var REFRESH_TOKEN_FILENAME = "refresh-token";
35607
35609
  var SELECTED_ORGANIZATION_FILENAME = "organization";
35610
+ var TRACE_API_KEY_PREFIX = "trace_sk_";
35611
+ var TRACE_API_KEY_ENV = "TRACE_API_KEY";
35612
+ var API_KEY_ID_LENGTH = 16;
35613
+ var API_KEY_SECRET_LENGTH = 22;
35614
+ var API_KEY_CHECKSUM_LENGTH = 6;
35615
+ var TRACE_API_KEY_PATTERN = new RegExp(
35616
+ `^${TRACE_API_KEY_PREFIX}[0-9A-Za-z]{${API_KEY_ID_LENGTH}}_[0-9A-Za-z]{${API_KEY_SECRET_LENGTH}}[0-9A-Za-z]{${API_KEY_CHECKSUM_LENGTH}}$`
35617
+ );
35618
+ var InvalidTraceApiKeyError = class extends Error {
35619
+ constructor() {
35620
+ super(
35621
+ `${TRACE_API_KEY_ENV} does not look like a Trace API key (expected ${TRACE_API_KEY_PREFIX}...)`
35622
+ );
35623
+ this.name = "InvalidTraceApiKeyError";
35624
+ }
35625
+ };
35608
35626
  function resolveTraceDir() {
35609
35627
  const override = process.env["TRACE_CLI_AUTH_DIR"];
35610
35628
  if (override && override.trim().length > 0) {
@@ -35717,6 +35735,48 @@ function removeToken() {
35717
35735
  }
35718
35736
  return removed;
35719
35737
  }
35738
+ function getTraceApiKeyEnv() {
35739
+ const value = process.env[TRACE_API_KEY_ENV];
35740
+ if (value === void 0) {
35741
+ return null;
35742
+ }
35743
+ const trimmed = value.trim();
35744
+ return trimmed.length > 0 ? trimmed : null;
35745
+ }
35746
+ function isTraceApiKeyEnvSet() {
35747
+ return getTraceApiKeyEnv() !== null;
35748
+ }
35749
+ function isTraceApiKeyToken(token) {
35750
+ return token.startsWith(TRACE_API_KEY_PREFIX);
35751
+ }
35752
+ function isValidTraceApiKeyShape(token) {
35753
+ return TRACE_API_KEY_PATTERN.test(token);
35754
+ }
35755
+ function publicApiKeyId(token) {
35756
+ const keyId = token.slice(
35757
+ TRACE_API_KEY_PREFIX.length,
35758
+ TRACE_API_KEY_PREFIX.length + API_KEY_ID_LENGTH
35759
+ );
35760
+ return `${TRACE_API_KEY_PREFIX}${keyId}`;
35761
+ }
35762
+ function resolveCredential(readStoredToken = readToken) {
35763
+ const envKey = getTraceApiKeyEnv();
35764
+ if (envKey !== null) {
35765
+ if (!isValidTraceApiKeyShape(envKey)) {
35766
+ throw new InvalidTraceApiKeyError();
35767
+ }
35768
+ return { token: envKey, kind: "api_key", source: "env" };
35769
+ }
35770
+ const token = readStoredToken();
35771
+ if (!token) {
35772
+ return null;
35773
+ }
35774
+ return {
35775
+ token,
35776
+ kind: isTraceApiKeyToken(token) ? "api_key" : "user",
35777
+ source: "file"
35778
+ };
35779
+ }
35720
35780
 
35721
35781
  // ../../node_modules/.bun/chalk@5.6.2/node_modules/chalk/source/vendor/ansi-styles/index.js
35722
35782
  var ANSI_BACKGROUND_OFFSET = 10;
@@ -36256,7 +36316,11 @@ function colorAccentUnderline(text, colorizer = source_default) {
36256
36316
  }
36257
36317
 
36258
36318
  // src/api-client.ts
36259
- var readTokenSafe = readToken;
36319
+ var HUMAN_PRINCIPAL_REQUIRED_MESSAGE = "Human principal required for this operation";
36320
+ var FORBIDDEN_STATUS = 403;
36321
+ function readTokenSafe() {
36322
+ return resolveCredential(readToken)?.token ?? null;
36323
+ }
36260
36324
  var PRODUCTION_API_URL = "https://api.securewithtrace.com";
36261
36325
  var UNAUTHORIZED_STATUS = 401;
36262
36326
  var REFRESH_AUTH_FAILURE_STATUS_CODES = /* @__PURE__ */ new Set([400, 401]);
@@ -36290,6 +36354,84 @@ function getApiBaseUrl() {
36290
36354
  }
36291
36355
  return raw.replace(/\/+$/, "");
36292
36356
  }
36357
+ function loadCredentialOrExit() {
36358
+ const credential = resolveCredentialOrExit();
36359
+ if (!credential) {
36360
+ console.error(colorError("Not logged in. Run `tracecli auth login` first."));
36361
+ process.exit(1);
36362
+ }
36363
+ return credential;
36364
+ }
36365
+ function resolveCredentialOrExit() {
36366
+ try {
36367
+ return resolveCredential(readToken);
36368
+ } catch (error48) {
36369
+ if (error48 instanceof InvalidTraceApiKeyError) {
36370
+ console.error(colorError(error48.message));
36371
+ process.exit(1);
36372
+ }
36373
+ throw error48;
36374
+ }
36375
+ }
36376
+ function jwtOrganizationId(credential) {
36377
+ if (!credential || credential.kind !== "user") {
36378
+ return null;
36379
+ }
36380
+ return extractOrganizationIdFromToken(credential.token);
36381
+ }
36382
+ function storedSelectionFor(credential) {
36383
+ if (!credential || credential.kind === "api_key") {
36384
+ return null;
36385
+ }
36386
+ return readSelectedOrganizationId();
36387
+ }
36388
+ function persistActiveOrganizationSelection(credential, activeOrganization, selectedOrganizationId, warnOnStaleSelection) {
36389
+ if (credential?.kind === "api_key") {
36390
+ return;
36391
+ }
36392
+ if (activeOrganization.staleSelectionDetected) {
36393
+ removeSelectedOrganizationId();
36394
+ writeSelectedOrganizationId(activeOrganization.organization.organizationId);
36395
+ if (warnOnStaleSelection && selectedOrganizationId) {
36396
+ console.warn(
36397
+ colorWarning(
36398
+ `Stored organization '${selectedOrganizationId}' is unavailable. Using '${activeOrganization.organization.name}' (${activeOrganization.organization.organizationId}) instead.`
36399
+ )
36400
+ );
36401
+ }
36402
+ return;
36403
+ }
36404
+ if (activeOrganization.source === "default") {
36405
+ writeSelectedOrganizationId(activeOrganization.organization.organizationId);
36406
+ }
36407
+ }
36408
+ function responseBody(result) {
36409
+ if (!Object.prototype.hasOwnProperty.call(result, "body")) {
36410
+ return void 0;
36411
+ }
36412
+ return Reflect.get(result, "body");
36413
+ }
36414
+ function responseMessage(result) {
36415
+ const body = responseBody(result);
36416
+ if (typeof body !== "object" || body === null || !("message" in body)) {
36417
+ return void 0;
36418
+ }
36419
+ const message = body.message;
36420
+ return typeof message === "string" ? message : void 0;
36421
+ }
36422
+ function isHumanPrincipalRequiredResult(result) {
36423
+ return result.status === FORBIDDEN_STATUS && responseMessage(result) === HUMAN_PRINCIPAL_REQUIRED_MESSAGE;
36424
+ }
36425
+ function exitApiKeyUnauthorized() {
36426
+ console.error(
36427
+ colorError("The API key is invalid, expired, or revoked. Check the dashboard's API Keys page.")
36428
+ );
36429
+ process.exit(1);
36430
+ }
36431
+ function exitHumanPrincipalRequired() {
36432
+ console.error(colorError("This command requires a human login; run `tracecli auth login`."));
36433
+ process.exit(1);
36434
+ }
36293
36435
  function createClient(token) {
36294
36436
  return initClient(v1Contract, {
36295
36437
  baseUrl: getApiBaseUrl(),
@@ -36371,7 +36513,7 @@ async function refreshStoredAccessToken() {
36371
36513
  if (!clientId || !refreshToken) {
36372
36514
  return null;
36373
36515
  }
36374
- const tokenOrganizationId = extractOrganizationIdFromToken(readToken()) ?? void 0;
36516
+ const tokenOrganizationId = jwtOrganizationId(resolveCredentialOrExit()) ?? void 0;
36375
36517
  try {
36376
36518
  return persistRefreshResult(await callRefresh(clientId, refreshToken), refreshToken);
36377
36519
  } catch (error48) {
@@ -36416,12 +36558,17 @@ async function refreshStoredAccessTokenForOrganization(workosOrganizationId) {
36416
36558
  }
36417
36559
  }
36418
36560
  async function authenticatedRequest(request) {
36419
- const token = readToken();
36420
- if (!token) {
36421
- console.error(colorError("Not logged in. Run `tracecli auth login` first."));
36422
- process.exit(1);
36561
+ const credential = loadCredentialOrExit();
36562
+ const firstResult = await request(createClient(credential.token));
36563
+ if (credential.kind === "api_key") {
36564
+ if (firstResult.status === UNAUTHORIZED_STATUS) {
36565
+ exitApiKeyUnauthorized();
36566
+ }
36567
+ if (isHumanPrincipalRequiredResult(firstResult)) {
36568
+ exitHumanPrincipalRequired();
36569
+ }
36570
+ return firstResult;
36423
36571
  }
36424
- const firstResult = await request(createClient(token));
36425
36572
  if (firstResult.status !== UNAUTHORIZED_STATUS) {
36426
36573
  return firstResult;
36427
36574
  }
@@ -36432,13 +36579,22 @@ async function authenticatedRequest(request) {
36432
36579
  return request(createClient(refreshedToken));
36433
36580
  }
36434
36581
  async function authenticatedRequestSafe(request) {
36435
- const token = readTokenSafe();
36436
- if (!token) {
36582
+ let credential;
36583
+ try {
36584
+ credential = resolveCredential(readToken);
36585
+ } catch (error48) {
36586
+ if (error48 instanceof InvalidTraceApiKeyError) {
36587
+ console.error(colorError(error48.message));
36588
+ process.exit(1);
36589
+ }
36590
+ throw error48;
36591
+ }
36592
+ if (!credential) {
36437
36593
  return null;
36438
36594
  }
36439
36595
  try {
36440
- const firstResult = await request(createClient(token));
36441
- if (firstResult.status !== UNAUTHORIZED_STATUS) {
36596
+ const firstResult = await request(createClient(credential.token));
36597
+ if (credential.kind === "api_key" || firstResult.status !== UNAUTHORIZED_STATUS) {
36442
36598
  return firstResult;
36443
36599
  }
36444
36600
  const refreshedToken = await refreshStoredAccessToken();
@@ -36514,8 +36670,9 @@ async function resolveOrganizationId(client) {
36514
36670
  process.exit(1);
36515
36671
  }
36516
36672
  const organizations = result.body;
36517
- const selectedOrganizationId = readSelectedOrganizationId();
36518
- const tokenOrganizationId = extractOrganizationIdFromToken(readToken());
36673
+ const credential = resolveCredentialOrExit();
36674
+ const selectedOrganizationId = storedSelectionFor(credential);
36675
+ const tokenOrganizationId = jwtOrganizationId(credential);
36519
36676
  const activeOrganization = determineActiveOrganization(
36520
36677
  organizations,
36521
36678
  selectedOrganizationId,
@@ -36530,19 +36687,7 @@ async function resolveOrganizationId(client) {
36530
36687
  console.error(colorError("Invalid organization ID received from API."));
36531
36688
  process.exit(1);
36532
36689
  }
36533
- if (activeOrganization.staleSelectionDetected) {
36534
- removeSelectedOrganizationId();
36535
- writeSelectedOrganizationId(id);
36536
- if (selectedOrganizationId) {
36537
- console.warn(
36538
- colorWarning(
36539
- `Stored organization '${selectedOrganizationId}' is unavailable. Using '${activeOrganization.organization.name}' (${id}) instead.`
36540
- )
36541
- );
36542
- }
36543
- } else if (activeOrganization.source === "default") {
36544
- writeSelectedOrganizationId(id);
36545
- }
36690
+ persistActiveOrganizationSelection(credential, activeOrganization, selectedOrganizationId, true);
36546
36691
  return id;
36547
36692
  }
36548
36693
  async function resolveOrganizationSlug() {
@@ -36558,8 +36703,9 @@ async function resolveOrganizationSlug() {
36558
36703
  process.exit(1);
36559
36704
  }
36560
36705
  const organizations = result.body;
36561
- const selectedOrganizationId = readSelectedOrganizationId();
36562
- const tokenOrganizationId = extractOrganizationIdFromToken(readToken());
36706
+ const credential = resolveCredentialOrExit();
36707
+ const selectedOrganizationId = storedSelectionFor(credential);
36708
+ const tokenOrganizationId = jwtOrganizationId(credential);
36563
36709
  const activeOrganization = determineActiveOrganization(
36564
36710
  organizations,
36565
36711
  selectedOrganizationId,
@@ -36583,8 +36729,9 @@ async function resolveOrganizationIdSafe(client) {
36583
36729
  return null;
36584
36730
  }
36585
36731
  const organizations = result.body;
36586
- const selectedOrganizationId = readSelectedOrganizationId();
36587
- const tokenOrganizationId = extractOrganizationIdFromToken(readToken());
36732
+ const credential = resolveCredentialOrExit();
36733
+ const selectedOrganizationId = storedSelectionFor(credential);
36734
+ const tokenOrganizationId = jwtOrganizationId(credential);
36588
36735
  const activeOrganization = determineActiveOrganization(
36589
36736
  organizations,
36590
36737
  selectedOrganizationId,
@@ -36597,12 +36744,12 @@ async function resolveOrganizationIdSafe(client) {
36597
36744
  if (!SAFE_ID_PATTERN.test(id)) {
36598
36745
  return null;
36599
36746
  }
36600
- if (activeOrganization.staleSelectionDetected) {
36601
- removeSelectedOrganizationId();
36602
- writeSelectedOrganizationId(id);
36603
- } else if (activeOrganization.source === "default") {
36604
- writeSelectedOrganizationId(id);
36605
- }
36747
+ persistActiveOrganizationSelection(
36748
+ credential,
36749
+ activeOrganization,
36750
+ selectedOrganizationId,
36751
+ false
36752
+ );
36606
36753
  return id;
36607
36754
  } catch {
36608
36755
  return null;
@@ -37115,6 +37262,9 @@ function decodeJwtExpiration(token) {
37115
37262
  return null;
37116
37263
  }
37117
37264
  }
37265
+ function shouldRetryCloneTokenAfterAuthFailure(kind) {
37266
+ return kind === "user";
37267
+ }
37118
37268
  function resolveLocalCloneAuthFailure() {
37119
37269
  const token = readTokenSafe();
37120
37270
  if (!token) {
@@ -37173,7 +37323,10 @@ function resolveWorkosOrganizationId(organizations, organizationId) {
37173
37323
  }
37174
37324
  return workosOrganizationId;
37175
37325
  }
37176
- async function maybeRefreshCloneTokenSessionForOrganization(organizationId, listOrganizations = listOrganizationsSafe, refreshForOrganization = (workosOrganizationId) => refreshStoredAccessTokenForOrganization(workosOrganizationId)) {
37326
+ async function maybeRefreshCloneTokenSessionForOrganization(organizationId, listOrganizations = listOrganizationsSafe, refreshForOrganization = (workosOrganizationId) => refreshStoredAccessTokenForOrganization(workosOrganizationId), credentialKind = () => resolveCredential(readToken)?.kind ?? null) {
37327
+ if (credentialKind() === "api_key") {
37328
+ return false;
37329
+ }
37177
37330
  const organizations = await listOrganizations();
37178
37331
  if (!organizations) {
37179
37332
  return false;
@@ -37479,7 +37632,7 @@ async function fetchCloneToken(repoUrl) {
37479
37632
  body: { repositoryUrl: repoUrl }
37480
37633
  })
37481
37634
  );
37482
- if (result && (result.status === 401 || result.status === 403)) {
37635
+ if (shouldRetryCloneTokenAfterAuthFailure(resolveCredential(readToken)?.kind) && result && (result.status === 401 || result.status === 403)) {
37483
37636
  const refreshed = await maybeRefreshCloneTokenSessionForOrganization(organizationId);
37484
37637
  if (refreshed) {
37485
37638
  result = await authenticatedRequestSafe(
@@ -39775,6 +39928,14 @@ function matchesOrgFilter(item, filter) {
39775
39928
  function sanitizeForOutput(value) {
39776
39929
  return value.replace(/[\r\n\t]/g, " ").trim();
39777
39930
  }
39931
+ var API_KEY_ENV_REFUSAL = "TRACE_API_KEY is set and takes precedence over ~/.trace. Unset it to manage a device-flow session.";
39932
+ function refuseIfApiKeyEnv(action) {
39933
+ if (!isTraceApiKeyEnvSet()) {
39934
+ return;
39935
+ }
39936
+ console.error(colorError(`Cannot ${action} while TRACE_API_KEY is set. ${API_KEY_ENV_REFUSAL}`));
39937
+ process.exit(1);
39938
+ }
39778
39939
  function normalizeForComparison(value) {
39779
39940
  return value.trim().toLowerCase();
39780
39941
  }
@@ -39813,7 +39974,8 @@ async function fetchOrganizationContext() {
39813
39974
  process.exit(1);
39814
39975
  }
39815
39976
  const organizations = result.body;
39816
- const selectedOrganizationId = readSelectedOrganizationId();
39977
+ const credential = resolveCredential(readToken);
39978
+ const selectedOrganizationId = credential?.kind === "api_key" ? null : readSelectedOrganizationId();
39817
39979
  const active = determineActiveOrganization(organizations, selectedOrganizationId);
39818
39980
  if (!active) {
39819
39981
  console.error("No organizations found for your account.");
@@ -39824,15 +39986,17 @@ async function fetchOrganizationContext() {
39824
39986
  activeSource: active.source,
39825
39987
  staleSelectionDetected: active.staleSelectionDetected
39826
39988
  });
39827
- if (persistencePlan.shouldRemoveStaleSelection) {
39828
- removeSelectedOrganizationId();
39829
- }
39830
- if (persistencePlan.shouldWriteSelection) {
39831
- writeSelectedOrganizationId(persistencePlan.effectiveSelectedOrganizationId);
39832
- if (selectedOrganizationId) {
39833
- console.warn(
39834
- `Stored organization '${selectedOrganizationId}' is unavailable. Using '${active.organization.name}' (${active.organization.organizationId}) instead.`
39835
- );
39989
+ if (credential?.kind !== "api_key") {
39990
+ if (persistencePlan.shouldRemoveStaleSelection) {
39991
+ removeSelectedOrganizationId();
39992
+ }
39993
+ if (persistencePlan.shouldWriteSelection) {
39994
+ writeSelectedOrganizationId(persistencePlan.effectiveSelectedOrganizationId);
39995
+ if (selectedOrganizationId) {
39996
+ console.warn(
39997
+ `Stored organization '${selectedOrganizationId}' is unavailable. Using '${active.organization.name}' (${active.organization.organizationId}) instead.`
39998
+ );
39999
+ }
39836
40000
  }
39837
40001
  }
39838
40002
  return {
@@ -39984,6 +40148,7 @@ Examples:
39984
40148
  tracecli auth login
39985
40149
  tracecli auth logout && tracecli auth login # re-authenticate`
39986
40150
  ).action(async () => {
40151
+ refuseIfApiKeyEnv("run `tracecli auth login`");
39987
40152
  const existingToken = readToken();
39988
40153
  if (existingToken) {
39989
40154
  console.log(
@@ -40028,6 +40193,7 @@ Examples:
40028
40193
  tracecli auth logout
40029
40194
  tracecli auth logout --no-confirm`
40030
40195
  ).action(async (options) => {
40196
+ refuseIfApiKeyEnv("run `tracecli auth logout`");
40031
40197
  if (options.confirm && process.stdin.isTTY && process.stdout.isTTY) {
40032
40198
  const readline = createInterface3({ input: process.stdin, output: process.stdout });
40033
40199
  try {
@@ -40053,12 +40219,45 @@ Examples:
40053
40219
  function findOrganizationById(organizations, activeOrgId) {
40054
40220
  return organizations.find((org) => org.organizationId === activeOrgId) ?? null;
40055
40221
  }
40056
- function buildAuthStatusInfo(token, refreshToken, selectedOrganizationId) {
40057
- if (!token) {
40222
+ function applyOrganizationsToAuthStatus(info, organizations) {
40223
+ if (info.authMethod === "api_key") {
40224
+ const active = determineActiveOrganization(organizations, null);
40225
+ if (!active) {
40226
+ return;
40227
+ }
40228
+ info.activeOrg = active.organization.organizationId;
40229
+ info.activeOrgName = active.organization.name;
40230
+ info.activeOrgSlug = active.organization.slug;
40231
+ return;
40232
+ }
40233
+ if (!info.activeOrg) {
40234
+ return;
40235
+ }
40236
+ const match = findOrganizationById(organizations, info.activeOrg);
40237
+ if (!match) {
40238
+ return;
40239
+ }
40240
+ info.activeOrgName = match.name;
40241
+ info.activeOrgSlug = match.slug;
40242
+ }
40243
+ function buildAuthStatusInfo(credential, refreshToken, selectedOrganizationId) {
40244
+ if (!credential) {
40058
40245
  return { loggedIn: false, expired: false, hasRefreshToken: refreshToken !== null };
40059
40246
  }
40247
+ if (credential.kind === "api_key") {
40248
+ return {
40249
+ loggedIn: true,
40250
+ authMethod: "api_key",
40251
+ keyId: publicApiKeyId(credential.token),
40252
+ source: credential.source === "env" ? "TRACE_API_KEY" : "file",
40253
+ expired: false,
40254
+ hasRefreshToken: false
40255
+ };
40256
+ }
40257
+ const token = credential.token;
40060
40258
  const base = {
40061
40259
  loggedIn: true,
40260
+ authMethod: "user",
40062
40261
  expired: false,
40063
40262
  hasRefreshToken: refreshToken !== null,
40064
40263
  ...selectedOrganizationId ? { activeOrg: selectedOrganizationId } : {}
@@ -40092,20 +40291,26 @@ Examples:
40092
40291
  tracecli auth status --output json
40093
40292
  tracecli auth status --output tsv`
40094
40293
  ).action(async (options) => {
40095
- const token = readToken();
40096
- const refreshToken = readRefreshToken();
40097
- const selectedOrganizationId = readSelectedOrganizationId();
40098
- const info = buildAuthStatusInfo(token, refreshToken, selectedOrganizationId);
40099
- if (info.loggedIn && info.activeOrg) {
40294
+ let credential;
40295
+ try {
40296
+ credential = resolveCredential(readToken);
40297
+ } catch (error48) {
40298
+ if (error48 instanceof InvalidTraceApiKeyError) {
40299
+ console.error(colorError(error48.message));
40300
+ process.exitCode = 1;
40301
+ return;
40302
+ }
40303
+ throw error48;
40304
+ }
40305
+ const refreshToken = credential?.kind === "user" ? readRefreshToken() : null;
40306
+ const selectedOrganizationId = credential?.kind === "api_key" ? null : readSelectedOrganizationId();
40307
+ const info = buildAuthStatusInfo(credential, refreshToken, selectedOrganizationId);
40308
+ if (info.loggedIn) {
40100
40309
  const result = await authenticatedRequestSafe(
40101
40310
  (client) => client.organization.listMyOrganizations()
40102
40311
  );
40103
40312
  if (result && result.status === 200) {
40104
- const match = findOrganizationById(result.body, info.activeOrg);
40105
- if (match) {
40106
- info.activeOrgName = match.name;
40107
- info.activeOrgSlug = match.slug;
40108
- }
40313
+ applyOrganizationsToAuthStatus(info, result.body);
40109
40314
  }
40110
40315
  }
40111
40316
  if (options.output === "json") {
@@ -40117,11 +40322,12 @@ Examples:
40117
40322
  }
40118
40323
  if (options.output === "tsv") {
40119
40324
  console.log(
40120
- "loggedIn expired hasRefreshToken userId orgId activeOrg activeOrgName activeOrgSlug"
40325
+ "loggedIn authMethod expired hasRefreshToken userId orgId activeOrg activeOrgName activeOrgSlug"
40121
40326
  );
40122
40327
  console.log(
40123
40328
  [
40124
40329
  String(info.loggedIn),
40330
+ info.authMethod ?? "",
40125
40331
  String(info.expired),
40126
40332
  String(info.hasRefreshToken),
40127
40333
  info.userId ?? "",
@@ -40142,11 +40348,20 @@ Examples:
40142
40348
  return;
40143
40349
  }
40144
40350
  console.log("Logged in to Trace");
40145
- if (info.userId) {
40146
- console.log(` User ID: ${info.userId}`);
40147
- }
40148
- if (info.orgId) {
40149
- console.log(` Org ID: ${info.orgId}`);
40351
+ if (info.authMethod === "api_key") {
40352
+ const sourceLabel = info.source === "TRACE_API_KEY" ? "API key (TRACE_API_KEY)" : "API key (~/.trace/token)";
40353
+ console.log(` Auth method: ${sourceLabel}`);
40354
+ if (info.keyId) {
40355
+ console.log(` Key ID: ${info.keyId}`);
40356
+ }
40357
+ } else {
40358
+ console.log(" Auth method: user session");
40359
+ if (info.userId) {
40360
+ console.log(` User ID: ${info.userId}`);
40361
+ }
40362
+ if (info.orgId) {
40363
+ console.log(` Org ID: ${info.orgId}`);
40364
+ }
40150
40365
  }
40151
40366
  if (info.activeOrg) {
40152
40367
  const orgLabel = info.activeOrgName ? `${sanitizeForOutput(info.activeOrgName)}${info.activeOrgSlug ? ` (${sanitizeForOutput(info.activeOrgSlug)})` : ""} \u2014 ${info.activeOrg}` : info.activeOrg;
@@ -40355,6 +40570,7 @@ Examples:
40355
40570
  tracecli org switch org_01KJTJFFG497Z1C54Z07AP6NA3
40356
40571
  tracecli org switch engineering --web`
40357
40572
  ).action(async (organization, options) => {
40573
+ refuseIfApiKeyEnv("switch organization");
40358
40574
  const normalizedOrganizationInput = organization?.trim() ?? "";
40359
40575
  if (options.web) {
40360
40576
  if (normalizedOrganizationInput.length > 0) {
@@ -40603,10 +40819,11 @@ Examples:
40603
40819
  console.error(`Failed to resolve organization (status ${listed.status}).`);
40604
40820
  process.exit(1);
40605
40821
  }
40822
+ const credential = resolveCredential(readToken);
40606
40823
  const resolution = determineActiveOrganization(
40607
40824
  listed.body,
40608
- readSelectedOrganizationId(),
40609
- extractOrganizationIdFromToken(readToken())
40825
+ credential?.kind === "api_key" ? null : readSelectedOrganizationId(),
40826
+ credential?.kind === "user" ? extractOrganizationIdFromToken(credential.token) : null
40610
40827
  );
40611
40828
  if (!resolution) {
40612
40829
  console.error("No organization found for your account.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securewithtrace",
3
- "version": "0.35.0",
3
+ "version": "0.36.0",
4
4
  "description": "Official Trace CLI for vulnerability intelligence in your terminal",
5
5
  "type": "module",
6
6
  "bin": {