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.
@@ -0,0 +1,83 @@
1
+ import chalk from "chalk";
2
+ import { Command } from "commander";
3
+ import yoctoSpinner from "yocto-spinner"
4
+ import { requireAuth } from "../../../lib/token.js";
5
+ import prisma from "../../../lib/db.js";
6
+ import { select } from "@clack/prompts";
7
+ import { startChat } from "../../chat/chat-with-ai.js";
8
+
9
+ const wakeUpAction = async () => {
10
+ const token = await requireAuth();
11
+
12
+ const spinner = yoctoSpinner({ text: "Fetching user information....." })
13
+ spinner.start()
14
+
15
+
16
+ const user = await prisma.user.findFirst({
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
+ });
32
+
33
+ spinner.stop()
34
+
35
+ if (!user) {
36
+ console.log(chalk.red("User not found"))
37
+ return;
38
+ }
39
+
40
+ console.log(chalk.green(`Welcome back, ${user.name}! \n`))
41
+
42
+ const choice = await select({
43
+ message: "Select an option:",
44
+ options: [
45
+ {
46
+ value: "chat",
47
+ label: "Chat",
48
+ hint: "Simple chat with AI"
49
+ },
50
+ {
51
+ value: "tool",
52
+ label: "Tool Calling",
53
+ hint: "Chat with tools (Google Search, Code Execution)"
54
+ },
55
+ {
56
+ value: "agent",
57
+ label: "Agent",
58
+ hint: "Advanced AI agent (coming soon)"
59
+ },
60
+ ],
61
+ })
62
+
63
+ switch (choice) {
64
+ case "chat":
65
+ console.log("chat is selected")
66
+ await startChat("chat")
67
+ break;
68
+ case "tool":
69
+ console.log(chalk.green("Tool calling is selected"))
70
+ break;
71
+ case "agent":
72
+ console.log(chalk.yellow("Agentic mode coming soon"))
73
+ break;
74
+
75
+ }
76
+
77
+
78
+
79
+ }
80
+
81
+ export const wakeup = new Command("wakeup")
82
+ .description("wake up the ai")
83
+ .action(wakeUpAction)
@@ -0,0 +1,296 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { cancel, confirm, intro, isCancel, outro } from "@clack/prompts";
4
+ import { logger } from "better-auth";
5
+ import { createAuthClient } from "better-auth/client";
6
+ import { deviceAuthorizationClient } from "better-auth/client/plugins";
7
+
8
+ import chalk from "chalk";
9
+ import { Command } from "commander";
10
+ import open from "open";
11
+ import os from "os";
12
+ import path from "path";
13
+ import yoctoSpinner from "yocto-spinner";
14
+ import * as z from "zod";
15
+ import dotenv from "dotenv";
16
+ import { clearStoredToken, getStoredToken, isTokenExpired, requireAuth, storeToken } from "../../../lib/token.js";
17
+ import prisma from "../../../lib/db.js";
18
+
19
+ dotenv.config();
20
+
21
+ const URL = "https://maverick-cli-backend.onrender.com";
22
+ const CLIENT_ID = process.env.GITHUB_CLIENT_ID;
23
+
24
+ // ~/.better-auth/token.json
25
+ export const CONFIG_DIR = path.join(os.homedir(), ".better-auth");
26
+ export const TOKEN_FILE = path.join(CONFIG_DIR, "token.json");
27
+
28
+ export async function loginAction(opts) {
29
+ // Validate CLI args
30
+ const OptionsSchema = z.object({
31
+ serverUrl: z.string().optional(),
32
+ clientId: z.string().optional(),
33
+ });
34
+
35
+ const parsed = OptionsSchema.parse(opts);
36
+
37
+ const serverUrl = parsed.serverUrl || URL;
38
+ const clientId = parsed.clientId || CLIENT_ID;
39
+
40
+ intro(chalk.cyan("🔐 Better Auth – Device Login"));
41
+
42
+ // Token check (optional)
43
+ const existingToken = await getStoredToken();
44
+ const expired = await isTokenExpired()
45
+
46
+ if (existingToken && !expired) {
47
+ const reAuth = await confirm({
48
+ message: "You are already logged in. Login again?",
49
+ initialValue: false,
50
+ });
51
+
52
+ if (isCancel(reAuth) || !reAuth) {
53
+ cancel("Login cancelled");
54
+ process.exit(0);
55
+ }
56
+ }
57
+
58
+ // Create auth client
59
+ const authClient = createAuthClient({
60
+ baseURL: serverUrl,
61
+ plugins: [deviceAuthorizationClient()],
62
+ });
63
+
64
+ const spinner = yoctoSpinner({ text: "Requesting device authorization..." });
65
+ spinner.start();
66
+
67
+ try {
68
+ // Request device code
69
+ const { data, error } = await authClient.device.code({
70
+ client_id: clientId,
71
+ scope: "openid profile email",
72
+ });
73
+
74
+ spinner.stop();
75
+
76
+ if (!data || error) {
77
+ logger.error(
78
+ `Failed to request device authorization: ${error?.error_description || error?.error
79
+ }`
80
+ );
81
+ process.exit(1);
82
+ }
83
+
84
+ const {
85
+ device_code,
86
+ user_code,
87
+ verification_uri,
88
+ verification_uri_complete,
89
+ interval = 5,
90
+ expires_in,
91
+ } = data;
92
+
93
+ console.log(chalk.cyan("\n🔗 Device Authorization Required"));
94
+ console.log(
95
+ `Visit: ${chalk.underline.blue(
96
+ verification_uri_complete || verification_uri
97
+ )}`
98
+ );
99
+ console.log(`Code: ${chalk.bold.green(user_code)}\n`);
100
+
101
+ // Auto-open browser
102
+ const shouldOpen = await confirm({
103
+ message: "Open browser automatically?",
104
+ initialValue: true,
105
+ });
106
+
107
+ if (!isCancel(shouldOpen) && shouldOpen) {
108
+ await open(verification_uri_complete || verification_uri);
109
+ }
110
+
111
+ console.log(
112
+ chalk.gray(
113
+ `⏳ Waiting for authorization (expires in ${Math.floor(
114
+ expires_in / 60
115
+ )}m)...`
116
+ )
117
+ );
118
+
119
+ const token = await pollForToken(
120
+ authClient,
121
+ device_code,
122
+ clientId,
123
+ interval
124
+ );
125
+
126
+ if (token) {
127
+ const saved = await storeToken(token);
128
+
129
+ if (!saved) {
130
+ console.log(chalk.yellow("⚠️ Warning: Could not save authentication token"));
131
+ console.log(chalk.yellow("You may need to login again on next use."));
132
+ }
133
+
134
+ outro(chalk.green("✔ Logged in successfully!"));
135
+
136
+ // Todo : Get the user data
137
+ }
138
+ } catch (err) {
139
+ spinner.stop();
140
+ process.exit(1);
141
+ }
142
+ }
143
+
144
+ // --------------------------------------
145
+ // POLLING FOR TOKEN
146
+ // --------------------------------------
147
+
148
+ async function pollForToken(authClient, deviceCode, clientId, interval) {
149
+ let pollInterval = interval;
150
+
151
+ return new Promise((resolve, reject) => {
152
+ const spinner = yoctoSpinner({ text: "", color: "cyan" });
153
+ let dots = 0;
154
+
155
+ const poll = async () => {
156
+ dots = (dots + 1) % 4;
157
+ spinner.text = chalk.gray(
158
+ `Polling for authorization ${".".repeat(dots)}${" ".repeat(3 - dots)}`
159
+ );
160
+
161
+ if (!spinner.isSpinning) spinner.start();
162
+
163
+ try {
164
+ const { data, error } = await authClient.device.token({
165
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
166
+ device_code: deviceCode,
167
+ client_id: clientId,
168
+ });
169
+
170
+ if (data?.access_token) {
171
+ spinner.stop();
172
+ resolve(data);
173
+ return;
174
+ }
175
+
176
+ if (error) {
177
+ switch (error.error) {
178
+ case "authorization_pending":
179
+ // Keep polling
180
+ break;
181
+ case "slow_down":
182
+ pollInterval += 5;
183
+ break;
184
+ case "access_denied":
185
+ spinner.stop();
186
+ console.error(chalk.red("✖ Access denied."));
187
+ reject(error);
188
+ return;
189
+ case "expired_token":
190
+ spinner.stop();
191
+ console.error(chalk.red("✖ Device code expired."));
192
+ reject(error);
193
+ return;
194
+ default:
195
+ spinner.stop();
196
+ reject(error);
197
+ return;
198
+ }
199
+ }
200
+ } catch (err) {
201
+ spinner.stop();
202
+ reject(err);
203
+ return;
204
+ }
205
+
206
+ setTimeout(poll, pollInterval * 1000);
207
+ };
208
+
209
+ poll();
210
+ });
211
+ }
212
+
213
+ export async function logoutAction() {
214
+ intro(chalk.bold("👋🏻 Logout"))
215
+
216
+ const token = await getStoredToken();
217
+
218
+ if (!token) {
219
+ console.log(chalk.yellow("You'r are not logged in"));
220
+ process.exit(0);
221
+ }
222
+
223
+ const shouldLogout = await confirm({
224
+ message: "Are you sure you want to logout?",
225
+ initialValue: false
226
+ })
227
+
228
+ if (isCancel(shouldLogout) || !shouldLogout) {
229
+ cancel("Loggout Cancelled")
230
+ process.exit(0)
231
+ }
232
+
233
+ const cleared = await clearStoredToken();
234
+
235
+ if (cleared) {
236
+ outro(chalk.green("✅ Successfully logged out!"))
237
+ } else {
238
+ console.log(chalk.yellow("⚠️ Could not clear token file."))
239
+ }
240
+
241
+ }
242
+
243
+
244
+
245
+ export async function whoamiAction(opts) {
246
+ const token = await requireAuth();
247
+
248
+ if (!token?.access_token) {
249
+ console.log("No access token found. Please login.")
250
+ process.exit(1)
251
+ }
252
+
253
+ const user = await prisma.user.findFirst({
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
+ })
268
+
269
+ console.log(
270
+ chalk.bold.greenBright(`\n USER: ${user.name}
271
+ 📧 EMAIL: ${user.email}
272
+ 🆔 ID : ${user.id}`)
273
+ );
274
+ }
275
+
276
+
277
+
278
+ // --------------------------------------
279
+ // COMMANDER SETUP
280
+ // --------------------------------------
281
+
282
+ export const login = new Command("login")
283
+ .description("Login to Better Auth")
284
+ .option("--server-url <url>", "The Better Auth server URL", URL)
285
+ .option("--client-id <id>", "The OAuth client ID", CLIENT_ID)
286
+ .action(loginAction);
287
+
288
+
289
+ export const logout = new Command("logout")
290
+ .description("Logout and Clear stored credentials")
291
+ .action(logoutAction)
292
+
293
+ export const whoami = new Command("whoami")
294
+ .description("Show current authenticated user")
295
+ .option("--server-url <url>", "The Better Auth server URL", URL)
296
+ .action(whoamiAction)
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+
3
+ import dotenv from "dotenv";
4
+ import chalk from "chalk";
5
+ import figlet from "figlet";
6
+ import { Command, program } from "commander";
7
+ import { login, logout, whoami } from "./commands/auth/login.js";
8
+ import { wakeup } from "./commands/ai/wakeUp.js";
9
+
10
+ import path from "path";
11
+ import { fileURLToPath } from "url";
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+
16
+ dotenv.config({ path: path.join(__dirname, "../../.env") });
17
+
18
+ async function main() {
19
+ // Sexy Banner
20
+ console.log(
21
+ chalk.hex("#6A5ACD")(
22
+ figlet.textSync("Maverick", {
23
+ font: "ANSI Shadow",
24
+ horizontalLayout: "default",
25
+ verticalLayout: "default",
26
+ width: 120
27
+ })
28
+ )
29
+ );
30
+
31
+ console.log(
32
+ chalk.greenBright("<---- Intelligent Command Line Interface ---->\n")
33
+ );
34
+
35
+
36
+ const program = new Command("Maverick");
37
+ program.version("1.4.3")
38
+ .description("Maverick Cli - A cli based AI tool")
39
+ .addCommand(login)
40
+ .addCommand(logout)
41
+ .addCommand(whoami)
42
+ .addCommand(wakeup)
43
+
44
+ program.action(() => {
45
+ program.help();
46
+ })
47
+
48
+ program.parse()
49
+ }
50
+
51
+
52
+
53
+ main().catch((err) => {
54
+ console.log(chalk.red("Error : Maverick don't wanna shown UP..!!; Because of this ->"), err)
55
+ process.exit(1)
56
+ });
@@ -0,0 +1,9 @@
1
+ import dotenv from "dotenv"
2
+
3
+ dotenv.config()
4
+
5
+ export const config = {
6
+
7
+ googleApiKey : process.env.GOOGLE_GENERATIVE_AI_API_KEY || " ",
8
+ model : process.env.ORBITAL_MODEL || "gemini-2.5-flash"
9
+ }
@@ -0,0 +1,106 @@
1
+ import { google } from "@ai-sdk/google";
2
+ import chalk from "chalk";
3
+
4
+ export const availableTools = [
5
+ {
6
+ id: "google_search",
7
+ name: "Google Search",
8
+ description: "Search the web using Google Search",
9
+ getTool: () => google({}),
10
+ enabled: true,
11
+ },
12
+ {
13
+ id: 'code_execution',
14
+ name: 'Code Execution',
15
+ description: 'Generate and execute python code to perform calculations, solve problems, or provide accurate results.',
16
+ getTool: () => google.tools.codeExecution({}),
17
+ enabled: true,
18
+ },
19
+ {
20
+ id: 'url_context',
21
+ name: 'URL Context',
22
+ description: 'Provide specific URLs that you want to analyse directly from the propmt. Suports upto 20 URLs per request.',
23
+ getTool: () => google.tools.urlContext({}),
24
+ enabled: true,
25
+ }
26
+
27
+ ]
28
+
29
+
30
+
31
+ export function getEnabledTools() {
32
+ const tools = {}
33
+ try {
34
+ for (const tool of availableTools) {
35
+ if (tool.enabled) {
36
+ tools[tool.id] = tool.getTool()
37
+ }
38
+ }
39
+ if (Object.keys(tools).length > 0) {
40
+ console.log(chalk.green(`[DEBUG] Enabled ${Object.keys(tools).join(", ")}`))
41
+ } else {
42
+ console.log(chalk.yellow(`[DEBUG] No tools enabled`))
43
+ }
44
+
45
+ return Object.keys(tools).length > 0 ? tools : undefined
46
+ } catch (error) {
47
+ console.error(chalk.red("Error getting enabled tools"), error)
48
+ console.error(chalk.red(`[ERROR] Error getting enabled tools`), error.message)
49
+ console.error(chalk.yellow('Make sure you have @ai-sdk/google version 2.0+ installed'))
50
+ console.error(chalk.yellow('You can install it using npm install @ai-sdk/google@latest'))
51
+ return undefined
52
+ }
53
+
54
+ }
55
+
56
+
57
+ export function toggleTool(toolsId) {
58
+
59
+ const tool = availableTools.find(t => t.id === toolsId)
60
+
61
+ if (tool) {
62
+ tool.enabled = !tool.enabled
63
+ console.log(chalk.gray(`[DEBUG] Tool ${toolsId} toggled to ${tool.enabled}`))
64
+
65
+ return tool.enabled
66
+ }
67
+
68
+ console.log(chalk.red(`[ERROR] Tool ${toolsId} not found`))
69
+
70
+ return false;
71
+ }
72
+
73
+
74
+ export function enableTools(toolsId) {
75
+
76
+ console.log(chalk.gray('[DEBUG] enableTools called with:'), toolsId)
77
+
78
+ availableTools.forEach(tool => {
79
+ const wasEnabled = tool.enabled
80
+
81
+ tool.enabled = toolsId.includes(tool.id)
82
+
83
+ if (tool.enabled !== wasEnabled) {
84
+ console.log(chalk.gray(`[DEBUG] Tool ${tool.id} ${wasEnabled} -> ${tool.enabled}`))
85
+
86
+ }
87
+ })
88
+
89
+ const enabledCount = availableTools.filter(t => t.enabled).length;
90
+ console.log(chalk.gray(`[DEBUG] Enabled ${enabledCount}/${availableTools.length} tools`))
91
+
92
+
93
+ }
94
+
95
+
96
+ export function getEnabledToolNames() {
97
+ const enabledTools = availableTools.filter(t => t.enabled).map(t => t.name)
98
+ console.log(chalk.gray('[DEBUG] getEnabledToolNames returning:'), enabledTools);
99
+ return enabledTools
100
+ }
101
+
102
+ export function resetTools() {
103
+ availableTools.forEach(tool => tool.enabled = false);
104
+
105
+ console.log(chalk.gray('[DEBUG] ALl tools have been reset (disabled)'))
106
+ }
package/src/index.js ADDED
@@ -0,0 +1,47 @@
1
+ import express from "express";
2
+ import dotenv from "dotenv";
3
+ import { toNodeHandler, fromNodeHeaders } from "better-auth/node";
4
+ import cors from "cors";
5
+ import { auth } from "./lib/auth.js"
6
+
7
+ dotenv.config()
8
+
9
+ const app = express()
10
+
11
+ // *----------- CORS --------------*
12
+ app.use(
13
+ cors({
14
+ origin: "https://maverick-cli.vercel.app",
15
+ methods: ["GET", "POST", "PUT", "DELETE"],
16
+ credentials: true,
17
+ })
18
+ );
19
+
20
+ app.all("/api/auth/*splat", toNodeHandler(auth))
21
+
22
+ app.use(express.json());
23
+
24
+ app.get("/api/me", async (req, res) => {
25
+ const session = await auth.api.getSession({
26
+ headers: fromNodeHeaders(req.headers),
27
+ });
28
+ return res.json(session);
29
+ })
30
+ app.head('/', (req, res) => {
31
+ res.status(200).end();
32
+ });
33
+
34
+
35
+ app.get("/device", async (req, res) => {
36
+ const { user_code } = req.query
37
+ res.redirect(`https://maverick-cli.vercel.app/device?user_code=${user_code}`)
38
+ })
39
+
40
+
41
+ app.get('/health', (req, res) => {
42
+ res.send("Backend is running")
43
+ })
44
+
45
+ app.listen(process.env.PORT, () => {
46
+ console.log(`running on PORT : http://localhost:${process.env.PORT}`)
47
+ })
@@ -0,0 +1,26 @@
1
+ import { betterAuth } from "better-auth";
2
+ import { prismaAdapter } from "better-auth/adapters/prisma";
3
+ import { deviceAuthorization } from "better-auth/plugins";
4
+ import prisma from "./db.js"
5
+
6
+ export const auth = betterAuth({
7
+ database: prismaAdapter(prisma, {
8
+ provider: "postgresql",
9
+ }),
10
+ basePath: "api/auth",
11
+ trustedOrigins: ["https://maverick-cli.vercel.app"],
12
+ socialProviders: {
13
+ github: {
14
+ clientId: process.env.GITHUB_CLIENT_ID,
15
+ clientSecret: process.env.GITHUB_CLIENT_SECRET
16
+ }
17
+ },
18
+ plugins: [
19
+ deviceAuthorization({
20
+ expiresIn: "30m",
21
+ interval: "5s",
22
+ }),
23
+ ],
24
+ });
25
+
26
+
package/src/lib/db.js ADDED
@@ -0,0 +1,9 @@
1
+ import {PrismaClient} from "@prisma/client";
2
+
3
+ const globalForPrisma = global
4
+
5
+ const prisma = new PrismaClient()
6
+
7
+ if(process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
8
+
9
+ export default prisma