@imisbahk/hive 0.1.2 → 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/FEATURES.md CHANGED
@@ -1,4 +1,4 @@
1
- • v0.1.1 command surface:
1
+ • v0.1.3 command surface:
2
2
 
3
3
  CLI commands
4
4
 
@@ -9,7 +9,9 @@
9
9
  - hive config provider → interactive provider/model/key update
10
10
  - hive config model → interactive model update
11
11
  - hive config key → interactive API key update
12
+ - hive config theme → interactive accent theme picker (live preview)
12
13
  - hive config show → show provider/model/agent/key status
14
+ - hive doctor → run full health check diagnostics
13
15
  - hive status → full local status report
14
16
  - hive nuke → permanently delete local Hive data + keys
15
17
  - hive help [command]
@@ -40,11 +42,17 @@
40
42
  - /hive config provider (interactive in chat)
41
43
  - /hive config model (interactive in chat)
42
44
  - /hive config key (interactive in chat)
45
+ - /hive config theme (interactive in chat)
43
46
  - /hive init and /hive nuke are shell-only safety commands
44
47
 
45
48
  Current features
46
49
 
47
50
  - Centered “HIVE / Command Centre” UI across command pages
51
+ - Unified CLI accent theme system stored in `~/.hive/hive.db`
52
+ - Built-in themes: amber (default), cyan, rose, slate, green
53
+ - Custom theme hex support with validation (`^#[0-9A-Fa-f]{6}$`)
54
+ - Live, real-time theme preview while navigating `hive config theme`
55
+ - ASCII HIVE wordmark, separators, prompts, step/success indicators now share one accent color
48
56
  - Chat-first CLI (hive opens chat)
49
57
  - Deprecated hive chat messaging
50
58
  - Live / autocomplete with scrollable suggestion viewport
@@ -52,4 +60,4 @@
52
60
  - Slash-command hardening (unknown slash commands handled locally, not sent to model)
53
61
  - Browser-augmented chat flow for search/browse prompts
54
62
  - In-chat provider/model switching without dropping back to shell
