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/src/lib/token.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import os from "os";
|
|
5
|
+
|
|
6
|
+
// Token file paths
|
|
7
|
+
export const CONFIG_DIR = path.join(os.homedir(), ".better-auth");
|
|
8
|
+
export const TOKEN_FILE = path.join(CONFIG_DIR, "token.json");
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Read the stored token data safely
|
|
12
|
+
*/
|
|
13
|
+
export async function getStoredToken() {
|
|
14
|
+
try {
|
|
15
|
+
const data = await fs.readFile(TOKEN_FILE, "utf-8");
|
|
16
|
+
return JSON.parse(data);
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Store a token with metadata
|
|
24
|
+
*/
|
|
25
|
+
export async function storeToken(token) {
|
|
26
|
+
try {
|
|
27
|
+
await fs.mkdir(CONFIG_DIR, { recursive: true });
|
|
28
|
+
|
|
29
|
+
const tokenData = {
|
|
30
|
+
access_token: token.access_token,
|
|
31
|
+
refresh_token: token.refresh_token,
|
|
32
|
+
token_type: token.token_type || "Bearer",
|
|
33
|
+
scope: token.scope,
|
|
34
|
+
expires_at: token.expires_in
|
|
35
|
+
? new Date(Date.now() + token.expires_in * 1000).toISOString()
|
|
36
|
+
: null,
|
|
37
|
+
created_at: new Date().toISOString(),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
await fs.writeFile(
|
|
41
|
+
TOKEN_FILE,
|
|
42
|
+
JSON.stringify(tokenData, null, 2),
|
|
43
|
+
"utf-8"
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
return true;
|
|
47
|
+
} catch (err) {
|
|
48
|
+
console.error(chalk.red("Failed to store token:"), err.message);
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Remove stored token
|
|
55
|
+
*/
|
|
56
|
+
export async function clearStoredToken() {
|
|
57
|
+
try {
|
|
58
|
+
await fs.rm(TOKEN_FILE, { force: true });
|
|
59
|
+
console.log(chalk.yellow("⚠ Token cleared"));
|
|
60
|
+
return true;
|
|
61
|
+
} catch (err) {
|
|
62
|
+
console.error(chalk.red("Failed to clear token:"), err.message);
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Check if token is expired or about to expire (5 min window)
|
|
69
|
+
*/
|
|
70
|
+
export async function isTokenExpired() {
|
|
71
|
+
const token = await getStoredToken();
|
|
72
|
+
|
|
73
|
+
if (!token || !token.expires_at) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const expiresAt = new Date(token.expires_at).getTime();
|
|
78
|
+
const now = Date.now();
|
|
79
|
+
|
|
80
|
+
// token is considered expired if less than 5 minutes remain
|
|
81
|
+
const FIVE_MINUTES = 5 * 60 * 1000;
|
|
82
|
+
return expiresAt - now < FIVE_MINUTES;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Require a valid token for authenticated commands
|
|
87
|
+
*/
|
|
88
|
+
export async function requireAuth() {
|
|
89
|
+
const token = await getStoredToken();
|
|
90
|
+
|
|
91
|
+
if (!token) {
|
|
92
|
+
console.log(chalk.red("You are not logged in."));
|
|
93
|
+
console.log(chalk.gray("Run: your-cli login\n"));
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (await isTokenExpired()) {
|
|
98
|
+
console.log(chalk.yellow("Your session has expired."));
|
|
99
|
+
console.log(chalk.gray("Run: your-cli login\n"));
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return token;
|
|
104
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
|
|
2
|
+
import prisma from "../lib/db.js"
|
|
3
|
+
|
|
4
|
+
export class ChatService {
|
|
5
|
+
|
|
6
|
+
async createConversation(userId, mode = "chat", title = null) {
|
|
7
|
+
return prisma.conversation.create({
|
|
8
|
+
data: {
|
|
9
|
+
userId,
|
|
10
|
+
mode,
|
|
11
|
+
title: title || `New ${mode} conversation`
|
|
12
|
+
}
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async getOrCreateConversation(userId, conversationId = null, mode = "chat") {
|
|
17
|
+
if (conversationId) {
|
|
18
|
+
const conversation = await prisma.conversation.findFirst({
|
|
19
|
+
where: {
|
|
20
|
+
id: conversationId,
|
|
21
|
+
userId
|
|
22
|
+
},
|
|
23
|
+
include: {
|
|
24
|
+
messages: {
|
|
25
|
+
orderBy: {
|
|
26
|
+
createdAt: "asc"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (conversation) {
|
|
33
|
+
return conversation
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return await this.createConversation(userId, mode)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async createMessage(conversationId, role, content) {
|
|
41
|
+
const contentStr = typeof content === "string"
|
|
42
|
+
? content
|
|
43
|
+
: JSON.stringify(content);
|
|
44
|
+
|
|
45
|
+
return await prisma.message.create({
|
|
46
|
+
data: {
|
|
47
|
+
conversationId,
|
|
48
|
+
role,
|
|
49
|
+
content: contentStr
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
async getMessages(conversationId) {
|
|
56
|
+
const messages = await prisma.message.findMany({
|
|
57
|
+
where: { conversationId },
|
|
58
|
+
orderBy: { createdAt: "asc" }
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
return messages.map((msg) => ({
|
|
62
|
+
...msg,
|
|
63
|
+
content: this.parseContent(msg.content)
|
|
64
|
+
}))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
async getUserConversation(userId) {
|
|
70
|
+
return await prisma.conversation.findMany({
|
|
71
|
+
where: { userId },
|
|
72
|
+
orderBy: { updatedAt: "desc" },
|
|
73
|
+
include: {
|
|
74
|
+
messages: {
|
|
75
|
+
take: 1,
|
|
76
|
+
orderBy: { createdAt: "desc" }
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
async deleteConversation(conversationId, userId) {
|
|
84
|
+
return await prisma.conversation.deleteMany({
|
|
85
|
+
where: {
|
|
86
|
+
id: conversationId,
|
|
87
|
+
userId,
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
async updateConversationTitle(conversationId, title) {
|
|
94
|
+
return await prisma.conversation.update({
|
|
95
|
+
where: { id: conversationId },
|
|
96
|
+
data: { title },
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
parseContent(content) {
|
|
101
|
+
try {
|
|
102
|
+
return JSON.parse(content)
|
|
103
|
+
} catch (error) {
|
|
104
|
+
return content
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
formatMessagesForAI(messages) {
|
|
110
|
+
return messages.map((msg) => ({
|
|
111
|
+
role: msg.role,
|
|
112
|
+
content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
|
|
113
|
+
}))
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
}
|