securewithtrace 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/index.js +216 -67
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -3352,6 +3352,9 @@ var {
|
|
|
3352
3352
|
Help
|
|
3353
3353
|
} = import_index.default;
|
|
3354
3354
|
|
|
3355
|
+
// src/auth/commands.ts
|
|
3356
|
+
import { createInterface } from "readline/promises";
|
|
3357
|
+
|
|
3355
3358
|
// ../../node_modules/.bun/@ts-rest+core@3.53.0-rc.1+f9199aabcde8d6a0/node_modules/@ts-rest/core/index.esm.mjs
|
|
3356
3359
|
var util;
|
|
3357
3360
|
(function(util2) {
|
|
@@ -25253,6 +25256,65 @@ Use an organization ID or exact slug.`
|
|
|
25253
25256
|
error: `Organization '${input}' not found. Run \`tracecli org list\` to see available organizations.`
|
|
25254
25257
|
};
|
|
25255
25258
|
}
|
|
25259
|
+
function resolveOrganizationSelectionInput(organizations, input) {
|
|
25260
|
+
const trimmedInput = input.trim();
|
|
25261
|
+
if (trimmedInput.length === 0) {
|
|
25262
|
+
return { error: "Organization selection cannot be empty." };
|
|
25263
|
+
}
|
|
25264
|
+
if (/^\d+$/.test(trimmedInput)) {
|
|
25265
|
+
const byNumericId = organizations.find(
|
|
25266
|
+
(organization) => normalizeForComparison(organization.organizationId) === normalizeForComparison(trimmedInput)
|
|
25267
|
+
);
|
|
25268
|
+
if (byNumericId) {
|
|
25269
|
+
return { organization: byNumericId };
|
|
25270
|
+
}
|
|
25271
|
+
const selectedIndex = Number.parseInt(trimmedInput, 10);
|
|
25272
|
+
if (selectedIndex < 1 || selectedIndex > organizations.length) {
|
|
25273
|
+
return {
|
|
25274
|
+
error: `Selection '${trimmedInput}' is out of range. Enter a number between 1 and ${organizations.length}.`
|
|
25275
|
+
};
|
|
25276
|
+
}
|
|
25277
|
+
const selectedOrganization = organizations[selectedIndex - 1];
|
|
25278
|
+
if (!selectedOrganization) {
|
|
25279
|
+
return {
|
|
25280
|
+
error: "Failed to resolve organization selection. Please try again with an organization ID or slug."
|
|
25281
|
+
};
|
|
25282
|
+
}
|
|
25283
|
+
return { organization: selectedOrganization };
|
|
25284
|
+
}
|
|
25285
|
+
return findOrganizationByInput(organizations, trimmedInput);
|
|
25286
|
+
}
|
|
25287
|
+
async function promptForOrganizationSelection(organizations, activeOrganizationId) {
|
|
25288
|
+
console.log("Select an organization:");
|
|
25289
|
+
for (const [index, organization] of organizations.entries()) {
|
|
25290
|
+
const marker = organization.organizationId === activeOrganizationId ? "*" : " ";
|
|
25291
|
+
console.log(
|
|
25292
|
+
`${marker} ${index + 1}) ${sanitizeForOutput(organization.name)} (${sanitizeForOutput(organization.slug)}) - ${organization.organizationId}`
|
|
25293
|
+
);
|
|
25294
|
+
}
|
|
25295
|
+
console.log("");
|
|
25296
|
+
const readline = createInterface({
|
|
25297
|
+
input: process.stdin,
|
|
25298
|
+
output: process.stdout
|
|
25299
|
+
});
|
|
25300
|
+
try {
|
|
25301
|
+
while (true) {
|
|
25302
|
+
const input = await readline.question(
|
|
25303
|
+
"Enter number, organization ID, slug, or name (press Enter to cancel): "
|
|
25304
|
+
);
|
|
25305
|
+
if (input.trim().length === 0) {
|
|
25306
|
+
return null;
|
|
25307
|
+
}
|
|
25308
|
+
const selection = resolveOrganizationSelectionInput(organizations, input);
|
|
25309
|
+
if ("organization" in selection) {
|
|
25310
|
+
return selection.organization;
|
|
25311
|
+
}
|
|
25312
|
+
console.error(selection.error);
|
|
25313
|
+
}
|
|
25314
|
+
} finally {
|
|
25315
|
+
readline.close();
|
|
25316
|
+
}
|
|
25317
|
+
}
|
|
25256
25318
|
async function refreshAfterOrganizationSwitch(workosOrganizationId, refreshForOrganization = (organizationId) => refreshStoredAccessTokenForOrganization(organizationId)) {
|
|
25257
25319
|
if (!workosOrganizationId || workosOrganizationId.trim().length === 0) {
|
|
25258
25320
|
return false;
|
|
@@ -25260,7 +25322,13 @@ async function refreshAfterOrganizationSwitch(workosOrganizationId, refreshForOr
|
|
|
25260
25322
|
return refreshForOrganization(workosOrganizationId);
|
|
25261
25323
|
}
|
|
25262
25324
|
function createLoginCommand() {
|
|
25263
|
-
return new Command("login").description("Authenticate with Trace").
|
|
25325
|
+
return new Command("login").description("Authenticate with Trace").addHelpText(
|
|
25326
|
+
"after",
|
|
25327
|
+
`
|
|
25328
|
+
Examples:
|
|
25329
|
+
tracecli auth login
|
|
25330
|
+
tracecli auth logout && tracecli auth login # re-authenticate`
|
|
25331
|
+
).action(async () => {
|
|
25264
25332
|
const existingToken = readToken();
|
|
25265
25333
|
if (existingToken) {
|
|
25266
25334
|
console.log("You are already logged in. Run `trace auth logout` first to re-authenticate.");
|
|
@@ -25294,7 +25362,12 @@ Authenticated as ${displayName} (${tokenResponse.user.email})`);
|
|
|
25294
25362
|
});
|
|
25295
25363
|
}
|
|
25296
25364
|
function createLogoutCommand() {
|
|
25297
|
-
return new Command("logout").description("Log out of Trace").
|
|
25365
|
+
return new Command("logout").description("Log out of Trace").addHelpText(
|
|
25366
|
+
"after",
|
|
25367
|
+
`
|
|
25368
|
+
Examples:
|
|
25369
|
+
tracecli auth logout`
|
|
25370
|
+
).action(() => {
|
|
25298
25371
|
const removed = removeToken();
|
|
25299
25372
|
if (removed) {
|
|
25300
25373
|
console.log("Logged out successfully. Local session data removed from ~/.trace/");
|
|
@@ -25303,66 +25376,96 @@ function createLogoutCommand() {
|
|
|
25303
25376
|
}
|
|
25304
25377
|
});
|
|
25305
25378
|
}
|
|
25379
|
+
function buildAuthStatusInfo(token, refreshToken, selectedOrganizationId) {
|
|
25380
|
+
if (!token) {
|
|
25381
|
+
return { loggedIn: false, expired: false, hasRefreshToken: refreshToken !== null };
|
|
25382
|
+
}
|
|
25383
|
+
const base = {
|
|
25384
|
+
loggedIn: true,
|
|
25385
|
+
expired: false,
|
|
25386
|
+
hasRefreshToken: refreshToken !== null,
|
|
25387
|
+
...selectedOrganizationId ? { activeOrg: selectedOrganizationId } : {}
|
|
25388
|
+
};
|
|
25389
|
+
try {
|
|
25390
|
+
const parts = token.split(".");
|
|
25391
|
+
const payload = parts[1];
|
|
25392
|
+
if (!payload) {
|
|
25393
|
+
return base;
|
|
25394
|
+
}
|
|
25395
|
+
const decoded = JSON.parse(Buffer.from(payload, "base64url").toString("utf-8"));
|
|
25396
|
+
const expiry = decoded.exp ? new Date(decoded.exp * 1e3) : null;
|
|
25397
|
+
const isExpired = expiry ? expiry < /* @__PURE__ */ new Date() : false;
|
|
25398
|
+
return {
|
|
25399
|
+
...base,
|
|
25400
|
+
...decoded.sub ? { userId: decoded.sub } : {},
|
|
25401
|
+
...decoded.org_id ? { orgId: decoded.org_id } : {},
|
|
25402
|
+
...expiry ? { expires: expiry.toISOString() } : {},
|
|
25403
|
+
expired: isExpired
|
|
25404
|
+
};
|
|
25405
|
+
} catch {
|
|
25406
|
+
return base;
|
|
25407
|
+
}
|
|
25408
|
+
}
|
|
25306
25409
|
function createStatusCommand() {
|
|
25307
|
-
return new Command("status").description("Show current authentication status").
|
|
25410
|
+
return new Command("status").description("Show current authentication status").option("-o, --output <format>", "Output format: table (default), json, tsv").addHelpText(
|
|
25411
|
+
"after",
|
|
25412
|
+
`
|
|
25413
|
+
Examples:
|
|
25414
|
+
tracecli auth status
|
|
25415
|
+
tracecli auth status --output json
|
|
25416
|
+
tracecli auth status --output tsv`
|
|
25417
|
+
).action((options) => {
|
|
25308
25418
|
const token = readToken();
|
|
25309
25419
|
const refreshToken = readRefreshToken();
|
|
25310
25420
|
const selectedOrganizationId = readSelectedOrganizationId();
|
|
25311
|
-
|
|
25312
|
-
|
|
25313
|
-
|
|
25314
|
-
|
|
25315
|
-
|
|
25316
|
-
|
|
25317
|
-
|
|
25318
|
-
|
|
25319
|
-
|
|
25320
|
-
|
|
25321
|
-
|
|
25322
|
-
|
|
25323
|
-
|
|
25324
|
-
|
|
25325
|
-
|
|
25326
|
-
|
|
25327
|
-
|
|
25328
|
-
|
|
25329
|
-
|
|
25330
|
-
|
|
25331
|
-
|
|
25332
|
-
|
|
25333
|
-
|
|
25334
|
-
|
|
25335
|
-
|
|
25336
|
-
|
|
25337
|
-
|
|
25338
|
-
|
|
25339
|
-
|
|
25340
|
-
|
|
25341
|
-
|
|
25342
|
-
|
|
25343
|
-
|
|
25344
|
-
|
|
25345
|
-
|
|
25346
|
-
|
|
25347
|
-
|
|
25348
|
-
|
|
25349
|
-
|
|
25350
|
-
|
|
25351
|
-
|
|
25352
|
-
|
|
25353
|
-
|
|
25354
|
-
|
|
25355
|
-
} catch {
|
|
25356
|
-
console.log("Logged in to Trace (access token stored at ~/.trace/token)");
|
|
25357
|
-
if (selectedOrganizationId) {
|
|
25358
|
-
console.log(` Active Org (CLI): ${selectedOrganizationId}`);
|
|
25359
|
-
}
|
|
25421
|
+
const info = buildAuthStatusInfo(token, refreshToken, selectedOrganizationId);
|
|
25422
|
+
if (options.output === "json") {
|
|
25423
|
+
console.log(JSON.stringify(info, null, 2));
|
|
25424
|
+
return;
|
|
25425
|
+
}
|
|
25426
|
+
if (options.output === "tsv") {
|
|
25427
|
+
console.log(
|
|
25428
|
+
[
|
|
25429
|
+
String(info.loggedIn),
|
|
25430
|
+
String(info.expired),
|
|
25431
|
+
String(info.hasRefreshToken),
|
|
25432
|
+
info.userId ?? "",
|
|
25433
|
+
info.orgId ?? "",
|
|
25434
|
+
info.activeOrg ?? ""
|
|
25435
|
+
].join(" ")
|
|
25436
|
+
);
|
|
25437
|
+
return;
|
|
25438
|
+
}
|
|
25439
|
+
if (!info.loggedIn) {
|
|
25440
|
+
console.log("Not logged in. Run `tracecli auth login` to authenticate.");
|
|
25441
|
+
return;
|
|
25442
|
+
}
|
|
25443
|
+
console.log("Logged in to Trace");
|
|
25444
|
+
if (info.userId) {
|
|
25445
|
+
console.log(` User ID: ${info.userId}`);
|
|
25446
|
+
}
|
|
25447
|
+
if (info.orgId) {
|
|
25448
|
+
console.log(` Org ID: ${info.orgId}`);
|
|
25449
|
+
}
|
|
25450
|
+
if (info.activeOrg) {
|
|
25451
|
+
console.log(` Active Org (CLI): ${info.activeOrg}`);
|
|
25452
|
+
}
|
|
25453
|
+
if (info.expires) {
|
|
25454
|
+
console.log(` Expires: ${info.expires}${info.expired ? " (EXPIRED)" : ""}`);
|
|
25455
|
+
}
|
|
25456
|
+
console.log(
|
|
25457
|
+
` Refresh: ${info.hasRefreshToken ? "Available (auto-refresh enabled)" : "Not available"}`
|
|
25458
|
+
);
|
|
25459
|
+
if (info.expired) {
|
|
25460
|
+
if (info.hasRefreshToken) {
|
|
25461
|
+
console.log(
|
|
25462
|
+
"\nYour access token is expired, but a refresh token is available and will be used automatically."
|
|
25463
|
+
);
|
|
25464
|
+
} else {
|
|
25360
25465
|
console.log(
|
|
25361
|
-
`
|
|
25466
|
+
"\nYour token has expired. Run `tracecli auth login` after logging out to re-authenticate."
|
|
25362
25467
|
);
|
|
25363
25468
|
}
|
|
25364
|
-
} else {
|
|
25365
|
-
console.log("Not logged in. Run `trace auth login` to authenticate.");
|
|
25366
25469
|
}
|
|
25367
25470
|
});
|
|
25368
25471
|
}
|
|
@@ -25459,27 +25562,57 @@ Examples:
|
|
|
25459
25562
|
});
|
|
25460
25563
|
}
|
|
25461
25564
|
function createOrgSwitchCommand() {
|
|
25462
|
-
return new Command("switch").description("Switch the active organization for subsequent CLI commands").argument("
|
|
25463
|
-
|
|
25464
|
-
|
|
25465
|
-
|
|
25466
|
-
|
|
25565
|
+
return new Command("switch").description("Switch the active organization for subsequent CLI commands").argument("[organization]", "Organization ID, slug, name, or menu selection index").addHelpText(
|
|
25566
|
+
"after",
|
|
25567
|
+
`
|
|
25568
|
+
Examples:
|
|
25569
|
+
tracecli org switch
|
|
25570
|
+
tracecli org switch engineering
|
|
25571
|
+
tracecli org switch org_01KJTJFFG497Z1C54Z07AP6NA3`
|
|
25572
|
+
).action(async (organization) => {
|
|
25573
|
+
const normalizedOrganizationInput = organization?.trim() ?? "";
|
|
25574
|
+
if (normalizedOrganizationInput.length === 0 && (!process.stdin.isTTY || !process.stdout.isTTY)) {
|
|
25575
|
+
console.error(
|
|
25576
|
+
"Missing required argument 'organization'. Run `tracecli org switch <organization>` in non-interactive environments."
|
|
25577
|
+
);
|
|
25467
25578
|
process.exit(1);
|
|
25468
25579
|
}
|
|
25469
|
-
|
|
25580
|
+
const organizationContext = await fetchOrganizationContext();
|
|
25581
|
+
let selectedOrganization = null;
|
|
25582
|
+
if (normalizedOrganizationInput.length > 0) {
|
|
25583
|
+
const selection = resolveOrganizationSelectionInput(
|
|
25584
|
+
organizationContext.organizations,
|
|
25585
|
+
normalizedOrganizationInput
|
|
25586
|
+
);
|
|
25587
|
+
if ("error" in selection) {
|
|
25588
|
+
console.error(selection.error);
|
|
25589
|
+
process.exit(1);
|
|
25590
|
+
}
|
|
25591
|
+
selectedOrganization = selection.organization;
|
|
25592
|
+
} else {
|
|
25593
|
+
selectedOrganization = await promptForOrganizationSelection(
|
|
25594
|
+
organizationContext.organizations,
|
|
25595
|
+
organizationContext.activeOrganization.organizationId
|
|
25596
|
+
);
|
|
25597
|
+
if (!selectedOrganization) {
|
|
25598
|
+
console.error("Organization switch cancelled.");
|
|
25599
|
+
process.exit(1);
|
|
25600
|
+
}
|
|
25601
|
+
}
|
|
25602
|
+
if (!isSafeOrganizationId(selectedOrganization.organizationId)) {
|
|
25470
25603
|
console.error(
|
|
25471
|
-
`Cannot switch to organization '${
|
|
25604
|
+
`Cannot switch to organization '${selectedOrganization.organizationId}': invalid organization ID format.`
|
|
25472
25605
|
);
|
|
25473
25606
|
process.exit(1);
|
|
25474
25607
|
}
|
|
25475
|
-
writeSelectedOrganizationId(
|
|
25608
|
+
writeSelectedOrganizationId(selectedOrganization.organizationId);
|
|
25476
25609
|
const refreshed = await refreshAfterOrganizationSwitch(
|
|
25477
|
-
|
|
25610
|
+
selectedOrganization.workosOrganizationId
|
|
25478
25611
|
);
|
|
25479
25612
|
console.log(
|
|
25480
|
-
`Active organization set to ${sanitizeForOutput(
|
|
25613
|
+
`Active organization set to ${sanitizeForOutput(selectedOrganization.name)} (${sanitizeForOutput(selectedOrganization.slug)})`
|
|
25481
25614
|
);
|
|
25482
|
-
console.log(` Org ID: ${
|
|
25615
|
+
console.log(` Org ID: ${selectedOrganization.organizationId}`);
|
|
25483
25616
|
if (!refreshed) {
|
|
25484
25617
|
console.warn(
|
|
25485
25618
|
"Token refresh for the selected organization was not completed. If repository commands fail, run `tracecli auth login` again."
|
|
@@ -26398,6 +26531,7 @@ function parseListLimit(value) {
|
|
|
26398
26531
|
return n;
|
|
26399
26532
|
}
|
|
26400
26533
|
function printRepositoriesTsv(repositories) {
|
|
26534
|
+
console.log("repositoryUrl platform status defaultBranch language");
|
|
26401
26535
|
for (const repo of repositories) {
|
|
26402
26536
|
console.log(
|
|
26403
26537
|
[
|
|
@@ -26463,6 +26597,10 @@ async function runRepositoryListCommand(options) {
|
|
|
26463
26597
|
params: { organizationId }
|
|
26464
26598
|
})
|
|
26465
26599
|
);
|
|
26600
|
+
if (result.status === 401) {
|
|
26601
|
+
console.error("Authentication failed. Run `tracecli auth login` to re-authenticate.");
|
|
26602
|
+
process.exit(1);
|
|
26603
|
+
}
|
|
26466
26604
|
if (result.status !== 200) {
|
|
26467
26605
|
console.error(`Failed to list repositories (status ${result.status}).`);
|
|
26468
26606
|
process.exit(1);
|
|
@@ -26515,6 +26653,10 @@ async function runDependencyFilter(organizationId, options) {
|
|
|
26515
26653
|
}
|
|
26516
26654
|
})
|
|
26517
26655
|
);
|
|
26656
|
+
if (result.status === 401) {
|
|
26657
|
+
console.error("Authentication failed. Run `tracecli auth login` to re-authenticate.");
|
|
26658
|
+
process.exit(1);
|
|
26659
|
+
}
|
|
26518
26660
|
if (result.status !== 200) {
|
|
26519
26661
|
console.error(`Failed to search dependencies (status ${result.status}).`);
|
|
26520
26662
|
process.exit(1);
|
|
@@ -26643,7 +26785,14 @@ async function fetchCloneToken(repoUrl) {
|
|
|
26643
26785
|
}
|
|
26644
26786
|
}
|
|
26645
26787
|
function createCloneCommand() {
|
|
26646
|
-
return new Command("clone").description("Clone a repository").argument("<repository>", "Repository URL or owner/repo shorthand").argument("[directory]", "Directory to clone into").option("--branch <branch>", "Branch to clone").option("--depth <depth>", "Shallow clone depth", parseCloneDepth).
|
|
26788
|
+
return new Command("clone").description("Clone a repository").argument("<repository>", "Repository URL or owner/repo shorthand").argument("[directory]", "Directory to clone into").option("--branch <branch>", "Branch to clone").option("--depth <depth>", "Shallow clone depth", parseCloneDepth).addHelpText(
|
|
26789
|
+
"after",
|
|
26790
|
+
`
|
|
26791
|
+
Examples:
|
|
26792
|
+
tracecli repo clone securewithtrace/trace
|
|
26793
|
+
tracecli repo clone securewithtrace/trace trace-src
|
|
26794
|
+
tracecli repo clone https://github.com/owner/repo --branch main --depth 1`
|
|
26795
|
+
).action(
|
|
26647
26796
|
async (repository, directory, options) => {
|
|
26648
26797
|
const cloneUrl = resolveRepoUrl(repository);
|
|
26649
26798
|
const targetDir = directory ?? extractRepoName(repository);
|