zammy 1.2.1 → 1.3.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/README.md +322 -239
- package/assets/zammy.gif +0 -0
- package/dist/index.js +2418 -200
- package/package.json +9 -2
- package/packages/plugins/docker/README.md +141 -0
- package/packages/plugins/docker/dist/index.d.ts +46 -0
- package/packages/plugins/docker/dist/index.d.ts.map +1 -0
- package/packages/plugins/docker/dist/index.js +402 -0
- package/packages/plugins/docker/dist/index.js.map +1 -0
- package/packages/plugins/docker/package.json +28 -0
- package/packages/plugins/docker/zammy-plugin.json +16 -0
- package/packages/plugins/faker/README.md +65 -0
- package/packages/plugins/faker/dist/index.d.ts +43 -0
- package/packages/plugins/faker/dist/index.d.ts.map +1 -0
- package/packages/plugins/faker/dist/index.js +349 -0
- package/packages/plugins/faker/dist/index.js.map +1 -0
- package/packages/plugins/faker/package.json +28 -0
- package/packages/plugins/faker/zammy-plugin.json +14 -0
- package/packages/plugins/network/README.md +126 -0
- package/packages/plugins/network/dist/index.d.ts +45 -0
- package/packages/plugins/network/dist/index.d.ts.map +1 -0
- package/packages/plugins/network/dist/index.js +406 -0
- package/packages/plugins/network/dist/index.js.map +1 -0
- package/packages/plugins/network/package.json +28 -0
- package/packages/plugins/network/zammy-plugin.json +17 -0
- package/packages/plugins/port/README.md +74 -0
- package/packages/plugins/port/dist/index.d.ts +47 -0
- package/packages/plugins/port/dist/index.d.ts.map +1 -0
- package/packages/plugins/port/dist/index.js +331 -0
- package/packages/plugins/port/dist/index.js.map +1 -0
- package/packages/plugins/port/package.json +28 -0
- package/packages/plugins/port/zammy-plugin.json +16 -0
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import * as
|
|
4
|
+
import * as readline4 from "readline";
|
|
5
5
|
|
|
6
6
|
// src/ui/banner.ts
|
|
7
7
|
import figlet from "figlet";
|
|
@@ -181,6 +181,15 @@ var box = {
|
|
|
181
181
|
return theme.dim(`${chars.tl}${chars.h.repeat(leftPad)} `) + title + theme.dim(` ${chars.h.repeat(rightPad)}${chars.tr}`);
|
|
182
182
|
}
|
|
183
183
|
};
|
|
184
|
+
var progressBar = (percent, width = 30, showPercent = true) => {
|
|
185
|
+
const filled = Math.round(percent / 100 * width);
|
|
186
|
+
const empty = width - filled;
|
|
187
|
+
let color = theme.success;
|
|
188
|
+
if (percent > 70) color = theme.warning;
|
|
189
|
+
if (percent > 90) color = theme.error;
|
|
190
|
+
const bar = color("\u2588".repeat(filled)) + theme.dim("\u2591".repeat(empty));
|
|
191
|
+
return showPercent ? `${bar} ${percent.toFixed(0)}%` : bar;
|
|
192
|
+
};
|
|
184
193
|
var bubble = {
|
|
185
194
|
say: (text, width = 50) => {
|
|
186
195
|
const lines = [];
|
|
@@ -534,7 +543,7 @@ var MIN_WIDTH_FOR_MASCOT2 = 90;
|
|
|
534
543
|
var MIN_WIDTH_FOR_FULL_LOGO = 55;
|
|
535
544
|
var MIN_WIDTH_FOR_COMPACT = 30;
|
|
536
545
|
async function displayBanner(simple = false) {
|
|
537
|
-
return new Promise((
|
|
546
|
+
return new Promise((resolve6) => {
|
|
538
547
|
const termWidth = process.stdout.columns || 80;
|
|
539
548
|
figlet("ZAMMY", {
|
|
540
549
|
font: "ANSI Shadow",
|
|
@@ -548,7 +557,7 @@ async function displayBanner(simple = false) {
|
|
|
548
557
|
console.log(theme.secondary(` "${greeting}"`));
|
|
549
558
|
console.log(theme.dim(" Type /help for commands"));
|
|
550
559
|
console.log("");
|
|
551
|
-
|
|
560
|
+
resolve6();
|
|
552
561
|
return;
|
|
553
562
|
}
|
|
554
563
|
let figletLines = [];
|
|
@@ -581,7 +590,7 @@ async function displayBanner(simple = false) {
|
|
|
581
590
|
console.log(theme.dim(` ${symbols.arrow} Type ${theme.primary("/")} to browse commands or ${theme.primary("/help")} for full list`));
|
|
582
591
|
console.log(theme.dim(` ${symbols.arrow} Shell commands start with ${theme.primary("!")} (e.g., ${theme.primary("!ls")}, ${theme.primary("!git")})`));
|
|
583
592
|
console.log("");
|
|
584
|
-
|
|
593
|
+
resolve6();
|
|
585
594
|
return;
|
|
586
595
|
}
|
|
587
596
|
const figletWidth = 50;
|
|
@@ -611,7 +620,7 @@ async function displayBanner(simple = false) {
|
|
|
611
620
|
console.log(theme.dim(` ${symbols.arrow} Type ${theme.primary("/")} to browse commands or ${theme.primary("/help")} for full list`));
|
|
612
621
|
console.log(theme.dim(` ${symbols.arrow} Shell commands start with ${theme.primary("!")} (e.g., ${theme.primary("!ls")}, ${theme.primary("!git")})`));
|
|
613
622
|
console.log("");
|
|
614
|
-
|
|
623
|
+
resolve6();
|
|
615
624
|
});
|
|
616
625
|
});
|
|
617
626
|
}
|
|
@@ -624,7 +633,17 @@ function getPrompt() {
|
|
|
624
633
|
// src/commands/registry.ts
|
|
625
634
|
var commands = /* @__PURE__ */ new Map();
|
|
626
635
|
function registerCommand(command) {
|
|
627
|
-
commands.set(command.name, command);
|
|
636
|
+
commands.set(command.name, { ...command, source: "core" });
|
|
637
|
+
}
|
|
638
|
+
function registerPluginCommand(command, pluginName) {
|
|
639
|
+
commands.set(command.name, { ...command, source: "plugin", pluginName });
|
|
640
|
+
}
|
|
641
|
+
function unregisterPluginCommands(pluginName) {
|
|
642
|
+
for (const [name, cmd] of commands.entries()) {
|
|
643
|
+
if (cmd.source === "plugin" && cmd.pluginName === pluginName) {
|
|
644
|
+
commands.delete(name);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
628
647
|
}
|
|
629
648
|
function getCommand(name) {
|
|
630
649
|
return commands.get(name);
|
|
@@ -632,6 +651,20 @@ function getCommand(name) {
|
|
|
632
651
|
function getAllCommands() {
|
|
633
652
|
return Array.from(commands.values());
|
|
634
653
|
}
|
|
654
|
+
function getPluginCommands() {
|
|
655
|
+
return Array.from(commands.values()).filter((cmd) => cmd.source === "plugin");
|
|
656
|
+
}
|
|
657
|
+
function checkCommandConflict(name) {
|
|
658
|
+
const existing = commands.get(name);
|
|
659
|
+
if (!existing) {
|
|
660
|
+
return { exists: false };
|
|
661
|
+
}
|
|
662
|
+
return {
|
|
663
|
+
exists: true,
|
|
664
|
+
source: existing.source,
|
|
665
|
+
pluginName: existing.pluginName
|
|
666
|
+
};
|
|
667
|
+
}
|
|
635
668
|
|
|
636
669
|
// src/commands/utilities/help.ts
|
|
637
670
|
var categories = {
|
|
@@ -639,7 +672,8 @@ var categories = {
|
|
|
639
672
|
"Fun": ["joke", "quote", "fortune", "dice", "flip", "pomodoro", "zammy"],
|
|
640
673
|
"Creative": ["asciiart", "figlet", "lorem", "color"],
|
|
641
674
|
"Dev": ["hash", "uuid", "encode"],
|
|
642
|
-
"Info": ["weather"]
|
|
675
|
+
"Info": ["weather"],
|
|
676
|
+
"System": ["plugin"]
|
|
643
677
|
};
|
|
644
678
|
registerCommand({
|
|
645
679
|
name: "help",
|
|
@@ -692,8 +726,24 @@ registerCommand({
|
|
|
692
726
|
}
|
|
693
727
|
console.log("");
|
|
694
728
|
}
|
|
729
|
+
const pluginCmds = getPluginCommands();
|
|
730
|
+
if (pluginCmds.length > 0) {
|
|
731
|
+
console.log(` ${symbols.gear} ${theme.b.secondary("Plugins")}`);
|
|
732
|
+
console.log(theme.dim(" " + "\u2500".repeat(46)));
|
|
733
|
+
for (const cmd of pluginCmds) {
|
|
734
|
+
const paddedName = cmd.name.padEnd(maxNameLength + 2);
|
|
735
|
+
const pluginBadge = cmd.pluginName ? theme.dim(`[${cmd.pluginName}]`) : "";
|
|
736
|
+
console.log(
|
|
737
|
+
` ${theme.command("/" + paddedName)} ${theme.dim("\u2502")} ${theme.dim(cmd.description)} ${pluginBadge}`
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
console.log("");
|
|
741
|
+
}
|
|
695
742
|
const categorizedNames = Object.values(categories).flat();
|
|
696
|
-
const
|
|
743
|
+
const pluginCmdNames = pluginCmds.map((c) => c.name);
|
|
744
|
+
const uncategorized = commands2.filter(
|
|
745
|
+
(c) => !categorizedNames.includes(c.name) && !pluginCmdNames.includes(c.name) && c.source === "core"
|
|
746
|
+
);
|
|
697
747
|
if (uncategorized.length > 0) {
|
|
698
748
|
console.log(` ${symbols.folder} ${theme.b.secondary("Other")}`);
|
|
699
749
|
console.log(theme.dim(" " + "\u2500".repeat(46)));
|
|
@@ -1142,7 +1192,7 @@ registerCommand({
|
|
|
1142
1192
|
console.log("");
|
|
1143
1193
|
let remaining = totalSeconds;
|
|
1144
1194
|
let spinnerIndex = 0;
|
|
1145
|
-
return new Promise((
|
|
1195
|
+
return new Promise((resolve6) => {
|
|
1146
1196
|
const interval = setInterval(() => {
|
|
1147
1197
|
process.stdout.write("\r\x1B[K");
|
|
1148
1198
|
if (remaining <= 0) {
|
|
@@ -1150,7 +1200,7 @@ registerCommand({
|
|
|
1150
1200
|
console.log(` ${symbols.sparkle} ${theme.success("TIME'S UP!")} ${symbols.sparkle}`);
|
|
1151
1201
|
console.log("");
|
|
1152
1202
|
process.stdout.write("\x07");
|
|
1153
|
-
|
|
1203
|
+
resolve6();
|
|
1154
1204
|
return;
|
|
1155
1205
|
}
|
|
1156
1206
|
const spinner = spinnerFrames[spinnerIndex % spinnerFrames.length];
|
|
@@ -1468,58 +1518,368 @@ registerCommand({
|
|
|
1468
1518
|
}
|
|
1469
1519
|
});
|
|
1470
1520
|
|
|
1521
|
+
// src/handlers/utilities/env.ts
|
|
1522
|
+
function getAllEnvVars() {
|
|
1523
|
+
return Object.entries(process.env).filter(([, value]) => value !== void 0).map(([name, value]) => ({ name, value })).sort((a, b3) => a.name.localeCompare(b3.name));
|
|
1524
|
+
}
|
|
1525
|
+
function getEnvVar(name) {
|
|
1526
|
+
const exactMatch = process.env[name];
|
|
1527
|
+
if (exactMatch !== void 0) return exactMatch;
|
|
1528
|
+
const upperName = name.toUpperCase();
|
|
1529
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
1530
|
+
if (key.toUpperCase() === upperName) {
|
|
1531
|
+
return value;
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
return void 0;
|
|
1535
|
+
}
|
|
1536
|
+
function searchEnvVars(query) {
|
|
1537
|
+
const lowerQuery = query.toLowerCase();
|
|
1538
|
+
return getAllEnvVars().filter(
|
|
1539
|
+
(env) => env.name.toLowerCase().includes(lowerQuery) || env.value.toLowerCase().includes(lowerQuery)
|
|
1540
|
+
);
|
|
1541
|
+
}
|
|
1542
|
+
function getPathEntries() {
|
|
1543
|
+
const pathVar = process.env.PATH || process.env.Path || "";
|
|
1544
|
+
const separator = process.platform === "win32" ? ";" : ":";
|
|
1545
|
+
return pathVar.split(separator).filter(Boolean);
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
// src/commands/utilities/env.ts
|
|
1549
|
+
registerCommand({
|
|
1550
|
+
name: "env",
|
|
1551
|
+
description: "View environment variables",
|
|
1552
|
+
usage: "/env [name|search|path]",
|
|
1553
|
+
async execute(args2) {
|
|
1554
|
+
const action = args2[0];
|
|
1555
|
+
console.log("");
|
|
1556
|
+
if (!action) {
|
|
1557
|
+
const vars = getAllEnvVars();
|
|
1558
|
+
console.log(` ${symbols.sparkle} ${theme.gradient("ENVIRONMENT VARIABLES")} ${theme.dim(`(${vars.length})`)}`);
|
|
1559
|
+
console.log("");
|
|
1560
|
+
for (const env of vars.slice(0, 30)) {
|
|
1561
|
+
const displayValue = env.value.length > 50 ? env.value.slice(0, 47) + "..." : env.value;
|
|
1562
|
+
console.log(` ${theme.primary(env.name.padEnd(20))} ${theme.dim("=")} ${displayValue}`);
|
|
1563
|
+
}
|
|
1564
|
+
if (vars.length > 30) {
|
|
1565
|
+
console.log("");
|
|
1566
|
+
console.log(` ${theme.dim(`... and ${vars.length - 30} more. Use /env search <query> to filter.`)}`);
|
|
1567
|
+
}
|
|
1568
|
+
console.log("");
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1571
|
+
if (action.toLowerCase() === "path") {
|
|
1572
|
+
const paths = getPathEntries();
|
|
1573
|
+
console.log(` ${symbols.sparkle} ${theme.gradient("PATH ENTRIES")} ${theme.dim(`(${paths.length})`)}`);
|
|
1574
|
+
console.log("");
|
|
1575
|
+
paths.forEach((p, i) => {
|
|
1576
|
+
console.log(` ${theme.dim(`${(i + 1).toString().padStart(2)}.`)} ${theme.primary(p)}`);
|
|
1577
|
+
});
|
|
1578
|
+
console.log("");
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1581
|
+
if (action.toLowerCase() === "search" && args2[1]) {
|
|
1582
|
+
const query = args2.slice(1).join(" ");
|
|
1583
|
+
const results = searchEnvVars(query);
|
|
1584
|
+
console.log(` ${symbols.sparkle} ${theme.gradient(`SEARCH: "${query}"`)} ${theme.dim(`(${results.length} matches)`)}`);
|
|
1585
|
+
console.log("");
|
|
1586
|
+
if (results.length === 0) {
|
|
1587
|
+
console.log(` ${theme.dim("No matches found")}`);
|
|
1588
|
+
} else {
|
|
1589
|
+
for (const env of results) {
|
|
1590
|
+
const displayValue = env.value.length > 50 ? env.value.slice(0, 47) + "..." : env.value;
|
|
1591
|
+
console.log(` ${theme.primary(env.name.padEnd(20))} ${theme.dim("=")} ${displayValue}`);
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
console.log("");
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
const value = getEnvVar(action);
|
|
1598
|
+
if (value !== void 0) {
|
|
1599
|
+
console.log(` ${theme.primary(action)} ${theme.dim("=")}`);
|
|
1600
|
+
console.log("");
|
|
1601
|
+
if (value.includes(process.platform === "win32" ? ";" : ":") && value.length > 100) {
|
|
1602
|
+
const separator = process.platform === "win32" ? ";" : ":";
|
|
1603
|
+
const parts = value.split(separator);
|
|
1604
|
+
parts.forEach((p, i) => {
|
|
1605
|
+
console.log(` ${theme.dim(`${(i + 1).toString().padStart(2)}.`)} ${p}`);
|
|
1606
|
+
});
|
|
1607
|
+
} else {
|
|
1608
|
+
console.log(` ${theme.success(value)}`);
|
|
1609
|
+
}
|
|
1610
|
+
} else {
|
|
1611
|
+
console.log(` ${symbols.cross} ${theme.error(`Environment variable not found: ${action}`)}`);
|
|
1612
|
+
}
|
|
1613
|
+
console.log("");
|
|
1614
|
+
}
|
|
1615
|
+
});
|
|
1616
|
+
|
|
1617
|
+
// src/handlers/utilities/size.ts
|
|
1618
|
+
import { statSync, readdirSync, existsSync as existsSync3 } from "fs";
|
|
1619
|
+
import { join as join3, basename } from "path";
|
|
1620
|
+
function formatBytes2(bytes) {
|
|
1621
|
+
if (bytes === 0) return "0 B";
|
|
1622
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
1623
|
+
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
1624
|
+
const size = bytes / Math.pow(1024, i);
|
|
1625
|
+
return `${size.toFixed(i > 0 ? 1 : 0)} ${units[i]}`;
|
|
1626
|
+
}
|
|
1627
|
+
function getSize(path) {
|
|
1628
|
+
if (!existsSync3(path)) return null;
|
|
1629
|
+
try {
|
|
1630
|
+
const stats = statSync(path);
|
|
1631
|
+
if (stats.isFile()) {
|
|
1632
|
+
return {
|
|
1633
|
+
path,
|
|
1634
|
+
name: basename(path),
|
|
1635
|
+
size: stats.size,
|
|
1636
|
+
isDirectory: false
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
if (stats.isDirectory()) {
|
|
1640
|
+
return getDirSize(path);
|
|
1641
|
+
}
|
|
1642
|
+
return null;
|
|
1643
|
+
} catch {
|
|
1644
|
+
return null;
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
function getDirSize(dirPath) {
|
|
1648
|
+
const children = [];
|
|
1649
|
+
let totalSize = 0;
|
|
1650
|
+
try {
|
|
1651
|
+
const entries = readdirSync(dirPath, { withFileTypes: true });
|
|
1652
|
+
for (const entry of entries) {
|
|
1653
|
+
const entryPath = join3(dirPath, entry.name);
|
|
1654
|
+
try {
|
|
1655
|
+
if (entry.isFile()) {
|
|
1656
|
+
const stats = statSync(entryPath);
|
|
1657
|
+
totalSize += stats.size;
|
|
1658
|
+
children.push({
|
|
1659
|
+
path: entryPath,
|
|
1660
|
+
name: entry.name,
|
|
1661
|
+
size: stats.size,
|
|
1662
|
+
isDirectory: false
|
|
1663
|
+
});
|
|
1664
|
+
} else if (entry.isDirectory()) {
|
|
1665
|
+
if (["node_modules", ".git", "dist", "build", ".next", "coverage"].includes(entry.name)) {
|
|
1666
|
+
const subSize = getQuickDirSize(entryPath);
|
|
1667
|
+
totalSize += subSize;
|
|
1668
|
+
children.push({
|
|
1669
|
+
path: entryPath,
|
|
1670
|
+
name: entry.name,
|
|
1671
|
+
size: subSize,
|
|
1672
|
+
isDirectory: true
|
|
1673
|
+
});
|
|
1674
|
+
} else {
|
|
1675
|
+
const subInfo = getDirSize(entryPath);
|
|
1676
|
+
totalSize += subInfo.size;
|
|
1677
|
+
children.push(subInfo);
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
} catch {
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
} catch {
|
|
1684
|
+
}
|
|
1685
|
+
children.sort((a, b3) => b3.size - a.size);
|
|
1686
|
+
return {
|
|
1687
|
+
path: dirPath,
|
|
1688
|
+
name: basename(dirPath),
|
|
1689
|
+
size: totalSize,
|
|
1690
|
+
isDirectory: true,
|
|
1691
|
+
children
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
function getQuickDirSize(dirPath) {
|
|
1695
|
+
let totalSize = 0;
|
|
1696
|
+
try {
|
|
1697
|
+
const entries = readdirSync(dirPath, { withFileTypes: true });
|
|
1698
|
+
for (const entry of entries) {
|
|
1699
|
+
const entryPath = join3(dirPath, entry.name);
|
|
1700
|
+
try {
|
|
1701
|
+
if (entry.isFile()) {
|
|
1702
|
+
totalSize += statSync(entryPath).size;
|
|
1703
|
+
} else if (entry.isDirectory()) {
|
|
1704
|
+
totalSize += getQuickDirSize(entryPath);
|
|
1705
|
+
}
|
|
1706
|
+
} catch {
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
} catch {
|
|
1710
|
+
}
|
|
1711
|
+
return totalSize;
|
|
1712
|
+
}
|
|
1713
|
+
function findLargestFiles(dirPath, count = 10) {
|
|
1714
|
+
const allFiles = [];
|
|
1715
|
+
function collectFiles(dir) {
|
|
1716
|
+
try {
|
|
1717
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
1718
|
+
for (const entry of entries) {
|
|
1719
|
+
const entryPath = join3(dir, entry.name);
|
|
1720
|
+
try {
|
|
1721
|
+
if (entry.isFile()) {
|
|
1722
|
+
const stats = statSync(entryPath);
|
|
1723
|
+
allFiles.push({
|
|
1724
|
+
path: entryPath,
|
|
1725
|
+
name: entry.name,
|
|
1726
|
+
size: stats.size,
|
|
1727
|
+
isDirectory: false
|
|
1728
|
+
});
|
|
1729
|
+
} else if (entry.isDirectory()) {
|
|
1730
|
+
if (!["node_modules", ".git"].includes(entry.name)) {
|
|
1731
|
+
collectFiles(entryPath);
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
} catch {
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
} catch {
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
collectFiles(dirPath);
|
|
1741
|
+
allFiles.sort((a, b3) => b3.size - a.size);
|
|
1742
|
+
return allFiles.slice(0, count);
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
// src/commands/utilities/size.ts
|
|
1746
|
+
import { resolve } from "path";
|
|
1747
|
+
registerCommand({
|
|
1748
|
+
name: "size",
|
|
1749
|
+
description: "Analyze file/folder sizes",
|
|
1750
|
+
usage: "/size [path] [--top N]",
|
|
1751
|
+
async execute(args2) {
|
|
1752
|
+
const targetPath = args2[0] || ".";
|
|
1753
|
+
const absPath = resolve(targetPath);
|
|
1754
|
+
const topIndex = args2.indexOf("--top");
|
|
1755
|
+
const showTop = topIndex !== -1 ? parseInt(args2[topIndex + 1]) || 10 : 0;
|
|
1756
|
+
console.log("");
|
|
1757
|
+
if (showTop > 0) {
|
|
1758
|
+
console.log(` ${symbols.sparkle} ${theme.gradient("LARGEST FILES")}`);
|
|
1759
|
+
console.log(` ${theme.dim(`in ${absPath}`)}`);
|
|
1760
|
+
console.log("");
|
|
1761
|
+
const largestFiles = findLargestFiles(absPath, showTop);
|
|
1762
|
+
if (largestFiles.length === 0) {
|
|
1763
|
+
console.log(` ${theme.dim("No files found")}`);
|
|
1764
|
+
} else {
|
|
1765
|
+
const maxSize = largestFiles[0].size;
|
|
1766
|
+
for (let i = 0; i < largestFiles.length; i++) {
|
|
1767
|
+
const file = largestFiles[i];
|
|
1768
|
+
const percent = file.size / maxSize * 100;
|
|
1769
|
+
const bar = progressBar(percent, 20);
|
|
1770
|
+
const relativePath = file.path.replace(absPath, ".").replace(/\\/g, "/");
|
|
1771
|
+
console.log(
|
|
1772
|
+
` ${theme.dim(`${(i + 1).toString().padStart(2)}.`)} ${bar} ${theme.primary(formatBytes2(file.size).padStart(10))} ${relativePath}`
|
|
1773
|
+
);
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
console.log("");
|
|
1777
|
+
return;
|
|
1778
|
+
}
|
|
1779
|
+
const info = getSize(absPath);
|
|
1780
|
+
if (!info) {
|
|
1781
|
+
console.log(` ${symbols.cross} ${theme.error(`Path not found: ${absPath}`)}`);
|
|
1782
|
+
console.log("");
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1785
|
+
console.log(` ${symbols.sparkle} ${theme.gradient("SIZE ANALYSIS")}`);
|
|
1786
|
+
console.log(` ${theme.dim(absPath)}`);
|
|
1787
|
+
console.log("");
|
|
1788
|
+
if (!info.isDirectory) {
|
|
1789
|
+
console.log(` ${theme.primary(info.name)}: ${theme.success(formatBytes2(info.size))}`);
|
|
1790
|
+
} else {
|
|
1791
|
+
console.log(` ${theme.secondary("Total:")} ${theme.success(formatBytes2(info.size))}`);
|
|
1792
|
+
console.log("");
|
|
1793
|
+
if (info.children && info.children.length > 0) {
|
|
1794
|
+
const maxSize = info.children[0].size;
|
|
1795
|
+
const displayCount = Math.min(15, info.children.length);
|
|
1796
|
+
for (let i = 0; i < displayCount; i++) {
|
|
1797
|
+
const child = info.children[i];
|
|
1798
|
+
const percent = child.size / maxSize * 100;
|
|
1799
|
+
const bar = progressBar(percent, 15);
|
|
1800
|
+
const icon = child.isDirectory ? symbols.folder : symbols.bullet;
|
|
1801
|
+
console.log(
|
|
1802
|
+
` ${bar} ${theme.primary(formatBytes2(child.size).padStart(10))} ${icon} ${child.name}`
|
|
1803
|
+
);
|
|
1804
|
+
}
|
|
1805
|
+
if (info.children.length > displayCount) {
|
|
1806
|
+
console.log("");
|
|
1807
|
+
console.log(` ${theme.dim(`... and ${info.children.length - displayCount} more items`)}`);
|
|
1808
|
+
}
|
|
1809
|
+
} else {
|
|
1810
|
+
console.log(` ${theme.dim("Directory is empty")}`);
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
console.log("");
|
|
1814
|
+
console.log(` ${theme.dim("Tip: /size . --top 10 shows largest files")}`);
|
|
1815
|
+
console.log("");
|
|
1816
|
+
}
|
|
1817
|
+
});
|
|
1818
|
+
|
|
1471
1819
|
// src/commands/fun/joke.ts
|
|
1472
1820
|
var fallbackJokes = [
|
|
1473
1821
|
{ setup: "Why do programmers prefer dark mode?", punchline: "Because light attracts bugs!" },
|
|
1474
1822
|
{ setup: "Why do Java developers wear glasses?", punchline: "Because they can't C#!" },
|
|
1475
1823
|
{ setup: "A SQL query walks into a bar, walks up to two tables and asks...", punchline: "Can I join you?" },
|
|
1476
1824
|
{ setup: "Why did the developer go broke?", punchline: "Because he used up all his cache!" },
|
|
1477
|
-
{ setup: "How many programmers does it take to change a light bulb?", punchline: "None, that's a hardware problem!" }
|
|
1825
|
+
{ setup: "How many programmers does it take to change a light bulb?", punchline: "None, that's a hardware problem!" },
|
|
1826
|
+
{ setup: "Why do programmers hate nature?", punchline: "It has too many bugs!" },
|
|
1827
|
+
{ setup: "What's a programmer's favorite hangout place?", punchline: "Foo Bar!" },
|
|
1828
|
+
{ setup: "Why was the JavaScript developer sad?", punchline: "Because he didn't Node how to Express himself!" },
|
|
1829
|
+
{ setup: "What do you call a computer that sings?", punchline: "A-Dell!" },
|
|
1830
|
+
{ setup: "Why did the developer quit his job?", punchline: "Because he didn't get arrays (a raise)!" }
|
|
1478
1831
|
];
|
|
1832
|
+
async function fetchJoke() {
|
|
1833
|
+
try {
|
|
1834
|
+
const response = await fetch("https://v2.jokeapi.dev/joke/Programming?type=twopart&safe-mode", {
|
|
1835
|
+
signal: AbortSignal.timeout(3e3)
|
|
1836
|
+
});
|
|
1837
|
+
if (response.ok) {
|
|
1838
|
+
const data = await response.json();
|
|
1839
|
+
if (!data.error && data.setup && data.delivery) {
|
|
1840
|
+
return { setup: data.setup, punchline: data.delivery };
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
} catch {
|
|
1844
|
+
}
|
|
1845
|
+
try {
|
|
1846
|
+
const response = await fetch("https://official-joke-api.appspot.com/jokes/programming/random", {
|
|
1847
|
+
signal: AbortSignal.timeout(3e3)
|
|
1848
|
+
});
|
|
1849
|
+
if (response.ok) {
|
|
1850
|
+
const data = await response.json();
|
|
1851
|
+
if (data && data[0]) {
|
|
1852
|
+
return { setup: data[0].setup, punchline: data[0].punchline };
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
} catch {
|
|
1856
|
+
}
|
|
1857
|
+
return fallbackJokes[Math.floor(Math.random() * fallbackJokes.length)];
|
|
1858
|
+
}
|
|
1479
1859
|
registerCommand({
|
|
1480
1860
|
name: "joke",
|
|
1481
|
-
description: "Get a random joke",
|
|
1861
|
+
description: "Get a random programming joke",
|
|
1482
1862
|
usage: "/joke",
|
|
1483
1863
|
async execute(_args) {
|
|
1484
1864
|
console.log("");
|
|
1485
1865
|
console.log(` ${symbols.dice} ${theme.sunset("Getting a joke...")}`);
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
console.log(bubble.say(joke.setup, 55));
|
|
1499
|
-
console.log(` ${symbols.sparkle}`);
|
|
1500
|
-
await new Promise((resolve3) => setTimeout(resolve3, 1500));
|
|
1501
|
-
console.log("");
|
|
1502
|
-
console.log(` ${theme.gold(" \u2726")} ${theme.b.success(joke.punchline)} ${theme.gold("\u2726")}`);
|
|
1503
|
-
console.log("");
|
|
1504
|
-
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")}`);
|
|
1505
|
-
console.log(` ${symbols.dice} ${theme.dim("Run /joke again for another!")}`);
|
|
1506
|
-
console.log("");
|
|
1507
|
-
} catch (error) {
|
|
1508
|
-
process.stdout.write("\x1B[1A\x1B[2K");
|
|
1509
|
-
const joke = fallbackJokes[Math.floor(Math.random() * fallbackJokes.length)];
|
|
1510
|
-
console.log("");
|
|
1511
|
-
console.log(bubble.say(joke.setup, 55));
|
|
1512
|
-
console.log(` ${symbols.sparkle}`);
|
|
1513
|
-
await new Promise((resolve3) => setTimeout(resolve3, 1500));
|
|
1514
|
-
console.log("");
|
|
1515
|
-
console.log(` ${theme.gold(" \u2726")} ${theme.b.success(joke.punchline)} ${theme.gold("\u2726")}`);
|
|
1516
|
-
console.log("");
|
|
1517
|
-
}
|
|
1866
|
+
const joke = await fetchJoke();
|
|
1867
|
+
process.stdout.write("\x1B[1A\x1B[2K");
|
|
1868
|
+
console.log("");
|
|
1869
|
+
console.log(bubble.say(joke.setup, 55));
|
|
1870
|
+
console.log(` ${symbols.sparkle}`);
|
|
1871
|
+
await new Promise((resolve6) => setTimeout(resolve6, 1500));
|
|
1872
|
+
console.log("");
|
|
1873
|
+
console.log(` ${theme.gold(" \u2726")} ${theme.b.success(joke.punchline)} ${theme.gold("\u2726")}`);
|
|
1874
|
+
console.log("");
|
|
1875
|
+
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")}`);
|
|
1876
|
+
console.log(` ${symbols.dice} ${theme.dim("Run /joke again for another!")}`);
|
|
1877
|
+
console.log("");
|
|
1518
1878
|
}
|
|
1519
1879
|
});
|
|
1520
1880
|
|
|
1521
1881
|
// src/commands/fun/quote.ts
|
|
1522
|
-
var
|
|
1882
|
+
var fallbackQuotes = [
|
|
1523
1883
|
{ text: "The only way to do great work is to love what you do.", author: "Steve Jobs" },
|
|
1524
1884
|
{ text: "Code is like humor. When you have to explain it, it's bad.", author: "Cory House" },
|
|
1525
1885
|
{ text: "First, solve the problem. Then, write the code.", author: "John Johnson" },
|
|
@@ -1536,6 +1896,33 @@ var quotes = [
|
|
|
1536
1896
|
{ text: "There are only two hard things in Computer Science: cache invalidation and naming things.", author: "Phil Karlton" },
|
|
1537
1897
|
{ text: "The best time to plant a tree was 20 years ago. The second best time is now.", author: "Chinese Proverb" }
|
|
1538
1898
|
];
|
|
1899
|
+
async function fetchQuote() {
|
|
1900
|
+
try {
|
|
1901
|
+
const response = await fetch("https://zenquotes.io/api/random", {
|
|
1902
|
+
signal: AbortSignal.timeout(3e3)
|
|
1903
|
+
});
|
|
1904
|
+
if (response.ok) {
|
|
1905
|
+
const data = await response.json();
|
|
1906
|
+
if (data && data[0]) {
|
|
1907
|
+
return { text: data[0].q, author: data[0].a };
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
} catch {
|
|
1911
|
+
}
|
|
1912
|
+
try {
|
|
1913
|
+
const response = await fetch("https://api.quotable.io/random", {
|
|
1914
|
+
signal: AbortSignal.timeout(3e3)
|
|
1915
|
+
});
|
|
1916
|
+
if (response.ok) {
|
|
1917
|
+
const data = await response.json();
|
|
1918
|
+
if (data && data.content) {
|
|
1919
|
+
return { text: data.content, author: data.author };
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
} catch {
|
|
1923
|
+
}
|
|
1924
|
+
return fallbackQuotes[Math.floor(Math.random() * fallbackQuotes.length)];
|
|
1925
|
+
}
|
|
1539
1926
|
function wrapText(text, maxWidth) {
|
|
1540
1927
|
const words = text.split(" ");
|
|
1541
1928
|
const lines = [];
|
|
@@ -1556,7 +1943,10 @@ registerCommand({
|
|
|
1556
1943
|
description: "Get an inspirational quote",
|
|
1557
1944
|
usage: "/quote",
|
|
1558
1945
|
async execute(_args) {
|
|
1559
|
-
|
|
1946
|
+
console.log("");
|
|
1947
|
+
console.log(` ${symbols.sparkle} ${theme.dim("Fetching wisdom...")}`);
|
|
1948
|
+
const quote = await fetchQuote();
|
|
1949
|
+
process.stdout.write("\x1B[1A\x1B[2K\x1B[1A\x1B[2K");
|
|
1560
1950
|
const wrapped = wrapText(quote.text, 55);
|
|
1561
1951
|
console.log("");
|
|
1562
1952
|
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")}`);
|
|
@@ -2106,8 +2496,8 @@ registerCommand({
|
|
|
2106
2496
|
|
|
2107
2497
|
// src/commands/creative/asciiart.ts
|
|
2108
2498
|
import Jimp from "jimp";
|
|
2109
|
-
import { existsSync as
|
|
2110
|
-
import { resolve } from "path";
|
|
2499
|
+
import { existsSync as existsSync4 } from "fs";
|
|
2500
|
+
import { resolve as resolve2 } from "path";
|
|
2111
2501
|
var CHAR_RAMPS = {
|
|
2112
2502
|
// Standard - 10 levels, balanced
|
|
2113
2503
|
standard: " .:-=+*#%@",
|
|
@@ -2212,8 +2602,8 @@ registerCommand({
|
|
|
2212
2602
|
i++;
|
|
2213
2603
|
}
|
|
2214
2604
|
}
|
|
2215
|
-
const fullPath =
|
|
2216
|
-
if (!
|
|
2605
|
+
const fullPath = resolve2(process.cwd(), imagePath);
|
|
2606
|
+
if (!existsSync4(fullPath)) {
|
|
2217
2607
|
console.log(theme.error(`File not found: ${imagePath}`));
|
|
2218
2608
|
return;
|
|
2219
2609
|
}
|
|
@@ -2865,53 +3255,1880 @@ registerCommand({
|
|
|
2865
3255
|
}
|
|
2866
3256
|
});
|
|
2867
3257
|
|
|
2868
|
-
// src/
|
|
3258
|
+
// src/handlers/dev/json.ts
|
|
3259
|
+
function validateJson(input) {
|
|
3260
|
+
try {
|
|
3261
|
+
const data = JSON.parse(input);
|
|
3262
|
+
return { valid: true, data };
|
|
3263
|
+
} catch (error) {
|
|
3264
|
+
const message = error instanceof Error ? error.message : "Invalid JSON";
|
|
3265
|
+
return { valid: false, error: message };
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
function formatJson(input, indent = 2) {
|
|
3269
|
+
try {
|
|
3270
|
+
const data = JSON.parse(input);
|
|
3271
|
+
const formatted = JSON.stringify(data, null, indent);
|
|
3272
|
+
return { valid: true, data, formatted };
|
|
3273
|
+
} catch (error) {
|
|
3274
|
+
const message = error instanceof Error ? error.message : "Invalid JSON";
|
|
3275
|
+
return { valid: false, error: message };
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3278
|
+
function minifyJson(input) {
|
|
3279
|
+
try {
|
|
3280
|
+
const data = JSON.parse(input);
|
|
3281
|
+
const formatted = JSON.stringify(data);
|
|
3282
|
+
return { valid: true, data, formatted };
|
|
3283
|
+
} catch (error) {
|
|
3284
|
+
const message = error instanceof Error ? error.message : "Invalid JSON";
|
|
3285
|
+
return { valid: false, error: message };
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3288
|
+
function queryJson(input, path) {
|
|
3289
|
+
try {
|
|
3290
|
+
const data = JSON.parse(input);
|
|
3291
|
+
const parts = path.replace(/^\$\.?/, "").split(".").filter(Boolean);
|
|
3292
|
+
let current = data;
|
|
3293
|
+
for (const part of parts) {
|
|
3294
|
+
const arrayMatch = part.match(/^(\w*)\[(\d+)\]$/);
|
|
3295
|
+
if (arrayMatch) {
|
|
3296
|
+
const [, key, index] = arrayMatch;
|
|
3297
|
+
if (key) {
|
|
3298
|
+
current = current[key];
|
|
3299
|
+
}
|
|
3300
|
+
if (Array.isArray(current)) {
|
|
3301
|
+
current = current[parseInt(index)];
|
|
3302
|
+
} else {
|
|
3303
|
+
return { valid: false, error: `Not an array at ${part}` };
|
|
3304
|
+
}
|
|
3305
|
+
} else {
|
|
3306
|
+
if (current && typeof current === "object") {
|
|
3307
|
+
current = current[part];
|
|
3308
|
+
} else {
|
|
3309
|
+
return { valid: false, error: `Cannot access ${part}` };
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
return { valid: true, data: current, formatted: JSON.stringify(current, null, 2) };
|
|
3314
|
+
} catch (error) {
|
|
3315
|
+
const message = error instanceof Error ? error.message : "Invalid JSON";
|
|
3316
|
+
return { valid: false, error: message };
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
function getJsonStats(input) {
|
|
3320
|
+
try {
|
|
3321
|
+
let countKeys2 = function(obj, depth = 0) {
|
|
3322
|
+
if (typeof obj !== "object" || obj === null) {
|
|
3323
|
+
return { keys: 0, maxDepth: depth };
|
|
3324
|
+
}
|
|
3325
|
+
let keys = 0;
|
|
3326
|
+
let maxDepth = depth;
|
|
3327
|
+
if (Array.isArray(obj)) {
|
|
3328
|
+
for (const item of obj) {
|
|
3329
|
+
const result = countKeys2(item, depth + 1);
|
|
3330
|
+
keys += result.keys;
|
|
3331
|
+
maxDepth = Math.max(maxDepth, result.maxDepth);
|
|
3332
|
+
}
|
|
3333
|
+
} else {
|
|
3334
|
+
keys = Object.keys(obj).length;
|
|
3335
|
+
for (const value of Object.values(obj)) {
|
|
3336
|
+
const result = countKeys2(value, depth + 1);
|
|
3337
|
+
keys += result.keys;
|
|
3338
|
+
maxDepth = Math.max(maxDepth, result.maxDepth);
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
return { keys, maxDepth };
|
|
3342
|
+
};
|
|
3343
|
+
var countKeys = countKeys2;
|
|
3344
|
+
const data = JSON.parse(input);
|
|
3345
|
+
const stats = countKeys2(data);
|
|
3346
|
+
const size = new Blob([input]).size;
|
|
3347
|
+
const sizeStr = size < 1024 ? `${size}B` : size < 1024 * 1024 ? `${(size / 1024).toFixed(1)}KB` : `${(size / 1024 / 1024).toFixed(1)}MB`;
|
|
3348
|
+
return { keys: stats.keys, depth: stats.maxDepth, size: sizeStr };
|
|
3349
|
+
} catch {
|
|
3350
|
+
return null;
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3353
|
+
|
|
3354
|
+
// src/commands/dev/json.ts
|
|
3355
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
|
|
2869
3356
|
registerCommand({
|
|
2870
|
-
name: "
|
|
2871
|
-
description: "
|
|
2872
|
-
usage: "/
|
|
3357
|
+
name: "json",
|
|
3358
|
+
description: "JSON tools (validate, format, query)",
|
|
3359
|
+
usage: "/json <action> <input>\n\n Actions: validate, format, minify, query, stats",
|
|
2873
3360
|
async execute(args2) {
|
|
2874
|
-
const
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
const response = await fetch(`https://wttr.in/${encodeURIComponent(city)}?format=j1`);
|
|
2878
|
-
if (!response.ok) {
|
|
2879
|
-
console.log(theme.error(`Could not fetch weather for "${city}"`));
|
|
2880
|
-
return;
|
|
2881
|
-
}
|
|
2882
|
-
const data = await response.json();
|
|
2883
|
-
const current = data.current_condition[0];
|
|
2884
|
-
const location = data.nearest_area[0];
|
|
2885
|
-
const temp = current.temp_C;
|
|
2886
|
-
const feelsLike = current.FeelsLikeC;
|
|
2887
|
-
const desc = current.weatherDesc[0].value;
|
|
2888
|
-
const humidity = current.humidity;
|
|
2889
|
-
const wind = current.windspeedKmph;
|
|
2890
|
-
const cityName = location.areaName[0].value;
|
|
2891
|
-
const country = location.country[0].value;
|
|
3361
|
+
const action = args2[0]?.toLowerCase();
|
|
3362
|
+
const input = args2.slice(1).join(" ");
|
|
3363
|
+
if (!action) {
|
|
2892
3364
|
console.log("");
|
|
2893
|
-
console.log(
|
|
3365
|
+
console.log(` ${symbols.sparkle} ${theme.gradient("JSON TOOLS")}`);
|
|
2894
3366
|
console.log("");
|
|
2895
|
-
console.log(` ${theme.
|
|
2896
|
-
console.log(
|
|
2897
|
-
console.log(`
|
|
3367
|
+
console.log(` ${theme.dim("Usage:")} /json <action> <input>`);
|
|
3368
|
+
console.log("");
|
|
3369
|
+
console.log(` ${theme.dim("Actions:")}`);
|
|
3370
|
+
console.log(` ${theme.primary("validate")} <json|@file> ${theme.dim("Check if JSON is valid")}`);
|
|
3371
|
+
console.log(` ${theme.primary("format")} <json|@file> ${theme.dim("Pretty-print JSON")}`);
|
|
3372
|
+
console.log(` ${theme.primary("minify")} <json|@file> ${theme.dim("Minify JSON")}`);
|
|
3373
|
+
console.log(` ${theme.primary("query")} <path> <json> ${theme.dim("Query with path (e.g., users[0].name)")}`);
|
|
3374
|
+
console.log(` ${theme.primary("stats")} <json|@file> ${theme.dim("Show JSON statistics")}`);
|
|
3375
|
+
console.log("");
|
|
3376
|
+
console.log(` ${theme.dim("Use @filename to read from file")}`);
|
|
3377
|
+
console.log("");
|
|
3378
|
+
return;
|
|
3379
|
+
}
|
|
3380
|
+
let jsonContent = input;
|
|
3381
|
+
if (input.startsWith("@")) {
|
|
3382
|
+
const filePath = input.slice(1);
|
|
3383
|
+
if (!existsSync5(filePath)) {
|
|
3384
|
+
console.log("");
|
|
3385
|
+
console.log(` ${symbols.cross} ${theme.error(`File not found: ${filePath}`)}`);
|
|
3386
|
+
console.log("");
|
|
3387
|
+
return;
|
|
3388
|
+
}
|
|
3389
|
+
jsonContent = readFileSync3(filePath, "utf-8");
|
|
3390
|
+
}
|
|
3391
|
+
console.log("");
|
|
3392
|
+
switch (action) {
|
|
3393
|
+
case "validate": {
|
|
3394
|
+
if (!jsonContent) {
|
|
3395
|
+
console.log(` ${symbols.warning} ${theme.warning("Usage:")} /json validate <json|@file>`);
|
|
3396
|
+
break;
|
|
3397
|
+
}
|
|
3398
|
+
const result = validateJson(jsonContent);
|
|
3399
|
+
if (result.valid) {
|
|
3400
|
+
console.log(` ${symbols.check} ${theme.success("Valid JSON")}`);
|
|
3401
|
+
} else {
|
|
3402
|
+
console.log(` ${symbols.cross} ${theme.error("Invalid JSON")}`);
|
|
3403
|
+
console.log(` ${theme.dim(result.error || "")}`);
|
|
3404
|
+
}
|
|
3405
|
+
break;
|
|
3406
|
+
}
|
|
3407
|
+
case "format":
|
|
3408
|
+
case "pretty": {
|
|
3409
|
+
if (!jsonContent) {
|
|
3410
|
+
console.log(` ${symbols.warning} ${theme.warning("Usage:")} /json format <json|@file>`);
|
|
3411
|
+
break;
|
|
3412
|
+
}
|
|
3413
|
+
const result = formatJson(jsonContent);
|
|
3414
|
+
if (result.valid && result.formatted) {
|
|
3415
|
+
console.log(` ${symbols.check} ${theme.success("Formatted JSON:")}`);
|
|
3416
|
+
console.log("");
|
|
3417
|
+
for (const line of result.formatted.split("\n")) {
|
|
3418
|
+
console.log(` ${theme.primary(line)}`);
|
|
3419
|
+
}
|
|
3420
|
+
} else {
|
|
3421
|
+
console.log(` ${symbols.cross} ${theme.error("Invalid JSON")}`);
|
|
3422
|
+
console.log(` ${theme.dim(result.error || "")}`);
|
|
3423
|
+
}
|
|
3424
|
+
break;
|
|
3425
|
+
}
|
|
3426
|
+
case "minify":
|
|
3427
|
+
case "min": {
|
|
3428
|
+
if (!jsonContent) {
|
|
3429
|
+
console.log(` ${symbols.warning} ${theme.warning("Usage:")} /json minify <json|@file>`);
|
|
3430
|
+
break;
|
|
3431
|
+
}
|
|
3432
|
+
const result = minifyJson(jsonContent);
|
|
3433
|
+
if (result.valid && result.formatted) {
|
|
3434
|
+
console.log(` ${symbols.check} ${theme.success("Minified:")}`);
|
|
3435
|
+
console.log("");
|
|
3436
|
+
console.log(` ${theme.primary(result.formatted)}`);
|
|
3437
|
+
} else {
|
|
3438
|
+
console.log(` ${symbols.cross} ${theme.error("Invalid JSON")}`);
|
|
3439
|
+
console.log(` ${theme.dim(result.error || "")}`);
|
|
3440
|
+
}
|
|
3441
|
+
break;
|
|
3442
|
+
}
|
|
3443
|
+
case "query":
|
|
3444
|
+
case "get": {
|
|
3445
|
+
const path = args2[1];
|
|
3446
|
+
const queryInput = args2.slice(2).join(" ");
|
|
3447
|
+
if (!path || !queryInput) {
|
|
3448
|
+
console.log(` ${symbols.warning} ${theme.warning("Usage:")} /json query <path> <json|@file>`);
|
|
3449
|
+
console.log("");
|
|
3450
|
+
console.log(` ${theme.dim("Examples:")}`);
|
|
3451
|
+
console.log(` /json query name '{"name": "John"}'`);
|
|
3452
|
+
console.log(` /json query users[0].email @data.json`);
|
|
3453
|
+
break;
|
|
3454
|
+
}
|
|
3455
|
+
let queryJsonContent = queryInput;
|
|
3456
|
+
if (queryInput.startsWith("@")) {
|
|
3457
|
+
const filePath = queryInput.slice(1);
|
|
3458
|
+
if (!existsSync5(filePath)) {
|
|
3459
|
+
console.log(` ${symbols.cross} ${theme.error(`File not found: ${filePath}`)}`);
|
|
3460
|
+
break;
|
|
3461
|
+
}
|
|
3462
|
+
queryJsonContent = readFileSync3(filePath, "utf-8");
|
|
3463
|
+
}
|
|
3464
|
+
const result = queryJson(queryJsonContent, path);
|
|
3465
|
+
if (result.valid) {
|
|
3466
|
+
console.log(` ${symbols.check} ${theme.success(`Result for "${path}":`)} `);
|
|
3467
|
+
console.log("");
|
|
3468
|
+
if (result.formatted) {
|
|
3469
|
+
for (const line of result.formatted.split("\n")) {
|
|
3470
|
+
console.log(` ${theme.primary(line)}`);
|
|
3471
|
+
}
|
|
3472
|
+
} else {
|
|
3473
|
+
console.log(` ${theme.dim("undefined")}`);
|
|
3474
|
+
}
|
|
3475
|
+
} else {
|
|
3476
|
+
console.log(` ${symbols.cross} ${theme.error(result.error || "Query failed")}`);
|
|
3477
|
+
}
|
|
3478
|
+
break;
|
|
3479
|
+
}
|
|
3480
|
+
case "stats":
|
|
3481
|
+
case "info": {
|
|
3482
|
+
if (!jsonContent) {
|
|
3483
|
+
console.log(` ${symbols.warning} ${theme.warning("Usage:")} /json stats <json|@file>`);
|
|
3484
|
+
break;
|
|
3485
|
+
}
|
|
3486
|
+
const stats = getJsonStats(jsonContent);
|
|
3487
|
+
if (stats) {
|
|
3488
|
+
console.log(` ${symbols.sparkle} ${theme.gradient("JSON STATS")}`);
|
|
3489
|
+
console.log("");
|
|
3490
|
+
console.log(` ${theme.dim("Total keys:")} ${theme.primary(stats.keys.toString())}`);
|
|
3491
|
+
console.log(` ${theme.dim("Max depth:")} ${theme.primary(stats.depth.toString())}`);
|
|
3492
|
+
console.log(` ${theme.dim("Size:")} ${theme.primary(stats.size)}`);
|
|
3493
|
+
} else {
|
|
3494
|
+
console.log(` ${symbols.cross} ${theme.error("Invalid JSON")}`);
|
|
3495
|
+
}
|
|
3496
|
+
break;
|
|
3497
|
+
}
|
|
3498
|
+
default:
|
|
3499
|
+
console.log(` ${symbols.cross} ${theme.error(`Unknown action: ${action}`)}`);
|
|
3500
|
+
console.log(` ${theme.dim("Run /json to see available actions")}`);
|
|
3501
|
+
}
|
|
3502
|
+
console.log("");
|
|
3503
|
+
}
|
|
3504
|
+
});
|
|
3505
|
+
|
|
3506
|
+
// src/handlers/dev/request.ts
|
|
3507
|
+
import https from "https";
|
|
3508
|
+
import http from "http";
|
|
3509
|
+
import { URL } from "url";
|
|
3510
|
+
async function makeRequest(urlStr, options = { method: "GET" }) {
|
|
3511
|
+
const startTime2 = Date.now();
|
|
3512
|
+
return new Promise((resolve6) => {
|
|
3513
|
+
const timeout = setTimeout(() => {
|
|
3514
|
+
resolve6({ success: false, error: "Request timed out" });
|
|
3515
|
+
}, options.timeout || 3e4);
|
|
3516
|
+
try {
|
|
3517
|
+
const url = new URL(urlStr.startsWith("http") ? urlStr : `https://${urlStr}`);
|
|
3518
|
+
const client = url.protocol === "https:" ? https : http;
|
|
3519
|
+
const reqOptions = {
|
|
3520
|
+
hostname: url.hostname,
|
|
3521
|
+
port: url.port || (url.protocol === "https:" ? 443 : 80),
|
|
3522
|
+
path: url.pathname + url.search,
|
|
3523
|
+
method: options.method.toUpperCase(),
|
|
3524
|
+
headers: {
|
|
3525
|
+
"User-Agent": "Zammy-CLI/1.0",
|
|
3526
|
+
...options.headers
|
|
3527
|
+
}
|
|
3528
|
+
};
|
|
3529
|
+
const req = client.request(reqOptions, (res) => {
|
|
3530
|
+
let body = "";
|
|
3531
|
+
res.on("data", (chunk) => {
|
|
3532
|
+
body += chunk.toString();
|
|
3533
|
+
});
|
|
3534
|
+
res.on("end", () => {
|
|
3535
|
+
clearTimeout(timeout);
|
|
3536
|
+
resolve6({
|
|
3537
|
+
success: true,
|
|
3538
|
+
statusCode: res.statusCode,
|
|
3539
|
+
statusMessage: res.statusMessage,
|
|
3540
|
+
headers: res.headers,
|
|
3541
|
+
body,
|
|
3542
|
+
time: Date.now() - startTime2
|
|
3543
|
+
});
|
|
3544
|
+
});
|
|
3545
|
+
});
|
|
3546
|
+
req.on("error", (error) => {
|
|
3547
|
+
clearTimeout(timeout);
|
|
3548
|
+
resolve6({ success: false, error: error.message });
|
|
3549
|
+
});
|
|
3550
|
+
if (options.body) {
|
|
3551
|
+
req.write(options.body);
|
|
3552
|
+
}
|
|
3553
|
+
req.end();
|
|
3554
|
+
} catch (error) {
|
|
3555
|
+
clearTimeout(timeout);
|
|
3556
|
+
resolve6({ success: false, error: error instanceof Error ? error.message : "Request failed" });
|
|
3557
|
+
}
|
|
3558
|
+
});
|
|
3559
|
+
}
|
|
3560
|
+
function tryParseJson(body) {
|
|
3561
|
+
try {
|
|
3562
|
+
const parsed = JSON.parse(body);
|
|
3563
|
+
return { isJson: true, formatted: JSON.stringify(parsed, null, 2) };
|
|
3564
|
+
} catch {
|
|
3565
|
+
return { isJson: false };
|
|
3566
|
+
}
|
|
3567
|
+
}
|
|
3568
|
+
|
|
3569
|
+
// src/commands/dev/request.ts
|
|
3570
|
+
registerCommand({
|
|
3571
|
+
name: "request",
|
|
3572
|
+
description: "Make HTTP requests",
|
|
3573
|
+
usage: "/request <method> <url> [options]",
|
|
3574
|
+
async execute(args2) {
|
|
3575
|
+
if (args2.length < 1) {
|
|
3576
|
+
console.log("");
|
|
3577
|
+
console.log(` ${symbols.sparkle} ${theme.gradient("HTTP REQUEST")}`);
|
|
3578
|
+
console.log("");
|
|
3579
|
+
console.log(` ${theme.dim("Usage:")} /request <method> <url> [options]`);
|
|
3580
|
+
console.log("");
|
|
3581
|
+
console.log(` ${theme.dim("Methods:")} GET, POST, PUT, DELETE, PATCH, HEAD`);
|
|
3582
|
+
console.log("");
|
|
3583
|
+
console.log(` ${theme.dim("Examples:")}`);
|
|
3584
|
+
console.log(` /request GET https://api.github.com`);
|
|
3585
|
+
console.log(` /request POST https://httpbin.org/post --body '{"name":"test"}'`);
|
|
3586
|
+
console.log(` /request GET api.example.com/users`);
|
|
3587
|
+
console.log("");
|
|
3588
|
+
return;
|
|
3589
|
+
}
|
|
3590
|
+
let method = "GET";
|
|
3591
|
+
let url = args2[0];
|
|
3592
|
+
const methods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
|
|
3593
|
+
if (methods.includes(args2[0].toUpperCase())) {
|
|
3594
|
+
method = args2[0].toUpperCase();
|
|
3595
|
+
url = args2[1];
|
|
3596
|
+
}
|
|
3597
|
+
if (!url) {
|
|
3598
|
+
console.log("");
|
|
3599
|
+
console.log(` ${symbols.cross} ${theme.error("URL is required")}`);
|
|
3600
|
+
console.log("");
|
|
3601
|
+
return;
|
|
3602
|
+
}
|
|
3603
|
+
const headers = {};
|
|
3604
|
+
let body;
|
|
3605
|
+
for (let i = 2; i < args2.length; i++) {
|
|
3606
|
+
if (args2[i] === "--header" || args2[i] === "-H") {
|
|
3607
|
+
const header = args2[++i];
|
|
3608
|
+
if (header) {
|
|
3609
|
+
const [key, ...valueParts] = header.split(":");
|
|
3610
|
+
headers[key.trim()] = valueParts.join(":").trim();
|
|
3611
|
+
}
|
|
3612
|
+
} else if (args2[i] === "--body" || args2[i] === "-d") {
|
|
3613
|
+
body = args2[++i];
|
|
3614
|
+
}
|
|
3615
|
+
}
|
|
3616
|
+
console.log("");
|
|
3617
|
+
console.log(` ${theme.dim(`${method} ${url}...`)}`);
|
|
3618
|
+
const result = await makeRequest(url, { method, headers, body });
|
|
3619
|
+
process.stdout.write("\x1B[1A\x1B[2K");
|
|
3620
|
+
if (!result.success) {
|
|
3621
|
+
console.log(` ${symbols.cross} ${theme.error(result.error || "Request failed")}`);
|
|
3622
|
+
console.log("");
|
|
3623
|
+
return;
|
|
3624
|
+
}
|
|
3625
|
+
const statusColor = result.statusCode && result.statusCode >= 200 && result.statusCode < 300 ? theme.success : result.statusCode && result.statusCode >= 400 ? theme.error : theme.warning;
|
|
3626
|
+
console.log(` ${statusColor(`${result.statusCode} ${result.statusMessage}`)} ${theme.dim(`(${result.time}ms)`)}`);
|
|
3627
|
+
console.log("");
|
|
3628
|
+
if (result.headers) {
|
|
3629
|
+
const importantHeaders = ["content-type", "content-length", "server", "date"];
|
|
3630
|
+
console.log(` ${theme.dim("Headers:")}`);
|
|
3631
|
+
for (const key of importantHeaders) {
|
|
3632
|
+
if (result.headers[key]) {
|
|
3633
|
+
console.log(` ${theme.secondary(key)}: ${result.headers[key]}`);
|
|
3634
|
+
}
|
|
3635
|
+
}
|
|
3636
|
+
console.log("");
|
|
3637
|
+
}
|
|
3638
|
+
if (result.body && method !== "HEAD") {
|
|
3639
|
+
console.log(` ${theme.dim("Body:")}`);
|
|
3640
|
+
const jsonResult = tryParseJson(result.body);
|
|
3641
|
+
if (jsonResult.isJson && jsonResult.formatted) {
|
|
3642
|
+
const lines = jsonResult.formatted.split("\n");
|
|
3643
|
+
const displayLines = lines.slice(0, 30);
|
|
3644
|
+
for (const line of displayLines) {
|
|
3645
|
+
console.log(` ${theme.primary(line)}`);
|
|
3646
|
+
}
|
|
3647
|
+
if (lines.length > 30) {
|
|
3648
|
+
console.log(` ${theme.dim(`... and ${lines.length - 30} more lines`)}`);
|
|
3649
|
+
}
|
|
3650
|
+
} else {
|
|
3651
|
+
const lines = result.body.split("\n").slice(0, 20);
|
|
3652
|
+
for (const line of lines) {
|
|
3653
|
+
console.log(` ${line.slice(0, 100)}`);
|
|
3654
|
+
}
|
|
3655
|
+
if (result.body.split("\n").length > 20) {
|
|
3656
|
+
console.log(` ${theme.dim("... (truncated)")}`);
|
|
3657
|
+
}
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3660
|
+
console.log("");
|
|
3661
|
+
}
|
|
3662
|
+
});
|
|
3663
|
+
|
|
3664
|
+
// src/handlers/dev/diff.ts
|
|
3665
|
+
import { readFileSync as readFileSync4, existsSync as existsSync6 } from "fs";
|
|
3666
|
+
function diffFiles(file1, file2) {
|
|
3667
|
+
if (!existsSync6(file1)) {
|
|
3668
|
+
return { success: false, lines: [], stats: { additions: 0, deletions: 0, unchanged: 0 }, error: `File not found: ${file1}` };
|
|
3669
|
+
}
|
|
3670
|
+
if (!existsSync6(file2)) {
|
|
3671
|
+
return { success: false, lines: [], stats: { additions: 0, deletions: 0, unchanged: 0 }, error: `File not found: ${file2}` };
|
|
3672
|
+
}
|
|
3673
|
+
try {
|
|
3674
|
+
const content1 = readFileSync4(file1, "utf-8");
|
|
3675
|
+
const content2 = readFileSync4(file2, "utf-8");
|
|
3676
|
+
return diffStrings(content1, content2);
|
|
3677
|
+
} catch (error) {
|
|
3678
|
+
return {
|
|
3679
|
+
success: false,
|
|
3680
|
+
lines: [],
|
|
3681
|
+
stats: { additions: 0, deletions: 0, unchanged: 0 },
|
|
3682
|
+
error: error instanceof Error ? error.message : "Failed to read files"
|
|
3683
|
+
};
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
function diffStrings(str1, str2) {
|
|
3687
|
+
const lines1 = str1.split("\n");
|
|
3688
|
+
const lines2 = str2.split("\n");
|
|
3689
|
+
const lcs = computeLCS(lines1, lines2);
|
|
3690
|
+
const diff = buildDiff(lines1, lines2, lcs);
|
|
3691
|
+
let additions = 0;
|
|
3692
|
+
let deletions = 0;
|
|
3693
|
+
let unchanged = 0;
|
|
3694
|
+
for (const line of diff) {
|
|
3695
|
+
if (line.type === "add") additions++;
|
|
3696
|
+
else if (line.type === "remove") deletions++;
|
|
3697
|
+
else if (line.type === "same") unchanged++;
|
|
3698
|
+
}
|
|
3699
|
+
return {
|
|
3700
|
+
success: true,
|
|
3701
|
+
lines: diff,
|
|
3702
|
+
stats: { additions, deletions, unchanged }
|
|
3703
|
+
};
|
|
3704
|
+
}
|
|
3705
|
+
function computeLCS(lines1, lines2) {
|
|
3706
|
+
const m = lines1.length;
|
|
3707
|
+
const n = lines2.length;
|
|
3708
|
+
const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
|
|
3709
|
+
for (let i = 1; i <= m; i++) {
|
|
3710
|
+
for (let j = 1; j <= n; j++) {
|
|
3711
|
+
if (lines1[i - 1] === lines2[j - 1]) {
|
|
3712
|
+
dp[i][j] = dp[i - 1][j - 1] + 1;
|
|
3713
|
+
} else {
|
|
3714
|
+
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
|
|
3715
|
+
}
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
return dp;
|
|
3719
|
+
}
|
|
3720
|
+
function buildDiff(lines1, lines2, lcs) {
|
|
3721
|
+
const result = [];
|
|
3722
|
+
let i = lines1.length;
|
|
3723
|
+
let j = lines2.length;
|
|
3724
|
+
const temp = [];
|
|
3725
|
+
while (i > 0 || j > 0) {
|
|
3726
|
+
if (i > 0 && j > 0 && lines1[i - 1] === lines2[j - 1]) {
|
|
3727
|
+
temp.push({ type: "same", content: lines1[i - 1], lineNum1: i, lineNum2: j });
|
|
3728
|
+
i--;
|
|
3729
|
+
j--;
|
|
3730
|
+
} else if (j > 0 && (i === 0 || lcs[i][j - 1] >= lcs[i - 1][j])) {
|
|
3731
|
+
temp.push({ type: "add", content: lines2[j - 1], lineNum2: j });
|
|
3732
|
+
j--;
|
|
3733
|
+
} else if (i > 0) {
|
|
3734
|
+
temp.push({ type: "remove", content: lines1[i - 1], lineNum1: i });
|
|
3735
|
+
i--;
|
|
3736
|
+
}
|
|
3737
|
+
}
|
|
3738
|
+
return temp.reverse();
|
|
3739
|
+
}
|
|
3740
|
+
function formatDiffStats(stats) {
|
|
3741
|
+
const parts = [];
|
|
3742
|
+
if (stats.additions > 0) parts.push(`+${stats.additions}`);
|
|
3743
|
+
if (stats.deletions > 0) parts.push(`-${stats.deletions}`);
|
|
3744
|
+
if (stats.unchanged > 0) parts.push(`=${stats.unchanged}`);
|
|
3745
|
+
return parts.join(", ");
|
|
3746
|
+
}
|
|
3747
|
+
|
|
3748
|
+
// src/commands/dev/diff.ts
|
|
3749
|
+
import { basename as basename2 } from "path";
|
|
3750
|
+
registerCommand({
|
|
3751
|
+
name: "diff",
|
|
3752
|
+
description: "Compare two files",
|
|
3753
|
+
usage: "/diff <file1> <file2>",
|
|
3754
|
+
async execute(args2) {
|
|
3755
|
+
if (args2.length < 2) {
|
|
3756
|
+
console.log("");
|
|
3757
|
+
console.log(` ${symbols.sparkle} ${theme.gradient("FILE DIFF")}`);
|
|
3758
|
+
console.log("");
|
|
3759
|
+
console.log(` ${theme.dim("Usage:")} /diff <file1> <file2>`);
|
|
3760
|
+
console.log("");
|
|
3761
|
+
console.log(` ${theme.dim("Example:")}`);
|
|
3762
|
+
console.log(` /diff old.json new.json`);
|
|
3763
|
+
console.log(` /diff config.ts config.backup.ts`);
|
|
3764
|
+
console.log("");
|
|
3765
|
+
return;
|
|
3766
|
+
}
|
|
3767
|
+
const file1 = args2[0];
|
|
3768
|
+
const file2 = args2[1];
|
|
3769
|
+
console.log("");
|
|
3770
|
+
const result = diffFiles(file1, file2);
|
|
3771
|
+
if (!result.success) {
|
|
3772
|
+
console.log(` ${symbols.cross} ${theme.error(result.error || "Diff failed")}`);
|
|
3773
|
+
console.log("");
|
|
3774
|
+
return;
|
|
3775
|
+
}
|
|
3776
|
+
console.log(` ${symbols.sparkle} ${theme.gradient("DIFF")}: ${theme.primary(basename2(file1))} ${theme.dim("\u2192")} ${theme.primary(basename2(file2))}`);
|
|
3777
|
+
console.log(` ${theme.dim(formatDiffStats(result.stats))}`);
|
|
3778
|
+
console.log("");
|
|
3779
|
+
if (result.stats.additions === 0 && result.stats.deletions === 0) {
|
|
3780
|
+
console.log(` ${symbols.check} ${theme.success("Files are identical")}`);
|
|
3781
|
+
console.log("");
|
|
3782
|
+
return;
|
|
3783
|
+
}
|
|
3784
|
+
let contextLines = 3;
|
|
3785
|
+
let lastShownIndex = -contextLines - 1;
|
|
3786
|
+
const linesToShow = [];
|
|
3787
|
+
result.lines.forEach((line, i) => {
|
|
3788
|
+
if (line.type !== "same") {
|
|
3789
|
+
for (let j = Math.max(0, i - contextLines); j <= Math.min(result.lines.length - 1, i + contextLines); j++) {
|
|
3790
|
+
if (!linesToShow.includes(j)) {
|
|
3791
|
+
linesToShow.push(j);
|
|
3792
|
+
}
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
});
|
|
3796
|
+
linesToShow.sort((a, b3) => a - b3);
|
|
3797
|
+
for (let i = 0; i < linesToShow.length; i++) {
|
|
3798
|
+
const lineIndex = linesToShow[i];
|
|
3799
|
+
const line = result.lines[lineIndex];
|
|
3800
|
+
if (i > 0 && linesToShow[i] - linesToShow[i - 1] > 1) {
|
|
3801
|
+
console.log(` ${theme.dim("...")}`);
|
|
3802
|
+
}
|
|
3803
|
+
const lineNum = line.lineNum1 || line.lineNum2 || "";
|
|
3804
|
+
const numStr = lineNum.toString().padStart(4);
|
|
3805
|
+
if (line.type === "add") {
|
|
3806
|
+
console.log(` ${theme.success("+")} ${theme.dim(numStr)} ${theme.success(line.content)}`);
|
|
3807
|
+
} else if (line.type === "remove") {
|
|
3808
|
+
console.log(` ${theme.error("-")} ${theme.dim(numStr)} ${theme.error(line.content)}`);
|
|
3809
|
+
} else {
|
|
3810
|
+
console.log(` ${theme.dim(" ")} ${theme.dim(numStr)} ${line.content}`);
|
|
3811
|
+
}
|
|
3812
|
+
}
|
|
3813
|
+
console.log("");
|
|
3814
|
+
}
|
|
3815
|
+
});
|
|
3816
|
+
|
|
3817
|
+
// src/commands/info/weather.ts
|
|
3818
|
+
registerCommand({
|
|
3819
|
+
name: "weather",
|
|
3820
|
+
description: "Get current weather for a city",
|
|
3821
|
+
usage: "/weather <city>",
|
|
3822
|
+
async execute(args2) {
|
|
3823
|
+
const city = args2.join(" ") || "London";
|
|
3824
|
+
console.log(theme.dim(`Fetching weather for ${city}...`));
|
|
3825
|
+
try {
|
|
3826
|
+
const response = await fetch(`https://wttr.in/${encodeURIComponent(city)}?format=j1`);
|
|
3827
|
+
if (!response.ok) {
|
|
3828
|
+
console.log(theme.error(`Could not fetch weather for "${city}"`));
|
|
3829
|
+
return;
|
|
3830
|
+
}
|
|
3831
|
+
const data = await response.json();
|
|
3832
|
+
const current = data.current_condition[0];
|
|
3833
|
+
const location = data.nearest_area[0];
|
|
3834
|
+
const temp = current.temp_C;
|
|
3835
|
+
const feelsLike = current.FeelsLikeC;
|
|
3836
|
+
const desc = current.weatherDesc[0].value;
|
|
3837
|
+
const humidity = current.humidity;
|
|
3838
|
+
const wind = current.windspeedKmph;
|
|
3839
|
+
const cityName = location.areaName[0].value;
|
|
3840
|
+
const country = location.country[0].value;
|
|
3841
|
+
console.log("");
|
|
3842
|
+
console.log(theme.highlight(`${cityName}, ${country}`));
|
|
3843
|
+
console.log("");
|
|
3844
|
+
console.log(` ${theme.primary(desc)}`);
|
|
3845
|
+
console.log(` Temperature: ${theme.warning(temp + "\xB0C")} (feels like ${feelsLike}\xB0C)`);
|
|
3846
|
+
console.log(` Humidity: ${humidity}%`);
|
|
2898
3847
|
console.log(` Wind: ${wind} km/h`);
|
|
2899
3848
|
console.log("");
|
|
2900
|
-
} catch (error) {
|
|
2901
|
-
console.log(theme.error(`Error fetching weather: ${error}`));
|
|
3849
|
+
} catch (error) {
|
|
3850
|
+
console.log(theme.error(`Error fetching weather: ${error}`));
|
|
3851
|
+
}
|
|
3852
|
+
}
|
|
3853
|
+
});
|
|
3854
|
+
|
|
3855
|
+
// src/plugins/loader.ts
|
|
3856
|
+
import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as readFileSync7, mkdirSync as mkdirSync2 } from "fs";
|
|
3857
|
+
import { join as join6, dirname as dirname3 } from "path";
|
|
3858
|
+
import { homedir as homedir3 } from "os";
|
|
3859
|
+
import { pathToFileURL, fileURLToPath as fileURLToPath2 } from "url";
|
|
3860
|
+
|
|
3861
|
+
// src/plugins/api.ts
|
|
3862
|
+
import { execSync, spawn as nodeSpawn } from "child_process";
|
|
3863
|
+
import { join as join5 } from "path";
|
|
3864
|
+
|
|
3865
|
+
// src/plugins/storage.ts
|
|
3866
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync } from "fs";
|
|
3867
|
+
import { join as join4 } from "path";
|
|
3868
|
+
function createPluginStorage(pluginName, dataDir) {
|
|
3869
|
+
const storagePath = join4(dataDir, "data.json");
|
|
3870
|
+
if (!existsSync7(dataDir)) {
|
|
3871
|
+
mkdirSync(dataDir, { recursive: true });
|
|
3872
|
+
}
|
|
3873
|
+
function loadData() {
|
|
3874
|
+
try {
|
|
3875
|
+
if (existsSync7(storagePath)) {
|
|
3876
|
+
const content = readFileSync5(storagePath, "utf-8");
|
|
3877
|
+
return JSON.parse(content);
|
|
3878
|
+
}
|
|
3879
|
+
} catch {
|
|
3880
|
+
}
|
|
3881
|
+
return {};
|
|
3882
|
+
}
|
|
3883
|
+
function saveData(data) {
|
|
3884
|
+
try {
|
|
3885
|
+
writeFileSync3(storagePath, JSON.stringify(data, null, 2), "utf-8");
|
|
3886
|
+
} catch (error) {
|
|
3887
|
+
console.error(`Failed to save plugin storage for ${pluginName}:`, error);
|
|
3888
|
+
}
|
|
3889
|
+
}
|
|
3890
|
+
return {
|
|
3891
|
+
get(key) {
|
|
3892
|
+
const data = loadData();
|
|
3893
|
+
return data[key];
|
|
3894
|
+
},
|
|
3895
|
+
set(key, value) {
|
|
3896
|
+
const data = loadData();
|
|
3897
|
+
data[key] = value;
|
|
3898
|
+
saveData(data);
|
|
3899
|
+
},
|
|
3900
|
+
delete(key) {
|
|
3901
|
+
const data = loadData();
|
|
3902
|
+
delete data[key];
|
|
3903
|
+
saveData(data);
|
|
3904
|
+
},
|
|
3905
|
+
clear() {
|
|
3906
|
+
saveData({});
|
|
3907
|
+
},
|
|
3908
|
+
getAll() {
|
|
3909
|
+
return loadData();
|
|
3910
|
+
}
|
|
3911
|
+
};
|
|
3912
|
+
}
|
|
3913
|
+
|
|
3914
|
+
// src/plugins/api.ts
|
|
3915
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
3916
|
+
import { fileURLToPath } from "url";
|
|
3917
|
+
import { dirname as dirname2 } from "path";
|
|
3918
|
+
function getZammyVersion() {
|
|
3919
|
+
try {
|
|
3920
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
3921
|
+
const __dirname = dirname2(__filename);
|
|
3922
|
+
const pkgPath = join5(__dirname, "..", "..", "package.json");
|
|
3923
|
+
const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
|
|
3924
|
+
return pkg.version || "0.0.0";
|
|
3925
|
+
} catch {
|
|
3926
|
+
return "0.0.0";
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
function createPluginUI() {
|
|
3930
|
+
return {
|
|
3931
|
+
theme: {
|
|
3932
|
+
primary: theme.primary,
|
|
3933
|
+
secondary: theme.secondary,
|
|
3934
|
+
accent: theme.accent,
|
|
3935
|
+
success: theme.success,
|
|
3936
|
+
warning: theme.warning,
|
|
3937
|
+
error: theme.error,
|
|
3938
|
+
info: theme.info,
|
|
3939
|
+
dim: theme.dim,
|
|
3940
|
+
gradient: theme.gradient
|
|
3941
|
+
},
|
|
3942
|
+
symbols: {
|
|
3943
|
+
check: symbols.check,
|
|
3944
|
+
cross: symbols.cross,
|
|
3945
|
+
star: symbols.star,
|
|
3946
|
+
arrow: symbols.arrow,
|
|
3947
|
+
bullet: symbols.bullet,
|
|
3948
|
+
folder: symbols.folder,
|
|
3949
|
+
file: "\u{1F4C4}",
|
|
3950
|
+
// 📄
|
|
3951
|
+
warning: symbols.warning,
|
|
3952
|
+
info: symbols.info,
|
|
3953
|
+
rocket: symbols.rocket,
|
|
3954
|
+
sparkles: symbols.sparkle
|
|
3955
|
+
},
|
|
3956
|
+
box: (content, options) => {
|
|
3957
|
+
const lines = content.split("\n");
|
|
3958
|
+
const width = Math.max(...lines.map((l) => l.replace(/\x1B\[[0-9;]*m/g, "").length)) + 4;
|
|
3959
|
+
return box.draw(lines, width);
|
|
3960
|
+
},
|
|
3961
|
+
progressBar: (current, total, width) => {
|
|
3962
|
+
const percent = Math.round(current / total * 100);
|
|
3963
|
+
return progressBar(percent, width || 30);
|
|
3964
|
+
}
|
|
3965
|
+
};
|
|
3966
|
+
}
|
|
3967
|
+
function createPluginLogger(pluginName) {
|
|
3968
|
+
const prefix = theme.dim(`[${pluginName}]`);
|
|
3969
|
+
return {
|
|
3970
|
+
info: (message) => console.log(`${prefix} ${theme.info(message)}`),
|
|
3971
|
+
warn: (message) => console.log(`${prefix} ${theme.warning(message)}`),
|
|
3972
|
+
error: (message) => console.log(`${prefix} ${theme.error(message)}`),
|
|
3973
|
+
debug: (message) => {
|
|
3974
|
+
if (process.env.ZAMMY_DEBUG) {
|
|
3975
|
+
console.log(`${prefix} ${theme.dim(message)}`);
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
};
|
|
3979
|
+
}
|
|
3980
|
+
function createPluginShell(manifest) {
|
|
3981
|
+
if (!manifest.permissions?.shell) {
|
|
3982
|
+
return void 0;
|
|
3983
|
+
}
|
|
3984
|
+
return {
|
|
3985
|
+
exec: (command, options) => {
|
|
3986
|
+
try {
|
|
3987
|
+
return execSync(command, {
|
|
3988
|
+
encoding: "utf-8",
|
|
3989
|
+
timeout: options?.timeout || 3e4,
|
|
3990
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3991
|
+
});
|
|
3992
|
+
} catch (error) {
|
|
3993
|
+
if (error && typeof error === "object" && "stdout" in error) {
|
|
3994
|
+
return error.stdout || "";
|
|
3995
|
+
}
|
|
3996
|
+
throw error;
|
|
3997
|
+
}
|
|
3998
|
+
},
|
|
3999
|
+
spawn: (command, args2) => {
|
|
4000
|
+
return new Promise((resolve6) => {
|
|
4001
|
+
const proc = nodeSpawn(command, args2 || [], {
|
|
4002
|
+
shell: true,
|
|
4003
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4004
|
+
});
|
|
4005
|
+
let stdout = "";
|
|
4006
|
+
let stderr = "";
|
|
4007
|
+
proc.stdout?.on("data", (data) => {
|
|
4008
|
+
stdout += data.toString();
|
|
4009
|
+
});
|
|
4010
|
+
proc.stderr?.on("data", (data) => {
|
|
4011
|
+
stderr += data.toString();
|
|
4012
|
+
});
|
|
4013
|
+
proc.on("close", (code) => {
|
|
4014
|
+
resolve6({ stdout, stderr, code: code || 0 });
|
|
4015
|
+
});
|
|
4016
|
+
proc.on("error", () => {
|
|
4017
|
+
resolve6({ stdout, stderr, code: 1 });
|
|
4018
|
+
});
|
|
4019
|
+
});
|
|
4020
|
+
}
|
|
4021
|
+
};
|
|
4022
|
+
}
|
|
4023
|
+
function createPluginAPI(manifest, pluginPath) {
|
|
4024
|
+
const dataDir = pluginPath;
|
|
4025
|
+
const context = {
|
|
4026
|
+
pluginName: manifest.name,
|
|
4027
|
+
pluginVersion: manifest.version,
|
|
4028
|
+
zammyVersion: getZammyVersion(),
|
|
4029
|
+
dataDir,
|
|
4030
|
+
cwd: process.cwd()
|
|
4031
|
+
};
|
|
4032
|
+
return {
|
|
4033
|
+
registerCommand: (command) => {
|
|
4034
|
+
registerPluginCommand(command, manifest.name);
|
|
4035
|
+
},
|
|
4036
|
+
registerCommands: (commands2) => {
|
|
4037
|
+
for (const command of commands2) {
|
|
4038
|
+
registerPluginCommand(command, manifest.name);
|
|
4039
|
+
}
|
|
4040
|
+
},
|
|
4041
|
+
ui: createPluginUI(),
|
|
4042
|
+
storage: createPluginStorage(manifest.name, dataDir),
|
|
4043
|
+
log: createPluginLogger(manifest.name),
|
|
4044
|
+
context,
|
|
4045
|
+
shell: createPluginShell(manifest)
|
|
4046
|
+
};
|
|
4047
|
+
}
|
|
4048
|
+
|
|
4049
|
+
// src/plugins/loader.ts
|
|
4050
|
+
var PLUGINS_DIR = join6(homedir3(), ".zammy", "plugins");
|
|
4051
|
+
function getZammyVersion2() {
|
|
4052
|
+
try {
|
|
4053
|
+
const __filename = fileURLToPath2(import.meta.url);
|
|
4054
|
+
const __dirname = dirname3(__filename);
|
|
4055
|
+
const possiblePaths = [
|
|
4056
|
+
join6(__dirname, "package.json"),
|
|
4057
|
+
// Same dir (bundled: dist/)
|
|
4058
|
+
join6(__dirname, "..", "package.json"),
|
|
4059
|
+
// One up (bundled: project root)
|
|
4060
|
+
join6(__dirname, "..", "..", "package.json")
|
|
4061
|
+
// Two up (source: src/plugins/)
|
|
4062
|
+
];
|
|
4063
|
+
for (const pkgPath of possiblePaths) {
|
|
4064
|
+
if (existsSync8(pkgPath)) {
|
|
4065
|
+
const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
4066
|
+
if (pkg.name === "zammy" && pkg.version) {
|
|
4067
|
+
return pkg.version;
|
|
4068
|
+
}
|
|
4069
|
+
}
|
|
4070
|
+
}
|
|
4071
|
+
return "0.0.0";
|
|
4072
|
+
} catch {
|
|
4073
|
+
return "0.0.0";
|
|
4074
|
+
}
|
|
4075
|
+
}
|
|
4076
|
+
function compareVersions(a, b3) {
|
|
4077
|
+
const partsA = a.split(".").map((n) => parseInt(n, 10) || 0);
|
|
4078
|
+
const partsB = b3.split(".").map((n) => parseInt(n, 10) || 0);
|
|
4079
|
+
for (let i = 0; i < 3; i++) {
|
|
4080
|
+
const numA = partsA[i] || 0;
|
|
4081
|
+
const numB = partsB[i] || 0;
|
|
4082
|
+
if (numA < numB) return -1;
|
|
4083
|
+
if (numA > numB) return 1;
|
|
4084
|
+
}
|
|
4085
|
+
return 0;
|
|
4086
|
+
}
|
|
4087
|
+
function checkVersionCompatibility(manifest, zammyVersion) {
|
|
4088
|
+
const minVersion = manifest.zammy?.minVersion;
|
|
4089
|
+
const maxVersion = manifest.zammy?.maxVersion;
|
|
4090
|
+
if (minVersion && compareVersions(zammyVersion, minVersion) < 0) {
|
|
4091
|
+
return {
|
|
4092
|
+
compatible: false,
|
|
4093
|
+
reason: `Requires Zammy v${minVersion}+, but you have v${zammyVersion}`
|
|
4094
|
+
};
|
|
4095
|
+
}
|
|
4096
|
+
if (maxVersion && compareVersions(zammyVersion, maxVersion) > 0) {
|
|
4097
|
+
return {
|
|
4098
|
+
compatible: false,
|
|
4099
|
+
reason: `Incompatible with Zammy v${zammyVersion} (max: v${maxVersion})`
|
|
4100
|
+
};
|
|
4101
|
+
}
|
|
4102
|
+
return { compatible: true };
|
|
4103
|
+
}
|
|
4104
|
+
var discoveredPlugins = /* @__PURE__ */ new Map();
|
|
4105
|
+
var loadedPlugins = /* @__PURE__ */ new Map();
|
|
4106
|
+
function ensurePluginsDir() {
|
|
4107
|
+
if (!existsSync8(PLUGINS_DIR)) {
|
|
4108
|
+
mkdirSync2(PLUGINS_DIR, { recursive: true });
|
|
4109
|
+
}
|
|
4110
|
+
}
|
|
4111
|
+
function getPluginsDir() {
|
|
4112
|
+
return PLUGINS_DIR;
|
|
4113
|
+
}
|
|
4114
|
+
async function discoverPlugins() {
|
|
4115
|
+
ensurePluginsDir();
|
|
4116
|
+
discoveredPlugins.clear();
|
|
4117
|
+
if (!existsSync8(PLUGINS_DIR)) {
|
|
4118
|
+
return [];
|
|
4119
|
+
}
|
|
4120
|
+
const zammyVersion = getZammyVersion2();
|
|
4121
|
+
const entries = readdirSync2(PLUGINS_DIR, { withFileTypes: true });
|
|
4122
|
+
const manifests = [];
|
|
4123
|
+
for (const entry of entries) {
|
|
4124
|
+
if (!entry.isDirectory()) continue;
|
|
4125
|
+
const pluginPath = join6(PLUGINS_DIR, entry.name);
|
|
4126
|
+
const manifestPath = join6(pluginPath, "zammy-plugin.json");
|
|
4127
|
+
if (!existsSync8(manifestPath)) continue;
|
|
4128
|
+
try {
|
|
4129
|
+
const manifestContent = readFileSync7(manifestPath, "utf-8");
|
|
4130
|
+
const manifest = JSON.parse(manifestContent);
|
|
4131
|
+
if (!manifest.name || !manifest.version || !manifest.main || !manifest.commands) {
|
|
4132
|
+
console.log(theme.warning(` ${symbols.warning} Invalid manifest for plugin in ${entry.name}`));
|
|
4133
|
+
continue;
|
|
4134
|
+
}
|
|
4135
|
+
const compatibility = checkVersionCompatibility(manifest, zammyVersion);
|
|
4136
|
+
if (!compatibility.compatible) {
|
|
4137
|
+
console.log(theme.warning(` ${symbols.warning} Plugin '${manifest.name}' skipped: ${compatibility.reason}`));
|
|
4138
|
+
continue;
|
|
4139
|
+
}
|
|
4140
|
+
discoveredPlugins.set(manifest.name, { manifest, path: pluginPath });
|
|
4141
|
+
manifests.push(manifest);
|
|
4142
|
+
} catch (error) {
|
|
4143
|
+
console.log(theme.warning(` ${symbols.warning} Failed to read manifest for ${entry.name}`));
|
|
4144
|
+
}
|
|
4145
|
+
}
|
|
4146
|
+
return manifests;
|
|
4147
|
+
}
|
|
4148
|
+
async function loadPlugin(name) {
|
|
4149
|
+
if (loadedPlugins.has(name)) {
|
|
4150
|
+
const existing = loadedPlugins.get(name);
|
|
4151
|
+
if (existing.state === "error") {
|
|
4152
|
+
return null;
|
|
4153
|
+
}
|
|
4154
|
+
return existing;
|
|
4155
|
+
}
|
|
4156
|
+
const discovered = discoveredPlugins.get(name);
|
|
4157
|
+
if (!discovered) {
|
|
4158
|
+
return null;
|
|
4159
|
+
}
|
|
4160
|
+
const { manifest, path: pluginPath } = discovered;
|
|
4161
|
+
try {
|
|
4162
|
+
const mainPath = join6(pluginPath, manifest.main);
|
|
4163
|
+
if (!existsSync8(mainPath)) {
|
|
4164
|
+
throw new Error(`Plugin entry point not found: ${mainPath}`);
|
|
4165
|
+
}
|
|
4166
|
+
const moduleUrl = pathToFileURL(mainPath).href;
|
|
4167
|
+
const module = await import(moduleUrl);
|
|
4168
|
+
const plugin = module.default;
|
|
4169
|
+
if (!plugin || typeof plugin.activate !== "function") {
|
|
4170
|
+
throw new Error("Plugin must export a default object with an activate function");
|
|
4171
|
+
}
|
|
4172
|
+
const api = createPluginAPI(manifest, pluginPath);
|
|
4173
|
+
try {
|
|
4174
|
+
await plugin.activate(api);
|
|
4175
|
+
} catch (activationError) {
|
|
4176
|
+
const msg = activationError instanceof Error ? activationError.message : String(activationError);
|
|
4177
|
+
throw new Error(`Plugin activation failed: ${msg}`);
|
|
4178
|
+
}
|
|
4179
|
+
const loaded = {
|
|
4180
|
+
manifest,
|
|
4181
|
+
instance: plugin,
|
|
4182
|
+
path: pluginPath,
|
|
4183
|
+
state: "active"
|
|
4184
|
+
};
|
|
4185
|
+
loadedPlugins.set(name, loaded);
|
|
4186
|
+
return loaded;
|
|
4187
|
+
} catch (error) {
|
|
4188
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4189
|
+
console.log(theme.error(` ${symbols.cross} Failed to load plugin '${name}': ${errorMessage}`));
|
|
4190
|
+
const failedPlugin = {
|
|
4191
|
+
manifest,
|
|
4192
|
+
instance: { activate: async () => {
|
|
4193
|
+
} },
|
|
4194
|
+
path: pluginPath,
|
|
4195
|
+
state: "error"
|
|
4196
|
+
};
|
|
4197
|
+
loadedPlugins.set(name, failedPlugin);
|
|
4198
|
+
return null;
|
|
4199
|
+
}
|
|
4200
|
+
}
|
|
4201
|
+
async function unloadPlugin(name) {
|
|
4202
|
+
const loaded = loadedPlugins.get(name);
|
|
4203
|
+
if (!loaded) {
|
|
4204
|
+
return false;
|
|
4205
|
+
}
|
|
4206
|
+
try {
|
|
4207
|
+
if (loaded.instance.deactivate) {
|
|
4208
|
+
await loaded.instance.deactivate();
|
|
4209
|
+
}
|
|
4210
|
+
unregisterPluginCommands(name);
|
|
4211
|
+
loadedPlugins.delete(name);
|
|
4212
|
+
return true;
|
|
4213
|
+
} catch (error) {
|
|
4214
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4215
|
+
console.log(theme.error(` ${symbols.cross} Failed to unload plugin '${name}': ${errorMessage}`));
|
|
4216
|
+
return false;
|
|
4217
|
+
}
|
|
4218
|
+
}
|
|
4219
|
+
function getDiscoveredPlugins() {
|
|
4220
|
+
return Array.from(discoveredPlugins.values()).map((p) => p.manifest);
|
|
4221
|
+
}
|
|
4222
|
+
function isPluginLoaded(name) {
|
|
4223
|
+
return loadedPlugins.has(name);
|
|
4224
|
+
}
|
|
4225
|
+
async function initPluginLoader() {
|
|
4226
|
+
const manifests = await discoverPlugins();
|
|
4227
|
+
for (const manifest of manifests) {
|
|
4228
|
+
for (const cmdName of manifest.commands) {
|
|
4229
|
+
const lazyExecute = async (args2) => {
|
|
4230
|
+
const loaded = await loadPlugin(manifest.name);
|
|
4231
|
+
if (!loaded) {
|
|
4232
|
+
console.log(theme.error(` ${symbols.cross} Failed to load plugin '${manifest.name}'`));
|
|
4233
|
+
console.log(theme.dim(` Try reinstalling: /plugin remove ${manifest.name} && /plugin install ${manifest.name}`));
|
|
4234
|
+
return;
|
|
4235
|
+
}
|
|
4236
|
+
const realCommand = getCommand(cmdName);
|
|
4237
|
+
if (realCommand && realCommand.execute !== lazyExecute) {
|
|
4238
|
+
await realCommand.execute(args2);
|
|
4239
|
+
} else {
|
|
4240
|
+
console.log(theme.error(` ${symbols.cross} Plugin '${manifest.name}' did not register command '${cmdName}'`));
|
|
4241
|
+
console.log(theme.dim(` The plugin may be misconfigured or corrupted.`));
|
|
4242
|
+
}
|
|
4243
|
+
};
|
|
4244
|
+
registerPluginCommand(
|
|
4245
|
+
{
|
|
4246
|
+
name: cmdName,
|
|
4247
|
+
description: `[${manifest.displayName || manifest.name}] Plugin command`,
|
|
4248
|
+
usage: `/${cmdName}`,
|
|
4249
|
+
execute: lazyExecute
|
|
4250
|
+
},
|
|
4251
|
+
manifest.name
|
|
4252
|
+
);
|
|
4253
|
+
}
|
|
4254
|
+
}
|
|
4255
|
+
}
|
|
4256
|
+
|
|
4257
|
+
// src/commands/plugin/list.ts
|
|
4258
|
+
async function listPlugins() {
|
|
4259
|
+
await discoverPlugins();
|
|
4260
|
+
const plugins = getDiscoveredPlugins();
|
|
4261
|
+
console.log("");
|
|
4262
|
+
if (plugins.length === 0) {
|
|
4263
|
+
console.log(` ${symbols.info} ${theme.dim("No plugins installed")}`);
|
|
4264
|
+
console.log("");
|
|
4265
|
+
console.log(` ${theme.dim("Install a plugin with:")} ${theme.primary("/plugin install <source>")}`);
|
|
4266
|
+
console.log(` ${theme.dim("Create a new plugin with:")} ${theme.primary("/plugin create")}`);
|
|
4267
|
+
console.log("");
|
|
4268
|
+
return;
|
|
4269
|
+
}
|
|
4270
|
+
console.log(` ${symbols.folder} ${theme.gradient("INSTALLED PLUGINS")} ${theme.dim(`(${plugins.length})`)}`);
|
|
4271
|
+
console.log("");
|
|
4272
|
+
for (const plugin of plugins) {
|
|
4273
|
+
const loaded = isPluginLoaded(plugin.name);
|
|
4274
|
+
const status = loaded ? theme.success("active") : theme.dim("idle");
|
|
4275
|
+
const statusIcon = loaded ? symbols.check : symbols.bullet;
|
|
4276
|
+
console.log(` ${statusIcon} ${theme.primary(plugin.displayName || plugin.name)} ${theme.dim(`v${plugin.version}`)}`);
|
|
4277
|
+
if (plugin.description) {
|
|
4278
|
+
console.log(` ${theme.dim(plugin.description)}`);
|
|
4279
|
+
}
|
|
4280
|
+
console.log(` ${theme.dim("Commands:")} ${plugin.commands.map((c) => theme.accent("/" + c)).join(", ")}`);
|
|
4281
|
+
if (plugin.permissions) {
|
|
4282
|
+
const perms = [];
|
|
4283
|
+
if (plugin.permissions.shell) perms.push("shell");
|
|
4284
|
+
if (plugin.permissions.filesystem) perms.push("fs");
|
|
4285
|
+
if (plugin.permissions.network) perms.push("net");
|
|
4286
|
+
if (perms.length > 0) {
|
|
4287
|
+
console.log(` ${theme.dim("Permissions:")} ${theme.warning(perms.join(", "))}`);
|
|
4288
|
+
}
|
|
4289
|
+
}
|
|
4290
|
+
console.log("");
|
|
4291
|
+
}
|
|
4292
|
+
}
|
|
4293
|
+
|
|
4294
|
+
// src/plugins/installer.ts
|
|
4295
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync3, cpSync, rmSync, readFileSync as readFileSync8, readdirSync as readdirSync3, createReadStream, createWriteStream } from "fs";
|
|
4296
|
+
import { join as join7, resolve as resolve3, dirname as dirname4 } from "path";
|
|
4297
|
+
import { execSync as execSync2 } from "child_process";
|
|
4298
|
+
import { tmpdir, platform as platform2 } from "os";
|
|
4299
|
+
import { createGunzip } from "zlib";
|
|
4300
|
+
import { pipeline } from "stream/promises";
|
|
4301
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4302
|
+
var isWindows = platform2() === "win32";
|
|
4303
|
+
function getZammyVersion3() {
|
|
4304
|
+
try {
|
|
4305
|
+
const __filename = fileURLToPath3(import.meta.url);
|
|
4306
|
+
const __dirname = dirname4(__filename);
|
|
4307
|
+
const possiblePaths = [
|
|
4308
|
+
join7(__dirname, "package.json"),
|
|
4309
|
+
join7(__dirname, "..", "package.json"),
|
|
4310
|
+
join7(__dirname, "..", "..", "package.json")
|
|
4311
|
+
];
|
|
4312
|
+
for (const pkgPath of possiblePaths) {
|
|
4313
|
+
if (existsSync9(pkgPath)) {
|
|
4314
|
+
const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
|
|
4315
|
+
if (pkg.name === "zammy" && pkg.version) {
|
|
4316
|
+
return pkg.version;
|
|
4317
|
+
}
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4320
|
+
return "0.0.0";
|
|
4321
|
+
} catch {
|
|
4322
|
+
return "0.0.0";
|
|
4323
|
+
}
|
|
4324
|
+
}
|
|
4325
|
+
function compareVersions2(a, b3) {
|
|
4326
|
+
const partsA = a.split(".").map((n) => parseInt(n, 10) || 0);
|
|
4327
|
+
const partsB = b3.split(".").map((n) => parseInt(n, 10) || 0);
|
|
4328
|
+
for (let i = 0; i < 3; i++) {
|
|
4329
|
+
const numA = partsA[i] || 0;
|
|
4330
|
+
const numB = partsB[i] || 0;
|
|
4331
|
+
if (numA < numB) return -1;
|
|
4332
|
+
if (numA > numB) return 1;
|
|
4333
|
+
}
|
|
4334
|
+
return 0;
|
|
4335
|
+
}
|
|
4336
|
+
function checkVersionCompatibility2(manifest) {
|
|
4337
|
+
const zammyVersion = getZammyVersion3();
|
|
4338
|
+
const minVersion = manifest.zammy?.minVersion;
|
|
4339
|
+
const maxVersion = manifest.zammy?.maxVersion;
|
|
4340
|
+
if (minVersion && compareVersions2(zammyVersion, minVersion) < 0) {
|
|
4341
|
+
return {
|
|
4342
|
+
compatible: false,
|
|
4343
|
+
reason: `Requires Zammy v${minVersion}+, but you have v${zammyVersion}`
|
|
4344
|
+
};
|
|
4345
|
+
}
|
|
4346
|
+
if (maxVersion && compareVersions2(zammyVersion, maxVersion) > 0) {
|
|
4347
|
+
return {
|
|
4348
|
+
compatible: false,
|
|
4349
|
+
reason: `Incompatible with Zammy v${zammyVersion} (max supported: v${maxVersion})`
|
|
4350
|
+
};
|
|
4351
|
+
}
|
|
4352
|
+
return { compatible: true };
|
|
4353
|
+
}
|
|
4354
|
+
async function extractTarGz(tarGzPath, destDir) {
|
|
4355
|
+
try {
|
|
4356
|
+
execSync2(`tar -xzf "${tarGzPath}" -C "${destDir}"`, {
|
|
4357
|
+
encoding: "utf-8",
|
|
4358
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4359
|
+
});
|
|
4360
|
+
return;
|
|
4361
|
+
} catch {
|
|
4362
|
+
}
|
|
4363
|
+
const tarPath = tarGzPath.replace(/\.tgz$|\.tar\.gz$/, ".tar");
|
|
4364
|
+
await pipeline(
|
|
4365
|
+
createReadStream(tarGzPath),
|
|
4366
|
+
createGunzip(),
|
|
4367
|
+
createWriteStream(tarPath)
|
|
4368
|
+
);
|
|
4369
|
+
try {
|
|
4370
|
+
execSync2(`tar -xf "${tarPath}" -C "${destDir}"`, {
|
|
4371
|
+
encoding: "utf-8",
|
|
4372
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4373
|
+
});
|
|
4374
|
+
rmSync(tarPath, { force: true });
|
|
4375
|
+
return;
|
|
4376
|
+
} catch {
|
|
4377
|
+
rmSync(tarPath, { force: true });
|
|
4378
|
+
}
|
|
4379
|
+
if (isWindows) {
|
|
4380
|
+
try {
|
|
4381
|
+
execSync2(`powershell -Command "tar -xf '${tarPath}' -C '${destDir}'"`, {
|
|
4382
|
+
encoding: "utf-8",
|
|
4383
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4384
|
+
});
|
|
4385
|
+
return;
|
|
4386
|
+
} catch {
|
|
4387
|
+
throw new Error(
|
|
4388
|
+
"Unable to extract plugin archive. Please ensure tar is available.\nOn Windows 10+, tar should be built-in. Try running: tar --version"
|
|
4389
|
+
);
|
|
4390
|
+
}
|
|
4391
|
+
}
|
|
4392
|
+
throw new Error("Unable to extract plugin archive. Please ensure tar is installed.");
|
|
4393
|
+
}
|
|
4394
|
+
function validateManifest(manifest) {
|
|
4395
|
+
const errors = [];
|
|
4396
|
+
if (!manifest || typeof manifest !== "object") {
|
|
4397
|
+
return { valid: false, errors: ["Manifest must be an object"] };
|
|
4398
|
+
}
|
|
4399
|
+
const m = manifest;
|
|
4400
|
+
if (!m.name || typeof m.name !== "string") {
|
|
4401
|
+
errors.push('Missing or invalid "name" field');
|
|
4402
|
+
}
|
|
4403
|
+
if (!m.version || typeof m.version !== "string") {
|
|
4404
|
+
errors.push('Missing or invalid "version" field');
|
|
4405
|
+
}
|
|
4406
|
+
if (!m.main || typeof m.main !== "string") {
|
|
4407
|
+
errors.push('Missing or invalid "main" field');
|
|
4408
|
+
}
|
|
4409
|
+
if (!m.commands || !Array.isArray(m.commands) || m.commands.length === 0) {
|
|
4410
|
+
errors.push('Missing or invalid "commands" field (must be non-empty array)');
|
|
4411
|
+
}
|
|
4412
|
+
if (!m.zammy || typeof m.zammy !== "object") {
|
|
4413
|
+
errors.push('Missing or invalid "zammy" field');
|
|
4414
|
+
} else {
|
|
4415
|
+
const zammy = m.zammy;
|
|
4416
|
+
if (!zammy.minVersion || typeof zammy.minVersion !== "string") {
|
|
4417
|
+
errors.push('Missing or invalid "zammy.minVersion" field');
|
|
4418
|
+
}
|
|
4419
|
+
}
|
|
4420
|
+
return { valid: errors.length === 0, errors };
|
|
4421
|
+
}
|
|
4422
|
+
function checkConflicts(manifest) {
|
|
4423
|
+
const conflicts = [];
|
|
4424
|
+
for (const cmd of manifest.commands) {
|
|
4425
|
+
const conflict = checkCommandConflict(cmd);
|
|
4426
|
+
if (conflict.exists) {
|
|
4427
|
+
if (conflict.source === "core") {
|
|
4428
|
+
conflicts.push(`Command '/${cmd}' conflicts with core zammy command`);
|
|
4429
|
+
} else {
|
|
4430
|
+
conflicts.push(`Command '/${cmd}' conflicts with plugin '${conflict.pluginName}'`);
|
|
4431
|
+
}
|
|
4432
|
+
}
|
|
4433
|
+
}
|
|
4434
|
+
return { hasConflicts: conflicts.length > 0, conflicts };
|
|
4435
|
+
}
|
|
4436
|
+
function formatPermissions(manifest) {
|
|
4437
|
+
const perms = [];
|
|
4438
|
+
const p = manifest.permissions;
|
|
4439
|
+
if (!p) return perms;
|
|
4440
|
+
if (p.shell) {
|
|
4441
|
+
perms.push(`${symbols.warning} shell: Can run system commands`);
|
|
4442
|
+
}
|
|
4443
|
+
if (p.filesystem) {
|
|
4444
|
+
if (p.filesystem === true) {
|
|
4445
|
+
perms.push(`${symbols.warning} filesystem: Full file system access`);
|
|
4446
|
+
} else if (Array.isArray(p.filesystem)) {
|
|
4447
|
+
perms.push(`${symbols.info} filesystem: Access to ${p.filesystem.join(", ")}`);
|
|
4448
|
+
}
|
|
4449
|
+
}
|
|
4450
|
+
if (p.network) {
|
|
4451
|
+
if (p.network === true) {
|
|
4452
|
+
perms.push(`${symbols.warning} network: Full network access`);
|
|
4453
|
+
} else if (Array.isArray(p.network)) {
|
|
4454
|
+
perms.push(`${symbols.info} network: Access to ${p.network.join(", ")}`);
|
|
4455
|
+
}
|
|
4456
|
+
}
|
|
4457
|
+
return perms;
|
|
4458
|
+
}
|
|
4459
|
+
async function installFromLocal(sourcePath) {
|
|
4460
|
+
try {
|
|
4461
|
+
const absPath = resolve3(sourcePath);
|
|
4462
|
+
if (!existsSync9(absPath)) {
|
|
4463
|
+
return { success: false, error: `Path not found: ${absPath}` };
|
|
4464
|
+
}
|
|
4465
|
+
const manifestPath = join7(absPath, "zammy-plugin.json");
|
|
4466
|
+
if (!existsSync9(manifestPath)) {
|
|
4467
|
+
return { success: false, error: "No zammy-plugin.json found in source directory" };
|
|
4468
|
+
}
|
|
4469
|
+
const manifestContent = readFileSync8(manifestPath, "utf-8");
|
|
4470
|
+
let manifest;
|
|
4471
|
+
try {
|
|
4472
|
+
manifest = JSON.parse(manifestContent);
|
|
4473
|
+
} catch {
|
|
4474
|
+
return { success: false, error: "Invalid JSON in zammy-plugin.json" };
|
|
4475
|
+
}
|
|
4476
|
+
const validation = validateManifest(manifest);
|
|
4477
|
+
if (!validation.valid) {
|
|
4478
|
+
return { success: false, error: `Invalid manifest: ${validation.errors.join(", ")}` };
|
|
4479
|
+
}
|
|
4480
|
+
const versionCheck = checkVersionCompatibility2(manifest);
|
|
4481
|
+
if (!versionCheck.compatible) {
|
|
4482
|
+
return { success: false, error: versionCheck.reason };
|
|
4483
|
+
}
|
|
4484
|
+
const mainPath = join7(absPath, manifest.main);
|
|
4485
|
+
if (!existsSync9(mainPath)) {
|
|
4486
|
+
return { success: false, error: `Entry point not found: ${manifest.main}` };
|
|
4487
|
+
}
|
|
4488
|
+
ensurePluginsDir();
|
|
4489
|
+
const targetDir = join7(getPluginsDir(), manifest.name);
|
|
4490
|
+
if (existsSync9(targetDir)) {
|
|
4491
|
+
rmSync(targetDir, { recursive: true, force: true });
|
|
4492
|
+
}
|
|
4493
|
+
cpSync(absPath, targetDir, { recursive: true });
|
|
4494
|
+
return { success: true, manifest };
|
|
4495
|
+
} catch (error) {
|
|
4496
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4497
|
+
return { success: false, error: message };
|
|
4498
|
+
}
|
|
4499
|
+
}
|
|
4500
|
+
async function installFromNpm(packageName) {
|
|
4501
|
+
const tempDir = join7(tmpdir(), `zammy-plugin-${Date.now()}`);
|
|
4502
|
+
try {
|
|
4503
|
+
mkdirSync3(tempDir, { recursive: true });
|
|
4504
|
+
console.log(theme.dim(` Downloading ${packageName}...`));
|
|
4505
|
+
execSync2(`npm pack ${packageName} --pack-destination="${tempDir}"`, {
|
|
4506
|
+
encoding: "utf-8",
|
|
4507
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
4508
|
+
timeout: 6e4
|
|
4509
|
+
});
|
|
4510
|
+
const files = readdirSync3(tempDir);
|
|
4511
|
+
const tarball = files.find((f) => f.endsWith(".tgz"));
|
|
4512
|
+
if (!tarball) {
|
|
4513
|
+
return { success: false, error: "Failed to download package" };
|
|
4514
|
+
}
|
|
4515
|
+
const extractDir = join7(tempDir, "extract");
|
|
4516
|
+
mkdirSync3(extractDir);
|
|
4517
|
+
await extractTarGz(join7(tempDir, tarball), extractDir);
|
|
4518
|
+
const packageDir = join7(extractDir, "package");
|
|
4519
|
+
if (!existsSync9(packageDir)) {
|
|
4520
|
+
return { success: false, error: "Invalid package structure" };
|
|
4521
|
+
}
|
|
4522
|
+
return await installFromLocal(packageDir);
|
|
4523
|
+
} catch (error) {
|
|
4524
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4525
|
+
return { success: false, error: `npm install failed: ${message}` };
|
|
4526
|
+
} finally {
|
|
4527
|
+
try {
|
|
4528
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
4529
|
+
} catch {
|
|
4530
|
+
}
|
|
4531
|
+
}
|
|
4532
|
+
}
|
|
4533
|
+
async function installFromGithub(repo) {
|
|
4534
|
+
const tempDir = join7(tmpdir(), `zammy-plugin-${Date.now()}`);
|
|
4535
|
+
try {
|
|
4536
|
+
let repoPath = repo.replace(/^github:/, "");
|
|
4537
|
+
let branch = "main";
|
|
4538
|
+
if (repoPath.includes("#")) {
|
|
4539
|
+
[repoPath, branch] = repoPath.split("#");
|
|
4540
|
+
}
|
|
4541
|
+
mkdirSync3(tempDir, { recursive: true });
|
|
4542
|
+
console.log(theme.dim(` Cloning ${repoPath}...`));
|
|
4543
|
+
execSync2(`git clone --depth 1 --branch ${branch} https://github.com/${repoPath}.git "${tempDir}"`, {
|
|
4544
|
+
encoding: "utf-8",
|
|
4545
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
4546
|
+
timeout: 12e4
|
|
4547
|
+
});
|
|
4548
|
+
const pkgJsonPath = join7(tempDir, "package.json");
|
|
4549
|
+
if (existsSync9(pkgJsonPath)) {
|
|
4550
|
+
const pkgJson = JSON.parse(readFileSync8(pkgJsonPath, "utf-8"));
|
|
4551
|
+
if (pkgJson.scripts?.build) {
|
|
4552
|
+
console.log(theme.dim(` Installing dependencies...`));
|
|
4553
|
+
execSync2("npm install", { cwd: tempDir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 12e4 });
|
|
4554
|
+
console.log(theme.dim(` Building...`));
|
|
4555
|
+
execSync2("npm run build", { cwd: tempDir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 12e4 });
|
|
4556
|
+
}
|
|
4557
|
+
}
|
|
4558
|
+
return await installFromLocal(tempDir);
|
|
4559
|
+
} catch (error) {
|
|
4560
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4561
|
+
return { success: false, error: `GitHub install failed: ${message}` };
|
|
4562
|
+
} finally {
|
|
4563
|
+
try {
|
|
4564
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
4565
|
+
} catch {
|
|
4566
|
+
}
|
|
4567
|
+
}
|
|
4568
|
+
}
|
|
4569
|
+
async function installFromGit(url) {
|
|
4570
|
+
const tempDir = join7(tmpdir(), `zammy-plugin-${Date.now()}`);
|
|
4571
|
+
try {
|
|
4572
|
+
mkdirSync3(tempDir, { recursive: true });
|
|
4573
|
+
console.log(theme.dim(` Cloning from ${url}...`));
|
|
4574
|
+
execSync2(`git clone --depth 1 "${url}" "${tempDir}"`, {
|
|
4575
|
+
encoding: "utf-8",
|
|
4576
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
4577
|
+
timeout: 12e4
|
|
4578
|
+
});
|
|
4579
|
+
const pkgJsonPath = join7(tempDir, "package.json");
|
|
4580
|
+
if (existsSync9(pkgJsonPath)) {
|
|
4581
|
+
const pkgJson = JSON.parse(readFileSync8(pkgJsonPath, "utf-8"));
|
|
4582
|
+
if (pkgJson.scripts?.build) {
|
|
4583
|
+
console.log(theme.dim(` Installing dependencies...`));
|
|
4584
|
+
execSync2("npm install", { cwd: tempDir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 12e4 });
|
|
4585
|
+
console.log(theme.dim(` Building...`));
|
|
4586
|
+
execSync2("npm run build", { cwd: tempDir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 12e4 });
|
|
4587
|
+
}
|
|
4588
|
+
}
|
|
4589
|
+
return await installFromLocal(tempDir);
|
|
4590
|
+
} catch (error) {
|
|
4591
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4592
|
+
return { success: false, error: `Git install failed: ${message}` };
|
|
4593
|
+
} finally {
|
|
4594
|
+
try {
|
|
4595
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
4596
|
+
} catch {
|
|
4597
|
+
}
|
|
4598
|
+
}
|
|
4599
|
+
}
|
|
4600
|
+
function removePlugin(name) {
|
|
4601
|
+
try {
|
|
4602
|
+
const pluginDir = join7(getPluginsDir(), name);
|
|
4603
|
+
if (!existsSync9(pluginDir)) {
|
|
4604
|
+
return { success: false, error: `Plugin '${name}' not found` };
|
|
4605
|
+
}
|
|
4606
|
+
rmSync(pluginDir, { recursive: true, force: true });
|
|
4607
|
+
return { success: true };
|
|
4608
|
+
} catch (error) {
|
|
4609
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4610
|
+
return { success: false, error: message };
|
|
4611
|
+
}
|
|
4612
|
+
}
|
|
4613
|
+
function detectSourceType(source) {
|
|
4614
|
+
if (source.startsWith("./") || source.startsWith("/") || source.startsWith("..") || /^[A-Za-z]:/.test(source)) {
|
|
4615
|
+
return "local";
|
|
4616
|
+
}
|
|
4617
|
+
if (source.startsWith("github:")) {
|
|
4618
|
+
return "github";
|
|
4619
|
+
}
|
|
4620
|
+
if (source.includes("github.com")) {
|
|
4621
|
+
return "github";
|
|
4622
|
+
}
|
|
4623
|
+
if (source.endsWith(".git") || source.startsWith("git@") || source.startsWith("git://")) {
|
|
4624
|
+
return "git";
|
|
4625
|
+
}
|
|
4626
|
+
if (/^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(source)) {
|
|
4627
|
+
return "npm";
|
|
4628
|
+
}
|
|
4629
|
+
return "unknown";
|
|
4630
|
+
}
|
|
4631
|
+
|
|
4632
|
+
// src/commands/plugin/install.ts
|
|
4633
|
+
import * as readline from "readline";
|
|
4634
|
+
async function confirm(message) {
|
|
4635
|
+
const rl = readline.createInterface({
|
|
4636
|
+
input: process.stdin,
|
|
4637
|
+
output: process.stdout
|
|
4638
|
+
});
|
|
4639
|
+
return new Promise((resolve6) => {
|
|
4640
|
+
rl.question(`${message} [y/N] `, (answer) => {
|
|
4641
|
+
rl.close();
|
|
4642
|
+
resolve6(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
4643
|
+
});
|
|
4644
|
+
});
|
|
4645
|
+
}
|
|
4646
|
+
async function installPlugin(args2) {
|
|
4647
|
+
const source = args2[0];
|
|
4648
|
+
if (!source) {
|
|
4649
|
+
console.log(theme.error(` ${symbols.cross} No source specified`));
|
|
4650
|
+
console.log("");
|
|
4651
|
+
console.log(` ${theme.primary("Usage:")} /plugin install <source>`);
|
|
4652
|
+
console.log("");
|
|
4653
|
+
console.log(` ${theme.dim("Examples:")}`);
|
|
4654
|
+
console.log(` ${theme.dim("/plugin install ./my-plugin")}`);
|
|
4655
|
+
console.log(` ${theme.dim("/plugin install zammy-plugin-git")}`);
|
|
4656
|
+
console.log(` ${theme.dim("/plugin install github:user/repo")}`);
|
|
4657
|
+
console.log("");
|
|
4658
|
+
return;
|
|
4659
|
+
}
|
|
4660
|
+
console.log("");
|
|
4661
|
+
console.log(` ${symbols.rocket} ${theme.primary("Installing plugin...")}`);
|
|
4662
|
+
const sourceType = detectSourceType(source);
|
|
4663
|
+
if (sourceType === "unknown") {
|
|
4664
|
+
console.log(theme.error(` ${symbols.cross} Could not determine source type for: ${source}`));
|
|
4665
|
+
return;
|
|
4666
|
+
}
|
|
4667
|
+
console.log(theme.dim(` Source type: ${sourceType}`));
|
|
4668
|
+
let result;
|
|
4669
|
+
switch (sourceType) {
|
|
4670
|
+
case "local":
|
|
4671
|
+
result = await installFromLocal(source);
|
|
4672
|
+
break;
|
|
4673
|
+
case "npm":
|
|
4674
|
+
result = await installFromNpm(source);
|
|
4675
|
+
break;
|
|
4676
|
+
case "github":
|
|
4677
|
+
result = await installFromGithub(source);
|
|
4678
|
+
break;
|
|
4679
|
+
case "git":
|
|
4680
|
+
result = await installFromGit(source);
|
|
4681
|
+
break;
|
|
4682
|
+
default:
|
|
4683
|
+
result = { success: false, error: "Unknown source type" };
|
|
4684
|
+
}
|
|
4685
|
+
if (!result.success) {
|
|
4686
|
+
console.log(theme.error(` ${symbols.cross} Installation failed: ${result.error}`));
|
|
4687
|
+
return;
|
|
4688
|
+
}
|
|
4689
|
+
const manifest = result.manifest;
|
|
4690
|
+
const conflicts = checkConflicts(manifest);
|
|
4691
|
+
if (conflicts.hasConflicts) {
|
|
4692
|
+
console.log("");
|
|
4693
|
+
console.log(theme.warning(` ${symbols.warning} Command conflicts detected:`));
|
|
4694
|
+
for (const conflict of conflicts.conflicts) {
|
|
4695
|
+
console.log(` ${theme.dim("-")} ${conflict}`);
|
|
4696
|
+
}
|
|
4697
|
+
console.log("");
|
|
4698
|
+
const proceed = await confirm(` ${theme.warning("Continue anyway?")}`);
|
|
4699
|
+
if (!proceed) {
|
|
4700
|
+
console.log(theme.dim(" Installation cancelled"));
|
|
4701
|
+
return;
|
|
4702
|
+
}
|
|
4703
|
+
}
|
|
4704
|
+
const permissions = formatPermissions(manifest);
|
|
4705
|
+
if (permissions.length > 0) {
|
|
4706
|
+
console.log("");
|
|
4707
|
+
console.log(theme.warning(` ${symbols.warning} Plugin requests permissions:`));
|
|
4708
|
+
for (const perm of permissions) {
|
|
4709
|
+
console.log(` ${perm}`);
|
|
4710
|
+
}
|
|
4711
|
+
console.log("");
|
|
4712
|
+
}
|
|
4713
|
+
await discoverPlugins();
|
|
4714
|
+
for (const cmdName of manifest.commands) {
|
|
4715
|
+
if (getCommand(cmdName)) continue;
|
|
4716
|
+
const lazyExecute = async (args3) => {
|
|
4717
|
+
const loaded = await loadPlugin(manifest.name);
|
|
4718
|
+
if (!loaded) {
|
|
4719
|
+
console.log(theme.error(` ${symbols.cross} Failed to load plugin '${manifest.name}'`));
|
|
4720
|
+
return;
|
|
4721
|
+
}
|
|
4722
|
+
const realCommand = getCommand(cmdName);
|
|
4723
|
+
if (realCommand && realCommand.execute !== lazyExecute) {
|
|
4724
|
+
await realCommand.execute(args3);
|
|
4725
|
+
} else {
|
|
4726
|
+
console.log(theme.error(` ${symbols.cross} Plugin '${manifest.name}' did not register command '${cmdName}'`));
|
|
4727
|
+
}
|
|
4728
|
+
};
|
|
4729
|
+
registerPluginCommand(
|
|
4730
|
+
{
|
|
4731
|
+
name: cmdName,
|
|
4732
|
+
description: `[${manifest.displayName || manifest.name}] ${manifest.description || "Plugin command"}`,
|
|
4733
|
+
usage: `/${cmdName}`,
|
|
4734
|
+
execute: lazyExecute
|
|
4735
|
+
},
|
|
4736
|
+
manifest.name
|
|
4737
|
+
);
|
|
4738
|
+
}
|
|
4739
|
+
console.log("");
|
|
4740
|
+
console.log(` ${symbols.check} ${theme.success("Plugin installed successfully!")}`);
|
|
4741
|
+
console.log("");
|
|
4742
|
+
console.log(` ${theme.primary(manifest.displayName || manifest.name)} ${theme.dim(`v${manifest.version}`)}`);
|
|
4743
|
+
if (manifest.description) {
|
|
4744
|
+
console.log(` ${theme.dim(manifest.description)}`);
|
|
4745
|
+
}
|
|
4746
|
+
console.log("");
|
|
4747
|
+
console.log(` ${theme.dim("Commands added:")} ${manifest.commands.map((c) => theme.accent("/" + c)).join(", ")}`);
|
|
4748
|
+
console.log("");
|
|
4749
|
+
}
|
|
4750
|
+
|
|
4751
|
+
// src/commands/plugin/remove.ts
|
|
4752
|
+
import * as readline2 from "readline";
|
|
4753
|
+
async function confirm2(message) {
|
|
4754
|
+
const rl = readline2.createInterface({
|
|
4755
|
+
input: process.stdin,
|
|
4756
|
+
output: process.stdout
|
|
4757
|
+
});
|
|
4758
|
+
return new Promise((resolve6) => {
|
|
4759
|
+
rl.question(`${message} [y/N] `, (answer) => {
|
|
4760
|
+
rl.close();
|
|
4761
|
+
resolve6(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
4762
|
+
});
|
|
4763
|
+
});
|
|
4764
|
+
}
|
|
4765
|
+
async function removePluginCommand(args2) {
|
|
4766
|
+
const forceIndex = args2.findIndex((a) => a === "-y" || a === "--yes");
|
|
4767
|
+
const skipConfirm = forceIndex !== -1;
|
|
4768
|
+
if (skipConfirm) {
|
|
4769
|
+
args2.splice(forceIndex, 1);
|
|
4770
|
+
}
|
|
4771
|
+
const name = args2[0];
|
|
4772
|
+
if (!name) {
|
|
4773
|
+
console.log(theme.error(` ${symbols.cross} No plugin name specified`));
|
|
4774
|
+
console.log("");
|
|
4775
|
+
console.log(` ${theme.primary("Usage:")} /plugin remove <name>`);
|
|
4776
|
+
console.log("");
|
|
4777
|
+
await discoverPlugins();
|
|
4778
|
+
const plugins2 = getDiscoveredPlugins();
|
|
4779
|
+
if (plugins2.length > 0) {
|
|
4780
|
+
console.log(` ${theme.dim("Installed plugins:")}`);
|
|
4781
|
+
for (const p of plugins2) {
|
|
4782
|
+
console.log(` ${theme.accent(p.name)}`);
|
|
4783
|
+
}
|
|
4784
|
+
console.log("");
|
|
4785
|
+
}
|
|
4786
|
+
return;
|
|
4787
|
+
}
|
|
4788
|
+
await discoverPlugins();
|
|
4789
|
+
const plugins = getDiscoveredPlugins();
|
|
4790
|
+
const plugin = plugins.find((p) => p.name === name || p.displayName?.toLowerCase() === name.toLowerCase());
|
|
4791
|
+
if (!plugin) {
|
|
4792
|
+
console.log(theme.error(` ${symbols.cross} Plugin '${name}' not found`));
|
|
4793
|
+
console.log("");
|
|
4794
|
+
const similar = plugins.filter(
|
|
4795
|
+
(p) => p.name.includes(name) || name.includes(p.name) || p.displayName?.toLowerCase().includes(name.toLowerCase())
|
|
4796
|
+
);
|
|
4797
|
+
if (similar.length > 0) {
|
|
4798
|
+
console.log(` ${theme.dim("Did you mean:")}`);
|
|
4799
|
+
for (const p of similar) {
|
|
4800
|
+
console.log(` ${theme.accent(p.name)}`);
|
|
4801
|
+
}
|
|
4802
|
+
console.log("");
|
|
4803
|
+
}
|
|
4804
|
+
return;
|
|
4805
|
+
}
|
|
4806
|
+
console.log("");
|
|
4807
|
+
console.log(` ${theme.warning("About to remove:")}`);
|
|
4808
|
+
console.log(` ${theme.primary(plugin.displayName || plugin.name)} ${theme.dim(`v${plugin.version}`)}`);
|
|
4809
|
+
console.log(` ${theme.dim("Commands:")} ${plugin.commands.map((c) => "/" + c).join(", ")}`);
|
|
4810
|
+
console.log("");
|
|
4811
|
+
if (!skipConfirm) {
|
|
4812
|
+
const proceed = await confirm2(` ${theme.warning("Remove this plugin?")}`);
|
|
4813
|
+
if (!proceed) {
|
|
4814
|
+
console.log(theme.dim(" Removal cancelled"));
|
|
4815
|
+
return;
|
|
4816
|
+
}
|
|
4817
|
+
}
|
|
4818
|
+
await unloadPlugin(plugin.name);
|
|
4819
|
+
const result = removePlugin(plugin.name);
|
|
4820
|
+
if (!result.success) {
|
|
4821
|
+
console.log(theme.error(` ${symbols.cross} Failed to remove: ${result.error}`));
|
|
4822
|
+
return;
|
|
4823
|
+
}
|
|
4824
|
+
await discoverPlugins();
|
|
4825
|
+
console.log("");
|
|
4826
|
+
console.log(` ${symbols.check} ${theme.success("Plugin removed successfully")}`);
|
|
4827
|
+
console.log("");
|
|
4828
|
+
}
|
|
4829
|
+
|
|
4830
|
+
// src/commands/plugin/create.ts
|
|
4831
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
4832
|
+
import { join as join8, resolve as resolve4 } from "path";
|
|
4833
|
+
import * as readline3 from "readline";
|
|
4834
|
+
async function prompt(message, defaultValue) {
|
|
4835
|
+
const rl = readline3.createInterface({
|
|
4836
|
+
input: process.stdin,
|
|
4837
|
+
output: process.stdout
|
|
4838
|
+
});
|
|
4839
|
+
const suffix = defaultValue ? ` ${theme.dim(`(${defaultValue})`)}` : "";
|
|
4840
|
+
return new Promise((resolvePromise) => {
|
|
4841
|
+
rl.question(` ${message}${suffix}: `, (answer) => {
|
|
4842
|
+
rl.close();
|
|
4843
|
+
resolvePromise(answer.trim() || defaultValue || "");
|
|
4844
|
+
});
|
|
4845
|
+
});
|
|
4846
|
+
}
|
|
4847
|
+
function generateManifest(name, displayName, description, commandName) {
|
|
4848
|
+
return JSON.stringify({
|
|
4849
|
+
name,
|
|
4850
|
+
version: "1.0.0",
|
|
4851
|
+
displayName,
|
|
4852
|
+
description,
|
|
4853
|
+
main: "./dist/index.js",
|
|
4854
|
+
commands: [commandName],
|
|
4855
|
+
zammy: {
|
|
4856
|
+
minVersion: "1.3.0"
|
|
4857
|
+
},
|
|
4858
|
+
permissions: {}
|
|
4859
|
+
}, null, 2);
|
|
4860
|
+
}
|
|
4861
|
+
function generatePackageJson(name, description) {
|
|
4862
|
+
return JSON.stringify({
|
|
4863
|
+
name,
|
|
4864
|
+
version: "1.0.0",
|
|
4865
|
+
description,
|
|
4866
|
+
type: "module",
|
|
4867
|
+
main: "dist/index.js",
|
|
4868
|
+
scripts: {
|
|
4869
|
+
build: "tsc",
|
|
4870
|
+
dev: "tsc --watch"
|
|
4871
|
+
},
|
|
4872
|
+
keywords: ["zammy-plugin"],
|
|
4873
|
+
devDependencies: {
|
|
4874
|
+
typescript: "^5.3.0"
|
|
4875
|
+
}
|
|
4876
|
+
}, null, 2);
|
|
4877
|
+
}
|
|
4878
|
+
function generateTsConfig() {
|
|
4879
|
+
return JSON.stringify({
|
|
4880
|
+
compilerOptions: {
|
|
4881
|
+
target: "ES2022",
|
|
4882
|
+
module: "NodeNext",
|
|
4883
|
+
moduleResolution: "NodeNext",
|
|
4884
|
+
outDir: "./dist",
|
|
4885
|
+
rootDir: "./src",
|
|
4886
|
+
strict: true,
|
|
4887
|
+
esModuleInterop: true,
|
|
4888
|
+
skipLibCheck: true,
|
|
4889
|
+
declaration: true
|
|
4890
|
+
},
|
|
4891
|
+
include: ["src/**/*"]
|
|
4892
|
+
}, null, 2);
|
|
4893
|
+
}
|
|
4894
|
+
function generateEntryPoint(commandName, displayName) {
|
|
4895
|
+
return `// ${displayName} - A zammy plugin
|
|
4896
|
+
|
|
4897
|
+
// Plugin types (these match zammy's PluginAPI interface)
|
|
4898
|
+
interface Command {
|
|
4899
|
+
name: string;
|
|
4900
|
+
description: string;
|
|
4901
|
+
usage: string;
|
|
4902
|
+
execute: (args: string[]) => Promise<void>;
|
|
4903
|
+
}
|
|
4904
|
+
|
|
4905
|
+
interface PluginAPI {
|
|
4906
|
+
registerCommand(command: Command): void;
|
|
4907
|
+
ui: {
|
|
4908
|
+
theme: {
|
|
4909
|
+
primary: (text: string) => string;
|
|
4910
|
+
secondary: (text: string) => string;
|
|
4911
|
+
accent: (text: string) => string;
|
|
4912
|
+
success: (text: string) => string;
|
|
4913
|
+
warning: (text: string) => string;
|
|
4914
|
+
error: (text: string) => string;
|
|
4915
|
+
info: (text: string) => string;
|
|
4916
|
+
dim: (text: string) => string;
|
|
4917
|
+
gradient: (text: string) => string;
|
|
4918
|
+
};
|
|
4919
|
+
symbols: {
|
|
4920
|
+
check: string;
|
|
4921
|
+
cross: string;
|
|
4922
|
+
star: string;
|
|
4923
|
+
arrow: string;
|
|
4924
|
+
bullet: string;
|
|
4925
|
+
folder: string;
|
|
4926
|
+
file: string;
|
|
4927
|
+
warning: string;
|
|
4928
|
+
info: string;
|
|
4929
|
+
rocket: string;
|
|
4930
|
+
sparkles: string;
|
|
4931
|
+
};
|
|
4932
|
+
};
|
|
4933
|
+
storage: {
|
|
4934
|
+
get<T>(key: string): T | undefined;
|
|
4935
|
+
set<T>(key: string, value: T): void;
|
|
4936
|
+
delete(key: string): void;
|
|
4937
|
+
};
|
|
4938
|
+
log: {
|
|
4939
|
+
info(message: string): void;
|
|
4940
|
+
warn(message: string): void;
|
|
4941
|
+
error(message: string): void;
|
|
4942
|
+
};
|
|
4943
|
+
context: {
|
|
4944
|
+
pluginName: string;
|
|
4945
|
+
pluginVersion: string;
|
|
4946
|
+
zammyVersion: string;
|
|
4947
|
+
dataDir: string;
|
|
4948
|
+
cwd: string;
|
|
4949
|
+
};
|
|
4950
|
+
}
|
|
4951
|
+
|
|
4952
|
+
interface ZammyPlugin {
|
|
4953
|
+
activate(api: PluginAPI): Promise<void> | void;
|
|
4954
|
+
deactivate?(): Promise<void> | void;
|
|
4955
|
+
}
|
|
4956
|
+
|
|
4957
|
+
const plugin: ZammyPlugin = {
|
|
4958
|
+
activate(api: PluginAPI) {
|
|
4959
|
+
const { theme, symbols } = api.ui;
|
|
4960
|
+
|
|
4961
|
+
api.registerCommand({
|
|
4962
|
+
name: '${commandName}',
|
|
4963
|
+
description: 'My custom command',
|
|
4964
|
+
usage: '/${commandName} [args]',
|
|
4965
|
+
async execute(args: string[]) {
|
|
4966
|
+
console.log('');
|
|
4967
|
+
console.log(\` \${symbols.star} \${theme.gradient('${displayName.toUpperCase()}')}\`);
|
|
4968
|
+
console.log('');
|
|
4969
|
+
console.log(\` \${theme.success('Hello from ${displayName}!')}\`);
|
|
4970
|
+
|
|
4971
|
+
if (args.length > 0) {
|
|
4972
|
+
console.log(\` \${theme.dim('Arguments:')} \${args.join(' ')}\`);
|
|
4973
|
+
}
|
|
4974
|
+
|
|
4975
|
+
console.log('');
|
|
4976
|
+
},
|
|
4977
|
+
});
|
|
4978
|
+
|
|
4979
|
+
api.log.info('Plugin activated!');
|
|
4980
|
+
},
|
|
4981
|
+
|
|
4982
|
+
deactivate() {
|
|
4983
|
+
// Cleanup if needed
|
|
4984
|
+
},
|
|
4985
|
+
};
|
|
4986
|
+
|
|
4987
|
+
export default plugin;
|
|
4988
|
+
`;
|
|
4989
|
+
}
|
|
4990
|
+
function generateReadme(name, displayName, description, commandName) {
|
|
4991
|
+
return `# ${displayName}
|
|
4992
|
+
|
|
4993
|
+
${description}
|
|
4994
|
+
|
|
4995
|
+
## Installation
|
|
4996
|
+
|
|
4997
|
+
\`\`\`bash
|
|
4998
|
+
/plugin install ./${name}
|
|
4999
|
+
\`\`\`
|
|
5000
|
+
|
|
5001
|
+
## Usage
|
|
5002
|
+
|
|
5003
|
+
\`\`\`bash
|
|
5004
|
+
/${commandName} [args]
|
|
5005
|
+
\`\`\`
|
|
5006
|
+
|
|
5007
|
+
## Development
|
|
5008
|
+
|
|
5009
|
+
\`\`\`bash
|
|
5010
|
+
# Build the plugin
|
|
5011
|
+
npm run build
|
|
5012
|
+
|
|
5013
|
+
# Watch for changes
|
|
5014
|
+
npm run dev
|
|
5015
|
+
\`\`\`
|
|
5016
|
+
|
|
5017
|
+
## License
|
|
5018
|
+
|
|
5019
|
+
MIT
|
|
5020
|
+
`;
|
|
5021
|
+
}
|
|
5022
|
+
async function createPlugin(args2) {
|
|
5023
|
+
console.log("");
|
|
5024
|
+
console.log(` ${symbols.sparkle} ${theme.gradient("CREATE NEW PLUGIN")}`);
|
|
5025
|
+
console.log("");
|
|
5026
|
+
let name = args2[0] || "";
|
|
5027
|
+
if (!name) {
|
|
5028
|
+
name = await prompt("Plugin name (e.g., my-plugin)", "zammy-plugin-example");
|
|
5029
|
+
}
|
|
5030
|
+
name = name.toLowerCase().replace(/\s+/g, "-");
|
|
5031
|
+
if (!name.startsWith("zammy-plugin-")) {
|
|
5032
|
+
}
|
|
5033
|
+
const displayName = await prompt("Display name", name.replace(/^zammy-plugin-/, "").replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()));
|
|
5034
|
+
const description = await prompt("Description", "A zammy plugin");
|
|
5035
|
+
const commandName = await prompt("Main command name", name.replace(/^zammy-plugin-/, "").replace(/-/g, ""));
|
|
5036
|
+
const targetDir = resolve4(process.cwd(), name);
|
|
5037
|
+
if (existsSync10(targetDir)) {
|
|
5038
|
+
console.log(theme.error(` ${symbols.cross} Directory already exists: ${name}`));
|
|
5039
|
+
return;
|
|
5040
|
+
}
|
|
5041
|
+
console.log("");
|
|
5042
|
+
console.log(theme.dim(` Creating plugin in ${targetDir}...`));
|
|
5043
|
+
try {
|
|
5044
|
+
mkdirSync4(targetDir);
|
|
5045
|
+
mkdirSync4(join8(targetDir, "src"));
|
|
5046
|
+
mkdirSync4(join8(targetDir, "dist"));
|
|
5047
|
+
writeFileSync5(join8(targetDir, "zammy-plugin.json"), generateManifest(name, displayName, description, commandName));
|
|
5048
|
+
writeFileSync5(join8(targetDir, "package.json"), generatePackageJson(name, description));
|
|
5049
|
+
writeFileSync5(join8(targetDir, "tsconfig.json"), generateTsConfig());
|
|
5050
|
+
writeFileSync5(join8(targetDir, "src", "index.ts"), generateEntryPoint(commandName, displayName));
|
|
5051
|
+
writeFileSync5(join8(targetDir, "README.md"), generateReadme(name, displayName, description, commandName));
|
|
5052
|
+
console.log("");
|
|
5053
|
+
console.log(` ${symbols.check} ${theme.success("Plugin created successfully!")}`);
|
|
5054
|
+
console.log("");
|
|
5055
|
+
console.log(` ${theme.primary("Next steps:")}`);
|
|
5056
|
+
console.log(` ${theme.dim("1.")} cd ${name}`);
|
|
5057
|
+
console.log(` ${theme.dim("2.")} npm install`);
|
|
5058
|
+
console.log(` ${theme.dim("3.")} npm run build`);
|
|
5059
|
+
console.log(` ${theme.dim("4.")} /plugin install ./${name}`);
|
|
5060
|
+
console.log("");
|
|
5061
|
+
console.log(` ${theme.dim("Edit")} ${theme.accent("src/index.ts")} ${theme.dim("to customize your plugin")}`);
|
|
5062
|
+
console.log("");
|
|
5063
|
+
} catch (error) {
|
|
5064
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5065
|
+
console.log(theme.error(` ${symbols.cross} Failed to create plugin: ${message}`));
|
|
5066
|
+
}
|
|
5067
|
+
}
|
|
5068
|
+
|
|
5069
|
+
// src/commands/plugin/index.ts
|
|
5070
|
+
registerCommand({
|
|
5071
|
+
name: "plugin",
|
|
5072
|
+
description: "Manage zammy plugins",
|
|
5073
|
+
usage: "/plugin <list|install|remove|create> [args]",
|
|
5074
|
+
async execute(args2) {
|
|
5075
|
+
const subcommand = args2[0]?.toLowerCase();
|
|
5076
|
+
if (!subcommand || subcommand === "help") {
|
|
5077
|
+
console.log("");
|
|
5078
|
+
console.log(` ${symbols.gear} ${theme.gradient("PLUGIN MANAGER")}`);
|
|
5079
|
+
console.log("");
|
|
5080
|
+
console.log(` ${theme.primary("Usage:")} /plugin <command> [args]`);
|
|
5081
|
+
console.log("");
|
|
5082
|
+
console.log(` ${theme.primary("Commands:")}`);
|
|
5083
|
+
console.log(` ${theme.accent("list")} ${theme.dim("Show installed plugins")}`);
|
|
5084
|
+
console.log(` ${theme.accent("install")} <source> ${theme.dim("Install a plugin")}`);
|
|
5085
|
+
console.log(` ${theme.accent("remove")} <name> ${theme.dim("Remove a plugin")}`);
|
|
5086
|
+
console.log(` ${theme.accent("create")} [name] ${theme.dim("Create a new plugin")}`);
|
|
5087
|
+
console.log("");
|
|
5088
|
+
console.log(` ${theme.primary("Install sources:")}`);
|
|
5089
|
+
console.log(` ${theme.dim("./path/to/plugin")} ${theme.dim("Local directory")}`);
|
|
5090
|
+
console.log(` ${theme.dim("package-name")} ${theme.dim("npm package")}`);
|
|
5091
|
+
console.log(` ${theme.dim("github:user/repo")} ${theme.dim("GitHub repository")}`);
|
|
5092
|
+
console.log(` ${theme.dim("https://...git")} ${theme.dim("Git URL")}`);
|
|
5093
|
+
console.log("");
|
|
5094
|
+
return;
|
|
5095
|
+
}
|
|
5096
|
+
switch (subcommand) {
|
|
5097
|
+
case "list":
|
|
5098
|
+
case "ls":
|
|
5099
|
+
await listPlugins();
|
|
5100
|
+
break;
|
|
5101
|
+
case "install":
|
|
5102
|
+
case "i":
|
|
5103
|
+
case "add":
|
|
5104
|
+
await installPlugin(args2.slice(1));
|
|
5105
|
+
break;
|
|
5106
|
+
case "remove":
|
|
5107
|
+
case "rm":
|
|
5108
|
+
case "uninstall":
|
|
5109
|
+
await removePluginCommand(args2.slice(1));
|
|
5110
|
+
break;
|
|
5111
|
+
case "create":
|
|
5112
|
+
case "new":
|
|
5113
|
+
case "init":
|
|
5114
|
+
await createPlugin(args2.slice(1));
|
|
5115
|
+
break;
|
|
5116
|
+
default:
|
|
5117
|
+
console.log(theme.error(` ${symbols.cross} Unknown subcommand: ${subcommand}`));
|
|
5118
|
+
console.log(theme.dim(` Use '/plugin help' to see available commands`));
|
|
2902
5119
|
}
|
|
2903
5120
|
}
|
|
2904
5121
|
});
|
|
2905
5122
|
|
|
2906
5123
|
// src/cli.ts
|
|
2907
|
-
import { exec, execSync, spawn } from "child_process";
|
|
2908
|
-
import { existsSync as
|
|
2909
|
-
import { resolve as
|
|
2910
|
-
import { homedir as
|
|
5124
|
+
import { exec, execSync as execSync3, spawn } from "child_process";
|
|
5125
|
+
import { existsSync as existsSync11, statSync as statSync2, readFileSync as readFileSync9, readdirSync as readdirSync4, writeFileSync as writeFileSync6, watchFile, unwatchFile } from "fs";
|
|
5126
|
+
import { resolve as resolve5, extname, basename as basename4, join as join9 } from "path";
|
|
5127
|
+
import { homedir as homedir5, platform as platform3, networkInterfaces } from "os";
|
|
2911
5128
|
import chalk5 from "chalk";
|
|
2912
|
-
var
|
|
5129
|
+
var isWindows2 = platform3() === "win32";
|
|
2913
5130
|
function translateCommand(cmd) {
|
|
2914
|
-
if (!
|
|
5131
|
+
if (!isWindows2) return cmd;
|
|
2915
5132
|
const parts = cmd.trim().split(/\s+/);
|
|
2916
5133
|
const command = parts[0].toLowerCase();
|
|
2917
5134
|
const args2 = parts.slice(1);
|
|
@@ -2963,21 +5180,21 @@ function translateCommand(cmd) {
|
|
|
2963
5180
|
function handleCd(args2) {
|
|
2964
5181
|
let targetPath = args2.trim();
|
|
2965
5182
|
if (!targetPath || targetPath === "~") {
|
|
2966
|
-
targetPath =
|
|
5183
|
+
targetPath = homedir5();
|
|
2967
5184
|
} else if (targetPath.startsWith("~/")) {
|
|
2968
|
-
targetPath =
|
|
5185
|
+
targetPath = resolve5(homedir5(), targetPath.slice(2));
|
|
2969
5186
|
} else if (targetPath === "-") {
|
|
2970
5187
|
console.log(theme.dim(process.cwd()));
|
|
2971
5188
|
return;
|
|
2972
5189
|
} else {
|
|
2973
|
-
targetPath =
|
|
5190
|
+
targetPath = resolve5(process.cwd(), targetPath);
|
|
2974
5191
|
}
|
|
2975
|
-
if (!
|
|
5192
|
+
if (!existsSync11(targetPath)) {
|
|
2976
5193
|
console.log(`${miniSlime.sad} ${theme.error(`Directory not found: ${targetPath}`)}`);
|
|
2977
5194
|
return;
|
|
2978
5195
|
}
|
|
2979
5196
|
try {
|
|
2980
|
-
const stats =
|
|
5197
|
+
const stats = statSync2(targetPath);
|
|
2981
5198
|
if (!stats.isDirectory()) {
|
|
2982
5199
|
console.log(`${miniSlime.sad} ${theme.error(`Not a directory: ${targetPath}`)}`);
|
|
2983
5200
|
return;
|
|
@@ -2992,13 +5209,13 @@ function handlePwd() {
|
|
|
2992
5209
|
console.log(theme.primary(process.cwd()));
|
|
2993
5210
|
}
|
|
2994
5211
|
function handleCat(args2) {
|
|
2995
|
-
const filePath =
|
|
2996
|
-
if (!
|
|
5212
|
+
const filePath = resolve5(process.cwd(), args2.trim());
|
|
5213
|
+
if (!existsSync11(filePath)) {
|
|
2997
5214
|
console.log(`${miniSlime.sad} ${theme.error(`File not found: ${args2}`)}`);
|
|
2998
5215
|
return;
|
|
2999
5216
|
}
|
|
3000
5217
|
try {
|
|
3001
|
-
const content =
|
|
5218
|
+
const content = readFileSync9(filePath, "utf-8");
|
|
3002
5219
|
const ext = extname(filePath).toLowerCase();
|
|
3003
5220
|
if ([".js", ".ts", ".jsx", ".tsx", ".json", ".css", ".html", ".py", ".go", ".rs"].includes(ext)) {
|
|
3004
5221
|
console.log(highlightSyntax(content, ext));
|
|
@@ -3097,13 +5314,13 @@ function handleLs(args2) {
|
|
|
3097
5314
|
const showAll = parts.some((a) => a === "-a" || a === "-la" || a === "-al");
|
|
3098
5315
|
const showLong = parts.some((a) => a === "-l" || a === "-la" || a === "-al");
|
|
3099
5316
|
const pathArgs = parts.filter((a) => !a.startsWith("-"));
|
|
3100
|
-
const targetPath = pathArgs.length > 0 ?
|
|
3101
|
-
if (!
|
|
5317
|
+
const targetPath = pathArgs.length > 0 ? resolve5(process.cwd(), pathArgs[0]) : process.cwd();
|
|
5318
|
+
if (!existsSync11(targetPath)) {
|
|
3102
5319
|
console.log(theme.error(`Directory not found: ${targetPath}`));
|
|
3103
5320
|
return;
|
|
3104
5321
|
}
|
|
3105
5322
|
try {
|
|
3106
|
-
const entries =
|
|
5323
|
+
const entries = readdirSync4(targetPath, { withFileTypes: true });
|
|
3107
5324
|
const filtered = showAll ? entries : entries.filter((e) => !e.name.startsWith("."));
|
|
3108
5325
|
console.log("");
|
|
3109
5326
|
console.log(theme.dim(` ${targetPath}`));
|
|
@@ -3119,14 +5336,14 @@ function handleLs(args2) {
|
|
|
3119
5336
|
return a.name.localeCompare(b3.name);
|
|
3120
5337
|
});
|
|
3121
5338
|
for (const entry of sorted) {
|
|
3122
|
-
const fullPath =
|
|
5339
|
+
const fullPath = resolve5(targetPath, entry.name);
|
|
3123
5340
|
const isDir = entry.isDirectory();
|
|
3124
5341
|
const ext = isDir ? "dir" : extname(entry.name).toLowerCase();
|
|
3125
5342
|
const iconInfo = fileIcons[ext] || fileIcons["default"];
|
|
3126
5343
|
let line = ` ${iconInfo.icon} `;
|
|
3127
5344
|
if (showLong) {
|
|
3128
5345
|
try {
|
|
3129
|
-
const stats =
|
|
5346
|
+
const stats = statSync2(fullPath);
|
|
3130
5347
|
const size = isDir ? chalk5.dim(" <DIR>") : formatSize(stats.size);
|
|
3131
5348
|
const date = stats.mtime.toLocaleDateString("en-US", { month: "short", day: "2-digit", year: "numeric" });
|
|
3132
5349
|
line += chalk5.dim(date.padEnd(13)) + size + " ";
|
|
@@ -3148,19 +5365,19 @@ function handleLs(args2) {
|
|
|
3148
5365
|
function handleTree(args2, maxDepth = 3) {
|
|
3149
5366
|
const parts = args2.trim().split(/\s+/).filter(Boolean);
|
|
3150
5367
|
const pathArgs = parts.filter((a) => !a.startsWith("-"));
|
|
3151
|
-
const targetPath = pathArgs.length > 0 ?
|
|
3152
|
-
if (!
|
|
5368
|
+
const targetPath = pathArgs.length > 0 ? resolve5(process.cwd(), pathArgs[0]) : process.cwd();
|
|
5369
|
+
if (!existsSync11(targetPath)) {
|
|
3153
5370
|
console.log(theme.error(`Directory not found: ${targetPath}`));
|
|
3154
5371
|
return;
|
|
3155
5372
|
}
|
|
3156
5373
|
console.log("");
|
|
3157
|
-
console.log(theme.primary(` \u{1F4C1} ${
|
|
5374
|
+
console.log(theme.primary(` \u{1F4C1} ${basename4(targetPath)}/`));
|
|
3158
5375
|
let dirCount = 0;
|
|
3159
5376
|
let fileCount = 0;
|
|
3160
5377
|
function printTree(dir, prefix, depth) {
|
|
3161
5378
|
if (depth > maxDepth) return;
|
|
3162
5379
|
try {
|
|
3163
|
-
const entries =
|
|
5380
|
+
const entries = readdirSync4(dir, { withFileTypes: true }).filter((e) => !e.name.startsWith(".") && !["node_modules", ".git", "dist", "build"].includes(e.name)).sort((a, b3) => {
|
|
3164
5381
|
if (a.isDirectory() && !b3.isDirectory()) return -1;
|
|
3165
5382
|
if (!a.isDirectory() && b3.isDirectory()) return 1;
|
|
3166
5383
|
return a.name.localeCompare(b3.name);
|
|
@@ -3173,7 +5390,7 @@ function handleTree(args2, maxDepth = 3) {
|
|
|
3173
5390
|
if (entry.isDirectory()) {
|
|
3174
5391
|
dirCount++;
|
|
3175
5392
|
console.log(theme.dim(prefix + connector) + iconInfo.icon + " " + chalk5.hex(iconInfo.color).bold(entry.name + "/"));
|
|
3176
|
-
printTree(
|
|
5393
|
+
printTree(resolve5(dir, entry.name), prefix + (isLast ? " " : "\u2502 "), depth + 1);
|
|
3177
5394
|
} else {
|
|
3178
5395
|
fileCount++;
|
|
3179
5396
|
console.log(theme.dim(prefix + connector) + iconInfo.icon + " " + chalk5.hex(iconInfo.color)(entry.name));
|
|
@@ -3187,18 +5404,18 @@ function handleTree(args2, maxDepth = 3) {
|
|
|
3187
5404
|
console.log(theme.dim(` ${dirCount} directories, ${fileCount} files`));
|
|
3188
5405
|
console.log("");
|
|
3189
5406
|
}
|
|
3190
|
-
var bookmarksFile =
|
|
5407
|
+
var bookmarksFile = join9(homedir5(), ".zammy-bookmarks.json");
|
|
3191
5408
|
function loadBookmarks() {
|
|
3192
5409
|
try {
|
|
3193
|
-
if (
|
|
3194
|
-
return JSON.parse(
|
|
5410
|
+
if (existsSync11(bookmarksFile)) {
|
|
5411
|
+
return JSON.parse(readFileSync9(bookmarksFile, "utf-8"));
|
|
3195
5412
|
}
|
|
3196
5413
|
} catch {
|
|
3197
5414
|
}
|
|
3198
5415
|
return {};
|
|
3199
5416
|
}
|
|
3200
5417
|
function saveBookmarks(bookmarks) {
|
|
3201
|
-
|
|
5418
|
+
writeFileSync6(bookmarksFile, JSON.stringify(bookmarks, null, 2));
|
|
3202
5419
|
}
|
|
3203
5420
|
function handleBookmark(args2) {
|
|
3204
5421
|
const parts = args2.trim().split(/\s+/);
|
|
@@ -3254,7 +5471,7 @@ function handleBookmark(args2) {
|
|
|
3254
5471
|
console.log(theme.primary(" \u{1F4CD} Directory Bookmarks"));
|
|
3255
5472
|
console.log("");
|
|
3256
5473
|
for (const key of keys.sort()) {
|
|
3257
|
-
const exists =
|
|
5474
|
+
const exists = existsSync11(bookmarks[key]);
|
|
3258
5475
|
const status = exists ? theme.success(symbols.check) : theme.error(symbols.cross);
|
|
3259
5476
|
console.log(` ${status} ${theme.primary(key.padEnd(15))} ${theme.dim("\u2192")} ${bookmarks[key]}`);
|
|
3260
5477
|
}
|
|
@@ -3271,10 +5488,10 @@ function handleFind(args2) {
|
|
|
3271
5488
|
function searchDir(dir, depth = 0) {
|
|
3272
5489
|
if (depth > 5 || results.length >= maxResults) return;
|
|
3273
5490
|
try {
|
|
3274
|
-
const entries =
|
|
5491
|
+
const entries = readdirSync4(dir, { withFileTypes: true });
|
|
3275
5492
|
for (const entry of entries) {
|
|
3276
5493
|
if (entry.name.startsWith(".") || ["node_modules", ".git", "dist", "build"].includes(entry.name)) continue;
|
|
3277
|
-
const fullPath =
|
|
5494
|
+
const fullPath = resolve5(dir, entry.name);
|
|
3278
5495
|
const matchPattern = pattern.replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
3279
5496
|
const regex = new RegExp(matchPattern, "i");
|
|
3280
5497
|
if (regex.test(entry.name)) {
|
|
@@ -3298,7 +5515,7 @@ function handleFind(args2) {
|
|
|
3298
5515
|
const relativePath = result.path.replace(searchPath, ".").replace(/\\/g, "/");
|
|
3299
5516
|
const ext = result.isDir ? "dir" : extname(result.path).toLowerCase();
|
|
3300
5517
|
const iconInfo = fileIcons[ext] || fileIcons["default"];
|
|
3301
|
-
const fileName =
|
|
5518
|
+
const fileName = basename4(result.path);
|
|
3302
5519
|
const dirPath = relativePath.slice(0, -fileName.length);
|
|
3303
5520
|
console.log(` ${iconInfo.icon} ${theme.dim(dirPath)}${chalk5.hex(iconInfo.color)(fileName)}${result.isDir ? "/" : ""}`);
|
|
3304
5521
|
}
|
|
@@ -3310,8 +5527,8 @@ function handleFind(args2) {
|
|
|
3310
5527
|
console.log("");
|
|
3311
5528
|
}
|
|
3312
5529
|
function handleDu(args2) {
|
|
3313
|
-
const targetPath = args2.trim() ?
|
|
3314
|
-
if (!
|
|
5530
|
+
const targetPath = args2.trim() ? resolve5(process.cwd(), args2.trim()) : process.cwd();
|
|
5531
|
+
if (!existsSync11(targetPath)) {
|
|
3315
5532
|
console.log(theme.error(` Path not found: ${targetPath}`));
|
|
3316
5533
|
return;
|
|
3317
5534
|
}
|
|
@@ -3319,10 +5536,10 @@ function handleDu(args2) {
|
|
|
3319
5536
|
console.log("");
|
|
3320
5537
|
console.log(theme.dim(" Calculating sizes..."));
|
|
3321
5538
|
try {
|
|
3322
|
-
const entries =
|
|
5539
|
+
const entries = readdirSync4(targetPath, { withFileTypes: true });
|
|
3323
5540
|
for (const entry of entries) {
|
|
3324
5541
|
if (entry.name.startsWith(".")) continue;
|
|
3325
|
-
const fullPath =
|
|
5542
|
+
const fullPath = resolve5(targetPath, entry.name);
|
|
3326
5543
|
let size = 0;
|
|
3327
5544
|
let skipped = false;
|
|
3328
5545
|
try {
|
|
@@ -3331,10 +5548,10 @@ function handleDu(args2) {
|
|
|
3331
5548
|
skipped = true;
|
|
3332
5549
|
size = 0;
|
|
3333
5550
|
} else {
|
|
3334
|
-
size =
|
|
5551
|
+
size = getDirSize2(fullPath);
|
|
3335
5552
|
}
|
|
3336
5553
|
} else {
|
|
3337
|
-
size =
|
|
5554
|
+
size = statSync2(fullPath).size;
|
|
3338
5555
|
}
|
|
3339
5556
|
items.push({ name: entry.name, size, isDir: entry.isDirectory(), skipped });
|
|
3340
5557
|
} catch {
|
|
@@ -3348,7 +5565,7 @@ function handleDu(args2) {
|
|
|
3348
5565
|
items.sort((a, b3) => b3.size - a.size);
|
|
3349
5566
|
const totalSize = items.reduce((sum, item) => sum + item.size, 0);
|
|
3350
5567
|
console.log("");
|
|
3351
|
-
console.log(theme.primary(` \u{1F4CA} Disk Usage: ${
|
|
5568
|
+
console.log(theme.primary(` \u{1F4CA} Disk Usage: ${basename4(targetPath)}`));
|
|
3352
5569
|
console.log(theme.dim(` Total: ${formatSizeSimple(totalSize)}`));
|
|
3353
5570
|
console.log("");
|
|
3354
5571
|
const maxItems = 15;
|
|
@@ -3377,19 +5594,19 @@ function handleDu(args2) {
|
|
|
3377
5594
|
console.log("");
|
|
3378
5595
|
}
|
|
3379
5596
|
var skipDirs = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", ".nuxt", "coverage", ".cache", "__pycache__", "venv", ".venv"]);
|
|
3380
|
-
function
|
|
5597
|
+
function getDirSize2(dir, depth = 0, maxDepth = 4) {
|
|
3381
5598
|
if (depth > maxDepth) return 0;
|
|
3382
5599
|
let size = 0;
|
|
3383
5600
|
try {
|
|
3384
|
-
const entries =
|
|
5601
|
+
const entries = readdirSync4(dir, { withFileTypes: true });
|
|
3385
5602
|
for (const entry of entries) {
|
|
3386
5603
|
if (skipDirs.has(entry.name)) continue;
|
|
3387
|
-
const fullPath =
|
|
5604
|
+
const fullPath = resolve5(dir, entry.name);
|
|
3388
5605
|
if (entry.isDirectory()) {
|
|
3389
|
-
size +=
|
|
5606
|
+
size += getDirSize2(fullPath, depth + 1, maxDepth);
|
|
3390
5607
|
} else {
|
|
3391
5608
|
try {
|
|
3392
|
-
size +=
|
|
5609
|
+
size += statSync2(fullPath).size;
|
|
3393
5610
|
} catch {
|
|
3394
5611
|
}
|
|
3395
5612
|
}
|
|
@@ -3408,7 +5625,7 @@ function handleGit(args2) {
|
|
|
3408
5625
|
const subcommand = args2.trim().split(/\s+/)[0] || "status";
|
|
3409
5626
|
console.log("");
|
|
3410
5627
|
try {
|
|
3411
|
-
|
|
5628
|
+
execSync3("git rev-parse --is-inside-work-tree", { stdio: "pipe", timeout: 5e3 });
|
|
3412
5629
|
} catch {
|
|
3413
5630
|
console.log(theme.error(" Not a git repository"));
|
|
3414
5631
|
console.log("");
|
|
@@ -3418,10 +5635,10 @@ function handleGit(args2) {
|
|
|
3418
5635
|
switch (subcommand) {
|
|
3419
5636
|
case "status":
|
|
3420
5637
|
case "s": {
|
|
3421
|
-
const branch =
|
|
5638
|
+
const branch = execSync3("git branch --show-current", { encoding: "utf-8", timeout: 5e3 }).trim();
|
|
3422
5639
|
console.log(` ${symbols.rocket} ${theme.primary("Branch:")} ${chalk5.hex("#98C379")(branch)}`);
|
|
3423
5640
|
console.log("");
|
|
3424
|
-
const status =
|
|
5641
|
+
const status = execSync3("git status --porcelain", { encoding: "utf-8", timeout: 5e3 });
|
|
3425
5642
|
if (!status.trim()) {
|
|
3426
5643
|
console.log(` ${symbols.check} ${theme.success("Working tree clean")}`);
|
|
3427
5644
|
} else {
|
|
@@ -3458,7 +5675,7 @@ function handleGit(args2) {
|
|
|
3458
5675
|
console.log("");
|
|
3459
5676
|
}
|
|
3460
5677
|
}
|
|
3461
|
-
const log =
|
|
5678
|
+
const log = execSync3("git log --oneline -3", { encoding: "utf-8", timeout: 3e3 }).trim();
|
|
3462
5679
|
if (log) {
|
|
3463
5680
|
console.log(theme.dim(" Recent commits:"));
|
|
3464
5681
|
log.split("\n").forEach((line) => {
|
|
@@ -3470,7 +5687,7 @@ function handleGit(args2) {
|
|
|
3470
5687
|
}
|
|
3471
5688
|
case "log":
|
|
3472
5689
|
case "l": {
|
|
3473
|
-
const log =
|
|
5690
|
+
const log = execSync3("git log --oneline -10", { encoding: "utf-8", timeout: 3e3 }).trim();
|
|
3474
5691
|
console.log(theme.primary(" \u{1F4DC} Recent Commits"));
|
|
3475
5692
|
console.log("");
|
|
3476
5693
|
log.split("\n").forEach((line) => {
|
|
@@ -3481,7 +5698,7 @@ function handleGit(args2) {
|
|
|
3481
5698
|
}
|
|
3482
5699
|
case "branch":
|
|
3483
5700
|
case "b": {
|
|
3484
|
-
const branches =
|
|
5701
|
+
const branches = execSync3("git branch -a", { encoding: "utf-8", timeout: 3e3 }).trim();
|
|
3485
5702
|
console.log(theme.primary(" \u{1F33F} Branches"));
|
|
3486
5703
|
console.log("");
|
|
3487
5704
|
branches.split("\n").forEach((line) => {
|
|
@@ -3494,7 +5711,7 @@ function handleGit(args2) {
|
|
|
3494
5711
|
break;
|
|
3495
5712
|
}
|
|
3496
5713
|
default:
|
|
3497
|
-
const result =
|
|
5714
|
+
const result = execSync3(`git ${args2}`, { encoding: "utf-8", timeout: 1e4 });
|
|
3498
5715
|
console.log(result);
|
|
3499
5716
|
}
|
|
3500
5717
|
} catch (error) {
|
|
@@ -3503,23 +5720,23 @@ function handleGit(args2) {
|
|
|
3503
5720
|
}
|
|
3504
5721
|
console.log("");
|
|
3505
5722
|
}
|
|
3506
|
-
var isMac =
|
|
3507
|
-
var isLinux =
|
|
5723
|
+
var isMac = platform3() === "darwin";
|
|
5724
|
+
var isLinux = platform3() === "linux";
|
|
3508
5725
|
function getClipboardCopyCmd() {
|
|
3509
|
-
if (
|
|
5726
|
+
if (isWindows2) return "clip";
|
|
3510
5727
|
if (isMac) return "pbcopy";
|
|
3511
5728
|
try {
|
|
3512
|
-
|
|
5729
|
+
execSync3("which xclip", { stdio: "pipe" });
|
|
3513
5730
|
return "xclip -selection clipboard";
|
|
3514
5731
|
} catch {
|
|
3515
5732
|
return "xsel --clipboard --input";
|
|
3516
5733
|
}
|
|
3517
5734
|
}
|
|
3518
5735
|
function getClipboardPasteCmd() {
|
|
3519
|
-
if (
|
|
5736
|
+
if (isWindows2) return 'powershell -command "Get-Clipboard"';
|
|
3520
5737
|
if (isMac) return "pbpaste";
|
|
3521
5738
|
try {
|
|
3522
|
-
|
|
5739
|
+
execSync3("which xclip", { stdio: "pipe" });
|
|
3523
5740
|
return "xclip -selection clipboard -o";
|
|
3524
5741
|
} catch {
|
|
3525
5742
|
return "xsel --clipboard --output";
|
|
@@ -3533,10 +5750,10 @@ function handleClipboard(args2) {
|
|
|
3533
5750
|
if (action === "copy" && content) {
|
|
3534
5751
|
try {
|
|
3535
5752
|
const copyCmd = getClipboardCopyCmd();
|
|
3536
|
-
if (
|
|
3537
|
-
|
|
5753
|
+
if (isWindows2) {
|
|
5754
|
+
execSync3(`echo ${content} | ${copyCmd}`, { stdio: "pipe", timeout: 3e3 });
|
|
3538
5755
|
} else {
|
|
3539
|
-
|
|
5756
|
+
execSync3(`echo "${content}" | ${copyCmd}`, { stdio: "pipe", timeout: 3e3 });
|
|
3540
5757
|
}
|
|
3541
5758
|
console.log(` ${symbols.check} ${theme.success("Copied to clipboard")}`);
|
|
3542
5759
|
} catch {
|
|
@@ -3548,7 +5765,7 @@ function handleClipboard(args2) {
|
|
|
3548
5765
|
} else if (action === "paste") {
|
|
3549
5766
|
try {
|
|
3550
5767
|
const pasteCmd = getClipboardPasteCmd();
|
|
3551
|
-
const result =
|
|
5768
|
+
const result = execSync3(pasteCmd, { encoding: "utf-8", timeout: 3e3 }).trim();
|
|
3552
5769
|
console.log(` ${symbols.clipboard} ${theme.dim("Clipboard contents:")}`);
|
|
3553
5770
|
console.log("");
|
|
3554
5771
|
console.log(result);
|
|
@@ -3559,14 +5776,14 @@ function handleClipboard(args2) {
|
|
|
3559
5776
|
}
|
|
3560
5777
|
}
|
|
3561
5778
|
} else if (action === "file" && parts[1]) {
|
|
3562
|
-
const filePath =
|
|
3563
|
-
if (
|
|
5779
|
+
const filePath = resolve5(process.cwd(), parts[1]);
|
|
5780
|
+
if (existsSync11(filePath)) {
|
|
3564
5781
|
try {
|
|
3565
5782
|
const copyCmd = getClipboardCopyCmd();
|
|
3566
|
-
if (
|
|
3567
|
-
|
|
5783
|
+
if (isWindows2) {
|
|
5784
|
+
execSync3(`type "${filePath}" | ${copyCmd}`, { stdio: "pipe", timeout: 5e3 });
|
|
3568
5785
|
} else {
|
|
3569
|
-
|
|
5786
|
+
execSync3(`cat "${filePath}" | ${copyCmd}`, { stdio: "pipe", timeout: 5e3 });
|
|
3570
5787
|
}
|
|
3571
5788
|
console.log(` ${symbols.check} ${theme.success("File contents copied to clipboard")}`);
|
|
3572
5789
|
} catch {
|
|
@@ -3592,14 +5809,14 @@ function handleClipboard(args2) {
|
|
|
3592
5809
|
console.log("");
|
|
3593
5810
|
}
|
|
3594
5811
|
function handlePretty(args2) {
|
|
3595
|
-
const filePath =
|
|
3596
|
-
if (!
|
|
5812
|
+
const filePath = resolve5(process.cwd(), args2.trim());
|
|
5813
|
+
if (!existsSync11(filePath)) {
|
|
3597
5814
|
console.log(theme.error(` File not found: ${args2}`));
|
|
3598
5815
|
return;
|
|
3599
5816
|
}
|
|
3600
5817
|
console.log("");
|
|
3601
5818
|
try {
|
|
3602
|
-
const content =
|
|
5819
|
+
const content = readFileSync9(filePath, "utf-8");
|
|
3603
5820
|
const ext = extname(filePath).toLowerCase();
|
|
3604
5821
|
if (ext === ".json") {
|
|
3605
5822
|
const parsed = JSON.parse(content);
|
|
@@ -3646,14 +5863,14 @@ function handleWatch(args2) {
|
|
|
3646
5863
|
console.log("");
|
|
3647
5864
|
return;
|
|
3648
5865
|
}
|
|
3649
|
-
const filePath =
|
|
3650
|
-
if (!
|
|
5866
|
+
const filePath = resolve5(process.cwd(), action);
|
|
5867
|
+
if (!existsSync11(filePath)) {
|
|
3651
5868
|
console.log(theme.error(` File not found: ${action}`));
|
|
3652
5869
|
console.log("");
|
|
3653
5870
|
return;
|
|
3654
5871
|
}
|
|
3655
5872
|
try {
|
|
3656
|
-
const stats =
|
|
5873
|
+
const stats = statSync2(filePath);
|
|
3657
5874
|
if (stats.isDirectory()) {
|
|
3658
5875
|
console.log(theme.error(` Cannot watch a directory: ${action}`));
|
|
3659
5876
|
console.log(theme.dim(" Please specify a file path"));
|
|
@@ -3669,12 +5886,12 @@ function handleWatch(args2) {
|
|
|
3669
5886
|
unwatchFile(activeWatcher);
|
|
3670
5887
|
}
|
|
3671
5888
|
activeWatcher = filePath;
|
|
3672
|
-
let lastSize =
|
|
5889
|
+
let lastSize = statSync2(filePath).size;
|
|
3673
5890
|
console.log(` ${symbols.info} ${theme.primary("Watching:")} ${filePath}`);
|
|
3674
5891
|
console.log(theme.dim(' (Type "!watch stop" to stop watching)'));
|
|
3675
5892
|
console.log("");
|
|
3676
5893
|
try {
|
|
3677
|
-
const content =
|
|
5894
|
+
const content = readFileSync9(filePath, "utf-8");
|
|
3678
5895
|
const lines = content.split("\n").slice(-10);
|
|
3679
5896
|
lines.forEach((line) => console.log(theme.dim(line)));
|
|
3680
5897
|
} catch {
|
|
@@ -3683,7 +5900,7 @@ function handleWatch(args2) {
|
|
|
3683
5900
|
watchFile(filePath, { interval: 500 }, (curr, prev) => {
|
|
3684
5901
|
if (curr.size > lastSize) {
|
|
3685
5902
|
try {
|
|
3686
|
-
const newContent =
|
|
5903
|
+
const newContent = readFileSync9(filePath, "utf-8");
|
|
3687
5904
|
const allLines = newContent.split("\n");
|
|
3688
5905
|
const oldLines = Math.floor(prev.size / 50);
|
|
3689
5906
|
const newLines = allLines.slice(-Math.max(1, allLines.length - oldLines));
|
|
@@ -3706,7 +5923,7 @@ function handleServe(args2) {
|
|
|
3706
5923
|
console.log(theme.dim(" Press Ctrl+C to stop"));
|
|
3707
5924
|
console.log("");
|
|
3708
5925
|
try {
|
|
3709
|
-
const npx =
|
|
5926
|
+
const npx = isWindows2 ? "npx.cmd" : "npx";
|
|
3710
5927
|
spawn(npx, ["serve", "-p", String(port)], {
|
|
3711
5928
|
cwd: process.cwd(),
|
|
3712
5929
|
stdio: "inherit"
|
|
@@ -3721,8 +5938,8 @@ function handlePs() {
|
|
|
3721
5938
|
console.log("");
|
|
3722
5939
|
try {
|
|
3723
5940
|
let result;
|
|
3724
|
-
if (
|
|
3725
|
-
result =
|
|
5941
|
+
if (isWindows2) {
|
|
5942
|
+
result = execSync3('tasklist /FO CSV /NH | findstr /V "System Idle"', { encoding: "utf-8", timeout: 5e3 });
|
|
3726
5943
|
const lines = result.trim().split("\n").slice(0, 15);
|
|
3727
5944
|
console.log(theme.dim(" Name PID Memory"));
|
|
3728
5945
|
console.log(theme.dim(" \u2500".repeat(25)));
|
|
@@ -3736,7 +5953,7 @@ function handlePs() {
|
|
|
3736
5953
|
}
|
|
3737
5954
|
});
|
|
3738
5955
|
} else {
|
|
3739
|
-
result =
|
|
5956
|
+
result = execSync3("ps aux | head -15", { encoding: "utf-8", timeout: 5e3 });
|
|
3740
5957
|
console.log(result);
|
|
3741
5958
|
}
|
|
3742
5959
|
} catch {
|
|
@@ -3795,10 +6012,10 @@ function handleIp() {
|
|
|
3795
6012
|
try {
|
|
3796
6013
|
let result;
|
|
3797
6014
|
try {
|
|
3798
|
-
result =
|
|
6015
|
+
result = execSync3("curl -s ifconfig.me", { encoding: "utf-8", timeout: 5e3 }).trim();
|
|
3799
6016
|
} catch {
|
|
3800
|
-
if (
|
|
3801
|
-
result =
|
|
6017
|
+
if (isWindows2) {
|
|
6018
|
+
result = execSync3('powershell -command "(Invoke-WebRequest -Uri ifconfig.me -UseBasicParsing).Content"', { encoding: "utf-8", timeout: 5e3 }).trim();
|
|
3802
6019
|
} else {
|
|
3803
6020
|
throw new Error("curl not available");
|
|
3804
6021
|
}
|
|
@@ -3864,13 +6081,13 @@ async function handleHttp(args2) {
|
|
|
3864
6081
|
let result;
|
|
3865
6082
|
try {
|
|
3866
6083
|
const curlCmd = method === "HEAD" ? `curl -sI "${fullUrl}"` : `curl -s -X ${method} "${fullUrl}"`;
|
|
3867
|
-
result =
|
|
6084
|
+
result = execSync3(curlCmd, { encoding: "utf-8", timeout: 1e4 });
|
|
3868
6085
|
} catch {
|
|
3869
|
-
if (
|
|
6086
|
+
if (isWindows2) {
|
|
3870
6087
|
if (method === "HEAD") {
|
|
3871
|
-
result =
|
|
6088
|
+
result = execSync3(`powershell -command "(Invoke-WebRequest -Uri '${fullUrl}' -Method Head -UseBasicParsing).Headers | ConvertTo-Json"`, { encoding: "utf-8", timeout: 1e4 });
|
|
3872
6089
|
} else {
|
|
3873
|
-
result =
|
|
6090
|
+
result = execSync3(`powershell -command "(Invoke-WebRequest -Uri '${fullUrl}' -Method ${method} -UseBasicParsing).Content"`, { encoding: "utf-8", timeout: 1e4 });
|
|
3874
6091
|
}
|
|
3875
6092
|
} else {
|
|
3876
6093
|
throw new Error("curl not available");
|
|
@@ -3908,22 +6125,22 @@ function handleDiff(args2) {
|
|
|
3908
6125
|
console.log("");
|
|
3909
6126
|
return;
|
|
3910
6127
|
}
|
|
3911
|
-
const path1 =
|
|
3912
|
-
const path2 =
|
|
3913
|
-
if (!
|
|
6128
|
+
const path1 = resolve5(process.cwd(), file1);
|
|
6129
|
+
const path2 = resolve5(process.cwd(), file2);
|
|
6130
|
+
if (!existsSync11(path1)) {
|
|
3914
6131
|
console.log(theme.error(` File not found: ${file1}`));
|
|
3915
6132
|
console.log("");
|
|
3916
6133
|
return;
|
|
3917
6134
|
}
|
|
3918
|
-
if (!
|
|
6135
|
+
if (!existsSync11(path2)) {
|
|
3919
6136
|
console.log(theme.error(` File not found: ${file2}`));
|
|
3920
6137
|
console.log("");
|
|
3921
6138
|
return;
|
|
3922
6139
|
}
|
|
3923
6140
|
try {
|
|
3924
|
-
const content1 =
|
|
3925
|
-
const content2 =
|
|
3926
|
-
console.log(theme.primary(` Comparing: ${
|
|
6141
|
+
const content1 = readFileSync9(path1, "utf-8").split("\n");
|
|
6142
|
+
const content2 = readFileSync9(path2, "utf-8").split("\n");
|
|
6143
|
+
console.log(theme.primary(` Comparing: ${basename4(file1)} \u2194 ${basename4(file2)}`));
|
|
3927
6144
|
console.log("");
|
|
3928
6145
|
const maxLines = Math.max(content1.length, content2.length);
|
|
3929
6146
|
let differences = 0;
|
|
@@ -3950,18 +6167,18 @@ function handleDiff(args2) {
|
|
|
3950
6167
|
}
|
|
3951
6168
|
console.log("");
|
|
3952
6169
|
}
|
|
3953
|
-
var aliasesFile =
|
|
6170
|
+
var aliasesFile = join9(homedir5(), ".zammy-aliases.json");
|
|
3954
6171
|
function loadAliases() {
|
|
3955
6172
|
try {
|
|
3956
|
-
if (
|
|
3957
|
-
return JSON.parse(
|
|
6173
|
+
if (existsSync11(aliasesFile)) {
|
|
6174
|
+
return JSON.parse(readFileSync9(aliasesFile, "utf-8"));
|
|
3958
6175
|
}
|
|
3959
6176
|
} catch {
|
|
3960
6177
|
}
|
|
3961
6178
|
return {};
|
|
3962
6179
|
}
|
|
3963
6180
|
function saveAliases(aliases) {
|
|
3964
|
-
|
|
6181
|
+
writeFileSync6(aliasesFile, JSON.stringify(aliases, null, 2));
|
|
3965
6182
|
}
|
|
3966
6183
|
function handleAlias(args2) {
|
|
3967
6184
|
const parts = args2.trim().split(/\s+/);
|
|
@@ -3991,7 +6208,7 @@ function handleAlias(args2) {
|
|
|
3991
6208
|
console.log(theme.dim(` Running: ${aliases[name]}`));
|
|
3992
6209
|
console.log("");
|
|
3993
6210
|
try {
|
|
3994
|
-
const result =
|
|
6211
|
+
const result = execSync3(aliases[name], { encoding: "utf-8", cwd: process.cwd(), timeout: 3e4 });
|
|
3995
6212
|
console.log(result);
|
|
3996
6213
|
} catch (error) {
|
|
3997
6214
|
const err = error;
|
|
@@ -4023,7 +6240,7 @@ function handleNotify(args2) {
|
|
|
4023
6240
|
const message = args2.trim() || "Notification from Zammy CLI";
|
|
4024
6241
|
console.log("");
|
|
4025
6242
|
try {
|
|
4026
|
-
if (
|
|
6243
|
+
if (isWindows2) {
|
|
4027
6244
|
const ps = `
|
|
4028
6245
|
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
|
|
4029
6246
|
[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null
|
|
@@ -4032,10 +6249,10 @@ function handleNotify(args2) {
|
|
|
4032
6249
|
$notifier = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("Zammy CLI")
|
|
4033
6250
|
$notifier.Show([Windows.UI.Notifications.ToastNotification]::new($template))
|
|
4034
6251
|
`;
|
|
4035
|
-
|
|
6252
|
+
execSync3(`powershell -command "${ps.replace(/\n/g, " ")}"`, { stdio: "pipe", timeout: 5e3 });
|
|
4036
6253
|
} else {
|
|
4037
|
-
const cmd =
|
|
4038
|
-
|
|
6254
|
+
const cmd = platform3() === "darwin" ? `osascript -e 'display notification "${message}" with title "Zammy CLI"'` : `notify-send "Zammy CLI" "${message}"`;
|
|
6255
|
+
execSync3(cmd, { stdio: "pipe", timeout: 3e3 });
|
|
4039
6256
|
}
|
|
4040
6257
|
console.log(` ${symbols.check} ${theme.success("Notification sent")}`);
|
|
4041
6258
|
} catch {
|
|
@@ -4065,7 +6282,7 @@ function handleGrep(args2) {
|
|
|
4065
6282
|
function searchFile(filePath) {
|
|
4066
6283
|
if (results.length >= maxResults) return;
|
|
4067
6284
|
try {
|
|
4068
|
-
const content =
|
|
6285
|
+
const content = readFileSync9(filePath, "utf-8");
|
|
4069
6286
|
const lines = content.split("\n");
|
|
4070
6287
|
lines.forEach((line, index) => {
|
|
4071
6288
|
if (results.length >= maxResults) return;
|
|
@@ -4083,11 +6300,11 @@ function handleGrep(args2) {
|
|
|
4083
6300
|
function searchDir(dir, depth = 0) {
|
|
4084
6301
|
if (depth > 4 || results.length >= maxResults) return;
|
|
4085
6302
|
try {
|
|
4086
|
-
const entries =
|
|
6303
|
+
const entries = readdirSync4(dir, { withFileTypes: true });
|
|
4087
6304
|
for (const entry of entries) {
|
|
4088
6305
|
if (results.length >= maxResults) break;
|
|
4089
6306
|
if (entry.name.startsWith(".") || ["node_modules", ".git", "dist", "build"].includes(entry.name)) continue;
|
|
4090
|
-
const fullPath =
|
|
6307
|
+
const fullPath = resolve5(dir, entry.name);
|
|
4091
6308
|
if (entry.isDirectory()) {
|
|
4092
6309
|
searchDir(fullPath, depth + 1);
|
|
4093
6310
|
} else {
|
|
@@ -4123,25 +6340,25 @@ function handleGrep(args2) {
|
|
|
4123
6340
|
console.log("");
|
|
4124
6341
|
}
|
|
4125
6342
|
function handleWc(args2) {
|
|
4126
|
-
const filePath =
|
|
6343
|
+
const filePath = resolve5(process.cwd(), args2.trim());
|
|
4127
6344
|
console.log("");
|
|
4128
6345
|
if (!args2.trim()) {
|
|
4129
6346
|
console.log(theme.error(" Usage: wc <file>"));
|
|
4130
6347
|
console.log("");
|
|
4131
6348
|
return;
|
|
4132
6349
|
}
|
|
4133
|
-
if (!
|
|
6350
|
+
if (!existsSync11(filePath)) {
|
|
4134
6351
|
console.log(theme.error(` File not found: ${args2}`));
|
|
4135
6352
|
console.log("");
|
|
4136
6353
|
return;
|
|
4137
6354
|
}
|
|
4138
6355
|
try {
|
|
4139
|
-
const content =
|
|
6356
|
+
const content = readFileSync9(filePath, "utf-8");
|
|
4140
6357
|
const lines = content.split("\n").length;
|
|
4141
6358
|
const words = content.split(/\s+/).filter((w) => w.length > 0).length;
|
|
4142
6359
|
const chars = content.length;
|
|
4143
6360
|
const bytes = Buffer.byteLength(content, "utf-8");
|
|
4144
|
-
console.log(theme.primary(` \u{1F4CA} ${
|
|
6361
|
+
console.log(theme.primary(` \u{1F4CA} ${basename4(args2)}`));
|
|
4145
6362
|
console.log("");
|
|
4146
6363
|
console.log(` ${chalk5.hex("#61AFEF")(lines.toLocaleString().padStart(8))} ${theme.dim("lines")}`);
|
|
4147
6364
|
console.log(` ${chalk5.hex("#98C379")(words.toLocaleString().padStart(8))} ${theme.dim("words")}`);
|
|
@@ -4166,16 +6383,16 @@ function handleHead(args2) {
|
|
|
4166
6383
|
console.log("");
|
|
4167
6384
|
return;
|
|
4168
6385
|
}
|
|
4169
|
-
const fullPath =
|
|
4170
|
-
if (!
|
|
6386
|
+
const fullPath = resolve5(process.cwd(), filePath);
|
|
6387
|
+
if (!existsSync11(fullPath)) {
|
|
4171
6388
|
console.log(theme.error(` File not found: ${filePath}`));
|
|
4172
6389
|
console.log("");
|
|
4173
6390
|
return;
|
|
4174
6391
|
}
|
|
4175
6392
|
try {
|
|
4176
|
-
const content =
|
|
6393
|
+
const content = readFileSync9(fullPath, "utf-8");
|
|
4177
6394
|
const fileLines = content.split("\n").slice(0, lines);
|
|
4178
|
-
console.log(theme.dim(` First ${lines} lines of ${
|
|
6395
|
+
console.log(theme.dim(` First ${lines} lines of ${basename4(filePath)}:`));
|
|
4179
6396
|
console.log("");
|
|
4180
6397
|
fileLines.forEach((line, i) => {
|
|
4181
6398
|
console.log(` ${chalk5.dim((i + 1).toString().padStart(4))} ${line}`);
|
|
@@ -4380,9 +6597,9 @@ async function parseAndExecute(input) {
|
|
|
4380
6597
|
}
|
|
4381
6598
|
|
|
4382
6599
|
// src/index.ts
|
|
4383
|
-
import { readdirSync as
|
|
4384
|
-
import { fileURLToPath } from "url";
|
|
4385
|
-
import { dirname, join as
|
|
6600
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync10 } from "fs";
|
|
6601
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
6602
|
+
import { dirname as dirname5, join as join10 } from "path";
|
|
4386
6603
|
import chalk6 from "chalk";
|
|
4387
6604
|
var SLIME_COLOR = "#9B59B6";
|
|
4388
6605
|
var EYE_COLOR = "#1A1A2E";
|
|
@@ -4467,10 +6684,10 @@ function setIdle() {
|
|
|
4467
6684
|
var args = process.argv.slice(2);
|
|
4468
6685
|
if (args.includes("--version") || args.includes("-v")) {
|
|
4469
6686
|
try {
|
|
4470
|
-
const __filename =
|
|
4471
|
-
const __dirname =
|
|
4472
|
-
const pkgPath =
|
|
4473
|
-
const pkg = JSON.parse(
|
|
6687
|
+
const __filename = fileURLToPath4(import.meta.url);
|
|
6688
|
+
const __dirname = dirname5(__filename);
|
|
6689
|
+
const pkgPath = join10(__dirname, "..", "package.json");
|
|
6690
|
+
const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
|
|
4474
6691
|
console.log(`zammy v${pkg.version}`);
|
|
4475
6692
|
} catch {
|
|
4476
6693
|
console.log("zammy v1.0.0");
|
|
@@ -4691,7 +6908,7 @@ function completer(line) {
|
|
|
4691
6908
|
const afterCommand = line.slice("/asciiart ".length);
|
|
4692
6909
|
const searchTerm = afterCommand.startsWith("@") ? afterCommand.slice(1) : afterCommand;
|
|
4693
6910
|
try {
|
|
4694
|
-
const files =
|
|
6911
|
+
const files = readdirSync5(process.cwd());
|
|
4695
6912
|
const imageFiles = files.filter((f) => {
|
|
4696
6913
|
const ext = f.toLowerCase().slice(f.lastIndexOf("."));
|
|
4697
6914
|
return IMAGE_EXTENSIONS.includes(ext);
|
|
@@ -4721,7 +6938,8 @@ async function main() {
|
|
|
4721
6938
|
console.log(theme.dim("For full features, run in a proper terminal or use: zammy --simple\n"));
|
|
4722
6939
|
}
|
|
4723
6940
|
await displayBanner(isSimpleMode);
|
|
4724
|
-
|
|
6941
|
+
await initPluginLoader();
|
|
6942
|
+
const rl = readline4.createInterface({
|
|
4725
6943
|
input: process.stdin,
|
|
4726
6944
|
output: process.stdout,
|
|
4727
6945
|
terminal: isTTY,
|