@imisbahk/hive 0.1.1 → 0.1.3
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/Aborted +0 -0
- package/FEATURES.md +10 -2
- package/bun.lock +554 -0
- package/dist/cli/commands/chat.d.ts.map +1 -1
- package/dist/cli/commands/chat.js +28 -8
- package/dist/cli/commands/chat.js.map +1 -1
- package/dist/cli/commands/config.d.ts +2 -0
- package/dist/cli/commands/config.d.ts.map +1 -1
- package/dist/cli/commands/config.js +207 -1
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +8 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +503 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/status.js +1 -1
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/theme.d.ts +22 -0
- package/dist/cli/theme.d.ts.map +1 -0
- package/dist/cli/theme.js +63 -0
- package/dist/cli/theme.js.map +1 -0
- package/dist/cli/ui.d.ts.map +1 -1
- package/dist/cli/ui.js +12 -7
- package/dist/cli/ui.js.map +1 -1
- package/package.json +2 -2
- package/releases/v1/v0.1/RELEASE-NOTES.md +55 -0
- package/src/cli/commands/chat.ts +31 -7
- package/src/cli/commands/config.ts +266 -1
- package/src/cli/commands/doctor.ts +655 -0
- package/src/cli/commands/status.ts +1 -1
- package/src/cli/index.ts +2 -0
- package/src/cli/theme.ts +88 -0
- package/src/cli/ui.ts +14 -7
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { closeHiveDatabase, getMetaValue, openHiveDatabase, } from "../storage/db.js";
|
|
3
|
+
export const DEFAULT_THEME_NAME = "amber";
|
|
4
|
+
export const DEFAULT_THEME_HEX = "#FFA500";
|
|
5
|
+
export const BUILT_IN_THEMES = {
|
|
6
|
+
amber: "#FFA500",
|
|
7
|
+
cyan: "#00BCD4",
|
|
8
|
+
rose: "#FF4081",
|
|
9
|
+
slate: "#90A4AE",
|
|
10
|
+
green: "#00E676",
|
|
11
|
+
};
|
|
12
|
+
export const HEX_COLOR_PATTERN = /^#[0-9A-Fa-f]{6}$/;
|
|
13
|
+
export function applyTheme(hex) {
|
|
14
|
+
const normalizedHex = normalizeHex(hex);
|
|
15
|
+
return chalk.hex(normalizedHex);
|
|
16
|
+
}
|
|
17
|
+
export function getTheme() {
|
|
18
|
+
let db = null;
|
|
19
|
+
try {
|
|
20
|
+
db = openHiveDatabase();
|
|
21
|
+
const storedName = getMetaValue(db, "theme");
|
|
22
|
+
const storedHex = getMetaValue(db, "theme_hex");
|
|
23
|
+
return resolveTheme(storedName, storedHex);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return makeTheme(DEFAULT_THEME_NAME, DEFAULT_THEME_HEX);
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
if (db) {
|
|
30
|
+
closeHiveDatabase(db);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function isValidHexColor(value) {
|
|
35
|
+
return HEX_COLOR_PATTERN.test(value);
|
|
36
|
+
}
|
|
37
|
+
function resolveTheme(storedName, storedHex) {
|
|
38
|
+
if (isBuiltInTheme(storedName)) {
|
|
39
|
+
return makeTheme(storedName, BUILT_IN_THEMES[storedName]);
|
|
40
|
+
}
|
|
41
|
+
if (storedName === "custom" && storedHex && isValidHexColor(storedHex)) {
|
|
42
|
+
return makeTheme("custom", storedHex);
|
|
43
|
+
}
|
|
44
|
+
return makeTheme(DEFAULT_THEME_NAME, DEFAULT_THEME_HEX);
|
|
45
|
+
}
|
|
46
|
+
function makeTheme(name, hex) {
|
|
47
|
+
const normalizedHex = normalizeHex(hex);
|
|
48
|
+
return {
|
|
49
|
+
name,
|
|
50
|
+
hex: normalizedHex,
|
|
51
|
+
accent: applyTheme(normalizedHex),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function normalizeHex(value) {
|
|
55
|
+
if (!isValidHexColor(value)) {
|
|
56
|
+
return DEFAULT_THEME_HEX;
|
|
57
|
+
}
|
|
58
|
+
return value.toUpperCase();
|
|
59
|
+
}
|
|
60
|
+
function isBuiltInTheme(value) {
|
|
61
|
+
return value !== null && value in BUILT_IN_THEMES;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=theme.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme.js","sourceRoot":"","sources":["../../src/cli/theme.ts"],"names":[],"mappings":"AAAA,OAAO,KAA6B,MAAM,OAAO,CAAC;AAElD,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAE1B,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAO,CAAC;AAC1C,MAAM,CAAC,MAAM,iBAAiB,GAAG,SAAS,CAAC;AAE3C,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,KAAK,EAAE,SAAS;IAChB,IAAI,EAAE,SAAS;IACf,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,SAAS;IAChB,KAAK,EAAE,SAAS;CACR,CAAC;AAEX,MAAM,CAAC,MAAM,iBAAiB,GAAG,mBAAmB,CAAC;AAWrD,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,MAAM,aAAa,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IACxC,OAAO,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,QAAQ;IACtB,IAAI,EAAE,GAA+C,IAAI,CAAC;IAE1D,IAAI,CAAC;QACH,EAAE,GAAG,gBAAgB,EAAE,CAAC;QACxB,MAAM,UAAU,GAAG,YAAY,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QAC7C,MAAM,SAAS,GAAG,YAAY,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC;QAChD,OAAO,YAAY,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC,kBAAkB,EAAE,iBAAiB,CAAC,CAAC;IAC1D,CAAC;YAAS,CAAC;QACT,IAAI,EAAE,EAAE,CAAC;YACP,iBAAiB,CAAC,EAAE,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,OAAO,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACvC,CAAC;AAED,SAAS,YAAY,CAAC,UAAyB,EAAE,SAAwB;IACvE,IAAI,cAAc,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,OAAO,SAAS,CAAC,UAAU,EAAE,eAAe,CAAC,UAAU,CAAC,CAAC,CAAC;IAC5D,CAAC;IAED,IAAI,UAAU,KAAK,QAAQ,IAAI,SAAS,IAAI,eAAe,CAAC,SAAS,CAAC,EAAE,CAAC;QACvE,OAAO,SAAS,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IACxC,CAAC;IAED,OAAO,SAAS,CAAC,kBAAkB,EAAE,iBAAiB,CAAC,CAAC;AAC1D,CAAC;AAED,SAAS,SAAS,CAAC,IAAe,EAAE,GAAW;IAC7C,MAAM,aAAa,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IACxC,OAAO;QACL,IAAI;QACJ,GAAG,EAAE,aAAa;QAClB,MAAM,EAAE,UAAU,CAAC,aAAa,CAAC;KAClC,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,KAAa;IACjC,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,iBAAiB,CAAC;IAC3B,CAAC;IAED,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC;AAC7B,CAAC;AAED,SAAS,cAAc,CAAC,KAAoB;IAC1C,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,IAAI,eAAe,CAAC;AACpD,CAAC"}
|
package/dist/cli/ui.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../src/cli/ui.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../src/cli/ui.ts"],"names":[],"mappings":"AAsBA,wBAAgB,gBAAgB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAmBzD;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAGnD;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEjD;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAGhD;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEhD;AAED,wBAAgB,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CASnD"}
|
package/dist/cli/ui.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
import chalk from "chalk";
|
|
4
|
+
import { getTheme } from "./theme.js";
|
|
4
5
|
const WORDMARK_LINES = [
|
|
5
6
|
" ██╗ ██╗██╗██╗ ██╗███████╗",
|
|
6
7
|
" ██║ ██║██║██║ ██║██╔════╝",
|
|
@@ -16,8 +17,9 @@ let cachedVersion = null;
|
|
|
16
17
|
export function renderHiveHeader(pageTitle) {
|
|
17
18
|
const terminalWidth = getTerminalWidth();
|
|
18
19
|
const separator = "─".repeat(getSeparatorWidth(terminalWidth));
|
|
20
|
+
const accent = getTheme().accent;
|
|
19
21
|
for (const line of WORDMARK_LINES) {
|
|
20
|
-
console.log(
|
|
22
|
+
console.log(accent.bold(centerText(line, terminalWidth)));
|
|
21
23
|
}
|
|
22
24
|
console.log("");
|
|
23
25
|
console.log(chalk.dim(centerText(`v${getCliVersion()}`, terminalWidth)));
|
|
@@ -25,27 +27,30 @@ export function renderHiveHeader(pageTitle) {
|
|
|
25
27
|
const commandCentreTitle = normalizedTitle
|
|
26
28
|
? `${COMMAND_CENTRE_LABEL} · ${normalizedTitle}`
|
|
27
29
|
: COMMAND_CENTRE_LABEL;
|
|
28
|
-
console.log(
|
|
29
|
-
console.log(
|
|
30
|
+
console.log(accent(centerText(commandCentreTitle, terminalWidth)));
|
|
31
|
+
console.log(accent(centerText(separator, terminalWidth)));
|
|
30
32
|
}
|
|
31
33
|
export function renderSuccess(message) {
|
|
32
|
-
|
|
34
|
+
const accent = getTheme().accent;
|
|
35
|
+
console.log(`${accent("✓")} ${message}`);
|
|
33
36
|
}
|
|
34
37
|
export function renderError(message) {
|
|
35
38
|
console.error(chalk.red(message));
|
|
36
39
|
}
|
|
37
40
|
export function renderStep(message) {
|
|
38
|
-
|
|
41
|
+
const accent = getTheme().accent;
|
|
42
|
+
console.log(`${accent("›")} ${message}`);
|
|
39
43
|
}
|
|
40
44
|
export function renderInfo(message) {
|
|
41
45
|
console.log(chalk.dim(message));
|
|
42
46
|
}
|
|
43
47
|
export function renderSeparator(text) {
|
|
48
|
+
const accent = getTheme().accent;
|
|
44
49
|
if (text) {
|
|
45
|
-
console.log(
|
|
50
|
+
console.log(accent(text));
|
|
46
51
|
return;
|
|
47
52
|
}
|
|
48
|
-
console.log(
|
|
53
|
+
console.log(accent("─".repeat(getSeparatorWidth(getTerminalWidth()))));
|
|
49
54
|
}
|
|
50
55
|
function getCliVersion() {
|
|
51
56
|
if (cachedVersion) {
|
package/dist/cli/ui.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/cli/ui.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,OAAO,MAAM,cAAc,CAAC;AAEnC,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,cAAc,GAAG;IACrB,gCAAgC;IAChC,gCAAgC;IAChC,gCAAgC;IAChC,gCAAgC;IAChC,gCAAgC;IAChC,gCAAgC;CACxB,CAAC;AAEX,MAAM,oBAAoB,GAAG,gBAAgB,CAAC;AAC9C,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAC/B,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAE/B,IAAI,aAAa,GAAkB,IAAI,CAAC;AAExC,MAAM,UAAU,gBAAgB,CAAC,SAAkB;IACjD,MAAM,aAAa,GAAG,gBAAgB,EAAE,CAAC;IACzC,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,iBAAiB,CAAC,aAAa,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/cli/ui.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imisbahk/hive",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Your agent. Always running. Always learning. Always working.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@anthropic-ai/sdk": "^0.36.3",
|
|
27
27
|
"@imisbahk/genie": "^1.0.0",
|
|
28
|
-
"@imisbahk/hive": "^0.1.
|
|
28
|
+
"@imisbahk/hive": "^0.1.1",
|
|
29
29
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
30
30
|
"better-sqlite3": "^12.6.2",
|
|
31
31
|
"chalk": "^5.3.0",
|
|
@@ -44,3 +44,58 @@ hive init
|
|
|
44
44
|
```bash
|
|
45
45
|
npm install -g @imisbahk/hive@0.1.1
|
|
46
46
|
```
|
|
47
|
+
|
|
48
|
+
## 🐝 v0.1.2 — Themes + Live Accent Preview
|
|
49
|
+
|
|
50
|
+
### What's in v0.1.2
|
|
51
|
+
|
|
52
|
+
- New `hive config theme` command to set the CLI accent theme.
|
|
53
|
+
- Built-in theme options:
|
|
54
|
+
- `amber` (`#FFA500`) default beehive accent
|
|
55
|
+
- `cyan` (`#00BCD4`)
|
|
56
|
+
- `rose` (`#FF4081`)
|
|
57
|
+
- `slate` (`#90A4AE`)
|
|
58
|
+
- `green` (`#00E676`)
|
|
59
|
+
- `custom` (user-provided hex)
|
|
60
|
+
- Live theme preview: moving through the picker updates the UI accent in real time before selection.
|
|
61
|
+
- Theme persistence in local DB metadata (`~/.hive/hive.db`):
|
|
62
|
+
- `theme`
|
|
63
|
+
- `theme_hex`
|
|
64
|
+
- Accent color is now consistent across the command centre UI:
|
|
65
|
+
- ASCII HIVE wordmark
|
|
66
|
+
- separators
|
|
67
|
+
- prompt symbol (`›`)
|
|
68
|
+
- agent name prefix in chat
|
|
69
|
+
- success indicator (`✓`)
|
|
70
|
+
- step indicator (`›`)
|
|
71
|
+
- New in-chat shortcut: `/hive config theme`
|
|
72
|
+
|
|
73
|
+
### Upgrade
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npm install -g @imisbahk/hive@0.1.2
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 🐝 v0.2.0 — Doctor (Health Checks)
|
|
80
|
+
|
|
81
|
+
### What's in v0.2.0
|
|
82
|
+
|
|
83
|
+
- New `hive doctor` command runs a full diagnostic pass across local Hive setup.
|
|
84
|
+
- Live, sequential output (no spinner) so checks feel immediate as they complete.
|
|
85
|
+
- Checks include:
|
|
86
|
+
- Agent initialized (DB record exists)
|
|
87
|
+
- Database readable + integrity check + size warning when large
|
|
88
|
+
- API key present in OS keychain
|
|
89
|
+
- Provider reachability (5s timeout)
|
|
90
|
+
- Prompts folder present with files
|
|
91
|
+
- Theme selection from local DB metadata
|
|
92
|
+
- Node version warning if < v20
|
|
93
|
+
- Playwright + Chromium installed
|
|
94
|
+
- Ollama running when provider is `ollama`
|
|
95
|
+
- Basic DB stats (messages, conversations, episodes when table exists)
|
|
96
|
+
|
|
97
|
+
### Upgrade
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npm install -g @imisbahk/hive@0.2.0
|
|
101
|
+
```
|
package/src/cli/commands/chat.ts
CHANGED
|
@@ -23,8 +23,10 @@ import {
|
|
|
23
23
|
runConfigModelCommandWithOptions,
|
|
24
24
|
runConfigProviderCommandWithOptions,
|
|
25
25
|
runConfigShowCommandWithOptions,
|
|
26
|
+
runConfigThemeCommandWithOptions,
|
|
26
27
|
} from "./config.js";
|
|
27
28
|
import { runStatusCommandWithOptions } from "./status.js";
|
|
29
|
+
import { getTheme } from "../theme.js";
|
|
28
30
|
|
|
29
31
|
interface ChatCommandOptions {
|
|
30
32
|
message?: string;
|
|
@@ -53,7 +55,8 @@ interface CommandSuggestion {
|
|
|
53
55
|
|
|
54
56
|
type HiveShortcutResult = "not-handled" | "handled" | "config-updated";
|
|
55
57
|
|
|
56
|
-
const
|
|
58
|
+
const PROMPT_SYMBOL = "›";
|
|
59
|
+
const USER_PROMPT = `you${PROMPT_SYMBOL} `;
|
|
57
60
|
const HIVE_SHORTCUT_PREFIX = "/hive";
|
|
58
61
|
const MAX_COMMAND_SUGGESTIONS = 8;
|
|
59
62
|
const COMMAND_LABEL_WIDTH = 24;
|
|
@@ -71,6 +74,7 @@ const COMMAND_HELP_TEXT = [
|
|
|
71
74
|
" /hive config provider interactive provider setup",
|
|
72
75
|
" /hive config model interactive model setup",
|
|
73
76
|
" /hive config key interactive key setup",
|
|
77
|
+
" /hive config theme interactive theme setup",
|
|
74
78
|
" /exit quit",
|
|
75
79
|
].join("\n");
|
|
76
80
|
const HIVE_SHORTCUT_HELP_TEXT = [
|
|
@@ -83,6 +87,7 @@ const HIVE_SHORTCUT_HELP_TEXT = [
|
|
|
83
87
|
" /hive config provider",
|
|
84
88
|
" /hive config model",
|
|
85
89
|
" /hive config key",
|
|
90
|
+
" /hive config theme",
|
|
86
91
|
"",
|
|
87
92
|
"Safety commands still run from shell:",
|
|
88
93
|
" /hive init",
|
|
@@ -155,6 +160,11 @@ const COMMAND_SUGGESTIONS: CommandSuggestion[] = [
|
|
|
155
160
|
insertText: "/hive config key",
|
|
156
161
|
description: "interactive key setup",
|
|
157
162
|
},
|
|
163
|
+
{
|
|
164
|
+
label: "/hive config theme",
|
|
165
|
+
insertText: "/hive config theme",
|
|
166
|
+
description: "interactive theme setup",
|
|
167
|
+
},
|
|
158
168
|
{
|
|
159
169
|
label: "/hive nuke",
|
|
160
170
|
insertText: "/hive nuke",
|
|
@@ -327,7 +337,7 @@ async function streamReply(
|
|
|
327
337
|
options: RunChatOptions,
|
|
328
338
|
agentName: string,
|
|
329
339
|
): Promise<string> {
|
|
330
|
-
process.stdout.write(
|
|
340
|
+
process.stdout.write(getTheme().accent(`${agentName}${PROMPT_SYMBOL} `));
|
|
331
341
|
|
|
332
342
|
let activeConversationId = conversationId;
|
|
333
343
|
|
|
@@ -457,7 +467,7 @@ async function runPreviewSession(options: ChatCommandOptions): Promise<void> {
|
|
|
457
467
|
|
|
458
468
|
async function streamPreviewReply(prompt: string, agentName: string): Promise<void> {
|
|
459
469
|
const response = `preview mode: received "${prompt}"`;
|
|
460
|
-
process.stdout.write(
|
|
470
|
+
process.stdout.write(getTheme().accent(`${agentName}${PROMPT_SYMBOL} `));
|
|
461
471
|
process.stdout.write(response);
|
|
462
472
|
process.stdout.write("\n");
|
|
463
473
|
renderSeparator(EXCHANGE_SEPARATOR);
|
|
@@ -563,6 +573,17 @@ async function handleHiveShortcut(
|
|
|
563
573
|
return "handled";
|
|
564
574
|
}
|
|
565
575
|
|
|
576
|
+
if (subcommand === "config theme") {
|
|
577
|
+
if (!options.allowInteractiveConfig) {
|
|
578
|
+
renderInfo("Interactive config commands are unavailable here.");
|
|
579
|
+
return "handled";
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
await runConfigThemeCommandWithOptions({ showHeader: false });
|
|
583
|
+
restoreChatInputAfterInteractiveCommand();
|
|
584
|
+
return "handled";
|
|
585
|
+
}
|
|
586
|
+
|
|
566
587
|
if (
|
|
567
588
|
subcommand === "init" ||
|
|
568
589
|
subcommand === "nuke"
|
|
@@ -598,6 +619,9 @@ function getCommandSuggestions(input: string): CommandSuggestion[] {
|
|
|
598
619
|
}
|
|
599
620
|
|
|
600
621
|
async function readPromptWithSuggestions(): Promise<string> {
|
|
622
|
+
const accent = getTheme().accent;
|
|
623
|
+
const promptPrefix = accent(USER_PROMPT);
|
|
624
|
+
|
|
601
625
|
if (!stdin.isTTY || !stdout.isTTY) {
|
|
602
626
|
const rl = createInterface({
|
|
603
627
|
input: stdin,
|
|
@@ -606,7 +630,7 @@ async function readPromptWithSuggestions(): Promise<string> {
|
|
|
606
630
|
});
|
|
607
631
|
|
|
608
632
|
try {
|
|
609
|
-
return (await rl.question(
|
|
633
|
+
return (await rl.question(promptPrefix)).trim();
|
|
610
634
|
} finally {
|
|
611
635
|
rl.close();
|
|
612
636
|
}
|
|
@@ -654,7 +678,7 @@ async function readPromptWithSuggestions(): Promise<string> {
|
|
|
654
678
|
|
|
655
679
|
readline.cursorTo(stdout, 0);
|
|
656
680
|
readline.clearLine(stdout, 0);
|
|
657
|
-
stdout.write(
|
|
681
|
+
stdout.write(`${promptPrefix}${buffer}`);
|
|
658
682
|
|
|
659
683
|
for (let index = 0; index < renderedSuggestionRows; index += 1) {
|
|
660
684
|
readline.moveCursor(stdout, 0, 1);
|
|
@@ -700,7 +724,7 @@ async function readPromptWithSuggestions(): Promise<string> {
|
|
|
700
724
|
|
|
701
725
|
readline.cursorTo(stdout, 0);
|
|
702
726
|
readline.clearLine(stdout, 0);
|
|
703
|
-
stdout.write(
|
|
727
|
+
stdout.write(`${promptPrefix}${buffer}`);
|
|
704
728
|
|
|
705
729
|
const rowsToRender = Math.max(renderedSuggestionRows, visibleSuggestions.length);
|
|
706
730
|
for (let index = 0; index < rowsToRender; index += 1) {
|
|
@@ -719,7 +743,7 @@ async function readPromptWithSuggestions(): Promise<string> {
|
|
|
719
743
|
const text = `${marker} ${label} ${suggestion.description}`;
|
|
720
744
|
|
|
721
745
|
if (absoluteIndex === selectedSuggestionIndex) {
|
|
722
|
-
stdout.write(
|
|
746
|
+
stdout.write(accent(text));
|
|
723
747
|
} else {
|
|
724
748
|
stdout.write(chalk.dim(text));
|
|
725
749
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
|
+
import * as readline from "node:readline";
|
|
2
3
|
|
|
3
4
|
import { Command } from "commander";
|
|
4
5
|
import inquirer from "inquirer";
|
|
@@ -15,6 +16,14 @@ import {
|
|
|
15
16
|
renderStep,
|
|
16
17
|
renderSuccess,
|
|
17
18
|
} from "../ui.js";
|
|
19
|
+
import {
|
|
20
|
+
BUILT_IN_THEMES,
|
|
21
|
+
DEFAULT_THEME_HEX,
|
|
22
|
+
applyTheme,
|
|
23
|
+
getTheme,
|
|
24
|
+
isValidHexColor,
|
|
25
|
+
type ThemeName,
|
|
26
|
+
} from "../theme.js";
|
|
18
27
|
import {
|
|
19
28
|
closeHiveDatabase,
|
|
20
29
|
getPrimaryAgent,
|
|
@@ -25,6 +34,21 @@ import {
|
|
|
25
34
|
} from "../../storage/db.js";
|
|
26
35
|
|
|
27
36
|
const KEYCHAIN_SERVICE = "hive";
|
|
37
|
+
const THEME_LABEL_WIDTH = 8;
|
|
38
|
+
const ENTER_ALT_SCREEN = "\u001B[?1049h";
|
|
39
|
+
const EXIT_ALT_SCREEN = "\u001B[?1049l";
|
|
40
|
+
const CLEAR_SCREEN = "\u001B[H\u001B[2J";
|
|
41
|
+
const THEME_SELECTOR_TITLE = "COMMAND CENTRE · CONFIG · THEME";
|
|
42
|
+
const THEME_SELECTOR_MAX_SEPARATOR_WIDTH = 72;
|
|
43
|
+
const THEME_SELECTOR_MIN_SEPARATOR_WIDTH = 24;
|
|
44
|
+
const THEME_WORDMARK_LINES = [
|
|
45
|
+
" ██╗ ██╗██╗██╗ ██╗███████╗",
|
|
46
|
+
" ██║ ██║██║██║ ██║██╔════╝",
|
|
47
|
+
" ███████║██║██║ ██║█████╗ ",
|
|
48
|
+
" ██╔══██║██║╚██╗ ██╔╝██╔══╝ ",
|
|
49
|
+
" ██║ ██║██║ ╚████╔╝ ███████╗",
|
|
50
|
+
" ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚══════╝",
|
|
51
|
+
] as const;
|
|
28
52
|
|
|
29
53
|
interface ConfigShowRenderOptions {
|
|
30
54
|
showHeader?: boolean;
|
|
@@ -34,10 +58,16 @@ interface ConfigInteractiveRenderOptions {
|
|
|
34
58
|
showHeader?: boolean;
|
|
35
59
|
}
|
|
36
60
|
|
|
61
|
+
interface ThemeOption {
|
|
62
|
+
name: ThemeName;
|
|
63
|
+
hex: string;
|
|
64
|
+
description?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
37
67
|
export function registerConfigCommand(program: Command): void {
|
|
38
68
|
const configCommand = program
|
|
39
69
|
.command("config")
|
|
40
|
-
.description("Update provider, model, or API keys without re-running init");
|
|
70
|
+
.description("Update provider, model, theme, or API keys without re-running init");
|
|
41
71
|
|
|
42
72
|
configCommand
|
|
43
73
|
.command("provider")
|
|
@@ -67,6 +97,13 @@ export function registerConfigCommand(program: Command): void {
|
|
|
67
97
|
await runConfigShowCommand();
|
|
68
98
|
});
|
|
69
99
|
|
|
100
|
+
configCommand
|
|
101
|
+
.command("theme")
|
|
102
|
+
.description("Change CLI accent theme")
|
|
103
|
+
.action(async () => {
|
|
104
|
+
await runConfigThemeCommand();
|
|
105
|
+
});
|
|
106
|
+
|
|
70
107
|
configCommand.action(() => {
|
|
71
108
|
renderHiveHeader("Config");
|
|
72
109
|
configCommand.outputHelp();
|
|
@@ -262,6 +299,10 @@ export async function runConfigShowCommand(): Promise<void> {
|
|
|
262
299
|
await runConfigShowCommandWithOptions();
|
|
263
300
|
}
|
|
264
301
|
|
|
302
|
+
export async function runConfigThemeCommand(): Promise<void> {
|
|
303
|
+
await runConfigThemeCommandWithOptions();
|
|
304
|
+
}
|
|
305
|
+
|
|
265
306
|
export async function runConfigShowCommandWithOptions(
|
|
266
307
|
options: ConfigShowRenderOptions = {},
|
|
267
308
|
): Promise<void> {
|
|
@@ -291,6 +332,59 @@ export async function runConfigShowCommandWithOptions(
|
|
|
291
332
|
}
|
|
292
333
|
}
|
|
293
334
|
|
|
335
|
+
export async function runConfigThemeCommandWithOptions(
|
|
336
|
+
options: ConfigInteractiveRenderOptions = {},
|
|
337
|
+
): Promise<void> {
|
|
338
|
+
const showHeader = options.showHeader ?? true;
|
|
339
|
+
if (showHeader) {
|
|
340
|
+
renderHiveHeader("Config · Theme");
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const spinner = ora("Loading themes...").start();
|
|
344
|
+
const db = openHiveDatabase();
|
|
345
|
+
|
|
346
|
+
try {
|
|
347
|
+
ensureInteractiveTerminal("`hive config theme` requires an interactive terminal.");
|
|
348
|
+
|
|
349
|
+
const currentTheme = getTheme();
|
|
350
|
+
const themeOptions = buildThemeOptions(currentTheme.hex, currentTheme.name);
|
|
351
|
+
|
|
352
|
+
spinner.stop();
|
|
353
|
+
|
|
354
|
+
const theme = await selectThemeOption(themeOptions, currentTheme.name);
|
|
355
|
+
|
|
356
|
+
let themeHex = resolveThemeHex(theme, currentTheme.hex);
|
|
357
|
+
|
|
358
|
+
if (theme === "custom") {
|
|
359
|
+
const answer = (await inquirer.prompt([
|
|
360
|
+
{
|
|
361
|
+
type: "input",
|
|
362
|
+
name: "hex",
|
|
363
|
+
message: "Enter hex color: #",
|
|
364
|
+
default: currentTheme.name === "custom" ? currentTheme.hex : undefined,
|
|
365
|
+
validate: validateHexColor,
|
|
366
|
+
},
|
|
367
|
+
])) as { hex: string };
|
|
368
|
+
|
|
369
|
+
themeHex = normalizeHexColor(answer.hex);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
spinner.start("Saving theme...");
|
|
373
|
+
setMetaValue(db, "theme", theme);
|
|
374
|
+
setMetaValue(db, "theme_hex", themeHex);
|
|
375
|
+
spinner.succeed("Theme saved.");
|
|
376
|
+
|
|
377
|
+
console.log(applyTheme(themeHex)("✓ Theme set. The Hive is now yours."));
|
|
378
|
+
} catch (error) {
|
|
379
|
+
if (spinner.isSpinning) {
|
|
380
|
+
spinner.fail("Failed to update theme.");
|
|
381
|
+
}
|
|
382
|
+
throw error;
|
|
383
|
+
} finally {
|
|
384
|
+
closeHiveDatabase(db);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
294
388
|
function ensureInteractiveTerminal(errorMessage: string): void {
|
|
295
389
|
if (!process.stdin.isTTY) {
|
|
296
390
|
throw new Error(errorMessage);
|
|
@@ -302,6 +396,169 @@ function printCurrentProviderAndModel(provider: ProviderName, model: string): vo
|
|
|
302
396
|
renderInfo(`Current model: ${model}`);
|
|
303
397
|
}
|
|
304
398
|
|
|
399
|
+
function formatThemeChoice(name: string, hex: string, description?: string): string {
|
|
400
|
+
const dot = applyTheme(hex)("●");
|
|
401
|
+
const paddedName = name.padEnd(THEME_LABEL_WIDTH, " ");
|
|
402
|
+
const descriptionSuffix = description ? ` (${description})` : "";
|
|
403
|
+
return `${dot} ${paddedName} ${hex}${descriptionSuffix}`;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function buildThemeOptions(currentHex: string, currentTheme: ThemeName): ThemeOption[] {
|
|
407
|
+
const customHex = currentTheme === "custom" ? currentHex : DEFAULT_THEME_HEX;
|
|
408
|
+
return [
|
|
409
|
+
{
|
|
410
|
+
name: "amber",
|
|
411
|
+
hex: BUILT_IN_THEMES.amber,
|
|
412
|
+
description: "default — beehive",
|
|
413
|
+
},
|
|
414
|
+
{ name: "cyan", hex: BUILT_IN_THEMES.cyan },
|
|
415
|
+
{ name: "rose", hex: BUILT_IN_THEMES.rose },
|
|
416
|
+
{ name: "slate", hex: BUILT_IN_THEMES.slate },
|
|
417
|
+
{ name: "green", hex: BUILT_IN_THEMES.green },
|
|
418
|
+
{ name: "custom", hex: customHex, description: "user provided hex" },
|
|
419
|
+
];
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function resolveThemeHex(theme: ThemeName, currentHex: string): string {
|
|
423
|
+
if (theme === "custom") {
|
|
424
|
+
return currentHex;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return BUILT_IN_THEMES[theme];
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async function selectThemeOption(
|
|
431
|
+
themeOptions: ThemeOption[],
|
|
432
|
+
currentTheme: ThemeName,
|
|
433
|
+
): Promise<ThemeName> {
|
|
434
|
+
const input = process.stdin;
|
|
435
|
+
const output = process.stdout;
|
|
436
|
+
readline.emitKeypressEvents(input);
|
|
437
|
+
|
|
438
|
+
const defaultIndex = themeOptions.findIndex((option) => option.name === currentTheme);
|
|
439
|
+
let selectedIndex = defaultIndex >= 0 ? defaultIndex : 0;
|
|
440
|
+
const wasRaw = input.isRaw ?? false;
|
|
441
|
+
|
|
442
|
+
if (!wasRaw) {
|
|
443
|
+
input.setRawMode(true);
|
|
444
|
+
}
|
|
445
|
+
input.resume();
|
|
446
|
+
output.write(ENTER_ALT_SCREEN);
|
|
447
|
+
|
|
448
|
+
return new Promise<ThemeName>((resolve, reject) => {
|
|
449
|
+
const cleanup = () => {
|
|
450
|
+
input.off("keypress", onKeypress);
|
|
451
|
+
if (!wasRaw) {
|
|
452
|
+
input.setRawMode(false);
|
|
453
|
+
}
|
|
454
|
+
output.write(EXIT_ALT_SCREEN);
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
const render = () => {
|
|
458
|
+
const selectedOption = themeOptions[selectedIndex] ?? themeOptions[0];
|
|
459
|
+
const accent = applyTheme(selectedOption?.hex ?? DEFAULT_THEME_HEX);
|
|
460
|
+
const terminalWidth = getThemeSelectorTerminalWidth(output.columns);
|
|
461
|
+
const headerLines = renderThemeSelectorHeader(accent, terminalWidth);
|
|
462
|
+
const lines = [
|
|
463
|
+
...headerLines,
|
|
464
|
+
"",
|
|
465
|
+
"Select a theme (live preview):",
|
|
466
|
+
"",
|
|
467
|
+
...themeOptions.map((option, index) => {
|
|
468
|
+
const marker = index === selectedIndex ? accent("›") : " ";
|
|
469
|
+
return `${marker} ${formatThemeChoice(option.name, option.hex, option.description)}`;
|
|
470
|
+
}),
|
|
471
|
+
"",
|
|
472
|
+
accent("Preview"),
|
|
473
|
+
`${accent("✓")} Theme set. The Hive is now yours.`,
|
|
474
|
+
`${accent("›")} Step indicator`,
|
|
475
|
+
`${accent("you›")} prompt ${accent("hive›")} agent`,
|
|
476
|
+
accent("────────────────────────────────────────"),
|
|
477
|
+
"",
|
|
478
|
+
"Enter to apply, Esc to cancel",
|
|
479
|
+
];
|
|
480
|
+
|
|
481
|
+
output.write(`${CLEAR_SCREEN}${lines.join("\n")}`);
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
const onKeypress = (keyText: string, key: readline.Key) => {
|
|
485
|
+
if ((key.ctrl && key.name === "c") || key.name === "escape") {
|
|
486
|
+
cleanup();
|
|
487
|
+
reject(new Error("Theme selection cancelled."));
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (key.name === "up") {
|
|
492
|
+
selectedIndex =
|
|
493
|
+
selectedIndex > 0 ? selectedIndex - 1 : themeOptions.length - 1;
|
|
494
|
+
render();
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (key.name === "down") {
|
|
499
|
+
selectedIndex =
|
|
500
|
+
selectedIndex < themeOptions.length - 1 ? selectedIndex + 1 : 0;
|
|
501
|
+
render();
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (key.name === "return" || key.name === "enter") {
|
|
506
|
+
const selectedOption = themeOptions[selectedIndex];
|
|
507
|
+
cleanup();
|
|
508
|
+
resolve(selectedOption?.name ?? "amber");
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const digit = Number.parseInt(keyText, 10);
|
|
513
|
+
if (!Number.isNaN(digit) && digit >= 1 && digit <= themeOptions.length) {
|
|
514
|
+
selectedIndex = digit - 1;
|
|
515
|
+
render();
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
input.on("keypress", onKeypress);
|
|
520
|
+
render();
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function renderThemeSelectorHeader(
|
|
525
|
+
accent: ReturnType<typeof applyTheme>,
|
|
526
|
+
terminalWidth: number,
|
|
527
|
+
): string[] {
|
|
528
|
+
const separator = "─".repeat(getThemeSelectorSeparatorWidth(terminalWidth));
|
|
529
|
+
const centredWordmark = THEME_WORDMARK_LINES.map((line) =>
|
|
530
|
+
accent.bold(centerText(line, terminalWidth)),
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
return [
|
|
534
|
+
...centredWordmark,
|
|
535
|
+
accent(centerText(THEME_SELECTOR_TITLE, terminalWidth)),
|
|
536
|
+
accent(centerText(separator, terminalWidth)),
|
|
537
|
+
];
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function getThemeSelectorTerminalWidth(columns: number | undefined): number {
|
|
541
|
+
if (typeof columns !== "number" || columns < 20) {
|
|
542
|
+
return 80;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return columns;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function getThemeSelectorSeparatorWidth(terminalWidth: number): number {
|
|
549
|
+
const usableWidth = Math.max(THEME_SELECTOR_MIN_SEPARATOR_WIDTH, terminalWidth - 8);
|
|
550
|
+
return Math.min(THEME_SELECTOR_MAX_SEPARATOR_WIDTH, usableWidth);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function centerText(value: string, totalWidth: number): string {
|
|
554
|
+
if (value.length >= totalWidth) {
|
|
555
|
+
return value;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const leftPadding = Math.floor((totalWidth - value.length) / 2);
|
|
559
|
+
return `${" ".repeat(leftPadding)}${value}`;
|
|
560
|
+
}
|
|
561
|
+
|
|
305
562
|
async function getKeyStatus(provider: ProviderName): Promise<"set" | "not set"> {
|
|
306
563
|
const apiKey = await resolveProviderApiKey(provider, apiKeyEnvVar(provider));
|
|
307
564
|
return apiKey ? "set" : "not set";
|
|
@@ -340,6 +597,14 @@ function requiredField(message: string): (value: string) => true | string {
|
|
|
340
597
|
};
|
|
341
598
|
}
|
|
342
599
|
|
|
600
|
+
function validateHexColor(value: string): true | string {
|
|
601
|
+
return isValidHexColor(value.trim()) || "Use #RRGGBB format.";
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function normalizeHexColor(value: string): string {
|
|
605
|
+
return value.trim().toUpperCase();
|
|
606
|
+
}
|
|
607
|
+
|
|
343
608
|
function assertNever(value: never): never {
|
|
344
609
|
throw new Error(`Unsupported provider: ${String(value)}`);
|
|
345
610
|
}
|