@bharatpanigrahi/onw-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,163 @@
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
+ const ApplicationSchema = z.object({
8
+ folderName: z.string().describe("Kebab-Case folder name for the application"),
9
+ description: z.string().describe("Brief description of what was created"),
10
+ files: z.array(
11
+ z
12
+ .object({
13
+ path: z.string().describe("Relative file path (e.g src.App.jsx)"),
14
+ content: z.string().describe("Complete File content"),
15
+ })
16
+ .describe("All files needed for the application"),
17
+ ),
18
+ setupCommands: z.array(
19
+ z
20
+ .string()
21
+ .describe(
22
+ "Bash commands to setup and run (e.g: npm install, npm run dev)",
23
+ ),
24
+ ),
25
+ dependecies: z.record(z.string(), z.string()).default({}),
26
+ });
27
+
28
+ function printSystem(message) {
29
+ console.log(message);
30
+ }
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
+
45
+ filesByDir[dir].push(parts[parts.length - 1]);
46
+ });
47
+
48
+ Object.keys(filesByDir)
49
+ .sort()
50
+ .forEach((dir) => {
51
+ if (dir) {
52
+ printSystem(chalk.white(`ā”œā”€ā”€${dir}/`));
53
+ filesByDir[dir].forEach((file) => {
54
+ printSystem(chalk.white(`│ └──${file}`));
55
+ });
56
+ } else {
57
+ filesByDir[dir].forEach((file) => {
58
+ printSystem(chalk.white(`ā”œā”€ā”€${file}`));
59
+ });
60
+ }
61
+ });
62
+ }
63
+
64
+ async function createApplicationFiles(baseDir, folderName, files) {
65
+ const appDir = path.join(baseDir, folderName);
66
+ await fs.mkdir(appDir, { recursive: true });
67
+
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, "utf-8");
76
+ printSystem(chalk.green(` āœ”ļø ${file.path}`));
77
+ }
78
+ return appDir;
79
+ }
80
+
81
+ export async function generateApplication(
82
+ description,
83
+ aiService,
84
+ cwd = process.cwd(),
85
+ ) {
86
+ try {
87
+ printSystem(chalk.cyan("šŸ¤– Claude is generating your application..."));
88
+ printSystem(chalk.gray(`Request: ${description}\n`));
89
+
90
+ printSystem(chalk.magenta("Agent Response: \n"));
91
+
92
+ const { object: application } = await generateObject({
93
+ model: aiService.model,
94
+ schema: ApplicationSchema,
95
+ prompt: `Create a complete, production-ready application for: ${description}
96
+
97
+ CRITICAL REQUIREMENTS:
98
+ 1. Generate ALL files needed for the application to run
99
+ 2. Include package.json with ALL dependencies and correct versions
100
+ 3. Include README.md with setup instructions
101
+ 4. Include configuration files (.gitignore, etc.)
102
+ 5. Write clean, well-commented, production-ready code
103
+ 6. Include error handling and input validation
104
+ 7. Use modern JavaScript/TypeScript best practices
105
+ 8. Make sure all imports and paths are correct
106
+ 9. NO PLACEHOLDERS - everything must be complete and working
107
+
108
+ Provide:
109
+ - A meaningful kebab-case folder name
110
+ - All necessary files with complete content
111
+ - Setup commands (cd folder, npm install, npm run dev, etc.)
112
+ - All dependencies with versions
113
+ `,
114
+ });
115
+
116
+ printSystem(chalk.green(`\n Generated: ${application.folderName} \n`));
117
+ printSystem(chalk.gray(`Description: ${application.description} \n`));
118
+
119
+ if (application.files.length === 0) {
120
+ throw new Error("No files were generated by the agent.");
121
+ }
122
+
123
+ displayFileTree(application.files, application.folderName);
124
+
125
+ printSystem(chalk.cyan("\nšŸ“ƒ Creating files...\n"));
126
+
127
+ const appDir = await createApplicationFiles(
128
+ cwd,
129
+ application.folderName,
130
+ application.files,
131
+ );
132
+
133
+ printSystem(chalk.cyan(`šŸ“ Location: ${chalk.bold(appDir)}\n`));
134
+
135
+ if (application.setupCommands.length > 0) {
136
+ printSystem(chalk.cyan("Next Steps:\n"));
137
+ printSystem(chalk.white("```bash"));
138
+
139
+ application.setupCommands.forEach((cmd) => {
140
+ printSystem(chalk.white(cmd));
141
+ });
142
+
143
+ printSystem(chalk.white("```\n"));
144
+
145
+ return {
146
+ folderName: application.folderName,
147
+ appDir,
148
+ files: application.files.map((f) => f.path),
149
+ commands: application.setupCommands,
150
+ success: true,
151
+ };
152
+ }
153
+ } catch (error) {
154
+ printSystem(
155
+ chalk.red(`\nāŒ Error generating application: ${error?.message}\n`),
156
+ );
157
+ if (error?.stack) {
158
+ printSystem(chalk.dim(error.stack + "\n"));
159
+ }
160
+
161
+ throw error;
162
+ }
163
+ }
@@ -0,0 +1,7 @@
1
+ import dotenv from "dotenv";
2
+ dotenv.config();
3
+
4
+ export const config = {
5
+ googleApiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY || "",
6
+ model: process.env.CLAUDE_MODEL || "gemini-2.5-flash",
7
+ };
@@ -0,0 +1,105 @@
1
+ import { google } from "@ai-sdk/google";
2
+ import chalk from "chalk";
3
+
4
+ export const avaiableTools = [
5
+ {
6
+ id: "google_search",
7
+ name: "Google Search",
8
+ description: "Search the web for up-to-date information.",
9
+ getTool: () => google.tools.googleSearch({}),
10
+ enabled: false,
11
+ },
12
+ {
13
+ id: "code_execution",
14
+ name: "Code Execution",
15
+ description:
16
+ "Generate and execute python code to perform calculations, solve problems, or provide accurate information.",
17
+ getTool: () => google.tools.codeExecution({}),
18
+ enabled: false,
19
+ },
20
+ {
21
+ id: "url_context",
22
+ name: "URL Context",
23
+ description: "Fetch and provide context from a given URL.",
24
+ getTool: () => google.tools.urlContext({}),
25
+ enabled: false,
26
+ },
27
+ ];
28
+
29
+ export const getEnabledTools = () => {
30
+ const tools = {};
31
+
32
+ try {
33
+ for (const toolConfig of avaiableTools) {
34
+ if (toolConfig.enabled) {
35
+ tools[toolConfig.id] = toolConfig.getTool();
36
+ }
37
+ }
38
+
39
+ // DEBUG logging to verify which tools are enabled
40
+ if (Object.keys(tools).length > 0) {
41
+ console.log(
42
+ chalk.gray(`[DEBUG] Enabled tools: ${Object.keys(tools).join(", ")}`),
43
+ );
44
+ } else {
45
+ console.log(chalk.gray("[DEBUG] No tools enabled"));
46
+ }
47
+
48
+ return tools;
49
+ } catch (error) {
50
+ console.error(
51
+ chalk.red("[ERROR] Failed to initialize tools: ", error?.message),
52
+ );
53
+ console.error(
54
+ chalk.yellow("Make sure you have @ai-sdk/google version 2.0+ installed"),
55
+ );
56
+ console.error(chalk.yellow("Run: npm install @ai-sdk/google@latest"));
57
+ return undefined;
58
+ }
59
+ };
60
+
61
+ export function toggleTool(toolId) {
62
+ const tool = avaiableTools.find((t) => t.id === toolId);
63
+ if (tool) {
64
+ tool.enabled = !tool.enabled;
65
+ console.log(
66
+ chalk.gray(`[DEBUG] Tool ${toolId} toggled to ${tool.enabled}`),
67
+ );
68
+ return tool.enabled;
69
+ }
70
+ console.log(chalk.red(`[DEBUG] Tool ${toolId} not found`));
71
+ return false;
72
+ }
73
+
74
+ export const enableTools = (toolIds) => {
75
+ console.log(chalk.gray(`[DEBUG] enableTools called with ${toolIds}`));
76
+
77
+ avaiableTools.forEach((tool) => {
78
+ const wasEnabled = tool.enabled;
79
+ tool.enabled = toolIds.includes(tool.id);
80
+
81
+ if (tool.enabled !== wasEnabled) {
82
+ console.log(
83
+ chalk.gray(`[DEBUG] ${tool.id} : ${wasEnabled} -> ${tool.enabled}`),
84
+ );
85
+ }
86
+
87
+ const enableCount = avaiableTools.filter((t) => t.enabled).length;
88
+ console.log(
89
+ chalk.gray(
90
+ `[DEBUG] Total tools enabled: ${enableCount}/${avaiableTools.length}`,
91
+ ),
92
+ );
93
+ });
94
+ };
95
+
96
+ export function getEnabledToolNames() {
97
+ const names = avaiableTools.filter((t) => t.enabled).map((t) => t.name);
98
+ console.log(chalk.gray("[DEBUG] getEnabledToolNames returning: "), names);
99
+ return names;
100
+ }
101
+
102
+ export function resetTools() {
103
+ avaiableTools.forEach((tool) => (tool.enabled = false));
104
+ console.log(chalk.gray("[DEBUG] All tools have been reset (disabled)"));
105
+ }
package/src/index.js ADDED
@@ -0,0 +1,36 @@
1
+ import express from "express";
2
+ import cors from "cors";
3
+ import { toNodeHandler } from "better-auth/node";
4
+ import dotenv from "dotenv";
5
+ import { auth } from "./lib/auth.js";
6
+
7
+ dotenv.config();
8
+ const app = express();
9
+
10
+ app.use(
11
+ cors({
12
+ origin: process.env.CORS_ORIGIN,
13
+ methods: ["GET", "POST", "PUT", "DELETE"],
14
+ credentials: true,
15
+ exposedHeaders: ["set-cookie"],
16
+ }),
17
+ );
18
+
19
+ app.all("/api/auth/*splat", toNodeHandler(auth));
20
+
21
+ app.use(express.json());
22
+
23
+ const PORT = process.env.PORT || 3000;
24
+
25
+ app.get("/", (req, res) => {
26
+ res.send("Hello World!");
27
+ });
28
+
29
+ app.get("/device", async (req, res) => {
30
+ const { user_code } = req.query;
31
+ res.redirect(`${process.env.CORS_ORIGIN}/device?user_code=${user_code}`);
32
+ });
33
+
34
+ app.listen(PORT, () => {
35
+ console.log(`Server is running on port ${PORT}`);
36
+ });
@@ -0,0 +1,34 @@
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
+ const crossSiteCookieAttributes = {
7
+ sameSite: "none",
8
+ secure: true,
9
+ };
10
+
11
+ export const auth = betterAuth({
12
+ database: prismaAdapter(prisma, {
13
+ provider: "postgresql",
14
+ }),
15
+ baseURL: process.env.BASE_URL,
16
+ trustedOrigins: [process.env.CORS_ORIGIN],
17
+ basePath: "/api/auth",
18
+ socialProviders: {
19
+ github: {
20
+ clientId: process.env.GITHUB_CLIENT_ID,
21
+ clientSecret: process.env.GITHUB_CLIENT_SECRET,
22
+ },
23
+ },
24
+ advanced: {
25
+ useSecureCookies: true,
26
+ defaultCookieAttributes: crossSiteCookieAttributes,
27
+ },
28
+ plugins: [
29
+ deviceAuthorization({
30
+ expiresIn: "30m",
31
+ interval: "5s",
32
+ }),
33
+ ],
34
+ });
package/src/lib/db.js ADDED
@@ -0,0 +1,8 @@
1
+ import { PrismaClient } from "@prisma/client";
2
+
3
+ const globalForPrisma = global;
4
+
5
+ const prisma = globalForPrisma.prisma ?? new PrismaClient();
6
+ if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
7
+
8
+ export default prisma;
@@ -0,0 +1,85 @@
1
+ import chalk from "chalk";
2
+ import fs from "node:fs/promises";
3
+ import { CONFIG_DIR, TOKEN_FILE } from "../cli/commands/auth/login.js";
4
+
5
+ export async function getStoredToken() {
6
+ try {
7
+ const data = await fs.readFile(TOKEN_FILE, "utf-8");
8
+ const token = JSON.parse(data);
9
+ return token;
10
+ } catch (error) {
11
+ // File doesn't exist or can't be read
12
+ return null;
13
+ }
14
+ }
15
+
16
+ export async function storeToken(token) {
17
+ try {
18
+ // Ensure the config directory exists
19
+ await fs.mkdir(CONFIG_DIR, { recursive: true });
20
+
21
+ // store token with metadata
22
+
23
+ const tokenData = {
24
+ access_token: token.access_token,
25
+ refresh_token: token.refresh_token,
26
+ token_type: token.token_type || "Bearer",
27
+ scope: token.scope,
28
+ expires_at: token.expires_in
29
+ ? new Date(Date.now() + token.expires_in * 1000).toISOString()
30
+ : null,
31
+ created_at: new Date().toISOString(),
32
+ };
33
+
34
+ await fs.writeFile(TOKEN_FILE, JSON.stringify(tokenData, null, 2), "utf-8");
35
+ return true;
36
+ } catch (error) {
37
+ console.error(chalk.red("Failed to store token: "), error?.message);
38
+ return false;
39
+ }
40
+ }
41
+
42
+ export async function clearStoredToken() {
43
+ try {
44
+ await fs.unlink(TOKEN_FILE);
45
+ return true;
46
+ } catch (error) {
47
+ // File doesn't exist or can't be deleted
48
+ return false;
49
+ }
50
+ }
51
+
52
+ export async function isTokenExpired() {
53
+ const token = await getStoredToken();
54
+ if (!token || !token.expires_at) {
55
+ return true;
56
+ }
57
+
58
+ const expiresAt = new Date(token.expires_at);
59
+ const now = new Date();
60
+
61
+ return expiresAt.getTime() - now.getTime() < 5 * 60 * 1000;
62
+ }
63
+
64
+ export async function requireAuth() {
65
+ const token = await getStoredToken();
66
+
67
+ if (!token) {
68
+ console.log(
69
+ chalk.red(
70
+ 'āŒ Not authenticated. Please run "claude-cli login" to authenticate.',
71
+ ),
72
+ );
73
+ process.exit(1);
74
+ }
75
+
76
+ if (await isTokenExpired()) {
77
+ console.log(
78
+ chalk.yellow("āš ļø Your session has expired. Please log in again."),
79
+ );
80
+ console.log(chalk.gray(" Run: claude login"));
81
+ process.exit(1);
82
+ }
83
+
84
+ return token;
85
+ }
@@ -0,0 +1,104 @@
1
+ import prisma from "../lib/db.js";
2
+
3
+ export class ChatService {
4
+ async createConversation(userId, mode = "chat", title = null) {
5
+ const conversation = await prisma.conversation.create({
6
+ data: {
7
+ title: title || `New ${mode} conversation`,
8
+ mode,
9
+ user: {
10
+ connect: { id: userId },
11
+ },
12
+ },
13
+ });
14
+ return conversation;
15
+ }
16
+
17
+ async getOrCreateConversations(userId, conversationId = null, mode = "chat") {
18
+ if (conversationId) {
19
+ const conversation = await prisma.conversation.findFirst({
20
+ where: {
21
+ id: conversationId,
22
+ userId,
23
+ },
24
+ include: {
25
+ messages: {
26
+ orderBy: {
27
+ createdAt: "asc",
28
+ },
29
+ },
30
+ },
31
+ });
32
+ if (conversation) return conversation;
33
+ }
34
+ return await this.createConversation(userId, mode);
35
+ }
36
+
37
+ async addMessage(conversationId, role, content) {
38
+ const contentStr =
39
+ typeof content === "string" ? content : JSON.stringify(content);
40
+ const message = await prisma.message.create({
41
+ data: {
42
+ role,
43
+ content: contentStr,
44
+ conversation: {
45
+ connect: { id: conversationId },
46
+ },
47
+ },
48
+ });
49
+ return message;
50
+ }
51
+
52
+ parseContent(content) {
53
+ try {
54
+ return JSON.parse(content);
55
+ } catch {
56
+ return content;
57
+ }
58
+ }
59
+
60
+ async getMessages(conversationId) {
61
+ const messages = await prisma.message.findMany({
62
+ where: { conversationId },
63
+ orderBy: { createdAt: "asc" },
64
+ });
65
+ return messages.map((msg) => ({
66
+ ...msg,
67
+ content: this.parseContent(msg.content),
68
+ }));
69
+ }
70
+
71
+ async getConversations(userId) {
72
+ const conversations = await prisma.conversation.findMany({
73
+ where: { userId },
74
+ orderBy: { updatedAt: "desc" },
75
+ include: {
76
+ messages: {
77
+ take: 1,
78
+ orderBy: { createdAt: "desc" },
79
+ },
80
+ },
81
+ });
82
+ return conversations;
83
+ }
84
+
85
+ async deleteConversation(conversationId, userId) {
86
+ return await prisma.conversation.delete({
87
+ where: { id: conversationId, userId },
88
+ });
89
+ }
90
+
91
+ async updateTitle(conversationId, title) {
92
+ return await prisma.conversation.update({
93
+ where: { id: conversationId },
94
+ data: { title },
95
+ });
96
+ }
97
+
98
+ formatMessagesForAI(messages) {
99
+ return messages.map((msg) => ({
100
+ role: msg.role,
101
+ content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content),
102
+ }));
103
+ }
104
+ }