@adithya-naik/cmd-tracker 1.0.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/LICENSE +21 -0
- package/README.md +187 -0
- package/bin/tracker.js +271 -0
- package/package.json +41 -0
- package/src/commands/clear.js +109 -0
- package/src/commands/export.js +92 -0
- package/src/commands/favorite.js +95 -0
- package/src/commands/hook.js +89 -0
- package/src/commands/init.js +153 -0
- package/src/commands/list.js +107 -0
- package/src/commands/save.js +63 -0
- package/src/commands/search.js +97 -0
- package/src/commands/stats.js +120 -0
- package/src/index.js +19 -0
- package/src/utils/categorizer.js +165 -0
- package/src/utils/hook.js +281 -0
- package/src/utils/storage.js +286 -0
- package/src/utils/validator.js +94 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* stats.js
|
|
3
|
+
*
|
|
4
|
+
* Handles "tracker stats" command
|
|
5
|
+
*
|
|
6
|
+
* Shows a breakdown of saved commands:
|
|
7
|
+
* → Total commands saved
|
|
8
|
+
* → Count per category
|
|
9
|
+
* → Percentage per category
|
|
10
|
+
* → Most used category
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { readCommands } = require("../utils/storage");
|
|
14
|
+
const { isInitialized, showInitError } = require("../utils/validator");
|
|
15
|
+
|
|
16
|
+
function statsCommand() {
|
|
17
|
+
|
|
18
|
+
if (!isInitialized()) {
|
|
19
|
+
showInitError();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const data = readCommands();
|
|
25
|
+
|
|
26
|
+
/*
|
|
27
|
+
* Calculate total commands across all categories
|
|
28
|
+
* Object.values() → gets all arrays from data object
|
|
29
|
+
* .reduce() → adds up all lengths
|
|
30
|
+
*
|
|
31
|
+
* Example:
|
|
32
|
+
* { git: [1,2], npm: [1] }
|
|
33
|
+
* → values → [[1,2], [1]]
|
|
34
|
+
* → reduce → 2 + 1 = 3
|
|
35
|
+
*/
|
|
36
|
+
const total = Object.values(data).reduce(
|
|
37
|
+
(sum, commands) => sum + commands.length, 0
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
if (total === 0) {
|
|
41
|
+
console.log("\n📭 No commands saved yet!");
|
|
42
|
+
console.log("💡 Use: tracker save \"your command\"\n");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log("\n📊 CMD-TRACKER — Statistics\n");
|
|
47
|
+
console.log("─".repeat(50));
|
|
48
|
+
|
|
49
|
+
/*
|
|
50
|
+
* Category icons — same as list.js for consistency
|
|
51
|
+
*/
|
|
52
|
+
const icons = {
|
|
53
|
+
git: "🔀",
|
|
54
|
+
npm: "📦",
|
|
55
|
+
docker: "🐳",
|
|
56
|
+
linux: "🐧",
|
|
57
|
+
node: "🟢",
|
|
58
|
+
angular: "🔴",
|
|
59
|
+
python: "🐍",
|
|
60
|
+
others: "📌"
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/*
|
|
64
|
+
* Track which category has most commands
|
|
65
|
+
*/
|
|
66
|
+
let topCategory = "";
|
|
67
|
+
let topCount = 0;
|
|
68
|
+
|
|
69
|
+
/*
|
|
70
|
+
* Loop through each category and show stats
|
|
71
|
+
*/
|
|
72
|
+
for (const [category, commands] of Object.entries(data)) {
|
|
73
|
+
|
|
74
|
+
/*
|
|
75
|
+
* Skip empty categories
|
|
76
|
+
*/
|
|
77
|
+
if (commands.length === 0) continue;
|
|
78
|
+
|
|
79
|
+
/*
|
|
80
|
+
* Calculate percentage
|
|
81
|
+
* Math.round() → rounds to nearest whole number
|
|
82
|
+
* 2.7 → 3, 2.3 → 2
|
|
83
|
+
*/
|
|
84
|
+
const percentage = Math.round((commands.length / total) * 100);
|
|
85
|
+
|
|
86
|
+
/*
|
|
87
|
+
* Build a simple visual bar
|
|
88
|
+
* "█".repeat(percentage/5) → each block = 5%
|
|
89
|
+
* So 50% → 10 blocks, 100% → 20 blocks
|
|
90
|
+
*/
|
|
91
|
+
const bar = "█".repeat(Math.round(percentage / 5));
|
|
92
|
+
|
|
93
|
+
const icon = icons[category] || "📌";
|
|
94
|
+
|
|
95
|
+
console.log(
|
|
96
|
+
`\n${icon} ${category.toUpperCase().padEnd(10)} ` +
|
|
97
|
+
`${String(commands.length).padStart(3)} commands ` +
|
|
98
|
+
`${String(percentage).padStart(3)}% ${bar}`
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
/*
|
|
102
|
+
* Track top category
|
|
103
|
+
*/
|
|
104
|
+
if (commands.length > topCount) {
|
|
105
|
+
topCount = commands.length;
|
|
106
|
+
topCategory = category;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
console.log("\n" + "─".repeat(50));
|
|
111
|
+
console.log(`📦 Total commands : ${total}`);
|
|
112
|
+
console.log(`🏆 Most used : ${topCategory} (${topCount} commands)\n`);
|
|
113
|
+
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.log("\n❌ Error reading statistics");
|
|
116
|
+
console.log("💡 Try running tracker init again\n");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
module.exports = { statsCommand };
|
package/src/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* index.js — Main entry point when package is required
|
|
3
|
+
*
|
|
4
|
+
* This file is used when someone does:
|
|
5
|
+
* const tracker = require('@adithya-naik/cmd-tracker')
|
|
6
|
+
*
|
|
7
|
+
* We export our core utilities so developers can
|
|
8
|
+
* use them programmatically in their own code
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { saveCommand, readCommands, initStorage } = require("./utils/storage");
|
|
12
|
+
const { categorize } = require("./utils/categorizer");
|
|
13
|
+
|
|
14
|
+
module.exports = {
|
|
15
|
+
saveCommand,
|
|
16
|
+
readCommands,
|
|
17
|
+
initStorage,
|
|
18
|
+
categorize
|
|
19
|
+
};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* categorizer.js
|
|
3
|
+
*
|
|
4
|
+
* This file has ONE job — look at a command and decide its category
|
|
5
|
+
* Example:
|
|
6
|
+
* "git status" → "git"
|
|
7
|
+
* "npm install" → "npm"
|
|
8
|
+
* "docker ps" → "docker"
|
|
9
|
+
* "ls -la" → "linux"
|
|
10
|
+
* "ng new my-app" → "others"
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
* We define all our categories and their keywords here
|
|
15
|
+
* This is called a CONFIGURATION OBJECT
|
|
16
|
+
*
|
|
17
|
+
* Why keep it here separately?
|
|
18
|
+
* → Easy to add new categories later
|
|
19
|
+
* → Easy to add new keywords to existing categories
|
|
20
|
+
* → Everything in one place — no hunting around in code
|
|
21
|
+
*/
|
|
22
|
+
const CATEGORIES = {
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
* Any command starting with "git" goes here
|
|
26
|
+
* Examples: git status, git push, git commit -m "msg"
|
|
27
|
+
*/
|
|
28
|
+
git: ["git"],
|
|
29
|
+
|
|
30
|
+
/*
|
|
31
|
+
* Any command starting with "npm" or "npx" goes here
|
|
32
|
+
* Examples: npm install, npm run dev, npx create-react-app
|
|
33
|
+
*/
|
|
34
|
+
npm: ["npm", "npx"],
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
* Any command starting with "docker" goes here
|
|
38
|
+
* Examples: docker ps, docker build, docker-compose up
|
|
39
|
+
*/
|
|
40
|
+
docker: ["docker", "docker-compose"],
|
|
41
|
+
|
|
42
|
+
/*
|
|
43
|
+
* Common linux commands go here
|
|
44
|
+
* Examples: ls -la, cd projects, mkdir new-folder
|
|
45
|
+
*/
|
|
46
|
+
linux: [
|
|
47
|
+
"ls",
|
|
48
|
+
"cd",
|
|
49
|
+
"pwd",
|
|
50
|
+
"mkdir",
|
|
51
|
+
"rm",
|
|
52
|
+
"cp",
|
|
53
|
+
"mv",
|
|
54
|
+
"cat",
|
|
55
|
+
"touch",
|
|
56
|
+
"chmod",
|
|
57
|
+
"chown",
|
|
58
|
+
"grep",
|
|
59
|
+
"find",
|
|
60
|
+
"echo",
|
|
61
|
+
"sudo",
|
|
62
|
+
"apt",
|
|
63
|
+
"curl",
|
|
64
|
+
"wget",
|
|
65
|
+
"nano",
|
|
66
|
+
"vim"
|
|
67
|
+
],
|
|
68
|
+
|
|
69
|
+
/*
|
|
70
|
+
* Node.js related commands
|
|
71
|
+
* Examples: node index.js, nodemon server.js
|
|
72
|
+
*/
|
|
73
|
+
node: ["node", "nodemon"],
|
|
74
|
+
|
|
75
|
+
/*
|
|
76
|
+
* Angular CLI commands
|
|
77
|
+
* Examples: ng new my-app, ng serve, ng generate component
|
|
78
|
+
*/
|
|
79
|
+
angular: ["ng"],
|
|
80
|
+
|
|
81
|
+
/*
|
|
82
|
+
* Python commands
|
|
83
|
+
* Examples: python app.py, pip install flask
|
|
84
|
+
*/
|
|
85
|
+
python: ["python", "python3", "pip", "pip3"],
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/*
|
|
89
|
+
* categorize() — the main function of this file
|
|
90
|
+
*
|
|
91
|
+
* It takes a command string as input
|
|
92
|
+
* It returns the category name as a string
|
|
93
|
+
*
|
|
94
|
+
* @param {string} command - the terminal command typed by user
|
|
95
|
+
* @returns {string} - category name (git/npm/docker/linux/node/angular/python/others)
|
|
96
|
+
*
|
|
97
|
+
* Example:
|
|
98
|
+
* categorize("git status") → "git"
|
|
99
|
+
* categorize("npm install") → "npm"
|
|
100
|
+
* categorize("ng serve") → "angular"
|
|
101
|
+
* categorize("abc xyz") → "others"
|
|
102
|
+
*/
|
|
103
|
+
function categorize(command) {
|
|
104
|
+
|
|
105
|
+
/*
|
|
106
|
+
* Safety check — if command is empty or not a string, return "others"
|
|
107
|
+
* This prevents crashes if something unexpected is passed in
|
|
108
|
+
*/
|
|
109
|
+
if (!command || typeof command !== "string") {
|
|
110
|
+
return "others";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/*
|
|
114
|
+
* .trim() → removes spaces from start and end
|
|
115
|
+
* .toLowerCase() → converts to lowercase so "Git Status" = "git status"
|
|
116
|
+
* .split(" ")[0] → takes only the FIRST word
|
|
117
|
+
*
|
|
118
|
+
* Example:
|
|
119
|
+
* " git status " → trim → "git status"
|
|
120
|
+
* → toLowerCase → "git status"
|
|
121
|
+
* → split(" ") → ["git", "status"]
|
|
122
|
+
* → [0] → "git"
|
|
123
|
+
*/
|
|
124
|
+
const firstWord = command.trim().toLowerCase().split(" ")[0];
|
|
125
|
+
|
|
126
|
+
/*
|
|
127
|
+
* Object.entries() converts our CATEGORIES object into an array like:
|
|
128
|
+
* [
|
|
129
|
+
* ["git", ["git"]],
|
|
130
|
+
* ["npm", ["npm", "npx"]],
|
|
131
|
+
* ...
|
|
132
|
+
* ]
|
|
133
|
+
*
|
|
134
|
+
* We loop through each [categoryName, keywords] pair
|
|
135
|
+
*/
|
|
136
|
+
for (const [categoryName, keywords] of Object.entries(CATEGORIES)) {
|
|
137
|
+
|
|
138
|
+
/*
|
|
139
|
+
* .includes() checks if our firstWord exists in the keywords array
|
|
140
|
+
*
|
|
141
|
+
* Example:
|
|
142
|
+
* categoryName = "npm"
|
|
143
|
+
* keywords = ["npm", "npx"]
|
|
144
|
+
* firstWord = "npx"
|
|
145
|
+
* keywords.includes("npx") → true → return "npm"
|
|
146
|
+
*/
|
|
147
|
+
if (keywords.includes(firstWord)) {
|
|
148
|
+
return categoryName;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/*
|
|
153
|
+
* If no category matched — return "others"
|
|
154
|
+
* This is our catch-all category
|
|
155
|
+
*/
|
|
156
|
+
return "others";
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/*
|
|
160
|
+
* module.exports → makes this function available to other files
|
|
161
|
+
* Without this line — no other file can use categorize()
|
|
162
|
+
*
|
|
163
|
+
* This is how Node.js shares code between files
|
|
164
|
+
*/
|
|
165
|
+
module.exports = { categorize };
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* hook.js
|
|
3
|
+
*
|
|
4
|
+
* This file handles the shell hook setup
|
|
5
|
+
* Shell hook = a small script added to user's shell config
|
|
6
|
+
* that runs automatically after EVERY command they type
|
|
7
|
+
*
|
|
8
|
+
* Supported shells:
|
|
9
|
+
* → bash → hooks into ~/.bashrc
|
|
10
|
+
* → zsh → hooks into ~/.zshrc
|
|
11
|
+
* → Both are most common shells on Mac/Linux
|
|
12
|
+
*
|
|
13
|
+
* How it works:
|
|
14
|
+
* 1. Detect user's shell (bash or zsh)
|
|
15
|
+
* 2. Add a hook script to their shell config file
|
|
16
|
+
* 3. Hook script calls "tracker save <command>" after every command
|
|
17
|
+
* 4. User sources their config → automatic capture starts!
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const fs = require("fs");
|
|
21
|
+
const path = require("path");
|
|
22
|
+
const os = require("os");
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
* os module — built into Node.js
|
|
26
|
+
* os.homedir() → gives home directory path
|
|
27
|
+
* Windows → C:\Users\Adithya
|
|
28
|
+
* Mac/Linux → /home/adithya
|
|
29
|
+
*/
|
|
30
|
+
const HOME_DIR = os.homedir();
|
|
31
|
+
|
|
32
|
+
/*
|
|
33
|
+
* Shell config file paths
|
|
34
|
+
* These are standard locations for shell config files
|
|
35
|
+
*/
|
|
36
|
+
const SHELL_CONFIGS = {
|
|
37
|
+
bash: path.join(HOME_DIR, ".bashrc"),
|
|
38
|
+
zsh: path.join(HOME_DIR, ".zshrc")
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/*
|
|
42
|
+
* This is the actual hook script we add to shell config
|
|
43
|
+
* It runs after every command the user types
|
|
44
|
+
*
|
|
45
|
+
* How it works line by line:
|
|
46
|
+
*
|
|
47
|
+
* _cmd_tracker_hook() → defines a function
|
|
48
|
+
* LAST_CMD=$(history 1 | sed ...) → gets the last command typed
|
|
49
|
+
* tracker save "$LAST_CMD" → saves it using our CLI
|
|
50
|
+
*
|
|
51
|
+
* precmd() → zsh hook that runs before each prompt (zsh only)
|
|
52
|
+
* PROMPT_COMMAND → bash variable that runs before each prompt (bash only)
|
|
53
|
+
*/
|
|
54
|
+
/*
|
|
55
|
+
* HOOK_SCRIPT — the shell script we add to user's config
|
|
56
|
+
*
|
|
57
|
+
* IMPORTANT: We use regular string (single quotes) NOT template literal
|
|
58
|
+
* Because ${PROMPT_COMMAND} is a BASH variable not a JavaScript variable
|
|
59
|
+
* If we use backticks → JavaScript tries to read PROMPT_COMMAND as JS variable
|
|
60
|
+
* If we use regular string → it stays as plain text ✅
|
|
61
|
+
*/
|
|
62
|
+
/*
|
|
63
|
+
* HOOK_SCRIPT — shell script added to user's config
|
|
64
|
+
*
|
|
65
|
+
* The sed command had escaped quote issues before
|
|
66
|
+
* Fix: use [[:space:]] instead of [ ] and avoid nested quotes
|
|
67
|
+
* This is cleaner and works on all bash/zsh versions
|
|
68
|
+
*/
|
|
69
|
+
const HOOK_SCRIPT = [
|
|
70
|
+
"",
|
|
71
|
+
"# cmd-tracker shell hook — auto saves every command you type",
|
|
72
|
+
"_cmd_tracker_hook() {",
|
|
73
|
+
" local LAST_CMD",
|
|
74
|
+
" LAST_CMD=$(history 1 | sed 's/^[[:space:]]*[0-9]*[[:space:]]*//')",
|
|
75
|
+
' if [ -n "$LAST_CMD" ]; then',
|
|
76
|
+
' tracker save "$LAST_CMD" > /dev/null 2>&1',
|
|
77
|
+
" fi",
|
|
78
|
+
"}",
|
|
79
|
+
"",
|
|
80
|
+
"# For zsh",
|
|
81
|
+
'if [ -n "$ZSH_VERSION" ]; then',
|
|
82
|
+
" autoload -U add-zsh-hook",
|
|
83
|
+
" add-zsh-hook precmd _cmd_tracker_hook",
|
|
84
|
+
"fi",
|
|
85
|
+
"",
|
|
86
|
+
"# For bash",
|
|
87
|
+
'if [ -n "$BASH_VERSION" ]; then',
|
|
88
|
+
' PROMPT_COMMAND="_cmd_tracker_hook${PROMPT_COMMAND:+;$PROMPT_COMMAND}"',
|
|
89
|
+
"fi",
|
|
90
|
+
""
|
|
91
|
+
].join("\n");
|
|
92
|
+
|
|
93
|
+
/*
|
|
94
|
+
* HOOK_MARKER — unique string we add to identify our hook
|
|
95
|
+
* We use this to:
|
|
96
|
+
* → Check if hook already exists (avoid duplicates)
|
|
97
|
+
* → Find and remove hook when user runs tracker unhook
|
|
98
|
+
*/
|
|
99
|
+
const HOOK_MARKER = "# cmd-tracker shell hook — auto saves every command you type";
|
|
100
|
+
|
|
101
|
+
/*
|
|
102
|
+
* detectShell() — detects which shell user is running
|
|
103
|
+
*
|
|
104
|
+
* process.env.SHELL → environment variable containing shell path
|
|
105
|
+
* Example:
|
|
106
|
+
* "/bin/bash" → bash
|
|
107
|
+
* "/bin/zsh" → zsh
|
|
108
|
+
*
|
|
109
|
+
* @returns {string} — "bash", "zsh", or "unknown"
|
|
110
|
+
*/
|
|
111
|
+
function detectShell() {
|
|
112
|
+
const shell = process.env.SHELL || "";
|
|
113
|
+
|
|
114
|
+
if (shell.includes("zsh")) return "zsh";
|
|
115
|
+
if (shell.includes("bash")) return "bash";
|
|
116
|
+
|
|
117
|
+
/*
|
|
118
|
+
* On Windows — check SHELL or ComSpec
|
|
119
|
+
* Most Windows users use bash via Git Bash
|
|
120
|
+
*/
|
|
121
|
+
const comspec = process.env.ComSpec || "";
|
|
122
|
+
if (comspec.includes("cmd.exe")) return "unknown";
|
|
123
|
+
|
|
124
|
+
return "bash"; // default to bash
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/*
|
|
128
|
+
* getConfigFile() — returns path to shell config file
|
|
129
|
+
*
|
|
130
|
+
* @param {string} shell — "bash" or "zsh"
|
|
131
|
+
* @returns {string} — full path to config file
|
|
132
|
+
*/
|
|
133
|
+
function getConfigFile(shell) {
|
|
134
|
+
return SHELL_CONFIGS[shell] || SHELL_CONFIGS.bash;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/*
|
|
138
|
+
* installHook() — adds hook script to shell config
|
|
139
|
+
*
|
|
140
|
+
* @returns {object} — { success: true/false, shell, configFile, message }
|
|
141
|
+
*/
|
|
142
|
+
function installHook() {
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
const shell = detectShell();
|
|
146
|
+
|
|
147
|
+
/*
|
|
148
|
+
* If shell is unknown — we can't install hook
|
|
149
|
+
* This happens on Windows CMD/PowerShell
|
|
150
|
+
*/
|
|
151
|
+
if (shell === "unknown") {
|
|
152
|
+
return {
|
|
153
|
+
success: false,
|
|
154
|
+
message: "❌ Automatic hook not supported on Windows CMD/PowerShell\n💡 Use Git Bash or WSL for automatic capture"
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const configFile = getConfigFile(shell);
|
|
159
|
+
|
|
160
|
+
/*
|
|
161
|
+
* Check if hook already installed
|
|
162
|
+
* Look for our unique marker in the config file
|
|
163
|
+
*/
|
|
164
|
+
if (fs.existsSync(configFile)) {
|
|
165
|
+
const content = fs.readFileSync(configFile, "utf-8");
|
|
166
|
+
|
|
167
|
+
if (content.includes(HOOK_MARKER)) {
|
|
168
|
+
return {
|
|
169
|
+
success: false,
|
|
170
|
+
message: `⚠️ Hook already installed in ${configFile}`
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/*
|
|
176
|
+
* Append hook script to shell config file
|
|
177
|
+
* appendFileSync → adds to end without deleting existing content
|
|
178
|
+
*/
|
|
179
|
+
fs.appendFileSync(configFile, HOOK_SCRIPT);
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
success: true,
|
|
183
|
+
shell,
|
|
184
|
+
configFile,
|
|
185
|
+
message: `✅ Hook installed in ${configFile}`
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
} catch (error) {
|
|
189
|
+
|
|
190
|
+
if (error.code === "EACCES") {
|
|
191
|
+
return {
|
|
192
|
+
success: false,
|
|
193
|
+
message: "❌ Permission denied! Try with sudo/admin permissions"
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
success: false,
|
|
199
|
+
message: `❌ Failed to install hook: ${error.message}`
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/*
|
|
205
|
+
* removeHook() — removes hook script from shell config
|
|
206
|
+
*
|
|
207
|
+
* @returns {object} — { success: true/false, message }
|
|
208
|
+
*/
|
|
209
|
+
function removeHook() {
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
const shell = detectShell();
|
|
213
|
+
const configFile = getConfigFile(shell);
|
|
214
|
+
|
|
215
|
+
if (!fs.existsSync(configFile)) {
|
|
216
|
+
return {
|
|
217
|
+
success: false,
|
|
218
|
+
message: "❌ Shell config file not found"
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const content = fs.readFileSync(configFile, "utf-8");
|
|
223
|
+
|
|
224
|
+
/*
|
|
225
|
+
* Check if hook exists
|
|
226
|
+
*/
|
|
227
|
+
if (!content.includes(HOOK_MARKER)) {
|
|
228
|
+
return {
|
|
229
|
+
success: false,
|
|
230
|
+
message: "⚠️ Hook not found in shell config"
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/*
|
|
235
|
+
* Remove hook script from config
|
|
236
|
+
* Split by marker → take everything before it
|
|
237
|
+
* Then trim trailing whitespace
|
|
238
|
+
*/
|
|
239
|
+
const newContent = content.split(HOOK_MARKER)[0].trimEnd() + "\n";
|
|
240
|
+
fs.writeFileSync(configFile, newContent);
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
success: true,
|
|
244
|
+
configFile,
|
|
245
|
+
message: `✅ Hook removed from ${configFile}`
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
} catch (error) {
|
|
249
|
+
return {
|
|
250
|
+
success: false,
|
|
251
|
+
message: `❌ Failed to remove hook: ${error.message}`
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/*
|
|
257
|
+
* isHookInstalled() — checks if hook is already installed
|
|
258
|
+
*
|
|
259
|
+
* @returns {boolean}
|
|
260
|
+
*/
|
|
261
|
+
function isHookInstalled() {
|
|
262
|
+
try {
|
|
263
|
+
const shell = detectShell();
|
|
264
|
+
const configFile = getConfigFile(shell);
|
|
265
|
+
|
|
266
|
+
if (!fs.existsSync(configFile)) return false;
|
|
267
|
+
|
|
268
|
+
const content = fs.readFileSync(configFile, "utf-8");
|
|
269
|
+
return content.includes(HOOK_MARKER);
|
|
270
|
+
|
|
271
|
+
} catch (error) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
module.exports = {
|
|
277
|
+
installHook,
|
|
278
|
+
removeHook,
|
|
279
|
+
isHookInstalled,
|
|
280
|
+
detectShell
|
|
281
|
+
};
|