mikasa-cli 1.0.1
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/package.json +39 -0
- package/prisma/migrations/20260122185342_trst_migration/migration.sql +7 -0
- package/prisma/migrations/20260122192114/migration.sql +78 -0
- package/prisma/migrations/20260123074430_device_flow/migration.sql +15 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +125 -0
- package/src/cli/ai/google-service.js +235 -0
- package/src/cli/chat/chat-with-ai-agent.js +219 -0
- package/src/cli/chat/chat-with-ai-tool.js +377 -0
- package/src/cli/chat/chat-with-ai.js +275 -0
- package/src/cli/commands/ai/wakeUp.js +81 -0
- package/src/cli/commands/auth/login.js +607 -0
- package/src/cli/main.js +56 -0
- package/src/config/agent.config.js +166 -0
- package/src/config/google.config.js +8 -0
- package/src/config/tool.config.js +112 -0
- package/src/index.js +119 -0
- package/src/lib/auth-client.js +9 -0
- package/src/lib/auth.js +52 -0
- package/src/lib/db.js +9 -0
- package/src/services/chat.services.js +152 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import boxen from "boxen";
|
|
3
|
+
import { text, isCancel, cancel, intro, outro } from "@clack/prompts";
|
|
4
|
+
import yoctoSpinner from "yocto-spinner";
|
|
5
|
+
import { marked } from "marked";
|
|
6
|
+
import { markedTerminal } from "marked-terminal";
|
|
7
|
+
import { AIService } from "../ai/google-service.js";
|
|
8
|
+
import { ChatService } from "../../services/chat.services.js";
|
|
9
|
+
// import ChatService from "../ai/google-service.js";
|
|
10
|
+
import { getStoredToken } from "../commands/auth/login.js";
|
|
11
|
+
import prisma from "../../lib/db.js";
|
|
12
|
+
|
|
13
|
+
// Configure marked to use terminal renderer
|
|
14
|
+
marked.use(
|
|
15
|
+
markedTerminal({
|
|
16
|
+
// Styling options for terminal output
|
|
17
|
+
code: chalk.cyan,
|
|
18
|
+
blockquote: chalk.gray.italic,
|
|
19
|
+
heading: chalk.green.bold,
|
|
20
|
+
firstHeading: chalk.magenta.underline.bold,
|
|
21
|
+
hr: chalk.reset,
|
|
22
|
+
listitem: chalk.reset,
|
|
23
|
+
list: chalk.reset,
|
|
24
|
+
paragraph: chalk.reset,
|
|
25
|
+
strong: chalk.bold,
|
|
26
|
+
em: chalk.italic,
|
|
27
|
+
codespan: chalk.yellow.bgBlack,
|
|
28
|
+
del: chalk.dim.gray.strikethrough,
|
|
29
|
+
link: chalk.blue.underline,
|
|
30
|
+
href: chalk.blue.underline,
|
|
31
|
+
})
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// Initialize services
|
|
35
|
+
const aiService = new AIService();
|
|
36
|
+
const chatService = new ChatService();
|
|
37
|
+
|
|
38
|
+
async function getUserFromToken() {
|
|
39
|
+
const token = await getStoredToken();
|
|
40
|
+
|
|
41
|
+
if (!token?.access_token) {
|
|
42
|
+
throw new Error("Not authenticated. Please run 'mikasa login' first.");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const spinner = yoctoSpinner({ text: "Authenticating..." }).start();
|
|
46
|
+
|
|
47
|
+
const user = await prisma.user.findFirst({
|
|
48
|
+
where: {
|
|
49
|
+
sessions: {
|
|
50
|
+
some: { token: token.access_token },
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (!user) {
|
|
56
|
+
spinner.error("User not found");
|
|
57
|
+
throw new Error("User not found. Please login again.");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
spinner.success(`Welcome back bebe.., ${user.name}!`);
|
|
61
|
+
return user;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function initConversation(userId, conversationId = null, mode = "chat") {
|
|
65
|
+
const spinner = yoctoSpinner({ text: "Loading conversation..." }).start();
|
|
66
|
+
|
|
67
|
+
const conversation = await chatService.getOrCreateConversation(
|
|
68
|
+
userId,
|
|
69
|
+
conversationId,
|
|
70
|
+
mode
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
spinner.success("Conversation loaded");
|
|
74
|
+
|
|
75
|
+
// Display conversation info in a box
|
|
76
|
+
const conversationInfo = boxen(
|
|
77
|
+
`${chalk.bold("Conversation")}: ${conversation.title}\n${chalk.gray("ID: " + conversation.id)}\n${chalk.gray("Mode: " + conversation.mode)}`,
|
|
78
|
+
{
|
|
79
|
+
padding: 1,
|
|
80
|
+
margin: { top: 1, bottom: 1 },
|
|
81
|
+
borderStyle: "round",
|
|
82
|
+
borderColor: "cyan",
|
|
83
|
+
title: "💬 Chat Session",
|
|
84
|
+
titleAlignment: "center",
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
console.log(conversationInfo);
|
|
89
|
+
|
|
90
|
+
// Display existing messages if any
|
|
91
|
+
if (conversation.messages?.length > 0) {
|
|
92
|
+
console.log(chalk.yellow("📜 Previous messages:\n"));
|
|
93
|
+
displayMessages(conversation.messages);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return conversation;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function displayMessages(messages) {
|
|
100
|
+
messages.forEach((msg) => {
|
|
101
|
+
if (msg.role === "user") {
|
|
102
|
+
const userBox = boxen(chalk.white(msg.content), {
|
|
103
|
+
padding: 1,
|
|
104
|
+
margin: { left: 2, bottom: 1 },
|
|
105
|
+
borderStyle: "round",
|
|
106
|
+
borderColor: "blue",
|
|
107
|
+
title: "👤 You",
|
|
108
|
+
titleAlignment: "left",
|
|
109
|
+
});
|
|
110
|
+
console.log(userBox);
|
|
111
|
+
} else {
|
|
112
|
+
// Render markdown for assistant messages
|
|
113
|
+
const renderedContent = marked.parse(msg.content);
|
|
114
|
+
const assistantBox = boxen(renderedContent.trim(), {
|
|
115
|
+
padding: 1,
|
|
116
|
+
margin: { left: 2, bottom: 1 },
|
|
117
|
+
borderStyle: "round",
|
|
118
|
+
borderColor: "green",
|
|
119
|
+
title: "🤖 Assistant",
|
|
120
|
+
titleAlignment: "left",
|
|
121
|
+
});
|
|
122
|
+
console.log(assistantBox);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function saveMessage(conversationId, role, content) {
|
|
128
|
+
return await chatService.addMessage(conversationId, role, content);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function getAIResponse(conversationId) {
|
|
132
|
+
const spinner = yoctoSpinner({
|
|
133
|
+
text: "Mikasa was thinking...",
|
|
134
|
+
color: "cyan"
|
|
135
|
+
}).start();
|
|
136
|
+
|
|
137
|
+
const dbMessages = await chatService.getMessages(conversationId);
|
|
138
|
+
const aiMessages = chatService.formatMessagesForAI(dbMessages);
|
|
139
|
+
|
|
140
|
+
let fullResponse = "";
|
|
141
|
+
let isFirstChunk = true;
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const result = await aiService.sendMessage(aiMessages, (chunk) => {
|
|
145
|
+
// Stop spinner on first chunk and show header
|
|
146
|
+
if (isFirstChunk) {
|
|
147
|
+
spinner.stop();
|
|
148
|
+
console.log("\n");
|
|
149
|
+
const header = chalk.green.bold("🤖 Assistant:");
|
|
150
|
+
console.log(header);
|
|
151
|
+
console.log(chalk.gray("─".repeat(60)));
|
|
152
|
+
isFirstChunk = false;
|
|
153
|
+
}
|
|
154
|
+
fullResponse += chunk;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Now render the complete markdown response
|
|
158
|
+
console.log("\n");
|
|
159
|
+
const renderedMarkdown = marked.parse(fullResponse);
|
|
160
|
+
console.log(renderedMarkdown);
|
|
161
|
+
console.log(chalk.gray("─".repeat(60)));
|
|
162
|
+
console.log("\n");
|
|
163
|
+
|
|
164
|
+
return result.content;
|
|
165
|
+
} catch (error) {
|
|
166
|
+
spinner.error("Failed to get AI response");
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function updateConversationTitle(conversationId, userInput, messageCount) {
|
|
172
|
+
if (messageCount === 1) {
|
|
173
|
+
const title = userInput.slice(0, 50) + (userInput.length > 50 ? "..." : "");
|
|
174
|
+
await chatService.updateTitle(conversationId, title);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function chatLoop(conversation) {
|
|
179
|
+
const helpBox = boxen(
|
|
180
|
+
`${chalk.gray('• Type your message and press Enter')}\n${chalk.gray('• Markdown formatting is supported in responses')}\n${chalk.gray('• Type "exit" to end conversation')}\n${chalk.gray('• Press Ctrl+C to quit anytime')}`,
|
|
181
|
+
{
|
|
182
|
+
padding: 1,
|
|
183
|
+
margin: { bottom: 1 },
|
|
184
|
+
borderStyle: "round",
|
|
185
|
+
borderColor: "gray",
|
|
186
|
+
dimBorder: true,
|
|
187
|
+
}
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
console.log(helpBox);
|
|
191
|
+
|
|
192
|
+
while (true) {
|
|
193
|
+
const userInput = await text({
|
|
194
|
+
message: chalk.blue("💬 Your message"),
|
|
195
|
+
placeholder: "Type your message...",
|
|
196
|
+
validate(value) {
|
|
197
|
+
if (!value || value.trim().length === 0) {
|
|
198
|
+
return "Message cannot be empty";
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Handle cancellation (Ctrl+C)
|
|
204
|
+
if (isCancel(userInput)) {
|
|
205
|
+
const exitBox = boxen(chalk.yellow("Chat session ended. Goodbye! 👋"), {
|
|
206
|
+
padding: 1,
|
|
207
|
+
margin: 1,
|
|
208
|
+
borderStyle: "round",
|
|
209
|
+
borderColor: "yellow",
|
|
210
|
+
});
|
|
211
|
+
console.log(exitBox);
|
|
212
|
+
process.exit(0);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Handle exit command
|
|
216
|
+
if (userInput.toLowerCase() === "exit") {
|
|
217
|
+
const exitBox = boxen(chalk.yellow("The chat session has ended, and Eren was calling me 👩🏻🦰, so goodbye! 👋"), {
|
|
218
|
+
padding: 1,
|
|
219
|
+
margin: 1,
|
|
220
|
+
borderStyle: "round",
|
|
221
|
+
borderColor: "yellow",
|
|
222
|
+
});
|
|
223
|
+
console.log(exitBox);
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
// Save user message
|
|
231
|
+
await saveMessage(conversation.id, "user", userInput);
|
|
232
|
+
|
|
233
|
+
// Get messages count before AI response
|
|
234
|
+
const messages = await chatService.getMessages(conversation.id);
|
|
235
|
+
|
|
236
|
+
// Get AI response with streaming and markdown rendering
|
|
237
|
+
const aiResponse = await getAIResponse(conversation.id);
|
|
238
|
+
|
|
239
|
+
// Save AI response
|
|
240
|
+
await saveMessage(conversation.id, "assistant", aiResponse);
|
|
241
|
+
|
|
242
|
+
// Update title if first exchange
|
|
243
|
+
await updateConversationTitle(conversation.id, userInput, messages.length);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Main entry point
|
|
248
|
+
export async function startChat(mode = "chat", conversationId = null) {
|
|
249
|
+
try {
|
|
250
|
+
// Display intro banner
|
|
251
|
+
intro(
|
|
252
|
+
boxen(chalk.bold.cyan("🚀 Mikasa AI Chat"), {
|
|
253
|
+
padding: 1,
|
|
254
|
+
borderStyle: "double",
|
|
255
|
+
borderColor: "cyan",
|
|
256
|
+
})
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
const user = await getUserFromToken();
|
|
260
|
+
const conversation = await initConversation(user.id, conversationId, mode);
|
|
261
|
+
await chatLoop(conversation);
|
|
262
|
+
|
|
263
|
+
// Display outro
|
|
264
|
+
outro(chalk.green("✨ Thanks for chatting and love you...!"));
|
|
265
|
+
} catch (error) {
|
|
266
|
+
const errorBox = boxen(chalk.red(`❌ Error: ${error.message}`), {
|
|
267
|
+
padding: 1,
|
|
268
|
+
margin: 1,
|
|
269
|
+
borderStyle: "round",
|
|
270
|
+
borderColor: "red",
|
|
271
|
+
});
|
|
272
|
+
console.log(errorBox);
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import yoctoSpinner from "yocto-spinner";
|
|
4
|
+
import { getStoredToken } from "../auth/login.js";
|
|
5
|
+
import prisma from "../../../lib/db.js";
|
|
6
|
+
import { select } from "@clack/prompts";
|
|
7
|
+
import { startChat } from "../../chat/chat-with-ai.js";
|
|
8
|
+
import { startToolChat } from "../../chat/chat-with-ai-tool.js";
|
|
9
|
+
import { startAgentChat } from "../../chat/chat-with-ai-agent.js";
|
|
10
|
+
|
|
11
|
+
const wakeUpAction = async () => {
|
|
12
|
+
const token = await getStoredToken();
|
|
13
|
+
|
|
14
|
+
if (!token?.access_token) {
|
|
15
|
+
console.log(chalk.red("Not authenticated. Please login."));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const spinner = yoctoSpinner({ text: "Fetching User Information..." });
|
|
20
|
+
spinner.start();
|
|
21
|
+
|
|
22
|
+
const user = await prisma.user.findFirst({
|
|
23
|
+
where: {
|
|
24
|
+
sessions: {
|
|
25
|
+
some: { token: token.access_token },
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
select: {
|
|
29
|
+
id: true,
|
|
30
|
+
name: true,
|
|
31
|
+
email: true,
|
|
32
|
+
image: true,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
spinner.stop();
|
|
37
|
+
|
|
38
|
+
if (!user) {
|
|
39
|
+
console.log(chalk.red("User not found."));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log(chalk.green(`\nWelcome back, ${user.name}!\n`));
|
|
44
|
+
|
|
45
|
+
const choice = await select({
|
|
46
|
+
message: "Select an option:",
|
|
47
|
+
options: [
|
|
48
|
+
{
|
|
49
|
+
value: "chat",
|
|
50
|
+
label: "Chat",
|
|
51
|
+
hint: "Simple chat with AI",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
value: "tool",
|
|
55
|
+
label: "Tool Calling",
|
|
56
|
+
hint: "Chat with tools (Google Search, Code Execution)",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
value: "agent",
|
|
60
|
+
label: "Agentic Mode",
|
|
61
|
+
hint: "Advanced AI agent (Coming soon new versions ...)",
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
switch (choice) {
|
|
67
|
+
case "chat":
|
|
68
|
+
await startChat("chat");
|
|
69
|
+
break;
|
|
70
|
+
case "tool":
|
|
71
|
+
await startToolChat();
|
|
72
|
+
break;
|
|
73
|
+
case "agent":
|
|
74
|
+
await startAgentChat();
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const wakeUp = new Command("wakeup")
|
|
80
|
+
.description("Wake up the AI")
|
|
81
|
+
.action(wakeUpAction);
|