blun-king-cli 1.0.0 → 1.1.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/bin/blun.js +309 -0
- package/package.json +1 -1
package/bin/blun.js
CHANGED
|
@@ -185,6 +185,16 @@ async function handleCommand(input) {
|
|
|
185
185
|
console.log(" /set telegram Configure Telegram bridge");
|
|
186
186
|
console.log(" /set verbose Toggle verbose logging");
|
|
187
187
|
console.log("");
|
|
188
|
+
console.log(C.yellow + " DEV" + C.reset);
|
|
189
|
+
console.log(" /sh <cmd> Run shell command");
|
|
190
|
+
console.log(" /git <cmd> Git commands (status, log, diff, add, commit, push, pull, branch, checkout, clone, init)");
|
|
191
|
+
console.log(" /ssh add/del/list Connect & manage SSH hosts");
|
|
192
|
+
console.log(" /ssh <host> <cmd> Run command on remote host");
|
|
193
|
+
console.log(" /deploy <method> Deploy: git, ssh, sync, pm2");
|
|
194
|
+
console.log(" /read <file> Read a file");
|
|
195
|
+
console.log(" /write <file> Write/create a file");
|
|
196
|
+
console.log(" /init Init project (AGENT.md, .gitignore, git init)");
|
|
197
|
+
console.log("");
|
|
188
198
|
console.log(C.yellow + " SYSTEM" + C.reset);
|
|
189
199
|
console.log(" /status Runtime status");
|
|
190
200
|
console.log(" /versions Prompt versions");
|
|
@@ -272,6 +282,34 @@ async function handleCommand(input) {
|
|
|
272
282
|
cmdFiles();
|
|
273
283
|
break;
|
|
274
284
|
|
|
285
|
+
case "/sh":
|
|
286
|
+
cmdShell(args);
|
|
287
|
+
break;
|
|
288
|
+
|
|
289
|
+
case "/git":
|
|
290
|
+
cmdGit(args);
|
|
291
|
+
break;
|
|
292
|
+
|
|
293
|
+
case "/ssh":
|
|
294
|
+
cmdSsh(args);
|
|
295
|
+
break;
|
|
296
|
+
|
|
297
|
+
case "/deploy":
|
|
298
|
+
cmdDeploy(args);
|
|
299
|
+
break;
|
|
300
|
+
|
|
301
|
+
case "/read":
|
|
302
|
+
cmdRead(args);
|
|
303
|
+
break;
|
|
304
|
+
|
|
305
|
+
case "/write":
|
|
306
|
+
cmdWrite(args);
|
|
307
|
+
break;
|
|
308
|
+
|
|
309
|
+
case "/init":
|
|
310
|
+
cmdInit();
|
|
311
|
+
break;
|
|
312
|
+
|
|
275
313
|
case "/exit":
|
|
276
314
|
case "/quit":
|
|
277
315
|
case "/q":
|
|
@@ -733,6 +771,277 @@ function cmdFiles() {
|
|
|
733
771
|
} catch(e) { printError(e.message); }
|
|
734
772
|
}
|
|
735
773
|
|
|
774
|
+
// ── Shell Execution ──
|
|
775
|
+
function cmdShell(command) {
|
|
776
|
+
if (!command) { printError("Usage: /sh <command>"); return; }
|
|
777
|
+
try {
|
|
778
|
+
console.log(C.dim + "$ " + command + C.reset);
|
|
779
|
+
var output = execSync(command, { cwd: config.workdir, encoding: "utf8", timeout: 30000, stdio: ["pipe", "pipe", "pipe"] });
|
|
780
|
+
console.log(output);
|
|
781
|
+
} catch(e) {
|
|
782
|
+
if (e.stdout) console.log(e.stdout);
|
|
783
|
+
if (e.stderr) console.log(C.red + e.stderr + C.reset);
|
|
784
|
+
printError("Exit code: " + (e.status || "unknown"));
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// ── Git Commands ──
|
|
789
|
+
function cmdGit(args) {
|
|
790
|
+
if (!args) {
|
|
791
|
+
// Show git status
|
|
792
|
+
cmdShell("git status --short");
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
var sub = args.split(/\s+/)[0];
|
|
796
|
+
var rest = args.slice(sub.length).trim();
|
|
797
|
+
|
|
798
|
+
switch(sub) {
|
|
799
|
+
case "status": cmdShell("git status"); break;
|
|
800
|
+
case "log": cmdShell("git log --oneline -10"); break;
|
|
801
|
+
case "diff": cmdShell("git diff --stat"); break;
|
|
802
|
+
case "add":
|
|
803
|
+
cmdShell("git add " + (rest || "."));
|
|
804
|
+
printSuccess("Files staged.");
|
|
805
|
+
break;
|
|
806
|
+
case "commit":
|
|
807
|
+
if (!rest) { printError("Usage: /git commit <message>"); return; }
|
|
808
|
+
cmdShell('git commit -m "' + rest.replace(/"/g, '\\"') + '"');
|
|
809
|
+
break;
|
|
810
|
+
case "push":
|
|
811
|
+
var remote = rest || "origin";
|
|
812
|
+
try {
|
|
813
|
+
var branch = execSync("git branch --show-current", { cwd: config.workdir, encoding: "utf8" }).trim();
|
|
814
|
+
printInfo("Pushing " + branch + " to " + remote + "...");
|
|
815
|
+
cmdShell("git push " + remote + " " + branch);
|
|
816
|
+
printSuccess("Pushed!");
|
|
817
|
+
} catch(e) { printError("Push failed: " + e.message); }
|
|
818
|
+
break;
|
|
819
|
+
case "pull":
|
|
820
|
+
cmdShell("git pull " + (rest || ""));
|
|
821
|
+
break;
|
|
822
|
+
case "branch":
|
|
823
|
+
cmdShell("git branch " + (rest || "-a"));
|
|
824
|
+
break;
|
|
825
|
+
case "checkout":
|
|
826
|
+
if (!rest) { printError("Usage: /git checkout <branch>"); return; }
|
|
827
|
+
cmdShell("git checkout " + rest);
|
|
828
|
+
break;
|
|
829
|
+
case "clone":
|
|
830
|
+
if (!rest) { printError("Usage: /git clone <url>"); return; }
|
|
831
|
+
cmdShell("git clone " + rest);
|
|
832
|
+
break;
|
|
833
|
+
case "init":
|
|
834
|
+
cmdShell("git init");
|
|
835
|
+
break;
|
|
836
|
+
case "remote":
|
|
837
|
+
cmdShell("git remote -v");
|
|
838
|
+
break;
|
|
839
|
+
default:
|
|
840
|
+
cmdShell("git " + args);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// ── SSH Commands ──
|
|
845
|
+
function cmdSsh(args) {
|
|
846
|
+
if (!args) {
|
|
847
|
+
// Show saved connections
|
|
848
|
+
var sshFile = path.join(CONFIG_DIR, "ssh.json");
|
|
849
|
+
if (fs.existsSync(sshFile)) {
|
|
850
|
+
var connections = JSON.parse(fs.readFileSync(sshFile, "utf8"));
|
|
851
|
+
console.log("");
|
|
852
|
+
console.log(C.bold + "SSH Connections:" + C.reset);
|
|
853
|
+
for (var name in connections) {
|
|
854
|
+
var c = connections[name];
|
|
855
|
+
console.log(" " + C.yellow + name + C.reset + " — " + c.user + "@" + c.host + (c.port !== 22 ? ":" + c.port : ""));
|
|
856
|
+
}
|
|
857
|
+
console.log("");
|
|
858
|
+
console.log(C.dim + " /ssh <name> <command> — Run command" + C.reset);
|
|
859
|
+
console.log(C.dim + " /ssh add <name> <user@host> — Add connection" + C.reset);
|
|
860
|
+
console.log(C.dim + " /ssh del <name> — Remove connection" + C.reset);
|
|
861
|
+
} else {
|
|
862
|
+
printInfo("No SSH connections saved. Use /ssh add <name> <user@host>");
|
|
863
|
+
}
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
var parts = args.split(/\s+/);
|
|
868
|
+
var sshFile = path.join(CONFIG_DIR, "ssh.json");
|
|
869
|
+
var connections = {};
|
|
870
|
+
if (fs.existsSync(sshFile)) {
|
|
871
|
+
try { connections = JSON.parse(fs.readFileSync(sshFile, "utf8")); } catch(e) {}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
if (parts[0] === "add" && parts[1] && parts[2]) {
|
|
875
|
+
var match = parts[2].match(/^([^@]+)@([^:]+)(?::(\d+))?$/);
|
|
876
|
+
if (!match) { printError("Format: user@host or user@host:port"); return; }
|
|
877
|
+
connections[parts[1]] = { user: match[1], host: match[2], port: parseInt(match[3] || "22"), key: parts[3] || null };
|
|
878
|
+
fs.writeFileSync(sshFile, JSON.stringify(connections, null, 2));
|
|
879
|
+
printSuccess("SSH connection '" + parts[1] + "' saved.");
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
if (parts[0] === "del" && parts[1]) {
|
|
884
|
+
delete connections[parts[1]];
|
|
885
|
+
fs.writeFileSync(sshFile, JSON.stringify(connections, null, 2));
|
|
886
|
+
printSuccess("SSH connection '" + parts[1] + "' removed.");
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// Execute command on saved connection
|
|
891
|
+
var connName = parts[0];
|
|
892
|
+
var remoteCmd = parts.slice(1).join(" ");
|
|
893
|
+
if (!connections[connName]) { printError("Unknown connection: " + connName + ". Use /ssh to list."); return; }
|
|
894
|
+
if (!remoteCmd) { printError("Usage: /ssh " + connName + " <command>"); return; }
|
|
895
|
+
|
|
896
|
+
var conn = connections[connName];
|
|
897
|
+
var sshCmd = "ssh";
|
|
898
|
+
if (conn.key) sshCmd += " -i " + conn.key;
|
|
899
|
+
sshCmd += " -o ConnectTimeout=10 -o StrictHostKeyChecking=no";
|
|
900
|
+
sshCmd += " -p " + conn.port;
|
|
901
|
+
sshCmd += " " + conn.user + "@" + conn.host;
|
|
902
|
+
sshCmd += ' "' + remoteCmd.replace(/"/g, '\\"') + '"';
|
|
903
|
+
|
|
904
|
+
printInfo("SSH " + connName + ": " + remoteCmd);
|
|
905
|
+
cmdShell(sshCmd);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// ── Deploy Command ──
|
|
909
|
+
async function cmdDeploy(args) {
|
|
910
|
+
if (!args) {
|
|
911
|
+
console.log("");
|
|
912
|
+
console.log(C.bold + "Deploy Options:" + C.reset);
|
|
913
|
+
console.log(" /deploy git — git add + commit + push");
|
|
914
|
+
console.log(" /deploy ssh <name> <cmd> — Run deploy command via SSH");
|
|
915
|
+
console.log(" /deploy sync <name> — rsync workdir to server");
|
|
916
|
+
console.log(" /deploy pm2 <name> <app> — pm2 restart on server");
|
|
917
|
+
console.log("");
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
var parts = args.split(/\s+/);
|
|
922
|
+
var action = parts[0];
|
|
923
|
+
|
|
924
|
+
if (action === "git") {
|
|
925
|
+
printInfo("Deploying via git...");
|
|
926
|
+
try {
|
|
927
|
+
var status = execSync("git status --porcelain", { cwd: config.workdir, encoding: "utf8" }).trim();
|
|
928
|
+
if (status) {
|
|
929
|
+
cmdShell("git add .");
|
|
930
|
+
var msg = parts.slice(1).join(" ") || "deploy: " + new Date().toISOString().slice(0, 16);
|
|
931
|
+
cmdShell('git commit -m "' + msg + '"');
|
|
932
|
+
}
|
|
933
|
+
var branch = execSync("git branch --show-current", { cwd: config.workdir, encoding: "utf8" }).trim();
|
|
934
|
+
cmdShell("git push origin " + branch);
|
|
935
|
+
printSuccess("Deployed via git push!");
|
|
936
|
+
} catch(e) { printError("Deploy failed: " + e.message); }
|
|
937
|
+
|
|
938
|
+
} else if (action === "ssh" && parts[1]) {
|
|
939
|
+
cmdSsh(parts.slice(1).join(" "));
|
|
940
|
+
|
|
941
|
+
} else if (action === "sync" && parts[1]) {
|
|
942
|
+
var sshFile = path.join(CONFIG_DIR, "ssh.json");
|
|
943
|
+
if (!fs.existsSync(sshFile)) { printError("No SSH connections. Use /ssh add first."); return; }
|
|
944
|
+
var connections = JSON.parse(fs.readFileSync(sshFile, "utf8"));
|
|
945
|
+
var conn = connections[parts[1]];
|
|
946
|
+
if (!conn) { printError("Unknown connection: " + parts[1]); return; }
|
|
947
|
+
var dest = parts[2] || "/root/" + path.basename(config.workdir);
|
|
948
|
+
var rsyncCmd = "rsync -avz --exclude node_modules --exclude .git";
|
|
949
|
+
if (conn.key) rsyncCmd += " -e 'ssh -i " + conn.key + " -p " + conn.port + "'";
|
|
950
|
+
rsyncCmd += " " + config.workdir + "/ " + conn.user + "@" + conn.host + ":" + dest + "/";
|
|
951
|
+
printInfo("Syncing to " + parts[1] + ":" + dest);
|
|
952
|
+
cmdShell(rsyncCmd);
|
|
953
|
+
|
|
954
|
+
} else if (action === "pm2" && parts[1] && parts[2]) {
|
|
955
|
+
cmdSsh(parts[1] + " pm2 restart " + parts[2]);
|
|
956
|
+
|
|
957
|
+
} else {
|
|
958
|
+
printError("Unknown deploy action. Use /deploy for help.");
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// ── Read/Write Files ──
|
|
963
|
+
function cmdRead(filePath) {
|
|
964
|
+
if (!filePath) { printError("Usage: /read <file>"); return; }
|
|
965
|
+
var full = path.resolve(config.workdir, filePath);
|
|
966
|
+
if (!fs.existsSync(full)) { printError("File not found: " + full); return; }
|
|
967
|
+
var content = fs.readFileSync(full, "utf8");
|
|
968
|
+
console.log("");
|
|
969
|
+
console.log(C.bold + "File: " + full + C.reset);
|
|
970
|
+
console.log(C.dim + "─".repeat(50) + C.reset);
|
|
971
|
+
var lines = content.split("\n");
|
|
972
|
+
for (var i = 0; i < lines.length; i++) {
|
|
973
|
+
console.log(C.dim + String(i + 1).padStart(4) + " │ " + C.reset + lines[i]);
|
|
974
|
+
}
|
|
975
|
+
console.log("");
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
function cmdWrite(args) {
|
|
979
|
+
if (!args) { printError("Usage: /write <file> <content> or /write <file> (then type content, end with EOF)"); return; }
|
|
980
|
+
var parts = args.split(/\s+/);
|
|
981
|
+
var filePath = parts[0];
|
|
982
|
+
var content = parts.slice(1).join(" ");
|
|
983
|
+
var full = path.resolve(config.workdir, filePath);
|
|
984
|
+
|
|
985
|
+
if (content) {
|
|
986
|
+
fs.writeFileSync(full, content);
|
|
987
|
+
printSuccess("Written: " + full + " (" + content.length + " bytes)");
|
|
988
|
+
} else {
|
|
989
|
+
printInfo("Type content (end with line 'EOF'):");
|
|
990
|
+
// This will be handled differently in interactive mode
|
|
991
|
+
printError("For multi-line write, use: /generate instead");
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
// ── Project Init ──
|
|
996
|
+
function cmdInit(args) {
|
|
997
|
+
var name = args || path.basename(config.workdir);
|
|
998
|
+
var projectDir = args ? path.join(config.workdir, args) : config.workdir;
|
|
999
|
+
|
|
1000
|
+
if (args && !fs.existsSync(projectDir)) {
|
|
1001
|
+
fs.mkdirSync(projectDir, { recursive: true });
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// Create project memory file
|
|
1005
|
+
var memFile = path.join(projectDir, "AGENT.md");
|
|
1006
|
+
if (!fs.existsSync(memFile)) {
|
|
1007
|
+
var agentContent = "# " + name + "\n\n" +
|
|
1008
|
+
"## Projekt\n" +
|
|
1009
|
+
"Beschreibung: \n\n" +
|
|
1010
|
+
"## Stack\n" +
|
|
1011
|
+
"- \n\n" +
|
|
1012
|
+
"## Regeln\n" +
|
|
1013
|
+
"- \n\n" +
|
|
1014
|
+
"## Status\n" +
|
|
1015
|
+
"Erstellt: " + new Date().toISOString().split("T")[0] + "\n";
|
|
1016
|
+
fs.writeFileSync(memFile, agentContent);
|
|
1017
|
+
printSuccess("AGENT.md erstellt in " + projectDir);
|
|
1018
|
+
} else {
|
|
1019
|
+
printInfo("AGENT.md existiert bereits.");
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// Git init if not already
|
|
1023
|
+
try {
|
|
1024
|
+
execSync("git rev-parse --git-dir", { cwd: projectDir, stdio: "pipe" });
|
|
1025
|
+
printInfo("Git repo already initialized.");
|
|
1026
|
+
} catch(e) {
|
|
1027
|
+
execSync("git init", { cwd: projectDir, stdio: "pipe" });
|
|
1028
|
+
printSuccess("Git repo initialized.");
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// Create .gitignore if missing
|
|
1032
|
+
var gitignore = path.join(projectDir, ".gitignore");
|
|
1033
|
+
if (!fs.existsSync(gitignore)) {
|
|
1034
|
+
fs.writeFileSync(gitignore, "node_modules/\n.env\n*.log\n.blun/\n");
|
|
1035
|
+
printSuccess(".gitignore erstellt.");
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
if (args) {
|
|
1039
|
+
config.workdir = projectDir;
|
|
1040
|
+
saveConfig(config);
|
|
1041
|
+
printSuccess("Workdir set to: " + projectDir);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
736
1045
|
// ── Main Loop ──
|
|
737
1046
|
async function main() {
|
|
738
1047
|
// Handle CLI args
|