@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,92 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* export.js
|
|
3
|
+
* Now with proper error handling using validator.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const path = require("path");
|
|
8
|
+
const { readCommands } = require("../utils/storage");
|
|
9
|
+
const { isInitialized, showInitError } = require("../utils/validator");
|
|
10
|
+
|
|
11
|
+
function exportCommand(options) {
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
* Check initialization first
|
|
15
|
+
*/
|
|
16
|
+
if (!isInitialized()) {
|
|
17
|
+
showInitError();
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const data = readCommands();
|
|
23
|
+
|
|
24
|
+
const total = Object.values(data).reduce(
|
|
25
|
+
(sum, commands) => sum + commands.length, 0
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
if (total === 0) {
|
|
29
|
+
console.log("\nš No commands to export yet!");
|
|
30
|
+
console.log("š” Use: tracker save \"your command\"\n");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (options.csv) {
|
|
35
|
+
exportAsCSV(data, total);
|
|
36
|
+
} else {
|
|
37
|
+
exportAsJSON(data, total);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.log("\nā Error exporting commands");
|
|
42
|
+
console.log("š” Try running tracker init again\n");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function exportAsJSON(data, total) {
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const exportData = {
|
|
50
|
+
exportedAt: new Date().toISOString(),
|
|
51
|
+
totalCommands: total,
|
|
52
|
+
commands: data
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const filePath = path.join(process.cwd(), "tracker-export.json");
|
|
56
|
+
fs.writeFileSync(filePath, JSON.stringify(exportData, null, 2));
|
|
57
|
+
|
|
58
|
+
console.log(`\nā
Exported ${total} commands to tracker-export.json`);
|
|
59
|
+
console.log(`š Location: ${filePath}\n`);
|
|
60
|
+
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.log("\nā Error creating JSON file");
|
|
63
|
+
console.log("š” Check if you have write permissions in this folder\n");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function exportAsCSV(data, total) {
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
let csvContent = "category,command,date\n";
|
|
71
|
+
|
|
72
|
+
for (const [category, commands] of Object.entries(data)) {
|
|
73
|
+
for (const item of commands) {
|
|
74
|
+
const date = new Date(item.time).toLocaleDateString();
|
|
75
|
+
csvContent += `${category},"${item.command}",${date}\n`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const filePath = path.join(process.cwd(), "tracker-export.csv");
|
|
80
|
+
fs.writeFileSync(filePath, csvContent);
|
|
81
|
+
|
|
82
|
+
console.log(`\nā
Exported ${total} commands to tracker-export.csv`);
|
|
83
|
+
console.log(`š Location: ${filePath}`);
|
|
84
|
+
console.log(`š” Open in Excel or Google Sheets for easy revision!\n`);
|
|
85
|
+
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.log("\nā Error creating CSV file");
|
|
88
|
+
console.log("š” Check if you have write permissions in this folder\n");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = { exportCommand };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* favorite.js
|
|
3
|
+
*
|
|
4
|
+
* Handles two commands:
|
|
5
|
+
* tracker favorite "git status" ā toggles favorite on that command
|
|
6
|
+
* tracker favorites ā lists all favorited commands
|
|
7
|
+
*
|
|
8
|
+
* Favorites are stored as { favorite: true } on each command object
|
|
9
|
+
* in commands.json
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { toggleFavorite, getFavorites } = require("../utils/storage");
|
|
13
|
+
const { isInitialized, showInitError } = require("../utils/validator");
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* favoriteCommand() ā toggles favorite on a specific command
|
|
17
|
+
*
|
|
18
|
+
* @param {string} command ā command to favorite/unfavorite
|
|
19
|
+
*/
|
|
20
|
+
function favoriteCommand(command) {
|
|
21
|
+
|
|
22
|
+
if (!isInitialized()) {
|
|
23
|
+
showInitError();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!command || !command.trim()) {
|
|
28
|
+
console.log("\nā Please provide a command to favorite");
|
|
29
|
+
console.log("š” Usage: tracker favorite \"git status\"\n");
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const result = toggleFavorite(command.trim());
|
|
35
|
+
|
|
36
|
+
if (!result.success) {
|
|
37
|
+
if (result.reason === "command not found") {
|
|
38
|
+
console.log(`\nā Command not found: "${command}"`);
|
|
39
|
+
console.log("š” Run tracker list to see saved commands\n");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
console.log("\nā Failed to update favorite\n");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (result.action === "added") {
|
|
47
|
+
console.log(`\nā Added to favorites: ${command}`);
|
|
48
|
+
console.log(`š Category: ${result.category}\n`);
|
|
49
|
+
} else {
|
|
50
|
+
console.log(`\nā
Removed from favorites: ${command}\n`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.log("\nā Error updating favorite");
|
|
55
|
+
console.log("š” Try running tracker init again\n");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/*
|
|
60
|
+
* favoritesCommand() ā lists all favorited commands
|
|
61
|
+
*/
|
|
62
|
+
function favoritesCommand() {
|
|
63
|
+
|
|
64
|
+
if (!isInitialized()) {
|
|
65
|
+
showInitError();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const favorites = getFavorites();
|
|
71
|
+
|
|
72
|
+
if (favorites.length === 0) {
|
|
73
|
+
console.log("\nā No favorites yet!");
|
|
74
|
+
console.log("š” Usage: tracker favorite \"git status\"\n");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log("\nā CMD-TRACKER ā Your Favorites\n");
|
|
79
|
+
console.log("ā".repeat(50));
|
|
80
|
+
|
|
81
|
+
favorites.forEach((item, index) => {
|
|
82
|
+
const date = new Date(item.time).toLocaleDateString();
|
|
83
|
+
console.log(`\n ${index + 1}. ${item.command}`);
|
|
84
|
+
console.log(` š ${item.category} š
${date}`);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
console.log("\n" + "ā".repeat(50));
|
|
88
|
+
console.log(`ā Total favorites: ${favorites.length}\n`);
|
|
89
|
+
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.log("\nā Error reading favorites\n");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = { favoriteCommand, favoritesCommand };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* hook.js (command)
|
|
3
|
+
*
|
|
4
|
+
* Handles "tracker hook" and "tracker unhook" commands
|
|
5
|
+
*
|
|
6
|
+
* tracker hook ā installs shell hook for auto capture
|
|
7
|
+
* tracker unhook ā removes shell hook
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
installHook,
|
|
12
|
+
removeHook,
|
|
13
|
+
isHookInstalled,
|
|
14
|
+
detectShell
|
|
15
|
+
} = require("../utils/hook");
|
|
16
|
+
|
|
17
|
+
const { isInitialized, showInitError } = require("../utils/validator");
|
|
18
|
+
|
|
19
|
+
/*
|
|
20
|
+
* hookCommand() ā runs when user types: tracker hook
|
|
21
|
+
* Installs the shell hook for automatic command capture
|
|
22
|
+
*/
|
|
23
|
+
function hookCommand() {
|
|
24
|
+
|
|
25
|
+
/*
|
|
26
|
+
* Must run tracker init before tracker hook
|
|
27
|
+
*/
|
|
28
|
+
if (!isInitialized()) {
|
|
29
|
+
showInitError();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log("\nšŖ Setting up automatic command capture...\n");
|
|
34
|
+
|
|
35
|
+
/*
|
|
36
|
+
* Show which shell was detected
|
|
37
|
+
*/
|
|
38
|
+
const shell = detectShell();
|
|
39
|
+
console.log(`š Detected shell: ${shell}`);
|
|
40
|
+
|
|
41
|
+
/*
|
|
42
|
+
* Check if already installed
|
|
43
|
+
*/
|
|
44
|
+
if (isHookInstalled()) {
|
|
45
|
+
console.log("\nā ļø Shell hook is already installed!");
|
|
46
|
+
console.log("š” Your commands are already being captured automatically");
|
|
47
|
+
console.log("š” Run: tracker unhook to disable\n");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/*
|
|
52
|
+
* Install the hook
|
|
53
|
+
*/
|
|
54
|
+
const result = installHook();
|
|
55
|
+
console.log("\n" + result.message);
|
|
56
|
+
|
|
57
|
+
if (result.success) {
|
|
58
|
+
console.log("\nšÆ Almost done! Run this command to activate:");
|
|
59
|
+
/*
|
|
60
|
+
* Always show Unix style path for source command
|
|
61
|
+
* Even on Windows ā Git Bash uses Unix paths
|
|
62
|
+
* ~/.bashrc works on all systems
|
|
63
|
+
*/
|
|
64
|
+
const sourceCmd = result.shell === "zsh" ? "~/.zshrc" : "~/.bashrc";
|
|
65
|
+
console.log(`\n source ${sourceCmd}\n`);
|
|
66
|
+
console.log("After that ā every command you type will be saved automatically! š\n");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/*
|
|
71
|
+
* unhookCommand() ā runs when user types: tracker unhook
|
|
72
|
+
* Removes the shell hook
|
|
73
|
+
*/
|
|
74
|
+
function unhookCommand() {
|
|
75
|
+
|
|
76
|
+
console.log("\nš Removing automatic command capture...\n");
|
|
77
|
+
|
|
78
|
+
const result = removeHook();
|
|
79
|
+
console.log(result.message);
|
|
80
|
+
|
|
81
|
+
if (result.success) {
|
|
82
|
+
console.log("\nšÆ Almost done! Run this to apply changes:");
|
|
83
|
+
const sourceCmd = result.shell === "zsh" ? "~/.zshrc" : "~/.bashrc";
|
|
84
|
+
console.log(`\n source ${sourceCmd}\n`);
|
|
85
|
+
console.log("After that ā automatic capture will be disabled\n");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = { hookCommand, unhookCommand };
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* init.js
|
|
3
|
+
* Now with proper error handling
|
|
4
|
+
*
|
|
5
|
+
* Error cases handled:
|
|
6
|
+
* ā Already initialized (runs tracker init twice)
|
|
7
|
+
* ā No write permissions in folder
|
|
8
|
+
* ā Any unexpected file system errors
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require("fs");
|
|
12
|
+
const path = require("path");
|
|
13
|
+
const { initStorage } = require("../utils/storage");
|
|
14
|
+
const { isInitialized } = require("../utils/validator");
|
|
15
|
+
|
|
16
|
+
function initCommand() {
|
|
17
|
+
|
|
18
|
+
console.log("š Initializing cmd-tracker in your project...\n");
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
|
|
22
|
+
/*
|
|
23
|
+
* Check if already initialized
|
|
24
|
+
* If user runs tracker init twice ā warn them
|
|
25
|
+
* but still continue in case files are corrupted
|
|
26
|
+
*/
|
|
27
|
+
if (isInitialized()) {
|
|
28
|
+
console.log("ā ļø cmd-tracker is already initialized in this project!");
|
|
29
|
+
console.log("š” Your existing commands are safe");
|
|
30
|
+
console.log("š” Running init again will not delete your saved commands\n");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
* Initialize storage ā creates .tracker folder
|
|
35
|
+
* and commands.json if they don't exist
|
|
36
|
+
*/
|
|
37
|
+
initStorage();
|
|
38
|
+
|
|
39
|
+
/*
|
|
40
|
+
* Update .gitignore
|
|
41
|
+
*/
|
|
42
|
+
updateGitignore();
|
|
43
|
+
|
|
44
|
+
console.log("\nā
cmd-tracker initialized successfully!");
|
|
45
|
+
console.log("š Created .tracker/commands.json in your project");
|
|
46
|
+
console.log("\nšÆ You can now use:");
|
|
47
|
+
console.log(" tracker list ā see all saved commands");
|
|
48
|
+
console.log(" tracker stats ā see command statistics");
|
|
49
|
+
console.log(" tracker search ā search your commands");
|
|
50
|
+
console.log(" tracker export ā export your commands");
|
|
51
|
+
console.log("\nš” Start using your terminal normally");
|
|
52
|
+
console.log(" Commands will be saved automatically!\n");
|
|
53
|
+
|
|
54
|
+
} catch (error) {
|
|
55
|
+
|
|
56
|
+
/*
|
|
57
|
+
* Handle specific error types
|
|
58
|
+
*
|
|
59
|
+
* error.code === "EACCES" ā permission denied
|
|
60
|
+
* This happens when user doesn't have write access
|
|
61
|
+
* to the current folder
|
|
62
|
+
*/
|
|
63
|
+
if (error.code === "EACCES") {
|
|
64
|
+
console.error("ā Permission denied!");
|
|
65
|
+
console.error("š” Try running with admin permissions");
|
|
66
|
+
console.error("š” Or check folder write permissions\n");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/*
|
|
71
|
+
* Handle no space left on disk
|
|
72
|
+
*/
|
|
73
|
+
if (error.code === "ENOSPC") {
|
|
74
|
+
console.error("ā No space left on disk!");
|
|
75
|
+
console.error("š” Free up some disk space and try again\n");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/*
|
|
80
|
+
* Any other unexpected error
|
|
81
|
+
*/
|
|
82
|
+
console.error("ā Failed to initialize cmd-tracker");
|
|
83
|
+
console.error(`Error: ${error.message}\n`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function updateGitignore() {
|
|
88
|
+
|
|
89
|
+
const gitignorePath = path.join(process.cwd(), ".gitignore");
|
|
90
|
+
/*
|
|
91
|
+
* Added tracker-export files to gitignore too
|
|
92
|
+
* Users should not push their exported files to GitHub
|
|
93
|
+
* These are personal revision files ā local only
|
|
94
|
+
*/
|
|
95
|
+
const trackerEntry = "\n# cmd-tracker personal data\n.tracker/\ntracker-export.json\ntracker-export.csv\n";
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
|
|
99
|
+
if (fs.existsSync(gitignorePath)) {
|
|
100
|
+
const gitignoreContent = fs.readFileSync(gitignorePath, "utf-8");
|
|
101
|
+
|
|
102
|
+
/*
|
|
103
|
+
* Check each entry separately
|
|
104
|
+
* So we can add missing entries without touching existing ones
|
|
105
|
+
*/
|
|
106
|
+
let entriesToAdd = "";
|
|
107
|
+
|
|
108
|
+
if (!gitignoreContent.includes(".tracker/")) {
|
|
109
|
+
entriesToAdd += ".tracker/\n";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!gitignoreContent.includes("tracker-export.json")) {
|
|
113
|
+
entriesToAdd += "tracker-export.json\n";
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!gitignoreContent.includes("tracker-export.csv")) {
|
|
117
|
+
entriesToAdd += "tracker-export.csv\n";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/*
|
|
121
|
+
* Only write if there's something new to add
|
|
122
|
+
*/
|
|
123
|
+
if (entriesToAdd === "") {
|
|
124
|
+
console.log("ā
.gitignore already up to date");
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
fs.appendFileSync(
|
|
129
|
+
gitignorePath,
|
|
130
|
+
"\n# cmd-tracker personal data\n" + entriesToAdd
|
|
131
|
+
);
|
|
132
|
+
console.log("ā
Updated .gitignore with cmd-tracker entries");
|
|
133
|
+
|
|
134
|
+
fs.appendFileSync(gitignorePath, trackerEntry);
|
|
135
|
+
console.log("ā
Added .tracker/ to your .gitignore");
|
|
136
|
+
|
|
137
|
+
} else {
|
|
138
|
+
fs.writeFileSync(gitignorePath, trackerEntry);
|
|
139
|
+
console.log("ā
Created .gitignore with .tracker/ entry");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
} catch (error) {
|
|
143
|
+
|
|
144
|
+
/*
|
|
145
|
+
* .gitignore update failed ā not critical
|
|
146
|
+
* tracker still works, just warn the user
|
|
147
|
+
*/
|
|
148
|
+
console.log("ā ļø Could not update .gitignore automatically");
|
|
149
|
+
console.log("š” Manually add .tracker/ to your .gitignore\n");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = { initCommand };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* list.js
|
|
3
|
+
*
|
|
4
|
+
* Handles "tracker list" and "tracker list <category>" commands
|
|
5
|
+
* Now with proper error handling using validator.js
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { readCommands } = require("../utils/storage");
|
|
9
|
+
const {
|
|
10
|
+
isInitialized,
|
|
11
|
+
showInitError,
|
|
12
|
+
isValidCategory,
|
|
13
|
+
showCategoryError
|
|
14
|
+
} = require("../utils/validator");
|
|
15
|
+
|
|
16
|
+
function listCommand(category) {
|
|
17
|
+
|
|
18
|
+
/*
|
|
19
|
+
* Check if tracker init was run first
|
|
20
|
+
* If not ā show helpful error and stop
|
|
21
|
+
*/
|
|
22
|
+
if (!isInitialized()) {
|
|
23
|
+
showInitError();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/*
|
|
28
|
+
* If category provided ā validate it
|
|
29
|
+
*/
|
|
30
|
+
if (category) {
|
|
31
|
+
if (!isValidCategory(category)) {
|
|
32
|
+
showCategoryError(category);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const data = readCommands();
|
|
37
|
+
const cat = category.toLowerCase();
|
|
38
|
+
displayCategory(cat, data[cat]);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/*
|
|
43
|
+
* No category ā show all commands
|
|
44
|
+
*/
|
|
45
|
+
try {
|
|
46
|
+
const data = readCommands();
|
|
47
|
+
let totalCommands = 0;
|
|
48
|
+
|
|
49
|
+
console.log("\nš CMD-TRACKER ā Your Command History\n");
|
|
50
|
+
console.log("ā".repeat(50));
|
|
51
|
+
|
|
52
|
+
for (const [cat, commands] of Object.entries(data)) {
|
|
53
|
+
if (commands.length === 0) continue;
|
|
54
|
+
displayCategory(cat, commands);
|
|
55
|
+
totalCommands += commands.length;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (totalCommands === 0) {
|
|
59
|
+
console.log("\nš No commands saved yet!");
|
|
60
|
+
console.log("š” Use: tracker save \"your command\"");
|
|
61
|
+
console.log("š” Or run commands normally if shell hook is set up\n");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log("ā".repeat(50));
|
|
66
|
+
console.log(`\nā
Total: ${totalCommands} commands saved\n`);
|
|
67
|
+
|
|
68
|
+
} catch (error) {
|
|
69
|
+
/*
|
|
70
|
+
* Something went wrong reading the file
|
|
71
|
+
* Show clear error instead of crashing
|
|
72
|
+
*/
|
|
73
|
+
console.log("\nā Error reading commands");
|
|
74
|
+
console.log(`š” Try running tracker init again\n`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function displayCategory(categoryName, commands) {
|
|
79
|
+
|
|
80
|
+
const icons = {
|
|
81
|
+
git: "š",
|
|
82
|
+
npm: "š¦",
|
|
83
|
+
docker: "š³",
|
|
84
|
+
linux: "š§",
|
|
85
|
+
node: "š¢",
|
|
86
|
+
angular: "š“",
|
|
87
|
+
python: "š",
|
|
88
|
+
others: "š"
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const icon = icons[categoryName] || "š";
|
|
92
|
+
|
|
93
|
+
console.log(`\n${icon} ${categoryName.toUpperCase()} (${commands.length})`);
|
|
94
|
+
console.log("ā".repeat(30));
|
|
95
|
+
|
|
96
|
+
if (commands.length === 0) {
|
|
97
|
+
console.log(" š No commands saved yet");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
commands.forEach((item, index) => {
|
|
102
|
+
const date = new Date(item.time).toLocaleDateString();
|
|
103
|
+
console.log(` ${String(index + 1).padStart(2, " ")}. ${item.command} (${date})`);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = { listCommand };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* save.js
|
|
3
|
+
* Now with proper error handling
|
|
4
|
+
*
|
|
5
|
+
* Error cases handled:
|
|
6
|
+
* ā tracker not initialized
|
|
7
|
+
* ā empty command
|
|
8
|
+
* ā file write failures
|
|
9
|
+
* ā any unexpected errors
|
|
10
|
+
*
|
|
11
|
+
* Important: ALL errors are silent here
|
|
12
|
+
* save runs automatically in background
|
|
13
|
+
* We NEVER want it to interrupt user's workflow
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const { saveCommand } = require("../utils/storage");
|
|
17
|
+
const { isInitialized } = require("../utils/validator");
|
|
18
|
+
|
|
19
|
+
function saveCommandAction(command) {
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
* Safety check ā empty command
|
|
23
|
+
* Return silently ā no error message needed
|
|
24
|
+
*/
|
|
25
|
+
if (!command || !command.trim()) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/*
|
|
30
|
+
* Check if tracker is initialized
|
|
31
|
+
*
|
|
32
|
+
* Unlike other commands ā we fail SILENTLY here
|
|
33
|
+
* Because save runs automatically via shell hook
|
|
34
|
+
* Showing errors every time would be very annoying
|
|
35
|
+
* for users who haven't run tracker init yet
|
|
36
|
+
*/
|
|
37
|
+
if (!isInitialized()) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
|
|
43
|
+
const result = saveCommand(command);
|
|
44
|
+
|
|
45
|
+
/*
|
|
46
|
+
* Only show output when command is newly saved
|
|
47
|
+
* Stay silent for duplicates
|
|
48
|
+
*/
|
|
49
|
+
if (result.saved) {
|
|
50
|
+
console.log(`ā
Saved [${result.category}]: ${command}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
} catch (error) {
|
|
54
|
+
|
|
55
|
+
/*
|
|
56
|
+
* Fail completely silently
|
|
57
|
+
* Never interrupt user's terminal workflow
|
|
58
|
+
*/
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = { saveCommandAction };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* search.js
|
|
3
|
+
*
|
|
4
|
+
* Handles "tracker search <query>" command
|
|
5
|
+
*
|
|
6
|
+
* tracker search "git" ā finds all commands containing "git"
|
|
7
|
+
* tracker search "install" ā finds all commands containing "install"
|
|
8
|
+
*
|
|
9
|
+
* Searches across ALL categories at once
|
|
10
|
+
* Returns matching commands with their category
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { readCommands } = require("../utils/storage");
|
|
14
|
+
const {
|
|
15
|
+
isInitialized,
|
|
16
|
+
showInitError,
|
|
17
|
+
isValidQuery
|
|
18
|
+
} = require("../utils/validator");
|
|
19
|
+
|
|
20
|
+
/*
|
|
21
|
+
* searchCommand() ā main function
|
|
22
|
+
*
|
|
23
|
+
* @param {string} query ā what user is searching for
|
|
24
|
+
* comes from: tracker search "git status"
|
|
25
|
+
*/
|
|
26
|
+
function searchCommand(query) {
|
|
27
|
+
|
|
28
|
+
/*
|
|
29
|
+
* Check initialization first
|
|
30
|
+
*/
|
|
31
|
+
if (!isInitialized()) {
|
|
32
|
+
showInitError();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
* Validate query
|
|
38
|
+
*/
|
|
39
|
+
if (!isValidQuery(query)) {
|
|
40
|
+
console.log("\nā Please provide a search query");
|
|
41
|
+
console.log("š” Usage: tracker search \"git\"\n");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const searchTerm = query.trim().toLowerCase();
|
|
47
|
+
const data = readCommands();
|
|
48
|
+
const results = [];
|
|
49
|
+
|
|
50
|
+
for (const [category, commands] of Object.entries(data)) {
|
|
51
|
+
for (const item of commands) {
|
|
52
|
+
if (item.command.toLowerCase().includes(searchTerm)) {
|
|
53
|
+
results.push({
|
|
54
|
+
command: item.command,
|
|
55
|
+
category: category,
|
|
56
|
+
time: item.time
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (results.length === 0) {
|
|
63
|
+
console.log(`\nš No commands found for: "${query}"\n`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log(`\nš Search results for: "${query}"`);
|
|
68
|
+
console.log("ā".repeat(50));
|
|
69
|
+
|
|
70
|
+
results.forEach((item, index) => {
|
|
71
|
+
|
|
72
|
+
const date = new Date(item.time).toLocaleDateString();
|
|
73
|
+
|
|
74
|
+
/*
|
|
75
|
+
* Highlight the search term in results
|
|
76
|
+
* Replace matched part with uppercase version
|
|
77
|
+
* So user can easily spot what matched
|
|
78
|
+
*/
|
|
79
|
+
const highlighted = item.command.replace(
|
|
80
|
+
new RegExp(searchTerm, "gi"),
|
|
81
|
+
(match) => `[${match.toUpperCase()}]`
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
console.log(`\n ${index + 1}. ${highlighted}`);
|
|
85
|
+
console.log(` š Category: ${item.category} š
${date}`);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
console.log("\n" + "ā".repeat(50));
|
|
89
|
+
console.log(`ā
Found ${results.length} matching command(s)\n`);
|
|
90
|
+
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.log("\nā Error searching commands");
|
|
93
|
+
console.log("š” Try running tracker init again\n");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = { searchCommand };
|