maverick-ai-cli 1.0.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/package.json +39 -0
- package/prisma/migrations/20251116054155_start/migration.sql +6 -0
- package/prisma/migrations/20251116060620_authentication/migration.sql +69 -0
- package/prisma/migrations/20251116104449_device_flow/migration.sql +15 -0
- package/prisma/migrations/20251117173518_migraton_ai_conversation/migration.sql +31 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +112 -0
- package/src/cli/ai/google-service.js +99 -0
- package/src/cli/chat/chat-with-ai.js +293 -0
- package/src/cli/commands/ai/wakeUp.js +83 -0
- package/src/cli/commands/auth/login.js +296 -0
- package/src/cli/main.js +56 -0
- package/src/config/google.config.js +9 -0
- package/src/config/tool.config.js +106 -0
- package/src/index.js +47 -0
- package/src/lib/auth.js +26 -0
- package/src/lib/db.js +9 -0
- package/src/lib/token.js +104 -0
- package/src/service/chat.service.js +117 -0
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "maverick-ai-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"maverick": "./src/cli/main.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
11
|
+
"start": "node src/index.js",
|
|
12
|
+
"dev": "nodemon src/index.js"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [],
|
|
15
|
+
"author": "CodeMaverick-143 AKA Arpit Sarang",
|
|
16
|
+
"license": "ISC",
|
|
17
|
+
"type": "module",
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@ai-sdk/google": "^2.0.33",
|
|
20
|
+
"@clack/prompts": "^0.11.0",
|
|
21
|
+
"@prisma/client": "^6.19.0",
|
|
22
|
+
"ai": "^5.0.93",
|
|
23
|
+
"better-auth": "^1.3.34",
|
|
24
|
+
"boxen": "^8.0.1",
|
|
25
|
+
"chalk": "^5.6.2",
|
|
26
|
+
"commander": "^14.0.2",
|
|
27
|
+
"cors": "^2.8.5",
|
|
28
|
+
"dotenv": "^17.2.3",
|
|
29
|
+
"express": "^5.1.0",
|
|
30
|
+
"figlet": "^1.9.4",
|
|
31
|
+
"marked": "^15.0.12",
|
|
32
|
+
"marked-terminal": "^7.3.0",
|
|
33
|
+
"nodemon": "^3.1.11",
|
|
34
|
+
"open": "^11.0.0",
|
|
35
|
+
"prisma": "^6.19.0",
|
|
36
|
+
"yocto-spinner": "^1.0.0",
|
|
37
|
+
"zod": "^4.1.12"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
-- CreateTable
|
|
2
|
+
CREATE TABLE "user" (
|
|
3
|
+
"id" TEXT NOT NULL,
|
|
4
|
+
"name" TEXT NOT NULL,
|
|
5
|
+
"email" TEXT NOT NULL,
|
|
6
|
+
"emailVerified" BOOLEAN NOT NULL DEFAULT false,
|
|
7
|
+
"image" TEXT,
|
|
8
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
9
|
+
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
10
|
+
|
|
11
|
+
CONSTRAINT "user_pkey" PRIMARY KEY ("id")
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
-- CreateTable
|
|
15
|
+
CREATE TABLE "session" (
|
|
16
|
+
"id" TEXT NOT NULL,
|
|
17
|
+
"expiresAt" TIMESTAMP(3) NOT NULL,
|
|
18
|
+
"token" TEXT NOT NULL,
|
|
19
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
20
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
21
|
+
"ipAddress" TEXT,
|
|
22
|
+
"userAgent" TEXT,
|
|
23
|
+
"userId" TEXT NOT NULL,
|
|
24
|
+
|
|
25
|
+
CONSTRAINT "session_pkey" PRIMARY KEY ("id")
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
-- CreateTable
|
|
29
|
+
CREATE TABLE "account" (
|
|
30
|
+
"id" TEXT NOT NULL,
|
|
31
|
+
"accountId" TEXT NOT NULL,
|
|
32
|
+
"providerId" TEXT NOT NULL,
|
|
33
|
+
"userId" TEXT NOT NULL,
|
|
34
|
+
"accessToken" TEXT,
|
|
35
|
+
"refreshToken" TEXT,
|
|
36
|
+
"idToken" TEXT,
|
|
37
|
+
"accessTokenExpiresAt" TIMESTAMP(3),
|
|
38
|
+
"refreshTokenExpiresAt" TIMESTAMP(3),
|
|
39
|
+
"scope" TEXT,
|
|
40
|
+
"password" TEXT,
|
|
41
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
42
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
43
|
+
|
|
44
|
+
CONSTRAINT "account_pkey" PRIMARY KEY ("id")
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
-- CreateTable
|
|
48
|
+
CREATE TABLE "verification" (
|
|
49
|
+
"id" TEXT NOT NULL,
|
|
50
|
+
"identifier" TEXT NOT NULL,
|
|
51
|
+
"value" TEXT NOT NULL,
|
|
52
|
+
"expiresAt" TIMESTAMP(3) NOT NULL,
|
|
53
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
54
|
+
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
55
|
+
|
|
56
|
+
CONSTRAINT "verification_pkey" PRIMARY KEY ("id")
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
-- CreateIndex
|
|
60
|
+
CREATE UNIQUE INDEX "user_email_key" ON "user"("email");
|
|
61
|
+
|
|
62
|
+
-- CreateIndex
|
|
63
|
+
CREATE UNIQUE INDEX "session_token_key" ON "session"("token");
|
|
64
|
+
|
|
65
|
+
-- AddForeignKey
|
|
66
|
+
ALTER TABLE "session" ADD CONSTRAINT "session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
67
|
+
|
|
68
|
+
-- AddForeignKey
|
|
69
|
+
ALTER TABLE "account" ADD CONSTRAINT "account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
-- CreateTable
|
|
2
|
+
CREATE TABLE "deviceCode" (
|
|
3
|
+
"id" TEXT NOT NULL,
|
|
4
|
+
"deviceCode" TEXT NOT NULL,
|
|
5
|
+
"userCode" TEXT NOT NULL,
|
|
6
|
+
"userId" TEXT,
|
|
7
|
+
"expiresAt" TIMESTAMP(3) NOT NULL,
|
|
8
|
+
"status" TEXT NOT NULL,
|
|
9
|
+
"lastPolledAt" TIMESTAMP(3),
|
|
10
|
+
"pollingInterval" INTEGER,
|
|
11
|
+
"clientId" TEXT,
|
|
12
|
+
"scope" TEXT,
|
|
13
|
+
|
|
14
|
+
CONSTRAINT "deviceCode_pkey" PRIMARY KEY ("id")
|
|
15
|
+
);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
-- CreateTable
|
|
2
|
+
CREATE TABLE "Conversation" (
|
|
3
|
+
"id" TEXT NOT NULL,
|
|
4
|
+
"userId" TEXT NOT NULL,
|
|
5
|
+
"title" TEXT,
|
|
6
|
+
"mode" TEXT NOT NULL DEFAULT 'chat',
|
|
7
|
+
"creadetAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
8
|
+
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
9
|
+
|
|
10
|
+
CONSTRAINT "Conversation_pkey" PRIMARY KEY ("id")
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
-- CreateTable
|
|
14
|
+
CREATE TABLE "Message" (
|
|
15
|
+
"id" TEXT NOT NULL,
|
|
16
|
+
"conversationId" TEXT NOT NULL,
|
|
17
|
+
"role" TEXT NOT NULL,
|
|
18
|
+
"content" TEXT NOT NULL,
|
|
19
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
20
|
+
|
|
21
|
+
CONSTRAINT "Message_pkey" PRIMARY KEY ("id")
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
-- CreateIndex
|
|
25
|
+
CREATE INDEX "Message_conversationId_idx" ON "Message"("conversationId");
|
|
26
|
+
|
|
27
|
+
-- AddForeignKey
|
|
28
|
+
ALTER TABLE "Conversation" ADD CONSTRAINT "Conversation_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
29
|
+
|
|
30
|
+
-- AddForeignKey
|
|
31
|
+
ALTER TABLE "Message" ADD CONSTRAINT "Message_conversationId_fkey" FOREIGN KEY ("conversationId") REFERENCES "Conversation"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
generator client {
|
|
2
|
+
provider = "prisma-client-js"
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
datasource db {
|
|
6
|
+
provider = "postgresql"
|
|
7
|
+
url = env("DATABASE_URL")
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
model Test {
|
|
11
|
+
id String @id @default(cuid())
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
model User {
|
|
15
|
+
id String @id
|
|
16
|
+
name String
|
|
17
|
+
email String
|
|
18
|
+
emailVerified Boolean @default(false)
|
|
19
|
+
image String?
|
|
20
|
+
createdAt DateTime @default(now())
|
|
21
|
+
updatedAt DateTime @default(now()) @updatedAt
|
|
22
|
+
sessions Session[]
|
|
23
|
+
accounts Account[]
|
|
24
|
+
conversations Conversation[]
|
|
25
|
+
|
|
26
|
+
@@unique([email])
|
|
27
|
+
@@map("user")
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
model Session {
|
|
31
|
+
id String @id
|
|
32
|
+
expiresAt DateTime
|
|
33
|
+
token String
|
|
34
|
+
createdAt DateTime @default(now())
|
|
35
|
+
updatedAt DateTime @updatedAt
|
|
36
|
+
ipAddress String?
|
|
37
|
+
userAgent String?
|
|
38
|
+
userId String
|
|
39
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
40
|
+
|
|
41
|
+
@@unique([token])
|
|
42
|
+
@@map("session")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
model Account {
|
|
46
|
+
id String @id
|
|
47
|
+
accountId String
|
|
48
|
+
providerId String
|
|
49
|
+
userId String
|
|
50
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
51
|
+
accessToken String?
|
|
52
|
+
refreshToken String?
|
|
53
|
+
idToken String?
|
|
54
|
+
accessTokenExpiresAt DateTime?
|
|
55
|
+
refreshTokenExpiresAt DateTime?
|
|
56
|
+
scope String?
|
|
57
|
+
password String?
|
|
58
|
+
createdAt DateTime @default(now())
|
|
59
|
+
updatedAt DateTime @updatedAt
|
|
60
|
+
|
|
61
|
+
@@map("account")
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
model Verification {
|
|
65
|
+
id String @id
|
|
66
|
+
identifier String
|
|
67
|
+
value String
|
|
68
|
+
expiresAt DateTime
|
|
69
|
+
createdAt DateTime @default(now())
|
|
70
|
+
updatedAt DateTime @default(now()) @updatedAt
|
|
71
|
+
|
|
72
|
+
@@map("verification")
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
model DeviceCode {
|
|
76
|
+
id String @id
|
|
77
|
+
deviceCode String
|
|
78
|
+
userCode String
|
|
79
|
+
userId String?
|
|
80
|
+
expiresAt DateTime
|
|
81
|
+
status String
|
|
82
|
+
lastPolledAt DateTime?
|
|
83
|
+
pollingInterval Int?
|
|
84
|
+
clientId String?
|
|
85
|
+
scope String?
|
|
86
|
+
|
|
87
|
+
@@map("deviceCode")
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
model Conversation {
|
|
91
|
+
id String @id @default(cuid())
|
|
92
|
+
userId String
|
|
93
|
+
title String?
|
|
94
|
+
mode String @default("chat")
|
|
95
|
+
creadetAt DateTime @default(now())
|
|
96
|
+
updatedAt DateTime @default(now()) @updatedAt
|
|
97
|
+
|
|
98
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
99
|
+
messages Message[]
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
model Message {
|
|
103
|
+
id String @id @default(cuid())
|
|
104
|
+
conversationId String
|
|
105
|
+
role String
|
|
106
|
+
content String
|
|
107
|
+
createdAt DateTime @default(now())
|
|
108
|
+
|
|
109
|
+
consversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
|
|
110
|
+
|
|
111
|
+
@@index([conversationId])
|
|
112
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { google } from "@ai-sdk/google";
|
|
2
|
+
import { convertToModelMessages, streamText } from "ai";
|
|
3
|
+
import { config } from "../../config/google.config.js";
|
|
4
|
+
import chalk from "chalk"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export class AIService {
|
|
8
|
+
constructor() {
|
|
9
|
+
if (!config.googleApiKey) {
|
|
10
|
+
throw new Error("GOOGLE_API_KEY is not set in env")
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
this.model = google(config.model, {
|
|
14
|
+
apiKey: config.googleApiKey,
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async sendMessage(messages, onChunk, tools = undefined, onToolCall = null) {
|
|
21
|
+
try {
|
|
22
|
+
const streamConfig = {
|
|
23
|
+
model: this.model,
|
|
24
|
+
messages: messages,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (tools && Object.keys(tools).length > 0) {
|
|
28
|
+
streamConfig.tools = tools;
|
|
29
|
+
streamConfig.maxSteps = 5 // Allow up to 5 tool calls steps
|
|
30
|
+
|
|
31
|
+
console.log(chalk.gray(`[DEBUG] Tools enabled : ${Object.keys(tools).join(", ")}`))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
const result = streamText(streamConfig);
|
|
37
|
+
let fullResponse = " "
|
|
38
|
+
|
|
39
|
+
for await (const chunk of result.textStream) {
|
|
40
|
+
fullResponse += chunk;
|
|
41
|
+
if (onChunk) {
|
|
42
|
+
onChunk(chunk)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const fullResult = result;
|
|
47
|
+
const toolCalls = [];
|
|
48
|
+
const toolResults = []
|
|
49
|
+
|
|
50
|
+
if (fullResult.steps && Array.isArray(fullResult.steps)) {
|
|
51
|
+
for (const step of fullResult.steps) {
|
|
52
|
+
if (step.toolCall && step.toolCall.lenght > 0) {
|
|
53
|
+
for (const toolCall of step.toolCalls) {
|
|
54
|
+
toolCalls.push(toolCall)
|
|
55
|
+
|
|
56
|
+
if (onToolCall) {
|
|
57
|
+
onToolCall(toolCall)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (step.toolResult && step.toolResult.length > 0) {
|
|
63
|
+
toolResults.push(toolResult)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
content: fullResponse,
|
|
71
|
+
finishResponse: fullResult.finishResponse,
|
|
72
|
+
usage: fullResult.usage,
|
|
73
|
+
toolCalls,
|
|
74
|
+
toolResults,
|
|
75
|
+
steps: fullResult.steps
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
} catch (error) {
|
|
79
|
+
|
|
80
|
+
console.error(chalk.red("AI Service Error:"), error.message);
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async getMessage(messages, tools = undefined) {
|
|
86
|
+
let fullResponse = "";
|
|
87
|
+
|
|
88
|
+
const result = await this.sendMessage(messages, (chunk) => {
|
|
89
|
+
fullResponse += chunk
|
|
90
|
+
}, tools)
|
|
91
|
+
|
|
92
|
+
return result.content
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
}
|
|
@@ -0,0 +1,293 @@
|
|
|
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 "../../service/chat.service.js";
|
|
9
|
+
import { getStoredToken } from "../../lib/token.js"
|
|
10
|
+
import prisma from "../../lib/db.js";
|
|
11
|
+
import { yo } from "zod/v4/locales";
|
|
12
|
+
|
|
13
|
+
marked.use(
|
|
14
|
+
markedTerminal({
|
|
15
|
+
code: chalk.cyan,
|
|
16
|
+
blockquote: chalk.gray.italic,
|
|
17
|
+
heading: chalk.green.bold, // all headings
|
|
18
|
+
firstHeading: chalk.green.bold.underline, // H1 specifically
|
|
19
|
+
hr: chalk.gray, // horizontal rules
|
|
20
|
+
listitem: chalk.white, // bullet items
|
|
21
|
+
list: chalk.white, // list container
|
|
22
|
+
paragraph: chalk.white, // paragraphs
|
|
23
|
+
strong: chalk.bold, // **bold**
|
|
24
|
+
em: chalk.italic, // *italic*
|
|
25
|
+
codespan: chalk.bgBlack.white, // inline code
|
|
26
|
+
del: chalk.strikethrough, // ~~strikethrough~~
|
|
27
|
+
link: chalk.blue, // markdown link text
|
|
28
|
+
href: chalk.underline.blue, // link href (actual URL)
|
|
29
|
+
})
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
const aiService = new AIService();
|
|
34
|
+
const chatService = new ChatService();
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async function getUserFromToken() {
|
|
38
|
+
const token = await getStoredToken()
|
|
39
|
+
if (!token?.access_token) {
|
|
40
|
+
throw new Error("Not authenticated. Please run 'maverick login' first.")
|
|
41
|
+
}
|
|
42
|
+
const spinner = yoctoSpinner({ text: "Authenticating...." }).start();
|
|
43
|
+
const user = await prisma.user.findFirst({
|
|
44
|
+
where: {
|
|
45
|
+
sessions: {
|
|
46
|
+
some: { token: token.access_token }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
if (!user) {
|
|
52
|
+
spinner.error("User not found");
|
|
53
|
+
throw new Error("User not found. Please login again")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
spinner.success(`Welcome back, ${user.name}!`)
|
|
57
|
+
return user;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
async function initConversation(userId, conversationId = null, mode = "chat") {
|
|
62
|
+
const spinner = yoctoSpinner({ text: "Loading conversation...." }).start()
|
|
63
|
+
|
|
64
|
+
const conversation = await chatService.getOrCreateConversation(
|
|
65
|
+
userId,
|
|
66
|
+
conversationId,
|
|
67
|
+
mode
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
spinner.success("Conversation Loaded")
|
|
71
|
+
|
|
72
|
+
const conversationInfo = boxen(
|
|
73
|
+
`${chalk.bold("Conversation")} : ${conversation.title} \n ${chalk.gray("ID: " + conversation.id)} \n ${chalk.gray("Mode: " + conversation.mode)}`, {
|
|
74
|
+
padding: 1,
|
|
75
|
+
margin: { top: 1, bottom: 1 },
|
|
76
|
+
borderStyle: "round",
|
|
77
|
+
borderColor: "cyan",
|
|
78
|
+
title: "💬 Chat Session",
|
|
79
|
+
titleAlignment: "center"
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
console.log(conversationInfo)
|
|
85
|
+
|
|
86
|
+
if (conversation.message?.length > 0) {
|
|
87
|
+
console.log(chalk.yellow(" Previous messages: \n"));
|
|
88
|
+
displayMessages(conversation.messages);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return conversation
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
function displayMessages(messages) {
|
|
96
|
+
messages.forEach(message => {
|
|
97
|
+
if (message.role === "user") {
|
|
98
|
+
const userBox = boxen(chalk.white(message.content), {
|
|
99
|
+
padding: 1,
|
|
100
|
+
margin: { top: 1, bottom: 1, left: 2, right: 2 },
|
|
101
|
+
borderStyle: "round",
|
|
102
|
+
borderColor: "cyan",
|
|
103
|
+
title: "You",
|
|
104
|
+
titleAlignment: "left"
|
|
105
|
+
})
|
|
106
|
+
console.log(userBox)
|
|
107
|
+
} else {
|
|
108
|
+
const renderContent = marked.parse(message.content)
|
|
109
|
+
const assistantBox = boxen(chalk.white(renderContent.trim()), {
|
|
110
|
+
padding: 1,
|
|
111
|
+
margin: { top: 1, bottom: 1, left: 2, right: 2 },
|
|
112
|
+
borderStyle: "round",
|
|
113
|
+
borderColor: "cyan",
|
|
114
|
+
title: "Assistant",
|
|
115
|
+
titleAlignment: "right"
|
|
116
|
+
})
|
|
117
|
+
console.log(assistantBox)
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
async function saveMessage(conversationId, role, content) {
|
|
124
|
+
const message = await chatService.createMessage(conversationId, role, content)
|
|
125
|
+
return message
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
async function getAIResponse(conversationId) {
|
|
131
|
+
const spinner = yoctoSpinner({ text: "Thinking...", color: "yellow" }).start();
|
|
132
|
+
const dbMessages = await chatService.getMessages(conversationId)
|
|
133
|
+
const aiMessages = chatService.formatMessagesForAI(dbMessages)
|
|
134
|
+
|
|
135
|
+
let fullResponse = ""
|
|
136
|
+
|
|
137
|
+
let isFirstChunk = true
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
|
|
141
|
+
const result = await aiService.sendMessage(aiMessages, (chunk) => {
|
|
142
|
+
if (isFirstChunk) {
|
|
143
|
+
|
|
144
|
+
spinner.stop();
|
|
145
|
+
console.log("\n")
|
|
146
|
+
const header = chalk.green.bold("Assistant: ")
|
|
147
|
+
console.log(header)
|
|
148
|
+
console.log(chalk.gray("-".repeat(60)))
|
|
149
|
+
isFirstChunk = false
|
|
150
|
+
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
fullResponse += chunk
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
console.log("\n");
|
|
158
|
+
const renderedMarkedown = marked.parse(fullResponse)
|
|
159
|
+
console.log(renderedMarkedown)
|
|
160
|
+
console.log(chalk.gray("-".repeat(60)))
|
|
161
|
+
console.log("\n")
|
|
162
|
+
|
|
163
|
+
return result.content;
|
|
164
|
+
|
|
165
|
+
} catch (error) {
|
|
166
|
+
spinner.error("Error getting AI response:")
|
|
167
|
+
throw error
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
async function updateConversationTitle(conversationId, userInput, messageCount) {
|
|
175
|
+
if (messageCount === 1) {
|
|
176
|
+
const spinner = yoctoSpinner({ text: "Updating conversation title...." }).start();
|
|
177
|
+
const title = userInput.slice(0, 50) + (userInput.length > 50 ? "..." : "");
|
|
178
|
+
await chatService.updateConversationTitle(conversationId, title)
|
|
179
|
+
spinner.success("Conversation title updated")
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
const chatLoop = async (conversation) => {
|
|
185
|
+
try {
|
|
186
|
+
|
|
187
|
+
const helpBox = boxen(`${chalk.gray(' • Type your messgae and press enter')}\n${chalk.gray(' • Markdown formatting is supported in responses')}\n${chalk.gray('• Type "exit" to end the conversation')}\n${chalk.gray('• press Ctrl+C to exit')}`,
|
|
188
|
+
{
|
|
189
|
+
padding: 1,
|
|
190
|
+
margin: 1,
|
|
191
|
+
borderStyle: "round",
|
|
192
|
+
borderColor: "cyan",
|
|
193
|
+
title: "Help",
|
|
194
|
+
titleAlignment: "center",
|
|
195
|
+
dimBorder: true
|
|
196
|
+
})
|
|
197
|
+
console.log(helpBox)
|
|
198
|
+
while (true) {
|
|
199
|
+
const userInput = await text({
|
|
200
|
+
message: chalk.blue("Your Message...."),
|
|
201
|
+
placeholder: "Type your message here...",
|
|
202
|
+
validate(value) {
|
|
203
|
+
if (value.trim() === "") return "Message cannot be empty"
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
if (isCancel(userInput)) {
|
|
209
|
+
const exitBox = boxen(chalk.yellow("Chat session ended. GoodBye! "), {
|
|
210
|
+
padding: 1,
|
|
211
|
+
margin: 1,
|
|
212
|
+
borderStyle: "round",
|
|
213
|
+
borderColor: "cyan",
|
|
214
|
+
title: "GoodBye",
|
|
215
|
+
titleAlignment: "center",
|
|
216
|
+
dimBorder: true
|
|
217
|
+
})
|
|
218
|
+
console.log(exitBox)
|
|
219
|
+
process.exit(0)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
if (userInput.trim().toLowerCase() === "exit") {
|
|
224
|
+
const exitBox = boxen(chalk.yellow("Chat session ended. GoodBye! "), {
|
|
225
|
+
padding: 1,
|
|
226
|
+
margin: 1,
|
|
227
|
+
borderStyle: "round",
|
|
228
|
+
borderColor: "cyan",
|
|
229
|
+
title: "GoodBye",
|
|
230
|
+
titleAlignment: "center",
|
|
231
|
+
dimBorder: true
|
|
232
|
+
})
|
|
233
|
+
console.log(exitBox)
|
|
234
|
+
break
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
await saveMessage(conversation.id, "user", userInput)
|
|
238
|
+
|
|
239
|
+
const message = await chatService.getMessages(conversation.id)
|
|
240
|
+
|
|
241
|
+
const aiResponse = await getAIResponse(conversation.id)
|
|
242
|
+
|
|
243
|
+
await saveMessage(conversation.id, "assistant", aiResponse)
|
|
244
|
+
|
|
245
|
+
await updateConversationTitle(conversation.id, userInput, message.length)
|
|
246
|
+
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
} catch (error) {
|
|
250
|
+
const errorBox = boxen(chalk.red(`Error: ${error.message}`), {
|
|
251
|
+
padding: 1,
|
|
252
|
+
margin: 1,
|
|
253
|
+
borderStyle: "round",
|
|
254
|
+
borderColor: "red",
|
|
255
|
+
title: "Error",
|
|
256
|
+
titleAlignment: "center",
|
|
257
|
+
dimBorder: true
|
|
258
|
+
})
|
|
259
|
+
console.log(errorBox)
|
|
260
|
+
process.exit(1)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
export async function startChat(mode = "chat", conversationId = null) {
|
|
267
|
+
try {
|
|
268
|
+
intro(
|
|
269
|
+
boxen(chalk.bold.cyan("Maverick AI Chat"), {
|
|
270
|
+
padding: 1,
|
|
271
|
+
borderStyle: "double",
|
|
272
|
+
borderColor: "cyan"
|
|
273
|
+
})
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
const user = await getUserFromToken()
|
|
277
|
+
const conversation = await initConversation(user.id, conversationId, mode);
|
|
278
|
+
await chatLoop(conversation)
|
|
279
|
+
|
|
280
|
+
outro(chalk.green('⚡️ Thanks For Chatting.....'))
|
|
281
|
+
|
|
282
|
+
} catch (error) {
|
|
283
|
+
const errorBox = boxen(chalk.red(`Error: ${error.message}`), {
|
|
284
|
+
padding: 1,
|
|
285
|
+
margin: 1,
|
|
286
|
+
borderStyle: "round",
|
|
287
|
+
borderColor: "red",
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
|