maverick-ai-cli 3.0.2 → 3.0.3
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/README.md +94 -31
- package/package.json +2 -3
- package/src/cli/chat/chat-with-ai.js +30 -38
- package/src/cli/commands/ai/wakeUp.js +2 -17
- package/src/cli/commands/auth/login.js +2 -16
- package/src/index.js +129 -0
- package/src/lib/api-client.js +63 -0
package/README.md
CHANGED
|
@@ -1,64 +1,73 @@
|
|
|
1
1
|
# Maverick AI CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An intelligent command-line interface powered by AI — your coding companion directly in your terminal. Authenticate via GitHub, chat with Gemini AI, and get intelligent responses with markdown rendering.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Install globally:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npm install -g maverick-ai-cli
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Or run directly with `npx`:
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
```bash
|
|
16
|
+
npx maverick-ai-cli <command>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
> **Prerequisites:** Node.js 18+ is required.
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
## Commands
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
### `login` — Authenticate via GitHub
|
|
20
24
|
|
|
21
25
|
```bash
|
|
22
26
|
maverick login
|
|
23
27
|
```
|
|
24
|
-
This will open a browser window for you to sign in with your GitHub account.
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
Opens a browser for GitHub OAuth device flow authentication. Your credentials are stored locally at `~/.better-auth/token.json`.
|
|
30
|
+
|
|
31
|
+
### `whoami` — Check current user
|
|
32
|
+
|
|
27
33
|
```bash
|
|
28
34
|
maverick whoami
|
|
29
35
|
```
|
|
30
36
|
|
|
31
|
-
|
|
37
|
+
### `logout` — Sign out
|
|
38
|
+
|
|
32
39
|
```bash
|
|
33
40
|
maverick logout
|
|
34
41
|
```
|
|
35
42
|
|
|
36
|
-
### AI
|
|
37
|
-
|
|
38
|
-
To start a chat session with Maverick:
|
|
43
|
+
### `wakeup` — Start AI session
|
|
39
44
|
|
|
40
45
|
```bash
|
|
41
46
|
maverick wakeup
|
|
42
47
|
```
|
|
43
48
|
|
|
44
|
-
|
|
45
|
-
- **Chat
|
|
46
|
-
- **
|
|
47
|
-
- **Agent
|
|
49
|
+
Select a mode:
|
|
50
|
+
- **Chat** — Conversational AI with markdown rendering
|
|
51
|
+
- **Tool Calling** — AI with Google Search & code execution *(coming soon)*
|
|
52
|
+
- **Agent** — Autonomous AI agent *(coming soon)*
|
|
48
53
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
54
|
+
### Chat Features
|
|
55
|
+
|
|
56
|
+
- Markdown rendering in terminal
|
|
57
|
+
- Context-aware conversations
|
|
58
|
+
- Persistent session history
|
|
59
|
+
- Streaming AI responses
|
|
53
60
|
|
|
54
61
|
## Development
|
|
55
62
|
|
|
56
|
-
|
|
63
|
+
### Server Setup
|
|
64
|
+
|
|
65
|
+
The backend server powers authentication and data storage. It's deployed at `https://maverick-cli.onrender.com`.
|
|
57
66
|
|
|
58
67
|
1. **Clone the repository:**
|
|
59
68
|
```bash
|
|
60
69
|
git clone https://github.com/CodeMaverick-143/maverick-cli.git
|
|
61
|
-
cd
|
|
70
|
+
cd maverick-cli/server
|
|
62
71
|
```
|
|
63
72
|
|
|
64
73
|
2. **Install dependencies:**
|
|
@@ -66,18 +75,20 @@ If you want to contribute or run the server locally:
|
|
|
66
75
|
npm install
|
|
67
76
|
```
|
|
68
77
|
|
|
69
|
-
3. **
|
|
70
|
-
Create a `.env` file in the `server` directory
|
|
78
|
+
3. **Environment Variables:**
|
|
79
|
+
Create a `.env` file in the `server` directory:
|
|
71
80
|
```env
|
|
72
81
|
DATABASE_URL="postgresql://..."
|
|
73
|
-
GITHUB_CLIENT_ID="
|
|
74
|
-
GITHUB_CLIENT_SECRET="
|
|
75
|
-
BETTER_AUTH_SECRET="
|
|
76
|
-
BETTER_AUTH_URL="
|
|
77
|
-
|
|
82
|
+
GITHUB_CLIENT_ID="your_github_oauth_app_client_id"
|
|
83
|
+
GITHUB_CLIENT_SECRET="your_github_oauth_app_client_secret"
|
|
84
|
+
BETTER_AUTH_SECRET="your_random_auth_secret"
|
|
85
|
+
BETTER_AUTH_URL="http://localhost:3005"
|
|
86
|
+
GOOGLE_GENERATIVE_AI_API_KEY="your_gemini_api_key"
|
|
87
|
+
ORBITAL_MODEL="gemini-2.5-flash"
|
|
88
|
+
PORT=3005
|
|
78
89
|
```
|
|
79
90
|
|
|
80
|
-
4. **
|
|
91
|
+
4. **Database Setup:**
|
|
81
92
|
```bash
|
|
82
93
|
npx prisma generate
|
|
83
94
|
npx prisma migrate dev
|
|
@@ -88,5 +99,57 @@ If you want to contribute or run the server locally:
|
|
|
88
99
|
npm run dev
|
|
89
100
|
```
|
|
90
101
|
|
|
91
|
-
|
|
102
|
+
### Architecture
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
server/
|
|
106
|
+
├── src/
|
|
107
|
+
│ ├── index.js # Express server + API endpoints
|
|
108
|
+
│ ├── cli/
|
|
109
|
+
│ │ ├── main.js # CLI entry point (Commander.js)
|
|
110
|
+
│ │ ├── commands/
|
|
111
|
+
│ │ │ ├── auth/login.js # login, logout, whoami
|
|
112
|
+
│ │ │ └── ai/wakeUp.js # wakeup command
|
|
113
|
+
│ │ ├── chat/
|
|
114
|
+
│ │ │ └── chat-with-ai.js # AI chat loop
|
|
115
|
+
│ │ └── ai/
|
|
116
|
+
│ │ └── google-service.js # Gemini AI SDK
|
|
117
|
+
│ ├── lib/
|
|
118
|
+
│ │ ├── api-client.js # CLI → Server HTTP client
|
|
119
|
+
│ │ ├── auth.js # Better Auth config (server)
|
|
120
|
+
│ │ ├── db.js # Prisma client (server)
|
|
121
|
+
│ │ └── token.js # Token storage (~/.better-auth/)
|
|
122
|
+
│ ├── config/
|
|
123
|
+
│ │ ├── google.config.js # AI model config
|
|
124
|
+
│ │ └── tool.config.js # Tool definitions
|
|
125
|
+
│ └── service/
|
|
126
|
+
│ └── chat.service.js # Chat DB service (server)
|
|
127
|
+
└── prisma/
|
|
128
|
+
└── schema.prisma # Database schema
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### API Endpoints
|
|
132
|
+
|
|
133
|
+
| Endpoint | Method | Description |
|
|
134
|
+
|---|---|---|
|
|
135
|
+
| `/api/auth/*` | ALL | Better Auth routes |
|
|
136
|
+
| `/api/me` | GET | Get current session |
|
|
137
|
+
| `/api/cli/user` | GET | Get user from Bearer token |
|
|
138
|
+
| `/api/cli/conversations` | POST | Create conversation |
|
|
139
|
+
| `/api/cli/conversations/:id` | GET | Get conversation + messages |
|
|
140
|
+
| `/api/cli/conversations/:id/title` | PUT | Update title |
|
|
141
|
+
| `/api/cli/conversations/:id/messages` | GET | List messages |
|
|
142
|
+
| `/api/cli/conversations/:id/messages` | POST | Create message |
|
|
143
|
+
|
|
144
|
+
## Tech Stack
|
|
145
|
+
|
|
146
|
+
- **Runtime:** Node.js
|
|
147
|
+
- **AI:** Google Gemini via Vercel AI SDK
|
|
148
|
+
- **Auth:** Better Auth (GitHub OAuth + Device Flow)
|
|
149
|
+
- **Database:** PostgreSQL + Prisma ORM
|
|
150
|
+
- **Server:** Express 5
|
|
151
|
+
- **CLI:** Commander.js, Clack prompts, Chalk
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
92
155
|
ISC
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "maverick-ai-cli",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
11
11
|
"start": "node src/index.js",
|
|
12
|
-
"dev": "nodemon src/index.js"
|
|
13
|
-
"postinstall": "prisma generate"
|
|
12
|
+
"dev": "nodemon src/index.js"
|
|
14
13
|
},
|
|
15
14
|
"keywords": [],
|
|
16
15
|
"author": "CodeMaverick-143 AKA Arpit Sarang",
|
|
@@ -5,10 +5,7 @@ import yoctoSpinner from "yocto-spinner";
|
|
|
5
5
|
import { marked } from "marked";
|
|
6
6
|
import { markedTerminal } from "marked-terminal"
|
|
7
7
|
import { AIService } from "../ai/google-service.js";
|
|
8
|
-
import {
|
|
9
|
-
import { getStoredToken } from "../../lib/token.js"
|
|
10
|
-
import prisma from "../../lib/db.js";
|
|
11
|
-
import { yo } from "zod/v4/locales";
|
|
8
|
+
import { apiClient } from "../../lib/api-client.js";
|
|
12
9
|
|
|
13
10
|
marked.use(
|
|
14
11
|
markedTerminal({
|
|
@@ -31,41 +28,36 @@ marked.use(
|
|
|
31
28
|
|
|
32
29
|
|
|
33
30
|
const aiService = new AIService();
|
|
34
|
-
const chatService = new ChatService();
|
|
35
31
|
|
|
36
32
|
|
|
37
33
|
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
34
|
const spinner = yoctoSpinner({ text: "Authenticating...." }).start();
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (!user) {
|
|
52
|
-
spinner.error("User not found");
|
|
53
|
-
throw new Error("User not found. Please login again")
|
|
35
|
+
try {
|
|
36
|
+
const user = await apiClient.getUser();
|
|
37
|
+
spinner.success(`Welcome back, ${user.name}!`)
|
|
38
|
+
return user;
|
|
39
|
+
} catch (err) {
|
|
40
|
+
spinner.error("Authentication failed");
|
|
41
|
+
throw err;
|
|
54
42
|
}
|
|
55
|
-
|
|
56
|
-
spinner.success(`Welcome back, ${user.name}!`)
|
|
57
|
-
return user;
|
|
58
43
|
}
|
|
59
44
|
|
|
60
45
|
|
|
61
46
|
async function initConversation(userId, conversationId = null, mode = "chat") {
|
|
62
47
|
const spinner = yoctoSpinner({ text: "Loading conversation...." }).start()
|
|
63
48
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
49
|
+
let conversation;
|
|
50
|
+
if (conversationId) {
|
|
51
|
+
try {
|
|
52
|
+
conversation = await apiClient.getConversation(conversationId);
|
|
53
|
+
} catch {
|
|
54
|
+
// conversation not found, create new one
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!conversation) {
|
|
59
|
+
conversation = await apiClient.createConversation(mode);
|
|
60
|
+
}
|
|
69
61
|
|
|
70
62
|
spinner.success("Conversation Loaded")
|
|
71
63
|
|
|
@@ -83,7 +75,7 @@ async function initConversation(userId, conversationId = null, mode = "chat") {
|
|
|
83
75
|
|
|
84
76
|
console.log(conversationInfo)
|
|
85
77
|
|
|
86
|
-
if (conversation.
|
|
78
|
+
if (conversation.messages?.length > 0) {
|
|
87
79
|
console.log(chalk.yellow(" Previous messages: \n"));
|
|
88
80
|
displayMessages(conversation.messages);
|
|
89
81
|
}
|
|
@@ -121,16 +113,18 @@ function displayMessages(messages) {
|
|
|
121
113
|
|
|
122
114
|
|
|
123
115
|
async function saveMessage(conversationId, role, content) {
|
|
124
|
-
|
|
125
|
-
return message
|
|
116
|
+
return await apiClient.createMessage(conversationId, role, content);
|
|
126
117
|
}
|
|
127
118
|
|
|
128
119
|
|
|
129
120
|
|
|
130
121
|
async function getAIResponse(conversationId) {
|
|
131
122
|
const spinner = yoctoSpinner({ text: "Thinking...", color: "yellow" }).start();
|
|
132
|
-
const dbMessages = await
|
|
133
|
-
const aiMessages =
|
|
123
|
+
const dbMessages = await apiClient.getMessages(conversationId);
|
|
124
|
+
const aiMessages = dbMessages.map(msg => ({
|
|
125
|
+
role: msg.role,
|
|
126
|
+
content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
|
|
127
|
+
}));
|
|
134
128
|
|
|
135
129
|
let fullResponse = ""
|
|
136
130
|
|
|
@@ -175,7 +169,7 @@ async function updateConversationTitle(conversationId, userInput, messageCount)
|
|
|
175
169
|
if (messageCount === 1) {
|
|
176
170
|
const spinner = yoctoSpinner({ text: "Updating conversation title...." }).start();
|
|
177
171
|
const title = userInput.slice(0, 50) + (userInput.length > 50 ? "..." : "");
|
|
178
|
-
await
|
|
172
|
+
await apiClient.updateTitle(conversationId, title);
|
|
179
173
|
spinner.success("Conversation title updated")
|
|
180
174
|
}
|
|
181
175
|
}
|
|
@@ -236,13 +230,13 @@ const chatLoop = async (conversation) => {
|
|
|
236
230
|
|
|
237
231
|
await saveMessage(conversation.id, "user", userInput)
|
|
238
232
|
|
|
239
|
-
const
|
|
233
|
+
const messages = await apiClient.getMessages(conversation.id)
|
|
240
234
|
|
|
241
235
|
const aiResponse = await getAIResponse(conversation.id)
|
|
242
236
|
|
|
243
237
|
await saveMessage(conversation.id, "assistant", aiResponse)
|
|
244
238
|
|
|
245
|
-
await updateConversationTitle(conversation.id, userInput,
|
|
239
|
+
await updateConversationTitle(conversation.id, userInput, messages.length)
|
|
246
240
|
|
|
247
241
|
}
|
|
248
242
|
|
|
@@ -289,5 +283,3 @@ export async function startChat(mode = "chat", conversationId = null) {
|
|
|
289
283
|
|
|
290
284
|
}
|
|
291
285
|
}
|
|
292
|
-
|
|
293
|
-
|
|
@@ -2,7 +2,7 @@ import chalk from "chalk";
|
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import yoctoSpinner from "yocto-spinner"
|
|
4
4
|
import { requireAuth } from "../../../lib/token.js";
|
|
5
|
-
import
|
|
5
|
+
import { apiClient } from "../../../lib/api-client.js";
|
|
6
6
|
import { select } from "@clack/prompts";
|
|
7
7
|
import { startChat } from "../../chat/chat-with-ai.js";
|
|
8
8
|
|
|
@@ -13,22 +13,7 @@ const wakeUpAction = async () => {
|
|
|
13
13
|
spinner.start()
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
const user = await
|
|
17
|
-
where: {
|
|
18
|
-
sessions: {
|
|
19
|
-
some: {
|
|
20
|
-
token: token.access_token
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
|
-
select: {
|
|
25
|
-
id: true,
|
|
26
|
-
name: true,
|
|
27
|
-
email: true,
|
|
28
|
-
image: true
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
});
|
|
16
|
+
const user = await apiClient.getUser();
|
|
32
17
|
|
|
33
18
|
spinner.stop()
|
|
34
19
|
|
|
@@ -14,7 +14,7 @@ import yoctoSpinner from "yocto-spinner";
|
|
|
14
14
|
import * as z from "zod";
|
|
15
15
|
import dotenv from "dotenv";
|
|
16
16
|
import { clearStoredToken, getStoredToken, isTokenExpired, requireAuth, storeToken } from "../../../lib/token.js";
|
|
17
|
-
import
|
|
17
|
+
import { apiClient } from "../../../lib/api-client.js";
|
|
18
18
|
|
|
19
19
|
dotenv.config();
|
|
20
20
|
|
|
@@ -250,21 +250,7 @@ export async function whoamiAction(opts) {
|
|
|
250
250
|
process.exit(1)
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
-
const user = await
|
|
254
|
-
where: {
|
|
255
|
-
sessions: {
|
|
256
|
-
some: {
|
|
257
|
-
token: token.access_token,
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
},
|
|
261
|
-
select: {
|
|
262
|
-
id: true,
|
|
263
|
-
name: true,
|
|
264
|
-
email: true,
|
|
265
|
-
image: true
|
|
266
|
-
},
|
|
267
|
-
})
|
|
253
|
+
const user = await apiClient.getUser();
|
|
268
254
|
|
|
269
255
|
console.log(
|
|
270
256
|
chalk.bold.greenBright(`\n USER: ${user.name}
|
package/src/index.js
CHANGED
|
@@ -44,6 +44,135 @@ app.get('/health', (req, res) => {
|
|
|
44
44
|
res.send("Backend is running")
|
|
45
45
|
})
|
|
46
46
|
|
|
47
|
+
|
|
48
|
+
async function authenticateToken(req, res, next) {
|
|
49
|
+
const authHeader = req.headers.authorization;
|
|
50
|
+
const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
|
|
51
|
+
|
|
52
|
+
if (!token) {
|
|
53
|
+
return res.status(401).json({ error: "Missing authorization token" });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const { default: prisma } = await import("./lib/db.js");
|
|
58
|
+
const user = await prisma.user.findFirst({
|
|
59
|
+
where: {
|
|
60
|
+
sessions: {
|
|
61
|
+
some: { token }
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
select: { id: true, name: true, email: true, image: true }
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (!user) {
|
|
68
|
+
return res.status(401).json({ error: "Invalid or expired token" });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
req.user = user;
|
|
72
|
+
next();
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.error("Auth middleware error:", err.message);
|
|
75
|
+
return res.status(500).json({ error: "Authentication failed" });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// GET /api/cli/user — get current user from token
|
|
80
|
+
app.get("/api/cli/user", authenticateToken, (req, res) => {
|
|
81
|
+
res.json(req.user);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// POST /api/cli/conversations — create a conversation
|
|
85
|
+
app.post("/api/cli/conversations", authenticateToken, async (req, res) => {
|
|
86
|
+
try {
|
|
87
|
+
const { default: prisma } = await import("./lib/db.js");
|
|
88
|
+
const { mode = "chat", title } = req.body || {};
|
|
89
|
+
const conversation = await prisma.conversation.create({
|
|
90
|
+
data: {
|
|
91
|
+
userId: req.user.id,
|
|
92
|
+
mode,
|
|
93
|
+
title: title || `New ${mode} conversation`
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
res.json(conversation);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
console.error("Create conversation error:", err.message);
|
|
99
|
+
res.status(500).json({ error: "Failed to create conversation" });
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// GET /api/cli/conversations/:id — get a conversation with messages
|
|
104
|
+
app.get("/api/cli/conversations/:id", authenticateToken, async (req, res) => {
|
|
105
|
+
try {
|
|
106
|
+
const { default: prisma } = await import("./lib/db.js");
|
|
107
|
+
const conversation = await prisma.conversation.findFirst({
|
|
108
|
+
where: { id: req.params.id, userId: req.user.id },
|
|
109
|
+
include: {
|
|
110
|
+
messages: { orderBy: { createdAt: "asc" } }
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (!conversation) {
|
|
115
|
+
return res.status(404).json({ error: "Conversation not found" });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
res.json(conversation);
|
|
119
|
+
} catch (err) {
|
|
120
|
+
console.error("Get conversation error:", err.message);
|
|
121
|
+
res.status(500).json({ error: "Failed to get conversation" });
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// PUT /api/cli/conversations/:id/title — update conversation title
|
|
126
|
+
app.put("/api/cli/conversations/:id/title", authenticateToken, async (req, res) => {
|
|
127
|
+
try {
|
|
128
|
+
const { default: prisma } = await import("./lib/db.js");
|
|
129
|
+
const { title } = req.body;
|
|
130
|
+
const conversation = await prisma.conversation.update({
|
|
131
|
+
where: { id: req.params.id },
|
|
132
|
+
data: { title }
|
|
133
|
+
});
|
|
134
|
+
res.json(conversation);
|
|
135
|
+
} catch (err) {
|
|
136
|
+
console.error("Update title error:", err.message);
|
|
137
|
+
res.status(500).json({ error: "Failed to update title" });
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// GET /api/cli/conversations/:id/messages — get messages
|
|
142
|
+
app.get("/api/cli/conversations/:id/messages", authenticateToken, async (req, res) => {
|
|
143
|
+
try {
|
|
144
|
+
const { default: prisma } = await import("./lib/db.js");
|
|
145
|
+
const messages = await prisma.message.findMany({
|
|
146
|
+
where: { conversationId: req.params.id },
|
|
147
|
+
orderBy: { createdAt: "asc" }
|
|
148
|
+
});
|
|
149
|
+
res.json(messages);
|
|
150
|
+
} catch (err) {
|
|
151
|
+
console.error("Get messages error:", err.message);
|
|
152
|
+
res.status(500).json({ error: "Failed to get messages" });
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// POST /api/cli/conversations/:id/messages — create a message
|
|
157
|
+
app.post("/api/cli/conversations/:id/messages", authenticateToken, async (req, res) => {
|
|
158
|
+
try {
|
|
159
|
+
const { default: prisma } = await import("./lib/db.js");
|
|
160
|
+
const { role, content } = req.body;
|
|
161
|
+
const contentStr = typeof content === "string" ? content : JSON.stringify(content);
|
|
162
|
+
const message = await prisma.message.create({
|
|
163
|
+
data: {
|
|
164
|
+
conversationId: req.params.id,
|
|
165
|
+
role,
|
|
166
|
+
content: contentStr
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
res.json(message);
|
|
170
|
+
} catch (err) {
|
|
171
|
+
console.error("Create message error:", err.message);
|
|
172
|
+
res.status(500).json({ error: "Failed to create message" });
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
47
176
|
app.listen(process.env.PORT, () => {
|
|
48
177
|
console.log(`running on PORT : http://localhost:${process.env.PORT}`)
|
|
49
178
|
})
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { getStoredToken } from "./token.js";
|
|
2
|
+
|
|
3
|
+
const BASE_URL = "https://maverick-cli.onrender.com";
|
|
4
|
+
|
|
5
|
+
async function getAuthHeaders() {
|
|
6
|
+
const token = await getStoredToken();
|
|
7
|
+
if (!token?.access_token) {
|
|
8
|
+
throw new Error("Not authenticated. Please run 'maverick login' first.");
|
|
9
|
+
}
|
|
10
|
+
return {
|
|
11
|
+
"Authorization": `Bearer ${token.access_token}`,
|
|
12
|
+
"Content-Type": "application/json"
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function request(method, path, body = null) {
|
|
17
|
+
const headers = await getAuthHeaders();
|
|
18
|
+
const options = { method, headers };
|
|
19
|
+
if (body) {
|
|
20
|
+
options.body = JSON.stringify(body);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const res = await fetch(`${BASE_URL}${path}`, options);
|
|
24
|
+
|
|
25
|
+
if (!res.ok) {
|
|
26
|
+
const err = await res.json().catch(() => ({ error: res.statusText }));
|
|
27
|
+
throw new Error(err.error || `API error: ${res.status}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return res.json();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const apiClient = {
|
|
34
|
+
/** Get current user from token */
|
|
35
|
+
async getUser() {
|
|
36
|
+
return request("GET", "/api/cli/user");
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
/** Create a new conversation */
|
|
40
|
+
async createConversation(mode = "chat", title = null) {
|
|
41
|
+
return request("POST", "/api/cli/conversations", { mode, title });
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
/** Get a conversation by ID (with messages) */
|
|
45
|
+
async getConversation(conversationId) {
|
|
46
|
+
return request("GET", `/api/cli/conversations/${conversationId}`);
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
/** Update conversation title */
|
|
50
|
+
async updateTitle(conversationId, title) {
|
|
51
|
+
return request("PUT", `/api/cli/conversations/${conversationId}/title`, { title });
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
/** Get messages for a conversation */
|
|
55
|
+
async getMessages(conversationId) {
|
|
56
|
+
return request("GET", `/api/cli/conversations/${conversationId}/messages`);
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
/** Create a message in a conversation */
|
|
60
|
+
async createMessage(conversationId, role, content) {
|
|
61
|
+
return request("POST", `/api/cli/conversations/${conversationId}/messages`, { role, content });
|
|
62
|
+
}
|
|
63
|
+
};
|