@adithya-naik/cmd-tracker 1.0.0 → 1.0.3
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/.github/CODEOWNERS +1 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +26 -0
- package/.github/workflows/ci.yml +95 -0
- package/.github/workflows/publish.yml +77 -0
- package/.husky/pre-commit +1 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/README.md +54 -1
- package/bin/tracker.js +45 -45
- package/eslint.config.js +30 -0
- package/package.json +20 -5
- package/src/commands/clear.js +13 -13
- package/src/commands/export.js +16 -16
- package/src/commands/favorite.js +16 -16
- package/src/commands/hook.js +24 -15
- package/src/commands/init.js +43 -43
- package/src/commands/list.js +33 -23
- package/src/commands/save.js +2 -2
- package/src/commands/search.js +18 -18
- package/src/commands/stats.js +55 -45
- package/src/index.js +2 -2
- package/src/utils/categorizer.js +135 -40
- package/src/utils/hook.js +92 -53
- package/src/utils/storage.js +26 -15
- package/src/utils/validator.js +26 -10
package/src/utils/categorizer.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* categorizer.js
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* This file has ONE job — look at a command and decide its category
|
|
5
5
|
* Example:
|
|
6
6
|
* "git status" → "git"
|
|
7
7
|
* "npm install" → "npm"
|
|
8
8
|
* "docker ps" → "docker"
|
|
9
9
|
* "ls -la" → "linux"
|
|
10
|
+
* "go get <pkgURL>" → "go"
|
|
10
11
|
* "ng new my-app" → "others"
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
/*
|
|
14
15
|
* We define all our categories and their keywords here
|
|
15
16
|
* This is called a CONFIGURATION OBJECT
|
|
16
|
-
*
|
|
17
|
+
*
|
|
17
18
|
* Why keep it here separately?
|
|
18
19
|
* → Easy to add new categories later
|
|
19
20
|
* → Easy to add new keywords to existing categories
|
|
@@ -25,75 +26,169 @@ const CATEGORIES = {
|
|
|
25
26
|
* Any command starting with "git" goes here
|
|
26
27
|
* Examples: git status, git push, git commit -m "msg"
|
|
27
28
|
*/
|
|
28
|
-
git: [
|
|
29
|
+
git: ['git'],
|
|
29
30
|
|
|
30
31
|
/*
|
|
31
32
|
* Any command starting with "npm" or "npx" goes here
|
|
32
33
|
* Examples: npm install, npm run dev, npx create-react-app
|
|
33
34
|
*/
|
|
34
|
-
npm: [
|
|
35
|
+
npm: ['npm', 'npx'],
|
|
35
36
|
|
|
36
37
|
/*
|
|
37
38
|
* Any command starting with "docker" goes here
|
|
38
39
|
* Examples: docker ps, docker build, docker-compose up
|
|
39
40
|
*/
|
|
40
|
-
docker: [
|
|
41
|
+
docker: ['docker', 'docker-compose'],
|
|
41
42
|
|
|
42
43
|
/*
|
|
43
44
|
* Common linux commands go here
|
|
44
45
|
* Examples: ls -la, cd projects, mkdir new-folder
|
|
45
46
|
*/
|
|
46
47
|
linux: [
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
"chmod",
|
|
57
|
-
"chown",
|
|
58
|
-
"grep",
|
|
59
|
-
"find",
|
|
60
|
-
"echo",
|
|
61
|
-
"sudo",
|
|
62
|
-
"apt",
|
|
63
|
-
"curl",
|
|
64
|
-
"wget",
|
|
65
|
-
"nano",
|
|
66
|
-
"vim"
|
|
48
|
+
'ls', 'cd', 'pwd', 'mkdir', 'rmdir',
|
|
49
|
+
'rm', 'cp', 'mv', 'cat', 'touch',
|
|
50
|
+
'chmod', 'chown', 'grep', 'find',
|
|
51
|
+
'echo', 'sudo', 'apt', 'apt-get',
|
|
52
|
+
'curl', 'wget', 'nano', 'vim',
|
|
53
|
+
'less', 'head', 'tail', 'ps',
|
|
54
|
+
'top', 'kill', 'df', 'du',
|
|
55
|
+
'tar', 'zip', 'unzip', 'ssh',
|
|
56
|
+
'scp', 'rsync'
|
|
67
57
|
],
|
|
68
58
|
|
|
69
59
|
/*
|
|
70
60
|
* Node.js related commands
|
|
71
61
|
* Examples: node index.js, nodemon server.js
|
|
72
62
|
*/
|
|
73
|
-
node: [
|
|
63
|
+
node: ['node', 'nodemon'],
|
|
74
64
|
|
|
75
65
|
/*
|
|
76
66
|
* Angular CLI commands
|
|
77
67
|
* Examples: ng new my-app, ng serve, ng generate component
|
|
78
68
|
*/
|
|
79
|
-
angular: [
|
|
69
|
+
angular: ['ng'],
|
|
80
70
|
|
|
81
71
|
/*
|
|
82
72
|
* Python commands
|
|
83
73
|
* Examples: python app.py, pip install flask
|
|
84
74
|
*/
|
|
85
|
-
python: [
|
|
75
|
+
python: ['python', 'python3', 'pip', 'pip3'],
|
|
76
|
+
|
|
77
|
+
go: ['go'],
|
|
78
|
+
|
|
79
|
+
/*
|
|
80
|
+
* Java ecosystem commands
|
|
81
|
+
* Examples: java Main, javac Main.java,
|
|
82
|
+
* mvn package, gradle build
|
|
83
|
+
*/
|
|
84
|
+
java: ['java', 'javac', 'mvn', 'gradle'],
|
|
85
|
+
|
|
86
|
+
/*
|
|
87
|
+
* Rust development commands
|
|
88
|
+
* Examples: cargo build, cargo run,
|
|
89
|
+
* cargo test, rustc main.rs
|
|
90
|
+
*/
|
|
91
|
+
rust: ['cargo', 'rustc'],
|
|
92
|
+
|
|
93
|
+
/*
|
|
94
|
+
* .NET commands
|
|
95
|
+
* Examples: dotnet run, dotnet build,
|
|
96
|
+
* dotnet new console
|
|
97
|
+
*/
|
|
98
|
+
dotnet: ['dotnet'],
|
|
99
|
+
|
|
100
|
+
/*
|
|
101
|
+
* Kubernetes tooling commands
|
|
102
|
+
* Examples: kubectl get pods,
|
|
103
|
+
* kubectl apply -f app.yaml,
|
|
104
|
+
* helm install my-app chart/
|
|
105
|
+
*/
|
|
106
|
+
kubernetes: ['kubectl', 'helm'],
|
|
107
|
+
|
|
108
|
+
/*
|
|
109
|
+
* Database CLI commands
|
|
110
|
+
* Examples: mysql -u root -p,
|
|
111
|
+
* psql mydb,
|
|
112
|
+
* mongosh,
|
|
113
|
+
* redis-cli
|
|
114
|
+
*/
|
|
115
|
+
database: [
|
|
116
|
+
'mysql',
|
|
117
|
+
'psql',
|
|
118
|
+
'sqlite3',
|
|
119
|
+
'mongosh',
|
|
120
|
+
'redis-cli'
|
|
121
|
+
],
|
|
122
|
+
|
|
123
|
+
/*
|
|
124
|
+
* Cloud provider CLI commands
|
|
125
|
+
* Examples: aws s3 ls,
|
|
126
|
+
* gcloud compute instances list,
|
|
127
|
+
* az login
|
|
128
|
+
*/
|
|
129
|
+
cloud: [
|
|
130
|
+
'aws',
|
|
131
|
+
'gcloud',
|
|
132
|
+
'az'
|
|
133
|
+
],
|
|
134
|
+
|
|
135
|
+
/*
|
|
136
|
+
* Package manager commands
|
|
137
|
+
* Examples: yarn install,
|
|
138
|
+
* pnpm dev,
|
|
139
|
+
* brew install git,
|
|
140
|
+
* snap install code
|
|
141
|
+
*/
|
|
142
|
+
packagemanagers: [
|
|
143
|
+
'yarn',
|
|
144
|
+
'pnpm',
|
|
145
|
+
'brew',
|
|
146
|
+
'snap'
|
|
147
|
+
],
|
|
148
|
+
|
|
149
|
+
/*
|
|
150
|
+
* Testing framework commands
|
|
151
|
+
* Examples: jest,
|
|
152
|
+
* vitest run,
|
|
153
|
+
* playwright test,
|
|
154
|
+
* cypress open
|
|
155
|
+
*/
|
|
156
|
+
testing: [
|
|
157
|
+
'jest',
|
|
158
|
+
'vitest',
|
|
159
|
+
'playwright',
|
|
160
|
+
'cypress'
|
|
161
|
+
],
|
|
162
|
+
|
|
163
|
+
/*
|
|
164
|
+
* AI coding assistant CLI commands
|
|
165
|
+
* Examples: claude, gemini, codex,
|
|
166
|
+
* opencode, aider, q chat
|
|
167
|
+
*/
|
|
168
|
+
ai: [
|
|
169
|
+
'claude',
|
|
170
|
+
'gemini',
|
|
171
|
+
'codex',
|
|
172
|
+
'c',
|
|
173
|
+
'opencode',
|
|
174
|
+
'aider',
|
|
175
|
+
'q',
|
|
176
|
+
'warp',
|
|
177
|
+
'ollama',
|
|
178
|
+
'huggingface-cli'
|
|
179
|
+
],
|
|
180
|
+
|
|
86
181
|
};
|
|
87
182
|
|
|
88
183
|
/*
|
|
89
184
|
* categorize() — the main function of this file
|
|
90
|
-
*
|
|
185
|
+
*
|
|
91
186
|
* It takes a command string as input
|
|
92
187
|
* It returns the category name as a string
|
|
93
|
-
*
|
|
188
|
+
*
|
|
94
189
|
* @param {string} command - the terminal command typed by user
|
|
95
190
|
* @returns {string} - category name (git/npm/docker/linux/node/angular/python/others)
|
|
96
|
-
*
|
|
191
|
+
*
|
|
97
192
|
* Example:
|
|
98
193
|
* categorize("git status") → "git"
|
|
99
194
|
* categorize("npm install") → "npm"
|
|
@@ -106,22 +201,22 @@ function categorize(command) {
|
|
|
106
201
|
* Safety check — if command is empty or not a string, return "others"
|
|
107
202
|
* This prevents crashes if something unexpected is passed in
|
|
108
203
|
*/
|
|
109
|
-
if (!command || typeof command !==
|
|
110
|
-
return
|
|
204
|
+
if (!command || typeof command !== 'string') {
|
|
205
|
+
return 'others';
|
|
111
206
|
}
|
|
112
207
|
|
|
113
208
|
/*
|
|
114
209
|
* .trim() → removes spaces from start and end
|
|
115
210
|
* .toLowerCase() → converts to lowercase so "Git Status" = "git status"
|
|
116
211
|
* .split(" ")[0] → takes only the FIRST word
|
|
117
|
-
*
|
|
212
|
+
*
|
|
118
213
|
* Example:
|
|
119
214
|
* " git status " → trim → "git status"
|
|
120
215
|
* → toLowerCase → "git status"
|
|
121
216
|
* → split(" ") → ["git", "status"]
|
|
122
217
|
* → [0] → "git"
|
|
123
218
|
*/
|
|
124
|
-
const firstWord = command.trim().toLowerCase().split(
|
|
219
|
+
const firstWord = command.trim().toLowerCase().split(' ')[0];
|
|
125
220
|
|
|
126
221
|
/*
|
|
127
222
|
* Object.entries() converts our CATEGORIES object into an array like:
|
|
@@ -130,14 +225,14 @@ function categorize(command) {
|
|
|
130
225
|
* ["npm", ["npm", "npx"]],
|
|
131
226
|
* ...
|
|
132
227
|
* ]
|
|
133
|
-
*
|
|
228
|
+
*
|
|
134
229
|
* We loop through each [categoryName, keywords] pair
|
|
135
230
|
*/
|
|
136
231
|
for (const [categoryName, keywords] of Object.entries(CATEGORIES)) {
|
|
137
232
|
|
|
138
233
|
/*
|
|
139
234
|
* .includes() checks if our firstWord exists in the keywords array
|
|
140
|
-
*
|
|
235
|
+
*
|
|
141
236
|
* Example:
|
|
142
237
|
* categoryName = "npm"
|
|
143
238
|
* keywords = ["npm", "npx"]
|
|
@@ -153,13 +248,13 @@ function categorize(command) {
|
|
|
153
248
|
* If no category matched — return "others"
|
|
154
249
|
* This is our catch-all category
|
|
155
250
|
*/
|
|
156
|
-
return
|
|
251
|
+
return 'others';
|
|
157
252
|
}
|
|
158
253
|
|
|
159
254
|
/*
|
|
160
255
|
* module.exports → makes this function available to other files
|
|
161
256
|
* Without this line — no other file can use categorize()
|
|
162
|
-
*
|
|
257
|
+
*
|
|
163
258
|
* This is how Node.js shares code between files
|
|
164
259
|
*/
|
|
165
|
-
module.exports = { categorize };
|
|
260
|
+
module.exports = { categorize };
|
package/src/utils/hook.js
CHANGED
|
@@ -8,18 +8,18 @@
|
|
|
8
8
|
* Supported shells:
|
|
9
9
|
* → bash → hooks into ~/.bashrc
|
|
10
10
|
* → zsh → hooks into ~/.zshrc
|
|
11
|
-
* →
|
|
11
|
+
* → fish → hooks into ~/.config/fish/config.fish
|
|
12
12
|
*
|
|
13
13
|
* How it works:
|
|
14
|
-
* 1. Detect user's shell (bash or
|
|
14
|
+
* 1. Detect user's shell (bash, zsh, or fish)
|
|
15
15
|
* 2. Add a hook script to their shell config file
|
|
16
16
|
* 3. Hook script calls "tracker save <command>" after every command
|
|
17
17
|
* 4. User sources their config → automatic capture starts!
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
const fs = require(
|
|
21
|
-
const path = require(
|
|
22
|
-
const os = require(
|
|
20
|
+
const fs = require('node:fs');
|
|
21
|
+
const path = require('node:path');
|
|
22
|
+
const os = require('node:os');
|
|
23
23
|
|
|
24
24
|
/*
|
|
25
25
|
* os module — built into Node.js
|
|
@@ -28,14 +28,15 @@ const os = require("os");
|
|
|
28
28
|
* Mac/Linux → /home/adithya
|
|
29
29
|
*/
|
|
30
30
|
const HOME_DIR = os.homedir();
|
|
31
|
-
|
|
32
31
|
/*
|
|
33
32
|
* Shell config file paths
|
|
34
33
|
* These are standard locations for shell config files
|
|
35
34
|
*/
|
|
36
35
|
const SHELL_CONFIGS = {
|
|
37
|
-
bash: path.join(HOME_DIR,
|
|
38
|
-
zsh: path.join(HOME_DIR,
|
|
36
|
+
bash: path.join(HOME_DIR, '.bashrc'),
|
|
37
|
+
zsh: path.join(HOME_DIR, '.zshrc'),
|
|
38
|
+
fish: path.join(HOME_DIR, '.config/fish/config.fish'),
|
|
39
|
+
|
|
39
40
|
};
|
|
40
41
|
|
|
41
42
|
/*
|
|
@@ -67,28 +68,43 @@ const SHELL_CONFIGS = {
|
|
|
67
68
|
* This is cleaner and works on all bash/zsh versions
|
|
68
69
|
*/
|
|
69
70
|
const HOOK_SCRIPT = [
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
'',
|
|
72
|
+
'# cmd-tracker shell hook — auto saves every command you type',
|
|
73
|
+
'_cmd_tracker_hook() {',
|
|
74
|
+
' local LAST_CMD',
|
|
75
|
+
' LAST_CMD=$(history 1 | sed \'s/^[[:space:]]*[0-9]*[[:space:]]*//\')',
|
|
75
76
|
' if [ -n "$LAST_CMD" ]; then',
|
|
76
77
|
' tracker save "$LAST_CMD" > /dev/null 2>&1',
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
' fi',
|
|
79
|
+
'}',
|
|
80
|
+
'',
|
|
81
|
+
'# For zsh',
|
|
81
82
|
'if [ -n "$ZSH_VERSION" ]; then',
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
' autoload -U add-zsh-hook',
|
|
84
|
+
' add-zsh-hook precmd _cmd_tracker_hook',
|
|
85
|
+
'fi',
|
|
86
|
+
'',
|
|
87
|
+
'# For bash',
|
|
87
88
|
'if [ -n "$BASH_VERSION" ]; then',
|
|
88
89
|
' PROMPT_COMMAND="_cmd_tracker_hook${PROMPT_COMMAND:+;$PROMPT_COMMAND}"',
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
].join(
|
|
90
|
+
'fi',
|
|
91
|
+
''
|
|
92
|
+
].join('\n');
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
const FISH_HOOK_SCRIPT = [
|
|
96
|
+
'',
|
|
97
|
+
'# cmd-tracker shell hook — auto saves every command you type',
|
|
98
|
+
'function __cmd_tracker_postexec --on-event fish_postexec',
|
|
99
|
+
' set -l last_cmd (history --max=1)',
|
|
100
|
+
'',
|
|
101
|
+
' if test -n "$last_cmd"',
|
|
102
|
+
' tracker save "$last_cmd" >/dev/null 2>&1',
|
|
103
|
+
' end',
|
|
104
|
+
'end',
|
|
105
|
+
'',
|
|
106
|
+
].join('\n');
|
|
107
|
+
|
|
92
108
|
|
|
93
109
|
/*
|
|
94
110
|
* HOOK_MARKER — unique string we add to identify our hook
|
|
@@ -96,32 +112,45 @@ const HOOK_SCRIPT = [
|
|
|
96
112
|
* → Check if hook already exists (avoid duplicates)
|
|
97
113
|
* → Find and remove hook when user runs tracker unhook
|
|
98
114
|
*/
|
|
99
|
-
const HOOK_MARKER =
|
|
115
|
+
const HOOK_MARKER = '# cmd-tracker shell hook — auto saves every command you type';
|
|
100
116
|
|
|
101
117
|
/*
|
|
102
118
|
* detectShell() — detects which shell user is running
|
|
103
119
|
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
120
|
+
* Priority:
|
|
121
|
+
* 1. process.env.FISH_VERSION — fish sets this automatically
|
|
122
|
+
* even when launched from another shell (parent $SHELL
|
|
123
|
+
* would still show bash/zsh)
|
|
124
|
+
* 2. process.env.SHELL — the standard shell path variable
|
|
125
|
+
* Examples: "/bin/bash" → bash, "/bin/zsh" → zsh
|
|
108
126
|
*
|
|
109
|
-
* @returns {string} — "bash", "zsh", or "unknown"
|
|
127
|
+
* @returns {string} — "bash", "zsh", "fish", or "unknown"
|
|
110
128
|
*/
|
|
111
129
|
function detectShell() {
|
|
112
|
-
const shell = process.env.SHELL ||
|
|
130
|
+
const shell = process.env.SHELL || '';
|
|
131
|
+
|
|
132
|
+
if (process.env.FISH_VERSION) return 'fish';
|
|
113
133
|
|
|
114
|
-
if (shell.includes(
|
|
115
|
-
if (shell.includes("bash")) return "bash";
|
|
134
|
+
if (shell.includes('fish')) return 'fish';
|
|
116
135
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
136
|
+
try {
|
|
137
|
+
const ppid = process.ppid;
|
|
138
|
+
const { execFileSync } = require('node:child_process');
|
|
139
|
+
const comm = execFileSync('/bin/ps', ['-o', 'comm=', '-p', String(ppid)], { encoding: 'utf-8' }).trim();
|
|
140
|
+
if (comm.includes('fish')) return 'fish';
|
|
141
|
+
if (comm.includes('zsh')) return 'zsh';
|
|
142
|
+
if (comm.includes('bash')) return 'bash';
|
|
143
|
+
} catch {
|
|
144
|
+
// ignore ps command errors
|
|
145
|
+
}
|
|
123
146
|
|
|
124
|
-
return
|
|
147
|
+
if (shell.includes('zsh')) return 'zsh';
|
|
148
|
+
if (shell.includes('bash')) return 'bash';
|
|
149
|
+
|
|
150
|
+
const comspec = process.env.ComSpec || '';
|
|
151
|
+
if (comspec.includes('cmd.exe')) return 'unknown';
|
|
152
|
+
|
|
153
|
+
return 'unknown';
|
|
125
154
|
}
|
|
126
155
|
|
|
127
156
|
/*
|
|
@@ -148,10 +177,10 @@ function installHook() {
|
|
|
148
177
|
* If shell is unknown — we can't install hook
|
|
149
178
|
* This happens on Windows CMD/PowerShell
|
|
150
179
|
*/
|
|
151
|
-
if (shell ===
|
|
180
|
+
if (shell === 'unknown') {
|
|
152
181
|
return {
|
|
153
182
|
success: false,
|
|
154
|
-
message:
|
|
183
|
+
message: '❌ Automatic hook not supported on Windows CMD/PowerShell\n💡 Use Git Bash or WSL for automatic capture'
|
|
155
184
|
};
|
|
156
185
|
}
|
|
157
186
|
|
|
@@ -162,7 +191,7 @@ function installHook() {
|
|
|
162
191
|
* Look for our unique marker in the config file
|
|
163
192
|
*/
|
|
164
193
|
if (fs.existsSync(configFile)) {
|
|
165
|
-
const content = fs.readFileSync(configFile,
|
|
194
|
+
const content = fs.readFileSync(configFile, 'utf-8');
|
|
166
195
|
|
|
167
196
|
if (content.includes(HOOK_MARKER)) {
|
|
168
197
|
return {
|
|
@@ -176,7 +205,17 @@ function installHook() {
|
|
|
176
205
|
* Append hook script to shell config file
|
|
177
206
|
* appendFileSync → adds to end without deleting existing content
|
|
178
207
|
*/
|
|
179
|
-
|
|
208
|
+
if (shell === 'fish') {
|
|
209
|
+
fs.mkdirSync(path.dirname(configFile), { recursive: true });
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/*
|
|
213
|
+
* Choose correct hook
|
|
214
|
+
*/
|
|
215
|
+
const hookScript =
|
|
216
|
+
shell === 'fish' ? FISH_HOOK_SCRIPT : HOOK_SCRIPT;
|
|
217
|
+
|
|
218
|
+
fs.appendFileSync(configFile, hookScript);
|
|
180
219
|
|
|
181
220
|
return {
|
|
182
221
|
success: true,
|
|
@@ -187,10 +226,10 @@ function installHook() {
|
|
|
187
226
|
|
|
188
227
|
} catch (error) {
|
|
189
228
|
|
|
190
|
-
if (error.code ===
|
|
229
|
+
if (error.code === 'EACCES') {
|
|
191
230
|
return {
|
|
192
231
|
success: false,
|
|
193
|
-
message:
|
|
232
|
+
message: '❌ Permission denied! Try with sudo/admin permissions'
|
|
194
233
|
};
|
|
195
234
|
}
|
|
196
235
|
|
|
@@ -215,11 +254,11 @@ function removeHook() {
|
|
|
215
254
|
if (!fs.existsSync(configFile)) {
|
|
216
255
|
return {
|
|
217
256
|
success: false,
|
|
218
|
-
message:
|
|
257
|
+
message: '❌ Shell config file not found'
|
|
219
258
|
};
|
|
220
259
|
}
|
|
221
260
|
|
|
222
|
-
const content = fs.readFileSync(configFile,
|
|
261
|
+
const content = fs.readFileSync(configFile, 'utf-8');
|
|
223
262
|
|
|
224
263
|
/*
|
|
225
264
|
* Check if hook exists
|
|
@@ -227,7 +266,7 @@ function removeHook() {
|
|
|
227
266
|
if (!content.includes(HOOK_MARKER)) {
|
|
228
267
|
return {
|
|
229
268
|
success: false,
|
|
230
|
-
message:
|
|
269
|
+
message: '⚠️ Hook not found in shell config'
|
|
231
270
|
};
|
|
232
271
|
}
|
|
233
272
|
|
|
@@ -236,7 +275,7 @@ function removeHook() {
|
|
|
236
275
|
* Split by marker → take everything before it
|
|
237
276
|
* Then trim trailing whitespace
|
|
238
277
|
*/
|
|
239
|
-
const newContent = content.split(HOOK_MARKER)[0].trimEnd() +
|
|
278
|
+
const newContent = content.split(HOOK_MARKER)[0].trimEnd() + '\n';
|
|
240
279
|
fs.writeFileSync(configFile, newContent);
|
|
241
280
|
|
|
242
281
|
return {
|
|
@@ -265,7 +304,7 @@ function isHookInstalled() {
|
|
|
265
304
|
|
|
266
305
|
if (!fs.existsSync(configFile)) return false;
|
|
267
306
|
|
|
268
|
-
const content = fs.readFileSync(configFile,
|
|
307
|
+
const content = fs.readFileSync(configFile, 'utf-8');
|
|
269
308
|
return content.includes(HOOK_MARKER);
|
|
270
309
|
|
|
271
310
|
} catch (error) {
|
|
@@ -278,4 +317,4 @@ module.exports = {
|
|
|
278
317
|
removeHook,
|
|
279
318
|
isHookInstalled,
|
|
280
319
|
detectShell
|
|
281
|
-
};
|
|
320
|
+
};
|
package/src/utils/storage.js
CHANGED
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
* Mac/Linux: /home/user/project/.tracker/commands.json
|
|
12
12
|
* path module handles this difference automatically
|
|
13
13
|
*/
|
|
14
|
-
const fs = require(
|
|
15
|
-
const path = require(
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
16
|
|
|
17
17
|
/*
|
|
18
18
|
* Import our categorize function from categorizer.js
|
|
19
19
|
* We need it here to know WHICH category to save the command in
|
|
20
20
|
*/
|
|
21
|
-
const { categorize } = require(
|
|
21
|
+
const { categorize } = require('./categorizer');
|
|
22
22
|
|
|
23
23
|
/*
|
|
24
24
|
* process.cwd() → Current Working Directory
|
|
@@ -31,13 +31,13 @@ const { categorize } = require("./categorizer");
|
|
|
31
31
|
* This is VERY important — we want to save commands in THEIR repo
|
|
32
32
|
* not in our package folder
|
|
33
33
|
*/
|
|
34
|
-
const TRACKER_DIR = path.join(process.cwd(),
|
|
34
|
+
const TRACKER_DIR = path.join(process.cwd(), '.tracker');
|
|
35
35
|
|
|
36
36
|
/*
|
|
37
37
|
* This is where all commands will be saved
|
|
38
38
|
* .tracker/commands.json inside the user's repo
|
|
39
39
|
*/
|
|
40
|
-
const COMMANDS_FILE = path.join(TRACKER_DIR,
|
|
40
|
+
const COMMANDS_FILE = path.join(TRACKER_DIR, 'commands.json');
|
|
41
41
|
|
|
42
42
|
/*
|
|
43
43
|
* This is our default empty structure
|
|
@@ -56,6 +56,16 @@ function getDefaultStructure() {
|
|
|
56
56
|
node: [],
|
|
57
57
|
angular: [],
|
|
58
58
|
python: [],
|
|
59
|
+
go: [],
|
|
60
|
+
java: [],
|
|
61
|
+
rust: [],
|
|
62
|
+
dotnet: [],
|
|
63
|
+
kubernetes: [],
|
|
64
|
+
database: [],
|
|
65
|
+
cloud: [],
|
|
66
|
+
packagemanagers: [],
|
|
67
|
+
testing: [],
|
|
68
|
+
ai: [],
|
|
59
69
|
others: [],
|
|
60
70
|
};
|
|
61
71
|
}
|
|
@@ -82,7 +92,7 @@ function initStorage() {
|
|
|
82
92
|
* Like "mkdir -p" in linux
|
|
83
93
|
*/
|
|
84
94
|
fs.mkdirSync(TRACKER_DIR, { recursive: true });
|
|
85
|
-
console.log(
|
|
95
|
+
console.log('✅ Created .tracker folder');
|
|
86
96
|
}
|
|
87
97
|
|
|
88
98
|
/*
|
|
@@ -99,7 +109,7 @@ function initStorage() {
|
|
|
99
109
|
COMMANDS_FILE,
|
|
100
110
|
JSON.stringify(getDefaultStructure(), null, 2)
|
|
101
111
|
);
|
|
102
|
-
console.log(
|
|
112
|
+
console.log('✅ Created .tracker/commands.json');
|
|
103
113
|
}
|
|
104
114
|
}
|
|
105
115
|
|
|
@@ -122,8 +132,9 @@ function readCommands() {
|
|
|
122
132
|
* fs.readFileSync() → reads the file content as text
|
|
123
133
|
* JSON.parse() → converts JSON text back to JavaScript object
|
|
124
134
|
*/
|
|
125
|
-
const fileContent = fs.readFileSync(COMMANDS_FILE,
|
|
126
|
-
|
|
135
|
+
const fileContent = fs.readFileSync(COMMANDS_FILE, 'utf-8');
|
|
136
|
+
const parsed = JSON.parse(fileContent);
|
|
137
|
+
return { ...getDefaultStructure(), ...parsed };
|
|
127
138
|
}
|
|
128
139
|
|
|
129
140
|
/*
|
|
@@ -138,7 +149,7 @@ function saveCommand(command) {
|
|
|
138
149
|
* Safety check — don't save empty commands
|
|
139
150
|
*/
|
|
140
151
|
if (!command || !command.trim()) {
|
|
141
|
-
return { saved: false, reason:
|
|
152
|
+
return { saved: false, reason: 'empty command' };
|
|
142
153
|
}
|
|
143
154
|
|
|
144
155
|
/*
|
|
@@ -172,7 +183,7 @@ function saveCommand(command) {
|
|
|
172
183
|
);
|
|
173
184
|
|
|
174
185
|
if (isDuplicate) {
|
|
175
|
-
return { saved: false, reason:
|
|
186
|
+
return { saved: false, reason: 'duplicate', category };
|
|
176
187
|
}
|
|
177
188
|
|
|
178
189
|
/*
|
|
@@ -207,7 +218,7 @@ function saveCommand(command) {
|
|
|
207
218
|
function toggleFavorite(command) {
|
|
208
219
|
|
|
209
220
|
if (!command || !command.trim()) {
|
|
210
|
-
return { success: false, reason:
|
|
221
|
+
return { success: false, reason: 'empty command' };
|
|
211
222
|
}
|
|
212
223
|
|
|
213
224
|
const cleanCommand = command.trim();
|
|
@@ -234,14 +245,14 @@ function toggleFavorite(command) {
|
|
|
234
245
|
|
|
235
246
|
return {
|
|
236
247
|
success: true,
|
|
237
|
-
action: currentFav ?
|
|
248
|
+
action: currentFav ? 'removed' : 'added',
|
|
238
249
|
command: cleanCommand,
|
|
239
250
|
category
|
|
240
251
|
};
|
|
241
252
|
}
|
|
242
253
|
}
|
|
243
254
|
|
|
244
|
-
return { success: false, reason:
|
|
255
|
+
return { success: false, reason: 'command not found' };
|
|
245
256
|
}
|
|
246
257
|
|
|
247
258
|
/*
|
|
@@ -283,4 +294,4 @@ module.exports = {
|
|
|
283
294
|
getFavorites,
|
|
284
295
|
TRACKER_DIR,
|
|
285
296
|
COMMANDS_FILE,
|
|
286
|
-
};
|
|
297
|
+
};
|