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.
@@ -0,0 +1,166 @@
1
+ import { promises as fs } from 'fs';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+ import { generateObject } from 'ai';
5
+ import { z } from 'zod';
6
+
7
+ /**
8
+ * Zod schema for structured application generation
9
+ */
10
+ const ApplicationSchema = z.object({
11
+ folderName: z.string().describe('Kebab-case folder name for the application'),
12
+ description: z.string().describe('Brief description of what was created'),
13
+ files: z.array(
14
+ z.object({
15
+ path: z.string().describe('Relative file path (e.g., src/App.jsx)'),
16
+ content: z.string().describe('Complete file content'),
17
+ })
18
+ ).describe('All files needed for the application'),
19
+ setupCommands: z.array(z.string()).describe('Bash commands to setup and run (e.g., npm install, npm run dev)'),
20
+ });
21
+
22
+ /**
23
+ * Console logging helpers
24
+ */
25
+ function printSystem(message) {
26
+ console.log(message);
27
+ }
28
+
29
+ /**
30
+ * Display file tree structure
31
+ */
32
+ function displayFileTree(files, folderName) {
33
+ printSystem(chalk.cyan('\nšŸ“‚ Project Structure:'));
34
+ printSystem(chalk.white(`${folderName}/`));
35
+
36
+ const filesByDir = {};
37
+ files.forEach(file => {
38
+ const parts = file.path.split('/');
39
+ const dir = parts.length > 1 ? parts.slice(0, -1).join('/') : '';
40
+
41
+ if (!filesByDir[dir]) {
42
+ filesByDir[dir] = [];
43
+ }
44
+ filesByDir[dir].push(parts[parts.length - 1]);
45
+ });
46
+
47
+ Object.keys(filesByDir).sort().forEach(dir => {
48
+ if (dir) {
49
+ printSystem(chalk.white(`ā”œā”€ā”€ ${dir}/`));
50
+ filesByDir[dir].forEach(file => {
51
+ printSystem(chalk.white(`│ └── ${file}`));
52
+ });
53
+ } else {
54
+ filesByDir[dir].forEach(file => {
55
+ printSystem(chalk.white(`ā”œā”€ā”€ ${file}`));
56
+ });
57
+ }
58
+ });
59
+ }
60
+
61
+ /**
62
+ * Create application files
63
+ */
64
+ async function createApplicationFiles(baseDir, folderName, files) {
65
+ const appDir = path.join(baseDir, folderName);
66
+
67
+ await fs.mkdir(appDir, { recursive: true });
68
+ printSystem(chalk.cyan(`\nšŸ“ Created directory: ${folderName}/`));
69
+
70
+ for (const file of files) {
71
+ const filePath = path.join(appDir, file.path);
72
+ const fileDir = path.dirname(filePath);
73
+
74
+ await fs.mkdir(fileDir, { recursive: true });
75
+ await fs.writeFile(filePath, file.content, 'utf8');
76
+ printSystem(chalk.green(` āœ“ ${file.path}`));
77
+ }
78
+
79
+ return appDir;
80
+ }
81
+
82
+ /**
83
+ * Generate application using structured output
84
+ */
85
+ export async function generateApplication(description, aiService, cwd = process.cwd()) {
86
+ try {
87
+ printSystem(chalk.cyan('\nšŸ¤– Agent Mode: Generating your application...\n'));
88
+ printSystem(chalk.gray(`Request: ${description}\n`));
89
+
90
+ printSystem(chalk.magenta('šŸ¤– Generating structured output...\n'));
91
+
92
+
93
+ const result = await generateObject({
94
+ model: aiService.model,
95
+ schema: ApplicationSchema,
96
+ prompt: `Create a complete, production-ready application for: ${description}
97
+
98
+ CRITICAL REQUIREMENTS:
99
+ 1. Generate ALL files needed for the application to run
100
+ 2. Include package.json with ALL dependencies and correct versions (if needed)
101
+ 3. Include README.md with setup instructions
102
+ 4. Include configuration files (.gitignore, etc.) if needed
103
+ 5. Write clean, well-commented, production-ready code
104
+ 6. Include error handling and input validation
105
+ 7. Use modern JavaScript/TypeScript best practices
106
+ 8. Make sure all imports and paths are correct
107
+ 9. NO PLACEHOLDERS - everything must be complete and working
108
+ 10. For simple HTML/CSS/JS projects, you can skip package.json if not needed
109
+
110
+ Provide:
111
+ - A meaningful kebab-case folder name
112
+ - All necessary files with complete content
113
+ - Setup commands (for example: cd folder, npm install, npm run dev OR just open index.html)
114
+ - Make it visually appealing and functional`,
115
+ });
116
+
117
+ const application = result.object;
118
+
119
+ printSystem(chalk.green(`\nāœ… Generated: ${application.folderName}\n`));
120
+ printSystem(chalk.gray(`Description: ${application.description}\n`));
121
+
122
+ if (!application.files || application.files.length === 0) {
123
+ throw new Error('No files were generated');
124
+ }
125
+
126
+ printSystem(chalk.green(`Files: ${application.files.length}\n`));
127
+
128
+ // Display file tree
129
+ displayFileTree(application.files, application.folderName);
130
+
131
+ // Create application directory and files
132
+ printSystem(chalk.cyan('\nšŸ“ Creating files...\n'));
133
+ const appDir = await createApplicationFiles(cwd, application.folderName, application.files);
134
+
135
+ // Display results
136
+ printSystem(chalk.green.bold(`\n✨ Application created successfully!\n`));
137
+ printSystem(chalk.cyan(`šŸ“ Location: ${chalk.bold(appDir)}\n`));
138
+
139
+ // Display setup commands
140
+ if (application.setupCommands && application.setupCommands.length > 0) {
141
+ printSystem(chalk.cyan('šŸ“‹ Next Steps:\n'));
142
+ printSystem(chalk.white('```bash'));
143
+ application.setupCommands.forEach(cmd => {
144
+ printSystem(chalk.white(cmd));
145
+ });
146
+ printSystem(chalk.white('```\n'));
147
+ } else {
148
+ printSystem(chalk.yellow('ā„¹ļø No setup commands provided\n'));
149
+ }
150
+
151
+ return {
152
+ folderName: application.folderName,
153
+ appDir,
154
+ files: application.files.map(f => f.path),
155
+ commands: application.setupCommands || [],
156
+ success: true,
157
+ };
158
+
159
+ } catch (err) {
160
+ printSystem(chalk.red(`\nāŒ Error generating application: ${err.message}\n`));
161
+ if (err.stack) {
162
+ printSystem(chalk.dim(err.stack + '\n'));
163
+ }
164
+ throw err;
165
+ }
166
+ }
@@ -0,0 +1,8 @@
1
+ import dotenv from 'dotenv';
2
+
3
+ dotenv.config();
4
+
5
+ export const config = {
6
+ googleApiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY || "",
7
+ model: process.env.MIKASA_MODEL || "gemini-2.5-flash",
8
+ }
@@ -0,0 +1,112 @@
1
+ import { google } from '@ai-sdk/google';
2
+ import chalk from 'chalk';
3
+
4
+ /**
5
+ * Available Google Generative AI tools configuration
6
+ * Note: Tools are instantiated lazily to avoid initialization errors
7
+ */
8
+ export const availableTools = [
9
+ {
10
+ id: 'google_search',
11
+ name: 'Google Search',
12
+ description: 'Access the latest information using Google search. Useful for current events, news, and real-time information.',
13
+ getTool: () => google.tools.googleSearch({}),
14
+ enabled: false,
15
+ },
16
+ {
17
+ id: 'code_execution',
18
+ name: 'Code Execution',
19
+ description: 'Generate and execute Python code to perform calculations, solve problems, or provide accurate information.',
20
+ getTool: () => google.tools.codeExecution({}),
21
+ enabled: false,
22
+ },
23
+ {
24
+ id: 'url_context',
25
+ name: 'URL Context',
26
+ description: 'Provide specific URLs that you want the model to analyze directly from the prompt. Supports up to 20 URLs per request.',
27
+ getTool: () => google.tools.urlContext({}),
28
+ enabled: false,
29
+ },
30
+ ];
31
+
32
+ /**
33
+ * Get enabled tools as a tools object for AI SDK
34
+ */
35
+ export function getEnabledTools() {
36
+ const tools = {};
37
+
38
+ try {
39
+ for (const toolConfig of availableTools) {
40
+ if (toolConfig.enabled) {
41
+ // Instantiate the tool when needed
42
+ tools[toolConfig.id] = toolConfig.getTool();
43
+ }
44
+ }
45
+
46
+ // Debug logging
47
+ if (Object.keys(tools).length > 0) {
48
+ console.log(chalk.gray(`[DEBUG] Enabled tools: ${Object.keys(tools).join(', ')}`));
49
+ } else {
50
+ console.log(chalk.yellow('[DEBUG] No tools enabled'));
51
+ }
52
+
53
+ return Object.keys(tools).length > 0 ? tools : undefined;
54
+ } catch (error) {
55
+ console.error(chalk.red('[ERROR] Failed to initialize tools:'), error.message);
56
+ console.error(chalk.yellow('Make sure you have @ai-sdk/google version 2.0+ installed'));
57
+ console.error(chalk.yellow('Run: npm install @ai-sdk/google@latest'));
58
+ return undefined;
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Toggle a tool's enabled state
64
+ */
65
+ export function toggleTool(toolId) {
66
+ const tool = availableTools.find(t => t.id === toolId);
67
+ if (tool) {
68
+ tool.enabled = !tool.enabled;
69
+ console.log(chalk.gray(`[DEBUG] Tool ${toolId} toggled to ${tool.enabled}`));
70
+ return tool.enabled;
71
+ }
72
+ console.log(chalk.red(`[DEBUG] Tool ${toolId} not found`));
73
+ return false;
74
+ }
75
+
76
+ /**
77
+ * Enable specific tools
78
+ */
79
+ export function enableTools(toolIds) {
80
+ console.log(chalk.gray('[DEBUG] enableTools called with:'), toolIds);
81
+
82
+ availableTools.forEach(tool => {
83
+ const wasEnabled = tool.enabled;
84
+ tool.enabled = toolIds.includes(tool.id);
85
+
86
+ if (tool.enabled !== wasEnabled) {
87
+ console.log(chalk.gray(`[DEBUG] ${tool.id}: ${wasEnabled} → ${tool.enabled}`));
88
+ }
89
+ });
90
+
91
+ const enabledCount = availableTools.filter(t => t.enabled).length;
92
+ console.log(chalk.gray(`[DEBUG] Total tools enabled: ${enabledCount}/${availableTools.length}`));
93
+ }
94
+
95
+ /**
96
+ * Get all enabled tool names
97
+ */
98
+ export function getEnabledToolNames() {
99
+ const names = availableTools.filter(t => t.enabled).map(t => t.name);
100
+ console.log(chalk.gray('[DEBUG] getEnabledToolNames returning:'), names);
101
+ return names;
102
+ }
103
+
104
+ /**
105
+ * Reset all tools (disable all)
106
+ */
107
+ export function resetTools() {
108
+ availableTools.forEach(tool => {
109
+ tool.enabled = false;
110
+ });
111
+ console.log(chalk.gray('[DEBUG] All tools have been reset (disabled)'));
112
+ }
package/src/index.js ADDED
@@ -0,0 +1,119 @@
1
+ // import express from 'express';
2
+ // import dotenv from 'dotenv';
3
+ // import { fromNodeHeaders, toNodeHandler } 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
+ // app.use(cors({
11
+ // origin: 'http://localhost:3000',
12
+ // methods: ['GET', 'POST', 'PUT', 'DELETE'],
13
+ // credentials: true,
14
+ // }));
15
+
16
+
17
+ // app.all("/api/auth/*spllat", toNodeHandler(auth));
18
+ // app.use(express.json());
19
+ // const PORT = process.env.PORT || 5000;
20
+
21
+ // app.get('/api/me', async (req, res) => {
22
+ // const session = await auth.getSession({
23
+ // headers: fromNodeHeaders(req.headers),
24
+ // });
25
+ // });
26
+
27
+ // // app.get("/", (req, res) => {
28
+ // // res.redirect("http://localhost:3000/");
29
+ // // });
30
+
31
+
32
+ // app.listen(PORT, () => {
33
+ // console.log(`Server is running on http://localhost:${PORT}`);
34
+ // });
35
+
36
+
37
+
38
+
39
+ import express from "express";
40
+ import dotenv from "dotenv";
41
+ import cors from "cors";
42
+ import { fromNodeHeaders, toNodeHandler } from "better-auth/node";
43
+ import { auth } from "./lib/auth.js";
44
+
45
+ dotenv.config();
46
+
47
+ const app = express();
48
+
49
+ app.use(cors({
50
+ origin: "https://mikasa-cli-xwcm.vercel.app",
51
+ credentials: true,
52
+ }));
53
+
54
+ app.use(express.json());
55
+
56
+ // āœ… REGEX ROUTE (WORKS WITH path-to-regexp v8)
57
+ app.all(/^\/api\/auth\/.*$/, toNodeHandler(auth));
58
+
59
+ // app.get("/api/me", async (req, res) => {
60
+ // const session = await auth.getSession({
61
+ // headers: fromNodeHeaders(req.headers),
62
+ // });
63
+ // res.json(session);
64
+ // });
65
+
66
+ // Fixed: This endpoint now properly handles Bearer token authentication
67
+ app.get("/api/me", async (req, res) => {
68
+ try {
69
+ const session = await auth.api.getSession({
70
+ headers: fromNodeHeaders(req.headers),
71
+ });
72
+
73
+ if (!session) {
74
+ return res.status(401).json({ error: "No active session" });
75
+ }
76
+
77
+ return res.json(session);
78
+ } catch (error) {
79
+ console.error("Session error:", error);
80
+ return res.status(500).json({ error: "Failed to get session", details: error.message });
81
+ }
82
+ });
83
+
84
+
85
+ // You can remove this endpoint if you're using the Bearer token approach above
86
+ app.get("/api/me/:access_token", async (req, res) => {
87
+ const { access_token } = req.params;
88
+ console.log(access_token);
89
+
90
+ try {
91
+ const session = await auth.api.getSession({
92
+ headers: {
93
+ authorization: `Bearer ${access_token}`
94
+ }
95
+ });
96
+
97
+ if (!session) {
98
+ return res.status(401).json({ error: "Invalid token" });
99
+ }
100
+
101
+ return res.json(session);
102
+ } catch (error) {
103
+ console.error("Token validation error:", error);
104
+ return res.status(401).json({ error: "Unauthorized", details: error.message });
105
+ }
106
+ });
107
+
108
+
109
+ app.get("/device", (req, res) => {
110
+ const { user_code } = req.query;
111
+ res.redirect(`https://mikasa-cli-xwcm.vercel.app/device?user_code=${user_code}`);
112
+ });
113
+
114
+ const PORT = process.env.PORT || 5000;
115
+
116
+ app.listen(PORT, () => {
117
+ console.log(`Server running on http://localhost:${PORT}`);
118
+ });
119
+
@@ -0,0 +1,9 @@
1
+ import { deviceAuthorizationClient } from "better-auth/client/plugins"
2
+ import { createAuthClient } from "better-auth/client"
3
+
4
+ export const authClient = createAuthClient({
5
+ baseURL: "https://mikasa-cli-2.onrender.com",
6
+ plugins: [
7
+ deviceAuthorizationClient(),
8
+ ],
9
+ })
@@ -0,0 +1,52 @@
1
+ import { betterAuth } from 'better-auth';
2
+ import { prismaAdapter } from 'better-auth/adapters/prisma';
3
+ import prisma from './db.js';
4
+ import { deviceAuthorization } from "better-auth/plugins";
5
+
6
+ export const auth = betterAuth({
7
+ database: prismaAdapter(prisma, {
8
+ provider: 'postgresql', // or 'postgresql', 'mysql', etc.
9
+ }),
10
+ basePath: '/api/auth',
11
+ trustedOrigins: ['https://mikasa-cli-xwcm.vercel.app'],
12
+ plugins: [
13
+ deviceAuthorization({
14
+ // verificationUri: "/device",
15
+ expiresIn: "30m", // default is 15 minutes
16
+ interval: "5s", // default is 5 seconds
17
+ }),
18
+ ],
19
+ socialProviders: {
20
+ github: {
21
+ clientId: process.env.GITHUB_CLIENT_ID || "Ov23lir2byjlHCgWLvEf",
22
+ clientSecret: process.env.GITHUB_CLIENT_SECRET || "1508f24efb3a1969480348b36b35302b6ad07952",
23
+ }
24
+ },
25
+ // logger: {
26
+ // level: "debug"
27
+ // }
28
+ });
29
+
30
+
31
+ // import { betterAuth } from "better-auth";
32
+ // import { prismaAdapter } from "better-auth/adapters/prisma";
33
+ // import prisma from "./db.js";
34
+
35
+ // export const auth = betterAuth({
36
+ // basePath: "/api/auth",
37
+ // origin: "http://localhost:30005",
38
+ // trustedOrigins: ["http://localhost:3000"],
39
+
40
+ // database: prismaAdapter(prisma, {
41
+ // provider: "postgresql",
42
+ // }),
43
+
44
+ // socialProviders: {
45
+ // github: {
46
+ // clientId: process.env.GITHUB_CLIENT_ID,
47
+ // clientSecret: process.env.GITHUB_CLIENT_SECRET,
48
+ // },
49
+ // },
50
+ // });
51
+
52
+
package/src/lib/db.js ADDED
@@ -0,0 +1,9 @@
1
+ import { PrismaClient } from '@prisma/client';
2
+
3
+
4
+ const globalForPrisma = global;
5
+ const prisma = new PrismaClient();
6
+
7
+ if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
8
+
9
+ export default prisma;
@@ -0,0 +1,152 @@
1
+
2
+ import { auth } from "../lib/auth.js";
3
+ import prisma from "../lib/db.js";
4
+
5
+ export class ChatService {
6
+ /**
7
+ * Create a new conversation
8
+ * @param {string} userId - User ID
9
+ * @param {string} mode - chat, tool, or agent
10
+ * @param {string} title - Optional conversation title
11
+ */
12
+ async createConversation(userId, mode = "chat", title = null) {
13
+
14
+ return await prisma.conversation.create({
15
+ data: {
16
+ userId,
17
+ mode,
18
+ title: title || `New ${mode} conversation`,
19
+ },
20
+ });
21
+ }
22
+
23
+ /**
24
+ * Get or create a conversation for user
25
+ * @param {string} userId - User ID
26
+ * @param {string} conversationId - Optional conversation ID
27
+ * @param {string} mode - chat, tool, or agent
28
+ */
29
+ async getOrCreateConversation(userId, conversationId = null, mode = "chat") {
30
+ if (conversationId) {
31
+ const conversation = await prisma.conversation.findFirst({
32
+ where: {
33
+ id: conversationId,
34
+ userId,
35
+ },
36
+ include: {
37
+ messages: {
38
+ orderBy: { createdAt: "asc" },
39
+ },
40
+ },
41
+ });
42
+
43
+ if (conversation) return conversation;
44
+ }
45
+
46
+ // Create new conversation if not found or not provided
47
+ return await this.createConversation(userId, mode);
48
+ }
49
+
50
+ /**
51
+ * Add a message to conversation
52
+ * @param {string} conversationId - Conversation ID
53
+ * @param {string} role - user, assistant, system, tool
54
+ * @param {string|object} content - Message content
55
+ */
56
+ async addMessage(conversationId, role, content) {
57
+ // Convert content to JSON string if it's an object
58
+ const contentStr = typeof content === "string"
59
+ ? content
60
+ : JSON.stringify(content);
61
+
62
+ return await prisma.message.create({
63
+ data: {
64
+ conversationId,
65
+ role,
66
+ content: contentStr,
67
+ },
68
+ });
69
+ }
70
+
71
+ /**
72
+ * Get conversation messages
73
+ * @param {string} conversationId - Conversation ID
74
+ */
75
+ async getMessages(conversationId) {
76
+ const messages = await prisma.message.findMany({
77
+ where: { conversationId },
78
+ orderBy: { createdAt: "asc" },
79
+ });
80
+
81
+ // Parse JSON content back to objects if needed
82
+ return messages.map((msg) => ({
83
+ ...msg,
84
+ content: this.parseContent(msg.content),
85
+ }));
86
+ }
87
+
88
+ /**
89
+ * Get all conversations for a user
90
+ * @param {string} userId - User ID
91
+ */
92
+ async getUserConversations(userId) {
93
+ return await prisma.conversation.findMany({
94
+ where: { userId },
95
+ orderBy: { updatedAt: "desc" },
96
+ include: {
97
+ messages: {
98
+ take: 1,
99
+ orderBy: { createdAt: "desc" },
100
+ },
101
+ },
102
+ });
103
+ }
104
+
105
+ /**
106
+ * Delete a conversation
107
+ * @param {string} conversationId - Conversation ID
108
+ * @param {string} userId - User ID (for security)
109
+ */
110
+ async deleteConversation(conversationId, userId) {
111
+ return await prisma.conversation.deleteMany({
112
+ where: {
113
+ id: conversationId,
114
+ userId,
115
+ },
116
+ });
117
+ }
118
+
119
+ /**
120
+ * Update conversation title
121
+ * @param {string} conversationId - Conversation ID
122
+ * @param {string} title - New title
123
+ */
124
+ async updateTitle(conversationId, title) {
125
+ return await prisma.conversation.update({
126
+ where: { id: conversationId },
127
+ data: { title },
128
+ });
129
+ }
130
+
131
+ /**
132
+ * Helper to parse content (JSON or string)
133
+ */
134
+ parseContent(content) {
135
+ try {
136
+ return JSON.parse(content);
137
+ } catch {
138
+ return content;
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Format messages for AI SDK
144
+ * @param {Array} messages - Database messages
145
+ */
146
+ formatMessagesForAI(messages) {
147
+ return messages.map((msg) => ({
148
+ role: msg.role,
149
+ content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content),
150
+ }));
151
+ }
152
+ }