@imisbahk/hive 0.1.2 → 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/doctor.d.ts +8 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +503 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/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 +5 -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 -55
- 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 -46
- 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/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 -66
- 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,11 +1,16 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
1
3
|
import { stdin, stdout } from "node:process";
|
|
2
4
|
import * as readline from "node:readline";
|
|
3
5
|
import { createInterface } from "node:readline/promises";
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
4
7
|
import chalk from "chalk";
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
8
|
+
import fetch from "node-fetch";
|
|
9
|
+
import { RUNTIME_SYSTEM_GUARDRAILS, buildBrowserAugmentedPrompt, HiveAgent, } from "../../agent/agent.js";
|
|
10
|
+
import { closeHiveDatabase, deleteKnowledge, findClosestKnowledge, getPrimaryAgent, getHiveHomeDir, insertKnowledge, listKnowledge, listConversationMessages, listRecentConversations, openHiveDatabase, updateConversationTitle, clearEpisodes, } from "../../storage/db.js";
|
|
7
11
|
import { createProvider } from "../../providers/index.js";
|
|
8
|
-
import { renderError, renderHiveHeader, renderInfo, renderSeparator, } from "../ui.js";
|
|
12
|
+
import { renderError, renderHiveHeader, renderInfo, renderSeparator, renderSuccess, } from "../ui.js";
|
|
13
|
+
import { openPage } from "../../browser/browser.js";
|
|
9
14
|
import { runConfigKeyCommandWithOptions, runConfigModelCommandWithOptions, runConfigProviderCommandWithOptions, runConfigShowCommandWithOptions, runConfigThemeCommandWithOptions, } from "./config.js";
|
|
10
15
|
import { runStatusCommandWithOptions } from "./status.js";
|
|
11
16
|
import { getTheme } from "../theme.js";
|
|
@@ -18,6 +23,24 @@ const COMMAND_HELP_TEXT = [
|
|
|
18
23
|
"Commands:",
|
|
19
24
|
" /help show commands",
|
|
20
25
|
" /new start a new conversation",
|
|
26
|
+
" /remember <fact> save a fact",
|
|
27
|
+
" /forget <thing> delete closest fact",
|
|
28
|
+
" /pin <fact> pin fact into context",
|
|
29
|
+
" /summarize <url> summarize a web page",
|
|
30
|
+
" /tldr summarize this conversation",
|
|
31
|
+
" /recap summarize persona + knowledge",
|
|
32
|
+
" /mode <name> switch response mode",
|
|
33
|
+
" /status show mode/provider/model",
|
|
34
|
+
" /export export conversation markdown",
|
|
35
|
+
" /save <title> name this conversation",
|
|
36
|
+
" /history list recent conversations",
|
|
37
|
+
" /clear clear the screen",
|
|
38
|
+
" /think <question>think step by step",
|
|
39
|
+
" /retry resend last message",
|
|
40
|
+
" /copy copy last reply",
|
|
41
|
+
" /hive memory list show knowledge items",
|
|
42
|
+
" /hive memory clear clear episodic memory",
|
|
43
|
+
" /hive memory show show current persona",
|
|
21
44
|
" /browse <url> read a webpage",
|
|
22
45
|
" browse <url> same as /browse",
|
|
23
46
|
" /search <query> search the web",
|
|
@@ -36,6 +59,9 @@ const HIVE_SHORTCUT_HELP_TEXT = [
|
|
|
36
59
|
" /hive help list shortcuts",
|
|
37
60
|
" /hive status run hive status",
|
|
38
61
|
" /hive config show run hive config show",
|
|
62
|
+
" /hive memory list list knowledge",
|
|
63
|
+
" /hive memory clear clear episodes",
|
|
64
|
+
" /hive memory show show persona",
|
|
39
65
|
"",
|
|
40
66
|
"Interactive config commands (in chat):",
|
|
41
67
|
" /hive config provider",
|
|
@@ -53,6 +79,13 @@ const PREVIEW_AGENT_NAME = "jarvis";
|
|
|
53
79
|
const PREVIEW_PROVIDER = "google";
|
|
54
80
|
const PREVIEW_MODEL = "gemini-2.0-flash";
|
|
55
81
|
const PREVIEW_NEW_MESSAGE = "Started a new preview conversation context.";
|
|
82
|
+
const MODE_PROMPTS = {
|
|
83
|
+
default: null,
|
|
84
|
+
research: "Every answer must be grounded in current web evidence. Perform web search as needed and cite sources inline.",
|
|
85
|
+
code: "Think and respond like a focused software engineer. Prioritize concise technical answers and code.",
|
|
86
|
+
brainstorm: "Be creative and opinionated. Offer bold suggestions and push back on weak ideas when helpful.",
|
|
87
|
+
brief: "Keep every response to a maximum of 3 sentences while preserving key details.",
|
|
88
|
+
};
|
|
56
89
|
const COMMAND_SUGGESTIONS = [
|
|
57
90
|
{
|
|
58
91
|
label: "/help",
|
|
@@ -64,6 +97,81 @@ const COMMAND_SUGGESTIONS = [
|
|
|
64
97
|
insertText: "/new",
|
|
65
98
|
description: "start a new conversation",
|
|
66
99
|
},
|
|
100
|
+
{
|
|
101
|
+
label: "/remember <fact>",
|
|
102
|
+
insertText: "/remember ",
|
|
103
|
+
description: "save to knowledge graph",
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
label: "/pin <fact>",
|
|
107
|
+
insertText: "/pin ",
|
|
108
|
+
description: "pin fact into context",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
label: "/forget <thing>",
|
|
112
|
+
insertText: "/forget ",
|
|
113
|
+
description: "delete closest fact",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
label: "/summarize <url>",
|
|
117
|
+
insertText: "/summarize ",
|
|
118
|
+
description: "summarize a web page",
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
label: "/tldr",
|
|
122
|
+
insertText: "/tldr",
|
|
123
|
+
description: "summarize this conversation",
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
label: "/recap",
|
|
127
|
+
insertText: "/recap",
|
|
128
|
+
description: "summarize persona & knowledge",
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
label: "/mode <name>",
|
|
132
|
+
insertText: "/mode ",
|
|
133
|
+
description: "switch response style",
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
label: "/export",
|
|
137
|
+
insertText: "/export",
|
|
138
|
+
description: "export conversation markdown",
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
label: "/save <title>",
|
|
142
|
+
insertText: "/save ",
|
|
143
|
+
description: "set conversation title",
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
label: "/history",
|
|
147
|
+
insertText: "/history",
|
|
148
|
+
description: "list recent conversations",
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
label: "/status",
|
|
152
|
+
insertText: "/status",
|
|
153
|
+
description: "show session status",
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
label: "/clear",
|
|
157
|
+
insertText: "/clear",
|
|
158
|
+
description: "clear the screen",
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
label: "/think <question>",
|
|
162
|
+
insertText: "/think ",
|
|
163
|
+
description: "think step by step",
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
label: "/retry",
|
|
167
|
+
insertText: "/retry",
|
|
168
|
+
description: "resend last message",
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
label: "/copy",
|
|
172
|
+
insertText: "/copy",
|
|
173
|
+
description: "copy last reply",
|
|
174
|
+
},
|
|
67
175
|
{
|
|
68
176
|
label: "/browse <url>",
|
|
69
177
|
insertText: "/browse ",
|
|
@@ -94,6 +202,21 @@ const COMMAND_SUGGESTIONS = [
|
|
|
94
202
|
insertText: "/hive config show",
|
|
95
203
|
description: "run hive config show",
|
|
96
204
|
},
|
|
205
|
+
{
|
|
206
|
+
label: "/hive memory list",
|
|
207
|
+
insertText: "/hive memory list",
|
|
208
|
+
description: "list knowledge entries",
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
label: "/hive memory clear",
|
|
212
|
+
insertText: "/hive memory clear",
|
|
213
|
+
description: "clear episodic memory",
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
label: "/hive memory show",
|
|
217
|
+
insertText: "/hive memory show",
|
|
218
|
+
description: "show current persona",
|
|
219
|
+
},
|
|
97
220
|
{
|
|
98
221
|
label: "/hive init",
|
|
99
222
|
insertText: "/hive init",
|
|
@@ -142,6 +265,7 @@ export function registerChatCommand(program) {
|
|
|
142
265
|
export async function runChatCommand(options, context = {}) {
|
|
143
266
|
console.clear();
|
|
144
267
|
renderHiveHeader("Chat");
|
|
268
|
+
void checkForUpdates();
|
|
145
269
|
const entrypoint = context.entrypoint ?? "chat-command";
|
|
146
270
|
if (entrypoint === "chat-command") {
|
|
147
271
|
renderInfo("`hive chat` is deprecated. Run `hive`.");
|
|
@@ -162,6 +286,7 @@ export async function runChatCommand(options, context = {}) {
|
|
|
162
286
|
let provider = await createProvider(activeProfile.provider);
|
|
163
287
|
let agent = new HiveAgent(db, provider, activeProfile);
|
|
164
288
|
let agentName = resolveAgentName(activeProfile.agent_name);
|
|
289
|
+
let currentMode = "default";
|
|
165
290
|
const model = options.model ?? activeProfile.model;
|
|
166
291
|
let conversationId = options.conversation;
|
|
167
292
|
const runOptions = {
|
|
@@ -169,6 +294,8 @@ export async function runChatCommand(options, context = {}) {
|
|
|
169
294
|
title: options.title,
|
|
170
295
|
temperature,
|
|
171
296
|
};
|
|
297
|
+
const lastUserPromptRef = { value: null };
|
|
298
|
+
const lastAssistantRef = { value: "" };
|
|
172
299
|
renderChatPreamble({
|
|
173
300
|
agentName,
|
|
174
301
|
provider: profile.provider,
|
|
@@ -178,7 +305,11 @@ export async function runChatCommand(options, context = {}) {
|
|
|
178
305
|
const augmentedMessage = await buildBrowserAugmentedPrompt(options.message, {
|
|
179
306
|
locationHint: profile.location ?? undefined,
|
|
180
307
|
});
|
|
181
|
-
|
|
308
|
+
const systemAddition = getModeSystemPrompt(currentMode);
|
|
309
|
+
lastUserPromptRef.value = options.message;
|
|
310
|
+
const streamResult = await streamReply(agent, augmentedMessage, conversationId, runOptions, agentName, systemAddition);
|
|
311
|
+
conversationId = streamResult.conversationId;
|
|
312
|
+
lastAssistantRef.value = streamResult.assistantText;
|
|
182
313
|
renderInfo(`conversation: ${conversationId}`);
|
|
183
314
|
return;
|
|
184
315
|
}
|
|
@@ -187,25 +318,53 @@ export async function runChatCommand(options, context = {}) {
|
|
|
187
318
|
if (prompt.length === 0) {
|
|
188
319
|
continue;
|
|
189
320
|
}
|
|
321
|
+
const normalizedPrompt = prompt.trim().toLowerCase();
|
|
190
322
|
if (prompt === "/") {
|
|
191
323
|
printChatHelp();
|
|
192
324
|
continue;
|
|
193
325
|
}
|
|
194
|
-
if (
|
|
326
|
+
if (normalizedPrompt === "/help") {
|
|
195
327
|
printChatHelp();
|
|
196
328
|
continue;
|
|
197
329
|
}
|
|
198
|
-
if (
|
|
330
|
+
if (normalizedPrompt === "/exit" || normalizedPrompt === "/quit") {
|
|
199
331
|
break;
|
|
200
332
|
}
|
|
201
|
-
if (
|
|
333
|
+
if (normalizedPrompt === "/new") {
|
|
202
334
|
conversationId = undefined;
|
|
335
|
+
currentMode = "default";
|
|
336
|
+
lastUserPromptRef.value = null;
|
|
337
|
+
lastAssistantRef.value = "";
|
|
203
338
|
renderInfo("Started a new conversation context.");
|
|
204
339
|
continue;
|
|
205
340
|
}
|
|
206
341
|
try {
|
|
342
|
+
const handled = await handleChatSlashCommand({
|
|
343
|
+
prompt,
|
|
344
|
+
db,
|
|
345
|
+
agent,
|
|
346
|
+
provider,
|
|
347
|
+
agentName,
|
|
348
|
+
conversationId,
|
|
349
|
+
activeProfilePersona: activeProfile.persona,
|
|
350
|
+
mode: currentMode,
|
|
351
|
+
providerName: activeProfile.provider,
|
|
352
|
+
modelName: runOptions.model ?? activeProfile.model,
|
|
353
|
+
setConversationId: (id) => {
|
|
354
|
+
conversationId = id;
|
|
355
|
+
},
|
|
356
|
+
setMode: (mode) => {
|
|
357
|
+
currentMode = mode;
|
|
358
|
+
},
|
|
359
|
+
lastUserPromptRef,
|
|
360
|
+
lastAssistantRef,
|
|
361
|
+
});
|
|
362
|
+
if (handled) {
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
207
365
|
const shortcutResult = await handleHiveShortcut(prompt, {
|
|
208
366
|
allowInteractiveConfig: true,
|
|
367
|
+
db,
|
|
209
368
|
});
|
|
210
369
|
if (shortcutResult === "handled") {
|
|
211
370
|
continue;
|
|
@@ -229,14 +388,17 @@ export async function runChatCommand(options, context = {}) {
|
|
|
229
388
|
continue;
|
|
230
389
|
}
|
|
231
390
|
if (isUnknownSlashCommand(prompt)) {
|
|
232
|
-
renderError(
|
|
233
|
-
renderInfo("Run `/help` to view supported commands.");
|
|
391
|
+
renderError("✗ Unknown command. Type /help for available commands.");
|
|
234
392
|
continue;
|
|
235
393
|
}
|
|
236
394
|
const augmentedPrompt = await buildBrowserAugmentedPrompt(prompt, {
|
|
237
395
|
locationHint: profile.location ?? undefined,
|
|
238
396
|
});
|
|
239
|
-
|
|
397
|
+
lastUserPromptRef.value = prompt;
|
|
398
|
+
const systemAddition = getModeSystemPrompt(currentMode);
|
|
399
|
+
const streamResult = await streamReply(agent, augmentedPrompt, conversationId, runOptions, agentName, systemAddition);
|
|
400
|
+
conversationId = streamResult.conversationId;
|
|
401
|
+
lastAssistantRef.value = streamResult.assistantText;
|
|
240
402
|
}
|
|
241
403
|
catch (error) {
|
|
242
404
|
renderError(formatError(error));
|
|
@@ -247,18 +409,21 @@ export async function runChatCommand(options, context = {}) {
|
|
|
247
409
|
closeHiveDatabase(db);
|
|
248
410
|
}
|
|
249
411
|
}
|
|
250
|
-
async function streamReply(agent, prompt, conversationId, options, agentName) {
|
|
412
|
+
async function streamReply(agent, prompt, conversationId, options, agentName, systemAddition) {
|
|
251
413
|
process.stdout.write(getTheme().accent(`${agentName}${PROMPT_SYMBOL} `));
|
|
252
414
|
let activeConversationId = conversationId;
|
|
415
|
+
let assistantText = "";
|
|
253
416
|
for await (const event of agent.chat(prompt, {
|
|
254
417
|
conversationId: activeConversationId,
|
|
255
418
|
model: options.model,
|
|
256
419
|
temperature: options.temperature,
|
|
257
420
|
title: options.title,
|
|
421
|
+
systemAddition,
|
|
258
422
|
})) {
|
|
259
423
|
if (event.type === "token") {
|
|
260
424
|
process.stdout.write(event.token);
|
|
261
425
|
activeConversationId = event.conversationId;
|
|
426
|
+
assistantText += event.token;
|
|
262
427
|
continue;
|
|
263
428
|
}
|
|
264
429
|
activeConversationId = event.conversationId;
|
|
@@ -268,7 +433,7 @@ async function streamReply(agent, prompt, conversationId, options, agentName) {
|
|
|
268
433
|
if (!activeConversationId) {
|
|
269
434
|
throw new Error("Conversation state was not returned by the agent.");
|
|
270
435
|
}
|
|
271
|
-
return activeConversationId;
|
|
436
|
+
return { conversationId: activeConversationId, assistantText };
|
|
272
437
|
}
|
|
273
438
|
function parseTemperature(raw) {
|
|
274
439
|
if (raw === undefined) {
|
|
@@ -355,6 +520,16 @@ function isHiveShortcut(prompt) {
|
|
|
355
520
|
const normalized = prompt.trim().toLowerCase();
|
|
356
521
|
return normalized === HIVE_SHORTCUT_PREFIX || normalized.startsWith(`${HIVE_SHORTCUT_PREFIX} `);
|
|
357
522
|
}
|
|
523
|
+
function getModeSystemPrompt(mode) {
|
|
524
|
+
return MODE_PROMPTS[mode] ?? undefined;
|
|
525
|
+
}
|
|
526
|
+
function combineSystemAdditions(parts) {
|
|
527
|
+
const merged = parts
|
|
528
|
+
.map((part) => part?.trim() ?? "")
|
|
529
|
+
.filter((part) => part.length > 0)
|
|
530
|
+
.join("\n\n");
|
|
531
|
+
return merged.length > 0 ? merged : undefined;
|
|
532
|
+
}
|
|
358
533
|
function isUnknownSlashCommand(prompt) {
|
|
359
534
|
const normalized = prompt.trim().toLowerCase();
|
|
360
535
|
if (!normalized.startsWith("/")) {
|
|
@@ -364,6 +539,26 @@ function isUnknownSlashCommand(prompt) {
|
|
|
364
539
|
normalized === "/new" ||
|
|
365
540
|
normalized === "/exit" ||
|
|
366
541
|
normalized === "/quit" ||
|
|
542
|
+
normalized === "/remember" ||
|
|
543
|
+
normalized.startsWith("/remember ") ||
|
|
544
|
+
normalized === "/forget" ||
|
|
545
|
+
normalized.startsWith("/forget ") ||
|
|
546
|
+
normalized === "/summarize" ||
|
|
547
|
+
normalized.startsWith("/summarize ") ||
|
|
548
|
+
normalized === "/tldr" ||
|
|
549
|
+
normalized === "/recap" ||
|
|
550
|
+
normalized === "/mode" ||
|
|
551
|
+
normalized.startsWith("/mode ") ||
|
|
552
|
+
normalized === "/export" ||
|
|
553
|
+
normalized === "/history" ||
|
|
554
|
+
normalized === "/clear" ||
|
|
555
|
+
normalized === "/think" ||
|
|
556
|
+
normalized.startsWith("/think ") ||
|
|
557
|
+
normalized.startsWith("/save") ||
|
|
558
|
+
normalized.startsWith("/pin") ||
|
|
559
|
+
normalized === "/status" ||
|
|
560
|
+
normalized === "/retry" ||
|
|
561
|
+
normalized === "/copy" ||
|
|
367
562
|
normalized === "/browse" ||
|
|
368
563
|
normalized.startsWith("/browse ") ||
|
|
369
564
|
normalized === "/search" ||
|
|
@@ -400,6 +595,52 @@ async function handleHiveShortcut(prompt, options = {}) {
|
|
|
400
595
|
restoreChatInputAfterInteractiveCommand();
|
|
401
596
|
return "handled";
|
|
402
597
|
}
|
|
598
|
+
if (subcommand === "memory list") {
|
|
599
|
+
const db = options.db;
|
|
600
|
+
if (!db) {
|
|
601
|
+
renderError("Memory commands unavailable: database not open.");
|
|
602
|
+
return "handled";
|
|
603
|
+
}
|
|
604
|
+
const rows = listKnowledge(db, { limit: 1000 });
|
|
605
|
+
if (rows.length === 0) {
|
|
606
|
+
renderInfo("No knowledge stored.");
|
|
607
|
+
return "handled";
|
|
608
|
+
}
|
|
609
|
+
rows.forEach((row, index) => {
|
|
610
|
+
const pinnedLabel = row.pinned ? " (pinned)" : "";
|
|
611
|
+
renderInfo(`${index + 1}. ${row.content}${pinnedLabel}`);
|
|
612
|
+
});
|
|
613
|
+
return "handled";
|
|
614
|
+
}
|
|
615
|
+
if (subcommand === "memory clear") {
|
|
616
|
+
const db = options.db;
|
|
617
|
+
if (!db) {
|
|
618
|
+
renderError("Memory commands unavailable: database not open.");
|
|
619
|
+
return "handled";
|
|
620
|
+
}
|
|
621
|
+
const confirmed = await promptYesNo("This will delete all episodic memories. Continue? (y/n) ");
|
|
622
|
+
if (!confirmed) {
|
|
623
|
+
renderInfo("Cancelled.");
|
|
624
|
+
return "handled";
|
|
625
|
+
}
|
|
626
|
+
clearEpisodes(db);
|
|
627
|
+
renderSuccess("Episodes cleared.");
|
|
628
|
+
return "handled";
|
|
629
|
+
}
|
|
630
|
+
if (subcommand === "memory show") {
|
|
631
|
+
const db = options.db;
|
|
632
|
+
if (!db) {
|
|
633
|
+
renderError("Memory commands unavailable: database not open.");
|
|
634
|
+
return "handled";
|
|
635
|
+
}
|
|
636
|
+
const agent = getPrimaryAgent(db);
|
|
637
|
+
if (!agent) {
|
|
638
|
+
renderError("Hive is not initialized. Run `hive init` first.");
|
|
639
|
+
return "handled";
|
|
640
|
+
}
|
|
641
|
+
renderInfo(agent.persona);
|
|
642
|
+
return "handled";
|
|
643
|
+
}
|
|
403
644
|
if (subcommand === "config provider") {
|
|
404
645
|
if (!options.allowInteractiveConfig) {
|
|
405
646
|
renderInfo("Interactive config commands are unavailable here.");
|
|
@@ -445,6 +686,316 @@ async function handleHiveShortcut(prompt, options = {}) {
|
|
|
445
686
|
renderInfo("Use `/hive help` to list available shortcuts.");
|
|
446
687
|
return "handled";
|
|
447
688
|
}
|
|
689
|
+
async function handleChatSlashCommand(input) {
|
|
690
|
+
const normalized = input.prompt.trim();
|
|
691
|
+
const lower = normalized.toLowerCase();
|
|
692
|
+
if (!lower.startsWith("/")) {
|
|
693
|
+
return false;
|
|
694
|
+
}
|
|
695
|
+
if (lower === "/clear") {
|
|
696
|
+
console.clear();
|
|
697
|
+
renderHiveHeader("Chat");
|
|
698
|
+
renderChatPreamble({
|
|
699
|
+
agentName: input.agentName,
|
|
700
|
+
provider: input.providerName,
|
|
701
|
+
model: input.modelName,
|
|
702
|
+
});
|
|
703
|
+
input.lastUserPromptRef.value = null;
|
|
704
|
+
input.lastAssistantRef.value = "";
|
|
705
|
+
return true;
|
|
706
|
+
}
|
|
707
|
+
if (lower.startsWith("/remember")) {
|
|
708
|
+
const fact = normalized.slice("/remember".length).trim();
|
|
709
|
+
if (fact.length === 0) {
|
|
710
|
+
renderError("Usage: /remember <fact>");
|
|
711
|
+
return true;
|
|
712
|
+
}
|
|
713
|
+
insertKnowledge(input.db, { content: fact });
|
|
714
|
+
renderSuccess("✓ Remembered.");
|
|
715
|
+
input.lastUserPromptRef.value = null;
|
|
716
|
+
return true;
|
|
717
|
+
}
|
|
718
|
+
if (lower.startsWith("/forget")) {
|
|
719
|
+
const query = normalized.slice("/forget".length).trim();
|
|
720
|
+
if (query.length === 0) {
|
|
721
|
+
renderError("Usage: /forget <thing>");
|
|
722
|
+
return true;
|
|
723
|
+
}
|
|
724
|
+
const match = findClosestKnowledge(input.db, query);
|
|
725
|
+
if (!match) {
|
|
726
|
+
renderError("No similar knowledge found.");
|
|
727
|
+
return true;
|
|
728
|
+
}
|
|
729
|
+
const confirmed = await promptYesNo(`Forget "${match.content}"? (y/n) `);
|
|
730
|
+
if (!confirmed) {
|
|
731
|
+
renderInfo("Kept.");
|
|
732
|
+
return true;
|
|
733
|
+
}
|
|
734
|
+
deleteKnowledge(input.db, match.id);
|
|
735
|
+
renderSuccess("✓ Forgotten.");
|
|
736
|
+
input.lastUserPromptRef.value = null;
|
|
737
|
+
return true;
|
|
738
|
+
}
|
|
739
|
+
if (lower.startsWith("/mode")) {
|
|
740
|
+
const modeName = normalized.slice("/mode".length).trim().toLowerCase();
|
|
741
|
+
if (!modeName || !Object.hasOwn(MODE_PROMPTS, modeName)) {
|
|
742
|
+
renderError("Usage: /mode <default|research|code|brainstorm|brief>");
|
|
743
|
+
return true;
|
|
744
|
+
}
|
|
745
|
+
input.setMode(modeName);
|
|
746
|
+
renderSuccess(`✓ Mode set to ${modeName}.`);
|
|
747
|
+
input.lastUserPromptRef.value = null;
|
|
748
|
+
return true;
|
|
749
|
+
}
|
|
750
|
+
if (lower.startsWith("/pin")) {
|
|
751
|
+
const fact = normalized.slice("/pin".length).trim();
|
|
752
|
+
if (fact.length === 0) {
|
|
753
|
+
renderError("Usage: /pin <fact>");
|
|
754
|
+
return true;
|
|
755
|
+
}
|
|
756
|
+
insertKnowledge(input.db, { content: fact, pinned: true });
|
|
757
|
+
renderSuccess("✓ Pinned.");
|
|
758
|
+
input.lastUserPromptRef.value = null;
|
|
759
|
+
return true;
|
|
760
|
+
}
|
|
761
|
+
if (lower === "/export") {
|
|
762
|
+
if (!input.conversationId) {
|
|
763
|
+
renderError("No conversation to export. Start chatting first.");
|
|
764
|
+
return true;
|
|
765
|
+
}
|
|
766
|
+
const messages = listConversationMessages(input.db, input.conversationId);
|
|
767
|
+
const exportDir = join(getHiveHomeDir(), "exports");
|
|
768
|
+
mkdirSync(exportDir, { recursive: true });
|
|
769
|
+
const exportPath = join(exportDir, `${input.conversationId}.md`);
|
|
770
|
+
writeFileSync(exportPath, formatConversationMarkdown(messages, input.conversationId));
|
|
771
|
+
renderSuccess(`✓ Exported to ${exportPath}`);
|
|
772
|
+
input.lastUserPromptRef.value = null;
|
|
773
|
+
return true;
|
|
774
|
+
}
|
|
775
|
+
if (lower === "/history") {
|
|
776
|
+
const rows = listRecentConversations(input.db, 10);
|
|
777
|
+
if (rows.length === 0) {
|
|
778
|
+
renderInfo("No past conversations found.");
|
|
779
|
+
return true;
|
|
780
|
+
}
|
|
781
|
+
rows.forEach((row, index) => {
|
|
782
|
+
const title = row.title?.trim().length ? row.title : "(untitled)";
|
|
783
|
+
renderInfo(`${index + 1}. ${title} · ${row.updated_at} · ${row.message_count} messages`);
|
|
784
|
+
});
|
|
785
|
+
const answer = await promptLine("Pick a conversation number (or blank to cancel): ");
|
|
786
|
+
if (!answer) {
|
|
787
|
+
return true;
|
|
788
|
+
}
|
|
789
|
+
const choice = Number.parseInt(answer, 10);
|
|
790
|
+
if (Number.isNaN(choice) || choice < 1 || choice > rows.length) {
|
|
791
|
+
renderError("Invalid selection.");
|
|
792
|
+
return true;
|
|
793
|
+
}
|
|
794
|
+
const selected = rows[choice - 1];
|
|
795
|
+
input.setConversationId(selected.id);
|
|
796
|
+
renderInfo(`Continuing conversation ${selected.title ?? selected.id}.`);
|
|
797
|
+
input.lastUserPromptRef.value = null;
|
|
798
|
+
input.lastAssistantRef.value = "";
|
|
799
|
+
return true;
|
|
800
|
+
}
|
|
801
|
+
if (lower === "/tldr") {
|
|
802
|
+
if (!input.conversationId) {
|
|
803
|
+
renderError("No conversation yet. Say something first.");
|
|
804
|
+
return true;
|
|
805
|
+
}
|
|
806
|
+
const history = listConversationMessages(input.db, input.conversationId);
|
|
807
|
+
if (history.length === 0) {
|
|
808
|
+
renderError("Nothing to summarize yet.");
|
|
809
|
+
return true;
|
|
810
|
+
}
|
|
811
|
+
await streamEphemeral({
|
|
812
|
+
provider: input.provider,
|
|
813
|
+
agentName: input.agentName,
|
|
814
|
+
model: input.modelName,
|
|
815
|
+
messages: buildEphemeralMessages({
|
|
816
|
+
persona: input.activeProfilePersona,
|
|
817
|
+
mode: input.mode,
|
|
818
|
+
systemInstruction: "Summarize this conversation in 3-5 bullet points.",
|
|
819
|
+
history,
|
|
820
|
+
}),
|
|
821
|
+
});
|
|
822
|
+
input.lastUserPromptRef.value = null;
|
|
823
|
+
return true;
|
|
824
|
+
}
|
|
825
|
+
if (lower === "/recap") {
|
|
826
|
+
const knowledge = listKnowledge(input.db, { limit: 500 });
|
|
827
|
+
await streamEphemeral({
|
|
828
|
+
provider: input.provider,
|
|
829
|
+
agentName: input.agentName,
|
|
830
|
+
model: input.modelName,
|
|
831
|
+
messages: buildRecapMessages({
|
|
832
|
+
persona: input.activeProfilePersona,
|
|
833
|
+
knowledge,
|
|
834
|
+
mode: input.mode,
|
|
835
|
+
}),
|
|
836
|
+
});
|
|
837
|
+
input.lastUserPromptRef.value = null;
|
|
838
|
+
return true;
|
|
839
|
+
}
|
|
840
|
+
if (lower.startsWith("/summarize")) {
|
|
841
|
+
const url = normalized.slice("/summarize".length).trim();
|
|
842
|
+
if (url.length === 0) {
|
|
843
|
+
renderError("Usage: /summarize <url>");
|
|
844
|
+
return true;
|
|
845
|
+
}
|
|
846
|
+
try {
|
|
847
|
+
const content = await openPage(url);
|
|
848
|
+
await streamEphemeral({
|
|
849
|
+
provider: input.provider,
|
|
850
|
+
agentName: input.agentName,
|
|
851
|
+
model: input.modelName,
|
|
852
|
+
messages: buildEphemeralMessages({
|
|
853
|
+
persona: input.activeProfilePersona,
|
|
854
|
+
mode: input.mode,
|
|
855
|
+
userMessage: `Summarize this page concisely:\n\n${content}`,
|
|
856
|
+
}),
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
catch (error) {
|
|
860
|
+
renderError(`Unable to summarize page: ${formatError(error)}`);
|
|
861
|
+
}
|
|
862
|
+
return true;
|
|
863
|
+
}
|
|
864
|
+
if (lower.startsWith("/save")) {
|
|
865
|
+
const title = normalized.slice("/save".length).trim();
|
|
866
|
+
if (!input.conversationId) {
|
|
867
|
+
renderError("No active conversation to title.");
|
|
868
|
+
return true;
|
|
869
|
+
}
|
|
870
|
+
if (title.length === 0) {
|
|
871
|
+
renderError("Usage: /save <title>");
|
|
872
|
+
return true;
|
|
873
|
+
}
|
|
874
|
+
updateConversationTitle(input.db, input.conversationId, title);
|
|
875
|
+
renderSuccess(`✓ Saved title "${title}".`);
|
|
876
|
+
input.lastUserPromptRef.value = null;
|
|
877
|
+
return true;
|
|
878
|
+
}
|
|
879
|
+
if (lower.startsWith("/think")) {
|
|
880
|
+
const question = normalized.slice("/think".length).trim();
|
|
881
|
+
if (question.length === 0) {
|
|
882
|
+
renderError("Usage: /think <question>");
|
|
883
|
+
return true;
|
|
884
|
+
}
|
|
885
|
+
await streamEphemeral({
|
|
886
|
+
provider: input.provider,
|
|
887
|
+
agentName: input.agentName,
|
|
888
|
+
model: input.modelName,
|
|
889
|
+
messages: buildEphemeralMessages({
|
|
890
|
+
persona: input.activeProfilePersona,
|
|
891
|
+
mode: input.mode,
|
|
892
|
+
userMessage: `Think through this step by step, show your reasoning:\n\n${question}`,
|
|
893
|
+
}),
|
|
894
|
+
});
|
|
895
|
+
return true;
|
|
896
|
+
}
|
|
897
|
+
if (lower === "/status") {
|
|
898
|
+
const info = [
|
|
899
|
+
`mode=${input.mode}`,
|
|
900
|
+
`provider=${input.providerName}`,
|
|
901
|
+
`model=${input.modelName}`,
|
|
902
|
+
`conversation=${input.conversationId ?? "none"}`,
|
|
903
|
+
].join(" · ");
|
|
904
|
+
renderInfo(info);
|
|
905
|
+
return true;
|
|
906
|
+
}
|
|
907
|
+
if (lower === "/retry") {
|
|
908
|
+
const userPrompt = input.lastUserPromptRef.value;
|
|
909
|
+
if (!userPrompt) {
|
|
910
|
+
renderError("Nothing to retry yet.");
|
|
911
|
+
return true;
|
|
912
|
+
}
|
|
913
|
+
const systemAddition = getModeSystemPrompt(input.mode);
|
|
914
|
+
const retryResult = await streamReply(input.agent, userPrompt, input.conversationId, { model: input.modelName }, input.agentName, systemAddition);
|
|
915
|
+
input.setConversationId(retryResult.conversationId);
|
|
916
|
+
input.lastAssistantRef.value = retryResult.assistantText;
|
|
917
|
+
return true;
|
|
918
|
+
}
|
|
919
|
+
if (lower === "/copy") {
|
|
920
|
+
if (!input.lastAssistantRef.value) {
|
|
921
|
+
renderError("Nothing to copy yet.");
|
|
922
|
+
return true;
|
|
923
|
+
}
|
|
924
|
+
const copied = copyToClipboard(input.lastAssistantRef.value);
|
|
925
|
+
if (copied) {
|
|
926
|
+
renderSuccess("✓ Copied last reply to clipboard.");
|
|
927
|
+
}
|
|
928
|
+
else {
|
|
929
|
+
renderError("Clipboard tool not available.");
|
|
930
|
+
}
|
|
931
|
+
return true;
|
|
932
|
+
}
|
|
933
|
+
return false;
|
|
934
|
+
}
|
|
935
|
+
function copyToClipboard(text) {
|
|
936
|
+
const platform = process.platform;
|
|
937
|
+
const buffer = Buffer.from(text, "utf8");
|
|
938
|
+
if (platform === "darwin") {
|
|
939
|
+
const result = spawnSync("pbcopy", [], { input: buffer });
|
|
940
|
+
return result.status === 0;
|
|
941
|
+
}
|
|
942
|
+
const result = spawnSync("xclip", ["-selection", "clipboard"], { input: buffer });
|
|
943
|
+
return result.status === 0;
|
|
944
|
+
}
|
|
945
|
+
let cachedLocalVersion = null;
|
|
946
|
+
async function checkForUpdates() {
|
|
947
|
+
try {
|
|
948
|
+
const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 3000));
|
|
949
|
+
const latest = (await Promise.race([
|
|
950
|
+
fetch("https://registry.npmjs.org/@imisbahk/hive/latest").then((response) => response.json()),
|
|
951
|
+
timeout,
|
|
952
|
+
]));
|
|
953
|
+
if (!latest?.version || typeof latest.version !== "string") {
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
const localVersion = getLocalVersion();
|
|
957
|
+
if (isVersionNewer(latest.version, localVersion)) {
|
|
958
|
+
const amber = chalk.hex("#ffbf00");
|
|
959
|
+
console.log(amber.dim(`✦ Update available v${localVersion} → npm update -g @imisbahk/hive`));
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
catch {
|
|
963
|
+
// Silently ignore update check failures.
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
function getLocalVersion() {
|
|
967
|
+
if (cachedLocalVersion) {
|
|
968
|
+
return cachedLocalVersion;
|
|
969
|
+
}
|
|
970
|
+
try {
|
|
971
|
+
const raw = readFileSync(new URL("../../../package.json", import.meta.url), "utf8");
|
|
972
|
+
const parsed = JSON.parse(raw);
|
|
973
|
+
if (parsed.version) {
|
|
974
|
+
cachedLocalVersion = parsed.version;
|
|
975
|
+
return cachedLocalVersion;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
catch {
|
|
979
|
+
// ignore
|
|
980
|
+
}
|
|
981
|
+
cachedLocalVersion = "0.0.0";
|
|
982
|
+
return cachedLocalVersion;
|
|
983
|
+
}
|
|
984
|
+
function isVersionNewer(remote, local) {
|
|
985
|
+
const toNumbers = (value) => value.split(".").map((part) => Number.parseInt(part, 10));
|
|
986
|
+
const r = toNumbers(remote);
|
|
987
|
+
const l = toNumbers(local);
|
|
988
|
+
const length = Math.max(r.length, l.length);
|
|
989
|
+
for (let index = 0; index < length; index += 1) {
|
|
990
|
+
const rv = r[index] ?? 0;
|
|
991
|
+
const lv = l[index] ?? 0;
|
|
992
|
+
if (rv > lv)
|
|
993
|
+
return true;
|
|
994
|
+
if (rv < lv)
|
|
995
|
+
return false;
|
|
996
|
+
}
|
|
997
|
+
return false;
|
|
998
|
+
}
|
|
448
999
|
function getCommandSuggestions(input) {
|
|
449
1000
|
const normalized = input.trimStart().toLowerCase();
|
|
450
1001
|
if (!normalized.startsWith("/")) {
|
|
@@ -456,6 +1007,85 @@ function getCommandSuggestions(input) {
|
|
|
456
1007
|
suggestion.label.toLowerCase().includes(normalized.slice(1)));
|
|
457
1008
|
return [...prefixMatches, ...fallbackMatches];
|
|
458
1009
|
}
|
|
1010
|
+
async function promptLine(question) {
|
|
1011
|
+
const rl = createInterface({
|
|
1012
|
+
input: stdin,
|
|
1013
|
+
output: stdout,
|
|
1014
|
+
terminal: true,
|
|
1015
|
+
});
|
|
1016
|
+
try {
|
|
1017
|
+
return (await rl.question(question)).trim();
|
|
1018
|
+
}
|
|
1019
|
+
finally {
|
|
1020
|
+
rl.close();
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
async function promptYesNo(question) {
|
|
1024
|
+
const answer = (await promptLine(question)).toLowerCase();
|
|
1025
|
+
return answer === "y" || answer === "yes";
|
|
1026
|
+
}
|
|
1027
|
+
async function streamEphemeral(input) {
|
|
1028
|
+
process.stdout.write(getTheme().accent(`${input.agentName}${PROMPT_SYMBOL} `));
|
|
1029
|
+
let hadOutput = false;
|
|
1030
|
+
for await (const token of input.provider.streamChat({
|
|
1031
|
+
model: input.model ?? input.provider.defaultModel,
|
|
1032
|
+
messages: input.messages,
|
|
1033
|
+
})) {
|
|
1034
|
+
hadOutput = true;
|
|
1035
|
+
process.stdout.write(token);
|
|
1036
|
+
}
|
|
1037
|
+
if (!hadOutput) {
|
|
1038
|
+
process.stdout.write("(no response)");
|
|
1039
|
+
}
|
|
1040
|
+
process.stdout.write("\n");
|
|
1041
|
+
renderSeparator(EXCHANGE_SEPARATOR);
|
|
1042
|
+
}
|
|
1043
|
+
function buildEphemeralMessages(input) {
|
|
1044
|
+
const messages = [
|
|
1045
|
+
{ role: "system", content: RUNTIME_SYSTEM_GUARDRAILS },
|
|
1046
|
+
{ role: "system", content: input.persona },
|
|
1047
|
+
];
|
|
1048
|
+
const modePrompt = getModeSystemPrompt(input.mode);
|
|
1049
|
+
if (modePrompt) {
|
|
1050
|
+
messages.push({ role: "system", content: modePrompt });
|
|
1051
|
+
}
|
|
1052
|
+
if (input.systemInstruction) {
|
|
1053
|
+
messages.push({ role: "system", content: input.systemInstruction });
|
|
1054
|
+
}
|
|
1055
|
+
if (input.history) {
|
|
1056
|
+
messages.push(...input.history.map((message) => ({
|
|
1057
|
+
role: message.role,
|
|
1058
|
+
content: message.content,
|
|
1059
|
+
})));
|
|
1060
|
+
}
|
|
1061
|
+
if (input.userMessage) {
|
|
1062
|
+
messages.push({ role: "user", content: input.userMessage });
|
|
1063
|
+
}
|
|
1064
|
+
return messages;
|
|
1065
|
+
}
|
|
1066
|
+
function buildRecapMessages(input) {
|
|
1067
|
+
const knowledgeLines = input.knowledge.length > 0
|
|
1068
|
+
? input.knowledge.map((row) => `- ${row.content}`).join("\n")
|
|
1069
|
+
: "No knowledge stored yet.";
|
|
1070
|
+
const userMessage = `Summarize everything you know about the user based on persona and knowledge facts below. Be concise.\n\nPersona:\n${input.persona}\n\nKnowledge facts:\n${knowledgeLines}`;
|
|
1071
|
+
return buildEphemeralMessages({
|
|
1072
|
+
persona: input.persona,
|
|
1073
|
+
mode: input.mode,
|
|
1074
|
+
userMessage,
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
function formatConversationMarkdown(messages, conversationId) {
|
|
1078
|
+
const lines = [`# Conversation ${conversationId}`, ""];
|
|
1079
|
+
for (const message of messages) {
|
|
1080
|
+
const speaker = message.role === "user"
|
|
1081
|
+
? "User"
|
|
1082
|
+
: message.role === "assistant"
|
|
1083
|
+
? "Hive"
|
|
1084
|
+
: "System";
|
|
1085
|
+
lines.push(`**${speaker}:**`, message.content, "");
|
|
1086
|
+
}
|
|
1087
|
+
return lines.join("\n");
|
|
1088
|
+
}
|
|
459
1089
|
async function readPromptWithSuggestions() {
|
|
460
1090
|
const accent = getTheme().accent;
|
|
461
1091
|
const promptPrefix = accent(USER_PROMPT);
|