@imisbahk/hive 0.1.0
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/.rocket/ARCHITECTURE.md +7 -0
- package/.rocket/README.md +31 -0
- package/.rocket/SYMBOLS.md +282 -0
- package/.rocket/config.json +18 -0
- package/001-local-first-storage.md +43 -0
- package/003-memory-architechture.md +71 -0
- package/CONTRIBUTING.md +149 -0
- package/LICENSE.md +21 -0
- package/README.md +146 -0
- package/dist/agent/agent.d.ts +32 -0
- package/dist/agent/agent.d.ts.map +1 -0
- package/dist/agent/agent.js +103 -0
- package/dist/agent/agent.js.map +1 -0
- package/dist/agent/index.d.ts +3 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +2 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/cli/commands/chat.d.ts +12 -0
- package/dist/cli/commands/chat.d.ts.map +1 -0
- package/dist/cli/commands/chat.js +117 -0
- package/dist/cli/commands/chat.js.map +1 -0
- package/dist/cli/commands/config.d.ts +7 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +234 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/init.d.ts +8 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +186 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/nuke.d.ts +4 -0
- package/dist/cli/commands/nuke.d.ts.map +1 -0
- package/dist/cli/commands/nuke.js +47 -0
- package/dist/cli/commands/nuke.js.map +1 -0
- package/dist/cli/commands/status.d.ts +4 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +114 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/helpers/providerPrompts.d.ts +13 -0
- package/dist/cli/helpers/providerPrompts.d.ts.map +1 -0
- package/dist/cli/helpers/providerPrompts.js +138 -0
- package/dist/cli/helpers/providerPrompts.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +31 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/providers/anthropic.d.ts +10 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +108 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/api-key.d.ts +3 -0
- package/dist/providers/api-key.d.ts.map +1 -0
- package/dist/providers/api-key.js +15 -0
- package/dist/providers/api-key.js.map +1 -0
- package/dist/providers/base.d.ts +41 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/base.js +157 -0
- package/dist/providers/base.js.map +1 -0
- package/dist/providers/google.d.ts +6 -0
- package/dist/providers/google.d.ts.map +1 -0
- package/dist/providers/google.js +19 -0
- package/dist/providers/google.js.map +1 -0
- package/dist/providers/groq.d.ts +6 -0
- package/dist/providers/groq.d.ts.map +1 -0
- package/dist/providers/groq.js +19 -0
- package/dist/providers/groq.js.map +1 -0
- package/dist/providers/index.d.ts +4 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +58 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/mistral.d.ts +6 -0
- package/dist/providers/mistral.d.ts.map +1 -0
- package/dist/providers/mistral.js +19 -0
- package/dist/providers/mistral.js.map +1 -0
- package/dist/providers/ollama.d.ts +6 -0
- package/dist/providers/ollama.d.ts.map +1 -0
- package/dist/providers/ollama.js +20 -0
- package/dist/providers/ollama.js.map +1 -0
- package/dist/providers/openai-compatible.d.ts +22 -0
- package/dist/providers/openai-compatible.d.ts.map +1 -0
- package/dist/providers/openai-compatible.js +36 -0
- package/dist/providers/openai-compatible.js.map +1 -0
- package/dist/providers/openai.d.ts +6 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +19 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/openrouter.d.ts +6 -0
- package/dist/providers/openrouter.d.ts.map +1 -0
- package/dist/providers/openrouter.js +19 -0
- package/dist/providers/openrouter.js.map +1 -0
- package/dist/providers/together.d.ts +6 -0
- package/dist/providers/together.d.ts.map +1 -0
- package/dist/providers/together.js +19 -0
- package/dist/providers/together.js.map +1 -0
- package/dist/storage/db.d.ts +48 -0
- package/dist/storage/db.d.ts.map +1 -0
- package/dist/storage/db.js +298 -0
- package/dist/storage/db.js.map +1 -0
- package/dist/storage/schema.d.ts +43 -0
- package/dist/storage/schema.d.ts.map +1 -0
- package/dist/storage/schema.js +69 -0
- package/dist/storage/schema.js.map +1 -0
- package/index.md +16 -0
- package/package.json +48 -0
- package/prompts/Behaviour.md +23 -0
- package/prompts/Code.md +12 -0
- package/prompts/Memory.md +11 -0
- package/prompts/System.md +6 -0
- package/releases/v1/v0.1/RELEASE-NOTES.md +0 -0
- package/src/agent/agent.ts +155 -0
- package/src/agent/index.ts +2 -0
- package/src/cli/commands/chat.ts +169 -0
- package/src/cli/commands/config.ts +282 -0
- package/src/cli/commands/init.ts +242 -0
- package/src/cli/commands/nuke.ts +60 -0
- package/src/cli/commands/status.ts +147 -0
- package/src/cli/helpers/providerPrompts.ts +192 -0
- package/src/cli/index.ts +38 -0
- package/src/providers/anthropic.ts +146 -0
- package/src/providers/api-key.ts +23 -0
- package/src/providers/base.ts +234 -0
- package/src/providers/google.ts +21 -0
- package/src/providers/groq.ts +21 -0
- package/src/providers/index.ts +65 -0
- package/src/providers/mistral.ts +21 -0
- package/src/providers/ollama.ts +22 -0
- package/src/providers/openai-compatible.ts +58 -0
- package/src/providers/openai.ts +21 -0
- package/src/providers/openrouter.ts +21 -0
- package/src/providers/together.ts +21 -0
- package/src/storage/db.ts +476 -0
- package/src/storage/schema.ts +116 -0
- package/tsconfig.json +51 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentRecord,
|
|
3
|
+
ConversationRecord,
|
|
4
|
+
HiveDatabase,
|
|
5
|
+
} from "../storage/db.js";
|
|
6
|
+
import {
|
|
7
|
+
appendMessage,
|
|
8
|
+
createConversation,
|
|
9
|
+
getConversationById,
|
|
10
|
+
getPrimaryAgent,
|
|
11
|
+
listMessages,
|
|
12
|
+
} from "../storage/db.js";
|
|
13
|
+
import type { Provider, StreamChatRequest } from "../providers/base.js";
|
|
14
|
+
import { createProvider } from "../providers/index.js";
|
|
15
|
+
|
|
16
|
+
export interface AgentChatOptions {
|
|
17
|
+
conversationId?: string;
|
|
18
|
+
title?: string;
|
|
19
|
+
model?: string;
|
|
20
|
+
temperature?: number;
|
|
21
|
+
maxTokens?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type AgentStreamEvent =
|
|
25
|
+
| {
|
|
26
|
+
type: "token";
|
|
27
|
+
conversationId: string;
|
|
28
|
+
token: string;
|
|
29
|
+
}
|
|
30
|
+
| {
|
|
31
|
+
type: "done";
|
|
32
|
+
conversationId: string;
|
|
33
|
+
assistantMessageId: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export class HiveAgent {
|
|
37
|
+
private readonly historyLimit = 80;
|
|
38
|
+
|
|
39
|
+
constructor(
|
|
40
|
+
private readonly db: HiveDatabase,
|
|
41
|
+
private readonly provider: Provider,
|
|
42
|
+
private readonly agent: AgentRecord,
|
|
43
|
+
) {}
|
|
44
|
+
|
|
45
|
+
static async load(db: HiveDatabase, provider?: Provider): Promise<HiveAgent> {
|
|
46
|
+
const agent = getPrimaryAgent(db);
|
|
47
|
+
if (!agent) {
|
|
48
|
+
throw new Error("Hive is not initialized. Run `hive init` first.");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const resolvedProvider = provider ?? (await createProvider(agent.provider));
|
|
52
|
+
return new HiveAgent(db, resolvedProvider, agent);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
getProfile(): AgentRecord {
|
|
56
|
+
return this.agent;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
startConversation(title?: string): ConversationRecord {
|
|
60
|
+
return createConversation(this.db, {
|
|
61
|
+
agentId: this.agent.id,
|
|
62
|
+
title,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async *chat(
|
|
67
|
+
userMessage: string,
|
|
68
|
+
options: AgentChatOptions = {},
|
|
69
|
+
): AsyncGenerator<AgentStreamEvent> {
|
|
70
|
+
const trimmed = userMessage.trim();
|
|
71
|
+
if (trimmed.length === 0) {
|
|
72
|
+
throw new Error("Cannot send an empty message.");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const conversation = this.resolveConversation(options.conversationId, options.title);
|
|
76
|
+
|
|
77
|
+
appendMessage(this.db, {
|
|
78
|
+
conversationId: conversation.id,
|
|
79
|
+
role: "user",
|
|
80
|
+
content: trimmed,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const history = listMessages(this.db, conversation.id, this.historyLimit);
|
|
84
|
+
const providerRequest: StreamChatRequest = {
|
|
85
|
+
model: options.model ?? this.agent.model,
|
|
86
|
+
temperature: options.temperature,
|
|
87
|
+
maxTokens: options.maxTokens,
|
|
88
|
+
messages: [
|
|
89
|
+
{
|
|
90
|
+
role: "system",
|
|
91
|
+
content: this.agent.persona,
|
|
92
|
+
},
|
|
93
|
+
...history.map((message) => ({
|
|
94
|
+
role: message.role,
|
|
95
|
+
content: message.content,
|
|
96
|
+
})),
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
let assistantText = "";
|
|
101
|
+
for await (const token of this.provider.streamChat(providerRequest)) {
|
|
102
|
+
assistantText += token;
|
|
103
|
+
yield {
|
|
104
|
+
type: "token",
|
|
105
|
+
conversationId: conversation.id,
|
|
106
|
+
token,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const savedMessage = appendMessage(this.db, {
|
|
111
|
+
conversationId: conversation.id,
|
|
112
|
+
role: "assistant",
|
|
113
|
+
content: assistantText,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
yield {
|
|
117
|
+
type: "done",
|
|
118
|
+
conversationId: conversation.id,
|
|
119
|
+
assistantMessageId: savedMessage.id,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private resolveConversation(
|
|
124
|
+
conversationId?: string,
|
|
125
|
+
title?: string,
|
|
126
|
+
): ConversationRecord {
|
|
127
|
+
if (!conversationId) {
|
|
128
|
+
return createConversation(this.db, {
|
|
129
|
+
agentId: this.agent.id,
|
|
130
|
+
title,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const existingConversation = getConversationById(this.db, conversationId);
|
|
135
|
+
if (!existingConversation) {
|
|
136
|
+
throw new Error(`Conversation \"${conversationId}\" was not found.`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (existingConversation.agent_id !== this.agent.id) {
|
|
140
|
+
throw new Error("Conversation does not belong to the initialized Hive agent.");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return existingConversation;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function buildDefaultPersona(ownerName: string): string {
|
|
148
|
+
return [
|
|
149
|
+
"You are The Hive: a local-first personal AI agent.",
|
|
150
|
+
`You are assisting ${ownerName}.`,
|
|
151
|
+
"Be direct, useful, and execution-focused.",
|
|
152
|
+
"Prefer concrete actions over abstract advice.",
|
|
153
|
+
"If context is missing, ask one concise clarifying question.",
|
|
154
|
+
].join("\n");
|
|
155
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { stdin, stdout } from "node:process";
|
|
2
|
+
import { createInterface } from "node:readline/promises";
|
|
3
|
+
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
|
|
7
|
+
import { HiveAgent } from "../../agent/agent.js";
|
|
8
|
+
import {
|
|
9
|
+
closeHiveDatabase,
|
|
10
|
+
getPrimaryAgent,
|
|
11
|
+
openHiveDatabase,
|
|
12
|
+
} from "../../storage/db.js";
|
|
13
|
+
import { createProvider } from "../../providers/index.js";
|
|
14
|
+
|
|
15
|
+
interface ChatCommandOptions {
|
|
16
|
+
message?: string;
|
|
17
|
+
conversation?: string;
|
|
18
|
+
model?: string;
|
|
19
|
+
title?: string;
|
|
20
|
+
temperature?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface RunChatOptions {
|
|
24
|
+
model?: string;
|
|
25
|
+
title?: string;
|
|
26
|
+
temperature?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function registerChatCommand(program: Command): void {
|
|
30
|
+
program
|
|
31
|
+
.command("chat")
|
|
32
|
+
.description("Talk to your Hive agent")
|
|
33
|
+
.option("-m, --message <text>", "send a single message and exit")
|
|
34
|
+
.option("-c, --conversation <id>", "continue an existing conversation")
|
|
35
|
+
.option("--model <model>", "override model for this session")
|
|
36
|
+
.option("--title <title>", "title for a newly created conversation")
|
|
37
|
+
.option("-t, --temperature <value>", "sampling temperature")
|
|
38
|
+
.action(async (options: ChatCommandOptions) => {
|
|
39
|
+
await runChatCommand(options);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function runChatCommand(options: ChatCommandOptions): Promise<void> {
|
|
44
|
+
const temperature = parseTemperature(options.temperature);
|
|
45
|
+
const db = openHiveDatabase();
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const profile = getPrimaryAgent(db);
|
|
49
|
+
if (!profile) {
|
|
50
|
+
console.error(chalk.red("Hive is not initialized. Run `hive init` first."));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const provider = await createProvider(profile.provider);
|
|
55
|
+
const agent = new HiveAgent(db, provider, profile);
|
|
56
|
+
|
|
57
|
+
let conversationId = options.conversation;
|
|
58
|
+
const runOptions: RunChatOptions = {
|
|
59
|
+
model: options.model,
|
|
60
|
+
title: options.title,
|
|
61
|
+
temperature,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
if (options.message) {
|
|
65
|
+
conversationId = await streamReply(
|
|
66
|
+
agent,
|
|
67
|
+
options.message,
|
|
68
|
+
conversationId,
|
|
69
|
+
runOptions,
|
|
70
|
+
);
|
|
71
|
+
console.log(chalk.dim(`conversation: ${conversationId}`));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const rl = createInterface({
|
|
76
|
+
input: stdin,
|
|
77
|
+
output: stdout,
|
|
78
|
+
terminal: true,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
console.log(chalk.dim("Type /exit to quit, /new to start a fresh conversation."));
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
while (true) {
|
|
85
|
+
const prompt = (await rl.question(chalk.cyan("you> "))).trim();
|
|
86
|
+
|
|
87
|
+
if (prompt.length === 0) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (prompt === "/exit" || prompt === "/quit") {
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (prompt === "/new") {
|
|
96
|
+
conversationId = undefined;
|
|
97
|
+
console.log(chalk.dim("Started a new conversation context."));
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
conversationId = await streamReply(agent, prompt, conversationId, runOptions);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
process.stdout.write("\n");
|
|
105
|
+
console.error(formatError(error));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} finally {
|
|
109
|
+
rl.close();
|
|
110
|
+
}
|
|
111
|
+
} finally {
|
|
112
|
+
closeHiveDatabase(db);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function streamReply(
|
|
117
|
+
agent: HiveAgent,
|
|
118
|
+
prompt: string,
|
|
119
|
+
conversationId: string | undefined,
|
|
120
|
+
options: RunChatOptions,
|
|
121
|
+
): Promise<string> {
|
|
122
|
+
process.stdout.write(chalk.green("hive> "));
|
|
123
|
+
|
|
124
|
+
let activeConversationId = conversationId;
|
|
125
|
+
|
|
126
|
+
for await (const event of agent.chat(prompt, {
|
|
127
|
+
conversationId: activeConversationId,
|
|
128
|
+
model: options.model,
|
|
129
|
+
temperature: options.temperature,
|
|
130
|
+
title: options.title,
|
|
131
|
+
})) {
|
|
132
|
+
if (event.type === "token") {
|
|
133
|
+
process.stdout.write(event.token);
|
|
134
|
+
activeConversationId = event.conversationId;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
activeConversationId = event.conversationId;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
process.stdout.write("\n");
|
|
142
|
+
|
|
143
|
+
if (!activeConversationId) {
|
|
144
|
+
throw new Error("Conversation state was not returned by the agent.");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return activeConversationId;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function parseTemperature(raw?: string): number | undefined {
|
|
151
|
+
if (raw === undefined) {
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const parsed = Number.parseFloat(raw);
|
|
156
|
+
if (Number.isNaN(parsed) || parsed < 0 || parsed > 2) {
|
|
157
|
+
throw new Error("Temperature must be a number between 0 and 2.");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return parsed;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function formatError(error: unknown): string {
|
|
164
|
+
if (error instanceof Error) {
|
|
165
|
+
return chalk.red(error.message);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return chalk.red(String(error));
|
|
169
|
+
}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import inquirer from "inquirer";
|
|
6
|
+
import keytar from "keytar";
|
|
7
|
+
import ora from "ora";
|
|
8
|
+
|
|
9
|
+
import { resolveProviderApiKey } from "../../providers/api-key.js";
|
|
10
|
+
import { normalizeProviderName, type ProviderName } from "../../providers/base.js";
|
|
11
|
+
import { promptForModel, promptForProvider } from "../helpers/providerPrompts.js";
|
|
12
|
+
import {
|
|
13
|
+
closeHiveDatabase,
|
|
14
|
+
getPrimaryAgent,
|
|
15
|
+
openHiveDatabase,
|
|
16
|
+
setMetaValue,
|
|
17
|
+
updatePrimaryAgentModel,
|
|
18
|
+
updatePrimaryAgentProviderAndModel,
|
|
19
|
+
} from "../../storage/db.js";
|
|
20
|
+
|
|
21
|
+
const KEYCHAIN_SERVICE = "hive";
|
|
22
|
+
|
|
23
|
+
export function registerConfigCommand(program: Command): void {
|
|
24
|
+
const configCommand = program
|
|
25
|
+
.command("config")
|
|
26
|
+
.description("Update provider, model, or API keys without re-running init");
|
|
27
|
+
|
|
28
|
+
configCommand
|
|
29
|
+
.command("provider")
|
|
30
|
+
.description("Change provider, model, and API key")
|
|
31
|
+
.action(async () => {
|
|
32
|
+
await runConfigProviderCommand();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
configCommand
|
|
36
|
+
.command("model")
|
|
37
|
+
.description("Change model for the current provider")
|
|
38
|
+
.action(async () => {
|
|
39
|
+
await runConfigModelCommand();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
configCommand
|
|
43
|
+
.command("key")
|
|
44
|
+
.description("Update API key for the current provider")
|
|
45
|
+
.action(async () => {
|
|
46
|
+
await runConfigKeyCommand();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
configCommand
|
|
50
|
+
.command("show")
|
|
51
|
+
.description("Show current provider, model, and key status")
|
|
52
|
+
.action(async () => {
|
|
53
|
+
await runConfigShowCommand();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function runConfigProviderCommand(): Promise<void> {
|
|
58
|
+
const spinner = ora("Loading configuration...").start();
|
|
59
|
+
const db = openHiveDatabase();
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
ensureInteractiveTerminal("`hive config provider` requires an interactive terminal.");
|
|
63
|
+
|
|
64
|
+
const agent = getPrimaryAgent(db);
|
|
65
|
+
if (!agent) {
|
|
66
|
+
spinner.stop();
|
|
67
|
+
console.error(chalk.red("Hive is not initialized. Run `hive init` first."));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const currentProvider = normalizeProviderName(agent.provider);
|
|
72
|
+
const currentModel = agent.model;
|
|
73
|
+
|
|
74
|
+
spinner.stop();
|
|
75
|
+
printCurrentProviderAndModel(currentProvider, currentModel);
|
|
76
|
+
|
|
77
|
+
const provider = await promptForProvider({
|
|
78
|
+
defaultProvider: currentProvider,
|
|
79
|
+
});
|
|
80
|
+
const model = await promptForModel(provider, {
|
|
81
|
+
defaultModel: provider === currentProvider ? currentModel : undefined,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
let apiKey: string | undefined;
|
|
85
|
+
if (provider !== "ollama") {
|
|
86
|
+
const answer = (await inquirer.prompt([
|
|
87
|
+
{
|
|
88
|
+
type: "password",
|
|
89
|
+
name: "apiKey",
|
|
90
|
+
message: "Enter your API key:",
|
|
91
|
+
mask: "*",
|
|
92
|
+
validate: requiredField("API key is required."),
|
|
93
|
+
},
|
|
94
|
+
])) as { apiKey: string };
|
|
95
|
+
|
|
96
|
+
apiKey = answer.apiKey.trim();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
spinner.start("Saving configuration...");
|
|
100
|
+
|
|
101
|
+
const updatedAgent = updatePrimaryAgentProviderAndModel(db, {
|
|
102
|
+
provider,
|
|
103
|
+
model,
|
|
104
|
+
});
|
|
105
|
+
setMetaValue(db, "provider", updatedAgent.provider);
|
|
106
|
+
setMetaValue(db, "model", updatedAgent.model);
|
|
107
|
+
|
|
108
|
+
if (provider !== "ollama" && apiKey) {
|
|
109
|
+
await keytar.setPassword(KEYCHAIN_SERVICE, provider, apiKey);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
spinner.succeed("Configuration saved.");
|
|
113
|
+
console.log("Provider updated. Run `hive chat` to use it.");
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (spinner.isSpinning) {
|
|
116
|
+
spinner.fail("Failed to update provider configuration.");
|
|
117
|
+
}
|
|
118
|
+
throw error;
|
|
119
|
+
} finally {
|
|
120
|
+
closeHiveDatabase(db);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function runConfigModelCommand(): Promise<void> {
|
|
125
|
+
const spinner = ora("Loading configuration...").start();
|
|
126
|
+
const db = openHiveDatabase();
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
ensureInteractiveTerminal("`hive config model` requires an interactive terminal.");
|
|
130
|
+
|
|
131
|
+
const agent = getPrimaryAgent(db);
|
|
132
|
+
if (!agent) {
|
|
133
|
+
spinner.stop();
|
|
134
|
+
console.error(chalk.red("Hive is not initialized. Run `hive init` first."));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const provider = normalizeProviderName(agent.provider);
|
|
139
|
+
const currentModel = agent.model;
|
|
140
|
+
|
|
141
|
+
spinner.stop();
|
|
142
|
+
printCurrentProviderAndModel(provider, currentModel);
|
|
143
|
+
|
|
144
|
+
const model = await promptForModel(provider, {
|
|
145
|
+
defaultModel: currentModel,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
spinner.start("Saving model...");
|
|
149
|
+
|
|
150
|
+
const updatedAgent = updatePrimaryAgentModel(db, model);
|
|
151
|
+
setMetaValue(db, "model", updatedAgent.model);
|
|
152
|
+
|
|
153
|
+
spinner.succeed("Configuration saved.");
|
|
154
|
+
console.log("Model updated. Run `hive chat` to use it.");
|
|
155
|
+
} catch (error) {
|
|
156
|
+
if (spinner.isSpinning) {
|
|
157
|
+
spinner.fail("Failed to update model configuration.");
|
|
158
|
+
}
|
|
159
|
+
throw error;
|
|
160
|
+
} finally {
|
|
161
|
+
closeHiveDatabase(db);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export async function runConfigKeyCommand(): Promise<void> {
|
|
166
|
+
const spinner = ora("Loading configuration...").start();
|
|
167
|
+
const db = openHiveDatabase();
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
ensureInteractiveTerminal("`hive config key` requires an interactive terminal.");
|
|
171
|
+
|
|
172
|
+
const agent = getPrimaryAgent(db);
|
|
173
|
+
if (!agent) {
|
|
174
|
+
spinner.stop();
|
|
175
|
+
console.error(chalk.red("Hive is not initialized. Run `hive init` first."));
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const provider = normalizeProviderName(agent.provider);
|
|
180
|
+
|
|
181
|
+
spinner.stop();
|
|
182
|
+
console.log(chalk.dim(`Current provider: ${provider}`));
|
|
183
|
+
|
|
184
|
+
const answer = (await inquirer.prompt([
|
|
185
|
+
{
|
|
186
|
+
type: "password",
|
|
187
|
+
name: "apiKey",
|
|
188
|
+
message: "Enter your API key:",
|
|
189
|
+
mask: "*",
|
|
190
|
+
validate: requiredField("API key is required."),
|
|
191
|
+
},
|
|
192
|
+
])) as { apiKey: string };
|
|
193
|
+
|
|
194
|
+
spinner.start("Saving key...");
|
|
195
|
+
await keytar.setPassword(KEYCHAIN_SERVICE, provider, answer.apiKey.trim());
|
|
196
|
+
|
|
197
|
+
spinner.succeed("Configuration saved.");
|
|
198
|
+
console.log("API key updated. Run `hive chat` to use it.");
|
|
199
|
+
} catch (error) {
|
|
200
|
+
if (spinner.isSpinning) {
|
|
201
|
+
spinner.fail("Failed to update API key.");
|
|
202
|
+
}
|
|
203
|
+
throw error;
|
|
204
|
+
} finally {
|
|
205
|
+
closeHiveDatabase(db);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export async function runConfigShowCommand(): Promise<void> {
|
|
210
|
+
const db = openHiveDatabase();
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
const agent = getPrimaryAgent(db);
|
|
214
|
+
if (!agent) {
|
|
215
|
+
console.error(chalk.red("Hive is not initialized. Run `hive init` first."));
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const provider = normalizeProviderName(agent.provider);
|
|
220
|
+
const keyStatus = await getKeyStatus(provider);
|
|
221
|
+
|
|
222
|
+
console.log(`Provider: ${provider}`);
|
|
223
|
+
console.log(`Model: ${agent.model}`);
|
|
224
|
+
console.log(`Agent name: ${agent.agent_name ?? "not set"}`);
|
|
225
|
+
console.log(`API key: ${keyStatus}`);
|
|
226
|
+
} finally {
|
|
227
|
+
closeHiveDatabase(db);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function ensureInteractiveTerminal(errorMessage: string): void {
|
|
232
|
+
if (!process.stdin.isTTY) {
|
|
233
|
+
throw new Error(errorMessage);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function printCurrentProviderAndModel(provider: ProviderName, model: string): void {
|
|
238
|
+
console.log(chalk.dim(`Current provider: ${provider}`));
|
|
239
|
+
console.log(chalk.dim(`Current model: ${model}`));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function getKeyStatus(provider: ProviderName): Promise<"set" | "not set"> {
|
|
243
|
+
const apiKey = await resolveProviderApiKey(provider, apiKeyEnvVar(provider));
|
|
244
|
+
return apiKey ? "set" : "not set";
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function apiKeyEnvVar(provider: ProviderName): string {
|
|
248
|
+
switch (provider) {
|
|
249
|
+
case "openai":
|
|
250
|
+
return "OPENAI_API_KEY";
|
|
251
|
+
case "anthropic":
|
|
252
|
+
return "ANTHROPIC_API_KEY";
|
|
253
|
+
case "ollama":
|
|
254
|
+
return "OLLAMA_API_KEY";
|
|
255
|
+
case "groq":
|
|
256
|
+
return "GROQ_API_KEY";
|
|
257
|
+
case "mistral":
|
|
258
|
+
return "MISTRAL_API_KEY";
|
|
259
|
+
case "google":
|
|
260
|
+
return "GOOGLE_API_KEY";
|
|
261
|
+
case "openrouter":
|
|
262
|
+
return "OPENROUTER_API_KEY";
|
|
263
|
+
case "together":
|
|
264
|
+
return "TOGETHER_API_KEY";
|
|
265
|
+
default:
|
|
266
|
+
return assertNever(provider);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function requiredField(message: string): (value: string) => true | string {
|
|
271
|
+
return (value: string) => {
|
|
272
|
+
if (value.trim().length > 0) {
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return message;
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function assertNever(value: never): never {
|
|
281
|
+
throw new Error(`Unsupported provider: ${String(value)}`);
|
|
282
|
+
}
|