@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
package/src/cli/commands/chat.ts
DELETED
|
@@ -1,864 +0,0 @@
|
|
|
1
|
-
import { stdin, stdout } from "node:process";
|
|
2
|
-
import * as readline from "node:readline";
|
|
3
|
-
import { createInterface } from "node:readline/promises";
|
|
4
|
-
|
|
5
|
-
import chalk from "chalk";
|
|
6
|
-
import { Command } from "commander";
|
|
7
|
-
|
|
8
|
-
import { buildBrowserAugmentedPrompt, HiveAgent } from "../../agent/agent.js";
|
|
9
|
-
import {
|
|
10
|
-
closeHiveDatabase,
|
|
11
|
-
getPrimaryAgent,
|
|
12
|
-
openHiveDatabase,
|
|
13
|
-
} from "../../storage/db.js";
|
|
14
|
-
import { createProvider } from "../../providers/index.js";
|
|
15
|
-
import {
|
|
16
|
-
renderError,
|
|
17
|
-
renderHiveHeader,
|
|
18
|
-
renderInfo,
|
|
19
|
-
renderSeparator,
|
|
20
|
-
} from "../ui.js";
|
|
21
|
-
import {
|
|
22
|
-
runConfigKeyCommandWithOptions,
|
|
23
|
-
runConfigModelCommandWithOptions,
|
|
24
|
-
runConfigProviderCommandWithOptions,
|
|
25
|
-
runConfigShowCommandWithOptions,
|
|
26
|
-
runConfigThemeCommandWithOptions,
|
|
27
|
-
} from "./config.js";
|
|
28
|
-
import { runStatusCommandWithOptions } from "./status.js";
|
|
29
|
-
import { getTheme } from "../theme.js";
|
|
30
|
-
|
|
31
|
-
interface ChatCommandOptions {
|
|
32
|
-
message?: string;
|
|
33
|
-
conversation?: string;
|
|
34
|
-
model?: string;
|
|
35
|
-
title?: string;
|
|
36
|
-
temperature?: string;
|
|
37
|
-
preview?: boolean;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
interface RunChatOptions {
|
|
41
|
-
model?: string;
|
|
42
|
-
title?: string;
|
|
43
|
-
temperature?: number;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
interface RunChatCommandContext {
|
|
47
|
-
entrypoint?: "default" | "chat-command";
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
interface CommandSuggestion {
|
|
51
|
-
label: string;
|
|
52
|
-
insertText: string;
|
|
53
|
-
description: string;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
type HiveShortcutResult = "not-handled" | "handled" | "config-updated";
|
|
57
|
-
|
|
58
|
-
const PROMPT_SYMBOL = "›";
|
|
59
|
-
const USER_PROMPT = `you${PROMPT_SYMBOL} `;
|
|
60
|
-
const HIVE_SHORTCUT_PREFIX = "/hive";
|
|
61
|
-
const MAX_COMMAND_SUGGESTIONS = 8;
|
|
62
|
-
const COMMAND_LABEL_WIDTH = 24;
|
|
63
|
-
const COMMAND_HELP_TEXT = [
|
|
64
|
-
"Commands:",
|
|
65
|
-
" /help show commands",
|
|
66
|
-
" /new start a new conversation",
|
|
67
|
-
" /browse <url> read a webpage",
|
|
68
|
-
" browse <url> same as /browse",
|
|
69
|
-
" /search <query> search the web",
|
|
70
|
-
" search <query> same as /search",
|
|
71
|
-
" /hive help show Hive command shortcuts",
|
|
72
|
-
" /hive status run `hive status`",
|
|
73
|
-
" /hive config show run `hive config show`",
|
|
74
|
-
" /hive config provider interactive provider setup",
|
|
75
|
-
" /hive config model interactive model setup",
|
|
76
|
-
" /hive config key interactive key setup",
|
|
77
|
-
" /hive config theme interactive theme setup",
|
|
78
|
-
" /exit quit",
|
|
79
|
-
].join("\n");
|
|
80
|
-
const HIVE_SHORTCUT_HELP_TEXT = [
|
|
81
|
-
"Hive shortcuts:",
|
|
82
|
-
" /hive help list shortcuts",
|
|
83
|
-
" /hive status run hive status",
|
|
84
|
-
" /hive config show run hive config show",
|
|
85
|
-
"",
|
|
86
|
-
"Interactive config commands (in chat):",
|
|
87
|
-
" /hive config provider",
|
|
88
|
-
" /hive config model",
|
|
89
|
-
" /hive config key",
|
|
90
|
-
" /hive config theme",
|
|
91
|
-
"",
|
|
92
|
-
"Safety commands still run from shell:",
|
|
93
|
-
" /hive init",
|
|
94
|
-
" /hive nuke",
|
|
95
|
-
].join("\n");
|
|
96
|
-
const CHAT_HINT_TEXT = "? for help | /exit to quit";
|
|
97
|
-
const EXCHANGE_SEPARATOR = "────";
|
|
98
|
-
const PREVIEW_AGENT_NAME = "jarvis";
|
|
99
|
-
const PREVIEW_PROVIDER = "google";
|
|
100
|
-
const PREVIEW_MODEL = "gemini-2.0-flash";
|
|
101
|
-
const PREVIEW_NEW_MESSAGE = "Started a new preview conversation context.";
|
|
102
|
-
const COMMAND_SUGGESTIONS: CommandSuggestion[] = [
|
|
103
|
-
{
|
|
104
|
-
label: "/help",
|
|
105
|
-
insertText: "/help",
|
|
106
|
-
description: "show chat commands",
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
label: "/new",
|
|
110
|
-
insertText: "/new",
|
|
111
|
-
description: "start a new conversation",
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
label: "/browse <url>",
|
|
115
|
-
insertText: "/browse ",
|
|
116
|
-
description: "read a webpage",
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
label: "/search <query>",
|
|
120
|
-
insertText: "/search ",
|
|
121
|
-
description: "search the web",
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
label: "/exit",
|
|
125
|
-
insertText: "/exit",
|
|
126
|
-
description: "quit chat",
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
label: "/hive help",
|
|
130
|
-
insertText: "/hive help",
|
|
131
|
-
description: "show Hive command shortcuts",
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
label: "/hive status",
|
|
135
|
-
insertText: "/hive status",
|
|
136
|
-
description: "run hive status",
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
label: "/hive config show",
|
|
140
|
-
insertText: "/hive config show",
|
|
141
|
-
description: "run hive config show",
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
label: "/hive init",
|
|
145
|
-
insertText: "/hive init",
|
|
146
|
-
description: "run hive init (outside chat)",
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
label: "/hive config provider",
|
|
150
|
-
insertText: "/hive config provider",
|
|
151
|
-
description: "interactive provider setup",
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
label: "/hive config model",
|
|
155
|
-
insertText: "/hive config model",
|
|
156
|
-
description: "interactive model setup",
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
label: "/hive config key",
|
|
160
|
-
insertText: "/hive config key",
|
|
161
|
-
description: "interactive key setup",
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
label: "/hive config theme",
|
|
165
|
-
insertText: "/hive config theme",
|
|
166
|
-
description: "interactive theme setup",
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
label: "/hive nuke",
|
|
170
|
-
insertText: "/hive nuke",
|
|
171
|
-
description: "run hive nuke (outside chat)",
|
|
172
|
-
},
|
|
173
|
-
];
|
|
174
|
-
|
|
175
|
-
export function registerChatCommand(program: Command): void {
|
|
176
|
-
program
|
|
177
|
-
.command("chat")
|
|
178
|
-
.description("(Deprecated) Talk to your Hive agent. Use `hive`.")
|
|
179
|
-
.option("-m, --message <text>", "send a single message and exit")
|
|
180
|
-
.option("-c, --conversation <id>", "continue an existing conversation")
|
|
181
|
-
.option("--model <model>", "override model for this session")
|
|
182
|
-
.option("--title <title>", "title for a newly created conversation")
|
|
183
|
-
.option("-t, --temperature <value>", "sampling temperature")
|
|
184
|
-
.option("--preview", "run chat UI preview without Hive initialization")
|
|
185
|
-
.action(async (options: ChatCommandOptions) => {
|
|
186
|
-
await runChatCommand(options, { entrypoint: "chat-command" });
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export async function runChatCommand(
|
|
191
|
-
options: ChatCommandOptions,
|
|
192
|
-
context: RunChatCommandContext = {},
|
|
193
|
-
): Promise<void> {
|
|
194
|
-
console.clear();
|
|
195
|
-
renderHiveHeader("Chat");
|
|
196
|
-
|
|
197
|
-
const entrypoint = context.entrypoint ?? "chat-command";
|
|
198
|
-
if (entrypoint === "chat-command") {
|
|
199
|
-
renderInfo("`hive chat` is deprecated. Run `hive`.");
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (options.preview) {
|
|
203
|
-
await runPreviewSession(options);
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const temperature = parseTemperature(options.temperature);
|
|
208
|
-
const db = openHiveDatabase();
|
|
209
|
-
|
|
210
|
-
try {
|
|
211
|
-
const profile = getPrimaryAgent(db);
|
|
212
|
-
if (!profile) {
|
|
213
|
-
renderError("Hive is not initialized. Run `hive init` first.");
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
let activeProfile = profile;
|
|
218
|
-
let provider = await createProvider(activeProfile.provider);
|
|
219
|
-
let agent = new HiveAgent(db, provider, activeProfile);
|
|
220
|
-
let agentName = resolveAgentName(activeProfile.agent_name);
|
|
221
|
-
const model = options.model ?? activeProfile.model;
|
|
222
|
-
|
|
223
|
-
let conversationId = options.conversation;
|
|
224
|
-
const runOptions: RunChatOptions = {
|
|
225
|
-
model,
|
|
226
|
-
title: options.title,
|
|
227
|
-
temperature,
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
renderChatPreamble({
|
|
231
|
-
agentName,
|
|
232
|
-
provider: profile.provider,
|
|
233
|
-
model,
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
if (options.message) {
|
|
237
|
-
const augmentedMessage = await buildBrowserAugmentedPrompt(options.message, {
|
|
238
|
-
locationHint: profile.location ?? undefined,
|
|
239
|
-
});
|
|
240
|
-
conversationId = await streamReply(
|
|
241
|
-
agent,
|
|
242
|
-
augmentedMessage,
|
|
243
|
-
conversationId,
|
|
244
|
-
runOptions,
|
|
245
|
-
agentName,
|
|
246
|
-
);
|
|
247
|
-
renderInfo(`conversation: ${conversationId}`);
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
while (true) {
|
|
252
|
-
const prompt = await readPromptWithSuggestions();
|
|
253
|
-
|
|
254
|
-
if (prompt.length === 0) {
|
|
255
|
-
continue;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (prompt === "/") {
|
|
259
|
-
printChatHelp();
|
|
260
|
-
continue;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
if (prompt === "/help") {
|
|
264
|
-
printChatHelp();
|
|
265
|
-
continue;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if (prompt === "/exit" || prompt === "/quit") {
|
|
269
|
-
break;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
if (prompt === "/new") {
|
|
273
|
-
conversationId = undefined;
|
|
274
|
-
renderInfo("Started a new conversation context.");
|
|
275
|
-
continue;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
try {
|
|
279
|
-
const shortcutResult = await handleHiveShortcut(prompt, {
|
|
280
|
-
allowInteractiveConfig: true,
|
|
281
|
-
});
|
|
282
|
-
if (shortcutResult === "handled") {
|
|
283
|
-
continue;
|
|
284
|
-
}
|
|
285
|
-
if (shortcutResult === "config-updated") {
|
|
286
|
-
const latestProfile = getPrimaryAgent(db);
|
|
287
|
-
if (!latestProfile) {
|
|
288
|
-
renderError("Hive is not initialized. Run `hive init` first.");
|
|
289
|
-
continue;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
activeProfile = latestProfile;
|
|
293
|
-
provider = await createProvider(activeProfile.provider);
|
|
294
|
-
agent = new HiveAgent(db, provider, activeProfile);
|
|
295
|
-
agentName = resolveAgentName(activeProfile.agent_name);
|
|
296
|
-
if (!options.model) {
|
|
297
|
-
runOptions.model = activeProfile.model;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
conversationId = undefined;
|
|
301
|
-
renderInfo(
|
|
302
|
-
`Switched to ${activeProfile.provider} · ${runOptions.model ?? activeProfile.model}.`,
|
|
303
|
-
);
|
|
304
|
-
renderInfo("Started a new conversation context.");
|
|
305
|
-
continue;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
if (isUnknownSlashCommand(prompt)) {
|
|
309
|
-
renderError(`Unknown command: ${prompt}`);
|
|
310
|
-
renderInfo("Run `/help` to view supported commands.");
|
|
311
|
-
continue;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
const augmentedPrompt = await buildBrowserAugmentedPrompt(prompt, {
|
|
315
|
-
locationHint: profile.location ?? undefined,
|
|
316
|
-
});
|
|
317
|
-
conversationId = await streamReply(
|
|
318
|
-
agent,
|
|
319
|
-
augmentedPrompt,
|
|
320
|
-
conversationId,
|
|
321
|
-
runOptions,
|
|
322
|
-
agentName,
|
|
323
|
-
);
|
|
324
|
-
} catch (error) {
|
|
325
|
-
renderError(formatError(error));
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
} finally {
|
|
329
|
-
closeHiveDatabase(db);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
async function streamReply(
|
|
334
|
-
agent: HiveAgent,
|
|
335
|
-
prompt: string,
|
|
336
|
-
conversationId: string | undefined,
|
|
337
|
-
options: RunChatOptions,
|
|
338
|
-
agentName: string,
|
|
339
|
-
): Promise<string> {
|
|
340
|
-
process.stdout.write(getTheme().accent(`${agentName}${PROMPT_SYMBOL} `));
|
|
341
|
-
|
|
342
|
-
let activeConversationId = conversationId;
|
|
343
|
-
|
|
344
|
-
for await (const event of agent.chat(prompt, {
|
|
345
|
-
conversationId: activeConversationId,
|
|
346
|
-
model: options.model,
|
|
347
|
-
temperature: options.temperature,
|
|
348
|
-
title: options.title,
|
|
349
|
-
})) {
|
|
350
|
-
if (event.type === "token") {
|
|
351
|
-
process.stdout.write(event.token);
|
|
352
|
-
activeConversationId = event.conversationId;
|
|
353
|
-
continue;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
activeConversationId = event.conversationId;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
process.stdout.write("\n");
|
|
360
|
-
renderSeparator(EXCHANGE_SEPARATOR);
|
|
361
|
-
|
|
362
|
-
if (!activeConversationId) {
|
|
363
|
-
throw new Error("Conversation state was not returned by the agent.");
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
return activeConversationId;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
function parseTemperature(raw?: string): number | undefined {
|
|
370
|
-
if (raw === undefined) {
|
|
371
|
-
return undefined;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
const parsed = Number.parseFloat(raw);
|
|
375
|
-
if (Number.isNaN(parsed) || parsed < 0 || parsed > 2) {
|
|
376
|
-
throw new Error("Temperature must be a number between 0 and 2.");
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
return parsed;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
function formatError(error: unknown): string {
|
|
383
|
-
if (error instanceof Error) {
|
|
384
|
-
return error.message;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
return String(error);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
function resolveAgentName(agentName: string | null | undefined): string {
|
|
391
|
-
const normalized = agentName?.trim();
|
|
392
|
-
if (normalized && normalized.length > 0) {
|
|
393
|
-
return normalized;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
return "hive";
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
function renderChatPreamble(input: {
|
|
400
|
-
agentName: string;
|
|
401
|
-
provider: string;
|
|
402
|
-
model: string;
|
|
403
|
-
}): void {
|
|
404
|
-
renderInfo(`${input.agentName} · ${input.provider} · ${input.model}`);
|
|
405
|
-
renderInfo(CHAT_HINT_TEXT);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
function printChatHelp(): void {
|
|
409
|
-
renderInfo(COMMAND_HELP_TEXT);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
async function runPreviewSession(options: ChatCommandOptions): Promise<void> {
|
|
413
|
-
const model = options.model ?? PREVIEW_MODEL;
|
|
414
|
-
const agentName = PREVIEW_AGENT_NAME;
|
|
415
|
-
|
|
416
|
-
renderChatPreamble({
|
|
417
|
-
agentName,
|
|
418
|
-
provider: PREVIEW_PROVIDER,
|
|
419
|
-
model,
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
if (options.message) {
|
|
423
|
-
await streamPreviewReply(options.message, agentName);
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
while (true) {
|
|
428
|
-
const prompt = await readPromptWithSuggestions();
|
|
429
|
-
|
|
430
|
-
if (prompt.length === 0) {
|
|
431
|
-
continue;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
if (prompt === "/") {
|
|
435
|
-
printChatHelp();
|
|
436
|
-
continue;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
if (prompt === "/help") {
|
|
440
|
-
printChatHelp();
|
|
441
|
-
continue;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
if (prompt === "/exit" || prompt === "/quit") {
|
|
445
|
-
break;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
if (prompt === "/new") {
|
|
449
|
-
renderInfo(PREVIEW_NEW_MESSAGE);
|
|
450
|
-
continue;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
if (isHiveShortcut(prompt)) {
|
|
454
|
-
renderInfo("Hive shortcuts are unavailable in preview mode.");
|
|
455
|
-
continue;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
if (isUnknownSlashCommand(prompt)) {
|
|
459
|
-
renderError(`Unknown command: ${prompt}`);
|
|
460
|
-
renderInfo("Run `/help` to view supported commands.");
|
|
461
|
-
continue;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
await streamPreviewReply(prompt, agentName);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
async function streamPreviewReply(prompt: string, agentName: string): Promise<void> {
|
|
469
|
-
const response = `preview mode: received "${prompt}"`;
|
|
470
|
-
process.stdout.write(getTheme().accent(`${agentName}${PROMPT_SYMBOL} `));
|
|
471
|
-
process.stdout.write(response);
|
|
472
|
-
process.stdout.write("\n");
|
|
473
|
-
renderSeparator(EXCHANGE_SEPARATOR);
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
function isHiveShortcut(prompt: string): boolean {
|
|
477
|
-
const normalized = prompt.trim().toLowerCase();
|
|
478
|
-
return normalized === HIVE_SHORTCUT_PREFIX || normalized.startsWith(`${HIVE_SHORTCUT_PREFIX} `);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
function isUnknownSlashCommand(prompt: string): boolean {
|
|
482
|
-
const normalized = prompt.trim().toLowerCase();
|
|
483
|
-
if (!normalized.startsWith("/")) {
|
|
484
|
-
return false;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
if (
|
|
488
|
-
normalized === "/help" ||
|
|
489
|
-
normalized === "/new" ||
|
|
490
|
-
normalized === "/exit" ||
|
|
491
|
-
normalized === "/quit" ||
|
|
492
|
-
normalized === "/browse" ||
|
|
493
|
-
normalized.startsWith("/browse ") ||
|
|
494
|
-
normalized === "/search" ||
|
|
495
|
-
normalized.startsWith("/search ") ||
|
|
496
|
-
normalized === HIVE_SHORTCUT_PREFIX ||
|
|
497
|
-
normalized.startsWith(`${HIVE_SHORTCUT_PREFIX} `)
|
|
498
|
-
) {
|
|
499
|
-
return false;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
return true;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
async function handleHiveShortcut(
|
|
506
|
-
prompt: string,
|
|
507
|
-
options: {
|
|
508
|
-
allowInteractiveConfig?: boolean;
|
|
509
|
-
} = {},
|
|
510
|
-
): Promise<HiveShortcutResult> {
|
|
511
|
-
const normalized = prompt.trim().replace(/\s+/g, " ");
|
|
512
|
-
const lower = normalized.toLowerCase();
|
|
513
|
-
|
|
514
|
-
if (lower === HIVE_SHORTCUT_PREFIX) {
|
|
515
|
-
renderInfo(HIVE_SHORTCUT_HELP_TEXT);
|
|
516
|
-
return "handled";
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
if (!lower.startsWith(`${HIVE_SHORTCUT_PREFIX} `)) {
|
|
520
|
-
return "not-handled";
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
const rawSubcommand = normalized.slice(HIVE_SHORTCUT_PREFIX.length).trim();
|
|
524
|
-
const subcommand = rawSubcommand.toLowerCase();
|
|
525
|
-
|
|
526
|
-
if (subcommand.length === 0 || subcommand === "help") {
|
|
527
|
-
renderInfo(HIVE_SHORTCUT_HELP_TEXT);
|
|
528
|
-
return "handled";
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
if (subcommand === "status") {
|
|
532
|
-
await runStatusCommandWithOptions({ showHeader: false });
|
|
533
|
-
restoreChatInputAfterInteractiveCommand();
|
|
534
|
-
return "handled";
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
if (subcommand === "config show") {
|
|
538
|
-
await runConfigShowCommandWithOptions({ showHeader: false });
|
|
539
|
-
restoreChatInputAfterInteractiveCommand();
|
|
540
|
-
return "handled";
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
if (subcommand === "config provider") {
|
|
544
|
-
if (!options.allowInteractiveConfig) {
|
|
545
|
-
renderInfo("Interactive config commands are unavailable here.");
|
|
546
|
-
return "handled";
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
await runConfigProviderCommandWithOptions({ showHeader: false });
|
|
550
|
-
restoreChatInputAfterInteractiveCommand();
|
|
551
|
-
return "config-updated";
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
if (subcommand === "config model") {
|
|
555
|
-
if (!options.allowInteractiveConfig) {
|
|
556
|
-
renderInfo("Interactive config commands are unavailable here.");
|
|
557
|
-
return "handled";
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
await runConfigModelCommandWithOptions({ showHeader: false });
|
|
561
|
-
restoreChatInputAfterInteractiveCommand();
|
|
562
|
-
return "config-updated";
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
if (subcommand === "config key") {
|
|
566
|
-
if (!options.allowInteractiveConfig) {
|
|
567
|
-
renderInfo("Interactive config commands are unavailable here.");
|
|
568
|
-
return "handled";
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
await runConfigKeyCommandWithOptions({ showHeader: false });
|
|
572
|
-
restoreChatInputAfterInteractiveCommand();
|
|
573
|
-
return "handled";
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
if (subcommand === "config theme") {
|
|
577
|
-
if (!options.allowInteractiveConfig) {
|
|
578
|
-
renderInfo("Interactive config commands are unavailable here.");
|
|
579
|
-
return "handled";
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
await runConfigThemeCommandWithOptions({ showHeader: false });
|
|
583
|
-
restoreChatInputAfterInteractiveCommand();
|
|
584
|
-
return "handled";
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
if (
|
|
588
|
-
subcommand === "init" ||
|
|
589
|
-
subcommand === "nuke"
|
|
590
|
-
) {
|
|
591
|
-
renderInfo(`Run \`hive ${rawSubcommand}\` from your shell. This command is interactive.`);
|
|
592
|
-
return "handled";
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
renderError(`Unknown Hive shortcut: /hive ${rawSubcommand}`);
|
|
596
|
-
renderInfo("Use `/hive help` to list available shortcuts.");
|
|
597
|
-
return "handled";
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
function getCommandSuggestions(input: string): CommandSuggestion[] {
|
|
601
|
-
const normalized = input.trimStart().toLowerCase();
|
|
602
|
-
if (!normalized.startsWith("/")) {
|
|
603
|
-
return [];
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
const prefixMatches = COMMAND_SUGGESTIONS.filter(
|
|
607
|
-
(suggestion) =>
|
|
608
|
-
suggestion.insertText.toLowerCase().startsWith(normalized) ||
|
|
609
|
-
suggestion.label.toLowerCase().startsWith(normalized),
|
|
610
|
-
);
|
|
611
|
-
|
|
612
|
-
const fallbackMatches = COMMAND_SUGGESTIONS.filter(
|
|
613
|
-
(suggestion) =>
|
|
614
|
-
!prefixMatches.includes(suggestion) &&
|
|
615
|
-
suggestion.label.toLowerCase().includes(normalized.slice(1)),
|
|
616
|
-
);
|
|
617
|
-
|
|
618
|
-
return [...prefixMatches, ...fallbackMatches];
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
async function readPromptWithSuggestions(): Promise<string> {
|
|
622
|
-
const accent = getTheme().accent;
|
|
623
|
-
const promptPrefix = accent(USER_PROMPT);
|
|
624
|
-
|
|
625
|
-
if (!stdin.isTTY || !stdout.isTTY) {
|
|
626
|
-
const rl = createInterface({
|
|
627
|
-
input: stdin,
|
|
628
|
-
output: stdout,
|
|
629
|
-
terminal: true,
|
|
630
|
-
});
|
|
631
|
-
|
|
632
|
-
try {
|
|
633
|
-
return (await rl.question(promptPrefix)).trim();
|
|
634
|
-
} finally {
|
|
635
|
-
rl.close();
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
return new Promise<string>((resolve) => {
|
|
640
|
-
stdin.resume();
|
|
641
|
-
readline.emitKeypressEvents(stdin);
|
|
642
|
-
|
|
643
|
-
const wasRaw = stdin.isRaw ?? false;
|
|
644
|
-
if (!wasRaw) {
|
|
645
|
-
stdin.setRawMode(true);
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
let buffer = "";
|
|
649
|
-
let selectedSuggestionIndex = 0;
|
|
650
|
-
let suggestionWindowStart = 0;
|
|
651
|
-
let renderedSuggestionRows = 0;
|
|
652
|
-
|
|
653
|
-
const cleanup = () => {
|
|
654
|
-
stdin.off("keypress", onKeypress);
|
|
655
|
-
if (!wasRaw) {
|
|
656
|
-
stdin.setRawMode(false);
|
|
657
|
-
}
|
|
658
|
-
};
|
|
659
|
-
|
|
660
|
-
const commit = () => {
|
|
661
|
-
const suggestions = getCommandSuggestions(buffer);
|
|
662
|
-
const selected = suggestions[selectedSuggestionIndex];
|
|
663
|
-
let value = buffer.trim();
|
|
664
|
-
|
|
665
|
-
if (
|
|
666
|
-
selected &&
|
|
667
|
-
value.startsWith("/") &&
|
|
668
|
-
(value === "/" ||
|
|
669
|
-
selected.insertText.toLowerCase().startsWith(value.toLowerCase()) ||
|
|
670
|
-
selected.label.toLowerCase().startsWith(value.toLowerCase()))
|
|
671
|
-
) {
|
|
672
|
-
value = selected.insertText.trimEnd();
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
if (value === "/") {
|
|
676
|
-
value = "/help";
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
readline.cursorTo(stdout, 0);
|
|
680
|
-
readline.clearLine(stdout, 0);
|
|
681
|
-
stdout.write(`${promptPrefix}${buffer}`);
|
|
682
|
-
|
|
683
|
-
for (let index = 0; index < renderedSuggestionRows; index += 1) {
|
|
684
|
-
readline.moveCursor(stdout, 0, 1);
|
|
685
|
-
readline.cursorTo(stdout, 0);
|
|
686
|
-
readline.clearLine(stdout, 0);
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
for (let index = 0; index < renderedSuggestionRows; index += 1) {
|
|
690
|
-
readline.moveCursor(stdout, 0, -1);
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
renderedSuggestionRows = 0;
|
|
694
|
-
readline.cursorTo(stdout, USER_PROMPT.length + buffer.length);
|
|
695
|
-
stdout.write("\n");
|
|
696
|
-
cleanup();
|
|
697
|
-
resolve(value);
|
|
698
|
-
};
|
|
699
|
-
|
|
700
|
-
const render = () => {
|
|
701
|
-
const suggestions = getCommandSuggestions(buffer);
|
|
702
|
-
if (selectedSuggestionIndex >= suggestions.length) {
|
|
703
|
-
selectedSuggestionIndex = Math.max(0, suggestions.length - 1);
|
|
704
|
-
}
|
|
705
|
-
if (suggestions.length === 0) {
|
|
706
|
-
suggestionWindowStart = 0;
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
const visibleSuggestionCount = Math.min(MAX_COMMAND_SUGGESTIONS, suggestions.length);
|
|
710
|
-
if (selectedSuggestionIndex < suggestionWindowStart) {
|
|
711
|
-
suggestionWindowStart = selectedSuggestionIndex;
|
|
712
|
-
}
|
|
713
|
-
if (
|
|
714
|
-
visibleSuggestionCount > 0 &&
|
|
715
|
-
selectedSuggestionIndex >= suggestionWindowStart + visibleSuggestionCount
|
|
716
|
-
) {
|
|
717
|
-
suggestionWindowStart = selectedSuggestionIndex - visibleSuggestionCount + 1;
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
const visibleSuggestions = suggestions.slice(
|
|
721
|
-
suggestionWindowStart,
|
|
722
|
-
suggestionWindowStart + visibleSuggestionCount,
|
|
723
|
-
);
|
|
724
|
-
|
|
725
|
-
readline.cursorTo(stdout, 0);
|
|
726
|
-
readline.clearLine(stdout, 0);
|
|
727
|
-
stdout.write(`${promptPrefix}${buffer}`);
|
|
728
|
-
|
|
729
|
-
const rowsToRender = Math.max(renderedSuggestionRows, visibleSuggestions.length);
|
|
730
|
-
for (let index = 0; index < rowsToRender; index += 1) {
|
|
731
|
-
readline.moveCursor(stdout, 0, 1);
|
|
732
|
-
readline.cursorTo(stdout, 0);
|
|
733
|
-
readline.clearLine(stdout, 0);
|
|
734
|
-
|
|
735
|
-
if (index >= visibleSuggestions.length) {
|
|
736
|
-
continue;
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
const suggestion = visibleSuggestions[index];
|
|
740
|
-
const absoluteIndex = suggestionWindowStart + index;
|
|
741
|
-
const marker = absoluteIndex === selectedSuggestionIndex ? ">" : " ";
|
|
742
|
-
const label = suggestion.label.padEnd(COMMAND_LABEL_WIDTH, " ");
|
|
743
|
-
const text = `${marker} ${label} ${suggestion.description}`;
|
|
744
|
-
|
|
745
|
-
if (absoluteIndex === selectedSuggestionIndex) {
|
|
746
|
-
stdout.write(accent(text));
|
|
747
|
-
} else {
|
|
748
|
-
stdout.write(chalk.dim(text));
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
for (let index = 0; index < rowsToRender; index += 1) {
|
|
753
|
-
readline.moveCursor(stdout, 0, -1);
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
readline.cursorTo(stdout, USER_PROMPT.length + buffer.length);
|
|
757
|
-
renderedSuggestionRows = visibleSuggestions.length;
|
|
758
|
-
};
|
|
759
|
-
|
|
760
|
-
const onKeypress = (str: string, key: readline.Key) => {
|
|
761
|
-
if ((key.ctrl && key.name === "c") || (key.ctrl && key.name === "d")) {
|
|
762
|
-
buffer = "/exit";
|
|
763
|
-
commit();
|
|
764
|
-
return;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
if (key.name === "return" || key.name === "enter") {
|
|
768
|
-
const suggestions = getCommandSuggestions(buffer);
|
|
769
|
-
const selected = suggestions[selectedSuggestionIndex];
|
|
770
|
-
const trimmed = buffer.trim();
|
|
771
|
-
|
|
772
|
-
if (
|
|
773
|
-
selected &&
|
|
774
|
-
trimmed.startsWith("/") &&
|
|
775
|
-
(trimmed === "/" ||
|
|
776
|
-
selected.insertText.toLowerCase().startsWith(trimmed.toLowerCase()) ||
|
|
777
|
-
selected.label.toLowerCase().startsWith(trimmed.toLowerCase()))
|
|
778
|
-
) {
|
|
779
|
-
buffer = selected.insertText;
|
|
780
|
-
selectedSuggestionIndex = 0;
|
|
781
|
-
suggestionWindowStart = 0;
|
|
782
|
-
|
|
783
|
-
// Commands that expect extra input should stay in edit mode.
|
|
784
|
-
if (buffer.endsWith(" ")) {
|
|
785
|
-
render();
|
|
786
|
-
return;
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
commit();
|
|
791
|
-
return;
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
if (key.name === "backspace") {
|
|
795
|
-
const chars = Array.from(buffer);
|
|
796
|
-
chars.pop();
|
|
797
|
-
buffer = chars.join("");
|
|
798
|
-
selectedSuggestionIndex = 0;
|
|
799
|
-
suggestionWindowStart = 0;
|
|
800
|
-
render();
|
|
801
|
-
return;
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
if (key.name === "up" || key.name === "down") {
|
|
805
|
-
const suggestions = getCommandSuggestions(buffer);
|
|
806
|
-
if (suggestions.length === 0) {
|
|
807
|
-
return;
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
if (key.name === "up") {
|
|
811
|
-
selectedSuggestionIndex =
|
|
812
|
-
selectedSuggestionIndex > 0
|
|
813
|
-
? selectedSuggestionIndex - 1
|
|
814
|
-
: suggestions.length - 1;
|
|
815
|
-
} else {
|
|
816
|
-
selectedSuggestionIndex =
|
|
817
|
-
selectedSuggestionIndex < suggestions.length - 1
|
|
818
|
-
? selectedSuggestionIndex + 1
|
|
819
|
-
: 0;
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
render();
|
|
823
|
-
return;
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
if (key.name === "tab") {
|
|
827
|
-
const suggestions = getCommandSuggestions(buffer);
|
|
828
|
-
if (suggestions.length === 0) {
|
|
829
|
-
return;
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
buffer = suggestions[selectedSuggestionIndex]?.insertText ?? buffer;
|
|
833
|
-
selectedSuggestionIndex = 0;
|
|
834
|
-
suggestionWindowStart = 0;
|
|
835
|
-
render();
|
|
836
|
-
return;
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
if (typeof str === "string" && str.length > 0 && !key.ctrl && !key.meta) {
|
|
840
|
-
buffer += str;
|
|
841
|
-
selectedSuggestionIndex = 0;
|
|
842
|
-
suggestionWindowStart = 0;
|
|
843
|
-
render();
|
|
844
|
-
}
|
|
845
|
-
};
|
|
846
|
-
|
|
847
|
-
stdin.on("keypress", onKeypress);
|
|
848
|
-
render();
|
|
849
|
-
});
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
function restoreChatInputAfterInteractiveCommand(): void {
|
|
853
|
-
if (!stdin.isTTY) {
|
|
854
|
-
return;
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
try {
|
|
858
|
-
stdin.setRawMode(false);
|
|
859
|
-
} catch {
|
|
860
|
-
// Ignore terminal mode recovery errors.
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
stdin.resume();
|
|
864
|
-
}
|