blun-king-cli 6.0.0 → 6.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 +81 -47
- package/lib/workspace.js +110 -0
- package/package.json +1 -1
package/bin/blun.js
CHANGED
|
@@ -1,100 +1,134 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const path = require(
|
|
3
|
-
const pkg = require(
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const pkg = require("../package.json");
|
|
4
4
|
|
|
5
5
|
const args = process.argv.slice(2);
|
|
6
|
-
const command = args[0] ||
|
|
6
|
+
const command = args[0] || "chat";
|
|
7
7
|
|
|
8
8
|
async function main() {
|
|
9
|
-
const ui = require(
|
|
10
|
-
const auth = require(
|
|
11
|
-
const client = require(
|
|
9
|
+
const ui = require("../lib/ui");
|
|
10
|
+
const auth = require("../lib/auth");
|
|
11
|
+
const client = require("../lib/client");
|
|
12
|
+
const workspace = require("../lib/workspace");
|
|
12
13
|
|
|
13
14
|
switch (command) {
|
|
14
|
-
case
|
|
15
|
-
case
|
|
16
|
-
case
|
|
17
|
-
console.log(
|
|
15
|
+
case "version":
|
|
16
|
+
case "--version":
|
|
17
|
+
case "-v":
|
|
18
|
+
console.log("@blun/cli v" + pkg.version);
|
|
18
19
|
break;
|
|
19
20
|
|
|
20
|
-
case
|
|
21
|
-
case
|
|
22
|
-
case
|
|
21
|
+
case "help":
|
|
22
|
+
case "--help":
|
|
23
|
+
case "-h":
|
|
23
24
|
ui.renderBanner();
|
|
24
|
-
ui.renderBox(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
25
|
+
ui.renderBox("BLUN CLI Commands", [
|
|
26
|
+
"blun Start interactive chat (default)",
|
|
27
|
+
"blun chat Interactive chat mode",
|
|
28
|
+
"blun ask \"...\" One-shot question",
|
|
29
|
+
"blun login Device login flow",
|
|
30
|
+
"blun logout Remove stored credentials",
|
|
31
|
+
"blun auth --api-key KEY Authenticate with API key",
|
|
32
|
+
"blun status Show system health",
|
|
33
|
+
"blun init Initialize BLUN project in current dir",
|
|
34
|
+
"blun version Show version",
|
|
35
|
+
"blun help Show this help"
|
|
36
|
+
].join("\n"), "cyan");
|
|
35
37
|
break;
|
|
36
38
|
|
|
37
|
-
case
|
|
38
|
-
try { await auth.login(); } catch (e) { ui.renderResponse(e.message,
|
|
39
|
+
case "login":
|
|
40
|
+
try { await auth.login(); } catch (e) { ui.renderResponse(e.message, "error"); process.exit(1); }
|
|
39
41
|
break;
|
|
40
42
|
|
|
41
|
-
case
|
|
43
|
+
case "logout":
|
|
42
44
|
auth.logout();
|
|
43
45
|
break;
|
|
44
46
|
|
|
45
|
-
case
|
|
46
|
-
if (args[1] ===
|
|
47
|
-
try { await auth.loginWithApiKey(args[2]); } catch (e) { ui.renderResponse(e.message,
|
|
47
|
+
case "auth":
|
|
48
|
+
if (args[1] === "--api-key" && args[2]) {
|
|
49
|
+
try { await auth.loginWithApiKey(args[2]); } catch (e) { ui.renderResponse(e.message, "error"); process.exit(1); }
|
|
48
50
|
} else {
|
|
49
|
-
console.error(
|
|
51
|
+
console.error("Usage: blun auth --api-key <KEY>");
|
|
50
52
|
process.exit(1);
|
|
51
53
|
}
|
|
52
54
|
break;
|
|
53
55
|
|
|
54
|
-
case
|
|
55
|
-
const question = args.slice(1).join(
|
|
56
|
-
if (!question) { console.error(
|
|
56
|
+
case "ask": {
|
|
57
|
+
const question = args.slice(1).join(" ");
|
|
58
|
+
if (!question) { console.error("Usage: blun ask \"your question\""); process.exit(1); }
|
|
57
59
|
try {
|
|
58
60
|
auth.ensureAuth();
|
|
59
|
-
const spinner = ui.renderSpinner(
|
|
61
|
+
const spinner = ui.renderSpinner("Thinking...");
|
|
60
62
|
const res = await client.sendMessage(question);
|
|
61
63
|
ui.stopSpinner(true);
|
|
62
64
|
const text = res.data?.response || res.data?.text || res.data?.message || JSON.stringify(res.data);
|
|
63
|
-
ui.renderResponse(text,
|
|
65
|
+
ui.renderResponse(text, "agent");
|
|
64
66
|
} catch (e) {
|
|
65
|
-
ui.renderResponse(e.message,
|
|
67
|
+
ui.renderResponse(e.message, "error");
|
|
66
68
|
process.exit(1);
|
|
67
69
|
}
|
|
68
70
|
break;
|
|
69
71
|
}
|
|
70
72
|
|
|
71
|
-
case
|
|
73
|
+
case "status":
|
|
72
74
|
try {
|
|
73
|
-
const spinner = ui.renderSpinner(
|
|
75
|
+
const spinner = ui.renderSpinner("Checking health...");
|
|
74
76
|
const res = await client.getHealth();
|
|
75
77
|
ui.stopSpinner(true);
|
|
76
|
-
if (res.status === 200 && typeof res.data ===
|
|
78
|
+
if (res.status === 200 && typeof res.data === "object") {
|
|
77
79
|
const d = res.data;
|
|
78
80
|
ui.renderTable(
|
|
79
|
-
[
|
|
80
|
-
Object.entries(d).map(([k, v]) => [k, typeof v ===
|
|
81
|
+
["Metric", "Value"],
|
|
82
|
+
Object.entries(d).map(([k, v]) => [k, typeof v === "object" ? JSON.stringify(v) : String(v)])
|
|
81
83
|
);
|
|
82
84
|
} else {
|
|
83
|
-
ui.renderResponse(
|
|
85
|
+
ui.renderResponse("Health check returned: " + res.status, "error");
|
|
84
86
|
}
|
|
85
87
|
} catch (e) {
|
|
86
|
-
ui.renderResponse(
|
|
88
|
+
ui.renderResponse("Health check failed: " + e.message, "error");
|
|
87
89
|
process.exit(1);
|
|
88
90
|
}
|
|
89
91
|
break;
|
|
90
92
|
|
|
91
|
-
case
|
|
93
|
+
case "init":
|
|
94
|
+
workspace.initProject(process.cwd());
|
|
95
|
+
break;
|
|
96
|
+
|
|
97
|
+
case "chat":
|
|
92
98
|
default: {
|
|
93
|
-
|
|
99
|
+
// Determine initial folder: path arg or cwd
|
|
100
|
+
let initialFolder = process.cwd();
|
|
101
|
+
if (command !== "chat" && command !== undefined) {
|
|
102
|
+
// default case — command might be a path
|
|
103
|
+
try {
|
|
104
|
+
const fs = require("fs");
|
|
105
|
+
const resolved = path.resolve(command);
|
|
106
|
+
if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {
|
|
107
|
+
initialFolder = resolved;
|
|
108
|
+
}
|
|
109
|
+
} catch {}
|
|
110
|
+
}
|
|
111
|
+
// Also check args[1] for "blun chat /path"
|
|
112
|
+
if (args[1]) {
|
|
113
|
+
try {
|
|
114
|
+
const fs = require("fs");
|
|
115
|
+
const resolved = path.resolve(args[1]);
|
|
116
|
+
if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {
|
|
117
|
+
initialFolder = resolved;
|
|
118
|
+
}
|
|
119
|
+
} catch {}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Workspace trust flow
|
|
123
|
+
const trustedFolder = await workspace.selectFolder(initialFolder);
|
|
124
|
+
process.chdir(trustedFolder);
|
|
125
|
+
workspace.loadProjectConfig(trustedFolder);
|
|
126
|
+
|
|
127
|
+
const { startChat } = require("../lib/chat");
|
|
94
128
|
await startChat();
|
|
95
129
|
break;
|
|
96
130
|
}
|
|
97
131
|
}
|
|
98
132
|
}
|
|
99
133
|
|
|
100
|
-
main().catch((e) => { console.error(
|
|
134
|
+
main().catch((e) => { console.error("Fatal:", e.message); process.exit(1); });
|
package/lib/workspace.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
const readline = require("readline");
|
|
5
|
+
const chalk = require("chalk");
|
|
6
|
+
|
|
7
|
+
const TRUST_FILE = path.join(os.homedir(), ".blun", "trusted-folders.json");
|
|
8
|
+
|
|
9
|
+
function loadTrusted() {
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(fs.readFileSync(TRUST_FILE, "utf8"));
|
|
12
|
+
} catch {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function saveTrusted(folders) {
|
|
18
|
+
const dir = path.dirname(TRUST_FILE);
|
|
19
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
20
|
+
fs.writeFileSync(TRUST_FILE, JSON.stringify(folders, null, 2));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isTrusted(folder) {
|
|
24
|
+
return loadTrusted().includes(path.resolve(folder));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function trustFolder(folder) {
|
|
28
|
+
const abs = path.resolve(folder);
|
|
29
|
+
const trusted = loadTrusted();
|
|
30
|
+
if (!trusted.includes(abs)) {
|
|
31
|
+
trusted.push(abs);
|
|
32
|
+
saveTrusted(trusted);
|
|
33
|
+
}
|
|
34
|
+
return abs;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function prompt(question) {
|
|
38
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
39
|
+
return new Promise((resolve) => rl.question(question, (a) => { rl.close(); resolve(a.trim()); }));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function selectFolder(initialFolder) {
|
|
43
|
+
let folder = path.resolve(initialFolder);
|
|
44
|
+
|
|
45
|
+
while (true) {
|
|
46
|
+
console.log();
|
|
47
|
+
console.log(chalk.bold.cyan(" Workspace: ") + chalk.white(folder));
|
|
48
|
+
console.log();
|
|
49
|
+
|
|
50
|
+
if (isTrusted(folder)) {
|
|
51
|
+
console.log(chalk.green(" ✓ Working in " + folder + " (trusted)"));
|
|
52
|
+
return folder;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log(chalk.yellow(" Do you trust the files in this folder?"));
|
|
56
|
+
console.log();
|
|
57
|
+
console.log(" " + chalk.bold("1)") + " Yes, I trust this folder");
|
|
58
|
+
console.log(" " + chalk.bold("2)") + " No, exit");
|
|
59
|
+
console.log(" " + chalk.bold("3)") + " Choose a different folder");
|
|
60
|
+
console.log();
|
|
61
|
+
|
|
62
|
+
const answer = await prompt(chalk.cyan(" Your choice (1/2/3): "));
|
|
63
|
+
|
|
64
|
+
if (answer === "1") {
|
|
65
|
+
trustFolder(folder);
|
|
66
|
+
console.log(chalk.green("\n ✓ Folder trusted and saved.\n"));
|
|
67
|
+
return folder;
|
|
68
|
+
} else if (answer === "2") {
|
|
69
|
+
console.log(chalk.red("\n Exiting.\n"));
|
|
70
|
+
process.exit(0);
|
|
71
|
+
} else if (answer === "3") {
|
|
72
|
+
const newPath = await prompt(chalk.cyan(" Enter folder path: "));
|
|
73
|
+
if (!newPath) continue;
|
|
74
|
+
const resolved = path.resolve(newPath);
|
|
75
|
+
if (!fs.existsSync(resolved)) {
|
|
76
|
+
console.log(chalk.red(" Folder does not exist: " + resolved));
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
folder = resolved;
|
|
80
|
+
} else {
|
|
81
|
+
console.log(chalk.red(" Invalid choice."));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function loadProjectConfig(folder) {
|
|
87
|
+
const mdPath = path.join(folder, "BLUN.md");
|
|
88
|
+
if (fs.existsSync(mdPath)) {
|
|
89
|
+
const content = fs.readFileSync(mdPath, "utf8");
|
|
90
|
+
console.log(chalk.green(" Found project config: BLUN.md"));
|
|
91
|
+
return content;
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function initProject(folder) {
|
|
97
|
+
const dir = path.resolve(folder);
|
|
98
|
+
const blunDir = path.join(dir, ".blun");
|
|
99
|
+
if (!fs.existsSync(blunDir)) fs.mkdirSync(blunDir, { recursive: true });
|
|
100
|
+
|
|
101
|
+
const mdPath = path.join(dir, "BLUN.md");
|
|
102
|
+
if (!fs.existsSync(mdPath)) {
|
|
103
|
+
fs.writeFileSync(mdPath, `# Project Name\n\nYour project name here.\n\n## Description\n\nBrief description of the project.\n\n## Rules\n\n- Add project-specific rules here\n\n## Context\n\n- Add relevant context for the AI assistant\n`);
|
|
104
|
+
}
|
|
105
|
+
console.log(chalk.green(" ✓ Initialized BLUN project in " + dir));
|
|
106
|
+
console.log(chalk.dim(" Created: .blun/ directory"));
|
|
107
|
+
console.log(chalk.dim(" Created: BLUN.md template"));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
module.exports = { trustFolder, isTrusted, selectFolder, initProject, loadProjectConfig };
|