@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,286 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* storage.js
|
|
3
|
+
*
|
|
4
|
+
* This file handles everything related to FILE OPERATIONS
|
|
5
|
+
* It saves commands, reads commands, and manages the .tracker folder
|
|
6
|
+
*
|
|
7
|
+
* Two files in Node.js help us work with files and folders:
|
|
8
|
+
* fs → File System → read, write, check if file exists
|
|
9
|
+
* path → helps build file paths that work on ALL operating systems
|
|
10
|
+
* Windows: C:\Users\project\.tracker\commands.json
|
|
11
|
+
* Mac/Linux: /home/user/project/.tracker/commands.json
|
|
12
|
+
* path module handles this difference automatically
|
|
13
|
+
*/
|
|
14
|
+
const fs = require("fs");
|
|
15
|
+
const path = require("path");
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
* Import our categorize function from categorizer.js
|
|
19
|
+
* We need it here to know WHICH category to save the command in
|
|
20
|
+
*/
|
|
21
|
+
const { categorize } = require("./categorizer");
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
* process.cwd() → Current Working Directory
|
|
25
|
+
* This gives us the path of the folder where user ran the command
|
|
26
|
+
*
|
|
27
|
+
* Example:
|
|
28
|
+
* User is in → C:\Adithya Naik\GitHub\my-linux-repo
|
|
29
|
+
* process.cwd() → "C:\Adithya Naik\GitHub\my-linux-repo"
|
|
30
|
+
*
|
|
31
|
+
* This is VERY important — we want to save commands in THEIR repo
|
|
32
|
+
* not in our package folder
|
|
33
|
+
*/
|
|
34
|
+
const TRACKER_DIR = path.join(process.cwd(), ".tracker");
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
* This is where all commands will be saved
|
|
38
|
+
* .tracker/commands.json inside the user's repo
|
|
39
|
+
*/
|
|
40
|
+
const COMMANDS_FILE = path.join(TRACKER_DIR, "commands.json");
|
|
41
|
+
|
|
42
|
+
/*
|
|
43
|
+
* This is our default empty structure
|
|
44
|
+
* When we create commands.json for the first time
|
|
45
|
+
* it starts with all empty arrays
|
|
46
|
+
*
|
|
47
|
+
* We use a function so we always get a FRESH object
|
|
48
|
+
* not the same object reused (important in JavaScript)
|
|
49
|
+
*/
|
|
50
|
+
function getDefaultStructure() {
|
|
51
|
+
return {
|
|
52
|
+
git: [],
|
|
53
|
+
npm: [],
|
|
54
|
+
docker: [],
|
|
55
|
+
linux: [],
|
|
56
|
+
node: [],
|
|
57
|
+
angular: [],
|
|
58
|
+
python: [],
|
|
59
|
+
others: [],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/*
|
|
64
|
+
* initStorage() — sets up the .tracker folder and commands.json
|
|
65
|
+
*
|
|
66
|
+
* This runs when user types: tracker init
|
|
67
|
+
* It creates the .tracker folder and commands.json if they don't exist
|
|
68
|
+
*/
|
|
69
|
+
function initStorage() {
|
|
70
|
+
|
|
71
|
+
/*
|
|
72
|
+
* fs.existsSync() → checks if a folder/file EXISTS
|
|
73
|
+
* Returns true if exists, false if not
|
|
74
|
+
*
|
|
75
|
+
* If .tracker folder doesn't exist → create it
|
|
76
|
+
*/
|
|
77
|
+
if (!fs.existsSync(TRACKER_DIR)) {
|
|
78
|
+
|
|
79
|
+
/*
|
|
80
|
+
* fs.mkdirSync() → creates a folder
|
|
81
|
+
* { recursive: true } → creates parent folders too if needed
|
|
82
|
+
* Like "mkdir -p" in linux
|
|
83
|
+
*/
|
|
84
|
+
fs.mkdirSync(TRACKER_DIR, { recursive: true });
|
|
85
|
+
console.log("✅ Created .tracker folder");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/*
|
|
89
|
+
* If commands.json doesn't exist → create it with default structure
|
|
90
|
+
*/
|
|
91
|
+
if (!fs.existsSync(COMMANDS_FILE)) {
|
|
92
|
+
|
|
93
|
+
/*
|
|
94
|
+
* fs.writeFileSync() → creates and writes to a file
|
|
95
|
+
* JSON.stringify() → converts JavaScript object to JSON string
|
|
96
|
+
* null, 2 → makes the JSON nicely formatted with 2 spaces indent
|
|
97
|
+
*/
|
|
98
|
+
fs.writeFileSync(
|
|
99
|
+
COMMANDS_FILE,
|
|
100
|
+
JSON.stringify(getDefaultStructure(), null, 2)
|
|
101
|
+
);
|
|
102
|
+
console.log("✅ Created .tracker/commands.json");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/*
|
|
107
|
+
* readCommands() — reads all saved commands from commands.json
|
|
108
|
+
*
|
|
109
|
+
* @returns {object} — the entire commands object with all categories
|
|
110
|
+
*/
|
|
111
|
+
function readCommands() {
|
|
112
|
+
|
|
113
|
+
/*
|
|
114
|
+
* If file doesn't exist yet — return empty structure
|
|
115
|
+
* This prevents crashes if someone runs tracker list before tracker init
|
|
116
|
+
*/
|
|
117
|
+
if (!fs.existsSync(COMMANDS_FILE)) {
|
|
118
|
+
return getDefaultStructure();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/*
|
|
122
|
+
* fs.readFileSync() → reads the file content as text
|
|
123
|
+
* JSON.parse() → converts JSON text back to JavaScript object
|
|
124
|
+
*/
|
|
125
|
+
const fileContent = fs.readFileSync(COMMANDS_FILE, "utf-8");
|
|
126
|
+
return JSON.parse(fileContent);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/*
|
|
130
|
+
* saveCommand() — saves a single command to commands.json
|
|
131
|
+
*
|
|
132
|
+
* @param {string} command — the terminal command to save
|
|
133
|
+
* @returns {object} — { saved: true/false, category: "git" etc, reason: "why not saved" }
|
|
134
|
+
*/
|
|
135
|
+
function saveCommand(command) {
|
|
136
|
+
|
|
137
|
+
/*
|
|
138
|
+
* Safety check — don't save empty commands
|
|
139
|
+
*/
|
|
140
|
+
if (!command || !command.trim()) {
|
|
141
|
+
return { saved: false, reason: "empty command" };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/*
|
|
145
|
+
* Clean the command — remove extra spaces
|
|
146
|
+
*/
|
|
147
|
+
const cleanCommand = command.trim();
|
|
148
|
+
|
|
149
|
+
/*
|
|
150
|
+
* Use our categorizer to find which category this command belongs to
|
|
151
|
+
*/
|
|
152
|
+
const category = categorize(cleanCommand);
|
|
153
|
+
|
|
154
|
+
/*
|
|
155
|
+
* Read existing commands from file
|
|
156
|
+
*/
|
|
157
|
+
const data = readCommands();
|
|
158
|
+
|
|
159
|
+
/*
|
|
160
|
+
* DUPLICATE CHECK
|
|
161
|
+
*
|
|
162
|
+
* .some() → loops through array and returns true if ANY item matches
|
|
163
|
+
*
|
|
164
|
+
* We check if this exact command already exists in this category
|
|
165
|
+
* If it does — don't save it again
|
|
166
|
+
*
|
|
167
|
+
* item.command → because each saved command is an object like:
|
|
168
|
+
* { command: "git status", time: "2026-05-06T..." }
|
|
169
|
+
*/
|
|
170
|
+
const isDuplicate = data[category].some(
|
|
171
|
+
(item) => item.command === cleanCommand
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
if (isDuplicate) {
|
|
175
|
+
return { saved: false, reason: "duplicate", category };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/*
|
|
179
|
+
* Create the command object with timestamp
|
|
180
|
+
* new Date().toISOString() → "2026-05-06T10:30:00.000Z"
|
|
181
|
+
* This gives us a full date+time in standard format
|
|
182
|
+
*/
|
|
183
|
+
const commandObject = {
|
|
184
|
+
command: cleanCommand,
|
|
185
|
+
time: new Date().toISOString(),
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/*
|
|
189
|
+
* Push the new command into the correct category array
|
|
190
|
+
*/
|
|
191
|
+
data[category].push(commandObject);
|
|
192
|
+
|
|
193
|
+
/*
|
|
194
|
+
* Write the updated data back to commands.json
|
|
195
|
+
*/
|
|
196
|
+
fs.writeFileSync(COMMANDS_FILE, JSON.stringify(data, null, 2));
|
|
197
|
+
|
|
198
|
+
return { saved: true, category };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/*
|
|
202
|
+
* toggleFavorite() — marks/unmarks a command as favorite
|
|
203
|
+
*
|
|
204
|
+
* @param {string} command — the command to favorite
|
|
205
|
+
* @returns {object} — { success, action: "added"/"removed", command }
|
|
206
|
+
*/
|
|
207
|
+
function toggleFavorite(command) {
|
|
208
|
+
|
|
209
|
+
if (!command || !command.trim()) {
|
|
210
|
+
return { success: false, reason: "empty command" };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const cleanCommand = command.trim();
|
|
214
|
+
const data = readCommands();
|
|
215
|
+
|
|
216
|
+
/*
|
|
217
|
+
* Search for command across all categories
|
|
218
|
+
*/
|
|
219
|
+
for (const [category, commands] of Object.entries(data)) {
|
|
220
|
+
const index = commands.findIndex(
|
|
221
|
+
(item) => item.command === cleanCommand
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
if (index !== -1) {
|
|
225
|
+
/*
|
|
226
|
+
* Toggle favorite flag
|
|
227
|
+
* If favorite exists and is true → set false
|
|
228
|
+
* If favorite doesn't exist or false → set true
|
|
229
|
+
*/
|
|
230
|
+
const currentFav = data[category][index].favorite || false;
|
|
231
|
+
data[category][index].favorite = !currentFav;
|
|
232
|
+
|
|
233
|
+
fs.writeFileSync(COMMANDS_FILE, JSON.stringify(data, null, 2));
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
success: true,
|
|
237
|
+
action: currentFav ? "removed" : "added",
|
|
238
|
+
command: cleanCommand,
|
|
239
|
+
category
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return { success: false, reason: "command not found" };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/*
|
|
248
|
+
* getFavorites() — returns all favorited commands
|
|
249
|
+
*
|
|
250
|
+
* @returns {array} — array of { command, category, time }
|
|
251
|
+
*/
|
|
252
|
+
function getFavorites() {
|
|
253
|
+
|
|
254
|
+
const data = readCommands();
|
|
255
|
+
const favorites = [];
|
|
256
|
+
|
|
257
|
+
for (const [category, commands] of Object.entries(data)) {
|
|
258
|
+
for (const item of commands) {
|
|
259
|
+
if (item.favorite === true) {
|
|
260
|
+
favorites.push({
|
|
261
|
+
command: item.command,
|
|
262
|
+
category,
|
|
263
|
+
time: item.time
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return favorites;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
/*
|
|
275
|
+
* module.exports → expose these functions to other files
|
|
276
|
+
* Only export what other files need to use
|
|
277
|
+
*/
|
|
278
|
+
module.exports = {
|
|
279
|
+
initStorage,
|
|
280
|
+
readCommands,
|
|
281
|
+
saveCommand,
|
|
282
|
+
toggleFavorite,
|
|
283
|
+
getFavorites,
|
|
284
|
+
TRACKER_DIR,
|
|
285
|
+
COMMANDS_FILE,
|
|
286
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* validator.js
|
|
3
|
+
*
|
|
4
|
+
* Central validation utility for all commands
|
|
5
|
+
* Instead of repeating checks in every command file
|
|
6
|
+
* we put all validation logic here in one place
|
|
7
|
+
*
|
|
8
|
+
* This is called the DRY principle:
|
|
9
|
+
* DRY = Don't Repeat Yourself
|
|
10
|
+
* Write once, use everywhere ✅
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require("fs");
|
|
14
|
+
const path = require("path");
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
* Path to .tracker folder
|
|
18
|
+
* process.cwd() → user's current project folder
|
|
19
|
+
*/
|
|
20
|
+
const TRACKER_DIR = path.join(process.cwd(), ".tracker");
|
|
21
|
+
const COMMANDS_FILE = path.join(TRACKER_DIR, "commands.json");
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
* isInitialized() — checks if tracker init was run
|
|
25
|
+
*
|
|
26
|
+
* Before any command works — .tracker/commands.json must exist
|
|
27
|
+
* If user skips tracker init and runs tracker list
|
|
28
|
+
* we catch it here and guide them
|
|
29
|
+
*
|
|
30
|
+
* @returns {boolean} — true if initialized, false if not
|
|
31
|
+
*/
|
|
32
|
+
function isInitialized() {
|
|
33
|
+
return fs.existsSync(TRACKER_DIR) && fs.existsSync(COMMANDS_FILE);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
* showInitError() — shows helpful error when not initialized
|
|
38
|
+
*
|
|
39
|
+
* Instead of a confusing crash — user sees a clear message
|
|
40
|
+
* telling them exactly what to do next
|
|
41
|
+
*/
|
|
42
|
+
function showInitError() {
|
|
43
|
+
console.log("\n❌ cmd-tracker is not initialized in this project!");
|
|
44
|
+
console.log("💡 Run this first: tracker init\n");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/*
|
|
48
|
+
* isValidCategory() — checks if category name is valid
|
|
49
|
+
*
|
|
50
|
+
* @param {string} category — category name to validate
|
|
51
|
+
* @returns {boolean} — true if valid, false if not
|
|
52
|
+
*/
|
|
53
|
+
function isValidCategory(category) {
|
|
54
|
+
const validCategories = [
|
|
55
|
+
"git", "npm", "docker", "linux",
|
|
56
|
+
"node", "angular", "python", "others"
|
|
57
|
+
];
|
|
58
|
+
return validCategories.includes(category.toLowerCase());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/*
|
|
62
|
+
* showCategoryError() — shows helpful error for invalid category
|
|
63
|
+
*
|
|
64
|
+
* @param {string} category — the invalid category user typed
|
|
65
|
+
*/
|
|
66
|
+
function showCategoryError(category) {
|
|
67
|
+
console.log(`\n❌ Unknown category: "${category}"`);
|
|
68
|
+
console.log("📋 Valid categories: git, npm, docker, linux, node, angular, python, others\n");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/*
|
|
72
|
+
* isValidQuery() — checks if search query is valid
|
|
73
|
+
*
|
|
74
|
+
* @param {string} query — search term to validate
|
|
75
|
+
* @returns {boolean} — true if valid, false if not
|
|
76
|
+
*/
|
|
77
|
+
function isValidQuery(query) {
|
|
78
|
+
/*
|
|
79
|
+
* Query must exist and have at least 1 character after trimming
|
|
80
|
+
*/
|
|
81
|
+
return query && query.trim().length > 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/*
|
|
85
|
+
* Export all validators
|
|
86
|
+
* Every command file will import what it needs from here
|
|
87
|
+
*/
|
|
88
|
+
module.exports = {
|
|
89
|
+
isInitialized,
|
|
90
|
+
showInitError,
|
|
91
|
+
isValidCategory,
|
|
92
|
+
showCategoryError,
|
|
93
|
+
isValidQuery
|
|
94
|
+
};
|