zammy 1.2.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 +349 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4223 -0
- package/package.json +48 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,4223 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import * as readline from "readline";
|
|
5
|
+
|
|
6
|
+
// src/ui/banner.ts
|
|
7
|
+
import figlet from "figlet";
|
|
8
|
+
|
|
9
|
+
// src/ui/colors.ts
|
|
10
|
+
import chalk from "chalk";
|
|
11
|
+
var palette = {
|
|
12
|
+
rose: "#FF6B6B",
|
|
13
|
+
coral: "#FF8E72",
|
|
14
|
+
peach: "#FFEAA7",
|
|
15
|
+
mint: "#96CEB4",
|
|
16
|
+
teal: "#4ECDC4",
|
|
17
|
+
sky: "#45B7D1",
|
|
18
|
+
lavender: "#DDA0DD",
|
|
19
|
+
purple: "#9B59B6",
|
|
20
|
+
gold: "#FFD700",
|
|
21
|
+
silver: "#C0C0C0"
|
|
22
|
+
};
|
|
23
|
+
var theme = {
|
|
24
|
+
primary: chalk.hex(palette.teal),
|
|
25
|
+
secondary: chalk.hex(palette.lavender),
|
|
26
|
+
success: chalk.hex("#2ECC71"),
|
|
27
|
+
warning: chalk.hex("#F39C12"),
|
|
28
|
+
error: chalk.hex("#E74C3C"),
|
|
29
|
+
dim: chalk.hex("#6C7A89"),
|
|
30
|
+
highlight: chalk.bold.white,
|
|
31
|
+
command: chalk.bold.hex(palette.teal),
|
|
32
|
+
prompt: chalk.bold.hex(palette.lavender),
|
|
33
|
+
accent: chalk.hex(palette.rose),
|
|
34
|
+
info: chalk.hex(palette.sky),
|
|
35
|
+
muted: chalk.dim.hex("#95A5A6"),
|
|
36
|
+
gold: chalk.hex(palette.gold),
|
|
37
|
+
rose: chalk.hex(palette.rose),
|
|
38
|
+
mint: chalk.hex(palette.mint),
|
|
39
|
+
peach: chalk.hex(palette.peach),
|
|
40
|
+
// Gradient text effects
|
|
41
|
+
gradient: (text) => {
|
|
42
|
+
const colors = [palette.rose, palette.coral, palette.peach, palette.mint, palette.teal];
|
|
43
|
+
return text.split("").map(
|
|
44
|
+
(char, i) => chalk.hex(colors[i % colors.length])(char)
|
|
45
|
+
).join("");
|
|
46
|
+
},
|
|
47
|
+
rainbow: (text) => {
|
|
48
|
+
const colors = ["#FF6B6B", "#FF8E72", "#FFEAA7", "#96CEB4", "#4ECDC4", "#45B7D1", "#DDA0DD"];
|
|
49
|
+
return text.split("").map(
|
|
50
|
+
(char, i) => chalk.hex(colors[i % colors.length])(char)
|
|
51
|
+
).join("");
|
|
52
|
+
},
|
|
53
|
+
ocean: (text) => {
|
|
54
|
+
const colors = ["#0077B6", "#00B4D8", "#48CAE4", "#90E0EF", "#CAF0F8"];
|
|
55
|
+
return text.split("").map(
|
|
56
|
+
(char, i) => chalk.hex(colors[i % colors.length])(char)
|
|
57
|
+
).join("");
|
|
58
|
+
},
|
|
59
|
+
sunset: (text) => {
|
|
60
|
+
const colors = ["#FF6B6B", "#FF8E53", "#FFA07A", "#FFB347", "#FFD700"];
|
|
61
|
+
return text.split("").map(
|
|
62
|
+
(char, i) => chalk.hex(colors[i % colors.length])(char)
|
|
63
|
+
).join("");
|
|
64
|
+
},
|
|
65
|
+
// Bold variants
|
|
66
|
+
b: {
|
|
67
|
+
primary: chalk.bold.hex(palette.teal),
|
|
68
|
+
secondary: chalk.bold.hex(palette.lavender),
|
|
69
|
+
success: chalk.bold.hex("#2ECC71"),
|
|
70
|
+
warning: chalk.bold.hex("#F39C12"),
|
|
71
|
+
error: chalk.bold.hex("#E74C3C")
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
var symbols = {
|
|
75
|
+
// Basic UI
|
|
76
|
+
arrow: "\u276F",
|
|
77
|
+
// ❯
|
|
78
|
+
check: "\u2714",
|
|
79
|
+
// ✔
|
|
80
|
+
cross: "\u2718",
|
|
81
|
+
// ✘
|
|
82
|
+
info: "\u2139",
|
|
83
|
+
// ℹ
|
|
84
|
+
warning: "\u26A0",
|
|
85
|
+
// ⚠
|
|
86
|
+
bullet: "\u2022",
|
|
87
|
+
// •
|
|
88
|
+
// Decorative
|
|
89
|
+
star: "\u2605",
|
|
90
|
+
// ★
|
|
91
|
+
heart: "\u2665",
|
|
92
|
+
// ♥
|
|
93
|
+
diamond: "\u2666",
|
|
94
|
+
// ♦
|
|
95
|
+
sparkle: "\u2728",
|
|
96
|
+
// ✨
|
|
97
|
+
lightning: "\u26A1",
|
|
98
|
+
// ⚡
|
|
99
|
+
// Emoji icons
|
|
100
|
+
fire: "\u{1F525}",
|
|
101
|
+
// 🔥
|
|
102
|
+
rocket: "\u{1F680}",
|
|
103
|
+
// 🚀
|
|
104
|
+
dice: "\u{1F3B2}",
|
|
105
|
+
// 🎲
|
|
106
|
+
coin: "\u{1FA99}",
|
|
107
|
+
// 🪙
|
|
108
|
+
lock: "\u{1F512}",
|
|
109
|
+
// 🔒
|
|
110
|
+
clock: "\u{1F552}",
|
|
111
|
+
// 🕒
|
|
112
|
+
chart: "\u{1F4CA}",
|
|
113
|
+
// 📊
|
|
114
|
+
note: "\u{1F4DD}",
|
|
115
|
+
// 📝
|
|
116
|
+
scroll: "\u{1F4DC}",
|
|
117
|
+
// 📜
|
|
118
|
+
clipboard: "\u{1F4CB}",
|
|
119
|
+
// 📋
|
|
120
|
+
palette: "\u{1F3A8}",
|
|
121
|
+
// 🎨
|
|
122
|
+
tomato: "\u{1F345}",
|
|
123
|
+
// 🍅
|
|
124
|
+
coffee: "\u2615",
|
|
125
|
+
// ☕
|
|
126
|
+
bell: "\u{1F514}",
|
|
127
|
+
// 🔔
|
|
128
|
+
gear: "\u2699",
|
|
129
|
+
// ⚙
|
|
130
|
+
folder: "\u{1F4C1}",
|
|
131
|
+
// 📁
|
|
132
|
+
terminal: "\u{1F4BB}",
|
|
133
|
+
// 💻
|
|
134
|
+
key: "\u{1F511}",
|
|
135
|
+
// 🔑
|
|
136
|
+
link: "\u{1F517}",
|
|
137
|
+
// 🔗
|
|
138
|
+
hourglass: "\u23F3"
|
|
139
|
+
// ⏳
|
|
140
|
+
};
|
|
141
|
+
var boxChars = {
|
|
142
|
+
rounded: { tl: "\u256D", tr: "\u256E", bl: "\u2570", br: "\u256F", h: "\u2500", v: "\u2502" },
|
|
143
|
+
sharp: { tl: "\u250C", tr: "\u2510", bl: "\u2514", br: "\u2518", h: "\u2500", v: "\u2502" },
|
|
144
|
+
double: { tl: "\u2554", tr: "\u2557", bl: "\u255A", br: "\u255D", h: "\u2550", v: "\u2551" },
|
|
145
|
+
heavy: { tl: "\u250F", tr: "\u2513", bl: "\u2517", br: "\u251B", h: "\u2501", v: "\u2503" }
|
|
146
|
+
};
|
|
147
|
+
var box = {
|
|
148
|
+
topLeft: "\u256D",
|
|
149
|
+
// ╭
|
|
150
|
+
topRight: "\u256E",
|
|
151
|
+
// ╮
|
|
152
|
+
bottomLeft: "\u2570",
|
|
153
|
+
// ╰
|
|
154
|
+
bottomRight: "\u256F",
|
|
155
|
+
// ╯
|
|
156
|
+
horizontal: "\u2500",
|
|
157
|
+
// ─
|
|
158
|
+
vertical: "\u2502",
|
|
159
|
+
// │
|
|
160
|
+
draw: (content, width = 50, style = "rounded") => {
|
|
161
|
+
const chars = boxChars[style];
|
|
162
|
+
const lines = [];
|
|
163
|
+
const innerWidth = width - 2;
|
|
164
|
+
lines.push(theme.dim(`${chars.tl}${chars.h.repeat(innerWidth)}${chars.tr}`));
|
|
165
|
+
content.forEach((line) => {
|
|
166
|
+
const stripped = line.replace(/\x1B\[[0-9;]*m/g, "");
|
|
167
|
+
const padding = innerWidth - stripped.length;
|
|
168
|
+
lines.push(theme.dim(chars.v) + line + " ".repeat(Math.max(0, padding)) + theme.dim(chars.v));
|
|
169
|
+
});
|
|
170
|
+
lines.push(theme.dim(`${chars.bl}${chars.h.repeat(innerWidth)}${chars.br}`));
|
|
171
|
+
return lines.join("\n");
|
|
172
|
+
},
|
|
173
|
+
// Simple title box
|
|
174
|
+
title: (title, width = 50) => {
|
|
175
|
+
const chars = boxChars.rounded;
|
|
176
|
+
const innerWidth = width - 2;
|
|
177
|
+
const titleLen = title.replace(/\x1B\[[0-9;]*m/g, "").length;
|
|
178
|
+
const leftPad = Math.floor((innerWidth - titleLen - 2) / 2);
|
|
179
|
+
const rightPad = innerWidth - titleLen - 2 - leftPad;
|
|
180
|
+
return theme.dim(`${chars.tl}${chars.h.repeat(leftPad)} `) + title + theme.dim(` ${chars.h.repeat(rightPad)}${chars.tr}`);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
var bubble = {
|
|
184
|
+
say: (text, width = 50) => {
|
|
185
|
+
const lines = [];
|
|
186
|
+
const innerWidth = width - 4;
|
|
187
|
+
const words = text.split(" ");
|
|
188
|
+
let currentLine = "";
|
|
189
|
+
const wrappedLines = [];
|
|
190
|
+
for (const word of words) {
|
|
191
|
+
if ((currentLine + " " + word).trim().length <= innerWidth) {
|
|
192
|
+
currentLine = (currentLine + " " + word).trim();
|
|
193
|
+
} else {
|
|
194
|
+
if (currentLine) wrappedLines.push(currentLine);
|
|
195
|
+
currentLine = word;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (currentLine) wrappedLines.push(currentLine);
|
|
199
|
+
lines.push(theme.dim(" \u256D" + "\u2500".repeat(innerWidth + 2) + "\u256E"));
|
|
200
|
+
for (const line of wrappedLines) {
|
|
201
|
+
const padding = innerWidth - line.length;
|
|
202
|
+
lines.push(theme.dim(" \u2502 ") + line + " ".repeat(padding) + theme.dim(" \u2502"));
|
|
203
|
+
}
|
|
204
|
+
lines.push(theme.dim(" \u2570" + "\u2500".repeat(innerWidth + 2) + "\u256F"));
|
|
205
|
+
lines.push(theme.dim(" \u2572"));
|
|
206
|
+
lines.push(theme.dim(" \u2572"));
|
|
207
|
+
return lines.join("\n");
|
|
208
|
+
},
|
|
209
|
+
think: (text, width = 50) => {
|
|
210
|
+
const lines = [];
|
|
211
|
+
const innerWidth = width - 4;
|
|
212
|
+
lines.push(theme.dim(" \u256D" + "\u2500".repeat(innerWidth + 2) + "\u256E"));
|
|
213
|
+
const padding = innerWidth - text.length;
|
|
214
|
+
lines.push(theme.dim(" \u2502 ") + text + " ".repeat(Math.max(0, padding)) + theme.dim(" \u2502"));
|
|
215
|
+
lines.push(theme.dim(" \u2570" + "\u2500".repeat(innerWidth + 2) + "\u256F"));
|
|
216
|
+
lines.push(theme.dim(" \u25CB"));
|
|
217
|
+
lines.push(theme.dim(" \u25CB"));
|
|
218
|
+
return lines.join("\n");
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
var categoryIcons = {
|
|
222
|
+
"Utilities": "\u{1F527}",
|
|
223
|
+
"Fun": "\u{1F3AE}",
|
|
224
|
+
"Creative": "\u{1F3A8}",
|
|
225
|
+
"Dev": "\u{1F4BB}",
|
|
226
|
+
"Info": "\u{1F4E1}"
|
|
227
|
+
};
|
|
228
|
+
var spinnerFrames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
229
|
+
|
|
230
|
+
// src/ui/banner.ts
|
|
231
|
+
async function displayBanner() {
|
|
232
|
+
return new Promise((resolve3) => {
|
|
233
|
+
figlet("ZAMMY", {
|
|
234
|
+
font: "ANSI Shadow",
|
|
235
|
+
horizontalLayout: "default"
|
|
236
|
+
}, (err, data) => {
|
|
237
|
+
console.log("");
|
|
238
|
+
if (err || !data) {
|
|
239
|
+
console.log(theme.gradient(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557"));
|
|
240
|
+
console.log(theme.gradient(" \u255A\u2550\u2550\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D"));
|
|
241
|
+
console.log(theme.gradient(" \u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2554\u255D "));
|
|
242
|
+
console.log(theme.gradient(" \u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u255A\u2588\u2588\u2554\u255D "));
|
|
243
|
+
console.log(theme.gradient(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 "));
|
|
244
|
+
console.log(theme.gradient(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D "));
|
|
245
|
+
} else {
|
|
246
|
+
data.split("\n").forEach((line) => {
|
|
247
|
+
console.log(theme.gradient(line));
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
console.log("");
|
|
251
|
+
console.log(` ${symbols.sparkle} ${theme.secondary("Your slice-of-life terminal companion")} ${symbols.sparkle}`);
|
|
252
|
+
console.log("");
|
|
253
|
+
console.log(theme.dim(` ${symbols.arrow} Type ${theme.primary("/")} to browse commands (use ${theme.primary("\u2191\u2193")} to navigate, ${theme.primary("Tab")} to select)`));
|
|
254
|
+
console.log(theme.dim(` ${symbols.arrow} Type ${theme.primary("/help")} for full command list`));
|
|
255
|
+
console.log(theme.dim(` ${symbols.arrow} Shell commands start with ${theme.primary("!")} (e.g., ${theme.primary("!ls")}, ${theme.primary("!cd")})`));
|
|
256
|
+
console.log("");
|
|
257
|
+
resolve3();
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// src/ui/prompt.ts
|
|
263
|
+
function getPrompt() {
|
|
264
|
+
return `${theme.b.primary("zammy")}${theme.dim(symbols.arrow)} `;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/commands/registry.ts
|
|
268
|
+
var commands = /* @__PURE__ */ new Map();
|
|
269
|
+
function registerCommand(command) {
|
|
270
|
+
commands.set(command.name, command);
|
|
271
|
+
}
|
|
272
|
+
function getCommand(name) {
|
|
273
|
+
return commands.get(name);
|
|
274
|
+
}
|
|
275
|
+
function getAllCommands() {
|
|
276
|
+
return Array.from(commands.values());
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// src/commands/utilities/help.ts
|
|
280
|
+
var categories = {
|
|
281
|
+
"Utilities": ["help", "exit", "calc", "password", "stats", "time", "countdown", "timer", "todo", "history"],
|
|
282
|
+
"Fun": ["joke", "quote", "fortune", "dice", "flip", "pomodoro"],
|
|
283
|
+
"Creative": ["asciiart", "figlet", "lorem", "color"],
|
|
284
|
+
"Dev": ["hash", "uuid", "encode"],
|
|
285
|
+
"Info": ["weather"]
|
|
286
|
+
};
|
|
287
|
+
registerCommand({
|
|
288
|
+
name: "help",
|
|
289
|
+
description: "Show all available commands",
|
|
290
|
+
usage: "/help [command]",
|
|
291
|
+
async execute(args2) {
|
|
292
|
+
const commands2 = getAllCommands();
|
|
293
|
+
if (args2.length > 0) {
|
|
294
|
+
const cmdName = args2[0].replace(/^\//, "");
|
|
295
|
+
const cmd = commands2.find((c) => c.name === cmdName);
|
|
296
|
+
if (cmd) {
|
|
297
|
+
console.log("");
|
|
298
|
+
console.log(box.draw([
|
|
299
|
+
"",
|
|
300
|
+
` ${symbols.info} ${theme.b.primary("/" + cmd.name)}`,
|
|
301
|
+
"",
|
|
302
|
+
` ${theme.dim("Description")}`,
|
|
303
|
+
` ${cmd.description}`,
|
|
304
|
+
"",
|
|
305
|
+
` ${theme.dim("Usage")}`,
|
|
306
|
+
` ${theme.primary(cmd.usage)}`,
|
|
307
|
+
""
|
|
308
|
+
], 55, "rounded"));
|
|
309
|
+
console.log("");
|
|
310
|
+
} else {
|
|
311
|
+
console.log("");
|
|
312
|
+
console.log(` ${symbols.cross} ${theme.error("Unknown command:")} ${theme.dim(cmdName)}`);
|
|
313
|
+
console.log(theme.dim(` Try /help to see all commands`));
|
|
314
|
+
console.log("");
|
|
315
|
+
}
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
console.log("");
|
|
319
|
+
console.log(` ${theme.rainbow("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501")}`);
|
|
320
|
+
console.log(` ${symbols.rocket} ${theme.gradient("ZAMMY COMMANDS")} ${symbols.rocket}`);
|
|
321
|
+
console.log(` ${theme.rainbow("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501")}`);
|
|
322
|
+
console.log("");
|
|
323
|
+
const maxNameLength = Math.max(...commands2.map((c) => c.name.length));
|
|
324
|
+
for (const [category, cmdNames] of Object.entries(categories)) {
|
|
325
|
+
const categoryCommands = commands2.filter((c) => cmdNames.includes(c.name));
|
|
326
|
+
if (categoryCommands.length === 0) continue;
|
|
327
|
+
const icon = categoryIcons[category] || symbols.folder;
|
|
328
|
+
console.log(` ${icon} ${theme.b.secondary(category)}`);
|
|
329
|
+
console.log(theme.dim(" " + "\u2500".repeat(46)));
|
|
330
|
+
for (const cmd of categoryCommands) {
|
|
331
|
+
const paddedName = cmd.name.padEnd(maxNameLength + 2);
|
|
332
|
+
console.log(
|
|
333
|
+
` ${theme.command("/" + paddedName)} ${theme.dim("\u2502")} ${theme.dim(cmd.description)}`
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
console.log("");
|
|
337
|
+
}
|
|
338
|
+
const categorizedNames = Object.values(categories).flat();
|
|
339
|
+
const uncategorized = commands2.filter((c) => !categorizedNames.includes(c.name));
|
|
340
|
+
if (uncategorized.length > 0) {
|
|
341
|
+
console.log(` ${symbols.folder} ${theme.b.secondary("Other")}`);
|
|
342
|
+
console.log(theme.dim(" " + "\u2500".repeat(46)));
|
|
343
|
+
for (const cmd of uncategorized) {
|
|
344
|
+
const paddedName = cmd.name.padEnd(maxNameLength + 2);
|
|
345
|
+
console.log(
|
|
346
|
+
` ${theme.command("/" + paddedName)} ${theme.dim("\u2502")} ${theme.dim(cmd.description)}`
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
console.log("");
|
|
350
|
+
}
|
|
351
|
+
console.log(theme.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
352
|
+
console.log(` ${symbols.terminal} ${theme.b.secondary("Enhanced Shell Commands")} ${theme.dim("(prefix with !")}`);
|
|
353
|
+
console.log(theme.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
354
|
+
console.log("");
|
|
355
|
+
console.log(` ${theme.dim("File Operations")}`);
|
|
356
|
+
console.log(` ${theme.primary("!ls")}${theme.dim(" [-la]")} ${theme.dim("\u2502")} ${theme.dim("Colorized file listing with icons")}`);
|
|
357
|
+
console.log(` ${theme.primary("!tree")} ${theme.dim("\u2502")} ${theme.dim("Directory tree visualization")}`);
|
|
358
|
+
console.log(` ${theme.primary("!cat")} ${theme.dim("<file>")} ${theme.dim("\u2502")} ${theme.dim("View file with syntax highlighting")}`);
|
|
359
|
+
console.log(` ${theme.primary("!find")} ${theme.dim("<pattern>")} ${theme.dim("\u2502")} ${theme.dim("Find files matching pattern")}`);
|
|
360
|
+
console.log(` ${theme.primary("!grep")} ${theme.dim("<pattern>")} ${theme.dim("\u2502")} ${theme.dim("Search in file contents")}`);
|
|
361
|
+
console.log(` ${theme.primary("!du")} ${theme.dim("\u2502")} ${theme.dim("Disk usage with visual bars")}`);
|
|
362
|
+
console.log(` ${theme.primary("!diff")} ${theme.dim("<f1> <f2>")} ${theme.dim("\u2502")} ${theme.dim("Compare two files")}`);
|
|
363
|
+
console.log(` ${theme.primary("!wc")} ${theme.dim("<file>")} ${theme.dim("\u2502")} ${theme.dim("Word/line/char count")}`);
|
|
364
|
+
console.log(` ${theme.primary("!head")} ${theme.dim("<file>")} ${theme.dim("\u2502")} ${theme.dim("Show first N lines")}`);
|
|
365
|
+
console.log("");
|
|
366
|
+
console.log(` ${theme.dim("Navigation")}`);
|
|
367
|
+
console.log(` ${theme.primary("!cd")} ${theme.dim("<path>")} ${theme.dim("\u2502")} ${theme.dim("Change directory")}`);
|
|
368
|
+
console.log(` ${theme.primary("!pwd")} ${theme.dim("\u2502")} ${theme.dim("Show current directory")}`);
|
|
369
|
+
console.log(` ${theme.primary("!bookmark")} ${theme.dim("[cmd]")} ${theme.dim("\u2502")} ${theme.dim("Save/jump to directory bookmarks")}`);
|
|
370
|
+
console.log("");
|
|
371
|
+
console.log(` ${theme.dim("Developer Tools")}`);
|
|
372
|
+
console.log(` ${theme.primary("!git")} ${theme.dim("[cmd]")} ${theme.dim("\u2502")} ${theme.dim("Enhanced git status/log/branch")}`);
|
|
373
|
+
console.log(` ${theme.primary("!json")} ${theme.dim("<file>")} ${theme.dim("\u2502")} ${theme.dim("Pretty print JSON with colors")}`);
|
|
374
|
+
console.log(` ${theme.primary("!http")} ${theme.dim("<url>")} ${theme.dim("\u2502")} ${theme.dim("Quick HTTP requests")}`);
|
|
375
|
+
console.log(` ${theme.primary("!epoch")} ${theme.dim("[time]")} ${theme.dim("\u2502")} ${theme.dim("Timestamp converter")}`);
|
|
376
|
+
console.log(` ${theme.primary("!serve")} ${theme.dim("[port]")} ${theme.dim("\u2502")} ${theme.dim("Quick HTTP server")}`);
|
|
377
|
+
console.log("");
|
|
378
|
+
console.log(` ${theme.dim("System")}`);
|
|
379
|
+
console.log(` ${theme.primary("!ip")} ${theme.dim("\u2502")} ${theme.dim("Show IP addresses")}`);
|
|
380
|
+
console.log(` ${theme.primary("!ps")} ${theme.dim("\u2502")} ${theme.dim("Process list")}`);
|
|
381
|
+
console.log(` ${theme.primary("!env")} ${theme.dim("[filter]")} ${theme.dim("\u2502")} ${theme.dim("Environment variables")}`);
|
|
382
|
+
console.log(` ${theme.primary("!clipboard")} ${theme.dim("[cmd]")}${theme.dim("\u2502")} ${theme.dim("Clipboard operations")}`);
|
|
383
|
+
console.log(` ${theme.primary("!notify")} ${theme.dim("<msg>")} ${theme.dim("\u2502")} ${theme.dim("Desktop notification")}`);
|
|
384
|
+
console.log("");
|
|
385
|
+
console.log(` ${theme.dim("Utilities")}`);
|
|
386
|
+
console.log(` ${theme.primary("!alias")} ${theme.dim("[cmd]")} ${theme.dim("\u2502")} ${theme.dim("Command aliases")}`);
|
|
387
|
+
console.log(` ${theme.primary("!watch")} ${theme.dim("<file>")} ${theme.dim("\u2502")} ${theme.dim("Watch file for changes")}`);
|
|
388
|
+
console.log(` ${theme.primary("!clear")} ${theme.dim("\u2502")} ${theme.dim("Clear screen")}`);
|
|
389
|
+
console.log("");
|
|
390
|
+
console.log(theme.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
391
|
+
console.log(` ${symbols.info} ${theme.dim("Details:")} ${theme.primary("/help")} ${theme.dim("<command>")}`);
|
|
392
|
+
console.log("");
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// src/commands/utilities/exit.ts
|
|
397
|
+
registerCommand({
|
|
398
|
+
name: "exit",
|
|
399
|
+
description: "Exit Zammy",
|
|
400
|
+
usage: "/exit",
|
|
401
|
+
async execute(_args) {
|
|
402
|
+
console.log("");
|
|
403
|
+
console.log(theme.secondary("Goodbye! See you next time."));
|
|
404
|
+
console.log("");
|
|
405
|
+
process.exit(0);
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// src/handlers/utilities/calc.ts
|
|
410
|
+
function evaluate(expression) {
|
|
411
|
+
const expr = expression.replace(/\s+/g, "");
|
|
412
|
+
if (!/^[\d+\-*/().%^]+$/.test(expr)) {
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
try {
|
|
416
|
+
const sanitized = expr.replace(/\^/g, "**");
|
|
417
|
+
const result = new Function(`return (${sanitized})`)();
|
|
418
|
+
if (typeof result !== "number" || !isFinite(result)) {
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
return result;
|
|
422
|
+
} catch {
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
function formatNumber(num) {
|
|
427
|
+
if (Math.abs(num) < 1e-4 || Math.abs(num) > 1e10) {
|
|
428
|
+
return num.toExponential(4);
|
|
429
|
+
}
|
|
430
|
+
const rounded = Math.round(num * 1e10) / 1e10;
|
|
431
|
+
const parts = rounded.toString().split(".");
|
|
432
|
+
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
433
|
+
return parts.join(".");
|
|
434
|
+
}
|
|
435
|
+
function calculate(expression) {
|
|
436
|
+
const result = evaluate(expression);
|
|
437
|
+
if (result === null) {
|
|
438
|
+
return {
|
|
439
|
+
expression,
|
|
440
|
+
error: "Invalid expression. Only numbers and operators (+, -, *, /, ^, %) are allowed."
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
return {
|
|
444
|
+
expression,
|
|
445
|
+
result,
|
|
446
|
+
formatted: formatNumber(result)
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
function isCalcError(result) {
|
|
450
|
+
return "error" in result;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// src/commands/utilities/calc.ts
|
|
454
|
+
registerCommand({
|
|
455
|
+
name: "calc",
|
|
456
|
+
description: "Calculate a math expression",
|
|
457
|
+
usage: "/calc <expression>",
|
|
458
|
+
async execute(args2) {
|
|
459
|
+
const expression = args2.join(" ");
|
|
460
|
+
if (!expression) {
|
|
461
|
+
console.log("");
|
|
462
|
+
console.log(` ${symbols.warning} ${theme.warning("Usage:")} ${theme.command("/calc")} ${theme.dim("<expression>")}`);
|
|
463
|
+
console.log("");
|
|
464
|
+
console.log(` ${theme.dim("Examples:")}`);
|
|
465
|
+
console.log(` ${theme.primary("/calc 2 + 2")} ${theme.dim("\u2192")} Basic math`);
|
|
466
|
+
console.log(` ${theme.primary("/calc (10 * 5) / 2")} ${theme.dim("\u2192")} Parentheses`);
|
|
467
|
+
console.log(` ${theme.primary("/calc 2^8")} ${theme.dim("\u2192")} Exponents`);
|
|
468
|
+
console.log(` ${theme.primary("/calc 100 % 7")} ${theme.dim("\u2192")} Modulo`);
|
|
469
|
+
console.log("");
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const result = calculate(expression);
|
|
473
|
+
console.log("");
|
|
474
|
+
if (isCalcError(result)) {
|
|
475
|
+
console.log(` ${symbols.cross} ${theme.error("Invalid expression:")} ${theme.dim(expression)}`);
|
|
476
|
+
console.log(` ${theme.dim("Only numbers and operators (+, -, *, /, ^, %) are allowed")}`);
|
|
477
|
+
} else {
|
|
478
|
+
console.log(` ${theme.dim("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510")}`);
|
|
479
|
+
console.log(` ${theme.dim("\u2502")} ${theme.secondary(expression)}${" ".repeat(Math.max(0, 37 - expression.length))}${theme.dim("\u2502")}`);
|
|
480
|
+
console.log(` ${theme.dim("\u2502")} ${theme.dim("=")} ${theme.b.success(result.formatted)}${" ".repeat(Math.max(0, 35 - result.formatted.length))}${theme.dim("\u2502")}`);
|
|
481
|
+
console.log(` ${theme.dim("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518")}`);
|
|
482
|
+
}
|
|
483
|
+
console.log("");
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
// src/handlers/utilities/password.ts
|
|
488
|
+
import { randomBytes } from "crypto";
|
|
489
|
+
var CHARSETS = {
|
|
490
|
+
uppercase: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
|
491
|
+
lowercase: "abcdefghijklmnopqrstuvwxyz",
|
|
492
|
+
numbers: "0123456789",
|
|
493
|
+
symbols: "!@#$%^&*()_+-=[]{}|;:,.<>?"
|
|
494
|
+
};
|
|
495
|
+
function generatePassword(length = 16, options = {}) {
|
|
496
|
+
const opts = {
|
|
497
|
+
uppercase: options.uppercase ?? true,
|
|
498
|
+
lowercase: options.lowercase ?? true,
|
|
499
|
+
numbers: options.numbers ?? true,
|
|
500
|
+
symbols: options.symbols ?? true
|
|
501
|
+
};
|
|
502
|
+
const safeLength = Math.min(Math.max(length, 4), 128);
|
|
503
|
+
let chars = "";
|
|
504
|
+
if (opts.uppercase) chars += CHARSETS.uppercase;
|
|
505
|
+
if (opts.lowercase) chars += CHARSETS.lowercase;
|
|
506
|
+
if (opts.numbers) chars += CHARSETS.numbers;
|
|
507
|
+
if (opts.symbols) chars += CHARSETS.symbols;
|
|
508
|
+
if (!chars) chars = CHARSETS.lowercase + CHARSETS.numbers;
|
|
509
|
+
const bytes = randomBytes(safeLength);
|
|
510
|
+
let password = "";
|
|
511
|
+
for (let i = 0; i < safeLength; i++) {
|
|
512
|
+
password += chars[bytes[i] % chars.length];
|
|
513
|
+
}
|
|
514
|
+
return {
|
|
515
|
+
password,
|
|
516
|
+
length: safeLength,
|
|
517
|
+
strength: calculateStrength(password),
|
|
518
|
+
options: opts
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
function calculateStrength(password) {
|
|
522
|
+
let score = 0;
|
|
523
|
+
if (password.length >= 8) score += 1;
|
|
524
|
+
if (password.length >= 12) score += 1;
|
|
525
|
+
if (password.length >= 16) score += 1;
|
|
526
|
+
if (/[a-z]/.test(password)) score += 1;
|
|
527
|
+
if (/[A-Z]/.test(password)) score += 1;
|
|
528
|
+
if (/[0-9]/.test(password)) score += 1;
|
|
529
|
+
if (/[^a-zA-Z0-9]/.test(password)) score += 1;
|
|
530
|
+
if (score <= 2) return { score, label: "Weak" };
|
|
531
|
+
if (score <= 4) return { score, label: "Fair" };
|
|
532
|
+
if (score <= 5) return { score, label: "Good" };
|
|
533
|
+
return { score, label: "Strong" };
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// src/commands/utilities/password.ts
|
|
537
|
+
function getStrengthColor(strength) {
|
|
538
|
+
switch (strength.label) {
|
|
539
|
+
case "Weak":
|
|
540
|
+
return theme.error;
|
|
541
|
+
case "Fair":
|
|
542
|
+
return theme.warning;
|
|
543
|
+
case "Good":
|
|
544
|
+
return theme.primary;
|
|
545
|
+
case "Strong":
|
|
546
|
+
return theme.success;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
registerCommand({
|
|
550
|
+
name: "password",
|
|
551
|
+
description: "Generate a secure password",
|
|
552
|
+
usage: "/password [length] [--no-symbols] [--no-numbers] [--no-upper] [--no-lower]",
|
|
553
|
+
async execute(args2) {
|
|
554
|
+
let length = 16;
|
|
555
|
+
const options = {
|
|
556
|
+
uppercase: true,
|
|
557
|
+
lowercase: true,
|
|
558
|
+
numbers: true,
|
|
559
|
+
symbols: true
|
|
560
|
+
};
|
|
561
|
+
for (const arg of args2) {
|
|
562
|
+
if (/^\d+$/.test(arg)) {
|
|
563
|
+
length = parseInt(arg);
|
|
564
|
+
} else if (arg === "--no-symbols") {
|
|
565
|
+
options.symbols = false;
|
|
566
|
+
} else if (arg === "--no-numbers") {
|
|
567
|
+
options.numbers = false;
|
|
568
|
+
} else if (arg === "--no-upper") {
|
|
569
|
+
options.uppercase = false;
|
|
570
|
+
} else if (arg === "--no-lower") {
|
|
571
|
+
options.lowercase = false;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
const result = generatePassword(length, options);
|
|
575
|
+
const strengthColor = getStrengthColor(result.strength);
|
|
576
|
+
const barLength = 20;
|
|
577
|
+
const filledLength = Math.round(result.strength.score / 7 * barLength);
|
|
578
|
+
const bar = strengthColor("\u2588".repeat(filledLength)) + theme.dim("\u2591".repeat(barLength - filledLength));
|
|
579
|
+
console.log("");
|
|
580
|
+
console.log(box.draw([
|
|
581
|
+
"",
|
|
582
|
+
` ${symbols.lock} ${theme.secondary("Generated Password")}`,
|
|
583
|
+
"",
|
|
584
|
+
` ${theme.highlight(result.password)}`,
|
|
585
|
+
"",
|
|
586
|
+
` ${theme.dim("Strength:")} ${bar} ${strengthColor(result.strength.label)}`,
|
|
587
|
+
` ${theme.dim("Length:")} ${result.length} characters`,
|
|
588
|
+
""
|
|
589
|
+
], 60));
|
|
590
|
+
console.log("");
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
// src/handlers/utilities/stats.ts
|
|
595
|
+
import { cpus, totalmem, freemem, uptime, platform, arch, hostname, userInfo } from "os";
|
|
596
|
+
function formatBytes(bytes) {
|
|
597
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
598
|
+
let unitIndex = 0;
|
|
599
|
+
let size = bytes;
|
|
600
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
601
|
+
size /= 1024;
|
|
602
|
+
unitIndex++;
|
|
603
|
+
}
|
|
604
|
+
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
|
605
|
+
}
|
|
606
|
+
function formatUptime(seconds) {
|
|
607
|
+
const days = Math.floor(seconds / 86400);
|
|
608
|
+
const hours = Math.floor(seconds % 86400 / 3600);
|
|
609
|
+
const minutes = Math.floor(seconds % 3600 / 60);
|
|
610
|
+
const parts = [];
|
|
611
|
+
if (days > 0) parts.push(`${days}d`);
|
|
612
|
+
if (hours > 0) parts.push(`${hours}h`);
|
|
613
|
+
if (minutes > 0) parts.push(`${minutes}m`);
|
|
614
|
+
return parts.join(" ") || "< 1m";
|
|
615
|
+
}
|
|
616
|
+
function getSystemStats() {
|
|
617
|
+
const cpu = cpus()[0];
|
|
618
|
+
const totalMem = totalmem();
|
|
619
|
+
const freeMem = freemem();
|
|
620
|
+
const usedMem = totalMem - freeMem;
|
|
621
|
+
const memPercent = usedMem / totalMem * 100;
|
|
622
|
+
const uptimeSecs = uptime();
|
|
623
|
+
const user = userInfo();
|
|
624
|
+
return {
|
|
625
|
+
user: {
|
|
626
|
+
username: user.username,
|
|
627
|
+
hostname: hostname()
|
|
628
|
+
},
|
|
629
|
+
platform: {
|
|
630
|
+
os: platform(),
|
|
631
|
+
arch: arch()
|
|
632
|
+
},
|
|
633
|
+
cpu: {
|
|
634
|
+
model: cpu.model,
|
|
635
|
+
cores: cpus().length
|
|
636
|
+
},
|
|
637
|
+
memory: {
|
|
638
|
+
total: totalMem,
|
|
639
|
+
used: usedMem,
|
|
640
|
+
free: freeMem,
|
|
641
|
+
percent: memPercent,
|
|
642
|
+
totalFormatted: formatBytes(totalMem),
|
|
643
|
+
usedFormatted: formatBytes(usedMem)
|
|
644
|
+
},
|
|
645
|
+
uptime: {
|
|
646
|
+
seconds: uptimeSecs,
|
|
647
|
+
formatted: formatUptime(uptimeSecs)
|
|
648
|
+
},
|
|
649
|
+
node: {
|
|
650
|
+
version: process.version
|
|
651
|
+
},
|
|
652
|
+
cwd: process.cwd()
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// src/commands/utilities/stats.ts
|
|
657
|
+
function createBar(percent, width = 20) {
|
|
658
|
+
const filled = Math.round(percent / 100 * width);
|
|
659
|
+
let color = theme.success;
|
|
660
|
+
if (percent > 70) color = theme.warning;
|
|
661
|
+
if (percent > 90) color = theme.error;
|
|
662
|
+
return color("\u2588".repeat(filled)) + theme.dim("\u2591".repeat(width - filled));
|
|
663
|
+
}
|
|
664
|
+
registerCommand({
|
|
665
|
+
name: "stats",
|
|
666
|
+
description: "Show system statistics",
|
|
667
|
+
usage: "/stats",
|
|
668
|
+
async execute(_args) {
|
|
669
|
+
const stats = getSystemStats();
|
|
670
|
+
console.log("");
|
|
671
|
+
console.log(box.draw([
|
|
672
|
+
"",
|
|
673
|
+
` ${symbols.chart} ${theme.secondary("System Statistics")}`,
|
|
674
|
+
"",
|
|
675
|
+
` ${theme.dim("User:")} ${theme.primary(stats.user.username)}@${theme.primary(stats.user.hostname)}`,
|
|
676
|
+
` ${theme.dim("Platform:")} ${theme.primary(stats.platform.os)} ${stats.platform.arch}`,
|
|
677
|
+
` ${theme.dim("CPU:")} ${theme.primary(stats.cpu.model)}`,
|
|
678
|
+
` ${theme.dim("Cores:")} ${theme.primary(stats.cpu.cores.toString())}`,
|
|
679
|
+
"",
|
|
680
|
+
` ${theme.dim("Memory:")} ${createBar(stats.memory.percent)} ${stats.memory.percent.toFixed(1)}%`,
|
|
681
|
+
` ${theme.dim(`${stats.memory.usedFormatted} / ${stats.memory.totalFormatted}`)}`,
|
|
682
|
+
"",
|
|
683
|
+
` ${theme.dim("Uptime:")} ${theme.primary(stats.uptime.formatted)}`,
|
|
684
|
+
` ${theme.dim("Node.js:")} ${theme.primary(stats.node.version)}`,
|
|
685
|
+
` ${theme.dim("CWD:")} ${theme.dim(stats.cwd)}`,
|
|
686
|
+
""
|
|
687
|
+
], 65));
|
|
688
|
+
console.log("");
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
// src/commands/utilities/time.ts
|
|
693
|
+
var clockDigits = {
|
|
694
|
+
"0": ["\u256D\u2500\u2500\u2500\u256E", "\u2502 \u2502", "\u2502 \u2502", "\u2502 \u2502", "\u2570\u2500\u2500\u2500\u256F"],
|
|
695
|
+
"1": [" \u2577 ", " \u2502 ", " \u2502 ", " \u2502 ", " \u2575 "],
|
|
696
|
+
"2": ["\u256D\u2500\u2500\u2500\u256E", " \u2502", "\u256D\u2500\u2500\u2500\u256F", "\u2502 ", "\u2570\u2500\u2500\u2500\u256F"],
|
|
697
|
+
"3": ["\u256D\u2500\u2500\u2500\u256E", " \u2502", " \u2500\u2500\u2500\u2524", " \u2502", "\u2570\u2500\u2500\u2500\u256F"],
|
|
698
|
+
"4": ["\u2577 \u2577", "\u2502 \u2502", "\u2570\u2500\u2500\u2500\u2524", " \u2502", " \u2575"],
|
|
699
|
+
"5": ["\u256D\u2500\u2500\u2500\u256E", "\u2502 ", "\u2570\u2500\u2500\u2500\u256E", " \u2502", "\u2570\u2500\u2500\u2500\u256F"],
|
|
700
|
+
"6": ["\u256D\u2500\u2500\u2500\u256E", "\u2502 ", "\u251C\u2500\u2500\u2500\u256E", "\u2502 \u2502", "\u2570\u2500\u2500\u2500\u256F"],
|
|
701
|
+
"7": ["\u256D\u2500\u2500\u2500\u256E", " \u2502", " \u2502", " \u2502", " \u2575"],
|
|
702
|
+
"8": ["\u256D\u2500\u2500\u2500\u256E", "\u2502 \u2502", "\u251C\u2500\u2500\u2500\u2524", "\u2502 \u2502", "\u2570\u2500\u2500\u2500\u256F"],
|
|
703
|
+
"9": ["\u256D\u2500\u2500\u2500\u256E", "\u2502 \u2502", "\u2570\u2500\u2500\u2500\u2524", " \u2502", "\u2570\u2500\u2500\u2500\u256F"],
|
|
704
|
+
":": [" ", " \u25CF ", " ", " \u25CF ", " "]
|
|
705
|
+
};
|
|
706
|
+
function renderTime(time) {
|
|
707
|
+
const chars = time.split("");
|
|
708
|
+
const lines = ["", "", "", "", ""];
|
|
709
|
+
chars.forEach((char) => {
|
|
710
|
+
const digit = clockDigits[char] || clockDigits["0"];
|
|
711
|
+
for (let i = 0; i < 5; i++) {
|
|
712
|
+
lines[i] += digit[i] + " ";
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
return lines;
|
|
716
|
+
}
|
|
717
|
+
registerCommand({
|
|
718
|
+
name: "time",
|
|
719
|
+
description: "Show current time",
|
|
720
|
+
usage: "/time [--digital]",
|
|
721
|
+
async execute(args2) {
|
|
722
|
+
const now = /* @__PURE__ */ new Date();
|
|
723
|
+
const hours = now.getHours().toString().padStart(2, "0");
|
|
724
|
+
const minutes = now.getMinutes().toString().padStart(2, "0");
|
|
725
|
+
const seconds = now.getSeconds().toString().padStart(2, "0");
|
|
726
|
+
const timeStr = `${hours}:${minutes}`;
|
|
727
|
+
const dateStr = now.toLocaleDateString("en-US", {
|
|
728
|
+
weekday: "long",
|
|
729
|
+
year: "numeric",
|
|
730
|
+
month: "long",
|
|
731
|
+
day: "numeric"
|
|
732
|
+
});
|
|
733
|
+
console.log("");
|
|
734
|
+
if (args2.includes("--digital")) {
|
|
735
|
+
console.log(` ${symbols.clock} ${theme.primary(`${hours}:${minutes}:${seconds}`)}`);
|
|
736
|
+
} else {
|
|
737
|
+
const clockLines = renderTime(timeStr);
|
|
738
|
+
clockLines.forEach((line) => console.log(" " + theme.primary(line)));
|
|
739
|
+
}
|
|
740
|
+
console.log("");
|
|
741
|
+
console.log(` ${theme.dim(dateStr)}`);
|
|
742
|
+
console.log(` ${theme.dim("Timezone:")} ${Intl.DateTimeFormat().resolvedOptions().timeZone}`);
|
|
743
|
+
console.log("");
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
// src/commands/utilities/countdown.ts
|
|
748
|
+
function parseTime(input) {
|
|
749
|
+
const match = input.match(/^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s?)?$/i);
|
|
750
|
+
if (!match) {
|
|
751
|
+
const num = parseInt(input);
|
|
752
|
+
return isNaN(num) ? null : num;
|
|
753
|
+
}
|
|
754
|
+
const hours = parseInt(match[1] || "0");
|
|
755
|
+
const minutes = parseInt(match[2] || "0");
|
|
756
|
+
const seconds = parseInt(match[3] || "0");
|
|
757
|
+
return hours * 3600 + minutes * 60 + seconds;
|
|
758
|
+
}
|
|
759
|
+
function formatTime(seconds) {
|
|
760
|
+
const h = Math.floor(seconds / 3600);
|
|
761
|
+
const m = Math.floor(seconds % 3600 / 60);
|
|
762
|
+
const s = seconds % 60;
|
|
763
|
+
if (h > 0) {
|
|
764
|
+
return `${h.toString().padStart(2, "0")}:${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
|
|
765
|
+
}
|
|
766
|
+
return `${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
|
|
767
|
+
}
|
|
768
|
+
registerCommand({
|
|
769
|
+
name: "countdown",
|
|
770
|
+
description: "Start a countdown timer",
|
|
771
|
+
usage: "/countdown <time> (e.g., 30s, 5m, 1h30m)",
|
|
772
|
+
async execute(args2) {
|
|
773
|
+
if (args2.length === 0) {
|
|
774
|
+
console.log(theme.error("Usage: /countdown <time>"));
|
|
775
|
+
console.log(theme.dim("Examples: /countdown 30, /countdown 5m, /countdown 1h30m"));
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
const totalSeconds = parseTime(args2.join(""));
|
|
779
|
+
if (totalSeconds === null || totalSeconds <= 0) {
|
|
780
|
+
console.log(theme.error("Invalid time format"));
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
console.log("");
|
|
784
|
+
console.log(` ${symbols.clock} ${theme.secondary("Countdown started!")} ${theme.dim("(Press Ctrl+C to cancel)")}`);
|
|
785
|
+
console.log("");
|
|
786
|
+
let remaining = totalSeconds;
|
|
787
|
+
let spinnerIndex = 0;
|
|
788
|
+
return new Promise((resolve3) => {
|
|
789
|
+
const interval = setInterval(() => {
|
|
790
|
+
process.stdout.write("\r\x1B[K");
|
|
791
|
+
if (remaining <= 0) {
|
|
792
|
+
clearInterval(interval);
|
|
793
|
+
console.log(` ${symbols.sparkle} ${theme.success("TIME'S UP!")} ${symbols.sparkle}`);
|
|
794
|
+
console.log("");
|
|
795
|
+
process.stdout.write("\x07");
|
|
796
|
+
resolve3();
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
const spinner = spinnerFrames[spinnerIndex % spinnerFrames.length];
|
|
800
|
+
const timeDisplay = formatTime(remaining);
|
|
801
|
+
let color = theme.primary;
|
|
802
|
+
if (remaining <= 10) color = theme.error;
|
|
803
|
+
else if (remaining <= 30) color = theme.warning;
|
|
804
|
+
process.stdout.write(` ${theme.secondary(spinner)} ${color(timeDisplay)} remaining...`);
|
|
805
|
+
remaining--;
|
|
806
|
+
spinnerIndex++;
|
|
807
|
+
}, 1e3);
|
|
808
|
+
process.stdout.write(` ${theme.secondary(spinnerFrames[0])} ${theme.primary(formatTime(remaining))} remaining...`);
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
// src/commands/utilities/timer.ts
|
|
814
|
+
var startTime = 0;
|
|
815
|
+
var pausedAt = 0;
|
|
816
|
+
var isRunning = false;
|
|
817
|
+
function formatTime2(ms) {
|
|
818
|
+
const totalSeconds = Math.floor(ms / 1e3);
|
|
819
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
820
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
821
|
+
const seconds = totalSeconds % 60;
|
|
822
|
+
const millis = Math.floor(ms % 1e3 / 10);
|
|
823
|
+
if (hours > 0) {
|
|
824
|
+
return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}.${millis.toString().padStart(2, "0")}`;
|
|
825
|
+
}
|
|
826
|
+
return `${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}.${millis.toString().padStart(2, "0")}`;
|
|
827
|
+
}
|
|
828
|
+
registerCommand({
|
|
829
|
+
name: "timer",
|
|
830
|
+
description: "Stopwatch timer",
|
|
831
|
+
usage: "/timer [start|stop|pause|reset|lap]",
|
|
832
|
+
async execute(args2) {
|
|
833
|
+
const action = args2[0]?.toLowerCase() || "status";
|
|
834
|
+
if (action === "start") {
|
|
835
|
+
if (isRunning) {
|
|
836
|
+
console.log(theme.dim(" Timer is already running"));
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
if (pausedAt > 0) {
|
|
840
|
+
startTime = Date.now() - pausedAt;
|
|
841
|
+
pausedAt = 0;
|
|
842
|
+
} else {
|
|
843
|
+
startTime = Date.now();
|
|
844
|
+
}
|
|
845
|
+
isRunning = true;
|
|
846
|
+
console.log("");
|
|
847
|
+
console.log(` ${symbols.sparkle} ${theme.gradient("TIMER STARTED")} ${symbols.sparkle}`);
|
|
848
|
+
console.log("");
|
|
849
|
+
console.log(theme.dim(" Use /timer lap to see current time"));
|
|
850
|
+
console.log(theme.dim(" Use /timer pause to pause"));
|
|
851
|
+
console.log(theme.dim(" Use /timer stop to stop"));
|
|
852
|
+
console.log("");
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
if (action === "stop") {
|
|
856
|
+
if (!isRunning && pausedAt === 0) {
|
|
857
|
+
console.log(theme.dim(" Timer is not running"));
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
const elapsed = isRunning ? Date.now() - startTime : pausedAt;
|
|
861
|
+
isRunning = false;
|
|
862
|
+
pausedAt = 0;
|
|
863
|
+
console.log("");
|
|
864
|
+
console.log(` ${symbols.clock} ${theme.gradient("TIMER STOPPED")} ${symbols.clock}`);
|
|
865
|
+
console.log("");
|
|
866
|
+
console.log(` ${theme.dim("Final time:")} ${theme.primary(formatTime2(elapsed))}`);
|
|
867
|
+
console.log("");
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
if (action === "pause") {
|
|
871
|
+
if (!isRunning) {
|
|
872
|
+
console.log(theme.dim(" Timer is not running"));
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
pausedAt = Date.now() - startTime;
|
|
876
|
+
isRunning = false;
|
|
877
|
+
console.log("");
|
|
878
|
+
console.log(` ${symbols.clock} ${theme.secondary("TIMER PAUSED")}`);
|
|
879
|
+
console.log(` ${theme.dim("Time:")} ${theme.primary(formatTime2(pausedAt))}`);
|
|
880
|
+
console.log(theme.dim(" Use /timer start to resume"));
|
|
881
|
+
console.log("");
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
if (action === "reset") {
|
|
885
|
+
isRunning = false;
|
|
886
|
+
startTime = 0;
|
|
887
|
+
pausedAt = 0;
|
|
888
|
+
console.log("");
|
|
889
|
+
console.log(` ${symbols.check} ${theme.success("Timer reset")}`);
|
|
890
|
+
console.log("");
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
if (action === "lap" || action === "status") {
|
|
894
|
+
if (!isRunning && pausedAt === 0) {
|
|
895
|
+
console.log("");
|
|
896
|
+
console.log(` ${symbols.clock} ${theme.gradient("STOPWATCH")} ${symbols.clock}`);
|
|
897
|
+
console.log("");
|
|
898
|
+
console.log(theme.dim(" Timer is not running"));
|
|
899
|
+
console.log(theme.dim(" Use /timer start to begin"));
|
|
900
|
+
console.log("");
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
const elapsed = isRunning ? Date.now() - startTime : pausedAt;
|
|
904
|
+
console.log("");
|
|
905
|
+
console.log(` ${symbols.clock} ${theme.gradient("STOPWATCH")} ${symbols.clock}`);
|
|
906
|
+
console.log("");
|
|
907
|
+
console.log(` ${theme.primary(formatTime2(elapsed))} ${isRunning ? theme.success("\u25CF") : theme.secondary("\u275A\u275A")}`);
|
|
908
|
+
console.log("");
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
console.log("");
|
|
912
|
+
console.log(theme.error("Unknown action. Use: start, stop, pause, reset, lap"));
|
|
913
|
+
console.log("");
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
// src/commands/utilities/todo.ts
|
|
918
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
919
|
+
import { homedir } from "os";
|
|
920
|
+
import { join } from "path";
|
|
921
|
+
var TODO_FILE = join(homedir(), ".zammy-todos.json");
|
|
922
|
+
function loadTodos() {
|
|
923
|
+
try {
|
|
924
|
+
if (existsSync(TODO_FILE)) {
|
|
925
|
+
return JSON.parse(readFileSync(TODO_FILE, "utf8"));
|
|
926
|
+
}
|
|
927
|
+
} catch {
|
|
928
|
+
}
|
|
929
|
+
return [];
|
|
930
|
+
}
|
|
931
|
+
function saveTodos(todos) {
|
|
932
|
+
writeFileSync(TODO_FILE, JSON.stringify(todos, null, 2));
|
|
933
|
+
}
|
|
934
|
+
registerCommand({
|
|
935
|
+
name: "todo",
|
|
936
|
+
description: "Manage your todo list",
|
|
937
|
+
usage: "/todo [add|done|remove|clear] [text|id]",
|
|
938
|
+
async execute(args2) {
|
|
939
|
+
const todos = loadTodos();
|
|
940
|
+
const action = args2[0]?.toLowerCase();
|
|
941
|
+
if (!action || action === "list") {
|
|
942
|
+
console.log("");
|
|
943
|
+
console.log(` ${symbols.clipboard} ${theme.gradient("TODO LIST")} ${symbols.clipboard}`);
|
|
944
|
+
console.log("");
|
|
945
|
+
if (todos.length === 0) {
|
|
946
|
+
console.log(theme.dim(" No todos yet! Add one with /todo add <task>"));
|
|
947
|
+
} else {
|
|
948
|
+
const pending = todos.filter((t) => !t.done);
|
|
949
|
+
const completed = todos.filter((t) => t.done);
|
|
950
|
+
if (pending.length > 0) {
|
|
951
|
+
console.log(` ${theme.secondary("Pending:")}`);
|
|
952
|
+
pending.forEach((t) => {
|
|
953
|
+
console.log(` ${theme.dim(`[${t.id}]`)} ${symbols.bullet} ${t.text}`);
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
if (completed.length > 0) {
|
|
957
|
+
if (pending.length > 0) console.log("");
|
|
958
|
+
console.log(` ${theme.secondary("Completed:")}`);
|
|
959
|
+
completed.forEach((t) => {
|
|
960
|
+
console.log(` ${theme.dim(`[${t.id}]`)} ${symbols.check} ${theme.dim(t.text)}`);
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
console.log("");
|
|
964
|
+
console.log(theme.dim(` ${pending.length} pending, ${completed.length} completed`));
|
|
965
|
+
}
|
|
966
|
+
console.log("");
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
if (action === "add") {
|
|
970
|
+
const text2 = args2.slice(1).join(" ");
|
|
971
|
+
if (!text2) {
|
|
972
|
+
console.log(theme.error("Usage: /todo add <task>"));
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
const id2 = todos.length > 0 ? Math.max(...todos.map((t) => t.id)) + 1 : 1;
|
|
976
|
+
todos.push({ id: id2, text: text2, done: false, created: (/* @__PURE__ */ new Date()).toISOString() });
|
|
977
|
+
saveTodos(todos);
|
|
978
|
+
console.log("");
|
|
979
|
+
console.log(theme.success(` ${symbols.check} Added: ${text2}`));
|
|
980
|
+
console.log("");
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
if (action === "done") {
|
|
984
|
+
const id2 = parseInt(args2[1]);
|
|
985
|
+
if (isNaN(id2)) {
|
|
986
|
+
console.log(theme.error("Usage: /todo done <id>"));
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
const todo = todos.find((t) => t.id === id2);
|
|
990
|
+
if (!todo) {
|
|
991
|
+
console.log(theme.error(`Todo #${id2} not found`));
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
todo.done = true;
|
|
995
|
+
saveTodos(todos);
|
|
996
|
+
console.log("");
|
|
997
|
+
console.log(theme.success(` ${symbols.check} Completed: ${todo.text}`));
|
|
998
|
+
console.log("");
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
if (action === "remove" || action === "rm") {
|
|
1002
|
+
const id2 = parseInt(args2[1]);
|
|
1003
|
+
if (isNaN(id2)) {
|
|
1004
|
+
console.log(theme.error("Usage: /todo remove <id>"));
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
const idx = todos.findIndex((t) => t.id === id2);
|
|
1008
|
+
if (idx === -1) {
|
|
1009
|
+
console.log(theme.error(`Todo #${id2} not found`));
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
const removed = todos.splice(idx, 1)[0];
|
|
1013
|
+
saveTodos(todos);
|
|
1014
|
+
console.log("");
|
|
1015
|
+
console.log(theme.success(` ${symbols.cross} Removed: ${removed.text}`));
|
|
1016
|
+
console.log("");
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
if (action === "clear") {
|
|
1020
|
+
const cleared = args2[1] === "all" ? todos.length : todos.filter((t) => t.done).length;
|
|
1021
|
+
if (args2[1] === "all") {
|
|
1022
|
+
saveTodos([]);
|
|
1023
|
+
console.log("");
|
|
1024
|
+
console.log(theme.success(` ${symbols.check} Cleared all ${cleared} todos`));
|
|
1025
|
+
} else {
|
|
1026
|
+
const remaining = todos.filter((t) => !t.done);
|
|
1027
|
+
saveTodos(remaining);
|
|
1028
|
+
console.log("");
|
|
1029
|
+
console.log(theme.success(` ${symbols.check} Cleared ${cleared} completed todos`));
|
|
1030
|
+
}
|
|
1031
|
+
console.log("");
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
const text = args2.join(" ");
|
|
1035
|
+
const id = todos.length > 0 ? Math.max(...todos.map((t) => t.id)) + 1 : 1;
|
|
1036
|
+
todos.push({ id, text, done: false, created: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1037
|
+
saveTodos(todos);
|
|
1038
|
+
console.log("");
|
|
1039
|
+
console.log(theme.success(` ${symbols.check} Added: ${text}`));
|
|
1040
|
+
console.log("");
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
|
|
1044
|
+
// src/commands/utilities/history.ts
|
|
1045
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, appendFileSync } from "fs";
|
|
1046
|
+
import { homedir as homedir2 } from "os";
|
|
1047
|
+
import { join as join2 } from "path";
|
|
1048
|
+
var HISTORY_FILE = join2(homedir2(), ".zammy-history");
|
|
1049
|
+
registerCommand({
|
|
1050
|
+
name: "history",
|
|
1051
|
+
description: "Show command history",
|
|
1052
|
+
usage: "/history [count|clear|search <term>]",
|
|
1053
|
+
async execute(args2) {
|
|
1054
|
+
const action = args2[0]?.toLowerCase();
|
|
1055
|
+
if (action === "clear") {
|
|
1056
|
+
try {
|
|
1057
|
+
writeFileSync2(HISTORY_FILE, "");
|
|
1058
|
+
console.log("");
|
|
1059
|
+
console.log(theme.success(` ${symbols.check} History cleared`));
|
|
1060
|
+
console.log("");
|
|
1061
|
+
} catch {
|
|
1062
|
+
console.log(theme.error("Failed to clear history"));
|
|
1063
|
+
}
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
let history = [];
|
|
1067
|
+
try {
|
|
1068
|
+
if (existsSync2(HISTORY_FILE)) {
|
|
1069
|
+
const lines = readFileSync2(HISTORY_FILE, "utf8").split("\n").filter((l) => l);
|
|
1070
|
+
history = lines.map((line) => {
|
|
1071
|
+
const [time, ...rest] = line.split("|");
|
|
1072
|
+
return { time, cmd: rest.join("|") };
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
} catch {
|
|
1076
|
+
}
|
|
1077
|
+
if (history.length === 0) {
|
|
1078
|
+
console.log("");
|
|
1079
|
+
console.log(theme.dim(" No command history yet"));
|
|
1080
|
+
console.log("");
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
if (action === "search") {
|
|
1084
|
+
const term = args2.slice(1).join(" ").toLowerCase();
|
|
1085
|
+
if (!term) {
|
|
1086
|
+
console.log(theme.error("Usage: /history search <term>"));
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
history = history.filter((h) => h.cmd.toLowerCase().includes(term));
|
|
1090
|
+
if (history.length === 0) {
|
|
1091
|
+
console.log("");
|
|
1092
|
+
console.log(theme.dim(` No history matching "${term}"`));
|
|
1093
|
+
console.log("");
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
const count = parseInt(action) || 20;
|
|
1098
|
+
const toShow = history.slice(-count);
|
|
1099
|
+
console.log("");
|
|
1100
|
+
console.log(` ${symbols.scroll} ${theme.gradient("COMMAND HISTORY")} ${symbols.scroll}`);
|
|
1101
|
+
console.log("");
|
|
1102
|
+
toShow.forEach((h, idx) => {
|
|
1103
|
+
const date = new Date(h.time);
|
|
1104
|
+
const timeStr = date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
1105
|
+
const dateStr = date.toLocaleDateString([], { month: "short", day: "numeric" });
|
|
1106
|
+
console.log(` ${theme.dim(`${dateStr} ${timeStr}`)} ${theme.primary(h.cmd)}`);
|
|
1107
|
+
});
|
|
1108
|
+
console.log("");
|
|
1109
|
+
console.log(theme.dim(` Showing ${toShow.length} of ${history.length} commands`));
|
|
1110
|
+
console.log("");
|
|
1111
|
+
}
|
|
1112
|
+
});
|
|
1113
|
+
|
|
1114
|
+
// src/commands/fun/joke.ts
|
|
1115
|
+
var fallbackJokes = [
|
|
1116
|
+
{ setup: "Why do programmers prefer dark mode?", punchline: "Because light attracts bugs!" },
|
|
1117
|
+
{ setup: "Why do Java developers wear glasses?", punchline: "Because they can't C#!" },
|
|
1118
|
+
{ setup: "A SQL query walks into a bar, walks up to two tables and asks...", punchline: "Can I join you?" },
|
|
1119
|
+
{ setup: "Why did the developer go broke?", punchline: "Because he used up all his cache!" },
|
|
1120
|
+
{ setup: "How many programmers does it take to change a light bulb?", punchline: "None, that's a hardware problem!" }
|
|
1121
|
+
];
|
|
1122
|
+
registerCommand({
|
|
1123
|
+
name: "joke",
|
|
1124
|
+
description: "Get a random joke",
|
|
1125
|
+
usage: "/joke",
|
|
1126
|
+
async execute(_args) {
|
|
1127
|
+
console.log("");
|
|
1128
|
+
console.log(` ${symbols.dice} ${theme.sunset("Getting a joke...")}`);
|
|
1129
|
+
try {
|
|
1130
|
+
const response = await fetch("https://official-joke-api.appspot.com/random_joke", {
|
|
1131
|
+
signal: AbortSignal.timeout(3e3)
|
|
1132
|
+
});
|
|
1133
|
+
let joke;
|
|
1134
|
+
if (!response.ok) {
|
|
1135
|
+
joke = fallbackJokes[Math.floor(Math.random() * fallbackJokes.length)];
|
|
1136
|
+
} else {
|
|
1137
|
+
joke = await response.json();
|
|
1138
|
+
}
|
|
1139
|
+
process.stdout.write("\x1B[1A\x1B[2K");
|
|
1140
|
+
console.log("");
|
|
1141
|
+
console.log(bubble.say(joke.setup, 55));
|
|
1142
|
+
console.log(` ${symbols.sparkle}`);
|
|
1143
|
+
await new Promise((resolve3) => setTimeout(resolve3, 1500));
|
|
1144
|
+
console.log("");
|
|
1145
|
+
console.log(` ${theme.gold(" \u2726")} ${theme.b.success(joke.punchline)} ${theme.gold("\u2726")}`);
|
|
1146
|
+
console.log("");
|
|
1147
|
+
console.log(` ${theme.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}`);
|
|
1148
|
+
console.log(` ${symbols.dice} ${theme.dim("Run /joke again for another!")}`);
|
|
1149
|
+
console.log("");
|
|
1150
|
+
} catch (error) {
|
|
1151
|
+
process.stdout.write("\x1B[1A\x1B[2K");
|
|
1152
|
+
const joke = fallbackJokes[Math.floor(Math.random() * fallbackJokes.length)];
|
|
1153
|
+
console.log("");
|
|
1154
|
+
console.log(bubble.say(joke.setup, 55));
|
|
1155
|
+
console.log(` ${symbols.sparkle}`);
|
|
1156
|
+
await new Promise((resolve3) => setTimeout(resolve3, 1500));
|
|
1157
|
+
console.log("");
|
|
1158
|
+
console.log(` ${theme.gold(" \u2726")} ${theme.b.success(joke.punchline)} ${theme.gold("\u2726")}`);
|
|
1159
|
+
console.log("");
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
// src/commands/fun/quote.ts
|
|
1165
|
+
var quotes = [
|
|
1166
|
+
{ text: "The only way to do great work is to love what you do.", author: "Steve Jobs" },
|
|
1167
|
+
{ text: "Code is like humor. When you have to explain it, it's bad.", author: "Cory House" },
|
|
1168
|
+
{ text: "First, solve the problem. Then, write the code.", author: "John Johnson" },
|
|
1169
|
+
{ text: "Experience is the name everyone gives to their mistakes.", author: "Oscar Wilde" },
|
|
1170
|
+
{ text: "The best error message is the one that never shows up.", author: "Thomas Fuchs" },
|
|
1171
|
+
{ text: "Simplicity is the soul of efficiency.", author: "Austin Freeman" },
|
|
1172
|
+
{ text: "Make it work, make it right, make it fast.", author: "Kent Beck" },
|
|
1173
|
+
{ text: "Any fool can write code that a computer can understand.", author: "Martin Fowler" },
|
|
1174
|
+
{ text: "Programming is the art of telling another human what one wants the computer to do.", author: "Donald Knuth" },
|
|
1175
|
+
{ text: "The most dangerous phrase is: We've always done it this way.", author: "Grace Hopper" },
|
|
1176
|
+
{ text: "Talk is cheap. Show me the code.", author: "Linus Torvalds" },
|
|
1177
|
+
{ text: "Perfection is achieved not when there is nothing more to add, but when there is nothing left to take away.", author: "Antoine de Saint-Exupery" },
|
|
1178
|
+
{ text: "It's not a bug, it's a feature.", author: "Every Developer Ever" },
|
|
1179
|
+
{ text: "There are only two hard things in Computer Science: cache invalidation and naming things.", author: "Phil Karlton" },
|
|
1180
|
+
{ text: "The best time to plant a tree was 20 years ago. The second best time is now.", author: "Chinese Proverb" }
|
|
1181
|
+
];
|
|
1182
|
+
function wrapText(text, maxWidth) {
|
|
1183
|
+
const words = text.split(" ");
|
|
1184
|
+
const lines = [];
|
|
1185
|
+
let currentLine = "";
|
|
1186
|
+
for (const word of words) {
|
|
1187
|
+
if ((currentLine + " " + word).trim().length <= maxWidth) {
|
|
1188
|
+
currentLine = (currentLine + " " + word).trim();
|
|
1189
|
+
} else {
|
|
1190
|
+
if (currentLine) lines.push(currentLine);
|
|
1191
|
+
currentLine = word;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
if (currentLine) lines.push(currentLine);
|
|
1195
|
+
return lines;
|
|
1196
|
+
}
|
|
1197
|
+
registerCommand({
|
|
1198
|
+
name: "quote",
|
|
1199
|
+
description: "Get an inspirational quote",
|
|
1200
|
+
usage: "/quote",
|
|
1201
|
+
async execute(_args) {
|
|
1202
|
+
const quote = quotes[Math.floor(Math.random() * quotes.length)];
|
|
1203
|
+
const wrapped = wrapText(quote.text, 55);
|
|
1204
|
+
console.log("");
|
|
1205
|
+
console.log(` ${theme.dim("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E")}`);
|
|
1206
|
+
console.log(` ${theme.dim("\u2502")} ${theme.dim("\u2502")}`);
|
|
1207
|
+
console.log(` ${theme.dim("\u2502")} ${theme.ocean("\u275D")} ${theme.dim("\u2502")}`);
|
|
1208
|
+
for (const line of wrapped) {
|
|
1209
|
+
const padding = 58 - line.length;
|
|
1210
|
+
console.log(` ${theme.dim("\u2502")} ${theme.highlight(line)}${" ".repeat(Math.max(0, padding))}${theme.dim("\u2502")}`);
|
|
1211
|
+
}
|
|
1212
|
+
console.log(` ${theme.dim("\u2502")} ${theme.ocean("\u275E")} ${theme.dim("\u2502")}`);
|
|
1213
|
+
console.log(` ${theme.dim("\u2502")} ${theme.dim("\u2502")}`);
|
|
1214
|
+
console.log(` ${theme.dim("\u2502")} ${theme.dim("\u2014")} ${theme.secondary(quote.author)}${" ".repeat(Math.max(0, 32 - quote.author.length))}${theme.dim("\u2502")}`);
|
|
1215
|
+
console.log(` ${theme.dim("\u2502")} ${theme.dim("\u2502")}`);
|
|
1216
|
+
console.log(` ${theme.dim("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F")}`);
|
|
1217
|
+
console.log("");
|
|
1218
|
+
}
|
|
1219
|
+
});
|
|
1220
|
+
|
|
1221
|
+
// src/handlers/fun/fortune.ts
|
|
1222
|
+
var FORTUNES = [
|
|
1223
|
+
"A beautiful, smart, and loving person will be coming into your life.",
|
|
1224
|
+
"A dubious friend may be an enemy in camouflage.",
|
|
1225
|
+
"A faithful friend is a strong defense.",
|
|
1226
|
+
"A fresh start will put you on your way.",
|
|
1227
|
+
"A golden egg of opportunity falls into your lap this month.",
|
|
1228
|
+
"A good time to finish up old tasks.",
|
|
1229
|
+
"A hunch is creativity trying to tell you something.",
|
|
1230
|
+
"A lifetime of happiness lies ahead of you.",
|
|
1231
|
+
"A light heart carries you through all the hard times.",
|
|
1232
|
+
"A new perspective will come with the new year.",
|
|
1233
|
+
"Accept something that you cannot change, and you will feel better.",
|
|
1234
|
+
"Adventure can be real happiness.",
|
|
1235
|
+
"Believe in yourself and others will too.",
|
|
1236
|
+
"Better ask twice than lose yourself once.",
|
|
1237
|
+
"Carve your name on your heart and not on marble.",
|
|
1238
|
+
"Change is happening in your life, so go with the flow!",
|
|
1239
|
+
"Curiosity kills boredom. Nothing can kill curiosity.",
|
|
1240
|
+
"Dedicate yourself with a calm mind to the task at hand.",
|
|
1241
|
+
"Discontent is the first step in the progress of a man or a nation.",
|
|
1242
|
+
"Distance yourself from the vain.",
|
|
1243
|
+
"Don't be discouraged, because every wrong attempt discarded is another step forward.",
|
|
1244
|
+
"Don't let your limitations overshadow your talents.",
|
|
1245
|
+
"Don't worry; prosperity will knock on your door soon.",
|
|
1246
|
+
"Every flower blooms in its own sweet time.",
|
|
1247
|
+
"Failure is the tuition you pay for success.",
|
|
1248
|
+
"Fortune not found. Abort, Retry, Ignore?",
|
|
1249
|
+
"Good news will come to you by mail.",
|
|
1250
|
+
"Hard work pays off in the future, laziness pays off now.",
|
|
1251
|
+
"He who laughs at himself never runs out of things to laugh at.",
|
|
1252
|
+
"If you refuse to accept anything but the best, you very often get it.",
|
|
1253
|
+
"It is never too late to become what you might have been.",
|
|
1254
|
+
"It's okay to look at the past and future. Just don't stare.",
|
|
1255
|
+
"Keep your face to the sunshine and you will never see shadows.",
|
|
1256
|
+
"Love is like wildflowers; it's often found in the most unlikely places.",
|
|
1257
|
+
"Nothing is impossible to a willing heart.",
|
|
1258
|
+
"Now is the time to try something new.",
|
|
1259
|
+
"Perhaps you've been too busy putting out fires.",
|
|
1260
|
+
"Smile when picking out the fruit but not when you pick out the gems.",
|
|
1261
|
+
"Someone is thinking of you.",
|
|
1262
|
+
"Soon you will be surrounded by good friends and laughter.",
|
|
1263
|
+
"Stop procrastinating. Starting tomorrow.",
|
|
1264
|
+
"The early bird gets the worm, but the second mouse gets the cheese.",
|
|
1265
|
+
"The greatest risk is not taking one.",
|
|
1266
|
+
"The harder you work, the luckier you get.",
|
|
1267
|
+
"The only way to have a friend is to be one.",
|
|
1268
|
+
"The person who will not stand for something will fall for anything.",
|
|
1269
|
+
"The secret to getting ahead is getting started.",
|
|
1270
|
+
"Today is a good day for new beginnings.",
|
|
1271
|
+
"You will be successful in your work.",
|
|
1272
|
+
"Your life will be happy and peaceful.",
|
|
1273
|
+
"Your road to glory will be rocky, but fulfilling."
|
|
1274
|
+
];
|
|
1275
|
+
var LUCKY_ITEMS = [
|
|
1276
|
+
"rubber duck debugging",
|
|
1277
|
+
"coffee",
|
|
1278
|
+
"mechanical keyboard",
|
|
1279
|
+
"dark mode",
|
|
1280
|
+
"git push --force",
|
|
1281
|
+
"Stack Overflow",
|
|
1282
|
+
"semicolons",
|
|
1283
|
+
"tabs",
|
|
1284
|
+
"spaces",
|
|
1285
|
+
"vim",
|
|
1286
|
+
"emacs",
|
|
1287
|
+
"VSCode",
|
|
1288
|
+
"console.log",
|
|
1289
|
+
"sudo"
|
|
1290
|
+
];
|
|
1291
|
+
function getFortune() {
|
|
1292
|
+
return {
|
|
1293
|
+
fortune: FORTUNES[Math.floor(Math.random() * FORTUNES.length)],
|
|
1294
|
+
luckyNumber: Math.floor(Math.random() * 100),
|
|
1295
|
+
luckyItem: LUCKY_ITEMS[Math.floor(Math.random() * LUCKY_ITEMS.length)]
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
function wrapText2(text, maxWidth) {
|
|
1299
|
+
const words = text.split(" ");
|
|
1300
|
+
let currentLine = "";
|
|
1301
|
+
const lines = [];
|
|
1302
|
+
for (const word of words) {
|
|
1303
|
+
if ((currentLine + " " + word).trim().length <= maxWidth) {
|
|
1304
|
+
currentLine = (currentLine + " " + word).trim();
|
|
1305
|
+
} else {
|
|
1306
|
+
if (currentLine) lines.push(currentLine);
|
|
1307
|
+
currentLine = word;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
if (currentLine) lines.push(currentLine);
|
|
1311
|
+
return lines;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
// src/commands/fun/fortune.ts
|
|
1315
|
+
registerCommand({
|
|
1316
|
+
name: "fortune",
|
|
1317
|
+
description: "Get your fortune told",
|
|
1318
|
+
usage: "/fortune",
|
|
1319
|
+
async execute(_args) {
|
|
1320
|
+
const result = getFortune();
|
|
1321
|
+
console.log("");
|
|
1322
|
+
console.log(` ${theme.sunset("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")}`);
|
|
1323
|
+
console.log(` ${theme.sunset("\u2551")} ${symbols.sparkle} ${theme.gold("F O R T U N E C O O K I E")} ${symbols.sparkle} ${theme.sunset("\u2551")}`);
|
|
1324
|
+
console.log(` ${theme.sunset("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D")}`);
|
|
1325
|
+
console.log("");
|
|
1326
|
+
console.log(` ${theme.gold(' .-"""-. ')}`);
|
|
1327
|
+
console.log(` ${theme.gold(" / \\ ")}`);
|
|
1328
|
+
console.log(` ${theme.gold(" | O O | ")}`);
|
|
1329
|
+
console.log(` ${theme.gold(" | .-----. | ")}`);
|
|
1330
|
+
console.log(` ${theme.gold(" \\ `-\xB4 / ")}`);
|
|
1331
|
+
console.log(` ${theme.gold(" `-.___.-\xB4 ")}`);
|
|
1332
|
+
console.log("");
|
|
1333
|
+
console.log(` ${theme.dim("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510")}`);
|
|
1334
|
+
console.log(` ${theme.dim("\u2502")} ${theme.dim("\u2502")}`);
|
|
1335
|
+
const lines = wrapText2(result.fortune, 55);
|
|
1336
|
+
for (const line of lines) {
|
|
1337
|
+
const padding = 55 - line.length;
|
|
1338
|
+
const leftPad = Math.floor(padding / 2);
|
|
1339
|
+
const rightPad = padding - leftPad;
|
|
1340
|
+
console.log(` ${theme.dim("\u2502")} ${" ".repeat(leftPad)}${theme.highlight(line)}${" ".repeat(rightPad)} ${theme.dim("\u2502")}`);
|
|
1341
|
+
}
|
|
1342
|
+
console.log(` ${theme.dim("\u2502")} ${theme.dim("\u2502")}`);
|
|
1343
|
+
console.log(` ${theme.dim("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518")}`);
|
|
1344
|
+
console.log("");
|
|
1345
|
+
console.log(` ${theme.dim("\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504")}`);
|
|
1346
|
+
console.log(` ${symbols.star} ${theme.dim("Lucky number:")} ${theme.gold(result.luckyNumber.toString().padStart(2, "0"))} ${symbols.sparkle} ${theme.dim("Lucky item:")} ${theme.primary(result.luckyItem)}`);
|
|
1347
|
+
console.log(` ${theme.dim("\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504\u2504")}`);
|
|
1348
|
+
console.log("");
|
|
1349
|
+
}
|
|
1350
|
+
});
|
|
1351
|
+
|
|
1352
|
+
// src/handlers/fun/dice.ts
|
|
1353
|
+
var DICE_ART = {
|
|
1354
|
+
1: [
|
|
1355
|
+
"\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
|
|
1356
|
+
"\u2502 \u2502",
|
|
1357
|
+
"\u2502 \u25CF \u2502",
|
|
1358
|
+
"\u2502 \u2502",
|
|
1359
|
+
"\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"
|
|
1360
|
+
],
|
|
1361
|
+
2: [
|
|
1362
|
+
"\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
|
|
1363
|
+
"\u2502 \u25CF \u2502",
|
|
1364
|
+
"\u2502 \u2502",
|
|
1365
|
+
"\u2502 \u25CF \u2502",
|
|
1366
|
+
"\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"
|
|
1367
|
+
],
|
|
1368
|
+
3: [
|
|
1369
|
+
"\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
|
|
1370
|
+
"\u2502 \u25CF \u2502",
|
|
1371
|
+
"\u2502 \u25CF \u2502",
|
|
1372
|
+
"\u2502 \u25CF \u2502",
|
|
1373
|
+
"\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"
|
|
1374
|
+
],
|
|
1375
|
+
4: [
|
|
1376
|
+
"\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
|
|
1377
|
+
"\u2502 \u25CF \u25CF \u2502",
|
|
1378
|
+
"\u2502 \u2502",
|
|
1379
|
+
"\u2502 \u25CF \u25CF \u2502",
|
|
1380
|
+
"\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"
|
|
1381
|
+
],
|
|
1382
|
+
5: [
|
|
1383
|
+
"\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
|
|
1384
|
+
"\u2502 \u25CF \u25CF \u2502",
|
|
1385
|
+
"\u2502 \u25CF \u2502",
|
|
1386
|
+
"\u2502 \u25CF \u25CF \u2502",
|
|
1387
|
+
"\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"
|
|
1388
|
+
],
|
|
1389
|
+
6: [
|
|
1390
|
+
"\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
|
|
1391
|
+
"\u2502 \u25CF \u25CF \u2502",
|
|
1392
|
+
"\u2502 \u25CF \u25CF \u2502",
|
|
1393
|
+
"\u2502 \u25CF \u25CF \u2502",
|
|
1394
|
+
"\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"
|
|
1395
|
+
]
|
|
1396
|
+
};
|
|
1397
|
+
function rollDice(count = 1, sides = 6) {
|
|
1398
|
+
const safeCount = Math.min(Math.max(1, count), 6);
|
|
1399
|
+
const safeSides = Math.max(2, sides);
|
|
1400
|
+
const rolls = [];
|
|
1401
|
+
for (let i = 0; i < safeCount; i++) {
|
|
1402
|
+
rolls.push(Math.floor(Math.random() * safeSides) + 1);
|
|
1403
|
+
}
|
|
1404
|
+
return {
|
|
1405
|
+
count: safeCount,
|
|
1406
|
+
sides: safeSides,
|
|
1407
|
+
rolls,
|
|
1408
|
+
total: rolls.reduce((a, b) => a + b, 0),
|
|
1409
|
+
isStandardD6: safeSides === 6 && safeCount <= 3
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
// src/commands/fun/dice.ts
|
|
1414
|
+
registerCommand({
|
|
1415
|
+
name: "dice",
|
|
1416
|
+
description: "Roll some dice",
|
|
1417
|
+
usage: "/dice [count] [sides]",
|
|
1418
|
+
async execute(args2) {
|
|
1419
|
+
const count = parseInt(args2[0]) || 1;
|
|
1420
|
+
const sides = parseInt(args2[1]) || 6;
|
|
1421
|
+
const result = rollDice(count, sides);
|
|
1422
|
+
console.log("");
|
|
1423
|
+
console.log(` ${symbols.dice} ${theme.secondary("Rolling")} ${theme.primary(result.count.toString())}d${theme.primary(result.sides.toString())}...`);
|
|
1424
|
+
console.log("");
|
|
1425
|
+
if (result.isStandardD6) {
|
|
1426
|
+
for (let line = 0; line < 5; line++) {
|
|
1427
|
+
const row = result.rolls.map((r) => theme.primary(DICE_ART[r][line])).join(" ");
|
|
1428
|
+
console.log(" " + row);
|
|
1429
|
+
}
|
|
1430
|
+
} else {
|
|
1431
|
+
const diceDisplay = result.rolls.map((r) => theme.gold(`[${r}]`)).join(" ");
|
|
1432
|
+
console.log(` ${diceDisplay}`);
|
|
1433
|
+
}
|
|
1434
|
+
console.log("");
|
|
1435
|
+
console.log(` ${theme.dim("Total:")} ${theme.success(result.total.toString())}`);
|
|
1436
|
+
console.log("");
|
|
1437
|
+
}
|
|
1438
|
+
});
|
|
1439
|
+
|
|
1440
|
+
// src/handlers/fun/flip.ts
|
|
1441
|
+
var COIN_ART = {
|
|
1442
|
+
heads: [
|
|
1443
|
+
" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E",
|
|
1444
|
+
" \u2571 \u2572",
|
|
1445
|
+
" \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2510 \u2502",
|
|
1446
|
+
" \u2502 \u2502 \u25C9 \u25C9 \u2502 \u2502",
|
|
1447
|
+
" \u2502 \u2502 \u25BD \u2502 \u2502",
|
|
1448
|
+
" \u2502 \u2502 \u2570\u2500\u256F \u2502 \u2502",
|
|
1449
|
+
" \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2518 \u2502",
|
|
1450
|
+
" \u2572 \u2571",
|
|
1451
|
+
" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"
|
|
1452
|
+
],
|
|
1453
|
+
tails: [
|
|
1454
|
+
" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E",
|
|
1455
|
+
" \u2571 \u2572",
|
|
1456
|
+
" \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2510 \u2502",
|
|
1457
|
+
" \u2502 \u2502 \u2605 \u2502 \u2502",
|
|
1458
|
+
" \u2502 \u2502 \u2550\u2550\u2550 \u2502 \u2502",
|
|
1459
|
+
" \u2502 \u2502 \u2605 \u2502 \u2502",
|
|
1460
|
+
" \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2518 \u2502",
|
|
1461
|
+
" \u2572 \u2571",
|
|
1462
|
+
" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"
|
|
1463
|
+
]
|
|
1464
|
+
};
|
|
1465
|
+
function flipCoins(count = 1) {
|
|
1466
|
+
const safeCount = Math.min(Math.max(1, count), 10);
|
|
1467
|
+
const flips = [];
|
|
1468
|
+
for (let i = 0; i < safeCount; i++) {
|
|
1469
|
+
flips.push(Math.random() < 0.5 ? "heads" : "tails");
|
|
1470
|
+
}
|
|
1471
|
+
return {
|
|
1472
|
+
count: safeCount,
|
|
1473
|
+
flips,
|
|
1474
|
+
headsCount: flips.filter((f) => f === "heads").length,
|
|
1475
|
+
tailsCount: flips.filter((f) => f === "tails").length
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
// src/commands/fun/flip.ts
|
|
1480
|
+
registerCommand({
|
|
1481
|
+
name: "flip",
|
|
1482
|
+
description: "Flip a coin",
|
|
1483
|
+
usage: "/flip [count]",
|
|
1484
|
+
async execute(args2) {
|
|
1485
|
+
const count = parseInt(args2[0]) || 1;
|
|
1486
|
+
const result = flipCoins(count);
|
|
1487
|
+
console.log("");
|
|
1488
|
+
console.log(` ${symbols.coin} ${theme.secondary("Flipping")} ${result.count > 1 ? theme.primary(result.count.toString()) + " coins" : "a coin"}...`);
|
|
1489
|
+
console.log("");
|
|
1490
|
+
if (result.count === 1) {
|
|
1491
|
+
const side = result.flips[0];
|
|
1492
|
+
const art = COIN_ART[side];
|
|
1493
|
+
const color = side === "heads" ? theme.gold : theme.primary;
|
|
1494
|
+
art.forEach((line) => console.log(" " + color(line)));
|
|
1495
|
+
console.log("");
|
|
1496
|
+
console.log(` ${theme.highlight("Result:")} ${color(side.toUpperCase())}`);
|
|
1497
|
+
} else {
|
|
1498
|
+
const display = result.flips.map(
|
|
1499
|
+
(f) => f === "heads" ? theme.gold("H") : theme.primary("T")
|
|
1500
|
+
).join(" ");
|
|
1501
|
+
console.log(` ${display}`);
|
|
1502
|
+
console.log("");
|
|
1503
|
+
console.log(` ${theme.gold("Heads:")} ${result.headsCount} ${theme.primary("Tails:")} ${result.tailsCount}`);
|
|
1504
|
+
}
|
|
1505
|
+
console.log("");
|
|
1506
|
+
}
|
|
1507
|
+
});
|
|
1508
|
+
|
|
1509
|
+
// src/commands/fun/pomodoro.ts
|
|
1510
|
+
var state = {
|
|
1511
|
+
running: false,
|
|
1512
|
+
mode: "work",
|
|
1513
|
+
endTime: 0,
|
|
1514
|
+
sessions: 0,
|
|
1515
|
+
interval: null
|
|
1516
|
+
};
|
|
1517
|
+
var DURATIONS = {
|
|
1518
|
+
work: 25 * 60 * 1e3,
|
|
1519
|
+
break: 5 * 60 * 1e3,
|
|
1520
|
+
longbreak: 15 * 60 * 1e3
|
|
1521
|
+
};
|
|
1522
|
+
function formatRemaining(ms) {
|
|
1523
|
+
const totalSeconds = Math.max(0, Math.ceil(ms / 1e3));
|
|
1524
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
1525
|
+
const seconds = totalSeconds % 60;
|
|
1526
|
+
return `${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
|
|
1527
|
+
}
|
|
1528
|
+
function getModeEmoji(mode) {
|
|
1529
|
+
switch (mode) {
|
|
1530
|
+
case "work":
|
|
1531
|
+
return symbols.fire;
|
|
1532
|
+
case "break":
|
|
1533
|
+
return symbols.coffee;
|
|
1534
|
+
case "longbreak":
|
|
1535
|
+
return symbols.sparkle;
|
|
1536
|
+
default:
|
|
1537
|
+
return symbols.clock;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
function getModeLabel(mode) {
|
|
1541
|
+
switch (mode) {
|
|
1542
|
+
case "work":
|
|
1543
|
+
return "FOCUS TIME";
|
|
1544
|
+
case "break":
|
|
1545
|
+
return "SHORT BREAK";
|
|
1546
|
+
case "longbreak":
|
|
1547
|
+
return "LONG BREAK";
|
|
1548
|
+
default:
|
|
1549
|
+
return mode;
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
registerCommand({
|
|
1553
|
+
name: "pomodoro",
|
|
1554
|
+
description: "Pomodoro timer (25/5 technique)",
|
|
1555
|
+
usage: "/pomodoro [start|stop|status|skip]",
|
|
1556
|
+
async execute(args2) {
|
|
1557
|
+
const action = args2[0]?.toLowerCase() || "status";
|
|
1558
|
+
if (action === "start") {
|
|
1559
|
+
if (state.running) {
|
|
1560
|
+
console.log(theme.dim(" Pomodoro is already running"));
|
|
1561
|
+
console.log(theme.dim(" Use /pomodoro status to see remaining time"));
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
state.running = true;
|
|
1565
|
+
state.mode = "work";
|
|
1566
|
+
state.endTime = Date.now() + DURATIONS.work;
|
|
1567
|
+
console.log("");
|
|
1568
|
+
console.log(` ${symbols.tomato} ${theme.gradient("POMODORO STARTED")} ${symbols.tomato}`);
|
|
1569
|
+
console.log("");
|
|
1570
|
+
console.log(` ${theme.primary("Focus time!")} 25 minutes of deep work.`);
|
|
1571
|
+
console.log(theme.dim(" Use /pomodoro status to check progress"));
|
|
1572
|
+
console.log(theme.dim(" Use /pomodoro skip to skip to break"));
|
|
1573
|
+
console.log("");
|
|
1574
|
+
if (state.interval) clearTimeout(state.interval);
|
|
1575
|
+
state.interval = setTimeout(() => {
|
|
1576
|
+
if (state.running) {
|
|
1577
|
+
console.log("\n");
|
|
1578
|
+
console.log(` ${symbols.bell} ${theme.success("POMODORO COMPLETE!")} ${symbols.bell}`);
|
|
1579
|
+
console.log(" Time for a break! Use /pomodoro start for next session");
|
|
1580
|
+
console.log("\n");
|
|
1581
|
+
state.sessions++;
|
|
1582
|
+
state.running = false;
|
|
1583
|
+
}
|
|
1584
|
+
}, DURATIONS.work);
|
|
1585
|
+
return;
|
|
1586
|
+
}
|
|
1587
|
+
if (action === "stop") {
|
|
1588
|
+
if (!state.running) {
|
|
1589
|
+
console.log(theme.dim(" Pomodoro is not running"));
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
if (state.interval) clearTimeout(state.interval);
|
|
1593
|
+
state.running = false;
|
|
1594
|
+
console.log("");
|
|
1595
|
+
console.log(` ${symbols.cross} ${theme.secondary("Pomodoro stopped")}`);
|
|
1596
|
+
console.log(` ${theme.dim("Sessions completed today:")} ${theme.primary(state.sessions.toString())}`);
|
|
1597
|
+
console.log("");
|
|
1598
|
+
return;
|
|
1599
|
+
}
|
|
1600
|
+
if (action === "skip") {
|
|
1601
|
+
if (!state.running) {
|
|
1602
|
+
console.log(theme.dim(" Pomodoro is not running"));
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1605
|
+
if (state.interval) clearTimeout(state.interval);
|
|
1606
|
+
if (state.mode === "work") {
|
|
1607
|
+
state.sessions++;
|
|
1608
|
+
state.mode = state.sessions % 4 === 0 ? "longbreak" : "break";
|
|
1609
|
+
state.endTime = Date.now() + DURATIONS[state.mode];
|
|
1610
|
+
console.log("");
|
|
1611
|
+
console.log(` ${getModeEmoji(state.mode)} ${theme.secondary(getModeLabel(state.mode))}`);
|
|
1612
|
+
console.log(` ${theme.dim("Time:")} ${formatRemaining(DURATIONS[state.mode])}`);
|
|
1613
|
+
console.log("");
|
|
1614
|
+
} else {
|
|
1615
|
+
state.mode = "work";
|
|
1616
|
+
state.endTime = Date.now() + DURATIONS.work;
|
|
1617
|
+
console.log("");
|
|
1618
|
+
console.log(` ${symbols.fire} ${theme.primary("Back to work!")}`);
|
|
1619
|
+
console.log(` ${theme.dim("Focus time:")} 25:00`);
|
|
1620
|
+
console.log("");
|
|
1621
|
+
}
|
|
1622
|
+
state.interval = setTimeout(() => {
|
|
1623
|
+
if (state.running) {
|
|
1624
|
+
console.log(`
|
|
1625
|
+
${symbols.bell} ${state.mode === "work" ? "Focus session" : "Break"} complete!
|
|
1626
|
+
`);
|
|
1627
|
+
state.running = false;
|
|
1628
|
+
}
|
|
1629
|
+
}, state.endTime - Date.now());
|
|
1630
|
+
return;
|
|
1631
|
+
}
|
|
1632
|
+
if (action === "status" || !action) {
|
|
1633
|
+
console.log("");
|
|
1634
|
+
console.log(` ${symbols.tomato} ${theme.gradient("POMODORO TIMER")} ${symbols.tomato}`);
|
|
1635
|
+
console.log("");
|
|
1636
|
+
if (!state.running) {
|
|
1637
|
+
console.log(theme.dim(" Not running. Use /pomodoro start"));
|
|
1638
|
+
console.log(` ${theme.dim("Sessions today:")} ${theme.primary(state.sessions.toString())}`);
|
|
1639
|
+
} else {
|
|
1640
|
+
const remaining = state.endTime - Date.now();
|
|
1641
|
+
const progress = 1 - remaining / DURATIONS[state.mode];
|
|
1642
|
+
const barWidth = 30;
|
|
1643
|
+
const filled = Math.round(progress * barWidth);
|
|
1644
|
+
const bar = theme.primary("\u2588".repeat(filled)) + theme.dim("\u2591".repeat(barWidth - filled));
|
|
1645
|
+
console.log(` ${getModeEmoji(state.mode)} ${theme.secondary(getModeLabel(state.mode))}`);
|
|
1646
|
+
console.log("");
|
|
1647
|
+
console.log(` ${bar} ${formatRemaining(remaining)}`);
|
|
1648
|
+
console.log("");
|
|
1649
|
+
console.log(` ${theme.dim("Sessions:")} ${theme.primary(state.sessions.toString())} ${theme.dim("| Next:")} ${state.mode === "work" ? "break" : "focus"}`);
|
|
1650
|
+
}
|
|
1651
|
+
console.log("");
|
|
1652
|
+
return;
|
|
1653
|
+
}
|
|
1654
|
+
console.log(theme.error("Unknown action. Use: start, stop, status, skip"));
|
|
1655
|
+
}
|
|
1656
|
+
});
|
|
1657
|
+
|
|
1658
|
+
// src/commands/creative/asciiart.ts
|
|
1659
|
+
import Jimp from "jimp";
|
|
1660
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1661
|
+
import { resolve } from "path";
|
|
1662
|
+
var CHAR_RAMPS = {
|
|
1663
|
+
// Standard - 10 levels, balanced
|
|
1664
|
+
standard: " .:-=+*#%@",
|
|
1665
|
+
// Detailed - 16 levels, more gradation
|
|
1666
|
+
detailed: " .'`^\",:;Il!i><~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$",
|
|
1667
|
+
// Block characters - uses Unicode block elements for smoother look
|
|
1668
|
+
blocks: " \u2591\u2592\u2593\u2588",
|
|
1669
|
+
// Simple - 5 levels for cleaner output
|
|
1670
|
+
simple: " .:\u2591\u2588",
|
|
1671
|
+
// Extended - Maximum depth with 70 characters
|
|
1672
|
+
extended: "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1{}[]?-_+~<>i!lI;:,\"^`'. "
|
|
1673
|
+
};
|
|
1674
|
+
var SOBEL_X = [
|
|
1675
|
+
[-1, 0, 1],
|
|
1676
|
+
[-2, 0, 2],
|
|
1677
|
+
[-1, 0, 1]
|
|
1678
|
+
];
|
|
1679
|
+
var SOBEL_Y = [
|
|
1680
|
+
[-1, -2, -1],
|
|
1681
|
+
[0, 0, 0],
|
|
1682
|
+
[1, 2, 1]
|
|
1683
|
+
];
|
|
1684
|
+
function getAsciiChar(brightness, ramp, inverted) {
|
|
1685
|
+
const b = Math.max(0, Math.min(255, brightness));
|
|
1686
|
+
const index = Math.floor(b / 255 * (ramp.length - 1));
|
|
1687
|
+
return inverted ? ramp[ramp.length - 1 - index] : ramp[index];
|
|
1688
|
+
}
|
|
1689
|
+
async function applyEdgeEnhancement(image, strength) {
|
|
1690
|
+
const width = image.getWidth();
|
|
1691
|
+
const height = image.getHeight();
|
|
1692
|
+
const edges = new Uint8Array(width * height);
|
|
1693
|
+
for (let y = 1; y < height - 1; y++) {
|
|
1694
|
+
for (let x = 1; x < width - 1; x++) {
|
|
1695
|
+
let gx = 0;
|
|
1696
|
+
let gy = 0;
|
|
1697
|
+
for (let ky = -1; ky <= 1; ky++) {
|
|
1698
|
+
for (let kx = -1; kx <= 1; kx++) {
|
|
1699
|
+
const pixel = Jimp.intToRGBA(image.getPixelColor(x + kx, y + ky));
|
|
1700
|
+
const gray = (pixel.r + pixel.g + pixel.b) / 3;
|
|
1701
|
+
gx += gray * SOBEL_X[ky + 1][kx + 1];
|
|
1702
|
+
gy += gray * SOBEL_Y[ky + 1][kx + 1];
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
edges[y * width + x] = Math.min(255, Math.sqrt(gx * gx + gy * gy));
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
for (let y = 0; y < height; y++) {
|
|
1709
|
+
for (let x = 0; x < width; x++) {
|
|
1710
|
+
const pixel = Jimp.intToRGBA(image.getPixelColor(x, y));
|
|
1711
|
+
const edge = edges[y * width + x] || 0;
|
|
1712
|
+
const blended = Math.min(255, pixel.r + edge * strength);
|
|
1713
|
+
const color = Jimp.rgbaToInt(blended, blended, blended, 255);
|
|
1714
|
+
image.setPixelColor(color, x, y);
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
registerCommand({
|
|
1719
|
+
name: "asciiart",
|
|
1720
|
+
description: "Convert an image to ASCII art",
|
|
1721
|
+
usage: "/asciiart <@image.png> [--width N] [--style standard|detailed|blocks|simple|extended] [--edges] [--invert] [--contrast N]",
|
|
1722
|
+
async execute(args2) {
|
|
1723
|
+
if (args2.length === 0) {
|
|
1724
|
+
console.log(theme.error("Usage: /asciiart <image-path> [options]"));
|
|
1725
|
+
console.log("");
|
|
1726
|
+
console.log(theme.secondary("Options:"));
|
|
1727
|
+
console.log(theme.dim(" --width N Output width in characters (default: 80)"));
|
|
1728
|
+
console.log(theme.dim(" --style S Character style: standard, detailed, blocks, simple, extended"));
|
|
1729
|
+
console.log(theme.dim(" --edges Enable edge enhancement for more detail"));
|
|
1730
|
+
console.log(theme.dim(" --invert Invert brightness (light bg terminals)"));
|
|
1731
|
+
console.log(theme.dim(" --contrast N Adjust contrast (-1 to 1, default: 0.3)"));
|
|
1732
|
+
console.log("");
|
|
1733
|
+
console.log(theme.dim("Example: /asciiart @photo.png --width 100 --style detailed --edges"));
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
let imagePath = args2[0];
|
|
1737
|
+
let width = 80;
|
|
1738
|
+
let style = "detailed";
|
|
1739
|
+
let edgeEnhance = false;
|
|
1740
|
+
let inverted = false;
|
|
1741
|
+
let contrastVal = 0.3;
|
|
1742
|
+
if (imagePath.startsWith("@")) {
|
|
1743
|
+
imagePath = imagePath.slice(1);
|
|
1744
|
+
}
|
|
1745
|
+
for (let i = 1; i < args2.length; i++) {
|
|
1746
|
+
const arg = args2[i].toLowerCase();
|
|
1747
|
+
if (arg === "--width" && args2[i + 1]) {
|
|
1748
|
+
width = parseInt(args2[i + 1], 10) || 80;
|
|
1749
|
+
i++;
|
|
1750
|
+
} else if (arg === "--style" && args2[i + 1]) {
|
|
1751
|
+
const s = args2[i + 1].toLowerCase();
|
|
1752
|
+
if (s in CHAR_RAMPS) {
|
|
1753
|
+
style = s;
|
|
1754
|
+
}
|
|
1755
|
+
i++;
|
|
1756
|
+
} else if (arg === "--edges") {
|
|
1757
|
+
edgeEnhance = true;
|
|
1758
|
+
} else if (arg === "--invert") {
|
|
1759
|
+
inverted = true;
|
|
1760
|
+
} else if (arg === "--contrast" && args2[i + 1]) {
|
|
1761
|
+
contrastVal = parseFloat(args2[i + 1]) || 0.3;
|
|
1762
|
+
contrastVal = Math.max(-1, Math.min(1, contrastVal));
|
|
1763
|
+
i++;
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
const fullPath = resolve(process.cwd(), imagePath);
|
|
1767
|
+
if (!existsSync3(fullPath)) {
|
|
1768
|
+
console.log(theme.error(`File not found: ${imagePath}`));
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1771
|
+
console.log(theme.dim(`Converting ${imagePath} to ASCII art...`));
|
|
1772
|
+
console.log(theme.dim(`Style: ${style}, Width: ${width}${edgeEnhance ? ", Edge enhancement: ON" : ""}`));
|
|
1773
|
+
console.log("");
|
|
1774
|
+
try {
|
|
1775
|
+
const image = await Jimp.read(fullPath);
|
|
1776
|
+
const aspectRatio = image.getHeight() / image.getWidth();
|
|
1777
|
+
const height = Math.floor(width * aspectRatio * 0.5);
|
|
1778
|
+
image.resize(width, height);
|
|
1779
|
+
image.greyscale();
|
|
1780
|
+
image.normalize();
|
|
1781
|
+
image.contrast(contrastVal);
|
|
1782
|
+
if (edgeEnhance) {
|
|
1783
|
+
await applyEdgeEnhancement(image, 0.4);
|
|
1784
|
+
}
|
|
1785
|
+
const ramp = CHAR_RAMPS[style];
|
|
1786
|
+
let ascii = "";
|
|
1787
|
+
for (let y = 0; y < height; y++) {
|
|
1788
|
+
for (let x = 0; x < width; x++) {
|
|
1789
|
+
const pixel = Jimp.intToRGBA(image.getPixelColor(x, y));
|
|
1790
|
+
const brightness = 0.299 * pixel.r + 0.587 * pixel.g + 0.114 * pixel.b;
|
|
1791
|
+
ascii += getAsciiChar(brightness, ramp, !inverted);
|
|
1792
|
+
}
|
|
1793
|
+
ascii += "\n";
|
|
1794
|
+
}
|
|
1795
|
+
console.log(ascii);
|
|
1796
|
+
} catch (error) {
|
|
1797
|
+
console.log(theme.error(`Error converting image: ${error}`));
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
});
|
|
1801
|
+
|
|
1802
|
+
// src/commands/creative/figlet.ts
|
|
1803
|
+
import figlet2 from "figlet";
|
|
1804
|
+
var FONTS = [
|
|
1805
|
+
"Standard",
|
|
1806
|
+
"Big",
|
|
1807
|
+
"Slant",
|
|
1808
|
+
"Small",
|
|
1809
|
+
"Banner",
|
|
1810
|
+
"Block",
|
|
1811
|
+
"Bubble",
|
|
1812
|
+
"Digital",
|
|
1813
|
+
"Ivrit",
|
|
1814
|
+
"Lean",
|
|
1815
|
+
"Mini",
|
|
1816
|
+
"Script",
|
|
1817
|
+
"Shadow",
|
|
1818
|
+
"Speed",
|
|
1819
|
+
"Star Wars"
|
|
1820
|
+
];
|
|
1821
|
+
registerCommand({
|
|
1822
|
+
name: "figlet",
|
|
1823
|
+
description: "Generate ASCII art text",
|
|
1824
|
+
usage: "/figlet <text> [--font <name>]",
|
|
1825
|
+
async execute(args2) {
|
|
1826
|
+
if (args2.length === 0) {
|
|
1827
|
+
console.log("");
|
|
1828
|
+
console.log(theme.error("Usage: /figlet <text> [--font <name>]"));
|
|
1829
|
+
console.log("");
|
|
1830
|
+
console.log(theme.dim(" Available fonts:"));
|
|
1831
|
+
console.log(` ${theme.secondary(FONTS.join(", "))}`);
|
|
1832
|
+
console.log("");
|
|
1833
|
+
console.log(theme.dim(" Example: /figlet Hello --font Slant"));
|
|
1834
|
+
console.log("");
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1837
|
+
let font = "Standard";
|
|
1838
|
+
let text = args2.join(" ");
|
|
1839
|
+
const fontIndex = args2.findIndex((a) => a.toLowerCase() === "--font");
|
|
1840
|
+
if (fontIndex !== -1 && args2[fontIndex + 1]) {
|
|
1841
|
+
font = args2[fontIndex + 1];
|
|
1842
|
+
text = [...args2.slice(0, fontIndex), ...args2.slice(fontIndex + 2)].join(" ");
|
|
1843
|
+
}
|
|
1844
|
+
if (!text) {
|
|
1845
|
+
console.log(theme.error("Please provide text to render"));
|
|
1846
|
+
return;
|
|
1847
|
+
}
|
|
1848
|
+
try {
|
|
1849
|
+
const result = figlet2.textSync(text, {
|
|
1850
|
+
font,
|
|
1851
|
+
horizontalLayout: "default",
|
|
1852
|
+
verticalLayout: "default"
|
|
1853
|
+
});
|
|
1854
|
+
console.log("");
|
|
1855
|
+
const lines = result.split("\n");
|
|
1856
|
+
const colors = ["#FF6B6B", "#4ECDC4", "#45B7D1", "#96CEB4", "#FFEAA7", "#DDA0DD", "#98D8C8"];
|
|
1857
|
+
lines.forEach((line, idx) => {
|
|
1858
|
+
if (line.trim()) {
|
|
1859
|
+
const color = colors[idx % colors.length];
|
|
1860
|
+
console.log(" " + theme.primary(line));
|
|
1861
|
+
} else {
|
|
1862
|
+
console.log("");
|
|
1863
|
+
}
|
|
1864
|
+
});
|
|
1865
|
+
console.log("");
|
|
1866
|
+
console.log(theme.dim(` Font: ${font}`));
|
|
1867
|
+
console.log("");
|
|
1868
|
+
} catch (e) {
|
|
1869
|
+
console.log(theme.error(`Font "${font}" not available. Try: Standard, Big, Slant, Small`));
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
});
|
|
1873
|
+
|
|
1874
|
+
// src/handlers/creative/lorem.ts
|
|
1875
|
+
var LOREM_WORDS = [
|
|
1876
|
+
"lorem",
|
|
1877
|
+
"ipsum",
|
|
1878
|
+
"dolor",
|
|
1879
|
+
"sit",
|
|
1880
|
+
"amet",
|
|
1881
|
+
"consectetur",
|
|
1882
|
+
"adipiscing",
|
|
1883
|
+
"elit",
|
|
1884
|
+
"sed",
|
|
1885
|
+
"do",
|
|
1886
|
+
"eiusmod",
|
|
1887
|
+
"tempor",
|
|
1888
|
+
"incididunt",
|
|
1889
|
+
"ut",
|
|
1890
|
+
"labore",
|
|
1891
|
+
"et",
|
|
1892
|
+
"dolore",
|
|
1893
|
+
"magna",
|
|
1894
|
+
"aliqua",
|
|
1895
|
+
"enim",
|
|
1896
|
+
"ad",
|
|
1897
|
+
"minim",
|
|
1898
|
+
"veniam",
|
|
1899
|
+
"quis",
|
|
1900
|
+
"nostrud",
|
|
1901
|
+
"exercitation",
|
|
1902
|
+
"ullamco",
|
|
1903
|
+
"laboris",
|
|
1904
|
+
"nisi",
|
|
1905
|
+
"aliquip",
|
|
1906
|
+
"ex",
|
|
1907
|
+
"ea",
|
|
1908
|
+
"commodo",
|
|
1909
|
+
"consequat",
|
|
1910
|
+
"duis",
|
|
1911
|
+
"aute",
|
|
1912
|
+
"irure",
|
|
1913
|
+
"in",
|
|
1914
|
+
"reprehenderit",
|
|
1915
|
+
"voluptate",
|
|
1916
|
+
"velit",
|
|
1917
|
+
"esse",
|
|
1918
|
+
"cillum",
|
|
1919
|
+
"fugiat",
|
|
1920
|
+
"nulla",
|
|
1921
|
+
"pariatur",
|
|
1922
|
+
"excepteur",
|
|
1923
|
+
"sint",
|
|
1924
|
+
"occaecat",
|
|
1925
|
+
"cupidatat",
|
|
1926
|
+
"non",
|
|
1927
|
+
"proident",
|
|
1928
|
+
"sunt",
|
|
1929
|
+
"culpa",
|
|
1930
|
+
"qui",
|
|
1931
|
+
"officia",
|
|
1932
|
+
"deserunt",
|
|
1933
|
+
"mollit",
|
|
1934
|
+
"anim",
|
|
1935
|
+
"id",
|
|
1936
|
+
"est",
|
|
1937
|
+
"laborum",
|
|
1938
|
+
"perspiciatis",
|
|
1939
|
+
"unde",
|
|
1940
|
+
"omnis",
|
|
1941
|
+
"iste",
|
|
1942
|
+
"natus",
|
|
1943
|
+
"error",
|
|
1944
|
+
"voluptatem",
|
|
1945
|
+
"accusantium",
|
|
1946
|
+
"doloremque",
|
|
1947
|
+
"laudantium",
|
|
1948
|
+
"totam",
|
|
1949
|
+
"rem",
|
|
1950
|
+
"aperiam",
|
|
1951
|
+
"eaque",
|
|
1952
|
+
"ipsa",
|
|
1953
|
+
"quae",
|
|
1954
|
+
"ab",
|
|
1955
|
+
"illo",
|
|
1956
|
+
"inventore",
|
|
1957
|
+
"veritatis",
|
|
1958
|
+
"quasi",
|
|
1959
|
+
"architecto",
|
|
1960
|
+
"beatae",
|
|
1961
|
+
"vitae",
|
|
1962
|
+
"dicta",
|
|
1963
|
+
"explicabo",
|
|
1964
|
+
"nemo",
|
|
1965
|
+
"ipsam",
|
|
1966
|
+
"quia",
|
|
1967
|
+
"voluptas",
|
|
1968
|
+
"aspernatur",
|
|
1969
|
+
"aut",
|
|
1970
|
+
"odit",
|
|
1971
|
+
"fugit",
|
|
1972
|
+
"consequuntur",
|
|
1973
|
+
"magni",
|
|
1974
|
+
"dolores",
|
|
1975
|
+
"eos",
|
|
1976
|
+
"ratione",
|
|
1977
|
+
"sequi"
|
|
1978
|
+
];
|
|
1979
|
+
function generateSentence(minWords = 5, maxWords = 15) {
|
|
1980
|
+
const length = Math.floor(Math.random() * (maxWords - minWords + 1)) + minWords;
|
|
1981
|
+
const words = [];
|
|
1982
|
+
for (let i = 0; i < length; i++) {
|
|
1983
|
+
words.push(LOREM_WORDS[Math.floor(Math.random() * LOREM_WORDS.length)]);
|
|
1984
|
+
}
|
|
1985
|
+
words[0] = words[0].charAt(0).toUpperCase() + words[0].slice(1);
|
|
1986
|
+
return words.join(" ") + ".";
|
|
1987
|
+
}
|
|
1988
|
+
function generateParagraph(sentences) {
|
|
1989
|
+
const result = [];
|
|
1990
|
+
for (let i = 0; i < sentences; i++) {
|
|
1991
|
+
result.push(generateSentence());
|
|
1992
|
+
}
|
|
1993
|
+
return result.join(" ");
|
|
1994
|
+
}
|
|
1995
|
+
function generateLorem(paragraphCount = 1, sentenceCount = 5) {
|
|
1996
|
+
const safeParagraphs = Math.min(Math.max(paragraphCount, 1), 10);
|
|
1997
|
+
const safeSentences = Math.min(Math.max(sentenceCount, 1), 20);
|
|
1998
|
+
const paragraphs = [];
|
|
1999
|
+
for (let i = 0; i < safeParagraphs; i++) {
|
|
2000
|
+
paragraphs.push(generateParagraph(safeSentences));
|
|
2001
|
+
}
|
|
2002
|
+
return {
|
|
2003
|
+
paragraphs,
|
|
2004
|
+
paragraphCount: safeParagraphs,
|
|
2005
|
+
sentenceCount: safeSentences
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
function wrapText3(text, width) {
|
|
2009
|
+
const words = text.split(" ");
|
|
2010
|
+
const lines = [];
|
|
2011
|
+
let currentLine = "";
|
|
2012
|
+
for (const word of words) {
|
|
2013
|
+
if (currentLine.length + word.length + 1 <= width) {
|
|
2014
|
+
currentLine += (currentLine ? " " : "") + word;
|
|
2015
|
+
} else {
|
|
2016
|
+
if (currentLine) lines.push(currentLine);
|
|
2017
|
+
currentLine = word;
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
if (currentLine) lines.push(currentLine);
|
|
2021
|
+
return lines;
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
// src/commands/creative/lorem.ts
|
|
2025
|
+
registerCommand({
|
|
2026
|
+
name: "lorem",
|
|
2027
|
+
description: "Generate Lorem Ipsum text",
|
|
2028
|
+
usage: "/lorem [paragraphs] [sentences]",
|
|
2029
|
+
async execute(args2) {
|
|
2030
|
+
const paragraphCount = parseInt(args2[0]) || 1;
|
|
2031
|
+
const sentenceCount = parseInt(args2[1]) || 5;
|
|
2032
|
+
const result = generateLorem(paragraphCount, sentenceCount);
|
|
2033
|
+
console.log("");
|
|
2034
|
+
console.log(` ${symbols.scroll} ${theme.gradient("LOREM IPSUM GENERATOR")} ${symbols.scroll}`);
|
|
2035
|
+
console.log("");
|
|
2036
|
+
for (let i = 0; i < result.paragraphs.length; i++) {
|
|
2037
|
+
const wrapped = wrapText3(result.paragraphs[i], 70);
|
|
2038
|
+
wrapped.forEach((line) => console.log(` ${theme.secondary(line)}`));
|
|
2039
|
+
if (i < result.paragraphs.length - 1) console.log("");
|
|
2040
|
+
}
|
|
2041
|
+
console.log("");
|
|
2042
|
+
console.log(theme.dim(` Generated ${result.paragraphCount} paragraph(s) with ${result.sentenceCount} sentence(s) each`));
|
|
2043
|
+
console.log("");
|
|
2044
|
+
}
|
|
2045
|
+
});
|
|
2046
|
+
|
|
2047
|
+
// src/commands/creative/color.ts
|
|
2048
|
+
import chalk2 from "chalk";
|
|
2049
|
+
|
|
2050
|
+
// src/handlers/creative/color.ts
|
|
2051
|
+
function hexToRgb(hex) {
|
|
2052
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
2053
|
+
return result ? {
|
|
2054
|
+
r: parseInt(result[1], 16),
|
|
2055
|
+
g: parseInt(result[2], 16),
|
|
2056
|
+
b: parseInt(result[3], 16)
|
|
2057
|
+
} : null;
|
|
2058
|
+
}
|
|
2059
|
+
function rgbToHex(r, g, b) {
|
|
2060
|
+
return "#" + [r, g, b].map((x) => x.toString(16).padStart(2, "0")).join("");
|
|
2061
|
+
}
|
|
2062
|
+
function rgbToHsl(r, g, b) {
|
|
2063
|
+
r /= 255;
|
|
2064
|
+
g /= 255;
|
|
2065
|
+
b /= 255;
|
|
2066
|
+
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
2067
|
+
let h = 0, s = 0;
|
|
2068
|
+
const l = (max + min) / 2;
|
|
2069
|
+
if (max !== min) {
|
|
2070
|
+
const d = max - min;
|
|
2071
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
2072
|
+
switch (max) {
|
|
2073
|
+
case r:
|
|
2074
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
2075
|
+
break;
|
|
2076
|
+
case g:
|
|
2077
|
+
h = ((b - r) / d + 2) / 6;
|
|
2078
|
+
break;
|
|
2079
|
+
case b:
|
|
2080
|
+
h = ((r - g) / d + 4) / 6;
|
|
2081
|
+
break;
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) };
|
|
2085
|
+
}
|
|
2086
|
+
function getLuminance(r, g, b) {
|
|
2087
|
+
return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
2088
|
+
}
|
|
2089
|
+
function randomColor() {
|
|
2090
|
+
return {
|
|
2091
|
+
r: Math.floor(Math.random() * 256),
|
|
2092
|
+
g: Math.floor(Math.random() * 256),
|
|
2093
|
+
b: Math.floor(Math.random() * 256)
|
|
2094
|
+
};
|
|
2095
|
+
}
|
|
2096
|
+
function parseColor(input) {
|
|
2097
|
+
let r, g, b;
|
|
2098
|
+
if (!input || input === "random") {
|
|
2099
|
+
const rgb = randomColor();
|
|
2100
|
+
r = rgb.r;
|
|
2101
|
+
g = rgb.g;
|
|
2102
|
+
b = rgb.b;
|
|
2103
|
+
} else {
|
|
2104
|
+
const hexMatch = input.match(/^#?([a-f\d]{6}|[a-f\d]{3})$/i);
|
|
2105
|
+
if (hexMatch) {
|
|
2106
|
+
let hex2 = hexMatch[1];
|
|
2107
|
+
if (hex2.length === 3) {
|
|
2108
|
+
hex2 = hex2.split("").map((c) => c + c).join("");
|
|
2109
|
+
}
|
|
2110
|
+
const rgb = hexToRgb(hex2);
|
|
2111
|
+
if (rgb) {
|
|
2112
|
+
r = rgb.r;
|
|
2113
|
+
g = rgb.g;
|
|
2114
|
+
b = rgb.b;
|
|
2115
|
+
} else {
|
|
2116
|
+
return { error: "Invalid hex color" };
|
|
2117
|
+
}
|
|
2118
|
+
} else {
|
|
2119
|
+
const rgbMatch = input.match(/^rgb?\s*\(?\s*(\d{1,3})\s*[,\s]\s*(\d{1,3})\s*[,\s]\s*(\d{1,3})\s*\)?$/i);
|
|
2120
|
+
if (rgbMatch) {
|
|
2121
|
+
r = Math.min(255, parseInt(rgbMatch[1]));
|
|
2122
|
+
g = Math.min(255, parseInt(rgbMatch[2]));
|
|
2123
|
+
b = Math.min(255, parseInt(rgbMatch[3]));
|
|
2124
|
+
} else {
|
|
2125
|
+
const nums = input.split(/[\s,]+/).map((n) => parseInt(n)).filter((n) => !isNaN(n));
|
|
2126
|
+
if (nums.length === 3) {
|
|
2127
|
+
r = Math.min(255, nums[0]);
|
|
2128
|
+
g = Math.min(255, nums[1]);
|
|
2129
|
+
b = Math.min(255, nums[2]);
|
|
2130
|
+
} else {
|
|
2131
|
+
return { error: "Could not parse color" };
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
const hex = rgbToHex(r, g, b);
|
|
2137
|
+
const hsl = rgbToHsl(r, g, b);
|
|
2138
|
+
const luminance = getLuminance(r, g, b);
|
|
2139
|
+
return {
|
|
2140
|
+
rgb: { r, g, b },
|
|
2141
|
+
hex,
|
|
2142
|
+
hsl,
|
|
2143
|
+
luminance,
|
|
2144
|
+
textColor: luminance > 0.5 ? "#000000" : "#FFFFFF"
|
|
2145
|
+
};
|
|
2146
|
+
}
|
|
2147
|
+
function isColorError(result) {
|
|
2148
|
+
return "error" in result;
|
|
2149
|
+
}
|
|
2150
|
+
function generateShades(rgb, steps = 10) {
|
|
2151
|
+
const shades = [];
|
|
2152
|
+
for (let i = 0; i < steps; i++) {
|
|
2153
|
+
const factor = 1 - i * (1 / steps);
|
|
2154
|
+
shades.push({
|
|
2155
|
+
r: Math.round(rgb.r * factor),
|
|
2156
|
+
g: Math.round(rgb.g * factor),
|
|
2157
|
+
b: Math.round(rgb.b * factor)
|
|
2158
|
+
});
|
|
2159
|
+
}
|
|
2160
|
+
return shades;
|
|
2161
|
+
}
|
|
2162
|
+
function generateTints(rgb, steps = 10) {
|
|
2163
|
+
const tints = [];
|
|
2164
|
+
for (let i = 0; i < steps; i++) {
|
|
2165
|
+
const factor = i * (1 / steps);
|
|
2166
|
+
tints.push({
|
|
2167
|
+
r: Math.round(rgb.r + (255 - rgb.r) * factor),
|
|
2168
|
+
g: Math.round(rgb.g + (255 - rgb.g) * factor),
|
|
2169
|
+
b: Math.round(rgb.b + (255 - rgb.b) * factor)
|
|
2170
|
+
});
|
|
2171
|
+
}
|
|
2172
|
+
return tints;
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
// src/commands/creative/color.ts
|
|
2176
|
+
registerCommand({
|
|
2177
|
+
name: "color",
|
|
2178
|
+
description: "Convert and preview colors",
|
|
2179
|
+
usage: "/color <hex|rgb|random>",
|
|
2180
|
+
async execute(args2) {
|
|
2181
|
+
const input = args2.join(" ");
|
|
2182
|
+
const result = parseColor(input);
|
|
2183
|
+
if (isColorError(result)) {
|
|
2184
|
+
console.log("");
|
|
2185
|
+
console.log(theme.error("Could not parse color. Examples:"));
|
|
2186
|
+
console.log(theme.dim(" /color #FF5733"));
|
|
2187
|
+
console.log(theme.dim(" /color rgb(255, 87, 51)"));
|
|
2188
|
+
console.log(theme.dim(" /color 255 87 51"));
|
|
2189
|
+
console.log(theme.dim(" /color random"));
|
|
2190
|
+
console.log("");
|
|
2191
|
+
return;
|
|
2192
|
+
}
|
|
2193
|
+
const { rgb, hex, hsl, textColor } = result;
|
|
2194
|
+
console.log("");
|
|
2195
|
+
console.log(` ${symbols.palette} ${theme.gradient("COLOR CONVERTER")} ${symbols.palette}`);
|
|
2196
|
+
console.log("");
|
|
2197
|
+
const colorBlock = chalk2.bgHex(hex).hex(textColor);
|
|
2198
|
+
console.log(` ${colorBlock(" ")}`);
|
|
2199
|
+
console.log(` ${colorBlock(" ")}`);
|
|
2200
|
+
console.log(` ${colorBlock(` ${hex.toUpperCase()} `.slice(0, 40))}`);
|
|
2201
|
+
console.log(` ${colorBlock(" ")}`);
|
|
2202
|
+
console.log(` ${colorBlock(" ")}`);
|
|
2203
|
+
console.log("");
|
|
2204
|
+
console.log(` ${theme.dim("HEX:")} ${theme.primary(hex.toUpperCase())}`);
|
|
2205
|
+
console.log(` ${theme.dim("RGB:")} ${theme.primary(`rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`)}`);
|
|
2206
|
+
console.log(` ${theme.dim("HSL:")} ${theme.primary(`hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)`)}`);
|
|
2207
|
+
console.log("");
|
|
2208
|
+
console.log(` ${theme.dim("Shades:")}`);
|
|
2209
|
+
let shadesLine = " ";
|
|
2210
|
+
const shades = generateShades(rgb);
|
|
2211
|
+
for (const shade of shades) {
|
|
2212
|
+
shadesLine += chalk2.bgRgb(shade.r, shade.g, shade.b)(" ");
|
|
2213
|
+
}
|
|
2214
|
+
console.log(shadesLine);
|
|
2215
|
+
console.log(` ${theme.dim("Tints:")}`);
|
|
2216
|
+
let tintsLine = " ";
|
|
2217
|
+
const tints = generateTints(rgb);
|
|
2218
|
+
for (const tint of tints) {
|
|
2219
|
+
tintsLine += chalk2.bgRgb(tint.r, tint.g, tint.b)(" ");
|
|
2220
|
+
}
|
|
2221
|
+
console.log(tintsLine);
|
|
2222
|
+
console.log("");
|
|
2223
|
+
}
|
|
2224
|
+
});
|
|
2225
|
+
|
|
2226
|
+
// src/handlers/dev/hash.ts
|
|
2227
|
+
import { createHash } from "crypto";
|
|
2228
|
+
var SUPPORTED_ALGORITHMS = ["md5", "sha1", "sha256", "sha512"];
|
|
2229
|
+
function isValidAlgorithm(algo) {
|
|
2230
|
+
return SUPPORTED_ALGORITHMS.includes(algo.toLowerCase());
|
|
2231
|
+
}
|
|
2232
|
+
function computeHash(text, algorithm = "sha256") {
|
|
2233
|
+
const hash = createHash(algorithm).update(text).digest("hex");
|
|
2234
|
+
return {
|
|
2235
|
+
algorithm: algorithm.toUpperCase(),
|
|
2236
|
+
input: text,
|
|
2237
|
+
hash
|
|
2238
|
+
};
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
// src/commands/dev/hash.ts
|
|
2242
|
+
registerCommand({
|
|
2243
|
+
name: "hash",
|
|
2244
|
+
description: "Hash text with various algorithms",
|
|
2245
|
+
usage: "/hash <algorithm> <text> or /hash <text>",
|
|
2246
|
+
async execute(args2) {
|
|
2247
|
+
if (args2.length === 0) {
|
|
2248
|
+
console.log("");
|
|
2249
|
+
console.log(theme.error("Usage: /hash <algorithm> <text>"));
|
|
2250
|
+
console.log(theme.dim(` Algorithms: ${SUPPORTED_ALGORITHMS.join(", ")}`));
|
|
2251
|
+
console.log(theme.dim(" Example: /hash sha256 hello world"));
|
|
2252
|
+
console.log(theme.dim(" Default: /hash <text> uses sha256"));
|
|
2253
|
+
console.log("");
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2256
|
+
let algorithm = "sha256";
|
|
2257
|
+
let text;
|
|
2258
|
+
if (isValidAlgorithm(args2[0])) {
|
|
2259
|
+
algorithm = args2[0].toLowerCase();
|
|
2260
|
+
text = args2.slice(1).join(" ");
|
|
2261
|
+
} else {
|
|
2262
|
+
text = args2.join(" ");
|
|
2263
|
+
}
|
|
2264
|
+
if (!text) {
|
|
2265
|
+
console.log(theme.error("Please provide text to hash"));
|
|
2266
|
+
return;
|
|
2267
|
+
}
|
|
2268
|
+
const result = computeHash(text, algorithm);
|
|
2269
|
+
console.log("");
|
|
2270
|
+
console.log(box.draw([
|
|
2271
|
+
"",
|
|
2272
|
+
` ${symbols.lock} ${theme.gradient("HASH RESULT")}`,
|
|
2273
|
+
"",
|
|
2274
|
+
` ${theme.dim("Algorithm:")} ${theme.primary(result.algorithm)}`,
|
|
2275
|
+
` ${theme.dim("Input:")} ${theme.secondary(result.input.length > 30 ? result.input.slice(0, 30) + "..." : result.input)}`,
|
|
2276
|
+
"",
|
|
2277
|
+
` ${theme.dim("Hash:")}`,
|
|
2278
|
+
` ${theme.success(result.hash)}`,
|
|
2279
|
+
""
|
|
2280
|
+
], 70));
|
|
2281
|
+
console.log("");
|
|
2282
|
+
}
|
|
2283
|
+
});
|
|
2284
|
+
|
|
2285
|
+
// src/handlers/dev/uuid.ts
|
|
2286
|
+
import { randomUUID } from "crypto";
|
|
2287
|
+
function generateUuids(count = 1) {
|
|
2288
|
+
const safeCount = Math.min(Math.max(1, count), 10);
|
|
2289
|
+
const uuids = [];
|
|
2290
|
+
for (let i = 0; i < safeCount; i++) {
|
|
2291
|
+
uuids.push(randomUUID());
|
|
2292
|
+
}
|
|
2293
|
+
return {
|
|
2294
|
+
uuids,
|
|
2295
|
+
count: safeCount
|
|
2296
|
+
};
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
// src/commands/dev/uuid.ts
|
|
2300
|
+
registerCommand({
|
|
2301
|
+
name: "uuid",
|
|
2302
|
+
description: "Generate UUID(s)",
|
|
2303
|
+
usage: "/uuid [count]",
|
|
2304
|
+
async execute(args2) {
|
|
2305
|
+
const count = parseInt(args2[0]) || 1;
|
|
2306
|
+
const result = generateUuids(count);
|
|
2307
|
+
console.log("");
|
|
2308
|
+
console.log(` ${symbols.sparkle} ${theme.gradient("UUID GENERATOR")} ${symbols.sparkle}`);
|
|
2309
|
+
console.log("");
|
|
2310
|
+
result.uuids.forEach((uuid, i) => {
|
|
2311
|
+
console.log(` ${theme.dim(`${i + 1}.`)} ${theme.primary(uuid)}`);
|
|
2312
|
+
});
|
|
2313
|
+
console.log("");
|
|
2314
|
+
if (result.count === 1) {
|
|
2315
|
+
console.log(theme.dim(" Tip: /uuid 5 generates 5 UUIDs"));
|
|
2316
|
+
}
|
|
2317
|
+
console.log("");
|
|
2318
|
+
}
|
|
2319
|
+
});
|
|
2320
|
+
|
|
2321
|
+
// src/handlers/dev/encode.ts
|
|
2322
|
+
var SUPPORTED_METHODS = ["base64", "url", "hex"];
|
|
2323
|
+
function isValidMethod(method) {
|
|
2324
|
+
return SUPPORTED_METHODS.includes(method.toLowerCase());
|
|
2325
|
+
}
|
|
2326
|
+
function encodeText(text, method, direction) {
|
|
2327
|
+
let output;
|
|
2328
|
+
if (direction === "encode") {
|
|
2329
|
+
switch (method) {
|
|
2330
|
+
case "base64":
|
|
2331
|
+
output = Buffer.from(text).toString("base64");
|
|
2332
|
+
break;
|
|
2333
|
+
case "url":
|
|
2334
|
+
output = encodeURIComponent(text);
|
|
2335
|
+
break;
|
|
2336
|
+
case "hex":
|
|
2337
|
+
output = Buffer.from(text).toString("hex");
|
|
2338
|
+
break;
|
|
2339
|
+
}
|
|
2340
|
+
} else {
|
|
2341
|
+
switch (method) {
|
|
2342
|
+
case "base64":
|
|
2343
|
+
output = Buffer.from(text, "base64").toString("utf-8");
|
|
2344
|
+
break;
|
|
2345
|
+
case "url":
|
|
2346
|
+
output = decodeURIComponent(text);
|
|
2347
|
+
break;
|
|
2348
|
+
case "hex":
|
|
2349
|
+
output = Buffer.from(text, "hex").toString("utf-8");
|
|
2350
|
+
break;
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
return {
|
|
2354
|
+
method: method.toUpperCase(),
|
|
2355
|
+
direction,
|
|
2356
|
+
input: text,
|
|
2357
|
+
output
|
|
2358
|
+
};
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
// src/commands/dev/encode.ts
|
|
2362
|
+
registerCommand({
|
|
2363
|
+
name: "encode",
|
|
2364
|
+
description: "Encode/decode text (base64, url, hex)",
|
|
2365
|
+
usage: "/encode <method> <encode|decode> <text>",
|
|
2366
|
+
async execute(args2) {
|
|
2367
|
+
if (args2.length < 2) {
|
|
2368
|
+
console.log("");
|
|
2369
|
+
console.log(theme.error("Usage: /encode <method> <encode|decode> <text>"));
|
|
2370
|
+
console.log(theme.dim(` Methods: ${SUPPORTED_METHODS.join(", ")}`));
|
|
2371
|
+
console.log(theme.dim(" Example: /encode base64 encode hello"));
|
|
2372
|
+
console.log(theme.dim(" Example: /encode url decode hello%20world"));
|
|
2373
|
+
console.log("");
|
|
2374
|
+
return;
|
|
2375
|
+
}
|
|
2376
|
+
const method = args2[0].toLowerCase();
|
|
2377
|
+
const action = args2[1].toLowerCase();
|
|
2378
|
+
const text = args2.slice(2).join(" ");
|
|
2379
|
+
if (!isValidMethod(method)) {
|
|
2380
|
+
console.log(theme.error(`Unknown method: ${method}. Use ${SUPPORTED_METHODS.join(", ")}`));
|
|
2381
|
+
return;
|
|
2382
|
+
}
|
|
2383
|
+
if (action !== "encode" && action !== "decode") {
|
|
2384
|
+
console.log(theme.error(`Unknown action: ${action}. Use encode or decode`));
|
|
2385
|
+
return;
|
|
2386
|
+
}
|
|
2387
|
+
if (!text) {
|
|
2388
|
+
console.log(theme.error("Please provide text to encode/decode"));
|
|
2389
|
+
return;
|
|
2390
|
+
}
|
|
2391
|
+
try {
|
|
2392
|
+
const result = encodeText(text, method, action);
|
|
2393
|
+
console.log("");
|
|
2394
|
+
console.log(box.draw([
|
|
2395
|
+
"",
|
|
2396
|
+
` ${symbols.sparkle} ${theme.gradient(result.method + " " + result.direction.toUpperCase())}`,
|
|
2397
|
+
"",
|
|
2398
|
+
` ${theme.dim("Input:")}`,
|
|
2399
|
+
` ${theme.secondary(result.input.length > 50 ? result.input.slice(0, 50) + "..." : result.input)}`,
|
|
2400
|
+
"",
|
|
2401
|
+
` ${theme.dim("Output:")}`,
|
|
2402
|
+
` ${theme.success(result.output.length > 50 ? result.output.slice(0, 50) + "..." : result.output)}`,
|
|
2403
|
+
"",
|
|
2404
|
+
...result.output.length > 50 ? [` ${theme.dim("(Full output: " + result.output.length + " chars)")}`] : [],
|
|
2405
|
+
""
|
|
2406
|
+
], 60));
|
|
2407
|
+
console.log("");
|
|
2408
|
+
if (result.output.length > 50) {
|
|
2409
|
+
console.log(theme.dim(" Full result:"));
|
|
2410
|
+
console.log(` ${result.output}`);
|
|
2411
|
+
console.log("");
|
|
2412
|
+
}
|
|
2413
|
+
} catch (e) {
|
|
2414
|
+
console.log(theme.error(`Failed to ${action}: Invalid input for ${method}`));
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
});
|
|
2418
|
+
|
|
2419
|
+
// src/commands/info/weather.ts
|
|
2420
|
+
registerCommand({
|
|
2421
|
+
name: "weather",
|
|
2422
|
+
description: "Get current weather for a city",
|
|
2423
|
+
usage: "/weather <city>",
|
|
2424
|
+
async execute(args2) {
|
|
2425
|
+
const city = args2.join(" ") || "London";
|
|
2426
|
+
console.log(theme.dim(`Fetching weather for ${city}...`));
|
|
2427
|
+
try {
|
|
2428
|
+
const response = await fetch(`https://wttr.in/${encodeURIComponent(city)}?format=j1`);
|
|
2429
|
+
if (!response.ok) {
|
|
2430
|
+
console.log(theme.error(`Could not fetch weather for "${city}"`));
|
|
2431
|
+
return;
|
|
2432
|
+
}
|
|
2433
|
+
const data = await response.json();
|
|
2434
|
+
const current = data.current_condition[0];
|
|
2435
|
+
const location = data.nearest_area[0];
|
|
2436
|
+
const temp = current.temp_C;
|
|
2437
|
+
const feelsLike = current.FeelsLikeC;
|
|
2438
|
+
const desc = current.weatherDesc[0].value;
|
|
2439
|
+
const humidity = current.humidity;
|
|
2440
|
+
const wind = current.windspeedKmph;
|
|
2441
|
+
const cityName = location.areaName[0].value;
|
|
2442
|
+
const country = location.country[0].value;
|
|
2443
|
+
console.log("");
|
|
2444
|
+
console.log(theme.highlight(`${cityName}, ${country}`));
|
|
2445
|
+
console.log("");
|
|
2446
|
+
console.log(` ${theme.primary(desc)}`);
|
|
2447
|
+
console.log(` Temperature: ${theme.warning(temp + "\xB0C")} (feels like ${feelsLike}\xB0C)`);
|
|
2448
|
+
console.log(` Humidity: ${humidity}%`);
|
|
2449
|
+
console.log(` Wind: ${wind} km/h`);
|
|
2450
|
+
console.log("");
|
|
2451
|
+
} catch (error) {
|
|
2452
|
+
console.log(theme.error(`Error fetching weather: ${error}`));
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
});
|
|
2456
|
+
|
|
2457
|
+
// src/cli.ts
|
|
2458
|
+
import { exec, execSync, spawn } from "child_process";
|
|
2459
|
+
import { existsSync as existsSync4, statSync, readFileSync as readFileSync3, readdirSync, writeFileSync as writeFileSync3, watchFile, unwatchFile } from "fs";
|
|
2460
|
+
import { resolve as resolve2, extname, basename, join as join3 } from "path";
|
|
2461
|
+
import { homedir as homedir3, platform as platform2, networkInterfaces } from "os";
|
|
2462
|
+
import chalk3 from "chalk";
|
|
2463
|
+
var isWindows = platform2() === "win32";
|
|
2464
|
+
function translateCommand(cmd) {
|
|
2465
|
+
if (!isWindows) return cmd;
|
|
2466
|
+
const parts = cmd.trim().split(/\s+/);
|
|
2467
|
+
const command = parts[0].toLowerCase();
|
|
2468
|
+
const args2 = parts.slice(1);
|
|
2469
|
+
switch (command) {
|
|
2470
|
+
case "ls": {
|
|
2471
|
+
const hasAll = args2.some((a) => a === "-a" || a === "-la" || a === "-al" || a === "-all");
|
|
2472
|
+
const hasLong = args2.some((a) => a === "-l" || a === "-la" || a === "-al");
|
|
2473
|
+
const pathArgs = args2.filter((a) => !a.startsWith("-"));
|
|
2474
|
+
let winCmd = "dir";
|
|
2475
|
+
if (hasAll) winCmd += " /a";
|
|
2476
|
+
if (pathArgs.length > 0) winCmd += " " + pathArgs.join(" ");
|
|
2477
|
+
return winCmd;
|
|
2478
|
+
}
|
|
2479
|
+
case "cat":
|
|
2480
|
+
return "type " + args2.join(" ");
|
|
2481
|
+
case "clear":
|
|
2482
|
+
return "cls";
|
|
2483
|
+
case "rm": {
|
|
2484
|
+
const hasRecursive = args2.some((a) => a === "-r" || a === "-rf" || a === "-fr");
|
|
2485
|
+
const hasForce = args2.some((a) => a === "-f" || a === "-rf" || a === "-fr");
|
|
2486
|
+
const pathArgs = args2.filter((a) => !a.startsWith("-"));
|
|
2487
|
+
if (hasRecursive) {
|
|
2488
|
+
return "rmdir /s" + (hasForce ? " /q" : "") + " " + pathArgs.join(" ");
|
|
2489
|
+
}
|
|
2490
|
+
return "del" + (hasForce ? " /f" : "") + " " + pathArgs.join(" ");
|
|
2491
|
+
}
|
|
2492
|
+
case "cp": {
|
|
2493
|
+
const hasRecursive = args2.some((a) => a === "-r" || a === "-R");
|
|
2494
|
+
const pathArgs = args2.filter((a) => !a.startsWith("-"));
|
|
2495
|
+
if (hasRecursive) {
|
|
2496
|
+
return "xcopy /e /i " + pathArgs.join(" ");
|
|
2497
|
+
}
|
|
2498
|
+
return "copy " + pathArgs.join(" ");
|
|
2499
|
+
}
|
|
2500
|
+
case "mv":
|
|
2501
|
+
return "move " + args2.join(" ");
|
|
2502
|
+
case "touch":
|
|
2503
|
+
return "type nul > " + args2.join(" ");
|
|
2504
|
+
case "grep":
|
|
2505
|
+
return "findstr " + args2.join(" ");
|
|
2506
|
+
case "which":
|
|
2507
|
+
return "where " + args2.join(" ");
|
|
2508
|
+
case "echo":
|
|
2509
|
+
return "echo " + args2.join(" ");
|
|
2510
|
+
default:
|
|
2511
|
+
return cmd;
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
function handleCd(args2) {
|
|
2515
|
+
let targetPath = args2.trim();
|
|
2516
|
+
if (!targetPath || targetPath === "~") {
|
|
2517
|
+
targetPath = homedir3();
|
|
2518
|
+
} else if (targetPath.startsWith("~/")) {
|
|
2519
|
+
targetPath = resolve2(homedir3(), targetPath.slice(2));
|
|
2520
|
+
} else if (targetPath === "-") {
|
|
2521
|
+
console.log(theme.dim(process.cwd()));
|
|
2522
|
+
return;
|
|
2523
|
+
} else {
|
|
2524
|
+
targetPath = resolve2(process.cwd(), targetPath);
|
|
2525
|
+
}
|
|
2526
|
+
if (!existsSync4(targetPath)) {
|
|
2527
|
+
console.log(theme.error(`Directory not found: ${targetPath}`));
|
|
2528
|
+
return;
|
|
2529
|
+
}
|
|
2530
|
+
try {
|
|
2531
|
+
const stats = statSync(targetPath);
|
|
2532
|
+
if (!stats.isDirectory()) {
|
|
2533
|
+
console.log(theme.error(`Not a directory: ${targetPath}`));
|
|
2534
|
+
return;
|
|
2535
|
+
}
|
|
2536
|
+
process.chdir(targetPath);
|
|
2537
|
+
console.log(theme.dim(process.cwd()));
|
|
2538
|
+
} catch (error) {
|
|
2539
|
+
console.log(theme.error(`Cannot access: ${targetPath}`));
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
function handlePwd() {
|
|
2543
|
+
console.log(theme.primary(process.cwd()));
|
|
2544
|
+
}
|
|
2545
|
+
function handleCat(args2) {
|
|
2546
|
+
const filePath = resolve2(process.cwd(), args2.trim());
|
|
2547
|
+
if (!existsSync4(filePath)) {
|
|
2548
|
+
console.log(theme.error(`File not found: ${args2}`));
|
|
2549
|
+
return;
|
|
2550
|
+
}
|
|
2551
|
+
try {
|
|
2552
|
+
const content = readFileSync3(filePath, "utf-8");
|
|
2553
|
+
const ext = extname(filePath).toLowerCase();
|
|
2554
|
+
if ([".js", ".ts", ".jsx", ".tsx", ".json", ".css", ".html", ".py", ".go", ".rs"].includes(ext)) {
|
|
2555
|
+
console.log(highlightSyntax(content, ext));
|
|
2556
|
+
} else {
|
|
2557
|
+
console.log(content);
|
|
2558
|
+
}
|
|
2559
|
+
} catch (error) {
|
|
2560
|
+
console.log(theme.error(`Cannot read file: ${args2}`));
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
function highlightSyntax(content, ext) {
|
|
2564
|
+
const keywords = {
|
|
2565
|
+
js: ["const", "let", "var", "function", "return", "if", "else", "for", "while", "class", "import", "export", "from", "async", "await", "try", "catch", "throw", "new", "this", "true", "false", "null", "undefined"],
|
|
2566
|
+
py: ["def", "class", "import", "from", "return", "if", "elif", "else", "for", "while", "try", "except", "with", "as", "True", "False", "None", "and", "or", "not", "in", "is", "lambda", "async", "await"]
|
|
2567
|
+
};
|
|
2568
|
+
const kw = ext === ".py" ? keywords.py : keywords.js;
|
|
2569
|
+
return content.split("\n").map((line) => {
|
|
2570
|
+
line = line.replace(/(["'`])(?:(?!\1)[^\\]|\\.)*?\1/g, (match) => chalk3.hex("#98C379")(match));
|
|
2571
|
+
line = line.replace(/(\/\/.*$|#.*$)/g, (match) => chalk3.hex("#5C6370")(match));
|
|
2572
|
+
line = line.replace(/\b(\d+\.?\d*)\b/g, (match) => chalk3.hex("#D19A66")(match));
|
|
2573
|
+
kw.forEach((keyword) => {
|
|
2574
|
+
const regex = new RegExp(`\\b(${keyword})\\b`, "g");
|
|
2575
|
+
line = line.replace(regex, chalk3.hex("#C678DD")(keyword));
|
|
2576
|
+
});
|
|
2577
|
+
return line;
|
|
2578
|
+
}).join("\n");
|
|
2579
|
+
}
|
|
2580
|
+
var fileIcons = {
|
|
2581
|
+
// Folders
|
|
2582
|
+
"dir": { icon: "\u{1F4C1}", color: "#61AFEF" },
|
|
2583
|
+
// Code
|
|
2584
|
+
".js": { icon: "\u{1F4DC}", color: "#F7DF1E" },
|
|
2585
|
+
".ts": { icon: "\u{1F4D8}", color: "#3178C6" },
|
|
2586
|
+
".jsx": { icon: "\u269B\uFE0F ", color: "#61DAFB" },
|
|
2587
|
+
".tsx": { icon: "\u269B\uFE0F ", color: "#3178C6" },
|
|
2588
|
+
".py": { icon: "\u{1F40D}", color: "#3776AB" },
|
|
2589
|
+
".go": { icon: "\u{1F439}", color: "#00ADD8" },
|
|
2590
|
+
".rs": { icon: "\u{1F980}", color: "#DEA584" },
|
|
2591
|
+
".java": { icon: "\u2615", color: "#ED8B00" },
|
|
2592
|
+
".cpp": { icon: "\u2699\uFE0F ", color: "#00599C" },
|
|
2593
|
+
".c": { icon: "\u2699\uFE0F ", color: "#A8B9CC" },
|
|
2594
|
+
".cs": { icon: "\u{1F3AF}", color: "#239120" },
|
|
2595
|
+
".rb": { icon: "\u{1F48E}", color: "#CC342D" },
|
|
2596
|
+
".php": { icon: "\u{1F418}", color: "#777BB4" },
|
|
2597
|
+
".swift": { icon: "\u{1F54A}\uFE0F ", color: "#FA7343" },
|
|
2598
|
+
".kt": { icon: "\u{1F3AF}", color: "#7F52FF" },
|
|
2599
|
+
// Web
|
|
2600
|
+
".html": { icon: "\u{1F310}", color: "#E34F26" },
|
|
2601
|
+
".css": { icon: "\u{1F3A8}", color: "#1572B6" },
|
|
2602
|
+
".scss": { icon: "\u{1F3A8}", color: "#CC6699" },
|
|
2603
|
+
".vue": { icon: "\u{1F49A}", color: "#4FC08D" },
|
|
2604
|
+
".svelte": { icon: "\u{1F525}", color: "#FF3E00" },
|
|
2605
|
+
// Data
|
|
2606
|
+
".json": { icon: "\u{1F4CB}", color: "#CBCB41" },
|
|
2607
|
+
".yaml": { icon: "\u{1F4CB}", color: "#CB171E" },
|
|
2608
|
+
".yml": { icon: "\u{1F4CB}", color: "#CB171E" },
|
|
2609
|
+
".xml": { icon: "\u{1F4CB}", color: "#E34F26" },
|
|
2610
|
+
".csv": { icon: "\u{1F4CA}", color: "#217346" },
|
|
2611
|
+
// Config
|
|
2612
|
+
".env": { icon: "\u{1F510}", color: "#ECD53F" },
|
|
2613
|
+
".gitignore": { icon: "\u{1F648}", color: "#F05032" },
|
|
2614
|
+
".dockerignore": { icon: "\u{1F433}", color: "#2496ED" },
|
|
2615
|
+
// Docs
|
|
2616
|
+
".md": { icon: "\u{1F4DD}", color: "#083FA1" },
|
|
2617
|
+
".txt": { icon: "\u{1F4C4}", color: "#6C7A89" },
|
|
2618
|
+
".pdf": { icon: "\u{1F4D5}", color: "#FF0000" },
|
|
2619
|
+
// Images
|
|
2620
|
+
".png": { icon: "\u{1F5BC}\uFE0F ", color: "#FFB13B" },
|
|
2621
|
+
".jpg": { icon: "\u{1F5BC}\uFE0F ", color: "#FFB13B" },
|
|
2622
|
+
".jpeg": { icon: "\u{1F5BC}\uFE0F ", color: "#FFB13B" },
|
|
2623
|
+
".gif": { icon: "\u{1F5BC}\uFE0F ", color: "#FFB13B" },
|
|
2624
|
+
".svg": { icon: "\u{1F3A8}", color: "#FFB13B" },
|
|
2625
|
+
".ico": { icon: "\u{1F5BC}\uFE0F ", color: "#FFB13B" },
|
|
2626
|
+
// Packages
|
|
2627
|
+
".zip": { icon: "\u{1F4E6}", color: "#FFC107" },
|
|
2628
|
+
".tar": { icon: "\u{1F4E6}", color: "#FFC107" },
|
|
2629
|
+
".gz": { icon: "\u{1F4E6}", color: "#FFC107" },
|
|
2630
|
+
".rar": { icon: "\u{1F4E6}", color: "#FFC107" },
|
|
2631
|
+
// Executables
|
|
2632
|
+
".exe": { icon: "\u26A1", color: "#00A4EF" },
|
|
2633
|
+
".sh": { icon: "\u26A1", color: "#4EAA25" },
|
|
2634
|
+
".bat": { icon: "\u26A1", color: "#C1F12E" },
|
|
2635
|
+
".cmd": { icon: "\u26A1", color: "#C1F12E" },
|
|
2636
|
+
// Default
|
|
2637
|
+
"default": { icon: "\u{1F4C4}", color: "#6C7A89" }
|
|
2638
|
+
};
|
|
2639
|
+
function formatSize(bytes) {
|
|
2640
|
+
if (bytes === 0) return chalk3.dim(" 0 B");
|
|
2641
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
2642
|
+
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
2643
|
+
const size = (bytes / Math.pow(1024, i)).toFixed(i > 0 ? 1 : 0);
|
|
2644
|
+
return chalk3.hex("#98C379")(size.padStart(5) + " " + units[i].padEnd(2));
|
|
2645
|
+
}
|
|
2646
|
+
function handleLs(args2) {
|
|
2647
|
+
const parts = args2.trim().split(/\s+/).filter(Boolean);
|
|
2648
|
+
const showAll = parts.some((a) => a === "-a" || a === "-la" || a === "-al");
|
|
2649
|
+
const showLong = parts.some((a) => a === "-l" || a === "-la" || a === "-al");
|
|
2650
|
+
const pathArgs = parts.filter((a) => !a.startsWith("-"));
|
|
2651
|
+
const targetPath = pathArgs.length > 0 ? resolve2(process.cwd(), pathArgs[0]) : process.cwd();
|
|
2652
|
+
if (!existsSync4(targetPath)) {
|
|
2653
|
+
console.log(theme.error(`Directory not found: ${targetPath}`));
|
|
2654
|
+
return;
|
|
2655
|
+
}
|
|
2656
|
+
try {
|
|
2657
|
+
const entries = readdirSync(targetPath, { withFileTypes: true });
|
|
2658
|
+
const filtered = showAll ? entries : entries.filter((e) => !e.name.startsWith("."));
|
|
2659
|
+
console.log("");
|
|
2660
|
+
console.log(theme.dim(` ${targetPath}`));
|
|
2661
|
+
console.log("");
|
|
2662
|
+
if (filtered.length === 0) {
|
|
2663
|
+
console.log(theme.dim(" (empty directory)"));
|
|
2664
|
+
console.log("");
|
|
2665
|
+
return;
|
|
2666
|
+
}
|
|
2667
|
+
const sorted = filtered.sort((a, b) => {
|
|
2668
|
+
if (a.isDirectory() && !b.isDirectory()) return -1;
|
|
2669
|
+
if (!a.isDirectory() && b.isDirectory()) return 1;
|
|
2670
|
+
return a.name.localeCompare(b.name);
|
|
2671
|
+
});
|
|
2672
|
+
for (const entry of sorted) {
|
|
2673
|
+
const fullPath = resolve2(targetPath, entry.name);
|
|
2674
|
+
const isDir = entry.isDirectory();
|
|
2675
|
+
const ext = isDir ? "dir" : extname(entry.name).toLowerCase();
|
|
2676
|
+
const iconInfo = fileIcons[ext] || fileIcons["default"];
|
|
2677
|
+
let line = ` ${iconInfo.icon} `;
|
|
2678
|
+
if (showLong) {
|
|
2679
|
+
try {
|
|
2680
|
+
const stats = statSync(fullPath);
|
|
2681
|
+
const size = isDir ? chalk3.dim(" <DIR>") : formatSize(stats.size);
|
|
2682
|
+
const date = stats.mtime.toLocaleDateString("en-US", { month: "short", day: "2-digit", year: "numeric" });
|
|
2683
|
+
line += chalk3.dim(date.padEnd(13)) + size + " ";
|
|
2684
|
+
} catch {
|
|
2685
|
+
line += chalk3.dim(" ") + " ";
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
const name = isDir ? chalk3.hex(iconInfo.color).bold(entry.name + "/") : chalk3.hex(iconInfo.color)(entry.name);
|
|
2689
|
+
line += name;
|
|
2690
|
+
console.log(line);
|
|
2691
|
+
}
|
|
2692
|
+
console.log("");
|
|
2693
|
+
console.log(theme.dim(` ${sorted.filter((e) => e.isDirectory()).length} directories, ${sorted.filter((e) => !e.isDirectory()).length} files`));
|
|
2694
|
+
console.log("");
|
|
2695
|
+
} catch (error) {
|
|
2696
|
+
console.log(theme.error(`Cannot read directory: ${targetPath}`));
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
function handleTree(args2, maxDepth = 3) {
|
|
2700
|
+
const parts = args2.trim().split(/\s+/).filter(Boolean);
|
|
2701
|
+
const pathArgs = parts.filter((a) => !a.startsWith("-"));
|
|
2702
|
+
const targetPath = pathArgs.length > 0 ? resolve2(process.cwd(), pathArgs[0]) : process.cwd();
|
|
2703
|
+
if (!existsSync4(targetPath)) {
|
|
2704
|
+
console.log(theme.error(`Directory not found: ${targetPath}`));
|
|
2705
|
+
return;
|
|
2706
|
+
}
|
|
2707
|
+
console.log("");
|
|
2708
|
+
console.log(theme.primary(` \u{1F4C1} ${basename(targetPath)}/`));
|
|
2709
|
+
let dirCount = 0;
|
|
2710
|
+
let fileCount = 0;
|
|
2711
|
+
function printTree(dir, prefix, depth) {
|
|
2712
|
+
if (depth > maxDepth) return;
|
|
2713
|
+
try {
|
|
2714
|
+
const entries = readdirSync(dir, { withFileTypes: true }).filter((e) => !e.name.startsWith(".") && !["node_modules", ".git", "dist", "build"].includes(e.name)).sort((a, b) => {
|
|
2715
|
+
if (a.isDirectory() && !b.isDirectory()) return -1;
|
|
2716
|
+
if (!a.isDirectory() && b.isDirectory()) return 1;
|
|
2717
|
+
return a.name.localeCompare(b.name);
|
|
2718
|
+
});
|
|
2719
|
+
entries.forEach((entry, index) => {
|
|
2720
|
+
const isLast = index === entries.length - 1;
|
|
2721
|
+
const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
2722
|
+
const ext = entry.isDirectory() ? "dir" : extname(entry.name).toLowerCase();
|
|
2723
|
+
const iconInfo = fileIcons[ext] || fileIcons["default"];
|
|
2724
|
+
if (entry.isDirectory()) {
|
|
2725
|
+
dirCount++;
|
|
2726
|
+
console.log(theme.dim(prefix + connector) + iconInfo.icon + " " + chalk3.hex(iconInfo.color).bold(entry.name + "/"));
|
|
2727
|
+
printTree(resolve2(dir, entry.name), prefix + (isLast ? " " : "\u2502 "), depth + 1);
|
|
2728
|
+
} else {
|
|
2729
|
+
fileCount++;
|
|
2730
|
+
console.log(theme.dim(prefix + connector) + iconInfo.icon + " " + chalk3.hex(iconInfo.color)(entry.name));
|
|
2731
|
+
}
|
|
2732
|
+
});
|
|
2733
|
+
} catch {
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
printTree(targetPath, " ", 1);
|
|
2737
|
+
console.log("");
|
|
2738
|
+
console.log(theme.dim(` ${dirCount} directories, ${fileCount} files`));
|
|
2739
|
+
console.log("");
|
|
2740
|
+
}
|
|
2741
|
+
var bookmarksFile = join3(homedir3(), ".zammy-bookmarks.json");
|
|
2742
|
+
function loadBookmarks() {
|
|
2743
|
+
try {
|
|
2744
|
+
if (existsSync4(bookmarksFile)) {
|
|
2745
|
+
return JSON.parse(readFileSync3(bookmarksFile, "utf-8"));
|
|
2746
|
+
}
|
|
2747
|
+
} catch {
|
|
2748
|
+
}
|
|
2749
|
+
return {};
|
|
2750
|
+
}
|
|
2751
|
+
function saveBookmarks(bookmarks) {
|
|
2752
|
+
writeFileSync3(bookmarksFile, JSON.stringify(bookmarks, null, 2));
|
|
2753
|
+
}
|
|
2754
|
+
function handleBookmark(args2) {
|
|
2755
|
+
const parts = args2.trim().split(/\s+/);
|
|
2756
|
+
const action = parts[0]?.toLowerCase();
|
|
2757
|
+
const name = parts[1];
|
|
2758
|
+
const bookmarks = loadBookmarks();
|
|
2759
|
+
console.log("");
|
|
2760
|
+
switch (action) {
|
|
2761
|
+
case "save":
|
|
2762
|
+
case "add":
|
|
2763
|
+
if (!name) {
|
|
2764
|
+
console.log(theme.error(" Usage: bookmark save <name>"));
|
|
2765
|
+
break;
|
|
2766
|
+
}
|
|
2767
|
+
bookmarks[name] = process.cwd();
|
|
2768
|
+
saveBookmarks(bookmarks);
|
|
2769
|
+
console.log(` ${symbols.check} ${theme.success("Saved bookmark:")} ${theme.primary(name)} ${theme.dim("\u2192")} ${process.cwd()}`);
|
|
2770
|
+
break;
|
|
2771
|
+
case "go":
|
|
2772
|
+
case "cd":
|
|
2773
|
+
if (!name || !bookmarks[name]) {
|
|
2774
|
+
console.log(theme.error(` Bookmark not found: ${name}`));
|
|
2775
|
+
console.log(theme.dim(' Use "bookmark list" to see all bookmarks'));
|
|
2776
|
+
break;
|
|
2777
|
+
}
|
|
2778
|
+
try {
|
|
2779
|
+
process.chdir(bookmarks[name]);
|
|
2780
|
+
console.log(` ${symbols.check} ${theme.dim("Jumped to")} ${theme.primary(name)}`);
|
|
2781
|
+
console.log(` ${theme.dim(process.cwd())}`);
|
|
2782
|
+
} catch {
|
|
2783
|
+
console.log(theme.error(` Cannot access: ${bookmarks[name]}`));
|
|
2784
|
+
}
|
|
2785
|
+
break;
|
|
2786
|
+
case "del":
|
|
2787
|
+
case "delete":
|
|
2788
|
+
case "rm":
|
|
2789
|
+
if (!name || !bookmarks[name]) {
|
|
2790
|
+
console.log(theme.error(` Bookmark not found: ${name}`));
|
|
2791
|
+
break;
|
|
2792
|
+
}
|
|
2793
|
+
delete bookmarks[name];
|
|
2794
|
+
saveBookmarks(bookmarks);
|
|
2795
|
+
console.log(` ${symbols.check} ${theme.success("Deleted bookmark:")} ${theme.primary(name)}`);
|
|
2796
|
+
break;
|
|
2797
|
+
case "list":
|
|
2798
|
+
case "ls":
|
|
2799
|
+
default:
|
|
2800
|
+
const keys = Object.keys(bookmarks);
|
|
2801
|
+
if (keys.length === 0) {
|
|
2802
|
+
console.log(theme.dim(" No bookmarks saved"));
|
|
2803
|
+
console.log(theme.dim(' Use "bookmark save <name>" to save current directory'));
|
|
2804
|
+
} else {
|
|
2805
|
+
console.log(theme.primary(" \u{1F4CD} Directory Bookmarks"));
|
|
2806
|
+
console.log("");
|
|
2807
|
+
for (const key of keys.sort()) {
|
|
2808
|
+
const exists = existsSync4(bookmarks[key]);
|
|
2809
|
+
const status = exists ? theme.success(symbols.check) : theme.error(symbols.cross);
|
|
2810
|
+
console.log(` ${status} ${theme.primary(key.padEnd(15))} ${theme.dim("\u2192")} ${bookmarks[key]}`);
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
break;
|
|
2814
|
+
}
|
|
2815
|
+
console.log("");
|
|
2816
|
+
}
|
|
2817
|
+
function handleFind(args2) {
|
|
2818
|
+
const pattern = args2.trim() || "*";
|
|
2819
|
+
const searchPath = process.cwd();
|
|
2820
|
+
const results = [];
|
|
2821
|
+
const maxResults = 50;
|
|
2822
|
+
function searchDir(dir, depth = 0) {
|
|
2823
|
+
if (depth > 5 || results.length >= maxResults) return;
|
|
2824
|
+
try {
|
|
2825
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
2826
|
+
for (const entry of entries) {
|
|
2827
|
+
if (entry.name.startsWith(".") || ["node_modules", ".git", "dist", "build"].includes(entry.name)) continue;
|
|
2828
|
+
const fullPath = resolve2(dir, entry.name);
|
|
2829
|
+
const matchPattern = pattern.replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
2830
|
+
const regex = new RegExp(matchPattern, "i");
|
|
2831
|
+
if (regex.test(entry.name)) {
|
|
2832
|
+
results.push({ path: fullPath, isDir: entry.isDirectory() });
|
|
2833
|
+
}
|
|
2834
|
+
if (entry.isDirectory() && results.length < maxResults) {
|
|
2835
|
+
searchDir(fullPath, depth + 1);
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
} catch {
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
console.log("");
|
|
2842
|
+
console.log(theme.dim(` Searching for: ${pattern}`));
|
|
2843
|
+
console.log("");
|
|
2844
|
+
searchDir(searchPath);
|
|
2845
|
+
if (results.length === 0) {
|
|
2846
|
+
console.log(theme.dim(" No matches found"));
|
|
2847
|
+
} else {
|
|
2848
|
+
for (const result of results) {
|
|
2849
|
+
const relativePath = result.path.replace(searchPath, ".").replace(/\\/g, "/");
|
|
2850
|
+
const ext = result.isDir ? "dir" : extname(result.path).toLowerCase();
|
|
2851
|
+
const iconInfo = fileIcons[ext] || fileIcons["default"];
|
|
2852
|
+
const fileName = basename(result.path);
|
|
2853
|
+
const dirPath = relativePath.slice(0, -fileName.length);
|
|
2854
|
+
console.log(` ${iconInfo.icon} ${theme.dim(dirPath)}${chalk3.hex(iconInfo.color)(fileName)}${result.isDir ? "/" : ""}`);
|
|
2855
|
+
}
|
|
2856
|
+
if (results.length >= maxResults) {
|
|
2857
|
+
console.log("");
|
|
2858
|
+
console.log(theme.dim(` ... and more (showing first ${maxResults} results)`));
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
console.log("");
|
|
2862
|
+
}
|
|
2863
|
+
function handleDu(args2) {
|
|
2864
|
+
const targetPath = args2.trim() ? resolve2(process.cwd(), args2.trim()) : process.cwd();
|
|
2865
|
+
if (!existsSync4(targetPath)) {
|
|
2866
|
+
console.log(theme.error(` Path not found: ${targetPath}`));
|
|
2867
|
+
return;
|
|
2868
|
+
}
|
|
2869
|
+
const items = [];
|
|
2870
|
+
console.log("");
|
|
2871
|
+
console.log(theme.dim(" Calculating sizes..."));
|
|
2872
|
+
try {
|
|
2873
|
+
const entries = readdirSync(targetPath, { withFileTypes: true });
|
|
2874
|
+
for (const entry of entries) {
|
|
2875
|
+
if (entry.name.startsWith(".")) continue;
|
|
2876
|
+
const fullPath = resolve2(targetPath, entry.name);
|
|
2877
|
+
let size = 0;
|
|
2878
|
+
let skipped = false;
|
|
2879
|
+
try {
|
|
2880
|
+
if (entry.isDirectory()) {
|
|
2881
|
+
if (skipDirs.has(entry.name)) {
|
|
2882
|
+
skipped = true;
|
|
2883
|
+
size = 0;
|
|
2884
|
+
} else {
|
|
2885
|
+
size = getDirSize(fullPath);
|
|
2886
|
+
}
|
|
2887
|
+
} else {
|
|
2888
|
+
size = statSync(fullPath).size;
|
|
2889
|
+
}
|
|
2890
|
+
items.push({ name: entry.name, size, isDir: entry.isDirectory(), skipped });
|
|
2891
|
+
} catch {
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
} catch {
|
|
2895
|
+
console.log(theme.error(` Cannot read: ${targetPath}`));
|
|
2896
|
+
return;
|
|
2897
|
+
}
|
|
2898
|
+
process.stdout.write("\x1B[1A\x1B[2K");
|
|
2899
|
+
items.sort((a, b) => b.size - a.size);
|
|
2900
|
+
const totalSize = items.reduce((sum, item) => sum + item.size, 0);
|
|
2901
|
+
console.log("");
|
|
2902
|
+
console.log(theme.primary(` \u{1F4CA} Disk Usage: ${basename(targetPath)}`));
|
|
2903
|
+
console.log(theme.dim(` Total: ${formatSizeSimple(totalSize)}`));
|
|
2904
|
+
console.log("");
|
|
2905
|
+
const maxItems = 15;
|
|
2906
|
+
const skippedItems = [];
|
|
2907
|
+
for (let i = 0; i < Math.min(items.length, maxItems); i++) {
|
|
2908
|
+
const item = items[i];
|
|
2909
|
+
if (item.skipped) {
|
|
2910
|
+
skippedItems.push(item.name);
|
|
2911
|
+
continue;
|
|
2912
|
+
}
|
|
2913
|
+
const percent = totalSize > 0 ? item.size / totalSize * 100 : 0;
|
|
2914
|
+
const barWidth = 20;
|
|
2915
|
+
const filled = Math.round(percent / 100 * barWidth);
|
|
2916
|
+
const bar = chalk3.hex("#4ECDC4")("\u2588".repeat(filled)) + chalk3.dim("\u2591".repeat(barWidth - filled));
|
|
2917
|
+
const icon = item.isDir ? "\u{1F4C1}" : "\u{1F4C4}";
|
|
2918
|
+
const name = item.name.length > 25 ? item.name.slice(0, 22) + "..." : item.name.padEnd(25);
|
|
2919
|
+
console.log(` ${icon} ${name} ${bar} ${formatSizeSimple(item.size).padStart(8)} ${chalk3.dim(`${percent.toFixed(1)}%`)}`);
|
|
2920
|
+
}
|
|
2921
|
+
if (items.length > maxItems) {
|
|
2922
|
+
console.log(theme.dim(` ... and ${items.length - maxItems} more items`));
|
|
2923
|
+
}
|
|
2924
|
+
if (skippedItems.length > 0) {
|
|
2925
|
+
console.log("");
|
|
2926
|
+
console.log(theme.dim(` Skipped (large dirs): ${skippedItems.join(", ")}`));
|
|
2927
|
+
}
|
|
2928
|
+
console.log("");
|
|
2929
|
+
}
|
|
2930
|
+
var skipDirs = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", ".nuxt", "coverage", ".cache", "__pycache__", "venv", ".venv"]);
|
|
2931
|
+
function getDirSize(dir, depth = 0, maxDepth = 4) {
|
|
2932
|
+
if (depth > maxDepth) return 0;
|
|
2933
|
+
let size = 0;
|
|
2934
|
+
try {
|
|
2935
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
2936
|
+
for (const entry of entries) {
|
|
2937
|
+
if (skipDirs.has(entry.name)) continue;
|
|
2938
|
+
const fullPath = resolve2(dir, entry.name);
|
|
2939
|
+
if (entry.isDirectory()) {
|
|
2940
|
+
size += getDirSize(fullPath, depth + 1, maxDepth);
|
|
2941
|
+
} else {
|
|
2942
|
+
try {
|
|
2943
|
+
size += statSync(fullPath).size;
|
|
2944
|
+
} catch {
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
} catch {
|
|
2949
|
+
}
|
|
2950
|
+
return size;
|
|
2951
|
+
}
|
|
2952
|
+
function formatSizeSimple(bytes) {
|
|
2953
|
+
if (bytes === 0) return "0 B";
|
|
2954
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
2955
|
+
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
2956
|
+
return (bytes / Math.pow(1024, i)).toFixed(1) + " " + units[i];
|
|
2957
|
+
}
|
|
2958
|
+
function handleGit(args2) {
|
|
2959
|
+
const subcommand = args2.trim().split(/\s+/)[0] || "status";
|
|
2960
|
+
console.log("");
|
|
2961
|
+
try {
|
|
2962
|
+
execSync("git rev-parse --is-inside-work-tree", { stdio: "pipe", timeout: 5e3 });
|
|
2963
|
+
} catch {
|
|
2964
|
+
console.log(theme.error(" Not a git repository"));
|
|
2965
|
+
console.log("");
|
|
2966
|
+
return;
|
|
2967
|
+
}
|
|
2968
|
+
try {
|
|
2969
|
+
switch (subcommand) {
|
|
2970
|
+
case "status":
|
|
2971
|
+
case "s": {
|
|
2972
|
+
const branch = execSync("git branch --show-current", { encoding: "utf-8", timeout: 5e3 }).trim();
|
|
2973
|
+
console.log(` ${symbols.rocket} ${theme.primary("Branch:")} ${chalk3.hex("#98C379")(branch)}`);
|
|
2974
|
+
console.log("");
|
|
2975
|
+
const status = execSync("git status --porcelain", { encoding: "utf-8", timeout: 5e3 });
|
|
2976
|
+
if (!status.trim()) {
|
|
2977
|
+
console.log(` ${symbols.check} ${theme.success("Working tree clean")}`);
|
|
2978
|
+
} else {
|
|
2979
|
+
const lines = status.trim().split("\n");
|
|
2980
|
+
const staged = [];
|
|
2981
|
+
const modified = [];
|
|
2982
|
+
const untracked = [];
|
|
2983
|
+
for (const line of lines) {
|
|
2984
|
+
const [index, work] = [line[0], line[1]];
|
|
2985
|
+
const file = line.slice(3);
|
|
2986
|
+
if (index === "A" || index === "M" || index === "D" || index === "R") {
|
|
2987
|
+
staged.push(`${index} ${file}`);
|
|
2988
|
+
}
|
|
2989
|
+
if (work === "M" || work === "D") {
|
|
2990
|
+
modified.push(`${work} ${file}`);
|
|
2991
|
+
}
|
|
2992
|
+
if (index === "?" && work === "?") {
|
|
2993
|
+
untracked.push(file);
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
if (staged.length > 0) {
|
|
2997
|
+
console.log(theme.success(" Staged changes:"));
|
|
2998
|
+
staged.forEach((f) => console.log(` ${symbols.check} ${chalk3.hex("#98C379")(f)}`));
|
|
2999
|
+
console.log("");
|
|
3000
|
+
}
|
|
3001
|
+
if (modified.length > 0) {
|
|
3002
|
+
console.log(theme.warning(" Modified:"));
|
|
3003
|
+
modified.forEach((f) => console.log(` ${symbols.bullet} ${chalk3.hex("#E5C07B")(f)}`));
|
|
3004
|
+
console.log("");
|
|
3005
|
+
}
|
|
3006
|
+
if (untracked.length > 0) {
|
|
3007
|
+
console.log(theme.dim(" Untracked:"));
|
|
3008
|
+
untracked.forEach((f) => console.log(` ${symbols.bullet} ${theme.dim(f)}`));
|
|
3009
|
+
console.log("");
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
const log = execSync("git log --oneline -3", { encoding: "utf-8", timeout: 3e3 }).trim();
|
|
3013
|
+
if (log) {
|
|
3014
|
+
console.log(theme.dim(" Recent commits:"));
|
|
3015
|
+
log.split("\n").forEach((line) => {
|
|
3016
|
+
const [hash, ...msg] = line.split(" ");
|
|
3017
|
+
console.log(` ${chalk3.hex("#61AFEF")(hash)} ${theme.dim(msg.join(" "))}`);
|
|
3018
|
+
});
|
|
3019
|
+
}
|
|
3020
|
+
break;
|
|
3021
|
+
}
|
|
3022
|
+
case "log":
|
|
3023
|
+
case "l": {
|
|
3024
|
+
const log = execSync("git log --oneline -10", { encoding: "utf-8", timeout: 3e3 }).trim();
|
|
3025
|
+
console.log(theme.primary(" \u{1F4DC} Recent Commits"));
|
|
3026
|
+
console.log("");
|
|
3027
|
+
log.split("\n").forEach((line) => {
|
|
3028
|
+
const [hash, ...msg] = line.split(" ");
|
|
3029
|
+
console.log(` ${chalk3.hex("#61AFEF")(hash)} ${msg.join(" ")}`);
|
|
3030
|
+
});
|
|
3031
|
+
break;
|
|
3032
|
+
}
|
|
3033
|
+
case "branch":
|
|
3034
|
+
case "b": {
|
|
3035
|
+
const branches = execSync("git branch -a", { encoding: "utf-8", timeout: 3e3 }).trim();
|
|
3036
|
+
console.log(theme.primary(" \u{1F33F} Branches"));
|
|
3037
|
+
console.log("");
|
|
3038
|
+
branches.split("\n").forEach((line) => {
|
|
3039
|
+
if (line.startsWith("*")) {
|
|
3040
|
+
console.log(` ${chalk3.hex("#98C379")(line)}`);
|
|
3041
|
+
} else {
|
|
3042
|
+
console.log(` ${theme.dim(line)}`);
|
|
3043
|
+
}
|
|
3044
|
+
});
|
|
3045
|
+
break;
|
|
3046
|
+
}
|
|
3047
|
+
default:
|
|
3048
|
+
const result = execSync(`git ${args2}`, { encoding: "utf-8", timeout: 1e4 });
|
|
3049
|
+
console.log(result);
|
|
3050
|
+
}
|
|
3051
|
+
} catch (error) {
|
|
3052
|
+
const err = error;
|
|
3053
|
+
console.log(theme.error(err.stderr || err.message || "Git command failed"));
|
|
3054
|
+
}
|
|
3055
|
+
console.log("");
|
|
3056
|
+
}
|
|
3057
|
+
var isMac = platform2() === "darwin";
|
|
3058
|
+
var isLinux = platform2() === "linux";
|
|
3059
|
+
function getClipboardCopyCmd() {
|
|
3060
|
+
if (isWindows) return "clip";
|
|
3061
|
+
if (isMac) return "pbcopy";
|
|
3062
|
+
try {
|
|
3063
|
+
execSync("which xclip", { stdio: "pipe" });
|
|
3064
|
+
return "xclip -selection clipboard";
|
|
3065
|
+
} catch {
|
|
3066
|
+
return "xsel --clipboard --input";
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
function getClipboardPasteCmd() {
|
|
3070
|
+
if (isWindows) return 'powershell -command "Get-Clipboard"';
|
|
3071
|
+
if (isMac) return "pbpaste";
|
|
3072
|
+
try {
|
|
3073
|
+
execSync("which xclip", { stdio: "pipe" });
|
|
3074
|
+
return "xclip -selection clipboard -o";
|
|
3075
|
+
} catch {
|
|
3076
|
+
return "xsel --clipboard --output";
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
function handleClipboard(args2) {
|
|
3080
|
+
const parts = args2.trim().split(/\s+/);
|
|
3081
|
+
const action = parts[0]?.toLowerCase();
|
|
3082
|
+
const content = parts.slice(1).join(" ");
|
|
3083
|
+
console.log("");
|
|
3084
|
+
if (action === "copy" && content) {
|
|
3085
|
+
try {
|
|
3086
|
+
const copyCmd = getClipboardCopyCmd();
|
|
3087
|
+
if (isWindows) {
|
|
3088
|
+
execSync(`echo ${content} | ${copyCmd}`, { stdio: "pipe", timeout: 3e3 });
|
|
3089
|
+
} else {
|
|
3090
|
+
execSync(`echo "${content}" | ${copyCmd}`, { stdio: "pipe", timeout: 3e3 });
|
|
3091
|
+
}
|
|
3092
|
+
console.log(` ${symbols.check} ${theme.success("Copied to clipboard")}`);
|
|
3093
|
+
} catch {
|
|
3094
|
+
console.log(theme.error(" Failed to copy to clipboard"));
|
|
3095
|
+
if (isLinux) {
|
|
3096
|
+
console.log(theme.dim(" (Install xclip or xsel: sudo apt install xclip)"));
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
} else if (action === "paste") {
|
|
3100
|
+
try {
|
|
3101
|
+
const pasteCmd = getClipboardPasteCmd();
|
|
3102
|
+
const result = execSync(pasteCmd, { encoding: "utf-8", timeout: 3e3 }).trim();
|
|
3103
|
+
console.log(` ${symbols.clipboard} ${theme.dim("Clipboard contents:")}`);
|
|
3104
|
+
console.log("");
|
|
3105
|
+
console.log(result);
|
|
3106
|
+
} catch {
|
|
3107
|
+
console.log(theme.error(" Failed to read clipboard"));
|
|
3108
|
+
if (isLinux) {
|
|
3109
|
+
console.log(theme.dim(" (Install xclip or xsel: sudo apt install xclip)"));
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
} else if (action === "file" && parts[1]) {
|
|
3113
|
+
const filePath = resolve2(process.cwd(), parts[1]);
|
|
3114
|
+
if (existsSync4(filePath)) {
|
|
3115
|
+
try {
|
|
3116
|
+
const copyCmd = getClipboardCopyCmd();
|
|
3117
|
+
if (isWindows) {
|
|
3118
|
+
execSync(`type "${filePath}" | ${copyCmd}`, { stdio: "pipe", timeout: 5e3 });
|
|
3119
|
+
} else {
|
|
3120
|
+
execSync(`cat "${filePath}" | ${copyCmd}`, { stdio: "pipe", timeout: 5e3 });
|
|
3121
|
+
}
|
|
3122
|
+
console.log(` ${symbols.check} ${theme.success("File contents copied to clipboard")}`);
|
|
3123
|
+
} catch {
|
|
3124
|
+
console.log(theme.error(" Failed to copy file to clipboard"));
|
|
3125
|
+
if (isLinux) {
|
|
3126
|
+
console.log(theme.dim(" (Install xclip or xsel: sudo apt install xclip)"));
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
} else {
|
|
3130
|
+
console.log(theme.error(` File not found: ${parts[1]}`));
|
|
3131
|
+
}
|
|
3132
|
+
} else {
|
|
3133
|
+
console.log(theme.primary(" \u{1F4CB} Clipboard Commands"));
|
|
3134
|
+
console.log("");
|
|
3135
|
+
console.log(` ${theme.dim("clipboard copy <text>")} ${theme.dim("-")} Copy text to clipboard`);
|
|
3136
|
+
console.log(` ${theme.dim("clipboard paste")} ${theme.dim("-")} Show clipboard contents`);
|
|
3137
|
+
console.log(` ${theme.dim("clipboard file <path>")} ${theme.dim("-")} Copy file contents to clipboard`);
|
|
3138
|
+
if (isLinux) {
|
|
3139
|
+
console.log("");
|
|
3140
|
+
console.log(theme.dim(" Note: Requires xclip or xsel on Linux"));
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
console.log("");
|
|
3144
|
+
}
|
|
3145
|
+
function handlePretty(args2) {
|
|
3146
|
+
const filePath = resolve2(process.cwd(), args2.trim());
|
|
3147
|
+
if (!existsSync4(filePath)) {
|
|
3148
|
+
console.log(theme.error(` File not found: ${args2}`));
|
|
3149
|
+
return;
|
|
3150
|
+
}
|
|
3151
|
+
console.log("");
|
|
3152
|
+
try {
|
|
3153
|
+
const content = readFileSync3(filePath, "utf-8");
|
|
3154
|
+
const ext = extname(filePath).toLowerCase();
|
|
3155
|
+
if (ext === ".json") {
|
|
3156
|
+
const parsed = JSON.parse(content);
|
|
3157
|
+
const pretty = JSON.stringify(parsed, null, 2);
|
|
3158
|
+
console.log(highlightJson(pretty));
|
|
3159
|
+
} else {
|
|
3160
|
+
console.log(content);
|
|
3161
|
+
}
|
|
3162
|
+
} catch (error) {
|
|
3163
|
+
console.log(theme.error(` Failed to parse: ${args2}`));
|
|
3164
|
+
console.log(theme.dim(` ${error}`));
|
|
3165
|
+
}
|
|
3166
|
+
console.log("");
|
|
3167
|
+
}
|
|
3168
|
+
function highlightJson(json) {
|
|
3169
|
+
return json.replace(/"([^"]+)":/g, (_, key) => chalk3.hex("#E06C75")(`"${key}"`) + ":").replace(/: "([^"]*)"/g, (_, val) => ": " + chalk3.hex("#98C379")(`"${val}"`)).replace(/: (\d+)/g, (_, num) => ": " + chalk3.hex("#D19A66")(num)).replace(/: (true|false)/g, (_, bool) => ": " + chalk3.hex("#56B6C2")(bool)).replace(/: (null)/g, (_, n) => ": " + chalk3.hex("#C678DD")(n));
|
|
3170
|
+
}
|
|
3171
|
+
var activeWatcher = null;
|
|
3172
|
+
function handleWatch(args2) {
|
|
3173
|
+
const parts = args2.trim().split(/\s+/);
|
|
3174
|
+
const action = parts[0];
|
|
3175
|
+
console.log("");
|
|
3176
|
+
if (action === "stop") {
|
|
3177
|
+
if (activeWatcher) {
|
|
3178
|
+
unwatchFile(activeWatcher);
|
|
3179
|
+
console.log(` ${symbols.check} ${theme.success("Stopped watching:")} ${activeWatcher}`);
|
|
3180
|
+
activeWatcher = null;
|
|
3181
|
+
} else {
|
|
3182
|
+
console.log(theme.dim(" No active watcher"));
|
|
3183
|
+
}
|
|
3184
|
+
console.log("");
|
|
3185
|
+
return;
|
|
3186
|
+
}
|
|
3187
|
+
if (!action) {
|
|
3188
|
+
console.log(theme.primary(" \u{1F441}\uFE0F File Watcher"));
|
|
3189
|
+
console.log("");
|
|
3190
|
+
console.log(` ${theme.dim("Usage:")}`);
|
|
3191
|
+
console.log(` ${theme.dim("watch <file>")} ${theme.dim("-")} Watch file for changes`);
|
|
3192
|
+
console.log(` ${theme.dim("watch stop")} ${theme.dim("-")} Stop watching`);
|
|
3193
|
+
if (activeWatcher) {
|
|
3194
|
+
console.log("");
|
|
3195
|
+
console.log(` ${theme.dim("Currently watching:")} ${activeWatcher}`);
|
|
3196
|
+
}
|
|
3197
|
+
console.log("");
|
|
3198
|
+
return;
|
|
3199
|
+
}
|
|
3200
|
+
const filePath = resolve2(process.cwd(), action);
|
|
3201
|
+
if (!existsSync4(filePath)) {
|
|
3202
|
+
console.log(theme.error(` File not found: ${action}`));
|
|
3203
|
+
console.log("");
|
|
3204
|
+
return;
|
|
3205
|
+
}
|
|
3206
|
+
try {
|
|
3207
|
+
const stats = statSync(filePath);
|
|
3208
|
+
if (stats.isDirectory()) {
|
|
3209
|
+
console.log(theme.error(` Cannot watch a directory: ${action}`));
|
|
3210
|
+
console.log(theme.dim(" Please specify a file path"));
|
|
3211
|
+
console.log("");
|
|
3212
|
+
return;
|
|
3213
|
+
}
|
|
3214
|
+
} catch {
|
|
3215
|
+
console.log(theme.error(` Cannot access: ${action}`));
|
|
3216
|
+
console.log("");
|
|
3217
|
+
return;
|
|
3218
|
+
}
|
|
3219
|
+
if (activeWatcher) {
|
|
3220
|
+
unwatchFile(activeWatcher);
|
|
3221
|
+
}
|
|
3222
|
+
activeWatcher = filePath;
|
|
3223
|
+
let lastSize = statSync(filePath).size;
|
|
3224
|
+
console.log(` ${symbols.info} ${theme.primary("Watching:")} ${filePath}`);
|
|
3225
|
+
console.log(theme.dim(' (Type "!watch stop" to stop watching)'));
|
|
3226
|
+
console.log("");
|
|
3227
|
+
try {
|
|
3228
|
+
const content = readFileSync3(filePath, "utf-8");
|
|
3229
|
+
const lines = content.split("\n").slice(-10);
|
|
3230
|
+
lines.forEach((line) => console.log(theme.dim(line)));
|
|
3231
|
+
} catch {
|
|
3232
|
+
console.log(theme.dim(" (Unable to read initial content)"));
|
|
3233
|
+
}
|
|
3234
|
+
watchFile(filePath, { interval: 500 }, (curr, prev) => {
|
|
3235
|
+
if (curr.size > lastSize) {
|
|
3236
|
+
try {
|
|
3237
|
+
const newContent = readFileSync3(filePath, "utf-8");
|
|
3238
|
+
const allLines = newContent.split("\n");
|
|
3239
|
+
const oldLines = Math.floor(prev.size / 50);
|
|
3240
|
+
const newLines = allLines.slice(-Math.max(1, allLines.length - oldLines));
|
|
3241
|
+
newLines.forEach((line) => {
|
|
3242
|
+
if (line.trim()) console.log(chalk3.hex("#98C379")(line));
|
|
3243
|
+
});
|
|
3244
|
+
} catch {
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
lastSize = curr.size;
|
|
3248
|
+
});
|
|
3249
|
+
}
|
|
3250
|
+
function handleServe(args2) {
|
|
3251
|
+
const port = parseInt(args2.trim()) || 3e3;
|
|
3252
|
+
console.log("");
|
|
3253
|
+
console.log(` ${symbols.rocket} ${theme.primary("Starting HTTP server...")}`);
|
|
3254
|
+
console.log(` ${theme.dim("Serving:")} ${process.cwd()}`);
|
|
3255
|
+
console.log(` ${theme.dim("URL:")} ${chalk3.hex("#61AFEF")(`http://localhost:${port}`)}`);
|
|
3256
|
+
console.log("");
|
|
3257
|
+
console.log(theme.dim(" Press Ctrl+C to stop"));
|
|
3258
|
+
console.log("");
|
|
3259
|
+
try {
|
|
3260
|
+
const npx = isWindows ? "npx.cmd" : "npx";
|
|
3261
|
+
spawn(npx, ["serve", "-p", String(port)], {
|
|
3262
|
+
cwd: process.cwd(),
|
|
3263
|
+
stdio: "inherit"
|
|
3264
|
+
});
|
|
3265
|
+
} catch {
|
|
3266
|
+
console.log(theme.error(' Failed to start server. Make sure "serve" is available.'));
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
function handlePs() {
|
|
3270
|
+
console.log("");
|
|
3271
|
+
console.log(theme.primary(" \u26A1 Running Processes"));
|
|
3272
|
+
console.log("");
|
|
3273
|
+
try {
|
|
3274
|
+
let result;
|
|
3275
|
+
if (isWindows) {
|
|
3276
|
+
result = execSync('tasklist /FO CSV /NH | findstr /V "System Idle"', { encoding: "utf-8", timeout: 5e3 });
|
|
3277
|
+
const lines = result.trim().split("\n").slice(0, 15);
|
|
3278
|
+
console.log(theme.dim(" Name PID Memory"));
|
|
3279
|
+
console.log(theme.dim(" \u2500".repeat(25)));
|
|
3280
|
+
lines.forEach((line) => {
|
|
3281
|
+
const parts = line.split('","').map((p) => p.replace(/"/g, ""));
|
|
3282
|
+
if (parts.length >= 5) {
|
|
3283
|
+
const name = parts[0].slice(0, 28).padEnd(30);
|
|
3284
|
+
const pid = parts[1].padStart(8);
|
|
3285
|
+
const mem = parts[4];
|
|
3286
|
+
console.log(` ${name}${pid} ${chalk3.hex("#98C379")(mem)}`);
|
|
3287
|
+
}
|
|
3288
|
+
});
|
|
3289
|
+
} else {
|
|
3290
|
+
result = execSync("ps aux | head -15", { encoding: "utf-8", timeout: 5e3 });
|
|
3291
|
+
console.log(result);
|
|
3292
|
+
}
|
|
3293
|
+
} catch {
|
|
3294
|
+
console.log(theme.error(" Failed to get process list"));
|
|
3295
|
+
}
|
|
3296
|
+
console.log("");
|
|
3297
|
+
}
|
|
3298
|
+
function handleEnv(args2) {
|
|
3299
|
+
const filter = args2.trim().toLowerCase();
|
|
3300
|
+
console.log("");
|
|
3301
|
+
console.log(theme.primary(" \u{1F510} Environment Variables"));
|
|
3302
|
+
console.log("");
|
|
3303
|
+
const env = process.env;
|
|
3304
|
+
const keys = Object.keys(env).sort();
|
|
3305
|
+
const filtered = filter ? keys.filter((k) => k.toLowerCase().includes(filter)) : keys;
|
|
3306
|
+
if (filtered.length === 0) {
|
|
3307
|
+
console.log(theme.dim(` No variables matching: ${filter}`));
|
|
3308
|
+
} else {
|
|
3309
|
+
const maxShow = 30;
|
|
3310
|
+
filtered.slice(0, maxShow).forEach((key) => {
|
|
3311
|
+
const value = env[key] || "";
|
|
3312
|
+
const displayValue = value.length > 50 ? value.slice(0, 47) + "..." : value;
|
|
3313
|
+
console.log(` ${chalk3.hex("#E06C75")(key.padEnd(25))} ${theme.dim("=")} ${displayValue}`);
|
|
3314
|
+
});
|
|
3315
|
+
if (filtered.length > maxShow) {
|
|
3316
|
+
console.log(theme.dim(` ... and ${filtered.length - maxShow} more`));
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
console.log("");
|
|
3320
|
+
}
|
|
3321
|
+
function handleIp() {
|
|
3322
|
+
console.log("");
|
|
3323
|
+
console.log(theme.primary(" \u{1F310} Network Information"));
|
|
3324
|
+
console.log("");
|
|
3325
|
+
try {
|
|
3326
|
+
const nets = networkInterfaces();
|
|
3327
|
+
const localIps = [];
|
|
3328
|
+
for (const name of Object.keys(nets)) {
|
|
3329
|
+
const netList = nets[name];
|
|
3330
|
+
if (!netList) continue;
|
|
3331
|
+
for (const net of netList) {
|
|
3332
|
+
const isIPv4 = net.family === "IPv4" || net.family === 4;
|
|
3333
|
+
if (isIPv4 && !net.internal) {
|
|
3334
|
+
localIps.push(`${name}: ${net.address}`);
|
|
3335
|
+
}
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3338
|
+
console.log(theme.dim(" Local IP:"));
|
|
3339
|
+
if (localIps.length === 0) {
|
|
3340
|
+
console.log(` ${theme.dim("(No network interfaces found)")}`);
|
|
3341
|
+
} else {
|
|
3342
|
+
localIps.forEach((ip) => console.log(` ${chalk3.hex("#98C379")(ip)}`));
|
|
3343
|
+
}
|
|
3344
|
+
console.log("");
|
|
3345
|
+
console.log(theme.dim(" Public IP:"));
|
|
3346
|
+
try {
|
|
3347
|
+
let result;
|
|
3348
|
+
try {
|
|
3349
|
+
result = execSync("curl -s ifconfig.me", { encoding: "utf-8", timeout: 5e3 }).trim();
|
|
3350
|
+
} catch {
|
|
3351
|
+
if (isWindows) {
|
|
3352
|
+
result = execSync('powershell -command "(Invoke-WebRequest -Uri ifconfig.me -UseBasicParsing).Content"', { encoding: "utf-8", timeout: 5e3 }).trim();
|
|
3353
|
+
} else {
|
|
3354
|
+
throw new Error("curl not available");
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
console.log(` ${chalk3.hex("#61AFEF")(result)}`);
|
|
3358
|
+
} catch {
|
|
3359
|
+
console.log(` ${theme.dim("(Could not fetch - requires curl or internet)")}`);
|
|
3360
|
+
}
|
|
3361
|
+
} catch (error) {
|
|
3362
|
+
console.log(theme.error(" Failed to get network info"));
|
|
3363
|
+
}
|
|
3364
|
+
console.log("");
|
|
3365
|
+
}
|
|
3366
|
+
function handleEpoch(args2) {
|
|
3367
|
+
const input = args2.trim();
|
|
3368
|
+
console.log("");
|
|
3369
|
+
console.log(theme.primary(" \u23F0 Timestamp Converter"));
|
|
3370
|
+
console.log("");
|
|
3371
|
+
if (!input || input === "now") {
|
|
3372
|
+
const now = /* @__PURE__ */ new Date();
|
|
3373
|
+
console.log(` ${theme.dim("Current Time:")}`);
|
|
3374
|
+
console.log(` ${chalk3.hex("#98C379")(now.toISOString())}`);
|
|
3375
|
+
console.log(` ${chalk3.hex("#61AFEF")(Math.floor(now.getTime() / 1e3).toString())} ${theme.dim("(Unix seconds)")}`);
|
|
3376
|
+
console.log(` ${chalk3.hex("#E5C07B")(now.getTime().toString())} ${theme.dim("(Unix milliseconds)")}`);
|
|
3377
|
+
} else if (/^\d{10,13}$/.test(input)) {
|
|
3378
|
+
const ms = input.length === 10 ? parseInt(input) * 1e3 : parseInt(input);
|
|
3379
|
+
const date = new Date(ms);
|
|
3380
|
+
console.log(` ${theme.dim("Epoch:")} ${chalk3.hex("#E5C07B")(input)}`);
|
|
3381
|
+
console.log(` ${theme.dim("Date:")} ${chalk3.hex("#98C379")(date.toISOString())}`);
|
|
3382
|
+
console.log(` ${chalk3.hex("#98C379")(date.toLocaleString())}`);
|
|
3383
|
+
} else {
|
|
3384
|
+
const date = new Date(input);
|
|
3385
|
+
if (!isNaN(date.getTime())) {
|
|
3386
|
+
console.log(` ${theme.dim("Date:")} ${chalk3.hex("#98C379")(input)}`);
|
|
3387
|
+
console.log(` ${theme.dim("Unix:")} ${chalk3.hex("#61AFEF")(Math.floor(date.getTime() / 1e3).toString())} ${theme.dim("(seconds)")}`);
|
|
3388
|
+
console.log(` ${chalk3.hex("#E5C07B")(date.getTime().toString())} ${theme.dim("(milliseconds)")}`);
|
|
3389
|
+
} else {
|
|
3390
|
+
console.log(theme.error(` Cannot parse: ${input}`));
|
|
3391
|
+
console.log(theme.dim(' Examples: epoch now, epoch 1703788800, epoch "2024-01-01"'));
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
console.log("");
|
|
3395
|
+
}
|
|
3396
|
+
async function handleHttp(args2) {
|
|
3397
|
+
const parts = args2.trim().split(/\s+/);
|
|
3398
|
+
const method = (parts[0] || "GET").toUpperCase();
|
|
3399
|
+
const url = parts[1];
|
|
3400
|
+
console.log("");
|
|
3401
|
+
if (!url) {
|
|
3402
|
+
console.log(theme.primary(" \u{1F517} HTTP Client"));
|
|
3403
|
+
console.log("");
|
|
3404
|
+
console.log(` ${theme.dim("Usage:")}`);
|
|
3405
|
+
console.log(` ${theme.dim("http GET <url>")} ${theme.dim("-")} Make GET request`);
|
|
3406
|
+
console.log(` ${theme.dim("http POST <url>")} ${theme.dim("-")} Make POST request`);
|
|
3407
|
+
console.log(` ${theme.dim("http HEAD <url>")} ${theme.dim("-")} Get headers only`);
|
|
3408
|
+
console.log("");
|
|
3409
|
+
return;
|
|
3410
|
+
}
|
|
3411
|
+
const fullUrl = url.startsWith("http") ? url : `https://${url}`;
|
|
3412
|
+
console.log(` ${symbols.rocket} ${theme.dim(method)} ${chalk3.hex("#61AFEF")(fullUrl)}`);
|
|
3413
|
+
console.log("");
|
|
3414
|
+
try {
|
|
3415
|
+
let result;
|
|
3416
|
+
try {
|
|
3417
|
+
const curlCmd = method === "HEAD" ? `curl -sI "${fullUrl}"` : `curl -s -X ${method} "${fullUrl}"`;
|
|
3418
|
+
result = execSync(curlCmd, { encoding: "utf-8", timeout: 1e4 });
|
|
3419
|
+
} catch {
|
|
3420
|
+
if (isWindows) {
|
|
3421
|
+
if (method === "HEAD") {
|
|
3422
|
+
result = execSync(`powershell -command "(Invoke-WebRequest -Uri '${fullUrl}' -Method Head -UseBasicParsing).Headers | ConvertTo-Json"`, { encoding: "utf-8", timeout: 1e4 });
|
|
3423
|
+
} else {
|
|
3424
|
+
result = execSync(`powershell -command "(Invoke-WebRequest -Uri '${fullUrl}' -Method ${method} -UseBasicParsing).Content"`, { encoding: "utf-8", timeout: 1e4 });
|
|
3425
|
+
}
|
|
3426
|
+
} else {
|
|
3427
|
+
throw new Error("curl not available");
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
if (result.trim().startsWith("{") || result.trim().startsWith("[")) {
|
|
3431
|
+
try {
|
|
3432
|
+
const parsed = JSON.parse(result);
|
|
3433
|
+
console.log(highlightJson(JSON.stringify(parsed, null, 2)));
|
|
3434
|
+
} catch {
|
|
3435
|
+
console.log(result);
|
|
3436
|
+
}
|
|
3437
|
+
} else {
|
|
3438
|
+
const lines = result.split("\n").slice(0, 50);
|
|
3439
|
+
lines.forEach((line) => console.log(line));
|
|
3440
|
+
if (result.split("\n").length > 50) {
|
|
3441
|
+
console.log(theme.dim(" ... (output truncated)"));
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3444
|
+
} catch (error) {
|
|
3445
|
+
const err = error;
|
|
3446
|
+
console.log(theme.error(` Request failed: ${err.message || "Unknown error"}`));
|
|
3447
|
+
}
|
|
3448
|
+
console.log("");
|
|
3449
|
+
}
|
|
3450
|
+
function handleDiff(args2) {
|
|
3451
|
+
const parts = args2.trim().split(/\s+/);
|
|
3452
|
+
const file1 = parts[0];
|
|
3453
|
+
const file2 = parts[1];
|
|
3454
|
+
console.log("");
|
|
3455
|
+
if (!file1 || !file2) {
|
|
3456
|
+
console.log(theme.primary(" \u{1F4DD} Diff Tool"));
|
|
3457
|
+
console.log("");
|
|
3458
|
+
console.log(` ${theme.dim("Usage: diff <file1> <file2>")}`);
|
|
3459
|
+
console.log("");
|
|
3460
|
+
return;
|
|
3461
|
+
}
|
|
3462
|
+
const path1 = resolve2(process.cwd(), file1);
|
|
3463
|
+
const path2 = resolve2(process.cwd(), file2);
|
|
3464
|
+
if (!existsSync4(path1)) {
|
|
3465
|
+
console.log(theme.error(` File not found: ${file1}`));
|
|
3466
|
+
console.log("");
|
|
3467
|
+
return;
|
|
3468
|
+
}
|
|
3469
|
+
if (!existsSync4(path2)) {
|
|
3470
|
+
console.log(theme.error(` File not found: ${file2}`));
|
|
3471
|
+
console.log("");
|
|
3472
|
+
return;
|
|
3473
|
+
}
|
|
3474
|
+
try {
|
|
3475
|
+
const content1 = readFileSync3(path1, "utf-8").split("\n");
|
|
3476
|
+
const content2 = readFileSync3(path2, "utf-8").split("\n");
|
|
3477
|
+
console.log(theme.primary(` Comparing: ${basename(file1)} \u2194 ${basename(file2)}`));
|
|
3478
|
+
console.log("");
|
|
3479
|
+
const maxLines = Math.max(content1.length, content2.length);
|
|
3480
|
+
let differences = 0;
|
|
3481
|
+
for (let i = 0; i < Math.min(maxLines, 100); i++) {
|
|
3482
|
+
const line1 = content1[i] || "";
|
|
3483
|
+
const line2 = content2[i] || "";
|
|
3484
|
+
if (line1 !== line2) {
|
|
3485
|
+
differences++;
|
|
3486
|
+
console.log(chalk3.hex("#E06C75")(` - ${(i + 1).toString().padStart(4)}: ${line1.slice(0, 70)}`));
|
|
3487
|
+
console.log(chalk3.hex("#98C379")(` + ${(i + 1).toString().padStart(4)}: ${line2.slice(0, 70)}`));
|
|
3488
|
+
console.log("");
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
if (differences === 0) {
|
|
3492
|
+
console.log(` ${symbols.check} ${theme.success("Files are identical")}`);
|
|
3493
|
+
} else {
|
|
3494
|
+
console.log(theme.dim(` Found ${differences} different line(s)`));
|
|
3495
|
+
}
|
|
3496
|
+
if (maxLines > 100) {
|
|
3497
|
+
console.log(theme.dim(` (Showing first 100 lines)`));
|
|
3498
|
+
}
|
|
3499
|
+
} catch (error) {
|
|
3500
|
+
console.log(theme.error(" Failed to compare files"));
|
|
3501
|
+
}
|
|
3502
|
+
console.log("");
|
|
3503
|
+
}
|
|
3504
|
+
var aliasesFile = join3(homedir3(), ".zammy-aliases.json");
|
|
3505
|
+
function loadAliases() {
|
|
3506
|
+
try {
|
|
3507
|
+
if (existsSync4(aliasesFile)) {
|
|
3508
|
+
return JSON.parse(readFileSync3(aliasesFile, "utf-8"));
|
|
3509
|
+
}
|
|
3510
|
+
} catch {
|
|
3511
|
+
}
|
|
3512
|
+
return {};
|
|
3513
|
+
}
|
|
3514
|
+
function saveAliases(aliases) {
|
|
3515
|
+
writeFileSync3(aliasesFile, JSON.stringify(aliases, null, 2));
|
|
3516
|
+
}
|
|
3517
|
+
function handleAlias(args2) {
|
|
3518
|
+
const parts = args2.trim().split(/\s+/);
|
|
3519
|
+
const action = parts[0]?.toLowerCase();
|
|
3520
|
+
const name = parts[1];
|
|
3521
|
+
const command = parts.slice(2).join(" ");
|
|
3522
|
+
const aliases = loadAliases();
|
|
3523
|
+
console.log("");
|
|
3524
|
+
if (action === "add" || action === "set") {
|
|
3525
|
+
if (!name || !command) {
|
|
3526
|
+
console.log(theme.error(" Usage: alias add <name> <command>"));
|
|
3527
|
+
} else {
|
|
3528
|
+
aliases[name] = command;
|
|
3529
|
+
saveAliases(aliases);
|
|
3530
|
+
console.log(` ${symbols.check} ${theme.success("Alias created:")} ${theme.primary(name)} ${theme.dim("\u2192")} ${command}`);
|
|
3531
|
+
}
|
|
3532
|
+
} else if (action === "del" || action === "rm") {
|
|
3533
|
+
if (!name || !aliases[name]) {
|
|
3534
|
+
console.log(theme.error(` Alias not found: ${name}`));
|
|
3535
|
+
} else {
|
|
3536
|
+
delete aliases[name];
|
|
3537
|
+
saveAliases(aliases);
|
|
3538
|
+
console.log(` ${symbols.check} ${theme.success("Alias deleted:")} ${theme.primary(name)}`);
|
|
3539
|
+
}
|
|
3540
|
+
} else if (action === "run" && name) {
|
|
3541
|
+
if (aliases[name]) {
|
|
3542
|
+
console.log(theme.dim(` Running: ${aliases[name]}`));
|
|
3543
|
+
console.log("");
|
|
3544
|
+
try {
|
|
3545
|
+
const result = execSync(aliases[name], { encoding: "utf-8", cwd: process.cwd(), timeout: 3e4 });
|
|
3546
|
+
console.log(result);
|
|
3547
|
+
} catch (error) {
|
|
3548
|
+
const err = error;
|
|
3549
|
+
if (err.killed) {
|
|
3550
|
+
console.log(theme.error(" Command timed out (30s limit)"));
|
|
3551
|
+
} else {
|
|
3552
|
+
console.log(theme.error(err.stderr || "Command failed"));
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
} else {
|
|
3556
|
+
console.log(theme.error(` Alias not found: ${name}`));
|
|
3557
|
+
}
|
|
3558
|
+
} else {
|
|
3559
|
+
const keys = Object.keys(aliases);
|
|
3560
|
+
if (keys.length === 0) {
|
|
3561
|
+
console.log(theme.dim(" No aliases defined"));
|
|
3562
|
+
console.log(theme.dim(' Use "alias add <name> <command>" to create one'));
|
|
3563
|
+
} else {
|
|
3564
|
+
console.log(theme.primary(" \u26A1 Command Aliases"));
|
|
3565
|
+
console.log("");
|
|
3566
|
+
for (const key of keys.sort()) {
|
|
3567
|
+
console.log(` ${theme.primary(key.padEnd(15))} ${theme.dim("\u2192")} ${aliases[key]}`);
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
}
|
|
3571
|
+
console.log("");
|
|
3572
|
+
}
|
|
3573
|
+
function handleNotify(args2) {
|
|
3574
|
+
const message = args2.trim() || "Notification from Zammy CLI";
|
|
3575
|
+
console.log("");
|
|
3576
|
+
try {
|
|
3577
|
+
if (isWindows) {
|
|
3578
|
+
const ps = `
|
|
3579
|
+
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
|
|
3580
|
+
[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null
|
|
3581
|
+
$template = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText01)
|
|
3582
|
+
$template.GetElementsByTagName("text").Item(0).AppendChild($template.CreateTextNode("${message.replace(/"/g, '\\"')}")) | Out-Null
|
|
3583
|
+
$notifier = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("Zammy CLI")
|
|
3584
|
+
$notifier.Show([Windows.UI.Notifications.ToastNotification]::new($template))
|
|
3585
|
+
`;
|
|
3586
|
+
execSync(`powershell -command "${ps.replace(/\n/g, " ")}"`, { stdio: "pipe", timeout: 5e3 });
|
|
3587
|
+
} else {
|
|
3588
|
+
const cmd = platform2() === "darwin" ? `osascript -e 'display notification "${message}" with title "Zammy CLI"'` : `notify-send "Zammy CLI" "${message}"`;
|
|
3589
|
+
execSync(cmd, { stdio: "pipe", timeout: 3e3 });
|
|
3590
|
+
}
|
|
3591
|
+
console.log(` ${symbols.check} ${theme.success("Notification sent")}`);
|
|
3592
|
+
} catch {
|
|
3593
|
+
console.log("\x07");
|
|
3594
|
+
console.log(` ${symbols.bell} ${theme.primary(message)}`);
|
|
3595
|
+
}
|
|
3596
|
+
console.log("");
|
|
3597
|
+
}
|
|
3598
|
+
function handleGrep(args2) {
|
|
3599
|
+
const parts = args2.trim().split(/\s+/);
|
|
3600
|
+
const pattern = parts[0];
|
|
3601
|
+
const filePattern = parts[1] || "*";
|
|
3602
|
+
console.log("");
|
|
3603
|
+
if (!pattern) {
|
|
3604
|
+
console.log(theme.primary(" \u{1F50D} Search in Files"));
|
|
3605
|
+
console.log("");
|
|
3606
|
+
console.log(` ${theme.dim("Usage: grep <pattern> [file-pattern]")}`);
|
|
3607
|
+
console.log(` ${theme.dim("Example: grep TODO *.ts")}`);
|
|
3608
|
+
console.log("");
|
|
3609
|
+
return;
|
|
3610
|
+
}
|
|
3611
|
+
console.log(theme.dim(` Searching for: ${pattern}`));
|
|
3612
|
+
console.log("");
|
|
3613
|
+
const results = [];
|
|
3614
|
+
const regex = new RegExp(pattern, "i");
|
|
3615
|
+
const maxResults = 30;
|
|
3616
|
+
function searchFile(filePath) {
|
|
3617
|
+
if (results.length >= maxResults) return;
|
|
3618
|
+
try {
|
|
3619
|
+
const content = readFileSync3(filePath, "utf-8");
|
|
3620
|
+
const lines = content.split("\n");
|
|
3621
|
+
lines.forEach((line, index) => {
|
|
3622
|
+
if (results.length >= maxResults) return;
|
|
3623
|
+
if (regex.test(line)) {
|
|
3624
|
+
results.push({
|
|
3625
|
+
file: filePath.replace(process.cwd(), ".").replace(/\\/g, "/"),
|
|
3626
|
+
line: index + 1,
|
|
3627
|
+
content: line.trim().slice(0, 80)
|
|
3628
|
+
});
|
|
3629
|
+
}
|
|
3630
|
+
});
|
|
3631
|
+
} catch {
|
|
3632
|
+
}
|
|
3633
|
+
}
|
|
3634
|
+
function searchDir(dir, depth = 0) {
|
|
3635
|
+
if (depth > 4 || results.length >= maxResults) return;
|
|
3636
|
+
try {
|
|
3637
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
3638
|
+
for (const entry of entries) {
|
|
3639
|
+
if (results.length >= maxResults) break;
|
|
3640
|
+
if (entry.name.startsWith(".") || ["node_modules", ".git", "dist", "build"].includes(entry.name)) continue;
|
|
3641
|
+
const fullPath = resolve2(dir, entry.name);
|
|
3642
|
+
if (entry.isDirectory()) {
|
|
3643
|
+
searchDir(fullPath, depth + 1);
|
|
3644
|
+
} else {
|
|
3645
|
+
const fileExt = extname(entry.name);
|
|
3646
|
+
const filePatternRegex = new RegExp(filePattern.replace(/\*/g, ".*"), "i");
|
|
3647
|
+
if (filePatternRegex.test(entry.name) || filePattern === "*") {
|
|
3648
|
+
if ([".ts", ".js", ".tsx", ".jsx", ".json", ".md", ".txt", ".css", ".html", ".py", ".go", ".rs"].includes(fileExt)) {
|
|
3649
|
+
searchFile(fullPath);
|
|
3650
|
+
}
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
} catch {
|
|
3655
|
+
}
|
|
3656
|
+
}
|
|
3657
|
+
searchDir(process.cwd());
|
|
3658
|
+
if (results.length === 0) {
|
|
3659
|
+
console.log(theme.dim(" No matches found"));
|
|
3660
|
+
} else {
|
|
3661
|
+
for (const result of results) {
|
|
3662
|
+
const highlightedContent = result.content.replace(
|
|
3663
|
+
regex,
|
|
3664
|
+
(match) => chalk3.hex("#FF6B6B").bold(match)
|
|
3665
|
+
);
|
|
3666
|
+
console.log(` ${theme.dim(result.file)}:${chalk3.hex("#61AFEF")(result.line)}`);
|
|
3667
|
+
console.log(` ${highlightedContent}`);
|
|
3668
|
+
console.log("");
|
|
3669
|
+
}
|
|
3670
|
+
if (results.length >= maxResults) {
|
|
3671
|
+
console.log(theme.dim(` ... showing first ${maxResults} results`));
|
|
3672
|
+
}
|
|
3673
|
+
}
|
|
3674
|
+
console.log("");
|
|
3675
|
+
}
|
|
3676
|
+
function handleWc(args2) {
|
|
3677
|
+
const filePath = resolve2(process.cwd(), args2.trim());
|
|
3678
|
+
console.log("");
|
|
3679
|
+
if (!args2.trim()) {
|
|
3680
|
+
console.log(theme.error(" Usage: wc <file>"));
|
|
3681
|
+
console.log("");
|
|
3682
|
+
return;
|
|
3683
|
+
}
|
|
3684
|
+
if (!existsSync4(filePath)) {
|
|
3685
|
+
console.log(theme.error(` File not found: ${args2}`));
|
|
3686
|
+
console.log("");
|
|
3687
|
+
return;
|
|
3688
|
+
}
|
|
3689
|
+
try {
|
|
3690
|
+
const content = readFileSync3(filePath, "utf-8");
|
|
3691
|
+
const lines = content.split("\n").length;
|
|
3692
|
+
const words = content.split(/\s+/).filter((w) => w.length > 0).length;
|
|
3693
|
+
const chars = content.length;
|
|
3694
|
+
const bytes = Buffer.byteLength(content, "utf-8");
|
|
3695
|
+
console.log(theme.primary(` \u{1F4CA} ${basename(args2)}`));
|
|
3696
|
+
console.log("");
|
|
3697
|
+
console.log(` ${chalk3.hex("#61AFEF")(lines.toLocaleString().padStart(8))} ${theme.dim("lines")}`);
|
|
3698
|
+
console.log(` ${chalk3.hex("#98C379")(words.toLocaleString().padStart(8))} ${theme.dim("words")}`);
|
|
3699
|
+
console.log(` ${chalk3.hex("#E5C07B")(chars.toLocaleString().padStart(8))} ${theme.dim("characters")}`);
|
|
3700
|
+
console.log(` ${chalk3.hex("#C678DD")(bytes.toLocaleString().padStart(8))} ${theme.dim("bytes")}`);
|
|
3701
|
+
} catch {
|
|
3702
|
+
console.log(theme.error(" Failed to read file"));
|
|
3703
|
+
}
|
|
3704
|
+
console.log("");
|
|
3705
|
+
}
|
|
3706
|
+
function handleHead(args2) {
|
|
3707
|
+
const parts = args2.trim().split(/\s+/);
|
|
3708
|
+
let lines = 10;
|
|
3709
|
+
let filePath = parts[0];
|
|
3710
|
+
if (parts[0] === "-n" && parts[1]) {
|
|
3711
|
+
lines = parseInt(parts[1]) || 10;
|
|
3712
|
+
filePath = parts[2];
|
|
3713
|
+
}
|
|
3714
|
+
console.log("");
|
|
3715
|
+
if (!filePath) {
|
|
3716
|
+
console.log(theme.error(" Usage: head [-n <lines>] <file>"));
|
|
3717
|
+
console.log("");
|
|
3718
|
+
return;
|
|
3719
|
+
}
|
|
3720
|
+
const fullPath = resolve2(process.cwd(), filePath);
|
|
3721
|
+
if (!existsSync4(fullPath)) {
|
|
3722
|
+
console.log(theme.error(` File not found: ${filePath}`));
|
|
3723
|
+
console.log("");
|
|
3724
|
+
return;
|
|
3725
|
+
}
|
|
3726
|
+
try {
|
|
3727
|
+
const content = readFileSync3(fullPath, "utf-8");
|
|
3728
|
+
const fileLines = content.split("\n").slice(0, lines);
|
|
3729
|
+
console.log(theme.dim(` First ${lines} lines of ${basename(filePath)}:`));
|
|
3730
|
+
console.log("");
|
|
3731
|
+
fileLines.forEach((line, i) => {
|
|
3732
|
+
console.log(` ${chalk3.dim((i + 1).toString().padStart(4))} ${line}`);
|
|
3733
|
+
});
|
|
3734
|
+
} catch {
|
|
3735
|
+
console.log(theme.error(" Failed to read file"));
|
|
3736
|
+
}
|
|
3737
|
+
console.log("");
|
|
3738
|
+
}
|
|
3739
|
+
async function executeShellCommand(command) {
|
|
3740
|
+
const parts = command.trim().split(/\s+/);
|
|
3741
|
+
const cmd = parts[0].toLowerCase();
|
|
3742
|
+
const args2 = parts.slice(1).join(" ");
|
|
3743
|
+
if (cmd === "cd") {
|
|
3744
|
+
handleCd(args2);
|
|
3745
|
+
return;
|
|
3746
|
+
}
|
|
3747
|
+
if (cmd === "pwd") {
|
|
3748
|
+
handlePwd();
|
|
3749
|
+
return;
|
|
3750
|
+
}
|
|
3751
|
+
if (cmd === "cat" || cmd === "type") {
|
|
3752
|
+
handleCat(args2);
|
|
3753
|
+
return;
|
|
3754
|
+
}
|
|
3755
|
+
if (cmd === "ls" || cmd === "dir") {
|
|
3756
|
+
handleLs(args2);
|
|
3757
|
+
return;
|
|
3758
|
+
}
|
|
3759
|
+
if (cmd === "tree") {
|
|
3760
|
+
handleTree(args2);
|
|
3761
|
+
return;
|
|
3762
|
+
}
|
|
3763
|
+
if (cmd === "clear" || cmd === "cls") {
|
|
3764
|
+
console.clear();
|
|
3765
|
+
await displayBanner();
|
|
3766
|
+
return;
|
|
3767
|
+
}
|
|
3768
|
+
if (cmd === "bookmark" || cmd === "bm") {
|
|
3769
|
+
handleBookmark(args2);
|
|
3770
|
+
return;
|
|
3771
|
+
}
|
|
3772
|
+
if (cmd === "find" || cmd === "search") {
|
|
3773
|
+
handleFind(args2);
|
|
3774
|
+
return;
|
|
3775
|
+
}
|
|
3776
|
+
if (cmd === "du" || cmd === "diskusage") {
|
|
3777
|
+
handleDu(args2);
|
|
3778
|
+
return;
|
|
3779
|
+
}
|
|
3780
|
+
if (cmd === "git" || cmd === "g") {
|
|
3781
|
+
handleGit(args2);
|
|
3782
|
+
return;
|
|
3783
|
+
}
|
|
3784
|
+
if (cmd === "clipboard" || cmd === "cb") {
|
|
3785
|
+
handleClipboard(args2);
|
|
3786
|
+
return;
|
|
3787
|
+
}
|
|
3788
|
+
if (cmd === "pretty" || cmd === "json") {
|
|
3789
|
+
handlePretty(args2);
|
|
3790
|
+
return;
|
|
3791
|
+
}
|
|
3792
|
+
if (cmd === "watch" || cmd === "tail") {
|
|
3793
|
+
handleWatch(args2);
|
|
3794
|
+
return;
|
|
3795
|
+
}
|
|
3796
|
+
if (cmd === "serve" || cmd === "http") {
|
|
3797
|
+
handleServe(args2);
|
|
3798
|
+
return;
|
|
3799
|
+
}
|
|
3800
|
+
if (cmd === "ps" || cmd === "processes") {
|
|
3801
|
+
handlePs();
|
|
3802
|
+
return;
|
|
3803
|
+
}
|
|
3804
|
+
if (cmd === "env") {
|
|
3805
|
+
handleEnv(args2);
|
|
3806
|
+
return;
|
|
3807
|
+
}
|
|
3808
|
+
if (cmd === "ip" || cmd === "ipaddr") {
|
|
3809
|
+
handleIp();
|
|
3810
|
+
return;
|
|
3811
|
+
}
|
|
3812
|
+
if (cmd === "epoch" || cmd === "timestamp" || cmd === "time") {
|
|
3813
|
+
handleEpoch(args2);
|
|
3814
|
+
return;
|
|
3815
|
+
}
|
|
3816
|
+
if (cmd === "http" || cmd === "curl" || cmd === "fetch") {
|
|
3817
|
+
await handleHttp(args2);
|
|
3818
|
+
return;
|
|
3819
|
+
}
|
|
3820
|
+
if (cmd === "diff" || cmd === "compare") {
|
|
3821
|
+
handleDiff(args2);
|
|
3822
|
+
return;
|
|
3823
|
+
}
|
|
3824
|
+
if (cmd === "alias") {
|
|
3825
|
+
handleAlias(args2);
|
|
3826
|
+
return;
|
|
3827
|
+
}
|
|
3828
|
+
if (cmd === "notify" || cmd === "alert") {
|
|
3829
|
+
handleNotify(args2);
|
|
3830
|
+
return;
|
|
3831
|
+
}
|
|
3832
|
+
if (cmd === "grep" || cmd === "rg") {
|
|
3833
|
+
handleGrep(args2);
|
|
3834
|
+
return;
|
|
3835
|
+
}
|
|
3836
|
+
if (cmd === "wc" || cmd === "count") {
|
|
3837
|
+
handleWc(args2);
|
|
3838
|
+
return;
|
|
3839
|
+
}
|
|
3840
|
+
if (cmd === "head") {
|
|
3841
|
+
handleHead(args2);
|
|
3842
|
+
return;
|
|
3843
|
+
}
|
|
3844
|
+
const translatedCmd = translateCommand(command);
|
|
3845
|
+
return new Promise((resolvePromise) => {
|
|
3846
|
+
const child = exec(translatedCmd, { cwd: process.cwd(), timeout: 3e4 }, (error, stdout, stderr) => {
|
|
3847
|
+
if (stdout) console.log(stdout);
|
|
3848
|
+
if (stderr) console.log(theme.error(stderr));
|
|
3849
|
+
if (error && !stderr) {
|
|
3850
|
+
if (error.killed) {
|
|
3851
|
+
console.log(theme.error("Command timed out (30s limit)"));
|
|
3852
|
+
} else {
|
|
3853
|
+
console.log(theme.error(`Error: ${error.message}`));
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
resolvePromise();
|
|
3857
|
+
});
|
|
3858
|
+
process.on("SIGINT", () => {
|
|
3859
|
+
child.kill("SIGINT");
|
|
3860
|
+
});
|
|
3861
|
+
});
|
|
3862
|
+
}
|
|
3863
|
+
async function parseAndExecute(input) {
|
|
3864
|
+
const trimmed = input.trim();
|
|
3865
|
+
if (!trimmed) {
|
|
3866
|
+
return;
|
|
3867
|
+
}
|
|
3868
|
+
if (trimmed.startsWith("!")) {
|
|
3869
|
+
const shellCmd = trimmed.slice(1).trim();
|
|
3870
|
+
if (!shellCmd) {
|
|
3871
|
+
console.log(theme.dim("Usage: !<command> (e.g., !ls, !cd folder, !cat file.txt)"));
|
|
3872
|
+
return;
|
|
3873
|
+
}
|
|
3874
|
+
await executeShellCommand(shellCmd);
|
|
3875
|
+
return;
|
|
3876
|
+
}
|
|
3877
|
+
if (!trimmed.startsWith("/")) {
|
|
3878
|
+
console.log("");
|
|
3879
|
+
console.log(` ${symbols.info} ${theme.dim("Commands start with")} ${theme.primary("/")} ${theme.dim("\u2022 Shell commands start with")} ${theme.primary("!")}`);
|
|
3880
|
+
console.log(` ${theme.dim(" Type")} ${theme.primary("/help")} ${theme.dim("for available commands")}`);
|
|
3881
|
+
console.log("");
|
|
3882
|
+
return;
|
|
3883
|
+
}
|
|
3884
|
+
const parts = trimmed.slice(1).split(/\s+/);
|
|
3885
|
+
const commandName = parts[0].toLowerCase();
|
|
3886
|
+
const args2 = parts.slice(1);
|
|
3887
|
+
const command = getCommand(commandName);
|
|
3888
|
+
if (!command) {
|
|
3889
|
+
console.log("");
|
|
3890
|
+
console.log(` ${symbols.cross} ${theme.error("Unknown command:")} ${theme.dim("/" + commandName)}`);
|
|
3891
|
+
console.log(` ${theme.dim(" Type")} ${theme.primary("/help")} ${theme.dim("to see all commands")}`);
|
|
3892
|
+
console.log("");
|
|
3893
|
+
return;
|
|
3894
|
+
}
|
|
3895
|
+
try {
|
|
3896
|
+
await command.execute(args2);
|
|
3897
|
+
} catch (error) {
|
|
3898
|
+
console.log("");
|
|
3899
|
+
console.log(` ${symbols.cross} ${theme.error("Error:")} ${theme.dim(String(error))}`);
|
|
3900
|
+
console.log("");
|
|
3901
|
+
}
|
|
3902
|
+
}
|
|
3903
|
+
|
|
3904
|
+
// src/index.ts
|
|
3905
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
|
|
3906
|
+
import { fileURLToPath } from "url";
|
|
3907
|
+
import { dirname, join as join4 } from "path";
|
|
3908
|
+
var args = process.argv.slice(2);
|
|
3909
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
3910
|
+
try {
|
|
3911
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
3912
|
+
const __dirname = dirname(__filename);
|
|
3913
|
+
const pkgPath = join4(__dirname, "..", "package.json");
|
|
3914
|
+
const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
3915
|
+
console.log(`zammy v${pkg.version}`);
|
|
3916
|
+
} catch {
|
|
3917
|
+
console.log("zammy v1.0.0");
|
|
3918
|
+
}
|
|
3919
|
+
process.exit(0);
|
|
3920
|
+
}
|
|
3921
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
3922
|
+
console.log(`
|
|
3923
|
+
zammy - Your slice-of-life terminal companion
|
|
3924
|
+
|
|
3925
|
+
Usage: zammy [options]
|
|
3926
|
+
|
|
3927
|
+
Options:
|
|
3928
|
+
-v, --version Show version number
|
|
3929
|
+
-h, --help Show this help message
|
|
3930
|
+
--simple Force simple mode (no interactive features)
|
|
3931
|
+
|
|
3932
|
+
Commands:
|
|
3933
|
+
Start zammy and type / to see all available commands
|
|
3934
|
+
Type ! for enhanced shell commands
|
|
3935
|
+
|
|
3936
|
+
Examples:
|
|
3937
|
+
zammy Start interactive shell
|
|
3938
|
+
zammy --simple Start in simple mode (for non-TTY terminals)
|
|
3939
|
+
zammy --version Show version
|
|
3940
|
+
`);
|
|
3941
|
+
process.exit(0);
|
|
3942
|
+
}
|
|
3943
|
+
var isTTY = process.stdin.isTTY && process.stdout.isTTY;
|
|
3944
|
+
var isSimpleMode = process.argv.includes("--simple") || !isTTY;
|
|
3945
|
+
var IMAGE_EXTENSIONS = [".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp"];
|
|
3946
|
+
var menu = {
|
|
3947
|
+
visible: false,
|
|
3948
|
+
items: [],
|
|
3949
|
+
selectedIndex: 0,
|
|
3950
|
+
scrollOffset: 0,
|
|
3951
|
+
prefix: "/",
|
|
3952
|
+
renderedLines: 0
|
|
3953
|
+
};
|
|
3954
|
+
var MAX_VISIBLE_ITEMS = 6;
|
|
3955
|
+
var SHELL_COMMANDS = [
|
|
3956
|
+
// File Operations
|
|
3957
|
+
{ name: "ls", description: "Colorized file listing with icons" },
|
|
3958
|
+
{ name: "tree", description: "Directory tree visualization" },
|
|
3959
|
+
{ name: "cat", description: "View file with syntax highlighting" },
|
|
3960
|
+
{ name: "find", description: "Find files matching pattern" },
|
|
3961
|
+
{ name: "grep", description: "Search in file contents" },
|
|
3962
|
+
{ name: "du", description: "Disk usage with visual bars" },
|
|
3963
|
+
{ name: "diff", description: "Compare two files" },
|
|
3964
|
+
{ name: "wc", description: "Word/line/char count" },
|
|
3965
|
+
{ name: "head", description: "Show first N lines" },
|
|
3966
|
+
// Navigation
|
|
3967
|
+
{ name: "cd", description: "Change directory" },
|
|
3968
|
+
{ name: "pwd", description: "Print working directory" },
|
|
3969
|
+
{ name: "bookmark", description: "Save/jump to directories" },
|
|
3970
|
+
// Developer Tools
|
|
3971
|
+
{ name: "git", description: "Enhanced git status/log/branch" },
|
|
3972
|
+
{ name: "json", description: "Pretty print JSON with colors" },
|
|
3973
|
+
{ name: "http", description: "Quick HTTP requests" },
|
|
3974
|
+
{ name: "epoch", description: "Timestamp converter" },
|
|
3975
|
+
{ name: "serve", description: "Quick HTTP server" },
|
|
3976
|
+
// System
|
|
3977
|
+
{ name: "ip", description: "Show IP addresses" },
|
|
3978
|
+
{ name: "ps", description: "Process list" },
|
|
3979
|
+
{ name: "env", description: "Environment variables" },
|
|
3980
|
+
{ name: "clipboard", description: "Clipboard operations" },
|
|
3981
|
+
{ name: "notify", description: "Desktop notification" },
|
|
3982
|
+
// Utilities
|
|
3983
|
+
{ name: "alias", description: "Command aliases" },
|
|
3984
|
+
{ name: "watch", description: "Watch file for changes" },
|
|
3985
|
+
{ name: "clear", description: "Clear screen (keeps banner)" },
|
|
3986
|
+
// Basic file operations (fallback to system)
|
|
3987
|
+
{ name: "mkdir", description: "Create directory" },
|
|
3988
|
+
{ name: "rm", description: "Remove files" },
|
|
3989
|
+
{ name: "cp", description: "Copy files" },
|
|
3990
|
+
{ name: "mv", description: "Move files" }
|
|
3991
|
+
];
|
|
3992
|
+
function getFilteredCommands(filter) {
|
|
3993
|
+
const commands2 = getAllCommands().map((c) => ({ name: c.name, description: c.description }));
|
|
3994
|
+
if (!filter) return commands2;
|
|
3995
|
+
return commands2.filter((c) => c.name.toLowerCase().startsWith(filter.toLowerCase()));
|
|
3996
|
+
}
|
|
3997
|
+
function getFilteredShellCommands(filter) {
|
|
3998
|
+
if (!filter) return SHELL_COMMANDS;
|
|
3999
|
+
return SHELL_COMMANDS.filter((c) => c.name.toLowerCase().startsWith(filter.toLowerCase()));
|
|
4000
|
+
}
|
|
4001
|
+
function getPromptLength() {
|
|
4002
|
+
return getPrompt().replace(/\x1B\[[0-9;]*m/g, "").length;
|
|
4003
|
+
}
|
|
4004
|
+
function clearBelowCursor() {
|
|
4005
|
+
process.stdout.write("\x1B[J");
|
|
4006
|
+
}
|
|
4007
|
+
function truncateText(text, maxLen) {
|
|
4008
|
+
if (text.length <= maxLen) return text;
|
|
4009
|
+
return text.slice(0, maxLen - 1) + "\u2026";
|
|
4010
|
+
}
|
|
4011
|
+
function renderMenu(currentLine) {
|
|
4012
|
+
if (!menu.visible || menu.items.length === 0) return;
|
|
4013
|
+
const out = process.stdout;
|
|
4014
|
+
const termWidth = process.stdout.columns || 80;
|
|
4015
|
+
const maxDescLen = Math.max(20, termWidth - 35);
|
|
4016
|
+
const totalItems = menu.items.length;
|
|
4017
|
+
const visibleCount = Math.min(MAX_VISIBLE_ITEMS, totalItems);
|
|
4018
|
+
const hasScrollUp = menu.scrollOffset > 0;
|
|
4019
|
+
const hasScrollDown = menu.scrollOffset + visibleCount < totalItems;
|
|
4020
|
+
let lines = [];
|
|
4021
|
+
if (hasScrollUp) {
|
|
4022
|
+
lines.push(theme.dim(" \u2191 more"));
|
|
4023
|
+
}
|
|
4024
|
+
for (let i = 0; i < visibleCount; i++) {
|
|
4025
|
+
const itemIndex = menu.scrollOffset + i;
|
|
4026
|
+
const cmd = menu.items[itemIndex];
|
|
4027
|
+
const isSelected = itemIndex === menu.selectedIndex;
|
|
4028
|
+
const pointer = isSelected ? theme.primary("> ") : " ";
|
|
4029
|
+
const name = isSelected ? theme.primary(`${menu.prefix}${cmd.name}`) : theme.dim(`${menu.prefix}${cmd.name}`);
|
|
4030
|
+
const truncDesc = truncateText(cmd.description, maxDescLen);
|
|
4031
|
+
const desc = theme.dim(` - ${truncDesc}`);
|
|
4032
|
+
lines.push(`${pointer}${name}${desc}`);
|
|
4033
|
+
}
|
|
4034
|
+
if (hasScrollDown) {
|
|
4035
|
+
lines.push(theme.dim(` \u2193 ${totalItems - menu.scrollOffset - visibleCount} more`));
|
|
4036
|
+
}
|
|
4037
|
+
const numLines = lines.length;
|
|
4038
|
+
const cursorCol = getPromptLength() + currentLine.length + 1;
|
|
4039
|
+
out.write(`\x1B[${cursorCol}G\x1B[K`);
|
|
4040
|
+
out.write("\n");
|
|
4041
|
+
clearBelowCursor();
|
|
4042
|
+
out.write(lines.join("\n"));
|
|
4043
|
+
out.write(`\x1B[${numLines}A`);
|
|
4044
|
+
out.write(`\x1B[${cursorCol}G`);
|
|
4045
|
+
menu.renderedLines = numLines;
|
|
4046
|
+
}
|
|
4047
|
+
function hideMenu(currentLine = "") {
|
|
4048
|
+
if (!menu.visible) return;
|
|
4049
|
+
if (menu.renderedLines > 0) {
|
|
4050
|
+
process.stdout.write("\n");
|
|
4051
|
+
clearBelowCursor();
|
|
4052
|
+
process.stdout.write("\x1B[1A");
|
|
4053
|
+
const cursorCol = getPromptLength() + currentLine.length + 1;
|
|
4054
|
+
process.stdout.write(`\x1B[${cursorCol}G`);
|
|
4055
|
+
}
|
|
4056
|
+
menu.visible = false;
|
|
4057
|
+
menu.items = [];
|
|
4058
|
+
menu.selectedIndex = 0;
|
|
4059
|
+
menu.scrollOffset = 0;
|
|
4060
|
+
menu.renderedLines = 0;
|
|
4061
|
+
}
|
|
4062
|
+
function showMenu(filter, prefix, currentLine) {
|
|
4063
|
+
const allItems = prefix === "/" ? getFilteredCommands(filter) : getFilteredShellCommands(filter);
|
|
4064
|
+
if (allItems.length === 0) {
|
|
4065
|
+
if (menu.visible) hideMenu(currentLine);
|
|
4066
|
+
return;
|
|
4067
|
+
}
|
|
4068
|
+
menu.visible = true;
|
|
4069
|
+
menu.items = allItems;
|
|
4070
|
+
menu.prefix = prefix;
|
|
4071
|
+
menu.selectedIndex = 0;
|
|
4072
|
+
menu.scrollOffset = 0;
|
|
4073
|
+
renderMenu(currentLine);
|
|
4074
|
+
}
|
|
4075
|
+
function selectMenuItem(rl) {
|
|
4076
|
+
if (!menu.visible || menu.items.length === 0) return null;
|
|
4077
|
+
const selected = menu.items[menu.selectedIndex];
|
|
4078
|
+
const newLine = menu.prefix + selected.name + " ";
|
|
4079
|
+
if (menu.renderedLines > 0) {
|
|
4080
|
+
process.stdout.write("\n");
|
|
4081
|
+
clearBelowCursor();
|
|
4082
|
+
process.stdout.write("\x1B[1A");
|
|
4083
|
+
}
|
|
4084
|
+
rl.line = newLine;
|
|
4085
|
+
rl.cursor = newLine.length;
|
|
4086
|
+
process.stdout.write(`\r${getPrompt()}${newLine}`);
|
|
4087
|
+
menu.visible = false;
|
|
4088
|
+
menu.items = [];
|
|
4089
|
+
menu.selectedIndex = 0;
|
|
4090
|
+
menu.scrollOffset = 0;
|
|
4091
|
+
menu.renderedLines = 0;
|
|
4092
|
+
return newLine;
|
|
4093
|
+
}
|
|
4094
|
+
function navigateMenu(direction, currentLine) {
|
|
4095
|
+
if (!menu.visible || menu.items.length === 0) return;
|
|
4096
|
+
const totalItems = menu.items.length;
|
|
4097
|
+
if (direction === "up") {
|
|
4098
|
+
if (menu.selectedIndex > 0) {
|
|
4099
|
+
menu.selectedIndex--;
|
|
4100
|
+
} else {
|
|
4101
|
+
menu.selectedIndex = totalItems - 1;
|
|
4102
|
+
menu.scrollOffset = Math.max(0, totalItems - MAX_VISIBLE_ITEMS);
|
|
4103
|
+
}
|
|
4104
|
+
} else {
|
|
4105
|
+
if (menu.selectedIndex < totalItems - 1) {
|
|
4106
|
+
menu.selectedIndex++;
|
|
4107
|
+
} else {
|
|
4108
|
+
menu.selectedIndex = 0;
|
|
4109
|
+
menu.scrollOffset = 0;
|
|
4110
|
+
}
|
|
4111
|
+
}
|
|
4112
|
+
const visibleCount = Math.min(MAX_VISIBLE_ITEMS, totalItems);
|
|
4113
|
+
if (menu.selectedIndex < menu.scrollOffset) {
|
|
4114
|
+
menu.scrollOffset = menu.selectedIndex;
|
|
4115
|
+
} else if (menu.selectedIndex >= menu.scrollOffset + visibleCount) {
|
|
4116
|
+
menu.scrollOffset = menu.selectedIndex - visibleCount + 1;
|
|
4117
|
+
}
|
|
4118
|
+
renderMenu(currentLine);
|
|
4119
|
+
}
|
|
4120
|
+
function completer(line) {
|
|
4121
|
+
const commands2 = getAllCommands().map((c) => "/" + c.name);
|
|
4122
|
+
if (line.startsWith("/")) {
|
|
4123
|
+
const input = line.toLowerCase();
|
|
4124
|
+
if (input.startsWith("/asciiart ")) {
|
|
4125
|
+
const afterCommand = line.slice("/asciiart ".length);
|
|
4126
|
+
const searchTerm = afterCommand.startsWith("@") ? afterCommand.slice(1) : afterCommand;
|
|
4127
|
+
try {
|
|
4128
|
+
const files = readdirSync2(process.cwd());
|
|
4129
|
+
const imageFiles = files.filter((f) => {
|
|
4130
|
+
const ext = f.toLowerCase().slice(f.lastIndexOf("."));
|
|
4131
|
+
return IMAGE_EXTENSIONS.includes(ext);
|
|
4132
|
+
});
|
|
4133
|
+
const matches2 = imageFiles.filter(
|
|
4134
|
+
(f) => f.toLowerCase().startsWith(searchTerm.toLowerCase())
|
|
4135
|
+
);
|
|
4136
|
+
const completions = matches2.map((f) => "/asciiart @" + f);
|
|
4137
|
+
return [completions.length ? completions : [], line];
|
|
4138
|
+
} catch {
|
|
4139
|
+
return [[], line];
|
|
4140
|
+
}
|
|
4141
|
+
}
|
|
4142
|
+
const matches = commands2.filter((c) => c.toLowerCase().startsWith(input));
|
|
4143
|
+
return [matches.length ? matches : [], line];
|
|
4144
|
+
}
|
|
4145
|
+
if (line.startsWith("!")) {
|
|
4146
|
+
const shellCmds = SHELL_COMMANDS.map((c) => "!" + c.name);
|
|
4147
|
+
const matches = shellCmds.filter((c) => c.toLowerCase().startsWith(line.toLowerCase()));
|
|
4148
|
+
return [matches.length ? matches : [], line];
|
|
4149
|
+
}
|
|
4150
|
+
return [[], line];
|
|
4151
|
+
}
|
|
4152
|
+
async function main() {
|
|
4153
|
+
if (isSimpleMode && !process.argv.includes("--simple")) {
|
|
4154
|
+
console.log(theme.dim("Note: Running in simple mode (no TTY detected). Use Tab for autocomplete."));
|
|
4155
|
+
console.log(theme.dim("For full features, run in a proper terminal or use: zammy --simple\n"));
|
|
4156
|
+
}
|
|
4157
|
+
await displayBanner();
|
|
4158
|
+
const rl = readline.createInterface({
|
|
4159
|
+
input: process.stdin,
|
|
4160
|
+
output: process.stdout,
|
|
4161
|
+
terminal: isTTY,
|
|
4162
|
+
completer
|
|
4163
|
+
});
|
|
4164
|
+
let prevLine = "";
|
|
4165
|
+
if (isTTY && !isSimpleMode) {
|
|
4166
|
+
process.stdin.on("keypress", (_char, key) => {
|
|
4167
|
+
if (!key) return;
|
|
4168
|
+
const currentLine = rl.line || "";
|
|
4169
|
+
if (menu.visible) {
|
|
4170
|
+
if (key.name === "up" || key.name === "down") {
|
|
4171
|
+
navigateMenu(key.name, currentLine);
|
|
4172
|
+
return;
|
|
4173
|
+
}
|
|
4174
|
+
if (key.name === "tab" || key.name === "return") {
|
|
4175
|
+
if (key.name === "tab") {
|
|
4176
|
+
selectMenuItem(rl);
|
|
4177
|
+
}
|
|
4178
|
+
return;
|
|
4179
|
+
}
|
|
4180
|
+
if (key.name === "escape") {
|
|
4181
|
+
hideMenu(currentLine);
|
|
4182
|
+
return;
|
|
4183
|
+
}
|
|
4184
|
+
}
|
|
4185
|
+
setImmediate(() => {
|
|
4186
|
+
const line = rl.line || "";
|
|
4187
|
+
if (line === prevLine) return;
|
|
4188
|
+
prevLine = line;
|
|
4189
|
+
if (line.startsWith("/") && !line.includes(" ")) {
|
|
4190
|
+
showMenu(line.slice(1), "/", line);
|
|
4191
|
+
} else if (line.startsWith("!") && !line.includes(" ")) {
|
|
4192
|
+
showMenu(line.slice(1), "!", line);
|
|
4193
|
+
} else if (menu.visible) {
|
|
4194
|
+
hideMenu(line);
|
|
4195
|
+
}
|
|
4196
|
+
});
|
|
4197
|
+
});
|
|
4198
|
+
}
|
|
4199
|
+
let lastCtrlC = 0;
|
|
4200
|
+
rl.on("SIGINT", () => {
|
|
4201
|
+
const now = Date.now();
|
|
4202
|
+
const line = rl.line || "";
|
|
4203
|
+
if (now - lastCtrlC < 500) {
|
|
4204
|
+
hideMenu(line);
|
|
4205
|
+
console.log("\n" + theme.secondary("Goodbye! See you next time.") + "\n");
|
|
4206
|
+
process.exit(0);
|
|
4207
|
+
} else {
|
|
4208
|
+
lastCtrlC = now;
|
|
4209
|
+
hideMenu(line);
|
|
4210
|
+
console.log("\n" + theme.dim("Press Ctrl+C again to exit"));
|
|
4211
|
+
rl.prompt();
|
|
4212
|
+
}
|
|
4213
|
+
});
|
|
4214
|
+
rl.setPrompt(getPrompt());
|
|
4215
|
+
rl.on("line", async (input) => {
|
|
4216
|
+
hideMenu(input);
|
|
4217
|
+
prevLine = "";
|
|
4218
|
+
await parseAndExecute(input);
|
|
4219
|
+
rl.prompt();
|
|
4220
|
+
});
|
|
4221
|
+
rl.prompt();
|
|
4222
|
+
}
|
|
4223
|
+
main().catch(console.error);
|