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 CHANGED
@@ -1,100 +1,134 @@
1
1
  #!/usr/bin/env node
2
- const path = require('path');
3
- const pkg = require('../package.json');
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] || 'chat';
6
+ const command = args[0] || "chat";
7
7
 
8
8
  async function main() {
9
- const ui = require('../lib/ui');
10
- const auth = require('../lib/auth');
11
- const client = require('../lib/client');
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 'version':
15
- case '--version':
16
- case '-v':
17
- console.log('@blun/cli v' + pkg.version);
15
+ case "version":
16
+ case "--version":
17
+ case "-v":
18
+ console.log("@blun/cli v" + pkg.version);
18
19
  break;
19
20
 
20
- case 'help':
21
- case '--help':
22
- case '-h':
21
+ case "help":
22
+ case "--help":
23
+ case "-h":
23
24
  ui.renderBanner();
24
- ui.renderBox('BLUN CLI Commands', [
25
- 'blun Start interactive chat (default)',
26
- 'blun chat Interactive chat mode',
27
- 'blun ask "..." One-shot question',
28
- 'blun login Device login flow',
29
- 'blun logout Remove stored credentials',
30
- 'blun auth --api-key KEY Authenticate with API key',
31
- 'blun status Show system health',
32
- 'blun version Show version',
33
- 'blun help Show this help'
34
- ].join('\n'), 'cyan');
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 'login':
38
- try { await auth.login(); } catch (e) { ui.renderResponse(e.message, 'error'); process.exit(1); }
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 'logout':
43
+ case "logout":
42
44
  auth.logout();
43
45
  break;
44
46
 
45
- case 'auth':
46
- if (args[1] === '--api-key' && args[2]) {
47
- try { await auth.loginWithApiKey(args[2]); } catch (e) { ui.renderResponse(e.message, 'error'); process.exit(1); }
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('Usage: blun auth --api-key <KEY>');
51
+ console.error("Usage: blun auth --api-key <KEY>");
50
52
  process.exit(1);
51
53
  }
52
54
  break;
53
55
 
54
- case 'ask': {
55
- const question = args.slice(1).join(' ');
56
- if (!question) { console.error('Usage: blun ask "your question"'); process.exit(1); }
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('Thinking...');
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, 'agent');
65
+ ui.renderResponse(text, "agent");
64
66
  } catch (e) {
65
- ui.renderResponse(e.message, 'error');
67
+ ui.renderResponse(e.message, "error");
66
68
  process.exit(1);
67
69
  }
68
70
  break;
69
71
  }
70
72
 
71
- case 'status':
73
+ case "status":
72
74
  try {
73
- const spinner = ui.renderSpinner('Checking health...');
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 === 'object') {
78
+ if (res.status === 200 && typeof res.data === "object") {
77
79
  const d = res.data;
78
80
  ui.renderTable(
79
- ['Metric', 'Value'],
80
- Object.entries(d).map(([k, v]) => [k, typeof v === 'object' ? JSON.stringify(v) : String(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('Health check returned: ' + res.status, 'error');
85
+ ui.renderResponse("Health check returned: " + res.status, "error");
84
86
  }
85
87
  } catch (e) {
86
- ui.renderResponse('Health check failed: ' + e.message, 'error');
88
+ ui.renderResponse("Health check failed: " + e.message, "error");
87
89
  process.exit(1);
88
90
  }
89
91
  break;
90
92
 
91
- case 'chat':
93
+ case "init":
94
+ workspace.initProject(process.cwd());
95
+ break;
96
+
97
+ case "chat":
92
98
  default: {
93
- const { startChat } = require('../lib/chat');
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('Fatal:', e.message); process.exit(1); });
134
+ main().catch((e) => { console.error("Fatal:", e.message); process.exit(1); });
@@ -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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "6.0.0",
3
+ "version": "6.1.0",
4
4
  "description": "BLUN AI Assistant - Command Line Interface",
5
5
  "bin": { "blun": "./bin/blun.js" },
6
6
  "files": ["bin/", "lib/", "README.md"],