@imisbahk/hive 0.1.0 → 0.1.2
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/.github/workflows/publish.yml +31 -0
- package/.rocket/README.md +8 -8
- package/.rocket/SYMBOLS.md +260 -117
- package/Aborted +0 -0
- package/CONTRIBUTING.md +2 -1
- package/FEATURES.md +55 -0
- package/README.md +13 -11
- package/bun.lock +554 -0
- package/dist/agent/agent.d.ts +10 -1
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +351 -1
- package/dist/agent/agent.js.map +1 -1
- package/dist/browser/browser.d.ts +9 -0
- package/dist/browser/browser.d.ts.map +1 -0
- package/dist/browser/browser.js +338 -0
- package/dist/browser/browser.js.map +1 -0
- package/dist/cli/commands/chat.d.ts +5 -1
- package/dist/cli/commands/chat.d.ts.map +1 -1
- package/dist/cli/commands/chat.js +580 -38
- package/dist/cli/commands/chat.js.map +1 -1
- package/dist/cli/commands/config.d.ts +13 -0
- package/dist/cli/commands/config.d.ts.map +1 -1
- package/dist/cli/commands/config.js +257 -16
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +39 -14
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/nuke.d.ts.map +1 -1
- package/dist/cli/commands/nuke.js +5 -4
- package/dist/cli/commands/nuke.js.map +1 -1
- package/dist/cli/commands/status.d.ts +5 -0
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +16 -6
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/index.js +34 -12
- 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 +7 -0
- package/dist/cli/ui.d.ts.map +1 -0
- package/dist/cli/ui.js +101 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/providers/base.d.ts +37 -1
- package/dist/providers/base.d.ts.map +1 -1
- package/dist/providers/base.js +104 -0
- package/dist/providers/base.js.map +1 -1
- package/dist/providers/openai-compatible.d.ts +2 -1
- package/dist/providers/openai-compatible.d.ts.map +1 -1
- package/dist/providers/openai-compatible.js +18 -1
- package/dist/providers/openai-compatible.js.map +1 -1
- package/package.json +9 -1
- package/prompts/Browser.md +13 -0
- package/prompts/Debugging.md +15 -0
- package/prompts/Execution.md +13 -0
- package/prompts/Planning.md +13 -0
- package/prompts/Product.md +14 -0
- package/prompts/Review.md +15 -0
- package/prompts/Safety.md +12 -0
- package/prompts/Search.md +14 -0
- package/prompts/Tools.md +14 -0
- package/prompts/Writing.md +13 -0
- package/releases/v1/v0.1/RELEASE-NOTES.md +46 -0
- package/src/agent/agent.ts +442 -2
- package/src/browser/browser.ts +410 -0
- package/src/cli/commands/chat.ts +729 -34
- package/src/cli/commands/config.ts +344 -16
- package/src/cli/commands/init.ts +60 -14
- package/src/cli/commands/nuke.ts +11 -7
- package/src/cli/commands/status.ts +29 -6
- package/src/cli/index.ts +37 -9
- package/src/cli/theme.ts +88 -0
- package/src/cli/ui.ts +127 -0
- package/src/providers/base.ts +176 -1
- package/src/providers/openai-compatible.ts +24 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
|
+
import * as readline from "node:readline";
|
|
2
3
|
|
|
3
|
-
import chalk from "chalk";
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import inquirer from "inquirer";
|
|
6
6
|
import keytar from "keytar";
|
|
@@ -9,6 +9,21 @@ import ora from "ora";
|
|
|
9
9
|
import { resolveProviderApiKey } from "../../providers/api-key.js";
|
|
10
10
|
import { normalizeProviderName, type ProviderName } from "../../providers/base.js";
|
|
11
11
|
import { promptForModel, promptForProvider } from "../helpers/providerPrompts.js";
|
|
12
|
+
import {
|
|
13
|
+
renderError,
|
|
14
|
+
renderHiveHeader,
|
|
15
|
+
renderInfo,
|
|
16
|
+
renderStep,
|
|
17
|
+
renderSuccess,
|
|
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";
|
|
12
27
|
import {
|
|
13
28
|
closeHiveDatabase,
|
|
14
29
|
getPrimaryAgent,
|
|
@@ -19,11 +34,40 @@ import {
|
|
|
19
34
|
} from "../../storage/db.js";
|
|
20
35
|
|
|
21
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;
|
|
52
|
+
|
|
53
|
+
interface ConfigShowRenderOptions {
|
|
54
|
+
showHeader?: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface ConfigInteractiveRenderOptions {
|
|
58
|
+
showHeader?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface ThemeOption {
|
|
62
|
+
name: ThemeName;
|
|
63
|
+
hex: string;
|
|
64
|
+
description?: string;
|
|
65
|
+
}
|
|
22
66
|
|
|
23
67
|
export function registerConfigCommand(program: Command): void {
|
|
24
68
|
const configCommand = program
|
|
25
69
|
.command("config")
|
|
26
|
-
.description("Update provider, model, or API keys without re-running init");
|
|
70
|
+
.description("Update provider, model, theme, or API keys without re-running init");
|
|
27
71
|
|
|
28
72
|
configCommand
|
|
29
73
|
.command("provider")
|
|
@@ -52,9 +96,31 @@ export function registerConfigCommand(program: Command): void {
|
|
|
52
96
|
.action(async () => {
|
|
53
97
|
await runConfigShowCommand();
|
|
54
98
|
});
|
|
99
|
+
|
|
100
|
+
configCommand
|
|
101
|
+
.command("theme")
|
|
102
|
+
.description("Change CLI accent theme")
|
|
103
|
+
.action(async () => {
|
|
104
|
+
await runConfigThemeCommand();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
configCommand.action(() => {
|
|
108
|
+
renderHiveHeader("Config");
|
|
109
|
+
configCommand.outputHelp();
|
|
110
|
+
});
|
|
55
111
|
}
|
|
56
112
|
|
|
57
113
|
export async function runConfigProviderCommand(): Promise<void> {
|
|
114
|
+
await runConfigProviderCommandWithOptions();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function runConfigProviderCommandWithOptions(
|
|
118
|
+
options: ConfigInteractiveRenderOptions = {},
|
|
119
|
+
): Promise<void> {
|
|
120
|
+
const showHeader = options.showHeader ?? true;
|
|
121
|
+
if (showHeader) {
|
|
122
|
+
renderHiveHeader("Config · Provider");
|
|
123
|
+
}
|
|
58
124
|
const spinner = ora("Loading configuration...").start();
|
|
59
125
|
const db = openHiveDatabase();
|
|
60
126
|
|
|
@@ -64,7 +130,7 @@ export async function runConfigProviderCommand(): Promise<void> {
|
|
|
64
130
|
const agent = getPrimaryAgent(db);
|
|
65
131
|
if (!agent) {
|
|
66
132
|
spinner.stop();
|
|
67
|
-
|
|
133
|
+
renderError("Hive is not initialized. Run `hive init` first.");
|
|
68
134
|
return;
|
|
69
135
|
}
|
|
70
136
|
|
|
@@ -110,7 +176,8 @@ export async function runConfigProviderCommand(): Promise<void> {
|
|
|
110
176
|
}
|
|
111
177
|
|
|
112
178
|
spinner.succeed("Configuration saved.");
|
|
113
|
-
|
|
179
|
+
renderSuccess("Provider updated.");
|
|
180
|
+
renderStep("Run `hive` to use it.");
|
|
114
181
|
} catch (error) {
|
|
115
182
|
if (spinner.isSpinning) {
|
|
116
183
|
spinner.fail("Failed to update provider configuration.");
|
|
@@ -122,6 +189,16 @@ export async function runConfigProviderCommand(): Promise<void> {
|
|
|
122
189
|
}
|
|
123
190
|
|
|
124
191
|
export async function runConfigModelCommand(): Promise<void> {
|
|
192
|
+
await runConfigModelCommandWithOptions();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export async function runConfigModelCommandWithOptions(
|
|
196
|
+
options: ConfigInteractiveRenderOptions = {},
|
|
197
|
+
): Promise<void> {
|
|
198
|
+
const showHeader = options.showHeader ?? true;
|
|
199
|
+
if (showHeader) {
|
|
200
|
+
renderHiveHeader("Config · Model");
|
|
201
|
+
}
|
|
125
202
|
const spinner = ora("Loading configuration...").start();
|
|
126
203
|
const db = openHiveDatabase();
|
|
127
204
|
|
|
@@ -131,7 +208,7 @@ export async function runConfigModelCommand(): Promise<void> {
|
|
|
131
208
|
const agent = getPrimaryAgent(db);
|
|
132
209
|
if (!agent) {
|
|
133
210
|
spinner.stop();
|
|
134
|
-
|
|
211
|
+
renderError("Hive is not initialized. Run `hive init` first.");
|
|
135
212
|
return;
|
|
136
213
|
}
|
|
137
214
|
|
|
@@ -151,7 +228,8 @@ export async function runConfigModelCommand(): Promise<void> {
|
|
|
151
228
|
setMetaValue(db, "model", updatedAgent.model);
|
|
152
229
|
|
|
153
230
|
spinner.succeed("Configuration saved.");
|
|
154
|
-
|
|
231
|
+
renderSuccess("Model updated.");
|
|
232
|
+
renderStep("Run `hive` to use it.");
|
|
155
233
|
} catch (error) {
|
|
156
234
|
if (spinner.isSpinning) {
|
|
157
235
|
spinner.fail("Failed to update model configuration.");
|
|
@@ -163,6 +241,16 @@ export async function runConfigModelCommand(): Promise<void> {
|
|
|
163
241
|
}
|
|
164
242
|
|
|
165
243
|
export async function runConfigKeyCommand(): Promise<void> {
|
|
244
|
+
await runConfigKeyCommandWithOptions();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export async function runConfigKeyCommandWithOptions(
|
|
248
|
+
options: ConfigInteractiveRenderOptions = {},
|
|
249
|
+
): Promise<void> {
|
|
250
|
+
const showHeader = options.showHeader ?? true;
|
|
251
|
+
if (showHeader) {
|
|
252
|
+
renderHiveHeader("Config · Key");
|
|
253
|
+
}
|
|
166
254
|
const spinner = ora("Loading configuration...").start();
|
|
167
255
|
const db = openHiveDatabase();
|
|
168
256
|
|
|
@@ -172,14 +260,14 @@ export async function runConfigKeyCommand(): Promise<void> {
|
|
|
172
260
|
const agent = getPrimaryAgent(db);
|
|
173
261
|
if (!agent) {
|
|
174
262
|
spinner.stop();
|
|
175
|
-
|
|
263
|
+
renderError("Hive is not initialized. Run `hive init` first.");
|
|
176
264
|
return;
|
|
177
265
|
}
|
|
178
266
|
|
|
179
267
|
const provider = normalizeProviderName(agent.provider);
|
|
180
268
|
|
|
181
269
|
spinner.stop();
|
|
182
|
-
|
|
270
|
+
renderInfo(`Current provider: ${provider}`);
|
|
183
271
|
|
|
184
272
|
const answer = (await inquirer.prompt([
|
|
185
273
|
{
|
|
@@ -195,7 +283,8 @@ export async function runConfigKeyCommand(): Promise<void> {
|
|
|
195
283
|
await keytar.setPassword(KEYCHAIN_SERVICE, provider, answer.apiKey.trim());
|
|
196
284
|
|
|
197
285
|
spinner.succeed("Configuration saved.");
|
|
198
|
-
|
|
286
|
+
renderSuccess("API key updated.");
|
|
287
|
+
renderStep("Run `hive` to use it.");
|
|
199
288
|
} catch (error) {
|
|
200
289
|
if (spinner.isSpinning) {
|
|
201
290
|
spinner.fail("Failed to update API key.");
|
|
@@ -207,22 +296,90 @@ export async function runConfigKeyCommand(): Promise<void> {
|
|
|
207
296
|
}
|
|
208
297
|
|
|
209
298
|
export async function runConfigShowCommand(): Promise<void> {
|
|
299
|
+
await runConfigShowCommandWithOptions();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export async function runConfigThemeCommand(): Promise<void> {
|
|
303
|
+
await runConfigThemeCommandWithOptions();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export async function runConfigShowCommandWithOptions(
|
|
307
|
+
options: ConfigShowRenderOptions = {},
|
|
308
|
+
): Promise<void> {
|
|
309
|
+
const showHeader = options.showHeader ?? true;
|
|
310
|
+
if (showHeader) {
|
|
311
|
+
renderHiveHeader("Config · Show");
|
|
312
|
+
}
|
|
313
|
+
|
|
210
314
|
const db = openHiveDatabase();
|
|
211
315
|
|
|
212
316
|
try {
|
|
213
317
|
const agent = getPrimaryAgent(db);
|
|
214
318
|
if (!agent) {
|
|
215
|
-
|
|
319
|
+
renderError("Hive is not initialized. Run `hive init` first.");
|
|
216
320
|
return;
|
|
217
321
|
}
|
|
218
322
|
|
|
219
323
|
const provider = normalizeProviderName(agent.provider);
|
|
220
324
|
const keyStatus = await getKeyStatus(provider);
|
|
221
325
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
326
|
+
renderStep(`Provider: ${provider}`);
|
|
327
|
+
renderStep(`Model: ${agent.model}`);
|
|
328
|
+
renderStep(`Agent name: ${agent.agent_name ?? "not set"}`);
|
|
329
|
+
renderStep(`API key: ${keyStatus}`);
|
|
330
|
+
} finally {
|
|
331
|
+
closeHiveDatabase(db);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
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;
|
|
226
383
|
} finally {
|
|
227
384
|
closeHiveDatabase(db);
|
|
228
385
|
}
|
|
@@ -235,8 +392,171 @@ function ensureInteractiveTerminal(errorMessage: string): void {
|
|
|
235
392
|
}
|
|
236
393
|
|
|
237
394
|
function printCurrentProviderAndModel(provider: ProviderName, model: string): void {
|
|
238
|
-
|
|
239
|
-
|
|
395
|
+
renderInfo(`Current provider: ${provider}`);
|
|
396
|
+
renderInfo(`Current model: ${model}`);
|
|
397
|
+
}
|
|
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}`;
|
|
240
560
|
}
|
|
241
561
|
|
|
242
562
|
async function getKeyStatus(provider: ProviderName): Promise<"set" | "not set"> {
|
|
@@ -277,6 +597,14 @@ function requiredField(message: string): (value: string) => true | string {
|
|
|
277
597
|
};
|
|
278
598
|
}
|
|
279
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
|
+
|
|
280
608
|
function assertNever(value: never): never {
|
|
281
609
|
throw new Error(`Unsupported provider: ${String(value)}`);
|
|
282
610
|
}
|
package/src/cli/commands/init.ts
CHANGED
|
@@ -2,7 +2,6 @@ import process from "node:process";
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
|
|
5
|
-
import chalk from "chalk";
|
|
6
5
|
import { Command } from "commander";
|
|
7
6
|
import inquirer from "inquirer";
|
|
8
7
|
import keytar from "keytar";
|
|
@@ -10,6 +9,12 @@ import ora from "ora";
|
|
|
10
9
|
|
|
11
10
|
import { buildDefaultPersona } from "../../agent/agent.js";
|
|
12
11
|
import { promptForModel, promptForProvider } from "../helpers/providerPrompts.js";
|
|
12
|
+
import {
|
|
13
|
+
renderHiveHeader,
|
|
14
|
+
renderInfo,
|
|
15
|
+
renderStep,
|
|
16
|
+
renderSuccess,
|
|
17
|
+
} from "../ui.js";
|
|
13
18
|
import {
|
|
14
19
|
closeHiveDatabase,
|
|
15
20
|
getHiveHomeDir,
|
|
@@ -49,6 +54,7 @@ export function registerInitCommand(program: Command): void {
|
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
export async function runInitCommand(options: InitCommandOptions = {}): Promise<void> {
|
|
57
|
+
renderHiveHeader("Init");
|
|
52
58
|
const spinner = ora("Preparing init...").start();
|
|
53
59
|
const db = openHiveDatabase();
|
|
54
60
|
|
|
@@ -71,7 +77,7 @@ export async function runInitCommand(options: InitCommandOptions = {}): Promise<
|
|
|
71
77
|
])) as { reinitialize: boolean };
|
|
72
78
|
|
|
73
79
|
if (!reinitialize) {
|
|
74
|
-
|
|
80
|
+
renderInfo("Initialization cancelled.");
|
|
75
81
|
return;
|
|
76
82
|
}
|
|
77
83
|
}
|
|
@@ -101,13 +107,13 @@ export async function runInitCommand(options: InitCommandOptions = {}): Promise<
|
|
|
101
107
|
copyPromptsDirectory(options.force ?? false);
|
|
102
108
|
|
|
103
109
|
spinner.succeed("Initialization complete.");
|
|
104
|
-
|
|
110
|
+
renderSuccess(`HIVE-ID: ${agent.id}`);
|
|
105
111
|
if (agent.agent_name) {
|
|
106
|
-
|
|
112
|
+
renderSuccess(`Agent name: ${agent.agent_name}`);
|
|
107
113
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
114
|
+
renderSuccess(`Provider: ${agent.provider}`);
|
|
115
|
+
renderSuccess(`Model: ${agent.model}`);
|
|
116
|
+
renderStep("Run `hive` to start talking.");
|
|
111
117
|
} catch (error) {
|
|
112
118
|
if (spinner.isSpinning) {
|
|
113
119
|
spinner.fail("Hive initialization failed.");
|
|
@@ -225,18 +231,58 @@ function copyPromptsDirectory(force: boolean): void {
|
|
|
225
231
|
const destinationPath = join(getHiveHomeDir(), "prompts");
|
|
226
232
|
|
|
227
233
|
if (!fs.existsSync(sourcePath)) {
|
|
228
|
-
|
|
234
|
+
renderInfo("Warning: prompts/ folder not found. Skipping prompts load.");
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (force && fs.existsSync(destinationPath)) {
|
|
239
|
+
fs.rmSync(destinationPath, { recursive: true, force: true });
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
fs.mkdirSync(destinationPath, { recursive: true });
|
|
243
|
+
const copiedFiles = syncPromptFiles(sourcePath, destinationPath, force);
|
|
244
|
+
|
|
245
|
+
if (copiedFiles === 0) {
|
|
246
|
+
renderStep("Prompts already up to date -> ~/.hive/prompts/");
|
|
229
247
|
return;
|
|
230
248
|
}
|
|
231
249
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
250
|
+
renderStep(`Prompts loaded -> ~/.hive/prompts/ (${copiedFiles} files)`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function syncPromptFiles(
|
|
254
|
+
sourceDirectory: string,
|
|
255
|
+
destinationDirectory: string,
|
|
256
|
+
overwriteExisting: boolean,
|
|
257
|
+
): number {
|
|
258
|
+
let copiedCount = 0;
|
|
259
|
+
const entries = fs.readdirSync(sourceDirectory, { withFileTypes: true });
|
|
260
|
+
|
|
261
|
+
for (const entry of entries) {
|
|
262
|
+
const sourceEntryPath = join(sourceDirectory, entry.name);
|
|
263
|
+
const destinationEntryPath = join(destinationDirectory, entry.name);
|
|
264
|
+
|
|
265
|
+
if (entry.isDirectory()) {
|
|
266
|
+
fs.mkdirSync(destinationEntryPath, { recursive: true });
|
|
267
|
+
copiedCount += syncPromptFiles(
|
|
268
|
+
sourceEntryPath,
|
|
269
|
+
destinationEntryPath,
|
|
270
|
+
overwriteExisting,
|
|
271
|
+
);
|
|
272
|
+
continue;
|
|
235
273
|
}
|
|
236
274
|
|
|
237
|
-
|
|
275
|
+
if (!entry.isFile()) {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (!overwriteExisting && fs.existsSync(destinationEntryPath)) {
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
fs.copyFileSync(sourceEntryPath, destinationEntryPath);
|
|
284
|
+
copiedCount += 1;
|
|
238
285
|
}
|
|
239
286
|
|
|
240
|
-
|
|
241
|
-
console.log("Prompts loaded → ~/.hive/prompts/");
|
|
287
|
+
return copiedCount;
|
|
242
288
|
}
|
package/src/cli/commands/nuke.ts
CHANGED
|
@@ -2,12 +2,17 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import { stdin, stdout } from "node:process";
|
|
3
3
|
import { createInterface } from "node:readline/promises";
|
|
4
4
|
|
|
5
|
-
import chalk from "chalk";
|
|
6
5
|
import { Command } from "commander";
|
|
7
6
|
import keytar from "keytar";
|
|
8
7
|
|
|
9
8
|
import { SUPPORTED_PROVIDER_NAMES } from "../../providers/base.js";
|
|
10
9
|
import { getHiveHomeDir } from "../../storage/db.js";
|
|
10
|
+
import {
|
|
11
|
+
renderError,
|
|
12
|
+
renderHiveHeader,
|
|
13
|
+
renderInfo,
|
|
14
|
+
renderSuccess,
|
|
15
|
+
} from "../ui.js";
|
|
11
16
|
|
|
12
17
|
const KEYCHAIN_SERVICE = "hive";
|
|
13
18
|
const NUKE_CONFIRMATION = "nuke";
|
|
@@ -22,10 +27,9 @@ export function registerNukeCommand(program: Command): void {
|
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
export async function runNukeCommand(): Promise<void> {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
),
|
|
30
|
+
renderHiveHeader("Nuke");
|
|
31
|
+
renderError(
|
|
32
|
+
"This will permanently delete your agent, all memory, all conversations, and all keys. This cannot be undone.",
|
|
29
33
|
);
|
|
30
34
|
|
|
31
35
|
const rl = createInterface({
|
|
@@ -42,7 +46,7 @@ export async function runNukeCommand(): Promise<void> {
|
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
if (confirmation !== NUKE_CONFIRMATION) {
|
|
45
|
-
|
|
49
|
+
renderInfo("Aborted.");
|
|
46
50
|
return;
|
|
47
51
|
}
|
|
48
52
|
|
|
@@ -56,5 +60,5 @@ export async function runNukeCommand(): Promise<void> {
|
|
|
56
60
|
}
|
|
57
61
|
}
|
|
58
62
|
|
|
59
|
-
|
|
63
|
+
renderSuccess("The Hive has been nuked. Gone.");
|
|
60
64
|
}
|