shell-aliasforge 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +293 -2
- package/dist/analyze/environment.js +84 -0
- package/dist/analyze/naming.js +84 -0
- package/dist/analyze/normalize.js +160 -0
- package/dist/analyze/render.js +23 -0
- package/dist/analyze/report.js +27 -0
- package/dist/analyze/reserved.js +33 -0
- package/dist/analyze/score.js +4 -0
- package/dist/analyze/stats.js +128 -0
- package/dist/analyze/suggest.js +68 -0
- package/dist/analyze/templates.js +90 -0
- package/dist/apply/apply.js +68 -0
- package/dist/apply/backup.js +20 -0
- package/dist/apply/block.js +98 -0
- package/dist/apply/diff.js +41 -0
- package/dist/apply/select.js +23 -0
- package/dist/cli.js +229 -0
- package/dist/errors.js +3 -0
- package/dist/history/bash.js +22 -0
- package/dist/history/detect.js +36 -0
- package/dist/history/fish.js +29 -0
- package/dist/history/index.js +24 -0
- package/dist/history/text.js +13 -0
- package/dist/history/types.js +1 -0
- package/dist/history/zsh.js +62 -0
- package/dist/risks/engine.js +74 -0
- package/dist/risks/mask.js +23 -0
- package/dist/risks/packs/cloud.js +60 -0
- package/dist/risks/packs/core.js +177 -0
- package/dist/risks/packs/docker.js +27 -0
- package/dist/risks/packs/git.js +27 -0
- package/dist/risks/packs/index.js +18 -0
- package/dist/risks/packs/kubectl.js +35 -0
- package/dist/risks/packs/systemd.js +19 -0
- package/dist/risks/report.js +30 -0
- package/dist/risks/types.js +1 -0
- package/package.json +49 -4
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
const R = String.raw;
|
|
2
|
+
// Reused fragments. "rm -r" and "rm -f" can come in any order or spelling.
|
|
3
|
+
const RECURSIVE = R `(?=[^;&|]*\s(?:-[a-zA-Z]*[rR][a-zA-Z]*|--recursive)(?=\s|$))`;
|
|
4
|
+
const FORCE = R `(?=[^;&|]*\s(?:-[a-zA-Z]*f[a-zA-Z]*|--force)(?=\s|$))`;
|
|
5
|
+
const SECRET_NAME = R `[A-Za-z0-9_]*(?:TOKEN|SECRET|PASSWORD|PASSWD|API_?KEY|ACCESS_?KEY|PRIVATE_?KEY)`;
|
|
6
|
+
const VALUE = R `(?<secret>[^\s"'$-][^\s"']*)`;
|
|
7
|
+
// A fish value must not be "=": that is SQL or config syntax, not a fish `set`.
|
|
8
|
+
const FISH_VALUE = R `(?<secret>[^\s"'$=-][^\s"']*)`;
|
|
9
|
+
export const core = [
|
|
10
|
+
{
|
|
11
|
+
id: "core/chmod-recursive-777",
|
|
12
|
+
category: "danger",
|
|
13
|
+
severity: "high",
|
|
14
|
+
title: "Recursive world-writable chmod",
|
|
15
|
+
advice: "Use a narrower mode (755 for directories, 644 for files) and limit the path.",
|
|
16
|
+
pattern: R `\bchmod(?=\s)(?=[^;&|]*\s(?:-[a-zA-Z]*R[a-zA-Z]*|--recursive)(?=\s|$))(?=[^;&|]*\s(?:0?777|a\+rwx|ugo\+rwx)(?=\s|$))`,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: "core/rm-rf-root",
|
|
20
|
+
category: "danger",
|
|
21
|
+
severity: "high",
|
|
22
|
+
title: "Recursive forced delete of / or ~",
|
|
23
|
+
advice: "Double-check the path. Prefer deleting a specific directory.",
|
|
24
|
+
pattern: R `\brm(?=\s)${RECURSIVE}${FORCE}[^;&|]*\s(?:\/\*?|~\/?\*?)(?=\s|$)`,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "core/rm-rf-unquoted-variable",
|
|
28
|
+
category: "danger",
|
|
29
|
+
severity: "high",
|
|
30
|
+
title: "Recursive forced delete of an unquoted variable",
|
|
31
|
+
advice: 'If the variable is empty or contains spaces this deletes the wrong thing. Quote it ("$DIR") and use ${DIR:?}.',
|
|
32
|
+
pattern: R `\brm(?=\s)${RECURSIVE}${FORCE}[^;&|]*\s\$\{?[A-Za-z_]\w*\}?(?:\/[^\s;&|]*)?(?=\s|$)`,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: "core/pipe-to-shell",
|
|
36
|
+
category: "danger",
|
|
37
|
+
severity: "high",
|
|
38
|
+
title: "Download piped straight into a shell",
|
|
39
|
+
advice: "Download the script, read it, then run it.",
|
|
40
|
+
pattern: R `\b(?:curl|wget)\b[^;&]*(?<!\|)\|(?!\|)\s*(?:sudo\s+(?:-\S+\s+)*)?(?:ba|z|da|k)?sh\b`,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "core/shell-from-download",
|
|
44
|
+
category: "danger",
|
|
45
|
+
severity: "high",
|
|
46
|
+
title: "Shell running a download without review",
|
|
47
|
+
advice: "Download the script, read it, then run it.",
|
|
48
|
+
pattern: R `\b(?:ba|z|da|k)?sh\s+(?:-c\s+)?["']?(?:<\(|\$\()\s*(?:curl|wget)\b`,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "core/dd-to-device",
|
|
52
|
+
category: "danger",
|
|
53
|
+
severity: "high",
|
|
54
|
+
title: "dd writing to a device",
|
|
55
|
+
advice: "Confirm the target with lsblk first. A wrong of= destroys that disk.",
|
|
56
|
+
pattern: R `\bdd(?=\s)[^;&|]*\sof=\/dev\/(?!(?:null|zero|stdout|stderr|tty)\b)\S+`,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "core/mkfs-device",
|
|
60
|
+
category: "danger",
|
|
61
|
+
severity: "high",
|
|
62
|
+
title: "mkfs on a device",
|
|
63
|
+
advice: "Confirm the device with lsblk first. This erases the filesystem.",
|
|
64
|
+
pattern: R `\bmkfs(?:\.\w+)?(?=\s)[^;&|]*\s\/dev\/\S+`,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: "core/fork-bomb",
|
|
68
|
+
category: "danger",
|
|
69
|
+
severity: "high",
|
|
70
|
+
title: "Fork bomb",
|
|
71
|
+
advice: "This command exhausts the machine's processes. Do not run it.",
|
|
72
|
+
pattern: R `:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:`,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "core/sudo-redirect",
|
|
76
|
+
category: "danger",
|
|
77
|
+
severity: "medium",
|
|
78
|
+
title: "sudo with an output redirect",
|
|
79
|
+
advice: "The redirect runs as you, not root. Use `| sudo tee FILE` or `sudo sh -c '...'`.",
|
|
80
|
+
pattern: R `\bsudo\s[^;&|<"']*?(?<![\d&>])>{1,2}(?!&)\s*(?!\/dev\/null\b)[^\s&>]`,
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: "core/bearer-token",
|
|
84
|
+
category: "secret",
|
|
85
|
+
severity: "high",
|
|
86
|
+
title: "Authorization: Bearer token",
|
|
87
|
+
advice: "Rotate the token and remove this entry from your history.",
|
|
88
|
+
pattern: R `Authorization:\s*Bearer\s+(?<secret>[A-Za-z0-9._~+\/=-]{8,})`,
|
|
89
|
+
flags: "i",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: "core/basic-auth-header",
|
|
93
|
+
category: "secret",
|
|
94
|
+
severity: "high",
|
|
95
|
+
title: "Authorization: Basic credentials",
|
|
96
|
+
advice: "Rotate the credentials and remove this entry from your history.",
|
|
97
|
+
pattern: R `Authorization:\s*Basic\s+(?<secret>[A-Za-z0-9+\/=]{8,})`,
|
|
98
|
+
flags: "i",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: "core/password-flag",
|
|
102
|
+
category: "secret",
|
|
103
|
+
severity: "high",
|
|
104
|
+
title: "Password passed as a flag",
|
|
105
|
+
advice: "Rotate the password. Use a prompt, an env file or a credential helper instead.",
|
|
106
|
+
pattern: R `(?:^|\s)--(?:password|passwd|pass|pwd)(?:=|\s+)["']?${VALUE}`,
|
|
107
|
+
flags: "i",
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: "core/mysql-password",
|
|
111
|
+
category: "secret",
|
|
112
|
+
severity: "high",
|
|
113
|
+
title: "MySQL password on the command line (-p<password>)",
|
|
114
|
+
advice: "Rotate the password. Use -p alone to be prompted, or a ~/.my.cnf file.",
|
|
115
|
+
pattern: R `\b(?:mysql|mysqldump|mysqladmin|mariadb)\b[^;&|]*\s-p${VALUE}`,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: "core/sshpass-password",
|
|
119
|
+
category: "secret",
|
|
120
|
+
severity: "high",
|
|
121
|
+
title: "sshpass password on the command line",
|
|
122
|
+
advice: "Rotate the password and switch to SSH keys.",
|
|
123
|
+
pattern: R `\bsshpass\b[^;&|]*\s-p\s*["']?${VALUE}`,
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: "core/curl-user-password",
|
|
127
|
+
category: "secret",
|
|
128
|
+
severity: "high",
|
|
129
|
+
title: "curl --user with a password",
|
|
130
|
+
advice: "Rotate the password. Use a ~/.netrc file or a token from an env var.",
|
|
131
|
+
pattern: R `\bcurl\b[^;&|]*\s(?:-u|--user)(?:=|\s+)["']?[^:\s"']+:(?<secret>[^\s"'$][^\s"']*)`,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: "core/export-secret",
|
|
135
|
+
category: "secret",
|
|
136
|
+
severity: "high",
|
|
137
|
+
title: "Secret exported in a command",
|
|
138
|
+
advice: "Rotate the value. Load secrets from a file or a secret manager instead of typing them.",
|
|
139
|
+
pattern: R `\bexport\s+${SECRET_NAME}=["']?${VALUE}`,
|
|
140
|
+
flags: "i",
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
id: "core/fish-set-secret",
|
|
144
|
+
category: "secret",
|
|
145
|
+
severity: "high",
|
|
146
|
+
title: "Secret set in a fish command",
|
|
147
|
+
advice: "Rotate the value. Load secrets from a file or a secret manager instead of typing them.",
|
|
148
|
+
pattern: R `(?:^|[;&|]\s*)set\s+(?:-\S+\s+)*${SECRET_NAME}\s+["']?${FISH_VALUE}`,
|
|
149
|
+
flags: "i",
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
id: "core/quoted-secret-assignment",
|
|
153
|
+
category: "secret",
|
|
154
|
+
severity: "high",
|
|
155
|
+
title: "Quoted secret assigned to a token, password or key",
|
|
156
|
+
advice: "Rotate the value. Keep secrets out of commands, SQL and JSON typed in the shell.",
|
|
157
|
+
pattern: R `[A-Za-z0-9_]*(?:token|secret|passw(?:or)?d|api_?key|access_?key|private_?key)["']?\s*[=:]\s*["'](?<secret>[^"'\s$<{][^"']{5,})["']`,
|
|
158
|
+
flags: "i",
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: "core/private-key-header",
|
|
162
|
+
category: "secret",
|
|
163
|
+
severity: "high",
|
|
164
|
+
title: "Private key pasted into a command",
|
|
165
|
+
advice: "Treat the key as compromised: replace it and remove this entry from your history.",
|
|
166
|
+
pattern: R `-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY(?: BLOCK)?-----`,
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: "core/url-credentials",
|
|
170
|
+
category: "secret",
|
|
171
|
+
severity: "high",
|
|
172
|
+
title: "Password inside a URL",
|
|
173
|
+
advice: "Rotate the password and remove this entry from your history.",
|
|
174
|
+
pattern: R `\b[a-z][a-z0-9+.-]*:\/\/[^\s:\/@"']+:(?<secret>[^\s@\/"'$][^\s@\/"']*)@`,
|
|
175
|
+
flags: "i",
|
|
176
|
+
},
|
|
177
|
+
];
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const R = String.raw;
|
|
2
|
+
export const docker = [
|
|
3
|
+
{
|
|
4
|
+
id: "docker/run-privileged",
|
|
5
|
+
category: "danger",
|
|
6
|
+
severity: "high",
|
|
7
|
+
title: "docker run --privileged",
|
|
8
|
+
advice: "A privileged container can take over the host. Add only the capabilities you need (--cap-add).",
|
|
9
|
+
pattern: R `\bdocker\s+(?:container\s+)?run\b[^;&|]*\s--privileged(?:=true)?(?=\s|$)`,
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
id: "docker/mount-socket",
|
|
13
|
+
category: "danger",
|
|
14
|
+
severity: "medium",
|
|
15
|
+
title: "Docker socket mounted into a container",
|
|
16
|
+
advice: "Anything in the container can control the host's Docker. Avoid it unless required.",
|
|
17
|
+
pattern: R `\bdocker\s+(?:container\s+)?run\b[^;&|]*\s(?:-v|--volume)[=\s]+\/var\/run\/docker\.sock`,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: "docker/login-password",
|
|
21
|
+
category: "secret",
|
|
22
|
+
severity: "high",
|
|
23
|
+
title: "docker login with a password flag",
|
|
24
|
+
advice: "Rotate the password. Use --password-stdin instead.",
|
|
25
|
+
pattern: R `\bdocker\s+login\b[^;&|]*\s(?:-p|--password)[=\s]+["']?(?<secret>[^\s"'$-][^\s"']*)`,
|
|
26
|
+
},
|
|
27
|
+
];
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const R = String.raw;
|
|
2
|
+
export const git = [
|
|
3
|
+
{
|
|
4
|
+
id: "git/force-push-main",
|
|
5
|
+
category: "danger",
|
|
6
|
+
severity: "high",
|
|
7
|
+
title: "Force push to main or master",
|
|
8
|
+
advice: "Rewriting a shared branch can lose teammates' commits. Use a feature branch or --force-with-lease on a topic branch.",
|
|
9
|
+
pattern: R `\bgit\s+push\b(?=[^;&|]*\s(?:-[a-zA-Z]*f[a-zA-Z]*|--force(?:-with-lease)?)(?:=\S+)?(?=\s|$))[^;&|]*\s\+?(?:\S*:)?(?:main|master)(?=\s|$)`,
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
id: "git/force-push-main-refspec",
|
|
13
|
+
category: "danger",
|
|
14
|
+
severity: "high",
|
|
15
|
+
title: "Force push to main or master (+refspec)",
|
|
16
|
+
advice: "A leading + forces the push. Avoid force-pushing shared branches.",
|
|
17
|
+
pattern: R `\bgit\s+push\b[^;&|]*\s\+(?:\S*:)?(?:main|master)(?=\s|$)`,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: "git/github-token",
|
|
21
|
+
category: "secret",
|
|
22
|
+
severity: "high",
|
|
23
|
+
title: "GitHub token",
|
|
24
|
+
advice: "Revoke the token at github.com/settings/tokens and remove this entry from your history.",
|
|
25
|
+
pattern: R `\b(?<secret>(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,})`,
|
|
26
|
+
},
|
|
27
|
+
];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { cloud } from "./cloud.js";
|
|
2
|
+
import { core } from "./core.js";
|
|
3
|
+
import { docker } from "./docker.js";
|
|
4
|
+
import { git } from "./git.js";
|
|
5
|
+
import { kubectl } from "./kubectl.js";
|
|
6
|
+
import { systemd } from "./systemd.js";
|
|
7
|
+
/**
|
|
8
|
+
* All packs. Specific packs come before `core` so that when two rules match
|
|
9
|
+
* the same secret, the more specific title wins.
|
|
10
|
+
*/
|
|
11
|
+
export const packs = [
|
|
12
|
+
{ name: "cloud", patterns: cloud },
|
|
13
|
+
{ name: "git", patterns: git },
|
|
14
|
+
{ name: "docker", patterns: docker },
|
|
15
|
+
{ name: "kubectl", patterns: kubectl },
|
|
16
|
+
{ name: "systemd", patterns: systemd },
|
|
17
|
+
{ name: "core", patterns: core },
|
|
18
|
+
];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const R = String.raw;
|
|
2
|
+
export const kubectl = [
|
|
3
|
+
{
|
|
4
|
+
id: "kubectl/delete-all",
|
|
5
|
+
category: "danger",
|
|
6
|
+
severity: "high",
|
|
7
|
+
title: "kubectl delete --all",
|
|
8
|
+
advice: "Check the namespace and context first (kubectl config current-context), and use --dry-run=client.",
|
|
9
|
+
pattern: R `\bkubectl\s+delete\b[^;&|]*\s(?:--all|--all-namespaces|-A)(?=\s|$)`,
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
id: "kubectl/delete-namespace",
|
|
13
|
+
category: "danger",
|
|
14
|
+
severity: "medium",
|
|
15
|
+
title: "kubectl delete namespace",
|
|
16
|
+
advice: "Deleting a namespace removes everything inside it. Confirm the context first.",
|
|
17
|
+
pattern: R `\bkubectl\s+delete\s+(?:ns|namespaces?)\b`,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: "kubectl/secret-literal",
|
|
21
|
+
category: "secret",
|
|
22
|
+
severity: "high",
|
|
23
|
+
title: "Secret value typed with --from-literal",
|
|
24
|
+
advice: "Rotate the value. Create secrets from a file or a secret manager.",
|
|
25
|
+
pattern: R `--from-literal[=\s]+["']?[^\s=]+=(?<secret>[^\s"']+)`,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "kubectl/token-flag",
|
|
29
|
+
category: "secret",
|
|
30
|
+
severity: "high",
|
|
31
|
+
title: "Token passed to kubectl",
|
|
32
|
+
advice: "Revoke the token and use a kubeconfig user entry instead.",
|
|
33
|
+
pattern: R `\bkubectl\b[^;&|]*\s--token(?:=|\s+)["']?(?<secret>[^\s"'$-][^\s"']*)`,
|
|
34
|
+
},
|
|
35
|
+
];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const R = String.raw;
|
|
2
|
+
export const systemd = [
|
|
3
|
+
{
|
|
4
|
+
id: "systemd/stop-remote-access",
|
|
5
|
+
category: "danger",
|
|
6
|
+
severity: "high",
|
|
7
|
+
title: "Stopping SSH or networking services",
|
|
8
|
+
advice: "On a remote machine this can lock you out. Keep a second session open.",
|
|
9
|
+
pattern: R `\bsystemctl\s+(?:--\S+\s+)*(?:stop|disable|mask)\s+(?:--\S+\s+)*(?:sshd?|networking|NetworkManager|systemd-networkd)(?:\.service)?(?=\s|$)`,
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
id: "systemd/isolate-rescue",
|
|
13
|
+
category: "danger",
|
|
14
|
+
severity: "medium",
|
|
15
|
+
title: "systemctl isolate to a minimal or power target",
|
|
16
|
+
advice: "This stops most services at once. Make sure you have console access.",
|
|
17
|
+
pattern: R `\bsystemctl\s+isolate\s+(?:rescue|emergency|poweroff|halt)(?:\.target)?(?=\s|$)`,
|
|
18
|
+
},
|
|
19
|
+
];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const ORDER = { high: 0, medium: 1, low: 2 };
|
|
2
|
+
export function sortFindings(findings) {
|
|
3
|
+
return [...findings].sort((a, b) => ORDER[a.severity] - ORDER[b.severity] || a.entry - b.entry);
|
|
4
|
+
}
|
|
5
|
+
function formatFinding(finding) {
|
|
6
|
+
const when = finding.date ? ` on ${finding.date}` : "";
|
|
7
|
+
return [
|
|
8
|
+
`[${finding.severity.toUpperCase()}] ${finding.title}`,
|
|
9
|
+
` entry #${finding.entry}${when}`,
|
|
10
|
+
` ${finding.preview}`,
|
|
11
|
+
` -> ${finding.advice}`,
|
|
12
|
+
].join("\n");
|
|
13
|
+
}
|
|
14
|
+
const SECRET_NOTE = "Rotate every credential listed above, then remove those entries from your history file. aliasforge never edits it.";
|
|
15
|
+
export function renderRisks(findings, entryCount) {
|
|
16
|
+
if (findings.length === 0)
|
|
17
|
+
return `No risky commands or secrets found in ${entryCount} history entries.`;
|
|
18
|
+
const secrets = findings.filter((f) => f.category === "secret").length;
|
|
19
|
+
const lines = [
|
|
20
|
+
`${findings.length} finding(s) in ${entryCount} history entries (${secrets} secret, ${findings.length - secrets} dangerous).`,
|
|
21
|
+
"",
|
|
22
|
+
sortFindings(findings).map(formatFinding).join("\n\n"),
|
|
23
|
+
];
|
|
24
|
+
if (secrets > 0)
|
|
25
|
+
lines.push("", SECRET_NOTE);
|
|
26
|
+
return lines.join("\n");
|
|
27
|
+
}
|
|
28
|
+
export function renderRisksJson(report) {
|
|
29
|
+
return JSON.stringify({ ...report, findings: sortFindings(report.findings) }, null, 2);
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shell-aliasforge",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Mine your shell history to suggest aliases and flag risky or secret-leaking commands. Local-first, offline, zero dependencies.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Pratik Surve",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/prsurve/aliasforge.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/prsurve/aliasforge#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/prsurve/aliasforge/issues"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"bin": {
|
|
17
|
+
"aliasforge": "dist/cli.js"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE"
|
|
23
|
+
],
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=24"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc -p tsconfig.build.json",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"test": "vitest run",
|
|
31
|
+
"dev": "tsx src/cli.ts",
|
|
32
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"shell",
|
|
36
|
+
"alias",
|
|
37
|
+
"history",
|
|
38
|
+
"cli",
|
|
39
|
+
"bash",
|
|
40
|
+
"zsh",
|
|
41
|
+
"fish",
|
|
42
|
+
"secrets",
|
|
43
|
+
"offline"
|
|
44
|
+
],
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^26.6.5",
|
|
47
|
+
"tsx": "^4.23.15",
|
|
48
|
+
"typescript": "^7.0.2",
|
|
49
|
+
"vitest": "^5.0.5"
|
|
50
|
+
}
|
|
51
|
+
}
|