@imisbahk/hive 0.1.3 → 0.1.4
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/.gitattributes +7 -0
- package/.rocket/README.md +9 -9
- package/dist/agent/agent.d.ts +4 -0
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +40 -4
- package/dist/agent/agent.js.map +1 -1
- package/dist/cli/commands/chat.d.ts.map +1 -1
- package/dist/cli/commands/chat.js +642 -12
- package/dist/cli/commands/chat.js.map +1 -1
- package/dist/cli/commands/memory.d.ts +3 -0
- package/dist/cli/commands/memory.d.ts.map +1 -0
- package/dist/cli/commands/memory.js +104 -0
- package/dist/cli/commands/memory.js.map +1 -0
- package/dist/cli/index.js +3 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/providers/api-key.js +1 -1
- package/dist/providers/api-key.js.map +1 -1
- package/dist/providers/base.js +1 -1
- package/dist/providers/base.js.map +1 -1
- package/dist/providers/openai-compatible.js +2 -2
- package/dist/providers/openai-compatible.js.map +1 -1
- package/dist/storage/db.d.ts +31 -2
- package/dist/storage/db.d.ts.map +1 -1
- package/dist/storage/db.js +165 -3
- package/dist/storage/db.js.map +1 -1
- package/dist/storage/schema.d.ts +12 -1
- package/dist/storage/schema.d.ts.map +1 -1
- package/dist/storage/schema.js +29 -1
- package/dist/storage/schema.js.map +1 -1
- package/package.json +8 -2
- package/.github/workflows/publish.yml +0 -31
- package/.rocket/ARCHITECTURE.md +0 -7
- package/.rocket/SYMBOLS.md +0 -425
- package/001-local-first-storage.md +0 -43
- package/003-memory-architechture.md +0 -71
- package/CONTRIBUTING.md +0 -150
- package/FEATURES.md +0 -63
- package/index.md +0 -16
- package/prompts/Behaviour.md +0 -23
- package/prompts/Browser.md +0 -13
- package/prompts/Code.md +0 -12
- package/prompts/Debugging.md +0 -15
- package/prompts/Execution.md +0 -13
- package/prompts/Memory.md +0 -11
- package/prompts/Planning.md +0 -13
- package/prompts/Product.md +0 -14
- package/prompts/Review.md +0 -15
- package/prompts/Safety.md +0 -12
- package/prompts/Search.md +0 -14
- package/prompts/System.md +0 -6
- package/prompts/Tools.md +0 -14
- package/prompts/Writing.md +0 -13
- package/releases/v1/v0.1/RELEASE-NOTES.md +0 -101
- package/src/agent/agent.ts +0 -595
- package/src/agent/index.ts +0 -2
- package/src/browser/browser.ts +0 -410
- package/src/cli/commands/chat.ts +0 -864
- package/src/cli/commands/config.ts +0 -610
- package/src/cli/commands/doctor.ts +0 -655
- package/src/cli/commands/init.ts +0 -288
- package/src/cli/commands/nuke.ts +0 -64
- package/src/cli/commands/status.ts +0 -170
- package/src/cli/helpers/providerPrompts.ts +0 -192
- package/src/cli/index.ts +0 -68
- package/src/cli/theme.ts +0 -88
- package/src/cli/ui.ts +0 -127
- package/src/providers/anthropic.ts +0 -146
- package/src/providers/api-key.ts +0 -23
- package/src/providers/base.ts +0 -409
- package/src/providers/google.ts +0 -21
- package/src/providers/groq.ts +0 -21
- package/src/providers/index.ts +0 -65
- package/src/providers/mistral.ts +0 -21
- package/src/providers/ollama.ts +0 -22
- package/src/providers/openai-compatible.ts +0 -82
- package/src/providers/openai.ts +0 -21
- package/src/providers/openrouter.ts +0 -21
- package/src/providers/together.ts +0 -21
- package/src/storage/db.ts +0 -476
- package/src/storage/schema.ts +0 -116
- package/tsconfig.json +0 -51
|
@@ -1,610 +0,0 @@
|
|
|
1
|
-
import process from "node:process";
|
|
2
|
-
import * as readline from "node:readline";
|
|
3
|
-
|
|
4
|
-
import { Command } from "commander";
|
|
5
|
-
import inquirer from "inquirer";
|
|
6
|
-
import keytar from "keytar";
|
|
7
|
-
import ora from "ora";
|
|
8
|
-
|
|
9
|
-
import { resolveProviderApiKey } from "../../providers/api-key.js";
|
|
10
|
-
import { normalizeProviderName, type ProviderName } from "../../providers/base.js";
|
|
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";
|
|
27
|
-
import {
|
|
28
|
-
closeHiveDatabase,
|
|
29
|
-
getPrimaryAgent,
|
|
30
|
-
openHiveDatabase,
|
|
31
|
-
setMetaValue,
|
|
32
|
-
updatePrimaryAgentModel,
|
|
33
|
-
updatePrimaryAgentProviderAndModel,
|
|
34
|
-
} from "../../storage/db.js";
|
|
35
|
-
|
|
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
|
-
}
|
|
66
|
-
|
|
67
|
-
export function registerConfigCommand(program: Command): void {
|
|
68
|
-
const configCommand = program
|
|
69
|
-
.command("config")
|
|
70
|
-
.description("Update provider, model, theme, or API keys without re-running init");
|
|
71
|
-
|
|
72
|
-
configCommand
|
|
73
|
-
.command("provider")
|
|
74
|
-
.description("Change provider, model, and API key")
|
|
75
|
-
.action(async () => {
|
|
76
|
-
await runConfigProviderCommand();
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
configCommand
|
|
80
|
-
.command("model")
|
|
81
|
-
.description("Change model for the current provider")
|
|
82
|
-
.action(async () => {
|
|
83
|
-
await runConfigModelCommand();
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
configCommand
|
|
87
|
-
.command("key")
|
|
88
|
-
.description("Update API key for the current provider")
|
|
89
|
-
.action(async () => {
|
|
90
|
-
await runConfigKeyCommand();
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
configCommand
|
|
94
|
-
.command("show")
|
|
95
|
-
.description("Show current provider, model, and key status")
|
|
96
|
-
.action(async () => {
|
|
97
|
-
await runConfigShowCommand();
|
|
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
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
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
|
-
}
|
|
124
|
-
const spinner = ora("Loading configuration...").start();
|
|
125
|
-
const db = openHiveDatabase();
|
|
126
|
-
|
|
127
|
-
try {
|
|
128
|
-
ensureInteractiveTerminal("`hive config provider` requires an interactive terminal.");
|
|
129
|
-
|
|
130
|
-
const agent = getPrimaryAgent(db);
|
|
131
|
-
if (!agent) {
|
|
132
|
-
spinner.stop();
|
|
133
|
-
renderError("Hive is not initialized. Run `hive init` first.");
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const currentProvider = normalizeProviderName(agent.provider);
|
|
138
|
-
const currentModel = agent.model;
|
|
139
|
-
|
|
140
|
-
spinner.stop();
|
|
141
|
-
printCurrentProviderAndModel(currentProvider, currentModel);
|
|
142
|
-
|
|
143
|
-
const provider = await promptForProvider({
|
|
144
|
-
defaultProvider: currentProvider,
|
|
145
|
-
});
|
|
146
|
-
const model = await promptForModel(provider, {
|
|
147
|
-
defaultModel: provider === currentProvider ? currentModel : undefined,
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
let apiKey: string | undefined;
|
|
151
|
-
if (provider !== "ollama") {
|
|
152
|
-
const answer = (await inquirer.prompt([
|
|
153
|
-
{
|
|
154
|
-
type: "password",
|
|
155
|
-
name: "apiKey",
|
|
156
|
-
message: "Enter your API key:",
|
|
157
|
-
mask: "*",
|
|
158
|
-
validate: requiredField("API key is required."),
|
|
159
|
-
},
|
|
160
|
-
])) as { apiKey: string };
|
|
161
|
-
|
|
162
|
-
apiKey = answer.apiKey.trim();
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
spinner.start("Saving configuration...");
|
|
166
|
-
|
|
167
|
-
const updatedAgent = updatePrimaryAgentProviderAndModel(db, {
|
|
168
|
-
provider,
|
|
169
|
-
model,
|
|
170
|
-
});
|
|
171
|
-
setMetaValue(db, "provider", updatedAgent.provider);
|
|
172
|
-
setMetaValue(db, "model", updatedAgent.model);
|
|
173
|
-
|
|
174
|
-
if (provider !== "ollama" && apiKey) {
|
|
175
|
-
await keytar.setPassword(KEYCHAIN_SERVICE, provider, apiKey);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
spinner.succeed("Configuration saved.");
|
|
179
|
-
renderSuccess("Provider updated.");
|
|
180
|
-
renderStep("Run `hive` to use it.");
|
|
181
|
-
} catch (error) {
|
|
182
|
-
if (spinner.isSpinning) {
|
|
183
|
-
spinner.fail("Failed to update provider configuration.");
|
|
184
|
-
}
|
|
185
|
-
throw error;
|
|
186
|
-
} finally {
|
|
187
|
-
closeHiveDatabase(db);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
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
|
-
}
|
|
202
|
-
const spinner = ora("Loading configuration...").start();
|
|
203
|
-
const db = openHiveDatabase();
|
|
204
|
-
|
|
205
|
-
try {
|
|
206
|
-
ensureInteractiveTerminal("`hive config model` requires an interactive terminal.");
|
|
207
|
-
|
|
208
|
-
const agent = getPrimaryAgent(db);
|
|
209
|
-
if (!agent) {
|
|
210
|
-
spinner.stop();
|
|
211
|
-
renderError("Hive is not initialized. Run `hive init` first.");
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const provider = normalizeProviderName(agent.provider);
|
|
216
|
-
const currentModel = agent.model;
|
|
217
|
-
|
|
218
|
-
spinner.stop();
|
|
219
|
-
printCurrentProviderAndModel(provider, currentModel);
|
|
220
|
-
|
|
221
|
-
const model = await promptForModel(provider, {
|
|
222
|
-
defaultModel: currentModel,
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
spinner.start("Saving model...");
|
|
226
|
-
|
|
227
|
-
const updatedAgent = updatePrimaryAgentModel(db, model);
|
|
228
|
-
setMetaValue(db, "model", updatedAgent.model);
|
|
229
|
-
|
|
230
|
-
spinner.succeed("Configuration saved.");
|
|
231
|
-
renderSuccess("Model updated.");
|
|
232
|
-
renderStep("Run `hive` to use it.");
|
|
233
|
-
} catch (error) {
|
|
234
|
-
if (spinner.isSpinning) {
|
|
235
|
-
spinner.fail("Failed to update model configuration.");
|
|
236
|
-
}
|
|
237
|
-
throw error;
|
|
238
|
-
} finally {
|
|
239
|
-
closeHiveDatabase(db);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
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
|
-
}
|
|
254
|
-
const spinner = ora("Loading configuration...").start();
|
|
255
|
-
const db = openHiveDatabase();
|
|
256
|
-
|
|
257
|
-
try {
|
|
258
|
-
ensureInteractiveTerminal("`hive config key` requires an interactive terminal.");
|
|
259
|
-
|
|
260
|
-
const agent = getPrimaryAgent(db);
|
|
261
|
-
if (!agent) {
|
|
262
|
-
spinner.stop();
|
|
263
|
-
renderError("Hive is not initialized. Run `hive init` first.");
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const provider = normalizeProviderName(agent.provider);
|
|
268
|
-
|
|
269
|
-
spinner.stop();
|
|
270
|
-
renderInfo(`Current provider: ${provider}`);
|
|
271
|
-
|
|
272
|
-
const answer = (await inquirer.prompt([
|
|
273
|
-
{
|
|
274
|
-
type: "password",
|
|
275
|
-
name: "apiKey",
|
|
276
|
-
message: "Enter your API key:",
|
|
277
|
-
mask: "*",
|
|
278
|
-
validate: requiredField("API key is required."),
|
|
279
|
-
},
|
|
280
|
-
])) as { apiKey: string };
|
|
281
|
-
|
|
282
|
-
spinner.start("Saving key...");
|
|
283
|
-
await keytar.setPassword(KEYCHAIN_SERVICE, provider, answer.apiKey.trim());
|
|
284
|
-
|
|
285
|
-
spinner.succeed("Configuration saved.");
|
|
286
|
-
renderSuccess("API key updated.");
|
|
287
|
-
renderStep("Run `hive` to use it.");
|
|
288
|
-
} catch (error) {
|
|
289
|
-
if (spinner.isSpinning) {
|
|
290
|
-
spinner.fail("Failed to update API key.");
|
|
291
|
-
}
|
|
292
|
-
throw error;
|
|
293
|
-
} finally {
|
|
294
|
-
closeHiveDatabase(db);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
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
|
-
|
|
314
|
-
const db = openHiveDatabase();
|
|
315
|
-
|
|
316
|
-
try {
|
|
317
|
-
const agent = getPrimaryAgent(db);
|
|
318
|
-
if (!agent) {
|
|
319
|
-
renderError("Hive is not initialized. Run `hive init` first.");
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const provider = normalizeProviderName(agent.provider);
|
|
324
|
-
const keyStatus = await getKeyStatus(provider);
|
|
325
|
-
|
|
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;
|
|
383
|
-
} finally {
|
|
384
|
-
closeHiveDatabase(db);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
function ensureInteractiveTerminal(errorMessage: string): void {
|
|
389
|
-
if (!process.stdin.isTTY) {
|
|
390
|
-
throw new Error(errorMessage);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function printCurrentProviderAndModel(provider: ProviderName, model: string): void {
|
|
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}`;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
async function getKeyStatus(provider: ProviderName): Promise<"set" | "not set"> {
|
|
563
|
-
const apiKey = await resolveProviderApiKey(provider, apiKeyEnvVar(provider));
|
|
564
|
-
return apiKey ? "set" : "not set";
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
function apiKeyEnvVar(provider: ProviderName): string {
|
|
568
|
-
switch (provider) {
|
|
569
|
-
case "openai":
|
|
570
|
-
return "OPENAI_API_KEY";
|
|
571
|
-
case "anthropic":
|
|
572
|
-
return "ANTHROPIC_API_KEY";
|
|
573
|
-
case "ollama":
|
|
574
|
-
return "OLLAMA_API_KEY";
|
|
575
|
-
case "groq":
|
|
576
|
-
return "GROQ_API_KEY";
|
|
577
|
-
case "mistral":
|
|
578
|
-
return "MISTRAL_API_KEY";
|
|
579
|
-
case "google":
|
|
580
|
-
return "GOOGLE_API_KEY";
|
|
581
|
-
case "openrouter":
|
|
582
|
-
return "OPENROUTER_API_KEY";
|
|
583
|
-
case "together":
|
|
584
|
-
return "TOGETHER_API_KEY";
|
|
585
|
-
default:
|
|
586
|
-
return assertNever(provider);
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
function requiredField(message: string): (value: string) => true | string {
|
|
591
|
-
return (value: string) => {
|
|
592
|
-
if (value.trim().length > 0) {
|
|
593
|
-
return true;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
return message;
|
|
597
|
-
};
|
|
598
|
-
}
|
|
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
|
-
|
|
608
|
-
function assertNever(value: never): never {
|
|
609
|
-
throw new Error(`Unsupported provider: ${String(value)}`);
|
|
610
|
-
}
|