securewithtrace 0.6.0 → 0.7.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 (3) hide show
  1. package/README.md +110 -0
  2. package/dist/index.js +369 -8
  3. 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-completion.zsh";
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 writeFileSync2 } from "fs";
3950
+ import { chmodSync, mkdtempSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
3588
3951
  import { tmpdir } from "os";
3589
- import { join as join2 } from "path";
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(join2(tmpdir(), "trace-askpass-"));
23956
- const scriptPath = join2(dir, "askpass.sh");
23957
- writeFileSync2(scriptPath, `#!/bin/sh
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.6.0",
4
- "description": "Trace CLI",
3
+ "version": "0.7.0",
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 .",