securewithtrace 0.6.0 → 0.7.1
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 +110 -0
- package/dist/index.js +369 -8
- package/package.json +12 -2
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# tracecli
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<strong>Continuous security vulnerability intelligence from your terminal.</strong>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://www.npmjs.com/package/securewithtrace"><img alt="npm version" src="https://img.shields.io/npm/v/securewithtrace?color=0ea5e9&label=npm"></a>
|
|
9
|
+
<a href="https://github.com/securewithtrace/trace/actions"><img alt="build status" src="https://img.shields.io/github/actions/workflow/status/securewithtrace/trace/release-please.yml?branch=main&label=release"></a>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
`tracecli` is the official CLI for Trace.
|
|
13
|
+
|
|
14
|
+
Use it to authenticate, browse connected repositories, clone repositories, and inspect vulnerabilities directly from the command line.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g securewithtrace
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Then verify:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
tracecli --help
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Requirements
|
|
29
|
+
|
|
30
|
+
- Bun runtime available on your PATH
|
|
31
|
+
- Git (for `tracecli repo clone`)
|
|
32
|
+
- A Trace account with access to at least one organization
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# 1) Authenticate
|
|
38
|
+
tracecli auth login
|
|
39
|
+
|
|
40
|
+
# 2) Confirm session
|
|
41
|
+
tracecli auth status
|
|
42
|
+
|
|
43
|
+
# 3) List connected repositories
|
|
44
|
+
tracecli repo list
|
|
45
|
+
|
|
46
|
+
# 4) View vulnerabilities
|
|
47
|
+
tracecli vulnerabilities list --severity HIGH
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## What You Can Do
|
|
51
|
+
|
|
52
|
+
- **Authenticate securely** with device authorization flow (`auth login/logout/status`)
|
|
53
|
+
- **List repositories** connected to your Trace organization (`repo list`)
|
|
54
|
+
- **Clone repositories** with secure token handling (`repo clone`)
|
|
55
|
+
- **Filter vulnerabilities** by repo, severity, and status (`vulnerabilities list`)
|
|
56
|
+
- **Export results as JSON** for automation (`vulnerabilities list --json`)
|
|
57
|
+
|
|
58
|
+
## Command Reference
|
|
59
|
+
|
|
60
|
+
### Authentication
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
tracecli auth login
|
|
64
|
+
tracecli auth status
|
|
65
|
+
tracecli auth logout
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Repositories
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# List org repositories known to Trace
|
|
72
|
+
tracecli repo list
|
|
73
|
+
|
|
74
|
+
# Clone by owner/repo shorthand
|
|
75
|
+
tracecli repo clone securewithtrace/trace
|
|
76
|
+
|
|
77
|
+
# Clone specific branch with shallow depth
|
|
78
|
+
tracecli repo clone securewithtrace/trace --branch main --depth 1
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Vulnerabilities
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# List vulnerabilities across your organization
|
|
85
|
+
tracecli vulnerabilities list
|
|
86
|
+
|
|
87
|
+
# Filter by repository
|
|
88
|
+
tracecli vulnerabilities list --repo securewithtrace/trace
|
|
89
|
+
|
|
90
|
+
# Filter by severity and status
|
|
91
|
+
tracecli vulnerabilities list --severity CRITICAL --status OPEN
|
|
92
|
+
|
|
93
|
+
# Machine-readable output
|
|
94
|
+
tracecli vulnerabilities list --json
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Supported filter values:
|
|
98
|
+
|
|
99
|
+
- `--severity`: `CRITICAL`, `HIGH`, `MEDIUM`, `LOW`
|
|
100
|
+
- `--status`: `OPEN`, `IN_PROGRESS`, `FIXED`, `IGNORED`, `FALSE_POSITIVE`
|
|
101
|
+
|
|
102
|
+
## Security Notes
|
|
103
|
+
|
|
104
|
+
- Auth tokens are stored at `~/.trace/token`.
|
|
105
|
+
- `repo clone` uses secure credential flow for GitHub clones when available.
|
|
106
|
+
- Credentials are not persisted in Git remote URLs after clone completes.
|
|
107
|
+
|
|
108
|
+
## Links
|
|
109
|
+
|
|
110
|
+
- Website: https://securewithtrace.com
|
package/dist/index.js
CHANGED
|
@@ -3583,10 +3583,373 @@ function showBanner() {
|
|
|
3583
3583
|
`);
|
|
3584
3584
|
}
|
|
3585
3585
|
|
|
3586
|
+
// src/completion/index.ts
|
|
3587
|
+
function extractCommandTree(cmd) {
|
|
3588
|
+
const BUILTIN_FLAGS = /* @__PURE__ */ new Set(["--help", "-h", "--version", "-V"]);
|
|
3589
|
+
const options = cmd.options.filter((o) => !o.hidden).filter((o) => !BUILTIN_FLAGS.has(o.long ?? "") && !BUILTIN_FLAGS.has(o.short ?? "")).map((o) => ({
|
|
3590
|
+
flags: o.flags,
|
|
3591
|
+
long: o.long ?? "",
|
|
3592
|
+
short: o.short ?? null,
|
|
3593
|
+
description: o.description,
|
|
3594
|
+
takesValue: o.required || o.optional
|
|
3595
|
+
}));
|
|
3596
|
+
const subcommands = cmd.commands.filter((c21) => !c21._hidden).map((c21) => extractCommandTree(c21));
|
|
3597
|
+
return {
|
|
3598
|
+
name: cmd.name(),
|
|
3599
|
+
description: cmd.description(),
|
|
3600
|
+
options,
|
|
3601
|
+
subcommands
|
|
3602
|
+
};
|
|
3603
|
+
}
|
|
3604
|
+
function escapeForShell(s) {
|
|
3605
|
+
return s.replace(/'/g, "'\\''");
|
|
3606
|
+
}
|
|
3607
|
+
function generateBashCompletion(tree, binName) {
|
|
3608
|
+
const lines = [];
|
|
3609
|
+
lines.push("#!/usr/bin/env bash");
|
|
3610
|
+
lines.push(`# Bash completion for ${binName}`);
|
|
3611
|
+
lines.push(`# Generated by ${binName} completion --shell bash`);
|
|
3612
|
+
lines.push("#");
|
|
3613
|
+
lines.push("# To enable, add to your ~/.bashrc:");
|
|
3614
|
+
lines.push(`# eval "$(${binName} completion --shell bash)"`);
|
|
3615
|
+
lines.push("");
|
|
3616
|
+
const funcName = `_${binName.replace(/[^a-zA-Z0-9]/g, "_")}_completions`;
|
|
3617
|
+
lines.push(`${funcName}() {`);
|
|
3618
|
+
lines.push(" local cur prev words cword");
|
|
3619
|
+
lines.push(" _get_comp_words_by_ref -n : cur prev words cword 2>/dev/null || {");
|
|
3620
|
+
lines.push(` cur="\${COMP_WORDS[COMP_CWORD]}"`);
|
|
3621
|
+
lines.push(` prev="\${COMP_WORDS[COMP_CWORD-1]}"`);
|
|
3622
|
+
lines.push(` words=("\${COMP_WORDS[@]}")`);
|
|
3623
|
+
lines.push(` cword="\${COMP_CWORD}"`);
|
|
3624
|
+
lines.push(" }");
|
|
3625
|
+
lines.push("");
|
|
3626
|
+
lines.push(" # Build the command chain (skip the binary name at index 0)");
|
|
3627
|
+
lines.push(` local cmd_chain=""`);
|
|
3628
|
+
lines.push(" local i");
|
|
3629
|
+
lines.push(" for (( i=1; i<cword; i++ )); do");
|
|
3630
|
+
lines.push(` case "\${words[i]}" in`);
|
|
3631
|
+
lines.push(" -*) ;; # skip options");
|
|
3632
|
+
lines.push(` *) cmd_chain="\${cmd_chain} \${words[i]}" ;;`);
|
|
3633
|
+
lines.push(" esac");
|
|
3634
|
+
lines.push(" done");
|
|
3635
|
+
lines.push(` cmd_chain="\${cmd_chain## }" # trim leading space`);
|
|
3636
|
+
lines.push("");
|
|
3637
|
+
const cases = collectBashCases(tree, "");
|
|
3638
|
+
lines.push(` local completions=""`);
|
|
3639
|
+
lines.push(` case "\${cmd_chain}" in`);
|
|
3640
|
+
for (const [path, { subcommands, options }] of cases) {
|
|
3641
|
+
const pattern = path === "" ? `''` : `'${escapeForShell(path)}'`;
|
|
3642
|
+
const all = [...subcommands, ...options];
|
|
3643
|
+
lines.push(` ${pattern})`);
|
|
3644
|
+
lines.push(` completions="${all.join(" ")}"`);
|
|
3645
|
+
lines.push(" ;;");
|
|
3646
|
+
}
|
|
3647
|
+
lines.push(" *)");
|
|
3648
|
+
lines.push(` completions=""`);
|
|
3649
|
+
lines.push(" ;;");
|
|
3650
|
+
lines.push(" esac");
|
|
3651
|
+
lines.push("");
|
|
3652
|
+
lines.push(` COMPREPLY=( $(compgen -W "\${completions}" -- "\${cur}") )`);
|
|
3653
|
+
lines.push(" return 0");
|
|
3654
|
+
lines.push("}");
|
|
3655
|
+
lines.push("");
|
|
3656
|
+
lines.push(`complete -o default -F ${funcName} ${binName}`);
|
|
3657
|
+
lines.push("");
|
|
3658
|
+
return lines.join("\n");
|
|
3659
|
+
}
|
|
3660
|
+
function collectBashCases(tree, prefix) {
|
|
3661
|
+
const cases = /* @__PURE__ */ new Map();
|
|
3662
|
+
const subcommandNames = tree.subcommands.map((c21) => c21.name);
|
|
3663
|
+
const optionNames = tree.options.flatMap((o) => o.short ? [o.long, o.short] : [o.long]);
|
|
3664
|
+
const extraOptions = prefix === "" ? ["--help", "--version"] : ["--help"];
|
|
3665
|
+
const allOptions = [.../* @__PURE__ */ new Set([...optionNames, ...extraOptions])];
|
|
3666
|
+
cases.set(prefix, { subcommands: subcommandNames, options: allOptions });
|
|
3667
|
+
for (const sub of tree.subcommands) {
|
|
3668
|
+
const subPath = prefix ? `${prefix} ${sub.name}` : sub.name;
|
|
3669
|
+
const subCases = collectBashCases(sub, subPath);
|
|
3670
|
+
for (const [k, v] of subCases) {
|
|
3671
|
+
cases.set(k, v);
|
|
3672
|
+
}
|
|
3673
|
+
}
|
|
3674
|
+
return cases;
|
|
3675
|
+
}
|
|
3676
|
+
function generateZshCompletion(tree, binName) {
|
|
3677
|
+
const lines = [];
|
|
3678
|
+
lines.push(`#compdef ${binName}`);
|
|
3679
|
+
lines.push(`# Zsh completion for ${binName}`);
|
|
3680
|
+
lines.push(`# Generated by ${binName} completion --shell zsh`);
|
|
3681
|
+
lines.push("#");
|
|
3682
|
+
lines.push("# To enable, add to your ~/.zshrc:");
|
|
3683
|
+
lines.push(`# eval "$(${binName} completion --shell zsh)"`);
|
|
3684
|
+
lines.push("");
|
|
3685
|
+
const funcName = `_${binName.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
3686
|
+
lines.push(`${funcName}() {`);
|
|
3687
|
+
lines.push(" local -a commands");
|
|
3688
|
+
lines.push(" local line state");
|
|
3689
|
+
lines.push("");
|
|
3690
|
+
generateZshCommandBlock(tree, lines, " ", binName, true);
|
|
3691
|
+
lines.push("}");
|
|
3692
|
+
lines.push("");
|
|
3693
|
+
generateZshSubFunctions(tree, lines, binName);
|
|
3694
|
+
lines.push(`compdef ${funcName} ${binName}`);
|
|
3695
|
+
lines.push("");
|
|
3696
|
+
return lines.join("\n");
|
|
3697
|
+
}
|
|
3698
|
+
function generateZshCommandBlock(tree, lines, indent, _binName, isRoot) {
|
|
3699
|
+
if (tree.subcommands.length > 0) {
|
|
3700
|
+
lines.push(`${indent}_arguments -C \\`);
|
|
3701
|
+
lines.push(`${indent} '(-h --help)'{-h,--help}'[Show help]' \\`);
|
|
3702
|
+
if (isRoot) {
|
|
3703
|
+
lines.push(`${indent} '(-V --version)'{-V,--version}'[Show version]' \\`);
|
|
3704
|
+
}
|
|
3705
|
+
for (const opt of tree.options) {
|
|
3706
|
+
const desc = escapeForShell(opt.description);
|
|
3707
|
+
if (opt.takesValue) {
|
|
3708
|
+
if (opt.short) {
|
|
3709
|
+
lines.push(
|
|
3710
|
+
`${indent} '(${opt.short} ${opt.long})'{${opt.short},${opt.long}}'[${desc}]:value: ' \\`
|
|
3711
|
+
);
|
|
3712
|
+
} else {
|
|
3713
|
+
lines.push(`${indent} '${opt.long}[${desc}]:value: ' \\`);
|
|
3714
|
+
}
|
|
3715
|
+
} else {
|
|
3716
|
+
if (opt.short) {
|
|
3717
|
+
lines.push(
|
|
3718
|
+
`${indent} '(${opt.short} ${opt.long})'{${opt.short},${opt.long}}'[${desc}]' \\`
|
|
3719
|
+
);
|
|
3720
|
+
} else {
|
|
3721
|
+
lines.push(`${indent} '${opt.long}[${desc}]' \\`);
|
|
3722
|
+
}
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3725
|
+
lines.push(`${indent} '1: :->command' \\`);
|
|
3726
|
+
lines.push(`${indent} '*::arg:->args'`);
|
|
3727
|
+
lines.push("");
|
|
3728
|
+
lines.push(`${indent}case "$state" in`);
|
|
3729
|
+
lines.push(`${indent} command)`);
|
|
3730
|
+
lines.push(`${indent} commands=(`);
|
|
3731
|
+
for (const sub of tree.subcommands) {
|
|
3732
|
+
const desc = escapeForShell(sub.description);
|
|
3733
|
+
lines.push(`${indent} '${sub.name}:${desc}'`);
|
|
3734
|
+
}
|
|
3735
|
+
lines.push(`${indent} )`);
|
|
3736
|
+
lines.push(`${indent} _describe 'command' commands`);
|
|
3737
|
+
lines.push(`${indent} ;;`);
|
|
3738
|
+
lines.push(`${indent} args)`);
|
|
3739
|
+
lines.push(`${indent} case "$line[1]" in`);
|
|
3740
|
+
for (const sub of tree.subcommands) {
|
|
3741
|
+
const subFunc = `_${tree.name.replace(/[^a-zA-Z0-9]/g, "_")}_${sub.name.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
3742
|
+
lines.push(`${indent} ${sub.name}) ${subFunc} ;;`);
|
|
3743
|
+
}
|
|
3744
|
+
lines.push(`${indent} esac`);
|
|
3745
|
+
lines.push(`${indent} ;;`);
|
|
3746
|
+
lines.push(`${indent}esac`);
|
|
3747
|
+
} else {
|
|
3748
|
+
lines.push(`${indent}_arguments \\`);
|
|
3749
|
+
lines.push(`${indent} '(-h --help)'{-h,--help}'[Show help]' \\`);
|
|
3750
|
+
for (const opt of tree.options) {
|
|
3751
|
+
const desc = escapeForShell(opt.description);
|
|
3752
|
+
if (opt.takesValue) {
|
|
3753
|
+
if (opt.short) {
|
|
3754
|
+
lines.push(
|
|
3755
|
+
`${indent} '(${opt.short} ${opt.long})'{${opt.short},${opt.long}}'[${desc}]:value: ' \\`
|
|
3756
|
+
);
|
|
3757
|
+
} else {
|
|
3758
|
+
lines.push(`${indent} '${opt.long}[${desc}]:value: ' \\`);
|
|
3759
|
+
}
|
|
3760
|
+
} else {
|
|
3761
|
+
if (opt.short) {
|
|
3762
|
+
lines.push(
|
|
3763
|
+
`${indent} '(${opt.short} ${opt.long})'{${opt.short},${opt.long}}'[${desc}]' \\`
|
|
3764
|
+
);
|
|
3765
|
+
} else {
|
|
3766
|
+
lines.push(`${indent} '${opt.long}[${desc}]' \\`);
|
|
3767
|
+
}
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
lines.push(`${indent} '*:file:_files'`);
|
|
3771
|
+
}
|
|
3772
|
+
}
|
|
3773
|
+
function generateZshSubFunctions(tree, lines, binName) {
|
|
3774
|
+
for (const sub of tree.subcommands) {
|
|
3775
|
+
const funcName = `_${tree.name.replace(/[^a-zA-Z0-9]/g, "_")}_${sub.name.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
3776
|
+
lines.push(`${funcName}() {`);
|
|
3777
|
+
generateZshCommandBlock(sub, lines, " ", binName, false);
|
|
3778
|
+
lines.push("}");
|
|
3779
|
+
lines.push("");
|
|
3780
|
+
if (sub.subcommands.length > 0) {
|
|
3781
|
+
generateZshSubFunctions(sub, lines, binName);
|
|
3782
|
+
}
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
function generateFishCompletion(tree, binName) {
|
|
3786
|
+
const lines = [];
|
|
3787
|
+
lines.push(`# Fish completion for ${binName}`);
|
|
3788
|
+
lines.push(`# Generated by ${binName} completion --shell fish`);
|
|
3789
|
+
lines.push("#");
|
|
3790
|
+
lines.push(`# To enable, add to your ~/.config/fish/completions/${binName}.fish:`);
|
|
3791
|
+
lines.push(`# ${binName} completion --shell fish | source`);
|
|
3792
|
+
lines.push("");
|
|
3793
|
+
lines.push(`complete -c ${binName} -f`);
|
|
3794
|
+
lines.push("");
|
|
3795
|
+
generateFishCommands(tree, lines, binName, []);
|
|
3796
|
+
lines.push("");
|
|
3797
|
+
return lines.join("\n");
|
|
3798
|
+
}
|
|
3799
|
+
function generateFishCommands(tree, lines, binName, parentPath) {
|
|
3800
|
+
const condition = buildFishCondition(binName, parentPath);
|
|
3801
|
+
for (const sub of tree.subcommands) {
|
|
3802
|
+
const desc = sub.description.replace(/'/g, "\\'");
|
|
3803
|
+
lines.push(`complete -c ${binName} ${condition} -a '${sub.name}' -d '${desc}'`);
|
|
3804
|
+
}
|
|
3805
|
+
for (const opt of tree.options) {
|
|
3806
|
+
const desc = opt.description.replace(/'/g, "\\'");
|
|
3807
|
+
const longFlag = opt.long.replace(/^--/, "");
|
|
3808
|
+
const parts = [`complete -c ${binName} ${condition}`];
|
|
3809
|
+
if (opt.short) {
|
|
3810
|
+
parts.push(`-s '${opt.short.replace(/^-/, "")}'`);
|
|
3811
|
+
}
|
|
3812
|
+
parts.push(`-l '${longFlag}'`);
|
|
3813
|
+
parts.push(`-d '${desc}'`);
|
|
3814
|
+
if (opt.takesValue) {
|
|
3815
|
+
parts.push("-r");
|
|
3816
|
+
}
|
|
3817
|
+
lines.push(parts.join(" "));
|
|
3818
|
+
}
|
|
3819
|
+
for (const sub of tree.subcommands) {
|
|
3820
|
+
const subPath = [...parentPath, sub.name];
|
|
3821
|
+
generateFishCommands(sub, lines, binName, subPath);
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
function buildFishCondition(binName, parentPath) {
|
|
3825
|
+
if (parentPath.length === 0) {
|
|
3826
|
+
return `-n '__fish_use_subcommand'`;
|
|
3827
|
+
}
|
|
3828
|
+
if (parentPath.length === 1) {
|
|
3829
|
+
return `-n '__fish_seen_subcommand_from ${parentPath[0]}'`;
|
|
3830
|
+
}
|
|
3831
|
+
const conditions = parentPath.map((p) => `__fish_seen_subcommand_from ${p}`);
|
|
3832
|
+
return `-n '${conditions.join("; and ")}'`;
|
|
3833
|
+
}
|
|
3834
|
+
function generateCompletion(program3, shell, binName) {
|
|
3835
|
+
const name = binName ?? program3.name();
|
|
3836
|
+
const tree = extractCommandTree(program3);
|
|
3837
|
+
switch (shell) {
|
|
3838
|
+
case "bash":
|
|
3839
|
+
return generateBashCompletion(tree, name);
|
|
3840
|
+
case "zsh":
|
|
3841
|
+
return generateZshCompletion(tree, name);
|
|
3842
|
+
case "fish":
|
|
3843
|
+
return generateFishCompletion(tree, name);
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3846
|
+
|
|
3847
|
+
// src/completion/install.ts
|
|
3848
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
3849
|
+
import { homedir as homedir2 } from "os";
|
|
3850
|
+
import { join as join2 } from "path";
|
|
3851
|
+
var TRACE_DIR2 = ".trace";
|
|
3852
|
+
function completionFileName(shell) {
|
|
3853
|
+
switch (shell) {
|
|
3854
|
+
case "bash":
|
|
3855
|
+
return "tracecli-completion.bash";
|
|
3856
|
+
case "zsh":
|
|
3857
|
+
return "_tracecli";
|
|
3858
|
+
case "fish":
|
|
3859
|
+
return "tracecli-completion.fish";
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3862
|
+
function getTraceDir() {
|
|
3863
|
+
return join2(homedir2(), TRACE_DIR2);
|
|
3864
|
+
}
|
|
3865
|
+
function getCompletionFilePath(shell) {
|
|
3866
|
+
return join2(getTraceDir(), completionFileName(shell));
|
|
3867
|
+
}
|
|
3868
|
+
function writeCompletionFile(shell, script) {
|
|
3869
|
+
const dir = getTraceDir();
|
|
3870
|
+
if (!existsSync2(dir)) {
|
|
3871
|
+
mkdirSync2(dir, { recursive: true });
|
|
3872
|
+
}
|
|
3873
|
+
const filePath = getCompletionFilePath(shell);
|
|
3874
|
+
writeFileSync2(filePath, script, "utf-8");
|
|
3875
|
+
return filePath;
|
|
3876
|
+
}
|
|
3877
|
+
function getSetupInstructions(shell) {
|
|
3878
|
+
const traceDir = `~/${TRACE_DIR2}`;
|
|
3879
|
+
switch (shell) {
|
|
3880
|
+
case "bash":
|
|
3881
|
+
return [
|
|
3882
|
+
"Add the following line to your .bashrc file:",
|
|
3883
|
+
"",
|
|
3884
|
+
" # The next line enables shell command completion for Trace",
|
|
3885
|
+
` source ${traceDir}/tracecli-completion.bash`,
|
|
3886
|
+
""
|
|
3887
|
+
].join("\n");
|
|
3888
|
+
case "zsh":
|
|
3889
|
+
return [
|
|
3890
|
+
"Add the following lines to your .zshrc file:",
|
|
3891
|
+
"",
|
|
3892
|
+
" # The next lines enable shell command completion for Trace",
|
|
3893
|
+
` fpath=(${traceDir} $fpath)`,
|
|
3894
|
+
" autoload -Uz compinit && compinit -i",
|
|
3895
|
+
""
|
|
3896
|
+
].join("\n");
|
|
3897
|
+
case "fish":
|
|
3898
|
+
return [
|
|
3899
|
+
"Copy the completion file to your fish completions directory:",
|
|
3900
|
+
"",
|
|
3901
|
+
` cp ${traceDir}/tracecli-completion.fish ~/.config/fish/completions/tracecli.fish`,
|
|
3902
|
+
""
|
|
3903
|
+
].join("\n");
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
|
|
3907
|
+
// src/completion/commands.ts
|
|
3908
|
+
var SUPPORTED_SHELLS = ["bash", "zsh", "fish"];
|
|
3909
|
+
function detectShell() {
|
|
3910
|
+
const shellEnv = process.env["SHELL"] ?? "";
|
|
3911
|
+
if (shellEnv.endsWith("/bash") || shellEnv.endsWith("/bash.exe")) {
|
|
3912
|
+
return "bash";
|
|
3913
|
+
}
|
|
3914
|
+
if (shellEnv.endsWith("/zsh")) {
|
|
3915
|
+
return "zsh";
|
|
3916
|
+
}
|
|
3917
|
+
if (shellEnv.endsWith("/fish")) {
|
|
3918
|
+
return "fish";
|
|
3919
|
+
}
|
|
3920
|
+
return null;
|
|
3921
|
+
}
|
|
3922
|
+
function resolveShell(shellOption) {
|
|
3923
|
+
const shell = shellOption ?? detectShell();
|
|
3924
|
+
if (!shell) {
|
|
3925
|
+
console.error(
|
|
3926
|
+
`Could not detect shell. Please specify one with --shell <${SUPPORTED_SHELLS.join("|")}>.`
|
|
3927
|
+
);
|
|
3928
|
+
process.exit(1);
|
|
3929
|
+
}
|
|
3930
|
+
if (!SUPPORTED_SHELLS.includes(shell)) {
|
|
3931
|
+
console.error(
|
|
3932
|
+
`Unsupported shell: '${shell}'. Supported shells: ${SUPPORTED_SHELLS.join(", ")}.`
|
|
3933
|
+
);
|
|
3934
|
+
process.exit(1);
|
|
3935
|
+
}
|
|
3936
|
+
return shell;
|
|
3937
|
+
}
|
|
3938
|
+
function createCompletionCommand(program3) {
|
|
3939
|
+
return new Command("completion").description("Generate shell completion script").option("--shell <shell>", `Shell type (${SUPPORTED_SHELLS.join(", ")})`).action((options) => {
|
|
3940
|
+
const shell = resolveShell(options.shell);
|
|
3941
|
+
const script = generateCompletion(program3, shell);
|
|
3942
|
+
const writtenPath = writeCompletionFile(shell, script);
|
|
3943
|
+
console.log(`Completion file written to ${writtenPath}`);
|
|
3944
|
+
console.log("");
|
|
3945
|
+
console.log(getSetupInstructions(shell));
|
|
3946
|
+
});
|
|
3947
|
+
}
|
|
3948
|
+
|
|
3586
3949
|
// src/repo/commands.ts
|
|
3587
|
-
import { chmodSync, mkdtempSync, rmSync as rmSync2, writeFileSync as
|
|
3950
|
+
import { chmodSync, mkdtempSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
3588
3951
|
import { tmpdir } from "os";
|
|
3589
|
-
import { join as
|
|
3952
|
+
import { join as join3 } from "path";
|
|
3590
3953
|
|
|
3591
3954
|
// ../../node_modules/.bun/@ts-rest+core@3.53.0-rc.1+f9199aabcde8d6a0/node_modules/@ts-rest/core/index.esm.mjs
|
|
3592
3955
|
var util;
|
|
@@ -23952,9 +24315,9 @@ function urlWithUsername(httpsUrl) {
|
|
|
23952
24315
|
return url2.toString();
|
|
23953
24316
|
}
|
|
23954
24317
|
function createAskPassScript(token) {
|
|
23955
|
-
const dir = mkdtempSync(
|
|
23956
|
-
const scriptPath =
|
|
23957
|
-
|
|
24318
|
+
const dir = mkdtempSync(join3(tmpdir(), "trace-askpass-"));
|
|
24319
|
+
const scriptPath = join3(dir, "askpass.sh");
|
|
24320
|
+
writeFileSync3(scriptPath, `#!/bin/sh
|
|
23958
24321
|
echo '${token.replace(/'/g, "'\\''")}'
|
|
23959
24322
|
`, {
|
|
23960
24323
|
mode: 448
|
|
@@ -24250,10 +24613,8 @@ program2.action(() => {
|
|
|
24250
24613
|
showBanner();
|
|
24251
24614
|
program2.outputHelp();
|
|
24252
24615
|
});
|
|
24253
|
-
program2.command("echo").description("Echo a message back to the terminal").argument("<message...>", "message to echo").action((messageParts) => {
|
|
24254
|
-
console.log(messageParts.join(" "));
|
|
24255
|
-
});
|
|
24256
24616
|
program2.addCommand(createAuthCommand());
|
|
24257
24617
|
program2.addCommand(createRepoCommand());
|
|
24258
24618
|
program2.addCommand(createVulnerabilitiesCommand());
|
|
24619
|
+
program2.addCommand(createCompletionCommand(program2));
|
|
24259
24620
|
program2.parse();
|
package/package.json
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "securewithtrace",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Trace CLI",
|
|
3
|
+
"version": "0.7.1",
|
|
4
|
+
"description": "Official Trace CLI for vulnerability intelligence in your terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"tracecli": "./dist/index.js"
|
|
8
8
|
},
|
|
9
|
+
"homepage": "https://securewithtrace.com",
|
|
10
|
+
"keywords": [
|
|
11
|
+
"trace",
|
|
12
|
+
"tracecli",
|
|
13
|
+
"security",
|
|
14
|
+
"vulnerability",
|
|
15
|
+
"devsecops",
|
|
16
|
+
"cli",
|
|
17
|
+
"appsec"
|
|
18
|
+
],
|
|
9
19
|
"scripts": {
|
|
10
20
|
"build": "tsup",
|
|
11
21
|
"lint": "biome check .",
|