55
- - Local-first storage (~/.hive), prompt loading from .hive/prompts, keychain-backed keys
63
+ - Local-first storage (~/.hive), prompt loading from .hive/prompts, keychain-backed keys
@@ -0,0 +1,8 @@
1
+ import { Command } from "commander";
2
+ interface DoctorOptions {
3
+ showHeader?: boolean;
4
+ }
5
+ export declare function registerDoctorCommand(program: Command): void;
6
+ export declare function runDoctorCommand(options?: DoctorOptions): Promise<void>;
7
+ export {};
8
+ //# sourceMappingURL=doctor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/doctor.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA8BpC,UAAU,aAAa;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAYD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAO5D;AAED,wBAAsB,gBAAgB,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwKjF"}
@@ -0,0 +1,503 @@
1
+ import * as fs from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import process from "node:process";
5
+ import Database from "better-sqlite3";
6
+ import fetch from "node-fetch";
7
+ import keytar from "keytar";
8
+ import { normalizeProviderName } from "../../providers/base.js";
9
+ import { closeHiveDatabase, getHiveDatabasePath, getHiveHomeDir, getMetaValue, getPrimaryAgent, } from "../../storage/db.js";
10
+ import { BUILT_IN_THEMES, DEFAULT_THEME_HEX, DEFAULT_THEME_NAME, isValidHexColor, } from "../theme.js";
11
+ import { renderError, renderHiveHeader, renderInfo, renderSuccess } from "../ui.js";
12
+ const KEYCHAIN_SERVICE = "hive";
13
+ const PROMPTS_DIRECTORY = "prompts";
14
+ const PROVIDER_PING_TIMEOUT_MS = 5_000;
15
+ const OLLAMA_PING_TIMEOUT_MS = 5_000;
16
+ const DB_SIZE_WARNING_BYTES = 100 * 1024 * 1024;
17
+ const NODE_MAJOR_WARNING_VERSION = 20;
18
+ const CHECK_LABEL_WIDTH = 22;
19
+ export function registerDoctorCommand(program) {
20
+ program
21
+ .command("doctor")
22
+ .description("Run a full Hive health check")
23
+ .action(async () => {
24
+ await runDoctorCommand();
25
+ });
26
+ }
27
+ export async function runDoctorCommand(options = {}) {
28
+ const showHeader = options.showHeader ?? true;
29
+ if (showHeader) {
30
+ renderHiveHeader("Doctor");
31
+ }
32
+ renderInfo("");
33
+ renderInfo("Running diagnostics...");
34
+ renderInfo("");
35
+ const counters = { warnings: 0, errors: 0 };
36
+ const dbPath = getHiveDatabasePath();
37
+ const promptsPath = join(getHiveHomeDir(), PROMPTS_DIRECTORY);
38
+ let dbSizeBytes = 0;
39
+ let db = null;
40
+ let providerName = null;
41
+ let providerLookupError = null;
42
+ let keychainApiKey = null;
43
+ const databaseCheck = checkDatabase(dbPath);
44
+ dbSizeBytes = databaseCheck.sizeBytes;
45
+ db = databaseCheck.ok ? databaseCheck.db : null;
46
+ let agentName = "missing";
47
+ if (db) {
48
+ const agent = getPrimaryAgent(db);
49
+ if (agent) {
50
+ agentName = agent.agent_name?.trim() ? agent.agent_name : agent.name;
51
+ renderSuccess(formatCheckLine("Agent initialized", agentName));
52
+ try {
53
+ providerName = normalizeProviderName(agent.provider);
54
+ }
55
+ catch {
56
+ providerName = null;
57
+ providerLookupError = `unsupported (${agent.provider})`;
58
+ }
59
+ }
60
+ else {
61
+ renderFailure("Agent initialized", "not initialized", counters);
62
+ }
63
+ }
64
+ else {
65
+ renderFailure("Agent initialized", "not checked (database unavailable)", counters);
66
+ }
67
+ if (databaseCheck.ok) {
68
+ renderSuccess(formatCheckLine("Database", `${displayPath(dbPath)} (${formatBytes(dbSizeBytes)})`));
69
+ }
70
+ else {
71
+ renderFailure("Database", databaseCheck.message, counters);
72
+ }
73
+ if (dbSizeBytes > DB_SIZE_WARNING_BYTES) {
74
+ renderWarning("Database size", `${formatBytes(dbSizeBytes)} exceeds ${formatBytes(DB_SIZE_WARNING_BYTES)}`, counters);
75
+ }
76
+ if (providerName) {
77
+ if (providerName === "ollama") {
78
+ renderSuccess(formatCheckLine("API Key", "not required (ollama)"));
79
+ }
80
+ else {
81
+ keychainApiKey = await readKeychainApiKey(providerName);
82
+ if (keychainApiKey && keychainApiKey.trim().length > 0) {
83
+ renderSuccess(formatCheckLine("API Key", "set"));
84
+ }
85
+ else {
86
+ renderFailure("API Key", "missing in keychain", counters);
87
+ }
88
+ }
89
+ }
90
+ else if (providerLookupError) {
91
+ renderFailure("API Key", "not checked (provider unsupported)", counters);
92
+ }
93
+ else {
94
+ renderFailure("API Key", "not checked (provider unavailable)", counters);
95
+ }
96
+ if (providerName) {
97
+ const providerReachable = await checkProviderReachable(providerName, keychainApiKey);
98
+ if (providerReachable.ok) {
99
+ renderSuccess(formatCheckLine("Provider", `${providerName} — reachable`));
100
+ }
101
+ else if (providerReachable.warning) {
102
+ renderWarning("Provider", `${providerName} — ${providerReachable.message}`, counters);
103
+ }
104
+ else {
105
+ renderFailure("Provider", `${providerName} — ${providerReachable.message}`, counters);
106
+ }
107
+ }
108
+ else if (providerLookupError) {
109
+ renderFailure("Provider", providerLookupError, counters);
110
+ }
111
+ else {
112
+ renderFailure("Provider", "not checked (provider unavailable)", counters);
113
+ }
114
+ const promptsCheck = checkPromptsDirectory(promptsPath);
115
+ if (promptsCheck.ok) {
116
+ renderSuccess(formatCheckLine("Prompts", `${promptsCheck.fileCount} files loaded`));
117
+ }
118
+ else {
119
+ renderFailure("Prompts", promptsCheck.message, counters);
120
+ }
121
+ if (db) {
122
+ const theme = resolveThemeDetails(db);
123
+ renderSuccess(formatCheckLine("Theme", `${theme.name} ${theme.hex}`));
124
+ }
125
+ else {
126
+ renderFailure("Theme", "not checked (database unavailable)", counters);
127
+ }
128
+ const nodeCheck = checkNodeVersion(process.version);
129
+ if (nodeCheck.ok) {
130
+ renderSuccess(formatCheckLine("Node version", process.version));
131
+ }
132
+ else {
133
+ renderWarning("Node version", nodeCheck.message, counters);
134
+ }
135
+ const playwrightCheck = await checkPlaywrightInstallation();
136
+ if (playwrightCheck.ok) {
137
+ renderSuccess(formatCheckLine("Playwright", "chromium installed"));
138
+ }
139
+ else {
140
+ renderFailure("Playwright", playwrightCheck.message, counters);
141
+ }
142
+ if (providerName === "ollama") {
143
+ const ollamaCheck = await checkOllamaRunning();
144
+ if (ollamaCheck.ok) {
145
+ renderSuccess(formatCheckLine("Ollama", "running"));
146
+ }
147
+ else {
148
+ renderWarning("Ollama", "not running", counters);
149
+ }
150
+ }
151
+ if (db) {
152
+ const messageCount = countRowsIfTableExists(db, "messages");
153
+ const conversationCount = countRowsIfTableExists(db, "conversations");
154
+ const episodeCount = countRowsIfTableExists(db, "episodes");
155
+ if (episodeCount === null) {
156
+ renderInfo(formatInfoLine("Memory", "episodes table not found"));
157
+ }
158
+ else {
159
+ renderInfo(formatInfoLine("Memory", `${episodeCount} episodes stored`));
160
+ }
161
+ if (messageCount === null) {
162
+ renderInfo(formatInfoLine("Messages", "messages table not found"));
163
+ }
164
+ else {
165
+ renderInfo(formatInfoLine("Messages", `${messageCount} total`));
166
+ }
167
+ if (conversationCount === null) {
168
+ renderInfo(formatInfoLine("Conversations", "conversations table not found"));
169
+ }
170
+ else {
171
+ renderInfo(formatInfoLine("Conversations", `${conversationCount} total`));
172
+ }
173
+ }
174
+ else {
175
+ renderInfo(formatInfoLine("Memory", "not checked"));
176
+ renderInfo(formatInfoLine("Conversations", "not checked"));
177
+ }
178
+ renderInfo("");
179
+ renderSummary(counters);
180
+ if (db) {
181
+ closeHiveDatabase(db);
182
+ }
183
+ }
184
+ function checkDatabase(databasePath) {
185
+ if (!fs.existsSync(databasePath)) {
186
+ return {
187
+ ok: false,
188
+ message: `${displayPath(databasePath)} not found`,
189
+ sizeBytes: 0,
190
+ };
191
+ }
192
+ const stats = fs.statSync(databasePath);
193
+ if (!stats.isFile()) {
194
+ return {
195
+ ok: false,
196
+ message: `${displayPath(databasePath)} is not a file`,
197
+ sizeBytes: 0,
198
+ };
199
+ }
200
+ try {
201
+ const db = new Database(databasePath, {
202
+ readonly: true,
203
+ fileMustExist: true,
204
+ });
205
+ const integrity = db.pragma("integrity_check", { simple: true });
206
+ if (integrity !== "ok") {
207
+ db.close();
208
+ return {
209
+ ok: false,
210
+ message: `integrity check failed (${String(integrity)})`,
211
+ sizeBytes: stats.size,
212
+ };
213
+ }
214
+ return { ok: true, db, sizeBytes: stats.size };
215
+ }
216
+ catch (error) {
217
+ return {
218
+ ok: false,
219
+ message: `unreadable (${formatError(error)})`,
220
+ sizeBytes: stats.size,
221
+ };
222
+ }
223
+ }
224
+ async function readKeychainApiKey(providerName) {
225
+ try {
226
+ return await keytar.getPassword(KEYCHAIN_SERVICE, providerName);
227
+ }
228
+ catch {
229
+ return null;
230
+ }
231
+ }
232
+ async function checkProviderReachable(providerName, apiKey) {
233
+ const target = buildProviderPingTarget(providerName, apiKey);
234
+ if (!target) {
235
+ return {
236
+ ok: false,
237
+ message: "missing API key",
238
+ };
239
+ }
240
+ try {
241
+ const response = await fetchWithTimeout(target.url, {
242
+ method: "GET",
243
+ headers: target.headers,
244
+ }, PROVIDER_PING_TIMEOUT_MS);
245
+ if (response.ok) {
246
+ return { ok: true };
247
+ }
248
+ if (response.status === 401 || response.status === 403) {
249
+ return { ok: false, message: `auth failed (${response.status})` };
250
+ }
251
+ if (response.status >= 500) {
252
+ return { ok: false, warning: true, message: `service unavailable (${response.status})` };
253
+ }
254
+ return { ok: false, message: `HTTP ${response.status}` };
255
+ }
256
+ catch (error) {
257
+ return {
258
+ ok: false,
259
+ warning: isTimeoutError(error),
260
+ message: isTimeoutError(error) ? "timeout after 5s" : formatError(error),
261
+ };
262
+ }
263
+ }
264
+ function buildProviderPingTarget(providerName, apiKey) {
265
+ switch (providerName) {
266
+ case "openai":
267
+ return buildOpenAICompatiblePing(process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1", apiKey);
268
+ case "google":
269
+ return buildOpenAICompatiblePing(process.env.GOOGLE_BASE_URL ?? "https://generativelanguage.googleapis.com/v1beta/openai", apiKey);
270
+ case "groq":
271
+ return buildOpenAICompatiblePing(process.env.GROQ_BASE_URL ?? "https://api.groq.com/openai/v1", apiKey);
272
+ case "mistral":
273
+ return buildOpenAICompatiblePing(process.env.MISTRAL_BASE_URL ?? "https://api.mistral.ai/v1", apiKey);
274
+ case "openrouter":
275
+ return buildOpenAICompatiblePing(process.env.OPENROUTER_BASE_URL ?? "https://openrouter.ai/api/v1", apiKey);
276
+ case "together":
277
+ return buildOpenAICompatiblePing(process.env.TOGETHER_BASE_URL ?? "https://api.together.xyz/v1", apiKey);
278
+ case "ollama":
279
+ return {
280
+ url: "http://localhost:11434/api/tags",
281
+ };
282
+ case "anthropic":
283
+ if (!apiKey || apiKey.trim().length === 0) {
284
+ return null;
285
+ }
286
+ return {
287
+ url: "https://api.anthropic.com/v1/models",
288
+ headers: {
289
+ "x-api-key": apiKey,
290
+ "anthropic-version": "2023-06-01",
291
+ },
292
+ };
293
+ default:
294
+ return null;
295
+ }
296
+ }
297
+ function buildOpenAICompatiblePing(baseUrl, apiKey) {
298
+ if (!apiKey || apiKey.trim().length === 0) {
299
+ return null;
300
+ }
301
+ return {
302
+ url: `${baseUrl.replace(/\/$/, "")}/models`,
303
+ headers: {
304
+ authorization: `Bearer ${apiKey}`,
305
+ },
306
+ };
307
+ }
308
+ function checkPromptsDirectory(promptsPath) {
309
+ if (!fs.existsSync(promptsPath)) {
310
+ return {
311
+ ok: false,
312
+ message: `${ensureTrailingSlash(displayPath(promptsPath))} missing`,
313
+ };
314
+ }
315
+ const stats = fs.statSync(promptsPath);
316
+ if (!stats.isDirectory()) {
317
+ return {
318
+ ok: false,
319
+ message: `${displayPath(promptsPath)} is not a directory`,
320
+ };
321
+ }
322
+ const fileCount = countFilesRecursively(promptsPath);
323
+ if (fileCount <= 0) {
324
+ return {
325
+ ok: false,
326
+ message: `${ensureTrailingSlash(displayPath(promptsPath))} has no files`,
327
+ };
328
+ }
329
+ return {
330
+ ok: true,
331
+ fileCount,
332
+ };
333
+ }
334
+ function checkNodeVersion(version) {
335
+ const major = parseNodeMajorVersion(version);
336
+ if (major !== null && major >= NODE_MAJOR_WARNING_VERSION) {
337
+ return {
338
+ ok: true,
339
+ message: version,
340
+ };
341
+ }
342
+ return {
343
+ ok: false,
344
+ message: `${version} (recommended v20+)`,
345
+ };
346
+ }
347
+ function parseNodeMajorVersion(version) {
348
+ const match = /^v(\d+)/.exec(version.trim());
349
+ if (!match) {
350
+ return null;
351
+ }
352
+ const major = Number.parseInt(match[1] ?? "", 10);
353
+ return Number.isNaN(major) ? null : major;
354
+ }
355
+ async function checkPlaywrightInstallation() {
356
+ try {
357
+ const playwright = (await import("playwright"));
358
+ const executablePath = playwright.chromium?.executablePath();
359
+ if (!executablePath || !fs.existsSync(executablePath)) {
360
+ return { ok: false, message: "chromium not installed" };
361
+ }
362
+ return { ok: true };
363
+ }
364
+ catch {
365
+ return { ok: false, message: "playwright not installed" };
366
+ }
367
+ }
368
+ async function checkOllamaRunning() {
369
+ try {
370
+ const response = await fetchWithTimeout("http://localhost:11434", { method: "GET" }, OLLAMA_PING_TIMEOUT_MS);
371
+ return { ok: response.ok };
372
+ }
373
+ catch {
374
+ return { ok: false };
375
+ }
376
+ }
377
+ function resolveThemeDetails(db) {
378
+ const rawThemeName = getMetaValue(db, "theme");
379
+ const rawThemeHex = getMetaValue(db, "theme_hex");
380
+ if (rawThemeName && rawThemeName in BUILT_IN_THEMES) {
381
+ const name = rawThemeName;
382
+ return { name, hex: BUILT_IN_THEMES[name] };
383
+ }
384
+ if (rawThemeName === "custom" && rawThemeHex && isValidHexColor(rawThemeHex)) {
385
+ return { name: "custom", hex: rawThemeHex.toUpperCase() };
386
+ }
387
+ return {
388
+ name: DEFAULT_THEME_NAME,
389
+ hex: DEFAULT_THEME_HEX,
390
+ };
391
+ }
392
+ function countRowsIfTableExists(db, tableName) {
393
+ if (!tableExists(db, tableName)) {
394
+ return null;
395
+ }
396
+ const row = db
397
+ .prepare(`SELECT COUNT(1) AS count FROM ${tableName}`)
398
+ .get();
399
+ return row.count;
400
+ }
401
+ function tableExists(db, tableName) {
402
+ const row = db
403
+ .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1")
404
+ .get(tableName);
405
+ return Boolean(row);
406
+ }
407
+ function countFilesRecursively(path) {
408
+ let total = 0;
409
+ const entries = fs.readdirSync(path, { withFileTypes: true });
410
+ for (const entry of entries) {
411
+ const absolutePath = join(path, entry.name);
412
+ if (entry.isDirectory()) {
413
+ total += countFilesRecursively(absolutePath);
414
+ continue;
415
+ }
416
+ if (entry.isFile()) {
417
+ total += 1;
418
+ }
419
+ }
420
+ return total;
421
+ }
422
+ function formatCheckLine(label, value) {
423
+ return `${label.padEnd(CHECK_LABEL_WIDTH, " ")} ${value}`;
424
+ }
425
+ function formatInfoLine(label, value) {
426
+ return `· ${formatCheckLine(label, value)}`;
427
+ }
428
+ function renderFailure(label, value, counters) {
429
+ counters.errors += 1;
430
+ renderError(`✗ ${formatCheckLine(label, value)}`);
431
+ }
432
+ function renderWarning(label, value, counters) {
433
+ counters.warnings += 1;
434
+ renderError(`✗ ${formatCheckLine(label, value)}`);
435
+ }
436
+ function renderSummary(counters) {
437
+ if (counters.errors === 0 && counters.warnings === 0) {
438
+ renderSuccess("All checks passed.");
439
+ return;
440
+ }
441
+ const warningWord = counters.warnings === 1 ? "warning" : "warnings";
442
+ const errorWord = counters.errors === 1 ? "error" : "errors";
443
+ const summary = `${counters.warnings} ${warningWord}, ${counters.errors} ${errorWord}.`;
444
+ if (counters.errors > 0) {
445
+ renderError(summary);
446
+ return;
447
+ }
448
+ renderInfo(summary);
449
+ }
450
+ async function fetchWithTimeout(url, init, timeoutMs) {
451
+ const controller = new AbortController();
452
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
453
+ try {
454
+ return await fetch(url, {
455
+ method: init.method,
456
+ headers: init.headers,
457
+ signal: controller.signal,
458
+ });
459
+ }
460
+ finally {
461
+ clearTimeout(timer);
462
+ }
463
+ }
464
+ function isTimeoutError(error) {
465
+ if (!error || typeof error !== "object") {
466
+ return false;
467
+ }
468
+ const maybeError = error;
469
+ return maybeError.name === "AbortError" || maybeError.type === "aborted";
470
+ }
471
+ function formatBytes(bytes) {
472
+ const units = ["B", "KB", "MB", "GB", "TB"];
473
+ let unitIndex = 0;
474
+ let value = bytes;
475
+ while (value >= 1024 && unitIndex < units.length - 1) {
476
+ value /= 1024;
477
+ unitIndex += 1;
478
+ }
479
+ if (unitIndex === 0) {
480
+ return `${value} ${units[unitIndex]}`;
481
+ }
482
+ return `${value.toFixed(1)} ${units[unitIndex]}`;
483
+ }
484
+ function displayPath(path) {
485
+ const home = homedir();
486
+ if (path === home) {
487
+ return "~";
488
+ }
489
+ if (path.startsWith(`${home}/`)) {
490
+ return `~/${path.slice(home.length + 1)}`;
491
+ }
492
+ return path;
493
+ }
494
+ function ensureTrailingSlash(path) {
495
+ return path.endsWith("/") ? path : `${path}/`;
496
+ }
497
+ function formatError(error) {
498
+ if (error instanceof Error) {
499
+ return error.message;
500
+ }
501
+ return String(error);
502
+ }
503
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../src/cli/commands/doctor.ts"],"names":[],"mappings":"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"}
package/dist/cli/index.js CHANGED
@@ -3,6 +3,7 @@ import "dotenv/config";
3
3
  import { Command } from "commander";
4
4
  import { registerChatCommand, runChatCommand } from "./commands/chat.js";
5
5
  import { registerConfigCommand } from "./commands/config.js";
6
+ import { registerDoctorCommand } from "./commands/doctor.js";
6
7
  import { registerInitCommand } from "./commands/init.js";
7
8
  import { registerNukeCommand } from "./commands/nuke.js";
8
9
  import { registerStatusCommand } from "./commands/status.js";
@@ -15,6 +16,7 @@ program
15
16
  registerInitCommand(program);
16
17
  registerChatCommand(program);
17
18
  registerConfigCommand(program);
19
+ registerDoctorCommand(program);
18
20
  registerStatusCommand(program);
19
21
  registerNukeCommand(program);
20
22
  const argv = process.argv.slice(2);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAEA,OAAO,eAAe,CAAC;AAEvB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAExD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,MAAM,CAAC;KACZ,WAAW,CAAC,8DAA8D,CAAC;KAC3E,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC7B,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC7B,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAC/B,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAC/B,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAE7B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAEnC,KAAK,IAAI,EAAE,CAAC;AAEZ,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC;QACH,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtB,MAAM,cAAc,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC;YACpD,OAAO;QACT,CAAC;QAED,IAAI,sBAAsB,CAAC,IAAI,CAAC,EAAE,CAAC;YACjC,gBAAgB,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC;QAC3C,CAAC;QAED,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;YAC3B,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC3B,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QAED,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3B,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC;AACH,CAAC;AAED,SAAS,sBAAsB,CAAC,IAAc;IAC5C,OAAO,IAAI,CAAC,CAAC,CAAC,KAAK,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAC9E,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAc;IACtC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,CAAC;QACvB,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC;IAC3B,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7D,OAAO,WAAW,IAAI,MAAM,CAAC;AAC/B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAEA,OAAO,eAAe,CAAC;AAEvB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAExD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,MAAM,CAAC;KACZ,WAAW,CAAC,8DAA8D,CAAC;KAC3E,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC7B,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC7B,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAC/B,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAC/B,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAC/B,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAE7B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAEnC,KAAK,IAAI,EAAE,CAAC;AAEZ,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC;QACH,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtB,MAAM,cAAc,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC;YACpD,OAAO;QACT,CAAC;QAED,IAAI,sBAAsB,CAAC,IAAI,CAAC,EAAE,CAAC;YACjC,gBAAgB,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC;QAC3C,CAAC;QAED,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;YAC3B,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC3B,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QAED,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3B,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC;AACH,CAAC;AAED,SAAS,sBAAsB,CAAC,IAAc;IAC5C,OAAO,IAAI,CAAC,CAAC,CAAC,KAAK,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAC9E,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAc;IACtC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,CAAC;QACvB,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC;IAC3B,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7D,OAAO,WAAW,IAAI,MAAM,CAAC;AAC/B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imisbahk/hive",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Your agent. Always running. Always learning. Always working.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
+ ```
@@ -0,0 +1,655 @@
1
+ import * as fs from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import process from "node:process";
5
+
6
+ import Database from "better-sqlite3";
7
+ import { Command } from "commander";
8
+ import fetch from "node-fetch";
9
+ import keytar from "keytar";
10
+
11
+ import { normalizeProviderName, type ProviderName } from "../../providers/base.js";
12
+ import {
13
+ closeHiveDatabase,
14
+ getHiveDatabasePath,
15
+ getHiveHomeDir,
16
+ getMetaValue,
17
+ getPrimaryAgent,
18
+ type HiveDatabase,
19
+ } from "../../storage/db.js";
20
+ import {
21
+ BUILT_IN_THEMES,
22
+ DEFAULT_THEME_HEX,
23
+ DEFAULT_THEME_NAME,
24
+ isValidHexColor,
25
+ type ThemeName,
26
+ } from "../theme.js";
27
+ import { renderError, renderHiveHeader, renderInfo, renderSuccess } from "../ui.js";
28
+
29
+ const KEYCHAIN_SERVICE = "hive";
30
+ const PROMPTS_DIRECTORY = "prompts";
31
+ const PROVIDER_PING_TIMEOUT_MS = 5_000;
32
+ const OLLAMA_PING_TIMEOUT_MS = 5_000;
33
+ const DB_SIZE_WARNING_BYTES = 100 * 1024 * 1024;
34
+ const NODE_MAJOR_WARNING_VERSION = 20;
35
+ const CHECK_LABEL_WIDTH = 22;
36
+
37
+ interface DoctorOptions {
38
+ showHeader?: boolean;
39
+ }
40
+
41
+ interface ThemeDetails {
42
+ name: ThemeName;
43
+ hex: string;
44
+ }
45
+
46
+ interface CheckCounter {
47
+ warnings: number;
48
+ errors: number;
49
+ }
50
+
51
+ export function registerDoctorCommand(program: Command): void {
52
+ program
53
+ .command("doctor")
54
+ .description("Run a full Hive health check")
55
+ .action(async () => {
56
+ await runDoctorCommand();
57
+ });
58
+ }
59
+
60
+ export async function runDoctorCommand(options: DoctorOptions = {}): Promise<void> {
61
+ const showHeader = options.showHeader ?? true;
62
+ if (showHeader) {
63
+ renderHiveHeader("Doctor");
64
+ }
65
+
66
+ renderInfo("");
67
+ renderInfo("Running diagnostics...");
68
+ renderInfo("");
69
+
70
+ const counters: CheckCounter = { warnings: 0, errors: 0 };
71
+ const dbPath = getHiveDatabasePath();
72
+ const promptsPath = join(getHiveHomeDir(), PROMPTS_DIRECTORY);
73
+
74
+ let dbSizeBytes = 0;
75
+ let db: HiveDatabase | null = null;
76
+ let providerName: ProviderName | null = null;
77
+ let providerLookupError: string | null = null;
78
+ let keychainApiKey: string | null = null;
79
+
80
+ const databaseCheck = checkDatabase(dbPath);
81
+ dbSizeBytes = databaseCheck.sizeBytes;
82
+ db = databaseCheck.ok ? databaseCheck.db : null;
83
+
84
+ let agentName = "missing";
85
+ if (db) {
86
+ const agent = getPrimaryAgent(db);
87
+ if (agent) {
88
+ agentName = agent.agent_name?.trim() ? agent.agent_name : agent.name;
89
+ renderSuccess(formatCheckLine("Agent initialized", agentName));
90
+
91
+ try {
92
+ providerName = normalizeProviderName(agent.provider);
93
+ } catch {
94
+ providerName = null;
95
+ providerLookupError = `unsupported (${agent.provider})`;
96
+ }
97
+ } else {
98
+ renderFailure("Agent initialized", "not initialized", counters);
99
+ }
100
+ } else {
101
+ renderFailure("Agent initialized", "not checked (database unavailable)", counters);
102
+ }
103
+
104
+ if (databaseCheck.ok) {
105
+ renderSuccess(formatCheckLine("Database", `${displayPath(dbPath)} (${formatBytes(dbSizeBytes)})`));
106
+ } else {
107
+ renderFailure("Database", databaseCheck.message, counters);
108
+ }
109
+
110
+ if (dbSizeBytes > DB_SIZE_WARNING_BYTES) {
111
+ renderWarning(
112
+ "Database size",
113
+ `${formatBytes(dbSizeBytes)} exceeds ${formatBytes(DB_SIZE_WARNING_BYTES)}`,
114
+ counters,
115
+ );
116
+ }
117
+
118
+ if (providerName) {
119
+ if (providerName === "ollama") {
120
+ renderSuccess(formatCheckLine("API Key", "not required (ollama)"));
121
+ } else {
122
+ keychainApiKey = await readKeychainApiKey(providerName);
123
+ if (keychainApiKey && keychainApiKey.trim().length > 0) {
124
+ renderSuccess(formatCheckLine("API Key", "set"));
125
+ } else {
126
+ renderFailure("API Key", "missing in keychain", counters);
127
+ }
128
+ }
129
+ } else if (providerLookupError) {
130
+ renderFailure("API Key", "not checked (provider unsupported)", counters);
131
+ } else {
132
+ renderFailure("API Key", "not checked (provider unavailable)", counters);
133
+ }
134
+
135
+ if (providerName) {
136
+ const providerReachable = await checkProviderReachable(providerName, keychainApiKey);
137
+ if (providerReachable.ok) {
138
+ renderSuccess(
139
+ formatCheckLine("Provider", `${providerName} — reachable`),
140
+ );
141
+ } else if (providerReachable.warning) {
142
+ renderWarning(
143
+ "Provider",
144
+ `${providerName} — ${providerReachable.message}`,
145
+ counters,
146
+ );
147
+ } else {
148
+ renderFailure("Provider", `${providerName} — ${providerReachable.message}`, counters);
149
+ }
150
+ } else if (providerLookupError) {
151
+ renderFailure("Provider", providerLookupError, counters);
152
+ } else {
153
+ renderFailure("Provider", "not checked (provider unavailable)", counters);
154
+ }
155
+
156
+ const promptsCheck = checkPromptsDirectory(promptsPath);
157
+ if (promptsCheck.ok) {
158
+ renderSuccess(
159
+ formatCheckLine("Prompts", `${promptsCheck.fileCount} files loaded`),
160
+ );
161
+ } else {
162
+ renderFailure("Prompts", promptsCheck.message, counters);
163
+ }
164
+
165
+ if (db) {
166
+ const theme = resolveThemeDetails(db);
167
+ renderSuccess(formatCheckLine("Theme", `${theme.name} ${theme.hex}`));
168
+ } else {
169
+ renderFailure("Theme", "not checked (database unavailable)", counters);
170
+ }
171
+
172
+ const nodeCheck = checkNodeVersion(process.version);
173
+ if (nodeCheck.ok) {
174
+ renderSuccess(formatCheckLine("Node version", process.version));
175
+ } else {
176
+ renderWarning("Node version", nodeCheck.message, counters);
177
+ }
178
+
179
+ const playwrightCheck = await checkPlaywrightInstallation();
180
+ if (playwrightCheck.ok) {
181
+ renderSuccess(formatCheckLine("Playwright", "chromium installed"));
182
+ } else {
183
+ renderFailure("Playwright", playwrightCheck.message, counters);
184
+ }
185
+
186
+ if (providerName === "ollama") {
187
+ const ollamaCheck = await checkOllamaRunning();
188
+ if (ollamaCheck.ok) {
189
+ renderSuccess(formatCheckLine("Ollama", "running"));
190
+ } else {
191
+ renderWarning("Ollama", "not running", counters);
192
+ }
193
+ }
194
+
195
+ if (db) {
196
+ const messageCount = countRowsIfTableExists(db, "messages");
197
+ const conversationCount = countRowsIfTableExists(db, "conversations");
198
+ const episodeCount = countRowsIfTableExists(db, "episodes");
199
+
200
+ if (episodeCount === null) {
201
+ renderInfo(formatInfoLine("Memory", "episodes table not found"));
202
+ } else {
203
+ renderInfo(formatInfoLine("Memory", `${episodeCount} episodes stored`));
204
+ }
205
+
206
+ if (messageCount === null) {
207
+ renderInfo(formatInfoLine("Messages", "messages table not found"));
208
+ } else {
209
+ renderInfo(formatInfoLine("Messages", `${messageCount} total`));
210
+ }
211
+
212
+ if (conversationCount === null) {
213
+ renderInfo(formatInfoLine("Conversations", "conversations table not found"));
214
+ } else {
215
+ renderInfo(formatInfoLine("Conversations", `${conversationCount} total`));
216
+ }
217
+ } else {
218
+ renderInfo(formatInfoLine("Memory", "not checked"));
219
+ renderInfo(formatInfoLine("Conversations", "not checked"));
220
+ }
221
+
222
+ renderInfo("");
223
+ renderSummary(counters);
224
+
225
+ if (db) {
226
+ closeHiveDatabase(db);
227
+ }
228
+ }
229
+
230
+ function checkDatabase(
231
+ databasePath: string,
232
+ ): { ok: true; db: HiveDatabase; sizeBytes: number } | { ok: false; message: string; sizeBytes: number } {
233
+ if (!fs.existsSync(databasePath)) {
234
+ return {
235
+ ok: false,
236
+ message: `${displayPath(databasePath)} not found`,
237
+ sizeBytes: 0,
238
+ };
239
+ }
240
+
241
+ const stats = fs.statSync(databasePath);
242
+ if (!stats.isFile()) {
243
+ return {
244
+ ok: false,
245
+ message: `${displayPath(databasePath)} is not a file`,
246
+ sizeBytes: 0,
247
+ };
248
+ }
249
+
250
+ try {
251
+ const db = new Database(databasePath, {
252
+ readonly: true,
253
+ fileMustExist: true,
254
+ });
255
+
256
+ const integrity = db.pragma("integrity_check", { simple: true });
257
+ if (integrity !== "ok") {
258
+ db.close();
259
+ return {
260
+ ok: false,
261
+ message: `integrity check failed (${String(integrity)})`,
262
+ sizeBytes: stats.size,
263
+ };
264
+ }
265
+
266
+ return { ok: true, db, sizeBytes: stats.size };
267
+ } catch (error) {
268
+ return {
269
+ ok: false,
270
+ message: `unreadable (${formatError(error)})`,
271
+ sizeBytes: stats.size,
272
+ };
273
+ }
274
+ }
275
+
276
+ async function readKeychainApiKey(providerName: ProviderName): Promise<string | null> {
277
+ try {
278
+ return await keytar.getPassword(KEYCHAIN_SERVICE, providerName);
279
+ } catch {
280
+ return null;
281
+ }
282
+ }
283
+
284
+ async function checkProviderReachable(
285
+ providerName: ProviderName,
286
+ apiKey: string | null,
287
+ ): Promise<{ ok: boolean; warning?: boolean; message?: string }> {
288
+ const target = buildProviderPingTarget(providerName, apiKey);
289
+ if (!target) {
290
+ return {
291
+ ok: false,
292
+ message: "missing API key",
293
+ };
294
+ }
295
+
296
+ try {
297
+ const response = await fetchWithTimeout(target.url, {
298
+ method: "GET",
299
+ headers: target.headers,
300
+ }, PROVIDER_PING_TIMEOUT_MS);
301
+
302
+ if (response.ok) {
303
+ return { ok: true };
304
+ }
305
+
306
+ if (response.status === 401 || response.status === 403) {
307
+ return { ok: false, message: `auth failed (${response.status})` };
308
+ }
309
+
310
+ if (response.status >= 500) {
311
+ return { ok: false, warning: true, message: `service unavailable (${response.status})` };
312
+ }
313
+
314
+ return { ok: false, message: `HTTP ${response.status}` };
315
+ } catch (error) {
316
+ return {
317
+ ok: false,
318
+ warning: isTimeoutError(error),
319
+ message: isTimeoutError(error) ? "timeout after 5s" : formatError(error),
320
+ };
321
+ }
322
+ }
323
+
324
+ function buildProviderPingTarget(
325
+ providerName: ProviderName,
326
+ apiKey: string | null,
327
+ ): { url: string; headers?: Record<string, string> } | null {
328
+ switch (providerName) {
329
+ case "openai":
330
+ return buildOpenAICompatiblePing(
331
+ process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1",
332
+ apiKey,
333
+ );
334
+ case "google":
335
+ return buildOpenAICompatiblePing(
336
+ process.env.GOOGLE_BASE_URL ?? "https://generativelanguage.googleapis.com/v1beta/openai",
337
+ apiKey,
338
+ );
339
+ case "groq":
340
+ return buildOpenAICompatiblePing(
341
+ process.env.GROQ_BASE_URL ?? "https://api.groq.com/openai/v1",
342
+ apiKey,
343
+ );
344
+ case "mistral":
345
+ return buildOpenAICompatiblePing(
346
+ process.env.MISTRAL_BASE_URL ?? "https://api.mistral.ai/v1",
347
+ apiKey,
348
+ );
349
+ case "openrouter":
350
+ return buildOpenAICompatiblePing(
351
+ process.env.OPENROUTER_BASE_URL ?? "https://openrouter.ai/api/v1",
352
+ apiKey,
353
+ );
354
+ case "together":
355
+ return buildOpenAICompatiblePing(
356
+ process.env.TOGETHER_BASE_URL ?? "https://api.together.xyz/v1",
357
+ apiKey,
358
+ );
359
+ case "ollama":
360
+ return {
361
+ url: "http://localhost:11434/api/tags",
362
+ };
363
+ case "anthropic":
364
+ if (!apiKey || apiKey.trim().length === 0) {
365
+ return null;
366
+ }
367
+
368
+ return {
369
+ url: "https://api.anthropic.com/v1/models",
370
+ headers: {
371
+ "x-api-key": apiKey,
372
+ "anthropic-version": "2023-06-01",
373
+ },
374
+ };
375
+ default:
376
+ return null;
377
+ }
378
+ }
379
+
380
+ function buildOpenAICompatiblePing(
381
+ baseUrl: string,
382
+ apiKey: string | null,
383
+ ): { url: string; headers?: Record<string, string> } | null {
384
+ if (!apiKey || apiKey.trim().length === 0) {
385
+ return null;
386
+ }
387
+
388
+ return {
389
+ url: `${baseUrl.replace(/\/$/, "")}/models`,
390
+ headers: {
391
+ authorization: `Bearer ${apiKey}`,
392
+ },
393
+ };
394
+ }
395
+
396
+ function checkPromptsDirectory(
397
+ promptsPath: string,
398
+ ): { ok: true; fileCount: number } | { ok: false; message: string } {
399
+ if (!fs.existsSync(promptsPath)) {
400
+ return {
401
+ ok: false,
402
+ message: `${ensureTrailingSlash(displayPath(promptsPath))} missing`,
403
+ };
404
+ }
405
+
406
+ const stats = fs.statSync(promptsPath);
407
+ if (!stats.isDirectory()) {
408
+ return {
409
+ ok: false,
410
+ message: `${displayPath(promptsPath)} is not a directory`,
411
+ };
412
+ }
413
+
414
+ const fileCount = countFilesRecursively(promptsPath);
415
+ if (fileCount <= 0) {
416
+ return {
417
+ ok: false,
418
+ message: `${ensureTrailingSlash(displayPath(promptsPath))} has no files`,
419
+ };
420
+ }
421
+
422
+ return {
423
+ ok: true,
424
+ fileCount,
425
+ };
426
+ }
427
+
428
+ function checkNodeVersion(version: string): { ok: boolean; message: string } {
429
+ const major = parseNodeMajorVersion(version);
430
+ if (major !== null && major >= NODE_MAJOR_WARNING_VERSION) {
431
+ return {
432
+ ok: true,
433
+ message: version,
434
+ };
435
+ }
436
+
437
+ return {
438
+ ok: false,
439
+ message: `${version} (recommended v20+)`,
440
+ };
441
+ }
442
+
443
+ function parseNodeMajorVersion(version: string): number | null {
444
+ const match = /^v(\d+)/.exec(version.trim());
445
+ if (!match) {
446
+ return null;
447
+ }
448
+
449
+ const major = Number.parseInt(match[1] ?? "", 10);
450
+ return Number.isNaN(major) ? null : major;
451
+ }
452
+
453
+ async function checkPlaywrightInstallation(): Promise<
454
+ { ok: true } | { ok: false; message: string }
455
+ > {
456
+ try {
457
+ const playwright = (await import("playwright")) as {
458
+ chromium?: {
459
+ executablePath: () => string;
460
+ };
461
+ };
462
+
463
+ const executablePath = playwright.chromium?.executablePath();
464
+ if (!executablePath || !fs.existsSync(executablePath)) {
465
+ return { ok: false, message: "chromium not installed" };
466
+ }
467
+
468
+ return { ok: true };
469
+ } catch {
470
+ return { ok: false, message: "playwright not installed" };
471
+ }
472
+ }
473
+
474
+ async function checkOllamaRunning(): Promise<{ ok: boolean }> {
475
+ try {
476
+ const response = await fetchWithTimeout(
477
+ "http://localhost:11434",
478
+ { method: "GET" },
479
+ OLLAMA_PING_TIMEOUT_MS,
480
+ );
481
+
482
+ return { ok: response.ok };
483
+ } catch {
484
+ return { ok: false };
485
+ }
486
+ }
487
+
488
+ function resolveThemeDetails(db: HiveDatabase): ThemeDetails {
489
+ const rawThemeName = getMetaValue(db, "theme");
490
+ const rawThemeHex = getMetaValue(db, "theme_hex");
491
+
492
+ if (rawThemeName && rawThemeName in BUILT_IN_THEMES) {
493
+ const name = rawThemeName as keyof typeof BUILT_IN_THEMES;
494
+ return { name, hex: BUILT_IN_THEMES[name] };
495
+ }
496
+
497
+ if (rawThemeName === "custom" && rawThemeHex && isValidHexColor(rawThemeHex)) {
498
+ return { name: "custom", hex: rawThemeHex.toUpperCase() };
499
+ }
500
+
501
+ return {
502
+ name: DEFAULT_THEME_NAME,
503
+ hex: DEFAULT_THEME_HEX,
504
+ };
505
+ }
506
+
507
+ function countRowsIfTableExists(db: HiveDatabase, tableName: string): number | null {
508
+ if (!tableExists(db, tableName)) {
509
+ return null;
510
+ }
511
+
512
+ const row = db
513
+ .prepare(`SELECT COUNT(1) AS count FROM ${tableName}`)
514
+ .get() as { count: number };
515
+
516
+ return row.count;
517
+ }
518
+
519
+ function tableExists(db: HiveDatabase, tableName: string): boolean {
520
+ const row = db
521
+ .prepare(
522
+ "SELECT name FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1",
523
+ )
524
+ .get(tableName) as { name: string } | undefined;
525
+
526
+ return Boolean(row);
527
+ }
528
+
529
+ function countFilesRecursively(path: string): number {
530
+ let total = 0;
531
+ const entries = fs.readdirSync(path, { withFileTypes: true });
532
+
533
+ for (const entry of entries) {
534
+ const absolutePath = join(path, entry.name);
535
+ if (entry.isDirectory()) {
536
+ total += countFilesRecursively(absolutePath);
537
+ continue;
538
+ }
539
+
540
+ if (entry.isFile()) {
541
+ total += 1;
542
+ }
543
+ }
544
+
545
+ return total;
546
+ }
547
+
548
+ function formatCheckLine(label: string, value: string): string {
549
+ return `${label.padEnd(CHECK_LABEL_WIDTH, " ")} ${value}`;
550
+ }
551
+
552
+ function formatInfoLine(label: string, value: string): string {
553
+ return `· ${formatCheckLine(label, value)}`;
554
+ }
555
+
556
+ function renderFailure(label: string, value: string, counters: CheckCounter): void {
557
+ counters.errors += 1;
558
+ renderError(`✗ ${formatCheckLine(label, value)}`);
559
+ }
560
+
561
+ function renderWarning(label: string, value: string, counters: CheckCounter): void {
562
+ counters.warnings += 1;
563
+ renderError(`✗ ${formatCheckLine(label, value)}`);
564
+ }
565
+
566
+ function renderSummary(counters: CheckCounter): void {
567
+ if (counters.errors === 0 && counters.warnings === 0) {
568
+ renderSuccess("All checks passed.");
569
+ return;
570
+ }
571
+
572
+ const warningWord = counters.warnings === 1 ? "warning" : "warnings";
573
+ const errorWord = counters.errors === 1 ? "error" : "errors";
574
+ const summary = `${counters.warnings} ${warningWord}, ${counters.errors} ${errorWord}.`;
575
+
576
+ if (counters.errors > 0) {
577
+ renderError(summary);
578
+ return;
579
+ }
580
+
581
+ renderInfo(summary);
582
+ }
583
+
584
+ async function fetchWithTimeout(
585
+ url: string,
586
+ init: {
587
+ method: "GET";
588
+ headers?: Record<string, string>;
589
+ },
590
+ timeoutMs: number,
591
+ ): Promise<Awaited<ReturnType<typeof fetch>>> {
592
+ const controller = new AbortController();
593
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
594
+
595
+ try {
596
+ return await fetch(url, {
597
+ method: init.method,
598
+ headers: init.headers,
599
+ signal: controller.signal,
600
+ });
601
+ } finally {
602
+ clearTimeout(timer);
603
+ }
604
+ }
605
+
606
+ function isTimeoutError(error: unknown): boolean {
607
+ if (!error || typeof error !== "object") {
608
+ return false;
609
+ }
610
+
611
+ const maybeError = error as { name?: string; type?: string };
612
+ return maybeError.name === "AbortError" || maybeError.type === "aborted";
613
+ }
614
+
615
+ function formatBytes(bytes: number): string {
616
+ const units = ["B", "KB", "MB", "GB", "TB"];
617
+ let unitIndex = 0;
618
+ let value = bytes;
619
+
620
+ while (value >= 1024 && unitIndex < units.length - 1) {
621
+ value /= 1024;
622
+ unitIndex += 1;
623
+ }
624
+
625
+ if (unitIndex === 0) {
626
+ return `${value} ${units[unitIndex]}`;
627
+ }
628
+
629
+ return `${value.toFixed(1)} ${units[unitIndex]}`;
630
+ }
631
+
632
+ function displayPath(path: string): string {
633
+ const home = homedir();
634
+ if (path === home) {
635
+ return "~";
636
+ }
637
+
638
+ if (path.startsWith(`${home}/`)) {
639
+ return `~/${path.slice(home.length + 1)}`;
640
+ }
641
+
642
+ return path;
643
+ }
644
+
645
+ function ensureTrailingSlash(path: string): string {
646
+ return path.endsWith("/") ? path : `${path}/`;
647
+ }
648
+
649
+ function formatError(error: unknown): string {
650
+ if (error instanceof Error) {
651
+ return error.message;
652
+ }
653
+
654
+ return String(error);
655
+ }
package/src/cli/index.ts CHANGED
@@ -6,6 +6,7 @@ import { Command } from "commander";
6
6
 
7
7
  import { registerChatCommand, runChatCommand } from "./commands/chat.js";
8
8
  import { registerConfigCommand } from "./commands/config.js";
9
+ import { registerDoctorCommand } from "./commands/doctor.js";
9
10
  import { registerInitCommand } from "./commands/init.js";
10
11
  import { registerNukeCommand } from "./commands/nuke.js";
11
12
  import { registerStatusCommand } from "./commands/status.js";
@@ -21,6 +22,7 @@ program
21
22
  registerInitCommand(program);
22
23
  registerChatCommand(program);
23
24
  registerConfigCommand(program);
25
+ registerDoctorCommand(program);
24
26
  registerStatusCommand(program);
25
27
  registerNukeCommand(program);
26
28